Merge branch 'develop' into feature/1484-star-prompt
This commit is contained in:
40 files changed
+1144
-134
No files matched your search
@@ -0,0 +1,89 @@
|
||||
import axios from 'axios';
|
||||
import fs from 'fs-extra';
|
||||
import path from 'path';
|
||||
import { URLSearchParams } from 'url';
|
||||
|
||||
// POEditor API information
|
||||
const API_TOKEN = process.env.POEDITOR_API;
|
||||
const PROJECT_ID = process.env.POEDITOR_PROJECT_ID;
|
||||
const LANGUAGES = (process.env.LANGUAGES || 'tr,en').split(',');
|
||||
const EXPORT_FORMAT = process.env.EXPORT_FORMAT || 'key_value_json';
|
||||
|
||||
// POEditor API endpoint
|
||||
const API_URL = 'https://api.poeditor.com/v2';
|
||||
|
||||
// Function to download translations
|
||||
async function downloadTranslations() {
|
||||
try {
|
||||
console.log('Downloading translations from POEditor...');
|
||||
console.log(`Using export format: ${EXPORT_FORMAT}`);
|
||||
|
||||
for (const language of LANGUAGES) {
|
||||
console.log(`Downloading translations for ${language} language...`);
|
||||
|
||||
// Get export URL from POEditor
|
||||
const exportResponse = await axios.post(`${API_URL}/projects/export`,
|
||||
new URLSearchParams({
|
||||
api_token: API_TOKEN,
|
||||
id: PROJECT_ID,
|
||||
language: language,
|
||||
type: EXPORT_FORMAT
|
||||
})
|
||||
);
|
||||
|
||||
if (exportResponse.data.response.status !== 'success') {
|
||||
throw new Error(`Failed to get export URL for ${language} language: ${JSON.stringify(exportResponse.data)}`);
|
||||
}
|
||||
|
||||
const fileUrl = exportResponse.data.result.url;
|
||||
console.log(`Export URL obtained for ${language}`);
|
||||
|
||||
// Download translation file
|
||||
const downloadResponse = await axios.get(fileUrl, { responseType: 'json' });
|
||||
const translations = downloadResponse.data;
|
||||
console.log(`Downloaded translations for ${language}`);
|
||||
|
||||
// Check the format of data returned from POEditor and convert if necessary
|
||||
let formattedTranslations = translations;
|
||||
|
||||
// If data is in array format, convert it to key-value format
|
||||
if (Array.isArray(translations)) {
|
||||
console.log(`Converting array format to key-value format for ${language}`);
|
||||
formattedTranslations = {};
|
||||
translations.forEach(item => {
|
||||
if (item.term && item.definition) {
|
||||
formattedTranslations[item.term] = item.definition;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Save file
|
||||
const outputPath = path.join(process.cwd(), 'temp', `${language}.json`);
|
||||
await fs.writeJson(outputPath, formattedTranslations, { spaces: 2 });
|
||||
|
||||
console.log(`Translations for ${language} language successfully downloaded and saved: ${outputPath}`);
|
||||
}
|
||||
|
||||
console.log('All translations successfully downloaded!');
|
||||
} catch (error) {
|
||||
console.error('An error occurred while downloading translations:', error);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
// Main function
|
||||
async function main() {
|
||||
try {
|
||||
// Clean temp folder
|
||||
await fs.emptyDir(path.join(process.cwd(), 'temp'));
|
||||
|
||||
// Download translations
|
||||
await downloadTranslations();
|
||||
} catch (error) {
|
||||
console.error('An error occurred during the process:', error);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
// Run script
|
||||
main();
|
||||
@@ -0,0 +1,41 @@
|
||||
# POEditor Translation Synchronization
|
||||
|
||||
This GitHub Actions workflow automatically downloads translation files from POEditor and integrates them into the project.
|
||||
|
||||
## How It Works
|
||||
|
||||
The workflow can be triggered in two ways:
|
||||
|
||||
1. **Manual Trigger**: You can manually run the "POEditor Translation Synchronization" workflow from the "Actions" tab in the GitHub interface.
|
||||
2. **Automatic Trigger**: The workflow runs automatically every day at midnight (UTC).
|
||||
|
||||
## Required Settings
|
||||
|
||||
For this workflow to function, you need to define the following secrets in your GitHub repository:
|
||||
|
||||
1. `POEDITOR_API_TOKEN`: Your POEditor API token
|
||||
2. `POEDITOR_PROJECT_ID`: Your POEditor project ID
|
||||
|
||||
You can add these secrets in the "Settings > Secrets and variables > Actions" section of your GitHub repository.
|
||||
|
||||
## Manual Execution
|
||||
|
||||
When running the workflow manually, you can specify which languages to download. Languages should be entered as comma-separated values (e.g., `tr,gb,es`).
|
||||
|
||||
If you don't specify any languages, the default languages `tr` and `en` will be downloaded.
|
||||
|
||||
## Output
|
||||
|
||||
When the workflow completes successfully:
|
||||
|
||||
1. Translation files for the specified languages are downloaded from POEditor
|
||||
2. These files are copied to the `src/locales/` directory
|
||||
3. Changes are automatically committed and pushed to the main branch
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
If the workflow fails:
|
||||
|
||||
1. Check the GitHub Actions logs
|
||||
2. Make sure your POEditor API token and project ID are correct
|
||||
3. Ensure that the languages you specified exist in your POEditor project
|
||||
@@ -0,0 +1,9 @@
|
||||
- name: First interaction
|
||||
uses: actions/first-interaction@v1.3.0
|
||||
with:
|
||||
# Token for the repository. Can be passed in using {{ secrets.GITHUB_TOKEN }}
|
||||
repo-token:
|
||||
# Comment to post on an individual's first issue
|
||||
issue-message: Thanks for opening your first issue! :) # optional
|
||||
# Comment to post on an individual's first pull request
|
||||
pr-message: Thanks for creating your first PR! :) # optional
|
||||
@@ -0,0 +1,92 @@
|
||||
name: POEditor Translation Synchronization
|
||||
|
||||
on:
|
||||
# For manual triggering
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
languages:
|
||||
description: "Languages to synchronize (comma separated, e.g.: tr,en,es)"
|
||||
required: false
|
||||
default: "tr,en"
|
||||
format:
|
||||
description: "Export format (key_value_json or json)"
|
||||
required: false
|
||||
default: "key_value_json"
|
||||
|
||||
# For automatic execution at a specific time (every day at midnight)
|
||||
schedule:
|
||||
- cron: "0 0 * * *"
|
||||
|
||||
jobs:
|
||||
sync-translations:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: "18"
|
||||
|
||||
- name: Create package.json for scripts
|
||||
run: |
|
||||
mkdir -p .github/scripts
|
||||
cat > .github/scripts/package.json << EOF
|
||||
{
|
||||
"name": "poeditor-scripts",
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"axios": "^1.6.0",
|
||||
"fs-extra": "^11.1.1"
|
||||
}
|
||||
}
|
||||
EOF
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
cd .github/scripts
|
||||
npm install
|
||||
|
||||
- name: Download translations from POEditor
|
||||
env:
|
||||
POEDITOR_API: ${{ secrets.POEDITOR_API }}
|
||||
POEDITOR_PROJECT_ID: ${{ secrets.POEDITOR_PROJECT_ID }}
|
||||
LANGUAGES: ${{ github.event.inputs.languages || 'tr,en' }}
|
||||
EXPORT_FORMAT: ${{ github.event.inputs.format || 'key_value_json' }}
|
||||
run: |
|
||||
mkdir -p temp
|
||||
node .github/scripts/download-translations.js
|
||||
|
||||
- name: Verify translation files
|
||||
run: |
|
||||
echo "Verifying translation files..."
|
||||
for file in temp/*.json; do
|
||||
echo "Checking $file"
|
||||
if [ ! -s "$file" ]; then
|
||||
echo "Error: $file is empty or does not exist"
|
||||
exit 1
|
||||
fi
|
||||
# Validate JSON format
|
||||
cat "$file" | jq . > /dev/null || { echo "Error: $file is not valid JSON"; exit 1; }
|
||||
done
|
||||
echo "All translation files are valid"
|
||||
|
||||
- name: Copy translations to project
|
||||
run: |
|
||||
mkdir -p src/locales
|
||||
cp -r temp/* src/locales/
|
||||
echo "Translation files copied to src/locales/"
|
||||
|
||||
- name: Commit changes
|
||||
run: |
|
||||
git config --local user.email "github-actions[bot]@users.noreply.github.com"
|
||||
git config --local user.name "github-actions[bot]"
|
||||
git add src/locales/*.json
|
||||
git diff --staged --quiet || git commit -m "Translations updated from POEditor"
|
||||
git push
|
||||
Generated
+4
-4
@@ -18,7 +18,7 @@
|
||||
"@mui/x-charts": "^7.5.1",
|
||||
"@mui/x-data-grid": "7.27.3",
|
||||
"@mui/x-date-pickers": "7.27.3",
|
||||
"@reduxjs/toolkit": "2.6.0",
|
||||
"@reduxjs/toolkit": "2.6.1",
|
||||
"@solana/wallet-adapter-base": "0.9.23",
|
||||
"@solana/wallet-adapter-material-ui": "0.16.34",
|
||||
"@solana/wallet-adapter-react": "0.15.35",
|
||||
@@ -4692,9 +4692,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@reduxjs/toolkit": {
|
||||
"version": "2.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-2.6.0.tgz",
|
||||
"integrity": "sha512-mWJCYpewLRyTuuzRSEC/IwIBBkYg2dKtQas8mty5MaV2iXzcmicS3gW554FDeOvLnY3x13NIk8MB1e8wHO7rqQ==",
|
||||
"version": "2.6.1",
|
||||
"resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-2.6.1.tgz",
|
||||
"integrity": "sha512-SSlIqZNYhqm/oMkXbtofwZSt9lrncblzo6YcZ9zoX+zLngRBrCOjK4lNLdkNucJF58RHOWrD9txT3bT3piH7Zw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"immer": "^10.0.3",
|
||||
|
||||
+1
-1
@@ -21,7 +21,7 @@
|
||||
"@mui/icons-material": "6.4.7",
|
||||
"@mui/lab": "6.0.0-beta.30",
|
||||
"@mui/material": "6.4.7",
|
||||
"@reduxjs/toolkit": "2.6.0",
|
||||
"@reduxjs/toolkit": "2.6.1",
|
||||
"@solana/wallet-adapter-base": "0.9.23",
|
||||
"@solana/wallet-adapter-material-ui": "0.16.34",
|
||||
"@solana/wallet-adapter-react": "0.15.35",
|
||||
|
||||
@@ -128,7 +128,7 @@ const Search = ({
|
||||
<Typography
|
||||
component="span"
|
||||
className="input-error"
|
||||
color={theme.palette.error.contrastText}
|
||||
color={theme.palette.error.main}
|
||||
mt={theme.spacing(2)}
|
||||
sx={{
|
||||
opacity: 0.8,
|
||||
@@ -188,8 +188,8 @@ const Search = ({
|
||||
color: "red",
|
||||
},
|
||||
"& li.MuiAutocomplete-option:hover:not([aria-selected='true'])": {
|
||||
color: theme.palette.primary.main,
|
||||
backgroundColor: theme.palette.primary.contrastTextSecondary,
|
||||
color: theme.palette.secondary.contrastText,
|
||||
backgroundColor: theme.palette.secondary.main,
|
||||
},
|
||||
"& .MuiAutocomplete-noOptions": {
|
||||
px: theme.spacing(6),
|
||||
|
||||
@@ -114,7 +114,7 @@ ColoredLabel.propTypes = {
|
||||
/**
|
||||
* @component
|
||||
* @param {Object} props
|
||||
* @param { 'up' | 'down' | 'cannot resolve'} props.status - The status for the label
|
||||
* @param {'up' | 'down' | 'paused' | 'pending' | 'cannot resolve' | 'published' | 'unpublished'} props.status - The status for the label
|
||||
* @param {string} props.text - The text of the label
|
||||
* @returns {JSX.Element}
|
||||
* @example
|
||||
@@ -128,6 +128,8 @@ const statusToTheme = {
|
||||
paused: "warning",
|
||||
pending: "warning",
|
||||
"cannot resolve": "error",
|
||||
published: "success",
|
||||
unpublished: "error",
|
||||
};
|
||||
|
||||
const StatusLabel = ({ status, text, customStyles }) => {
|
||||
@@ -156,7 +158,7 @@ const StatusLabel = ({ status, text, customStyles }) => {
|
||||
};
|
||||
|
||||
StatusLabel.propTypes = {
|
||||
status: PropTypes.oneOf(["up", "down", "paused", "pending", "cannot resolve"]),
|
||||
status: PropTypes.oneOf(["up", "down", "paused", "pending", "cannot resolve", "published", "unpublished"]),
|
||||
text: PropTypes.string,
|
||||
customStyles: PropTypes.object,
|
||||
};
|
||||
|
||||
@@ -14,7 +14,14 @@ const Link = ({ level, label, url }) => {
|
||||
const theme = useTheme();
|
||||
|
||||
const levelConfig = {
|
||||
primary: {},
|
||||
primary: {
|
||||
color: theme.palette.primary.contrastTextTertiary,
|
||||
sx: {
|
||||
":hover": {
|
||||
color: theme.palette.primary.contrastTextSecondary,
|
||||
},
|
||||
},
|
||||
},
|
||||
secondary: {
|
||||
color: theme.palette.primary.contrastTextSecondary,
|
||||
sx: {
|
||||
|
||||
@@ -0,0 +1,273 @@
|
||||
import { useState, useMemo } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogActions,
|
||||
Button,
|
||||
Typography,
|
||||
Box,
|
||||
Tabs,
|
||||
Tab
|
||||
} from "@mui/material";
|
||||
import { useTheme } from "@emotion/react";
|
||||
import TabPanel from "./TabPanel";
|
||||
import TabComponent from "./TabComponent";
|
||||
|
||||
const NotificationIntegrationModal = ({
|
||||
open,
|
||||
onClose,
|
||||
monitor,
|
||||
setMonitor,
|
||||
// Optional prop to configure available notification types
|
||||
notificationTypes = null
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const theme = useTheme();
|
||||
const [tabValue, setTabValue] = useState(0);
|
||||
|
||||
// Define notification types
|
||||
const DEFAULT_NOTIFICATION_TYPES = [
|
||||
{
|
||||
id: 'slack',
|
||||
label: t('notifications.slack.label'),
|
||||
description: t('notifications.slack.description'),
|
||||
fields: [
|
||||
{
|
||||
id: 'webhook',
|
||||
label: t('notifications.slack.webhookLabel'),
|
||||
placeholder: t('notifications.slack.webhookPlaceholder'),
|
||||
type: 'text'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'discord',
|
||||
label: t('notifications.discord.label'),
|
||||
description: t('notifications.discord.description'),
|
||||
fields: [
|
||||
{
|
||||
id: 'webhook',
|
||||
label: t('notifications.discord.webhookLabel'),
|
||||
placeholder: t('notifications.discord.webhookPlaceholder'),
|
||||
type: 'text'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'telegram',
|
||||
label: t('notifications.telegram.label'),
|
||||
description: t('notifications.telegram.description'),
|
||||
fields: [
|
||||
{
|
||||
id: 'token',
|
||||
label: t('notifications.telegram.tokenLabel'),
|
||||
placeholder: t('notifications.telegram.tokenPlaceholder'),
|
||||
type: 'text'
|
||||
},
|
||||
{
|
||||
id: 'chatId',
|
||||
label: t('notifications.telegram.chatIdLabel'),
|
||||
placeholder: t('notifications.telegram.chatIdPlaceholder'),
|
||||
type: 'text'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'webhook',
|
||||
label: t('notifications.webhook.label'),
|
||||
description: t('notifications.webhook.description'),
|
||||
fields: [
|
||||
{
|
||||
id: 'url',
|
||||
label: t('notifications.webhook.urlLabel'),
|
||||
placeholder: t('notifications.webhook.urlPlaceholder'),
|
||||
type: 'text'
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
// Use provided notification types or default to our translated ones
|
||||
const activeNotificationTypes = notificationTypes || DEFAULT_NOTIFICATION_TYPES;
|
||||
|
||||
// Memoized function to initialize integrations state
|
||||
const initialIntegrationsState = useMemo(() => {
|
||||
const state = {};
|
||||
|
||||
activeNotificationTypes.forEach(type => {
|
||||
// Add enabled flag for each notification type
|
||||
state[type.id] = monitor?.notifications?.some(n => n.type === type.id) || false;
|
||||
|
||||
// Add state for each field in the notification type
|
||||
type.fields.forEach(field => {
|
||||
const fieldKey = `${type.id}${field.id.charAt(0).toUpperCase() + field.id.slice(1)}`;
|
||||
state[fieldKey] = monitor?.notifications?.find(n => n.type === type.id)?.[field.id] || "";
|
||||
});
|
||||
});
|
||||
|
||||
return state;
|
||||
}, [monitor, activeNotificationTypes]); // Only recompute when these dependencies change
|
||||
|
||||
const [integrations, setIntegrations] = useState(initialIntegrationsState);
|
||||
|
||||
const handleChangeTab = (event, newValue) => {
|
||||
setTabValue(newValue);
|
||||
};
|
||||
|
||||
const handleIntegrationChange = (type, checked) => {
|
||||
setIntegrations(prev => ({
|
||||
...prev,
|
||||
[type]: checked
|
||||
}));
|
||||
};
|
||||
|
||||
const handleInputChange = (type, value) => {
|
||||
setIntegrations(prev => ({
|
||||
...prev,
|
||||
[type]: value
|
||||
}));
|
||||
};
|
||||
|
||||
const handleTestNotification = (type) => {
|
||||
console.log(`Testing ${type} notification`);
|
||||
//implement the test notification functionality
|
||||
};
|
||||
|
||||
const handleSave = () => {
|
||||
//notifications array for selected integrations
|
||||
const notifications = [...(monitor?.notifications || [])];
|
||||
|
||||
// Get all notification types IDs
|
||||
const existingTypes = activeNotificationTypes.map(type => type.id);
|
||||
|
||||
// Filter out notifications that are configurable in this modal
|
||||
const filteredNotifications = notifications.filter(
|
||||
notification => !existingTypes.includes(notification.type)
|
||||
);
|
||||
|
||||
// Add each enabled notification with its configured fields
|
||||
activeNotificationTypes.forEach(type => {
|
||||
if (integrations[type.id]) {
|
||||
const notificationObject = {
|
||||
type: type.id
|
||||
};
|
||||
|
||||
// Add each field value to the notification object
|
||||
type.fields.forEach(field => {
|
||||
const fieldKey = `${type.id}${field.id.charAt(0).toUpperCase() + field.id.slice(1)}`;
|
||||
notificationObject[field.id] = integrations[fieldKey];
|
||||
});
|
||||
|
||||
filteredNotifications.push(notificationObject);
|
||||
}
|
||||
});
|
||||
|
||||
// Update monitor with new notifications
|
||||
setMonitor(prev => ({
|
||||
...prev,
|
||||
notifications: filteredNotifications
|
||||
}));
|
||||
|
||||
onClose();
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
open={open}
|
||||
onClose={onClose}
|
||||
fullWidth
|
||||
maxWidth="md"
|
||||
sx={{
|
||||
'& .MuiDialog-paper': {
|
||||
width: `calc(80% - ${theme.spacing(40)})`,
|
||||
maxWidth: `${theme.breakpoints.values.md - 70}px`
|
||||
}
|
||||
}}
|
||||
>
|
||||
<DialogContent>
|
||||
<Box sx={{
|
||||
display: 'flex',
|
||||
height: `calc(26vh - ${theme.spacing(20)})`
|
||||
}}>
|
||||
{/* Left sidebar with tabs */}
|
||||
<Box sx={{
|
||||
borderRight: 1,
|
||||
borderColor: theme.palette.primary.lowContrast,
|
||||
width: '30%',
|
||||
maxWidth: theme.spacing(120),
|
||||
pr: theme.spacing(10)
|
||||
}}>
|
||||
<Typography variant="subtitle1" sx={{
|
||||
my: theme.spacing(1),
|
||||
fontWeight: 'bold',
|
||||
fontSize: theme.typography.fontSize * 0.9,
|
||||
color: theme.palette.primary.contrastTextSecondary,
|
||||
pl: theme.spacing(4)
|
||||
}}>
|
||||
{t('notifications.addOrEditNotifications')}
|
||||
</Typography>
|
||||
|
||||
<Tabs
|
||||
orientation="vertical"
|
||||
variant="scrollable"
|
||||
value={tabValue}
|
||||
onChange={handleChangeTab}
|
||||
aria-label="Notification tabs"
|
||||
>
|
||||
{activeNotificationTypes.map((type) => (
|
||||
<Tab
|
||||
key={type.id}
|
||||
label={type.label}
|
||||
orientation="vertical"
|
||||
disableRipple
|
||||
/>
|
||||
))}
|
||||
</Tabs>
|
||||
</Box>
|
||||
|
||||
{/* Right side content */}
|
||||
<Box sx={{
|
||||
flex: 1,
|
||||
pl: theme.spacing(7.5),
|
||||
overflowY: 'auto'
|
||||
}}>
|
||||
{activeNotificationTypes.map((type, index) => (
|
||||
<TabPanel key={type.id} value={tabValue} index={index}>
|
||||
<TabComponent
|
||||
type={type}
|
||||
integrations={integrations}
|
||||
handleIntegrationChange={handleIntegrationChange}
|
||||
handleInputChange={handleInputChange}
|
||||
handleTestNotification={handleTestNotification}
|
||||
/>
|
||||
</TabPanel>
|
||||
))}
|
||||
</Box>
|
||||
</Box>
|
||||
</DialogContent>
|
||||
<DialogActions sx={{
|
||||
p: theme.spacing(4),
|
||||
display: 'flex',
|
||||
justifyContent: 'flex-end',
|
||||
mb: theme.spacing(5),
|
||||
mr: theme.spacing(5)
|
||||
}}>
|
||||
<Button
|
||||
variant="contained"
|
||||
color="accent"
|
||||
onClick={handleSave}
|
||||
sx={{
|
||||
width: 'auto',
|
||||
minWidth: theme.spacing(60),
|
||||
px: theme.spacing(8)
|
||||
}}
|
||||
>
|
||||
{t('common.save', 'Save')}
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
|
||||
export default NotificationIntegrationModal;
|
||||
@@ -0,0 +1,100 @@
|
||||
import React from "react";
|
||||
import {
|
||||
Typography,
|
||||
Box,
|
||||
Button
|
||||
} from "@mui/material";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useTheme } from "@emotion/react";
|
||||
import TextInput from "../../../src/Components/Inputs/TextInput";
|
||||
import Checkbox from "../../../src/Components/Inputs/Checkbox";
|
||||
|
||||
const TabComponent = ({
|
||||
type,
|
||||
integrations,
|
||||
handleIntegrationChange,
|
||||
handleInputChange,
|
||||
handleTestNotification
|
||||
}) => {
|
||||
const theme = useTheme();
|
||||
const { t } = useTranslation();
|
||||
|
||||
// Helper to get the field state key (e.g., slackWebhook, telegramToken)
|
||||
const getFieldKey = (typeId, fieldId) => {
|
||||
return `${typeId}${fieldId.charAt(0).toUpperCase() + fieldId.slice(1)}`;
|
||||
};
|
||||
|
||||
// Check if all fields have values to enable test button
|
||||
const areAllFieldsFilled = () => {
|
||||
return type.fields.every(field => {
|
||||
const fieldKey = getFieldKey(type.id, field.id);
|
||||
return integrations[fieldKey];
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Typography variant="subtitle1" component="h4" sx={{
|
||||
fontWeight: 'bold',
|
||||
color: theme.palette.primary.contrastTextSecondary
|
||||
}}>
|
||||
{type.label}
|
||||
</Typography>
|
||||
|
||||
<Typography sx={{
|
||||
mt: theme.spacing(0.5),
|
||||
mb: theme.spacing(1.5),
|
||||
color: theme.palette.primary.contrastTextTertiary
|
||||
}}>
|
||||
{type.description}
|
||||
</Typography>
|
||||
|
||||
<Box sx={{ pl: theme.spacing(1.5) }}>
|
||||
<Checkbox
|
||||
id={`enable-${type.id}`}
|
||||
label={t('notifications.enableNotifications', { platform: type.label })}
|
||||
isChecked={integrations[type.id]}
|
||||
onChange={(e) => handleIntegrationChange(type.id, e.target.checked)}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
{type.fields.map(field => {
|
||||
const fieldKey = getFieldKey(type.id, field.id);
|
||||
|
||||
return (
|
||||
<Box key={field.id} sx={{ mt: theme.spacing(1) }}>
|
||||
<Typography sx={{
|
||||
mb: theme.spacing(2),
|
||||
fontWeight: 'bold',
|
||||
color: theme.palette.primary.contrastTextSecondary
|
||||
}}>
|
||||
{field.label}
|
||||
</Typography>
|
||||
|
||||
<TextInput
|
||||
id={`${type.id}-${field.id}`}
|
||||
type={field.type}
|
||||
placeholder={field.placeholder}
|
||||
value={integrations[fieldKey]}
|
||||
onChange={(e) => handleInputChange(fieldKey, e.target.value)}
|
||||
disabled={!integrations[type.id]}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
})}
|
||||
|
||||
<Box sx={{ mt: theme.spacing(1) }}>
|
||||
<Button
|
||||
variant="text"
|
||||
color="info"
|
||||
onClick={() => handleTestNotification(type.id)}
|
||||
disabled={!integrations[type.id] || !areAllFieldsFilled()}
|
||||
>
|
||||
{t('notifications.testNotification')}
|
||||
</Button>
|
||||
</Box>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default TabComponent;
|
||||
@@ -0,0 +1,46 @@
|
||||
import React from "react";
|
||||
import PropTypes from "prop-types";
|
||||
import { Box } from "@mui/material";
|
||||
import { useTheme } from "@emotion/react";
|
||||
|
||||
/**
|
||||
* TabPanel component that displays content for the selected tab.
|
||||
*
|
||||
* @component
|
||||
* @param {Object} props - The component props.
|
||||
* @param {React.ReactNode} props.children - The content to be displayed when this tab panel is selected.
|
||||
* @param {number} props.value - The currently selected tab value.
|
||||
* @param {number} props.index - The index of this specific tab panel.
|
||||
* @param {Object} props.other - Any additional props to be spread to the root element.
|
||||
* @returns {React.ReactElement|null} The rendered tab panel or null if not selected.
|
||||
*/
|
||||
function TabPanel({ children, value, index, ...other }) {
|
||||
const theme = useTheme();
|
||||
|
||||
return (
|
||||
<div
|
||||
role="tabpanel"
|
||||
hidden={value !== index}
|
||||
id={`notification-tabpanel-${index}`}
|
||||
aria-labelledby={`notification-tab-${index}`}
|
||||
{...other}
|
||||
>
|
||||
{value === index && (
|
||||
<Box sx={{ pt: theme.spacing(3) }}>
|
||||
{children}
|
||||
</Box>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
TabPanel.propTypes = {
|
||||
|
||||
children: PropTypes.node,
|
||||
|
||||
index: PropTypes.number.isRequired,
|
||||
|
||||
value: PropTypes.number.isRequired
|
||||
};
|
||||
|
||||
export default TabPanel;
|
||||
@@ -0,0 +1,51 @@
|
||||
.wallet-adapter-dropdown {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: var(--env-var-spacing-1);
|
||||
}
|
||||
|
||||
.wallet-adapter-button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: auto;
|
||||
height: var(--env-var-height-2) !important;
|
||||
font-size: var(--env-var-font-size-medium-plus) !important;
|
||||
font-weight: 500 !important;
|
||||
margin: 0;
|
||||
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;
|
||||
}
|
||||
|
||||
.wallet-adapter-modal-title {
|
||||
font-size: var(--font-size-h5) !important;
|
||||
}
|
||||
|
||||
/* Separator styling */
|
||||
.wallet-adapter-modal-divider {
|
||||
background-color: var(--border-color) !important;
|
||||
margin: var(--spacing-md) 0 !important;
|
||||
}
|
||||
|
||||
/* Responsive fixes */
|
||||
@media (max-width: 1200px) {
|
||||
.wallet-adapter-button {
|
||||
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)
|
||||
var(--env-var-spacing-1-minus) !important;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 900px) {
|
||||
.wallet-adapter-modal-wrapper {
|
||||
flex-direction: column !important;
|
||||
}
|
||||
|
||||
.wallet-adapter-modal-divider {
|
||||
margin: var(--spacing-sm) 0 !important;
|
||||
}
|
||||
}
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
import { WalletModalProvider } from "@solana/wallet-adapter-react-ui";
|
||||
import { clusterApiUrl } from "@solana/web3.js";
|
||||
import PropTypes from "prop-types";
|
||||
import "./index.css";
|
||||
|
||||
// Default styles that can be overridden by your app
|
||||
import "@solana/wallet-adapter-react-ui/styles.css";
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
import React from 'react';
|
||||
import AppBar from '../../Components/Common/AppBar';
|
||||
import Footer from '../../Components/Common/Footer';
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
const AboutUs = () => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<div>
|
||||
<AppBar />
|
||||
<h1>About Us</h1>
|
||||
<h1>{t("aboutus")}</h1>
|
||||
<Footer />
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -5,6 +5,7 @@ import { useDispatch } from "react-redux";
|
||||
import { useNavigate } from "react-router";
|
||||
import { createToast } from "../../Utils/toastUtils";
|
||||
import { forgotPassword } from "../../Features/Auth/authSlice";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import Background from "../../assets/Images/background-grid.svg?react";
|
||||
import EmailIcon from "../../assets/icons/email.svg?react";
|
||||
import Logo from "../../assets/icons/checkmate-icon.svg?react";
|
||||
@@ -15,6 +16,7 @@ const CheckEmail = () => {
|
||||
const theme = useTheme();
|
||||
const navigate = useNavigate();
|
||||
const dispatch = useDispatch();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const [email, setEmail] = useState();
|
||||
const [disabled, setDisabled] = useState(false);
|
||||
@@ -161,9 +163,9 @@ const CheckEmail = () => {
|
||||
<EmailIcon alt="email icon" />
|
||||
</IconBox>
|
||||
</Stack>
|
||||
<Typography component="h1">Check your email</Typography>
|
||||
<Typography component="h1">{t("authCheckEmailTitle")}</Typography>
|
||||
<Typography>
|
||||
We sent a password reset link to{" "}
|
||||
{t("authCheckEmailDescription")}{" "}
|
||||
<Typography
|
||||
className="email-sent-to"
|
||||
component="span"
|
||||
@@ -181,22 +183,22 @@ const CheckEmail = () => {
|
||||
maxWidth: 400,
|
||||
}}
|
||||
>
|
||||
Open email app
|
||||
{t("authCheckEmailOpenEmailButton")}
|
||||
</Button>
|
||||
<Typography sx={{ alignSelf: "center", mt: theme.spacing(6) }}>
|
||||
Didn't receive the email?{" "}
|
||||
{t("authCheckEmailDidntReceiveEmail")}{" "}
|
||||
<Typography
|
||||
component="span"
|
||||
onClick={resendToken}
|
||||
sx={{
|
||||
color: theme.palette.primary.main,
|
||||
color: theme.palette.accent.main,
|
||||
userSelect: "none",
|
||||
pointerEvents: disabled ? "none" : "auto",
|
||||
cursor: disabled ? "default" : "pointer",
|
||||
opacity: disabled ? 0.5 : 1,
|
||||
}}
|
||||
>
|
||||
Click to resend
|
||||
{t("authCheckEmailClickToResend")}
|
||||
</Typography>
|
||||
</Typography>
|
||||
</Stack>
|
||||
@@ -205,15 +207,15 @@ const CheckEmail = () => {
|
||||
textAlign="center"
|
||||
p={theme.spacing(12)}
|
||||
>
|
||||
<Typography display="inline-block">Go back to —</Typography>
|
||||
<Typography display="inline-block">{t("goBackTo")}</Typography>
|
||||
<Typography
|
||||
component="span"
|
||||
ml={theme.spacing(2)}
|
||||
color={theme.palette.primary.main}
|
||||
color={theme.palette.accent.main}
|
||||
onClick={handleNavigate}
|
||||
sx={{ userSelect: "none" }}
|
||||
>
|
||||
Log In
|
||||
{t("authLoginTitle")}
|
||||
</Typography>
|
||||
</Box>
|
||||
</Stack>
|
||||
|
||||
@@ -11,6 +11,7 @@ import Logo from "../../assets/icons/checkmate-icon.svg?react";
|
||||
import Key from "../../assets/icons/key.svg?react";
|
||||
import Background from "../../assets/Images/background-grid.svg?react";
|
||||
import IconBox from "../../Components/IconBox";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import "./index.css";
|
||||
|
||||
const ForgotPassword = () => {
|
||||
@@ -29,6 +30,8 @@ const ForgotPassword = () => {
|
||||
email && setForm({ email: sessionStorage.getItem("email") });
|
||||
}, []);
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
||||
const handleSubmit = async (event) => {
|
||||
event.preventDefault();
|
||||
|
||||
@@ -162,7 +165,7 @@ const ForgotPassword = () => {
|
||||
<Key alt="password key icon" />
|
||||
</IconBox>
|
||||
</Stack>
|
||||
<Typography component="h1">Forgot password?</Typography>
|
||||
<Typography component="h1">{t("authForgotPasswordTitle")}</Typography>
|
||||
<Typography>No worries, we'll send you reset instructions.</Typography>
|
||||
</Box>
|
||||
<Box
|
||||
@@ -195,7 +198,7 @@ const ForgotPassword = () => {
|
||||
mt: theme.spacing(15),
|
||||
}}
|
||||
>
|
||||
Send instructions
|
||||
{t("continue")}
|
||||
</Button>
|
||||
</Box>
|
||||
</Stack>
|
||||
@@ -204,15 +207,15 @@ const ForgotPassword = () => {
|
||||
textAlign="center"
|
||||
p={theme.spacing(12)}
|
||||
>
|
||||
<Typography display="inline-block">Go back to —</Typography>
|
||||
<Typography display="inline-block">{t("goBackTo")}</Typography>
|
||||
<Typography
|
||||
component="span"
|
||||
color={theme.palette.primary.main}
|
||||
color={theme.palette.accent.main}
|
||||
ml={theme.spacing(2)}
|
||||
onClick={handleNavigate}
|
||||
sx={{ userSelect: "none" }}
|
||||
>
|
||||
Log In
|
||||
{t("authLoginTitle")}
|
||||
</Typography>
|
||||
</Box>
|
||||
</Stack>
|
||||
|
||||
@@ -8,12 +8,14 @@ import Background from "../../assets/Images/background-grid.svg?react";
|
||||
import ConfirmIcon from "../../assets/icons/check-outlined.svg?react";
|
||||
import Logo from "../../assets/icons/checkmate-icon.svg?react";
|
||||
import IconBox from "../../Components/IconBox";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import "./index.css";
|
||||
|
||||
const NewPasswordConfirmed = () => {
|
||||
const theme = useTheme();
|
||||
const navigate = useNavigate();
|
||||
const dispatch = useDispatch();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const handleNavigate = () => {
|
||||
dispatch(clearAuthState());
|
||||
@@ -97,9 +99,9 @@ const NewPasswordConfirmed = () => {
|
||||
<ConfirmIcon alt="password confirm icon" />
|
||||
</IconBox>
|
||||
</Stack>
|
||||
<Typography component="h1">Password reset</Typography>
|
||||
<Typography component="h1">{t("passwordreset")}</Typography>
|
||||
<Typography mb={theme.spacing(2)}>
|
||||
Your password has been successfully reset. Click below to log in magically.
|
||||
{t("authNewPasswordConfirmed")}
|
||||
</Typography>
|
||||
</Box>
|
||||
<Button
|
||||
@@ -110,7 +112,7 @@ const NewPasswordConfirmed = () => {
|
||||
maxWidth: 400,
|
||||
}}
|
||||
>
|
||||
Continue
|
||||
{t("continue")}
|
||||
</Button>
|
||||
</Stack>
|
||||
</Stack>
|
||||
@@ -118,15 +120,15 @@ const NewPasswordConfirmed = () => {
|
||||
textAlign="center"
|
||||
p={theme.spacing(12)}
|
||||
>
|
||||
<Typography display="inline-block">Go back to —</Typography>
|
||||
<Typography display="inline-block">{t("goBackTo")}</Typography>
|
||||
<Typography
|
||||
component="span"
|
||||
color={theme.palette.primary.main}
|
||||
color={theme.palette.accent.main}
|
||||
ml={theme.spacing(2)}
|
||||
onClick={handleNavigate}
|
||||
sx={{ userSelect: "none" }}
|
||||
>
|
||||
Log In
|
||||
{t("authLoginTitle")}
|
||||
</Typography>
|
||||
</Box>
|
||||
</Stack>
|
||||
|
||||
@@ -35,7 +35,7 @@ const LandingPage = ({ isSuperAdmin, onSignup }) => {
|
||||
textAlign="center"
|
||||
>
|
||||
<Box>
|
||||
<Typography component="h1">Sign Up</Typography>
|
||||
<Typography component="h1">{t("signUP")}</Typography>
|
||||
<Typography>
|
||||
{isSuperAdmin
|
||||
? t("authRegisterCreateSuperAdminAccount")
|
||||
@@ -67,7 +67,7 @@ const LandingPage = ({ isSuperAdmin, onSignup }) => {
|
||||
</Box>
|
||||
<Box maxWidth={400}>
|
||||
<Typography className="tos-p">
|
||||
{t("authRegisterBySigningUp")}
|
||||
{t("authRegisterBySigningUp")}{" "}
|
||||
<Typography
|
||||
component="span"
|
||||
onClick={() => {
|
||||
@@ -239,7 +239,6 @@ const Register = ({ isSuperAdmin }) => {
|
||||
const action = await dispatch(register(registerForm));
|
||||
if (action.payload.success) {
|
||||
const authToken = action.payload.data;
|
||||
localStorage.setItem("token", authToken);
|
||||
navigate("/uptime");
|
||||
createToast({
|
||||
body: "Welcome! Your account was created successfully.",
|
||||
|
||||
@@ -4,6 +4,7 @@ import { useTheme } from "@emotion/react";
|
||||
import { Box, Button, Stack, Typography } from "@mui/material";
|
||||
import ArrowBackRoundedIcon from "@mui/icons-material/ArrowBackRounded";
|
||||
import TextInput from "../../../../Components/Inputs/TextInput";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
StepOne.propTypes = {
|
||||
form: PropTypes.object,
|
||||
@@ -28,6 +29,7 @@ StepOne.propTypes = {
|
||||
function StepOne({ form, errors, onSubmit, onChange, onBack }) {
|
||||
const theme = useTheme();
|
||||
const inputRef = useRef(null);
|
||||
const { t } = useTranslation();
|
||||
|
||||
useEffect(() => {
|
||||
if (inputRef.current) {
|
||||
@@ -43,8 +45,8 @@ function StepOne({ form, errors, onSubmit, onChange, onBack }) {
|
||||
textAlign="center"
|
||||
>
|
||||
<Box>
|
||||
<Typography component="h1">Sign Up</Typography>
|
||||
<Typography>Enter your personal details</Typography>
|
||||
<Typography component="h1">{t("signUp")}</Typography>
|
||||
<Typography>{t("authRegisterStepOnePersonalDetails")}</Typography>
|
||||
</Box>
|
||||
|
||||
<Box
|
||||
@@ -107,7 +109,7 @@ function StepOne({ form, errors, onSubmit, onChange, onBack }) {
|
||||
}}
|
||||
>
|
||||
<ArrowBackRoundedIcon />
|
||||
Back
|
||||
{t("commonBack")}
|
||||
</Button>
|
||||
<Button
|
||||
variant="contained"
|
||||
@@ -123,7 +125,7 @@ function StepOne({ form, errors, onSubmit, onChange, onBack }) {
|
||||
},
|
||||
}}
|
||||
>
|
||||
Continue
|
||||
{t("continue")}
|
||||
</Button>
|
||||
</Stack>
|
||||
</Box>
|
||||
|
||||
@@ -19,6 +19,7 @@ import { monitorValidation } from "../../../Validation/validation";
|
||||
import { useParams } from "react-router-dom";
|
||||
import { useCreateDistributedUptimeMonitor } from "./Hooks/useCreateDistributedUptimeMonitor";
|
||||
import { useMonitorFetch } from "./Hooks/useMonitorFetch";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
// Constants
|
||||
const MS_PER_MINUTE = 60000;
|
||||
@@ -41,6 +42,7 @@ const parseUrl = (url) => {
|
||||
const CreateDistributedUptime = () => {
|
||||
const { monitorId } = useParams();
|
||||
const isCreate = typeof monitorId === "undefined";
|
||||
const { t } = useTranslation();
|
||||
|
||||
let BREADCRUMBS = [
|
||||
{ name: `distributed uptime`, path: "/distributed-uptime" },
|
||||
@@ -191,14 +193,14 @@ const CreateDistributedUptime = () => {
|
||||
fontSize="inherit"
|
||||
fontWeight="inherit"
|
||||
>
|
||||
monitor
|
||||
{t("monitor")}
|
||||
</Typography>
|
||||
</Typography>
|
||||
<ConfigBox>
|
||||
<Box>
|
||||
<Typography component="h2">General settings</Typography>
|
||||
<Typography component="h2">{t("settingsGeneralSettings")}</Typography>
|
||||
<Typography component="p">
|
||||
Here you can select the URL of the host, together with the type of monitor.
|
||||
{t("distributedUptimeCreateSelectURL")}
|
||||
</Typography>
|
||||
</Box>
|
||||
<Stack gap={theme.spacing(15)}>
|
||||
@@ -232,9 +234,9 @@ const CreateDistributedUptime = () => {
|
||||
</ConfigBox>
|
||||
<ConfigBox>
|
||||
<Box>
|
||||
<Typography component="h2">Checks to perform</Typography>
|
||||
<Typography component="h2">{t("distributedUptimeCreateChecks")}</Typography>
|
||||
<Typography component="p">
|
||||
You can always add or remove checks after adding your site.
|
||||
{t("distributedUptimeCreateChecksDescription")}
|
||||
</Typography>
|
||||
</Box>
|
||||
<Stack gap={theme.spacing(12)}>
|
||||
@@ -259,14 +261,14 @@ const CreateDistributedUptime = () => {
|
||||
filled={https.toString()}
|
||||
onClick={() => setHttps(true)}
|
||||
>
|
||||
HTTPS
|
||||
{t("https")}
|
||||
</Button>
|
||||
<Button
|
||||
variant="group"
|
||||
filled={(!https).toString()}
|
||||
onClick={() => setHttps(false)}
|
||||
>
|
||||
HTTP
|
||||
{t("http")}
|
||||
</Button>
|
||||
</ButtonGroup>
|
||||
) : (
|
||||
@@ -291,9 +293,9 @@ const CreateDistributedUptime = () => {
|
||||
</ConfigBox>
|
||||
<ConfigBox>
|
||||
<Box>
|
||||
<Typography component="h2">Incident notifications</Typography>
|
||||
<Typography component="h2">{t("distributedUptimeCreateIncidentNotification")}</Typography>
|
||||
<Typography component="p">
|
||||
When there is an incident, notify users.
|
||||
{t("distributedUptimeCreateIncidentDescription")}
|
||||
</Typography>
|
||||
</Box>
|
||||
<Stack gap={theme.spacing(6)}>
|
||||
@@ -310,7 +312,7 @@ const CreateDistributedUptime = () => {
|
||||
</ConfigBox>
|
||||
<ConfigBox>
|
||||
<Box>
|
||||
<Typography component="h2">Advanced settings</Typography>
|
||||
<Typography component="h2">{t("distributedUptimeCreateAdvancedSettings")}</Typography>
|
||||
</Box>
|
||||
<Stack gap={theme.spacing(12)}>
|
||||
<Select
|
||||
|
||||
@@ -5,6 +5,7 @@ import SmartToyIcon from "@mui/icons-material/SmartToy";
|
||||
import Dot from "../../../../../Components/Dot";
|
||||
// Utils
|
||||
import { useTheme } from "@emotion/react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
const MESSAGES = [
|
||||
"I've checked the network status, and we're seeing excellent performance across all regions.",
|
||||
@@ -16,6 +17,7 @@ const MESSAGES = [
|
||||
|
||||
const ChatBot = ({ sx }) => {
|
||||
const theme = useTheme();
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<ColContainer
|
||||
backgroundColor={theme.palette.chatbot.background}
|
||||
@@ -37,7 +39,7 @@ const ChatBot = ({ sx }) => {
|
||||
color={theme.palette.chatbot.textAccent}
|
||||
sx={{ opacity: 0.4 }}
|
||||
>
|
||||
Now
|
||||
{t("now")}
|
||||
</Typography>
|
||||
</Stack>
|
||||
<Typography>{MESSAGES[Math.floor(Math.random() * MESSAGES.length)]}</Typography>
|
||||
|
||||
@@ -1,17 +1,18 @@
|
||||
// Components
|
||||
import { Box, Stack, Typography, Button } from "@mui/material";
|
||||
import { Box, Stack, Button } from "@mui/material";
|
||||
import Image from "../../../../../Components/Image";
|
||||
import SettingsIcon from "../../../../../assets/icons/settings-bold.svg?react";
|
||||
|
||||
//Utils
|
||||
import { useTheme } from "@mui/material/styles";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { useLocation } from "react-router-dom";
|
||||
import PropTypes from "prop-types";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
const Controls = ({ isDeleteOpen, setIsDeleteOpen, isDeleting, monitorId }) => {
|
||||
const theme = useTheme();
|
||||
const navigate = useNavigate();
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<Stack
|
||||
@@ -25,7 +26,7 @@ const Controls = ({ isDeleteOpen, setIsDeleteOpen, isDeleting, monitorId }) => {
|
||||
onClick={() => setIsDeleteOpen(!isDeleteOpen)}
|
||||
loading={isDeleting}
|
||||
>
|
||||
Delete
|
||||
{t("delete")}
|
||||
</Button>
|
||||
</Box>
|
||||
<Box>
|
||||
@@ -45,7 +46,7 @@ const Controls = ({ isDeleteOpen, setIsDeleteOpen, isDeleting, monitorId }) => {
|
||||
},
|
||||
}}
|
||||
>
|
||||
<SettingsIcon /> Configure
|
||||
<SettingsIcon /> {t("configure")}
|
||||
</Button>
|
||||
</Box>
|
||||
</Stack>
|
||||
|
||||
@@ -6,9 +6,11 @@ import { ColContainer } from "../../../../../Components/StandardContainer";
|
||||
|
||||
// Utils
|
||||
import { useTheme } from "@emotion/react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
const DeviceTicker = ({ data, width = "100%", connectionStatus }) => {
|
||||
const theme = useTheme();
|
||||
|
||||
const { t } = useTranslation();
|
||||
const statusColor = {
|
||||
up: theme.palette.success.main,
|
||||
down: theme.palette.error.main,
|
||||
@@ -44,13 +46,13 @@ const DeviceTicker = ({ data, width = "100%", connectionStatus }) => {
|
||||
<thead>
|
||||
<tr>
|
||||
<th style={{ textAlign: "left", paddingLeft: theme.spacing(4) }}>
|
||||
<Typography>COUNTRY</Typography>
|
||||
<Typography>{t("country")}</Typography>
|
||||
</th>
|
||||
<th style={{ textAlign: "left", paddingLeft: theme.spacing(4) }}>
|
||||
<Typography>CITY</Typography>
|
||||
<Typography>{t("city")}</Typography>
|
||||
</th>
|
||||
<th style={{ textAlign: "right", paddingLeft: theme.spacing(4) }}>
|
||||
<Typography>RESPONSE</Typography>
|
||||
<Typography>{t("response")}</Typography>
|
||||
</th>
|
||||
<th style={{ textAlign: "right", paddingLeft: theme.spacing(4) }}>
|
||||
<Typography sx={{ whiteSpace: "nowrap" }}>{"UPT BURNED"}</Typography>
|
||||
@@ -74,7 +76,7 @@ const DeviceTicker = ({ data, width = "100%", connectionStatus }) => {
|
||||
<Typography>{city}</Typography>
|
||||
</td>
|
||||
<td style={{ textAlign: "right", paddingLeft: theme.spacing(4) }}>
|
||||
<Typography>{Math.floor(dataPoint.responseTime)} ms</Typography>
|
||||
<Typography>{Math.floor(dataPoint.responseTime)} {t("ms")}</Typography>
|
||||
</td>
|
||||
<td style={{ textAlign: "right", paddingLeft: theme.spacing(4) }}>
|
||||
<Typography color={theme.palette.warning.main}>
|
||||
|
||||
+5
-2
@@ -4,9 +4,12 @@ import { formatDateWithTz } from "../../../../../../Utils/timeUtils";
|
||||
import PropTypes from "prop-types";
|
||||
import { useTheme } from "@emotion/react";
|
||||
import AccessTimeIcon from "@mui/icons-material/AccessTime";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
const CustomToolTip = ({ active, payload, label }) => {
|
||||
const uiTimezone = useSelector((state) => state.ui.timezone);
|
||||
const theme = useTheme();
|
||||
const { t } = useTranslation();
|
||||
if (active && payload && payload.length) {
|
||||
const responseTime = payload[0]?.payload?.originalAvgResponseTime
|
||||
? payload[0]?.payload?.originalAvgResponseTime
|
||||
@@ -51,7 +54,7 @@ const CustomToolTip = ({ active, payload, label }) => {
|
||||
component="span"
|
||||
sx={{ opacity: 0.8 }}
|
||||
>
|
||||
Response time:
|
||||
{t("responseTime")}
|
||||
</Typography>
|
||||
<Typography component="span">
|
||||
{Math.floor(responseTime)}
|
||||
@@ -59,7 +62,7 @@ const CustomToolTip = ({ active, payload, label }) => {
|
||||
component="span"
|
||||
sx={{ opacity: 0.8 }}
|
||||
>
|
||||
ms
|
||||
{t("ms")}
|
||||
</Typography>
|
||||
</Typography>
|
||||
</Stack>
|
||||
|
||||
+5
-2
@@ -8,8 +8,11 @@ import ResponseTimeIcon from "../../../../../assets/icons/response-time-icon.svg
|
||||
|
||||
// Utils
|
||||
import PropTypes from "prop-types";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
const DistributedUptimeResponseChart = ({ checks }) => {
|
||||
const [chartType, setChartType] = useState("bar");
|
||||
const { t } = useTranslation();
|
||||
let Chart = null;
|
||||
if (chartType === "area") {
|
||||
Chart = DistributedUptimeResponseAreaChart;
|
||||
@@ -23,13 +26,13 @@ const DistributedUptimeResponseChart = ({ checks }) => {
|
||||
direction="row"
|
||||
alignItems="center"
|
||||
>
|
||||
<Typography>Bar</Typography>
|
||||
<Typography>{t("bar")}</Typography>
|
||||
<Switch
|
||||
color="main"
|
||||
value={chartType}
|
||||
onChange={(e) => setChartType(e.target.checked ? "area" : "bar")}
|
||||
/>
|
||||
<Typography>Area</Typography>
|
||||
<Typography>{t("area")}</Typography>
|
||||
</Stack>
|
||||
<ChartBox
|
||||
icon={<ResponseTimeIcon />}
|
||||
|
||||
@@ -1,15 +1,18 @@
|
||||
import { Stack, Typography, Box } from "@mui/material";
|
||||
import SolanaLogo from "../../../../../assets/icons/solana_logo.svg?react";
|
||||
import { useTheme } from "@mui/material/styles";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
const Footer = () => {
|
||||
const theme = useTheme();
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<Stack
|
||||
justifyContent="space-between"
|
||||
alignItems="center"
|
||||
spacing={2}
|
||||
>
|
||||
<Typography variant="h2">Made with ❤️ by UpRock & Bluewave Labs</Typography>
|
||||
<Typography variant="h2">{t("distributedUptimeDetailsFooterHeading")}</Typography>
|
||||
<Stack
|
||||
width="100%"
|
||||
direction="row"
|
||||
@@ -17,12 +20,12 @@ const Footer = () => {
|
||||
justifyContent="center"
|
||||
alignItems="center"
|
||||
>
|
||||
<Typography variant="h2">Built on</Typography>
|
||||
<Typography variant="h2">{t("distributedUptimeDetailsFooterBuilt")}</Typography>
|
||||
<SolanaLogo
|
||||
width={15}
|
||||
height={15}
|
||||
/>
|
||||
<Typography variant="h2">Solana</Typography>
|
||||
<Typography variant="h2">{t("distributedUptimeDetailsFooterSolana")}</Typography>
|
||||
</Stack>
|
||||
</Stack>
|
||||
);
|
||||
|
||||
@@ -1,15 +1,17 @@
|
||||
import { Stack, Typography } from "@mui/material";
|
||||
import { useTheme } from "@mui/material/styles";
|
||||
import PropTypes from "prop-types";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
const MonitorHeader = ({ monitor }) => {
|
||||
const theme = useTheme();
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<Stack direction="row">
|
||||
<Stack gap={theme.spacing(2)}>
|
||||
<Typography variant="h1">{monitor.name}</Typography>
|
||||
<Typography variant="h2">
|
||||
Distributed Uptime Monitoring powered by DePIN
|
||||
{t("distributedUptimeDetailsMonitorHeader")}
|
||||
</Typography>
|
||||
</Stack>
|
||||
</Stack>
|
||||
|
||||
@@ -9,11 +9,6 @@ import PropTypes from "prop-types";
|
||||
|
||||
const StatBoxes = ({ monitor, lastUpdateTrigger }) => {
|
||||
const theme = useTheme();
|
||||
let timeSinceLastCheck = 0;
|
||||
|
||||
if (monitor?.lastCheckTimestamp) {
|
||||
timeSinceLastCheck = Date.now() - monitor?.lastCheckTimestamp;
|
||||
}
|
||||
|
||||
return (
|
||||
<Stack
|
||||
@@ -37,7 +32,7 @@ const StatBoxes = ({ monitor, lastUpdateTrigger }) => {
|
||||
heading={"Last check"}
|
||||
subHeading={
|
||||
<LastUpdate
|
||||
lastUpdateTime={timeSinceLastCheck}
|
||||
lastUpdateTime={monitor?.timeSinceLastCheck}
|
||||
suffix={"seconds ago"}
|
||||
/>
|
||||
}
|
||||
|
||||
@@ -9,9 +9,11 @@ import ShareComponent from "../../../../../Components/ShareComponent";
|
||||
// Utils
|
||||
import { useTheme } from "@emotion/react";
|
||||
import PropTypes from "prop-types";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
const StatusHeader = ({ monitor, connectionStatus, elementToCapture }) => {
|
||||
const theme = useTheme();
|
||||
const { t } = useTranslation();
|
||||
const COLOR_MAP = {
|
||||
up: theme.palette.successSecondary.main,
|
||||
down: theme.palette.error.lowContrast,
|
||||
@@ -28,7 +30,6 @@ const StatusHeader = ({ monitor, connectionStatus, elementToCapture }) => {
|
||||
};
|
||||
|
||||
let bgColor = COLOR_MAP[connectionStatus];
|
||||
|
||||
return (
|
||||
<ColContainer backgroundColor={bgColor}>
|
||||
<Stack
|
||||
@@ -59,17 +60,18 @@ const StatusHeader = ({ monitor, connectionStatus, elementToCapture }) => {
|
||||
backgroundColor={theme.palette.successSecondary.lowContrast}
|
||||
color={theme.palette.success.lowContrast}
|
||||
>
|
||||
Uptime: {(monitor.totalUptime * 100).toFixed(2)}%
|
||||
{t("distributedUptimeDetailsStatusHeaderUptime")}{" "}
|
||||
{(monitor.uptimePercentage * 100).toFixed(2)}%
|
||||
</Typography>
|
||||
</Stack>
|
||||
<Typography
|
||||
variant="body2"
|
||||
color={theme.palette.success.lowContrast}
|
||||
>
|
||||
Last updated{" "}
|
||||
{t("distributedUptimeDetailsStatusHeaderLastUpdate")}{" "}
|
||||
<LastUpdate
|
||||
suffix={"seconds ago"}
|
||||
lastUpdateTime={monitor.timeSinceLastCheck}
|
||||
lastUpdateTime={monitor?.timeSinceLastCheck}
|
||||
/>
|
||||
</Typography>
|
||||
</Stack>
|
||||
|
||||
@@ -22,6 +22,7 @@ import { useIsAdmin } from "../../../Hooks/useIsAdmin";
|
||||
import { useSubscribeToDetails } from "./Hooks/useSubscribeToDetails";
|
||||
import { useDeleteMonitor } from "./Hooks/useDeleteMonitor";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
const DistributedUptimeDetails = () => {
|
||||
const { monitorId } = useParams();
|
||||
@@ -32,6 +33,7 @@ const DistributedUptimeDetails = () => {
|
||||
// Utils
|
||||
const theme = useTheme();
|
||||
const isAdmin = useIsAdmin();
|
||||
const { t } = useTranslation();
|
||||
const navigate = useNavigate();
|
||||
const [isLoading, networkError, connectionStatus, monitor, lastUpdateTrigger] =
|
||||
useSubscribeToDetails({ monitorId, dateRange });
|
||||
@@ -55,9 +57,9 @@ const DistributedUptimeDetails = () => {
|
||||
marginY={theme.spacing(4)}
|
||||
color={theme.palette.primary.contrastTextTertiary}
|
||||
>
|
||||
Network error
|
||||
{t("networkError")}
|
||||
</Typography>
|
||||
<Typography>Please check your connection</Typography>
|
||||
<Typography>{t("checkConnection")}</Typography>
|
||||
</GenericFallback>
|
||||
);
|
||||
}
|
||||
@@ -70,7 +72,7 @@ const DistributedUptimeDetails = () => {
|
||||
<Stack gap={theme.spacing(10)}>
|
||||
<Breadcrumbs list={BREADCRUMBS} />
|
||||
<GenericFallback>
|
||||
<Typography>There is no check history for this monitor yet.</Typography>
|
||||
<Typography>{t("distributedUptimeDetailsNoMonitorHistory")}</Typography>
|
||||
</GenericFallback>
|
||||
</Stack>
|
||||
);
|
||||
|
||||
@@ -13,6 +13,7 @@ import { useTheme } from "@mui/material/styles";
|
||||
import { useIsAdmin } from "../../../Hooks/useIsAdmin";
|
||||
import { useSubscribeToMonitors } from "./Hooks/useSubscribeToMonitors";
|
||||
import SkeletonLayout from "./Components/Skeleton";
|
||||
import { useTranslation } from "react-i18next";
|
||||
// Constants
|
||||
const BREADCRUMBS = [{ name: `Distributed Uptime`, path: "/distributed-uptime" }];
|
||||
|
||||
@@ -24,6 +25,7 @@ const DistributedUptimeMonitors = () => {
|
||||
// Utils
|
||||
const theme = useTheme();
|
||||
const isAdmin = useIsAdmin();
|
||||
const { t } = useTranslation();
|
||||
const [isLoading, networkError, monitors, monitorsSummary, filteredMonitors] =
|
||||
useSubscribeToMonitors(page, rowsPerPage);
|
||||
// Handlers
|
||||
@@ -48,9 +50,9 @@ const DistributedUptimeMonitors = () => {
|
||||
marginY={theme.spacing(4)}
|
||||
color={theme.palette.primary.contrastTextTertiary}
|
||||
>
|
||||
Network error
|
||||
{t("networkError")}
|
||||
</Typography>
|
||||
<Typography>Please check your connection</Typography>
|
||||
<Typography>{t("checkConnection")}</Typography>
|
||||
</GenericFallback>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -298,7 +298,7 @@ const Settings = () => {
|
||||
{t("settingsWalletDescription")}
|
||||
</Typography>
|
||||
</Box>
|
||||
<Box>
|
||||
<Box sx={{ display: 'flex', flexWrap: 'wrap', justifyContent: 'flex-start', gap: 2 }}>
|
||||
<Stack
|
||||
direction="row"
|
||||
spacing={2}
|
||||
@@ -401,6 +401,7 @@ const Settings = () => {
|
||||
</ConfigBox>
|
||||
)}
|
||||
|
||||
|
||||
<ConfigBox>
|
||||
<Box>
|
||||
<Typography component="h1">{t("settingsAbout")}</Typography>
|
||||
|
||||
@@ -24,7 +24,6 @@ const PublicStatus = () => {
|
||||
const [isDeleteOpen, setIsDeleteOpen] = useState(false);
|
||||
// Utils
|
||||
const theme = useTheme();
|
||||
const isAdmin = useIsAdmin();
|
||||
const location = useLocation();
|
||||
const navigate = useNavigate();
|
||||
|
||||
@@ -33,7 +32,6 @@ const PublicStatus = () => {
|
||||
const [deleteStatusPage, isDeleting] = useStatusPageDelete(fetchStatusPage, url);
|
||||
|
||||
// Setup
|
||||
const currentPath = location.pathname;
|
||||
let sx = { paddingLeft: theme.spacing(20), paddingRight: theme.spacing(20) };
|
||||
let link = undefined;
|
||||
const isPublic = location.pathname.startsWith("/status/uptime/public");
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import DataTable from "../../../../../Components/Table";
|
||||
import { useTheme } from "@emotion/react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { ColoredLabel } from "../../../../../Components/Label";
|
||||
import { StatusLabel } from "../../../../../Components/Label";
|
||||
import ArrowOutwardIcon from "@mui/icons-material/ArrowOutward";
|
||||
import { Stack, Typography } from "@mui/material";
|
||||
const StatusPagesTable = ({ data }) => {
|
||||
@@ -25,7 +25,7 @@ const StatusPagesTable = ({ data }) => {
|
||||
row.type === "distributed"
|
||||
? `/status/distributed/public/${row.url}`
|
||||
: `/status/uptime/public/${row.url}`;
|
||||
navigate(url);
|
||||
window.open(url, "_blank", "noopener,noreferrer")
|
||||
}
|
||||
},
|
||||
render: (row) => {
|
||||
@@ -66,12 +66,11 @@ const StatusPagesTable = ({ data }) => {
|
||||
id: "status",
|
||||
content: "Status",
|
||||
render: (row) => {
|
||||
const status = row.isPublished ? "published" : "unpublished";
|
||||
return (
|
||||
<ColoredLabel
|
||||
label={row.isPublished ? "Published" : "Unpublished"}
|
||||
color={
|
||||
row.isPublished ? theme.palette.success.main : theme.palette.warning.main
|
||||
}
|
||||
<StatusLabel
|
||||
status={status}
|
||||
text={row.isPublished ? "Published" : "Unpublished"}
|
||||
/>
|
||||
);
|
||||
},
|
||||
|
||||
@@ -23,6 +23,7 @@ import Radio from "../../../Components/Inputs/Radio";
|
||||
import Checkbox from "../../../Components/Inputs/Checkbox";
|
||||
import Select from "../../../Components/Inputs/Select";
|
||||
import ConfigBox from "../../../Components/ConfigBox";
|
||||
import NotificationIntegrationModal from "../../../Components/NotificationIntegrationModal/NotificationIntegrationModal";
|
||||
const CreateMonitor = () => {
|
||||
const MS_PER_MINUTE = 60000;
|
||||
const SELECT_VALUES = [
|
||||
@@ -80,6 +81,11 @@ const CreateMonitor = () => {
|
||||
];
|
||||
|
||||
// State
|
||||
const [isNotificationModalOpen, setIsNotificationModalOpen] = useState(false);
|
||||
|
||||
const handleOpenNotificationModal = () => {
|
||||
setIsNotificationModalOpen(true);
|
||||
};
|
||||
const [errors, setErrors] = useState({});
|
||||
const [https, setHttps] = useState(true);
|
||||
const [monitor, setMonitor] = useState({
|
||||
@@ -407,15 +413,15 @@ const CreateMonitor = () => {
|
||||
onChange={(event) => handleNotifications(event, "email")}
|
||||
/>
|
||||
|
||||
<Box mt={theme.spacing(2)}>
|
||||
{/* <Box mt={theme.spacing(2)}>
|
||||
<Button
|
||||
variant="contained"
|
||||
color="accent"
|
||||
onClick={handleAddNotification}
|
||||
onClick={handleOpenNotificationModal}
|
||||
>
|
||||
Notification Integration
|
||||
</Button>
|
||||
</Box>
|
||||
</Box> */}
|
||||
|
||||
</Stack>
|
||||
</ConfigBox>
|
||||
@@ -512,6 +518,13 @@ const CreateMonitor = () => {
|
||||
</Button>
|
||||
</Stack>
|
||||
</Stack>
|
||||
|
||||
<NotificationIntegrationModal
|
||||
open={isNotificationModalOpen}
|
||||
onClose={() => setIsNotificationModalOpen(false)}
|
||||
monitor={monitor}
|
||||
setMonitor={setMonitor}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -2,18 +2,30 @@ import { useState } from "react";
|
||||
import { useEffect } from "react";
|
||||
import { networkService } from "../../../../main";
|
||||
import { createToast } from "../../../../Utils/toastUtils";
|
||||
export const useChecksFetch = ({ monitorId, dateRange, page, rowsPerPage }) => {
|
||||
export const useChecksFetch = ({
|
||||
monitorId,
|
||||
monitorType,
|
||||
dateRange,
|
||||
page,
|
||||
rowsPerPage,
|
||||
}) => {
|
||||
const [checks, setChecks] = useState(undefined);
|
||||
const [checksCount, setChecksCount] = useState(undefined);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [networkError, setNetworkError] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (!monitorType) {
|
||||
console.warn("Monitor Type is not provided. Fetching checks will not proceed.");
|
||||
return;
|
||||
}
|
||||
|
||||
const fetchChecks = async () => {
|
||||
try {
|
||||
setIsLoading(true);
|
||||
const res = await networkService.getChecksByMonitor({
|
||||
monitorId: monitorId,
|
||||
type: monitorType,
|
||||
sortOrder: "desc",
|
||||
limit: null,
|
||||
dateRange: dateRange,
|
||||
@@ -31,7 +43,7 @@ export const useChecksFetch = ({ monitorId, dateRange, page, rowsPerPage }) => {
|
||||
}
|
||||
};
|
||||
fetchChecks();
|
||||
}, [monitorId, dateRange, page, rowsPerPage]);
|
||||
}, [monitorId, monitorType, dateRange, page, rowsPerPage]);
|
||||
|
||||
return [checks, checksCount, isLoading, networkError];
|
||||
};
|
||||
|
||||
@@ -59,8 +59,11 @@ const UptimeDetails = () => {
|
||||
uiTimezone,
|
||||
});
|
||||
|
||||
const monitorType = monitor?.type;
|
||||
|
||||
const [checks, checksCount, checksAreLoading, checksNetworkError] = useChecksFetch({
|
||||
monitorId,
|
||||
monitorType,
|
||||
dateRange,
|
||||
page,
|
||||
rowsPerPage,
|
||||
|
||||
+213
-35
@@ -117,6 +117,33 @@ const baseTheme = (palette) => ({
|
||||
color: `${theme.palette.secondary.contrastText} !important`,
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
props: { variant: 'text', color: 'info' },
|
||||
style: {
|
||||
textDecoration: 'underline',
|
||||
color: theme.palette.text.primary,
|
||||
padding: 0,
|
||||
margin: 0,
|
||||
fontSize: typographyLevels.m,
|
||||
fontWeight: theme.typography.body2.fontWeight,
|
||||
backgroundColor: 'transparent',
|
||||
'&:hover': {
|
||||
backgroundColor: 'transparent',
|
||||
textDecoration: 'underline'
|
||||
},
|
||||
"&.Mui-disabled": {
|
||||
backgroundColor: theme.palette.secondary.main,
|
||||
color: theme.palette.primary.contrastText,
|
||||
"&.MuiButton-text": {
|
||||
backgroundColor: 'transparent'
|
||||
}
|
||||
},
|
||||
minWidth: 0,
|
||||
boxShadow: 'none',
|
||||
border: 'none'
|
||||
},
|
||||
},
|
||||
],
|
||||
height: 34,
|
||||
fontWeight: 400,
|
||||
@@ -305,13 +332,13 @@ const baseTheme = (palette) => ({
|
||||
color: palette.error.main,
|
||||
opacity: 0.8,
|
||||
fontSize: "var(--env-var-font-size-medium)",
|
||||
|
||||
marginLeft: 0,
|
||||
},
|
||||
"& .MuiFormHelperText-root.Mui-error": {
|
||||
opacity: 0.8,
|
||||
fontSize: "var(--env-var-font-size-medium)",
|
||||
color: palette.error.main,
|
||||
whiteSpace: 'nowrap',
|
||||
},
|
||||
}),
|
||||
},
|
||||
@@ -328,35 +355,68 @@ const baseTheme = (palette) => ({
|
||||
},
|
||||
MuiTab: {
|
||||
styleOverrides: {
|
||||
root: ({ theme }) => ({
|
||||
fontSize: 13,
|
||||
color: theme.palette.tertiary.contrastText,
|
||||
backgroundColor: theme.palette.tertiary.main,
|
||||
textTransform: "none",
|
||||
minWidth: "fit-content",
|
||||
paddingY: theme.spacing(6),
|
||||
fontWeight: 400,
|
||||
borderBottom: "2px solid transparent",
|
||||
borderRight: `1px solid ${theme.palette.primary.lowContrast}`,
|
||||
"&:first-of-type": { borderTopLeftRadius: "8px" },
|
||||
"&:last-child": { borderTopRightRadius: "8px", borderRight: 0 },
|
||||
"&:focus-visible": {
|
||||
color: theme.palette.primary.contrastText,
|
||||
borderColor: theme.palette.tertiary.contrastText,
|
||||
borderRightColor: theme.palette.primary.lowContrast,
|
||||
},
|
||||
"&.Mui-selected": {
|
||||
backgroundColor: theme.palette.secondary.main,
|
||||
color: theme.palette.secondary.contrastText,
|
||||
borderColor: theme.palette.secondary.contrastText,
|
||||
borderRightColor: theme.palette.primary.lowContrast,
|
||||
},
|
||||
"&:hover": {
|
||||
borderColor: theme.palette.primary.lowContrast,
|
||||
},
|
||||
}),
|
||||
root: ({ theme }) => ({
|
||||
fontSize: theme.typography.fontSize - 1,
|
||||
color: theme.palette.tertiary.contrastText,
|
||||
backgroundColor: theme.palette.tertiary.main,
|
||||
textTransform: "none",
|
||||
minWidth: "fit-content",
|
||||
padding: `${theme.spacing(6)}px ${theme.spacing(4)}px`,
|
||||
fontWeight: 400,
|
||||
borderBottom: `${theme.shape.borderThick}px solid transparent`,
|
||||
borderRight: `${theme.shape.borderRadius / 2}px solid ${theme.palette.primary.lowContrast}`,
|
||||
"&:first-of-type": { borderTopLeftRadius: theme.shape.borderRadius * 4 },
|
||||
"&:last-child": { borderTopRightRadius: theme.shape.borderRadius * 4, borderRight: 0 },
|
||||
"&:focus-visible": {
|
||||
color: theme.palette.primary.contrastText,
|
||||
borderColor: theme.palette.tertiary.contrastText,
|
||||
borderRightColor: theme.palette.primary.lowContrast,
|
||||
},
|
||||
"&.Mui-selected": {
|
||||
backgroundColor: theme.palette.secondary.main,
|
||||
color: theme.palette.secondary.contrastText,
|
||||
borderColor: theme.palette.secondary.contrastText,
|
||||
borderRightColor: theme.palette.primary.lowContrast,
|
||||
},
|
||||
"&:hover": {
|
||||
borderColor: theme.palette.primary.lowContrast,
|
||||
},
|
||||
}),
|
||||
},
|
||||
},
|
||||
variants: [
|
||||
{
|
||||
props: { orientation: 'vertical' },
|
||||
style: ({ theme }) => ({
|
||||
alignItems: 'flex-start',
|
||||
padding: `${theme.spacing(1)}px ${theme.spacing(2)}px ${theme.spacing(1)}px ${theme.spacing(6)}px`,
|
||||
minHeight: theme.spacing(12),
|
||||
color: theme.palette.primary.contrastText,
|
||||
backgroundColor: theme.palette.primary.main,
|
||||
border: 'none',
|
||||
borderBottom: 'none',
|
||||
borderRight: 'none',
|
||||
borderRadius: theme.shape.borderRadius * 3,
|
||||
margin: `${theme.spacing(1)}px ${theme.spacing(2)}px`,
|
||||
'&.Mui-selected': {
|
||||
color: theme.palette.primary.contrastText,
|
||||
backgroundColor: theme.palette.tertiary.main,
|
||||
opacity: 1,
|
||||
border: 'none',
|
||||
borderBottom: 'none',
|
||||
borderRight: 'none',
|
||||
borderRadius: theme.shape.borderRadius * 3,
|
||||
minHeight: theme.spacing(14)
|
||||
},
|
||||
'&:hover': {
|
||||
backgroundColor: theme.palette.tertiary.main,
|
||||
border: 'none',
|
||||
borderRadius: theme.shape.borderRadius * 3,
|
||||
minHeight: theme.spacing(14)
|
||||
}
|
||||
}),
|
||||
},
|
||||
],
|
||||
},
|
||||
MuiSvgIcon: {
|
||||
styleOverrides: {
|
||||
root: ({ theme }) => ({
|
||||
@@ -366,13 +426,23 @@ const baseTheme = (palette) => ({
|
||||
},
|
||||
MuiTabs: {
|
||||
styleOverrides: {
|
||||
root: ({ theme }) => ({
|
||||
"& .MuiTabs-indicator": {
|
||||
backgroundColor: theme.palette.tertiary.contrastText,
|
||||
},
|
||||
}),
|
||||
root: ({ theme }) => ({
|
||||
"& .MuiTabs-indicator": {
|
||||
backgroundColor: theme.palette.tertiary.contrastText,
|
||||
},
|
||||
}),
|
||||
},
|
||||
},
|
||||
variants: [
|
||||
{
|
||||
props: { orientation: 'vertical' },
|
||||
style: {
|
||||
"& .MuiTabs-indicator": {
|
||||
display: 'none',
|
||||
}
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
MuiSwitch: {
|
||||
styleOverrides: {
|
||||
root: ({ theme }) => ({
|
||||
@@ -394,6 +464,114 @@ const baseTheme = (palette) => ({
|
||||
}),
|
||||
},
|
||||
},
|
||||
|
||||
// This code is added for clock in maintenance page
|
||||
// code starts from here.
|
||||
MuiClock: {
|
||||
// Directly target the clock component
|
||||
styleOverrides: {
|
||||
root: ({ theme }) => ({
|
||||
backgroundColor: theme.palette.primary.main, // Alternative target
|
||||
"& .MuiClock-clock": {
|
||||
// Inner clock face
|
||||
backgroundColor: theme.palette.secondary.main,
|
||||
},
|
||||
}),
|
||||
},
|
||||
},
|
||||
MuiClockPicker: {
|
||||
styleOverrides: {
|
||||
root: ({ theme }) => ({
|
||||
backgroundColor: theme.palette.secondary.main, // Outer container background
|
||||
"& .MuiClock-root": {
|
||||
color: theme.palette.primary.lowContrast,
|
||||
},
|
||||
"& .MuiClock-clock": {
|
||||
backgroundColor: theme.palette.background.default, // Clock face background
|
||||
borderColor: theme.palette.secondary.lowContrast,
|
||||
},
|
||||
}),
|
||||
},
|
||||
},
|
||||
// The clock pointer ( pointer to number like hour/minute hand)
|
||||
MuiClockPointer: {
|
||||
styleOverrides: {
|
||||
root: ({ theme }) => ({
|
||||
// Main pointer line color
|
||||
backgroundColor: theme.palette.accent.main,
|
||||
"& .MuiClockPointer-thumb": {
|
||||
backgroundColor: theme.palette.grey[500], // Use your desired grey
|
||||
},
|
||||
}),
|
||||
},
|
||||
},
|
||||
// This is for numbers in the clock (circular one's)
|
||||
MuiClockNumber: {
|
||||
styleOverrides: {
|
||||
root: ({ theme }) => ({
|
||||
color: theme.palette.primary.contrastText,
|
||||
"&.Mui-selected": {
|
||||
color: theme.palette.accent.contrastText,
|
||||
backgroundColor: theme.palette.accent.main,
|
||||
},
|
||||
}),
|
||||
},
|
||||
},
|
||||
// This is for 00:00 am and pm (top bar)
|
||||
MuiTimePickerToolbar: {
|
||||
styleOverrides: {
|
||||
root: ({ theme }) => ({
|
||||
backgroundColor: theme.palette.secondary.lowContrast,
|
||||
// General text color
|
||||
"& .MuiTypography-root": {
|
||||
color: theme.palette.primary.contrastTextTertiary,
|
||||
},
|
||||
// Selected time (hour/minute) color
|
||||
"& .Mui-selected": {
|
||||
color: `${theme.palette.accent.main} !important`,
|
||||
},
|
||||
// AM/PM buttons color
|
||||
"& .MuiButtonBase-root": {
|
||||
"&.Mui-selected": {
|
||||
backgroundColor: theme.palette.accent.main,
|
||||
},
|
||||
},
|
||||
}),
|
||||
},
|
||||
},
|
||||
// left and right direction style
|
||||
MuiPickersArrowSwitcher: {
|
||||
styleOverrides: {
|
||||
root: ({ theme }) => ({
|
||||
"& .MuiIconButton-root": {
|
||||
color: theme.palette.primary.contrastText,
|
||||
},
|
||||
}),
|
||||
},
|
||||
},
|
||||
// cancel and okay actions style
|
||||
MuiDialogActions: {
|
||||
styleOverrides: {
|
||||
root: ({ theme }) => ({
|
||||
backgroundColor: theme.palette.primary.main,
|
||||
"& .MuiButton-root": {
|
||||
color: theme.palette.primary.contrastText,
|
||||
},
|
||||
}),
|
||||
},
|
||||
},
|
||||
// code ends here.
|
||||
|
||||
// For labels of input fields
|
||||
MuiInputLabel: {
|
||||
styleOverrides: {
|
||||
root: ({ theme }) => ({
|
||||
"&.Mui-focused": {
|
||||
color: theme.palette.accent.main,
|
||||
},
|
||||
}),
|
||||
},
|
||||
},
|
||||
},
|
||||
shape: {
|
||||
borderRadius: 2,
|
||||
|
||||
+67
-2
@@ -1,12 +1,29 @@
|
||||
{
|
||||
"dontHaveAccount": "Don't have account",
|
||||
"https": "HTTPS",
|
||||
"http": "HTTP",
|
||||
"monitor": "monitor",
|
||||
"aboutus": "About Us",
|
||||
"email": "E-mail",
|
||||
"forgotPassword": "Forgot Password",
|
||||
"password": "password",
|
||||
"signUp": "Sign up",
|
||||
"signUP": "Sign Up",
|
||||
"submit": "Submit",
|
||||
"title": "Title",
|
||||
"continue": "Continue",
|
||||
"now": "Now",
|
||||
"delete": "Delete",
|
||||
"configure": "Configure",
|
||||
"networkError": "Network error",
|
||||
"responseTime": "Response time:",
|
||||
"ms": "ms",
|
||||
"bar": "Bar",
|
||||
"area": "Area",
|
||||
"country": "COUNTRY",
|
||||
"city": "CITY",
|
||||
"response": "RESPONSE",
|
||||
"checkConnection": "Please check your connection",
|
||||
"enterEmail": "Enter your email",
|
||||
"authLoginTitle": "Log In",
|
||||
"authLoginEnterPassword": "Enter your password",
|
||||
@@ -16,11 +33,13 @@
|
||||
"commonBack": "Back",
|
||||
"authForgotPasswordTitle": "Forgot password?",
|
||||
"authForgotPasswordResetPassword": "Reset password",
|
||||
"passwordreset": "Password Reset",
|
||||
"authRegisterAlreadyHaveAccount": "Already have an account?",
|
||||
"commonAppName": "Checkmate",
|
||||
"authLoginEnterEmail": "Enter your email",
|
||||
"authRegisterTitle": "Create an account",
|
||||
"authRegisterStepOneTitle": "Create your account",
|
||||
"authRegisterStepOnePersonalDetails": "Enter your personal details",
|
||||
"authRegisterStepOneDescription": "Enter your details to get started",
|
||||
"authRegisterStepTwoTitle": "Set up your profile",
|
||||
"authRegisterStepTwoDescription": "Tell us more about yourself",
|
||||
@@ -30,9 +49,10 @@
|
||||
"authForgotPasswordSendInstructions": "Send instructions",
|
||||
"authForgotPasswordBackTo": "Back to",
|
||||
"authCheckEmailTitle": "Check your email",
|
||||
"authCheckEmailDescription": "We sent a password reset link to {{email}}",
|
||||
"authCheckEmailDescription": "We sent a password reset link to",
|
||||
"authCheckEmailResendEmail": "Resend email",
|
||||
"authCheckEmailBackTo": "Back to",
|
||||
"authCheckEmailOpenEmailButton": "Open email app",
|
||||
"goBackTo": "Go back to",
|
||||
"authCheckEmailDidntReceiveEmail": "Didn't receive the email?",
|
||||
"authCheckEmailClickToResend": "Click to resend",
|
||||
@@ -42,6 +62,7 @@
|
||||
"authSetNewPasswordConfirmPassword": "Confirm password",
|
||||
"confirmPassword": "Confirm your password",
|
||||
"authSetNewPasswordResetPassword": "Reset password",
|
||||
"authNewPasswordConfirmed": "Your password has been successfully reset. Click below to log in magically.",
|
||||
"authSetNewPasswordBackTo": "Back to",
|
||||
"authPasswordMustBeAtLeast": "Must be at least",
|
||||
"authPasswordCharactersLong": "8 characters long",
|
||||
@@ -66,6 +87,19 @@
|
||||
"distributedRightCategoryTitle": "Monitor",
|
||||
"distributedStatusServerMonitors": "Server Monitors",
|
||||
"distributedStatusServerMonitorsDescription": "Monitor status of related servers",
|
||||
"distributedUptimeCreateSelectURL": "Here you can select the URL of the host, together with the type of monitor.",
|
||||
"distributedUptimeCreateChecks": "Checks to perform",
|
||||
"distributedUptimeCreateChecksDescription": "You can always add or remove checks after adding your site.",
|
||||
"distributedUptimeCreateIncidentNotification": "Incident notifications",
|
||||
"distributedUptimeCreateIncidentDescription": "When there is an incident, notify users.",
|
||||
"distributedUptimeCreateAdvancedSettings": "Advanced settings",
|
||||
"distributedUptimeDetailsNoMonitorHistory": "There is no check history for this monitor yet.",
|
||||
"distributedUptimeDetailsFooterHeading": "Made with ❤️ by UpRock & Bluewave Labs",
|
||||
"distributedUptimeDetailsFooterBuilt": "Built on",
|
||||
"distributedUptimeDetailsFooterSolana": "Solana",
|
||||
"distributedUptimeDetailsMonitorHeader": "Distributed Uptime Monitoring powered by DePIN",
|
||||
"distributedUptimeDetailsStatusHeaderUptime": "Uptime:",
|
||||
"distributedUptimeDetailsStatusHeaderLastUpdate": "Last updated",
|
||||
|
||||
"settingsGeneralSettings": "General settings",
|
||||
"settingsDisplayTimezone": "Display timezone",
|
||||
@@ -110,5 +144,36 @@
|
||||
"settingsFailedToDeleteMonitors": "Failed to delete all monitors",
|
||||
"starPromptTitle": "Star Checkmate",
|
||||
"starPromptDescription": "See the latest releases and help grow the community on GitHub"
|
||||
|
||||
"notifications": {
|
||||
"enableNotifications": "Enable {{platform}} notifications",
|
||||
"testNotification": "Test notification",
|
||||
"addOrEditNotifications": "Add or edit notifications",
|
||||
"slack": {
|
||||
"label": "Slack",
|
||||
"description": "To enable Slack notifications, create a Slack app and enable incoming webhooks. After that, simply provide the webhook URL here.",
|
||||
"webhookLabel": "Webhook URL",
|
||||
"webhookPlaceholder": "https://hooks.slack.com/services/..."
|
||||
},
|
||||
"discord": {
|
||||
"label": "Discord",
|
||||
"description": "To send data to a Discord channel from Checkmate via Discord notifications using webhooks, you can use Discord's incoming Webhooks feature.",
|
||||
"webhookLabel": "Discord Webhook URL",
|
||||
"webhookPlaceholder": "https://discord.com/api/webhooks/..."
|
||||
},
|
||||
"telegram": {
|
||||
"label": "Telegram",
|
||||
"description": "To enable Telegram notifications, create a Telegram bot using BotFather, an official bot for creating and managing Telegram bots. Then, get the API token and chat ID and write them down here.",
|
||||
"tokenLabel": "Your bot token",
|
||||
"tokenPlaceholder": "123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11",
|
||||
"chatIdLabel": "Your Chat ID",
|
||||
"chatIdPlaceholder": "-1001234567890"
|
||||
},
|
||||
"webhook": {
|
||||
"label": "Webhooks",
|
||||
"description": "You can set up a custom webhook to receive notifications when incidents occur.",
|
||||
"urlLabel": "Webhook URL",
|
||||
"urlPlaceholder": "https://your-server.com/webhook"
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Reference in new issue
Block a user