Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c008d2e046 | ||
|
|
7cfdad3563 | ||
|
|
5b86d892c5 | ||
|
|
459906ef25 | ||
|
|
1e064fb771 | ||
|
|
5254dc1984 | ||
|
|
f245e49b20 | ||
|
|
f25e54eefe | ||
|
|
bfa713fd7e | ||
|
|
73cad941b0 | ||
|
|
7d0668279e | ||
|
|
9b2a9f5731 | ||
|
|
9d130ee935 | ||
|
|
2758e5f050 | ||
|
|
426545a429 | ||
|
|
75f85cc4c8 | ||
|
|
530bea6f45 | ||
|
|
a12d733e02 | ||
|
|
2e332f791b | ||
|
|
deedc9eabd | ||
|
|
5c5e563d42 | ||
|
|
a777777ac6 | ||
|
|
0dee7cf84f | ||
|
|
1b81251c93 | ||
|
|
94c2cd0582 | ||
|
|
c1af1cf2cd | ||
|
|
ab964416ae | ||
|
|
bfa6197ee4 | ||
|
|
3586fc4e6d | ||
|
|
5ddb74e70c | ||
|
|
2d52ebad40 | ||
|
|
02ae6f3bd4 | ||
|
|
7ff8299e6b | ||
|
|
ad30bf6ee8 | ||
|
|
8ec6c23250 | ||
|
|
a18a62f80e | ||
|
|
a0ba1b63c3 | ||
|
|
e7ed85405b | ||
|
|
118ad67369 | ||
|
|
d28e2bf5fb | ||
|
|
9681c7e2ee | ||
|
|
fa7624e436 | ||
|
|
4aaa1b6674 | ||
|
|
e8d636dcfd | ||
|
|
124683f8c1 | ||
|
|
dd93b9f36b | ||
|
|
b16634ade9 | ||
|
|
ba703ac377 | ||
|
|
37c0dbf567 | ||
|
|
47705f85ef | ||
|
|
b78389f113 | ||
|
|
26fa94abfb | ||
|
|
6d39301ca8 | ||
|
|
429d68859c | ||
|
|
d3b446685f | ||
|
|
03bb550003 | ||
|
|
97a73a385a | ||
|
|
3ddea2ef8b | ||
|
|
9330f2fb0d | ||
|
|
35157d87fe | ||
|
|
36ae38391b | ||
|
|
deec5a096e | ||
|
|
7b117d938f | ||
|
|
e04212f9e9 | ||
|
|
62e259e3e6 | ||
|
|
dab2bb466f | ||
|
|
0628fb2e41 | ||
|
|
fefe124b87 | ||
|
|
d5d3e14d96 | ||
|
|
785b20d903 | ||
|
|
016cd6fef0 | ||
|
|
ff819b3f9f | ||
|
|
667a388fe1 | ||
|
|
d9d25bb108 | ||
|
|
18eba3dbee | ||
|
|
b6699c8aef | ||
|
|
d6681885e7 | ||
|
|
870e84b539 | ||
|
|
c13a3e93b2 | ||
|
|
72cd3b592e | ||
|
|
4d5c942464 | ||
|
|
20911697cc | ||
|
|
08e1e22dad | ||
|
|
885c63b147 | ||
|
|
7651596617 | ||
|
|
fd1edf9651 | ||
|
|
9414b87e27 | ||
|
|
942104aea2 | ||
|
|
3ff0f6d399 | ||
|
|
d07063b21b | ||
|
|
0be0c5bf3f | ||
|
|
f82665006c | ||
|
|
27e02c53b8 | ||
|
|
17dcdfcb06 | ||
|
|
6ba52156b8 | ||
|
|
dbb6e4d37f | ||
|
|
586c9d054c | ||
|
|
8643ac5661 | ||
|
|
79806322b1 | ||
|
|
13e80edb03 | ||
|
|
4b22bcebdd | ||
|
|
b907ae9705 | ||
|
|
de1d1f8cad | ||
|
|
1cb1f48cdc | ||
|
|
02e48ad8da | ||
|
|
8e6eeabb48 | ||
|
|
c22a0cea99 | ||
|
|
d13c4a5531 | ||
|
|
4b950c8a83 | ||
|
|
cf344b1809 | ||
|
|
aaba9e8d42 | ||
|
|
dbcdb24da6 | ||
|
|
083c395f10 | ||
|
|
8ef45a7fb9 | ||
|
|
5872879c9a | ||
|
|
f6a2a66386 | ||
|
|
d71181e65b | ||
|
|
bea8ddb330 | ||
|
|
0906e883c5 | ||
|
|
e7694afeba | ||
|
|
9d69bd6782 | ||
|
|
e6a387b8b2 | ||
|
|
b471c49cbf | ||
|
|
79abb0fd84 | ||
|
|
69bcd07c01 | ||
|
|
4a07c4f985 | ||
|
|
443c002c31 | ||
|
|
fcfb225410 | ||
|
|
ed03c17c22 | ||
|
|
1d6a5a4442 | ||
|
|
f415ca543b | ||
|
|
f7beb4db0e | ||
|
|
3fda54ca41 | ||
|
|
5ba352028d | ||
|
|
0fdb0a9e05 | ||
|
|
93fab04d7c | ||
|
|
27649b33c0 | ||
|
|
591fe567ec | ||
|
|
eb67c7edff | ||
|
|
03a915a915 | ||
|
|
6db850dd4d | ||
|
|
85a428dd92 | ||
|
|
24cf4896aa | ||
|
|
f9fcd4ff3a | ||
|
|
54db96a03e | ||
|
|
9263f9f950 | ||
|
|
04f75efa11 | ||
|
|
a6e900e52a | ||
|
|
d48d3b9ff4 | ||
|
|
8888a007f2 | ||
|
|
e6b823d89e | ||
|
|
7327f24c47 | ||
|
|
a3a381aad1 | ||
|
|
2b046dfe18 | ||
|
|
9da9ef6939 | ||
|
|
18b439bad6 | ||
|
|
0f91d3b6e1 | ||
|
|
edad05b63b | ||
|
|
54d7cbb121 | ||
|
|
3c48eacc98 | ||
|
|
7964e731e4 | ||
|
|
8ffaaea28d | ||
|
|
8585500e7c | ||
|
|
8a0725276b | ||
|
|
a0f41b03ba | ||
|
|
95743508c8 | ||
|
|
3da5be3602 | ||
|
|
f6e460d97a | ||
|
|
be20fb0dbb | ||
|
|
e40b1f9c5b | ||
|
|
7a1b51a1fd | ||
|
|
077fae2147 | ||
|
|
d825e6af7d | ||
|
|
6dc5894b9a | ||
|
|
12925c515a | ||
|
|
8c64221f88 |
No files matched your search
@@ -1,45 +1,60 @@
|
|||||||
import axios from 'axios';
|
import axios from "axios";
|
||||||
import fs from 'fs-extra';
|
import fs from "fs-extra";
|
||||||
import path from 'path';
|
import path from "path";
|
||||||
import { URLSearchParams } from 'url';
|
import { URLSearchParams } from "url";
|
||||||
|
|
||||||
// POEditor API information
|
// POEditor API information
|
||||||
const API_TOKEN = process.env.POEDITOR_API;
|
const API_TOKEN = process.env.POEDITOR_API;
|
||||||
const PROJECT_ID = process.env.POEDITOR_PROJECT_ID;
|
const PROJECT_ID = process.env.POEDITOR_PROJECT_ID;
|
||||||
const LANGUAGES = (process.env.LANGUAGES || 'tr,en').split(',');
|
const LANGUAGES = (process.env.LANGUAGES || "tr,en").split(",");
|
||||||
const EXPORT_FORMAT = process.env.EXPORT_FORMAT || 'key_value_json';
|
const EXPORT_FORMAT = process.env.EXPORT_FORMAT || "key_value_json";
|
||||||
|
|
||||||
// POEditor API endpoint
|
// POEditor API endpoint
|
||||||
const API_URL = 'https://api.poeditor.com/v2';
|
const API_URL = "https://api.poeditor.com/v2";
|
||||||
|
|
||||||
|
function normalizeLanguageCode(language) {
|
||||||
|
if (language.includes("-")) {
|
||||||
|
const [base, region] = language.split("-");
|
||||||
|
return `${base}-${region.toUpperCase()}`;
|
||||||
|
}
|
||||||
|
return language;
|
||||||
|
}
|
||||||
|
|
||||||
// Function to download translations
|
// Function to download translations
|
||||||
async function downloadTranslations() {
|
async function downloadTranslations() {
|
||||||
try {
|
try {
|
||||||
console.log('Downloading translations from POEditor...');
|
console.log("Downloading translations from POEditor...");
|
||||||
console.log(`Using export format: ${EXPORT_FORMAT}`);
|
console.log(`Using export format: ${EXPORT_FORMAT}`);
|
||||||
|
|
||||||
for (const language of LANGUAGES) {
|
for (const language of LANGUAGES) {
|
||||||
console.log(`Downloading translations for ${language} language...`);
|
console.log(`Downloading translations for ${language} language...`);
|
||||||
|
|
||||||
// Get export URL from POEditor
|
// Get export URL from POEditor
|
||||||
const exportResponse = await axios.post(`${API_URL}/projects/export`,
|
const exportResponse = await axios.post(
|
||||||
|
`${API_URL}/projects/export`,
|
||||||
new URLSearchParams({
|
new URLSearchParams({
|
||||||
api_token: API_TOKEN,
|
api_token: API_TOKEN,
|
||||||
id: PROJECT_ID,
|
id: PROJECT_ID,
|
||||||
language: language,
|
language: language,
|
||||||
type: EXPORT_FORMAT
|
type: EXPORT_FORMAT,
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
if (exportResponse.data.response.status !== 'success') {
|
if (exportResponse.data.response.status !== "success") {
|
||||||
throw new Error(`Failed to get export URL for ${language} language: ${JSON.stringify(exportResponse.data)}`);
|
throw new Error(
|
||||||
|
`Failed to get export URL for ${language} language: ${JSON.stringify(
|
||||||
|
exportResponse.data
|
||||||
|
)}`
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const fileUrl = exportResponse.data.result.url;
|
const fileUrl = exportResponse.data.result.url;
|
||||||
console.log(`Export URL obtained for ${language}`);
|
console.log(`Export URL obtained for ${language}`);
|
||||||
|
|
||||||
// Download translation file
|
// Download translation file
|
||||||
const downloadResponse = await axios.get(fileUrl, { responseType: 'json' });
|
const downloadResponse = await axios.get(fileUrl, {
|
||||||
|
responseType: "json",
|
||||||
|
});
|
||||||
const translations = downloadResponse.data;
|
const translations = downloadResponse.data;
|
||||||
console.log(`Downloaded translations for ${language}`);
|
console.log(`Downloaded translations for ${language}`);
|
||||||
|
|
||||||
@@ -48,9 +63,11 @@ async function downloadTranslations() {
|
|||||||
|
|
||||||
// If data is in array format, convert it to key-value format
|
// If data is in array format, convert it to key-value format
|
||||||
if (Array.isArray(translations)) {
|
if (Array.isArray(translations)) {
|
||||||
console.log(`Converting array format to key-value format for ${language}`);
|
console.log(
|
||||||
|
`Converting array format to key-value format for ${language}`
|
||||||
|
);
|
||||||
formattedTranslations = {};
|
formattedTranslations = {};
|
||||||
translations.forEach(item => {
|
translations.forEach((item) => {
|
||||||
if (item.term && item.definition) {
|
if (item.term && item.definition) {
|
||||||
formattedTranslations[item.term] = item.definition;
|
formattedTranslations[item.term] = item.definition;
|
||||||
}
|
}
|
||||||
@@ -58,16 +75,19 @@ async function downloadTranslations() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Determine the output filename based on language
|
// Determine the output filename based on language
|
||||||
const filename = language === 'en' ? 'gb.json' : `${language}.json`;
|
const normalizedLanguage = normalizeLanguageCode(language);
|
||||||
const outputPath = path.join(process.cwd(), 'temp', filename);
|
const filename = `${normalizedLanguage}.json`;
|
||||||
|
const outputPath = path.join(process.cwd(), "temp", filename);
|
||||||
await fs.writeJson(outputPath, formattedTranslations, { spaces: 2 });
|
await fs.writeJson(outputPath, formattedTranslations, { spaces: 2 });
|
||||||
|
|
||||||
console.log(`Translations for ${language} language successfully downloaded and saved as: ${filename}`);
|
console.log(
|
||||||
|
`Translations for ${language} language successfully downloaded and saved as: ${filename}`
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('All translations successfully downloaded!');
|
console.log("All translations successfully downloaded!");
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('An error occurred while downloading translations:', error);
|
console.error("An error occurred while downloading translations:", error);
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -76,12 +96,12 @@ async function downloadTranslations() {
|
|||||||
async function main() {
|
async function main() {
|
||||||
try {
|
try {
|
||||||
// Clean temp folder
|
// Clean temp folder
|
||||||
await fs.emptyDir(path.join(process.cwd(), 'temp'));
|
await fs.emptyDir(path.join(process.cwd(), "temp"));
|
||||||
|
|
||||||
// Download translations
|
// Download translations
|
||||||
await downloadTranslations();
|
await downloadTranslations();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('An error occurred during the process:', error);
|
console.error("An error occurred during the process:", error);
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,6 +36,9 @@ jobs:
|
|||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v3
|
||||||
|
|
||||||
- name: Log in to GitHub Container Registry
|
- name: Log in to GitHub Container Registry
|
||||||
uses: docker/login-action@v3
|
uses: docker/login-action@v3
|
||||||
with:
|
with:
|
||||||
|
|||||||
@@ -0,0 +1,44 @@
|
|||||||
|
name: Distribution deploy - Monolithic Multiarch
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: ["master"]
|
||||||
|
workflow_dispatch:
|
||||||
|
jobs:
|
||||||
|
docker-build-and-push-server:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v3
|
||||||
|
|
||||||
|
- name: Log in to GitHub Container Registry
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
registry: ghcr.io
|
||||||
|
username: ${{ github.actor }}
|
||||||
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Build and push multi-arch Docker image
|
||||||
|
uses: docker/build-push-action@v5
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
file: ./docker/dist-arm/server.Dockerfile
|
||||||
|
push: true
|
||||||
|
tags: ghcr.io/bluewave-labs/checkmate:backend-dist-mono-multiarch
|
||||||
|
platforms: linux/amd64,linux/arm64
|
||||||
|
labels: |
|
||||||
|
org.opencontainers.image.source=https://github.com/bluewave-labs/checkmate
|
||||||
|
|
||||||
|
# - name: Build Server Docker image
|
||||||
|
# run: |
|
||||||
|
# docker build \
|
||||||
|
# -t ghcr.io/bluewave-labs/checkmate:backend-dist-mono-multiarch \
|
||||||
|
# -f ./docker/dist-arm/server.Dockerfile \
|
||||||
|
# --label org.opencontainers.image.source=https://github.com/bluewave-labs/checkmate \
|
||||||
|
# .
|
||||||
|
|
||||||
|
# - name: Push Server Docker image
|
||||||
|
# run: docker push ghcr.io/bluewave-labs/checkmate:backend-dist-mono-multiarch
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
name: Distribution deploy - Monolithic
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: ["master"]
|
||||||
|
workflow_dispatch:
|
||||||
|
jobs:
|
||||||
|
docker-build-and-push-server:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Log in to GitHub Container Registry
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
registry: ghcr.io
|
||||||
|
username: ${{ github.actor }}
|
||||||
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Build Server Docker image
|
||||||
|
run: |
|
||||||
|
docker build \
|
||||||
|
-t ghcr.io/bluewave-labs/checkmate:backend-dist-mono \
|
||||||
|
-f ./docker/dist-mono/server.Dockerfile \
|
||||||
|
--label org.opencontainers.image.source=https://github.com/bluewave-labs/checkmate \
|
||||||
|
.
|
||||||
|
|
||||||
|
- name: Push Server Docker image
|
||||||
|
run: docker push ghcr.io/bluewave-labs/checkmate:backend-dist-mono
|
||||||
@@ -7,13 +7,12 @@ on:
|
|||||||
languages:
|
languages:
|
||||||
description: "Languages to synchronize (comma separated, e.g.: tr,en,es)"
|
description: "Languages to synchronize (comma separated, e.g.: tr,en,es)"
|
||||||
required: false
|
required: false
|
||||||
default: "tr,en"
|
default: "ar,zh-tw,cs,en,fi,fr,de,pt-br,ru,es,tr"
|
||||||
format:
|
format:
|
||||||
description: "Export format (key_value_json or json)"
|
description: "Export format (key_value_json or json)"
|
||||||
required: false
|
required: false
|
||||||
default: "key_value_json"
|
default: "key_value_json"
|
||||||
|
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
contents: write
|
contents: write
|
||||||
pull-requests: write
|
pull-requests: write
|
||||||
@@ -93,6 +92,12 @@ jobs:
|
|||||||
id: date
|
id: date
|
||||||
run: echo "date=$(date +'%Y-%m-%d %H:%M:%S')" >> $GITHUB_OUTPUT
|
run: echo "date=$(date +'%Y-%m-%d %H:%M:%S')" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
- name: Format client code
|
||||||
|
run: |
|
||||||
|
cd client
|
||||||
|
npm ci
|
||||||
|
npm run format
|
||||||
|
|
||||||
- name: Create Pull Request
|
- name: Create Pull Request
|
||||||
uses: peter-evans/create-pull-request@v7
|
uses: peter-evans/create-pull-request@v7
|
||||||
with:
|
with:
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ If you have any questions, suggestions or comments, please use our [Discord chan
|
|||||||
|
|
||||||
## 🧩 Features
|
## 🧩 Features
|
||||||
|
|
||||||
- Completely open source, deployable on your servers
|
- Completely open source, deployable on your servers or home devices (e.g Raspberry Pi 4 or 5)
|
||||||
- Website monitoring
|
- Website monitoring
|
||||||
- Page speed monitoring
|
- Page speed monitoring
|
||||||
- Infrastructure monitoring (memory, disk usage, CPU performance etc) - requires [Capture](https://github.com/bluewave-labs/capture)
|
- Infrastructure monitoring (memory, disk usage, CPU performance etc) - requires [Capture](https://github.com/bluewave-labs/capture)
|
||||||
@@ -65,18 +65,17 @@ If you have any questions, suggestions or comments, please use our [Discord chan
|
|||||||
- SSL monitoring
|
- SSL monitoring
|
||||||
- Port monitoring
|
- Port monitoring
|
||||||
- Incidents at a glance
|
- Incidents at a glance
|
||||||
- E-mail notifications
|
- Status pages
|
||||||
|
- E-mail, Webhooks, Discord, Telegram, Slack notifications
|
||||||
- Scheduled maintenance
|
- Scheduled maintenance
|
||||||
|
- JSON query monitoring
|
||||||
|
- Support for multiple languages
|
||||||
|
|
||||||
**Short term roadmap:**
|
**Short term roadmap:** ([Milestone 2.2](https://github.com/bluewave-labs/Checkmate/milestone/8))
|
||||||
|
|
||||||
- Status pages (**in progress**) https://github.com/bluewave-labs/Checkmate/issues/1131
|
- Better notifications
|
||||||
- Translations (i18n) (**in progress**)
|
- Network monitoring
|
||||||
- Better notification options (Webhooks, Discord, Telegram, Slack) (**in progress**) https://github.com/bluewave-labs/Checkmate/issues/1545
|
- ..and a few more features
|
||||||
- JSON query monitoring (**in progress**) https://github.com/bluewave-labs/Checkmate/issues/1573
|
|
||||||
- Tagging/grouping monitors https://github.com/bluewave-labs/Checkmate/issues/1546
|
|
||||||
- More configuration options
|
|
||||||
- DNS monitoring
|
|
||||||
|
|
||||||
## 🏗️ Screenshots
|
## 🏗️ Screenshots
|
||||||
|
|
||||||
@@ -108,9 +107,9 @@ If you have any questions, suggestions or comments, please use our [Discord chan
|
|||||||
|
|
||||||
## 🤝 Contributing
|
## 🤝 Contributing
|
||||||
|
|
||||||
We are [Alex](http://github.com/ajhollid) (team lead), [Vishnu](http://github.com/vishnusn77), [Mohadeseh](http://github.com/mohicody), [Gorkem](http://github.com/gorkem-bwl/), [Owaise](http://github.com/Owaiseimdad), [Aryaman](https://github.com/Br0wnHammer) and [Mert](https://github/com/mertssmnoglu) helping individuals and businesses monitor their infra and servers.
|
We are [Alex](http://github.com/ajhollid) (team lead), [Vishnu](http://github.com/vishnusn77), [Mohadeseh](http://github.com/mohicody), [Gorkem](http://github.com/gorkem-bwl/), [Owaise](http://github.com/Owaiseimdad), [Aryaman](https://github.com/Br0wnHammer) and [Mert](https://github.com/mertssmnoglu) helping individuals and businesses monitor their infra and servers.
|
||||||
|
|
||||||
We pride ourselves on building strong connections with contributors at every level. Despite being a young project, Checkmate has already earned 5000+ stars and attracted 60 contributors from around the globe.
|
We pride ourselves on building strong connections with contributors at every level. Despite being a young project, Checkmate has already earned 5800+ stars and attracted 70+ contributors from around the globe.
|
||||||
|
|
||||||
Our repo is starred by employees from **Google, Microsoft, Intel, Cisco, Tencent, Electronic Arts, ByteDance, JP Morgan Chase, Deloitte, Accenture, Foxconn, Broadcom, China Telecom, Barclays, Capgemini, Wipro, Cloudflare, Dassault Systèmes and NEC**, so don’t hold back — jump in, contribute and learn with us!
|
Our repo is starred by employees from **Google, Microsoft, Intel, Cisco, Tencent, Electronic Arts, ByteDance, JP Morgan Chase, Deloitte, Accenture, Foxconn, Broadcom, China Telecom, Barclays, Capgemini, Wipro, Cloudflare, Dassault Systèmes and NEC**, so don’t hold back — jump in, contribute and learn with us!
|
||||||
|
|
||||||
|
|||||||
@@ -1,2 +1 @@
|
|||||||
|
|
||||||
This directory contains the client side (frontend) of Checkmate.
|
This directory contains the client side (frontend) of Checkmate.
|
||||||
+19
-12
@@ -1,17 +1,24 @@
|
|||||||
<!doctype html>
|
<!doctype html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<link
|
||||||
|
rel="icon"
|
||||||
|
href="./checkmate_favicon.svg"
|
||||||
|
/>
|
||||||
|
<meta
|
||||||
|
name="viewport"
|
||||||
|
content="width=device-width, initial-scale=1.0"
|
||||||
|
/>
|
||||||
|
|
||||||
<head>
|
<title>Checkmate</title>
|
||||||
<meta charset="UTF-8" />
|
</head>
|
||||||
<link rel="icon" href="./checkmate_favicon.svg" />
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
||||||
|
|
||||||
<title>Checkmate</title>
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body>
|
|
||||||
<div id="root"></div>
|
|
||||||
<script type="module" src="/src/main.jsx"></script>
|
|
||||||
</body>
|
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div id="root"></div>
|
||||||
|
<script
|
||||||
|
type="module"
|
||||||
|
src="/src/main.jsx"
|
||||||
|
></script>
|
||||||
|
</body>
|
||||||
</html>
|
</html>
|
||||||
Generated
+1394
-509
File diff suppressed because it is too large.
Load diff
+11
-6
@@ -8,28 +8,30 @@
|
|||||||
"build": "vite build",
|
"build": "vite build",
|
||||||
"build-dev": "vite build --mode development",
|
"build-dev": "vite build --mode development",
|
||||||
"lint": "eslint . --ext js,jsx --report-unused-disable-directives --max-warnings 0",
|
"lint": "eslint . --ext js,jsx --report-unused-disable-directives --max-warnings 0",
|
||||||
"preview": "vite preview"
|
"preview": "vite preview",
|
||||||
|
"format": "prettier --write .",
|
||||||
|
"format-check": "prettier --check ."
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@emotion/react": "^11.13.3",
|
"@emotion/react": "^11.13.3",
|
||||||
"@emotion/styled": "^11.13.0",
|
"@emotion/styled": "^11.13.0",
|
||||||
"@fontsource/roboto": "^5.0.13",
|
"@fontsource/roboto": "^5.0.13",
|
||||||
"@hello-pangea/dnd": "^18.0.0",
|
"@hello-pangea/dnd": "^18.0.0",
|
||||||
"@mui/x-charts": "^7.5.1",
|
|
||||||
"@mui/x-data-grid": "7.29.0",
|
|
||||||
"@mui/x-date-pickers": "7.29.0",
|
|
||||||
"@mui/icons-material": "6.4.11",
|
"@mui/icons-material": "6.4.11",
|
||||||
"@mui/lab": "6.0.0-dev.240424162023-9968b4889d",
|
"@mui/lab": "6.0.0-dev.240424162023-9968b4889d",
|
||||||
"@mui/material": "6.4.11",
|
"@mui/material": "6.4.11",
|
||||||
|
"@mui/x-charts": "^7.5.1",
|
||||||
|
"@mui/x-data-grid": "7.29.0",
|
||||||
|
"@mui/x-date-pickers": "7.29.0",
|
||||||
"@reduxjs/toolkit": "2.7.0",
|
"@reduxjs/toolkit": "2.7.0",
|
||||||
"axios": "^1.7.4",
|
"axios": "^1.7.4",
|
||||||
"dayjs": "1.11.13",
|
"dayjs": "1.11.13",
|
||||||
"flag-icons": "7.3.2",
|
"flag-icons": "7.3.2",
|
||||||
"jwt-decode": "^4.0.0",
|
|
||||||
"html2canvas": "^1.4.1",
|
"html2canvas": "^1.4.1",
|
||||||
"i18next": "^24.2.2",
|
"i18next": "^24.2.2",
|
||||||
"immutability-helper": "^3.1.1",
|
"immutability-helper": "^3.1.1",
|
||||||
"joi": "17.13.3",
|
"joi": "17.13.3",
|
||||||
|
"jwt-decode": "^4.0.0",
|
||||||
"maplibre-gl": "5.3.1",
|
"maplibre-gl": "5.3.1",
|
||||||
"mui-color-input": "^6.0.0",
|
"mui-color-input": "^6.0.0",
|
||||||
"react": "18.3.1",
|
"react": "18.3.1",
|
||||||
@@ -63,6 +65,9 @@
|
|||||||
"eslint-plugin-react-hooks": "^5.0.0",
|
"eslint-plugin-react-hooks": "^5.0.0",
|
||||||
"eslint-plugin-react-refresh": "^0.4.6",
|
"eslint-plugin-react-refresh": "^0.4.6",
|
||||||
"prettier": "^3.3.3",
|
"prettier": "^3.3.3",
|
||||||
"vite": "^5.2.0"
|
"vite": "^5.4.19"
|
||||||
|
},
|
||||||
|
"optionalDependencies": {
|
||||||
|
"@rollup/rollup-linux-arm64-musl": "4.41.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,7 +1,5 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
||||||
"extends": [
|
"extends": ["config:recommended"],
|
||||||
"config:recommended"
|
"labels": ["dependencies"]
|
||||||
],
|
|
||||||
"labels": ["dependencies"]
|
|
||||||
}
|
}
|
||||||
@@ -3,28 +3,28 @@ import Button from "@mui/material/Button";
|
|||||||
import { styled } from "@mui/material/styles";
|
import { styled } from "@mui/material/styles";
|
||||||
|
|
||||||
const RoundGradientButton = styled(Button)(({ theme }) => ({
|
const RoundGradientButton = styled(Button)(({ theme }) => ({
|
||||||
position: "relative",
|
position: "relative",
|
||||||
border: "5px solid transparent",
|
border: "5px solid transparent",
|
||||||
backgroundClip: "padding-box",
|
backgroundClip: "padding-box",
|
||||||
borderRadius: 30,
|
borderRadius: 30,
|
||||||
fontSize: "1.2rem",
|
fontSize: "1.2rem",
|
||||||
color: theme.palette.primary.contrastText,
|
color: theme.palette.primary.contrastText,
|
||||||
backgroundColor: theme.palette.background.main,
|
backgroundColor: theme.palette.background.main,
|
||||||
|
|
||||||
"&:after": {
|
"&:after": {
|
||||||
position: "absolute",
|
position: "absolute",
|
||||||
top: -3,
|
top: -3,
|
||||||
left: -3,
|
left: -3,
|
||||||
right: -3,
|
right: -3,
|
||||||
bottom: -3,
|
bottom: -3,
|
||||||
background:
|
background:
|
||||||
theme.palette.mode === "dark"
|
theme.palette.mode === "dark"
|
||||||
? "linear-gradient(90deg, #842bd2, #ff5451, #8c52ff)"
|
? "linear-gradient(90deg, #842bd2, #ff5451, #8c52ff)"
|
||||||
: "linear-gradient(90deg, #842bd2, #ff5451, #8c52ff)",
|
: "linear-gradient(90deg, #842bd2, #ff5451, #8c52ff)",
|
||||||
content: '""',
|
content: '""',
|
||||||
zIndex: -1,
|
zIndex: -1,
|
||||||
borderRadius: 30,
|
borderRadius: 30,
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
export default RoundGradientButton;
|
export default RoundGradientButton;
|
||||||
@@ -31,78 +31,76 @@ import IconBox from "../../IconBox";
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
const EmptyView = ({
|
const EmptyView = ({
|
||||||
icon,
|
icon,
|
||||||
header,
|
header,
|
||||||
message = "No Data",
|
message = "No Data",
|
||||||
headingLevel = "h2",
|
headingLevel = "h2",
|
||||||
justifyContent = "flex-start",
|
justifyContent = "flex-start",
|
||||||
height = "100%"
|
height = "100%",
|
||||||
}) => {
|
}) => {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
return (
|
return (
|
||||||
<Stack
|
<Stack
|
||||||
flex={1}
|
flex={1}
|
||||||
direction="row"
|
direction="row"
|
||||||
sx={{
|
sx={{
|
||||||
backgroundColor: theme.palette.primary.main,
|
backgroundColor: theme.palette.primary.main,
|
||||||
|
|
||||||
border: 1,
|
border: 1,
|
||||||
borderStyle: "solid",
|
borderStyle: "solid",
|
||||||
borderColor: theme.palette.primary.lowContrast,
|
borderColor: theme.palette.primary.lowContrast,
|
||||||
borderRadius: 2,
|
borderRadius: 2,
|
||||||
borderTopRightRadius: 4,
|
borderTopRightRadius: 4,
|
||||||
borderBottomRightRadius: 4,
|
borderBottomRightRadius: 4,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Stack
|
<Stack
|
||||||
flex={1}
|
flex={1}
|
||||||
alignItems="center"
|
alignItems="center"
|
||||||
sx={{
|
sx={{
|
||||||
padding: theme.spacing(8),
|
padding: theme.spacing(8),
|
||||||
justifyContent,
|
justifyContent,
|
||||||
gap: theme.spacing(8),
|
gap: theme.spacing(8),
|
||||||
height,
|
height,
|
||||||
"& h2": {
|
"& h2": {
|
||||||
color: theme.palette.primary.contrastTextSecondary,
|
color: theme.palette.primary.contrastTextSecondary,
|
||||||
fontSize: 15,
|
fontSize: 15,
|
||||||
fontWeight: 500,
|
fontWeight: 500,
|
||||||
},
|
},
|
||||||
|
|
||||||
"& tspan, & text": {
|
"& tspan, & text": {
|
||||||
fill: theme.palette.primary.contrastTextTertiary,
|
fill: theme.palette.primary.contrastTextTertiary,
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Stack
|
<Stack
|
||||||
alignSelf="flex-start"
|
alignSelf="flex-start"
|
||||||
direction="row"
|
direction="row"
|
||||||
alignItems="center"
|
alignItems="center"
|
||||||
gap={theme.spacing(6)}
|
gap={theme.spacing(6)}
|
||||||
>
|
>
|
||||||
{icon && <IconBox>{icon}</IconBox>}
|
{icon && <IconBox>{icon}</IconBox>}
|
||||||
{header && <Typography component="h2">{header}</Typography>}
|
{header && <Typography component="h2">{header}</Typography>}
|
||||||
</Stack>
|
</Stack>
|
||||||
<Stack
|
<Stack
|
||||||
flex={1}
|
flex={1}
|
||||||
justifyContent="center"
|
justifyContent="center"
|
||||||
alignItems="center"
|
alignItems="center"
|
||||||
>
|
>
|
||||||
<Typography component={headingLevel}>
|
<Typography component={headingLevel}>{message}</Typography>
|
||||||
{message}
|
</Stack>
|
||||||
</Typography>
|
</Stack>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Stack>
|
);
|
||||||
</Stack>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
EmptyView.propTypes = {
|
EmptyView.propTypes = {
|
||||||
message: PropTypes.string,
|
message: PropTypes.string,
|
||||||
icon: PropTypes.node,
|
icon: PropTypes.node,
|
||||||
header: PropTypes.string,
|
header: PropTypes.string,
|
||||||
headingLevel: PropTypes.oneOf(['h1', 'h2', 'h3']),
|
headingLevel: PropTypes.oneOf(["h1", "h2", "h3"]),
|
||||||
justifyContent: PropTypes.string,
|
justifyContent: PropTypes.string,
|
||||||
height: PropTypes.string
|
height: PropTypes.string,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default EmptyView;
|
export default EmptyView;
|
||||||
@@ -18,7 +18,13 @@ const ChartBox = ({
|
|||||||
}) => {
|
}) => {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
if (isEmpty) {
|
if (isEmpty) {
|
||||||
return <EmptyView icon={icon} header={header} message={noDataMessage} />;
|
return (
|
||||||
|
<EmptyView
|
||||||
|
icon={icon}
|
||||||
|
header={header}
|
||||||
|
message={noDataMessage}
|
||||||
|
/>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<Stack
|
<Stack
|
||||||
@@ -95,5 +101,5 @@ ChartBox.propTypes = {
|
|||||||
header: PropTypes.string,
|
header: PropTypes.string,
|
||||||
height: PropTypes.string,
|
height: PropTypes.string,
|
||||||
noDataMessage: PropTypes.string,
|
noDataMessage: PropTypes.string,
|
||||||
isEmpty: PropTypes.bool
|
isEmpty: PropTypes.bool,
|
||||||
};
|
};
|
||||||
@@ -1,214 +1,255 @@
|
|||||||
import * as React from 'react';
|
import * as React from "react";
|
||||||
import Box from '@mui/material/Box';
|
import Box from "@mui/material/Box";
|
||||||
import Container from '@mui/material/Container';
|
import Container from "@mui/material/Container";
|
||||||
import IconButton from '@mui/material/IconButton';
|
import IconButton from "@mui/material/IconButton";
|
||||||
import Link from '@mui/material/Link';
|
import Link from "@mui/material/Link";
|
||||||
import Stack from '@mui/material/Stack';
|
import Stack from "@mui/material/Stack";
|
||||||
import Typography from '@mui/material/Typography';
|
import Typography from "@mui/material/Typography";
|
||||||
import { FaFacebook, FaLinkedin, FaGithub, FaTwitter, FaEnvelope } from 'react-icons/fa';
|
import { FaFacebook, FaLinkedin, FaGithub, FaTwitter, FaEnvelope } from "react-icons/fa";
|
||||||
|
|
||||||
|
|
||||||
function Copyright() {
|
function Copyright() {
|
||||||
return (
|
return (
|
||||||
<Typography sx={{ color: 'text.secondary', mt: 1 }}>
|
<Typography sx={{ color: "text.secondary", mt: 1 }}>
|
||||||
{'Copyright © '}
|
{"Copyright © "}
|
||||||
<Link color="text.secondary" href="https://prism.uprock.com/">
|
<Link
|
||||||
UpRock
|
color="text.secondary"
|
||||||
</Link>
|
href="https://prism.uprock.com/"
|
||||||
|
>
|
||||||
{new Date().getFullYear()}
|
UpRock
|
||||||
</Typography>
|
</Link>
|
||||||
);
|
|
||||||
|
{new Date().getFullYear()}
|
||||||
|
</Typography>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function Footer() {
|
export default function Footer() {
|
||||||
return (
|
return (
|
||||||
<Container
|
<Container
|
||||||
sx={{
|
sx={{
|
||||||
display: 'flex',
|
display: "flex",
|
||||||
flexDirection: 'column',
|
flexDirection: "column",
|
||||||
alignItems: 'center',
|
alignItems: "center",
|
||||||
gap: { xs: 4, sm: 8 },
|
gap: { xs: 4, sm: 8 },
|
||||||
py: { xs: 24, sm: 24 },
|
py: { xs: 24, sm: 24 },
|
||||||
px: { xs: 12, sm: 12 },
|
px: { xs: 12, sm: 12 },
|
||||||
textAlign: { sm: 'center', md: 'left' },
|
textAlign: { sm: "center", md: "left" },
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: "flex",
|
||||||
|
justifyContent: "space-between",
|
||||||
|
pt: { xs: 4, sm: 8 },
|
||||||
|
width: "100%",
|
||||||
|
borderColor: "divider",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<Link
|
||||||
|
color="text.secondary"
|
||||||
|
href="https://uprock.com/privacy-policy"
|
||||||
|
>
|
||||||
|
Privacy Policy
|
||||||
|
</Link>
|
||||||
|
<Typography sx={{ display: "inline", mx: 0.5, opacity: 0.5 }}>
|
||||||
|
•
|
||||||
|
</Typography>
|
||||||
|
<Link
|
||||||
|
color="text.secondary"
|
||||||
|
href="https://uprock.com/terms-of-use"
|
||||||
|
>
|
||||||
|
Terms of Service
|
||||||
|
</Link>
|
||||||
|
<Copyright />
|
||||||
|
</div>
|
||||||
|
<Stack
|
||||||
|
direction="row"
|
||||||
|
spacing={1}
|
||||||
|
useFlexGap
|
||||||
|
sx={{ justifyContent: "left", color: "text.secondary" }}
|
||||||
|
>
|
||||||
|
<IconButton
|
||||||
|
color="inherit"
|
||||||
|
size="small"
|
||||||
|
href="mailto:prism@uprock.com?subject=Interested%20in%20UpRock%20Prism"
|
||||||
|
aria-label="Contact Us"
|
||||||
|
sx={{ alignSelf: "center" }}
|
||||||
|
>
|
||||||
|
<FaEnvelope />
|
||||||
|
</IconButton>
|
||||||
|
<IconButton
|
||||||
|
color="inherit"
|
||||||
|
size="small"
|
||||||
|
href="https://facebook.com/uprockcom"
|
||||||
|
aria-label="Facebook"
|
||||||
|
sx={{ alignSelf: "center" }}
|
||||||
|
>
|
||||||
|
<FaFacebook />
|
||||||
|
</IconButton>
|
||||||
|
<IconButton
|
||||||
|
color="inherit"
|
||||||
|
size="small"
|
||||||
|
href="https://x.com/uprockcom"
|
||||||
|
aria-label="X"
|
||||||
|
sx={{ alignSelf: "center" }}
|
||||||
|
>
|
||||||
|
<FaTwitter />
|
||||||
|
</IconButton>
|
||||||
|
<IconButton
|
||||||
|
color="inherit"
|
||||||
|
size="small"
|
||||||
|
href="https://www.linkedin.com/company/uprock/"
|
||||||
|
aria-label="LinkedIn"
|
||||||
|
sx={{ alignSelf: "center" }}
|
||||||
|
>
|
||||||
|
<FaLinkedin />
|
||||||
|
</IconButton>
|
||||||
|
<IconButton
|
||||||
|
color="inherit"
|
||||||
|
size="small"
|
||||||
|
href="https://github.com/uprockcom"
|
||||||
|
aria-label="GitHub"
|
||||||
|
sx={{ alignSelf: "center" }}
|
||||||
|
>
|
||||||
|
<FaGithub />
|
||||||
|
</IconButton>
|
||||||
|
</Stack>
|
||||||
|
</Box>
|
||||||
|
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
display: 'flex',
|
display: "flex",
|
||||||
justifyContent: 'space-between',
|
flexDirection: "column",
|
||||||
pt: { xs: 4, sm: 8 },
|
alignItems: "center",
|
||||||
width: '100%',
|
mt: 4,
|
||||||
borderColor: 'divider',
|
}}
|
||||||
}}
|
>
|
||||||
>
|
<Typography
|
||||||
<div>
|
variant="h2"
|
||||||
<Link color="text.secondary" href="https://uprock.com/privacy-policy">
|
sx={{ color: "text.secondary" }}
|
||||||
Privacy Policy
|
>
|
||||||
</Link>
|
Made with ❤️ by
|
||||||
<Typography sx={{ display: 'inline', mx: 0.5, opacity: 0.5 }}>
|
<Link
|
||||||
•
|
href="https://uprock.com"
|
||||||
</Typography>
|
color="inherit"
|
||||||
<Link color="text.secondary" href="https://uprock.com/terms-of-use">
|
sx={{ mx: 0.5 }}
|
||||||
Terms of Service
|
>
|
||||||
</Link>
|
UpRock
|
||||||
<Copyright />
|
</Link>
|
||||||
</div>
|
&
|
||||||
<Stack
|
<Link
|
||||||
direction="row"
|
href="https://bluewavelabs.ca"
|
||||||
spacing={1}
|
color="inherit"
|
||||||
useFlexGap
|
sx={{ mx: 0.5 }}
|
||||||
sx={{ justifyContent: 'left', color: 'text.secondary' }}
|
>
|
||||||
>
|
Bluewave Labs
|
||||||
<IconButton
|
</Link>
|
||||||
color="inherit"
|
</Typography>
|
||||||
size="small"
|
<Box
|
||||||
href="mailto:prism@uprock.com?subject=Interested%20in%20UpRock%20Prism"
|
sx={{
|
||||||
aria-label="Contact Us"
|
display: "flex",
|
||||||
sx={{ alignSelf: 'center' }}
|
alignItems: "center",
|
||||||
>
|
mt: 2,
|
||||||
<FaEnvelope />
|
}}
|
||||||
</IconButton>
|
>
|
||||||
<IconButton
|
<Typography
|
||||||
color="inherit"
|
variant="h2"
|
||||||
size="small"
|
sx={{ color: "text.secondary", mr: 1 }}
|
||||||
href="https://facebook.com/uprockcom"
|
>
|
||||||
aria-label="Facebook"
|
Built on
|
||||||
sx={{ alignSelf: 'center' }}
|
</Typography>
|
||||||
>
|
<svg
|
||||||
<FaFacebook />
|
id="Layer_1"
|
||||||
</IconButton>
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
<IconButton
|
xmlnsXlink="http://www.w3.org/1999/xlink"
|
||||||
color="inherit"
|
x="0px"
|
||||||
size="small"
|
y="0px"
|
||||||
href="https://x.com/uprockcom"
|
viewBox="0 0 397.7 311.7"
|
||||||
aria-label="X"
|
xmlSpace="preserve"
|
||||||
sx={{ alignSelf: 'center' }}
|
width="15"
|
||||||
>
|
height="15"
|
||||||
<FaTwitter />
|
>
|
||||||
</IconButton>
|
<style type="text/css">
|
||||||
<IconButton
|
{`.st0{fill:url(#SVGID_1_);}
|
||||||
color="inherit"
|
|
||||||
size="small"
|
|
||||||
href="https://www.linkedin.com/company/uprock/"
|
|
||||||
aria-label="LinkedIn"
|
|
||||||
sx={{ alignSelf: 'center' }}
|
|
||||||
>
|
|
||||||
<FaLinkedin />
|
|
||||||
</IconButton>
|
|
||||||
<IconButton
|
|
||||||
color="inherit"
|
|
||||||
size="small"
|
|
||||||
href="https://github.com/uprockcom"
|
|
||||||
aria-label="GitHub"
|
|
||||||
sx={{ alignSelf: 'center' }}
|
|
||||||
>
|
|
||||||
<FaGithub />
|
|
||||||
</IconButton>
|
|
||||||
|
|
||||||
</Stack>
|
|
||||||
</Box>
|
|
||||||
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
display: 'flex',
|
|
||||||
flexDirection: 'column',
|
|
||||||
alignItems: 'center',
|
|
||||||
mt: 4,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Typography variant="h2" sx={{ color: 'text.secondary' }}>
|
|
||||||
Made with ❤️ by
|
|
||||||
<Link href="https://uprock.com" color="inherit" sx={{ mx: 0.5 }}>
|
|
||||||
UpRock
|
|
||||||
</Link>
|
|
||||||
&
|
|
||||||
<Link href="https://bluewavelabs.ca" color="inherit" sx={{ mx: 0.5 }}>
|
|
||||||
Bluewave Labs
|
|
||||||
</Link>
|
|
||||||
</Typography>
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
display: 'flex',
|
|
||||||
alignItems: 'center',
|
|
||||||
mt: 2,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Typography variant="h2" sx={{ color: 'text.secondary', mr: 1 }}>
|
|
||||||
Built on
|
|
||||||
</Typography>
|
|
||||||
<svg
|
|
||||||
id="Layer_1"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
xmlnsXlink="http://www.w3.org/1999/xlink"
|
|
||||||
x="0px"
|
|
||||||
y="0px"
|
|
||||||
viewBox="0 0 397.7 311.7"
|
|
||||||
xmlSpace="preserve"
|
|
||||||
width="15"
|
|
||||||
height="15"
|
|
||||||
>
|
|
||||||
<style type="text/css">
|
|
||||||
{`.st0{fill:url(#SVGID_1_);}
|
|
||||||
.st1{fill:url(#SVGID_2_);}
|
.st1{fill:url(#SVGID_2_);}
|
||||||
.st2{fill:url(#SVGID_3_);}`}
|
.st2{fill:url(#SVGID_3_);}`}
|
||||||
</style>
|
</style>
|
||||||
<linearGradient
|
<linearGradient
|
||||||
id="SVGID_1_"
|
id="SVGID_1_"
|
||||||
gradientUnits="userSpaceOnUse"
|
gradientUnits="userSpaceOnUse"
|
||||||
x1="360.8791"
|
x1="360.8791"
|
||||||
y1="351.4553"
|
y1="351.4553"
|
||||||
x2="141.213"
|
x2="141.213"
|
||||||
y2="-69.2936"
|
y2="-69.2936"
|
||||||
gradientTransform="matrix(1 0 0 -1 0 314)"
|
gradientTransform="matrix(1 0 0 -1 0 314)"
|
||||||
>
|
>
|
||||||
<stop offset="0" style={{ stopColor: 'rgb(0, 255, 163)' }} />
|
<stop
|
||||||
<stop offset="1" style={{ stopColor: 'rgb(220, 31, 255)' }} />
|
offset="0"
|
||||||
</linearGradient>
|
style={{ stopColor: "rgb(0, 255, 163)" }}
|
||||||
<path
|
/>
|
||||||
className="st0"
|
<stop
|
||||||
d="M64.6,237.9c2.4-2.4,5.7-3.8,9.2-3.8h317.4c5.8,0,8.7,7,4.6,11.1l-62.7,62.7c-2.4,2.4-5.7,3.8-9.2,3.8H6.5 c-5.8,0-8.7-7-4.6-11.1L64.6,237.9z"
|
offset="1"
|
||||||
/>
|
style={{ stopColor: "rgb(220, 31, 255)" }}
|
||||||
<linearGradient
|
/>
|
||||||
id="SVGID_2_"
|
</linearGradient>
|
||||||
gradientUnits="userSpaceOnUse"
|
<path
|
||||||
x1="264.8291"
|
className="st0"
|
||||||
y1="401.6014"
|
d="M64.6,237.9c2.4-2.4,5.7-3.8,9.2-3.8h317.4c5.8,0,8.7,7,4.6,11.1l-62.7,62.7c-2.4,2.4-5.7,3.8-9.2,3.8H6.5 c-5.8,0-8.7-7-4.6-11.1L64.6,237.9z"
|
||||||
x2="45.163"
|
/>
|
||||||
y2="-19.1475"
|
<linearGradient
|
||||||
gradientTransform="matrix(1 0 0 -1 0 314)"
|
id="SVGID_2_"
|
||||||
>
|
gradientUnits="userSpaceOnUse"
|
||||||
<stop offset="0" style={{ stopColor: 'rgb(0, 255, 163)' }} />
|
x1="264.8291"
|
||||||
<stop offset="1" style={{ stopColor: 'rgb(220, 31, 255)' }} />
|
y1="401.6014"
|
||||||
</linearGradient>
|
x2="45.163"
|
||||||
<path
|
y2="-19.1475"
|
||||||
className="st1"
|
gradientTransform="matrix(1 0 0 -1 0 314)"
|
||||||
d="M64.6,3.8C67.1,1.4,70.4,0,73.8,0h317.4c5.8,0,8.7,7,4.6,11.1l-62.7,62.7c-2.4,2.4-5.7,3.8-9.2,3.8H6.5 c-5.8,0-8.7-7-4.6-11.1L64.6,3.8z"
|
>
|
||||||
/>
|
<stop
|
||||||
<linearGradient
|
offset="0"
|
||||||
id="SVGID_3_"
|
style={{ stopColor: "rgb(0, 255, 163)" }}
|
||||||
gradientUnits="userSpaceOnUse"
|
/>
|
||||||
x1="312.5484"
|
<stop
|
||||||
y1="376.688"
|
offset="1"
|
||||||
x2="92.8822"
|
style={{ stopColor: "rgb(220, 31, 255)" }}
|
||||||
y2="-44.061"
|
/>
|
||||||
gradientTransform="matrix(1 0 0 -1 0 314)"
|
</linearGradient>
|
||||||
>
|
<path
|
||||||
<stop offset="0" style={{ stopColor: 'rgb(0, 255, 163)' }} />
|
className="st1"
|
||||||
<stop offset="1" style={{ stopColor: 'rgb(220, 31, 255)' }} />
|
d="M64.6,3.8C67.1,1.4,70.4,0,73.8,0h317.4c5.8,0,8.7,7,4.6,11.1l-62.7,62.7c-2.4,2.4-5.7,3.8-9.2,3.8H6.5 c-5.8,0-8.7-7-4.6-11.1L64.6,3.8z"
|
||||||
</linearGradient>
|
/>
|
||||||
<path
|
<linearGradient
|
||||||
className="st2"
|
id="SVGID_3_"
|
||||||
d="M333.1,120.1c-2.4-2.4-5.7-3.8-9.2-3.8H6.5c-5.8,0-8.7,7-4.6,11.1l62.7,62.7c2.4,2.4,5.7,3.8,9.2,3.8h317.4 c5.8,0,8.7-7,4.6-11.1L333.1,120.1z"
|
gradientUnits="userSpaceOnUse"
|
||||||
/>
|
x1="312.5484"
|
||||||
</svg>
|
y1="376.688"
|
||||||
<Typography variant="h2" sx={{ color: 'text.secondary', ml: 1 }}>
|
x2="92.8822"
|
||||||
Solana
|
y2="-44.061"
|
||||||
</Typography>
|
gradientTransform="matrix(1 0 0 -1 0 314)"
|
||||||
</Box>
|
>
|
||||||
</Box>
|
<stop
|
||||||
</Container>
|
offset="0"
|
||||||
);
|
style={{ stopColor: "rgb(0, 255, 163)" }}
|
||||||
|
/>
|
||||||
|
<stop
|
||||||
|
offset="1"
|
||||||
|
style={{ stopColor: "rgb(220, 31, 255)" }}
|
||||||
|
/>
|
||||||
|
</linearGradient>
|
||||||
|
<path
|
||||||
|
className="st2"
|
||||||
|
d="M333.1,120.1c-2.4-2.4-5.7-3.8-9.2-3.8H6.5c-5.8,0-8.7,7-4.6,11.1l62.7,62.7c2.4,2.4,5.7,3.8,9.2,3.8h317.4 c5.8,0,8.7-7,4.6-11.1L333.1,120.1z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
<Typography
|
||||||
|
variant="h2"
|
||||||
|
sx={{ color: "text.secondary", ml: 1 }}
|
||||||
|
>
|
||||||
|
Solana
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
</Container>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
@@ -29,7 +29,7 @@ const ConfigBox = styled(Stack)(({ theme }) => ({
|
|||||||
},
|
},
|
||||||
"& h1, & h2": {
|
"& h1, & h2": {
|
||||||
color: theme.palette.primary.contrastTextSecondary,
|
color: theme.palette.primary.contrastTextSecondary,
|
||||||
}
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
export default ConfigBox;
|
export default ConfigBox;
|
||||||
@@ -19,31 +19,40 @@ import ConfigBox from "../ConfigBox";
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
const ConfigRow = ({ title, description, children }) => {
|
const ConfigRow = ({ title, description, children }) => {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ConfigBox>
|
<ConfigBox>
|
||||||
<Box>
|
<Box>
|
||||||
<Typography component="h2" variant="h2">
|
<Typography
|
||||||
{title}
|
component="h2"
|
||||||
</Typography>
|
variant="h2"
|
||||||
{description && (
|
>
|
||||||
<Typography variant="body2" mt={theme.spacing(2)}>
|
{title}
|
||||||
{description}
|
</Typography>
|
||||||
</Typography>
|
{description && (
|
||||||
)}
|
<Typography
|
||||||
</Box>
|
variant="body2"
|
||||||
<Stack gap={theme.spacing(15)} mt={theme.spacing(4)}>
|
mt={theme.spacing(2)}
|
||||||
{children}
|
>
|
||||||
</Stack>
|
{description}
|
||||||
</ConfigBox>
|
</Typography>
|
||||||
);
|
)}
|
||||||
|
</Box>
|
||||||
|
<Stack
|
||||||
|
gap={theme.spacing(15)}
|
||||||
|
mt={theme.spacing(4)}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</Stack>
|
||||||
|
</ConfigBox>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
ConfigRow.propTypes = {
|
ConfigRow.propTypes = {
|
||||||
title: PropTypes.string.isRequired,
|
title: PropTypes.string.isRequired,
|
||||||
description: PropTypes.string,
|
description: PropTypes.string,
|
||||||
children: PropTypes.node,
|
children: PropTypes.node,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default ConfigRow;
|
export default ConfigRow;
|
||||||
@@ -42,10 +42,14 @@ const FilterHeader = ({ header, options, value, onChange, multiple = true }) =>
|
|||||||
return header;
|
return header;
|
||||||
}
|
}
|
||||||
|
|
||||||
return header + " | " + selected
|
return (
|
||||||
.map((value) => options.find((option) => option.value === value)?.label)
|
header +
|
||||||
.filter(Boolean)
|
" | " +
|
||||||
.join(", ");
|
selected
|
||||||
|
.map((value) => options.find((option) => option.value === value)?.label)
|
||||||
|
.filter(Boolean)
|
||||||
|
.join(", ")
|
||||||
|
);
|
||||||
}}
|
}}
|
||||||
MenuProps={{
|
MenuProps={{
|
||||||
anchorOrigin: {
|
anchorOrigin: {
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import Dot from "../Dot";
|
|||||||
* @param {number} params.percentage - The percentage to display.
|
* @param {number} params.percentage - The percentage to display.
|
||||||
* @returns {React.ElementType} Returns a div element with the host details.
|
* @returns {React.ElementType} Returns a div element with the host details.
|
||||||
*/
|
*/
|
||||||
const Host = ({ url, title, percentageColor, percentage }) => {
|
const Host = ({ url, title, percentageColor, percentage, showURL }) => {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
return (
|
return (
|
||||||
<Stack>
|
<Stack>
|
||||||
@@ -40,7 +40,7 @@ const Host = ({ url, title, percentageColor, percentage }) => {
|
|||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</Stack>
|
</Stack>
|
||||||
<span style={{ opacity: 0.6 }}>{url}</span>
|
{showURL && <span style={{ opacity: 0.6 }}>{url}</span>}
|
||||||
</Stack>
|
</Stack>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -50,6 +50,7 @@ Host.propTypes = {
|
|||||||
percentageColor: PropTypes.string,
|
percentageColor: PropTypes.string,
|
||||||
percentage: PropTypes.string,
|
percentage: PropTypes.string,
|
||||||
url: PropTypes.string,
|
url: PropTypes.string,
|
||||||
|
showURL: PropTypes.bool,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Host;
|
export default Host;
|
||||||
@@ -36,199 +36,198 @@ const ImageUpload = ({
|
|||||||
error,
|
error,
|
||||||
}) => {
|
}) => {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [uploadComplete, setUploadComplete] = useState(false);
|
const [uploadComplete, setUploadComplete] = useState(false);
|
||||||
const [completedFile, setCompletedFile] = useState(null);
|
const [completedFile, setCompletedFile] = useState(null);
|
||||||
const [file, setFile] = useState(null);
|
const [file, setFile] = useState(null);
|
||||||
const [progress, setProgress] = useState({ value: 0, isLoading: false });
|
const [progress, setProgress] = useState({ value: 0, isLoading: false });
|
||||||
const intervalRef = useRef(null);
|
const intervalRef = useRef(null);
|
||||||
const [localError, setLocalError] = useState(null);
|
const [localError, setLocalError] = useState(null);
|
||||||
const [isDragging, setIsDragging] = useState(false);
|
const [isDragging, setIsDragging] = useState(false);
|
||||||
|
|
||||||
const roundStyle = previewIsRound ? { borderRadius: "50%" } : {};
|
const roundStyle = previewIsRound ? { borderRadius: "50%" } : {};
|
||||||
|
|
||||||
const handleImageChange = useCallback(
|
const handleImageChange = useCallback(
|
||||||
(file) => {
|
(file) => {
|
||||||
if (!file) return;
|
if (!file) return;
|
||||||
|
|
||||||
const isValidType = accept.some((type) =>
|
const isValidType = accept.some((type) => file.type.includes(type));
|
||||||
file.type.includes(type)
|
const isValidSize = file.size <= maxSize;
|
||||||
);
|
|
||||||
const isValidSize = file.size <= maxSize;
|
|
||||||
|
|
||||||
if (!isValidType) {
|
if (!isValidType) {
|
||||||
setLocalError(t('invalidFileFormat'));
|
setLocalError(t("invalidFileFormat"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!isValidSize) {
|
if (!isValidSize) {
|
||||||
setLocalError(t('invalidFileSize'));
|
setLocalError(t("invalidFileSize"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
setLocalError(null);
|
setLocalError(null);
|
||||||
|
|
||||||
const previewFile = {
|
const previewFile = {
|
||||||
src: URL.createObjectURL(file),
|
src: URL.createObjectURL(file),
|
||||||
name: file.name,
|
name: file.name,
|
||||||
file,
|
file,
|
||||||
};
|
};
|
||||||
|
|
||||||
setFile(previewFile);
|
setFile(previewFile);
|
||||||
setProgress({ value: 0, isLoading: true });
|
setProgress({ value: 0, isLoading: true });
|
||||||
|
|
||||||
intervalRef.current = setInterval(() => {
|
intervalRef.current = setInterval(() => {
|
||||||
setProgress((prev) => {
|
setProgress((prev) => {
|
||||||
const buffer = 12;
|
const buffer = 12;
|
||||||
if (prev.value + buffer >= 100) {
|
if (prev.value + buffer >= 100) {
|
||||||
clearInterval(intervalRef.current);
|
clearInterval(intervalRef.current);
|
||||||
setUploadComplete(true);
|
setUploadComplete(true);
|
||||||
setCompletedFile(previewFile);
|
setCompletedFile(previewFile);
|
||||||
return { value: 100, isLoading: false };
|
return { value: 100, isLoading: false };
|
||||||
}
|
}
|
||||||
return { value: prev.value + buffer, isLoading: true };
|
return { value: prev.value + buffer, isLoading: true };
|
||||||
});
|
});
|
||||||
}, 120);
|
}, 120);
|
||||||
},
|
},
|
||||||
[maxSize, accept]
|
[maxSize, accept]
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (uploadComplete && completedFile) {
|
|
||||||
onChange?.(completedFile);
|
|
||||||
setUploadComplete(false);
|
|
||||||
setCompletedFile(null);
|
|
||||||
}
|
|
||||||
}, [uploadComplete, completedFile, onChange]);
|
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (uploadComplete && completedFile) {
|
||||||
|
onChange?.(completedFile);
|
||||||
|
setUploadComplete(false);
|
||||||
|
setCompletedFile(null);
|
||||||
|
}
|
||||||
|
}, [uploadComplete, completedFile, onChange]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{src ? (
|
{src ? (
|
||||||
<Stack direction="row" justifyContent="center">
|
<Stack
|
||||||
<Image
|
direction="row"
|
||||||
alt="Uploaded preview"
|
justifyContent="center"
|
||||||
src={src}
|
>
|
||||||
width="250px"
|
<Image
|
||||||
height="250px"
|
alt="Uploaded preview"
|
||||||
sx={{ ...roundStyle }}
|
src={src}
|
||||||
/>
|
width="250px"
|
||||||
</Stack>
|
height="250px"
|
||||||
) : (
|
sx={{ ...roundStyle }}
|
||||||
<>
|
/>
|
||||||
<Box
|
</Stack>
|
||||||
className="image-field-wrapper"
|
) : (
|
||||||
mt={theme.spacing(8)}
|
<>
|
||||||
onDragEnter={() => setIsDragging(true)}
|
<Box
|
||||||
onDragLeave={() => setIsDragging(false)}
|
className="image-field-wrapper"
|
||||||
onDrop={() => setIsDragging(false)}
|
mt={theme.spacing(8)}
|
||||||
sx={{
|
onDragEnter={() => setIsDragging(true)}
|
||||||
position: "relative",
|
onDragLeave={() => setIsDragging(false)}
|
||||||
height: "fit-content",
|
onDrop={() => setIsDragging(false)}
|
||||||
border: "dashed",
|
sx={{
|
||||||
borderRadius: theme.shape.borderRadius,
|
position: "relative",
|
||||||
borderColor: isDragging
|
height: "fit-content",
|
||||||
? theme.palette.primary.main
|
border: "dashed",
|
||||||
: theme.palette.primary.lowContrast,
|
borderRadius: theme.shape.borderRadius,
|
||||||
backgroundColor: isDragging
|
borderColor: isDragging
|
||||||
? "hsl(215, 87%, 51%, 0.05)"
|
? theme.palette.primary.main
|
||||||
: "transparent",
|
: theme.palette.primary.lowContrast,
|
||||||
borderWidth: "2px",
|
backgroundColor: isDragging ? "hsl(215, 87%, 51%, 0.05)" : "transparent",
|
||||||
transition: "0.2s",
|
borderWidth: "2px",
|
||||||
"&:hover": {
|
transition: "0.2s",
|
||||||
borderColor: theme.palette.primary.main,
|
"&:hover": {
|
||||||
backgroundColor: "hsl(215, 87%, 51%, 0.05)",
|
borderColor: theme.palette.primary.main,
|
||||||
},
|
backgroundColor: "hsl(215, 87%, 51%, 0.05)",
|
||||||
}}
|
},
|
||||||
>
|
}}
|
||||||
<TextField
|
>
|
||||||
type="file"
|
<TextField
|
||||||
onChange={(e) => handleImageChange(e?.target?.files?.[0])}
|
type="file"
|
||||||
sx={{
|
onChange={(e) => handleImageChange(e?.target?.files?.[0])}
|
||||||
width: "100%",
|
sx={{
|
||||||
"& .MuiInputBase-input[type='file']": {
|
width: "100%",
|
||||||
opacity: 0,
|
"& .MuiInputBase-input[type='file']": {
|
||||||
cursor: "pointer",
|
opacity: 0,
|
||||||
maxWidth: "500px",
|
cursor: "pointer",
|
||||||
minHeight: "175px",
|
maxWidth: "500px",
|
||||||
zIndex: 1,
|
minHeight: "175px",
|
||||||
},
|
zIndex: 1,
|
||||||
"& fieldset": {
|
},
|
||||||
padding: 0,
|
"& fieldset": {
|
||||||
border: "none",
|
padding: 0,
|
||||||
},
|
border: "none",
|
||||||
}}
|
},
|
||||||
/>
|
}}
|
||||||
<Stack
|
/>
|
||||||
alignItems="center"
|
<Stack
|
||||||
gap="4px"
|
alignItems="center"
|
||||||
sx={{
|
gap="4px"
|
||||||
position: "absolute",
|
sx={{
|
||||||
top: "50%",
|
position: "absolute",
|
||||||
left: "50%",
|
top: "50%",
|
||||||
transform: "translate(-50%, -50%)",
|
left: "50%",
|
||||||
zIndex: 0,
|
transform: "translate(-50%, -50%)",
|
||||||
width: "100%",
|
zIndex: 0,
|
||||||
}}
|
width: "100%",
|
||||||
>
|
}}
|
||||||
<IconButton
|
>
|
||||||
sx={{
|
<IconButton
|
||||||
pointerEvents: "none",
|
sx={{
|
||||||
borderRadius: theme.shape.borderRadius,
|
pointerEvents: "none",
|
||||||
border: `solid ${theme.shape.borderThick}px ${theme.palette.primary.lowContrast}`,
|
borderRadius: theme.shape.borderRadius,
|
||||||
boxShadow: theme.shape.boxShadow,
|
border: `solid ${theme.shape.borderThick}px ${theme.palette.primary.lowContrast}`,
|
||||||
}}
|
boxShadow: theme.shape.boxShadow,
|
||||||
>
|
}}
|
||||||
<CloudUploadIcon />
|
>
|
||||||
</IconButton>
|
<CloudUploadIcon />
|
||||||
<Typography
|
</IconButton>
|
||||||
component="h2"
|
<Typography
|
||||||
color={theme.palette.primary.contrastTextTertiary}
|
component="h2"
|
||||||
>
|
color={theme.palette.primary.contrastTextTertiary}
|
||||||
<Typography
|
>
|
||||||
component="span"
|
<Typography
|
||||||
fontSize="inherit"
|
component="span"
|
||||||
color="info"
|
fontSize="inherit"
|
||||||
fontWeight={500}
|
color="info"
|
||||||
>
|
fontWeight={500}
|
||||||
{t('ClickUpload')}
|
>
|
||||||
</Typography>{" "}
|
{t("ClickUpload")}
|
||||||
or {t('DragandDrop')}
|
</Typography>{" "}
|
||||||
</Typography>
|
or {t("DragandDrop")}
|
||||||
<Typography
|
</Typography>
|
||||||
component="p"
|
<Typography
|
||||||
color={theme.palette.primary.contrastTextTertiary}
|
component="p"
|
||||||
sx={{ opacity: 0.6 }}
|
color={theme.palette.primary.contrastTextTertiary}
|
||||||
>({t('MaxSize')}: {Math.round(maxSize / 1024 / 1024)}MB)
|
sx={{ opacity: 0.6 }}
|
||||||
</Typography>
|
>
|
||||||
</Stack>
|
({t("MaxSize")}: {Math.round(maxSize / 1024 / 1024)}MB)
|
||||||
</Box>
|
</Typography>
|
||||||
{(localError || progress.isLoading || progress.value !== 0) && (
|
</Stack>
|
||||||
<ProgressUpload
|
</Box>
|
||||||
icon={<ImageIcon />}
|
{(localError || progress.isLoading || progress.value !== 0) && (
|
||||||
label={file?.name || "Upload failed"}
|
<ProgressUpload
|
||||||
size={file?.size}
|
icon={<ImageIcon />}
|
||||||
progress={progress.value}
|
label={file?.name || "Upload failed"}
|
||||||
onClick={() => {
|
size={file?.size}
|
||||||
clearInterval(intervalRef.current);
|
progress={progress.value}
|
||||||
setFile(null);
|
onClick={() => {
|
||||||
setProgress({ value: 0, isLoading: false });
|
clearInterval(intervalRef.current);
|
||||||
setLocalError(null);
|
setFile(null);
|
||||||
onChange(undefined);
|
setProgress({ value: 0, isLoading: false });
|
||||||
}}
|
setLocalError(null);
|
||||||
error={localError || error}
|
onChange(undefined);
|
||||||
/>
|
}}
|
||||||
)}
|
error={localError || error}
|
||||||
<Typography
|
/>
|
||||||
component="p"
|
)}
|
||||||
color={theme.palette.primary.contrastTextTertiary}
|
<Typography
|
||||||
sx={{ opacity: 0.6 }}
|
component="p"
|
||||||
>
|
color={theme.palette.primary.contrastTextTertiary}
|
||||||
{t('SupportedFormats')}: {accept.join(", ").toUpperCase()}
|
sx={{ opacity: 0.6 }}
|
||||||
</Typography>
|
>
|
||||||
</>
|
{t("SupportedFormats")}: {accept.join(", ").toUpperCase()}
|
||||||
)}
|
</Typography>
|
||||||
</>
|
</>
|
||||||
);
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
ImageUpload.propTypes = {
|
ImageUpload.propTypes = {
|
||||||
|
|||||||
@@ -155,6 +155,7 @@ const Search = ({
|
|||||||
renderOption={(props, option) => {
|
renderOption={(props, option) => {
|
||||||
const { key, ...optionProps } = props;
|
const { key, ...optionProps } = props;
|
||||||
const hasSecondaryLabel = secondaryLabel && option[secondaryLabel] !== undefined;
|
const hasSecondaryLabel = secondaryLabel && option[secondaryLabel] !== undefined;
|
||||||
|
const port = option["port"];
|
||||||
return (
|
return (
|
||||||
<ListItem
|
<ListItem
|
||||||
key={key}
|
key={key}
|
||||||
@@ -169,7 +170,9 @@ const Search = ({
|
|||||||
}
|
}
|
||||||
>
|
>
|
||||||
{option[filteredBy] +
|
{option[filteredBy] +
|
||||||
(hasSecondaryLabel ? ` (${option[secondaryLabel]})` : "")}
|
(hasSecondaryLabel
|
||||||
|
? ` (${option[secondaryLabel]}${port ? `: ${port}` : ""})`
|
||||||
|
: "")}
|
||||||
</ListItem>
|
</ListItem>
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -158,7 +158,15 @@ const StatusLabel = ({ status, text, customStyles }) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
StatusLabel.propTypes = {
|
StatusLabel.propTypes = {
|
||||||
status: PropTypes.oneOf(["up", "down", "paused", "pending", "cannot resolve", "published", "unpublished"]),
|
status: PropTypes.oneOf([
|
||||||
|
"up",
|
||||||
|
"down",
|
||||||
|
"paused",
|
||||||
|
"pending",
|
||||||
|
"cannot resolve",
|
||||||
|
"published",
|
||||||
|
"unpublished",
|
||||||
|
]),
|
||||||
text: PropTypes.string,
|
text: PropTypes.string,
|
||||||
customStyles: PropTypes.object,
|
customStyles: PropTypes.object,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -21,107 +21,44 @@ const LanguageSelector = () => {
|
|||||||
value={language}
|
value={language}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
size="small"
|
size="small"
|
||||||
sx={{
|
sx={{ minWidth: 80 }}
|
||||||
height: 28,
|
|
||||||
width: 64,
|
|
||||||
backgroundColor: theme.palette.primary.main,
|
|
||||||
color: theme.palette.primary.contrastText,
|
|
||||||
borderRadius: theme.shape.borderRadius,
|
|
||||||
fontSize: 10,
|
|
||||||
"& .MuiOutlinedInput-notchedOutline": {
|
|
||||||
borderColor: theme.palette.primary.lowContrast,
|
|
||||||
borderRadius: theme.shape.borderRadius,
|
|
||||||
},
|
|
||||||
"&:hover .MuiOutlinedInput-notchedOutline": {
|
|
||||||
borderColor: theme.palette.primary.lowContrast,
|
|
||||||
},
|
|
||||||
"&.Mui-focused .MuiOutlinedInput-notchedOutline": {
|
|
||||||
borderColor: theme.palette.primary.lowContrast,
|
|
||||||
},
|
|
||||||
"& .MuiSvgIcon-root": {
|
|
||||||
color: theme.palette.primary.contrastText,
|
|
||||||
width: 16,
|
|
||||||
height: 16,
|
|
||||||
right: 4,
|
|
||||||
top: "calc(50% - 8px)",
|
|
||||||
},
|
|
||||||
"& .MuiSelect-select": {
|
|
||||||
padding: "2px 20px 2px 8px",
|
|
||||||
display: "flex",
|
|
||||||
alignItems: "center",
|
|
||||||
fontSize: 10,
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
MenuProps={{
|
|
||||||
PaperProps: {
|
|
||||||
sx: {
|
|
||||||
backgroundColor: theme.palette.primary.main,
|
|
||||||
borderRadius: theme.shape.borderRadius,
|
|
||||||
marginTop: 1,
|
|
||||||
width: 64,
|
|
||||||
"& .MuiMenuItem-root": {
|
|
||||||
padding: "2px 8px",
|
|
||||||
minHeight: 28,
|
|
||||||
fontSize: 10,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
anchorOrigin: {
|
|
||||||
vertical: "bottom",
|
|
||||||
horizontal: "left",
|
|
||||||
},
|
|
||||||
transformOrigin: {
|
|
||||||
vertical: "top",
|
|
||||||
horizontal: "left",
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
{languages.map((lang) => {
|
{languages.map((lang) => {
|
||||||
const flag = lang ? `fi fi-${lang}` : null;
|
let parsedLang = lang === "en" ? "gb" : lang;
|
||||||
|
|
||||||
|
// Fix for Czech
|
||||||
|
if (parsedLang === "cs") {
|
||||||
|
parsedLang = "cz";
|
||||||
|
}
|
||||||
|
if (parsedLang.includes("-")) {
|
||||||
|
parsedLang = parsedLang.split("-")[1].toLowerCase();
|
||||||
|
console.log("parsedLang", parsedLang);
|
||||||
|
}
|
||||||
|
|
||||||
|
const flag = parsedLang ? `fi fi-${parsedLang}` : null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MenuItem
|
<MenuItem
|
||||||
key={lang}
|
key={lang}
|
||||||
value={lang}
|
value={lang}
|
||||||
sx={{
|
|
||||||
color: theme.palette.primary.contrastText,
|
|
||||||
"&:hover": {
|
|
||||||
backgroundColor: theme.palette.primary.lowContrast,
|
|
||||||
},
|
|
||||||
"&.Mui-selected": {
|
|
||||||
backgroundColor: theme.palette.primary.lowContrast,
|
|
||||||
"&:hover": {
|
|
||||||
backgroundColor: theme.palette.primary.lowContrast,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<Stack
|
<Stack
|
||||||
direction="row"
|
direction="row"
|
||||||
spacing={2}
|
spacing={theme.spacing(2)}
|
||||||
alignItems="center"
|
alignItems="center"
|
||||||
ml={0.5}
|
|
||||||
>
|
>
|
||||||
<Box
|
<Box
|
||||||
component="span"
|
component="span"
|
||||||
sx={{
|
sx={{
|
||||||
width: 16,
|
|
||||||
height: 12,
|
|
||||||
display: "flex",
|
display: "flex",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
"& img": {
|
|
||||||
width: "100%",
|
|
||||||
height: "100%",
|
|
||||||
objectFit: "cover",
|
|
||||||
borderRadius: 0.5,
|
|
||||||
},
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{flag && <span className={flag} />}
|
{flag && <span className={flag} />}
|
||||||
</Box>
|
</Box>
|
||||||
<Box
|
<Box
|
||||||
component="span"
|
component="span"
|
||||||
sx={{ textTransform: "uppercase", fontSize: 10 }}
|
sx={{ textTransform: "uppercase" }}
|
||||||
>
|
>
|
||||||
{lang}
|
{lang}
|
||||||
</Box>
|
</Box>
|
||||||
|
|||||||
@@ -4,13 +4,7 @@ import PropTypes from "prop-types";
|
|||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { useTheme } from "@emotion/react";
|
import { useTheme } from "@emotion/react";
|
||||||
|
|
||||||
const CreateMonitorHeader = ({
|
const CreateMonitorHeader = ({ isAdmin, label, isLoading = true, path, bulkPath }) => {
|
||||||
isAdmin,
|
|
||||||
label,
|
|
||||||
isLoading = true,
|
|
||||||
path,
|
|
||||||
bulkPath,
|
|
||||||
}) => {
|
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
|
|||||||
@@ -0,0 +1,120 @@
|
|||||||
|
import Stack from "@mui/material/Stack";
|
||||||
|
import Status from "./status";
|
||||||
|
import Skeleton from "./skeleton";
|
||||||
|
import Button from "@mui/material/Button";
|
||||||
|
import SettingsOutlinedIcon from "@mui/icons-material/SettingsOutlined";
|
||||||
|
import PauseOutlinedIcon from "@mui/icons-material/PauseOutlined";
|
||||||
|
import PlayArrowOutlinedIcon from "@mui/icons-material/PlayArrowOutlined";
|
||||||
|
import EmailIcon from "@mui/icons-material/Email";
|
||||||
|
|
||||||
|
// Utils
|
||||||
|
import PropTypes from "prop-types";
|
||||||
|
import { useNavigate } from "react-router-dom";
|
||||||
|
import { useTheme } from "@mui/material/styles";
|
||||||
|
import { usePauseMonitor } from "../../Hooks/useMonitorControls";
|
||||||
|
import { useSendTestEmail } from "../../Hooks/useSendTestEmail";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* MonitorDetailsControlHeader component displays the control header for monitor details.
|
||||||
|
* It includes status display, pause/resume button, and a configure button for admins.
|
||||||
|
*
|
||||||
|
* @component
|
||||||
|
* @param {Object} props - Component props
|
||||||
|
* @param {string} props.path - The base path for navigation
|
||||||
|
* @param {boolean} [props.isLoading=false] - Flag indicating if the data is loading
|
||||||
|
* @param {boolean} [props.isAdmin=false] - Flag indicating if the user is an admin
|
||||||
|
* @param {Object} props.monitor - The monitor object containing details
|
||||||
|
* @param {Function} props.triggerUpdate - Function to trigger an update
|
||||||
|
* @returns {JSX.Element} The rendered component
|
||||||
|
*/
|
||||||
|
const MonitorDetailsControlHeader = ({
|
||||||
|
path,
|
||||||
|
isLoading = false,
|
||||||
|
isAdmin = false,
|
||||||
|
monitor,
|
||||||
|
triggerUpdate,
|
||||||
|
}) => {
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const theme = useTheme();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const [pauseMonitor, isPausing, error] = usePauseMonitor({
|
||||||
|
monitorId: monitor?._id,
|
||||||
|
triggerUpdate,
|
||||||
|
});
|
||||||
|
|
||||||
|
const [isSending, emailError, sendTestEmail] = useSendTestEmail();
|
||||||
|
|
||||||
|
if (isLoading) {
|
||||||
|
return <Skeleton />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Stack
|
||||||
|
direction="row"
|
||||||
|
justifyContent="space-between"
|
||||||
|
>
|
||||||
|
<Status monitor={monitor} />
|
||||||
|
|
||||||
|
<Stack
|
||||||
|
direction="row"
|
||||||
|
gap={theme.spacing(2)}
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
color="secondary"
|
||||||
|
loading={isSending}
|
||||||
|
startIcon={<EmailIcon />}
|
||||||
|
onClick={() => {
|
||||||
|
sendTestEmail();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t("sendTestEmail")}
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
color="secondary"
|
||||||
|
onClick={(e) => {
|
||||||
|
navigate(`/incidents/${monitor?._id}`);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t("menu.incidents")}
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
color="secondary"
|
||||||
|
loading={isPausing}
|
||||||
|
startIcon={
|
||||||
|
monitor?.isActive ? <PauseOutlinedIcon /> : <PlayArrowOutlinedIcon />
|
||||||
|
}
|
||||||
|
onClick={() => {
|
||||||
|
pauseMonitor();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{monitor?.isActive ? "Pause" : "Resume"}
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
{isAdmin && (
|
||||||
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
color="secondary"
|
||||||
|
startIcon={<SettingsOutlinedIcon />}
|
||||||
|
onClick={() => navigate(`/${path}/configure/${monitor._id}`)}
|
||||||
|
>
|
||||||
|
Configure
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</Stack>
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
MonitorDetailsControlHeader.propTypes = {
|
||||||
|
path: PropTypes.string,
|
||||||
|
isLoading: PropTypes.bool,
|
||||||
|
isAdmin: PropTypes.bool,
|
||||||
|
monitor: PropTypes.object,
|
||||||
|
triggerUpdate: PropTypes.func,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default MonitorDetailsControlHeader;
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
import { Stack, Skeleton } from "@mui/material";
|
||||||
|
|
||||||
|
const SkeletonLayout = () => {
|
||||||
|
return (
|
||||||
|
<Stack
|
||||||
|
direction="row"
|
||||||
|
justifyContent="space-between"
|
||||||
|
>
|
||||||
|
<Skeleton
|
||||||
|
height={40}
|
||||||
|
variant="rounded"
|
||||||
|
width="15%"
|
||||||
|
/>
|
||||||
|
<Skeleton
|
||||||
|
height={40}
|
||||||
|
variant="rounded"
|
||||||
|
width="15%"
|
||||||
|
/>
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SkeletonLayout;
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
// Components
|
||||||
|
import Stack from "@mui/material/Stack";
|
||||||
|
import PulseDot from "../../Components/Animated/PulseDot";
|
||||||
|
import Typography from "@mui/material/Typography";
|
||||||
|
import Dot from "../../Components/Dot";
|
||||||
|
// Utils
|
||||||
|
import { formatDurationRounded } from "../../Utils/timeUtils";
|
||||||
|
import PropTypes from "prop-types";
|
||||||
|
import { useTheme } from "@emotion/react";
|
||||||
|
import useUtils from "../../Pages/Uptime/Monitors/Hooks/useUtils";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Status component displays the status information of a monitor.
|
||||||
|
* It includes the monitor's name, URL, and check interval.
|
||||||
|
*
|
||||||
|
* @component
|
||||||
|
* @param {Object} props - Component props
|
||||||
|
* @param {Object} props.monitor - The monitor object containing details
|
||||||
|
* @param {string} props.monitor.name - The name of the monitor
|
||||||
|
* @param {string} props.monitor.url - The URL of the monitor
|
||||||
|
* @param {number} props.monitor.interval - The interval at which the monitor checks
|
||||||
|
* @returns {JSX.Element} The rendered component
|
||||||
|
*/
|
||||||
|
const Status = ({ monitor }) => {
|
||||||
|
const theme = useTheme();
|
||||||
|
const { statusColor, determineState } = useUtils();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Stack>
|
||||||
|
<Typography variant="h1">{monitor?.name}</Typography>
|
||||||
|
<Stack
|
||||||
|
direction="row"
|
||||||
|
alignItems={"center"}
|
||||||
|
gap={theme.spacing(4)}
|
||||||
|
>
|
||||||
|
<PulseDot color={statusColor[determineState(monitor)]} />
|
||||||
|
<Typography
|
||||||
|
variant="h2"
|
||||||
|
style={{ fontFamily: "monospace", fontWeight: "bolder" }}
|
||||||
|
>
|
||||||
|
{monitor?.url?.replace(/^https?:\/\//, "") || "..."}
|
||||||
|
</Typography>
|
||||||
|
<Dot />
|
||||||
|
<Typography>
|
||||||
|
Checking every {formatDurationRounded(monitor?.interval)}.
|
||||||
|
</Typography>
|
||||||
|
</Stack>
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
Status.propTypes = {
|
||||||
|
monitor: PropTypes.object,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Status;
|
||||||
@@ -28,8 +28,9 @@ const MonitorStatusHeader = ({ path, isLoading = false, isAdmin, monitor }) => {
|
|||||||
gap={theme.spacing(4)}
|
gap={theme.spacing(4)}
|
||||||
>
|
>
|
||||||
<PulseDot color={statusColor[determineState(monitor)]} />
|
<PulseDot color={statusColor[determineState(monitor)]} />
|
||||||
<Typography variant="h2"
|
<Typography
|
||||||
style={{fontFamily : "monospace" , fontWeight : 'bolder'}}
|
variant="h2"
|
||||||
|
style={{ fontFamily: "monospace", fontWeight: "bolder" }}
|
||||||
>
|
>
|
||||||
{monitor?.url?.replace(/^https?:\/\//, "") || "..."}
|
{monitor?.url?.replace(/^https?:\/\//, "") || "..."}
|
||||||
</Typography>
|
</Typography>
|
||||||
|
|||||||
@@ -1,109 +1,115 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import {
|
import { Typography, Box, Button, CircularProgress } from "@mui/material";
|
||||||
Typography,
|
|
||||||
Box,
|
|
||||||
Button,
|
|
||||||
CircularProgress
|
|
||||||
} from "@mui/material";
|
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { useTheme } from "@emotion/react";
|
import { useTheme } from "@emotion/react";
|
||||||
import TextInput from "../../../Components/Inputs/TextInput";
|
import TextInput from "../../../Components/Inputs/TextInput";
|
||||||
import Checkbox from "../../../Components/Inputs/Checkbox";
|
import Checkbox from "../../../Components/Inputs/Checkbox";
|
||||||
|
|
||||||
const TabComponent = ({
|
const TabComponent = ({
|
||||||
type,
|
type,
|
||||||
integrations,
|
integrations,
|
||||||
handleIntegrationChange,
|
handleIntegrationChange,
|
||||||
handleInputChange,
|
handleInputChange,
|
||||||
handleTestNotification,
|
handleTestNotification,
|
||||||
isLoading
|
isLoading,
|
||||||
}) => {
|
}) => {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
// Helper to get the field state key (e.g., slackWebhook, telegramToken)
|
// Helper to get the field state key (e.g., slackWebhook, telegramToken)
|
||||||
const getFieldKey = (typeId, fieldId) => {
|
const getFieldKey = (typeId, fieldId) => {
|
||||||
return `${typeId}${fieldId.charAt(0).toUpperCase() + fieldId.slice(1)}`;
|
return `${typeId}${fieldId.charAt(0).toUpperCase() + fieldId.slice(1)}`;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Check if all fields have values to enable test button
|
// Check if all fields have values to enable test button
|
||||||
const areAllFieldsFilled = () => {
|
const areAllFieldsFilled = () => {
|
||||||
return type.fields.every(field => {
|
return type.fields.every((field) => {
|
||||||
const fieldKey = getFieldKey(type.id, field.id);
|
const fieldKey = getFieldKey(type.id, field.id);
|
||||||
return integrations[fieldKey];
|
return integrations[fieldKey];
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Typography variant="subtitle1" component="h4" sx={{
|
<Typography
|
||||||
fontWeight: 'bold',
|
variant="subtitle1"
|
||||||
color: theme.palette.primary.contrastTextSecondary
|
component="h4"
|
||||||
}}>
|
sx={{
|
||||||
{type.label}
|
fontWeight: "bold",
|
||||||
</Typography>
|
color: theme.palette.primary.contrastTextSecondary,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{type.label}
|
||||||
|
</Typography>
|
||||||
|
|
||||||
<Typography sx={{
|
<Typography
|
||||||
mt: theme.spacing(0.5),
|
sx={{
|
||||||
mb: theme.spacing(1.5),
|
mt: theme.spacing(0.5),
|
||||||
color: theme.palette.primary.contrastTextTertiary
|
mb: theme.spacing(1.5),
|
||||||
}}>
|
color: theme.palette.primary.contrastTextTertiary,
|
||||||
{type.description}
|
}}
|
||||||
</Typography>
|
>
|
||||||
|
{type.description}
|
||||||
|
</Typography>
|
||||||
|
|
||||||
<Box sx={{ pl: theme.spacing(1.5) }}>
|
<Box sx={{ pl: theme.spacing(1.5) }}>
|
||||||
<Checkbox
|
<Checkbox
|
||||||
id={`enable-${type.id}`}
|
id={`enable-${type.id}`}
|
||||||
label={t('notifications.enableNotifications', { platform: type.label })}
|
label={t("notifications.enableNotifications", { platform: type.label })}
|
||||||
isChecked={integrations[type.id]}
|
isChecked={integrations[type.id]}
|
||||||
onChange={(e) => handleIntegrationChange(type.id, e.target.checked)}
|
onChange={(e) => handleIntegrationChange(type.id, e.target.checked)}
|
||||||
disabled={isLoading}
|
disabled={isLoading}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
{type.fields.map(field => {
|
{type.fields.map((field) => {
|
||||||
const fieldKey = getFieldKey(type.id, field.id);
|
const fieldKey = getFieldKey(type.id, field.id);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box key={field.id} sx={{ mt: theme.spacing(1) }}>
|
<Box
|
||||||
<Typography sx={{
|
key={field.id}
|
||||||
mb: theme.spacing(2),
|
sx={{ mt: theme.spacing(1) }}
|
||||||
fontWeight: 'bold',
|
>
|
||||||
color: theme.palette.primary.contrastTextSecondary
|
<Typography
|
||||||
}}>
|
sx={{
|
||||||
{field.label}
|
mb: theme.spacing(2),
|
||||||
</Typography>
|
fontWeight: "bold",
|
||||||
|
color: theme.palette.primary.contrastTextSecondary,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{field.label}
|
||||||
|
</Typography>
|
||||||
|
|
||||||
<TextInput
|
<TextInput
|
||||||
id={`${type.id}-${field.id}`}
|
id={`${type.id}-${field.id}`}
|
||||||
type={field.type}
|
type={field.type}
|
||||||
placeholder={field.placeholder}
|
placeholder={field.placeholder}
|
||||||
value={integrations[fieldKey]}
|
value={integrations[fieldKey]}
|
||||||
onChange={(e) => handleInputChange(fieldKey, e.target.value)}
|
onChange={(e) => handleInputChange(fieldKey, e.target.value)}
|
||||||
disabled={!integrations[type.id] || isLoading}
|
disabled={!integrations[type.id] || isLoading}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
|
||||||
<Box sx={{ mt: theme.spacing(1) }}>
|
<Box sx={{ mt: theme.spacing(1) }}>
|
||||||
<Button
|
<Button
|
||||||
variant="text"
|
variant="text"
|
||||||
color="info"
|
color="info"
|
||||||
onClick={() => handleTestNotification(type.id)}
|
onClick={() => handleTestNotification(type.id)}
|
||||||
disabled={!integrations[type.id] || !areAllFieldsFilled() || isLoading}
|
disabled={!integrations[type.id] || !areAllFieldsFilled() || isLoading}
|
||||||
>
|
>
|
||||||
{isLoading ? (
|
{isLoading ? (
|
||||||
<CircularProgress
|
<CircularProgress
|
||||||
size={theme.spacing(8)}
|
size={theme.spacing(8)}
|
||||||
sx={{ mr: theme.spacing(1), color: theme.palette.accent.main}}
|
sx={{ mr: theme.spacing(1), color: theme.palette.accent.main }}
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
{t('notifications.testNotification')}
|
{t("notifications.testNotification")}
|
||||||
</Button>
|
</Button>
|
||||||
</Box>
|
</Box>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default TabComponent;
|
export default TabComponent;
|
||||||
@@ -15,32 +15,27 @@ import { useTheme } from "@emotion/react";
|
|||||||
* @returns {React.ReactElement|null} The rendered tab panel or null if not selected.
|
* @returns {React.ReactElement|null} The rendered tab panel or null if not selected.
|
||||||
*/
|
*/
|
||||||
function TabPanel({ children, value, index, ...other }) {
|
function TabPanel({ children, value, index, ...other }) {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
role="tabpanel"
|
role="tabpanel"
|
||||||
hidden={value !== index}
|
hidden={value !== index}
|
||||||
id={`notification-tabpanel-${index}`}
|
id={`notification-tabpanel-${index}`}
|
||||||
aria-labelledby={`notification-tab-${index}`}
|
aria-labelledby={`notification-tab-${index}`}
|
||||||
{...other}
|
{...other}
|
||||||
>
|
>
|
||||||
{value === index && (
|
{value === index && <Box sx={{ pt: theme.spacing(3) }}>{children}</Box>}
|
||||||
<Box sx={{ pt: theme.spacing(3) }}>
|
</div>
|
||||||
{children}
|
);
|
||||||
</Box>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TabPanel.propTypes = {
|
TabPanel.propTypes = {
|
||||||
|
children: PropTypes.node,
|
||||||
|
|
||||||
children: PropTypes.node,
|
index: PropTypes.number.isRequired,
|
||||||
|
|
||||||
index: PropTypes.number.isRequired,
|
value: PropTypes.number.isRequired,
|
||||||
|
|
||||||
value: PropTypes.number.isRequired
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default TabPanel;
|
export default TabPanel;
|
||||||
@@ -1,128 +1,129 @@
|
|||||||
import { useState } from 'react';
|
import { useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { networkService } from '../../../Utils/NetworkService';
|
import { networkService } from "../../../Utils/NetworkService";
|
||||||
import { createToast } from '../../../Utils/toastUtils';
|
import { createToast } from "../../../Utils/toastUtils";
|
||||||
|
|
||||||
// Define constants for notification types to avoid magic values
|
// Define constants for notification types to avoid magic values
|
||||||
const NOTIFICATION_TYPES = {
|
const NOTIFICATION_TYPES = {
|
||||||
SLACK: 'slack',
|
SLACK: "slack",
|
||||||
DISCORD: 'discord',
|
DISCORD: "discord",
|
||||||
TELEGRAM: 'telegram',
|
TELEGRAM: "telegram",
|
||||||
WEBHOOK: 'webhook'
|
WEBHOOK: "webhook",
|
||||||
};
|
};
|
||||||
|
|
||||||
// Define constants for field IDs
|
// Define constants for field IDs
|
||||||
const FIELD_IDS = {
|
const FIELD_IDS = {
|
||||||
WEBHOOK: 'webhook',
|
WEBHOOK: "webhook",
|
||||||
TOKEN: 'token',
|
TOKEN: "token",
|
||||||
CHAT_ID: 'chatId',
|
CHAT_ID: "chatId",
|
||||||
URL: 'url'
|
URL: "url",
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Custom hook for notification-related operations
|
* Custom hook for notification-related operations
|
||||||
*/
|
*/
|
||||||
const useNotifications = () => {
|
const useNotifications = () => {
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [error, setError] = useState(undefined);
|
const [error, setError] = useState(undefined);
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Send a test notification
|
* Send a test notification
|
||||||
* @param {string} type - The notification type (slack, discord, telegram, webhook)
|
* @param {string} type - The notification type (slack, discord, telegram, webhook)
|
||||||
* @param {object} config - Configuration object with necessary params
|
* @param {object} config - Configuration object with necessary params
|
||||||
*/
|
*/
|
||||||
const sendTestNotification = async (type, config) => {
|
const sendTestNotification = async (type, config) => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
setError(undefined);
|
setError(undefined);
|
||||||
|
|
||||||
// Validation based on notification type
|
// Validation based on notification type
|
||||||
let payload = { platform: type };
|
let payload = { platform: type };
|
||||||
let isValid = true;
|
let isValid = true;
|
||||||
let errorMessage = '';
|
let errorMessage = "";
|
||||||
|
|
||||||
switch(type) {
|
switch (type) {
|
||||||
case NOTIFICATION_TYPES.SLACK:
|
case NOTIFICATION_TYPES.SLACK:
|
||||||
payload.webhookUrl = config.webhook;
|
payload.webhookUrl = config.webhook;
|
||||||
if (typeof payload.webhookUrl === 'undefined' || payload.webhookUrl === '') {
|
if (typeof payload.webhookUrl === "undefined" || payload.webhookUrl === "") {
|
||||||
isValid = false;
|
isValid = false;
|
||||||
errorMessage = t('notifications.slack.webhookRequired');
|
errorMessage = t("notifications.slack.webhookRequired");
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case NOTIFICATION_TYPES.DISCORD:
|
case NOTIFICATION_TYPES.DISCORD:
|
||||||
payload.webhookUrl = config.webhook;
|
payload.webhookUrl = config.webhook;
|
||||||
if (typeof payload.webhookUrl === 'undefined' || payload.webhookUrl === '') {
|
if (typeof payload.webhookUrl === "undefined" || payload.webhookUrl === "") {
|
||||||
isValid = false;
|
isValid = false;
|
||||||
errorMessage = t('notifications.discord.webhookRequired');
|
errorMessage = t("notifications.discord.webhookRequired");
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case NOTIFICATION_TYPES.TELEGRAM:
|
case NOTIFICATION_TYPES.TELEGRAM:
|
||||||
payload.botToken = config.token;
|
payload.botToken = config.token;
|
||||||
payload.chatId = config.chatId;
|
payload.chatId = config.chatId;
|
||||||
if (typeof payload.botToken === 'undefined' || payload.botToken === '' ||
|
if (
|
||||||
typeof payload.chatId === 'undefined' || payload.chatId === '') {
|
typeof payload.botToken === "undefined" ||
|
||||||
isValid = false;
|
payload.botToken === "" ||
|
||||||
errorMessage = t('notifications.telegram.fieldsRequired');
|
typeof payload.chatId === "undefined" ||
|
||||||
}
|
payload.chatId === ""
|
||||||
break;
|
) {
|
||||||
|
isValid = false;
|
||||||
|
errorMessage = t("notifications.telegram.fieldsRequired");
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
case NOTIFICATION_TYPES.WEBHOOK:
|
case NOTIFICATION_TYPES.WEBHOOK:
|
||||||
payload.webhookUrl = config.url;
|
payload.webhookUrl = config.url;
|
||||||
payload.platform = NOTIFICATION_TYPES.SLACK;
|
payload.platform = NOTIFICATION_TYPES.SLACK;
|
||||||
if (typeof payload.webhookUrl === 'undefined' || payload.webhookUrl === '') {
|
if (typeof payload.webhookUrl === "undefined" || payload.webhookUrl === "") {
|
||||||
isValid = false;
|
isValid = false;
|
||||||
errorMessage = t('notifications.webhook.urlRequired');
|
errorMessage = t("notifications.webhook.urlRequired");
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
isValid = false;
|
isValid = false;
|
||||||
errorMessage = t('notifications.unsupportedType');
|
errorMessage = t("notifications.unsupportedType");
|
||||||
}
|
}
|
||||||
|
|
||||||
// If validation fails, show error and return
|
// If validation fails, show error and return
|
||||||
if (isValid === false) {
|
if (isValid === false) {
|
||||||
createToast({
|
createToast({
|
||||||
body: errorMessage,
|
body: errorMessage,
|
||||||
variant: "error"
|
variant: "error",
|
||||||
});
|
});
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await networkService.testNotification({
|
const response = await networkService.testNotification({
|
||||||
platform: type,
|
platform: type,
|
||||||
payload: payload
|
payload: payload,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (response.data.success === true) {
|
if (response.data.success === true) {
|
||||||
createToast({
|
createToast({
|
||||||
body: t('notifications.testSuccess'),
|
body: t("notifications.testSuccess"),
|
||||||
variant: "info"
|
variant: "info",
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
throw new Error(response.data.msg || t('notifications.testFailed'));
|
throw new Error(response.data.msg || t("notifications.testFailed"));
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const errorMsg = error.response?.data?.msg || error.message || t('notifications.networkError');
|
const errorMsg =
|
||||||
createToast({
|
error.response?.data?.msg || error.message || t("notifications.networkError");
|
||||||
body: `${t('notifications.testFailed')}: ${errorMsg}`,
|
createToast({
|
||||||
variant: "error"
|
body: `${t("notifications.testFailed")}: ${errorMsg}`,
|
||||||
});
|
variant: "error",
|
||||||
setError(errorMsg);
|
});
|
||||||
} finally {
|
setError(errorMsg);
|
||||||
setLoading(false);
|
} finally {
|
||||||
}
|
setLoading(false);
|
||||||
};
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return [
|
return [loading, error, sendTestNotification];
|
||||||
loading,
|
|
||||||
error,
|
|
||||||
sendTestNotification
|
|
||||||
];
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default useNotifications;
|
export default useNotifications;
|
||||||
@@ -93,7 +93,7 @@ const ProgressUpload = ({ icon, label, size, progress = 0, onClick, error }) =>
|
|||||||
<Typography
|
<Typography
|
||||||
component="h2"
|
component="h2"
|
||||||
mb={theme.spacing(1.5)}
|
mb={theme.spacing(1.5)}
|
||||||
sx={{ wordBreak: 'break-all' }}
|
sx={{ wordBreak: "break-all" }}
|
||||||
>
|
>
|
||||||
{error ? error : label}
|
{error ? error : label}
|
||||||
</Typography>
|
</Typography>
|
||||||
|
|||||||
@@ -170,8 +170,8 @@ function Sidebar() {
|
|||||||
<MenuItem
|
<MenuItem
|
||||||
key={item.name}
|
key={item.name}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
closePopup()
|
closePopup();
|
||||||
navigate(item.path)
|
navigate(item.path);
|
||||||
}}
|
}}
|
||||||
sx={{
|
sx={{
|
||||||
gap: theme.spacing(2),
|
gap: theme.spacing(2),
|
||||||
@@ -732,11 +732,15 @@ function Sidebar() {
|
|||||||
{authState.user?.firstName} {authState.user?.lastName}
|
{authState.user?.firstName} {authState.user?.lastName}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography sx={{ textTransform: "capitalize" }}>
|
<Typography sx={{ textTransform: "capitalize" }}>
|
||||||
{authState.user?.role?.includes("superadmin") ? t("roles.superAdmin") :
|
{authState.user?.role?.includes("superadmin")
|
||||||
authState.user?.role?.includes("admin") ? t("roles.admin") :
|
? t("roles.superAdmin")
|
||||||
authState.user?.role?.includes("user") ? t("roles.teamMember") :
|
: authState.user?.role?.includes("admin")
|
||||||
authState.user?.role?.includes("demo") ? t("roles.demoUser") :
|
? t("roles.admin")
|
||||||
authState.user?.role}
|
: authState.user?.role?.includes("user")
|
||||||
|
? t("roles.teamMember")
|
||||||
|
: authState.user?.role?.includes("demo")
|
||||||
|
? t("roles.demoUser")
|
||||||
|
: authState.user?.role}
|
||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
<Stack
|
<Stack
|
||||||
|
|||||||
@@ -1,101 +1,108 @@
|
|||||||
import React from 'react';
|
import React from "react";
|
||||||
import { Typography, IconButton, Stack, Box } from '@mui/material';
|
import { Typography, IconButton, Stack, Box } from "@mui/material";
|
||||||
import CloseIcon from '@mui/icons-material/Close';
|
import CloseIcon from "@mui/icons-material/Close";
|
||||||
import { useTheme } from '@emotion/react';
|
import { useTheme } from "@emotion/react";
|
||||||
import { useSelector, useDispatch } from 'react-redux';
|
import { useSelector, useDispatch } from "react-redux";
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from "react-i18next";
|
||||||
import { setStarPromptOpen } from '../../Features/UI/uiSlice';
|
import { setStarPromptOpen } from "../../Features/UI/uiSlice";
|
||||||
|
|
||||||
const StarPrompt = ({
|
const StarPrompt = ({ repoUrl = "https://github.com/bluewave-labs/checkmate" }) => {
|
||||||
repoUrl = 'https://github.com/bluewave-labs/checkmate'
|
const theme = useTheme();
|
||||||
}) => {
|
const dispatch = useDispatch();
|
||||||
const theme = useTheme();
|
const { t } = useTranslation();
|
||||||
const dispatch = useDispatch();
|
const isOpen = useSelector((state) => state.ui?.starPromptOpen ?? true);
|
||||||
const { t } = useTranslation();
|
const mode = useSelector((state) => state.ui.mode);
|
||||||
const isOpen = useSelector((state) => state.ui?.starPromptOpen ?? true);
|
|
||||||
const mode = useSelector((state) => state.ui.mode);
|
|
||||||
|
|
||||||
const handleClose = () => {
|
const handleClose = () => {
|
||||||
dispatch(setStarPromptOpen(false));
|
dispatch(setStarPromptOpen(false));
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleStarClick = () => {
|
const handleStarClick = () => {
|
||||||
window.open(repoUrl, '_blank');
|
window.open(repoUrl, "_blank");
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!isOpen) return null;
|
if (!isOpen) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack
|
<Stack
|
||||||
direction="column"
|
direction="column"
|
||||||
sx={{
|
sx={{
|
||||||
width: '100%',
|
width: "100%",
|
||||||
padding: `${theme.spacing(6)} ${theme.spacing(6)}`,
|
padding: `${theme.spacing(6)} ${theme.spacing(6)}`,
|
||||||
borderTop: `1px solid ${theme.palette.primary.lowContrast}`,
|
borderTop: `1px solid ${theme.palette.primary.lowContrast}`,
|
||||||
borderBottom: `1px solid ${theme.palette.primary.lowContrast}`,
|
borderBottom: `1px solid ${theme.palette.primary.lowContrast}`,
|
||||||
borderRadius: 0,
|
borderRadius: 0,
|
||||||
gap: theme.spacing(1.5),
|
gap: theme.spacing(1.5),
|
||||||
backgroundColor: theme.palette.primary.main,
|
backgroundColor: theme.palette.primary.main,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Stack direction="row" justifyContent="space-between" alignItems="center" width="100%" pl={theme.spacing(4)}>
|
<Stack
|
||||||
<Typography
|
direction="row"
|
||||||
variant="subtitle2"
|
justifyContent="space-between"
|
||||||
sx={{
|
alignItems="center"
|
||||||
color: mode === 'dark' ? theme.palette.primary.contrastText : theme.palette.text.primary,
|
width="100%"
|
||||||
mt: theme.spacing(3)
|
pl={theme.spacing(4)}
|
||||||
}}
|
>
|
||||||
>
|
<Typography
|
||||||
{t('starPromptTitle')}
|
variant="subtitle2"
|
||||||
</Typography>
|
sx={{
|
||||||
<IconButton
|
color:
|
||||||
onClick={handleClose}
|
mode === "dark"
|
||||||
size="small"
|
? theme.palette.primary.contrastText
|
||||||
sx={{
|
: theme.palette.text.primary,
|
||||||
color: theme.palette.text.primary,
|
mt: theme.spacing(3),
|
||||||
padding: 0,
|
}}
|
||||||
marginTop: theme.spacing(-5),
|
>
|
||||||
'&:hover': {
|
{t("starPromptTitle")}
|
||||||
backgroundColor: 'transparent',
|
</Typography>
|
||||||
opacity: 0.8
|
<IconButton
|
||||||
},
|
onClick={handleClose}
|
||||||
}}
|
size="small"
|
||||||
>
|
sx={{
|
||||||
<CloseIcon sx={{ fontSize: '1.25rem' }} />
|
color: theme.palette.text.primary,
|
||||||
</IconButton>
|
padding: 0,
|
||||||
</Stack>
|
marginTop: theme.spacing(-5),
|
||||||
|
"&:hover": {
|
||||||
|
backgroundColor: "transparent",
|
||||||
|
opacity: 0.8,
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<CloseIcon sx={{ fontSize: "1.25rem" }} />
|
||||||
|
</IconButton>
|
||||||
|
</Stack>
|
||||||
|
|
||||||
<Typography
|
<Typography
|
||||||
variant="body1"
|
variant="body1"
|
||||||
sx={{
|
sx={{
|
||||||
color: theme.palette.text.secondary,
|
color: theme.palette.text.secondary,
|
||||||
fontSize: '0.938rem',
|
fontSize: "0.938rem",
|
||||||
lineHeight: 1.5,
|
lineHeight: 1.5,
|
||||||
mb: 1,
|
mb: 1,
|
||||||
px: theme.spacing(4)
|
px: theme.spacing(4),
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{t('starPromptDescription')}
|
{t("starPromptDescription")}
|
||||||
</Typography>
|
</Typography>
|
||||||
|
|
||||||
<Box
|
<Box
|
||||||
component="img"
|
component="img"
|
||||||
src={`https://img.shields.io/github/stars/bluewave-labs/checkmate?label=checkmate&style=social${mode === 'dark' ? '&color=white' : ''}`}
|
src={`https://img.shields.io/github/stars/bluewave-labs/checkmate?label=checkmate&style=social${mode === "dark" ? "&color=white" : ""}`}
|
||||||
alt="GitHub stars"
|
alt="GitHub stars"
|
||||||
onClick={handleStarClick}
|
onClick={handleStarClick}
|
||||||
sx={{
|
sx={{
|
||||||
cursor: 'pointer',
|
cursor: "pointer",
|
||||||
transform: 'scale(0.65)',
|
transform: "scale(0.65)",
|
||||||
transformOrigin: 'left center',
|
transformOrigin: "left center",
|
||||||
'&:hover': {
|
"&:hover": {
|
||||||
opacity: 0.8
|
opacity: 0.8,
|
||||||
},
|
},
|
||||||
pl: theme.spacing(4),
|
pl: theme.spacing(4),
|
||||||
filter: mode === 'dark' ? 'invert(1)' : 'none'
|
filter: mode === "dark" ? "invert(1)" : "none",
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Stack>
|
</Stack>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default StarPrompt;
|
export default StarPrompt;
|
||||||
@@ -20,7 +20,11 @@ const CustomTabList = ({ value, onChange, children, ...props }) => {
|
|||||||
"& .MuiTabs-root": { height: "fit-content", minHeight: "0" },
|
"& .MuiTabs-root": { height: "fit-content", minHeight: "0" },
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<TabList value={value} onChange={onChange} {...props}>
|
<TabList
|
||||||
|
value={value}
|
||||||
|
onChange={onChange}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
{children}
|
{children}
|
||||||
</TabList>
|
</TabList>
|
||||||
</Box>
|
</Box>
|
||||||
|
|||||||
@@ -94,7 +94,10 @@ const PasswordPanel = () => {
|
|||||||
const action = await dispatch(update({ localData }));
|
const action = await dispatch(update({ localData }));
|
||||||
if (action.payload.success) {
|
if (action.payload.success) {
|
||||||
createToast({
|
createToast({
|
||||||
body: t("passwordPanel.passwordChangedSuccess", "Your password was changed successfully."),
|
body: t(
|
||||||
|
"passwordPanel.passwordChangedSuccess",
|
||||||
|
"Your password was changed successfully."
|
||||||
|
),
|
||||||
});
|
});
|
||||||
setLocalData({
|
setLocalData({
|
||||||
password: "",
|
password: "",
|
||||||
@@ -104,7 +107,10 @@ const PasswordPanel = () => {
|
|||||||
} else {
|
} else {
|
||||||
// TODO: Check for other errors?
|
// TODO: Check for other errors?
|
||||||
createToast({
|
createToast({
|
||||||
body: t("passwordPanel.passwordInputIncorrect", "Your password input was incorrect."),
|
body: t(
|
||||||
|
"passwordPanel.passwordInputIncorrect",
|
||||||
|
"Your password input was incorrect."
|
||||||
|
),
|
||||||
});
|
});
|
||||||
setErrors({ password: "*" + action.payload.msg + "." });
|
setErrors({ password: "*" + action.payload.msg + "." });
|
||||||
}
|
}
|
||||||
@@ -153,7 +159,10 @@ const PasswordPanel = () => {
|
|||||||
<TextInput
|
<TextInput
|
||||||
type="password"
|
type="password"
|
||||||
id="edit-current-password"
|
id="edit-current-password"
|
||||||
placeholder={t("passwordPanel.enterCurrentPassword", "Enter your current password")}
|
placeholder={t(
|
||||||
|
"passwordPanel.enterCurrentPassword",
|
||||||
|
"Enter your current password"
|
||||||
|
)}
|
||||||
autoComplete="current-password"
|
autoComplete="current-password"
|
||||||
value={localData.password}
|
value={localData.password}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
@@ -219,7 +228,10 @@ const PasswordPanel = () => {
|
|||||||
<Box sx={{ maxWidth: "70ch" }}>
|
<Box sx={{ maxWidth: "70ch" }}>
|
||||||
<Alert
|
<Alert
|
||||||
variant="warning"
|
variant="warning"
|
||||||
body={t("passwordPanel.passwordRequirements", "New password must contain at least 8 characters and must have at least one uppercase letter, one lowercase letter, one number and one special character.")}
|
body={t(
|
||||||
|
"passwordPanel.passwordRequirements",
|
||||||
|
"New password must contain at least 8 characters and must have at least one uppercase letter, one lowercase letter, one number and one special character."
|
||||||
|
)}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -121,19 +121,18 @@ const ProfilePanel = () => {
|
|||||||
const handleSaveProfile = async (event) => {
|
const handleSaveProfile = async (event) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
const nameChanged =
|
const nameChanged =
|
||||||
localData.firstName !== user.firstName ||
|
localData.firstName !== user.firstName || localData.lastName !== user.lastName;
|
||||||
localData.lastName !== user.lastName;
|
|
||||||
|
|
||||||
const avatarChanged =
|
const avatarChanged =
|
||||||
localData.deleteProfileImage === true ||
|
localData.deleteProfileImage === true ||
|
||||||
(localData.file && localData.file !== `data:image/png;base64,${user.avatarImage}`);
|
(localData.file && localData.file !== `data:image/png;base64,${user.avatarImage}`);
|
||||||
|
|
||||||
if (!nameChanged && !avatarChanged) {
|
if (!nameChanged && !avatarChanged) {
|
||||||
createToast({
|
createToast({
|
||||||
body: "Unable to update profile — no changes detected.",
|
body: "Unable to update profile — no changes detected.",
|
||||||
});
|
});
|
||||||
setErrors({ unchanged: "unable to update profile" });
|
setErrors({ unchanged: "unable to update profile" });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const action = await dispatch(update({ localData }));
|
const action = await dispatch(update({ localData }));
|
||||||
@@ -205,7 +204,7 @@ const ProfilePanel = () => {
|
|||||||
>
|
>
|
||||||
{/* This 0.9 is a bit magic numbering, refactor */}
|
{/* This 0.9 is a bit magic numbering, refactor */}
|
||||||
<Box flex={0.9}>
|
<Box flex={0.9}>
|
||||||
<Typography component="h1">{t('FirstName')}</Typography>
|
<Typography component="h1">{t("FirstName")}</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
<TextInput
|
<TextInput
|
||||||
id="edit-first-name"
|
id="edit-first-name"
|
||||||
@@ -223,7 +222,7 @@ const ProfilePanel = () => {
|
|||||||
gap={SPACING_GAP}
|
gap={SPACING_GAP}
|
||||||
>
|
>
|
||||||
<Box flex={0.9}>
|
<Box flex={0.9}>
|
||||||
<Typography component="h1">{t('LastName')}</Typography>
|
<Typography component="h1">{t("LastName")}</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
<TextInput
|
<TextInput
|
||||||
id="edit-last-name"
|
id="edit-last-name"
|
||||||
@@ -241,12 +240,12 @@ const ProfilePanel = () => {
|
|||||||
gap={SPACING_GAP}
|
gap={SPACING_GAP}
|
||||||
>
|
>
|
||||||
<Stack flex={0.9}>
|
<Stack flex={0.9}>
|
||||||
<Typography component="h1">{t('email')}</Typography>
|
<Typography component="h1">{t("email")}</Typography>
|
||||||
<Typography
|
<Typography
|
||||||
component="p"
|
component="p"
|
||||||
sx={{ opacity: 0.6 }}
|
sx={{ opacity: 0.6 }}
|
||||||
>
|
>
|
||||||
{t('EmailDescriptionText')}
|
{t("EmailDescriptionText")}
|
||||||
</Typography>
|
</Typography>
|
||||||
</Stack>
|
</Stack>
|
||||||
<TextInput
|
<TextInput
|
||||||
@@ -264,12 +263,12 @@ const ProfilePanel = () => {
|
|||||||
gap={SPACING_GAP}
|
gap={SPACING_GAP}
|
||||||
>
|
>
|
||||||
<Stack flex={0.9}>
|
<Stack flex={0.9}>
|
||||||
<Typography component="h1">{t('YourPhoto')}</Typography>
|
<Typography component="h1">{t("YourPhoto")}</Typography>
|
||||||
<Typography
|
<Typography
|
||||||
component="p"
|
component="p"
|
||||||
sx={{ opacity: 0.6 }}
|
sx={{ opacity: 0.6 }}
|
||||||
>
|
>
|
||||||
{t('PhotoDescriptionText')}
|
{t("PhotoDescriptionText")}
|
||||||
</Typography>
|
</Typography>
|
||||||
</Stack>
|
</Stack>
|
||||||
<Stack
|
<Stack
|
||||||
@@ -293,14 +292,14 @@ const ProfilePanel = () => {
|
|||||||
color="error"
|
color="error"
|
||||||
onClick={handleDeletePicture}
|
onClick={handleDeletePicture}
|
||||||
>
|
>
|
||||||
{t('delete')}
|
{t("delete")}
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
variant="contained" // CAIO_REVIEW
|
variant="contained" // CAIO_REVIEW
|
||||||
color="accent"
|
color="accent"
|
||||||
onClick={openPictureModal}
|
onClick={openPictureModal}
|
||||||
>
|
>
|
||||||
{t('update')}
|
{t("update")}
|
||||||
</Button>
|
</Button>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Stack>
|
</Stack>
|
||||||
@@ -325,7 +324,7 @@ const ProfilePanel = () => {
|
|||||||
disabled={Object.keys(errors).length !== 0 && !errors?.picture && true}
|
disabled={Object.keys(errors).length !== 0 && !errors?.picture && true}
|
||||||
sx={{ px: theme.spacing(12) }}
|
sx={{ px: theme.spacing(12) }}
|
||||||
>
|
>
|
||||||
{t('save')}
|
{t("save")}
|
||||||
</Button>
|
</Button>
|
||||||
</Box>
|
</Box>
|
||||||
</Stack>
|
</Stack>
|
||||||
@@ -344,12 +343,12 @@ const ProfilePanel = () => {
|
|||||||
spellCheck="false"
|
spellCheck="false"
|
||||||
>
|
>
|
||||||
<Box mb={theme.spacing(6)}>
|
<Box mb={theme.spacing(6)}>
|
||||||
<Typography component="h1">{t('DeleteAccountTitle')}</Typography>
|
<Typography component="h1">{t("DeleteAccountTitle")}</Typography>
|
||||||
<Typography
|
<Typography
|
||||||
component="p"
|
component="p"
|
||||||
sx={{ opacity: 0.6 }}
|
sx={{ opacity: 0.6 }}
|
||||||
>
|
>
|
||||||
{t('DeleteDescriptionText')}
|
{t("DeleteDescriptionText")}
|
||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
<Button
|
<Button
|
||||||
@@ -357,17 +356,17 @@ const ProfilePanel = () => {
|
|||||||
color="error"
|
color="error"
|
||||||
onClick={() => setIsOpen("delete")}
|
onClick={() => setIsOpen("delete")}
|
||||||
>
|
>
|
||||||
{t('DeleteAccountButton')}
|
{t("DeleteAccountButton")}
|
||||||
</Button>
|
</Button>
|
||||||
</Box>
|
</Box>
|
||||||
)}
|
)}
|
||||||
<Dialog
|
<Dialog
|
||||||
open={isModalOpen("delete")}
|
open={isModalOpen("delete")}
|
||||||
theme={theme}
|
theme={theme}
|
||||||
title={t('DeleteWarningTitle')}
|
title={t("DeleteWarningTitle")}
|
||||||
description={t('DeleteAccountWarning')}
|
description={t("DeleteAccountWarning")}
|
||||||
onCancel={() => setIsOpen("")}
|
onCancel={() => setIsOpen("")}
|
||||||
confirmationButtonLabel={t('DeleteAccountButton')}
|
confirmationButtonLabel={t("DeleteAccountButton")}
|
||||||
onConfirm={handleDeleteAccount}
|
onConfirm={handleDeleteAccount}
|
||||||
isLoading={isLoading}
|
isLoading={isLoading}
|
||||||
/>
|
/>
|
||||||
@@ -410,7 +409,7 @@ const ProfilePanel = () => {
|
|||||||
color="info"
|
color="info"
|
||||||
onClick={removePicture}
|
onClick={removePicture}
|
||||||
>
|
>
|
||||||
{t('remove')}
|
{t("remove")}
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
variant="contained"
|
variant="contained"
|
||||||
@@ -418,7 +417,7 @@ const ProfilePanel = () => {
|
|||||||
onClick={handleUpdatePicture}
|
onClick={handleUpdatePicture}
|
||||||
disabled={!!errors.picture || !file?.src}
|
disabled={!!errors.picture || !file?.src}
|
||||||
>
|
>
|
||||||
{t('update')}
|
{t("update")}
|
||||||
</Button>
|
</Button>
|
||||||
</Stack>
|
</Stack>
|
||||||
</GenericDialog>
|
</GenericDialog>
|
||||||
|
|||||||
@@ -48,7 +48,8 @@ const TeamPanel = () => {
|
|||||||
{row.firstName + " " + row.lastName}
|
{row.firstName + " " + row.lastName}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography>
|
<Typography>
|
||||||
{t("teamPanel.table.created")} {new Date(row.createdAt).toLocaleDateString()}
|
{t("teamPanel.table.created")}{" "}
|
||||||
|
{new Date(row.createdAt).toLocaleDateString()}
|
||||||
</Typography>
|
</Typography>
|
||||||
</Stack>
|
</Stack>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -16,18 +16,21 @@
|
|||||||
font-size: var(--env-var-font-size-medium-plus) !important;
|
font-size: var(--env-var-font-size-medium-plus) !important;
|
||||||
font-weight: 500 !important;
|
font-weight: 500 !important;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: calc((var(--env-var-height-2) - var(--env-var-font-size-medium-plus) * 1.2) / 2) var(--env-var-spacing-1);
|
padding: calc(
|
||||||
|
(var(--env-var-height-2) - var(--env-var-font-size-medium-plus) * 1.2) / 2
|
||||||
|
)
|
||||||
|
var(--env-var-spacing-1);
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.wallet-adapter-modal-title {
|
.wallet-adapter-modal-title {
|
||||||
font-size: var(--font-size-h5) !important;
|
font-size: var(--font-size-h5) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Separator styling */
|
/* Separator styling */
|
||||||
.wallet-adapter-modal-divider {
|
.wallet-adapter-modal-divider {
|
||||||
background-color: var(--border-color) !important;
|
background-color: var(--border-color) !important;
|
||||||
margin: var(--spacing-md) 0 !important;
|
margin: var(--spacing-md) 0 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Responsive fixes */
|
/* Responsive fixes */
|
||||||
@@ -36,16 +39,15 @@
|
|||||||
font-size: var(--env-var-font-size-medium) !important;
|
font-size: var(--env-var-font-size-medium) !important;
|
||||||
padding: calc((var(--env-var-height-2) - var(--env-var-font-size-medium) * 1.2) / 2)
|
padding: calc((var(--env-var-height-2) - var(--env-var-font-size-medium) * 1.2) / 2)
|
||||||
var(--env-var-spacing-1-minus) !important;
|
var(--env-var-spacing-1-minus) !important;
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 900px) {
|
@media (max-width: 900px) {
|
||||||
.wallet-adapter-modal-wrapper {
|
.wallet-adapter-modal-wrapper {
|
||||||
flex-direction: column !important;
|
flex-direction: column !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.wallet-adapter-modal-divider {
|
.wallet-adapter-modal-divider {
|
||||||
margin: var(--spacing-sm) 0 !important;
|
margin: var(--spacing-sm) 0 !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -20,6 +20,7 @@ const initialState = {
|
|||||||
collapsed: false,
|
collapsed: false,
|
||||||
},
|
},
|
||||||
mode: initialMode,
|
mode: initialMode,
|
||||||
|
showURL: false,
|
||||||
greeting: { index: 0, lastUpdate: null },
|
greeting: { index: 0, lastUpdate: null },
|
||||||
timezone: "America/Toronto",
|
timezone: "America/Toronto",
|
||||||
distributedUptimeEnabled: false,
|
distributedUptimeEnabled: false,
|
||||||
@@ -46,6 +47,9 @@ const uiSlice = createSlice({
|
|||||||
setMode: (state, action) => {
|
setMode: (state, action) => {
|
||||||
state.mode = action.payload;
|
state.mode = action.payload;
|
||||||
},
|
},
|
||||||
|
setShowURL: (state, action) => {
|
||||||
|
state.showURL = action.payload;
|
||||||
|
},
|
||||||
setGreeting(state, action) {
|
setGreeting(state, action) {
|
||||||
state.greeting.index = action.payload.index;
|
state.greeting.index = action.payload.index;
|
||||||
state.greeting.lastUpdate = action.payload.lastUpdate;
|
state.greeting.lastUpdate = action.payload.lastUpdate;
|
||||||
@@ -67,6 +71,7 @@ export const {
|
|||||||
setRowsPerPage,
|
setRowsPerPage,
|
||||||
toggleSidebar,
|
toggleSidebar,
|
||||||
setMode,
|
setMode,
|
||||||
|
setShowURL,
|
||||||
setGreeting,
|
setGreeting,
|
||||||
setTimezone,
|
setTimezone,
|
||||||
setDistributedUptimeEnabled,
|
setDistributedUptimeEnabled,
|
||||||
|
|||||||
@@ -2,26 +2,26 @@ import { useState } from "react";
|
|||||||
import { networkService } from "../main";
|
import { networkService } from "../main";
|
||||||
|
|
||||||
export const useBulkMonitors = () => {
|
export const useBulkMonitors = () => {
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
|
|
||||||
const createBulkMonitors = async (file, user) => {
|
const createBulkMonitors = async (file, user) => {
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
|
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
formData.append("csvFile", file);
|
formData.append("csvFile", file);
|
||||||
formData.append("userId", user._id);
|
formData.append("userId", user._id);
|
||||||
formData.append("teamId", user.teamId);
|
formData.append("teamId", user.teamId);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await networkService.createBulkMonitors(formData);
|
const response = await networkService.createBulkMonitors(formData);
|
||||||
return [true, response.data, null]; // [success, data, error]
|
return [true, response.data, null]; // [success, data, error]
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
const errorMessage = err?.response?.data?.msg || err.message;
|
const errorMessage = err?.response?.data?.msg || err.message;
|
||||||
return [false, null, errorMessage];
|
return [false, null, errorMessage];
|
||||||
} finally {
|
} finally {
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return [createBulkMonitors, isLoading];
|
return [createBulkMonitors, isLoading];
|
||||||
};
|
};
|
||||||
@@ -3,13 +3,12 @@ import { networkService } from "../main";
|
|||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
import { createToast } from "../Utils/toastUtils";
|
import { createToast } from "../Utils/toastUtils";
|
||||||
|
|
||||||
export const useFetchUptimeMonitorDetails = ({ monitorId, dateRange }) => {
|
export const useFetchUptimeMonitorDetails = ({ monitorId, dateRange, trigger }) => {
|
||||||
const [networkError, setNetworkError] = useState(false);
|
const [networkError, setNetworkError] = useState(false);
|
||||||
const [isLoading, setIsLoading] = useState(true);
|
const [isLoading, setIsLoading] = useState(true);
|
||||||
const [monitor, setMonitor] = useState(undefined);
|
const [monitor, setMonitor] = useState(undefined);
|
||||||
const [monitorStats, setMonitorStats] = useState(undefined);
|
const [monitorStats, setMonitorStats] = useState(undefined);
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fetchMonitors = async () => {
|
const fetchMonitors = async () => {
|
||||||
try {
|
try {
|
||||||
@@ -29,7 +28,7 @@ export const useFetchUptimeMonitorDetails = ({ monitorId, dateRange }) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
fetchMonitors();
|
fetchMonitors();
|
||||||
}, [dateRange, monitorId, navigate]);
|
}, [dateRange, monitorId, navigate, trigger]);
|
||||||
return [monitor, monitorStats, isLoading, networkError];
|
return [monitor, monitorStats, isLoading, networkError];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
import { useState } from "react";
|
||||||
|
import { networkService } from "../main";
|
||||||
|
import { createToast } from "../Utils/toastUtils";
|
||||||
|
|
||||||
|
const usePauseMonitor = ({ monitorId, triggerUpdate }) => {
|
||||||
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
|
const [error, setError] = useState(undefined);
|
||||||
|
const pauseMonitor = async () => {
|
||||||
|
try {
|
||||||
|
setIsLoading(true);
|
||||||
|
const res = await networkService.pauseMonitorById({ monitorId });
|
||||||
|
createToast({
|
||||||
|
body: res.data.data.isActive
|
||||||
|
? "Monitor resumed successfully"
|
||||||
|
: "Monitor paused successfully",
|
||||||
|
});
|
||||||
|
triggerUpdate();
|
||||||
|
} catch (error) {
|
||||||
|
setError(error);
|
||||||
|
} finally {
|
||||||
|
setIsLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return [pauseMonitor, isLoading, error];
|
||||||
|
};
|
||||||
|
|
||||||
|
export { usePauseMonitor };
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
import { useCallback } from "react";
|
import { useCallback } from "react";
|
||||||
|
import { useTheme } from "@mui/material";
|
||||||
|
|
||||||
const useMonitorUtils = () => {
|
const useMonitorUtils = () => {
|
||||||
const getMonitorWithPercentage = useCallback((monitor, theme) => {
|
const getMonitorWithPercentage = useCallback((monitor, theme) => {
|
||||||
@@ -36,7 +37,16 @@ const useMonitorUtils = () => {
|
|||||||
return monitor?.status == true ? "up" : "down";
|
return monitor?.status == true ? "up" : "down";
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return { getMonitorWithPercentage, determineState };
|
const theme = useTheme();
|
||||||
|
|
||||||
|
const statusColor = {
|
||||||
|
up: theme.palette.success.lowContrast,
|
||||||
|
down: theme.palette.error.lowContrast,
|
||||||
|
paused: theme.palette.warning.lowContrast,
|
||||||
|
pending: theme.palette.warning.lowContrast,
|
||||||
|
};
|
||||||
|
|
||||||
|
return { getMonitorWithPercentage, determineState, statusColor };
|
||||||
};
|
};
|
||||||
|
|
||||||
export { useMonitorUtils };
|
export { useMonitorUtils };
|
||||||
@@ -2,27 +2,54 @@ import { useState } from "react";
|
|||||||
import { networkService } from "../main";
|
import { networkService } from "../main";
|
||||||
import { useSelector } from "react-redux";
|
import { useSelector } from "react-redux";
|
||||||
import { createToast } from "../Utils/toastUtils";
|
import { createToast } from "../Utils/toastUtils";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
const useSendTestEmail = () => {
|
const useSendTestEmail = () => {
|
||||||
const [isSending, setIsSending] = useState(false);
|
const [isSending, setIsSending] = useState(false);
|
||||||
const [error, setError] = useState(null);
|
const [error, setError] = useState(null);
|
||||||
const user = useSelector((state) => state.auth.user);
|
const user = useSelector((state) => state.auth.user);
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const sendTestEmail = async () => {
|
/**
|
||||||
|
* Send a test email with optional email configuration
|
||||||
|
* @param {Object} emailConfig - Optional email configuration parameters
|
||||||
|
*/
|
||||||
|
const sendTestEmail = async (emailConfig = null) => {
|
||||||
try {
|
try {
|
||||||
setIsSending(true);
|
setIsSending(true);
|
||||||
setError(null);
|
setError(null);
|
||||||
|
|
||||||
const response = await networkService.sendTestEmail({ to: user.email });
|
// Send the test email with or without configuration
|
||||||
|
const response = await networkService.sendTestEmail({
|
||||||
|
to: user.email,
|
||||||
|
emailConfig,
|
||||||
|
});
|
||||||
|
|
||||||
if (typeof response?.data?.data?.messageId !== "undefined") {
|
if (typeof response?.data?.data?.messageId !== "undefined") {
|
||||||
createToast({
|
createToast({
|
||||||
body: "Test email sent successfully",
|
body: t("settingsTestEmailSuccess", "Test email sent successfully"),
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
throw new Error("Failed to send test email");
|
throw new Error(
|
||||||
|
response?.data?.error ||
|
||||||
|
t("settingsTestEmailFailed", "Failed to send test email")
|
||||||
|
);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
createToast({
|
||||||
|
body: t("failedToSendEmail"),
|
||||||
|
});
|
||||||
setError(error);
|
setError(error);
|
||||||
|
createToast({
|
||||||
|
body: t(
|
||||||
|
"settingsTestEmailFailedWithReason",
|
||||||
|
"Failed to send test email: {{reason}}",
|
||||||
|
{
|
||||||
|
reason: error.message || t("settingsTestEmailUnknownError", "Unknown error"),
|
||||||
|
}
|
||||||
|
),
|
||||||
|
variant: "error",
|
||||||
|
});
|
||||||
} finally {
|
} finally {
|
||||||
setIsSending(false);
|
setIsSending(false);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import React from 'react';
|
import React from "react";
|
||||||
import AppBar from '../../Components/Common/AppBar';
|
import AppBar from "../../Components/Common/AppBar";
|
||||||
import Footer from '../../Components/Common/Footer';
|
import Footer from "../../Components/Common/Footer";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
const AboutUs = () => {
|
const AboutUs = () => {
|
||||||
|
|||||||
@@ -31,21 +31,19 @@ const Account = ({ open = "profile" }) => {
|
|||||||
let tabList = [
|
let tabList = [
|
||||||
{ name: t("menu.profile"), value: "profile" },
|
{ name: t("menu.profile"), value: "profile" },
|
||||||
{ name: t("menu.password"), value: "password" },
|
{ name: t("menu.password"), value: "password" },
|
||||||
{ name: t("menu.team"), value: "team" }
|
{ name: t("menu.team"), value: "team" },
|
||||||
];
|
];
|
||||||
const hideTeams = !requiredRoles.some((role) => user.role.includes(role));
|
const hideTeams = !requiredRoles.some((role) => user.role.includes(role));
|
||||||
if (hideTeams) {
|
if (hideTeams) {
|
||||||
tabList = [
|
tabList = [
|
||||||
{ name: t("menu.profile"), value: "profile" },
|
{ name: t("menu.profile"), value: "profile" },
|
||||||
{ name: t("menu.password"), value: "password" }
|
{ name: t("menu.password"), value: "password" },
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove password for demo
|
// Remove password for demo
|
||||||
if (user.role.includes("demo")) {
|
if (user.role.includes("demo")) {
|
||||||
tabList = [
|
tabList = [{ name: t("menu.profile"), value: "profile" }];
|
||||||
{ name: t("menu.profile"), value: "profile" }
|
|
||||||
];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleKeyDown = (event) => {
|
const handleKeyDown = (event) => {
|
||||||
@@ -70,7 +68,11 @@ const Account = ({ open = "profile" }) => {
|
|||||||
py={theme.spacing(12)}
|
py={theme.spacing(12)}
|
||||||
>
|
>
|
||||||
<TabContext value={tab}>
|
<TabContext value={tab}>
|
||||||
<CustomTabList value={tab} onChange={handleTabChange} aria-label="account tabs">
|
<CustomTabList
|
||||||
|
value={tab}
|
||||||
|
onChange={handleTabChange}
|
||||||
|
aria-label="account tabs"
|
||||||
|
>
|
||||||
{tabList.map((item, index) => (
|
{tabList.map((item, index) => (
|
||||||
<Tab
|
<Tab
|
||||||
label={item.name}
|
label={item.name}
|
||||||
|
|||||||
@@ -75,11 +75,11 @@ const EmailStep = ({ form, errors, onSubmit, onChange }) => {
|
|||||||
width: "30%",
|
width: "30%",
|
||||||
px: theme.spacing(6),
|
px: theme.spacing(6),
|
||||||
borderRadius: `${theme.shape.borderRadius}px !important`,
|
borderRadius: `${theme.shape.borderRadius}px !important`,
|
||||||
'&.MuiButtonBase-root': {
|
"&.MuiButtonBase-root": {
|
||||||
borderRadius: `${theme.shape.borderRadius}px !important`
|
borderRadius: `${theme.shape.borderRadius}px !important`,
|
||||||
},
|
},
|
||||||
'&.MuiButton-root': {
|
"&.MuiButton-root": {
|
||||||
borderRadius: `${theme.shape.borderRadius}px !important`
|
borderRadius: `${theme.shape.borderRadius}px !important`,
|
||||||
},
|
},
|
||||||
"&.Mui-focusVisible": {
|
"&.Mui-focusVisible": {
|
||||||
outline: `2px solid ${theme.palette.primary.main}`,
|
outline: `2px solid ${theme.palette.primary.main}`,
|
||||||
|
|||||||
@@ -79,11 +79,11 @@ const PasswordStep = ({ form, errors, onSubmit, onChange, onBack }) => {
|
|||||||
sx={{
|
sx={{
|
||||||
px: theme.spacing(5),
|
px: theme.spacing(5),
|
||||||
borderRadius: `${theme.shape.borderRadius}px !important`,
|
borderRadius: `${theme.shape.borderRadius}px !important`,
|
||||||
'&.MuiButtonBase-root': {
|
"&.MuiButtonBase-root": {
|
||||||
borderRadius: `${theme.shape.borderRadius}px !important`
|
borderRadius: `${theme.shape.borderRadius}px !important`,
|
||||||
},
|
},
|
||||||
'&.MuiButton-root': {
|
"&.MuiButton-root": {
|
||||||
borderRadius: `${theme.shape.borderRadius}px !important`
|
borderRadius: `${theme.shape.borderRadius}px !important`,
|
||||||
},
|
},
|
||||||
"& svg.MuiSvgIcon-root": {
|
"& svg.MuiSvgIcon-root": {
|
||||||
mr: theme.spacing(3),
|
mr: theme.spacing(3),
|
||||||
@@ -108,11 +108,11 @@ const PasswordStep = ({ form, errors, onSubmit, onChange, onBack }) => {
|
|||||||
width: "30%",
|
width: "30%",
|
||||||
px: theme.spacing(4),
|
px: theme.spacing(4),
|
||||||
borderRadius: `${theme.shape.borderRadius}px !important`,
|
borderRadius: `${theme.shape.borderRadius}px !important`,
|
||||||
'&.MuiButtonBase-root': {
|
"&.MuiButtonBase-root": {
|
||||||
borderRadius: `${theme.shape.borderRadius}px !important`
|
borderRadius: `${theme.shape.borderRadius}px !important`,
|
||||||
},
|
},
|
||||||
'&.MuiButton-root': {
|
"&.MuiButton-root": {
|
||||||
borderRadius: `${theme.shape.borderRadius}px !important`
|
borderRadius: `${theme.shape.borderRadius}px !important`,
|
||||||
},
|
},
|
||||||
"&.Mui-focusVisible": {
|
"&.Mui-focusVisible": {
|
||||||
outline: `2px solid ${theme.palette.primary.main}`,
|
outline: `2px solid ${theme.palette.primary.main}`,
|
||||||
|
|||||||
@@ -43,8 +43,6 @@ const Login = () => {
|
|||||||
const [errors, setErrors] = useState({});
|
const [errors, setErrors] = useState({});
|
||||||
const [step, setStep] = useState(0);
|
const [step, setStep] = useState(0);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (authToken) {
|
if (authToken) {
|
||||||
navigate("/uptime");
|
navigate("/uptime");
|
||||||
@@ -84,7 +82,9 @@ const Login = () => {
|
|||||||
);
|
);
|
||||||
if (error) {
|
if (error) {
|
||||||
const errorMessage = error.details[0].message;
|
const errorMessage = error.details[0].message;
|
||||||
const translatedMessage = errorMessage.startsWith('auth') ? t(errorMessage) : errorMessage;
|
const translatedMessage = errorMessage.startsWith("auth")
|
||||||
|
? t(errorMessage)
|
||||||
|
: errorMessage;
|
||||||
setErrors((prev) => ({ ...prev, email: translatedMessage }));
|
setErrors((prev) => ({ ...prev, email: translatedMessage }));
|
||||||
createToast({ body: translatedMessage });
|
createToast({ body: translatedMessage });
|
||||||
} else {
|
} else {
|
||||||
@@ -103,7 +103,9 @@ const Login = () => {
|
|||||||
createToast({
|
createToast({
|
||||||
body:
|
body:
|
||||||
error.details && error.details.length > 0
|
error.details && error.details.length > 0
|
||||||
? (error.details[0].message.startsWith('auth') ? t(error.details[0].message) : error.details[0].message)
|
? error.details[0].message.startsWith("auth")
|
||||||
|
? t(error.details[0].message)
|
||||||
|
: error.details[0].message
|
||||||
: t("Error validating data."),
|
: t("Error validating data."),
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
@@ -229,7 +231,7 @@ const Login = () => {
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Registration link */}
|
{/* Registration link */}
|
||||||
<Box textAlign="center" >
|
<Box textAlign="center">
|
||||||
<Typography
|
<Typography
|
||||||
className="forgot-p"
|
className="forgot-p"
|
||||||
display="inline-block"
|
display="inline-block"
|
||||||
@@ -242,10 +244,10 @@ const Login = () => {
|
|||||||
color={theme.palette.accent.main}
|
color={theme.palette.accent.main}
|
||||||
ml={theme.spacing(2)}
|
ml={theme.spacing(2)}
|
||||||
sx={{
|
sx={{
|
||||||
cursor: 'pointer',
|
cursor: "pointer",
|
||||||
'&:hover': {
|
"&:hover": {
|
||||||
color: theme.palette.accent.darker
|
color: theme.palette.accent.darker,
|
||||||
}
|
},
|
||||||
}}
|
}}
|
||||||
onClick={() => navigate("/register")}
|
onClick={() => navigate("/register")}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -100,9 +100,7 @@ const NewPasswordConfirmed = () => {
|
|||||||
</IconBox>
|
</IconBox>
|
||||||
</Stack>
|
</Stack>
|
||||||
<Typography component="h1">{t("passwordreset")}</Typography>
|
<Typography component="h1">{t("passwordreset")}</Typography>
|
||||||
<Typography mb={theme.spacing(2)}>
|
<Typography mb={theme.spacing(2)}>{t("authNewPasswordConfirmed")}</Typography>
|
||||||
{t("authNewPasswordConfirmed")}
|
|
||||||
</Typography>
|
|
||||||
</Box>
|
</Box>
|
||||||
<Button
|
<Button
|
||||||
variant="contained"
|
variant="contained"
|
||||||
|
|||||||
@@ -105,7 +105,7 @@ const LandingPage = ({ isSuperAdmin, onSignup }) => {
|
|||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)
|
),
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Typography>
|
</Typography>
|
||||||
|
|||||||
@@ -68,11 +68,14 @@ function StepTwo({ form, errors, onSubmit, onChange, onBack }) {
|
|||||||
onInput={(e) => (e.target.value = e.target.value.toLowerCase())}
|
onInput={(e) => (e.target.value = e.target.value.toLowerCase())}
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
error={errors.email ? true : false}
|
error={errors.email ? true : false}
|
||||||
helperText={errors.email && (
|
helperText={
|
||||||
errors.email.includes("required") ? t("authRegisterEmailRequired") :
|
errors.email &&
|
||||||
errors.email.includes("valid email") ? t("authRegisterEmailInvalid") :
|
(errors.email.includes("required")
|
||||||
t(errors.email)
|
? t("authRegisterEmailRequired")
|
||||||
)}
|
: errors.email.includes("valid email")
|
||||||
|
? t("authRegisterEmailInvalid")
|
||||||
|
: t(errors.email))
|
||||||
|
}
|
||||||
ref={inputRef}
|
ref={inputRef}
|
||||||
/>
|
/>
|
||||||
<Stack
|
<Stack
|
||||||
|
|||||||
@@ -199,9 +199,7 @@ const CreateDistributedUptime = () => {
|
|||||||
<ConfigBox>
|
<ConfigBox>
|
||||||
<Box>
|
<Box>
|
||||||
<Typography component="h2">{t("settingsGeneralSettings")}</Typography>
|
<Typography component="h2">{t("settingsGeneralSettings")}</Typography>
|
||||||
<Typography component="p">
|
<Typography component="p">{t("distributedUptimeCreateSelectURL")}</Typography>
|
||||||
{t("distributedUptimeCreateSelectURL")}
|
|
||||||
</Typography>
|
|
||||||
</Box>
|
</Box>
|
||||||
<Stack gap={theme.spacing(15)}>
|
<Stack gap={theme.spacing(15)}>
|
||||||
<TextInput
|
<TextInput
|
||||||
@@ -293,7 +291,9 @@ const CreateDistributedUptime = () => {
|
|||||||
</ConfigBox>
|
</ConfigBox>
|
||||||
<ConfigBox>
|
<ConfigBox>
|
||||||
<Box>
|
<Box>
|
||||||
<Typography component="h2">{t("distributedUptimeCreateIncidentNotification")}</Typography>
|
<Typography component="h2">
|
||||||
|
{t("distributedUptimeCreateIncidentNotification")}
|
||||||
|
</Typography>
|
||||||
<Typography component="p">
|
<Typography component="p">
|
||||||
{t("distributedUptimeCreateIncidentDescription")}
|
{t("distributedUptimeCreateIncidentDescription")}
|
||||||
</Typography>
|
</Typography>
|
||||||
@@ -312,7 +312,9 @@ const CreateDistributedUptime = () => {
|
|||||||
</ConfigBox>
|
</ConfigBox>
|
||||||
<ConfigBox>
|
<ConfigBox>
|
||||||
<Box>
|
<Box>
|
||||||
<Typography component="h2">{t("distributedUptimeCreateAdvancedSettings")}</Typography>
|
<Typography component="h2">
|
||||||
|
{t("distributedUptimeCreateAdvancedSettings")}
|
||||||
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
<Stack gap={theme.spacing(12)}>
|
<Stack gap={theme.spacing(12)}>
|
||||||
<Select
|
<Select
|
||||||
|
|||||||
@@ -76,7 +76,9 @@ const DeviceTicker = ({ data, width = "100%", connectionStatus }) => {
|
|||||||
<Typography>{city}</Typography>
|
<Typography>{city}</Typography>
|
||||||
</td>
|
</td>
|
||||||
<td style={{ textAlign: "right", paddingLeft: theme.spacing(4) }}>
|
<td style={{ textAlign: "right", paddingLeft: theme.spacing(4) }}>
|
||||||
<Typography>{Math.floor(dataPoint.responseTime)} {t("ms")}</Typography>
|
<Typography>
|
||||||
|
{Math.floor(dataPoint.responseTime)} {t("ms")}
|
||||||
|
</Typography>
|
||||||
</td>
|
</td>
|
||||||
<td style={{ textAlign: "right", paddingLeft: theme.spacing(4) }}>
|
<td style={{ textAlign: "right", paddingLeft: theme.spacing(4) }}>
|
||||||
<Typography color={theme.palette.warning.main}>
|
<Typography color={theme.palette.warning.main}>
|
||||||
|
|||||||
@@ -10,9 +10,7 @@ const MonitorHeader = ({ monitor }) => {
|
|||||||
<Stack direction="row">
|
<Stack direction="row">
|
||||||
<Stack gap={theme.spacing(2)}>
|
<Stack gap={theme.spacing(2)}>
|
||||||
<Typography variant="h1">{monitor.name}</Typography>
|
<Typography variant="h1">{monitor.name}</Typography>
|
||||||
<Typography variant="h2">
|
<Typography variant="h2">{t("distributedUptimeDetailsMonitorHeader")}</Typography>
|
||||||
{t("distributedUptimeDetailsMonitorHeader")}
|
|
||||||
</Typography>
|
|
||||||
</Stack>
|
</Stack>
|
||||||
</Stack>
|
</Stack>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -2,7 +2,14 @@ import { useState, useEffect } from "react";
|
|||||||
import { networkService } from "../../../main";
|
import { networkService } from "../../../main";
|
||||||
import { createToast } from "../../../Utils/toastUtils";
|
import { createToast } from "../../../Utils/toastUtils";
|
||||||
import { useSelector } from "react-redux";
|
import { useSelector } from "react-redux";
|
||||||
const useChecksFetch = ({ selectedMonitor, selectedMonitorType, filter, dateRange, page, rowsPerPage }) => {
|
const useChecksFetch = ({
|
||||||
|
selectedMonitor,
|
||||||
|
selectedMonitorType,
|
||||||
|
filter,
|
||||||
|
dateRange,
|
||||||
|
page,
|
||||||
|
rowsPerPage,
|
||||||
|
}) => {
|
||||||
//Redux
|
//Redux
|
||||||
const { user } = useSelector((state) => state.auth);
|
const { user } = useSelector((state) => state.auth);
|
||||||
|
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ const useMonitorsFetch = ({ teamId }) => {
|
|||||||
acc[monitor._id] = {
|
acc[monitor._id] = {
|
||||||
_id: monitor._id,
|
_id: monitor._id,
|
||||||
name: monitor.name,
|
name: monitor.name,
|
||||||
type: monitor.type
|
type: monitor.type,
|
||||||
};
|
};
|
||||||
return acc;
|
return acc;
|
||||||
}, {});
|
}, {});
|
||||||
|
|||||||
@@ -19,7 +19,9 @@ const Incidents = () => {
|
|||||||
const { user } = useSelector((state) => state.auth);
|
const { user } = useSelector((state) => state.auth);
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const BREADCRUMBS = [{ name: t("incidentsPageTitle", "Incidents"), path: "/incidents" }];
|
const BREADCRUMBS = [
|
||||||
|
{ name: t("incidentsPageTitle", "Incidents"), path: "/incidents" },
|
||||||
|
];
|
||||||
|
|
||||||
// Local state
|
// Local state
|
||||||
const [selectedMonitor, setSelectedMonitor] = useState("0");
|
const [selectedMonitor, setSelectedMonitor] = useState("0");
|
||||||
|
|||||||
@@ -6,7 +6,10 @@ import { useSelector, useDispatch } from "react-redux";
|
|||||||
|
|
||||||
// Utility and Network
|
// Utility and Network
|
||||||
import { infrastructureMonitorValidation } from "../../../Validation/validation";
|
import { infrastructureMonitorValidation } from "../../../Validation/validation";
|
||||||
import { createInfrastructureMonitor, updateInfrastructureMonitor } from "../../../Features/InfrastructureMonitors/infrastructureMonitorsSlice";
|
import {
|
||||||
|
createInfrastructureMonitor,
|
||||||
|
updateInfrastructureMonitor,
|
||||||
|
} from "../../../Features/InfrastructureMonitors/infrastructureMonitorsSlice";
|
||||||
import { useHardwareMonitorsFetch } from "../Details/Hooks/useHardwareMonitorsFetch";
|
import { useHardwareMonitorsFetch } from "../Details/Hooks/useHardwareMonitorsFetch";
|
||||||
import { capitalizeFirstLetter } from "../../../Utils/stringUtils";
|
import { capitalizeFirstLetter } from "../../../Utils/stringUtils";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
@@ -90,20 +93,26 @@ const CreateInfrastructureMonitor = () => {
|
|||||||
setInfrastructureMonitor({
|
setInfrastructureMonitor({
|
||||||
url: monitor.url.replace(/^https?:\/\//, ""),
|
url: monitor.url.replace(/^https?:\/\//, ""),
|
||||||
name: monitor.name || "",
|
name: monitor.name || "",
|
||||||
notifications: monitor.notifications?.filter(n => typeof n === "object") || [],
|
notifications: monitor.notifications?.filter((n) => typeof n === "object") || [],
|
||||||
notify_email: (monitor.notifications?.length ?? 0) > 0,
|
notify_email: (monitor.notifications?.length ?? 0) > 0,
|
||||||
interval: monitor.interval / MS_PER_MINUTE,
|
interval: monitor.interval / MS_PER_MINUTE,
|
||||||
cpu: monitor.thresholds?.usage_cpu !== undefined,
|
cpu: monitor.thresholds?.usage_cpu !== undefined,
|
||||||
usage_cpu: monitor.thresholds?.usage_cpu ? monitor.thresholds.usage_cpu * 100 : "",
|
usage_cpu: monitor.thresholds?.usage_cpu ? monitor.thresholds.usage_cpu * 100 : "",
|
||||||
|
|
||||||
memory: monitor.thresholds?.usage_memory !== undefined,
|
memory: monitor.thresholds?.usage_memory !== undefined,
|
||||||
usage_memory: monitor.thresholds?.usage_memory ? monitor.thresholds.usage_memory * 100 : "",
|
usage_memory: monitor.thresholds?.usage_memory
|
||||||
|
? monitor.thresholds.usage_memory * 100
|
||||||
|
: "",
|
||||||
|
|
||||||
disk: monitor.thresholds?.usage_disk !== undefined,
|
disk: monitor.thresholds?.usage_disk !== undefined,
|
||||||
usage_disk: monitor.thresholds?.usage_disk ? monitor.thresholds.usage_disk * 100 : "",
|
usage_disk: monitor.thresholds?.usage_disk
|
||||||
|
? monitor.thresholds.usage_disk * 100
|
||||||
|
: "",
|
||||||
|
|
||||||
temperature: monitor.thresholds?.usage_temperature !== undefined,
|
temperature: monitor.thresholds?.usage_temperature !== undefined,
|
||||||
usage_temperature: monitor.thresholds?.usage_temperature ? monitor.thresholds.usage_temperature * 100 : "",
|
usage_temperature: monitor.thresholds?.usage_temperature
|
||||||
|
? monitor.thresholds.usage_temperature * 100
|
||||||
|
: "",
|
||||||
secret: monitor.secret || "",
|
secret: monitor.secret || "",
|
||||||
});
|
});
|
||||||
setHttps(monitor.url.startsWith("https"));
|
setHttps(monitor.url.startsWith("https"));
|
||||||
@@ -199,7 +208,9 @@ const CreateInfrastructureMonitor = () => {
|
|||||||
: await dispatch(updateInfrastructureMonitor({ monitorId, monitor: form }));
|
: await dispatch(updateInfrastructureMonitor({ monitorId, monitor: form }));
|
||||||
if (action.meta.requestStatus === "fulfilled") {
|
if (action.meta.requestStatus === "fulfilled") {
|
||||||
createToast({
|
createToast({
|
||||||
body: isCreate ? t("infrastructureMonitorCreated") : t("infrastructureMonitorUpdated"),
|
body: isCreate
|
||||||
|
? t("infrastructureMonitorCreated")
|
||||||
|
: t("infrastructureMonitorUpdated"),
|
||||||
});
|
});
|
||||||
navigate("/infrastructure");
|
navigate("/infrastructure");
|
||||||
} else {
|
} else {
|
||||||
@@ -242,17 +253,18 @@ const CreateInfrastructureMonitor = () => {
|
|||||||
notifications.push({ type, address: value });
|
notifications.push({ type, address: value });
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
notifications = notifications.filter((n) => !(n.type === type && n.address === value));
|
notifications = notifications.filter(
|
||||||
|
(n) => !(n.type === type && n.address === value)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
setInfrastructureMonitor((prev) => ({
|
setInfrastructureMonitor((prev) => ({
|
||||||
...prev,
|
...prev,
|
||||||
notifications,
|
notifications,
|
||||||
...(type === "email" ? { notify_email: checked } : {})
|
...(type === "email" ? { notify_email: checked } : {}),
|
||||||
}));
|
}));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box className="create-infrastructure-monitor">
|
<Box className="create-infrastructure-monitor">
|
||||||
<Breadcrumbs
|
<Breadcrumbs
|
||||||
@@ -260,8 +272,10 @@ const CreateInfrastructureMonitor = () => {
|
|||||||
{ name: "Infrastructure monitors", path: "/infrastructure" },
|
{ name: "Infrastructure monitors", path: "/infrastructure" },
|
||||||
...(isCreate
|
...(isCreate
|
||||||
? [{ name: "Create", path: "/infrastructure/create" }]
|
? [{ name: "Create", path: "/infrastructure/create" }]
|
||||||
: [{ name: "Details", path: `/infrastructure/${monitorId}` }, { name: "Configure", path: `/infrastructure/configure/${monitorId}` }]
|
: [
|
||||||
),
|
{ name: "Details", path: `/infrastructure/${monitorId}` },
|
||||||
|
{ name: "Configure", path: `/infrastructure/configure/${monitorId}` },
|
||||||
|
]),
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
<Stack
|
<Stack
|
||||||
@@ -281,7 +295,7 @@ const CreateInfrastructureMonitor = () => {
|
|||||||
component="span"
|
component="span"
|
||||||
fontSize="inherit"
|
fontSize="inherit"
|
||||||
>
|
>
|
||||||
{t(isCreate? "infrastructureCreateYour": "infrastructureEditYour")}{" "}
|
{t(isCreate ? "infrastructureCreateYour" : "infrastructureEditYour")}{" "}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography
|
<Typography
|
||||||
component="span"
|
component="span"
|
||||||
@@ -294,7 +308,12 @@ const CreateInfrastructureMonitor = () => {
|
|||||||
</Typography>
|
</Typography>
|
||||||
<ConfigBox>
|
<ConfigBox>
|
||||||
<Stack gap={theme.spacing(6)}>
|
<Stack gap={theme.spacing(6)}>
|
||||||
<Typography component="h2">{t("settingsGeneralSettings")}</Typography>
|
<Typography
|
||||||
|
component="h2"
|
||||||
|
variant="h2"
|
||||||
|
>
|
||||||
|
{t("settingsGeneralSettings")}
|
||||||
|
</Typography>
|
||||||
<Typography component="p">
|
<Typography component="p">
|
||||||
{t("infrastructureCreateGeneralSettingsDescription")}
|
{t("infrastructureCreateGeneralSettingsDescription")}
|
||||||
</Typography>
|
</Typography>
|
||||||
@@ -323,25 +342,25 @@ const CreateInfrastructureMonitor = () => {
|
|||||||
disabled={!isCreate}
|
disabled={!isCreate}
|
||||||
/>
|
/>
|
||||||
{isCreate && (
|
{isCreate && (
|
||||||
<Box>
|
<Box>
|
||||||
<Typography component="p">{t("infrastructureProtocol")}</Typography>
|
<Typography component="p">{t("infrastructureProtocol")}</Typography>
|
||||||
<ButtonGroup>
|
<ButtonGroup>
|
||||||
<Button
|
<Button
|
||||||
variant="group"
|
variant="group"
|
||||||
filled={https.toString()}
|
filled={https.toString()}
|
||||||
onClick={() => setHttps(true)}
|
onClick={() => setHttps(true)}
|
||||||
>
|
>
|
||||||
{t("https")}
|
{t("https")}
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
variant="group"
|
variant="group"
|
||||||
filled={(!https).toString()}
|
filled={(!https).toString()}
|
||||||
onClick={() => setHttps(false)}
|
onClick={() => setHttps(false)}
|
||||||
>
|
>
|
||||||
{t("http")}
|
{t("http")}
|
||||||
</Button>
|
</Button>
|
||||||
</ButtonGroup>
|
</ButtonGroup>
|
||||||
</Box>
|
</Box>
|
||||||
)}
|
)}
|
||||||
<TextInput
|
<TextInput
|
||||||
type="text"
|
type="text"
|
||||||
@@ -368,7 +387,12 @@ const CreateInfrastructureMonitor = () => {
|
|||||||
</ConfigBox>
|
</ConfigBox>
|
||||||
<ConfigBox>
|
<ConfigBox>
|
||||||
<Box>
|
<Box>
|
||||||
<Typography component="h2">{t("distributedUptimeCreateIncidentNotification")}</Typography>
|
<Typography
|
||||||
|
component="h2"
|
||||||
|
variant="h2"
|
||||||
|
>
|
||||||
|
{t("distributedUptimeCreateIncidentNotification")}
|
||||||
|
</Typography>
|
||||||
<Typography component="p">
|
<Typography component="p">
|
||||||
{t("distributedUptimeCreateIncidentDescription")}
|
{t("distributedUptimeCreateIncidentDescription")}
|
||||||
</Typography>
|
</Typography>
|
||||||
@@ -385,7 +409,12 @@ const CreateInfrastructureMonitor = () => {
|
|||||||
</ConfigBox>
|
</ConfigBox>
|
||||||
<ConfigBox>
|
<ConfigBox>
|
||||||
<Box>
|
<Box>
|
||||||
<Typography component="h2">{t("infrastructureCustomizeAlerts")}</Typography>
|
<Typography
|
||||||
|
component="h2"
|
||||||
|
variant="h2"
|
||||||
|
>
|
||||||
|
{t("infrastructureCustomizeAlerts")}
|
||||||
|
</Typography>
|
||||||
<Typography component="p">
|
<Typography component="p">
|
||||||
{t("infrastructureAlertNotificationDescription")}
|
{t("infrastructureAlertNotificationDescription")}
|
||||||
</Typography>
|
</Typography>
|
||||||
@@ -432,7 +461,12 @@ const CreateInfrastructureMonitor = () => {
|
|||||||
</ConfigBox>
|
</ConfigBox>
|
||||||
<ConfigBox>
|
<ConfigBox>
|
||||||
<Box>
|
<Box>
|
||||||
<Typography component="h2">{t("distributedUptimeCreateAdvancedSettings")}</Typography>
|
<Typography
|
||||||
|
component="h2"
|
||||||
|
variant="h2"
|
||||||
|
>
|
||||||
|
{t("distributedUptimeCreateAdvancedSettings")}
|
||||||
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
<Stack gap={theme.spacing(12)}>
|
<Stack gap={theme.spacing(12)}>
|
||||||
<Select
|
<Select
|
||||||
@@ -455,7 +489,7 @@ const CreateInfrastructureMonitor = () => {
|
|||||||
onClick={handleCreateInfrastructureMonitor}
|
onClick={handleCreateInfrastructureMonitor}
|
||||||
loading={monitorState?.isLoading}
|
loading={monitorState?.isLoading}
|
||||||
>
|
>
|
||||||
{t(isCreate? "infrastructureCreateMonitor": "infrastructureEditMonitor")}
|
{t(isCreate ? "infrastructureCreateMonitor" : "infrastructureEditMonitor")}
|
||||||
</Button>
|
</Button>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|||||||
@@ -16,8 +16,8 @@ const Gauge = ({ value, heading, metricOne, valueOne, metricTwo, valueTwo }) =>
|
|||||||
mb: theme.spacing(2),
|
mb: theme.spacing(2),
|
||||||
mt: theme.spacing(2),
|
mt: theme.spacing(2),
|
||||||
pr: theme.spacing(2),
|
pr: theme.spacing(2),
|
||||||
textAlign: 'right'
|
textAlign: "right",
|
||||||
}
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<BaseContainer>
|
<BaseContainer>
|
||||||
@@ -32,14 +32,19 @@ const Gauge = ({ value, heading, metricOne, valueOne, metricTwo, valueTwo }) =>
|
|||||||
flexDirection: "column",
|
flexDirection: "column",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
width: "100%",
|
width: "100%",
|
||||||
backgroundColor: theme.palette.gradient.color1
|
backgroundColor: theme.palette.gradient.color1,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<CustomGauge
|
<CustomGauge
|
||||||
progress={value}
|
progress={value}
|
||||||
radius={100}
|
radius={100}
|
||||||
/>
|
/>
|
||||||
<Typography component="h2" sx={{ fontWeight: 600 }}>{heading}</Typography>
|
<Typography
|
||||||
|
component="h2"
|
||||||
|
sx={{ fontWeight: 600 }}
|
||||||
|
>
|
||||||
|
{heading}
|
||||||
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
|
|||||||
@@ -2,11 +2,10 @@ import { useEffect, useState } from "react";
|
|||||||
import { networkService } from "../../../../main";
|
import { networkService } from "../../../../main";
|
||||||
|
|
||||||
const useHardwareMonitorsFetch = ({ monitorId, dateRange }) => {
|
const useHardwareMonitorsFetch = ({ monitorId, dateRange }) => {
|
||||||
|
|
||||||
// Abort early if creating monitor
|
// Abort early if creating monitor
|
||||||
if (!monitorId) {
|
if (!monitorId) {
|
||||||
return { monitor: undefined, isLoading: false, networkError: undefined };
|
return { monitor: undefined, isLoading: false, networkError: undefined };
|
||||||
}
|
}
|
||||||
const [isLoading, setIsLoading] = useState(true);
|
const [isLoading, setIsLoading] = useState(true);
|
||||||
const [networkError, setNetworkError] = useState(false);
|
const [networkError, setNetworkError] = useState(false);
|
||||||
const [monitor, setMonitor] = useState(undefined);
|
const [monitor, setMonitor] = useState(undefined);
|
||||||
|
|||||||
@@ -53,10 +53,10 @@ const Filter = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
m: theme.spacing(2),
|
m: theme.spacing(2),
|
||||||
ml: theme.spacing(4),
|
ml: theme.spacing(4),
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<FilterHeader
|
<FilterHeader
|
||||||
header={t("status")}
|
header={t("status")}
|
||||||
@@ -80,10 +80,10 @@ const Filter = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
Filter.propTypes = {
|
Filter.propTypes = {
|
||||||
selectedStatus: PropTypes.arrayOf(PropTypes.string),
|
selectedStatus: PropTypes.arrayOf(PropTypes.string),
|
||||||
setSelectedStatus: PropTypes.func.isRequired,
|
setSelectedStatus: PropTypes.func.isRequired,
|
||||||
setToFilterStatus: PropTypes.func.isRequired,
|
setToFilterStatus: PropTypes.func.isRequired,
|
||||||
handleReset: PropTypes.func.isRequired,
|
handleReset: PropTypes.func.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Filter;
|
export default Filter;
|
||||||
@@ -98,9 +98,6 @@ const MonitorsTable = ({ shouldRender, monitors, isAdmin, handleActionMenuDelete
|
|||||||
const mem = (monitor?.checks[0]?.memory.usage_percent ?? 0) * 100;
|
const mem = (monitor?.checks[0]?.memory.usage_percent ?? 0) * 100;
|
||||||
const disk = (monitor?.checks[0]?.disk[0]?.usage_percent ?? 0) * 100;
|
const disk = (monitor?.checks[0]?.disk[0]?.usage_percent ?? 0) * 100;
|
||||||
const status = determineState(monitor);
|
const status = determineState(monitor);
|
||||||
const uptimePercentage = ((monitor?.uptimePercentage ?? 0) * 100)
|
|
||||||
.toFixed(2)
|
|
||||||
.toString();
|
|
||||||
const percentageColor =
|
const percentageColor =
|
||||||
monitor.uptimePercentage < 0.25
|
monitor.uptimePercentage < 0.25
|
||||||
? theme.palette.error.main
|
? theme.palette.error.main
|
||||||
@@ -117,7 +114,6 @@ const MonitorsTable = ({ shouldRender, monitors, isAdmin, handleActionMenuDelete
|
|||||||
mem,
|
mem,
|
||||||
disk,
|
disk,
|
||||||
status,
|
status,
|
||||||
uptimePercentage,
|
|
||||||
percentageColor,
|
percentageColor,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -73,7 +73,7 @@ const InfrastructureMonitors = () => {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isLoading && typeof summary?.totalMonitors === "undefined" ) {
|
if (!isLoading && typeof summary?.totalMonitors === "undefined") {
|
||||||
return (
|
return (
|
||||||
<Fallback
|
<Fallback
|
||||||
vowelStart={true}
|
vowelStart={true}
|
||||||
|
|||||||
@@ -134,9 +134,7 @@ const Integrations = () => {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Typography component="h1">{t("integrations")}</Typography>
|
<Typography component="h1">{t("integrations")}</Typography>
|
||||||
<Typography mb={theme.spacing(12)}>
|
<Typography mb={theme.spacing(12)}>{t("integrationsPrism")}</Typography>
|
||||||
{t("integrationsPrism")}
|
|
||||||
</Typography>
|
|
||||||
<Grid
|
<Grid
|
||||||
container
|
container
|
||||||
spacing={theme.spacing(12)}
|
spacing={theme.spacing(12)}
|
||||||
|
|||||||
@@ -137,7 +137,7 @@ const CreateMaintenance = () => {
|
|||||||
const response = await networkService.getMonitorsByTeamId({
|
const response = await networkService.getMonitorsByTeamId({
|
||||||
teamId: user.teamId,
|
teamId: user.teamId,
|
||||||
limit: null,
|
limit: null,
|
||||||
types: ["http", "ping", "pagespeed"],
|
types: ["http", "ping", "pagespeed", "port"],
|
||||||
});
|
});
|
||||||
const monitors = response.data.data.monitors;
|
const monitors = response.data.data.monitors;
|
||||||
setMonitors(monitors);
|
setMonitors(monitors);
|
||||||
|
|||||||
@@ -310,7 +310,12 @@ const PageSpeedConfigure = () => {
|
|||||||
</Stack>
|
</Stack>
|
||||||
<ConfigBox>
|
<ConfigBox>
|
||||||
<Box>
|
<Box>
|
||||||
<Typography component="h2">{t("settingsGeneralSettings")}</Typography>
|
<Typography
|
||||||
|
component="h2"
|
||||||
|
variant="h2"
|
||||||
|
>
|
||||||
|
{t("settingsGeneralSettings")}
|
||||||
|
</Typography>
|
||||||
<Typography component="p">
|
<Typography component="p">
|
||||||
{t("pageSpeedConfigureSettingsDescription")}
|
{t("pageSpeedConfigureSettingsDescription")}
|
||||||
</Typography>
|
</Typography>
|
||||||
@@ -349,7 +354,12 @@ const PageSpeedConfigure = () => {
|
|||||||
</ConfigBox>
|
</ConfigBox>
|
||||||
<ConfigBox>
|
<ConfigBox>
|
||||||
<Box>
|
<Box>
|
||||||
<Typography component="h2">{t("distributedUptimeCreateIncidentNotification")}</Typography>
|
<Typography
|
||||||
|
component="h2"
|
||||||
|
variant="h2"
|
||||||
|
>
|
||||||
|
{t("distributedUptimeCreateIncidentNotification")}
|
||||||
|
</Typography>
|
||||||
<Typography component="p">
|
<Typography component="p">
|
||||||
{t("distributedUptimeCreateIncidentDescription")}
|
{t("distributedUptimeCreateIncidentDescription")}
|
||||||
</Typography>
|
</Typography>
|
||||||
@@ -394,9 +404,7 @@ const PageSpeedConfigure = () => {
|
|||||||
value=""
|
value=""
|
||||||
onChange={() => logger.warn("disabled")}
|
onChange={() => logger.warn("disabled")}
|
||||||
/>
|
/>
|
||||||
<Typography mt={theme.spacing(4)}>
|
<Typography mt={theme.spacing(4)}>{t("seperateEmails")}</Typography>
|
||||||
{t("seperateEmails")}
|
|
||||||
</Typography>
|
|
||||||
</Box>
|
</Box>
|
||||||
) : (
|
) : (
|
||||||
""
|
""
|
||||||
@@ -405,7 +413,12 @@ const PageSpeedConfigure = () => {
|
|||||||
</ConfigBox>
|
</ConfigBox>
|
||||||
<ConfigBox>
|
<ConfigBox>
|
||||||
<Box>
|
<Box>
|
||||||
<Typography component="h2">{t("distributedUptimeCreateAdvancedSettings")}</Typography>
|
<Typography
|
||||||
|
component="h2"
|
||||||
|
variant="h2"
|
||||||
|
>
|
||||||
|
{t("distributedUptimeCreateAdvancedSettings")}
|
||||||
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
<Stack gap={theme.spacing(20)}>
|
<Stack gap={theme.spacing(20)}>
|
||||||
<Select
|
<Select
|
||||||
|
|||||||
@@ -216,10 +216,13 @@ const CreatePageSpeed = () => {
|
|||||||
</Typography>
|
</Typography>
|
||||||
<ConfigBox>
|
<ConfigBox>
|
||||||
<Box>
|
<Box>
|
||||||
<Typography component="h2">{t("settingsGeneralSettings")}</Typography>
|
<Typography
|
||||||
<Typography component="p">
|
component="h2"
|
||||||
{t("distributedUptimeCreateSelectURL")}
|
variant="h2"
|
||||||
|
>
|
||||||
|
{t("settingsGeneralSettings")}
|
||||||
</Typography>
|
</Typography>
|
||||||
|
<Typography component="p">{t("distributedUptimeCreateSelectURL")}</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
<Stack gap={theme.spacing(15)}>
|
<Stack gap={theme.spacing(15)}>
|
||||||
<TextInput
|
<TextInput
|
||||||
@@ -251,7 +254,12 @@ const CreatePageSpeed = () => {
|
|||||||
</ConfigBox>
|
</ConfigBox>
|
||||||
<ConfigBox>
|
<ConfigBox>
|
||||||
<Box>
|
<Box>
|
||||||
<Typography component="h2">{t("distributedUptimeCreateChecks")}</Typography>
|
<Typography
|
||||||
|
component="h2"
|
||||||
|
variant="h2"
|
||||||
|
>
|
||||||
|
{t("distributedUptimeCreateChecks")}
|
||||||
|
</Typography>
|
||||||
<Typography component="p">
|
<Typography component="p">
|
||||||
{t("distributedUptimeCreateChecksDescription")}
|
{t("distributedUptimeCreateChecksDescription")}
|
||||||
</Typography>
|
</Typography>
|
||||||
@@ -300,7 +308,12 @@ const CreatePageSpeed = () => {
|
|||||||
</ConfigBox>
|
</ConfigBox>
|
||||||
<ConfigBox>
|
<ConfigBox>
|
||||||
<Box>
|
<Box>
|
||||||
<Typography component="h2">{t("distributedUptimeCreateIncidentNotification")}</Typography>
|
<Typography
|
||||||
|
component="h2"
|
||||||
|
variant="h2"
|
||||||
|
>
|
||||||
|
{t("distributedUptimeCreateIncidentNotification")}
|
||||||
|
</Typography>
|
||||||
<Typography component="p">
|
<Typography component="p">
|
||||||
{t("distributedUptimeCreateIncidentDescription")}
|
{t("distributedUptimeCreateIncidentDescription")}
|
||||||
</Typography>
|
</Typography>
|
||||||
@@ -320,7 +333,12 @@ const CreatePageSpeed = () => {
|
|||||||
</ConfigBox>
|
</ConfigBox>
|
||||||
<ConfigBox>
|
<ConfigBox>
|
||||||
<Box>
|
<Box>
|
||||||
<Typography component="h2">{t("distributedUptimeCreateAdvancedSettings")}</Typography>
|
<Typography
|
||||||
|
component="h2"
|
||||||
|
variant="h2"
|
||||||
|
>
|
||||||
|
{t("distributedUptimeCreateAdvancedSettings")}
|
||||||
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
<Stack gap={theme.spacing(12)}>
|
<Stack gap={theme.spacing(12)}>
|
||||||
<Select
|
<Select
|
||||||
|
|||||||
@@ -12,150 +12,158 @@ import ThemeSwitch from "../Components/ThemeSwitch";
|
|||||||
import LanguageSelector from "../Components/LanguageSelector";
|
import LanguageSelector from "../Components/LanguageSelector";
|
||||||
|
|
||||||
const ServerUnreachable = () => {
|
const ServerUnreachable = () => {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
// State for tracking connection check status
|
// State for tracking connection check status
|
||||||
const [isCheckingConnection, setIsCheckingConnection] = useState(false);
|
const [isCheckingConnection, setIsCheckingConnection] = useState(false);
|
||||||
|
|
||||||
const handleRetry = React.useCallback(async () => {
|
const handleRetry = React.useCallback(async () => {
|
||||||
setIsCheckingConnection(true);
|
setIsCheckingConnection(true);
|
||||||
try {
|
try {
|
||||||
// Try to connect to the backend with a simple API call
|
// Try to connect to the backend with a simple API call
|
||||||
// We'll use any lightweight endpoint that doesn't require authentication
|
// We'll use any lightweight endpoint that doesn't require authentication
|
||||||
await networkService.axiosInstance.get('/health', { timeout: 5000 });
|
await networkService.axiosInstance.get("/health", { timeout: 5000 });
|
||||||
|
|
||||||
// If successful, show toast and navigate to login page
|
// If successful, show toast and navigate to login page
|
||||||
createToast({
|
createToast({
|
||||||
body: t("backendReconnected", "Connection to server restored"),
|
body: t("backendReconnected", "Connection to server restored"),
|
||||||
});
|
});
|
||||||
navigate("/login");
|
navigate("/login");
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// If still unreachable, stay on this page and show toast
|
// If still unreachable, stay on this page and show toast
|
||||||
createToast({
|
createToast({
|
||||||
body: t("backendStillUnreachable", "Server is still unreachable"),
|
body: t("backendStillUnreachable", "Server is still unreachable"),
|
||||||
});
|
});
|
||||||
} finally {
|
} finally {
|
||||||
setIsCheckingConnection(false);
|
setIsCheckingConnection(false);
|
||||||
}
|
}
|
||||||
}, [navigate, t]);
|
}, [navigate, t]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack
|
<Stack
|
||||||
className="login-page auth"
|
className="login-page auth"
|
||||||
overflow="hidden"
|
overflow="hidden"
|
||||||
>
|
>
|
||||||
<Box
|
<Box
|
||||||
className="background-pattern-svg"
|
className="background-pattern-svg"
|
||||||
sx={{
|
sx={{
|
||||||
"& svg g g:last-of-type path": {
|
"& svg g g:last-of-type path": {
|
||||||
stroke: theme.palette.primary.lowContrast,
|
stroke: theme.palette.primary.lowContrast,
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Background style={{ width: "100%" }} />
|
<Background style={{ width: "100%" }} />
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
{/* Header with logo */}
|
{/* Header with logo */}
|
||||||
<Stack
|
<Stack
|
||||||
direction="row"
|
direction="row"
|
||||||
alignItems="center"
|
alignItems="center"
|
||||||
justifyContent="space-between"
|
justifyContent="space-between"
|
||||||
px={theme.spacing(12)}
|
px={theme.spacing(12)}
|
||||||
gap={theme.spacing(4)}
|
gap={theme.spacing(4)}
|
||||||
>
|
>
|
||||||
<Stack
|
<Stack
|
||||||
direction="row"
|
direction="row"
|
||||||
alignItems="center"
|
alignItems="center"
|
||||||
gap={theme.spacing(4)}
|
gap={theme.spacing(4)}
|
||||||
>
|
>
|
||||||
<Logo style={{ borderRadius: theme.shape.borderRadius }} />
|
<Logo style={{ borderRadius: theme.shape.borderRadius }} />
|
||||||
<Typography sx={{ userSelect: "none" }}>Checkmate</Typography>
|
<Typography sx={{ userSelect: "none" }}>Checkmate</Typography>
|
||||||
</Stack>
|
</Stack>
|
||||||
<Stack
|
<Stack
|
||||||
direction="row"
|
direction="row"
|
||||||
spacing={2}
|
spacing={2}
|
||||||
alignItems="center"
|
alignItems="center"
|
||||||
>
|
>
|
||||||
<LanguageSelector />
|
<LanguageSelector />
|
||||||
<ThemeSwitch />
|
<ThemeSwitch />
|
||||||
</Stack>
|
</Stack>
|
||||||
</Stack>
|
</Stack>
|
||||||
<Stack
|
<Stack
|
||||||
width="100%"
|
width="100%"
|
||||||
maxWidth={600}
|
maxWidth={600}
|
||||||
flex={1}
|
flex={1}
|
||||||
justifyContent="center"
|
justifyContent="center"
|
||||||
px={{ xs: theme.spacing(12), lg: theme.spacing(20) }}
|
px={{ xs: theme.spacing(12), lg: theme.spacing(20) }}
|
||||||
pb={theme.spacing(20)}
|
pb={theme.spacing(20)}
|
||||||
mx="auto"
|
mx="auto"
|
||||||
rowGap={theme.spacing(8)}
|
rowGap={theme.spacing(8)}
|
||||||
sx={{
|
sx={{
|
||||||
"& > .MuiStack-root": {
|
"& > .MuiStack-root": {
|
||||||
border: 1,
|
border: 1,
|
||||||
borderRadius: theme.spacing(5),
|
borderRadius: theme.spacing(5),
|
||||||
borderColor: theme.palette.primary.lowContrast,
|
borderColor: theme.palette.primary.lowContrast,
|
||||||
backgroundColor: theme.palette.primary.main,
|
backgroundColor: theme.palette.primary.main,
|
||||||
padding: {
|
padding: {
|
||||||
xs: theme.spacing(12),
|
xs: theme.spacing(12),
|
||||||
sm: theme.spacing(20),
|
sm: theme.spacing(20),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Stack spacing={theme.spacing(6)} alignItems="center">
|
<Stack
|
||||||
<Box sx={{
|
spacing={theme.spacing(6)}
|
||||||
width: theme.spacing(220),
|
alignItems="center"
|
||||||
mx: 'auto',
|
>
|
||||||
'& .alert.row-stack': {
|
<Box
|
||||||
width: '100%',
|
sx={{
|
||||||
alignItems: 'center',
|
width: theme.spacing(220),
|
||||||
gap: theme.spacing(3)
|
mx: "auto",
|
||||||
}
|
"& .alert.row-stack": {
|
||||||
}}>
|
width: "100%",
|
||||||
<Alert
|
alignItems: "center",
|
||||||
variant="error"
|
gap: theme.spacing(3),
|
||||||
body={t("backendUnreachable", "Server Unreachable")}
|
},
|
||||||
hasIcon={true}
|
}}
|
||||||
/>
|
>
|
||||||
</Box>
|
<Alert
|
||||||
<Box mt={theme.spacing(2)}>
|
variant="error"
|
||||||
<Typography
|
body={t("backendUnreachable", "Server Unreachable")}
|
||||||
variant="body1"
|
hasIcon={true}
|
||||||
align="center"
|
/>
|
||||||
color={theme.palette.primary.contrastTextSecondary}
|
</Box>
|
||||||
>
|
<Box mt={theme.spacing(2)}>
|
||||||
{t("backendUnreachableMessage", "The Checkmate server is not responding. Please check your deployment configuration or try again later.")}
|
<Typography
|
||||||
</Typography>
|
variant="body1"
|
||||||
</Box>
|
align="center"
|
||||||
<Box sx={{ mt: theme.spacing(4) }}>
|
color={theme.palette.primary.contrastTextSecondary}
|
||||||
<Button
|
>
|
||||||
variant="contained"
|
{t(
|
||||||
color="accent"
|
"backendUnreachableMessage",
|
||||||
onClick={handleRetry}
|
"The Checkmate server is not responding. Please check your deployment configuration or try again later."
|
||||||
disabled={isCheckingConnection}
|
)}
|
||||||
className="dashboard-style-button"
|
</Typography>
|
||||||
sx={{
|
</Box>
|
||||||
px: theme.spacing(6),
|
<Box sx={{ mt: theme.spacing(4) }}>
|
||||||
borderRadius: `${theme.shape.borderRadius}px !important`,
|
<Button
|
||||||
'&.MuiButtonBase-root': {
|
variant="contained"
|
||||||
borderRadius: `${theme.shape.borderRadius}px !important`
|
color="accent"
|
||||||
},
|
onClick={handleRetry}
|
||||||
'&.MuiButton-root': {
|
disabled={isCheckingConnection}
|
||||||
borderRadius: `${theme.shape.borderRadius}px !important`
|
className="dashboard-style-button"
|
||||||
}
|
sx={{
|
||||||
}}
|
px: theme.spacing(6),
|
||||||
>
|
borderRadius: `${theme.shape.borderRadius}px !important`,
|
||||||
{isCheckingConnection ?
|
"&.MuiButtonBase-root": {
|
||||||
t("retryingConnection", "Retrying Connection...") :
|
borderRadius: `${theme.shape.borderRadius}px !important`,
|
||||||
t("retryConnection", "Retry Connection")}
|
},
|
||||||
</Button>
|
"&.MuiButton-root": {
|
||||||
</Box>
|
borderRadius: `${theme.shape.borderRadius}px !important`,
|
||||||
</Stack>
|
},
|
||||||
</Stack>
|
}}
|
||||||
</Stack>
|
>
|
||||||
);
|
{isCheckingConnection
|
||||||
|
? t("retryingConnection", "Retrying Connection...")
|
||||||
|
: t("retryConnection", "Retry Connection")}
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
</Stack>
|
||||||
|
</Stack>
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default ServerUnreachable;
|
export default ServerUnreachable;
|
||||||
@@ -12,7 +12,12 @@ const SettingsAbout = () => {
|
|||||||
return (
|
return (
|
||||||
<ConfigBox>
|
<ConfigBox>
|
||||||
<Box>
|
<Box>
|
||||||
<Typography component="h1">{t("settingsAbout")}</Typography>
|
<Typography
|
||||||
|
component="h1"
|
||||||
|
variant="h2"
|
||||||
|
>
|
||||||
|
{t("settingsAbout")}
|
||||||
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
<Box>
|
<Box>
|
||||||
<Typography component="h2">Checkmate {2.1}</Typography>
|
<Typography component="h2">Checkmate {2.1}</Typography>
|
||||||
|
|||||||
@@ -23,7 +23,12 @@ const SettingsDemoMonitors = ({ isAdmin, HEADER_SX, handleChange, isLoading }) =
|
|||||||
<>
|
<>
|
||||||
<ConfigBox>
|
<ConfigBox>
|
||||||
<Box>
|
<Box>
|
||||||
<Typography component="h1">{t("settingsDemoMonitors")}</Typography>
|
<Typography
|
||||||
|
component="h1"
|
||||||
|
variant="h2"
|
||||||
|
>
|
||||||
|
{t("settingsDemoMonitors")}
|
||||||
|
</Typography>
|
||||||
<Typography sx={HEADER_SX}>{t("settingsDemoMonitorsDescription")}</Typography>
|
<Typography sx={HEADER_SX}>{t("settingsDemoMonitorsDescription")}</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
<Box>
|
<Box>
|
||||||
@@ -48,7 +53,12 @@ const SettingsDemoMonitors = ({ isAdmin, HEADER_SX, handleChange, isLoading }) =
|
|||||||
</ConfigBox>
|
</ConfigBox>
|
||||||
<ConfigBox>
|
<ConfigBox>
|
||||||
<Box>
|
<Box>
|
||||||
<Typography component="h1">{t("settingsSystemReset")}</Typography>
|
<Typography
|
||||||
|
component="h1"
|
||||||
|
variant="h2"
|
||||||
|
>
|
||||||
|
{t("settingsSystemReset")}
|
||||||
|
</Typography>
|
||||||
<Typography sx={{ mt: theme.spacing(2) }}>
|
<Typography sx={{ mt: theme.spacing(2) }}>
|
||||||
{t("settingsSystemResetDescription")}
|
{t("settingsSystemResetDescription")}
|
||||||
</Typography>
|
</Typography>
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import { useState } from "react";
|
|||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { PasswordEndAdornment } from "../../Components/Inputs/TextInput/Adornments";
|
import { PasswordEndAdornment } from "../../Components/Inputs/TextInput/Adornments";
|
||||||
import { useSendTestEmail } from "../../Hooks/useSendTestEmail";
|
import { useSendTestEmail } from "../../Hooks/useSendTestEmail";
|
||||||
|
import { createToast } from "../../Utils/toastUtils";
|
||||||
|
|
||||||
const SettingsEmail = ({
|
const SettingsEmail = ({
|
||||||
isAdmin,
|
isAdmin,
|
||||||
@@ -29,7 +30,7 @@ const SettingsEmail = ({
|
|||||||
const [hasBeenReset, setHasBeenReset] = useState(false);
|
const [hasBeenReset, setHasBeenReset] = useState(false);
|
||||||
|
|
||||||
// Network
|
// Network
|
||||||
const [isSending, error, sendTestEmail] = useSendTestEmail();
|
const [isSending, , sendTestEmail] = useSendTestEmail(); // Using empty placeholder for unused error variable
|
||||||
|
|
||||||
// Handlers
|
// Handlers
|
||||||
const handlePasswordChange = (e) => {
|
const handlePasswordChange = (e) => {
|
||||||
@@ -40,6 +41,33 @@ const SettingsEmail = ({
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle sending test email with current form values
|
||||||
|
*/
|
||||||
|
const handleSendTestEmail = () => {
|
||||||
|
// Collect current form values
|
||||||
|
const emailConfig = {
|
||||||
|
systemEmailHost: settingsData?.settings?.systemEmailHost,
|
||||||
|
systemEmailPort: settingsData?.settings?.systemEmailPort,
|
||||||
|
systemEmailUser: settingsData?.settings?.systemEmailUser,
|
||||||
|
systemEmailAddress: settingsData?.settings?.systemEmailAddress,
|
||||||
|
systemEmailPassword: password || settingsData?.settings?.systemEmailPassword,
|
||||||
|
systemEmailConnectionHost: settingsData?.settings?.systemEmailConnectionHost,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Basic validation
|
||||||
|
if (!emailConfig.systemEmailHost || !emailConfig.systemEmailPort) {
|
||||||
|
createToast({
|
||||||
|
body: t("settingsEmailRequiredFields", "Email host and port are required"),
|
||||||
|
variant: "error",
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send test email with current form values
|
||||||
|
sendTestEmail(emailConfig);
|
||||||
|
};
|
||||||
|
|
||||||
if (!isAdmin) {
|
if (!isAdmin) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -47,7 +75,12 @@ const SettingsEmail = ({
|
|||||||
return (
|
return (
|
||||||
<ConfigBox>
|
<ConfigBox>
|
||||||
<Box>
|
<Box>
|
||||||
<Typography component="h1">{t("settingsEmail")}</Typography>
|
<Typography
|
||||||
|
component="h1"
|
||||||
|
variant="h2"
|
||||||
|
>
|
||||||
|
{t("settingsEmail")}
|
||||||
|
</Typography>
|
||||||
<Typography sx={HEADER_SX}>{t("settingsEmailDescription")}</Typography>
|
<Typography sx={HEADER_SX}>{t("settingsEmailDescription")}</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
<Box>
|
<Box>
|
||||||
@@ -122,14 +155,23 @@ const SettingsEmail = ({
|
|||||||
</Button>
|
</Button>
|
||||||
</Box>
|
</Box>
|
||||||
)}
|
)}
|
||||||
|
<Box>
|
||||||
|
<Typography>{t("settingsEmailConnectionHost")}</Typography>
|
||||||
|
<TextInput
|
||||||
|
name="systemEmailConnectionHost"
|
||||||
|
placeholder="bluewavelabs.ca"
|
||||||
|
value={settingsData?.settings?.systemEmailConnectionHost ?? ""}
|
||||||
|
onChange={handleChange}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
<Box>
|
<Box>
|
||||||
<Button
|
<Button
|
||||||
variant="contained"
|
variant="contained"
|
||||||
color="accent"
|
color="accent"
|
||||||
loading={isSending}
|
loading={isSending}
|
||||||
onClick={sendTestEmail}
|
onClick={handleSendTestEmail}
|
||||||
>
|
>
|
||||||
Send test e-mail
|
{t("settingsTestEmail", "Send test e-mail")}
|
||||||
</Button>
|
</Button>
|
||||||
</Box>
|
</Box>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|||||||
@@ -41,7 +41,12 @@ const SettingsPagespeed = ({
|
|||||||
return (
|
return (
|
||||||
<ConfigBox>
|
<ConfigBox>
|
||||||
<Box>
|
<Box>
|
||||||
<Typography component="h1">{t("pageSpeedApiKeyFieldTitle")}</Typography>
|
<Typography
|
||||||
|
component="h1"
|
||||||
|
variant="h2"
|
||||||
|
>
|
||||||
|
{t("pageSpeedApiKeyFieldTitle")}
|
||||||
|
</Typography>
|
||||||
<Typography sx={HEADING_SX}>{t("pageSpeedApiKeyFieldDescription")}</Typography>
|
<Typography sx={HEADING_SX}>{t("pageSpeedApiKeyFieldDescription")}</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
<Stack gap={theme.spacing(20)}>
|
<Stack gap={theme.spacing(20)}>
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ const SettingsStats = ({ isAdmin, HEADING_SX, handleChange, settingsData, errors
|
|||||||
<Box>
|
<Box>
|
||||||
<Typography
|
<Typography
|
||||||
component="h1"
|
component="h1"
|
||||||
|
variant="h2"
|
||||||
sx={HEADING_SX}
|
sx={HEADING_SX}
|
||||||
>
|
>
|
||||||
{t("settingsHistoryAndMonitoring")}
|
{t("settingsHistoryAndMonitoring")}
|
||||||
|
|||||||
@@ -15,7 +15,12 @@ const SettingsTimeZone = ({ HEADING_SX, handleChange, timezone }) => {
|
|||||||
return (
|
return (
|
||||||
<ConfigBox>
|
<ConfigBox>
|
||||||
<Box>
|
<Box>
|
||||||
<Typography component="h1">{t("settingsGeneralSettings")}</Typography>
|
<Typography
|
||||||
|
component="h1"
|
||||||
|
variant="h2"
|
||||||
|
>
|
||||||
|
{t("settingsGeneralSettings")}
|
||||||
|
</Typography>
|
||||||
<Typography sx={HEADING_SX}>
|
<Typography sx={HEADING_SX}>
|
||||||
<Typography component="span">{t("settingsDisplayTimezone")}</Typography>-{" "}
|
<Typography component="span">{t("settingsDisplayTimezone")}</Typography>-{" "}
|
||||||
{t("settingsDisplayTimezoneDescription")}
|
{t("settingsDisplayTimezoneDescription")}
|
||||||
|
|||||||
@@ -16,7 +16,12 @@ const SettingsUI = ({ HEADING_SX, handleChange, mode, language }) => {
|
|||||||
return (
|
return (
|
||||||
<ConfigBox>
|
<ConfigBox>
|
||||||
<Box>
|
<Box>
|
||||||
<Typography component="h1">{t("settingsAppearance")}</Typography>
|
<Typography
|
||||||
|
component="h1"
|
||||||
|
variant="h2"
|
||||||
|
>
|
||||||
|
{t("settingsAppearance")}
|
||||||
|
</Typography>
|
||||||
<Typography sx={HEADING_SX}>{t("settingsAppearanceDescription")}</Typography>
|
<Typography sx={HEADING_SX}>{t("settingsAppearanceDescription")}</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
<Stack gap={theme.spacing(20)}>
|
<Stack gap={theme.spacing(20)}>
|
||||||
|
|||||||
@@ -0,0 +1,43 @@
|
|||||||
|
import Box from "@mui/material/Box";
|
||||||
|
import Stack from "@mui/material/Stack";
|
||||||
|
import Typography from "@mui/material/Typography";
|
||||||
|
import ConfigBox from "../../Components/ConfigBox";
|
||||||
|
import Select from "../../Components/Inputs/Select";
|
||||||
|
|
||||||
|
// Utils
|
||||||
|
import { useTheme } from "@emotion/react";
|
||||||
|
import { PropTypes } from "prop-types";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
|
const SettingsURL = ({ HEADING_SX, handleChange, showURL }) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const theme = useTheme();
|
||||||
|
return (
|
||||||
|
<ConfigBox>
|
||||||
|
<Box>
|
||||||
|
<Typography component="h1">{t("settingsURLTitle")}</Typography>
|
||||||
|
<Typography sx={HEADING_SX}>{t("settingsURLDescription")}</Typography>
|
||||||
|
</Box>
|
||||||
|
<Stack gap={theme.spacing(20)}>
|
||||||
|
<Select
|
||||||
|
name="showURL"
|
||||||
|
label={t("settingsURLSelectTitle")}
|
||||||
|
value={showURL}
|
||||||
|
onChange={handleChange}
|
||||||
|
items={[
|
||||||
|
{ _id: true, name: t("settingsURLEnabled") },
|
||||||
|
{ _id: false, name: t("settingURLDisabled") },
|
||||||
|
]}
|
||||||
|
></Select>
|
||||||
|
</Stack>
|
||||||
|
</ConfigBox>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
SettingsURL.propTypes = {
|
||||||
|
HEADING_SX: PropTypes.object,
|
||||||
|
handleChange: PropTypes.func,
|
||||||
|
showURL: PropTypes.bool,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SettingsURL;
|
||||||
@@ -3,6 +3,7 @@ import Typography from "@mui/material/Typography";
|
|||||||
import Breadcrumbs from "../../Components/Breadcrumbs";
|
import Breadcrumbs from "../../Components/Breadcrumbs";
|
||||||
import SettingsTimeZone from "./SettingsTimeZone";
|
import SettingsTimeZone from "./SettingsTimeZone";
|
||||||
import SettingsUI from "./SettingsUI";
|
import SettingsUI from "./SettingsUI";
|
||||||
|
import SettingsURL from "./SettingsURL";
|
||||||
import SettingsPagespeed from "./SettingsPagespeed";
|
import SettingsPagespeed from "./SettingsPagespeed";
|
||||||
import SettingsDemoMonitors from "./SettingsDemoMonitors";
|
import SettingsDemoMonitors from "./SettingsDemoMonitors";
|
||||||
import SettingsAbout from "./SettingsAbout";
|
import SettingsAbout from "./SettingsAbout";
|
||||||
@@ -15,7 +16,7 @@ import { useState } from "react";
|
|||||||
import { useTheme } from "@emotion/react";
|
import { useTheme } from "@emotion/react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { useSelector, useDispatch } from "react-redux";
|
import { useSelector, useDispatch } from "react-redux";
|
||||||
import { setTimezone, setMode, setLanguage } from "../../Features/UI/uiSlice";
|
import { setTimezone, setMode, setLanguage, setShowURL } from "../../Features/UI/uiSlice";
|
||||||
import SettingsStats from "./SettingsStats";
|
import SettingsStats from "./SettingsStats";
|
||||||
import {
|
import {
|
||||||
deleteMonitorChecksByTeamId,
|
deleteMonitorChecksByTeamId,
|
||||||
@@ -31,7 +32,7 @@ const BREADCRUMBS = [{ name: `Settings`, path: "/settings" }];
|
|||||||
|
|
||||||
const Settings = () => {
|
const Settings = () => {
|
||||||
// Redux state
|
// Redux state
|
||||||
const { mode, language, timezone } = useSelector((state) => state.ui);
|
const { mode, language, timezone, showURL } = useSelector((state) => state.ui);
|
||||||
const { user } = useSelector((state) => state.auth);
|
const { user } = useSelector((state) => state.auth);
|
||||||
|
|
||||||
// Local state
|
// Local state
|
||||||
@@ -57,6 +58,11 @@ const Settings = () => {
|
|||||||
const handleChange = async (e) => {
|
const handleChange = async (e) => {
|
||||||
const { name, value } = e.target;
|
const { name, value } = e.target;
|
||||||
|
|
||||||
|
// Special case for showURL until handled properly in the backend
|
||||||
|
if (name === "showURL") {
|
||||||
|
dispatch(setShowURL(value));
|
||||||
|
return;
|
||||||
|
}
|
||||||
// Build next state early
|
// Build next state early
|
||||||
const newSettingsData = {
|
const newSettingsData = {
|
||||||
...settingsData,
|
...settingsData,
|
||||||
@@ -127,6 +133,7 @@ const Settings = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleSave = () => {
|
const handleSave = () => {
|
||||||
|
console.log(settingsData.settings);
|
||||||
const { error } = settingsValidation.validate(settingsData.settings, {
|
const { error } = settingsValidation.validate(settingsData.settings, {
|
||||||
abortEarly: false,
|
abortEarly: false,
|
||||||
});
|
});
|
||||||
@@ -164,6 +171,11 @@ const Settings = () => {
|
|||||||
setSettingsData={setSettingsData}
|
setSettingsData={setSettingsData}
|
||||||
isApiKeySet={settingsData?.pagespeedKeySet ?? false}
|
isApiKeySet={settingsData?.pagespeedKeySet ?? false}
|
||||||
/>
|
/>
|
||||||
|
<SettingsURL
|
||||||
|
HEADING_SX={HEADING_SX}
|
||||||
|
handleChange={handleChange}
|
||||||
|
showURL={showURL}
|
||||||
|
/>
|
||||||
<SettingsStats
|
<SettingsStats
|
||||||
isAdmin={isAdmin}
|
isAdmin={isAdmin}
|
||||||
HEADING_SX={HEADING_SX}
|
HEADING_SX={HEADING_SX}
|
||||||
|
|||||||
@@ -9,7 +9,12 @@ const ConfigStack = ({ title, description, children }) => {
|
|||||||
return (
|
return (
|
||||||
<ConfigBox>
|
<ConfigBox>
|
||||||
<Stack gap={theme.spacing(6)}>
|
<Stack gap={theme.spacing(6)}>
|
||||||
<Typography component="h2">{title}</Typography>
|
<Typography
|
||||||
|
component="h2"
|
||||||
|
variant="h2"
|
||||||
|
>
|
||||||
|
{title}
|
||||||
|
</Typography>
|
||||||
<Typography component="p">{description}</Typography>
|
<Typography component="p">{description}</Typography>
|
||||||
</Stack>
|
</Stack>
|
||||||
{children}
|
{children}
|
||||||
|
|||||||
@@ -34,10 +34,13 @@ const TabSettings = ({
|
|||||||
<Stack gap={theme.spacing(10)}>
|
<Stack gap={theme.spacing(10)}>
|
||||||
<ConfigBox>
|
<ConfigBox>
|
||||||
<Stack>
|
<Stack>
|
||||||
<Typography component="h2">{t("access")}</Typography>
|
<Typography
|
||||||
<Typography component="p">
|
component="h2"
|
||||||
{t("statusPageCreateSettings")}
|
variant="h2"
|
||||||
|
>
|
||||||
|
{t("access")}
|
||||||
</Typography>
|
</Typography>
|
||||||
|
<Typography component="p">{t("statusPageCreateSettings")}</Typography>
|
||||||
</Stack>
|
</Stack>
|
||||||
<Stack gap={theme.spacing(18)}>
|
<Stack gap={theme.spacing(18)}>
|
||||||
<Checkbox
|
<Checkbox
|
||||||
@@ -51,7 +54,12 @@ const TabSettings = ({
|
|||||||
</ConfigBox>
|
</ConfigBox>
|
||||||
<ConfigBox>
|
<ConfigBox>
|
||||||
<Stack gap={theme.spacing(6)}>
|
<Stack gap={theme.spacing(6)}>
|
||||||
<Typography component="h2">{t("basicInformation")}</Typography>
|
<Typography
|
||||||
|
component="h2"
|
||||||
|
variant="h2"
|
||||||
|
>
|
||||||
|
{t("basicInformation")}
|
||||||
|
</Typography>
|
||||||
<Typography component="p">
|
<Typography component="p">
|
||||||
{t("statusPageCreateBasicInfoDescription")}
|
{t("statusPageCreateBasicInfoDescription")}
|
||||||
</Typography>
|
</Typography>
|
||||||
@@ -82,7 +90,12 @@ const TabSettings = ({
|
|||||||
</ConfigBox>
|
</ConfigBox>
|
||||||
<ConfigBox>
|
<ConfigBox>
|
||||||
<Stack gap={theme.spacing(6)}>
|
<Stack gap={theme.spacing(6)}>
|
||||||
<Typography component="h2">{t("timezone")}</Typography>
|
<Typography
|
||||||
|
component="h2"
|
||||||
|
variant="h2"
|
||||||
|
>
|
||||||
|
{t("timezone")}
|
||||||
|
</Typography>
|
||||||
<Typography component="p">
|
<Typography component="p">
|
||||||
{t("statusPageCreateSelectTimeZoneDescription")}
|
{t("statusPageCreateSelectTimeZoneDescription")}
|
||||||
</Typography>
|
</Typography>
|
||||||
@@ -100,7 +113,12 @@ const TabSettings = ({
|
|||||||
</ConfigBox>
|
</ConfigBox>
|
||||||
<ConfigBox>
|
<ConfigBox>
|
||||||
<Stack gap={theme.spacing(6)}>
|
<Stack gap={theme.spacing(6)}>
|
||||||
<Typography component="h2">{t("settingsAppearance")}</Typography>
|
<Typography
|
||||||
|
component="h2"
|
||||||
|
variant="h2"
|
||||||
|
>
|
||||||
|
{t("settingsAppearance")}
|
||||||
|
</Typography>
|
||||||
<Typography component="p">
|
<Typography component="p">
|
||||||
{t("statusPageCreateAppearanceDescription")}
|
{t("statusPageCreateAppearanceDescription")}
|
||||||
</Typography>
|
</Typography>
|
||||||
|
|||||||
@@ -23,7 +23,6 @@ const Tabs = ({
|
|||||||
setTab,
|
setTab,
|
||||||
TAB_LIST,
|
TAB_LIST,
|
||||||
}) => {
|
}) => {
|
||||||
|
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
return (
|
return (
|
||||||
<TabContext value={TAB_LIST[tab]}>
|
<TabContext value={TAB_LIST[tab]}>
|
||||||
@@ -63,7 +62,6 @@ const Tabs = ({
|
|||||||
setSelectedMonitors={setSelectedMonitors}
|
setSelectedMonitors={setSelectedMonitors}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
</TabContext>
|
</TabContext>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ const useMonitorsFetch = () => {
|
|||||||
const response = await networkService.getMonitorsByTeamId({
|
const response = await networkService.getMonitorsByTeamId({
|
||||||
teamId: user.teamId,
|
teamId: user.teamId,
|
||||||
limit: null, // donot return any checks for the monitors
|
limit: null, // donot return any checks for the monitors
|
||||||
types: ["http"], // status page is available only for the uptime type
|
types: ["http", "ping"], // status page is available for uptime and ping monitors
|
||||||
});
|
});
|
||||||
setMonitors(response.data.data.monitors);
|
setMonitors(response.data.data.monitors);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@@ -184,7 +184,6 @@ const CreateStatusPage = () => {
|
|||||||
size: null,
|
size: null,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
setForm((prev) => {
|
setForm((prev) => {
|
||||||
return {
|
return {
|
||||||
...prev,
|
...prev,
|
||||||
@@ -194,6 +193,8 @@ const CreateStatusPage = () => {
|
|||||||
monitors: statusPageMonitors.map((monitor) => monitor._id),
|
monitors: statusPageMonitors.map((monitor) => monitor._id),
|
||||||
color: statusPage?.color,
|
color: statusPage?.color,
|
||||||
logo: newLogo,
|
logo: newLogo,
|
||||||
|
showCharts: statusPage?.showCharts ?? true,
|
||||||
|
showUptimePercentage: statusPage?.showUptimePercentage ?? true,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
setSelectedMonitors(statusPageMonitors);
|
setSelectedMonitors(statusPageMonitors);
|
||||||
|
|||||||
@@ -8,9 +8,15 @@ import { StatusLabel } from "../../../../../Components/Label";
|
|||||||
import { useTheme } from "@mui/material/styles";
|
import { useTheme } from "@mui/material/styles";
|
||||||
import useUtils from "../../../../Uptime/Monitors/Hooks/useUtils";
|
import useUtils from "../../../../Uptime/Monitors/Hooks/useUtils";
|
||||||
import PropTypes from "prop-types";
|
import PropTypes from "prop-types";
|
||||||
|
|
||||||
|
import { useSelector } from "react-redux";
|
||||||
|
|
||||||
const MonitorsList = ({ isLoading = false, shouldRender = true, monitors = [] }) => {
|
const MonitorsList = ({ isLoading = false, shouldRender = true, monitors = [] }) => {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const { determineState } = useUtils();
|
const { determineState } = useUtils();
|
||||||
|
|
||||||
|
const { showURL } = useSelector((state) => state.ui);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{monitors?.map((monitor) => {
|
{monitors?.map((monitor) => {
|
||||||
@@ -27,6 +33,7 @@ const MonitorsList = ({ isLoading = false, shouldRender = true, monitors = [] })
|
|||||||
title={monitor.name}
|
title={monitor.name}
|
||||||
percentageColor={monitor.percentageColor}
|
percentageColor={monitor.percentageColor}
|
||||||
percentage={monitor.percentage}
|
percentage={monitor.percentage}
|
||||||
|
showURL={showURL}
|
||||||
/>
|
/>
|
||||||
<Stack
|
<Stack
|
||||||
direction="row"
|
direction="row"
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ import { useState } from "react";
|
|||||||
import { useParams } from "react-router-dom";
|
import { useParams } from "react-router-dom";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
const PublicStatus = () => {
|
const PublicStatus = () => {
|
||||||
const { url } = useParams();
|
const { url } = useParams();
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,8 @@ import { Button, Typography } from "@mui/material";
|
|||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import PropTypes from "prop-types";
|
import PropTypes from "prop-types";
|
||||||
|
|
||||||
const UploadFile = ({ onFileSelect }) => { // Changed prop to onFileSelect
|
const UploadFile = ({ onFileSelect }) => {
|
||||||
|
// Changed prop to onFileSelect
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const [file, setFile] = useState();
|
const [file, setFile] = useState();
|
||||||
const [error, setError] = useState("");
|
const [error, setError] = useState("");
|
||||||
@@ -22,13 +23,13 @@ const UploadFile = ({ onFileSelect }) => { // Changed prop to onFileSelect
|
|||||||
// Basic file validation
|
// Basic file validation
|
||||||
if (!selectedFile) return;
|
if (!selectedFile) return;
|
||||||
|
|
||||||
if (!selectedFile.name.endsWith('.csv')) {
|
if (!selectedFile.name.endsWith(".csv")) {
|
||||||
setError(t("bulkImport.invalidFileType"));
|
setError(t("bulkImport.invalidFileType"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
setFile(selectedFile);
|
setFile(selectedFile);
|
||||||
onFileSelect(selectedFile); // Pass the file directly to parent
|
onFileSelect(selectedFile); // Pass the file directly to parent
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -63,8 +64,7 @@ const UploadFile = ({ onFileSelect }) => { // Changed prop to onFileSelect
|
|||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
|
|
||||||
UploadFile.prototype = {
|
UploadFile.prototype = {
|
||||||
onFileSelect: PropTypes.func.isRequired,
|
onFileSelect: PropTypes.func.isRequired,
|
||||||
|
|||||||
@@ -61,7 +61,12 @@ const BulkImport = () => {
|
|||||||
</Typography>
|
</Typography>
|
||||||
<ConfigBox>
|
<ConfigBox>
|
||||||
<Box>
|
<Box>
|
||||||
<Typography component="h2">{t("bulkImport.selectFileTips")}</Typography>
|
<Typography
|
||||||
|
component="h2"
|
||||||
|
variant="h2"
|
||||||
|
>
|
||||||
|
{t("bulkImport.selectFileTips")}
|
||||||
|
</Typography>
|
||||||
<Typography component="p">
|
<Typography component="p">
|
||||||
<Trans
|
<Trans
|
||||||
i18nKey="bulkImport.selectFileDescription"
|
i18nKey="bulkImport.selectFileDescription"
|
||||||
|
|||||||
@@ -34,6 +34,10 @@ import SkeletonLayout from "./skeleton";
|
|||||||
import "./index.css";
|
import "./index.css";
|
||||||
import Dialog from "../../../Components/Dialog";
|
import Dialog from "../../../Components/Dialog";
|
||||||
import NotificationIntegrationModal from "../../../Components/NotificationIntegrationModal/Components/NotificationIntegrationModal";
|
import NotificationIntegrationModal from "../../../Components/NotificationIntegrationModal/Components/NotificationIntegrationModal";
|
||||||
|
import { usePauseMonitor } from "../../../Hooks/useMonitorControls";
|
||||||
|
import PauseOutlinedIcon from "@mui/icons-material/PauseOutlined";
|
||||||
|
import PlayArrowOutlinedIcon from "@mui/icons-material/PlayArrowOutlined";
|
||||||
|
import { useMonitorUtils } from "../../../Hooks/useMonitorUtils";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parses a URL string and returns a URL object.
|
* Parses a URL string and returns a URL object.
|
||||||
@@ -83,11 +87,19 @@ const Configure = () => {
|
|||||||
include: "ok",
|
include: "ok",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const [trigger, setTrigger] = useState(false);
|
||||||
|
const triggerUpdate = () => {
|
||||||
|
setTrigger(!trigger);
|
||||||
|
};
|
||||||
|
const [pauseMonitor, isPausing, error] = usePauseMonitor({
|
||||||
|
monitorId: monitor?._id,
|
||||||
|
triggerUpdate,
|
||||||
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fetchMonitor = async () => {
|
const fetchMonitor = async () => {
|
||||||
try {
|
try {
|
||||||
const action = await dispatch(getUptimeMonitorById({ monitorId }));
|
const action = await dispatch(getUptimeMonitorById({ monitorId }));
|
||||||
|
|
||||||
if (getUptimeMonitorById.fulfilled.match(action)) {
|
if (getUptimeMonitorById.fulfilled.match(action)) {
|
||||||
const monitor = action.payload.data;
|
const monitor = action.payload.data;
|
||||||
setMonitor(monitor);
|
setMonitor(monitor);
|
||||||
@@ -100,7 +112,7 @@ const Configure = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
fetchMonitor();
|
fetchMonitor();
|
||||||
}, [monitorId, navigate]);
|
}, [monitorId, navigate, trigger]);
|
||||||
|
|
||||||
const handleChange = (event, name) => {
|
const handleChange = (event, name) => {
|
||||||
let { checked, value, id } = event.target;
|
let { checked, value, id } = event.target;
|
||||||
@@ -158,23 +170,6 @@ const Configure = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handlePause = async () => {
|
|
||||||
try {
|
|
||||||
const action = await dispatch(pauseUptimeMonitor({ monitorId }));
|
|
||||||
if (pauseUptimeMonitor.fulfilled.match(action)) {
|
|
||||||
const monitor = action.payload.data;
|
|
||||||
setMonitor(monitor);
|
|
||||||
const state = action?.payload?.data.isActive === false ? "paused" : "resumed";
|
|
||||||
createToast({ body: `Monitor ${state} successfully.` });
|
|
||||||
} else if (pauseUptimeMonitor.rejected.match(action)) {
|
|
||||||
throw new Error(action.error.message);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
logger.error("Error pausing monitor: " + monitorId);
|
|
||||||
createToast({ body: "Failed to pause monitor" });
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleSubmit = async (event) => {
|
const handleSubmit = async (event) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
const action = await dispatch(updateUptimeMonitor({ monitor: monitor }));
|
const action = await dispatch(updateUptimeMonitor({ monitor: monitor }));
|
||||||
@@ -219,17 +214,7 @@ const Configure = () => {
|
|||||||
setIsNotificationModalOpen(false);
|
setIsNotificationModalOpen(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
const statusColor = {
|
const { determineState, statusColor } = useMonitorUtils();
|
||||||
true: theme.palette.success.main,
|
|
||||||
false: theme.palette.error.main,
|
|
||||||
undefined: theme.palette.warning.main,
|
|
||||||
};
|
|
||||||
|
|
||||||
const statusMsg = {
|
|
||||||
true: "Your site is up.",
|
|
||||||
false: "Your site is down.",
|
|
||||||
undefined: "Pending...",
|
|
||||||
};
|
|
||||||
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
@@ -274,7 +259,7 @@ const Configure = () => {
|
|||||||
gap={theme.spacing(2)}
|
gap={theme.spacing(2)}
|
||||||
>
|
>
|
||||||
<Tooltip
|
<Tooltip
|
||||||
title={statusMsg[monitor?.status ?? undefined]}
|
title={t(`statusMsg.${[determineState(monitor)]}`)}
|
||||||
disableInteractive
|
disableInteractive
|
||||||
slotProps={{
|
slotProps={{
|
||||||
popper: {
|
popper: {
|
||||||
@@ -290,7 +275,7 @@ const Configure = () => {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Box>
|
<Box>
|
||||||
<PulseDot color={statusColor[monitor?.status ?? undefined]} />
|
<PulseDot color={statusColor[determineState(monitor)]} />
|
||||||
</Box>
|
</Box>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
<Typography
|
<Typography
|
||||||
@@ -324,42 +309,26 @@ const Configure = () => {
|
|||||||
</Stack>
|
</Stack>
|
||||||
</Box>
|
</Box>
|
||||||
<Box
|
<Box
|
||||||
|
justifyContent="space-between"
|
||||||
sx={{
|
sx={{
|
||||||
alignSelf: "flex-end",
|
alignSelf: "flex-end",
|
||||||
ml: "auto",
|
ml: "auto",
|
||||||
|
display: "flex",
|
||||||
|
gap: theme.spacing(2),
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Button
|
<Button
|
||||||
variant="contained"
|
variant="contained"
|
||||||
color="secondary"
|
color="secondary"
|
||||||
loading={isLoading}
|
loading={isPausing}
|
||||||
sx={{
|
startIcon={
|
||||||
pl: theme.spacing(4),
|
monitor?.isActive ? <PauseOutlinedIcon /> : <PlayArrowOutlinedIcon />
|
||||||
pr: theme.spacing(6),
|
}
|
||||||
mr: theme.spacing(6),
|
onClick={() => {
|
||||||
"& svg": {
|
pauseMonitor();
|
||||||
mr: theme.spacing(2),
|
|
||||||
width: 22,
|
|
||||||
height: 22,
|
|
||||||
"& path": {
|
|
||||||
stroke: theme.palette.primary.contrastTextTertiary,
|
|
||||||
strokeWidth: 1.7,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}}
|
}}
|
||||||
onClick={handlePause}
|
|
||||||
>
|
>
|
||||||
{monitor?.isActive ? (
|
{monitor?.isActive ? t("pause") : t("resume")}
|
||||||
<>
|
|
||||||
<PauseIcon />
|
|
||||||
{t("pause")}
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
<ResumeIcon />
|
|
||||||
{t("resume")}
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
loading={isLoading}
|
loading={isLoading}
|
||||||
@@ -374,7 +343,12 @@ const Configure = () => {
|
|||||||
</Stack>
|
</Stack>
|
||||||
<ConfigBox>
|
<ConfigBox>
|
||||||
<Box>
|
<Box>
|
||||||
<Typography component="h2">{t("settingsGeneralSettings")}</Typography>
|
<Typography
|
||||||
|
component="h2"
|
||||||
|
variant="h2"
|
||||||
|
>
|
||||||
|
{t("settingsGeneralSettings")}
|
||||||
|
</Typography>
|
||||||
<Typography component="p">
|
<Typography component="p">
|
||||||
{t("distributedUptimeCreateSelectURL")}
|
{t("distributedUptimeCreateSelectURL")}
|
||||||
</Typography>
|
</Typography>
|
||||||
@@ -420,7 +394,10 @@ const Configure = () => {
|
|||||||
</ConfigBox>
|
</ConfigBox>
|
||||||
<ConfigBox>
|
<ConfigBox>
|
||||||
<Box>
|
<Box>
|
||||||
<Typography component="h2">
|
<Typography
|
||||||
|
component="h2"
|
||||||
|
variant="h2"
|
||||||
|
>
|
||||||
{t("distributedUptimeCreateIncidentNotification")}
|
{t("distributedUptimeCreateIncidentNotification")}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography component="p">
|
<Typography component="p">
|
||||||
@@ -488,7 +465,12 @@ const Configure = () => {
|
|||||||
</ConfigBox>
|
</ConfigBox>
|
||||||
<ConfigBox>
|
<ConfigBox>
|
||||||
<Box>
|
<Box>
|
||||||
<Typography component="h2">{t("ignoreTLSError")}</Typography>
|
<Typography
|
||||||
|
component="h2"
|
||||||
|
variant="h2"
|
||||||
|
>
|
||||||
|
{t("ignoreTLSError")}
|
||||||
|
</Typography>
|
||||||
<Typography component="p">{t("ignoreTLSErrorDescription")}</Typography>
|
<Typography component="p">{t("ignoreTLSErrorDescription")}</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
<Stack>
|
<Stack>
|
||||||
@@ -508,7 +490,10 @@ const Configure = () => {
|
|||||||
</ConfigBox>
|
</ConfigBox>
|
||||||
<ConfigBox>
|
<ConfigBox>
|
||||||
<Box>
|
<Box>
|
||||||
<Typography component="h2">
|
<Typography
|
||||||
|
component="h2"
|
||||||
|
variant="h2"
|
||||||
|
>
|
||||||
{t("distributedUptimeCreateAdvancedSettings")}
|
{t("distributedUptimeCreateAdvancedSettings")}
|
||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
|
|||||||
@@ -261,7 +261,12 @@ const CreateMonitor = () => {
|
|||||||
</Typography>
|
</Typography>
|
||||||
<ConfigBox>
|
<ConfigBox>
|
||||||
<Box>
|
<Box>
|
||||||
<Typography component="h2">{t("distributedUptimeCreateChecks")}</Typography>
|
<Typography
|
||||||
|
component="h2"
|
||||||
|
variant="h2"
|
||||||
|
>
|
||||||
|
{t("distributedUptimeCreateChecks")}
|
||||||
|
</Typography>
|
||||||
<Typography component="p">
|
<Typography component="p">
|
||||||
{t("distributedUptimeCreateChecksDescription")}
|
{t("distributedUptimeCreateChecksDescription")}
|
||||||
</Typography>
|
</Typography>
|
||||||
@@ -342,7 +347,12 @@ const CreateMonitor = () => {
|
|||||||
</ConfigBox>
|
</ConfigBox>
|
||||||
<ConfigBox>
|
<ConfigBox>
|
||||||
<Box>
|
<Box>
|
||||||
<Typography component="h2">{t("settingsGeneralSettings")}</Typography>
|
<Typography
|
||||||
|
component="h2"
|
||||||
|
variant="h2"
|
||||||
|
>
|
||||||
|
{t("settingsGeneralSettings")}
|
||||||
|
</Typography>
|
||||||
<Typography component="p">{t("uptimeCreateSelectURL")}</Typography>
|
<Typography component="p">{t("uptimeCreateSelectURL")}</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
<Stack gap={theme.spacing(15)}>
|
<Stack gap={theme.spacing(15)}>
|
||||||
@@ -386,7 +396,10 @@ const CreateMonitor = () => {
|
|||||||
</ConfigBox>
|
</ConfigBox>
|
||||||
<ConfigBox>
|
<ConfigBox>
|
||||||
<Box>
|
<Box>
|
||||||
<Typography component="h2">
|
<Typography
|
||||||
|
component="h2"
|
||||||
|
variant="h2"
|
||||||
|
>
|
||||||
{t("distributedUptimeCreateIncidentNotification")}
|
{t("distributedUptimeCreateIncidentNotification")}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography component="p">
|
<Typography component="p">
|
||||||
@@ -417,7 +430,12 @@ const CreateMonitor = () => {
|
|||||||
</ConfigBox>
|
</ConfigBox>
|
||||||
<ConfigBox>
|
<ConfigBox>
|
||||||
<Box>
|
<Box>
|
||||||
<Typography component="h2">{t("ignoreTLSError")}</Typography>
|
<Typography
|
||||||
|
component="h2"
|
||||||
|
variant="h2"
|
||||||
|
>
|
||||||
|
{t("ignoreTLSError")}
|
||||||
|
</Typography>
|
||||||
<Typography component="p">{t("ignoreTLSErrorDescription")}</Typography>
|
<Typography component="p">{t("ignoreTLSErrorDescription")}</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
<Stack>
|
<Stack>
|
||||||
@@ -437,7 +455,10 @@ const CreateMonitor = () => {
|
|||||||
</ConfigBox>
|
</ConfigBox>
|
||||||
<ConfigBox>
|
<ConfigBox>
|
||||||
<Box>
|
<Box>
|
||||||
<Typography component="h2">
|
<Typography
|
||||||
|
component="h2"
|
||||||
|
variant="h2"
|
||||||
|
>
|
||||||
{t("distributedUptimeCreateAdvancedSettings")}
|
{t("distributedUptimeCreateAdvancedSettings")}
|
||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
|
|||||||
@@ -75,7 +75,7 @@ const ChartBoxes = ({
|
|||||||
<Typography component="span">
|
<Typography component="span">
|
||||||
{hoveredUptimeData !== null
|
{hoveredUptimeData !== null
|
||||||
? Math.floor(hoveredUptimeData?.originalAvgResponseTime ?? 0)
|
? Math.floor(hoveredUptimeData?.originalAvgResponseTime ?? 0)
|
||||||
: Math.floor(monitorData?.groupedUptimePercentage ?? 0 * 100)}
|
: Math.floor((monitorData?.groupedUptimePercentage ?? 0) * 100)}
|
||||||
<Typography component="span">
|
<Typography component="span">
|
||||||
{hoveredUptimeData !== null ? " ms" : " %"}
|
{hoveredUptimeData !== null ? " ms" : " %"}
|
||||||
</Typography>
|
</Typography>
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
// Components
|
// Components
|
||||||
import Breadcrumbs from "../../../Components/Breadcrumbs";
|
import Breadcrumbs from "../../../Components/Breadcrumbs";
|
||||||
|
import MonitorDetailsControlHeader from "../../../Components/MonitorDetailsControlHeader";
|
||||||
import MonitorStatusHeader from "../../../Components/MonitorStatusHeader";
|
import MonitorStatusHeader from "../../../Components/MonitorStatusHeader";
|
||||||
import MonitorTimeFrameHeader from "../../../Components/MonitorTimeFrameHeader";
|
import MonitorTimeFrameHeader from "../../../Components/MonitorTimeFrameHeader";
|
||||||
import ChartBoxes from "./Components/ChartBoxes";
|
import ChartBoxes from "./Components/ChartBoxes";
|
||||||
@@ -36,9 +37,9 @@ const UptimeDetails = () => {
|
|||||||
|
|
||||||
// Local state
|
// Local state
|
||||||
const [dateRange, setDateRange] = useState("recent");
|
const [dateRange, setDateRange] = useState("recent");
|
||||||
|
|
||||||
const [page, setPage] = useState(0);
|
const [page, setPage] = useState(0);
|
||||||
const [rowsPerPage, setRowsPerPage] = useState(5);
|
const [rowsPerPage, setRowsPerPage] = useState(5);
|
||||||
|
const [trigger, setTrigger] = useState(false);
|
||||||
|
|
||||||
// Utils
|
// Utils
|
||||||
const dateFormat =
|
const dateFormat =
|
||||||
@@ -52,6 +53,7 @@ const UptimeDetails = () => {
|
|||||||
useFetchUptimeMonitorDetails({
|
useFetchUptimeMonitorDetails({
|
||||||
monitorId,
|
monitorId,
|
||||||
dateRange,
|
dateRange,
|
||||||
|
trigger,
|
||||||
});
|
});
|
||||||
|
|
||||||
const monitor = monitorData?.monitor;
|
const monitor = monitorData?.monitor;
|
||||||
@@ -73,6 +75,10 @@ const UptimeDetails = () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Handlers
|
// Handlers
|
||||||
|
const triggerUpdate = () => {
|
||||||
|
setTrigger(!trigger);
|
||||||
|
};
|
||||||
|
|
||||||
const handlePageChange = (_, newPage) => {
|
const handlePageChange = (_, newPage) => {
|
||||||
setPage(newPage);
|
setPage(newPage);
|
||||||
};
|
};
|
||||||
@@ -101,11 +107,12 @@ const UptimeDetails = () => {
|
|||||||
return (
|
return (
|
||||||
<Stack gap={theme.spacing(10)}>
|
<Stack gap={theme.spacing(10)}>
|
||||||
<Breadcrumbs list={BREADCRUMBS} />
|
<Breadcrumbs list={BREADCRUMBS} />
|
||||||
<MonitorStatusHeader
|
<MonitorDetailsControlHeader
|
||||||
path={"uptime"}
|
path={"uptime"}
|
||||||
isAdmin={isAdmin}
|
isAdmin={isAdmin}
|
||||||
shouldRender={!monitorIsLoading}
|
isLoading={monitorIsLoading}
|
||||||
monitor={monitor}
|
monitor={monitor}
|
||||||
|
triggerUpdate={triggerUpdate}
|
||||||
/>
|
/>
|
||||||
<GenericFallback>
|
<GenericFallback>
|
||||||
<Typography>{t("distributedUptimeDetailsNoMonitorHistory")}</Typography>
|
<Typography>{t("distributedUptimeDetailsNoMonitorHistory")}</Typography>
|
||||||
@@ -117,11 +124,12 @@ const UptimeDetails = () => {
|
|||||||
return (
|
return (
|
||||||
<Stack gap={theme.spacing(10)}>
|
<Stack gap={theme.spacing(10)}>
|
||||||
<Breadcrumbs list={BREADCRUMBS} />
|
<Breadcrumbs list={BREADCRUMBS} />
|
||||||
<MonitorStatusHeader
|
<MonitorDetailsControlHeader
|
||||||
path={"uptime"}
|
path={"uptime"}
|
||||||
isAdmin={isAdmin}
|
isAdmin={isAdmin}
|
||||||
isLoading={monitorIsLoading}
|
isLoading={monitorIsLoading}
|
||||||
monitor={monitor}
|
monitor={monitor}
|
||||||
|
triggerUpdate={triggerUpdate}
|
||||||
/>
|
/>
|
||||||
<UptimeStatusBoxes
|
<UptimeStatusBoxes
|
||||||
isLoading={monitorIsLoading}
|
isLoading={monitorIsLoading}
|
||||||
|
|||||||
@@ -150,7 +150,10 @@ const UptimeDataTable = ({
|
|||||||
id: "responseTime",
|
id: "responseTime",
|
||||||
content: t("responseTime"),
|
content: t("responseTime"),
|
||||||
render: (row) => (
|
render: (row) => (
|
||||||
<Box display="flex" justifyContent="center">
|
<Box
|
||||||
|
display="flex"
|
||||||
|
justifyContent="center"
|
||||||
|
>
|
||||||
<BarChart checks={row.monitor.checks.slice().reverse()} />
|
<BarChart checks={row.monitor.checks.slice().reverse()} />
|
||||||
</Box>
|
</Box>
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -1047,9 +1047,12 @@ class NetworkService {
|
|||||||
form.url && fd.append("url", form.url);
|
form.url && fd.append("url", form.url);
|
||||||
form.timezone && fd.append("timezone", form.timezone);
|
form.timezone && fd.append("timezone", form.timezone);
|
||||||
form.color && fd.append("color", form.color);
|
form.color && fd.append("color", form.color);
|
||||||
form.showCharts && fd.append("showCharts", form.showCharts);
|
if (form.showCharts !== undefined) {
|
||||||
form.showUptimePercentage &&
|
fd.append("showCharts", String(form.showCharts));
|
||||||
fd.append("showUptimePercentage", form.showUptimePercentage);
|
}
|
||||||
|
if (form.showUptimePercentage !== undefined) {
|
||||||
|
fd.append("showUptimePercentage", String(form.showUptimePercentage));
|
||||||
|
}
|
||||||
form.monitors &&
|
form.monitors &&
|
||||||
form.monitors.forEach((monitorId) => {
|
form.monitors.forEach((monitorId) => {
|
||||||
fd.append("monitors[]", monitorId);
|
fd.append("monitors[]", monitorId);
|
||||||
@@ -1154,11 +1157,32 @@ class NetworkService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ************************************
|
// ************************************
|
||||||
// Send tes t email
|
// Send test email
|
||||||
// ************************************
|
// ************************************
|
||||||
async sendTestEmail(config) {
|
async sendTestEmail(config) {
|
||||||
const { to } = config;
|
// Extract recipient and email configuration
|
||||||
return this.axiosInstance.post(`/monitors/test-email`, { to });
|
const { to, emailConfig } = config;
|
||||||
|
|
||||||
|
// If emailConfig is provided, use the new endpoint with direct parameters
|
||||||
|
if (emailConfig) {
|
||||||
|
return this.axiosInstance.post(`/settings/test-email`, {
|
||||||
|
to,
|
||||||
|
systemEmailHost: emailConfig.systemEmailHost,
|
||||||
|
systemEmailPort: emailConfig.systemEmailPort,
|
||||||
|
systemEmailAddress: emailConfig.systemEmailAddress,
|
||||||
|
systemEmailPassword: emailConfig.systemEmailPassword,
|
||||||
|
// Only include these if they are present
|
||||||
|
...(emailConfig.systemEmailConnectionHost && {
|
||||||
|
systemEmailConnectionHost: emailConfig.systemEmailConnectionHost,
|
||||||
|
}),
|
||||||
|
...(emailConfig.systemEmailUser && {
|
||||||
|
systemEmailUser: emailConfig.systemEmailUser,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback to original behavior for backward compatibility
|
||||||
|
return this.axiosInstance.post(`/settings/test-email`, { to });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -167,7 +167,7 @@ const Greeting = ({ type = "" }) => {
|
|||||||
fontSize="inherit"
|
fontSize="inherit"
|
||||||
color={theme.palette.primary.contrastTextTertiary}
|
color={theme.palette.primary.contrastTextTertiary}
|
||||||
>
|
>
|
||||||
{t("greeting.prepend", { defaultValue: prepend })}, {" "}
|
{t("greeting.prepend", { defaultValue: prepend })},{" "}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography
|
<Typography
|
||||||
component="span"
|
component="span"
|
||||||
@@ -183,7 +183,8 @@ const Greeting = ({ type = "" }) => {
|
|||||||
lineHeight={1}
|
lineHeight={1}
|
||||||
color={theme.palette.primary.contrastTextTertiary}
|
color={theme.palette.primary.contrastTextTertiary}
|
||||||
>
|
>
|
||||||
{t("greeting.append", { defaultValue: append })} — {t("greeting.overview", { type: t(`menu.${type}`) })}
|
{t("greeting.append", { defaultValue: append })} —{" "}
|
||||||
|
{t("greeting.overview", { type: t(`menu.${type}`) })}
|
||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { initReactI18next } from "react-i18next";
|
|||||||
import { setLanguage } from "../Features/UI/uiSlice";
|
import { setLanguage } from "../Features/UI/uiSlice";
|
||||||
import store from "../store";
|
import store from "../store";
|
||||||
|
|
||||||
const primaryLanguage = "gb";
|
const primaryLanguage = "en";
|
||||||
|
|
||||||
const translations = import.meta.glob("../locales/*.json", { eager: true });
|
const translations = import.meta.glob("../locales/*.json", { eager: true });
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,6 @@
|
|||||||
* @returns the Update Errors with the specific field with id being either removed or updated
|
* @returns the Update Errors with the specific field with id being either removed or updated
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
const buildErrors = (prev, id, error) => {
|
const buildErrors = (prev, id, error) => {
|
||||||
const updatedErrors = { ...prev };
|
const updatedErrors = { ...prev };
|
||||||
if (error && id == error.details[0].path) {
|
if (error && id == error.details[0].path) {
|
||||||
@@ -69,7 +68,7 @@ const hasValidationErrors = (form, validation, setErrors) => {
|
|||||||
"_id",
|
"_id",
|
||||||
"__v",
|
"__v",
|
||||||
"createdAt",
|
"createdAt",
|
||||||
"updatedAt"
|
"updatedAt",
|
||||||
].includes(err.path[0])
|
].includes(err.path[0])
|
||||||
) {
|
) {
|
||||||
newErrors[err.path[0]] = err.message ?? "Validation error";
|
newErrors[err.path[0]] = err.message ?? "Validation error";
|
||||||
|
|||||||
@@ -23,7 +23,8 @@ const lastnameSchema = joi
|
|||||||
.messages({
|
.messages({
|
||||||
"string.empty": "Surname is required",
|
"string.empty": "Surname is required",
|
||||||
"string.max": "Surname must be less than 50 characters",
|
"string.max": "Surname must be less than 50 characters",
|
||||||
"string.pattern.base": "Surname must contain only letters, spaces, apostrophes, or hyphens"
|
"string.pattern.base":
|
||||||
|
"Surname must contain only letters, spaces, apostrophes, or hyphens",
|
||||||
});
|
});
|
||||||
|
|
||||||
const passwordSchema = joi
|
const passwordSchema = joi
|
||||||
@@ -103,67 +104,69 @@ const credentials = joi.object({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const monitorValidation = joi.object({
|
const monitorValidation = joi.object({
|
||||||
url: joi
|
url: joi.when("type", {
|
||||||
.when("type", {
|
is: "docker",
|
||||||
is: "docker",
|
then: joi
|
||||||
then: joi
|
.string()
|
||||||
.string()
|
.trim()
|
||||||
.trim()
|
.regex(/^[a-z0-9]{64}$/)
|
||||||
.regex(/^[a-z0-9]{64}$/),
|
.messages({
|
||||||
otherwise: joi
|
"string.empty": "This field is required.",
|
||||||
.string()
|
"string.pattern.base": "Please enter a valid 64-character Docker container ID.",
|
||||||
.trim()
|
}),
|
||||||
.custom((value, helpers) => {
|
otherwise: joi
|
||||||
// Regex from https://gist.github.com/dperini/729294
|
.string()
|
||||||
var urlRegex = new RegExp(
|
.trim()
|
||||||
"^" +
|
.custom((value, helpers) => {
|
||||||
// protocol identifier (optional)
|
// Regex from https://gist.github.com/dperini/729294
|
||||||
// short syntax // still required
|
var urlRegex = new RegExp(
|
||||||
"(?:(?:https?|ftp):\\/\\/)?" +
|
"^" +
|
||||||
// user:pass BasicAuth (optional)
|
// protocol identifier (optional)
|
||||||
"(?:" +
|
// short syntax // still required
|
||||||
// IP address dotted notation octets
|
"(?:(?:https?|ftp):\\/\\/)?" +
|
||||||
// excludes loopback network 0.0.0.0
|
// user:pass BasicAuth (optional)
|
||||||
// excludes reserved space >= 224.0.0.0
|
"(?:" +
|
||||||
// excludes network & broadcast addresses
|
// IP address dotted notation octets
|
||||||
// (first & last IP address of each class)
|
// excludes loopback network 0.0.0.0
|
||||||
"(?:[1-9]\\d?|1\\d\\d|2[01]\\d|22[0-3])" +
|
// excludes reserved space >= 224.0.0.0
|
||||||
"(?:\\.(?:1?\\d{1,2}|2[0-4]\\d|25[0-5])){2}" +
|
// excludes network & broadcast addresses
|
||||||
"(?:\\.(?:[1-9]\\d?|1\\d\\d|2[0-4]\\d|25[0-4]))" +
|
// (first & last IP address of each class)
|
||||||
"|" +
|
"(?:[1-9]\\d?|1\\d\\d|2[01]\\d|22[0-3])" +
|
||||||
// host & domain names, may end with dot
|
"(?:\\.(?:1?\\d{1,2}|2[0-4]\\d|25[0-5])){2}" +
|
||||||
// can be replaced by a shortest alternative
|
"(?:\\.(?:[1-9]\\d?|1\\d\\d|2[0-4]\\d|25[0-4]))" +
|
||||||
// (?![-_])(?:[-\\w\\u00a1-\\uffff]{0,63}[^-_]\\.)+
|
"|" +
|
||||||
"(?:" +
|
// host & domain names, may end with dot
|
||||||
"(?:" +
|
// can be replaced by a shortest alternative
|
||||||
"[a-z0-9\\u00a1-\\uffff]" +
|
// (?![-_])(?:[-\\w\\u00a1-\\uffff]{0,63}[^-_]\\.)+
|
||||||
"[a-z0-9\\u00a1-\\uffff_-]{0,62}" +
|
"(?:" +
|
||||||
")?" +
|
"(?:" +
|
||||||
"[a-z0-9\\u00a1-\\uffff]\\." +
|
"[a-z0-9\\u00a1-\\uffff]" +
|
||||||
")+" +
|
"[a-z0-9\\u00a1-\\uffff_-]{0,62}" +
|
||||||
// TLD identifier name, may end with dot
|
")?" +
|
||||||
"(?:[a-z\\u00a1-\\uffff]{2,}\\.?)" +
|
"[a-z0-9\\u00a1-\\uffff]\\." +
|
||||||
")" +
|
")+" +
|
||||||
// port number (optional)
|
// TLD identifier name, may end with dot
|
||||||
"(?::\\d{2,5})?" +
|
"(?:[a-z\\u00a1-\\uffff]{2,}\\.?)" +
|
||||||
// resource path (optional)
|
")" +
|
||||||
"(?:[/?#]\\S*)?" +
|
// port number (optional)
|
||||||
"$",
|
"(?::\\d{2,5})?" +
|
||||||
"i"
|
// resource path (optional)
|
||||||
);
|
"(?:[/?#]\\S*)?" +
|
||||||
if (!urlRegex.test(value)) {
|
"$",
|
||||||
return helpers.error("string.invalidUrl");
|
"i"
|
||||||
}
|
);
|
||||||
|
if (!urlRegex.test(value)) {
|
||||||
|
return helpers.error("string.invalidUrl");
|
||||||
|
}
|
||||||
|
|
||||||
return value;
|
return value;
|
||||||
}),
|
})
|
||||||
})
|
.messages({
|
||||||
.messages({
|
"string.empty": "This field is required.",
|
||||||
"string.empty": "This field is required.",
|
"string.uri": "The URL you provided is not valid.",
|
||||||
"string.uri": "The URL you provided is not valid.",
|
"string.invalidUrl": "Please enter a valid URL with optional port",
|
||||||
"string.invalidUrl": "Please enter a valid URL with optional port",
|
}),
|
||||||
"string.pattern.base": "Please enter a valid container ID.",
|
}),
|
||||||
}),
|
|
||||||
port: joi
|
port: joi
|
||||||
.number()
|
.number()
|
||||||
.integer()
|
.integer()
|
||||||
@@ -171,7 +174,7 @@ const monitorValidation = joi.object({
|
|||||||
.max(65535)
|
.max(65535)
|
||||||
.when("type", {
|
.when("type", {
|
||||||
is: "port",
|
is: "port",
|
||||||
then: joi.required().messages({
|
then: joi.number().messages({
|
||||||
"number.base": "Port must be a number.",
|
"number.base": "Port must be a number.",
|
||||||
"number.min": "Port must be at least 1.",
|
"number.min": "Port must be at least 1.",
|
||||||
"number.max": "Port must be at most 65535.",
|
"number.max": "Port must be at most 65535.",
|
||||||
@@ -265,6 +268,7 @@ const statusPageValidation = joi.object({
|
|||||||
showUptimePercentage: joi.boolean(),
|
showUptimePercentage: joi.boolean(),
|
||||||
showCharts: joi.boolean(),
|
showCharts: joi.boolean(),
|
||||||
});
|
});
|
||||||
|
|
||||||
const settingsValidation = joi.object({
|
const settingsValidation = joi.object({
|
||||||
checkTTL: joi.number().required().messages({
|
checkTTL: joi.number().required().messages({
|
||||||
"string.empty": "Please enter a value",
|
"string.empty": "Please enter a value",
|
||||||
@@ -278,6 +282,7 @@ const settingsValidation = joi.object({
|
|||||||
systemEmailAddress: joi.string().allow(""),
|
systemEmailAddress: joi.string().allow(""),
|
||||||
systemEmailPassword: joi.string().allow(""),
|
systemEmailPassword: joi.string().allow(""),
|
||||||
systemEmailUser: joi.string().allow(""),
|
systemEmailUser: joi.string().allow(""),
|
||||||
|
systemEmailConnectionHost: joi.string().allow(""),
|
||||||
});
|
});
|
||||||
|
|
||||||
const dayjsValidator = (value, helpers) => {
|
const dayjsValidator = (value, helpers) => {
|
||||||
@@ -307,6 +312,7 @@ const advancedSettingsValidation = joi.object({
|
|||||||
systemEmailPort: joi.number().allow(null, ""),
|
systemEmailPort: joi.number().allow(null, ""),
|
||||||
systemEmailAddress: joi.string().allow(""),
|
systemEmailAddress: joi.string().allow(""),
|
||||||
systemEmailPassword: joi.string().allow(""),
|
systemEmailPassword: joi.string().allow(""),
|
||||||
|
systemEmailConnectionHost: joi.string().allow(""),
|
||||||
jwtTTLNum: joi.number().messages({
|
jwtTTLNum: joi.number().messages({
|
||||||
"number.base": "JWT TTL is required.",
|
"number.base": "JWT TTL is required.",
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -52,8 +52,8 @@ html {
|
|||||||
--env-var-default-1: 24px;
|
--env-var-default-1: 24px;
|
||||||
--env-var-default-2: 40px;
|
--env-var-default-2: 40px;
|
||||||
|
|
||||||
--env-var-shadow-1: 0px 4px 24px -4px rgba(16, 24, 40, 0.08),
|
--env-var-shadow-1:
|
||||||
0px 3px 3px -3px rgba(16, 24, 40, 0.03);
|
0px 4px 24px -4px rgba(16, 24, 40, 0.08), 0px 3px 3px -3px rgba(16, 24, 40, 0.03);
|
||||||
}
|
}
|
||||||
|
|
||||||
.MuiInputBase-root.Mui-disabled input {
|
.MuiInputBase-root.Mui-disabled input {
|
||||||
|
|||||||
@@ -0,0 +1,557 @@
|
|||||||
|
{
|
||||||
|
"dontHaveAccount": "",
|
||||||
|
"email": "",
|
||||||
|
"forgotPassword": "",
|
||||||
|
"password": "",
|
||||||
|
"signUp": "",
|
||||||
|
"submit": "",
|
||||||
|
"title": "",
|
||||||
|
"continue": "",
|
||||||
|
"enterEmail": "",
|
||||||
|
"authLoginTitle": "",
|
||||||
|
"authLoginEnterPassword": "",
|
||||||
|
"commonPassword": "",
|
||||||
|
"commonBack": "",
|
||||||
|
"authForgotPasswordTitle": "",
|
||||||
|
"authForgotPasswordResetPassword": "",
|
||||||
|
"createPassword": "",
|
||||||
|
"createAPassword": "",
|
||||||
|
"authRegisterAlreadyHaveAccount": "",
|
||||||
|
"commonAppName": "",
|
||||||
|
"authLoginEnterEmail": "",
|
||||||
|
"authRegisterTitle": "",
|
||||||
|
"authRegisterStepOneTitle": "",
|
||||||
|
"authRegisterStepOneDescription": "",
|
||||||
|
"authRegisterStepTwoTitle": "",
|
||||||
|
"authRegisterStepTwoDescription": "",
|
||||||
|
"authRegisterStepThreeTitle": "",
|
||||||
|
"authRegisterStepThreeDescription": "",
|
||||||
|
"authForgotPasswordDescription": "",
|
||||||
|
"authForgotPasswordSendInstructions": "",
|
||||||
|
"authForgotPasswordBackTo": "",
|
||||||
|
"authCheckEmailTitle": "",
|
||||||
|
"authCheckEmailDescription": "",
|
||||||
|
"authCheckEmailResendEmail": "",
|
||||||
|
"authCheckEmailBackTo": "",
|
||||||
|
"goBackTo": "",
|
||||||
|
"authCheckEmailDidntReceiveEmail": "",
|
||||||
|
"authCheckEmailClickToResend": "",
|
||||||
|
"authSetNewPasswordTitle": "",
|
||||||
|
"authSetNewPasswordDescription": "",
|
||||||
|
"authSetNewPasswordNewPassword": "",
|
||||||
|
"authSetNewPasswordConfirmPassword": "",
|
||||||
|
"confirmPassword": "",
|
||||||
|
"authSetNewPasswordResetPassword": "",
|
||||||
|
"authSetNewPasswordBackTo": "",
|
||||||
|
"authPasswordMustBeAtLeast": "",
|
||||||
|
"authPasswordCharactersLong": "",
|
||||||
|
"authPasswordMustContainAtLeast": "",
|
||||||
|
"authPasswordSpecialCharacter": "",
|
||||||
|
"authPasswordOneNumber": "",
|
||||||
|
"authPasswordUpperCharacter": "",
|
||||||
|
"authPasswordLowerCharacter": "",
|
||||||
|
"authPasswordConfirmAndPassword": "",
|
||||||
|
"authPasswordMustMatch": "",
|
||||||
|
"authRegisterCreateAccount": "",
|
||||||
|
"authRegisterCreateSuperAdminAccount": "",
|
||||||
|
"authRegisterSignUpWithEmail": "",
|
||||||
|
"authRegisterBySigningUp": "",
|
||||||
|
"distributedStatusHeaderText": "",
|
||||||
|
"distributedStatusSubHeaderText": "",
|
||||||
|
"settingsGeneralSettings": "",
|
||||||
|
"settingsDisplayTimezone": "",
|
||||||
|
"settingsDisplayTimezoneDescription": "",
|
||||||
|
"settingsAppearance": "",
|
||||||
|
"settingsAppearanceDescription": "",
|
||||||
|
"settingsThemeMode": "",
|
||||||
|
"settingsLanguage": "",
|
||||||
|
"settingsDistributedUptime": "",
|
||||||
|
"settingsDistributedUptimeDescription": "",
|
||||||
|
"settingsEnabled": "",
|
||||||
|
"settingsDisabled": "",
|
||||||
|
"settingsHistoryAndMonitoring": "",
|
||||||
|
"settingsHistoryAndMonitoringDescription": "",
|
||||||
|
"settingsTTLLabel": "",
|
||||||
|
"settingsTTLOptionalLabel": "",
|
||||||
|
"settingsClearAllStats": "",
|
||||||
|
"settingsClearAllStatsButton": "",
|
||||||
|
"settingsClearAllStatsDialogTitle": "",
|
||||||
|
"settingsClearAllStatsDialogDescription": "",
|
||||||
|
"settingsClearAllStatsDialogConfirm": "",
|
||||||
|
"settingsDemoMonitors": "",
|
||||||
|
"settingsDemoMonitorsDescription": "",
|
||||||
|
"settingsAddDemoMonitors": "",
|
||||||
|
"settingsAddDemoMonitorsButton": "",
|
||||||
|
"settingsRemoveAllMonitors": "",
|
||||||
|
"settingsRemoveAllMonitorsButton": "",
|
||||||
|
"settingsRemoveAllMonitorsDialogTitle": "",
|
||||||
|
"settingsRemoveAllMonitorsDialogConfirm": "",
|
||||||
|
"settingsWallet": "",
|
||||||
|
"settingsWalletDescription": "",
|
||||||
|
"settingsAbout": "",
|
||||||
|
"settingsDevelopedBy": "",
|
||||||
|
"settingsSave": "",
|
||||||
|
"settingsSuccessSaved": "",
|
||||||
|
"settingsFailedToSave": "",
|
||||||
|
"settingsStatsCleared": "",
|
||||||
|
"settingsFailedToClearStats": "",
|
||||||
|
"settingsDemoMonitorsAdded": "",
|
||||||
|
"settingsFailedToAddDemoMonitors": "",
|
||||||
|
"settingsMonitorsDeleted": "",
|
||||||
|
"settingsFailedToDeleteMonitors": "",
|
||||||
|
"starPromptTitle": "",
|
||||||
|
"starPromptDescription": "",
|
||||||
|
"https": "",
|
||||||
|
"http": "",
|
||||||
|
"monitor": "",
|
||||||
|
"aboutus": "",
|
||||||
|
"now": "",
|
||||||
|
"delete": "",
|
||||||
|
"configure": "",
|
||||||
|
"networkError": "",
|
||||||
|
"responseTime": "",
|
||||||
|
"ms": "",
|
||||||
|
"bar": "",
|
||||||
|
"area": "",
|
||||||
|
"country": "",
|
||||||
|
"city": "",
|
||||||
|
"response": "",
|
||||||
|
"checkConnection": "",
|
||||||
|
"passwordreset": "",
|
||||||
|
"authRegisterStepOnePersonalDetails": "",
|
||||||
|
"authCheckEmailOpenEmailButton": "",
|
||||||
|
"authNewPasswordConfirmed": "",
|
||||||
|
"monitorStatusUp": "",
|
||||||
|
"monitorStatusDown": "",
|
||||||
|
"webhookSendSuccess": "",
|
||||||
|
"webhookSendError": "",
|
||||||
|
"webhookUnsupportedPlatform": "",
|
||||||
|
"distributedRightCategoryTitle": "",
|
||||||
|
"distributedStatusServerMonitors": "",
|
||||||
|
"distributedStatusServerMonitorsDescription": "",
|
||||||
|
"distributedUptimeCreateSelectURL": "",
|
||||||
|
"distributedUptimeCreateChecks": "",
|
||||||
|
"distributedUptimeCreateChecksDescription": "",
|
||||||
|
"distributedUptimeCreateIncidentNotification": "",
|
||||||
|
"distributedUptimeCreateIncidentDescription": "",
|
||||||
|
"distributedUptimeCreateAdvancedSettings": "",
|
||||||
|
"distributedUptimeDetailsNoMonitorHistory": "",
|
||||||
|
"distributedUptimeDetailsFooterHeading": "",
|
||||||
|
"distributedUptimeDetailsFooterBuilt": "",
|
||||||
|
"distributedUptimeDetailsFooterSolana": "",
|
||||||
|
"distributedUptimeDetailsMonitorHeader": "",
|
||||||
|
"distributedUptimeDetailsStatusHeaderUptime": "",
|
||||||
|
"distributedUptimeDetailsStatusHeaderLastUpdate": "",
|
||||||
|
"notifications": {
|
||||||
|
"enableNotifications": "",
|
||||||
|
"testNotification": "",
|
||||||
|
"addOrEditNotifications": "",
|
||||||
|
"slack": {
|
||||||
|
"label": "",
|
||||||
|
"description": "",
|
||||||
|
"webhookLabel": "",
|
||||||
|
"webhookPlaceholder": "",
|
||||||
|
"webhookRequired": ""
|
||||||
|
},
|
||||||
|
"discord": {
|
||||||
|
"label": "",
|
||||||
|
"description": "",
|
||||||
|
"webhookLabel": "",
|
||||||
|
"webhookPlaceholder": "",
|
||||||
|
"webhookRequired": ""
|
||||||
|
},
|
||||||
|
"telegram": {
|
||||||
|
"label": "",
|
||||||
|
"description": "",
|
||||||
|
"tokenLabel": "",
|
||||||
|
"tokenPlaceholder": "",
|
||||||
|
"chatIdLabel": "",
|
||||||
|
"chatIdPlaceholder": "",
|
||||||
|
"fieldsRequired": ""
|
||||||
|
},
|
||||||
|
"webhook": {
|
||||||
|
"label": "",
|
||||||
|
"description": "",
|
||||||
|
"urlLabel": "",
|
||||||
|
"urlPlaceholder": "",
|
||||||
|
"urlRequired": ""
|
||||||
|
},
|
||||||
|
"testNotificationDevelop": "",
|
||||||
|
"integrationButton": "",
|
||||||
|
"testSuccess": "",
|
||||||
|
"testFailed": "",
|
||||||
|
"unsupportedType": "",
|
||||||
|
"networkError": ""
|
||||||
|
},
|
||||||
|
"testLocale": "",
|
||||||
|
"add": "",
|
||||||
|
"monitors": "",
|
||||||
|
"distributedUptimeStatusCreateStatusPage": "",
|
||||||
|
"distributedUptimeStatusCreateStatusPageAccess": "",
|
||||||
|
"distributedUptimeStatusCreateStatusPageReady": "",
|
||||||
|
"distributedUptimeStatusBasicInfoHeader": "",
|
||||||
|
"distributedUptimeStatusBasicInfoDescription": "",
|
||||||
|
"distributedUptimeStatusLogoHeader": "",
|
||||||
|
"distributedUptimeStatusLogoDescription": "",
|
||||||
|
"distributedUptimeStatusLogoUploadButton": "",
|
||||||
|
"distributedUptimeStatusStandardMonitorsHeader": "",
|
||||||
|
"distributedUptimeStatusStandardMonitorsDescription": "",
|
||||||
|
"distributedUptimeStatusCreateYour": "",
|
||||||
|
"distributedUptimeStatusEditYour": "",
|
||||||
|
"distributedUptimeStatusPublishedLabel": "",
|
||||||
|
"distributedUptimeStatusCompanyNameLabel": "",
|
||||||
|
"distributedUptimeStatusPageAddressLabel": "",
|
||||||
|
"distributedUptimeStatus30Days": "",
|
||||||
|
"distributedUptimeStatus60Days": "",
|
||||||
|
"distributedUptimeStatus90Days": "",
|
||||||
|
"distributedUptimeStatusPageNotSetUp": "",
|
||||||
|
"distributedUptimeStatusContactAdmin": "",
|
||||||
|
"distributedUptimeStatusPageNotPublic": "",
|
||||||
|
"distributedUptimeStatusPageDeleteDialog": "",
|
||||||
|
"distributedUptimeStatusPageDeleteConfirm": "",
|
||||||
|
"distributedUptimeStatusPageDeleteDescription": "",
|
||||||
|
"distributedUptimeStatusDevices": "",
|
||||||
|
"distributedUptimeStatusUpt": "",
|
||||||
|
"distributedUptimeStatusUptBurned": "",
|
||||||
|
"distributedUptimeStatusUptLogo": "",
|
||||||
|
"incidentsTableNoIncidents": "",
|
||||||
|
"incidentsTablePaginationLabel": "",
|
||||||
|
"incidentsTableMonitorName": "",
|
||||||
|
"incidentsTableStatus": "",
|
||||||
|
"incidentsTableDateTime": "",
|
||||||
|
"incidentsTableStatusCode": "",
|
||||||
|
"incidentsTableMessage": "",
|
||||||
|
"incidentsOptionsHeader": "",
|
||||||
|
"incidentsOptionsHeaderFilterBy": "",
|
||||||
|
"incidentsOptionsHeaderFilterAll": "",
|
||||||
|
"incidentsOptionsHeaderFilterDown": "",
|
||||||
|
"incidentsOptionsHeaderFilterCannotResolve": "",
|
||||||
|
"incidentsOptionsHeaderShow": "",
|
||||||
|
"incidentsOptionsHeaderLastHour": "",
|
||||||
|
"incidentsOptionsHeaderLastDay": "",
|
||||||
|
"incidentsOptionsHeaderLastWeek": "",
|
||||||
|
"incidentsOptionsPlaceholderAllServers": "",
|
||||||
|
"infrastructureCreateYour": "",
|
||||||
|
"infrastructureCreateGeneralSettingsDescription": "",
|
||||||
|
"infrastructureServerRequirement": "",
|
||||||
|
"infrastructureCustomizeAlerts": "",
|
||||||
|
"infrastructureAlertNotificationDescription": "",
|
||||||
|
"infrastructureCreateMonitor": "",
|
||||||
|
"infrastructureProtocol": "",
|
||||||
|
"infrastructureServerUrlLabel": "",
|
||||||
|
"infrastructureDisplayNameLabel": "",
|
||||||
|
"infrastructureAuthorizationSecretLabel": "",
|
||||||
|
"gb": "",
|
||||||
|
"mb": "",
|
||||||
|
"mem": "",
|
||||||
|
"memoryUsage": "",
|
||||||
|
"cpu": "",
|
||||||
|
"cpuUsage": "",
|
||||||
|
"cpuTemperature": "",
|
||||||
|
"diskUsage": "",
|
||||||
|
"used": "",
|
||||||
|
"total": "",
|
||||||
|
"cores": "",
|
||||||
|
"frequency": "",
|
||||||
|
"status": "",
|
||||||
|
"cpuPhysical": "",
|
||||||
|
"cpuLogical": "",
|
||||||
|
"cpuFrequency": "",
|
||||||
|
"avgCpuTemperature": "",
|
||||||
|
"memory": "",
|
||||||
|
"disk": "",
|
||||||
|
"uptime": "",
|
||||||
|
"os": "",
|
||||||
|
"host": "",
|
||||||
|
"actions": "",
|
||||||
|
"integrations": "",
|
||||||
|
"integrationsPrism": "",
|
||||||
|
"integrationsSlack": "",
|
||||||
|
"integrationsSlackInfo": "",
|
||||||
|
"integrationsDiscord": "",
|
||||||
|
"integrationsDiscordInfo": "",
|
||||||
|
"integrationsZapier": "",
|
||||||
|
"integrationsZapierInfo": "",
|
||||||
|
"commonSave": "",
|
||||||
|
"createYour": "",
|
||||||
|
"createMonitor": "",
|
||||||
|
"pause": "",
|
||||||
|
"resume": "",
|
||||||
|
"editing": "",
|
||||||
|
"url": "",
|
||||||
|
"access": "",
|
||||||
|
"timezone": "",
|
||||||
|
"features": "",
|
||||||
|
"administrator": "",
|
||||||
|
"loginHere": "",
|
||||||
|
"displayName": "",
|
||||||
|
"urlMonitor": "",
|
||||||
|
"portToMonitor": "",
|
||||||
|
"websiteMonitoring": "",
|
||||||
|
"websiteMonitoringDescription": "",
|
||||||
|
"pingMonitoring": "",
|
||||||
|
"pingMonitoringDescription": "",
|
||||||
|
"dockerContainerMonitoring": "",
|
||||||
|
"dockerContainerMonitoringDescription": "",
|
||||||
|
"portMonitoring": "",
|
||||||
|
"portMonitoringDescription": "",
|
||||||
|
"createMaintenanceWindow": "",
|
||||||
|
"createMaintenance": "",
|
||||||
|
"editMaintenance": "",
|
||||||
|
"maintenanceWindowName": "",
|
||||||
|
"friendlyNameInput": "",
|
||||||
|
"friendlyNamePlaceholder": "",
|
||||||
|
"maintenanceRepeat": "",
|
||||||
|
"maintenance": "",
|
||||||
|
"duration": "",
|
||||||
|
"addMonitors": "",
|
||||||
|
"window": "",
|
||||||
|
"cancel": "",
|
||||||
|
"message": "",
|
||||||
|
"low": "",
|
||||||
|
"high": "",
|
||||||
|
"statusCode": "",
|
||||||
|
"date&Time": "",
|
||||||
|
"type": "",
|
||||||
|
"statusPageName": "",
|
||||||
|
"publicURL": "",
|
||||||
|
"repeat": "",
|
||||||
|
"edit": "",
|
||||||
|
"createA": "",
|
||||||
|
"remove": "",
|
||||||
|
"maintenanceWindowDescription": "",
|
||||||
|
"startTime": "",
|
||||||
|
"timeZoneInfo": "",
|
||||||
|
"monitorsToApply": "",
|
||||||
|
"nextWindow": "",
|
||||||
|
"notFoundButton": "",
|
||||||
|
"pageSpeedConfigureSettingsDescription": "",
|
||||||
|
"monitorDisplayName": "",
|
||||||
|
"whenNewIncident": "",
|
||||||
|
"notifySMS": "",
|
||||||
|
"notifyEmails": "",
|
||||||
|
"seperateEmails": "",
|
||||||
|
"checkFrequency": "",
|
||||||
|
"matchMethod": "",
|
||||||
|
"expectedValue": "",
|
||||||
|
"deleteDialogTitle": "",
|
||||||
|
"deleteDialogDescription": "",
|
||||||
|
"pageSpeedMonitor": "",
|
||||||
|
"shown": "",
|
||||||
|
"ago": "",
|
||||||
|
"companyName": "",
|
||||||
|
"pageSpeedDetailsPerformanceReport": "",
|
||||||
|
"pageSpeedDetailsPerformanceReportCalculator": "",
|
||||||
|
"checkingEvery": "",
|
||||||
|
"statusPageCreateSettings": "",
|
||||||
|
"basicInformation": "",
|
||||||
|
"statusPageCreateBasicInfoDescription": "",
|
||||||
|
"statusPageCreateSelectTimeZoneDescription": "",
|
||||||
|
"statusPageCreateAppearanceDescription": "",
|
||||||
|
"statusPageCreateSettingsCheckboxLabel": "",
|
||||||
|
"statusPageCreateBasicInfoStatusPageAddress": "",
|
||||||
|
"statusPageCreateTabsContent": "",
|
||||||
|
"statusPageCreateTabsContentDescription": "",
|
||||||
|
"statusPageCreateTabsContentFeaturesDescription": "",
|
||||||
|
"showCharts": "",
|
||||||
|
"showUptimePercentage": "",
|
||||||
|
"removeLogo": "",
|
||||||
|
"statusPageStatus": "",
|
||||||
|
"statusPageStatusContactAdmin": "",
|
||||||
|
"statusPageStatusNotPublic": "",
|
||||||
|
"statusPageStatusNoPage": "",
|
||||||
|
"statusPageStatusServiceStatus": "",
|
||||||
|
"deleteStatusPage": "",
|
||||||
|
"deleteStatusPageConfirm": "",
|
||||||
|
"deleteStatusPageDescription": "",
|
||||||
|
"uptimeCreate": "",
|
||||||
|
"uptimeCreateJsonPath": "",
|
||||||
|
"uptimeCreateJsonPathQuery": "",
|
||||||
|
"maintenanceTableActionMenuDialogTitle": "",
|
||||||
|
"infrastructureEditYour": "",
|
||||||
|
"infrastructureEditMonitor": "",
|
||||||
|
"infrastructureMonitorCreated": "",
|
||||||
|
"infrastructureMonitorUpdated": "",
|
||||||
|
"errorInvalidTypeId": "",
|
||||||
|
"errorInvalidFieldId": "",
|
||||||
|
"inviteNoTokenFound": "",
|
||||||
|
"pageSpeedWarning": "",
|
||||||
|
"pageSpeedLearnMoreLink": "",
|
||||||
|
"pageSpeedAddApiKey": "",
|
||||||
|
"update": "",
|
||||||
|
"invalidFileFormat": "",
|
||||||
|
"invalidFileSize": "",
|
||||||
|
"ClickUpload": "",
|
||||||
|
"DragandDrop": "",
|
||||||
|
"MaxSize": "",
|
||||||
|
"SupportedFormats": "",
|
||||||
|
"FirstName": "",
|
||||||
|
"LastName": "",
|
||||||
|
"EmailDescriptionText": "",
|
||||||
|
"YourPhoto": "",
|
||||||
|
"PhotoDescriptionText": "",
|
||||||
|
"save": "",
|
||||||
|
"DeleteDescriptionText": "",
|
||||||
|
"DeleteAccountWarning": "",
|
||||||
|
"DeleteWarningTitle": "",
|
||||||
|
"authRegisterFirstName": "",
|
||||||
|
"authRegisterLastName": "",
|
||||||
|
"authRegisterEmail": "",
|
||||||
|
"authRegisterEmailRequired": "",
|
||||||
|
"bulkImport": {
|
||||||
|
"title": "",
|
||||||
|
"selectFileTips": "",
|
||||||
|
"selectFileDescription": "",
|
||||||
|
"selectFile": "",
|
||||||
|
"parsingFailed": "",
|
||||||
|
"uploadSuccess": "",
|
||||||
|
"validationFailed": "",
|
||||||
|
"noFileSelected": "",
|
||||||
|
"fallbackPage": ""
|
||||||
|
},
|
||||||
|
"welcomeBack": "",
|
||||||
|
"authRegisterLoginLink": "",
|
||||||
|
"validationNameRequired": "",
|
||||||
|
"validationNameTooLong": "",
|
||||||
|
"validationNameInvalidCharacters": "",
|
||||||
|
"settingsSystemReset": "",
|
||||||
|
"settingsSystemResetDescription": "",
|
||||||
|
"DeleteAccountTitle": "",
|
||||||
|
"DeleteAccountButton": "",
|
||||||
|
"authRegisterEmailInvalid": "",
|
||||||
|
"publicLink": "",
|
||||||
|
"doNotHaveAccount": "",
|
||||||
|
"registerHere": "",
|
||||||
|
"backendUnreachable": "",
|
||||||
|
"backendUnreachableMessage": "",
|
||||||
|
"backendUnreachableError": "",
|
||||||
|
"retryConnection": "",
|
||||||
|
"retryingConnection": "",
|
||||||
|
"backendReconnected": "",
|
||||||
|
"backendStillUnreachable": "",
|
||||||
|
"backendConnectionError": "",
|
||||||
|
"maskedPageSpeedKeyPlaceholder": "",
|
||||||
|
"pageSpeedApiKeyFieldTitle": "",
|
||||||
|
"pageSpeedApiKeyFieldLabel": "",
|
||||||
|
"pageSpeedApiKeyFieldDescription": "",
|
||||||
|
"pageSpeedApiKeyFieldResetLabel": "",
|
||||||
|
"reset": "",
|
||||||
|
"ignoreTLSError": "",
|
||||||
|
"tlsErrorIgnored": "",
|
||||||
|
"ignoreTLSErrorDescription": "",
|
||||||
|
"createNew": "",
|
||||||
|
"greeting": {
|
||||||
|
"prepend": "",
|
||||||
|
"append": "",
|
||||||
|
"overview": ""
|
||||||
|
},
|
||||||
|
"monitorStatus": {
|
||||||
|
"up": "",
|
||||||
|
"down": "",
|
||||||
|
"paused": ""
|
||||||
|
},
|
||||||
|
"roles": {
|
||||||
|
"superAdmin": "",
|
||||||
|
"admin": "",
|
||||||
|
"teamMember": "",
|
||||||
|
"demoUser": ""
|
||||||
|
},
|
||||||
|
"teamPanel": {
|
||||||
|
"teamMembers": "",
|
||||||
|
"filter": {
|
||||||
|
"all": "",
|
||||||
|
"member": ""
|
||||||
|
},
|
||||||
|
"inviteTeamMember": "",
|
||||||
|
"inviteNewTeamMember": "",
|
||||||
|
"inviteDescription": "",
|
||||||
|
"email": "",
|
||||||
|
"selectRole": "",
|
||||||
|
"inviteLink": "",
|
||||||
|
"cancel": "",
|
||||||
|
"noMembers": "",
|
||||||
|
"getToken": "",
|
||||||
|
"emailToken": "",
|
||||||
|
"table": {
|
||||||
|
"name": "",
|
||||||
|
"email": "",
|
||||||
|
"role": "",
|
||||||
|
"created": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"monitorState": {
|
||||||
|
"paused": "",
|
||||||
|
"resumed": "",
|
||||||
|
"active": ""
|
||||||
|
},
|
||||||
|
"menu": {
|
||||||
|
"uptime": "",
|
||||||
|
"pagespeed": "",
|
||||||
|
"infrastructure": "",
|
||||||
|
"distributedUptime": "",
|
||||||
|
"incidents": "",
|
||||||
|
"statusPages": "",
|
||||||
|
"maintenance": "",
|
||||||
|
"integrations": "",
|
||||||
|
"settings": "",
|
||||||
|
"support": "",
|
||||||
|
"discussions": "",
|
||||||
|
"docs": "",
|
||||||
|
"changelog": "",
|
||||||
|
"profile": "",
|
||||||
|
"password": "",
|
||||||
|
"team": "",
|
||||||
|
"logOut": ""
|
||||||
|
},
|
||||||
|
"settingsEmail": "",
|
||||||
|
"settingsEmailDescription": "",
|
||||||
|
"settingsEmailHost": "",
|
||||||
|
"settingsEmailPort": "",
|
||||||
|
"settingsEmailAddress": "",
|
||||||
|
"settingsEmailPassword": "",
|
||||||
|
"settingsEmailUser": "",
|
||||||
|
"settingsEmailFieldResetLabel": "",
|
||||||
|
"state": "",
|
||||||
|
"statusBreadCrumbsStatusPages": "",
|
||||||
|
"statusBreadCrumbsDetails": "",
|
||||||
|
"authForgotPasswordInstructions": "",
|
||||||
|
"settingsThemeModeLight": "",
|
||||||
|
"settingsThemeModeDark": "",
|
||||||
|
"settingsClearAllStatsDialogCancel": "",
|
||||||
|
"commonSaving": "",
|
||||||
|
"navControls": "",
|
||||||
|
"incidentsPageTitle": "",
|
||||||
|
"passwordPanel": {
|
||||||
|
"passwordChangedSuccess": "",
|
||||||
|
"passwordInputIncorrect": "",
|
||||||
|
"currentPassword": "",
|
||||||
|
"enterCurrentPassword": "",
|
||||||
|
"newPassword": "",
|
||||||
|
"enterNewPassword": "",
|
||||||
|
"confirmNewPassword": "",
|
||||||
|
"passwordRequirements": "",
|
||||||
|
"saving": ""
|
||||||
|
},
|
||||||
|
"uptimeCreateSelectURL": "",
|
||||||
|
"settingsEmailConnectionHost": "",
|
||||||
|
"sendTestEmail": "",
|
||||||
|
"emailSent": "",
|
||||||
|
"failedToSendEmail": "",
|
||||||
|
"settingsTestEmail": "",
|
||||||
|
"settingsTestEmailSuccess": "",
|
||||||
|
"settingsTestEmailFailed": "",
|
||||||
|
"settingsTestEmailFailedWithReason": "",
|
||||||
|
"settingsTestEmailUnknownError": "",
|
||||||
|
"settingsEmailRequiredFields": "",
|
||||||
|
"statusMsg": {
|
||||||
|
"paused": "",
|
||||||
|
"up": "",
|
||||||
|
"down": "",
|
||||||
|
"pending": ""
|
||||||
|
},
|
||||||
|
"settingsURLTitle": "",
|
||||||
|
"settingsURLDescription": "",
|
||||||
|
"settingsURLSelectTitle": "",
|
||||||
|
"settingsURLEnabled": "",
|
||||||
|
"settingURLDisabled": ""
|
||||||
|
}
|
||||||
Loaded 100 of 146 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user