From fb84a79a041e265963419321179b1bbc47a3a0e4 Mon Sep 17 00:00:00 2001 From: karenvicent Date: Thu, 11 Sep 2025 16:29:08 -0400 Subject: [PATCH 001/101] Add Discord embed message formatting for notifications --- server/src/locales/en.json | 15 +++++- .../infrastructure/notificationService.js | 18 +++---- .../infrastructure/notificationUtils.js | 54 ++++++++++++++++++- server/src/service/system/stringService.js | 4 ++ 4 files changed, 78 insertions(+), 13 deletions(-) diff --git a/server/src/locales/en.json b/server/src/locales/en.json index 74296d0a5..22ab0d157 100755 --- a/server/src/locales/en.json +++ b/server/src/locales/en.json @@ -162,5 +162,18 @@ "monitorDownAlert": "Downtime Alert: One of your monitors went offline.\n📌 Monitor: {monitorName}\n📅 Time: {time}\n⚠ïļ Status: DOWN\n📟 Status Code: {code}\n\u200B\n", "sendTestEmail": "Test email sent successfully", "errorForValidEmailAddress": "A valid recipient email address is required.", - "testEmailSubject": "Test E-mail from Checkmate" + "testEmailSubject": "Test E-mail from Checkmate", + "discordNotification": { + "monitor": "Monitor", + "status": "Status", + "statusCode": "Status Code", + "time": "Time", + "up": "UP", + "down": "DOWN", + "checkmate": "Checkmate", + "url": "URL", + "unknown": "Unknown", + "uptimeAlert": "Uptime Alert: One of your monitors is back online", + "downtimeAlert": "Downtime Alert: One of your monitors went offline" + } } diff --git a/server/src/service/infrastructure/notificationService.js b/server/src/service/infrastructure/notificationService.js index 8c34519f3..ff92360e2 100644 --- a/server/src/service/infrastructure/notificationService.js +++ b/server/src/service/infrastructure/notificationService.js @@ -16,7 +16,7 @@ class NotificationService { return NotificationService.SERVICE_NAME; } - sendNotification = async ({ notification, subject, content, html }) => { + sendNotification = async ({ notification, subject, content, html, discordContent }) => { const { type, address } = notification; if (type === "email") { @@ -28,7 +28,7 @@ class NotificationService { // Create a body for webhooks let body = { text: content }; if (type === "discord") { - body = { content }; + body = !discordContent ? { content } : discordContent; } if (type === "slack" || type === "discord" || type === "webhook") { @@ -55,7 +55,6 @@ class NotificationService { const notificationIDs = networkResponse.monitor?.notifications ?? []; if (notificationIDs.length === 0) return false; - if (networkResponse.monitor.type === "hardware") { const thresholds = networkResponse?.monitor?.thresholds; @@ -63,30 +62,29 @@ class NotificationService { const metrics = networkResponse?.payload?.data ?? null; if (metrics === null) return false; // No metrics, we're done - const alerts = await this.notificationUtils.buildHardwareAlerts(networkResponse); + const [alerts, discordContent] = await this.notificationUtils.buildHardwareAlerts(networkResponse); if (alerts.length === 0) return false; const { subject, html } = await this.notificationUtils.buildHardwareEmail(networkResponse, alerts); const content = await this.notificationUtils.buildHardwareNotificationMessage(alerts); - const success = await this.notifyAll({ notificationIDs, subject, html, content }); + const success = await this.notifyAll({ notificationIDs, subject, html, content, discordContent }); return success; } // Status monitors const { subject, html } = await this.notificationUtils.buildStatusEmail(networkResponse); - const content = await this.notificationUtils.buildWebhookMessage(networkResponse); - const success = this.notifyAll({ notificationIDs, subject, html, content }); + const [content, discordContent] = await this.notificationUtils.buildWebhookMessage(networkResponse); + const success = this.notifyAll({ notificationIDs, subject, html, content, discordContent }); return success; } - async notifyAll({ notificationIDs, subject, html, content }) { + async notifyAll({ notificationIDs, subject, html, content, discordContent }) { const notifications = await this.db.notificationModule.getNotificationsByIds(notificationIDs); - // Map each notification to a test promise const promises = notifications.map(async (notification) => { try { - await this.sendNotification({ notification, subject, content, html }); + await this.sendNotification({ notification, subject, content, html, discordContent }); return true; } catch (err) { return false; diff --git a/server/src/service/infrastructure/notificationUtils.js b/server/src/service/infrastructure/notificationUtils.js index 22c7f4df7..62404ee29 100644 --- a/server/src/service/infrastructure/notificationUtils.js +++ b/server/src/service/infrastructure/notificationUtils.js @@ -70,7 +70,25 @@ class NotificationUtils { .replace("{time}", formattedTime) .replace("{code}", code || "Unknown"); } - return messageText; + const dn = this.stringService.discordNotification; + let discordMessageText = { + embeds: [ + { + title: status ? "Uptime Alert: One of your monitors is back online" : "Downtime Alert: One of your monitors went offline", + color: status ? 5763719 : 15548997, + + fields: [ + { name: dn.monitor, value: monitor?.name ?? dn.unknown, inline: true }, + { name: dn.status, value: status ? dn.up : dn.down, inline: true }, + { name: dn.statusCode, value: String(code ?? dn.unknown), inline: true }, + { name: dn.time, value: formattedTime, inline: true }, + { name: dn.url, value: monitor?.url ?? dn.unknown, inline: false }, + ], + footer: { text: dn.checkmate }, + }, + ], + }; + return [messageText, discordMessageText]; }; buildHardwareAlerts = async (networkResponse) => { @@ -88,6 +106,36 @@ class NotificationUtils { }; const alertsToSend = []; + const discordEmbeds = []; + const formatDiscordAlert = { + cpu: () => ({ + title: "CPU alert", + description: `Your current CPU usage (${(cpuUsage * 100).toFixed(0)}%) is above your threshold (${(cpuThreshold * 100).toFixed(0)}%)`, + color: 15548997, + + footer: { text: "Checkmate" }, + }), + + memory: () => ({ + title: "Memory alert", + description: `Your current memory usage (${(memoryUsage * 100).toFixed(0)}%) is above your threshold (${(memoryThreshold * 100).toFixed(0)}%)`, + color: 15548997, + + footer: { text: "Checkmate" }, + }), + + disk: () => ({ + title: "Disk alert", + description: `Your current disk usage is above your threshold (${(diskThreshold * 100).toFixed(0)}%)`, + color: 15548997, + footer: { text: "Checkmate" }, + fields: disk.map((d, idx) => ({ + name: `Disk ${idx}`, + value: `${(d.usage_percent * 100).toFixed(0)}%`, + inline: true, + })), + }), + }; const alertTypes = ["cpu", "memory", "disk"]; for (const type of alertTypes) { // Iterate over each alert type to see if any need to be decremented @@ -108,11 +156,13 @@ class NotificationUtils { .join(", ")} is above your threshold (${(diskThreshold * 100).toFixed(0)}%)`, }; alertsToSend.push(formatAlert[type]()); + discordEmbeds.push(formatDiscordAlert[type]()); } } } await monitor.save(); - return alertsToSend; + const discordPayload = discordEmbeds.length ? { embeds: discordEmbeds } : null; + return [alertsToSend, discordPayload]; }; buildHardwareEmail = async (networkResponse, alerts) => { diff --git a/server/src/service/system/stringService.js b/server/src/service/system/stringService.js index 5df3410f9..231550be7 100755 --- a/server/src/service/system/stringService.js +++ b/server/src/service/system/stringService.js @@ -216,6 +216,10 @@ class StringService { return this.translationService.getTranslation("monitorDownAlert"); } + get discordNotification() { + return this.translationService.getTranslation("discordNotification"); + } + getWebhookUnsupportedPlatform(platform) { return this.translationService.getTranslation("webhookUnsupportedPlatform").replace("{platform}", platform); } From 1008d819f7d1033e8551cfc991d10f18d7f51c2b Mon Sep 17 00:00:00 2001 From: "Gorkem Cetin (BWL)" <167266851+gorkem-bwl@users.noreply.github.com> Date: Thu, 11 Sep 2025 22:40:11 -0400 Subject: [PATCH 002/101] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 048cf0706..b93794d4f 100644 --- a/README.md +++ b/README.md @@ -52,7 +52,7 @@ Usage instructions can be found [here](https://docs.checkmate.so/checkmate-2.1). See installation instructions in [Checkmate documentation portal](https://docs.checkmate.so/checkmate-2.1/users-guide/quickstart). -Alternatively, you can also use [Coolify](https://coolify.io/), [Elestio](https://elest.io/open-source/checkmate), [K8s](./charts/helm/checkmate/INSTALLATION.md) or [Pikapods](https://www.pikapods.com/) to quickly spin off a Checkmate instance. If you would like to monitor your server infrastructure, you'll need [Capture agent](https://github.com/bluewave-labs/capture). Capture repository also contains the installation instructions. +Alternatively, you can also use [Coolify](https://coolify.io/), [Elestio](https://elest.io/open-source/checkmate), [K8s](./charts/helm/checkmate/INSTALLATION.md), [Sive Host](https://sive.host) (South Africa) or [Pikapods](https://www.pikapods.com/) to quickly spin off a Checkmate instance. If you would like to monitor your server infrastructure, you'll need [Capture agent](https://github.com/bluewave-labs/capture). Capture repository also contains the installation instructions. ### Using a Custom CA From 041a26154033a6ad4c134c56c5dfb883d5883f06 Mon Sep 17 00:00:00 2001 From: karenvicent Date: Fri, 12 Sep 2025 10:13:38 -0400 Subject: [PATCH 003/101] add llama sugestions --- server/src/service/infrastructure/notificationService.js | 4 ++-- server/src/service/infrastructure/notificationUtils.js | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/server/src/service/infrastructure/notificationService.js b/server/src/service/infrastructure/notificationService.js index ff92360e2..96fc53592 100644 --- a/server/src/service/infrastructure/notificationService.js +++ b/server/src/service/infrastructure/notificationService.js @@ -16,7 +16,7 @@ class NotificationService { return NotificationService.SERVICE_NAME; } - sendNotification = async ({ notification, subject, content, html, discordContent }) => { + sendNotification = async ({ notification, subject, content, html, discordContent = null }) => { const { type, address } = notification; if (type === "email") { @@ -79,7 +79,7 @@ class NotificationService { return success; } - async notifyAll({ notificationIDs, subject, html, content, discordContent }) { + async notifyAll({ notificationIDs, subject, html, content, discordContent = null }) { const notifications = await this.db.notificationModule.getNotificationsByIds(notificationIDs); // Map each notification to a test promise const promises = notifications.map(async (notification) => { diff --git a/server/src/service/infrastructure/notificationUtils.js b/server/src/service/infrastructure/notificationUtils.js index 62404ee29..b32886d52 100644 --- a/server/src/service/infrastructure/notificationUtils.js +++ b/server/src/service/infrastructure/notificationUtils.js @@ -74,7 +74,7 @@ class NotificationUtils { let discordMessageText = { embeds: [ { - title: status ? "Uptime Alert: One of your monitors is back online" : "Downtime Alert: One of your monitors went offline", + title: status ? dn.uptimeAlert : dn.downtimeAlert, color: status ? 5763719 : 15548997, fields: [ @@ -129,9 +129,9 @@ class NotificationUtils { description: `Your current disk usage is above your threshold (${(diskThreshold * 100).toFixed(0)}%)`, color: 15548997, footer: { text: "Checkmate" }, - fields: disk.map((d, idx) => ({ + fields: (Array.isArray(disk) ? disk : []).map((d, idx) => ({ name: `Disk ${idx}`, - value: `${(d.usage_percent * 100).toFixed(0)}%`, + value: `${(d?.usage_percent * 100).toFixed(0)}%`, inline: true, })), }), From 272e2ba95558236ae387b4dd589693e3b3d03f79 Mon Sep 17 00:00:00 2001 From: karenvicent Date: Mon, 15 Sep 2025 08:34:51 -0400 Subject: [PATCH 004/101] add monitor name and url to infrastructure alert --- .../infrastructure/notificationService.js | 16 ++++++---- .../infrastructure/notificationUtils.js | 32 +++++++++++++------ 2 files changed, 32 insertions(+), 16 deletions(-) diff --git a/server/src/service/infrastructure/notificationService.js b/server/src/service/infrastructure/notificationService.js index 96fc53592..6c5b8b25e 100644 --- a/server/src/service/infrastructure/notificationService.js +++ b/server/src/service/infrastructure/notificationService.js @@ -16,7 +16,7 @@ class NotificationService { return NotificationService.SERVICE_NAME; } - sendNotification = async ({ notification, subject, content, html, discordContent = null }) => { + sendNotification = async ({ notification, subject, content, html, discordContent = null, webhookBody = null }) => { const { type, address } = notification; if (type === "email") { @@ -30,7 +30,9 @@ class NotificationService { if (type === "discord") { body = !discordContent ? { content } : discordContent; } - + if (type === "webhook") { + body = !webhookBody ? { content } : webhookBody; + } if (type === "slack" || type === "discord" || type === "webhook") { const response = await this.networkService.requestWebhook(type, address, body); return response.status; @@ -66,9 +68,9 @@ class NotificationService { if (alerts.length === 0) return false; const { subject, html } = await this.notificationUtils.buildHardwareEmail(networkResponse, alerts); - const content = await this.notificationUtils.buildHardwareNotificationMessage(alerts); - - const success = await this.notifyAll({ notificationIDs, subject, html, content, discordContent }); + const content = await this.notificationUtils.buildHardwareNotificationMessage(alerts, monitor); + const webhookBody = await this.notificationUtils.buildHardwareWebhookBody(alerts, monitor); + const success = await this.notifyAll({ notificationIDs, subject, html, content, discordContent, webhookBody }); return success; } @@ -79,12 +81,12 @@ class NotificationService { return success; } - async notifyAll({ notificationIDs, subject, html, content, discordContent = null }) { + async notifyAll({ notificationIDs, subject, html, content, discordContent = null, webhookBody = null }) { const notifications = await this.db.notificationModule.getNotificationsByIds(notificationIDs); // Map each notification to a test promise const promises = notifications.map(async (notification) => { try { - await this.sendNotification({ notification, subject, content, html, discordContent }); + await this.sendNotification({ notification, subject, content, html, discordContent, webhookBody }); return true; } catch (err) { return false; diff --git a/server/src/service/infrastructure/notificationUtils.js b/server/src/service/infrastructure/notificationUtils.js index b32886d52..d6b92cc0e 100644 --- a/server/src/service/infrastructure/notificationUtils.js +++ b/server/src/service/infrastructure/notificationUtils.js @@ -107,12 +107,16 @@ class NotificationUtils { const alertsToSend = []; const discordEmbeds = []; + const monitorInfoFields = [ + { name: "Monitor", value: monitor.name, inline: true }, + { name: "URL", value: monitor.url, inline: false }, + ]; const formatDiscordAlert = { cpu: () => ({ title: "CPU alert", description: `Your current CPU usage (${(cpuUsage * 100).toFixed(0)}%) is above your threshold (${(cpuThreshold * 100).toFixed(0)}%)`, color: 15548997, - + fields: monitorInfoFields, footer: { text: "Checkmate" }, }), @@ -120,7 +124,7 @@ class NotificationUtils { title: "Memory alert", description: `Your current memory usage (${(memoryUsage * 100).toFixed(0)}%) is above your threshold (${(memoryThreshold * 100).toFixed(0)}%)`, color: 15548997, - + fields: monitorInfoFields, footer: { text: "Checkmate" }, }), @@ -129,11 +133,14 @@ class NotificationUtils { description: `Your current disk usage is above your threshold (${(diskThreshold * 100).toFixed(0)}%)`, color: 15548997, footer: { text: "Checkmate" }, - fields: (Array.isArray(disk) ? disk : []).map((d, idx) => ({ - name: `Disk ${idx}`, - value: `${(d?.usage_percent * 100).toFixed(0)}%`, - inline: true, - })), + fields: [ + ...monitorInfoFields, + ...(Array.isArray(disk) ? disk : []).map((d, idx) => ({ + name: `Disk ${idx}`, + value: `${(d?.usage_percent * 100).toFixed(0)}%`, + inline: true, + })), + ], }), }; const alertTypes = ["cpu", "memory", "disk"]; @@ -174,8 +181,15 @@ class NotificationUtils { return { subject, html }; }; - buildHardwareNotificationMessage = (alerts) => { - return alerts.map((alert) => alert).join("\n"); + buildHardwareNotificationMessage = (alerts, monitor) => { + const alertsHeader = [`Monitor: ${monitor.name}`, `URL: ${monitor.url}`]; + const alertText = alerts.length > 0 ? [...alertsHeader, ...alerts] : []; + return alertText.map((alert) => alert).join("\n"); + }; + buildHardwareWebhookBody = (alerts, monitor) => { + const content = alerts.map((alert) => alert).join("\n"); + const body = { text: content, name: monitor.name, url: monitor.url }; + return body; }; } From 00de974528045070abb5a0a45424bd00563cc868 Mon Sep 17 00:00:00 2001 From: karenvicent Date: Mon, 15 Sep 2025 12:28:37 -0400 Subject: [PATCH 005/101] Fix: Skip notifications for monitors during active maintenance windows --- .../SuperSimpleQueue/SuperSimpleQueueHelper.js | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/server/src/service/infrastructure/SuperSimpleQueue/SuperSimpleQueueHelper.js b/server/src/service/infrastructure/SuperSimpleQueue/SuperSimpleQueueHelper.js index ac8da9169..b348e2534 100644 --- a/server/src/service/infrastructure/SuperSimpleQueue/SuperSimpleQueueHelper.js +++ b/server/src/service/infrastructure/SuperSimpleQueue/SuperSimpleQueueHelper.js @@ -28,11 +28,12 @@ class SuperSimpleQueueHelper { return async (monitor) => { try { const monitorId = monitor._id; + const teamId = monitor.teamId; if (!monitorId) { throw new Error("No monitor id"); } - const maintenanceWindowActive = await this.isInMaintenanceWindow(monitorId); + const maintenanceWindowActive = await this.isInMaintenanceWindow(monitorId, teamId); if (maintenanceWindowActive) { this.logger.info({ message: `Monitor ${monitorId} is in maintenance window`, @@ -77,8 +78,11 @@ class SuperSimpleQueueHelper { }; }; - async isInMaintenanceWindow(monitorId) { - const maintenanceWindows = await this.db.maintenanceWindowModule.getMaintenanceWindowsByMonitorId(monitorId); + async isInMaintenanceWindow(monitorId, teamId) { + const maintenanceWindows = await this.db.maintenanceWindowModule.getMaintenanceWindowsByMonitorId({ + monitorId: monitorId.toString(), + teamId: teamId.toString(), + }); // Check for active maintenance window: const maintenanceWindowIsActive = maintenanceWindows.reduce((acc, window) => { if (window.active) { From f68341c31503444cc98fe5b74b0ad8b6abf9c2c3 Mon Sep 17 00:00:00 2001 From: Bamz Kittisak Udomsri Date: Tue, 16 Sep 2025 20:41:13 +0700 Subject: [PATCH 006/101] feat: thailang --- .github/scripts/download-translations.js | 2 +- .github/workflows/poeditor-sync.yml | 2 +- client/src/locales/th.json | 1122 ++++++++++++++++++++++ 3 files changed, 1124 insertions(+), 2 deletions(-) create mode 100644 client/src/locales/th.json diff --git a/.github/scripts/download-translations.js b/.github/scripts/download-translations.js index 185992fd5..b814a2ba2 100644 --- a/.github/scripts/download-translations.js +++ b/.github/scripts/download-translations.js @@ -7,7 +7,7 @@ import { URLSearchParams } from "url"; const API_TOKEN = process.env.POEDITOR_API; const PROJECT_ID = process.env.POEDITOR_PROJECT_ID; const LANGUAGES = ( - process.env.LANGUAGES || "ar,zh-tw,cs,en,fi,fr,de,pt-br,ru,es,tr,ja,zh-cn" + process.env.LANGUAGES || "ar,zh-tw,cs,en,fi,fr,de,pt-br,ru,es,tr,ja,zh-cn,th" ).split(","); const EXPORT_FORMAT = process.env.EXPORT_FORMAT || "key_value_json"; diff --git a/.github/workflows/poeditor-sync.yml b/.github/workflows/poeditor-sync.yml index 87396d3d0..c9bb92b72 100644 --- a/.github/workflows/poeditor-sync.yml +++ b/.github/workflows/poeditor-sync.yml @@ -7,7 +7,7 @@ on: languages: description: "Languages to synchronize (comma separated, e.g.: tr,en,es)" required: false - default: "ar,zh-tw,cs,en,fi,fr,de,pt-br,ru,es,tr,ja,zh-cn" + default: "ar,zh-tw,cs,en,fi,fr,de,pt-br,ru,es,tr,ja,zh-cn,th" format: description: "Export format (key_value_json or json)" required: false diff --git a/client/src/locales/th.json b/client/src/locales/th.json new file mode 100644 index 000000000..4e60d61e0 --- /dev/null +++ b/client/src/locales/th.json @@ -0,0 +1,1122 @@ +{ + "submit": "āļĒāļ­āļĄāļĢāļąāļš", + "title": "āļŦāļąāļ§āļ‚āđ‰āļ­", + "distributedStatusHeaderText": "āļ•āļĢāļ§āļˆāļŠāļ­āļšāđāļšāļšāđ€āļĢāļĩāļĒāļĨāđ„āļ—āļĄāđŒāļšāļ™āļ­āļļāļ›āļāļĢāļ“āđŒāļˆāļĢāļīāļ‡", + "distributedStatusSubHeaderText": "āļĢāļ°āļšāļšāļ—āļģāļ‡āļēāļ™āļ”āđ‰āļ§āļĒāļ­āļļāļ›āļāļĢāļ“āđŒāļ™āļąāļšāļĨāđ‰āļēāļ™āļ—āļąāđˆāļ§āđ‚āļĨāļ āđƒāļŦāđ‰āļ„āļļāļ“āļ•āļĢāļ§āļˆāļŠāļ­āļšāļ›āļĢāļ°āļŠāļīāļ—āļ˜āļīāļ āļēāļžāļĢāļ°āļšāļšāļ•āļēāļĄāļ āļđāļĄāļīāļ āļēāļ„ āļ›āļĢāļ°āđ€āļ—āļĻ āļŦāļĢāļ·āļ­āđ€āļĄāļ·āļ­āļ‡āđ„āļ”āđ‰", + "settingsDisabled": "āļ›āļīāļ”āļāļēāļĢāđƒāļŠāđ‰āļ‡āļēāļ™", + "settingsSuccessSaved": "āļšāļąāļ™āļ—āļķāļāļāļēāļĢāļ•āļąāđ‰āļ‡āļ„āđˆāļēāđ€āļĢāļĩāļĒāļšāļĢāđ‰āļ­āļĒāđāļĨāđ‰āļ§", + "settingsFailedToSave": "āđ„āļĄāđˆāļŠāļēāļĄāļēāļĢāļ–āļšāļąāļ™āļ—āļķāļāļāļēāļĢāļ•āļąāđ‰āļ‡āļ„āđˆāļēāđ„āļ”āđ‰", + "settingsStatsCleared": "āļŠāļ–āļīāļ•āļīāļ–āļđāļāļĨāđ‰āļēāļ‡āđ€āļĢāļĩāļĒāļšāļĢāđ‰āļ­āļĒāđāļĨāđ‰āļ§", + "settingsFailedToClearStats": "āđ„āļĄāđˆāļŠāļēāļĄāļēāļĢāļ–āļĨāđ‰āļēāļ‡āļŠāļ–āļīāļ•āļīāđ„āļ”āđ‰", + "settingsMonitorsDeleted": "āļĨāļšāļĄāļ­āļ™āļīāđ€āļ•āļ­āļĢāđŒāļ­āļ­āļāļ—āļąāđ‰āļ‡āļŦāļĄāļ”āđ€āļĢāļĩāļĒāļšāļĢāđ‰āļ­āļĒāđāļĨāđ‰āļ§", + "settingsFailedToDeleteMonitors": "āđ„āļĄāđˆāļŠāļēāļĄāļēāļĢāļ–āļĨāļšāļĄāļ­āļ™āļīāđ€āļ•āļ­āļĢāđŒāļ­āļ­āļāļ—āļąāđ‰āļ‡āļŦāļĄāļ”āđ„āļ”āđ‰", + "starPromptTitle": "āđƒāļŦāđ‰āļ”āļēāļ§āļāļąāļšāļ‰āļąāļ™", + "starPromptDescription": "āļ”āļđāļāļēāļĢāļ­āļąāļ›āđ€āļ”āļ•āļĨāđˆāļēāļŠāļļāļ” āđāļĨāļ°āļĄāļĩāļŠāđˆāļ§āļ™āļĢāđˆāļ§āļĄāđƒāļ™āļāļēāļĢāļ‚āļĒāļēāļĒāļŠāļļāļĄāļŠāļ™āļšāļ™ GitHub", + "https": "HTTPS", + "http": "HTTP", + "monitor": "monitor", + "aboutus": "āđ€āļāļĩāđˆāļĒāļ§āļāļąāļšāļ‰āļąāļ™", + "now": "āļ•āļ­āļ™āļ™āļĩāđ‰", + "delete": "āļĨāļš", + "configure": "āļ•āļąāđ‰āļ‡āļ„āđˆāļē", + "responseTime": "āļĢāļ°āļĒāļ°āđ€āļ§āļĨāļēāļāļēāļĢāļ•āļ­āļšāļŠāļ™āļ­āļ‡", + "ms": "ms", + "bar": "Bar", + "area": "āļžāļ·āđ‰āļ™āļ—āļĩāđˆ", + "country": "āļ›āļĢāļ°āđ€āļ—āļĻ", + "city": "āļˆāļąāļ‡āļŦāļ§āļąāļ”", + "response": "āļāļēāļĢāļ•āļ­āļšāļŠāļ™āļ­āļ‡", + "monitorStatusUp": "āļĄāļ­āļ™āļīāđ€āļ•āļ­āļĢāđŒ {name} ({url}) āļŠāļ–āļēāļ™āļ°āļ•āļ­āļ™āļ™āļĩāđ‰āļ­āļ­āļ™āđ„āļĨāļ™āđŒāđāļĨāļ°āļ•āļ­āļšāļŠāļ™āļ­āļ‡āļ›āļāļ•āļī", + "monitorStatusDown": "āļĄāļ­āļ™āļīāđ€āļ•āļ­āļĢāđŒ {name} ({url}) āļŠāļ–āļēāļ™āļ°āļ•āļ­āļ™āļ™āļĩāđ‰āļĨāđˆāļĄāđāļĨāļ°āđ„āļĄāđˆāļ•āļ­āļšāļŠāļ™āļ­āļ‡", + "webhookSendSuccess": "āļāļēāļĢāđāļˆāđ‰āļ‡āđ€āļ•āļ·āļ­āļ™āļœāđˆāļēāļ™ Webhook āļ–āļđāļāļŠāđˆāļ‡āđ€āļĢāļĩāļĒāļšāļĢāđ‰āļ­āļĒāđāļĨāđ‰āļ§", + "webhookSendError": "āđ„āļĄāđˆāļŠāļēāļĄāļēāļĢāļ–āļŠāđˆāļ‡āļāļēāļĢāđāļˆāđ‰āļ‡āđ€āļ•āļ·āļ­āļ™āļœāđˆāļēāļ™ Webhook āđ„āļ›āļ—āļĩāđˆ {platform} āđ„āļ”āđ‰", + "webhookUnsupportedPlatform": "āļĢāļ°āļšāļšāđ„āļĄāđˆāļĢāļ­āļ‡āļĢāļąāļšāđāļžāļĨāļ•āļŸāļ­āļĢāđŒāļĄ {platform} āļ™āļĩāđ‰", + "distributedRightCategoryTitle": "āļĄāļ­āļ™āļīāđ€āļ•āļ­āļĢāđŒ", + "distributedStatusServerMonitors": "āļĄāļ­āļ™āļīāđ€āļ•āļ­āļĢāđŒāđ€āļŠāļīāļĢāđŒāļŸāđ€āļ§āļ­āļĢāđŒ", + "distributedStatusServerMonitorsDescription": "āļ•āļĢāļ§āļˆāļŠāļ­āļšāļŠāļ–āļēāļ™āļ°āļ‚āļ­āļ‡āđ€āļ‹āļīāļĢāđŒāļŸāđ€āļ§āļ­āļĢāđŒāļ—āļĩāđˆāđ€āļāļĩāđˆāļĒāļ§āļ‚āđ‰āļ­āļ‡", + "distributedUptimeCreateSelectURL": "āļ„āļļāļ“āļŠāļēāļĄāļēāļĢāļ–āļĢāļ°āļšāļļ URL āļ‚āļ­āļ‡āđ‚āļŪāļŠāļ•āđŒāđāļĨāļ°āđ€āļĨāļ·āļ­āļāļ›āļĢāļ°āđ€āļ āļ—āļ‚āļ­āļ‡āļ•āļąāļ§āļ•āļĢāļ§āļˆāļŠāļ­āļšāđ„āļ”āđ‰āļ—āļĩāđˆāļ™āļĩāđˆ", + "distributedUptimeCreateChecks": "āļĢāļēāļĒāļāļēāļĢāļ•āļĢāļ§āļˆāļŠāļ­āļš", + "distributedUptimeCreateChecksDescription": "āļ„āļļāļ“āļŠāļēāļĄāļēāļĢāļ–āđ€āļžāļīāđˆāļĄāļŦāļĢāļ·āļ­āļĨāļšāļāļēāļĢāļ•āļĢāļ§āļˆāļŠāļ­āļšāđ„āļ”āđ‰āđ€āļŠāļĄāļ­āļŦāļĨāļąāļ‡āļˆāļēāļāđ€āļžāļīāđˆāļĄāđ€āļ§āđ‡āļšāđ„āļ‹āļ•āđŒāļ‚āļ­āļ‡āļ„āļļāļ“āđāļĨāđ‰āļ§", + "distributedUptimeCreateIncidentNotification": "āļāļēāļĢāđāļˆāđ‰āļ‡āđ€āļ•āļ·āļ­āļ™āļ›āļąāļāļŦāļē", + "distributedUptimeCreateIncidentDescription": "āđ€āļĄāļ·āđˆāļ­āđ€āļāļīāļ”āđ€āļŦāļ•āļļāļāļēāļĢāļ“āđŒāļœāļīāļ”āļ›āļāļ•āļī āđƒāļŦāđ‰āđāļˆāđ‰āļ‡āđ€āļ•āļ·āļ­āļ™āļœāļđāđ‰āđƒāļŠāđ‰āļ‡āļēāļ™", + "distributedUptimeCreateAdvancedSettings": "āļ•āļąāđ‰āļ‡āļ„āđˆāļēāļ‚āļąāđ‰āļ™āļŠāļđāļ‡", + "distributedUptimeDetailsNoMonitorHistory": "āļĒāļąāļ‡āđ„āļĄāđˆāļĄāļĩāļ›āļĢāļ°āļ§āļąāļ•āļīāļāļēāļĢāļ•āļĢāļ§āļˆāļŠāļ­āļšāļŠāļģāļŦāļĢāļąāļšāļĄāļ­āļ™āļīāđ€āļ•āļ­āļĢāđŒāļ™āļĩāđ‰", + "distributedUptimeDetailsStatusHeaderUptime": "āđ€āļ§āļĨāļēāļĢāļ°āļšāļšāļ—āļģāļ‡āļēāļ™:", + "distributedUptimeDetailsStatusHeaderLastUpdate": "āđāļāđ‰āđ„āļ‚āļĨāđˆāļēāļŠāļļāļ”", + "notifications": { + "enableNotifications": "āđ€āļ›āļīāļ”āļāļēāļĢāđāļˆāđ‰āļ‡āđ€āļ•āļ·āļ­āļ™āļ‚āļ­āļ‡āđāļžāļĨāļ•āļŸāļ­āļĢāđŒāļĄ {{platform}} āļ™āļĩāđ‰", + "testNotification": "āļ—āļ”āļŠāļ­āļšāļĢāļ°āļšāļšāļāļēāļĢāđāļˆāđ‰āļ‡āđ€āļ•āļ·āļ­āļ™", + "addOrEditNotifications": "āđ€āļžāļīāđˆāļĄāđāļĨāļ°āđāļāđ‰āđ„āļ‚āļĢāļ°āļšāļšāļāļēāļĢāđāļˆāđ‰āļ‡āđ€āļ•āļ·āļ­āļ™", + "slack": { + "label": "Slack", + "description": "āđ€āļžāļ·āđˆāļ­āđ€āļ›āļīāļ”āđƒāļŠāđ‰āļ‡āļēāļ™āļāļēāļĢāđāļˆāđ‰āļ‡āđ€āļ•āļ·āļ­āļ™āļœāđˆāļēāļ™ Slack āđƒāļŦāđ‰āļŠāļĢāđ‰āļēāļ‡āđāļ­āļ› Slack āđāļĨāļ°āđ€āļ›āļīāļ”āđƒāļŠāđ‰āļ‡āļēāļ™ Incoming Webhooks āļŦāļĨāļąāļ‡āļˆāļēāļāļ™āļąāđ‰āļ™ āđ€āļžāļĩāļĒāļ‡āđƒāļŠāđˆ URL āļ‚āļ­āļ‡ Webhook āļ—āļĩāđˆāļ™āļĩāđˆ", + "webhookLabel": "URL āļ‚āļ­āļ‡ Webhook/āđ€āļ§āđ‡āļšāļŪāļļāļ„", + "webhookPlaceholder": "https://hooks.slack.com/services/...", + "webhookRequired": "āļ•āđ‰āļ­āļ‡āļĢāļ°āļšāļļ URL āļ‚āļ­āļ‡ Slack webhook" + }, + "discord": { + "label": "Discord", + "description": "āđ€āļžāļ·āđˆāļ­āļŠāđˆāļ‡āļ‚āđ‰āļ­āļĄāļđāļĨāđ„āļ›āļĒāļąāļ‡āļŠāđˆāļ­āļ‡ Discord āļœāđˆāļēāļ™ Discord notifications āđ‚āļ”āļĒāđƒāļŠāđ‰ Webhooks āđƒāļŦāđ‰āđ€āļ›āļīāļ”āđƒāļŠāđ‰āļ‡āļēāļ™ Incoming Webhooks āļ‚āļ­āļ‡ Discord", + "webhookLabel": "URL āļ‚āļ­āļ‡ Webhook/āđ€āļ§āđ‡āļšāļŪāļļāļ„ āļŠāļģāļŦāļĢāļąāļš Discord", + "webhookPlaceholder": "https://discord.com/api/webhooks/...", + "webhookRequired": "āļ•āđ‰āļ­āļ‡āļĢāļ°āļšāļļ URL āļ‚āļ­āļ‡ Discord webhook" + }, + "telegram": { + "label": "Telegram", + "description": "āđ€āļžāļ·āđˆāļ­āđ€āļ›āļīāļ”āđƒāļŠāđ‰āļ‡āļēāļ™āļāļēāļĢāđāļˆāđ‰āļ‡āđ€āļ•āļ·āļ­āļ™āļœāđˆāļēāļ™ Telegram āđƒāļŦāđ‰āļŠāļĢāđ‰āļēāļ‡āļšāļ­āļ— Telegram āđ‚āļ”āļĒāđƒāļŠāđ‰ BotFather āļ‹āļķāđˆāļ‡āđ€āļ›āđ‡āļ™āļšāļ­āļ—āļ—āļēāļ‡āļāļēāļĢāļŠāļģāļŦāļĢāļąāļšāļŠāļĢāđ‰āļēāļ‡āđāļĨāļ°āļˆāļąāļ”āļāļēāļĢāļšāļ­āļ— Telegram āļˆāļēāļāļ™āļąāđ‰āļ™āļ™āļģ API token āđāļĨāļ° Chat ID āļĄāļēāđƒāļŠāđˆāļ—āļĩāđˆāļ™āļĩāđˆ", + "tokenLabel": "Token/āđ‚āļ—āđ€āļ„āļ™ āļ‚āļ­āļ‡āļšāļ­āļ—", + "tokenPlaceholder": "123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11", + "chatIdLabel": "Chat ID", + "chatIdPlaceholder": "-1001234567890", + "fieldsRequired": "āļ•āđ‰āļ­āļ‡āļĢāļ°āļšāļļāđ‚āļ—āđ€āļ„āđ‡āļ™āđāļĨāļ°āļĢāļŦāļąāļŠāđāļŠāļ—āļ‚āļ­āļ‡ Telegram" + }, + "webhook": { + "label": "Webhooks", + "description": "āļ„āļļāļ“āļŠāļēāļĄāļēāļĢāļ–āļ•āļąāđ‰āļ‡āļ„āđˆāļēāđ€āļ§āđ‡āļšāļŪāļļāļ„āđāļšāļšāļāļģāļŦāļ™āļ”āđ€āļ­āļ‡āđ€āļžāļ·āđˆāļ­āļĢāļąāļšāļāļēāļĢāđāļˆāđ‰āļ‡āđ€āļ•āļ·āļ­āļ™āđ€āļĄāļ·āđˆāļ­āđ€āļāļīāļ”āđ€āļŦāļ•āļļāļāļēāļĢāļ“āđŒāļœāļīāļ”āļ›āļāļ•āļī", + "urlLabel": "URL āļ‚āļ­āļ‡ Webhook", + "urlPlaceholder": "https://your-server.com/webhook", + "urlRequired": "āļ•āđ‰āļ­āļ‡āļĢāļ°āļšāļļ URL āļ‚āļ­āļ‡ Webhook" + }, + "testNotificationDevelop": "āļ—āļ”āļŠāļ­āļšāļāļēāļĢāđāļˆāđ‰āļ‡āđ€āļ•āļ·āļ­āļ™ 2", + "integrationButton": "āļāļēāļĢāļĢāļ§āļĄāļāļēāļĢāđāļˆāđ‰āļ‡āđ€āļ•āļ·āļ­āļ™", + "testSuccess": "āļŠāđˆāļ‡āļāļēāļĢāđāļˆāđ‰āļ‡āđ€āļ•āļ·āļ­āļ™āļ—āļ”āļŠāļ­āļšāđ€āļĢāļĩāļĒāļšāļĢāđ‰āļ­āļĒāđāļĨāđ‰āļ§!", + "testFailed": "āđ„āļĄāđˆāļŠāļēāļĄāļēāļĢāļ–āļŠāđˆāļ‡āļāļēāļĢāđāļˆāđ‰āļ‡āđ€āļ•āļ·āļ­āļ™āļ—āļ”āļŠāļ­āļšāđ„āļ”āđ‰", + "unsupportedType": "āļāļēāļĢāđāļˆāđ‰āļ‡āđ€āļ•āļ·āļ­āļ™āđ„āļĄāđˆāđ„āļ”āđ‰āļĢāļąāļšāļāļēāļĢāļŠāļ™āļąāļšāļŠāļ™āļļāļ™āļ›āļĢāļ°āđ€āļ āļ—āļ™āļĩāđ‰", + "networkError": "āđ€āļāļīāļ”āļ‚āđ‰āļ­āļœāļīāļ”āļžāļĨāļēāļ”āļ‚āļ­āļ‡āđ€āļ„āļĢāļ·āļ­āļ‚āđˆāļēāļĒ", + "fallback": { + "title": "āļŠāđˆāļ­āļ‡āļ—āļēāļ‡āđāļˆāđ‰āļ‡āđ€āļ•āļ·āļ­āļ™āđƒāļŠāđ‰āđ€āļžāļ·āđˆāļ­:", + "checks": [ + "āđāļˆāđ‰āļ‡āļ—āļĩāļĄāđ€āļāļĩāđˆāļĒāļ§āļāļąāļšāļāļēāļĢāļŦāļĒāļļāļ”āļ—āļģāļ‡āļēāļ™āļŦāļĢāļ·āļ­āļ›āļąāļāļŦāļēāļ›āļĢāļ°āļŠāļīāļ—āļ˜āļīāļ āļēāļž", + "āđāļˆāđ‰āļ‡āļ§āļīāļĻāļ§āļāļĢāđ€āļĄāļ·āđˆāļ­āđ€āļāļīāļ”āđ€āļŦāļ•āļļāļāļēāļĢāļ“āđŒ", + "āđƒāļŦāđ‰āļœāļđāđ‰āļ”āļđāđāļĨāļĢāļ°āļšāļšāļ—āļĢāļēāļšāđ€āļāļĩāđˆāļĒāļ§āļāļąāļšāļāļēāļĢāđ€āļ›āļĨāļĩāđˆāļĒāļ™āđāļ›āļĨāļ‡āļ‚āļ­āļ‡āļĢāļ°āļšāļš" + ], + "actionButton": "āļĄāļēāļŠāļĢāđ‰āļēāļ‡āļŠāđˆāļ­āļ‡āļ—āļēāļ‡āļāļēāļĢāđāļˆāđ‰āļ‡āđ€āļ•āļ·āļ­āļ™āđāļĢāļāļ‚āļ­āļ‡āļ„āļļāļ“āļāļąāļ™āđ€āļ–āļ­āļ°!" + }, + "createButton": "āļŠāļĢāđ‰āļēāļ‡āļŠāđˆāļ­āļ‡āļ—āļēāļ‡āđāļˆāđ‰āļ‡āđ€āļ•āļ·āļ­āļ™", + "createTitle": "āļŠāđˆāļ­āļ‡āļ—āļēāļ‡āđāļˆāđ‰āļ‡āđ€āļ•āļ·āļ­āļ™", + "create": { + "success": "āļŠāļĢāđ‰āļēāļ‡āļŠāđˆāļ­āļ‡āļ—āļēāļ‡āđāļˆāđ‰āļ‡āđ€āļ•āļ·āļ­āļ™āļŠāļģāđ€āļĢāđ‡āļˆāđāļĨāđ‰āļ§", + "failed": "āđ„āļĄāđˆāļŠāļēāļĄāļēāļĢāļ–āļŠāļĢāđ‰āļēāļ‡āļŠāđˆāļ­āļ‡āļ—āļēāļ‡āđāļˆāđ‰āļ‡āđ€āļ•āļ·āļ­āļ™āđ„āļ”āđ‰" + }, + "fetch": { + "success": "āļ”āļķāļ‡āļ‚āđ‰āļ­āļĄāļđāļĨāļāļēāļĢāđāļˆāđ‰āļ‡āđ€āļ•āļ·āļ­āļ™āļŠāļģāđ€āļĢāđ‡āļˆāđāļĨāđ‰āļ§", + "failed": "āđ„āļĄāđˆāļŠāļēāļĄāļēāļĢāļ–āļ”āļķāļ‡āļ‚āđ‰āļ­āļĄāļđāļĨāļāļēāļĢāđāļˆāđ‰āļ‡āđ€āļ•āļ·āļ­āļ™āđ„āļ”āđ‰" + }, + "delete": { + "success": "āļĨāļšāļŠāđˆāļ­āļ‡āļ—āļēāļ‡āđāļˆāđ‰āļ‡āđ€āļ•āļ·āļ­āļ™āļŠāļģāđ€āļĢāđ‡āļˆāđāļĨāđ‰āļ§", + "failed": "āđ„āļĄāđˆāļŠāļēāļĄāļēāļĢāļ–āļĨāļšāļŠāđˆāļ­āļ‡āļ—āļēāļ‡āđāļˆāđ‰āļ‡āđ€āļ•āļ·āļ­āļ™āđ„āļ”āđ‰" + }, + "edit": { + "success": "āļ›āļĢāļąāļšāļ›āļĢāļļāļ‡āļŠāđˆāļ­āļ‡āļ—āļēāļ‡āđāļˆāđ‰āļ‡āđ€āļ•āļ·āļ­āļ™āļŠāļģāđ€āļĢāđ‡āļˆāđāļĨāđ‰āļ§", + "failed": "āđ„āļĄāđˆāļŠāļēāļĄāļēāļĢāļ–āļ›āļĢāļąāļšāļ›āļĢāļļāļ‡āļāļēāļĢāđāļˆāđ‰āļ‡āđ€āļ•āļ·āļ­āļ™āđ„āļ”āđ‰" + }, + "test": { + "success": "āļŠāđˆāļ‡āļāļēāļĢāđāļˆāđ‰āļ‡āđ€āļ•āļ·āļ­āļ™āļ—āļ”āļŠāļ­āļšāļŠāļģāđ€āļĢāđ‡āļˆāđāļĨāđ‰āļ§", + "failed": "āđ„āļĄāđˆāļŠāļēāļĄāļēāļĢāļ–āļŠāđˆāļ‡āļāļēāļĢāđāļˆāđ‰āļ‡āđ€āļ•āļ·āļ­āļ™āļ—āļ”āļŠāļ­āļšāđ„āļ”āđ‰" + } + }, + "testLocale": "testLocale", + "add": "āđ€āļžāļīāđˆāļĄ", + "monitors": "monitors", + "distributedUptimeStatusCreateStatusPage": "āļŦāļ™āđ‰āļēāļ•āļĢāļ§āļˆāļŠāļ­āļšāļŠāļ–āļēāļ™āļ°āļĢāļ°āļšāļš", + "distributedUptimeStatusCreateStatusPageAccess": "āļŠāļīāļ—āļ˜āļīāđŒāđ€āļ‚āđ‰āļēāļ–āļķāļ‡", + "distributedUptimeStatusCreateStatusPageReady": "āđ€āļĄāļ·āđˆāļ­āļŦāļ™āđ‰āļēāļŠāļ–āļēāļ™āļ°āļžāļĢāđ‰āļ­āļĄ āļ„āļļāļ“āļŠāļēāļĄāļēāļĢāļ–āļ•āļąāđ‰āļ‡āļ„āđˆāļēāđ€āļ›āđ‡āļ™āđ€āļœāļĒāđāļžāļĢāđˆāļŠāļđāđˆāļŠāļēāļ˜āļēāļĢāļ“āļ°āđ„āļ”āđ‰", + "distributedUptimeStatusBasicInfoHeader": "āļ‚āđ‰āļ­āļĄāļđāļĨāļ—āļąāđˆāļ§āđ„āļ›", + "distributedUptimeStatusBasicInfoDescription": "āļĢāļ°āļšāļļāļŠāļ·āđˆāļ­āļšāļĢāļīāļĐāļąāļ—āđāļĨāļ°āļ‹āļąāļšāđ‚āļ”āđ€āļĄāļ™āļŠāļģāļŦāļĢāļąāļšāļŦāļ™āđ‰āļēāļŠāļ–āļēāļ™āļ°āļ‚āļ­āļ‡āļ„āļļāļ“", + "distributedUptimeStatusLogoHeader": "āđ‚āļĨāđ‚āļāđ‰", + "distributedUptimeStatusLogoDescription": "āļ­āļąāļ›āđ‚āļŦāļĨāļ”āđ‚āļĨāđ‚āļāđ‰āđƒāļŦāđ‰ Status Page āļ‚āļ­āļ‡āļ„āļļāļ“", + "distributedUptimeStatusLogoUploadButton": "āļ­āļąāļžāđ‚āļŦāļĨāļ”āđ‚āļĨāđ‚āļāđ‰", + "distributedUptimeStatusStandardMonitorsHeader": "āļĄāļ­āļ™āļīāđ€āļ•āļ­āļĢāđŒāļžāļ·āđ‰āļ™āļāļēāļ™", + "distributedUptimeStatusStandardMonitorsDescription": "āđ€āļžāļīāđˆāļĄāļ•āļąāļ§āļ•āļĢāļ§āļˆāļŠāļ­āļšāļĄāļēāļ•āļĢāļāļēāļ™āđ„āļ›āļĒāļąāļ‡ Status Page āļ‚āļ­āļ‡āļ„āļļāļ“", + "distributedUptimeStatusCreateYour": "Create your", + "distributedUptimeStatusEditYour": "Edit your", + "distributedUptimeStatusPublishedLabel": "āđ€āļœāļĒāđāļžāļĢāđˆāđāļĨāļ°āļĄāļ­āļ‡āđ€āļŦāđ‡āļ™āđ„āļ”āđ‰āļ•āđˆāļ­āļŠāļēāļ˜āļēāļĢāļ“āļ°", + "distributedUptimeStatusCompanyNameLabel": "āļŠāļ·āđˆāļ­āļšāļĢāļīāļĐāļąāļ—", + "distributedUptimeStatusPageAddressLabel": "āļ—āļĩāđˆāļ­āļĒāļđāđˆāļŦāļ™āđ‰āļēāļŠāļ–āļēāļ™āļ°āļ‚āļ­āļ‡āļ„āļļāļ“", + "distributedUptimeStatus30Days": "30 āļ§āļąāļ™", + "distributedUptimeStatus60Days": "60 āļ§āļąāļ™", + "distributedUptimeStatus90Days": "90 āļ§āļąāļ™", + "distributedUptimeStatusPageNotSetUp": "āļŦāļ™āđ‰āļēāļŠāļ–āļēāļ™āļ°āļĒāļąāļ‡āđ„āļĄāđˆāļ–āļđāļāļŠāļĢāđ‰āļēāļ‡āļ‚āļķāđ‰āļ™", + "distributedUptimeStatusContactAdmin": "āļāļĢāļļāļ“āļēāļ•āļīāļ”āļ•āđˆāļ­āļœāļđāđ‰āļ”āļđāđāļĨāļĢāļ°āļšāļšāļ‚āļ­āļ‡āļ„āļļāļ“ āđ€āļžāļ·āđˆāļ­āļ‚āļ­āļ„āļ§āļēāļĄāļŠāđˆāļ§āļĒāđ€āļŦāļĨāļ·āļ­", + "distributedUptimeStatusPageNotPublic": "āļŦāļ™āđ‰āļēāļŠāļ–āļēāļ™āļ°āļ™āļĩāđ‰āđ„āļĄāđˆāđ€āļ›āļīāļ”āđ€āļœāļĒāļ•āđˆāļ­āļŠāļēāļ˜āļēāļĢāļ“āļ°", + "distributedUptimeStatusPageDeleteDialog": "āļ„āļļāļ“āļ•āđ‰āļ­āļ‡āļāļēāļĢāļĨāļšāļŦāļ™āđ‰āļēāļŠāļ–āļēāļ™āļ°āļ™āļĩāđ‰āļŦāļĢāļ·āļ­āđ„āļĄāđˆ?", + "distributedUptimeStatusPageDeleteConfirm": "āđƒāļŠāđˆ āļ‰āļąāļ™āļ•āđ‰āļ­āļ‡āļāļēāļĢāļĨāļšāļŦāļ™āđ‰āļēāļŠāļ–āļēāļ™āļ°āļ™āļĩāđ‰", + "distributedUptimeStatusPageDeleteDescription": "āđ€āļĄāļ·āđˆāļ­āļ–āļđāļāļĨāļšāđāļĨāđ‰āļ§ āļŦāļ™āđ‰āļēāļŠāļ–āļēāļ™āļ°āļ‚āļ­āļ‡āļ„āļļāļ“āļˆāļ°āđ„āļĄāđˆāļŠāļēāļĄāļēāļĢāļ–āļāļđāđ‰āļ„āļ·āļ™āđ„āļ”āđ‰", + "distributedUptimeStatusDevices": "āļ­āļļāļ›āļāļĢāļ“āđŒ", + "distributedUptimeStatusUpt": "UPT", + "distributedUptimeStatusUptBurned": "UPT Burned", + "distributedUptimeStatusUptLogo": "Upt Logo", + "incidentsTableNoIncidents": "āļĒāļąāļ‡āđ„āļĄāđˆāļĄāļĩāđ€āļŦāļ•āļļāļāļēāļĢāļ“āđŒāļœāļīāļ”āļ›āļāļ•āļīāļšāļąāļ™āļ—āļķāļāđ„āļ§āđ‰", + "incidentsTablePaginationLabel": "āđ€āļŦāļ•āļļāļāļēāļĢāļ“āđŒāļœāļīāļ”āļ›āļāļ•āļī", + "incidentsTableMonitorName": "āļŠāļ·āđˆāļ­āļĄāļ­āļ™āļīāđ€āļ•āļ­āļĢāđŒ", + "incidentsTableStatus": "āļŠāļ–āļēāļ™āļ°", + "incidentsTableDateTime": "āļ§āļąāļ™āđāļĨāļ°āđ€āļ§āļĨāļē", + "incidentsTableStatusCode": "āļĢāļŦāļąāļŠāļŠāļ–āļēāļ™āļ°", + "incidentsTableMessage": "āļ‚āđ‰āļ­āļ„āļ§āļēāļĄ", + "incidentsOptionsHeader": "āđ€āļŦāļ•āļļāļāļēāļĢāļ“āđŒāļœāļīāļ”āļ›āļāļ•āļīāļŠāļģāļŦāļĢāļąāļš:", + "incidentsOptionsHeaderFilterBy": "āļāļĢāļ­āļ‡āđ‚āļ”āļĒ:", + "incidentsOptionsHeaderFilterAll": "āļ—āđ‰āļąāļ‡āļŦāļĄāļ”", + "incidentsOptionsHeaderFilterDown": "āļĨāđˆāļĄ", + "incidentsOptionsHeaderFilterCannotResolve": "āđ„āļĄāđˆāđāļāđ‰āđ„āļ‚/āļŦāļēāļŠāļēāđ€āļŦāļ•āļļāđ„āļĄāđˆāđ„āļ”āđ‰", + "incidentsOptionsHeaderShow": "āđāļŠāļ”āļ‡:", + "incidentsOptionsHeaderLastHour": "āļŠāļąāđˆāļ§āđ‚āļĄāļ‡āļ—āļĩāđˆāđāļĨāđ‰āļ§", + "incidentsOptionsHeaderLastDay": "āļ§āļąāļ™āļ—āļĩāđˆāđāļĨāđ‰āļ§", + "incidentsOptionsHeaderLastWeek": "āļŠāļąāļ›āļ”āļēāļŦāđŒāļ—āļĩāđˆāđāļĨāđ‰āļ§", + "incidentsOptionsPlaceholderAllServers": "āđ€āļŠāļīāļĢāđŒāļŸāđ€āļ§āļ­āļĢāđŒāļ—āļąāđ‰āļ‡āļŦāļĄāļ”", + "infrastructureCreateYour": "āļŠāļĢāđ‰āļēāļ‡", + "infrastructureCreateGeneralSettingsDescription": "āļ„āļļāļ“āļŠāļēāļĄāļēāļĢāļ–āđ€āļĨāļ·āļ­āļ URL āļ‚āļ­āļ‡āđ‚āļŪāļŠāļ•āđŒ āļžāļĢāđ‰āļ­āļĄāļāļąāļšāļŠāļ·āđˆāļ­āļ—āļĩāđˆāđ€āļ›āđ‡āļ™āļĄāļīāļ•āļĢ Friendly Name āđāļĨāļ°āļĢāļŦāļąāļŠāļĨāļąāļšāļāļēāļĢāļ­āļ™āļļāļāļēāļ•āđ€āļžāļ·āđˆāļ­āđ€āļŠāļ·āđˆāļ­āļĄāļ•āđˆāļ­āļāļąāļšāļ•āļąāļ§āđāļ—āļ™āđ€āļ‹āļīāļĢāđŒāļŸāđ€āļ§āļ­āļĢāđŒ Server Agent", + "infrastructureServerRequirement": "āđ€āļ‹āļīāļĢāđŒāļŸāđ€āļ§āļ­āļĢāđŒāļ—āļĩāđˆāļ„āļļāļ“āļĄāļ­āļ™āļīāđ€āļ•āļ­āļĢāđŒāļ•āđ‰āļ­āļ‡āļāļģāļĨāļąāļ‡āļ—āļģāļ‡āļēāļ™āļ”āđ‰āļ§āļĒ", + "infrastructureCustomizeAlerts": "āļ›āļĢāļąāļšāđāļ•āđˆāļ‡āļāļēāļĢāđāļˆāđ‰āļ‡āđ€āļ•āļ·āļ­āļ™", + "infrastructureAlertNotificationDescription": "āļŠāđˆāļ‡āļāļēāļĢāđāļˆāđ‰āļ‡āđ€āļ•āļ·āļ­āļ™āļ–āļķāļ‡āļœāļđāđ‰āđƒāļŠāđ‰āđ€āļĄāļ·āđˆāļ­āļ„āđˆāļēāļ‚āļĩāļ”āļˆāļģāļāļąāļ”āđ€āļāļīāļ™āđ€āļ›āļ­āļĢāđŒāđ€āļ‹āđ‡āļ™āļ•āđŒāļ—āļĩāđˆāļāļģāļŦāļ™āļ”", + "infrastructureCreateMonitor": "āļŠāļĢāđ‰āļēāļ‡ Infrastructure Monitor", + "infrastructureProtocol": "āđ‚āļ›āļĢāđ‚āļ•āļ„āļ­āļĨ", + "infrastructureServerUrlLabel": "URL āļ‚āļ­āļ‡āđ€āļ‹āļīāļĢāđŒāļŸāđ€āļ§āļ­āļĢāđŒ", + "infrastructureDisplayNameLabel": "āļŠāļ·āđˆāļ­āļ—āļĩāđˆāđāļŠāļ”āļ‡", + "infrastructureAuthorizationSecretLabel": "Authorization secret / āļ„āļĩāļĒāđŒāļĨāļąāļšāļŠāļģāļŦāļĢāļąāļšāļāļēāļĢāļĒāļ·āļ™āļĒāļąāļ™āļ•āļąāļ§āļ•āļ™", + "gb": "āļāļīāļāļ°āđ„āļšāļ•āđŒ GB", + "mb": "āđ€āļĄāļāļ°āđ„āļšāļ•āđŒ MB", + "mem": "āđāļĢāļĄ", + "memoryUsage": "āļāļēāļĢāđƒāļŠāđ‰āļ‡āļēāļ™āđāļĢāļĄ", + "cpu": "āļ‹āļĩāļžāļĩāļĒāļđ", + "cpuUsage": "āļāļēāļĢāđƒāļŠāđ‰āļ‡āļēāļ™ CPU", + "cpuTemperature": "āļ­āļļāļ“āļŦāļ āļđāļĄāļī CPU", + "diskUsage": "āļāļēāļĢāđƒāļŠāđ‰āļ‡āļēāļ™ Disk", + "used": "āđƒāļŠāđ‰āļ‡āļēāļ™", + "total": "āļ—āļąāđ‰āļ‡āļŦāļĄāļ”", + "cores": "āļ„āļ­āļĢāđŒ", + "frequency": "āļ„āļ§āļēāļĄāļ–āļĩāđˆ", + "status": "āļŠāļ–āļēāļ™āļ°", + "cpuPhysical": "āļ‹āļĩāļžāļĩāļĒāļđ (āļ„āļ­āļĢāđŒāļŦāļĨāļąāļ)", + "cpuLogical": "āļ‹āļĩāļžāļĩāļĒāļđ (āļ„āļ­āļĢāđŒāđ€āļŠāļĄāļ·āļ­āļ™)", + "cpuFrequency": "āļ„āļ§āļēāļĄāļ–āļĩāđˆāļ‚āļ­āļ‡ CPU", + "avgCpuTemperature": "āļ­āļļāļ“āļŦāļ āļđāļĄāļīāđ€āļ‰āļĨāļĩāđˆāļĒāļ‚āļ­āļ‡āļ‹āļĩāļžāļĩāļĒāļđ", + "memory": "āđāļĢāļĄ", + "disk": "āļ”āļīāļŠ/āļžāļ·āđ‰āļ™āļ—āļĩāđˆ", + "uptime": "", + "os": "āļĢāļ°āļšāļšāļ›āļāļīāļšāļąāļ•āļīāļāļēāļĢ", + "host": "āđ‚āļŪāļŠ", + "actions": "āļˆāļąāļ”āļāļēāļĢ", + "integrations": "āļāļēāļĢāļĢāļ§āļĄāļĢāļ°āļšāļš", + "integrationsPrism": "āđ€āļŠāļ·āđˆāļ­āļĄāļ•āđˆāļ­ Prism āļāļąāļšāļšāļĢāļīāļāļēāļĢāļ—āļĩāđˆāļ„āļļāļ“āļŠāļ·āđˆāļ™āļŠāļ­āļš", + "integrationsSlack": "Slack", + "integrationsSlackInfo": "āđ€āļŠāļ·āđˆāļ­āļĄāļ•āđˆāļ­āļāļąāļš Slack āđāļĨāļ°āļ”āļđāđ€āļŦāļ•āļļāļāļēāļĢāļ“āđŒāļœāļīāļ”āļ›āļāļ•āļīāđƒāļ™āļŠāđˆāļ­āļ‡āļ—āļēāļ‡", + "integrationsDiscord": "Discord", + "integrationsDiscordInfo": "āđ€āļŠāļ·āđˆāļ­āļĄāļ•āđˆāļ­āļāļąāļš Discord āđāļĨāļ°āļ”āļđāđ€āļŦāļ•āļļāļāļēāļĢāļ“āđŒāļœāļīāļ”āļ›āļāļ•āļīāđ„āļ”āđ‰āđ‚āļ”āļĒāļ•āļĢāļ‡āđƒāļ™āļŠāđˆāļ­āļ‡āļ—āļēāļ‡", + "integrationsZapier": "Zapier", + "integrationsZapierInfo": "āļŠāđˆāļ‡āđ€āļŦāļ•āļļāļāļēāļĢāļ“āđŒāļ—āļąāđ‰āļ‡āļŦāļĄāļ”āđ„āļ›āļĒāļąāļ‡ Zapier āđāļĨāđ‰āļ§āļŠāļēāļĄāļēāļĢāļ–āļ”āļđāđ„āļ”āđ‰āļ—āļļāļāļ—āļĩāđˆ", + "commonSave": "āļšāļąāļ™āļ—āļķāļ", + "createYour": "Create your", + "createMonitor": "", + "pause": "āļžāļąāļāļŠāļąāđˆāļ§āļ„āļĢāļēāļ§", + "resume": "āļ”āļģāđ€āļ™āļīāļ™āļāļēāļĢāļ•āđˆāļ­", + "editing": "āļāļģāļĨāļąāļ‡āļ›āļĢāļąāļšāļ›āļĢāļļāļ‡â€Ķ", + "url": "URL", + "access": "āļāļēāļĢāđ€āļ‚āđ‰āļēāļ–āļķāļ‡", + "timezone": "āđ€āļ‚āļ•āđ€āļ§āļĨāļē", + "features": "āļŸāļĩāđ€āļˆāļ­āļĢāđŒ", + "administrator": "āļœāļđāđ‰āļ”āļđāđāļĨāļĢāļ°āļšāļš", + "loginHere": "āļāļĢāļļāļ“āļēāđ€āļ‚āđ‰āļēāļŠāļđāđˆāļĢāļ°āļšāļšāļ—āļĩāđˆāļ™āļĩāđˆ", + "displayName": "āļŠāļ·āđˆāļ­āļ—āļĩāđˆāđāļŠāļ”āļ‡", + "urlMonitor": "āļĨāļīāļ‡āļāđŒāļ—āļĩāđˆāļˆāļ°āļĄāļ­āļ™āļīāđ€āļ•āļ­āļĢāđŒ", + "portToMonitor": "āļžāļ­āļĢāđŒāļ•āļ—āļĩāđˆāļˆāļ°āļĄāļ­āļ™āļīāđ€āļ•āļ­āļĢāđŒ", + "websiteMonitoring": "āđ€āļ§āđ‡āļšāđ„āļŠāļ•āđŒāļ—āļĩāđˆāļˆāļ°āļĄāļ­āļ™āļīāđ€āļ•āļ­āļĢāđŒ", + "websiteMonitoringDescription": "āđƒāļŠāđ‰ HTTP(s) āđ€āļžāļ·āđˆāļ­āļ•āļĢāļ§āļˆāļŠāļ­āļšāđ€āļ§āđ‡āļšāđ„āļ‹āļ•āđŒāļŦāļĢāļ·āļ­ API endpoint āļ‚āļ­āļ‡āļ„āļļāļ“", + "pingMonitoring": "āļĄāļ­āļ™āļīāđ€āļ•āļ­āļĢāđŒāđāļšāļš Ping", + "pingMonitoringDescription": "āļ•āļĢāļ§āļˆāļŠāļ­āļšāļ§āđˆāļēāđ€āļ‹āļīāļĢāđŒāļŸāđ€āļ§āļ­āļĢāđŒāļ‚āļ­āļ‡āļ„āļļāļ“āļžāļĢāđ‰āļ­āļĄāđƒāļŠāđ‰āļ‡āļēāļ™āļŦāļĢāļ·āļ­āđ„āļĄāđˆ", + "dockerContainerMonitoring": "āļĄāļ­āļ™āļīāđ€āļ•āļ­āļĢāđŒāļ„āļ­āļ™āđ€āļ—āļ™āđ€āļ™āļ­āļĢāđŒāđƒāļ™ Docker", + "dockerContainerMonitoringDescription": "āļ•āļĢāļ§āļˆāļŠāļ­āļšāļ§āđˆāļēāļ„āļ­āļ™āđ€āļ—āļ™āđ€āļ™āļ­āļĢāđŒ Docker āļ‚āļ­āļ‡āļ„āļļāļ“āļāļģāļĨāļąāļ‡āļ—āļģāļ‡āļēāļ™āļ­āļĒāļđāđˆāļŦāļĢāļ·āļ­āđ„āļĄāđˆ", + "portMonitoring": "āļĄāļ­āļ™āļīāđ€āļ•āļ­āļĢāđŒāđāļšāļš Ping", + "portMonitoringDescription": "āļ•āļĢāļ§āļˆāļŠāļ­āļšāļ§āđˆāļēāļžāļ­āļĢāđŒāļ•āļ‚āļ­āļ‡āļ„āļļāļ“āđ€āļ›āļīāļ”āļ­āļĒāļđāđˆāļŦāļĢāļ·āļ­āđ„āļĄāđˆ", + "createMaintenanceWindow": "āļŠāļĢāđ‰āļēāļ‡āļŠāđˆāļ§āļ‡āđ€āļ§āļĨāļēāļšāļģāļĢāļļāļ‡āļĢāļąāļāļĐāļē", + "createMaintenance": "āļŠāļĢāđ‰āļēāļ‡āļāļēāļĢāļšāļģāļĢāļļāļ‡āļĢāļąāļāļĐāļē", + "editMaintenance": "āđāļāđ‰āđ„āļ‚āļāļēāļĢāļšāļģāļĢāļļāļ‡āļĢāļąāļāļĐāļē", + "maintenanceWindowName": "āļŠāļ·āđˆāļ­āļŦāļ™āđ‰āļēāļšāļģāļĢāļļāļ‡āļĢāļąāļāļĐāļē", + "friendlyNameInput": "āļŠāļ·āđˆāļ­āđāļŠāļ”āļ‡āļœāļĨ", + "friendlyNamePlaceholder": "Maintenance āđ€āļĢāļīāđˆāļĄāļ•āđ‰āļ™āđ€āļ§āļĨāļē __ : __ āđāļĨāļ°āđƒāļŠāđ‰āđ€āļ§āļĨāļē ___ āļ™āļēāļ—āļĩ", + "maintenanceRepeat": "āļ—āļģāļ‹āđ‰āļģāļāļēāļĢāļšāļģāļĢāļļāļ‡āļĢāļąāļāļĐāļē", + "maintenance": "āļāļēāļĢāļšāļģāļĢāļļāļ‡āļĢāļąāļāļĐāļē", + "duration": "āļĢāļ°āļĒāļ°āđ€āļ§āļĨāļē", + "addMonitors": "āđ€āļžāļīāđˆāļĄāļĄāļ­āļ™āļīāđ€āļ•āļ­āļĢāđŒ", + "window": "āļŠāđˆāļ§āļ‡āđ€āļ§āļĨāļē", + "cancel": "āļĒāļāđ€āļĨāļīāļ", + "message": "āļ‚āđ‰āļ­āļ„āļ§āļēāļĄ", + "low": "Low", + "high": "High", + "statusCode": "āļĢāļŦāļąāļŠāļŠāļ–āļēāļ™āļ°", + "date&Time": "āļ§āļąāļ™āļ—āļĩāđˆāđāļĨāļ°āđ€āļ§āļĨāļē", + "type": "āļ›āļĢāļ°āđ€āļ āļ—", + "statusPageName": "āļŠāļ·āđˆāļ­ Status Page", + "publicURL": "URL āļŠāļēāļ˜āļēāļĢāļ“āļ°", + "repeat": "āļ—āļģāļ‹āđ‰āļģ", + "edit": "āđāļāđ‰āđ„āļ‚", + "createA": "āļŠāļĢāđ‰āļēāļ‡ A", + "remove": "āļĨāļš", + "maintenanceWindowDescription": "āļŠāđˆāļ§āļ‡āđ€āļ§āļĨāļēāļāļēāļĢāļšāļģāļĢāļļāļ‡āļĢāļąāļāļĐāļē āļāļēāļĢāļ•āļĢāļ§āļˆāļŠāļ­āļšāļ—āļąāđ‰āļ‡āļŦāļĄāļ”āļ‚āļ­āļ‡āļĄāļ­āļ™āļīāđ€āļ•āļ­āļĢāđŒāļ—āļĩāđˆāļ–āļđāļāđ€āļĨāļ·āļ­āļāļˆāļ°āļ–āļđāļāļĢāļ°āļ‡āļąāļš āđ„āļĄāđˆāļ—āļģāļāļēāļĢāļ•āļĢāļ§āļˆāļŠāļ­āļšāđ€āļ„āļĢāļ·āļ­āļ‚āđˆāļēāļĒāđƒāļ” āđ† āļ—āļģāđƒāļŦāđ‰āđ„āļĄāđˆāļĄāļĩāļāļēāļĢāļ­āļąāļ›āđ€āļ”āļ•āļŠāļ–āļēāļ™āļ°āļŦāļĢāļ·āļ­āđāļˆāđ‰āļ‡āđ€āļ•āļ·āļ­āļ™āđ€āļāļīāļ”āļ‚āļķāđ‰āļ™ āļĄāļ­āļ™āļīāđ€āļ•āļ­āļĢāđŒāļ‚āļ­āļ‡āļ„āļļāļ“āļˆāļ°āļ„āļ‡āđāļŠāļ”āļ‡āļŠāļ–āļēāļ™āļ°āļĨāđˆāļēāļŠāļļāļ”āļ—āļĩāđˆāļ—āļĢāļēāļš āđāļĨāļ°āļŦāļ™āđ‰āļēāļŠāļ–āļēāļ™āļ°āļˆāļ°āļĄāļĩāļŠāļąāļāļĨāļąāļāļĐāļ“āđŒāļšāđˆāļ‡āļšāļ­āļāļ§āđˆāļēāļāļģāļĨāļąāļ‡āļ­āļĒāļđāđˆāļĢāļ°āļŦāļ§āđˆāļēāļ‡āļāļēāļĢāļšāļģāļĢāļļāļ‡āļĢāļąāļāļĐāļē āđ€āļĄāļ·āđˆāļ­āđ€āļ§āļĨāļēāļāļēāļĢāļšāļģāļĢāļļāļ‡āļĢāļąāļāļĐāļēāļŠāļīāđ‰āļ™āļŠāļļāļ” āļāļēāļĢāļ•āļĢāļ§āļˆāļŠāļ­āļšāļˆāļ°āļāļĨāļąāļšāļĄāļēāļ—āļģāļ‡āļēāļ™āđ‚āļ”āļĒāļ­āļąāļ•āđ‚āļ™āļĄāļąāļ•āļī āđāļĨāļ°āļŦāļēāļāļžāļšāļ›āļąāļāļŦāļē āļāļēāļĢāđāļˆāđ‰āļ‡āđ€āļ•āļ·āļ­āļ™āļˆāļ°āļ–āļđāļāļŠāđˆāļ‡āļ­āļ­āļ āļŠāđˆāļ§āļ‡āđ€āļ§āļĨāļēāļāļēāļĢāļšāļģāļĢāļļāļ‡āļĢāļąāļāļĐāļēāļˆāļ°āđ„āļĄāđˆāļ–āļđāļāļ™āļąāļšāļĢāļ§āļĄāđƒāļ™āļāļēāļĢāļ„āļģāļ™āļ§āļ“āļ„āļ§āļēāļĄāļžāļĢāđ‰āļ­āļĄāđƒāļŠāđ‰āļ‡āļēāļ™", + "startTime": "āđ€āļ§āļĨāļēāđ€āļĢāļīāđˆāļĄāļ•āđ‰āļ™", + "timeZoneInfo": "āļ§āļąāļ™āļ—āļĩāđˆāđāļĨāļ°āđ€āļ§āļĨāļēāļ—āļąāđ‰āļ‡āļŦāļĄāļ”āđ€āļ›āđ‡āļ™āđ€āļ§āļĨāļēāļ•āļēāļĄāđ‚āļ‹āļ™ GMT+0", + "monitorsToApply": "āļĄāļ­āļ™āļīāđ€āļ•āļ­āļĢāđŒāļ—āļĩāđˆāļˆāļ°āđƒāļŠāđ‰āļŠāđˆāļ§āļ‡āđ€āļ§āļĨāļēāļāļēāļĢāļšāļģāļĢāļļāļ‡āļĢāļąāļāļĐāļē", + "nextWindow": "āļŠāđˆāļ§āļ‡āđ€āļ§āļĨāļēāļ–āļąāļ”āđ„āļ›", + "notFoundButton": "āđ„āļ›āļĒāļąāļ‡āļŦāļ™āđ‰āļēāļˆāļ­āđāļ”āļŠāļšāļ­āļĢāđŒāļ”āļŦāļĨāļąāļ", + "pageSpeedConfigureSettingsDescription": "āļ„āļļāļ“āļŠāļēāļĄāļēāļĢāļ–āđ€āļĨāļ·āļ­āļ URL āļ‚āļ­āļ‡āđ‚āļŪāļŠāļ•āđŒ āļžāļĢāđ‰āļ­āļĄāļāļąāļšāļ›āļĢāļ°āđ€āļ āļ—āļ‚āļ­āļ‡āļĄāļ­āļ™āļīāđ€āļ•āļ­āļĢāđŒāđ„āļ”āđ‰", + "monitorDisplayName": "āļŠāļ·āđˆāļ­āļ—āļĩāđˆāđāļŠāļ”āļ‡āļ‚āļ­āļ‡āļĄāļ­āļ™āļīāđ€āļ•āļ­āļĢāđŒ", + "whenNewIncident": "āđ€āļĄāļ·āđˆāļ­āđ€āļāļīāļ”āđ€āļŦāļ•āļļāļāļēāļĢāļ“āđŒāđƒāļŦāļĄāđˆ", + "notifySMS": "āđāļˆāđ‰āļ‡āđ€āļ•āļ·āļ­āļ™āļ—āļēāļ‡SMS (āļāļģāļĨāļąāļ‡āļˆāļ°āļĄāļē)", + "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": "āļ™āļīāļžāļˆāļ™āđŒāļ™āļĩāđ‰āļˆāļ°āļ–āļđāļāļ›āļĢāļ°āđ€āļĄāļīāļ™āļāļąāļšāļ‚āđ‰āļ­āļĄāļđāļĨ JSON āļ—āļĩāđˆāļ•āļ­āļšāļāļĨāļąāļš āđāļĨāļ°āļœāļĨāļĨāļąāļžāļ˜āđŒāļˆāļ°āļ–āļđāļāđƒāļŠāđ‰āđ€āļžāļ·āđˆāļ­āļ•āļĢāļ§āļˆāļŠāļ­āļšāļāļąāļšāļ„āđˆāļēāļ—āļĩāđˆāļ„āļēāļ”āļŦāļ§āļąāļ‡ āļ”āļđ", + "uptimeCreateJsonPathQuery": "for query language documentation.", + "maintenanceTableActionMenuDialogTitle": "āļ„āļļāļ“āđāļ™āđˆāđƒāļˆāļŦāļĢāļ·āļ­āļ§āđˆāļēāļ•āđ‰āļ­āļ‡āļāļēāļĢāļĨāļšāļŠāđˆāļ§āļ‡āđ€āļ§āļĨāļēāļāļēāļĢāļšāļģāļĢāļļāļ‡āļĢāļąāļāļĐāļēāļ™āļĩāđ‰?", + "infrastructureEditYour": "Edit your", + "infrastructureEditMonitor": "āļšāļąāļ™āļ—āļķāļ Infrastructure āļĄāļ­āļ™āļīāđ€āļ•āļ­āļĢāđŒ", + "infrastructureMonitorCreated": "āļŠāļĢāđ‰āļēāļ‡āļĄāļ­āļ™āļīāđ€āļ•āļ­āļĢāđŒ Infrastructure āđ€āļĢāļĩāļĒāļšāļĢāđ‰āļ­āļĒāđāļĨāđ‰āļ§!", + "infrastructureMonitorUpdated": "āļ­āļąāļžāđ€āļ”āļ—āļĄāļ­āļ™āļīāđ€āļ•āļ­āļĢāđŒ Infrastructure āđ€āļĢāļĩāļĒāļšāļĢāđ‰āļ­āļĒāđāļĨāđ‰āļ§!", + "errorInvalidTypeId": "āļ›āļĢāļ°āđ€āļ āļ—āļāļēāļĢāđāļˆāđ‰āļ‡āđ€āļ•āļ·āļ­āļ™āļ—āļĩāđˆāļĢāļ°āļšāļļāđ„āļĄāđˆāļ–āļđāļāļ•āđ‰āļ­āļ‡", + "errorInvalidFieldId": "āļĢāļŦāļąāļŠāļŸāļīāļĨāļ”āđŒāļ—āļĩāđˆāļĢāļ°āļšāļļāđ„āļĄāđˆāļ–āļđāļāļ•āđ‰āļ­āļ‡", + "inviteNoTokenFound": "āđ„āļĄāđˆāļžāļšāđ‚āļ—āđ€āļ„āđ‡āļ™āļ„āļģāđ€āļŠāļīāļ", + "pageSpeedWarning": "āļ„āļģāđ€āļ•āļ·āļ­āļ™: āļ„āļļāļ“āļĒāļąāļ‡āđ„āļĄāđˆāđ„āļ”āđ‰āđ€āļžāļīāđˆāļĄāļ„āļĩāļĒāđŒ Google PageSpeed API āļŦāļēāļāđ„āļĄāđˆāļĄāļĩāļ„āļĩāļĒāđŒāļ™āļĩāđ‰ āļĄāļ­āļ™āļīāđ€āļ•āļ­āļĢāđŒāļ„āļ§āļēāļĄāđ€āļĢāđ‡āļ§āļŦāļ™āđ‰āļēāđ€āļ§āđ‡āļšāļˆāļ°āđ„āļĄāđˆāļ—āļģāļ‡āļēāļ™", + "pageSpeedLearnMoreLink": "āļ„āļĨāļīāļāļ—āļĩāđˆāļ™āļĩāđˆ", + "pageSpeedAddApiKey": "āđ€āļžāļ·āđˆāļ­āđ€āļžāļīāđˆāļĄāļ„āļĩāļĒāđŒ API āļ‚āļ­āļ‡āļ„āļļāļ“", + "update": "āļ­āļąāļ›āđ€āļ”āļ•", + "invalidFileFormat": "āļĢāļđāļ›āđāļšāļšāđ„āļŸāļĨāđŒāđ„āļĄāđˆāļĢāļ­āļ‡āļĢāļąāļš!", + "invalidFileSize": "āļ‚āļ™āļēāļ”āđ„āļŸāļĨāđŒāđƒāļŦāļāđˆāđ€āļāļīāļ™āđ„āļ›!", + "ClickUpload": "āļ„āļĨāļīāļāđ€āļžāļ·āđˆāļ­āļ­āļąāļ›āđ‚āļŦāļĨāļ”", + "DragandDrop": "āļĨāļēāļāđāļĨāļ°āļ§āļēāļ‡āđ€āļĨāļĒ", + "MaxSize": "āļ‚āļ™āļēāļ”āļŠāļđāļ‡āļŠāļļāļ”", + "SupportedFormats": "āļĢāļđāļ›āđāļšāļšāļ—āļĩāđˆāļĢāļ­āļ‡āļĢāļąāļš", + "FirstName": "āļŠāļ·āđˆāļ­āļˆāļĢāļīāļ‡", + "LastName": "āļ™āļēāļĄāļŠāļāļļāļĨ", + "EmailDescriptionText": "āļ™āļĩāđˆāļ„āļ·āļ­āļ—āļĩāđˆāļ­āļĒāļđāđˆāļ­āļĩāđ€āļĄāļĨāļ›āļąāļˆāļˆāļļāļšāļąāļ™āļ‚āļ­āļ‡āļ„āļļāļ“ â€” āđ„āļĄāđˆāļŠāļēāļĄāļēāļĢāļ–āđāļāđ‰āđ„āļ‚āđ„āļ”āđ‰", + "YourPhoto": "āļĢāļđāļ›āđ‚āļ›āļĢāđ„āļŸāļĨāđŒ", + "PhotoDescriptionText": "āļĢāļđāļ›āļ™āļĩāđ‰āļˆāļ°āđāļŠāļ”āļ‡āļšāļ™āļŦāļ™āđ‰āļēāļ›āļĢāļ°āļ§āļąāļ•āļīāļ‚āļ­āļ‡āļ„āļļāļ“", + "save": "āļšāļąāļ™āļ—āļķāļ", + "DeleteDescriptionText": "āļāļēāļĢāļ—āļģāđ€āļŠāđˆāļ™āļ™āļĩāđ‰āļˆāļ°āļĨāļšāļšāļąāļāļŠāļĩāđāļĨāļ°āļ‚āđ‰āļ­āļĄāļđāļĨāļ—āļąāđ‰āļ‡āļŦāļĄāļ”āļ—āļĩāđˆāđ€āļāļĩāđˆāļĒāļ§āļ‚āđ‰āļ­āļ‡āļšāļ™āđ€āļ‹āļīāļĢāđŒāļŸāđ€āļ§āļ­āļĢāđŒ āđ„āļĄāđˆāļŠāļēāļĄāļēāļĢāļ–āļāļđāđ‰āļ„āļ·āļ™āđ„āļ”āđ‰", + "DeleteAccountWarning": "āļāļēāļĢāļĨāļšāļšāļąāļāļŠāļĩāļŦāļĄāļēāļĒāļ„āļ§āļēāļĄāļ§āđˆāļēāļ„āļļāļ“āļˆāļ°āđ„āļĄāđˆāļŠāļēāļĄāļēāļĢāļ–āđ€āļ‚āđ‰āļēāļŠāļđāđˆāļĢāļ°āļšāļšāļ­āļĩāļ āđāļĨāļ°āļ‚āđ‰āļ­āļĄāļđāļĨāļ—āļąāđ‰āļ‡āļŦāļĄāļ”āļ‚āļ­āļ‡āļ„āļļāļ“āļˆāļ°āļ–āļđāļāļĨāļš āđ„āļĄāđˆāļŠāļēāļĄāļēāļĢāļ–āļāļđāđ‰āļ„āļ·āļ™āđ„āļ”āđ‰", + "DeleteWarningTitle": "āļ„āļļāļ“āđāļ™āđˆāđƒāļˆāļŦāļĢāļ·āļ­āļ§āđˆāļēāļ•āđ‰āļ­āļ‡āļāļēāļĢāļĨāļšāļšāļąāļāļŠāļĩāļ™āļĩāđ‰?", + "bulkImport": { + "title": "āļ™āļģāđ€āļ‚āđ‰āļēāļˆāļģāļ™āļ§āļ™āļĄāļēāļ", + "selectFileTips": "āđ€āļĨāļ·āļ­āļāđ„āļŸāļĨāđŒ CSV āđ€āļžāļ·āđˆāļ­āļ™āļģāđ€āļ‚āđ‰āļē", + "selectFileDescription": "āļ„āļļāļ“āļŠāļēāļĄāļēāļĢāļ–āļ”āļēāļ§āļ™āđŒāđ‚āļŦāļĨāļ” āļŦāļĢāļ·āļ­ sample āļ‚āļ­āļ‡āđ€āļĢāļēāđ„āļ”āđ‰", + "selectFile": "āđ€āļĨāļ·āļ­āļāđ„āļŸāļĨāđŒ", + "parsingFailed": "āļāļēāļĢāđāļĒāļāļ§āļīāđ€āļ„āļĢāļēāļ°āļŦāđŒāļĨāđ‰āļĄāđ€āļŦāļĨāļ§", + "uploadSuccess": "āļŠāļĢāđ‰āļēāļ‡āļĄāļ­āļ™āļīāđ€āļ•āļ­āļĢāđŒāđ€āļĢāļĩāļĒāļšāļĢāđ‰āļ­āļĒāđāļĨāđ‰āļ§!", + "validationFailed": "āļāļēāļĢāļ•āļĢāļ§āļˆāļŠāļ­āļšāļĨāđ‰āļĄāđ€āļŦāļĨāļ§", + "noFileSelected": "āļĒāļąāļ‡āđ„āļĄāđˆāđ„āļ”āđ‰āđ€āļĨāļ·āļ­āļāđ„āļŸāļĨāđŒ", + "fallbackPage": "āļ™āļģāđ€āļ‚āđ‰āļēāđ„āļŸāļĨāđŒāđ€āļžāļ·āđˆāļ­āļ­āļąāļ›āđ‚āļŦāļĨāļ”āļĢāļēāļĒāļāļēāļĢāđ€āļ‹āļīāļĢāđŒāļŸāđ€āļ§āļ­āļĢāđŒāđ€āļ›āđ‡āļ™āļāļĨāļļāđˆāļĄ", + "invalidFileType": "āļ›āļĢāļ°āđ€āļ āļ—āđ„āļŸāļĨāđŒāđ„āļĄāđˆāļ–āļđāļāļ•āđ‰āļ­āļ‡", + "uploadFailed": "āļ­āļąāļ›āđ‚āļŦāļĨāļ”āļĨāđ‰āļĄāđ€āļŦāļĨāļ§" + }, + "DeleteAccountTitle": "āļĨāļšāļšāļąāļāļŠāļĩ", + "DeleteAccountButton": "āļĨāļšāļšāļąāļāļŠāļĩ", + "publicLink": "āļĨāļīāļ‡āļāđŒāļŠāļēāļ˜āļēāļĢāļ“āļ°", + "maskedPageSpeedKeyPlaceholder": "*************************************", + "reset": "āļĢāļĩāđ€āļŠāđ‡āļ•", + "ignoreTLSError": "āļĨāļ°āđ€āļ§āđ‰āļ™āļ‚āđ‰āļ­āļœāļīāļ”āļžāļĨāļēāļ” TLS/SSL", + "tlsErrorIgnored": "āļĨāļ°āđ€āļ§āđ‰āļ™āļ‚āđ‰āļ­āļœāļīāļ”āļžāļĨāļēāļ” TLS/SSL āđāļĨāđ‰āļ§", + "ignoreTLSErrorDescription": "āļĨāļ°āđ€āļ§āđ‰āļ™āļ‚āđ‰āļ­āļœāļīāļ”āļžāļĨāļēāļ” TLS/SSL āđāļĨāļ°āļ”āļģāđ€āļ™āļīāļ™āļāļēāļĢāļ•āļĢāļ§āļˆāļŠāļ­āļšāļ„āļ§āļēāļĄāļžāļĢāđ‰āļ­āļĄāđƒāļŠāđ‰āļ‡āļēāļ™āļ‚āļ­āļ‡āđ€āļ§āđ‡āļšāđ„āļ‹āļ•āđŒāļ•āđˆāļ­", + "createNew": "āļŠāļĢāđ‰āļēāļ‡āđƒāļŦāļĄāđˆ", + "greeting": { + "prepend": "āļŠāļ§āļąāļŠāļ”āļĩ", + "append": "āļŠāđˆāļ§āļ‡āļšāđˆāļēāļĒāļ„āļ·āļ­āđ€āļ§āļĨāļēāđ€āļĨāđˆāļ™āļ‚āļ­āļ‡āļ„āļļāļ“—āļĄāļēāļ—āļģāđƒāļŦāđ‰āļŠāļļāļ”āļĒāļ­āļ”āļāļąāļ™āđ€āļ–āļ­āļ°!", + "overview": "āļ™āļĩāđˆāļ„āļ·āļ­āļ āļēāļžāļĢāļ§āļĄāļĢāļ°āļšāļšāļĄāļ­āļ™āļīāđ€āļ•āļ­āļĢāđŒāļ›āļĢāļ°āđ€āļ āļ— {{type}} āļ‚āļ­āļ‡āļ„āļļāļ“" + }, + "roles": { + "superAdmin": "Super admin", + "admin": "āđāļ­āļ”āļĄāļīāļ™", + "teamMember": "āļŠāļĄāļēāļŠāļīāļāđƒāļ™āļ—āļĩāļĄ", + "demoUser": "āļœāļđāđ‰āđƒāļŠāđ‰ Demo" + }, + "teamPanel": { + "teamMembers": "āļŠāļĄāļēāļŠāļīāļāđƒāļ™āļ—āļĩāļĄ", + "filter": { + "all": "āļ—āļąāđ‰āļ‡āļŦāļĄāļ”", + "member": "āļŠāļĄāļēāļŠāļīāļ" + }, + "inviteTeamMember": "āđ€āļŠāļīāļāļŠāļĄāļēāļŠāļīāļāđ€āļ‚āđ‰āļēāļ—āļĩāļĄ", + "inviteNewTeamMember": "āđ€āļŠāļīāļāļŠāļĄāļēāļŠāļīāļāļ—āļĩāļĄāđƒāļŦāļĄāđˆ", + "inviteDescription": "āđ€āļĄāļ·āđˆāļ­āļ„āļļāļ“āđ€āļžāļīāđˆāļĄāļŠāļĄāļēāļŠāļīāļāļ—āļĩāļĄāđƒāļŦāļĄāđˆ āđ€āļ‚āļēāļˆāļ°āļŠāļēāļĄāļēāļĢāļ–āđ€āļ‚āđ‰āļēāļ–āļķāļ‡āļĄāļ­āļ™āļīāđ€āļ•āļ­āļĢāđŒāļ—āļąāđ‰āļ‡āļŦāļĄāļ”āđ„āļ”āđ‰", + "email": "āļ­āļĩāđ€āļĄāļĨ", + "selectRole": "āđ€āļĨāļ·āļ­āļāļšāļ—āļšāļēāļ—", + "inviteLink": "āļĨāļīāļ‡āļāđŒāđ€āļŠāļīāļ", + "cancel": "āļĒāļāđ€āļĨāļīāļ", + "noMembers": "āļĒāļąāļ‡āđ„āļĄāđˆāļĄāļĩāļŠāļĄāļēāļŠāļīāļāļ—āļĩāļĄāđƒāļ™āļšāļ—āļšāļēāļ—āļ™āļĩāđ‰", + "getToken": "āļĢāļąāļšāđ‚āļ—āđ€āļ„āđ‡āļ™", + "emailToken": "Token āļ—āļēāļ‡āļ­āļĩāđ€āļĄāļĨ", + "table": { + "name": "āļŠāļ·āđˆāļ­", + "email": "āļ­āļĩāđ€āļĄāļĨ", + "role": "āļšāļ—āļšāļēāļ—", + "created": "āļŠāļĢāđ‰āļēāļ‡āđ€āļĄāļ·āđˆāļ­" + }, + "addTeamMember": { + "addMemberMenu": "āđ€āļžāļīāđˆāļĄāļŠāļĄāļēāļŠāļīāļāļ—āļĩāļĄ", + "title": "āļĨāļ‡āļ—āļ°āđ€āļšāļĩāļĒāļ™āļŠāļĄāļēāļŠāļīāļāļ—āļĩāļĄāđƒāļŦāļĄāđˆ", + "description": "āļŠāļĢāđ‰āļēāļ‡āļœāļđāđ‰āđƒāļŠāđ‰āđƒāļŦāļĄāđˆāđāļĨāļ°āđāļŠāļĢāđŒāļ‚āđ‰āļ­āļĄāļđāļĨāļāļēāļĢāđ€āļ‚āđ‰āļēāļŠāļđāđˆāļĢāļ°āļšāļšāđƒāļŦāđ‰āļāļąāļšāļžāļ§āļāđ€āļ‚āļē āļ§āļīāļ˜āļĩāļ™āļĩāđ‰āļˆāļ°āļ—āļģāđƒāļŦāđ‰āļŠāļĄāļēāļŠāļīāļāļŠāļēāļĄāļēāļĢāļ–āđ€āļ‚āđ‰āļēāļ–āļķāļ‡āļ•āļąāļ§āļ•āļĢāļ§āļˆāļŠāļ­āļšāļ—āļąāđ‰āļ‡āļŦāļĄāļ”āđ„āļ”āđ‰āļ—āļąāļ™āļ—āļĩ", + "addButton": "āđ€āļžāļīāđˆāļĄāļŠāļĄāļēāļŠāļīāļ" + }, + "register": "āļĨāļ‡āļ—āļ°āđ€āļšāļĩāļĒāļ™āļŠāļĄāļēāļŠāļīāļāļ—āļĩāļĄ", + "registerToast": { + "success": "āļŠāļĢāđ‰āļēāļ‡āļœāļđāđ‰āđƒāļŠāđ‰āđ€āļĢāļĩāļĒāļšāļĢāđ‰āļ­āļĒāđāļĨāđ‰āļ§ āļāļĢāļļāļ“āļēāđāļŠāļĢāđŒāļ‚āđ‰āļ­āļĄāļđāļĨāļāļēāļĢāđ€āļ‚āđ‰āļēāļŠāļđāđˆāļĢāļ°āļšāļšāđƒāļŦāđ‰āļŠāļĄāļēāļŠāļīāļāļ­āļĒāđˆāļēāļ‡āļ›āļĨāļ­āļ”āļ āļąāļĒ", + "dbUserExists": "āļœāļđāđ‰āđƒāļŠāđ‰āļ™āļĩāđ‰āļĄāļĩāļ­āļĒāļđāđˆāđāļĨāđ‰āļ§", + "unknownError": "āđ€āļāļīāļ”āļ‚āđ‰āļ­āļœāļīāļ”āļžāļĨāļēāļ”āļ—āļĩāđˆāđ„āļĄāđˆāļ—āļĢāļēāļšāļŠāļēāđ€āļŦāļ•āļļ" + }, + "registerTeamMember": { + "title": "āļĨāļ‡āļ—āļ°āđ€āļšāļĩāļĒāļ™āļŠāļĄāļēāļŠāļīāļāļ—āļĩāļĄ", + "auth": { + "common": { + "inputs": { + "firstName": { + "errors": { + "empty": "āļāļĢāļļāļ“āļēāļāļĢāļ­āļāļŠāļ·āđˆāļ­", + "pattern": "āļŠāļ·āđˆāļ­āļ•āđ‰āļ­āļ‡āļĄāļĩāđ€āļ‰āļžāļēāļ°āļ•āļąāļ§āļ­āļąāļāļĐāļĢ āđ€āļ§āđ‰āļ™āļ§āļĢāļĢāļ„ āđ€āļ„āļĢāļ·āđˆāļ­āļ‡āļŦāļĄāļēāļĒāļ­āļąāļāļ›āļĢāļ°āļāļēāļĻ āļŦāļĢāļ·āļ­āļ‚āļĩāļ”āļāļĨāļēāļ‡" + } + }, + "lastName": { + "errors": { + "empty": "āļāļĢāļļāļ“āļēāļāļĢāļ­āļāļ™āļēāļĄāļŠāļāļļāļĨ", + "pattern": "āļ™āļēāļĄāļŠāļāļļāļĨāļ•āđ‰āļ­āļ‡āļĄāļĩāđ€āļ‰āļžāļēāļ°āļ•āļąāļ§āļ­āļąāļāļĐāļĢ āđ€āļ§āđ‰āļ™āļ§āļĢāļĢāļ„ āđ€āļ„āļĢāļ·āđˆāļ­āļ‡āļŦāļĄāļēāļĒāļ­āļąāļāļ›āļĢāļ°āļāļēāļĻ āļŦāļĢāļ·āļ­āļ‚āļĩāļ”āļāļĨāļēāļ‡" + } + }, + "email": { + "errors": { + "empty": "āļāļĢāļļāļ“āļēāļāļĢāļ­āļāļ­āļĩāđ€āļĄāļĨāđ€āļžāļ·āđˆāļ­āļ”āļģāđ€āļ™āļīāļ™āļāļēāļĢāļ•āđˆāļ­", + "invalid": "āļāļĢāļļāļ“āļēāļ•āļĢāļ§āļˆāļŠāļ­āļšāļ„āļ§āļēāļĄāļ–āļđāļāļ•āđ‰āļ­āļ‡āļ‚āļ­āļ‡āļ­āļĩāđ€āļĄāļĨāļ­āļĩāļāļ„āļĢāļąāđ‰āļ‡" + } + }, + "role": { + "errors": { + "empty": "āļ•āđ‰āļ­āļ‡āļĢāļ°āļšāļļāļšāļ—āļšāļēāļ—" + } + } + } + } + } + }, + "role": "āļšāļ—āļšāļēāļ—" + }, + "monitorState": { + "paused": "āļŦāļĒāļļāļ”āļŠāļąāđˆāļ§āļ„āļĢāļēāļ§", + "resumed": "āļ”āļģāđ€āļ™āļīāļ™āļāļēāļĢāļ•āđˆāļ­", + "active": "āđƒāļŠāđ‰āļ‡āļēāļ™āļ­āļĒāļđāđˆ" + }, + "menu": { + "uptime": "āļ„āļ§āļēāļĄāļžāļĢāđ‰āļ­āļĄāđƒāļŠāđ‰āļ‡āļēāļ™", + "pagespeed": "āļ„āļ§āļēāļĄāđ€āļĢāđ‡āļ§āļŦāļ™āđ‰āļēāđ€āļ§āđ‡āļš", + "infrastructure": "āđ‚āļ„āļĢāļ‡āļŠāļĢāđ‰āļēāļ‡āļžāļ·āđ‰āļ™āļāļēāļ™", + "incidents": "āđ€āļŦāļ•āļļāļāļēāļĢāļ“āđŒ", + "statusPages": "āļŦāļ™āđ‰āļēāļŠāļ–āļēāļ™āļ°", + "maintenance": "āļāļēāļĢāļšāļģāļĢāļļāļ‡āļĢāļąāļāļĐāļē", + "integrations": "āļāļēāļĢāļĢāļ§āļĄāļĢāļ°āļšāļš", + "settings": "āļāļēāļĢāļ•āļąāđ‰āļ‡āļ„āđˆāļē", + "support": "āļāļēāļĢāļŠāļ™āļąāļšāļŠāļ™āļļāļ™", + "discussions": "āļāļēāļĢāļŠāļ™āļ—āļ™āļē", + "docs": "āđ€āļ­āļāļŠāļēāļĢ", + "changelog": "āļšāļąāļ™āļ—āļķāļāļāļēāļĢāđ€āļ›āļĨāļĩāđˆāļĒāļ™āđāļ›āļĨāļ‡", + "profile": "āđ‚āļ›āļĢāđ„āļŸāļĨāđŒ", + "password": "āļĢāļŦāļąāļŠāļœāđˆāļēāļ™", + "team": "āļ—āļĩāļĄ", + "logOut": "āļ­āļ­āļāļˆāļēāļāļĢāļ°āļšāļš", + "notifications": "āļāļēāļĢāđāļˆāđ‰āļ‡āđ€āļ•āļ·āļ­āļ™", + "logs": "Logs" + }, + "settingsEmailUser": "āļ­āļĩāđ€āļĄāļĨāļœāļđāđ‰āđƒāļŠāđ‰ - āļŠāļ·āđˆāļ­āļœāļđāđ‰āđƒāļŠāđ‰āļŠāļģāļŦāļĢāļąāļšāļāļēāļĢāļĒāļ·āļ™āļĒāļąāļ™āļ•āļąāļ§āļ•āļ™ āļˆāļ°āđƒāļŠāđ‰āđāļ—āļ™āļ­āļĩāđ€āļĄāļĨāļŦāļēāļāļĢāļ°āļšāļļāđ„āļ§āđ‰", + "state": "āļŠāļ–āļēāļ™āļ°", + "statusBreadCrumbsStatusPages": "āļŦāļ™āđ‰āļēāļŠāļ–āļēāļ™āļ°", + "statusBreadCrumbsDetails": "āļĢāļēāļĒāļĨāļ°āđ€āļ­āļĩāļĒāļ”", + "commonSaving": "āļāļģāļĨāļąāļ‡āļšāļąāļ™āļ—āļķāļ...", + "navControls": "āļāļēāļĢāļ„āļ§āļšāļ„āļļāļĄ", + "incidentsPageTitle": "āđ€āļŦāļ•āļļāļāļēāļĢāļ“āđŒ", + "passwordPanel": { + "passwordChangedSuccess": "āļĢāļŦāļąāļŠāļœāđˆāļēāļ™āļ‚āļ­āļ‡āļ„āļļāļ“āļ–āļđāļāđ€āļ›āļĨāļĩāđˆāļĒāļ™āđ€āļĢāļĩāļĒāļšāļĢāđ‰āļ­āļĒāđāļĨāđ‰āļ§", + "passwordInputIncorrect": "āļ„āļļāļ“āļ›āđ‰āļ­āļ™āļĢāļŦāļąāļŠāļœāđˆāļēāļ™āļ—āļĩāđˆāđ„āļĄāđˆāļ–āļđāļāļ•āđ‰āļ­āļ‡", + "currentPassword": "āļĢāļŦāļąāļŠāļœāđˆāļēāļ™āļ›āļąāļˆāļˆāļļāļšāļąāļ™", + "enterCurrentPassword": "āļāļĢāļ­āļāļĢāļŦāļąāļŠāļœāđˆāļēāļ™āļ›āļąāļˆāļˆāļļāļšāļąāļ™āļ‚āļ­āļ‡āļ„āļļāļ“", + "newPassword": "āļĢāļŦāļąāļŠāļœāđˆāļēāļ™āđƒāļŦāļĄāđˆ", + "enterNewPassword": "āļāļĢāļ­āļāļĢāļŦāļąāļŠāļœāđˆāļēāļ™āđƒāļŦāļĄāđˆāļ‚āļ­āļ‡āļ„āļļāļ“", + "confirmNewPassword": "āļĒāļ·āļ™āļĒāļąāļ™āļĢāļŦāļąāļŠāļœāđˆāļēāļ™āđƒāļŦāļĄāđˆāļ­āļĩāļāļ„āļĢāļąāđ‰āļ‡", + "passwordRequirements": "āļĢāļŦāļąāļŠāļœāđˆāļēāļ™āđƒāļŦāļĄāđˆāļ•āđ‰āļ­āļ‡āļĄāļĩāļ­āļĒāđˆāļēāļ‡āļ™āđ‰āļ­āļĒ 8 āļ•āļąāļ§āļ­āļąāļāļĐāļĢ āđāļĨāļ°āļ•āđ‰āļ­āļ‡āļĄāļĩāļ•āļąāļ§āļ­āļąāļāļĐāļĢāđƒāļŦāļāđˆ āļ•āļąāļ§āļ­āļąāļāļĐāļĢāđ€āļĨāđ‡āļ āļ•āļąāļ§āđ€āļĨāļ‚ āđāļĨāļ°āļ­āļąāļāļ‚āļĢāļ°āļžāļīāđ€āļĻāļĐāļ­āļĒāđˆāļēāļ‡āļĨāļ°āļ­āļĒāđˆāļēāļ‡ 1", + "saving": "āļāļģāļĨāļąāļ‡āļšāļąāļ™āļ—āļķāļ..." + }, + "emailSent": "āļŠāđˆāļ‡āļ­āļĩāđ€āļĄāļĨāđ€āļĢāļĩāļĒāļšāļĢāđ‰āļ­āļĒāđāļĨāđ‰āļ§", + "failedToSendEmail": "āļŠāđˆāļ‡āļ­āļĩāđ€āļĄāļĨāļĨāđ‰āļĄāđ€āļŦāļĨāļ§", + "settingsTestEmailSuccess": "āļŠāđˆāļ‡āļ­āļĩāđ€āļĄāļĨāļ—āļ”āļŠāļ­āļšāđ€āļĢāļĩāļĒāļšāļĢāđ‰āļ­āļĒāđāļĨāđ‰āļ§", + "settingsTestEmailFailed": "āļŠāđˆāļ‡āļ­āļĩāđ€āļĄāļĨāļ—āļ”āļŠāļ­āļšāļĨāđ‰āļĄāđ€āļŦāļĨāļ§", + "settingsTestEmailFailedWithReason": "āļŠāđˆāļ‡āļ­āļĩāđ€āļĄāļĨāļ—āļ”āļŠāļ­āļšāļĨāđ‰āļĄāđ€āļŦāļĨāļ§: {{reason}}", + "settingsTestEmailUnknownError": "āļ‚āđ‰āļ­āļœāļīāļ”āļžāļĨāļēāļ”āđ„āļĄāđˆāļ—āļĢāļēāļšāļŠāļēāđ€āļŦāļ•āļļ", + "statusMsg": { + "paused": "āļāļēāļĢāļ•āļĢāļ§āļˆāļŠāļ­āļšāļ–āļđāļāļžāļąāļāļŠāļąāđˆāļ§āļ„āļĢāļēāļ§", + "up": "āđ€āļ§āđ‡āļšāđ„āļ‹āļ•āđŒāļ‚āļ­āļ‡āļ„āļļāļ“āđƒāļŠāđ‰āļ‡āļēāļ™āđ„āļ”āđ‰", + "down": "āđ€āļ§āđ‡āļšāđ„āļ‹āļ•āđŒāļ‚āļ­āļ‡āļ„āļļāļ“āļĨāđˆāļĄ", + "pending": "āļĢāļ­āļ”āļģāđ€āļ™āļīāļ™āļāļēāļĢ..." + }, + "uptimeGeneralInstructions": { + "http": "āļāļĢāļ­āļ URL āļŦāļĢāļ·āļ­ IP āļ—āļĩāđˆāļ•āđ‰āļ­āļ‡āļāļēāļĢāļ•āļĢāļ§āļˆāļŠāļ­āļš (āđ€āļŠāđˆāļ™ https://example.com/\n āļŦāļĢāļ·āļ­ 192.168.1.100) āđāļĨāļ°āļ•āļąāđ‰āļ‡āļŠāļ·āđˆāļ­āļ—āļĩāđˆāļŠāļąāļ”āđ€āļˆāļ™āđƒāļŦāđ‰āđāļŠāļ”āļ‡āļšāļ™āđāļ”āļŠāļšāļ­āļĢāđŒāļ”", + "ping": "āļāļĢāļ­āļāļ—āļĩāđˆāļ­āļĒāļđāđˆ IP āļŦāļĢāļ·āļ­āļŠāļ·āđˆāļ­āđ‚āļŪāļŠāļ•āđŒāļ—āļĩāđˆāļ•āđ‰āļ­āļ‡āļāļēāļĢ ping (āđ€āļŠāđˆāļ™ 192.168.1.100 āļŦāļĢāļ·āļ­ example.com) āđāļĨāļ°āļ•āļąāđ‰āļ‡āļŠāļ·āđˆāļ­āļ—āļĩāđˆāļŠāļąāļ”āđ€āļˆāļ™āđƒāļŦāđ‰āđāļŠāļ”āļ‡āļšāļ™āđāļ”āļŠāļšāļ­āļĢāđŒāļ”", + "docker": "āļāļĢāļ­āļāļŠāļ·āđˆāļ­āļŦāļĢāļ·āļ­ ID āļ‚āļ­āļ‡āļ„āļ­āļ™āđ€āļ—āļ™āđ€āļ™āļ­āļĢāđŒ Docker āļ„āļļāļ“āļŠāļēāļĄāļēāļĢāļ–āđƒāļŠāđ‰āļŠāļ·āđˆāļ­āļ„āļ­āļ™āđ€āļ—āļ™āđ€āļ™āļ­āļĢāđŒ (āđ€āļŠāđˆāļ™ my-app) āļŦāļĢāļ·āļ­ ID āļ‚āļ­āļ‡āļ„āļ­āļ™āđ€āļ—āļ™āđ€āļ™āļ­āļĢāđŒ (ID āđ€āļ•āđ‡āļĄ 64 āļ•āļąāļ§āļ­āļąāļāļĐāļĢ āļŦāļĢāļ·āļ­ ID āļŠāļąāđ‰āļ™)", + "port": "āļāļĢāļ­āļ URL āļŦāļĢāļ·āļ­ IP āļ‚āļ­āļ‡āđ€āļ‹āļīāļĢāđŒāļŸāđ€āļ§āļ­āļĢāđŒ āļŦāļĄāļēāļĒāđ€āļĨāļ‚āļžāļ­āļĢāđŒāļ• āđāļĨāļ°āļ•āļąāđ‰āļ‡āļŠāļ·āđˆāļ­āļ—āļĩāđˆāļŠāļąāļ”āđ€āļˆāļ™āđƒāļŦāđ‰āđāļŠāļ”āļ‡āļšāļ™āđāļ”āļŠāļšāļ­āļĢāđŒāļ”", + "game": "āļāļĢāļ­āļāļ—āļĩāđˆāļ­āļĒāļđāđˆ IP āļŦāļĢāļ·āļ­āļŠāļ·āđˆāļ­āđ‚āļŪāļŠāļ•āđŒāđāļĨāļ°āļŦāļĄāļēāļĒāđ€āļĨāļ‚āļžāļ­āļĢāđŒāļ•āđ€āļžāļ·āđˆāļ­āļ—āļģāļāļēāļĢ ping (āđ€āļŠāđˆāļ™ 192.168.1.100 āļŦāļĢāļ·āļ­ example.com) āđāļĨāļ°āđ€āļĨāļ·āļ­āļāļ›āļĢāļ°āđ€āļ āļ—āđ€āļāļĄ" + }, + "common": { + "appName": "Checkmate", + "monitoringAgentName": "Capture", + "buttons": { + "toggleTheme": "āļŠāļĨāļąāļšāđ‚āļŦāļĄāļ”āļŠāļ§āđˆāļēāļ‡ & āļĄāļ·āļ”" + }, + "toasts": { + "networkError": "āļ‚āđ‰āļ­āļœāļīāļ”āļžāļĨāļēāļ”āđ€āļ„āļĢāļ·āļ­āļ‚āđˆāļēāļĒ", + "checkConnection": "āļāļĢāļļāļ“āļēāļ•āļĢāļ§āļˆāļŠāļ­āļšāļāļēāļĢāđ€āļŠāļ·āđˆāļ­āļĄāļ•āđˆāļ­āļ‚āļ­āļ‡āļ„āļļāļ“", + "unknownError": "āļ‚āđ‰āļ­āļœāļīāļ”āļžāļĨāļēāļ”āđ„āļĄāđˆāļ—āļĢāļēāļšāļŠāļēāđ€āļŦāļ•āļļ" + } + }, + "auth": { + "common": { + "navigation": { + "continue": "āļ”āļģāđ€āļ™āļīāļ™āļāļēāļĢāļ•āđˆāļ­", + "back": "āļĒāđ‰āļ­āļ™āļāļĨāļąāļš" + }, + "inputs": { + "email": { + "label": "āļ­āļĩāđ€āļĄāļĨ", + "placeholder": "jordan.ellis@domain.com", + "errors": { + "empty": "āđ€āļžāļ·āđˆāļ­āļ”āļģāđ€āļ™āļīāļ™āļāļēāļĢāļ•āđˆāļ­ āļāļĢāļļāļ“āļēāļāļĢāļ­āļāļ—āļĩāđˆāļ­āļĒāļđāđˆāļ­āļĩāđ€āļĄāļĨāļ‚āļ­āļ‡āļ„āļļāļ“", + "invalid": "āļāļĢāļļāļ“āļēāļ•āļĢāļ§āļˆāļŠāļ­āļšāļ„āļ§āļēāļĄāļ–āļđāļāļ•āđ‰āļ­āļ‡āļ‚āļ­āļ‡āļ­āļĩāđ€āļĄāļĨāļ—āļĩāđˆāļāļĢāļ­āļ" + } + }, + "password": { + "label": "āļĢāļŦāļąāļŠāļœāđˆāļēāļ™", + "rules": { + "length": { + "beginning": "āļ•āđ‰āļ­āļ‡āļĄāļĩāļ„āļ§āļēāļĄāļĒāļēāļ§āļ­āļĒāđˆāļēāļ‡āļ™āđ‰āļ­āļĒ", + "highlighted": "8 āļ•āļąāļ§āļ­āļąāļāļĐāļĢ" + }, + "special": { + "beginning": "āļ•āđ‰āļ­āļ‡āļĄāļĩāļ­āļĒāđˆāļēāļ‡āļ™āđ‰āļ­āļĒ", + "highlighted": "āļ­āļąāļāļ‚āļĢāļ°āļžāļīāđ€āļĻāļĐ 1 āļ•āļąāļ§" + }, + "number": { + "beginning": "āļ•āđ‰āļ­āļ‡āļĄāļĩāļ­āļĒāđˆāļēāļ‡āļ™āđ‰āļ­āļĒ", + "highlighted": "āļ•āļąāļ§āđ€āļĨāļ‚ 1 āļ•āļąāļ§" + }, + "uppercase": { + "beginning": "āļ•āđ‰āļ­āļ‡āļĄāļĩāļ­āļĒāđˆāļēāļ‡āļ™āđ‰āļ­āļĒ", + "highlighted": "āļ•āļąāļ§āļ­āļąāļāļĐāļĢāđƒāļŦāļāđˆ 1 āļ•āļąāļ§" + }, + "lowercase": { + "beginning": "āļ•āđ‰āļ­āļ‡āļĄāļĩāļ­āļĒāđˆāļēāļ‡āļ™āđ‰āļ­āļĒ", + "highlighted": "āļ•āļąāļ§āļ­āļąāļāļĐāļĢāđ€āļĨāđ‡āļ 1 āļ•āļąāļ§" + }, + "match": { + "beginning": "āļĢāļŦāļąāļŠāļœāđˆāļēāļ™", + "highlighted": "āļ•āđ‰āļ­āļ‡āļ•āļĢāļ‡āļāļąāļ™" + } + }, + "errors": { + "empty": "āļāļĢāļļāļ“āļēāļāļĢāļ­āļāļĢāļŦāļąāļŠāļœāđˆāļēāļ™āļ‚āļ­āļ‡āļ„āļļāļ“", + "length": "āļĢāļŦāļąāļŠāļœāđˆāļēāļ™āļ•āđ‰āļ­āļ‡āļĄāļĩāļ„āļ§āļēāļĄāļĒāļēāļ§āļ­āļĒāđˆāļēāļ‡āļ™āđ‰āļ­āļĒ 8 āļ•āļąāļ§āļ­āļąāļāļĐāļĢ", + "uppercase": "āļĢāļŦāļąāļŠāļœāđˆāļēāļ™āļ•āđ‰āļ­āļ‡āļĄāļĩāļ•āļąāļ§āļ­āļąāļāļĐāļĢāđƒāļŦāļāđˆāļ­āļĒāđˆāļēāļ‡āļ™āđ‰āļ­āļĒ 1 āļ•āļąāļ§", + "lowercase": "āļĢāļŦāļąāļŠāļœāđˆāļēāļ™āļ•āđ‰āļ­āļ‡āļĄāļĩāļ•āļąāļ§āļ­āļąāļāļĐāļĢāđ€āļĨāđ‡āļāļ­āļĒāđˆāļēāļ‡āļ™āđ‰āļ­āļĒ 1 āļ•āļąāļ§", + "number": "āļĢāļŦāļąāļŠāļœāđˆāļēāļ™āļ•āđ‰āļ­āļ‡āļĄāļĩāļ•āļąāļ§āđ€āļĨāļ‚āļ­āļĒāđˆāļēāļ‡āļ™āđ‰āļ­āļĒ 1 āļ•āļąāļ§", + "special": "āļĢāļŦāļąāļŠāļœāđˆāļēāļ™āļ•āđ‰āļ­āļ‡āļĄāļĩāļ­āļąāļāļ‚āļĢāļ°āļžāļīāđ€āļĻāļĐāļ­āļĒāđˆāļēāļ‡āļ™āđ‰āļ­āļĒ 1 āļ•āļąāļ§", + "incorrect": "āļĢāļŦāļąāļŠāļœāđˆāļēāļ™āļ—āļĩāđˆāļ„āļļāļ“āļāļĢāļ­āļāđ„āļĄāđˆāļ•āļĢāļ‡āļāļąāļšāļ‚āđ‰āļ­āļĄāļđāļĨāļ‚āļ­āļ‡āđ€āļĢāļē" + } + }, + "passwordConfirm": { + "label": "āļĒāļ·āļ™āļĒāļąāļ™āļĢāļŦāļąāļŠāļœāđˆāļēāļ™", + "placeholder": "āļāļĢāļ­āļāļĢāļŦāļąāļŠāļœāđˆāļēāļ™āļ­āļĩāļāļ„āļĢāļąāđ‰āļ‡āđ€āļžāļ·āđˆāļ­āļĒāļ·āļ™āļĒāļąāļ™", + "errors": { + "empty": "āļāļĢāļļāļ“āļēāļāļĢāļ­āļāļĢāļŦāļąāļŠāļœāđˆāļēāļ™āļ­āļĩāļāļ„āļĢāļąāđ‰āļ‡āđ€āļžāļ·āđˆāļ­āļĒāļ·āļ™āļĒāļąāļ™ (āļŠāđˆāļ§āļĒāļĨāļ”āļ‚āđ‰āļ­āļœāļīāļ”āļžāļĨāļēāļ”āļˆāļēāļāļāļēāļĢāļžāļīāļĄāļžāđŒāļœāļīāļ”)", + "different": "āļĢāļŦāļąāļŠāļœāđˆāļēāļ™āļ—āļĩāđˆāļāļĢāļ­āļāđ„āļĄāđˆāļ•āļĢāļ‡āļāļąāļ™ āļ­āļēāļˆāļĄāļĩāļ•āļąāļ§āđƒāļ”āļ•āļąāļ§āļŦāļ™āļķāđˆāļ‡āļžāļīāļĄāļžāđŒāļœāļīāļ”" + } + }, + "firstName": { + "label": "āļŠāļ·āđˆāļ­", + "placeholder": "Brownyroll", + "errors": { + "empty": "āļāļĢāļļāļ“āļēāļāļĢāļ­āļāļŠāļ·āđˆāļ­āļ‚āļ­āļ‡āļ„āļļāļ“", + "length": "āļŠāļ·āđˆāļ­āļ•āđ‰āļ­āļ‡āļĄāļĩāļ„āļ§āļēāļĄāļĒāļēāļ§āđ„āļĄāđˆāđ€āļāļīāļ™ 50 āļ•āļąāļ§āļ­āļąāļāļĐāļĢ", + "pattern": "āļŠāļ·āđˆāļ­āļ•āđ‰āļ­āļ‡āļ›āļĢāļ°āļāļ­āļšāļ”āđ‰āļ§āļĒāļ•āļąāļ§āļ­āļąāļāļĐāļĢ āļŠāđˆāļ­āļ‡āļ§āđˆāļēāļ‡ āđ€āļ„āļĢāļ·āđˆāļ­āļ‡āļŦāļĄāļēāļĒ ' āļŦāļĢāļ·āļ­ - āđ€āļ—āđˆāļēāļ™āļąāđ‰āļ™" + } + }, + "lastName": { + "label": "āļ™āļēāļĄāļŠāļāļļāļĨ", + "placeholder": ".", + "errors": { + "empty": "āļāļĢāļļāļ“āļēāļāļĢāļ­āļāļ™āļēāļĄāļŠāļāļļāļĨāļ‚āļ­āļ‡āļ„āļļāļ“", + "length": "āļ™āļēāļĄāļŠāļāļļāļĨāļ•āđ‰āļ­āļ‡āļĄāļĩāļ„āļ§āļēāļĄāļĒāļēāļ§āđ„āļĄāđˆāđ€āļāļīāļ™ 50 āļ•āļąāļ§āļ­āļąāļāļĐāļĢ", + "pattern": "āļ™āļēāļĄāļŠāļāļļāļĨāļ•āđ‰āļ­āļ‡āļ›āļĢāļ°āļāļ­āļšāļ”āđ‰āļ§āļĒāļ•āļąāļ§āļ­āļąāļāļĐāļĢ āļŠāđˆāļ­āļ‡āļ§āđˆāļēāļ‡ āđ€āļ„āļĢāļ·āđˆāļ­āļ‡āļŦāļĄāļēāļĒ ' āļŦāļĢāļ·āļ­ - āđ€āļ—āđˆāļēāļ™āļąāđ‰āļ™" + } + } + }, + "errors": { + "validation": "āđ€āļāļīāļ”āļ‚āđ‰āļ­āļœāļīāļ”āļžāļĨāļēāļ”āđƒāļ™āļāļēāļĢāļ•āļĢāļ§āļˆāļŠāļ­āļšāļ‚āđ‰āļ­āļĄāļđāļĨ" + }, + "fields": { + "password": { + "errors": { + "incorrect": "āļĢāļŦāļąāļŠāļœāđˆāļēāļ™āļ—āļĩāđˆāļ„āļļāļ“āļ›āđ‰āļ­āļ™āđ„āļĄāđˆāļ•āļĢāļ‡āļāļąāļšāļ‚āđ‰āļ­āļĄāļđāļĨāļ‚āļ­āļ‡āđ€āļĢāļē" + } + }, + "role": { + "errors": { + "min": "āļ•āđ‰āļ­āļ‡āļĄāļĩāļ­āļĒāđˆāļēāļ‡āļ™āđ‰āļ­āļĒāļŦāļ™āļķāđˆāļ‡āļšāļ—āļšāļēāļ—" + } + } + } + }, + "login": { + "heading": "āđ€āļ‚āđ‰āļēāļŠāļđāđˆāļĢāļ°āļšāļšāđ€āļžāļ·āđˆāļ­āļ”āļģāđ€āļ™āļīāļ™āļāļēāļĢāļ•āđˆāļ­", + "subheadings": { + "stepOne": "āļāļĢāļ­āļāļ­āļĩāđ€āļĄāļĨāļ‚āļ­āļ‡āļ„āļļāļ“", + "stepTwo": "āļāļĢāļ­āļāļĢāļŦāļąāļŠāļœāđˆāļēāļ™āļ‚āļ­āļ‡āļ„āļļāļ“" + }, + "links": { + "forgotPassword": "āļĨāļ·āļĄāļĢāļŦāļąāļŠāļœāđˆāļēāļ™?", + "register": "āļĒāļąāļ‡āđ„āļĄāđˆāļĄāļĩāļšāļąāļāļŠāļĩāđƒāļŠāđˆāđ„āļŦāļĄ?", + "forgotPasswordLink": "āļĢāļĩāđ€āļ‹āđ‡āļ•āļĢāļŦāļąāļŠāļœāđˆāļēāļ™", + "registerLink": "āļŠāļĄāļąāļ„āļĢāļŠāļĄāļēāļŠāļīāļāļ—āļĩāđˆāļ™āļĩāđˆ" + }, + "toasts": { + "success": "āļĒāļīāļ™āļ”āļĩāļ•āđ‰āļ­āļ™āļĢāļąāļšāļāļĨāļąāļš! āļ„āļļāļ“āđ€āļ‚āđ‰āļēāļŠāļđāđˆāļĢāļ°āļšāļšāđ€āļĢāļĩāļĒāļšāļĢāđ‰āļ­āļĒāđāļĨāđ‰āļ§", + "incorrectPassword": "āļĢāļŦāļąāļŠāļœāđˆāļēāļ™āđ„āļĄāđˆāļ–āļđāļāļ•āđ‰āļ­āļ‡" + }, + "errors": { + "password": { + "incorrect": "āļĢāļŦāļąāļŠāļœāđˆāļēāļ™āļ—āļĩāđˆāļ„āļļāļ“āļ›āđ‰āļ­āļ™āđ„āļĄāđˆāļ•āļĢāļ‡āļāļąāļšāļ‚āđ‰āļ­āļĄāļđāļĨāļ‚āļ­āļ‡āđ€āļĢāļē" + } + }, + "welcome": "āļĒāļīāļ™āļ”āļĩāļ•āđ‰āļ­āļ™āļĢāļąāļšāļāļĨāļąāļšāļŠāļđāđˆ Checkmate!" + }, + "registration": { + "heading": { + "superAdmin": "āļŠāļĢāđ‰āļēāļ‡ Super Admin", + "user": "āļŠāļĄāļąāļ„āļĢāļŠāļĄāļēāļŠāļīāļ" + }, + "subheadings": { + "stepOne": "āļāļĢāļ­āļāļĢāļēāļĒāļĨāļ°āđ€āļ­āļĩāļĒāļ”āļŠāđˆāļ§āļ™āļ•āļąāļ§āļ‚āļ­āļ‡āļ„āļļāļ“", + "stepTwo": "āļāļĢāļ­āļāļ­āļĩāđ€āļĄāļĨāļ‚āļ­āļ‡āļ„āļļāļ“", + "stepThree": "āļŠāļĢāđ‰āļēāļ‡āļĢāļŦāļąāļŠāļœāđˆāļēāļ™āļ‚āļ­āļ‡āļ„āļļāļ“" + }, + "description": { + "superAdmin": "āļŠāļĢāđ‰āļēāļ‡āļšāļąāļāļŠāļĩ Super Admin āđ€āļžāļ·āđˆāļ­āđ€āļĢāļīāđˆāļĄāļ•āđ‰āļ™āđƒāļŠāđ‰āļ‡āļēāļ™", + "user": "āļŠāļĄāļąāļ„āļĢāļŠāļĄāļēāļŠāļīāļāļœāļđāđ‰āđƒāļŠāđ‰āđāļĨāļ°āļ‚āļ­āļŠāļīāļ—āļ˜āļīāđŒāđ€āļ‚āđ‰āļēāļ–āļķāļ‡āļĄāļ­āļ™āļīāđ€āļ•āļ­āļĢāđŒāļˆāļēāļ Super Admin" + }, + "gettingStartedButton": { + "superAdmin": "āļŠāļĢāđ‰āļēāļ‡āļšāļąāļāļŠāļĩ Super Admin", + "user": "āļŠāļĄāļąāļ„āļĢāļŠāļĄāļēāļŠāļīāļāļœāļđāđ‰āđƒāļŠāđ‰āļ—āļąāđˆāļ§āđ„āļ›" + }, + "termsAndPolicies": "āļāļēāļĢāļŠāļĢāđ‰āļēāļ‡āļšāļąāļāļŠāļĩāļ–āļ·āļ­āļ§āđˆāļēāļ„āļļāļ“āļĒāļ­āļĄāļĢāļąāļš āļ‚āđ‰āļ­āļāļģāļŦāļ™āļ”āļāļēāļĢāđƒāļŦāđ‰āļšāļĢāļīāļāļēāļĢ āđāļĨāļ° āļ™āđ‚āļĒāļšāļēāļĒāļ„āļ§āļēāļĄāđ€āļ›āđ‡āļ™āļŠāđˆāļ§āļ™āļ•āļąāļ§ āļ‚āļ­āļ‡āđ€āļĢāļē", + "links": { + "login": "āļĄāļĩāļšāļąāļāļŠāļĩāđāļĨāđ‰āļ§? āđ€āļ‚āđ‰āļēāļŠāļđāđˆāļĢāļ°āļšāļš" + }, + "toasts": { + "success": "āļĒāļīāļ™āļ”āļĩāļ•āđ‰āļ­āļ™āļĢāļąāļš! āļŠāļĢāđ‰āļēāļ‡āļšāļąāļāļŠāļĩāđ€āļĢāļĩāļĒāļšāļĢāđ‰āļ­āļĒāđāļĨāđ‰āļ§" + }, + "welcome": "āļĒāļīāļ™āļ”āļĩāļ•āđ‰āļ­āļ™āļĢāļąāļšāļŠāļđāđˆ Checkmate!" + }, + "forgotPassword": { + "heading": "āļĨāļ·āļĄāļĢāļŦāļąāļŠāļœāđˆāļēāļ™?", + "subheadings": { + "stepOne": "āđ„āļĄāđˆāļ•āđ‰āļ­āļ‡āļāļąāļ‡āļ§āļĨ āđ€āļĢāļēāļˆāļ°āļŠāđˆāļ‡āļ„āļģāđāļ™āļ°āļ™āļģāđ€āļžāļ·āđˆāļ­āļĢāļĩāđ€āļ‹āđ‡āļ•āļĢāļŦāļąāļŠāļœāđˆāļēāļ™āđƒāļŦāđ‰āļ„āļļāļ“", + "stepTwo": "āđ€āļĢāļēāđ„āļ”āđ‰āļŠāđˆāļ‡āļĨāļīāļ‡āļāđŒāļĢāļĩāđ€āļ‹āđ‡āļ•āļĢāļŦāļąāļŠāļœāđˆāļēāļ™āđ„āļ›āļĒāļąāļ‡ ", + "stepThree": "āļĢāļŦāļąāļŠāļœāđˆāļēāļ™āđƒāļŦāļĄāđˆāļ•āđ‰āļ­āļ‡āđāļ•āļāļ•āđˆāļēāļ‡āļˆāļēāļāļĢāļŦāļąāļŠāļœāđˆāļēāļ™āļ—āļĩāđˆāđƒāļŠāđ‰āļāđˆāļ­āļ™āļŦāļ™āđ‰āļēāļ™āļĩāđ‰", + "stepFour": "āļĢāļĩāđ€āļ‹āđ‡āļ•āļĢāļŦāļąāļŠāļœāđˆāļēāļ™āđ€āļĢāļĩāļĒāļšāļĢāđ‰āļ­āļĒāđāļĨāđ‰āļ§ āļ„āļĨāļīāļāļ”āđ‰āļēāļ™āļĨāđˆāļēāļ‡āđ€āļžāļ·āđˆāļ­āđ€āļ‚āđ‰āļēāļŠāļđāđˆāļĢāļ°āļšāļšāļ—āļąāļ™āļ—āļĩ" + }, + "buttons": { + "openEmail": "āđ€āļ›āļīāļ”āđāļ­āļ›āļ­āļĩāđ€āļĄāļĨ", + "resetPassword": "āļĢāļĩāđ€āļ‹āđ‡āļ•āļĢāļŦāļąāļŠāļœāđˆāļēāļ™" + }, + "imageAlts": { + "passwordKey": "", + "email": "", + "lock": "", + "passwordConfirm": "" + }, + "links": { + "login": "āļāļĨāļąāļšāđ„āļ›āļ—āļĩāđˆ āđ€āļ‚āđ‰āļēāļŠāļđāđˆāļĢāļ°āļšāļš", + "resend": "āđ„āļĄāđˆāđ„āļ”āđ‰āļĢāļąāļšāļ­āļĩāđ€āļĄāļĨ? āļ„āļĨāļīāļāđ€āļžāļ·āđˆāļ­āļŠāđˆāļ‡āļ­āļĩāļāļ„āļĢāļąāđ‰āļ‡" + }, + "toasts": { + "sent": "āļ„āļģāđāļ™āļ°āļ™āļģāļ–āļđāļāļŠāđˆāļ‡āđ„āļ›āļĒāļąāļ‡ .", + "emailNotFound": "āđ„āļĄāđˆāļžāļšāļ­āļĩāđ€āļĄāļĨāļ™āļĩāđ‰", + "redirect": "āļāļģāļĨāļąāļ‡āđ€āļ›āļĨāļĩāđˆāļĒāļ™āļŦāļ™āđ‰āļēāđƒāļ™ ...", + "success": "āļĢāļĩāđ€āļ‹āđ‡āļ•āļĢāļŦāļąāļŠāļœāđˆāļēāļ™āļŠāļģāđ€āļĢāđ‡āļˆāđāļĨāđ‰āļ§", + "error": "āđ„āļĄāđˆāļŠāļēāļĄāļēāļĢāļ–āļĢāļĩāđ€āļ‹āđ‡āļ•āļĢāļŦāļąāļŠāļœāđˆāļēāļ™āđ„āļ”āđ‰ āļāļĢāļļāļ“āļēāļĨāļ­āļ‡āļ­āļĩāļāļ„āļĢāļąāđ‰āļ‡āļ āļēāļĒāļŦāļĨāļąāļ‡āļŦāļĢāļ·āļ­āļ•āļīāļ”āļ•āđˆāļ­āļāđˆāļēāļĒāļŠāļ™āļąāļšāļŠāļ™āļļāļ™" + } + } + }, + "errorPages": { + "serverUnreachable": { + "toasts": { + "reconnected": "āđ€āļŠāļ·āđˆāļ­āļĄāļ•āđˆāļ­āļāļąāļšāđ€āļ‹āļīāļĢāđŒāļŸāđ€āļ§āļ­āļĢāđŒāļŠāļģāđ€āļĢāđ‡āļˆāđāļĨāđ‰āļ§", + "stillUnreachable": "āđ€āļ‹āļīāļĢāđŒāļŸāđ€āļ§āļ­āļĢāđŒāļĒāļąāļ‡āđ„āļĄāđˆāļŠāļēāļĄāļēāļĢāļ–āđ€āļ‚āđ‰āļēāļ–āļķāļ‡āđ„āļ”āđ‰ āļāļĢāļļāļ“āļēāļĨāļ­āļ‡āļ­āļĩāļāļ„āļĢāļąāđ‰āļ‡āļ āļēāļĒāļŦāļĨāļąāļ‡" + }, + "alertBox": "āđ€āļāļīāļ”āļ‚āđ‰āļ­āļœāļīāļ”āļžāļĨāļēāļ”āļāļēāļĢāđ€āļŠāļ·āđˆāļ­āļĄāļ•āđˆāļ­āđ€āļ‹āļīāļĢāđŒāļŸāđ€āļ§āļ­āļĢāđŒ", + "description": "āđ„āļĄāđˆāļŠāļēāļĄāļēāļĢāļ–āđ€āļŠāļ·āđˆāļ­āļĄāļ•āđˆāļ­āļāļąāļšāđ€āļ‹āļīāļĢāđŒāļŸāđ€āļ§āļ­āļĢāđŒāđ„āļ”āđ‰ āļāļĢāļļāļ“āļēāļ•āļĢāļ§āļˆāļŠāļ­āļšāļāļēāļĢāđ€āļŠāļ·āđˆāļ­āļĄāļ•āđˆāļ­āļ­āļīāļ™āđ€āļ—āļ­āļĢāđŒāđ€āļ™āđ‡āļ•āļ‚āļ­āļ‡āļ„āļļāļ“āļŦāļĢāļ·āļ­āļĒāļ·āļ™āļĒāļąāļ™āļāļēāļĢāļ•āļąāđ‰āļ‡āļ„āđˆāļē deployment āļŦāļēāļāļ›āļąāļāļŦāļēāļĒāļąāļ‡āļ„āļ‡āļ­āļĒāļđāđˆ", + "retryButton": { + "default": "āļĨāļ­āļ‡āđ€āļŠāļ·āđˆāļ­āļĄāļ•āđˆāļ­āļ­āļĩāļāļ„āļĢāļąāđ‰āļ‡", + "processing": "āļāļģāļĨāļąāļ‡āđ€āļŠāļ·āđˆāļ­āļĄāļ•āđˆāļ­â€Ķ" + } + } + }, + "createNotifications": { + "title": "āļŠāļĢāđ‰āļēāļ‡āļŠāđˆāļ­āļ‡āļ—āļēāļ‡āđāļˆāđ‰āļ‡āđ€āļ•āļ·āļ­āļ™", + "nameSettings": { + "title": "āļŠāļ·āđˆāļ­", + "description": "āļŠāļ·āđˆāļ­āļ—āļĩāđˆāļšāđˆāļ‡āļšāļ­āļāļ–āļķāļ‡āļāļēāļĢāļ•āļąāđ‰āļ‡āļ„āđˆāļēāļāļēāļĢāļĢāļ§āļĄāļĢāļ°āļšāļšāļ‚āļ­āļ‡āļ„āļļāļ“", + "nameLabel": "āļŠāļ·āđˆāļ­", + "namePlaceholder": "āđ€āļŠāđˆāļ™ āļāļēāļĢāđāļˆāđ‰āļ‡āđ€āļ•āļ·āļ­āļ™āļœāđˆāļēāļ™ Slack" + }, + "typeSettings": { + "title": "āļ›āļĢāļ°āđ€āļ āļ—", + "description": "āđ€āļĨāļ·āļ­āļāļ›āļĢāļ°āđ€āļ āļ—āļ‚āļ­āļ‡āļŠāđˆāļ­āļ‡āļ—āļēāļ‡āđāļˆāđ‰āļ‡āđ€āļ•āļ·āļ­āļ™āļ—āļĩāđˆāļ„āļļāļ“āļ•āđ‰āļ­āļ‡āļāļēāļĢāļŠāļĢāđ‰āļēāļ‡", + "typeLabel": "āļ›āļĢāļ°āđ€āļ āļ—" + }, + "emailSettings": { + "title": "āļ­āļĩāđ€āļĄāļĨ", + "description": "āļ—āļĩāđˆāļ­āļĒāļđāđˆāļ­āļĩāđ€āļĄāļĨāļ›āļĨāļēāļĒāļ—āļēāļ‡", + "emailLabel": "āļ­āļĩāđ€āļĄāļĨ", + "emailPlaceholder": "e.g. john@example.com" + }, + "slackSettings": { + "title": "Slack", + "description": "āļ•āļąāđ‰āļ‡āļ„āđˆāļē Slack webhook āļ‚āļ­āļ‡āļ„āļļāļ“āļ—āļĩāđˆāļ™āļĩāđˆ", + "webhookLabel": "URL āļ‚āļ­āļ‡ Slack webhook", + "webhookPlaceholder": "https://hooks.slack.com/services/..." + }, + "pagerdutySettings": { + "title": "PagerDuty", + "description": "āļ•āļąāđ‰āļ‡āļ„āđˆāļēāļāļēāļĢāļĢāļ§āļĄāļĢāļ°āļšāļš PagerDuty āļ‚āļ­āļ‡āļ„āļļāļ“āļ—āļĩāđˆāļ™āļĩāđˆ", + "integrationKeyLabel": "āļ„āļĩāļĒāđŒāļāļēāļĢāļĢāļ§āļĄāļĢāļ°āļšāļš", + "integrationKeyPlaceholder": "1234567890" + }, + "discordSettings": { + "title": "Discord", + "description": "āļ•āļąāđ‰āļ‡āļ„āđˆāļē Discord webhook āļ‚āļ­āļ‡āļ„āļļāļ“āļ—āļĩāđˆāļ™āļĩāđˆ", + "webhookLabel": "URL āļ‚āļ­āļ‡ Discord Webhook", + "webhookPlaceholder": "https://your-server.com/webhook" + }, + "webhookSettings": { + "title": "Webhook", + "description": "āļ•āļąāđ‰āļ‡āļ„āđˆāļē webhook āļ‚āļ­āļ‡āļ„āļļāļ“āļ—āļĩāđˆāļ™āļĩāđˆ", + "webhookLabel": "Webhook URL", + "webhookPlaceholder": "https://your-server.com/webhook" + }, + "testNotification": "āļ—āļ”āļŠāļ­āļšāļāļēāļĢāđāļˆāđ‰āļ‡āđ€āļ•āļ·āļ­āļ™", + "dialogDeleteTitle": "āļ„āļļāļ“āđāļ™āđˆāđƒāļˆāļŦāļĢāļ·āļ­āđ„āļĄāđˆāļ§āđˆāļēāļ•āđ‰āļ­āļ‡āļāļēāļĢāļĨāļšāļāļēāļĢāđāļˆāđ‰āļ‡āđ€āļ•āļ·āļ­āļ™āļ™āļĩāđ‰?", + "dialogDeleteConfirm": "āļĨāļš" + }, + "notificationConfig": { + "title": "āļāļēāļĢāđāļˆāđ‰āļ‡āđ€āļ•āļ·āļ­āļ™", + "description": "āđ€āļĨāļ·āļ­āļāļŠāđˆāļ­āļ‡āļ—āļēāļ‡āđāļˆāđ‰āļ‡āđ€āļ•āļ·āļ­āļ™āļ—āļĩāđˆāļ„āļļāļ“āļ•āđ‰āļ­āļ‡āļāļēāļĢāđƒāļŠāđ‰" + }, + "monitorStatus": { + "checkingEvery": "āļ•āļĢāļ§āļˆāļŠāļ­āļšāļ—āļļāļ {{interval}}", + "withCaptureAgent": "āđ‚āļ”āļĒāđƒāļŠāđ‰ Capture agent {{version}}", + "up": "āļ­āļ­āļ™āđ„āļĨāļ™āđŒ", + "down": "āļ­āļ­āļŸāđ„āļĨāļ™āđŒ", + "paused": "āļŦāļĒāļļāļ”āļŠāļąāđˆāļ§āļ„āļĢāļēāļ§" + }, + "advancedMatching": "āļāļēāļĢāļˆāļąāļšāļ„āļđāđˆāļ‚āļąāđ‰āļ™āļŠāļđāļ‡", + "sendTestNotifications": "āļŠāđˆāļ‡āļāļēāļĢāđāļˆāđ‰āļ‡āđ€āļ•āļ·āļ­āļ™āļ—āļ”āļŠāļ­āļš", + "selectAll": "Select all", + "showAdminLoginLink": "āđāļŠāļ”āļ‡āļĨāļīāļ‡āļāđŒ \"āļœāļđāđ‰āļ”āļđāđāļĨāļĢāļ°āļšāļš? āđ€āļ‚āđ‰āļēāļŠāļđāđˆāļĢāļ°āļšāļšāļ—āļĩāđˆāļ™āļĩāđˆ\" āļšāļ™āļŦāļ™āđ‰āļēāļŠāļ–āļēāļ™āļ°", + "logsPage": { + "title": "Logs", + "description": "āļĢāļ°āļšāļš Logs 1000 āļšāļĢāļĢāļ—āļąāļ”āļĨāđˆāļēāļŠāļļāļ”", + "tabs": { + "queue": "āļ„āļīāļ§āļ‡āļēāļ™", + "logs": "Logs āđ€āļ‹āļīāļĢāđŒāļŸāđ€āļ§āļ­āļĢāđŒ", + "diagnostics": "āļ§āļīāļ™āļīāļˆāļ‰āļąāļĒ" + }, + "toast": { + "fetchLogsSuccess": "āļ”āļķāļ‡ Logs āļŠāļģāđ€āļĢāđ‡āļˆāđāļĨāđ‰āļ§" + }, + "logLevelSelect": { + "title": "Logs level", + "values": { + "all": "All", + "info": "Info", + "warn": "Warn", + "error": "Error", + "debug": "Debug" + } + } + }, + "queuePage": { + "title": "Queue", + "refreshButton": "Refresh", + "flushButton": "Flush queue", + "jobTable": { + "title": "āļ‡āļēāļ™āļ—āļĩāđˆāļ­āļĒāļđāđˆāđƒāļ™āļ„āļīāļ§āļ›āļąāļˆāļˆāļļāļšāļąāļ™", + "idHeader": "āļĢāļŦāļąāļŠāļĄāļ­āļ™āļīāđ€āļ•āļ­āļĢāđŒ", + "urlHeader": "URL", + "typeHeader": "Type", + "activeHeader": "āđƒāļŠāđ‰āļ‡āļēāļ™āļ­āļĒāļđāđˆ", + "lockedAtHeader": "āļĨāđ‡āļ­āļāđ€āļĄāļ·āđˆāļ­", + "runCountHeader": "āļˆāļģāļ™āļ§āļ™āļĢāļąāļ™", + "failCountHeader": "āļˆāļģāļ™āļ§āļ™āļĨāđ‰āļĄāđ€āļŦāļĨāļ§", + "lastRunHeader": "āļĢāļąāļ™āļĨāđˆāļēāļŠāļļāļ”āđ€āļĄāļ·āđˆāļ­", + "lastFinishedAtHeader": "āđ€āļŠāļĢāđ‡āļˆāļŠāļīāđ‰āļ™āļĨāđˆāļēāļŠāļļāļ”āđ€āļĄāļ·āđˆāļ­", + "lastRunTookHeader": "āļĢāļąāļ™āļ„āļĢāļąāđ‰āļ‡āļĨāđˆāļēāļŠāļļāļ”āđƒāļŠāđ‰āđ€āļ§āļĨāļē", + "intervalHeader": "āļŠāđˆāļ§āļ‡āđ€āļ§āļĨāļē" + }, + "metricsTable": { + "title": "āļ„āļīāļ§āđ€āļĄāļ•āļĢāļīāļ", + "metricHeader": "āđ€āļĄāļ•āļĢāļīāļ", + "valueHeader": "Value" + }, + "failedJobTable": { + "title": "āļ‡āļēāļ™āļ—āļĩāđˆāļĨāđ‰āļĄāđ€āļŦāļĨāļ§", + "monitorIdHeader": "āļĢāļŦāļąāļŠāļĄāļ­āļ™āļīāđ€āļ•āļ­āļĢāđŒ", + "monitorUrlHeader": "URL āļĄāļ­āļ™āļīāđ€āļ•āļ­āļĢāđŒ", + "failCountHeader": "āļˆāļģāļ™āļ§āļ™āļĨāđ‰āļĄāđ€āļŦāļĨāļ§", + "failedAtHeader": "āļĨāđ‰āļĄāđ€āļŦāļĨāļ§āļĨāđˆāļēāļŠāļļāļ”āđ€āļĄāļ·āđˆāļ­", + "failReasonHeader": "āļŠāļēāđ€āļŦāļ•āļļāļ—āļĩāđˆāļĨāđ‰āļĄāđ€āļŦāļĨāļ§" + } + }, + "export": { + "title": "āļŠāđˆāļ‡āļ­āļ­āļāļĄāļ­āļ™āļīāđ€āļ•āļ­āļĢāđŒ", + "success": "āļŠāđˆāļ‡āļ­āļ­āļāļĄāļ­āļ™āļīāđ€āļ•āļ­āļĢāđŒāļŠāļģāđ€āļĢāđ‡āļˆāđāļĨāđ‰āļ§", + "failed": "āđ„āļĄāđˆāļŠāļēāļĄāļēāļĢāļ–āļŠāđˆāļ‡āļ­āļ­āļāļĄāļ­āļ™āļīāđ€āļ•āļ­āļĢāđŒāđ„āļ”āđ‰" + }, + "monitorActions": { + "title": "āļŠāđˆāļ‡āļ­āļ­āļ/āļ™āļģāđ€āļ‚āđ‰āļē", + "import": "āļ™āļģāđ€āļ‚āđ‰āļēāļĄāļ­āļ™āļīāđ€āļ•āļ­āļĢāđŒ", + "export": "āļŠāđˆāļ‡āļ­āļ­āļāļĄāļ­āļ™āļīāđ€āļ•āļ­āļĢāđŒ", + "deleteSuccess": "āļĨāļšāļĄāļ­āļ™āļīāđ€āļ•āļ­āļĢāđŒāļŠāļģāđ€āļĢāđ‡āļˆ", + "deleteFailed": "āļĨāļšāļĄāļ­āļ™āļīāđ€āļ•āļ­āļĢāđŒāļĨāđ‰āļĄāđ€āļŦāļĨāļ§", + "details": "āļĢāļēāļĒāļĨāļ°āđ€āļ­āļĩāļĒāļ”" + }, + "settingsPage": { + "aboutSettings": { + "labelDevelopedBy": "āļžāļąāļ’āļ™āļēāđ‚āļ”āļĒ Bluewave Labs", + "labelVersion": "āđ€āļ§āļ­āļĢāđŒāļŠāļąāļ™", + "title": "āđ€āļāļĩāđˆāļĒāļ§āļāļąāļš" + }, + "demoMonitorsSettings": { + "buttonAddMonitors": "āđ€āļžāļīāđˆāļĄāļĄāļ­āļ™āļīāđ€āļ•āļ­āļĢāđŒāļ•āļąāļ§āļ­āļĒāđˆāļēāļ‡", + "description": "āđ€āļžāļīāđˆāļĄāļĄāļ­āļ™āļīāđ€āļ•āļ­āļĢāđŒāļ•āļąāļ§āļ­āļĒāđˆāļēāļ‡āđ€āļžāļ·āđˆāļ­āļāļēāļĢāļŠāļēāļ˜āļīāļ•", + "title": "āļĄāļ­āļ™āļīāđ€āļ•āļ­āļĢāđŒāļŠāļēāļ˜āļīāļ•" + }, + "emailSettings": { + "buttonSendTestEmail": "āļŠāđˆāļ‡āļ­āļĩāđ€āļĄāļĨāļ—āļ”āļŠāļ­āļš", + "description": "āļ•āļąāđ‰āļ‡āļ„āđˆāļēāļ­āļĩāđ€āļĄāļĨāļŠāļģāļŦāļĢāļąāļšāļĢāļ°āļšāļšāļ‚āļ­āļ‡āļ„āļļāļ“ āđƒāļŠāđ‰āļŠāļģāļŦāļĢāļąāļšāļŠāđˆāļ‡āļāļēāļĢāđāļˆāđ‰āļ‡āđ€āļ•āļ·āļ­āļ™āđāļĨāļ°āđ€āļ•āļ·āļ­āļ™āļ āļąāļĒ", + "descriptionTransport": "āļŠāļĢāđ‰āļēāļ‡ SMTP transport āļŠāļģāļŦāļĢāļąāļš NodeMailer", + "labelAddress": "āļ—āļĩāđˆāļ­āļĒāļđāđˆāļ­āļĩāđ€āļĄāļĨ - āđƒāļŠāđ‰āļŠāļģāļŦāļĢāļąāļšāļāļēāļĢāļĒāļ·āļ™āļĒāļąāļ™āļ•āļąāļ§āļ•āļ™", + "labelConnectionHost": "āđ‚āļŪāļŠāļ•āđŒāđ€āļŠāļ·āđˆāļ­āļĄāļ•āđˆāļ­āļ­āļĩāđ€āļĄāļĨ - āļŠāļ·āđˆāļ­āđ‚āļŪāļŠāļ•āđŒāļ—āļĩāđˆāđƒāļŠāđ‰āđƒāļ™āļ„āļģāļ—āļąāļāļ—āļēāļĒ HELO/EHLO", + "labelHost": "āđ‚āļŪāļŠāļ•āđŒāļ­āļĩāđ€āļĄāļĨ - āļŠāļ·āđˆāļ­āđ‚āļŪāļŠāļ•āđŒāļŦāļĢāļ·āļ­ IP āļŠāļģāļŦāļĢāļąāļšāđ€āļŠāļ·āđˆāļ­āļĄāļ•āđˆāļ­", + "labelIgnoreTLS": "āļ›āļīāļ”āđƒāļŠāđ‰āļ‡āļēāļ™ STARTTLS: āđ„āļĄāđˆāđƒāļŠāđ‰ TLS āđāļĄāđ‰āđ€āļ‹āļīāļĢāđŒāļŸāđ€āļ§āļ­āļĢāđŒāļĢāļ­āļ‡āļĢāļąāļš", + "labelPassword": "āļĢāļŦāļąāļŠāļœāđˆāļēāļ™āļ­āļĩāđ€āļĄāļĨ - āļŠāļģāļŦāļĢāļąāļšāļĒāļ·āļ™āļĒāļąāļ™āļ•āļąāļ§āļ•āļ™", + "labelPasswordSet": "āļ•āļąāđ‰āļ‡āļĢāļŦāļąāļŠāļœāđˆāļēāļ™āđ€āļĢāļĩāļĒāļšāļĢāđ‰āļ­āļĒāđāļĨāđ‰āļ§ āļ„āļĨāļīāļ āļĢāļĩāđ€āļ‹āđ‡āļ• āđ€āļžāļ·āđˆāļ­āđ€āļ›āļĨāļĩāđˆāļĒāļ™", + "labelPool": "āđ€āļ›āļīāļ”āđƒāļŠāđ‰ connection pooling: āđƒāļŠāđ‰āļāļēāļĢāđ€āļŠāļ·āđˆāļ­āļĄāļ•āđˆāļ­āđ€āļ”āļīāļĄāļ‹āđ‰āļģāđ€āļžāļ·āđˆāļ­āđ€āļžāļīāđˆāļĄāļ›āļĢāļ°āļŠāļīāļ—āļ˜āļīāļ āļēāļž", + "labelPort": "āļžāļ­āļĢāđŒāļ•āļ­āļĩāđ€āļĄāļĨ - āļžāļ­āļĢāđŒāļ•āļŠāļģāļŦāļĢāļąāļšāđ€āļŠāļ·āđˆāļ­āļĄāļ•āđˆāļ­", + "labelRejectUnauthorized": "āļ›āļāļīāđ€āļŠāļ˜āđƒāļšāļĢāļąāļšāļĢāļ­āļ‡āđ„āļĄāđˆāļ–āļđāļāļ•āđ‰āļ­āļ‡: āļ›āļāļīāđ€āļŠāļ˜āļāļēāļĢāđ€āļŠāļ·āđˆāļ­āļĄāļ•āđˆāļ­āļ—āļĩāđˆāđƒāļŠāđ‰āđƒāļšāļĢāļąāļšāļĢāļ­āļ‡ self-signed āļŦāļĢāļ·āļ­āđ„āļĄāđˆāđ€āļŠāļ·āđˆāļ­āļ–āļ·āļ­", + "labelRequireTLS": "āļšāļąāļ‡āļ„āļąāļš STARTTLS: āļ•āđ‰āļ­āļ‡āļāļēāļĢāļ­āļąāļ›āđ€āļāļĢāļ” TLS āļĨāđ‰āļĄāđ€āļŦāļĨāļ§āļŦāļēāļāđ„āļĄāđˆāļĢāļ­āļ‡āļĢāļąāļš", + "labelSecure": "āđƒāļŠāđ‰ SSL (āđāļ™āļ°āļ™āļģ): āđ€āļ‚āđ‰āļēāļĢāļŦāļąāļŠāļāļēāļĢāđ€āļŠāļ·āđˆāļ­āļĄāļ•āđˆāļ­āļ”āđ‰āļ§āļĒ SSL/TLS", + "labelTLSServername": "āļŠāļ·āđˆāļ­āđ€āļ‹āļīāļĢāđŒāļŸāđ€āļ§āļ­āļĢāđŒ TLS - āļŠāļ·āđˆāļ­āđ‚āļŪāļŠāļ•āđŒāđ€āļŠāļĢāļīāļĄāļŠāļģāļŦāļĢāļąāļšāļ•āļĢāļ§āļˆāļŠāļ­āļš TLS āđ€āļĄāļ·āđˆāļ­āđ‚āļŪāļŠāļ•āđŒāđ€āļ›āđ‡āļ™ IP", + "labelUser": "āļœāļđāđ‰āđƒāļŠāđ‰āļ­āļĩāđ€āļĄāļĨ - āļŠāļ·āđˆāļ­āļœāļđāđ‰āđƒāļŠāđ‰āļŠāļģāļŦāļĢāļąāļšāļĒāļ·āļ™āļĒāļąāļ™āļ•āļąāļ§āļ•āļ™ āđƒāļŠāđ‰āđāļ—āļ™āļ—āļĩāđˆāļ­āļĩāđ€āļĄāļĨāļ–āđ‰āļēāļĄāļĩāļāļēāļĢāļĢāļ°āļšāļļ", + "linkTransport": "āļ”āļđāļ‚āđ‰āļ­āļāļģāļŦāļ™āļ”āļ—āļĩāđˆāļ™āļĩāđˆ", + "placeholderUser": "āļ›āļĨāđˆāļ­āļĒāļ§āđˆāļēāļ‡āļŦāļēāļāđ„āļĄāđˆāļˆāļģāđ€āļ›āđ‡āļ™", + "title": "āļ­āļĩāđ€āļĄāļĨ", + "toastEmailRequiredFieldsError": "āļ•āđ‰āļ­āļ‡āļĢāļ°āļšāļļāļ­āļĩāđ€āļĄāļĨ, āđ‚āļŪāļŠāļ•āđŒ, āļžāļ­āļĢāđŒāļ• āđāļĨāļ°āļĢāļŦāļąāļŠāļœāđˆāļēāļ™" + }, + "pageSpeedSettings": { + "description": "āđƒāļŠāđˆ Google PageSpeed API Key āļ‚āļ­āļ‡āļ„āļļāļ“āđ€āļžāļ·āđˆāļ­āđ€āļ›āļīāļ”āđƒāļŠāđ‰āļ‡āļēāļ™āļāļēāļĢāļ•āļĢāļ§āļˆāļŠāļ­āļš PageSpeed āļ‚āļ­āļ‡ Google āļ„āļĨāļīāļ āļĢāļĩāđ€āļ‹āđ‡āļ• āđ€āļžāļ·āđˆāļ­āļ­āļąāļ›āđ€āļ”āļ• Key", + "labelApiKeySet": "āļ•āļąāđ‰āļ‡āļ„āđˆāļē API key āđāļĨāđ‰āļ§ āļ„āļĨāļīāļ āļĢāļĩāđ€āļ‹āđ‡āļ• āđ€āļžāļ·āđˆāļ­āđ€āļ›āļĨāļĩāđˆāļĒāļ™", + "labelApiKey": "PageSpeed API key", + "title": "Google PageSpeed API key" + }, + "saveButtonLabel": "āļšāļąāļ™āļ—āļķāļ", + "statsSettings": { + "clearAllStatsButton": "āļĨāđ‰āļēāļ‡āļŠāļ–āļīāļ•āļīāļ—āļąāđ‰āļ‡āļŦāļĄāļ”", + "clearAllStatsDescription": "āļĨāđ‰āļēāļ‡āļŠāļ–āļīāļ•āļīāļ—āļąāđ‰āļ‡āļŦāļĄāļ” āļāļēāļĢāļāļĢāļ°āļ—āļģāļ™āļĩāđ‰āđ„āļĄāđˆāļŠāļēāļĄāļēāļĢāļ–āļĒāđ‰āļ­āļ™āļāļĨāļąāļšāđ„āļ”āđ‰", + "clearAllStatsDialogConfirm": "āđƒāļŠāđˆ āļĨāđ‰āļēāļ‡āļŠāļ–āļīāļ•āļīāļ—āļąāđ‰āļ‡āļŦāļĄāļ”", + "clearAllStatsDialogDescription": "āđ€āļĄāļ·āđˆāļ­āļ–āļđāļāļĨāļšāđāļĨāđ‰āļ§ āļ›āļĢāļ°āļ§āļąāļ•āļīāļāļēāļĢāļ•āļĢāļ§āļˆāļŠāļ­āļšāđāļĨāļ°āļŠāļ–āļīāļ•āļīāđ„āļĄāđˆāļŠāļēāļĄāļēāļĢāļ–āļāļđāđ‰āļ„āļ·āļ™āđ„āļ”āđ‰", + "clearAllStatsDialogTitle": "āļ„āļļāļ“āļ•āđ‰āļ­āļ‡āļāļēāļĢāļĨāđ‰āļēāļ‡āļŠāļ–āļīāļ•āļīāļ—āļąāđ‰āļ‡āļŦāļĄāļ”āļŦāļĢāļ·āļ­āđ„āļĄāđˆ?", + "description": "āļāļģāļŦāļ™āļ”āļĢāļ°āļĒāļ°āđ€āļ§āļĨāļēāļ—āļĩāđˆāļ•āđ‰āļ­āļ‡āļāļēāļĢāđ€āļāđ‡āļšāļ‚āđ‰āļ­āļĄāļđāļĨāļ›āļĢāļ°āļ§āļąāļ•āļī āļ„āļļāļ“āļĒāļąāļ‡āļŠāļēāļĄāļēāļĢāļ–āļĨāđ‰āļēāļ‡āļ‚āđ‰āļ­āļĄāļđāļĨāļ—āļąāđ‰āļ‡āļŦāļĄāļ”āļ—āļĩāđˆāļĄāļĩāļ­āļĒāļđāđˆāđ„āļ”āđ‰", + "labelTTL": "āļˆāļģāļ™āļ§āļ™āļ§āļąāļ™āļ—āļĩāđˆāļ•āđ‰āļ­āļ‡āļāļēāļĢāđ€āļāđ‡āļšāļ›āļĢāļ°āļ§āļąāļ•āļīāļāļēāļĢāļ•āļĢāļ§āļˆāļŠāļ­āļš", + "labelTTLOptional": "0 āļŦāļĄāļēāļĒāļ–āļķāļ‡āđ„āļĄāđˆāļˆāļģāļāļąāļ”", + "title": "āļ›āļĢāļ°āļ§āļąāļ•āļīāļāļēāļĢāļ•āļĢāļ§āļˆāļŠāļ­āļš" + }, + "systemResetSettings": { + "buttonRemoveAllMonitors": "āļĨāļšāļĄāļ­āļ™āļīāđ€āļ•āļ­āļĢāđŒāļ—āļąāđ‰āļ‡āļŦāļĄāļ”", + "description": "āļĨāļšāļĄāļ­āļ™āļīāđ€āļ•āļ­āļĢāđŒāļ—āļąāđ‰āļ‡āļŦāļĄāļ”āļ­āļ­āļāļˆāļēāļāļĢāļ°āļšāļšāļ‚āļ­āļ‡āļ„āļļāļ“", + "dialogConfirm": "āđƒāļŠāđˆ āļĨāļšāļĄāļ­āļ™āļīāđ€āļ•āļ­āļĢāđŒāļ—āļąāđ‰āļ‡āļŦāļĄāļ”", + "dialogDescription": "āđ€āļĄāļ·āđˆāļ­āļ–āļđāļāļĨāļšāđāļĨāđ‰āļ§ āļĄāļ­āļ™āļīāđ€āļ•āļ­āļĢāđŒāđ„āļĄāđˆāļŠāļēāļĄāļēāļĢāļ–āļāļđāđ‰āļ„āļ·āļ™āđ„āļ”āđ‰", + "dialogTitle": "āļ„āļļāļ“āļ•āđ‰āļ­āļ‡āļāļēāļĢāļĨāļšāļĄāļ­āļ™āļīāđ€āļ•āļ­āļĢāđŒāļ—āļąāđ‰āļ‡āļŦāļĄāļ”āļŦāļĢāļ·āļ­āđ„āļĄāđˆ?", + "title": "āļĢāļĩāđ€āļ‹āđ‡āļ•āļĢāļ°āļšāļš" + }, + "timezoneSettings": { + "description": "āđ€āļĨāļ·āļ­āļāđ‚āļ‹āļ™āđ€āļ§āļĨāļēāļ—āļĩāđˆāđƒāļŠāđ‰āđāļŠāļ”āļ‡āļ§āļąāļ™āļ—āļĩāđˆāđāļĨāļ°āđ€āļ§āļĨāļēāđƒāļ™āđāļ­āļ›āļ—āļąāđ‰āļ‡āļŦāļĄāļ”", + "label": "āđ‚āļ‹āļ™āđ€āļ§āļĨāļēāļ—āļĩāđˆāđāļŠāļ”āļ‡", + "title": "āđ‚āļ‹āļ™āđ€āļ§āļĨāļēāļ—āļĩāđˆāđāļŠāļ”āļ‡" + }, + "title": "āļāļēāļĢāļ•āļąāđ‰āļ‡āļ„āđˆāļē", + "uiSettings": { + "description": "āļŠāļĨāļąāļšāđ‚āļŦāļĄāļ”āļŠāļ§āđˆāļēāļ‡/āļĄāļ·āļ” āļŦāļĢāļ·āļ­āđ€āļ›āļĨāļĩāđˆāļĒāļ™āļ āļēāļĐāļēāļ­āļīāļ™āđ€āļ—āļ­āļĢāđŒāđ€āļŸāļ‹āļœāļđāđ‰āđƒāļŠāđ‰", + "labelLanguage": "āļ āļēāļĐāļē", + "labelTheme": "āđ‚āļŦāļĄāļ”āļ˜āļĩāļĄ", + "title": "āļĢāļđāļ›āļĨāļąāļāļĐāļ“āđŒ" + }, + "urlSettings": { + "description": "āđāļŠāļ”āļ‡ IP/URL āļ‚āļ­āļ‡āļĄāļ­āļ™āļīāđ€āļ•āļ­āļĢāđŒāļšāļ™āļŦāļ™āđ‰āļēāļŠāļ–āļēāļ™āļ°āļŠāļēāļ˜āļēāļĢāļ“āļ° āļŦāļēāļāļ›āļīāļ”āđƒāļŠāđ‰āļ‡āļēāļ™ āļˆāļ°āđāļŠāļ”āļ‡āđ€āļ‰āļžāļēāļ°āļŠāļ·āđˆāļ­āļĄāļ­āļ™āļīāđ€āļ•āļ­āļĢāđŒāđ€āļžāļ·āđˆāļ­āļ›āđ‰āļ­āļ‡āļāļąāļ™āļ‚āđ‰āļ­āļĄāļđāļĨāļŠāļģāļ„āļąāļ", + "label": "āđāļŠāļ”āļ‡ IP/URL āļšāļ™āļŦāļ™āđ‰āļēāļŠāļ–āļēāļ™āļ°", + "selectDisabled": "āļ›āļīāļ”āđƒāļŠāđ‰āļ‡āļēāļ™", + "selectEnabled": "āđ€āļ›āļīāļ”āđƒāļŠāđ‰āļ‡āļēāļ™", + "title": "IP/URL āļ‚āļ­āļ‡āļĄāļ­āļ™āļīāđ€āļ•āļ­āļĢāđŒāļšāļ™āļŦāļ™āđ‰āļēāļŠāļ–āļēāļ™āļ°" + }, + "globalThresholds": { + "title": "āđ€āļāļ“āļ‘āđŒāļāļēāļĢāļ•āļĢāļ§āļˆāļŠāļ­āļšāđāļšāļšāļĢāļ§āļĄ", + "description": "āļāļģāļŦāļ™āļ”āļ„āđˆāļēāđ€āļāļ“āļ‘āđŒāļ‚āļ­āļ‡ CPU, āļŦāļ™āđˆāļ§āļĒāļ„āļ§āļēāļĄāļˆāļģ, āļ”āļīāļŠāļāđŒ āđāļĨāļ°āļ­āļļāļ“āļŦāļ āļđāļĄāļī āļŦāļēāļāļĢāļ°āļšāļļāļ„āđˆāļēāđ„āļ§āđ‰ āļĢāļ°āļšāļšāļˆāļ°āđ€āļ›āļīāļ”āđƒāļŠāđ‰āļ‡āļēāļ™āļāļēāļĢāļ•āļĢāļ§āļˆāļŠāļ­āļšāđ‚āļ”āļĒāļ­āļąāļ•āđ‚āļ™āļĄāļąāļ•āļī" + } + }, + "statusPageCreate": { + "buttonSave": "āļšāļąāļ™āļ—āļķāļ" + }, + "incidentsOptionsHeaderFilterResolved": "āđāļāđ‰āđ„āļ‚āđāļĨāđ‰āļ§", + "settingsSave": "āļšāļąāļ™āļ—āļķāļ", + "statusPageCreateAppearanceTitle": "āļĢāļđāļ›āļĨāļąāļāļĐāļ“āđŒ", + "confirmPassword": "āļĒāļ·āļ™āļĒāļąāļ™āļĢāļŦāļąāļŠāļœāđˆāļēāļ™", + "monitorHooks": { + "failureAddDemoMonitors": "āđ„āļĄāđˆāļŠāļēāļĄāļēāļĢāļ–āđ€āļžāļīāđˆāļĄāļĄāļ­āļ™āļīāđ€āļ•āļ­āļĢāđŒāļ•āļąāļ§āļ­āļĒāđˆāļēāļ‡āđ„āļ”āđ‰", + "successAddDemoMonitors": "āđ€āļžāļīāđˆāļĄāļĄāļ­āļ™āļīāđ€āļ•āļ­āļĢāđŒāļ•āļąāļ§āļ­āļĒāđˆāļēāļ‡āļŠāļģāđ€āļĢāđ‡āļˆ" + }, + "settingsAppearance": "āļĢāļđāļ›āļĨāļąāļāļĐāļ“āđŒ", + "settingsDisplayTimezone": "āđ‚āļ‹āļ™āđ€āļ§āļĨāļēāļ—āļĩāđˆāđāļŠāļ”āļ‡", + "settingsGeneralSettings": "āļāļēāļĢāļ•āļąāđ‰āļ‡āļ„āđˆāļēāļ—āļąāđˆāļ§āđ„āļ›", + "incidentsOptionsHeaderTotalIncidents": "āļˆāļģāļ™āļ§āļ™āđ€āļŦāļ•āļļāļāļēāļĢāļ“āđŒāļ—āļąāđ‰āļ‡āļŦāļĄāļ”", + "statusPage": { + "deleteSuccess": "āļĨāļšāļŦāļ™āđ‰āļēāļŠāļ–āļēāļ™āļ°āļŠāļģāđ€āļĢāđ‡āļˆ", + "deleteFailed": "āļĨāļšāļŦāļ™āđ‰āļēāļŠāļ–āļēāļ™āļ°āļĨāđ‰āļĄāđ€āļŦāļĨāļ§", + "createSuccess": "āļŠāļĢāđ‰āļēāļ‡āļŦāļ™āđ‰āļēāļŠāļ–āļēāļ™āļ°āļŠāļģāđ€āļĢāđ‡āļˆ", + "updateSuccess": "āļ­āļąāļ›āđ€āļ”āļ•āļŦāļ™āđ‰āļēāļŠāļ–āļēāļ™āļ°āļŠāļģāđ€āļĢāđ‡āļˆ", + "generalSettings": "āļāļēāļĢāļ•āļąāđ‰āļ‡āļ„āđˆāļēāļ—āļąāđˆāļ§āđ„āļ›", + "contents": "āđ€āļ™āļ·āđ‰āļ­āļŦāļē", + "fallback": { + "checks": [ + "āļ•āļĢāļ§āļˆāļŠāļ­āļšāđāļĨāļ°āđāļŠāļ”āļ‡āļŠāļļāļ‚āļ āļēāļžāļ‚āļ­āļ‡āļšāļĢāļīāļāļēāļĢāđāļšāļšāđ€āļĢāļĩāļĒāļĨāđ„āļ—āļĄāđŒ", + "āļ•āļīāļ”āļ•āļēāļĄāļŦāļĨāļēāļĒāļšāļĢāļīāļāļēāļĢāđāļĨāļ°āđāļŠāļĢāđŒāļŠāļ–āļēāļ™āļ°", + "āđāļˆāđ‰āļ‡āļœāļđāđ‰āđƒāļŠāđ‰āđƒāļŦāđ‰āļ—āļĢāļēāļšāđ€āļāļĩāđˆāļĒāļ§āļāļąāļšāļāļēāļĢāļŦāļĒāļļāļ”āļ—āļģāļ‡āļēāļ™āđāļĨāļ°āļ›āļĢāļ°āļŠāļīāļ—āļ˜āļīāļ āļēāļž" + ], + "title": "āļŦāļ™āđ‰āļēāļŠāļ–āļēāļ™āļ°āđƒāļŠāđ‰āļŠāļģāļŦāļĢāļąāļš:", + "actionButton": "āļĄāļēāļŠāļĢāđ‰āļēāļ‡āļŦāļ™āđ‰āļēāļŠāļ–āļēāļ™āļ°āđāļĢāļāļāļąāļ™āđ€āļ–āļ­āļ°!" + } + }, + "testNotificationsDisabled": "āļĒāļąāļ‡āđ„āļĄāđˆāļĄāļĩāļāļēāļĢāļ•āļąāđ‰āļ‡āļ„āđˆāļēāļāļēāļĢāđāļˆāđ‰āļ‡āđ€āļ•āļ·āļ­āļ™āļŠāļģāļŦāļĢāļąāļšāļĄāļ­āļ™āļīāđ€āļ•āļ­āļĢāđŒāļ™āļĩāđ‰ āļ„āļļāļ“āļ•āđ‰āļ­āļ‡āđ€āļžāļīāđˆāļĄāđ‚āļ”āļĒāļ„āļĨāļīāļāļ›āļļāđˆāļĄ 'āļ•āļąāđ‰āļ‡āļ„āđˆāļē'", + "incidentsTableResolvedAt": "āđāļāđ‰āđ„āļ‚āđ€āļĄāļ·āđˆāļ­", + "incidentsTableActionResolve": "āđāļāđ‰āđ„āļ‚", + "checkHooks": { + "failureResolveOne": "āđāļāđ‰āđ„āļ‚āđ€āļŦāļ•āļļāļāļēāļĢāļ“āđŒāļĨāđ‰āļĄāđ€āļŦāļĨāļ§", + "failureResolveAll": "āđāļāđ‰āđ„āļ‚āđ€āļŦāļ•āļļāļāļēāļĢāļ“āđŒāļ—āļąāđ‰āļ‡āļŦāļĄāļ”āļĨāđ‰āļĄāđ€āļŦāļĨāļ§", + "failureResolveMonitor": "āđ„āļĄāđˆāļŠāļēāļĄāļēāļĢāļ–āđāļāđ‰āđ„āļ‚āđ€āļŦāļ•āļļāļāļēāļĢāļ“āđŒāļ‚āļ­āļ‡āļ•āļąāļ§āļ•āļĢāļ§āļˆāļŠāļ­āļšāđ„āļ”āđ‰" + }, + "checkFormError": "āļāļĢāļļāļ“āļēāļ•āļĢāļ§āļˆāļŠāļ­āļšāđāļšāļšāļŸāļ­āļĢāđŒāļĄāļŠāļģāļŦāļĢāļąāļšāļ‚āđ‰āļ­āļœāļīāļ”āļžāļĨāļēāļ”", + "diagnosticsPage": { + "diagnosticDescription": "āļ§āļīāļ™āļīāļˆāļ‰āļąāļĒāļĢāļ°āļšāļš", + "statsDescription": "āļŠāļ–āļīāļ•āļīāļĢāļ°āļšāļš", + "gauges": { + "heapAllocationTitle": "āļāļēāļĢāļˆāļąāļ”āļŠāļĢāļĢ Heap", + "heapAllocationSubtitle": "% āļ‚āļ­āļ‡āļŦāļ™āđˆāļ§āļĒāļ„āļ§āļēāļĄāļˆāļģāļ—āļĩāđˆāđƒāļŠāđ‰āđ„āļ”āđ‰", + "heapUsageTitle": "āļāļēāļĢāđƒāļŠāđ‰āļ‡āļēāļ™ Heap", + "heapUsageSubtitle": "% āļ‚āļ­āļ‡āļŦāļ™āđˆāļ§āļĒāļ„āļ§āļēāļĄāļˆāļģāļ—āļĩāđˆāđƒāļŠāđ‰āđ„āļ”āđ‰", + "heapUtilizationTitle": "āļāļēāļĢāđƒāļŠāđ‰āļ‡āļēāļ™ Heap", + "heapUtilizationSubtitle": "% āļ‚āļ­āļ‡āļ—āļĩāđˆāļˆāļąāļ”āļŠāļĢāļĢāđāļĨāđ‰āļ§", + "instantCpuUsageTitle": "āļāļēāļĢāđƒāļŠāđ‰āļ‡āļēāļ™ CPU āļ—āļąāļ™āļ—āļĩ", + "instantCpuUsageSubtitle": "% āļ‚āļ­āļ‡āđ€āļ§āļĨāļē 1 āļ§āļīāļ™āļēāļ—āļĩāļ—āļĩāđˆ CPU āđƒāļŠāđ‰" + }, + "stats": { + "eventLoopDelayTitle": "āļ„āļ§āļēāļĄāļĨāđˆāļēāļŠāđ‰āļēāļ§āļ‡āļˆāļĢ Event Loop", + "uptimeTitle": "āđ€āļ§āļĨāļēāļ—āļģāļ‡āļēāļ™", + "usedHeapSizeTitle": "āļ‚āļ™āļēāļ”āļ—āļĩāđˆāđƒāļŠāđ‰ Heap", + "totalHeapSizeTitle": "āļĢāļ§āļĄāļ‚āļ™āļēāļ” Heap", + "osMemoryLimitTitle": "āļ‚āļĩāļ”āļˆāļģāļāļąāļ”āļŦāļ™āđˆāļ§āļĒāļ„āļ§āļēāļĄāļˆāļģ OS" + } + }, + "pageSpeedLighthouseAPI": "āđƒāļŠāđ‰ Lighthouse PageSpeed API āđ€āļžāļ·āđˆāļ­āļ•āļĢāļ§āļˆāļŠāļ­āļšāđ€āļ§āđ‡āļšāđ„āļ‹āļ•āđŒāļ‚āļ­āļ‡āļ„āļļāļ“", + "time": { + "threeMinutes": "3 āļ™āļēāļ—āļĩ", + "fiveMinutes": "5 āļ™āļēāļ—āļĩ", + "tenMinutes": "10 āļ™āļēāļ—āļĩ", + "twentyMinutes": "20 āļ™āļēāļ—āļĩ", + "oneHour": "1 āļŠāļąāđˆāļ§āđ‚āļĄāļ‡", + "oneDay": "1 āļ§āļąāļ™", + "oneWeek": "1 āļŠāļąāļ›āļ”āļēāļŦāđŒ", + "fourMinutes": "4 āļ™āļēāļ—āļĩ", + "oneMinute": "1 āļ™āļēāļ—āļĩ", + "twoMinutes": "2 āļ™āļēāļ—āļĩ", + "fifteenSeconds": "15 āļ§āļīāļ™āļēāļ—āļĩ", + "thirtySeconds": "30 āļ§āļīāļ™āļēāļ—āļĩ" + }, + "general": { + "noOptionsFound": "āđ„āļĄāđˆāļžāļš {{unit}}" + }, + "infrastructureMonitor": { + "fallback": { + "checks": [ + "āļ•āļīāļ”āļ•āļēāļĄāļ›āļĢāļ°āļŠāļīāļ—āļ˜āļīāļ āļēāļžāļ‚āļ­āļ‡āđ€āļ‹āļīāļĢāđŒāļŸāđ€āļ§āļ­āļĢāđŒāļ‚āļ­āļ‡āļ„āļļāļ“", + "āļĢāļ°āļšāļļāļˆāļļāļ”āļ„āļ­āļ‚āļ§āļ”āđāļĨāļ°āđ€āļžāļīāđˆāļĄāļ›āļĢāļ°āļŠāļīāļ—āļ˜āļīāļ āļēāļžāļāļēāļĢāđƒāļŠāđ‰āļ‡āļēāļ™", + "āļĄāļąāđˆāļ™āđƒāļˆāđƒāļ™āļ„āļ§āļēāļĄāđ€āļŠāļ–āļĩāļĒāļĢāļ”āđ‰āļ§āļĒāļāļēāļĢāļ•āļĢāļ§āļˆāļŠāļ­āļšāđāļšāļšāđ€āļĢāļĩāļĒāļĨāđ„āļ—āļĄāđŒ" + ], + "title": "āļ•āļąāļ§āļ•āļĢāļ§āļˆāļŠāļ­āļšāđ‚āļ„āļĢāļ‡āļŠāļĢāđ‰āļēāļ‡āļžāļ·āđ‰āļ™āļāļēāļ™āđƒāļŠāđ‰āļŠāļģāļŦāļĢāļąāļš:", + "actionButton": "āļĄāļēāļŠāļĢāđ‰āļēāļ‡āļ•āļąāļ§āļ•āļĢāļ§āļˆāļŠāļ­āļšāđ‚āļ„āļĢāļ‡āļŠāļĢāđ‰āļēāļ‡āļžāļ·āđ‰āļ™āļāļēāļ™āļ•āļąāļ§āđāļĢāļāļāļąāļ™āđ€āļ–āļ­āļ°!" + } + }, + "maintenanceWindow": { + "fallback": { + "checks": [ + "āļāļģāļŦāļ™āļ”āļŠāđˆāļ§āļ‡āđ€āļ§āļĨāļēāļšāļģāļĢāļļāļ‡āļĢāļąāļāļĐāļē", + "āļĨāļ”āļ„āļ§āļēāļĄāđ€āļ‚āđ‰āļēāđƒāļˆāļœāļīāļ”", + "āļŦāļĒāļļāļ”āļŠāđˆāļ‡āļāļēāļĢāđāļˆāđ‰āļ‡āđ€āļ•āļ·āļ­āļ™āđƒāļ™āļŠāđˆāļ§āļ‡āđ€āļ§āļĨāļēāļšāļģāļĢāļļāļ‡āļĢāļąāļāļĐāļē" + ], + "title": "āļŠāđˆāļ§āļ‡āđ€āļ§āļĨāļēāļšāļģāļĢāļļāļ‡āļĢāļąāļāļĐāļēāđƒāļŠāđ‰āļŠāļģāļŦāļĢāļąāļš:", + "actionButton": "āļĄāļēāļŠāļĢāđ‰āļēāļ‡āļŠāđˆāļ§āļ‡āđ€āļ§āļĨāļēāļšāļģāļĢāļļāļ‡āļĢāļąāļāļĐāļēāđāļĢāļāļāļąāļ™āđ€āļ–āļ­āļ°!" + } + }, + "pageSpeed": { + "fallback": { + "checks": [ + "āļĢāļēāļĒāļ‡āļēāļ™āđ€āļāļĩāđˆāļĒāļ§āļāļąāļšāļ›āļĢāļ°āļŠāļšāļāļēāļĢāļ“āđŒāļœāļđāđ‰āđƒāļŠāđ‰āļ‚āļ­āļ‡āļŦāļ™āđ‰āļēāđ€āļ§āđ‡āļš", + "āļŠāđˆāļ§āļĒāļ§āļīāđ€āļ„āļĢāļēāļ°āļŦāđŒāļ„āļ§āļēāļĄāđ€āļĢāđ‡āļ§āļ‚āļ­āļ‡āļŦāļ™āđ‰āļēāđ€āļ§āđ‡āļš", + "āđƒāļŦāđ‰āļ„āļģāđāļ™āļ°āļ™āļģāđƒāļ™āļāļēāļĢāļ›āļĢāļąāļšāļ›āļĢāļļāļ‡āļŦāļ™āđ‰āļēāđ€āļ§āđ‡āļš" + ], + "title": "āļ•āļąāļ§āļ•āļĢāļ§āļˆāļŠāļ­āļš PageSpeed āđƒāļŠāđ‰āļŠāļģāļŦāļĢāļąāļš:", + "actionButton": "āļĄāļēāļŠāļĢāđ‰āļēāļ‡āļ•āļąāļ§āļ•āļĢāļ§āļˆāļŠāļ­āļš PageSpeed āļ•āļąāļ§āđāļĢāļāļāļąāļ™āđ€āļ–āļ­āļ°!" + } + }, + "uptimeMonitor": { + "fallback": { + "checks": [ + "āļ•āļĢāļ§āļˆāļŠāļ­āļšāļ§āđˆāļēāđ€āļ§āđ‡āļšāđ„āļ‹āļ•āđŒāļŦāļĢāļ·āļ­āđ€āļ‹āļīāļĢāđŒāļŸāđ€āļ§āļ­āļĢāđŒāļ­āļ­āļ™āđ„āļĨāļ™āđŒāđāļĨāļ°āļ•āļ­āļšāļŠāļ™āļ­āļ‡āļŦāļĢāļ·āļ­āđ„āļĄāđˆ", + "āđāļˆāđ‰āļ‡āļ—āļĩāļĄāđ€āļĄāļ·āđˆāļ­āđ€āļāļīāļ”āļ›āļąāļāļŦāļēāļāļēāļĢāļŦāļĒāļļāļ”āļ—āļģāļ‡āļēāļ™āļŦāļĢāļ·āļ­āļ›āļĢāļ°āļŠāļīāļ—āļ˜āļīāļ āļēāļž", + "āļ•āļĢāļ§āļˆāļŠāļ­āļš HTTP endpoints, ping, āļ„āļ­āļ™āđ€āļ—āļ™āđ€āļ™āļ­āļĢāđŒ āđāļĨāļ°āļžāļ­āļĢāđŒāļ•", + "āļ•āļīāļ”āļ•āļēāļĄāļŠāļ–āļīāļ•āļī uptime āđāļĨāļ°āļ„āļ§āļēāļĄāđ€āļŠāļ–āļĩāļĒāļĢāļĒāđ‰āļ­āļ™āļŦāļĨāļąāļ‡" + ], + "title": "āļ•āļąāļ§āļ•āļĢāļ§āļˆāļŠāļ­āļš Uptime āđƒāļŠāđ‰āļŠāļģāļŦāļĢāļąāļš:", + "actionButton": "āļĄāļēāļŠāļĢāđ‰āļēāļ‡āļ•āļąāļ§āļ•āļĢāļ§āļˆāļŠāļ­āļš Uptime āļ•āļąāļ§āđāļĢāļāļāļąāļ™āđ€āļ–āļ­āļ°!" + } + }, + "editUserPage": { + "form": { + "email": "āļ­āļĩāđ€āļĄāļĨ", + "firstName": "āļŠāļ·āđˆāļ­āļˆāļĢāļīāļ‡", + "lastName": "āļ™āļēāļĄāļŠāļāļļāļĨ", + "role": "āļšāļ—āļšāļēāļ—", + "save": "āļšāļąāļ™āļ—āļķāļ" + }, + "table": { + "actionHeader": "āļāļēāļĢāļ”āļģāđ€āļ™āļīāļ™āļāļēāļĢ", + "roleHeader": "āļšāļ—āļšāļēāļ—" + }, + "title": "āđāļāđ‰āđ„āļ‚āļœāļđāđ‰āđƒāļŠāđ‰", + "toast": { + "successUserUpdate": "āļ­āļąāļ›āđ€āļ”āļ•āļœāļđāđ‰āđƒāļŠāđ‰āđ€āļĢāļĩāļĒāļšāļĢāđ‰āļ­āļĒāđāļĨāđ‰āļ§", + "validationErrors": "āļ‚āđ‰āļ­āļœāļīāļ”āļžāļĨāļēāļ”āđƒāļ™āļāļēāļĢāļ•āļĢāļ§āļˆāļŠāļ­āļšāļ‚āđ‰āļ­āļĄāļđāļĨ" + } + }, + "incidentsPageActionResolveMonitor": "āđāļāđ‰āđ„āļ‚āđ€āļŦāļ•āļļāļāļēāļĢāļ“āđŒāļ‚āļ­āļ‡āļ•āļąāļ§āļ•āļĢāļ§āļˆāļŠāļ­āļš", + "incidentsPageActionResolveAll": "āđāļāđ‰āđ„āļ‚āđ€āļŦāļ•āļļāļāļēāļĢāļ“āđŒāļ—āļąāđ‰āļ‡āļŦāļĄāļ”", + "matchMethodOptions": { + "equal": "Equal", + "equalPlaceholder": "success", + "include": "Include", + "includePlaceholder": "ok", + "regex": "Regex", + "regexPlaceholder": "^(success|ok)$", + "text": "āļ§āļīāļ˜āļĩāļāļēāļĢāļˆāļąāļšāļ„āļđāđˆ" + }, + "monitorType": { + "docker": { + "label": "āļŠāļ·āđˆāļ­/ID āļ„āļ­āļ™āđ€āļ—āļ™āđ€āļ™āļ­āļĢāđŒ", + "namePlaceholder": "āļ„āļ­āļ™āđ€āļ—āļ™āđ€āļ™āļ­āļĢāđŒāļ‚āļ­āļ‡āļ‰āļąāļ™", + "placeholder": "my-app or abcd1234" + }, + "http": { + "label": "URL āļ—āļĩāđˆāļ•āđ‰āļ­āļ‡āļāļēāļĢāļ•āļĢāļ§āļˆāļŠāļ­āļš", + "namePlaceholder": "Google", + "placeholder": "google.com" + }, + "ping": { + "label": "āļ—āļĩāđˆāļ­āļĒāļđāđˆ IP āļ—āļĩāđˆāļ•āđ‰āļ­āļ‡āļāļēāļĢāļ•āļĢāļ§āļˆāļŠāļ­āļš", + "namePlaceholder": "Google", + "placeholder": "1.1.1.1" + }, + "port": { + "label": "āļ—āļĩāđˆāļ­āļĒāļđāđˆ IP āļ—āļĩāđˆāļ•āđ‰āļ­āļ‡āļāļēāļĢāļ•āļĢāļ§āļˆāļŠāļ­āļš", + "namePlaceholder": "Localhost:5173", + "placeholder": "localhost" + }, + "game": { + "label": "URL āļ—āļĩāđˆāļ•āđ‰āļ­āļ‡āļāļēāļĢāļĄāļ­āļ™āļīāđ€āļ•āļ­āļĢāđŒ", + "namePlaceholder": "localhost:5173", + "placeholder": "localhost" + } + }, + "uptimeAdvancedMatching": { + "jsonPath": "JSON Path" + }, + "bytesPerSecond": "āđ„āļšāļ•āđŒāļ•āđˆāļ­āļ§āļīāļ™āļēāļ—āļĩ", + "bytesReceived": "āđ„āļšāļ•āđŒāļ—āļĩāđˆāļĢāļąāļš", + "bytesSent": "āđ„āļšāļ•āđŒāļ—āļĩāđˆāļŠāđˆāļ‡", + "chooseGame": "āđ€āļĨāļ·āļ­āļāđ€āļāļĄ", + "createMonitorPage": { + "incidentConfigDescription": "āļāļēāļĢāđƒāļŠāđ‰āļŦāļ™āđ‰āļēāļ•āđˆāļēāļ‡āđ€āļĨāļ·āđˆāļ­āļ™āļˆāļ°āļŠāđˆāļ§āļĒāļāļģāļŦāļ™āļ”āđ€āļ§āļĨāļēāļ—āļĩāđˆāļ•āļąāļ§āļ•āļĢāļ§āļˆāļŠāļ­āļšāļĨāđ‰āļĄāđ€āļŦāļĨāļ§ āļŠāļ–āļēāļ™āļ°āļ‚āļ­āļ‡āļ•āļąāļ§āļ•āļĢāļ§āļˆāļŠāļ­āļšāļˆāļ°āđ€āļ›āļĨāļĩāđˆāļĒāļ™āļāđ‡āļ•āđˆāļ­āđ€āļĄāļ·āđˆāļ­āđ€āļ›āļ­āļĢāđŒāđ€āļ‹āđ‡āļ™āļ•āđŒāļ‚āļ­āļ‡āļāļēāļĢāļ•āļĢāļ§āļˆāļŠāļ­āļšāđƒāļ™āļŦāļ™āđ‰āļēāļ•āđˆāļēāļ‡āđ€āļĨāļ·āđˆāļ­āļ™āļ•āļĢāļ‡āļ•āļēāļĄāļ„āđˆāļēāļ—āļĩāđˆāļāļģāļŦāļ™āļ”", + "incidentConfigStatusWindowLabel": "āļ•āđ‰āļ­āļ‡āļĄāļĩāļāļēāļĢāļ•āļĢāļ§āļˆāļŠāļ­āļšāļāļĩāđˆāļ„āļĢāļąāđ‰āļ‡āđƒāļ™āļŦāļ™āđ‰āļēāļ•āđˆāļēāļ‡āđ€āļĨāļ·āđˆāļ­āļ™?", + "incidentConfigStatusWindowThresholdLabel": "āļ•āđ‰āļ­āļ‡āļāļēāļĢāđƒāļŦāđ‰āļāļĩāđˆāđ€āļ›āļ­āļĢāđŒāđ€āļ‹āđ‡āļ™āļ•āđŒāļ‚āļ­āļ‡āļāļēāļĢāļ•āļĢāļ§āļˆāļŠāļ­āļšāđƒāļ™āļŦāļ™āđ‰āļēāļ•āđˆāļēāļ‡āđ€āļĨāļ·āđˆāļ­āļ™āļĨāđ‰āļĄāđ€āļŦāļĨāļ§/āļŠāļģāđ€āļĢāđ‡āļˆ āļāđˆāļ­āļ™āļ—āļĩāđˆāļˆāļ°āđ€āļ›āļĨāļĩāđˆāļĒāļ™āļŠāļ–āļēāļ™āļ°āļ‚āļ­āļ‡āļ•āļąāļ§āļ•āļĢāļ§āļˆāļŠāļ­āļš?", + "incidentConfigTitle": "āđ€āļŦāļ•āļļāļāļēāļĢāļ“āđŒ" + }, + "dataRate": "āļ­āļąāļ•āļĢāļēāļāļēāļĢāļŠāđˆāļ‡āļ‚āđ‰āļ­āļĄāļđāļĨ", + "dataReceived": "āļ‚āđ‰āļ­āļĄāļđāļĨāļ—āļĩāđˆāļĢāļąāļš", + "dataSent": "āļ‚āđ‰āļ­āļĄāļđāļĨāļ—āļĩāđˆāļŠāđˆāļ‡", + "details": "āļĢāļēāļĒāļĨāļ°āđ€āļ­āļĩāļĒāļ”", + "drops": "āđāļžāđ‡āļāđ€āļāđ‡āļ•āļ—āļĩāđˆāļŦāļĨāđˆāļ™", + "errors": "āļ‚āđ‰āļ­āļœāļīāļ”āļžāļĨāļēāļ”", + "errorsIn": "āļ‚āđ‰āļ­āļœāļīāļ”āļžāļĨāļēāļ”āļ‚āļēāđ€āļ‚āđ‰āļē", + "errorsOut": "āļ‚āđ‰āļ­āļœāļīāļ”āļžāļĨāļēāļ”āļ‚āļēāļ­āļ­āļ", + "gameServerMonitoring": "āļĄāļ­āļ™āļīāđ€āļ•āļ­āļĢāđŒāđ€āļ‹āļīāļĢāđŒāļŸāđ€āļ§āļ­āļĢāđŒāđ€āļāļĄ", + "gameServerMonitoringDescription": "āļ•āļĢāļ§āļˆāļŠāļ­āļšāļ§āđˆāļēāđ€āļ‹āļīāļĢāđŒāļŸāđ€āļ§āļ­āļĢāđŒāđ€āļāļĄāļ‚āļ­āļ‡āļ„āļļāļ“āļāļģāļĨāļąāļ‡āļ—āļģāļ‡āļēāļ™āļ­āļĒāļđāđˆāļŦāļĢāļ·āļ­āđ„āļĄāđˆ", + "network": "āđ€āļ™āđ‡āļ•āđ€āļ§āļ­āļĢāđŒ", + "networkDrops": "āļāļēāļĢāļ”āļĢāļ­āļ›āļ‚āļ­āļ‡āđ€āļ™āđ‡āļ•āđ€āļ§āļ­āļĢāđŒ", + "networkErrors": "āđ€āļ™āđ‡āļ•āđ€āļ§āļ­āļĢāđŒ āļĨāđ‰āļĄāđ€āļŦāļĨāļ§", + "networkInterface": "āđ€āļ™āđ‡āļ•āđ€āļ§āļ­āļĢāđŒ āļ­āļīāļ™āđ€āļ—āļ­āļĢāđŒāđ€āļŸāļ‹", + "noNetworkStatsAvailable": "āđ„āļĄāđˆāļĄāļĩāļŠāļ–āļīāļ•āļīāļ‚āļ­āļ‡āđ€āļ™āđ‡āļ•āđ€āļ§āļ­āļĢāđŒ", + "packetsPerSecond": "āđāļžāđ‡āļāđ€āļāđ‡āļ•āļ•āđˆāļ­āļ§āļīāļ™āļēāļ—āļĩ", + "packetsReceived": "āđāļžāđ‡āļāđ€āļāđ‡āļ•āļ—āļĩāđˆāļĢāļąāļš", + "packetsReceivedRate": "āļ­āļąāļ•āļĢāļēāļāļēāļĢāļĢāļąāļšāđāļžāđ‡āļāđ€āļāđ‡āļ•", + "packetsSent": "āđāļžāđ‡āļāđ€āļāđ‡āļ•āļ—āļĩāđˆāļŠāđˆāļ‡", + "rate": "āļ­āļąāļ•āļĢāļē", + "selectInterface": "āđ€āļĨāļ·āļ­āļāļ­āļīāļ™āđ€āļ—āļ­āļĢāđŒāđ€āļŸāļ‹" +} \ No newline at end of file From 105100e349bdec9c76f611a055e72f5a30f4146b Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Wed, 17 Sep 2025 15:07:55 -0700 Subject: [PATCH 007/101] bump deps --- client/package-lock.json | 30 ++++++++++++++++++++++-------- client/package.json | 2 +- 2 files changed, 23 insertions(+), 9 deletions(-) diff --git a/client/package-lock.json b/client/package-lock.json index 32446c5d0..ee1ab1bc1 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -45,7 +45,7 @@ "eslint-plugin-react-hooks": "^5.0.0", "eslint-plugin-react-refresh": "^0.4.6", "prettier": "^3.3.3", - "vite": "6.3.5" + "vite": "6.3.6" } }, "node_modules/@ampproject/remapping": { @@ -2422,13 +2422,13 @@ } }, "node_modules/axios": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.9.0.tgz", - "integrity": "sha512-re4CqKTJaURpzbLHtIi6XpDv20/CnpXOtjRY5/CU32L8gU8ek9UIivcfvSWvmKEngmVbrUtPpdDwWDWL7DNHvg==", + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.12.2.tgz", + "integrity": "sha512-vMJzPewAlRyOgxV2dU0Cuz2O8zzzx9VYtbJOaBgXFeLc4IV/Eg50n4LowmehOOR61S8ZMpc2K5Sa7g6A4jfkUw==", "license": "MIT", "dependencies": { "follow-redirects": "^1.15.6", - "form-data": "^4.0.0", + "form-data": "^4.0.4", "proxy-from-env": "^1.1.0" } }, @@ -3756,6 +3756,20 @@ "dev": true, "license": "ISC" }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, "node_modules/function-bind": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", @@ -6375,9 +6389,9 @@ } }, "node_modules/vite": { - "version": "6.3.5", - "resolved": "https://registry.npmjs.org/vite/-/vite-6.3.5.tgz", - "integrity": "sha512-cZn6NDFE7wdTpINgs++ZJ4N49W2vRp8LCKrn3Ob1kYNtOo21vfDoaV5GzBfLU4MovSAB8uNRm4jgzVQZ+mBzPQ==", + "version": "6.3.6", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.3.6.tgz", + "integrity": "sha512-0msEVHJEScQbhkbVTb/4iHZdJ6SXp/AvxL2sjwYQFfBqleHtnCqv1J3sa9zbWz/6kW1m9Tfzn92vW+kZ1WV6QA==", "license": "MIT", "dependencies": { "esbuild": "^0.25.0", diff --git a/client/package.json b/client/package.json index 92da6fd13..3108f7121 100644 --- a/client/package.json +++ b/client/package.json @@ -58,6 +58,6 @@ "eslint-plugin-react-hooks": "^5.0.0", "eslint-plugin-react-refresh": "^0.4.6", "prettier": "^3.3.3", - "vite": "6.3.5" + "vite": "6.3.6" } } From 00fc7b803858f41e9aeb2f457caf5860abc36801 Mon Sep 17 00:00:00 2001 From: Franco Carrara Date: Wed, 17 Sep 2025 23:23:16 -0300 Subject: [PATCH 008/101] Allow jsonPath to be used along expectedValue and validate the response. --- .../service/infrastructure/networkService.js | 44 +++++++++++++++---- 1 file changed, 35 insertions(+), 9 deletions(-) diff --git a/server/src/service/infrastructure/networkService.js b/server/src/service/infrastructure/networkService.js index 50cf16b85..2b0b15059 100644 --- a/server/src/service/infrastructure/networkService.js +++ b/server/src/service/infrastructure/networkService.js @@ -1,5 +1,3 @@ -import { match } from "assert"; - const SERVICE_NAME = "NetworkService"; class NetworkService { @@ -107,7 +105,7 @@ class NetworkService { } async requestHttp(monitor) { - const { url, secret, _id, name, teamId, type, ignoreTlsErrors, jsonPath, matchMethod, expectedValue } = monitor; + const { url, secret, _id, teamId, type, ignoreTlsErrors, jsonPath, matchMethod, expectedValue } = monitor; const httpResponse = { monitorId: _id, teamId: teamId, @@ -156,33 +154,61 @@ class NetworkService { return httpResponse; } - if (expectedValue) { + if (expectedValue && !jsonPath) { let ok = false; if (matchMethod === "equal") ok = payload === expectedValue; - if (matchMethod === "include") ok = payload.includes(expectedValue); - if (matchMethod === "regex") ok = new RegExp(expectedValue).test(payload); + if (matchMethod === "include" && typeof payload === "string") ok = payload.includes(expectedValue); + if (matchMethod === "regex" && typeof payload === "string") ok = new RegExp(expectedValue).test(payload); if (ok === true) { return httpResponse; } else { httpResponse.code = 500; httpResponse.status = false; + httpResponse.message = this.stringService.httpExpectedValueFail || "Expected value did not match"; return httpResponse; } } if (jsonPath) { const contentType = response.headers["content-type"]; - const isJson = contentType?.includes("application/json"); if (!isJson) { httpResponse.status = false; httpResponse.message = this.stringService.httpNotJson; return httpResponse; } - try { - this.jmespath.search(payload, jsonPath); + const extracted = this.jmespath.search(payload, jsonPath); + if (expectedValue) { + let ok = false; + if (matchMethod === "equal") ok = extracted === expectedValue; + if (matchMethod === "include" && typeof extracted === "string") ok = extracted.includes(expectedValue); + if (matchMethod === "regex" && typeof extracted === "string") ok = new RegExp(expectedValue).test(extracted); + + if (ok) { + httpResponse.extracted = extracted; + return httpResponse; + } else { + httpResponse.status = false; + httpResponse.code = 500; + httpResponse.message = this.stringService.httpJsonPathFail || "JSONPath did not match expected value"; + httpResponse.extracted = extracted; + return httpResponse; + } + } else { + const isFalsey = extracted === false || extracted === "false" || extracted === undefined || extracted === null; + if (!isFalsey) { + httpResponse.extracted = extracted; + return httpResponse; + } else { + httpResponse.status = false; + httpResponse.code = 500; + httpResponse.message = this.stringService.httpJsonPathFail || "JSONPath result was false"; + httpResponse.extracted = extracted; + return httpResponse; + } + } } catch { httpResponse.status = false; httpResponse.message = this.stringService.httpJsonPathError; From 4039517683ece57198882955a60baec5c238362b Mon Sep 17 00:00:00 2001 From: Franco Carrara Date: Wed, 17 Sep 2025 23:33:45 -0300 Subject: [PATCH 009/101] Add response messages to string service --- server/src/locales/en.json | 2 ++ server/src/service/infrastructure/networkService.js | 6 +++--- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/server/src/locales/en.json b/server/src/locales/en.json index 22ab0d157..1345f84a8 100755 --- a/server/src/locales/en.json +++ b/server/src/locales/en.json @@ -149,10 +149,12 @@ "statusPageByTeamId": "Got status pages by team id successfully", "httpNetworkError": "Network error", "httpNotJson": "Response data is not json", + "httpExpectedValueFail": "Expected value did not match", "httpJsonPathError": "Failed to parse json data", "httpEmptyResult": "Result is empty", "httpMatchSuccess": "Response data match successfully", "httpMatchFail": "Failed to match response data", + "httpJsonPathFail": "JSONPath did not match expected value", "webhookSendSuccess": "Webhook notification sent successfully", "telegramRequiresBotTokenAndChatId": "Telegram notifications require both botToken and chatId", "webhookUrlRequired": "Webhook URL is required", diff --git a/server/src/service/infrastructure/networkService.js b/server/src/service/infrastructure/networkService.js index 2b0b15059..47ef6878f 100644 --- a/server/src/service/infrastructure/networkService.js +++ b/server/src/service/infrastructure/networkService.js @@ -165,7 +165,7 @@ class NetworkService { } else { httpResponse.code = 500; httpResponse.status = false; - httpResponse.message = this.stringService.httpExpectedValueFail || "Expected value did not match"; + httpResponse.message = this.stringService.httpExpectedValueFail; return httpResponse; } } @@ -192,7 +192,7 @@ class NetworkService { } else { httpResponse.status = false; httpResponse.code = 500; - httpResponse.message = this.stringService.httpJsonPathFail || "JSONPath did not match expected value"; + httpResponse.message = this.stringService.httpJsonPathFail; httpResponse.extracted = extracted; return httpResponse; } @@ -204,7 +204,7 @@ class NetworkService { } else { httpResponse.status = false; httpResponse.code = 500; - httpResponse.message = this.stringService.httpJsonPathFail || "JSONPath result was false"; + httpResponse.message = this.stringService.httpJsonPathFail; httpResponse.extracted = extracted; return httpResponse; } From c666cb3879e7cd6511e08753f41042918ecab6fb Mon Sep 17 00:00:00 2001 From: Franco Carrara Date: Wed, 17 Sep 2025 23:46:20 -0300 Subject: [PATCH 010/101] fix formatting issues --- client/src/locales/th.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/locales/th.json b/client/src/locales/th.json index 4e60d61e0..c3f571b6b 100644 --- a/client/src/locales/th.json +++ b/client/src/locales/th.json @@ -1119,4 +1119,4 @@ "packetsSent": "āđāļžāđ‡āļāđ€āļāđ‡āļ•āļ—āļĩāđˆāļŠāđˆāļ‡", "rate": "āļ­āļąāļ•āļĢāļē", "selectInterface": "āđ€āļĨāļ·āļ­āļāļ­āļīāļ™āđ€āļ—āļ­āļĢāđŒāđ€āļŸāļ‹" -} \ No newline at end of file +} From 386db5029a38fe207a8db7d5a675392f26244762 Mon Sep 17 00:00:00 2001 From: Owaise Date: Fri, 19 Sep 2025 00:03:18 +0530 Subject: [PATCH 011/101] Addition of incident tab in creation and config page which is same. --- .../hooks/useInfrastructureMonitorForm.jsx | 24 ++++++++----- .../Create/hooks/useInfrastructureSubmit.jsx | 2 ++ .../src/Pages/Infrastructure/Create/index.jsx | 35 ++++++++++++++++++- client/src/Validation/validation.js | 10 ++++++ 4 files changed, 61 insertions(+), 10 deletions(-) diff --git a/client/src/Pages/Infrastructure/Create/hooks/useInfrastructureMonitorForm.jsx b/client/src/Pages/Infrastructure/Create/hooks/useInfrastructureMonitorForm.jsx index fa8a6ebe9..f0a701517 100644 --- a/client/src/Pages/Infrastructure/Create/hooks/useInfrastructureMonitorForm.jsx +++ b/client/src/Pages/Infrastructure/Create/hooks/useInfrastructureMonitorForm.jsx @@ -1,4 +1,4 @@ -import { useState } from "react"; +import { useState, useCallback } from "react"; const useInfrastructureMonitorForm = () => { const [infrastructureMonitor, setInfrastructureMonitor] = useState({ url: "", @@ -6,6 +6,8 @@ const useInfrastructureMonitorForm = () => { notifications: [], notify_email: false, interval: 0.25, + statusWindowSize: 5, + statusWindowThreshold: 60, cpu: false, usage_cpu: "", memory: false, @@ -29,9 +31,10 @@ const useInfrastructureMonitorForm = () => { [event.target.name]: event.target.checked, }); }; - const initializeInfrastructureMonitorForCreate = (globalSettings) => { + const initializeInfrastructureMonitorForCreate = useCallback((globalSettings) => { const gt = globalSettings?.data?.settings?.globalThresholds || {}; - setInfrastructureMonitor({ + setInfrastructureMonitor((prev) => ({ + ...prev, url: "", name: "", notifications: [], @@ -45,17 +48,20 @@ const useInfrastructureMonitorForm = () => { temperature: gt.temperature !== undefined, usage_temperature: gt.temperature !== undefined ? gt.temperature.toString() : "", secret: "", - }); - }; + })); + }, []); - const initializeInfrastructureMonitorForUpdate = (monitor) => { + const initializeInfrastructureMonitorForUpdate = useCallback((monitor) => { const MS_PER_MINUTE = 60000; const { thresholds = {} } = monitor; - setInfrastructureMonitor({ + setInfrastructureMonitor((prev) => ({ + ...prev, url: monitor.url.replace(/^https?:\/\//, ""), name: monitor.name || "", notifications: monitor.notifications || [], interval: monitor.interval / MS_PER_MINUTE, + statusWindowSize: monitor.statusWindowSize, + statusWindowThreshold: monitor.statusWindowThreshold, cpu: thresholds.usage_cpu !== undefined, usage_cpu: thresholds.usage_cpu !== undefined ? (thresholds.usage_cpu * 100).toString() : "", @@ -75,8 +81,8 @@ const useInfrastructureMonitorForm = () => { ? (thresholds.usage_temperature * 100).toString() : "", secret: monitor.secret || "", - }); - }; + })); + }, []); return { infrastructureMonitor, setInfrastructureMonitor, diff --git a/client/src/Pages/Infrastructure/Create/hooks/useInfrastructureSubmit.jsx b/client/src/Pages/Infrastructure/Create/hooks/useInfrastructureSubmit.jsx index da564ae7c..9b3ee0d04 100644 --- a/client/src/Pages/Infrastructure/Create/hooks/useInfrastructureSubmit.jsx +++ b/client/src/Pages/Infrastructure/Create/hooks/useInfrastructureSubmit.jsx @@ -12,6 +12,8 @@ const useInfrastructureSubmit = () => { ? infrastructureMonitor.url : infrastructureMonitor.name, interval: infrastructureMonitor.interval * MS_PER_MINUTE, + statusWindowSize: infrastructureMonitor.statusWindowSize, + statusWindowThreshold: infrastructureMonitor.statusWindowThreshold, cpu: infrastructureMonitor.cpu, ...(infrastructureMonitor.cpu ? { usage_cpu: infrastructureMonitor.usage_cpu } diff --git a/client/src/Pages/Infrastructure/Create/index.jsx b/client/src/Pages/Infrastructure/Create/index.jsx index 28007c538..1b1c6e9ba 100644 --- a/client/src/Pages/Infrastructure/Create/index.jsx +++ b/client/src/Pages/Infrastructure/Create/index.jsx @@ -86,7 +86,7 @@ const CreateInfrastructureMonitor = () => { setHttps(monitor.url.startsWith("https")); initializeInfrastructureMonitorForUpdate(monitor); } - }, [isCreate, monitor, globalSettings, globalSettingsLoading]); + }, [isCreate, monitor, globalSettings, globalSettingsLoading, initializeInfrastructureMonitorForCreate, initializeInfrastructureMonitorForUpdate]); // Handlers const onSubmit = async (event) => { @@ -281,6 +281,39 @@ const CreateInfrastructureMonitor = () => { setNotifications={infrastructureMonitor.notifications} /> + + + + {t("createMonitorPage.incidentConfigTitle")} + + + {t("createMonitorPage.incidentConfigDescription")} + + + + + + + Date: Fri, 19 Sep 2025 00:06:31 +0530 Subject: [PATCH 012/101] Fixed the formatting. --- client/src/Pages/Infrastructure/Create/index.jsx | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/client/src/Pages/Infrastructure/Create/index.jsx b/client/src/Pages/Infrastructure/Create/index.jsx index 1b1c6e9ba..a9d16c926 100644 --- a/client/src/Pages/Infrastructure/Create/index.jsx +++ b/client/src/Pages/Infrastructure/Create/index.jsx @@ -86,7 +86,14 @@ const CreateInfrastructureMonitor = () => { setHttps(monitor.url.startsWith("https")); initializeInfrastructureMonitorForUpdate(monitor); } - }, [isCreate, monitor, globalSettings, globalSettingsLoading, initializeInfrastructureMonitorForCreate, initializeInfrastructureMonitorForUpdate]); + }, [ + isCreate, + monitor, + globalSettings, + globalSettingsLoading, + initializeInfrastructureMonitorForCreate, + initializeInfrastructureMonitorForUpdate, + ]); // Handlers const onSubmit = async (event) => { From b35fee1be831214a89388573f153f3908327f05b Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Thu, 18 Sep 2025 12:02:49 -0700 Subject: [PATCH 013/101] bump version --- client/vite.config.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/vite.config.js b/client/vite.config.js index 158dc5e4e..07b5de748 100644 --- a/client/vite.config.js +++ b/client/vite.config.js @@ -5,7 +5,7 @@ import { execSync } from "child_process"; export default defineConfig(({ mode }) => { const env = loadEnv(mode, process.cwd(), ""); - let version = "3.1.8"; + let version = "3.2.0"; return { base: "/", From ddb7c248328b46618dccfcea2554c082a4cf988d Mon Sep 17 00:00:00 2001 From: GitHub Action Date: Thu, 18 Sep 2025 20:25:11 +0000 Subject: [PATCH 014/101] feat: translations updated from POEditor --- client/src/locales/ar.json | 50 +- client/src/locales/cs.json | 50 +- client/src/locales/de.json | 50 +- client/src/locales/en.json | 30 +- client/src/locales/es.json | 50 +- client/src/locales/fi.json | 50 +- client/src/locales/fr.json | 50 +- client/src/locales/ja.json | 50 +- client/src/locales/pt-BR.json | 50 +- client/src/locales/ru.json | 176 ++++-- client/src/locales/tr.json | 50 +- client/src/locales/zh-CN.json | 1114 +++++++++++++++++++++++++++++++++ client/src/locales/zh-TW.json | 50 +- 13 files changed, 1720 insertions(+), 100 deletions(-) create mode 100644 client/src/locales/zh-CN.json diff --git a/client/src/locales/ar.json b/client/src/locales/ar.json index 60a2db4a4..d8019d5de 100644 --- a/client/src/locales/ar.json +++ b/client/src/locales/ar.json @@ -376,7 +376,52 @@ "email": "", "role": "", "created": "" - } + }, + "addTeamMember": { + "addMemberMenu": "", + "title": "", + "description": "", + "addButton": "" + }, + "register": "", + "registerToast": { + "success": "", + "dbUserExists": "", + "unknownError": "" + }, + "registerTeamMember": { + "title": "", + "auth": { + "common": { + "inputs": { + "firstName": { + "errors": { + "empty": "", + "pattern": "" + } + }, + "lastName": { + "errors": { + "empty": "", + "pattern": "" + } + }, + "email": { + "errors": { + "empty": "", + "invalid": "" + } + }, + "role": { + "errors": { + "empty": "" + } + } + } + } + } + }, + "role": "" }, "monitorState": { "paused": "", @@ -742,7 +787,8 @@ "failCountHeader": "", "lastRunHeader": "", "lastFinishedAtHeader": "", - "lastRunTookHeader": "" + "lastRunTookHeader": "", + "intervalHeader": "" }, "metricsTable": { "title": "", diff --git a/client/src/locales/cs.json b/client/src/locales/cs.json index 10322c471..71c7e24b9 100644 --- a/client/src/locales/cs.json +++ b/client/src/locales/cs.json @@ -376,7 +376,52 @@ "email": "", "role": "", "created": "" - } + }, + "addTeamMember": { + "addMemberMenu": "", + "title": "", + "description": "", + "addButton": "" + }, + "register": "", + "registerToast": { + "success": "", + "dbUserExists": "", + "unknownError": "" + }, + "registerTeamMember": { + "title": "", + "auth": { + "common": { + "inputs": { + "firstName": { + "errors": { + "empty": "", + "pattern": "" + } + }, + "lastName": { + "errors": { + "empty": "", + "pattern": "" + } + }, + "email": { + "errors": { + "empty": "", + "invalid": "" + } + }, + "role": { + "errors": { + "empty": "" + } + } + } + } + } + }, + "role": "" }, "monitorState": { "paused": "", @@ -742,7 +787,8 @@ "failCountHeader": "", "lastRunHeader": "", "lastFinishedAtHeader": "", - "lastRunTookHeader": "" + "lastRunTookHeader": "", + "intervalHeader": "" }, "metricsTable": { "title": "", diff --git a/client/src/locales/de.json b/client/src/locales/de.json index 115d9733f..ef8df5df4 100644 --- a/client/src/locales/de.json +++ b/client/src/locales/de.json @@ -380,7 +380,52 @@ "email": "Email", "role": "Rolle", "created": "Angelegt" - } + }, + "addTeamMember": { + "addMemberMenu": "", + "title": "", + "description": "", + "addButton": "" + }, + "register": "", + "registerToast": { + "success": "", + "dbUserExists": "", + "unknownError": "" + }, + "registerTeamMember": { + "title": "", + "auth": { + "common": { + "inputs": { + "firstName": { + "errors": { + "empty": "", + "pattern": "" + } + }, + "lastName": { + "errors": { + "empty": "", + "pattern": "" + } + }, + "email": { + "errors": { + "empty": "", + "invalid": "" + } + }, + "role": { + "errors": { + "empty": "" + } + } + } + } + } + }, + "role": "" }, "monitorState": { "paused": "Pausiert", @@ -746,7 +791,8 @@ "failCountHeader": "Anzahl Fehler", "lastRunHeader": "Letzter Lauf um", "lastFinishedAtHeader": "Zuletzt abgeschlossen um", - "lastRunTookHeader": "Letzter Lauf dauerte" + "lastRunTookHeader": "Letzter Lauf dauerte", + "intervalHeader": "" }, "metricsTable": { "title": "Warteschlangenmetriken", diff --git a/client/src/locales/en.json b/client/src/locales/en.json index 1aeb4b6a2..d89ebc44e 100644 --- a/client/src/locales/en.json +++ b/client/src/locales/en.json @@ -360,12 +360,6 @@ "demoUser": "Demo user" }, "teamPanel": { - "addTeamMember": { - "addMemberMenu": "Add Team Member", - "title": "Register new team member", - "description": "Create a new user and share the credentials with them. This method gives the member immediate access to all monitors.", - "addButton": "Add Member" - }, "teamMembers": "Team members", "filter": { "all": "All", @@ -381,6 +375,18 @@ "noMembers": "There are no team members with this role", "getToken": "Get token", "emailToken": "E-mail token", + "table": { + "name": "Name", + "email": "Email", + "role": "Role", + "created": "Created" + }, + "addTeamMember": { + "addMemberMenu": "Add Team Member", + "title": "Register new team member", + "description": "Create a new user and share the credentials with them. This method gives the member immediate access to all monitors.", + "addButton": "Add Member" + }, "register": "Register a team member", "registerToast": { "success": "User created, share credentials with the member securely.", @@ -419,13 +425,7 @@ } } }, - "role": "Role", - "table": { - "name": "Name", - "email": "Email", - "role": "Role", - "created": "Created" - } + "role": "Role" }, "monitorState": { "paused": "Paused", @@ -785,14 +785,14 @@ "idHeader": "Monitor ID", "urlHeader": "URL", "typeHeader": "Type", - "intervalHeader": "Interval", "activeHeader": "Active", "lockedAtHeader": "Locked at", "runCountHeader": "Run count", "failCountHeader": "Fail count", "lastRunHeader": "Last run at", "lastFinishedAtHeader": "Last finished at", - "lastRunTookHeader": "Last run took" + "lastRunTookHeader": "Last run took", + "intervalHeader": "Interval" }, "metricsTable": { "title": "Queue metrics", diff --git a/client/src/locales/es.json b/client/src/locales/es.json index 52fd8c97b..440d40027 100644 --- a/client/src/locales/es.json +++ b/client/src/locales/es.json @@ -376,7 +376,52 @@ "email": "", "role": "", "created": "" - } + }, + "addTeamMember": { + "addMemberMenu": "", + "title": "", + "description": "", + "addButton": "" + }, + "register": "", + "registerToast": { + "success": "", + "dbUserExists": "", + "unknownError": "" + }, + "registerTeamMember": { + "title": "", + "auth": { + "common": { + "inputs": { + "firstName": { + "errors": { + "empty": "", + "pattern": "" + } + }, + "lastName": { + "errors": { + "empty": "", + "pattern": "" + } + }, + "email": { + "errors": { + "empty": "", + "invalid": "" + } + }, + "role": { + "errors": { + "empty": "" + } + } + } + } + } + }, + "role": "" }, "monitorState": { "paused": "", @@ -742,7 +787,8 @@ "failCountHeader": "", "lastRunHeader": "", "lastFinishedAtHeader": "", - "lastRunTookHeader": "" + "lastRunTookHeader": "", + "intervalHeader": "" }, "metricsTable": { "title": "", diff --git a/client/src/locales/fi.json b/client/src/locales/fi.json index d42a9f1f9..d98ece35b 100644 --- a/client/src/locales/fi.json +++ b/client/src/locales/fi.json @@ -376,7 +376,52 @@ "email": "SÃĪhkÃķposti", "role": "Rooli", "created": "Luotu" - } + }, + "addTeamMember": { + "addMemberMenu": "", + "title": "", + "description": "", + "addButton": "" + }, + "register": "", + "registerToast": { + "success": "", + "dbUserExists": "", + "unknownError": "" + }, + "registerTeamMember": { + "title": "", + "auth": { + "common": { + "inputs": { + "firstName": { + "errors": { + "empty": "", + "pattern": "" + } + }, + "lastName": { + "errors": { + "empty": "", + "pattern": "" + } + }, + "email": { + "errors": { + "empty": "", + "invalid": "" + } + }, + "role": { + "errors": { + "empty": "" + } + } + } + } + } + }, + "role": "" }, "monitorState": { "paused": "Tauota", @@ -742,7 +787,8 @@ "failCountHeader": "", "lastRunHeader": "", "lastFinishedAtHeader": "", - "lastRunTookHeader": "" + "lastRunTookHeader": "", + "intervalHeader": "" }, "metricsTable": { "title": "", diff --git a/client/src/locales/fr.json b/client/src/locales/fr.json index d2a5fd19d..bfce12843 100644 --- a/client/src/locales/fr.json +++ b/client/src/locales/fr.json @@ -380,7 +380,52 @@ "email": "Email", "role": "RÃīle", "created": "CrÃĐÃĐe" - } + }, + "addTeamMember": { + "addMemberMenu": "", + "title": "", + "description": "", + "addButton": "" + }, + "register": "", + "registerToast": { + "success": "", + "dbUserExists": "", + "unknownError": "" + }, + "registerTeamMember": { + "title": "", + "auth": { + "common": { + "inputs": { + "firstName": { + "errors": { + "empty": "", + "pattern": "" + } + }, + "lastName": { + "errors": { + "empty": "", + "pattern": "" + } + }, + "email": { + "errors": { + "empty": "", + "invalid": "" + } + }, + "role": { + "errors": { + "empty": "" + } + } + } + } + } + }, + "role": "" }, "monitorState": { "paused": "Pause", @@ -746,7 +791,8 @@ "failCountHeader": "Nombre d'ÃĐchecs", "lastRunHeader": "DerniÃĻre tentative le", "lastFinishedAtHeader": "DerniÃĻre finalisation à", - "lastRunTookHeader": "DerniÃĻre exÃĐcution effectuÃĐe" + "lastRunTookHeader": "DerniÃĻre exÃĐcution effectuÃĐe", + "intervalHeader": "" }, "metricsTable": { "title": "MÃĐtriques des files", diff --git a/client/src/locales/ja.json b/client/src/locales/ja.json index 4c90c485e..6107c19d0 100644 --- a/client/src/locales/ja.json +++ b/client/src/locales/ja.json @@ -380,7 +380,52 @@ "email": "ãƒĄãƒžãƒŦ", "role": "å―đå‰ē", "created": "ä―œæˆæ—Ĩ" - } + }, + "addTeamMember": { + "addMemberMenu": "", + "title": "", + "description": "", + "addButton": "" + }, + "register": "", + "registerToast": { + "success": "", + "dbUserExists": "", + "unknownError": "" + }, + "registerTeamMember": { + "title": "", + "auth": { + "common": { + "inputs": { + "firstName": { + "errors": { + "empty": "", + "pattern": "" + } + }, + "lastName": { + "errors": { + "empty": "", + "pattern": "" + } + }, + "email": { + "errors": { + "empty": "", + "invalid": "" + } + }, + "role": { + "errors": { + "empty": "" + } + } + } + } + } + }, + "role": "" }, "monitorState": { "paused": "ä™‚停æ­Ē", @@ -746,7 +791,8 @@ "failCountHeader": "åĪąæ•—å›žæ•°", "lastRunHeader": "最įĩ‚åŪŸčĄŒæ—Ĩ時", "lastFinishedAtHeader": "最įĩ‚åŪŒäš†æ—Ĩ時", - "lastRunTookHeader": "最įĩ‚åŪŸčĄŒæ™‚é–“" + "lastRunTookHeader": "最įĩ‚åŪŸčĄŒæ™‚é–“", + "intervalHeader": "" }, "metricsTable": { "title": "キãƒĨãƒžãƒĄãƒˆãƒŠãƒƒã‚Ŋ", diff --git a/client/src/locales/pt-BR.json b/client/src/locales/pt-BR.json index 80fd34474..9e1fa6bc0 100644 --- a/client/src/locales/pt-BR.json +++ b/client/src/locales/pt-BR.json @@ -380,7 +380,52 @@ "email": "E-mail", "role": "Cargo", "created": "Criado" - } + }, + "addTeamMember": { + "addMemberMenu": "", + "title": "", + "description": "", + "addButton": "" + }, + "register": "", + "registerToast": { + "success": "", + "dbUserExists": "", + "unknownError": "" + }, + "registerTeamMember": { + "title": "", + "auth": { + "common": { + "inputs": { + "firstName": { + "errors": { + "empty": "", + "pattern": "" + } + }, + "lastName": { + "errors": { + "empty": "", + "pattern": "" + } + }, + "email": { + "errors": { + "empty": "", + "invalid": "" + } + }, + "role": { + "errors": { + "empty": "" + } + } + } + } + } + }, + "role": "" }, "monitorState": { "paused": "Pausado", @@ -746,7 +791,8 @@ "failCountHeader": "Contagem de falhas", "lastRunHeader": "Última corrida em", "lastFinishedAtHeader": "Última conclusÃĢo em", - "lastRunTookHeader": "A Última corrida aconteceu" + "lastRunTookHeader": "A Última corrida aconteceu", + "intervalHeader": "" }, "metricsTable": { "title": "MÃĐtricas de fila", diff --git a/client/src/locales/ru.json b/client/src/locales/ru.json index ecb1dd8f8..aa54de393 100644 --- a/client/src/locales/ru.json +++ b/client/src/locales/ru.json @@ -376,7 +376,52 @@ "email": "Email", "role": "РÐūÐŧҌ", "created": "ÐĄÐūзÐīÐ°Ð―Ðū" - } + }, + "addTeamMember": { + "addMemberMenu": "", + "title": "", + "description": "", + "addButton": "" + }, + "register": "", + "registerToast": { + "success": "", + "dbUserExists": "", + "unknownError": "" + }, + "registerTeamMember": { + "title": "", + "auth": { + "common": { + "inputs": { + "firstName": { + "errors": { + "empty": "", + "pattern": "" + } + }, + "lastName": { + "errors": { + "empty": "", + "pattern": "" + } + }, + "email": { + "errors": { + "empty": "", + "invalid": "" + } + }, + "role": { + "errors": { + "empty": "" + } + } + } + } + } + }, + "role": "" }, "monitorState": { "paused": "ÐžŅŅ‚Ð°Ð―ÐūÐēÐŧÐĩÐ―Ðū", @@ -421,23 +466,23 @@ "passwordRequirements": "НÐūÐēŅ‹Ðđ ÐŋÐ°Ņ€ÐūÐŧҌ ÐīÐūÐŧÐķÐĩÐ― ҁÐūÐīÐĩŅ€ÐķÐ°Ņ‚ŅŒ Ð―Ðĩ ОÐĩÐ―ÐĩÐĩ 8 ҁÐļОÐēÐūÐŧÐūÐē Ðļ ҁÐūÐīÐĩŅ€ÐķÐ°Ņ‚ŅŒ КаК ОÐļÐ―ÐļÐžŅƒÐž ÐūÐīÐ―Ņƒ заÐģÐŧаÐēÐ―ŅƒŅŽ ÐąŅƒÐšÐē҃, ÐūÐīÐ―Ņƒ ҁ҂ҀÐūŅ‡Ð―ŅƒŅŽ ÐąŅƒÐšÐē҃, ÐūÐīÐ―Ņƒ ҆ÐļŅ„Ņ€Ņƒ Ðļ ÐūÐīÐļÐ― ҁÐŋÐĩ҆ÐļаÐŧŅŒÐ―Ņ‹Ðđ ҁÐļОÐēÐūÐŧ.", "saving": "ÐĄÐūŅ…Ņ€Ð°Ð―ÐĩÐ―ÐļÐĩ..." }, - "emailSent": "", - "failedToSendEmail": "", - "settingsTestEmailSuccess": "", - "settingsTestEmailFailed": "", - "settingsTestEmailFailedWithReason": "", + "emailSent": "ПÐļŅŅŒÐžÐū ҃ҁÐŋÐĩŅˆÐ―Ðū ÐūŅ‚ÐŋŅ€Ð°ÐēÐŧÐĩÐ―Ðū", + "failedToSendEmail": "НÐĩ ҃ÐīаÐŧÐūҁҌ ÐūŅ‚ÐŋŅ€Ð°ÐēÐļŅ‚ŅŒ ÐŋÐļŅŅŒÐžÐū", + "settingsTestEmailSuccess": "ÐĒÐĩҁ҂ÐūÐēÐūÐĩ ÐŋÐļŅŅŒÐžÐū ҃ҁÐŋÐĩŅˆÐ―Ðū ÐūŅ‚ÐŋŅ€Ð°ÐēÐŧÐĩÐ―Ðū", + "settingsTestEmailFailed": "НÐĩ ҃ÐīаÐŧÐūҁҌ ÐūŅ‚ÐŋŅ€Ð°ÐēÐļŅ‚ŅŒ Ņ‚Ðĩҁ҂ÐūÐēÐūÐĩ ÐŋÐļŅŅŒÐžÐū", + "settingsTestEmailFailedWithReason": "НÐĩ ҃ÐīаÐŧÐūҁҌ ÐūŅ‚ÐŋŅ€Ð°ÐēÐļŅ‚ŅŒ Ņ‚Ðĩҁ҂ÐūÐēÐūÐĩ ÐŋÐļŅŅŒÐžÐū: {{reason}}", "settingsTestEmailUnknownError": "НÐĩÐļзÐēÐĩŅŅ‚Ð―Ð°Ņ Ðū҈ÐļÐąÐšÐ°", "statusMsg": { - "paused": "", + "paused": "МÐūÐ―ÐļŅ‚ÐūŅ€ÐļÐ―Ðģ ÐŋŅ€ÐļÐūŅŅ‚Ð°Ð―ÐūÐēÐŧÐĩÐ―", "up": "", "down": "", - "pending": "" + "pending": "ОÐķÐļÐīÐ°Ð―ÐļÐĩ..." }, "uptimeGeneralInstructions": { - "http": "", - "ping": "", - "docker": "", - "port": "", + "http": "ВÐēÐĩÐīÐļŅ‚Ðĩ URL-аÐīŅ€Ðĩҁ ÐļÐŧÐļ IP-аÐīŅ€Ðĩҁ ÐīÐŧŅ ОÐūÐ―ÐļŅ‚ÐūŅ€ÐļÐ―Ðģа (Ð―Ð°ÐŋŅ€ÐļОÐĩŅ€, https://example.com/ ÐļÐŧÐļ 192.168.1.100) Ðļ ÐīÐūÐąÐ°ÐēŅŒŅ‚Ðĩ ÐŋÐūÐ―ŅŅ‚Ð―ÐūÐĩ ÐļÐžŅ, КÐūŅ‚ÐūŅ€ÐūÐĩ ÐąŅƒÐīÐĩŅ‚ ÐūŅ‚ÐūÐąŅ€Ð°ÐķÐ°Ņ‚ŅŒŅŅ Ð―Ð° ÐŋÐ°Ð―ÐĩÐŧÐļ ÐļÐ―ŅŅ‚Ņ€ŅƒÐžÐĩÐ―Ņ‚ÐūÐē.", + "ping": "ВÐĩÐīÐļŅ‚Ðĩ IP-аÐīŅ€Ðĩҁ ÐļÐŧÐļ ÐļÐžŅ Ņ…ÐūŅŅ‚Ð° ÐīÐŧŅ ÐŋÐļÐ―Ðģа (Ð―Ð°ÐŋŅ€ÐļОÐĩŅ€, 192.168.1.100 ÐļÐŧÐļ example.com) Ðļ ÐīÐūÐąÐ°ÐēŅŒŅ‚Ðĩ ÐŋÐūÐ―ŅŅ‚Ð―ÐūÐĩ ÐļÐžŅ, КÐūŅ‚ÐūŅ€ÐūÐĩ ÐąŅƒÐīÐĩŅ‚ ÐūŅ‚ÐūÐąŅ€Ð°ÐķÐ°Ņ‚ŅŒŅŅ Ð―Ð° ÐŋÐ°Ð―ÐĩÐŧÐļ ÐļÐ―ŅŅ‚Ņ€ŅƒÐžÐĩÐ―Ņ‚ÐūÐē.", + "docker": "ВÐēÐĩÐīÐļŅ‚Ðĩ Docker ID ÐēÐ°ŅˆÐĩÐģÐū КÐūÐ―Ņ‚ÐĩÐđÐ―ÐĩŅ€Ð°. Docker ID ÐīÐūÐŧÐķÐĩÐ― ÐąŅ‹Ņ‚ŅŒ ÐŋÐūÐŧÐ―Ņ‹Ðž 64-ҁÐļОÐēÐūÐŧŅŒÐ―Ņ‹Ðž Docker ID. Ð’Ņ‹ ОÐūÐķÐĩŅ‚Ðĩ заÐŋŅƒŅŅ‚ÐļŅ‚ŅŒ docker inspect , ҇҂ÐūÐąŅ‹ ÐŋÐūÐŧŅƒŅ‡ÐļŅ‚ŅŒ ÐŋÐūÐŧÐ―Ņ‹Ðđ ID КÐūÐ―Ņ‚ÐĩÐđÐ―ÐĩŅ€Ð°.", + "port": "ВÐēÐĩÐīÐļŅ‚Ðĩ URL-аÐīŅ€Ðĩҁ ÐļÐŧÐļ IP-аÐīŅ€Ðĩҁ ҁÐĩŅ€ÐēÐĩŅ€Ð°, Ð―ÐūОÐĩŅ€ ÐŋÐūŅ€Ņ‚Ð° Ðļ ÐŋÐūÐ―ŅŅ‚Ð―ÐūÐĩ ÐļÐžŅ, КÐūŅ‚ÐūŅ€ÐūÐĩ ÐąŅƒÐīÐĩŅ‚ ÐūŅ‚ÐūÐąŅ€Ð°ÐķÐ°Ņ‚ŅŒŅŅ Ð―Ð° ÐŋÐ°Ð―ÐĩÐŧÐļ ÐļÐ―ŅŅ‚Ņ€ŅƒÐžÐĩÐ―Ņ‚ÐūÐē.", "game": "" }, "common": { @@ -447,24 +492,24 @@ "toggleTheme": "" }, "toasts": { - "networkError": "", - "checkConnection": "", + "networkError": "ÐžŅˆÐļÐąÐšÐ° ҁÐĩŅ‚Ðļ", + "checkConnection": "ПÐūÐķаÐŧ҃ÐđŅŅ‚Ð°, ÐŋŅ€ÐūÐēÐĩŅ€ŅŒŅ‚Ðĩ ҁÐēÐūÐĩ ҁÐĩŅ‚ÐĩÐēÐūÐĩ ҁÐūÐĩÐīÐļÐ―ÐĩÐ―ÐļÐĩ", "unknownError": "НÐĩÐļзÐēÐĩŅŅ‚Ð―Ð°Ņ Ðū҈ÐļÐąÐšÐ°" } }, "auth": { "common": { "navigation": { - "continue": "", + "continue": "ÐŸŅ€ÐūÐīÐūÐŧÐķÐļŅ‚ŅŒ", "back": "НазаÐī" }, "inputs": { "email": { - "label": "", + "label": "ЭÐŧÐĩÐšŅ‚Ņ€ÐūÐ―Ð―Ð°Ņ ÐŋÐūŅ‡Ņ‚Ð°", "placeholder": "", "errors": { "empty": "ДÐŧŅ ÐŋŅ€ÐūÐīÐūÐŧÐķÐĩÐ―ÐļŅ ÐēÐēÐĩÐīÐļŅ‚Ðĩ ҁÐēÐūÐđ аÐīŅ€Ðĩҁ ŅÐŧÐĩÐšŅ‚Ņ€ÐūÐ―Ð―ÐūÐđ ÐŋÐū҇҂ҋ", - "invalid": "" + "invalid": "ПÐūÐķаÐŧ҃ÐđŅŅ‚Ð°, ÐŋŅ€ÐūÐēÐĩŅ€ŅŒŅ‚Ðĩ ÐŋŅ€Ð°ÐēÐļÐŧŅŒÐ―ÐūŅŅ‚ŅŒ ÐēÐēÐĩÐīŅ‘Ð―Ð―ÐūÐģÐū аÐīŅ€ÐĩŅÐ° ŅÐŧÐĩÐšŅ‚Ņ€ÐūÐ―Ð―ÐūÐđ ÐŋÐū҇҂ҋ" } }, "password": { @@ -491,49 +536,49 @@ "highlighted": "ÐūÐīÐ―Ņƒ ÐŋŅ€ÐūÐŋÐļŅÐ―ŅƒŅŽ ÐąŅƒÐšÐē҃" }, "match": { - "beginning": "", - "highlighted": "" + "beginning": "ÐŸÐ°Ņ€ÐūÐŧÐļ", + "highlighted": "ÐīÐūÐŧÐķÐ―Ņ‹ ҁÐūÐēÐŋаÐīÐ°Ņ‚ŅŒ" } }, "errors": { - "empty": "", - "length": "", - "uppercase": "", - "lowercase": "", - "number": "", - "special": "", - "incorrect": "" + "empty": "ПÐūÐķаÐŧ҃ÐđŅŅ‚Ð°, ÐēÐēÐĩÐīÐļŅ‚Ðĩ ÐŋÐ°Ņ€ÐūÐŧҌ", + "length": "ÐŸÐ°Ņ€ÐūÐŧҌ ÐīÐūÐŧÐķÐĩÐ― ÐąŅ‹Ņ‚ŅŒ ÐīÐŧÐļÐ―ÐūÐđ Ð―Ðĩ ОÐĩÐ―ÐĩÐĩ 8 ҁÐļОÐēÐūÐŧÐūÐē", + "uppercase": "ÐŸÐ°Ņ€ÐūÐŧҌ ÐīÐūÐŧÐķÐĩÐ― ҁÐūÐīÐĩŅ€ÐķÐ°Ņ‚ŅŒ ОÐļÐ―ÐļÐžŅƒÐž 1 ÐŋŅ€ÐūÐŋÐļŅÐ―ŅƒŅŽ ÐąŅƒÐšÐē҃", + "lowercase": "ÐŸÐ°Ņ€ÐūÐŧҌ ÐīÐūÐŧÐķÐĩÐ― ҁÐūÐīÐĩŅ€ÐķÐ°Ņ‚ŅŒ ОÐļÐ―ÐļÐžŅƒÐž 1 ҁ҂ҀÐūŅ‡Ð―ŅƒŅŽ ÐąŅƒÐšÐē҃", + "number": "ÐŸÐ°Ņ€ÐūÐŧҌ ÐīÐūÐŧÐķÐĩÐ― ҁÐūÐīÐĩŅ€ÐķÐ°Ņ‚ŅŒ ОÐļÐ―ÐļÐžŅƒÐž 1 ҆ÐļŅ„Ņ€Ņƒ", + "special": "ÐŸÐ°Ņ€ÐūÐŧҌ ÐīÐūÐŧÐķÐĩÐ― ҁÐūÐīÐĩŅ€ÐķÐ°Ņ‚ŅŒ ОÐļÐ―ÐļÐžŅƒÐž 1 ҁÐŋÐĩ҆ҁÐļОÐēÐūÐŧ", + "incorrect": "ÐŸŅ€ÐĩÐīÐūŅŅ‚Ð°ÐēÐŧÐĩÐ―Ð―Ņ‹Ðđ ÐēаОÐļ ÐŋÐ°Ņ€ÐūÐŧҌ Ð―Ðĩ ҁÐūÐūŅ‚ÐēÐĩ҂ҁ҂Ðē҃ÐĩŅ‚ Ð―Ð°ŅˆÐļО заÐŋÐļŅŅÐž" } }, "passwordConfirm": { - "label": "", - "placeholder": "", + "label": "ПÐūÐīŅ‚ÐēÐĩŅ€ÐīÐļŅ‚Ðĩ ÐŋÐ°Ņ€ÐūÐŧҌ", + "placeholder": "ВÐēÐĩÐīÐļŅ‚Ðĩ ÐŋÐ°Ņ€ÐūÐŧҌ Ðĩ҉Ðĩ Ņ€Ð°Ð· ÐīÐŧŅ ÐŋÐūÐīŅ‚ÐēÐĩŅ€ÐķÐīÐĩÐ―ÐļŅ", "errors": { - "empty": "", - "different": "" + "empty": "ПÐūÐķаÐŧ҃ÐđŅŅ‚Ð°, ÐēÐēÐĩÐīÐļŅ‚Ðĩ ҁÐēÐūÐđ ÐŋÐ°Ņ€ÐūÐŧҌ Ðĩ҉Ðĩ Ņ€Ð°Ð· ÐīÐŧŅ ÐŋÐūÐīŅ‚ÐēÐĩŅ€ÐķÐīÐĩÐ―ÐļŅ (ÐŋÐūОÐūÐģаÐĩŅ‚ ÐļÐ·ÐąÐĩÐķÐ°Ņ‚ŅŒ ÐūÐŋÐĩŅ‡Ð°Ņ‚ÐūК)", + "different": "ВÐēÐĩÐīŅ‘Ð―Ð―Ņ‹Ðĩ ÐŋÐ°Ņ€ÐūÐŧÐļ Ð―Ðĩ ҁÐūÐēÐŋаÐīÐ°ŅŽŅ‚, ÐēÐĩŅ€ÐūŅŅ‚Ð―Ðū, ÐūÐīÐļÐ― Ðļз Ð―ÐļŅ… Ð―Ð°ÐŋÐĩŅ‡Ð°Ņ‚Ð°Ð― ҁ Ðū҈ÐļÐąÐšÐūÐđ" } }, "firstName": { - "label": "", + "label": "Ð˜ÐžŅ", "placeholder": "", "errors": { - "empty": "", - "length": "", - "pattern": "" + "empty": "ПÐūÐķаÐŧ҃ÐđŅŅ‚Ð°, ÐēÐēÐĩÐīÐļŅ‚Ðĩ ҁÐēÐūÐĩ ÐļÐžŅ", + "length": "Ð˜ÐžŅ ÐīÐūÐŧÐķÐ―Ðū ÐąŅ‹Ņ‚ŅŒ КÐūŅ€Ðū҇Ðĩ 50 ҁÐļОÐēÐūÐŧÐūÐē", + "pattern": "Ð˜ÐžŅ ÐīÐūÐŧÐķÐ―Ðū ҁÐūÐīÐĩŅ€ÐķÐ°Ņ‚ŅŒ Ņ‚ÐūÐŧŅŒÐšÐū ÐąŅƒÐšÐēŅ‹, ÐŋŅ€ÐūÐąÐĩÐŧŅ‹, аÐŋÐūҁ҂ҀÐū҄ҋ ÐļÐŧÐļ ÐīÐĩŅ„Ðļҁҋ" } }, "lastName": { - "label": "", + "label": "ÐĪаОÐļÐŧÐļŅ", "placeholder": "", "errors": { - "empty": "", - "length": "", - "pattern": "" + "empty": "ПÐūÐķаÐŧ҃ÐđŅŅ‚Ð°, ÐēÐēÐĩÐīÐļŅ‚Ðĩ ҁÐēÐūŅŽ Ņ„Ð°ÐžÐļÐŧÐļŅŽ", + "length": "ÐĪаОÐļÐŧÐļŅ ÐīÐūÐŧÐķÐ―Ð° ÐąŅ‹Ņ‚ŅŒ КÐūŅ€Ðū҇Ðĩ 50 ҁÐļОÐēÐūÐŧÐūÐē", + "pattern": "ÐĪаОÐļÐŧÐļŅ ÐīÐūÐŧÐķÐ―Ð° ҁÐūÐīÐĩŅ€ÐķÐ°Ņ‚ŅŒ Ņ‚ÐūÐŧŅŒÐšÐū ÐąŅƒÐšÐēŅ‹, ÐŋŅ€ÐūÐąÐĩÐŧŅ‹, аÐŋÐūҁ҂ҀÐū҄ҋ ÐļÐŧÐļ ÐīÐĩŅ„Ðļҁҋ" } } }, "errors": { - "validation": "" + "validation": "ÐžŅˆÐļÐąÐšÐ° ÐŋŅ€Ðļ ÐŋŅ€ÐūÐēÐĩŅ€ÐšÐĩ ÐīÐ°Ð―Ð―Ņ‹Ņ…." }, "fields": { "password": { @@ -549,20 +594,20 @@ } }, "login": { - "heading": "", + "heading": "ВÐūÐđÐīÐļŅ‚Ðĩ, ҇҂ÐūÐąŅ‹ ÐŋŅ€ÐūÐīÐūÐŧÐķÐļŅ‚ŅŒ", "subheadings": { - "stepOne": "", - "stepTwo": "" + "stepOne": "ВÐēÐĩÐīÐļŅ‚Ðĩ ҁÐēÐūŅŽ ŅÐŧÐĩÐšŅ‚Ņ€ÐūÐ―Ð―ŅƒŅŽ ÐŋÐūŅ‡Ņ‚Ņƒ", + "stepTwo": "ВÐēÐĩÐīÐļŅ‚Ðĩ ҁÐēÐūÐđ ÐŋÐ°Ņ€ÐūÐŧҌ" }, "links": { - "forgotPassword": "", - "register": "", + "forgotPassword": "Ð—Ð°ÐąŅ‹ÐŧÐļ ÐŋÐ°Ņ€ÐūÐŧҌ?", + "register": "ÐĢ ÐēÐ°Ņ Ð―ÐĩŅ‚ ŅƒŅ‡ÐĩŅ‚Ð―ÐūÐđ заÐŋÐļҁÐļ?", "forgotPasswordLink": "", "registerLink": "" }, "toasts": { - "success": "", - "incorrectPassword": "" + "success": "ÐĄ ÐēÐūзÐēŅ€Ð°Ņ‰ÐĩÐ―ÐļÐĩО! Ð’Ņ‹ ҃ҁÐŋÐĩŅˆÐ―Ðū ÐēÐū҈ÐŧÐļ Ðē ҁÐļҁ҂ÐĩÐžŅƒ.", + "incorrectPassword": "НÐĩÐēÐĩŅ€Ð―Ņ‹Ðđ ÐŋÐ°Ņ€ÐūÐŧҌ" }, "errors": { "password": { @@ -573,42 +618,42 @@ }, "registration": { "heading": { - "superAdmin": "", - "user": "" + "superAdmin": "ÐĄÐūзÐīÐ°Ņ‚ŅŒ ҁ҃ÐŋÐĩŅ€-аÐīОÐļÐ―ÐļŅŅ‚Ņ€Ð°Ņ‚ÐūŅ€Ð°", + "user": "Ð—Ð°Ņ€ÐĩÐģÐļҁ҂ҀÐļŅ€ÐūÐēÐ°Ņ‚ŅŒŅŅ" }, "subheadings": { - "stepOne": "", - "stepTwo": "", - "stepThree": "" + "stepOne": "ВÐēÐĩÐīÐļŅ‚Ðĩ ҁÐēÐūÐļ ÐŧÐļŅ‡Ð―Ņ‹Ðĩ ÐīÐ°Ð―Ð―Ņ‹Ðĩ", + "stepTwo": "ВÐēÐĩÐīÐļŅ‚Ðĩ ÐēÐ°ŅˆŅƒ ŅÐŧÐĩÐšŅ‚Ņ€ÐūÐ―Ð―ŅƒŅŽ ÐŋÐūŅ‡Ņ‚Ņƒ", + "stepThree": "ÐĄÐūзÐīаÐđŅ‚Ðĩ ҁÐēÐūÐđ ÐŋÐ°Ņ€ÐūÐŧҌ" }, "description": { - "superAdmin": "", - "user": "" + "superAdmin": "ÐĄÐūзÐīаÐđŅ‚Ðĩ ҁÐēÐūÐđ Ð°ÐšÐšÐ°ŅƒÐ―Ņ‚ ҁ҃ÐŋÐĩŅ€-аÐīОÐļÐ―ÐļŅŅ‚Ņ€Ð°Ņ‚ÐūŅ€Ð°, ҇҂ÐūÐąŅ‹ Ð―Ð°Ņ‡Ð°Ņ‚ŅŒ", + "user": "Ð—Ð°Ņ€ÐĩÐģÐļҁ҂ҀÐļŅ€ŅƒÐđŅ‚ÐĩҁҌ КаК ÐŋÐūÐŧŅŒÐ·ÐūÐēÐ°Ņ‚ÐĩÐŧҌ Ðļ ÐūÐąŅ€Ð°Ņ‚ÐļŅ‚ÐĩҁҌ К ҁ҃ÐŋÐĩŅ€-аÐīОÐļÐ―ÐļŅŅ‚Ņ€Ð°Ņ‚ÐūŅ€Ņƒ ÐīÐŧŅ ÐīÐūŅŅ‚ŅƒÐŋа К ÐēÐ°ŅˆÐļО ОÐūÐ―ÐļŅ‚ÐūŅ€Ð°Ðž" }, "gettingStartedButton": { - "superAdmin": "", - "user": "" + "superAdmin": "ÐĄÐūзÐīÐ°Ņ‚ŅŒ ŅƒŅ‡ÐĩŅ‚Ð―ŅƒŅŽ заÐŋÐļҁҌ ҁ҃ÐŋÐĩŅ€-аÐīОÐļÐ―ÐļŅŅ‚Ņ€Ð°Ņ‚ÐūŅ€Ð°", + "user": "Ð—Ð°Ņ€ÐĩÐģÐļҁ҂ҀÐļŅ€ÐūÐēÐ°Ņ‚ŅŒŅŅ КаК ÐūÐąŅ‹Ņ‡Ð―Ņ‹Ðđ ÐŋÐūÐŧŅŒÐ·ÐūÐēÐ°Ņ‚ÐĩÐŧҌ" }, - "termsAndPolicies": "", + "termsAndPolicies": "ÐĄÐūзÐīаÐēÐ°Ņ ŅƒŅ‡ÐĩŅ‚Ð―ŅƒŅŽ заÐŋÐļҁҌ, ÐēŅ‹ ҁÐūÐģÐŧÐ°ŅˆÐ°ÐĩŅ‚ÐĩҁҌ ҁ Ð―Ð°ŅˆÐļОÐļ ÐĢҁÐŧÐūÐēÐļŅÐžÐļ ÐŋŅ€ÐĩÐīÐūŅŅ‚Ð°ÐēÐŧÐĩÐ―ÐļŅ ҃ҁÐŧ҃Ðģ Ðļ ПÐūÐŧÐļŅ‚ÐļКÐūÐđ КÐūÐ―Ņ„ÐļÐīÐĩÐ―Ņ†ÐļаÐŧŅŒÐ―Ðūҁ҂Ðļ.", "links": { - "login": "" + "login": "ÐĢÐķÐĩ ÐĩŅŅ‚ŅŒ ŅƒŅ‡ÐĩŅ‚Ð―Ð°Ņ заÐŋÐļҁҌ? ВÐūÐđŅ‚Ðļ" }, "toasts": { - "success": "" + "success": "ДÐūÐąŅ€Ðū ÐŋÐūÐķаÐŧÐūÐēÐ°Ņ‚ŅŒ! Ð’Ð°ŅˆÐ° ŅƒŅ‡ÐĩŅ‚Ð―Ð°Ņ заÐŋÐļҁҌ ÐąŅ‹Ðŧа ҁÐūзÐīÐ°Ð―Ð° ҃ҁÐŋÐĩŅˆÐ―Ðū." }, "welcome": "" }, "forgotPassword": { - "heading": "", + "heading": "Ð—Ð°ÐąŅ‹ÐŧÐļ ÐŋÐ°Ņ€ÐūÐŧҌ?", "subheadings": { - "stepOne": "", - "stepTwo": "", - "stepThree": "", - "stepFour": "" + "stepOne": "НÐĩ ÐēÐūÐŧÐ―ŅƒÐđŅ‚ÐĩҁҌ, ÐžŅ‹ ÐēŅ‹ŅˆÐŧÐĩО ÐēаО ÐļÐ―ŅŅ‚Ņ€ŅƒÐšŅ†ÐļÐļ ÐīÐŧŅ ŅÐąŅ€ÐūŅÐ°.", + "stepTwo": "ÐœŅ‹ ÐēҋҁÐŧаÐŧÐļ ҁҁҋÐŧÐšŅƒ ÐīÐŧŅ ŅÐąŅ€ÐūŅÐ° ÐŋÐ°Ņ€ÐūÐŧŅ Ð―Ð° ", + "stepThree": "Ð’Ð°Ņˆ Ð―ÐūÐēŅ‹Ðđ ÐŋÐ°Ņ€ÐūÐŧҌ ÐīÐūÐŧÐķÐĩÐ― ÐūŅ‚ÐŧÐļŅ‡Ð°Ņ‚ŅŒŅŅ ÐūŅ‚ ÐēÐ°ŅˆÐļŅ… ÐŋŅ€ÐĩÐīŅ‹ÐīŅƒŅ‰ÐļŅ… ÐŋÐ°Ņ€ÐūÐĩÐđ.", + "stepFour": "Ð’Ð°Ņˆ ÐŋÐ°Ņ€ÐūÐŧҌ ÐąŅ‹Ðŧ ҃ҁÐŋÐĩŅˆÐ―Ðū ŅÐąŅ€Ðū҈ÐĩÐ―. НаÐķОÐļŅ‚Ðĩ Ð―ÐļÐķÐĩ, ҇҂ÐūÐąŅ‹ ÐēÐūÐŧ҈ÐĩÐąÐ―Ņ‹Ðž ÐūÐąŅ€Ð°Ð·ÐūО ÐēÐūÐđŅ‚Ðļ Ðē ҁÐļҁ҂ÐĩÐžŅƒ." }, "buttons": { "openEmail": "", - "resetPassword": "" + "resetPassword": "ÐĄÐąŅ€ÐūҁÐļŅ‚ŅŒ ÐŋÐ°Ņ€ÐūÐŧҌ" }, "imageAlts": { "passwordKey": "", @@ -742,7 +787,8 @@ "failCountHeader": "", "lastRunHeader": "", "lastFinishedAtHeader": "", - "lastRunTookHeader": "" + "lastRunTookHeader": "", + "intervalHeader": "" }, "metricsTable": { "title": "", diff --git a/client/src/locales/tr.json b/client/src/locales/tr.json index 8271da521..0ab45ebe9 100644 --- a/client/src/locales/tr.json +++ b/client/src/locales/tr.json @@ -376,7 +376,52 @@ "email": "Eposta", "role": "Rol", "created": "Oluşturulma" - } + }, + "addTeamMember": { + "addMemberMenu": "", + "title": "", + "description": "", + "addButton": "" + }, + "register": "", + "registerToast": { + "success": "", + "dbUserExists": "", + "unknownError": "" + }, + "registerTeamMember": { + "title": "", + "auth": { + "common": { + "inputs": { + "firstName": { + "errors": { + "empty": "", + "pattern": "" + } + }, + "lastName": { + "errors": { + "empty": "", + "pattern": "" + } + }, + "email": { + "errors": { + "empty": "", + "invalid": "" + } + }, + "role": { + "errors": { + "empty": "" + } + } + } + } + } + }, + "role": "" }, "monitorState": { "paused": "Durduruldu", @@ -742,7 +787,8 @@ "failCountHeader": "", "lastRunHeader": "", "lastFinishedAtHeader": "Son tamamlama", - "lastRunTookHeader": "" + "lastRunTookHeader": "", + "intervalHeader": "" }, "metricsTable": { "title": "", diff --git a/client/src/locales/zh-CN.json b/client/src/locales/zh-CN.json new file mode 100644 index 000000000..c2d1aa6fc --- /dev/null +++ b/client/src/locales/zh-CN.json @@ -0,0 +1,1114 @@ +{ + "submit": "提äšĪ", + "title": "标éǘ", + "distributedStatusHeaderText": "åŪžæ—ķã€įœŸåŪžčŪūå·č͆ᛖ", + "distributedStatusSubHeaderText": "åœĻå…Ļįƒæ•°į™ūäļ‡čŪūåĪ‡įš„æ”Ŋ持äļ‹ïžŒæŒ‰å…ĻįƒåŒšåŸŸã€å›―åŪķæˆ–城åŸĨįœ‹įģŧįŧŸæ€§čƒ―", + "settingsDisabled": "į́į”Ļ", + "settingsSuccessSaved": "čŪūį―Ūäŋå­˜æˆåŠŸ", + "settingsFailedToSave": "äŋå­˜čŪūį―ŪåĪąčīĨ", + "settingsStatsCleared": "įŧŸčŪĄæ•°æŪå·ē成功æļ…é™Ī", + "settingsFailedToClearStats": "æļ…é™ĪįŧŸčŪĄæ•°æŪåĪąčīĨ", + "settingsMonitorsDeleted": "å·ē成功删é™Īæ‰€æœ‰į›‘č§†å™Ļ", + "settingsFailedToDeleteMonitors": "删é™Īæ‰€æœ‰į›‘č§†å™ĻåĪąčīĨ", + "starPromptTitle": "打星Checkmate", + "starPromptDescription": "åœĻ GitHub äŸĨįœ‹æœ€æ–°į‰ˆæœŽïžŒåđķåļŪåŠĐįĪūåŒšå‘åą•åĢŪåΧ", + "https": "HTTPS", + "http": "HTTP", + "monitor": "į›‘æŽ§", + "aboutus": "å…ģ䚎我äŧŽ", + "now": "įŽ°åœĻ", + "delete": "删é™Ī", + "configure": "配į―Ū", + "responseTime": "响嚔æ—ķé—ī", + "ms": "æŊŦį§’", + "bar": "æĄ", + "area": "匚域", + "country": "å›―åŪķ", + "city": "城åļ‚", + "response": "响嚔", + "monitorStatusUp": "į›‘æŽ§į›Ū标 {name} ({url}) įŽ°åœĻåĪ„äšŽæ­ĢåļļįŠķ态åđķæ­ĢåœĻ响嚔", + "monitorStatusDown": "į›‘æŽ§į›Ū标 {name} ({url}) å·ēäļ‹įšŋä— å“åš”", + "webhookSendSuccess": "Webhook通įŸĨ发送成功", + "webhookSendError": "向 {platform} 发送 Webhook 通įŸĨæ—ķ凚错", + "webhookUnsupportedPlatform": "äļæ”ŊæŒįš„åđģ台{platform}", + "distributedRightCategoryTitle": "į›‘æŽ§", + "distributedStatusServerMonitors": "æœåŠĄå™Ļį›‘æŽ§", + "distributedStatusServerMonitorsDescription": "į›‘æŽ§į›ļå…ģæœåŠĄå™Ļįš„įŠķ态", + "distributedUptimeCreateSelectURL": "åœĻčŋ™é‡ŒïžŒæ‚ĻåŊäŧĨ选æ‹Đäļŧæœšįš„URLäŧĨåŠį›‘æŽ§įąŧ型。", + "distributedUptimeCreateChecks": "分åļƒåžæ­ĢåļļčŋčĄŒæ—ķé—ī创åŧšæĢ€æŸĨ", + "distributedUptimeCreateChecksDescription": "åœĻæ·ŧ加įŦ™į‚đäđ‹åŽïžŒæ‚Ļ随æ—ķåŊäŧĨåĒžåŠ æˆ–åˆ é™ĪæĢ€æŸĨéĄđ。", + "distributedUptimeCreateIncidentNotification": "之äŧķ通įŸĨ", + "distributedUptimeCreateIncidentDescription": "å―“å‘į”Ÿäš‹äŧķæ—ķ通įŸĨį”Ļ户。", + "distributedUptimeCreateAdvancedSettings": "éŦ˜įš§čŪūį―Ū", + "distributedUptimeDetailsNoMonitorHistory": "æ­Īį›‘æŽ§å™Ļ尚朊有æĢ€æŸĨ历åēčŪ°å―•ã€‚", + "distributedUptimeDetailsStatusHeaderUptime": "čŋčĄŒæ—ķé—īïžš", + "distributedUptimeDetailsStatusHeaderLastUpdate": "最后æ›ī新䚎", + "notifications": { + "enableNotifications": "åŊį”Ļ {{platform}} 通įŸĨ", + "testNotification": "æĩ‹čŊ•通įŸĨ", + "addOrEditNotifications": "æ·ŧ加或äŋŪæ”đ通įŸĨæ–đ垏", + "slack": { + "label": "Slack", + "description": "č́åŊį”Ļ Slack 通įŸĨčŊ·å…ˆåˆ›åŧšäļ€äļŠ Slack åš”į”ĻåđķåŊį”Ļäž å…Ĩįš„ Webhook。äđ‹åŽïžŒåŠéœ€åœĻæ­ĪåĪ„æäū› Webhook URL åģåŊ。", + "webhookLabel": "Webhook į―‘å€", + "webhookPlaceholder": "https://hooks.slack.com/services/...", + "webhookRequired": "需č́ Slack Webhook URL" + }, + "discord": { + "label": "Discord", + "description": "čĶé€ščŋ‡ Discord 通įŸĨ将数æŪäŧŽ Checkmate 发送到 Discord éĒ‘é“ïžŒåŊäŧĨä―ŋį”Ļ Discord įš„å…ĨįŦ™ WebHooks åŠŸčƒ―ã€‚", + "webhookLabel": "Discord æŽĨæ”ķį―‘å€", + "webhookPlaceholder": "https://discord.com/api/webhooks/...", + "webhookRequired": "需č́ Discord Webhook URL" + }, + "telegram": { + "label": "Telegram", + "description": "č́åŊį”Ļ Telegram 通įŸĨčŊ·ä―ŋį”Ļ BotFather创åŧšäļ€äļŠ Telegram 朚å™Ļ䚚。į„ķåŽïžŒčŽ·å– API äŧĪį‰Œå’ŒčŠåĪĐ ID åđķåœĻæ­ĪåĪ„åĄŦ写。", + "tokenLabel": "ä― įš„æœšå™Ļäšštoken", + "tokenPlaceholder": "123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11", + "chatIdLabel": "ä― įš„Chat ID", + "chatIdPlaceholder": "-1001234567890", + "fieldsRequired": "需č́ Telegram äŧĪį‰Œå’ŒčŠåĪĐ ID" + }, + "webhook": { + "label": "Webhooks", + "description": "ä― åŊäŧĨčŪūį―Ū臩åۚäđ‰įš„ WebhookäŧĨäūŋåœĻ之äŧķå‘į”Ÿæ—ķæŽĨæ”ķ通įŸĨ。", + "urlLabel": "Webhook URL", + "urlPlaceholder": "https://your-server.com/webhook", + "urlRequired": "Webhook URL æ˜Ŋåŋ…åĄŦéĄđ" + }, + "testNotificationDevelop": "æĩ‹čŊ•通įŸĨ 2", + "integrationButton": "通įŸĨ集成", + "testSuccess": "æĩ‹čŊ•通įŸĨ发送成功", + "testFailed": "æĩ‹čŊ•通įŸĨ发送åĪąčīĨ", + "unsupportedType": "äļæ”ŊæŒįš„é€šįŸĨįąŧ型", + "networkError": "å‘į”Ÿį―‘įŧœé”™čŊŊ", + "fallback": { + "title": "通įŸĨ通道į”Ļ䚎", + "checks": [ + "提醒å›Ē队有å…ģ停朚æ—ķé—īæˆ–æ€§čƒ―é—Ūéǘ", + "čŪĐå·ĨįĻ‹åļˆįŸĨ道之äŧķå‘į”Ÿæ—ķ", + "čŪĐįŪĄį†å‘˜äš†č§ĢįģŧįŧŸæ›īæ”đ" + ], + "actionButton": "čŪĐæˆ‘äŧŽåˆ›åŧšæ‚Ļįš„įŽŽäļ€äļŠé€šįŸĨæļ é“" + }, + "createButton": "创åŧšé€šįŸĨæļ é“", + "createTitle": "通įŸĨæļ é“", + "create": { + "success": "通įŸĨ创åŧ𿈐功", + "failed": "通įŸĨ创åŧšåĪąčīĨ" + }, + "fetch": { + "success": "æˆåŠŸčŽ·å–é€šįŸĨ", + "failed": "无æģ•čŽ·å–é€šįŸĨ" + }, + "delete": { + "success": "通įŸĨå·ē成功删é™Ī", + "failed": "删é™Ī通įŸĨåĪąčīĨ" + }, + "edit": { + "success": "通įŸĨå·ē成功æ›ī新", + "failed": "无æģ•æ›ī新通įŸĨ" + }, + "test": { + "success": "æĩ‹čŊ•通įŸĨ发送成功", + "failed": "æĩ‹čŊ•通įŸĨ发送åĪąčīĨ" + } + }, + "testLocale": "æĩ‹čŊ•匚域čŪūį―Ū", + "add": "æ·ŧ加", + "monitors": "ᛑ视å™Ļ", + "distributedUptimeStatusCreateStatusPage": "įŠķ态éĄĩ", + "distributedUptimeStatusCreateStatusPageAccess": "čŪŋé—Ū", + "distributedUptimeStatusCreateStatusPageReady": "åĶ‚æžœįŠķ态éĄĩéĒ准åĪ‡å°ąįŧŠïžŒæ‚ĻåŊäŧĨ标čŪ°äļšå·ē发åļƒã€‚", + "distributedUptimeStatusBasicInfoHeader": "åŸšįĄ€äŋĄæŊ", + "distributedUptimeStatusBasicInfoDescription": "åۚäđ‰å…Žåļåį§°å’ŒįŠķ态éĄĩæŒ‡å‘įš„å­åŸŸåã€‚", + "distributedUptimeStatusLogoHeader": "Logo", + "distributedUptimeStatusLogoDescription": "äļšæ‚Ļįš„įŠķ态éĄĩéĒäļŠäž äļ€äļŠ Logo", + "distributedUptimeStatusLogoUploadButton": "äļŠäž  Logo", + "distributedUptimeStatusStandardMonitorsHeader": "æ ‡å‡†į›‘č§†å™Ļ", + "distributedUptimeStatusStandardMonitorsDescription": "å°†æ ‡å‡†į›‘č§†å™Ļ附加到æ‚Ļįš„įŠķ态éĄĩéĒ。", + "distributedUptimeStatusCreateYour": "创åŧšæ‚Ļįš„", + "distributedUptimeStatusEditYour": "įž–čū‘æ‚Ļįš„", + "distributedUptimeStatusPublishedLabel": "发åļƒåđķ兎垀åŊ见", + "distributedUptimeStatusCompanyNameLabel": "å…Žåļåį§°", + "distributedUptimeStatusPageAddressLabel": "æ‚Ļįš„įŠķ态éĄĩéĒ地址", + "distributedUptimeStatus30Days": "30 åĪĐ", + "distributedUptimeStatus60Days": "60 åĪĐ", + "distributedUptimeStatus90Days": "90 åĪĐ", + "distributedUptimeStatusPageNotSetUp": "朊čŪūį―ŪįŠķ态éĄĩéĒ。", + "distributedUptimeStatusContactAdmin": "čŊ·č”įģŧæ‚Ļįš„įŪĄį†å‘˜", + "distributedUptimeStatusPageNotPublic": "æ­ĪįŠķ态éĄĩéĒäļå…Žåž€ã€‚", + "distributedUptimeStatusPageDeleteDialog": "ä― æƒģ删é™Īčŋ™äļŠįŠķ态éĄĩ吗", + "distributedUptimeStatusPageDeleteConfirm": "įĄŪåŪšïžŒåˆ é™ĪįŠķ态éĄĩ", + "distributedUptimeStatusPageDeleteDescription": "删é™Ī后将无æģ•æĒåĪæ‚Ļįš„įŠķ态éĄĩéĒ。", + "distributedUptimeStatusDevices": "čŪūå·", + "distributedUptimeStatusUpt": "UPT", + "distributedUptimeStatusUptBurned": "分åļƒåžæ­ĢåļļčŋčĄŒæ—ķé—īįŠķ态čŋčĄŒå·ēåŪŒæˆ", + "distributedUptimeStatusUptLogo": "分åļƒåžUptimeStatusUptLogo", + "incidentsTableNoIncidents": "æ— äš‹äŧķčŪ°å―•", + "incidentsTablePaginationLabel": "之äŧķ", + "incidentsTableMonitorName": "į›‘æŽ§åį§°", + "incidentsTableStatus": "įŠķ态", + "incidentsTableDateTime": "æ—Ĩ期&æ—ķé—ī", + "incidentsTableStatusCode": "įŠķæ€į ", + "incidentsTableMessage": "äŋĄæŊ", + "incidentsOptionsHeader": "之äŧķ:", + "incidentsOptionsHeaderFilterBy": "᭛选:", + "incidentsOptionsHeaderFilterAll": "å…ĻéƒĻ", + "incidentsOptionsHeaderFilterDown": "įĶŧįšŋ", + "incidentsOptionsHeaderFilterCannotResolve": "无æģ•č§Ģ析", + "incidentsOptionsHeaderShow": "æ˜ūįĪšïžš", + "incidentsOptionsHeaderLastHour": "čŋ‡åŽŧäļ€å°æ—ķ", + "incidentsOptionsHeaderLastDay": "čŋ‡åŽŧäļ€åĪĐ", + "incidentsOptionsHeaderLastWeek": "čŋ‡åŽŧäļ€å‘Ļ", + "incidentsOptionsPlaceholderAllServers": "æ‰€æœ‰æœåŠĄå™Ļ", + "infrastructureCreateYour": "创åŧšä― įš„", + "infrastructureCreateGeneralSettingsDescription": "åœĻčŋ™é‡Œæ‚ĻåŊäŧĨ选æ‹Đäļŧæœšįš„ URLäŧĨ及čŋžæŽĨåˆ°æœåŠĄå™ĻäŧĢᐆæ—ķä―ŋį”Ļįš„å‹åĨ―åį§°å’ŒæŽˆæƒåŊ†é’Ĩ。", + "infrastructureServerRequirement": "ä― æ­ĢåœĻį›‘æŽ§įš„æœåŠĄå™Ļåŋ…éĄŧčŋčĄŒåœĻ", + "infrastructureCustomizeAlerts": "臩åۚäđ‰å‘Šč­Ķ", + "infrastructureAlertNotificationDescription": "å―“é˜ˆå€žčķ…čŋ‡æŒ‡åۚį™ū分æŊ”æ—ķ向į”Ļ户(äŧŽ)发送通įŸĨ。", + "infrastructureCreateMonitor": "创åŧšåŸšįĄ€čŪūæ–―į›‘æŽ§", + "infrastructureProtocol": "协čŪŪ", + "infrastructureServerUrlLabel": "æœåŠĄå™Ļ URL", + "infrastructureDisplayNameLabel": "æ˜ūįĪšåį§°", + "infrastructureAuthorizationSecretLabel": "授权åŊ†é’Ĩ", + "gb": "GB", + "mb": "MB", + "mem": "内存", + "memoryUsage": "内存ä―ŋį”ĻįŽ‡", + "cpu": "CPU", + "cpuUsage": "CPU ä―ŋį”ĻįŽ‡", + "cpuTemperature": "CPU æļĐåšĶ", + "diskUsage": "įĄŽį›˜ä―ŋį”ĻįŽ‡", + "used": "å·ēä―ŋį”Ļ", + "total": "æ€ŧå…ą", + "cores": "æ ļåŋƒ", + "frequency": "éĒ‘įŽ‡", + "status": "įŠķ态", + "cpuPhysical": "CPUį‰Đᐆæ ļåŋƒïž‰", + "cpuLogical": "CPUé€ŧčū‘æ ļåŋƒïž‰", + "cpuFrequency": "CPU éĒ‘įŽ‡", + "avgCpuTemperature": "CPU åđģ均æļĐåšĶ", + "memory": "内存", + "disk": "įĄŽį›˜", + "uptime": "čŋčĄŒæ—ķé—ī", + "os": "įģŧįŧŸ", + "host": "äļŧ朚", + "actions": "åŠĻä―œ", + "integrations": "įŧ§æ‰ŋ", + "integrationsPrism": "将 Prism čŋžæŽĨ到æ‚Ļ喜æŽĒįš„æœåŠĄã€‚", + "integrationsSlack": "集成Slack", + "integrationsSlackInfo": "集成SlackInfo", + "integrationsDiscord": "集成Discord", + "integrationsDiscordInfo": "集成DiscordäŋĄæŊ", + "integrationsZapier": "集成Zapier", + "integrationsZapierInfo": "集成ZapierInfo", + "commonSave": "äŋå­˜", + "createYour": "创åŧšä― įš„", + "createMonitor": "创åŧšį›‘控", + "pause": "暂停", + "resume": "æĒåĪ", + "editing": "įž–čū‘äļ­...", + "url": "į―‘å€", + "access": "čŪŋé—Ū", + "timezone": "æ—ķ匚", + "features": "į‰đåū", + "administrator": "įŪĄį†å‘˜ïžŸ", + "loginHere": "į‚đå‡ŧæ­ĪåĪ„į™ŧå―•", + "displayName": "æ˜ūįĪšåį§°", + "urlMonitor": "į›‘æŽ§įš„ URL", + "portToMonitor": "į›‘æŽ§įŦŊåĢ", + "websiteMonitoring": "į―‘įŦ™į›‘控", + "websiteMonitoringDescription": "ä―ŋį”Ļ HTTPsïž‰į›‘æŽ§æ‚Ļįš„į―‘įŦ™æˆ– API įŦŊį‚đ。", + "pingMonitoring": "pingį›‘æŽ§", + "pingMonitoringDescription": "æĢ€æŸĨæ‚Ļįš„æœåŠĄå™Ļæ˜ŊåĶåŊį”Ļ。", + "dockerContainerMonitoring": "DockeråŪđå™Ļį›‘æŽ§", + "dockerContainerMonitoringDescription": "æĢ€æŸĨä― įš„ Docker åŪđå™Ļæ˜ŊåĶæ­ĢåœĻčŋčĄŒã€‚", + "portMonitoring": "įŦŊåĢį›‘æŽ§", + "portMonitoringDescription": "æĢ€æŸĨä― įš„įŦŊåĢæ˜ŊåĶå·ē打垀。", + "createMaintenanceWindow": "创åŧšįŧĪįŠ—åĢ", + "createMaintenance": "创åŧšįŧĪ", + "editMaintenance": "įž–čū‘įŧĪ", + "maintenanceWindowName": "įŧĪįŠ—åĢåį§°", + "friendlyNameInput": "友åĨ―åį§°", + "friendlyNamePlaceholder": "įŧο—ķé—ī䚎 __:__ 持įŧ­ ___ 分钟", + "maintenanceRepeat": "įŧĪ重åĪ", + "maintenance": "įŧĪ", + "duration": "期é—ī", + "addMonitors": "æ·ŧåŠ į›‘č§†å™Ļ", + "window": "įŠ—åĢ", + "cancel": "取æķˆ", + "message": "äŋĄæŊ", + "low": "ä―Žįš„", + "high": "éŦ˜įš„", + "statusCode": "įŠķæ€į ", + "date&Time": "æ—Ĩ期&æ—ķé—ī", + "type": "įąŧ型", + "statusPageName": "įŠķ态éĄĩåį§°", + "publicURL": "å…Žå…ą URL", + "repeat": "重åĪ", + "edit": "įž–čū‘", + "createA": "创åŧšA", + "remove": "į§ŧé™Ī", + "maintenanceWindowDescription": "åœĻįŧο—ķæŪĩå†…ïžŒæ‰€é€‰į›‘č§†å™Ļįš„æ‰€æœ‰į›‘č§†éƒ―å°†æš‚åœã€‚äļäžšæ‰§čĄŒäŧŧä―•į―‘įŧœæĢ€æŸĨäŧŽč€Œé˜ŧæ­Ēč§Ķ发äŧŧä―•įŠķ态æ›ī新或通įŸĨ。æ‚Ļįš„æ˜ūįĪšå™Ļ将å†ŧįŧ“åœĻå…ķæœ€åŽįš„å·ēįŸĨįŠķ态åđķäļ”įŠķ态éĄĩéĒ将æ˜ūįĪšįŧοŒ‡įĪšå™Ļ。įŧο—ķæŪĩįŧ“æŸåŽïžŒį›‘æŽ§äžšč‡ŠåŠĻæĒåĪïžŒåĶ‚æžœæĢ€æĩ‹åˆ°é—ŪéĒ˜ïžŒå°†č§Ķ发č­ĶæŠĨ。įŧäļčŪĄå…Ĩæ­ĢåļļčŋčĄŒæ—ķé—īčŪĄįŪ—ã€‚", + "startTime": "垀始æ—ķé—ī", + "timeZoneInfo": "所有æ—Ĩ期和æ—ķé—ī均äļš GMT+0 æ—ķ匚。", + "monitorsToApply": "į›‘æŽ§å™Ļåš”į”Ļ", + "nextWindow": "äļ‹äļ€äļŠįŠ—åĢ", + "notFoundButton": "朊æ‰ū到按é’Ū", + "pageSpeedConfigureSettingsDescription": "æ‚ĻåŊäŧĨåœĻæ­Īå΄选æ‹Đäļŧæœšįš„ URL äŧĨåŠį›‘č§†å™Ļįąŧ型。", + "monitorDisplayName": "ᛑ视å™Ļæ˜ūįĪšåį§°", + "whenNewIncident": "å―“æ–°äš‹äŧķå‘į”Ÿæ—ķ", + "notifySMS": "通čŋ‡įŸ­äŋĄé€šįŸĨåģ将æŽĻ凚", + "notifyEmails": "čŋ˜é€ščŋ‡į”ĩ子é‚Ūäŧķ通įŸĨåΚäļŠåœ°å€ïžˆåģ将æŽĻ凚", + "seperateEmails": "å•į‹Žįš„į”ĩ子é‚Ūäŧķ", + "checkFrequency": "æĢ€æŸĨéĒ‘įŽ‡", + "matchMethod": "matchæ–đæģ•", + "expectedValue": "éĒ„æœŸå€ž", + "deleteDialogTitle": "įĄŪåۚč́删é™Īæ­Īᛑ视å™Ļ吗?", + "deleteDialogDescription": "ä—Ķ删é™Ī将无æģ•æĢ€įīĒæ­Īᛑ视å™Ļ。", + "pageSpeedMonitor": "éĄĩéĒ速åšĶį›‘æŽ§å™Ļ", + "shown": "æ˜ūįĪš", + "ago": "前", + "companyName": "å…Žåļåį§°", + "pageSpeedDetailsPerformanceReport": "倞æ˜Ŋäž°čŪĄå€žïžŒåŊčƒ―äžšæœ‰æ‰€äļåŒã€‚", + "pageSpeedDetailsPerformanceReportCalculator": "éĄĩéĒ速åšĶčŊĶæƒ…æ€§čƒ―æŠĨ告čŪĄįŪ—å™Ļ", + "checkingEvery": "æĢ€æŸĨæŊäļ€äļŠ", + "statusPageCreateSettings": "åĶ‚æžœæ‚Ļįš„įŠķ态éĄĩéĒå·ē准åĪ‡å°ąįŧŠïžŒæ‚ĻåŊäŧĨ将å…ķ标čŪ°äļšå·ē发åļƒã€‚", + "basicInformation": "åŸšįĄ€äŋĄæŊ", + "statusPageCreateBasicInfoDescription": "åۚäđ‰å…Žåļåį§°å’ŒįŠķ态éĄĩéĒæŒ‡å‘įš„å­åŸŸã€‚", + "statusPageCreateSelectTimeZoneDescription": "选æ‹ĐįŠķ态éĄĩéĒįš„æ˜ūįĪšæ—ķ匚。", + "statusPageCreateAppearanceDescription": "åۚäđ‰å…Žå…ąįŠķ态éĄĩéĒįš„éŧ˜čŪĪåĪ–č§‚ã€‚", + "statusPageCreateSettingsCheckboxLabel": "å·ē发åļƒåđķ向兎䞗åŊ见", + "statusPageCreateBasicInfoStatusPageAddress": "æ‚Ļįš„įŠķ态éĄĩéĒ地址", + "statusPageCreateTabsContent": "įŠķ态éĄĩæœåŠĄå™Ļ", + "statusPageCreateTabsContentDescription": "æ‚ĻåŊäŧĨå°†į›‘č§†įš„äŧŧæ„æ•°é‡įš„æœåŠĄå™Ļæ·ŧ加到įŠķ态éĄĩéĒ。æ‚Ļčŋ˜åŊäŧĨ重新排嚏åۃäŧŽäŧĨ获åū—最ä―ģč§‚įœ‹ä―“éŠŒã€‚", + "statusPageCreateTabsContentFeaturesDescription": "åœĻįŠķ态éĄĩä˜ūįĪšæ›īåΚčŊĶįŧ†äŋĄæŊ", + "showCharts": "æ˜ūįĪšå›ūčĄĻ", + "showUptimePercentage": "æ˜ūįĪšæ­ĢåļļčŋčĄŒæ—ķé—īį™ū分æŊ”", + "removeLogo": "į§ŧé™ĪLogo", + "statusPageStatus": "朊čŪūį―Ū兎垀įŠķ态éĄĩéĒ。", + "statusPageStatusContactAdmin": "čŊ·č”įģŧæ‚Ļįš„įŪĄį†å‘˜", + "statusPageStatusNotPublic": "æ­ĪįŠķ态éĄĩéĒäļå…Žåž€ã€‚", + "statusPageStatusNoPage": "čŋ™é‡ŒæēĄæœ‰įŠķ态éĄĩéĒ。", + "statusPageStatusServiceStatus": "æœåŠĄįŠķ态", + "deleteStatusPage": "č́删é™Īæ­ĪįŠķ态éĄĩéĒ吗", + "deleteStatusPageConfirm": "æ˜Ŋ删é™ĪįŠķ态éĄĩ", + "deleteStatusPageDescription": "ä—Ķ删é™Īæ‚Ļįš„įŠķ态éĄĩéĒ将无æģ•æĢ€įīĒ。", + "uptimeCreate": "期望倞į”Ļ䚎äļŽå“åš”įŧ“æžœčŋ›čĄŒåŒđ配åŒđ配įĄŪåۚįŠķ态。", + "uptimeCreateJsonPath": "将æ đæŪ响嚔 JSON 数æŪčŊ„äž°æ­ĪčĄĻčūū垏åđķ将įŧ“æžœį”Ļ䚎äļŽéĒ„æœŸå€žčŋ›čĄŒåŒđé…ã€‚įœ‹", + "uptimeCreateJsonPathQuery": "æŸĨčŊĒčŊ­čĻ€æ–‡æĄĢ。", + "maintenanceTableActionMenuDialogTitle": "æ‚ĻįœŸįš„æƒģ删é™Īæ­ĪįŧĪįŠ—åĢ吗", + "infrastructureEditYour": "įž–čū‘æ‚Ļįš„", + "infrastructureEditMonitor": "äŋå­˜åŸšįĄ€æžķæž„į›‘č§†å™Ļ", + "infrastructureMonitorCreated": "åŸšįĄ€čŪūæ–―į›‘æŽ§åˆ›åŧšæˆåŠŸïž", + "infrastructureMonitorUpdated": "åŸšįĄ€čŪūæ–―į›‘æŽ§æ›ī新成功", + "errorInvalidTypeId": "提äū›įš„通įŸĨįąŧ型无效", + "errorInvalidFieldId": "提äū›įš„å­—æŪĩ ID 无效", + "inviteNoTokenFound": "朊æ‰ū到邀čŊ·äŧĪį‰Œ", + "pageSpeedWarning": "č­Ķ告æ‚Ļ尚朊æ·ŧ加 Google PageSpeed API åŊ†é’Ĩ。æēĄæœ‰åŪƒïžŒPageSpeed ᛑ视å™Ļ将无æģ•čŋčĄŒã€‚", + "pageSpeedLearnMoreLink": "į‚đå‡ŧčŋ™é‡Œ", + "pageSpeedAddApiKey": "äŧĨæ·ŧ加æ‚Ļįš„ API åŊ†é’Ĩ。", + "update": "æ›ī新", + "invalidFileFormat": "äļæ”ŊæŒįš„æ–‡äŧķæ žåžïž", + "invalidFileSize": "文äŧķåĪŠåĪ§äš†ïž", + "ClickUpload": "į‚đå‡ŧäļŠäž ", + "DragandDrop": "拖æ”ū", + "MaxSize": "最åĪ§å°šåŊļ", + "SupportedFormats": "æ”ŊæŒįš„æ žåž", + "FirstName": "名", + "LastName": "姓", + "EmailDescriptionText": "čŋ™æ˜Ŋæ‚Ļå―“å‰įš„į”ĩ子é‚Ūäŧķ地址 - 无æģ•æ›īæ”đ。", + "YourPhoto": "äļŠäšščĩ„æ–™į…§į‰‡", + "PhotoDescriptionText": "čŋ™åž į…§į‰‡å°†æ˜ūįĪšåœĻæ‚Ļįš„äļŠäšščĩ„æ–™éĄĩéĒäļ­ã€‚", + "save": "äŋå­˜", + "DeleteDescriptionText": "čŋ™å°†äŧŽæœåŠĄå™Ļäļ­åˆ é™Īåˆ·å’Œæ‰€æœ‰å…ģč”æ•°æŪ。čŋ™æ˜ŊäļåŊé€†įš„ã€‚", + "DeleteAccountWarning": "删é™Īæ‚Ļįš„åˆ·æ„å‘ģį€æ‚Ļ将无æģ•å†æŽĄį™ŧå―•ïžŒåđķä‚Ļįš„æ‰€æœ‰æ•°æŪéƒ―å°†čĒŦ删é™Ī。čŋ™æ˜ŊäļåŊé€†įš„ã€‚", + "DeleteWarningTitle": "įœŸįš„åˆ é™Īčŋ™äļŠåˆ·ïžŸ", + "bulkImport": { + "title": "æ‰đ量åŊžå…Ĩ", + "selectFileTips": "选æ‹Đč́äļŠäž įš„ CSV 文äŧķ", + "selectFileDescription": "æ‚ĻåŊäŧĨäļ‹č――我äŧŽįš„æˆ–įĪšäū‹", + "selectFile": "选æ‹Đ文äŧķ", + "parsingFailed": "č§Ģ析åĪąčīĨ", + "uploadSuccess": "ᛑ视å™Ļ创åŧšæˆåŠŸïž", + "validationFailed": "驌čŊåĪąčīĨ", + "noFileSelected": "朊选æ‹Đ文äŧķ", + "fallbackPage": "åŊžå…Ĩ文äŧķäŧĨæ‰đ量äļŠäž æœåŠĄå™Ļ列čĄĻ", + "invalidFileType": "文äŧķįąŧ型无效", + "uploadFailed": "äļŠäž åĪąčīĨ" + }, + "DeleteAccountTitle": "删é™ĪčīĶæˆ·", + "DeleteAccountButton": "删é™Īåˆ·", + "publicLink": "å…Žå…ąé“ūæŽĨ", + "maskedPageSpeedKeyPlaceholder": "*************************************", + "reset": "重į―Ū", + "ignoreTLSError": "åŋ―į•Ĩ TLS/SSL 错čŊŊ", + "tlsErrorIgnored": "å·ēåŋ―į•Ĩ TLS/SSL 错čŊŊ", + "ignoreTLSErrorDescription": "åŋ―į•Ĩ TLS/SSL 错čŊŊåđķįŧ§įŧ­æĢ€æŸĨį―‘įŦ™åŊį”Ļ性", + "createNew": "新åŧš", + "greeting": { + "prepend": "ä― åĨ―", + "append": "äļ‹åˆæ—ķé—īïžŒå°―æƒ…å‘æŒĨ", + "overview": "äŧĨä˜Ŋæ‚Ļįš„ {{type}} ᛑ视å™Ļįš„æĶ‚čŋ°ã€‚" + }, + "roles": { + "superAdmin": "čķ…įš§įŪĄį†å‘˜", + "admin": "įŪĄį†", + "teamMember": "å›Ē队成员", + "demoUser": "æž”įĪšį”Ļ户" + }, + "teamPanel": { + "teamMembers": "å›Ē队成员", + "filter": { + "all": "å…ĻéƒĻ", + "member": "成员" + }, + "inviteTeamMember": "邀čŊ·æ–°å›Ē队成员", + "inviteNewTeamMember": "邀čŊ·æ–°å›Ē队成员", + "inviteDescription": "å―“æ‚Ļæ·ŧåŠ æ–°įš„å›Ē队成员æ—ķäŧ–äŧŽå°†åŊäŧĨčŪŋé—Ūæ‰€æœ‰į›‘视å™Ļ。", + "email": "į”ĩ子é‚Ūäŧķ", + "selectRole": "选æ‹Đ角č‰ē", + "inviteLink": "邀čŊ·é“ūæŽĨ", + "cancel": "取æķˆ", + "noMembers": "æēĄæœ‰å…·æœ‰æ­Ī角č‰ēįš„å›Ē队成员", + "getToken": "čŽ·å–Token", + "emailToken": "į”ĩ子é‚ŪäŧķäŧĪį‰Œ", + "table": { + "name": "名字", + "email": "é‚ŪįŪą", + "role": "角č‰ē", + "created": "创åŧš" + }, + "addTeamMember": { + "addMemberMenu": "æ·ŧ加å›Ē队成员", + "title": "æģĻ册新å›Ē队成员", + "description": "创åŧšäļ€äļŠæ–°į”Ļ户åđķäļŽäŧ–äŧŽå…ąäšŦ凭æŪ。æ­Īæ–đæģ•ä―ŋæˆå‘˜čƒ―åΟįŦ‹åģčŪŋé—Ūæ‰€æœ‰į›‘视å™Ļ。", + "addButton": "æ·ŧ加成员" + }, + "register": "æģĻ册å›Ē队成员", + "registerToast": { + "success": "į”Ļ户创åŧšïžŒåŪ‰å…Ļ地äļŽæˆå‘˜å…ąäšŦ凭æŪ。", + "dbUserExists": "į”Ļ户å·ē存åœĻ。", + "unknownError": "å‘į”ŸæœŠįŸĨ错čŊŊ。" + }, + "registerTeamMember": { + "title": "æģĻ册å›Ē队成员", + "auth": { + "common": { + "inputs": { + "firstName": { + "errors": { + "empty": "čŊ·čū“å…Ĩ姓名", + "pattern": "åį§°åŠčƒ―åŒ…åŦ字æŊã€įĐšæ žã€æ’‡å·æˆ–čŋžå­—įŽĶ" + } + }, + "lastName": { + "errors": { + "empty": "čŊ·čū“å…Ĩ姓氏", + "pattern": "å§“æ°åŠčƒ―åŒ…åŦ字æŊã€įĐšæ žã€æ’‡å·æˆ–čŋžå­—įŽĶ" + } + }, + "email": { + "errors": { + "empty": "č́įŧ§įŧ­ïžŒčŊ·čū“å…Ĩį”ĩ子é‚Ūäŧķ地址", + "invalid": "čŊ·é‡æ–°æĢ€æŸĨčū“å…Ĩįš„į”ĩ子é‚Ūäŧķåœ°å€įš„æœ‰æ•ˆæ€§" + } + }, + "role": { + "errors": { + "empty": "角č‰ēæ˜Ŋåŋ…éœ€įš„" + } + } + } + } + } + }, + "role": "角č‰ē" + }, + "monitorState": { + "paused": "暂停", + "resumed": "æĒåĪ", + "active": "å·ēæŋ€æīŧ" + }, + "menu": { + "uptime": "äļŠįšŋæ—ķé—ī", + "pagespeed": "éĄĩéĒ速åšĶ", + "infrastructure": "åŸšįĄ€čŪūæ–―", + "incidents": "之äŧķ", + "statusPages": "įŠķ态éĄĩ", + "maintenance": "įŧĪ", + "integrations": "集成", + "settings": "čŪūį―Ū", + "support": "æ”Ŋ持", + "discussions": "čŪĻčŪš", + "docs": "文æĄĢ", + "changelog": "变æ›īæ—Ĩåŋ—", + "profile": "į”Ļ户配į―Ū", + "password": "åŊ†į ", + "team": "å›Ē队", + "logOut": "į™ŧ凚", + "notifications": "通įŸĨ", + "logs": "æ—Ĩåŋ—" + }, + "settingsEmailUser": "į”ĩ子é‚Ūäŧķį”Ļ户 - į”Ļ䚎čšŦäŧ―驌čŊįš„į”Ļ户名åĶ‚æžœæŒ‡åŪšïžŒåˆ™č͆ᛖį”ĩ子é‚Ūäŧķ地址", + "state": "įŠķ态", + "statusBreadCrumbsStatusPages": "įŠķ态éĄĩ", + "statusBreadCrumbsDetails": "čŊĶæƒ…", + "commonSaving": "äŋå­˜äļ­...", + "navControls": "控åˆķ", + "incidentsPageTitle": "之äŧķéĄĩéĒ标éǘ", + "passwordPanel": { + "passwordChangedSuccess": "æ‚Ļįš„åŊ†į å·ē成功æ›īæ”đ。", + "passwordInputIncorrect": "æ‚Ļįš„åŊ†į čū“å…Ĩ错čŊŊ。", + "currentPassword": "å―“å‰åŊ†į ", + "enterCurrentPassword": "čū“å…Ĩå―“å‰åŊ†į ", + "newPassword": "新åŊ†į ", + "enterNewPassword": "čū“å…Ĩ新åŊ†į ", + "confirmNewPassword": "įĄŪčŪĪæ–°åŊ†į ", + "passwordRequirements": "新åŊ†į åŋ…éĄŧč‡ģ少包åŦ 8 äļŠå­—įŽĶåđķäļ”åŋ…éĄŧč‡ģ少包åŦäļ€äļŠåΧ写字æŊã€äļ€äļŠå°å†™å­—æŊã€äļ€äļŠæ•°å­—å’Œäļ€äļŠį‰đæŪŠå­—įŽĶ。", + "saving": "äŋå­˜..." + }, + "emailSent": "į”ĩ子é‚Ūäŧķå·ē发送", + "failedToSendEmail": "į”ĩ子é‚Ūäŧķ发送åĪąčīĨ", + "settingsTestEmailSuccess": "čŪūį―Ūæĩ‹čŊ•į”ĩ子é‚ŪäŧķæˆåŠŸ", + "settingsTestEmailFailed": "发送æĩ‹čŊ•į”ĩ子é‚ŪäŧķåĪąčīĨ", + "settingsTestEmailFailedWithReason": "发送æĩ‹čŊ•į”ĩ子é‚ŪäŧķåĪąčīĨïžš {{reason}}", + "settingsTestEmailUnknownError": "朊įŸĨ错čŊŊ", + "statusMsg": { + "paused": "ᛑ视å·ē暂停。", + "up": "æ‚Ļįš„į―‘įŦ™å·ēäļŠįšŋ。", + "down": "æ‚Ļįš„į―‘įŦ™å·ēäļ‹įšŋ。", + "pending": "į­‰åū…äļ­..." + }, + "uptimeGeneralInstructions": { + "http": "čū“å…ĨčĶį›‘æŽ§įš„ URL 或 IPäū‹åĶ‚.. https://example.com/ or 192.168.1.100åđķæ·ŧ加äŧŠčĄĻæŋä˜ūįĪšįš„æļ…æ™°æ˜ūįĪšåį§°ã€‚", + "ping": "čū“å…Ĩč́ ping įš„ IP 地址或äļŧ朚名äū‹åĶ‚.. 192.168.1.100 或 example.comåđķæ·ŧ加æ˜ūįĪšåœĻäŧŠčĄĻæŋäļŠįš„æļ…æ™°æ˜ūįĪšåį§°ã€‚", + "docker": "᠁čū“å…Ĩ Docker åŪđå™Ļåį§°æˆ– ID。æ‚ĻåŊäŧĨä―ŋį”ĻåŪđå™Ļåį§°ïžˆäū‹åĶ‚.. my-app或åŪđå™Ļ IDåیæ•īįš„ 64 字įŽĶ ID æˆ–įŸ­ ID。", + "port": "čū“å…ĨæœåŠĄå™Ļįš„ URL 或 IP、įŦŊåĢ号和äŧŠčĄĻæŋä˜ūįĪšįš„æļ…æ™°æ˜ūįĪšåį§°ã€‚", + "game": "čū“å…Ĩč́ ping įš„ IP 地址或äļŧ朚名和įŦŊåĢ号äū‹åĶ‚.. 192.168.1.100 或 example.comåđķ选æ‹Đæļ￈įąŧ型。" + }, + "common": { + "appName": "Checkmate", + "monitoringAgentName": "æ•čŽ·", + "buttons": { + "toggleTheme": "切æĒäļŧéĒ˜æ˜Ž&暗" + }, + "toasts": { + "networkError": "į―‘įŧœé”™čŊŊ", + "checkConnection": "čŊ·æĢ€æŸĨä― įš„į―‘įŧœčŋžæŽĨ", + "unknownError": "朊įŸĨ错čŊŊ" + } + }, + "auth": { + "common": { + "navigation": { + "continue": "įŧ§įŧ­", + "back": "čŋ”回" + }, + "inputs": { + "email": { + "label": "į”ĩ子é‚Ūäŧķ", + "placeholder": "jordan.ellis@domain.com", + "errors": { + "empty": "č́įŧ§įŧ­ïžŒčŊ·čū“å…Ĩæ‚Ļįš„į”ĩ子é‚Ūäŧķ地址", + "invalid": "čŊ·é‡æ–°æĢ€æŸĨčū“å…Ĩįš„į”ĩ子é‚Ūäŧķåœ°å€įš„æœ‰æ•ˆæ€§" + } + }, + "password": { + "label": "åŊ†į ", + "rules": { + "length": { + "beginning": "åŋ…éĄŧč‡ģ少", + "highlighted": "8 äļŠå­—įŽĶé•ŋ" + }, + "special": { + "beginning": "åŋ…éĄŧč‡ģ少包åŦ", + "highlighted": "äļ€äļŠį‰đæŪŠå­—įŽĶ" + }, + "number": { + "beginning": "åŋ…éĄŧč‡ģ少包åŦ", + "highlighted": "äļ€äļŠæ•°å­—" + }, + "uppercase": { + "beginning": "åŋ…éĄŧč‡ģ少包åŦ", + "highlighted": "äļ€äļŠåΧ写字æŊ" + }, + "lowercase": { + "beginning": "åŋ…éĄŧč‡ģ少包åŦ", + "highlighted": "äļ€äļŠå°å†™å­—æŊ" + }, + "match": { + "beginning": "åŊ†į ", + "highlighted": "åŋ…éĄŧåŒđ配" + } + }, + "errors": { + "empty": "čŊ·čū“å…Ĩæ‚Ļįš„åŊ†į ", + "length": "åŊ†į é•ŋåšĶåŋ…éĄŧč‡ģ少äļš 8 äļŠå­—įŽĶ", + "uppercase": "åŊ†į åŋ…éĄŧč‡ģ少包åŦ 1 äļŠåΧ写字æŊ", + "lowercase": "åŊ†į åŋ…éĄŧč‡ģ少包åŦ 1 äļŠå°å†™å­—æŊ", + "number": "åŊ†į åŋ…éĄŧč‡ģ少包åŦ 1 äļŠæ•°å­—", + "special": "åŊ†į åŋ…éĄŧč‡ģ少包åŦ 1 äļŠį‰đæŪŠå­—įŽĶ", + "incorrect": "æ‚Ļ提äū›įš„åŊ†į äļŽæˆ‘äŧŽįš„čŪ°å―•äļįŽĶ" + } + }, + "passwordConfirm": { + "label": "įĄŪčŪĪåŊ†į ", + "placeholder": "重新čū“å…ĨåŊ†į äŧĨįĄŪčŪĪ", + "errors": { + "empty": "čŊ·å†æŽĄčū“å…Ĩæ‚Ļįš„åŊ†į äŧĨčŋ›čĄŒįĄŪčŪĪ有åŠĐ䚎č§Ģå†ģ拞写错čŊŊ", + "different": "čū“å…Ĩįš„åŊ†į äļåŒđ配因æ­Īå…ķäļ­äļ€äļŠåŊ†į åŊčƒ―čū“å…Ĩ错čŊŊ" + } + }, + "firstName": { + "label": "名字", + "placeholder": "Jordan", + "errors": { + "empty": "čŊ·čū“å…Ĩæ‚Ļįš„å§“å", + "length": "姓名åŋ…éĄŧ少䚎 50 äļŠå­—įŽĶ", + "pattern": "åį§°åŠčƒ―åŒ…åŦ字æŊã€įĐšæ žã€æ’‡å·æˆ–čŋžå­—įŽĶ" + } + }, + "lastName": { + "label": "姓", + "placeholder": "Ellis", + "errors": { + "empty": "čŊ·čū“å…Ĩæ‚Ļįš„å§“æ°", + "length": "姓氏åŋ…éĄŧ少䚎 50 äļŠå­—įŽĶ", + "pattern": "å§“æ°åŠčƒ―åŒ…åŦ字æŊã€įĐšæ žã€æ’‡å·æˆ–čŋžå­—įŽĶ" + } + } + }, + "errors": { + "validation": "驌čŊæ•°æŪæ—ķ凚错。" + }, + "fields": { + "password": { + "errors": { + "incorrect": "æ‚Ļ提äū›įš„åŊ†į äļŽæˆ‘äŧŽįš„čŪ°å―•äļįŽĶ" + } + }, + "role": { + "errors": { + "min": "åŋ…éĄŧč‡ģ少čŪūį―Ūäļ€äļŠč§’č‰ē" + } + } + } + }, + "login": { + "heading": "į™ŧå―•äŧĨįŧ§įŧ­", + "subheadings": { + "stepOne": "čū“å…Ĩæ‚Ļįš„į”ĩ子é‚Ūäŧķ", + "stepTwo": "čū“å…Ĩæ‚Ļįš„åŊ†į " + }, + "links": { + "forgotPassword": "åŋ˜čŪ°åŊ†į ïžŸ", + "register": "æēĄæœ‰åˆ·ïžŸ", + "forgotPasswordLink": "重į―ŪåŊ†į ", + "registerLink": "æģĻ册é“ūæŽĨ" + }, + "toasts": { + "success": "æŽĒčŋŽå›žæĨæ‚Ļå·ē成功į™ŧå―•ã€‚", + "incorrectPassword": "åŊ†į é”™čŊŊ" + }, + "errors": { + "password": { + "incorrect": "æ‚Ļ提äū›įš„åŊ†į äļŽæˆ‘äŧŽįš„čŪ°å―•äļįŽĶ" + } + }, + "welcome": "æŽĒčŋŽå›žåˆ° Checkmate" + }, + "registration": { + "heading": { + "superAdmin": "创åŧščķ…įš§įŪĄį†å‘˜", + "user": "æģĻ册" + }, + "subheadings": { + "stepOne": "čū“å…Ĩæ‚Ļįš„äļŠäšščŊĶįŧ†äŋĄæŊ", + "stepTwo": "čū“å…Ĩæ‚Ļįš„į”ĩ子é‚Ūäŧķ", + "stepThree": "创åŧšåŊ†į " + }, + "description": { + "superAdmin": "创åŧščķ…įš§į”Ļ户åˆ·äŧĨ垀始ä―ŋį”Ļ", + "user": "æģĻ册äļšį”Ļ户åđķčŊ·æą‚čķ…įš§įŪĄį†å‘˜čŪŋé—Ūæ‚Ļįš„į›‘č§†å™Ļ" + }, + "gettingStartedButton": { + "superAdmin": "创åŧščķ…įš§įŪĄį†å‘˜åˆ·", + "user": "æģĻ册æ™Ū通į”Ļ户" + }, + "termsAndPolicies": "创åŧšåˆ·åģčĄĻįĪšæ‚Ļ同意我äŧŽįš„æœåŠĄæĄæŽūå’Œéšį§æ”ŋį­– 。", + "links": { + "login": "å·ēæ‹Ĩ有åļå·ïžŸ į™ŧå―•" + }, + "toasts": { + "success": "æŽĒčŋŽïžæ‚Ļįš„čīĶ号å·ē成功创åŧšã€‚" + }, + "welcome": "æŽĒčŋŽæĨ到Checkmate" + }, + "forgotPassword": { + "heading": "åŋ˜čŪ°åŊ†į ïžŸ", + "subheadings": { + "stepOne": "äļį”Ļ担åŋƒïžŒæˆ‘äŧŽäžšå‘æ‚Ļ发送重į―ŪčŊŽã€‚", + "stepTwo": "我äŧŽå·ē发送åŊ†į é‡į―Ūé“ūæŽĨ到 ", + "stepThree": "æ‚Ļįš„æ–°åŊ†į åŋ…éĄŧäļŽäŧĨ前ä―ŋį”Ļįš„åŊ†į äļåŒã€‚", + "stepFour": "æ‚Ļįš„åŊ†į å·ē成功重į―Ū。į‚đå‡ŧä–đį™ŧå―•ã€‚" + }, + "buttons": { + "openEmail": "打垀į”ĩ子é‚Ūäŧķåš”į”Ļ", + "resetPassword": "重į―ŪåŊ†į " + }, + "imageAlts": { + "passwordKey": "åŊ†į å›ū标", + "email": "į”ĩ子é‚Ūäŧķå›ū标", + "lock": "锁åۚå›ū标", + "passwordConfirm": "åŊ†į įĄŪčŪĪå›ū标" + }, + "links": { + "login": "čŋ”回到į™ŧå―•", + "resend": "æēĄæœ‰æ”ķ到į”ĩ子é‚Ūäŧķ į‚đå‡ŧ重新发送" + }, + "toasts": { + "sent": "指äŧĪ发送č‡ģ .", + "emailNotFound": "朊æ‰ū到į”ĩ子é‚Ūäŧķ。", + "redirect": "将åœĻį§’åŽé‡åŪšå‘...", + "success": "æ‚Ļįš„åŊ†į å·ē成功重į―Ū。", + "error": "无æģ•重į―ŪåŊ†į ã€‚čŊ·įĻåŽé‡čŊ•æˆ–č”įģŧæ”Ŋ持䚚员。" + } + } + }, + "errorPages": { + "serverUnreachable": { + "toasts": { + "reconnected": "å·ē成功重新čŋžæŽĨåˆ°æœåŠĄå™Ļ。", + "stillUnreachable": "æœåŠĄå™Ļäŧį„ķ无æģ•čŪŋé—Ū。čŊ·įĻåŽé‡čŊ•。" + }, + "alertBox": "æœåŠĄå™ĻčŋžæŽĨ错čŊŊ", + "description": "我äŧŽæ— æģ•čŋžæŽĨåˆ°æœåŠĄå™Ļ。åĶ‚æžœé—Ūéǘäŧį„ķ存åœĻčŊ·æĢ€æŸĨæ‚Ļįš„äš’č”į―‘čŋžæŽĨ或銌čŊæ‚Ļįš„éƒĻį―ē配į―Ū。", + "retryButton": { + "default": "尝čŊ•重čŋž", + "processing": "čŋžæŽĨäļ­..." + } + } + }, + "createNotifications": { + "title": "创åŧšé€šįŸĨ通道", + "nameSettings": { + "title": "名字", + "description": "é›†æˆįš„æčŋ°æ€§åį§°ã€‚", + "nameLabel": "名字", + "namePlaceholder": "äū‹åĶ‚.. Slack 通įŸĨ" + }, + "typeSettings": { + "title": "įąŧ型", + "description": "选æ‹ĐčĶåˆ›åŧšįš„通įŸĨ通道įąŧ型。", + "typeLabel": "įąŧ型" + }, + "emailSettings": { + "title": "é‚ŪįŪą", + "description": "į›Ū标į”ĩ子é‚Ūäŧķ地址。", + "emailLabel": "į”ĩ子é‚Ūäŧķ地址", + "emailPlaceholder": "äū‹åĶ‚.. john@example.com" + }, + "slackSettings": { + "title": "Slack", + "description": "åœĻæ­ĪåĪ„é…į―Ūæ‚Ļįš„ Slack Webhook", + "webhookLabel": "Slack webhook į―‘å€", + "webhookPlaceholder": "https://hooks.slack.com/services/..." + }, + "pagerdutySettings": { + "title": "PagerDuty", + "description": "åœĻæ­ĪåĪ„é…į―Ūæ‚Ļįš„ PagerDuty 集成", + "integrationKeyLabel": "集成åŊ†é’Ĩ", + "integrationKeyPlaceholder": "1234567890" + }, + "discordSettings": { + "title": "Discord", + "description": "åœĻæ­ĪåĪ„é…į―Ūæ‚Ļįš„ Discord Webhook", + "webhookLabel": "Discord Webhook į―‘å€", + "webhookPlaceholder": "https://your-server.com/webhook" + }, + "webhookSettings": { + "title": "Webhook", + "description": "åœĻæ­ĪåĪ„é…į―Ū Webhook", + "webhookLabel": "Webhookį―‘å€", + "webhookPlaceholder": "https://your-server.com/webhook" + }, + "testNotification": "æĩ‹čŊ•通įŸĨ", + "dialogDeleteTitle": "ä― įĄŪčŪĪč́删é™Īæ­Ī通įŸĨ吗", + "dialogDeleteConfirm": "删é™Ī" + }, + "notificationConfig": { + "title": "通įŸĨ", + "description": "选æ‹Đč́ä―ŋį”Ļįš„é€šįŸĨæļ é“" + }, + "monitorStatus": { + "checkingEvery": "æŊéš” {{interval}} æĢ€æŸĨäļ€æŽĄ", + "withCaptureAgent": "ä―ŋį”Ļ Capture äŧĢᐆ {{version}}", + "up": "åœĻįšŋ", + "down": "įĶŧįšŋ", + "paused": "暂停" + }, + "advancedMatching": "éŦ˜įš§åŒđ配", + "sendTestNotifications": "发送æĩ‹čŊ•通įŸĨ", + "selectAll": "å…Ļ选", + "showAdminLoginLink": "æ˜ūįĪšâ€œįŪĄį†å‘˜ïžŸåœĻæ­ĪåĪ„į™ŧå―•â€œįŠķ态éĄĩéĒäļŠįš„é“ūæŽĨ", + "logsPage": { + "title": "æ—Ĩåŋ—", + "description": "įģŧįŧŸæ—Ĩåŋ— - 最后 1000 行", + "tabs": { + "queue": "ä―œäļšé˜Ÿåˆ—", + "logs": "æœåŠĄå™Ļæ—Ĩåŋ—", + "diagnostics": "čŊŠæ–­" + }, + "toast": { + "fetchLogsSuccess": "æ—Ĩåŋ—čŽ·å–æˆåŠŸ" + }, + "logLevelSelect": { + "title": "æ—Ĩåŋ—į­‰įš§", + "values": { + "all": "å…ĻéƒĻ", + "info": "äŋĄæŊ", + "warn": "č­Ķ告", + "error": "错čŊŊ", + "debug": "调čŊ•" + } + } + }, + "queuePage": { + "title": "队列", + "refreshButton": "刷新", + "flushButton": "刷新队列", + "jobTable": { + "title": "å―“å‰åœĻ队列äļ­įš„ä―œäļš", + "idHeader": "ᛑ视å™Ļ ID", + "urlHeader": "į―‘å€", + "typeHeader": "įąŧ型", + "activeHeader": "有效", + "lockedAtHeader": "锁åۚåœĻ", + "runCountHeader": "čŋčĄŒčŪĄæ•°", + "failCountHeader": "åĪąčīĨčŪĄæ•°", + "lastRunHeader": "äļŠæŽĄčŋčĄŒæ—ķé—ī", + "lastFinishedAtHeader": "最后åŪŒæˆæ—ķé—ī", + "lastRunTookHeader": "äļŠæŽĄčŋčĄŒčŠąčīđ乆", + "intervalHeader": "é—ī隔" + }, + "metricsTable": { + "title": "队列指标", + "metricHeader": "åšĶ量", + "valueHeader": "倞" + }, + "failedJobTable": { + "title": "åĪąčīĨįš„ä―œäļš", + "monitorIdHeader": "ᛑ视å™ĻID", + "monitorUrlHeader": "į›‘æŽ§į―‘å€", + "failCountHeader": "åĪąčīĨčŪĄæ•°", + "failedAtHeader": "äļŠæŽĄåĪąčīĨæ—ķ䚎", + "failReasonHeader": "åĪąčīĨ原因" + } + }, + "export": { + "title": "åŊžå‡šį›‘视å™Ļ", + "success": "ᛑ视å™ĻåŊžå‡šæˆåŠŸïž", + "failed": "åŊžå‡šį›‘视å™ĻåĪąčīĨ" + }, + "monitorActions": { + "title": "åŊžå‡š/åŊžå…Ĩ", + "import": "åŊžå…Ĩᛑ视å™Ļ", + "export": "åŊžå‡šį›‘视å™Ļ", + "deleteSuccess": "ᛑ视éĄđ删é™Ī成功", + "deleteFailed": "删é™Īᛑ视éĄđåĪąčīĨ", + "details": "čŊĶæƒ…" + }, + "settingsPage": { + "aboutSettings": { + "labelDevelopedBy": "į”ą Bluewave Labs 垀发", + "labelVersion": "į‰ˆæœŽ", + "title": "å…ģ䚎" + }, + "demoMonitorsSettings": { + "buttonAddMonitors": "æ·ŧ加枔įĪšį›‘č§†å™Ļ", + "description": "æ·ŧ加įĪšäū‹į›‘视å™ĻäŧĨčŋ›čĄŒæž”įĪšã€‚", + "title": "įĪšäū‹į›‘视å™Ļ" + }, + "emailSettings": { + "buttonSendTestEmail": "发送æĩ‹čŊ•į”ĩ子é‚Ūäŧķ", + "description": "配į―ŪįģŧįŧŸįš„į”ĩ子é‚ŪäŧķčŪūį―Ū。čŋ™į”Ļ䚎发送通įŸĨ和č­ĶæŠĨ。", + "descriptionTransport": "čŋ™å°†äļš NodeMailer 构åŧš SMTP äž čū“", + "labelAddress": "į”ĩ子é‚Ūäŧķ地址 - į”Ļ䚎čšŦäŧ―驌čŊ", + "labelConnectionHost": "į”ĩ子é‚ŪäŧķčŋžæŽĨäļŧ朚 - č́åœĻ HELO/EHLO é—Ū候čŊ­äļ­ä―ŋį”Ļįš„äļŧ朚名", + "labelHost": "į”ĩ子é‚Ūäŧķäļŧ朚 - č́čŋžæŽĨåˆ°įš„äļŧ朚名或 IP 地址", + "labelIgnoreTLS": "į́į”Ļ STARTTLSïžšåģä―ŋæœåŠĄå™Ļæ”Ŋ持 TLSäđŸäļč́ä―ŋį”Ļ TLS", + "labelPassword": "į”ĩ子é‚ŪäŧķåŊ†į  - čšŦäŧ―驌čŊåŊ†į ", + "labelPasswordSet": "å·ēčŪūį―ŪåŊ†į ã€‚单å‡ŧ重į―ŪäŧĨæ›īæ”đåŪƒã€‚", + "labelPool": "åŊį”ĻčŋžæŽĨæą ïžšé‡į”ĻįŽ°æœ‰čŋžæŽĨäŧĨ提éŦ˜æ€§čƒ―", + "labelPort": "į”ĩ子é‚ŪäŧķįŦŊåĢ - č́čŋžæŽĨįš„įŦŊåĢ", + "labelRejectUnauthorized": "拒įŧæ— æ•ˆčŊäđĶïžšæ‹’įŧå…·æœ‰č‡Šį­ū名或äļå—äŋĄäŧŧįš„čŊäđĶįš„čŋžæŽĨ", + "labelRequireTLS": "åžšåˆķ STARTTLS需č́ TLS å‡įš§ïžŒåĶ‚æžœäļæ”Ŋ持则åĪąčīĨ", + "labelSecure": "ä―ŋį”Ļ SSLæŽĻčïž‰ïžšä―ŋį”Ļ SSL/TLS 加åŊ†čŋžæŽĨ", + "labelTLSServername": "TLS æœåŠĄå™Ļåį§° - å―“äļŧ朚æ˜Ŋ IP æ—ķį”Ļ䚎 TLS 驌čŊįš„åŊ选äļŧ朚名", + "labelUser": "į”ĩ子é‚Ūäŧķį”Ļ户 - į”Ļ䚎čšŦäŧ―驌čŊįš„į”Ļ户名åĶ‚æžœæŒ‡åŪšïžŒåˆ™č͆ᛖį”ĩ子é‚Ūäŧķ地址", + "linkTransport": "åœĻæ­ĪåĪ„æŸĨįœ‹č§„æ ž", + "placeholderUser": "åĶ‚æžœäļéœ€čĶïžŒčŊ·į•™įĐš", + "title": "į”ĩ子é‚Ūäŧķ", + "toastEmailRequiredFieldsError": "需č́čū“å…Ĩį”ĩ子é‚Ūäŧķ地址、äļŧ朚、įŦŊåĢ和åŊ†į " + }, + "pageSpeedSettings": { + "description": "čū“å…Ĩæ‚Ļįš„ Google PageSpeed API åŊ†é’ĨäŧĨåŊį”Ļ Google PageSpeed į›‘æŽ§ã€‚å•å‡ŧ重į―ŪäŧĨæ›ī新åŊ†é’Ĩ。", + "labelApiKeySet": "API åŊ†é’Ĩå·ēčŪūį―Ū。单å‡ŧ重į―ŪäŧĨæ›īæ”đåŪƒã€‚", + "labelApiKey": "ageSpeed API åŊ†é’Ĩ", + "title": "Google PageSpeed API åŊ†é’Ĩ" + }, + "saveButtonLabel": "äŋå­˜", + "statsSettings": { + "clearAllStatsButton": "æļ…é™Ī所有įŧŸčŪĄ", + "clearAllStatsDescription": "æļ…é™Ī所有įŧŸčŪĄæ•°æŪ。čŋ™æ˜ŊäļåŊé€†č―Žįš„ã€‚", + "clearAllStatsDialogConfirm": "æ˜Ŋįš„ïžŒæļ…é™Ī所有įŧŸčŪĄæ•°æŪ", + "clearAllStatsDialogDescription": "ä—Ķ删é™ĪïžŒį›‘æŽ§åŽ†åēčŪ°å―•å’ŒįŧŸčŪĄäŋĄæŊ将无æģ•æĢ€įīĒ。", + "clearAllStatsDialogTitle": "ä― æƒģæļ…é™Īæ‰€æœ‰įš„įŧŸčŪĄæ•°æŪ吗", + "description": "åۚäđ‰č́äŋį•™åކåē数æŪįš„æ—ķé—ī。æ‚Ļčŋ˜åŊäŧĨæļ…é™Īæ‰€æœ‰įŽ°æœ‰æ•°æŪ。", + "labelTTL": "č́äŋį•™į›‘控历åēčŪ°å―•įš„åĪĐæ•°ã€‚", + "labelTTLOptional": "0 čĄĻįĪšæ— é™", + "title": "æŸĨįœ‹åŽ†åēčŪ°å―•" + }, + "systemResetSettings": { + "buttonRemoveAllMonitors": "į§ŧé™Īæ‰€æœ‰į›‘č§†éĄđ", + "description": "äŧŽįģŧįŧŸäļ­į§ŧé™Īæ‰€æœ‰į›‘č§†éĄđ。", + "dialogConfirm": "įĄŪåŪšïžŒį§ŧé™Īæ‰€æœ‰į›‘č§†éĄđ", + "dialogDescription": "į§ŧé™Ī后无æģ•æĒåĪčŋ™äš›į›‘视éĄđ。", + "dialogTitle": "ä― æƒģį§ŧé™Īæ‰€æœ‰įš„į›‘č§†éĄđ吗", + "title": "įģŧįŧŸåĪä―" + }, + "timezoneSettings": { + "description": "选æ‹Đį”Ļ䚎åœĻæ•īäļŠåš”į”ĻįĻ‹åšäļ­æ˜ūįĪšæ—Ĩ期和æ—ķé—īæ—ķ匚。", + "label": "æ˜ūįĪšæ—ķ匚", + "title": "æ˜ūįĪšæ—ķ匚" + }, + "title": "čŪūį―Ū", + "uiSettings": { + "description": "åœĻäšŪč‰ē和暗č‰ēæĻĄåžäđ‹é—ī切æĒ或æ›īæ”đį”Ļæˆ·į•ŒéĒčŊ­čĻ€ã€‚", + "labelLanguage": "čŊ­čĻ€", + "labelTheme": "äļŧéǘæĻĄåž", + "title": "åĪ–č§‚" + }, + "urlSettings": { + "description": "åœĻå…Žå…ąįŠķ态éĄĩéĒä˜ūįĪšį›‘æŽ§įš„ IP 地址或 URL。åĶ‚æžœį́į”Ļ则äŧ…æ˜ūįĪšį›‘æŽ§åį§°äŧĨäŋæŠĪ敏感äŋĄæŊ。", + "label": "åœĻįŠķ态éĄĩéĒæ˜ūįĪš IP/URL", + "selectDisabled": "į́į”Ļ", + "selectEnabled": "åŊį”Ļ", + "title": "åœĻįŠķ态éĄĩéĒäļŠį›‘控 IP/URL" + }, + "globalThresholds": { + "title": "å…Ļåą€é˜ˆå€ž", + "description": "配į―Ū CPU、内存、įĢį›˜å’ŒæļĐåšĶ阈倞。åĶ‚æžœæäū›äš†é˜ˆå€žïžŒåˆ™äžšč‡ŠåŠĻåŊį”Ļį›‘æŽ§ã€‚" + } + }, + "statusPageCreate": { + "buttonSave": "äŋå­˜" + }, + "incidentsOptionsHeaderFilterResolved": "å·ēč§Ģå†ģ", + "settingsSave": "äŋå­˜", + "statusPageCreateAppearanceTitle": "åĪ–č§‚", + "confirmPassword": "įĄŪčŪĪåŊ†į ", + "monitorHooks": { + "failureAddDemoMonitors": "æœŠčƒ―æ·ŧ加įĪšäū‹į›‘视å™Ļ", + "successAddDemoMonitors": "成功æ·ŧ加įĪšäū‹į›‘视å™Ļ" + }, + "settingsAppearance": "åĪ–č§‚", + "settingsDisplayTimezone": "æ˜ūįĪšæ—ķ匚", + "settingsGeneralSettings": "åļļ规čŪūį―Ū", + "incidentsOptionsHeaderTotalIncidents": "之äŧķæ€ŧ数", + "statusPage": { + "deleteSuccess": "įŠķ态éĄĩéĒå·ē成功删é™Ī", + "deleteFailed": "删é™ĪįŠķ态éĄĩéĒåĪąčīĨ", + "createSuccess": "įŠķ态éĄĩ创åŧ𿈐功", + "updateSuccess": "įŠķ态éĄĩæ›ī新成功", + "generalSettings": "åļļ规čŪūį―Ū", + "contents": "内åŪđ", + "fallback": { + "checks": [ + "åŪžæ—ķį›‘æŽ§å’Œæ˜ūįĪšæœåŠĄįš„čŋčĄŒįŠķå†ĩ", + "跟čļŠåΚäļŠæœåŠĄåđķå…ąäšŦå…ķįŠķ态", + "čŪĐį”Ļ户䚆č§Ģäļ­æ–­å’Œæ€§čƒ―" + ], + "title": "įŠķ态éĄĩį”Ļ䚎", + "actionButton": "čŪĐæˆ‘äŧŽåˆ›åŧšä― įš„įŽŽäļ€äļŠįŠķ态éĄĩ" + } + }, + "testNotificationsDisabled": "æ­Īᛑ视å™ĻæēĄæœ‰é€šįŸĨčŪūį―Ū。æ‚Ļ需čĶé€ščŋ‡å•å‡ŧ‘配į―Ū’按é’ŪæĨæ·ŧ加äļ€äļŠ", + "incidentsTableResolvedAt": "č§Ģå†ģ䚎", + "incidentsTableActionResolve": "č§Ģå†ģ", + "checkHooks": { + "failureResolveOne": "æœŠčƒ―č§Ģå†ģ之äŧķ。", + "failureResolveAll": "æœŠčƒ―č§Ģå†ģ所有䚋äŧķ。", + "failureResolveMonitor": "故障排é™Īį›‘æŽ§äš‹äŧķåĪąčīĨ。" + }, + "checkFormError": "čŊ·æĢ€æŸĨčĄĻ单äļ­įš„é”™čŊŊ。", + "diagnosticsPage": { + "diagnosticDescription": "įģŧįŧŸčŊŠæ–­", + "statsDescription": "įģŧįŧŸįŧŸčŪĄæ•°æŪ", + "gauges": { + "heapAllocationTitle": "堆分配", + "heapAllocationSubtitle": "% įš„åŊį”Ļ内存占į”ĻįŽ‡", + "heapUsageTitle": "堆ä―ŋį”Ļ情å†ĩ", + "heapUsageSubtitle": "% įš„åŊį”Ļ内存占į”ĻįŽ‡", + "heapUtilizationTitle": "堆åˆĐį”ĻįŽ‡", + "heapUtilizationSubtitle": "% įš„åˆ†é…į™ū分æŊ”", + "instantCpuUsageTitle": "å―“å‰ CPU ä―ŋį”ĻįŽ‡", + "instantCpuUsageSubtitle": "% įš„1į§’CPU占į”ĻįŽ‡" + }, + "stats": { + "eventLoopDelayTitle": "之äŧķåūŠįŽŊåŧķčŋŸ", + "uptimeTitle": "čŋčĄŒæ—ķé—ī", + "usedHeapSizeTitle": "å·ēį”Ļ堆内存åĪ§å°", + "totalHeapSizeTitle": "æ€ŧ堆åĪ§å°", + "osMemoryLimitTitle": "įģŧįŧŸå†…存限åˆķ" + } + }, + "pageSpeedLighthouseAPI": "ä―ŋį”Ļ Lighthouse PageSpeed API į›‘æŽ§æ‚Ļįš„į―‘įŦ™", + "time": { + "threeMinutes": "3 分钟", + "fiveMinutes": "5 分钟", + "tenMinutes": "10 分钟", + "twentyMinutes": "20 分钟", + "oneHour": "1 小æ—ķ", + "oneDay": "1 åĪĐ", + "oneWeek": "1 å‘Ļ", + "fourMinutes": "4 分钟", + "oneMinute": "1 分钟", + "twoMinutes": "2 分钟", + "fifteenSeconds": "15 į§’", + "thirtySeconds": "30 į§’" + }, + "general": { + "noOptionsFound": "{{unit}} 朊æ‰ū到" + }, + "infrastructureMonitor": { + "fallback": { + "checks": [ + "čŋ―čļŠæœåŠĄå™Ļæ€§čƒ―čĄĻįŽ°", + "čŊ†åˆŦį“ķéLjåđķ䞘化čĩ„暐åˆĐį”ĻįŽ‡", + "通čŋ‡åŪžæ—ķį›‘æŽ§äŋéšœįģŧįŧŸåŊ靠性" + ], + "title": "åŸšįĄ€čŪūæ–―į›‘č§†å™Ļį”Ļ䚎", + "actionButton": "čŪĐæˆ‘äŧŽåˆ›åŧšä― įš„įŽŽäļ€äļŠåŸšįĄ€čŪūæ–―č§†å™Ļ" + } + }, + "maintenanceWindow": { + "fallback": { + "checks": ["标čŪ°įŧο—ķæŪĩ", "æķˆé™Ī所有čŊŊč§Ģ", "停æ­ĒåœĻįŧĪįŠ—åĢ内发送č­ĶæŠĨ"], + "title": "įŧο—ķæŪĩį”Ļ䚎", + "actionButton": "čŪĐæˆ‘äŧŽåˆ›åŧšæ‚Ļįš„įŽŽäļ€äļŠįŧο—ķæŪĩ" + } + }, + "pageSpeed": { + "fallback": { + "checks": ["æŠĨ告éĄĩéĒįš„į”Ļæˆ·ä―“銌", "åļŪåŠĐåˆ†æžį―‘éĄĩ速åšĶ", "å°ąåĶ‚ä―•æ”đčŋ›éĄĩéĒ提凚åŧščŪŪ"], + "title": "PageSpeed ᛑ视å™Ļį”Ļ䚎", + "actionButton": "čŪĐæˆ‘äŧŽåˆ›åŧšæ‚Ļįš„įŽŽäļ€äļŠ PageSpeed ᛑ视å™Ļ" + } + }, + "uptimeMonitor": { + "fallback": { + "checks": [ + "æĢ€æĩ‹į―‘įŦ™æˆ–æœåŠĄå™Ļæ˜ŊåĶåœĻįšŋ及响嚔æ­Ģåļļ", + "向å›Ēé˜Ÿå‘é€åœæœšæˆ–æ€§čƒ―é—Ūéǘč­ĶæŠĨ", + "į›‘æŽ§ HTTP įŦŊį‚đ、Ping æĩ‹čŊ•、åŪđå™Ļ及įŦŊåĢįŠķ态", + "čŋ―čļŠåކåēčŋčĄŒæ—ķé•ŋäļŽåŊ靠性čķ‹åŠŋ" + ], + "title": "æ­ĢåļļčŋčĄŒæ—ķé—īᛑ视å™Ļį”Ļ䚎", + "actionButton": "čŪĐæˆ‘äŧŽåˆ›åŧšæ‚Ļįš„įŽŽäļ€äļŠæ­ĢåļļčŋčĄŒæ—ķé—īᛑ视å™Ļ" + } + }, + "editUserPage": { + "form": { + "email": "į”ĩ子é‚Ūäŧķ", + "firstName": "名", + "lastName": "姓氏", + "role": "角č‰ē", + "save": "äŋå­˜" + }, + "table": { + "actionHeader": "æ“ä―œ", + "roleHeader": "角č‰ē" + }, + "title": "įž–čū‘į”Ļ户", + "toast": { + "successUserUpdate": "į”Ļ户æ›ī新成功", + "validationErrors": "驌čŊé”™čŊŊ" + } + }, + "incidentsPageActionResolveMonitor": "č§Ģå†ģį›‘æŽ§äš‹äŧķ", + "incidentsPageActionResolveAll": "č§Ģå†ģ所有䚋äŧķ", + "matchMethodOptions": { + "equal": "į›ļį­‰", + "equalPlaceholder": "成功", + "include": "包åŦ", + "includePlaceholder": "OK", + "regex": "æ­Ģ则čĄĻčūū垏", + "regexPlaceholder": "^(success|ok)$", + "text": "åŒđ配æ–đæģ•" + }, + "monitorType": { + "docker": { + "label": "åŪđå™Ļ ID", + "namePlaceholder": "æˆ‘įš„åŪđå™Ļ", + "placeholder": "æˆ‘įš„åš”į”ĻįĻ‹åšæˆ– abcd1234" + }, + "http": { + "label": "čĶį›‘æŽ§įš„ URL", + "namePlaceholder": "Google", + "placeholder": "google.com" + }, + "ping": { + "label": "čĶį›‘æŽ§įš„ IP 地址", + "namePlaceholder": "Google", + "placeholder": "1.1.1.1" + }, + "port": { + "label": "čĶį›‘æŽ§įš„ URL", + "namePlaceholder": "localhost:5173", + "placeholder": "localhost" + }, + "game": { + "label": "äļĒåŒ…į›‘æŽ§į―‘å€", + "namePlaceholder": "localhost:5173", + "placeholder": "localhost" + } + }, + "uptimeAdvancedMatching": { + "jsonPath": "JSON č·Ŋåū„" + }, + "bytesPerSecond": "æŊį§’å­—čŠ‚æ•°", + "bytesReceived": "å·ēæŽĨæ”ķå­—čŠ‚æ•°", + "bytesSent": "å·ēå‘é€å­—čŠ‚æ•°", + "chooseGame": "选æ‹Đæļ￈", + "createMonitorPage": { + "incidentConfigDescription": "æŧ‘åŠĻįŠ—åĢį”Ļ䚎įĄŪåŪšį›‘č§†å™Ļä―•æ—ķå‘į”Ÿæ•…éšœã€‚äŧ…å―“æŧ‘åŠĻįŠ—åĢäļ­įš„æĢ€æŸĨį™ū分æŊ”æŧĄčķ쿌‡åŪšå€žæ—ķïžŒį›‘č§†å™Ļįš„įŠķ态才䞚æ”đ变。", + "incidentConfigStatusWindowLabel": "åœĻæŧ‘åŠĻįŠ—åĢäļ­åš”包åŦåĪšå°‘æŽĄæĢ€æĩ‹ïžŸ", + "incidentConfigStatusWindowThresholdLabel": "åœĻᛑ视å™ĻįŠķ态æ”đ变äđ‹å‰ïžŒæŧ‘åŠĻįŠ—åĢäļ­įš„æĢ€æŸĨåĪąčīĨ/æˆåŠŸįš„į™ū分æŊ”æ˜ŊåĪšå°‘ïžŸ", + "incidentConfigTitle": "之äŧķ" + }, + "dataRate": "数æŪé€ŸįŽ‡", + "dataReceived": "å·ēæŽĨæ”ķ数æŪ", + "dataSent": "å·ē发送数æŪ", + "details": "čŊĶæƒ…", + "drops": "äļĒ包", + "errors": "错čŊŊ", + "errorsIn": "错čŊŊ䚎", + "errorsOut": "错čŊŊčū“凚", + "gameServerMonitoring": "æļ￈æœåŠĄå™Ļᛑ视", + "gameServerMonitoringDescription": "æĢ€æŸĨä― įš„æļ￈æœåŠĄå™Ļæ˜ŊåĶæ­ĢåœĻčŋčĄŒ", + "network": "į―‘įŧœ", + "networkDrops": "į―‘įŧœäļĒ包", + "networkErrors": "į―‘įŧœé”™čŊŊ", + "networkInterface": "į―‘įŧœæŽĨåĢ", + "noNetworkStatsAvailable": "æēĄæœ‰åŊį”Ļįš„į―‘įŧœįŧŸčŪĄæ•°æŪ。", + "packetsPerSecond": "æŊį§’æ•°æŪ包数", + "packetsReceived": "å·ēæŽĨæ”ķ数æŪ包", + "packetsReceivedRate": "数æŪ包æŽĨæ”ķįŽ‡", + "packetsSent": "å·ē发送数æŪ包", + "rate": "速åšĶ", + "selectInterface": "选æ‹ĐæŽĨåĢ" +} diff --git a/client/src/locales/zh-TW.json b/client/src/locales/zh-TW.json index 8e0dc2ed2..e57ebd487 100644 --- a/client/src/locales/zh-TW.json +++ b/client/src/locales/zh-TW.json @@ -380,7 +380,52 @@ "email": "", "role": "", "created": "" - } + }, + "addTeamMember": { + "addMemberMenu": "", + "title": "", + "description": "", + "addButton": "" + }, + "register": "", + "registerToast": { + "success": "", + "dbUserExists": "", + "unknownError": "" + }, + "registerTeamMember": { + "title": "", + "auth": { + "common": { + "inputs": { + "firstName": { + "errors": { + "empty": "", + "pattern": "" + } + }, + "lastName": { + "errors": { + "empty": "", + "pattern": "" + } + }, + "email": { + "errors": { + "empty": "", + "invalid": "" + } + }, + "role": { + "errors": { + "empty": "" + } + } + } + } + } + }, + "role": "" }, "monitorState": { "paused": "", @@ -746,7 +791,8 @@ "failCountHeader": "", "lastRunHeader": "", "lastFinishedAtHeader": "", - "lastRunTookHeader": "" + "lastRunTookHeader": "", + "intervalHeader": "" }, "metricsTable": { "title": "", From d8ed9abebf2c182e99aa309828ade4dc703cebfd Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Mon, 22 Sep 2025 12:53:44 -0700 Subject: [PATCH 015/101] add gitignore --- .gitignore | 3 +- server/.gitignore | 3 +- server/package-lock.json | 530 ++++- server/package.json | 7 +- server/src/config/routes.js | 22 +- .../src/routes/{ => v1}/announcementsRoute.js | 0 server/src/routes/{ => v1}/authRoute.js | 6 +- server/src/routes/{ => v1}/checkRoute.js | 7 +- server/src/routes/{ => v1}/diagnosticRoute.js | 4 +- server/src/routes/{ => v1}/inviteRoute.js | 4 +- server/src/routes/{ => v1}/logRoutes.js | 2 +- .../routes/{ => v1}/maintenanceWindowRoute.js | 2 +- server/src/routes/{ => v1}/monitorRoute.js | 4 +- .../src/routes/{ => v1}/notificationRoute.js | 0 server/src/routes/{ => v1}/queueRoute.js | 2 +- server/src/routes/{ => v1}/settingsRoute.js | 2 +- server/src/routes/{ => v1}/statusPageRoute.js | 2 +- .../tests/controllers/authController.test.js | 962 --------- .../tests/controllers/checkController.test.js | 372 ---- .../tests/controllers/controllerUtils.test.js | 157 -- .../controllers/inviteController.test.js | 202 -- .../maintenanceWindowController.test.js | 422 ---- .../controllers/monitorController.test.js | 1116 ---------- .../tests/controllers/queueController.test.js | 176 -- .../controllers/settingsController.test.js | 109 - .../controllers/statusPageController.test.js | 127 -- server/tests/db/checkModule.test.js | 589 ------ server/tests/db/hardwareCheckModule.test.js | 136 -- server/tests/db/inviteModule.test.js | 103 - .../tests/db/maintenanceWindowModule.test.js | 255 --- server/tests/db/monitorModule.test.js | 1865 ----------------- server/tests/db/notificationModule.test.js | 76 - server/tests/db/pageSpeedCheckModule.test.js | 63 - server/tests/db/recoveryModule.test.js | 168 -- server/tests/db/settingsModule.test.js | 56 - server/tests/db/statusPageModule.test.js | 70 - server/tests/db/userModule.test.js | 294 --- server/tests/services/emailService.test.js | 168 -- server/tests/services/jobQueue.test.js | 813 ------- server/tests/services/networkService.test.js | 439 ---- .../services/notificationService.test.js | 285 --- server/tests/services/settingsService.test.js | 140 -- server/tests/services/statusService.test.js | 255 --- server/tests/utils/dataUtils.test.js | 61 - server/tests/utils/imageProcessing.test.js | 53 - server/tests/utils/logger.test.js | 139 -- server/tests/utils/messages.test.js | 23 - server/tests/utils/utils.test.js | 50 - server/tsconfig.json | 26 + 49 files changed, 590 insertions(+), 9780 deletions(-) rename server/src/routes/{ => v1}/announcementsRoute.js (100%) rename server/src/routes/{ => v1}/authRoute.js (85%) rename server/src/routes/{ => v1}/checkRoute.js (76%) rename server/src/routes/{ => v1}/diagnosticRoute.js (77%) rename server/src/routes/{ => v1}/inviteRoute.js (82%) rename server/src/routes/{ => v1}/logRoutes.js (85%) rename server/src/routes/{ => v1}/maintenanceWindowRoute.js (91%) rename server/src/routes/{ => v1}/monitorRoute.js (94%) rename server/src/routes/{ => v1}/notificationRoute.js (100%) rename server/src/routes/{ => v1}/queueRoute.js (93%) rename server/src/routes/{ => v1}/settingsRoute.js (90%) rename server/src/routes/{ => v1}/statusPageRoute.js (93%) delete mode 100755 server/tests/controllers/authController.test.js delete mode 100755 server/tests/controllers/checkController.test.js delete mode 100755 server/tests/controllers/controllerUtils.test.js delete mode 100755 server/tests/controllers/inviteController.test.js delete mode 100755 server/tests/controllers/maintenanceWindowController.test.js delete mode 100755 server/tests/controllers/monitorController.test.js delete mode 100755 server/tests/controllers/queueController.test.js delete mode 100755 server/tests/controllers/settingsController.test.js delete mode 100755 server/tests/controllers/statusPageController.test.js delete mode 100755 server/tests/db/checkModule.test.js delete mode 100755 server/tests/db/hardwareCheckModule.test.js delete mode 100755 server/tests/db/inviteModule.test.js delete mode 100755 server/tests/db/maintenanceWindowModule.test.js delete mode 100755 server/tests/db/monitorModule.test.js delete mode 100755 server/tests/db/notificationModule.test.js delete mode 100755 server/tests/db/pageSpeedCheckModule.test.js delete mode 100755 server/tests/db/recoveryModule.test.js delete mode 100755 server/tests/db/settingsModule.test.js delete mode 100755 server/tests/db/statusPageModule.test.js delete mode 100755 server/tests/db/userModule.test.js delete mode 100755 server/tests/services/emailService.test.js delete mode 100755 server/tests/services/jobQueue.test.js delete mode 100755 server/tests/services/networkService.test.js delete mode 100755 server/tests/services/notificationService.test.js delete mode 100755 server/tests/services/settingsService.test.js delete mode 100755 server/tests/services/statusService.test.js delete mode 100755 server/tests/utils/dataUtils.test.js delete mode 100755 server/tests/utils/imageProcessing.test.js delete mode 100755 server/tests/utils/logger.test.js delete mode 100755 server/tests/utils/messages.test.js delete mode 100755 server/tests/utils/utils.test.js create mode 100644 server/tsconfig.json diff --git a/.gitignore b/.gitignore index 600d2d33b..172702b40 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ -.vscode \ No newline at end of file +.vscode +.VSCodeCounter \ No newline at end of file diff --git a/server/.gitignore b/server/.gitignore index 4bc621f83..9ae4e58ed 100755 --- a/server/.gitignore +++ b/server/.gitignore @@ -8,4 +8,5 @@ coverage .clinic node_modules .vscode/* -public \ No newline at end of file +public +dist \ No newline at end of file diff --git a/server/package-lock.json b/server/package-lock.json index 3e1130dc4..e2affbca0 100644 --- a/server/package-lock.json +++ b/server/package-lock.json @@ -54,7 +54,8 @@ "mocha": "11.1.0", "nodemon": "3.1.9", "prettier": "^3.3.3", - "sinon": "19.0.2" + "sinon": "19.0.2", + "tsx": "4.20.5" } }, "node_modules/@asamuzakjp/css-color": { @@ -258,6 +259,448 @@ "tslib": "^2.4.0" } }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.10.tgz", + "integrity": "sha512-0NFWnA+7l41irNuaSVlLfgNT12caWJVLzp5eAVhZ0z1qpxbockccEt3s+149rE64VUI3Ml2zt8Nv5JVc4QXTsw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.10.tgz", + "integrity": "sha512-dQAxF1dW1C3zpeCDc5KqIYuZ1tgAdRXNoZP7vkBIRtKZPYe2xVr/d3SkirklCHudW1B45tGiUlz2pUWDfbDD4w==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.10.tgz", + "integrity": "sha512-LSQa7eDahypv/VO6WKohZGPSJDq5OVOo3UoFR1E4t4Gj1W7zEQMUhI+lo81H+DtB+kP+tDgBp+M4oNCwp6kffg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.10.tgz", + "integrity": "sha512-MiC9CWdPrfhibcXwr39p9ha1x0lZJ9KaVfvzA0Wxwz9ETX4v5CHfF09bx935nHlhi+MxhA63dKRRQLiVgSUtEg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.10.tgz", + "integrity": "sha512-JC74bdXcQEpW9KkV326WpZZjLguSZ3DfS8wrrvPMHgQOIEIG/sPXEN/V8IssoJhbefLRcRqw6RQH2NnpdprtMA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.10.tgz", + "integrity": "sha512-tguWg1olF6DGqzws97pKZ8G2L7Ig1vjDmGTwcTuYHbuU6TTjJe5FXbgs5C1BBzHbJ2bo1m3WkQDbWO2PvamRcg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.10.tgz", + "integrity": "sha512-3ZioSQSg1HT2N05YxeJWYR+Libe3bREVSdWhEEgExWaDtyFbbXWb49QgPvFH8u03vUPX10JhJPcz7s9t9+boWg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.10.tgz", + "integrity": "sha512-LLgJfHJk014Aa4anGDbh8bmI5Lk+QidDmGzuC2D+vP7mv/GeSN+H39zOf7pN5N8p059FcOfs2bVlrRr4SK9WxA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.10.tgz", + "integrity": "sha512-oR31GtBTFYCqEBALI9r6WxoU/ZofZl962pouZRTEYECvNF/dtXKku8YXcJkhgK/beU+zedXfIzHijSRapJY3vg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.10.tgz", + "integrity": "sha512-5luJWN6YKBsawd5f9i4+c+geYiVEw20FVW5x0v1kEMWNq8UctFjDiMATBxLvmmHA4bf7F6hTRaJgtghFr9iziQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.10.tgz", + "integrity": "sha512-NrSCx2Kim3EnnWgS4Txn0QGt0Xipoumb6z6sUtl5bOEZIVKhzfyp/Lyw4C1DIYvzeW/5mWYPBFJU3a/8Yr75DQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.10.tgz", + "integrity": "sha512-xoSphrd4AZda8+rUDDfD9J6FUMjrkTz8itpTITM4/xgerAZZcFW7Dv+sun7333IfKxGG8gAq+3NbfEMJfiY+Eg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.10.tgz", + "integrity": "sha512-ab6eiuCwoMmYDyTnyptoKkVS3k8fy/1Uvq7Dj5czXI6DF2GqD2ToInBI0SHOp5/X1BdZ26RKc5+qjQNGRBelRA==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.10.tgz", + "integrity": "sha512-NLinzzOgZQsGpsTkEbdJTCanwA5/wozN9dSgEl12haXJBzMTpssebuXR42bthOF3z7zXFWH1AmvWunUCkBE4EA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.10.tgz", + "integrity": "sha512-FE557XdZDrtX8NMIeA8LBJX3dC2M8VGXwfrQWU7LB5SLOajfJIxmSdyL/gU1m64Zs9CBKvm4UAuBp5aJ8OgnrA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.10.tgz", + "integrity": "sha512-3BBSbgzuB9ajLoVZk0mGu+EHlBwkusRmeNYdqmznmMc9zGASFjSsxgkNsqmXugpPk00gJ0JNKh/97nxmjctdew==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.10.tgz", + "integrity": "sha512-QSX81KhFoZGwenVyPoberggdW1nrQZSvfVDAIUXr3WqLRZGZqWk/P4T8p2SP+de2Sr5HPcvjhcJzEiulKgnxtA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.10.tgz", + "integrity": "sha512-AKQM3gfYfSW8XRk8DdMCzaLUFB15dTrZfnX8WXQoOUpUBQ+NaAFCP1kPS/ykbbGYz7rxn0WS48/81l9hFl3u4A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.10.tgz", + "integrity": "sha512-7RTytDPGU6fek/hWuN9qQpeGPBZFfB4zZgcz2VK2Z5VpdUxEI8JKYsg3JfO0n/Z1E/6l05n0unDCNc4HnhQGig==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.10.tgz", + "integrity": "sha512-5Se0VM9Wtq797YFn+dLimf2Zx6McttsH2olUBsDml+lm0GOCRVebRWUvDtkY4BWYv/3NgzS8b/UM3jQNh5hYyw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.10.tgz", + "integrity": "sha512-XkA4frq1TLj4bEMB+2HnI0+4RnjbuGZfet2gs/LNs5Hc7D89ZQBHQ0gL2ND6Lzu1+QVkjp3x1gIcPKzRNP8bXw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.10.tgz", + "integrity": "sha512-AVTSBhTX8Y/Fz6OmIVBip9tJzZEUcY8WLh7I59+upa5/GPhh2/aM6bvOMQySspnCCHvFi79kMtdJS1w0DXAeag==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.10.tgz", + "integrity": "sha512-fswk3XT0Uf2pGJmOpDB7yknqhVkJQkAQOcW/ccVOtfx05LkbWOaRAtn5SaqXypeKQra1QaEa841PgrSL9ubSPQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.10.tgz", + "integrity": "sha512-ah+9b59KDTSfpaCg6VdJoOQvKjI33nTaQr4UluQwW7aEwZQsbMCfTmfEO4VyewOxx4RaDT/xCy9ra2GPWmO7Kw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.10.tgz", + "integrity": "sha512-QHPDbKkrGO8/cz9LKVnJU22HOi4pxZnZhhA2HYHez5Pz4JeffhDjf85E57Oyco163GnzNCVkZK0b/n4Y0UHcSw==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.10.tgz", + "integrity": "sha512-9KpxSVFCu0iK1owoez6aC/s/EdUQLDN3adTxGCqxMVhrPDj6bt5dbrHDXUuq+Bs2vATFBBrQS5vdQ/Ed2P+nbw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, "node_modules/@eslint-community/eslint-utils": { "version": "4.8.0", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.8.0.tgz", @@ -3163,6 +3606,48 @@ "node": ">= 0.4" } }, + "node_modules/esbuild": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.10.tgz", + "integrity": "sha512-9RiGKvCwaqxO2owP61uQ4BgNborAQskMR6QusfWzQqv7AZOg5oGehdY2pRJMTKuwxd1IDBP4rSbI5lHzU7SMsQ==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.10", + "@esbuild/android-arm": "0.25.10", + "@esbuild/android-arm64": "0.25.10", + "@esbuild/android-x64": "0.25.10", + "@esbuild/darwin-arm64": "0.25.10", + "@esbuild/darwin-x64": "0.25.10", + "@esbuild/freebsd-arm64": "0.25.10", + "@esbuild/freebsd-x64": "0.25.10", + "@esbuild/linux-arm": "0.25.10", + "@esbuild/linux-arm64": "0.25.10", + "@esbuild/linux-ia32": "0.25.10", + "@esbuild/linux-loong64": "0.25.10", + "@esbuild/linux-mips64el": "0.25.10", + "@esbuild/linux-ppc64": "0.25.10", + "@esbuild/linux-riscv64": "0.25.10", + "@esbuild/linux-s390x": "0.25.10", + "@esbuild/linux-x64": "0.25.10", + "@esbuild/netbsd-arm64": "0.25.10", + "@esbuild/netbsd-x64": "0.25.10", + "@esbuild/openbsd-arm64": "0.25.10", + "@esbuild/openbsd-x64": "0.25.10", + "@esbuild/openharmony-arm64": "0.25.10", + "@esbuild/sunos-x64": "0.25.10", + "@esbuild/win32-arm64": "0.25.10", + "@esbuild/win32-ia32": "0.25.10", + "@esbuild/win32-x64": "0.25.10" + } + }, "node_modules/escalade": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", @@ -4079,6 +4564,19 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/get-tsconfig": { + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.10.1.tgz", + "integrity": "sha512-auHyJ4AgMz7vgS8Hp3N6HXSmlMdUyhSUrfBF16w153rxtLIEOE+HGqaBppczZvnHLqQJfiHotCYpNhl0lUROFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, "node_modules/glob": { "version": "10.4.5", "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", @@ -7578,6 +8076,16 @@ "node": ">=4" } }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, "node_modules/responselike": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/responselike/-/responselike-3.0.0.tgz", @@ -8551,6 +9059,26 @@ "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", "license": "0BSD" }, + "node_modules/tsx": { + "version": "4.20.5", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.20.5.tgz", + "integrity": "sha512-+wKjMNU9w/EaQayHXb7WA7ZaHY6hN8WgfvHNQ3t1PnU91/7O8TcTnIhCDYTZwnt8JsO9IBqZ30Ln1r7pPF52Aw==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "~0.25.0", + "get-tsconfig": "^4.7.5" + }, + "bin": { + "tsx": "dist/cli.mjs" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + } + }, "node_modules/tweetnacl": { "version": "0.14.5", "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", diff --git a/server/package.json b/server/package.json index d879776a8..c57c3a4ce 100755 --- a/server/package.json +++ b/server/package.json @@ -6,7 +6,9 @@ "type": "module", "scripts": { "test": "c8 mocha", - "dev": "nodemon src/index.js", + "dev": "nodemon --exec tsx src/index.js", + "start": "nodemon ./dist/index.js", + "build": "tsc", "lint": "eslint .", "lint-fix": "eslint --fix .", "format": "prettier --write .", @@ -61,6 +63,7 @@ "mocha": "11.1.0", "nodemon": "3.1.9", "prettier": "^3.3.3", - "sinon": "19.0.2" + "sinon": "19.0.2", + "tsx": "4.20.5" } } diff --git a/server/src/config/routes.js b/server/src/config/routes.js index 7056393f9..2ec93a2dc 100644 --- a/server/src/config/routes.js +++ b/server/src/config/routes.js @@ -1,17 +1,17 @@ import { verifyJWT } from "../middleware/verifyJWT.js"; import { authApiLimiter } from "../middleware/rateLimiter.js"; -import AuthRoutes from "../routes/authRoute.js"; -import InviteRoutes from "../routes/inviteRoute.js"; -import MonitorRoutes from "../routes/monitorRoute.js"; -import CheckRoutes from "../routes/checkRoute.js"; -import SettingsRoutes from "../routes/settingsRoute.js"; -import MaintenanceWindowRoutes from "../routes/maintenanceWindowRoute.js"; -import StatusPageRoutes from "../routes/statusPageRoute.js"; -import QueueRoutes from "../routes/queueRoute.js"; -import LogRoutes from "../routes/logRoutes.js"; -import DiagnosticRoutes from "../routes/diagnosticRoute.js"; -import NotificationRoutes from "../routes/notificationRoute.js"; +import AuthRoutes from "../routes/v1/authRoute.js"; +import InviteRoutes from "../routes/v1//inviteRoute.js"; +import MonitorRoutes from "../routes/v1/monitorRoute.js"; +import CheckRoutes from "../routes/v1/checkRoute.js"; +import SettingsRoutes from "../routes/v1/settingsRoute.js"; +import MaintenanceWindowRoutes from "../routes/v1/maintenanceWindowRoute.js"; +import StatusPageRoutes from "../routes/v1/statusPageRoute.js"; +import QueueRoutes from "../routes/v1/queueRoute.js"; +import LogRoutes from "../routes/v1/logRoutes.js"; +import DiagnosticRoutes from "../routes/v1//diagnosticRoute.js"; +import NotificationRoutes from "../routes/v1/notificationRoute.js"; export const setupRoutes = (app, controllers) => { const authRoutes = new AuthRoutes(controllers.authController); diff --git a/server/src/routes/announcementsRoute.js b/server/src/routes/v1/announcementsRoute.js similarity index 100% rename from server/src/routes/announcementsRoute.js rename to server/src/routes/v1/announcementsRoute.js diff --git a/server/src/routes/authRoute.js b/server/src/routes/v1/authRoute.js similarity index 85% rename from server/src/routes/authRoute.js rename to server/src/routes/v1/authRoute.js index e790c60b0..f69f94f43 100755 --- a/server/src/routes/authRoute.js +++ b/server/src/routes/v1/authRoute.js @@ -1,9 +1,7 @@ import { Router } from "express"; -import { verifyJWT } from "../middleware/verifyJWT.js"; -import { verifyOwnership } from "../middleware/verifyOwnership.js"; -import { isAllowed } from "../middleware/isAllowed.js"; +import { verifyJWT } from "../../middleware/verifyJWT.js"; +import { isAllowed } from "../../middleware/isAllowed.js"; import multer from "multer"; -import User from "../db/models/User.js"; const upload = multer(); diff --git a/server/src/routes/checkRoute.js b/server/src/routes/v1/checkRoute.js similarity index 76% rename from server/src/routes/checkRoute.js rename to server/src/routes/v1/checkRoute.js index 8bdb2d4fb..673fe51e0 100755 --- a/server/src/routes/checkRoute.js +++ b/server/src/routes/v1/checkRoute.js @@ -1,9 +1,6 @@ import { Router } from "express"; -import { verifyOwnership } from "../middleware/verifyOwnership.js"; -import { verifyTeamAccess } from "../middleware/verifyTeamAccess.js"; -import { isAllowed } from "../middleware/isAllowed.js"; -import Monitor from "../db/models/Monitor.js"; -import Check from "../db/models/Check.js"; + +import { isAllowed } from "../../middleware/isAllowed.js"; class CheckRoutes { constructor(checkController) { diff --git a/server/src/routes/diagnosticRoute.js b/server/src/routes/v1/diagnosticRoute.js similarity index 77% rename from server/src/routes/diagnosticRoute.js rename to server/src/routes/v1/diagnosticRoute.js index a15f5a038..1b7cc06be 100755 --- a/server/src/routes/diagnosticRoute.js +++ b/server/src/routes/v1/diagnosticRoute.js @@ -1,6 +1,6 @@ import { Router } from "express"; -import { verifyJWT } from "../middleware/verifyJWT.js"; -import { isAllowed } from "../middleware/isAllowed.js"; +import { verifyJWT } from "../../middleware/verifyJWT.js"; +import { isAllowed } from "../../middleware/isAllowed.js"; class DiagnosticRoutes { constructor(diagnosticController) { diff --git a/server/src/routes/inviteRoute.js b/server/src/routes/v1/inviteRoute.js similarity index 82% rename from server/src/routes/inviteRoute.js rename to server/src/routes/v1/inviteRoute.js index d495c5476..74fcfeb91 100755 --- a/server/src/routes/inviteRoute.js +++ b/server/src/routes/v1/inviteRoute.js @@ -1,6 +1,6 @@ import { Router } from "express"; -import { verifyJWT } from "../middleware/verifyJWT.js"; -import { isAllowed } from "../middleware/isAllowed.js"; +import { verifyJWT } from "../../middleware/verifyJWT.js"; +import { isAllowed } from "../../middleware/isAllowed.js"; class InviteRoutes { constructor(inviteController) { diff --git a/server/src/routes/logRoutes.js b/server/src/routes/v1/logRoutes.js similarity index 85% rename from server/src/routes/logRoutes.js rename to server/src/routes/v1/logRoutes.js index 867826dde..feabe669a 100755 --- a/server/src/routes/logRoutes.js +++ b/server/src/routes/v1/logRoutes.js @@ -1,5 +1,5 @@ import { Router } from "express"; -import { isAllowed } from "../middleware/isAllowed.js"; +import { isAllowed } from "../../middleware/isAllowed.js"; class LogRoutes { constructor(logController) { this.router = Router(); diff --git a/server/src/routes/maintenanceWindowRoute.js b/server/src/routes/v1/maintenanceWindowRoute.js similarity index 91% rename from server/src/routes/maintenanceWindowRoute.js rename to server/src/routes/v1/maintenanceWindowRoute.js index 774434ed6..622e98f88 100755 --- a/server/src/routes/maintenanceWindowRoute.js +++ b/server/src/routes/v1/maintenanceWindowRoute.js @@ -1,5 +1,5 @@ import { Router } from "express"; -import MaintenanceWindow from "../db/models/MaintenanceWindow.js"; +import MaintenanceWindow from "../../db/models/MaintenanceWindow.js"; class MaintenanceWindowRoutes { constructor(maintenanceWindowController) { this.router = Router(); diff --git a/server/src/routes/monitorRoute.js b/server/src/routes/v1/monitorRoute.js similarity index 94% rename from server/src/routes/monitorRoute.js rename to server/src/routes/v1/monitorRoute.js index 043d361d1..f8f219b83 100755 --- a/server/src/routes/monitorRoute.js +++ b/server/src/routes/v1/monitorRoute.js @@ -1,7 +1,7 @@ import { Router } from "express"; -import { isAllowed } from "../middleware/isAllowed.js"; +import { isAllowed } from "../../middleware/isAllowed.js"; import multer from "multer"; -import { fetchMonitorCertificate } from "../controllers/controllerUtils.js"; +import { fetchMonitorCertificate } from "../../controllers/controllerUtils.js"; const upload = multer({ storage: multer.memoryStorage(), // Store file in memory as Buffer }); diff --git a/server/src/routes/notificationRoute.js b/server/src/routes/v1/notificationRoute.js similarity index 100% rename from server/src/routes/notificationRoute.js rename to server/src/routes/v1/notificationRoute.js diff --git a/server/src/routes/queueRoute.js b/server/src/routes/v1/queueRoute.js similarity index 93% rename from server/src/routes/queueRoute.js rename to server/src/routes/v1/queueRoute.js index 55e829b91..59e1cb744 100755 --- a/server/src/routes/queueRoute.js +++ b/server/src/routes/v1/queueRoute.js @@ -1,5 +1,5 @@ import { Router } from "express"; -import { isAllowed } from "../middleware/isAllowed.js"; +import { isAllowed } from "../../middleware/isAllowed.js"; class QueueRoutes { constructor(queueController) { this.router = Router(); diff --git a/server/src/routes/settingsRoute.js b/server/src/routes/v1/settingsRoute.js similarity index 90% rename from server/src/routes/settingsRoute.js rename to server/src/routes/v1/settingsRoute.js index 88eb67e65..76556e923 100755 --- a/server/src/routes/settingsRoute.js +++ b/server/src/routes/v1/settingsRoute.js @@ -1,5 +1,5 @@ import { Router } from "express"; -import { isAllowed } from "../middleware/isAllowed.js"; +import { isAllowed } from "../../middleware/isAllowed.js"; class SettingsRoutes { constructor(settingsController) { diff --git a/server/src/routes/statusPageRoute.js b/server/src/routes/v1/statusPageRoute.js similarity index 93% rename from server/src/routes/statusPageRoute.js rename to server/src/routes/v1/statusPageRoute.js index 53eb21fba..8300066a8 100755 --- a/server/src/routes/statusPageRoute.js +++ b/server/src/routes/v1/statusPageRoute.js @@ -1,5 +1,5 @@ import { Router } from "express"; -import { verifyJWT } from "../middleware/verifyJWT.js"; +import { verifyJWT } from "../../middleware/verifyJWT.js"; import multer from "multer"; const upload = multer(); diff --git a/server/tests/controllers/authController.test.js b/server/tests/controllers/authController.test.js deleted file mode 100755 index 341c8eda6..000000000 --- a/server/tests/controllers/authController.test.js +++ /dev/null @@ -1,962 +0,0 @@ -import { - issueToken, - registerUser, - loginUser, - editUser, - checkSuperadminExists, - requestRecovery, - validateRecovery, - resetPassword, - deleteUser, - getAllUsers, -} from "../../controllers/authController.js"; -import jwt from "jsonwebtoken"; -import { errorMessages, successMessages } from "../../utils/messages.js"; -import sinon from "sinon"; -import { tokenType } from "../../utils/utils.js"; -import logger from "../../utils/logger.js"; - -describe("Auth Controller - issueToken", function () { - let stub; - - afterEach(function () { - sinon.restore(); // Restore stubs after each test - }); - - it("should reject with an error if jwt.sign fails", function () { - const error = new Error("jwt.sign error"); - stub = sinon.stub(jwt, "sign").throws(error); - const payload = { id: "123" }; - const appSettings = { jwtSecret: "my_secret" }; - expect(() => issueToken(payload, tokenType.ACCESS_TOKEN, appSettings)).to.throw(error); - }); - - it("should return a token if jwt.sign is successful and appSettings.jwtTTL is not defined", function () { - const payload = { id: "123" }; - const appSettings = { jwtSecret: "my_secret" }; - const expectedToken = "mockToken"; - - stub = sinon.stub(jwt, "sign").returns(expectedToken); - const token = issueToken(payload, tokenType.ACCESS_TOKEN, appSettings); - expect(token).to.equal(expectedToken); - }); - - it("should return a token if jwt.sign is successful and appSettings.jwtTTL is defined", function () { - const payload = { id: "123" }; - const appSettings = { jwtSecret: "my_secret", jwtTTL: "1s" }; - const expectedToken = "mockToken"; - - stub = sinon.stub(jwt, "sign").returns(expectedToken); - const token = issueToken(payload, tokenType.ACCESS_TOKEN, appSettings); - expect(token).to.equal(expectedToken); - }); - - it("should return a refresh token if jwt.sign is successful and appSettings.refreshTokenTTL is not defined", function () { - const payload = {}; - const appSettings = { refreshTokenSecret: "my_refresh_secret" }; - const expectedToken = "mockRefreshToken"; - - stub = sinon.stub(jwt, "sign").returns(expectedToken); - const token = issueToken(payload, tokenType.REFRESH_TOKEN, appSettings); - expect(token).to.equal(expectedToken); - }); - - it("should return a refresh token if jwt.sign is successful and appSettings.refreshTokenTTL is defined", function () { - const payload = {}; - const appSettings = { - refreshTokenSecret: "my_refresh_secret", - refreshTokenTTL: "7d", - }; - const expectedToken = "mockRefreshToken"; - - stub = sinon.stub(jwt, "sign").returns(expectedToken); - const token = issueToken(payload, tokenType.REFRESH_TOKEN, appSettings); - expect(token).to.equal(expectedToken); - }); -}); - -describe("Auth Controller - registerUser", function () { - let req, res, next; - - beforeEach(function () { - req = { - body: { - firstName: "firstname", - lastName: "lastname", - email: "test@test.com", - password: "Uptime1!", - role: ["admin"], - teamId: "123", - inviteToken: "invite", - }, - db: { - checkSuperadmin: sinon.stub(), - getInviteTokenAndDelete: sinon.stub(), - updateAppSettings: sinon.stub(), - insertUser: sinon.stub(), - }, - settingsService: { - getSettings: sinon.stub().resolves({ - jwtSecret: "my_secret", - refreshTokenSecret: "my_refresh_secret", - }), - }, - emailService: { - buildAndSendEmail: sinon.stub(), - }, - file: {}, - }; - res = { - status: sinon.stub().returnsThis(), - json: sinon.stub(), - }; - next = sinon.stub(); - sinon.stub(logger, "error"); - }); - - afterEach(function () { - sinon.restore(); - }); - - it("should reject with an error if body validation fails", async function () { - req.body = {}; - await registerUser(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].status).to.equal(422); - }); - - it("should reject with an error if checkSuperadmin fails", async function () { - req.db.checkSuperadmin.throws(new Error("checkSuperadmin error")); - await registerUser(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].message).to.equal("checkSuperadmin error"); - }); - - it("should reject with an error if getInviteTokenAndDelete fails", async function () { - req.db.checkSuperadmin.returns(true); - req.db.getInviteTokenAndDelete.throws(new Error("getInviteTokenAndDelete error")); - await registerUser(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].message).to.equal("getInviteTokenAndDelete error"); - }); - - it("should reject with an error if updateAppSettings fails", async function () { - req.db.checkSuperadmin.returns(false); - req.db.updateAppSettings.throws(new Error("updateAppSettings error")); - await registerUser(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].message).to.equal("updateAppSettings error"); - }); - - it("should reject with an error if insertUser fails", async function () { - req.db.checkSuperadmin.resolves(false); - req.db.updateAppSettings.resolves(); - req.db.insertUser.rejects(new Error("insertUser error")); - await registerUser(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].message).to.equal("insertUser error"); - }); - - it("should reject with an error if settingsService.getSettings fails", async function () { - req.db.checkSuperadmin.resolves(false); - req.db.updateAppSettings.resolves(); - req.db.insertUser.resolves({ _id: "123" }); - req.settingsService.getSettings.rejects(new Error("settingsService.getSettings error")); - await registerUser(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].message).to.equal("settingsService.getSettings error"); - }); - - it("should log an error if emailService.buildAndSendEmail fails", async function () { - req.db.checkSuperadmin.resolves(false); - req.db.updateAppSettings.resolves(); - req.db.insertUser.returns({ _id: "123" }); - req.settingsService.getSettings.returns({ - jwtSecret: "my_secret", - refreshTokenSecret: "my_secret", - }); - req.emailService.buildAndSendEmail.rejects(new Error("emailService error")); - await registerUser(req, res, next); - expect(logger.error.calledOnce).to.be.true; - expect(logger.error.firstCall.args[0].message).to.equal("emailService error"); - }); - - it("should return a success message and data if all operations are successful", async function () { - const user = { _id: "123" }; - req.db.checkSuperadmin.resolves(false); - req.db.updateAppSettings.resolves(); - req.db.insertUser.returns(user); - req.settingsService.getSettings.returns({ - jwtSecret: "my_secret", - refreshTokenSecret: "my_secret", - }); - req.emailService.buildAndSendEmail.resolves("message-id"); - await registerUser(req, res, next); - expect(res.status.calledWith(200)).to.be.true; - expect( - res.json.calledWith({ - success: true, - msg: successMessages.AUTH_CREATE_USER, - data: { user, token: sinon.match.string, refreshToken: sinon.match.string }, - }) - ).to.be.true; - expect(next.notCalled).to.be.true; - }); - - it("should return a success message and data if all operations are successful and superAdmin true", async function () { - const user = { _id: "123" }; - req.db.checkSuperadmin.resolves(true); - req.db.updateAppSettings.resolves(); - req.db.insertUser.returns(user); - req.settingsService.getSettings.returns({ - jwtSecret: "my_secret", - refreshTokenSecret: "my_secret", - }); - req.emailService.buildAndSendEmail.resolves("message-id"); - await registerUser(req, res, next); - expect(res.status.calledWith(200)).to.be.true; - expect( - res.json.calledWith({ - success: true, - msg: successMessages.AUTH_CREATE_USER, - data: { user, token: sinon.match.string, refreshToken: sinon.match.string }, - }) - ).to.be.true; - expect(next.notCalled).to.be.true; - }); -}); - -describe("Auth Controller - loginUser", function () { - let req, res, next, user; - - beforeEach(function () { - req = { - body: { email: "test@example.com", password: "Password123!" }, - db: { - getUserByEmail: sinon.stub(), - }, - settingsService: { - getSettings: sinon.stub().resolves({ - jwtSecret: "my_secret", - refreshTokenSecret: "my_refresh_token", - }), - }, - }; - res = { - status: sinon.stub().returnsThis(), - json: sinon.stub(), - }; - next = sinon.stub(); - user = { - _doc: { - email: "test@example.com", - }, - comparePassword: sinon.stub(), - }; - }); - - it("should reject with an error if validation fails", async function () { - req.body = {}; - await loginUser(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].status).to.equal(422); - }); - - it("should reject with an error if getUserByEmail fails", async function () { - req.db.getUserByEmail.rejects(new Error("getUserByEmail error")); - await loginUser(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].message).to.equal("getUserByEmail error"); - }); - - it("should login user successfully", async function () { - req.db.getUserByEmail.resolves(user); - user.comparePassword.resolves(true); - await loginUser(req, res, next); - expect(res.status.calledWith(200)).to.be.true; - expect( - res.json.calledWith({ - success: true, - msg: successMessages.AUTH_LOGIN_USER, - data: { - user: { - email: "test@example.com", - avatarImage: undefined, - }, - token: sinon.match.string, - refreshToken: sinon.match.string, - }, - }) - ).to.be.true; - expect(next.notCalled).to.be.true; - }); - - it("should reject a user with an incorrect password", async function () { - req.body = { - email: "test@test.com", - password: "Password123!", - }; - req.db.getUserByEmail.resolves(user); - user.comparePassword.resolves(false); - await loginUser(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].message).to.equal(errorMessages.AUTH_INCORRECT_PASSWORD); - }); -}); - -describe("Auth Controller - refreshAuthToken", function () { - let req, res, next, issueTokenStub; - - beforeEach(function () { - req = { - headers: { - "x-refresh-token": "valid_refresh_token", - authorization: "Bearer old_auth_token", - }, - settingsService: { - getSettings: sinon.stub().resolves({ - jwtSecret: "my_secret", - refreshTokenSecret: "my_refresh_secret", - }), - }, - }; - res = { - status: sinon.stub().returnsThis(), - json: sinon.stub(), - }; - next = sinon.stub(); - sinon.stub(jwt, "verify"); - - issueTokenStub = sinon.stub().returns("new_auth_token"); - sinon.replace({ issueToken }, "issueToken", issueTokenStub); - }); - - afterEach(function () { - sinon.restore(); - }); - - it("should reject if no refresh token is provided", async function () { - delete req.headers["x-refresh-token"]; - await refreshAuthToken(req, res, next); - - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].message).to.equal(errorMessages.NO_REFRESH_TOKEN); - expect(next.firstCall.args[0].status).to.equal(401); - }); - - it("should reject if the refresh token is invalid", async function () { - jwt.verify.yields(new Error("invalid token")); - await refreshAuthToken(req, res, next); - - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].message).to.equal(errorMessages.INVALID_REFRESH_TOKEN); - expect(next.firstCall.args[0].status).to.equal(401); - }); - - it("should reject if the refresh token is expired", async function () { - const error = new Error("Token expired"); - error.name = "TokenExpiredError"; - jwt.verify.yields(error); - await refreshAuthToken(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].message).to.equal(errorMessages.EXPIRED_REFRESH_TOKEN); - expect(next.firstCall.args[0].status).to.equal(401); - }); - - it("should reject if settingsService.getSettings fails", async function () { - req.settingsService.getSettings.rejects(new Error("settingsService.getSettings error")); - await refreshAuthToken(req, res, next); - - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].message).to.equal("settingsService.getSettings error"); - }); - - it("should generate a new auth token if the refresh token is valid", async function () { - const decodedPayload = { expiresIn: "60" }; - jwt.verify.callsFake(() => { - return decodedPayload; - }); - await refreshAuthToken(req, res, next); - - expect(res.status.calledWith(200)).to.be.true; - expect( - res.json.calledWith({ - success: true, - msg: successMessages.AUTH_TOKEN_REFRESHED, - data: { - user: decodedPayload, - token: sinon.match.string, - refreshToken: "valid_refresh_token", - }, - }) - ).to.be.true; - }); -}); - -describe("Auth Controller - editUser", function () { - let req, res, next, stub, user; - - beforeEach(function () { - req = { - params: { userId: "123" }, - body: { password: "Password1!", newPassword: "Password2!" }, - headers: { authorization: "Bearer token" }, - user: { _id: "123" }, - settingsService: { - getSettings: sinon.stub().returns({ jwtSecret: "my_secret" }), - }, - db: { - getUserByEmail: sinon.stub(), - updateUser: sinon.stub(), - }, - }; - res = { - status: sinon.stub().returnsThis(), - json: sinon.stub(), - }; - next = sinon.stub(); - stub = sinon.stub(jwt, "verify").returns({ email: "test@example.com" }); - }); - - afterEach(function () { - sinon.restore(); - stub.restore(); - }); - - it("should reject with an error if param validation fails", async function () { - req.params = {}; - await editUser(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].status).to.equal(422); - }); - - it("should reject with an error if body validation fails", async function () { - req.body = { invalid: 1 }; - await editUser(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].status).to.equal(422); - }); - - it("should reject with an error if param.userId !== req.user._id", async function () { - req.params = { userId: "456" }; - await editUser(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].status).to.equal(401); - }); - - it("should reject with an error if !req.body.password and getUserByEmail fails", async function () { - req.db.getUserByEmail.rejects(new Error("getUserByEmail error")); - await editUser(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].message).to.equal("getUserByEmail error"); - }); - - it("should reject with an error if user.comparePassword fails", async function () { - req.db.getUserByEmail.returns({ - comparePassword: sinon.stub().rejects(new Error("Bad Password Match")), - }); - await editUser(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].message).to.equal("Bad Password Match"); - }); - - it("should reject with an error if user.comparePassword returns false", async function () { - req.db.getUserByEmail.returns({ - comparePassword: sinon.stub().returns(false), - }); - await editUser(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].status).to.equal(401); - expect(next.firstCall.args[0].message).to.equal(errorMessages.AUTH_INCORRECT_PASSWORD); - }); - - it("should edit a user if it receives a proper request", async function () { - const user = { - comparePassword: sinon.stub().resolves(true), - }; - req.db.getUserByEmail.resolves(user); - req.db.updateUser.resolves({ email: "test@example.com" }); - - await editUser(req, res, next); - - expect(req.db.getUserByEmail.calledOnce).to.be.true; - expect(req.db.updateUser.calledOnce).to.be.true; - expect(res.status.calledWith(200)).to.be.true; - expect( - res.json.calledWith({ - success: true, - msg: successMessages.AUTH_UPDATE_USER, - data: { email: "test@example.com" }, - }) - ).to.be.true; - expect(next.notCalled).to.be.true; - }); - - it("should edit a user if it receives a proper request and both password fields are undefined", async function () { - req.body.password = undefined; - req.body.newPassword = undefined; - req.db.getUserByEmail.resolves(user); - req.db.updateUser.resolves({ email: "test@example.com" }); - - await editUser(req, res, next); - expect(res.status.calledWith(200)).to.be.true; - expect( - res.json.calledWith({ - success: true, - msg: successMessages.AUTH_UPDATE_USER, - data: { email: "test@example.com" }, - }) - ).to.be.true; - expect(next.notCalled).to.be.true; - }); - - it("should reject an edit request if password format is incorrect", async function () { - req.body = { password: "bad_password", newPassword: "bad_password" }; - const user = { - comparePassword: sinon.stub().resolves(true), - }; - req.db.getUserByEmail.resolves(user); - - await editUser(req, res, next); - - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].status).to.equal(422); - }); -}); - -describe("Auth Controller - checkSuperadminExists", function () { - let req, res, next; - - beforeEach(function () { - req = { - db: { - checkSuperadmin: sinon.stub(), - }, - }; - res = { - status: sinon.stub().returnsThis(), - json: sinon.stub(), - }; - next = sinon.stub(); - }); - - it("should reject with an error if checkSuperadmin fails", async function () { - req.db.checkSuperadmin.rejects(new Error("checkSuperadmin error")); - await checkSuperadminExists(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].message).to.equal("checkSuperadmin error"); - }); - - it("should return true if a superadmin exists", async function () { - req.db.checkSuperadmin.resolves(true); - await checkSuperadminExists(req, res, next); - expect(res.status.calledWith(200)).to.be.true; - expect( - res.json.calledWith({ - success: true, - msg: successMessages.AUTH_SUPERADMIN_EXISTS, - data: true, - }) - ).to.be.true; - expect(next.notCalled).to.be.true; - }); - - it("should return false if a superadmin does not exist", async function () { - req.db.checkSuperadmin.resolves(false); - await checkSuperadminExists(req, res, next); - expect(res.status.calledWith(200)).to.be.true; - expect( - res.json.calledWith({ - success: true, - msg: successMessages.AUTH_SUPERADMIN_EXISTS, - data: false, - }) - ).to.be.true; - expect(next.notCalled).to.be.true; - }); -}); - -describe("Auth Controller - requestRecovery", function () { - let req, res, next; - - beforeEach(function () { - req = { - body: { email: "test@test.com" }, - db: { - getUserByEmail: sinon.stub(), - requestRecoveryToken: sinon.stub(), - }, - settingsService: { - getSettings: sinon.stub().returns({ clientHost: "http://localhost" }), - }, - emailService: { - buildAndSendEmail: sinon.stub(), - }, - }; - res = { - status: sinon.stub().returnsThis(), - json: sinon.stub(), - }; - next = sinon.stub(); - }); - - it("should reject with an error if validation fails", async function () { - req.body = {}; - await requestRecovery(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].status).to.equal(422); - }); - - it("should reject with an error if getUserByEmail fails", async function () { - req.db.getUserByEmail.rejects(new Error("getUserByEmail error")); - await requestRecovery(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].message).to.equal("getUserByEmail error"); - }); - - it("should throw an error if the user is not found", async function () { - req.db.getUserByEmail.resolves(null); - await requestRecovery(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - // expect(next.firstCall.args[0].message).to.equal( - // errorMessages.FRIENDLY_ERROR - // ); - }); - - it("should throw an error if the email is not provided", async function () { - req.body = {}; - await requestRecovery(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].status).to.equal(422); - }); - - it("should return a success message if the email is provided", async function () { - const user = { firstName: "John" }; - const recoveryToken = { token: "recovery-token" }; - const msgId = "message-id"; - req.db.getUserByEmail.resolves(user); - req.db.requestRecoveryToken.resolves(recoveryToken); - req.emailService.buildAndSendEmail.resolves(msgId); - await requestRecovery(req, res, next); - expect(req.db.getUserByEmail.calledOnceWith("test@test.com")).to.be.true; - expect(req.db.requestRecoveryToken.calledOnceWith(req, res)).to.be.true; - expect( - req.emailService.buildAndSendEmail.calledOnceWith( - "passwordResetTemplate", - { - name: "John", - email: "test@test.com", - url: "http://localhost/set-new-password/recovery-token", - }, - "test@test.com", - "Checkmate Password Reset" - ) - ).to.be.true; - expect(res.status.calledOnceWith(200)).to.be.true; - expect( - res.json.calledOnceWith({ - success: true, - msg: successMessages.AUTH_CREATE_RECOVERY_TOKEN, - data: msgId, - }) - ).to.be.true; - expect(next.notCalled).to.be.true; - }); -}); - -describe("Auth Controller - validateRecovery", function () { - let req, res, next; - - beforeEach(function () { - req = { - body: { recoveryToken: "recovery-token" }, - db: { - validateRecoveryToken: sinon.stub(), - }, - }; - res = { - status: sinon.stub().returnsThis(), - json: sinon.stub(), - }; - next = sinon.stub(); - }); - - it("should reject with an error if validation fails", async function () { - req.body = {}; - await validateRecovery(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].status).to.equal(422); - }); - - it("should reject with an error if validateRecoveryToken fails", async function () { - req.db.validateRecoveryToken.rejects(new Error("validateRecoveryToken error")); - await validateRecovery(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].message).to.equal("validateRecoveryToken error"); - }); - - it("should return a success message if the token is valid", async function () { - req.db.validateRecoveryToken.resolves(); - await validateRecovery(req, res, next); - expect(res.status.calledOnceWith(200)).to.be.true; - expect( - res.json.calledOnceWith({ - success: true, - msg: successMessages.AUTH_VERIFY_RECOVERY_TOKEN, - }) - ).to.be.true; - expect(next.notCalled).to.be.true; - }); -}); - -describe("Auth Controller - resetPassword", function () { - let req, res, next, newPasswordValidation, handleValidationError, handleError; - - beforeEach(function () { - req = { - body: { - recoveryToken: "recovery-token", - password: "Password1!", - }, - db: { - resetPassword: sinon.stub(), - }, - settingsService: { - getSettings: sinon.stub(), - }, - }; - res = { - status: sinon.stub().returnsThis(), - json: sinon.stub(), - }; - next = sinon.stub(); - newPasswordValidation = { - validateAsync: sinon.stub(), - }; - handleValidationError = sinon.stub(); - handleError = sinon.stub(); - }); - - it("should reject with an error if validation fails", async function () { - req.body = { password: "bad_password" }; - await resetPassword(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].status).to.equal(422); - }); - - it("should reject with an error if resetPassword fails", async function () { - const error = new Error("resetPassword error"); - newPasswordValidation.validateAsync.resolves(); - req.db.resetPassword.rejects(error); - await resetPassword(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].message).to.equal("resetPassword error"); - }); - - it("should reset password successfully", async function () { - const user = { _doc: {} }; - const appSettings = { jwtSecret: "my_secret" }; - const token = "token"; - - newPasswordValidation.validateAsync.resolves(); - req.db.resetPassword.resolves(user); - req.settingsService.getSettings.resolves(appSettings); - - await resetPassword(req, res, next); - - expect(req.db.resetPassword.calledOnceWith(req, res)).to.be.true; - expect(req.settingsService.getSettings.calledOnce).to.be.true; - expect(res.status.calledOnceWith(200)).to.be.true; - expect( - res.json.calledOnceWith({ - success: true, - msg: successMessages.AUTH_RESET_PASSWORD, - data: { user: sinon.match.object, token: sinon.match.string }, - }) - ).to.be.true; - expect(next.notCalled).to.be.true; - }); -}); - -describe("Auth Controller - deleteUser", function () { - let req, res, next, handleError; - - beforeEach(function () { - req = { - headers: { - authorization: "Bearer token", - }, - db: { - getUserByEmail: sinon.stub(), - getMonitorsByTeamId: sinon.stub(), - deleteJob: sinon.stub(), - deleteChecks: sinon.stub(), - deletePageSpeedChecksByMonitorId: sinon.stub(), - deleteNotificationsByMonitorId: sinon.stub(), - deleteTeam: sinon.stub(), - deleteAllOtherUsers: sinon.stub(), - deleteMonitorsByUserId: sinon.stub(), - deleteUser: sinon.stub(), - }, - jobQueue: { - deleteJob: sinon.stub(), - }, - }; - res = { - status: sinon.stub().returnsThis(), - json: sinon.stub(), - }; - next = sinon.stub(); - - sinon.stub(jwt, "decode"); - - handleError = sinon.stub(); - }); - - afterEach(function () { - sinon.restore(); - }); - - it("should throw an error if user is not found", async function () { - jwt.decode.returns({ email: "test@example.com" }); - req.db.getUserByEmail.throws(new Error(errorMessages.DB_USER_NOT_FOUND)); - - await deleteUser(req, res, next); - - expect(req.db.getUserByEmail.calledOnceWith("test@example.com")).to.be.true; - expect(next.calledOnce).to.be.true; - expect(next.firstCall.args[0].message).to.equal(errorMessages.DB_USER_NOT_FOUND); - expect(res.status.notCalled).to.be.true; - expect(res.json.notCalled).to.be.true; - }); - - it("should delete user and associated data if user is superadmin", async function () { - const user = { - _id: "user_id", - email: "test@example.com", - role: ["superadmin"], - teamId: "team_id", - }; - const monitors = [{ _id: "monitor_id" }]; - - jwt.decode.returns({ email: "test@example.com" }); - req.db.getUserByEmail.resolves(user); - req.db.getMonitorsByTeamId.resolves({ monitors }); - - await deleteUser(req, res, next); - - expect(req.db.getUserByEmail.calledOnceWith("test@example.com")).to.be.true; - expect( - req.db.getMonitorsByTeamId.calledOnceWith({ - params: { teamId: "team_id" }, - }) - ).to.be.true; - expect(req.jobQueue.deleteJob.calledOnceWith(monitors[0])).to.be.true; - expect(req.db.deleteChecks.calledOnceWith("monitor_id")).to.be.true; - expect(req.db.deletePageSpeedChecksByMonitorId.calledOnceWith("monitor_id")).to.be.true; - expect(req.db.deleteNotificationsByMonitorId.calledOnceWith("monitor_id")).to.be.true; - expect(req.db.deleteTeam.calledOnceWith("team_id")).to.be.true; - expect(req.db.deleteAllOtherUsers.calledOnce).to.be.true; - expect(req.db.deleteMonitorsByUserId.calledOnceWith("user_id")).to.be.true; - expect(req.db.deleteUser.calledOnceWith("user_id")).to.be.true; - expect(res.status.calledOnceWith(200)).to.be.true; - expect( - res.json.calledOnceWith({ - success: true, - msg: successMessages.AUTH_DELETE_USER, - }) - ).to.be.true; - expect(next.notCalled).to.be.true; - }); - - it("should delete user if user is not superadmin", async function () { - const user = { - _id: "user_id", - email: "test@example.com", - role: ["user"], - teamId: "team_id", - }; - - jwt.decode.returns({ email: "test@example.com" }); - req.db.getUserByEmail.resolves(user); - - await deleteUser(req, res, next); - - expect(req.db.getUserByEmail.calledOnceWith("test@example.com")).to.be.true; - expect( - req.db.getMonitorsByTeamId.calledOnceWith({ - params: { teamId: "team_id" }, - }) - ).to.be.true; - expect(req.db.deleteUser.calledOnceWith("user_id")).to.be.true; - expect(res.status.calledOnceWith(200)).to.be.true; - expect( - res.json.calledOnceWith({ - success: true, - msg: successMessages.AUTH_DELETE_USER, - }) - ).to.be.true; - expect(next.notCalled).to.be.true; - }); - - it("should handle errors", async function () { - const error = new Error("Something went wrong"); - const SERVICE_NAME = "AuthController"; - jwt.decode.returns({ email: "test@example.com" }); - req.db.getUserByEmail.rejects(error); - await deleteUser(req, res, next); - expect(next.calledOnce).to.be.true; - expect(next.firstCall.args[0].message).to.equal("Something went wrong"); - expect(res.status.notCalled).to.be.true; - expect(res.json.notCalled).to.be.true; - }); -}); - -describe("Auth Controller - getAllUsers", function () { - let req, res, next; - - beforeEach(function () { - req = { - db: { - getAllUsers: sinon.stub(), - }, - }; - res = { - status: sinon.stub().returnsThis(), - json: sinon.stub(), - }; - next = sinon.stub(); - }); - - afterEach(function () { - sinon.restore(); // Restore the original methods after each test - }); - - it("should return 200 and all users", async function () { - const allUsers = [{ id: 1, name: "John Doe" }]; - req.db.getAllUsers.resolves(allUsers); - - await getAllUsers(req, res, next); - - expect(req.db.getAllUsers.calledOnce).to.be.true; - expect(res.status.calledOnceWith(200)).to.be.true; - expect( - res.json.calledOnceWith({ - success: true, - msg: "Got all users", - data: allUsers, - }) - ).to.be.true; - expect(next.notCalled).to.be.true; - }); - - it("should call next with error when an exception occurs", async function () { - const error = new Error("Something went wrong"); - req.db.getAllUsers.rejects(error); - await getAllUsers(req, res, next); - expect(req.db.getAllUsers.calledOnce).to.be.true; - expect(next.calledOnce).to.be.true; - expect(res.status.notCalled).to.be.true; - expect(res.json.notCalled).to.be.true; - }); -}); diff --git a/server/tests/controllers/checkController.test.js b/server/tests/controllers/checkController.test.js deleted file mode 100755 index c5fb57d50..000000000 --- a/server/tests/controllers/checkController.test.js +++ /dev/null @@ -1,372 +0,0 @@ -import { createCheck, getChecks, getTeamChecks, deleteChecks, deleteChecksByTeamId, updateChecksTTL } from "../../controllers/checkController.js"; -import jwt from "jsonwebtoken"; -import { errorMessages, successMessages } from "../../utils/messages.js"; -import sinon from "sinon"; -describe("Check Controller - createCheck", function () { - let req, res, next, handleError; - - beforeEach(function () { - req = { - params: {}, - body: {}, - db: { - createCheck: sinon.stub(), - }, - }; - res = { - status: sinon.stub().returnsThis(), - json: sinon.stub(), - }; - next = sinon.stub(); - handleError = sinon.stub(); - }); - - afterEach(function () { - sinon.restore(); // Restore the original methods after each test - }); - - it("should reject with a validation if params are invalid", async function () { - await createCheck(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].status).to.equal(422); - }); - - it("should reject with a validation error if body is invalid", async function () { - req.params = { - monitorId: "monitorId", - }; - await createCheck(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].status).to.equal(422); - }); - - it("should call next with error if data retrieval fails", async function () { - req.params = { - monitorId: "monitorId", - }; - req.body = { - monitorId: "monitorId", - status: true, - responseTime: 100, - statusCode: 200, - message: "message", - }; - req.db.createCheck.rejects(new Error("error")); - await createCheck(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - }); - - it("should return a success message if check is created", async function () { - req.params = { - monitorId: "monitorId", - }; - req.db.createCheck.resolves({ id: "123" }); - req.body = { - monitorId: "monitorId", - status: true, - responseTime: 100, - statusCode: 200, - message: "message", - }; - await createCheck(req, res, next); - expect(res.status.calledWith(200)).to.be.true; - expect( - res.json.calledWith({ - success: true, - msg: successMessages.CHECK_CREATE, - data: { id: "123" }, - }) - ).to.be.true; - expect(next.notCalled).to.be.true; - }); -}); - -describe("Check Controller - getChecks", function () { - let req, res, next; - - beforeEach(function () { - req = { - params: {}, - query: {}, - db: { - getChecks: sinon.stub(), - getChecksCount: sinon.stub(), - }, - }; - res = { - status: sinon.stub().returnsThis(), - json: sinon.stub(), - }; - next = sinon.stub(); - }); - - afterEach(function () { - sinon.restore(); - }); - - it("should reject with a validation error if params are invalid", async function () { - await getChecks(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].status).to.equal(422); - }); - - it("should return a success message if checks are found", async function () { - req.params = { - monitorId: "monitorId", - }; - req.db.getChecks.resolves([{ id: "123" }]); - req.db.getChecksCount.resolves(1); - await getChecks(req, res, next); - expect(res.status.calledWith(200)).to.be.true; - expect( - res.json.calledWith({ - success: true, - msg: successMessages.CHECK_GET, - data: { checksCount: 1, checks: [{ id: "123" }] }, - }) - ).to.be.true; - expect(next.notCalled).to.be.true; - }); - - it("should call next with error if data retrieval fails", async function () { - req.params = { - monitorId: "monitorId", - }; - req.db.getChecks.rejects(new Error("error")); - await getChecks(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - }); -}); - -describe("Check Controller - getTeamChecks", function () { - let req, res, next; - - beforeEach(function () { - req = { - params: {}, - query: {}, - db: { - getTeamChecks: sinon.stub(), - }, - }; - res = { - status: sinon.stub().returnsThis(), - json: sinon.stub(), - }; - next = sinon.stub(); - }); - - afterEach(function () { - sinon.restore(); - }); - - it("should reject with a validation error if params are invalid", async function () { - await getTeamChecks(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].status).to.equal(422); - }); - - it("should return 200 and check data on successful validation and data retrieval", async function () { - req.params = { teamId: "1" }; - const checkData = [{ id: 1, name: "Check 1" }]; - req.db.getTeamChecks.resolves(checkData); - - await getTeamChecks(req, res, next); - expect(req.db.getTeamChecks.calledOnceWith(req)).to.be.true; - expect(res.status.calledOnceWith(200)).to.be.true; - expect( - res.json.calledOnceWith({ - success: true, - msg: successMessages.CHECK_GET, - data: checkData, - }) - ).to.be.true; - }); - - it("should call next with error if data retrieval fails", async function () { - req.params = { teamId: "1" }; - req.db.getTeamChecks.rejects(new Error("Retrieval Error")); - await getTeamChecks(req, res, next); - expect(req.db.getTeamChecks.calledOnceWith(req)).to.be.true; - expect(next.firstCall.args[0]).to.be.an("error"); - expect(res.status.notCalled).to.be.true; - expect(res.json.notCalled).to.be.true; - }); -}); - -describe("Check Controller - deleteChecks", function () { - let req, res, next; - - beforeEach(function () { - req = { - params: {}, - db: { - deleteChecks: sinon.stub(), - }, - }; - res = { - status: sinon.stub().returnsThis(), - json: sinon.stub(), - }; - next = sinon.stub(); - }); - - afterEach(function () { - sinon.restore(); - }); - - it("should reject with an error if param validation fails", async function () { - await deleteChecks(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].status).to.equal(422); - }); - - it("should call next with error if data retrieval fails", async function () { - req.params = { monitorId: "1" }; - req.db.deleteChecks.rejects(new Error("Deletion Error")); - await deleteChecks(req, res, next); - expect(req.db.deleteChecks.calledOnceWith(req.params.monitorId)).to.be.true; - expect(next.firstCall.args[0]).to.be.an("error"); - expect(res.status.notCalled).to.be.true; - expect(res.json.notCalled).to.be.true; - }); - - it("should delete checks successfully", async function () { - req.params = { monitorId: "123" }; - req.db.deleteChecks.resolves(1); - await deleteChecks(req, res, next); - expect(req.db.deleteChecks.calledOnceWith(req.params.monitorId)).to.be.true; - expect(res.status.calledOnceWith(200)).to.be.true; - expect( - res.json.calledOnceWith({ - success: true, - msg: successMessages.CHECK_DELETE, - data: { deletedCount: 1 }, - }) - ).to.be.true; - }); -}); - -describe("Check Controller - deleteChecksByTeamId", function () { - let req, res, next; - - beforeEach(function () { - req = { - params: {}, - db: { - deleteChecksByTeamId: sinon.stub(), - }, - }; - res = { - status: sinon.stub().returnsThis(), - json: sinon.stub(), - }; - next = sinon.stub(); - }); - - afterEach(function () { - sinon.restore(); - }); - - it("should reject with an error if param validation fails", async function () { - await deleteChecksByTeamId(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].status).to.equal(422); - }); - - it("should call next with error if data retrieval fails", async function () { - req.params = { teamId: "1" }; - req.db.deleteChecksByTeamId.rejects(new Error("Deletion Error")); - await deleteChecksByTeamId(req, res, next); - expect(req.db.deleteChecksByTeamId.calledOnceWith(req.params.teamId)).to.be.true; - expect(next.firstCall.args[0]).to.be.an("error"); - expect(res.status.notCalled).to.be.true; - expect(res.json.notCalled).to.be.true; - }); - - it("should delete checks successfully", async function () { - req.params = { teamId: "123" }; - req.db.deleteChecksByTeamId.resolves(1); - await deleteChecksByTeamId(req, res, next); - expect(req.db.deleteChecksByTeamId.calledOnceWith(req.params.teamId)).to.be.true; - expect(res.status.calledOnceWith(200)).to.be.true; - expect( - res.json.calledOnceWith({ - success: true, - msg: successMessages.CHECK_DELETE, - data: { deletedCount: 1 }, - }) - ).to.be.true; - }); -}); - -describe("Check Controller - updateCheckTTL", function () { - let stub, req, res, next; - - beforeEach(function () { - stub = sinon.stub(jwt, "verify").callsFake(() => { - return { teamId: "123" }; - }); - - req = { - body: {}, - headers: { authorization: "Bearer token" }, - settingsService: { - getSettings: sinon.stub().returns({ jwtSecret: "my_secret" }), - }, - db: { - updateChecksTTL: sinon.stub(), - }, - }; - res = { - status: sinon.stub().returnsThis(), - json: sinon.stub(), - }; - next = sinon.stub(); - }); - - afterEach(function () { - sinon.restore(); - stub.restore(); - }); - - it("should reject if body validation fails", async function () { - await updateChecksTTL(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].status).to.equal(422); - }); - - it("should throw a JwtError if verification fails", async function () { - stub.restore(); - req.body = { - ttl: 1, - }; - await updateChecksTTL(req, res, next); - expect(next.firstCall.args[0]).to.be.instanceOf(jwt.JsonWebTokenError); - }); - - it("should call next with error if data retrieval fails", async function () { - req.body = { - ttl: 1, - }; - req.db.updateChecksTTL.rejects(new Error("Update Error")); - await updateChecksTTL(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - }); - - it("should update TTL successfully", async function () { - req.body = { - ttl: 1, - }; - req.db.updateChecksTTL.resolves(); - await updateChecksTTL(req, res, next); - expect(req.db.updateChecksTTL.calledOnceWith("123", 1 * 86400)).to.be.true; - expect(res.status.calledOnceWith(200)).to.be.true; - expect( - res.json.calledOnceWith({ - success: true, - msg: successMessages.CHECK_UPDATE_TTL, - }) - ).to.be.true; - }); -}); diff --git a/server/tests/controllers/controllerUtils.test.js b/server/tests/controllers/controllerUtils.test.js deleted file mode 100755 index a55318857..000000000 --- a/server/tests/controllers/controllerUtils.test.js +++ /dev/null @@ -1,157 +0,0 @@ -import sinon from "sinon"; - -import { handleValidationError, handleError, fetchMonitorCertificate } from "../../controllers/controllerUtils.js"; -import { expect } from "chai"; -import sslChecker from "ssl-checker"; -import { afterEach } from "node:test"; -import exp from "constants"; - -describe("controllerUtils - handleValidationError", function () { - it("should set status to 422", function () { - const error = {}; - const serviceName = "TestService"; - const result = handleValidationError(error, serviceName); - expect(result.status).to.equal(422); - }); - - it("should set service to the provided serviceName", function () { - const error = {}; - const serviceName = "TestService"; - const result = handleValidationError(error, serviceName); - expect(result.service).to.equal(serviceName); - }); - - it("should set message to error.details[0].message if present", function () { - const error = { - details: [{ message: "Detail message" }], - }; - const serviceName = "TestService"; - const result = handleValidationError(error, serviceName); - expect(result.message).to.equal("Detail message"); - }); - - it("should set message to error.message if error.details is not present", function () { - const error = { - message: "Error message", - }; - const serviceName = "TestService"; - const result = handleValidationError(error, serviceName); - expect(result.message).to.equal("Error message"); - }); - - it('should set message to "Validation Error" if neither error.details nor error.message is present', function () { - const error = {}; - const serviceName = "TestService"; - const result = handleValidationError(error, serviceName); - expect(result.message).to.equal("Validation Error"); - }); -}); - -describe("controllerUtils - handleError", function () { - it("should set stats to the provided status if error.code is undefined", function () { - const error = {}; - const serviceName = "TestService"; - const method = "testMethod"; - const status = 400; - const result = handleError(error, serviceName, method, status); - expect(result.status).to.equal(status); - }); - - it("should not overwrite error.code if it is already defined", function () { - const error = { status: 404 }; - const serviceName = "TestService"; - const method = "testMethod"; - const status = 400; - const result = handleError(error, serviceName, method, status); - expect(result.status).to.equal(404); - }); - - it("should set service to the provided serviceName if error.service is undefined", function () { - const error = {}; - const serviceName = "TestService"; - const method = "testMethod"; - const result = handleError(error, serviceName, method); - expect(result.service).to.equal(serviceName); - }); - - it("should not overwrite error.service if it is already defined", function () { - const error = { service: "ExistingService" }; - const serviceName = "TestService"; - const method = "testMethod"; - const result = handleError(error, serviceName, method); - expect(result.service).to.equal("ExistingService"); - }); - - it("should set method to the provided method if error.method is undefined", function () { - const error = {}; - const serviceName = "TestService"; - const method = "testMethod"; - const result = handleError(error, serviceName, method); - expect(result.method).to.equal(method); - }); - - it("should not overwrite error.method if it is already defined", function () { - const error = { method: "existingMethod" }; - const serviceName = "TestService"; - const method = "testMethod"; - const result = handleError(error, serviceName, method); - expect(result.method).to.equal("existingMethod"); - }); - - it("should set code to 500 if error.code is undefined and no code is provided", function () { - const error = {}; - const serviceName = "TestService"; - const method = "testMethod"; - const result = handleError(error, serviceName, method); - expect(result.status).to.equal(500); - }); -}); - -describe("controllerUtils - fetchMonitorCertificate", function () { - let sslChecker, monitor; - - beforeEach(function () { - monitor = { - url: "https://www.google.com", - }; - sslChecker = sinon.stub(); - }); - - afterEach(() => { - sinon.restore(); - }); - - it("should reject with an error if a URL does not parse", async function () { - monitor.url = "invalidurl"; - try { - await fetchMonitorCertificate(sslChecker, monitor); - } catch (error) { - expect(error).to.be.an("error"); - expect(error.message).to.equal("Invalid URL"); - } - }); - - it("should reject with an error if sslChecker throws an error", async function () { - sslChecker.rejects(new Error("Test error")); - try { - await fetchMonitorCertificate(sslChecker, monitor); - } catch (error) { - expect(error).to.be.an("error"); - expect(error.message).to.equal("Test error"); - } - }); - - it("should return a certificate if sslChecker resolves", async function () { - sslChecker.resolves({ validTo: "2022-01-01" }); - const result = await fetchMonitorCertificate(sslChecker, monitor); - expect(result).to.deep.equal({ validTo: "2022-01-01" }); - }); - - it("should throw an error if a ssl-checker returns null", async function () { - sslChecker.returns(null); - await fetchMonitorCertificate(sslChecker, monitor).catch((error) => { - expect(error).to.be.an("error"); - expect(error.message).to.equal("Certificate not found"); - }); - }); -}); diff --git a/server/tests/controllers/inviteController.test.js b/server/tests/controllers/inviteController.test.js deleted file mode 100755 index 635bdf653..000000000 --- a/server/tests/controllers/inviteController.test.js +++ /dev/null @@ -1,202 +0,0 @@ -import { issueInvitation, inviteVerifyController } from "../../controllers/inviteController.js"; -import jwt from "jsonwebtoken"; -import sinon from "sinon"; -import joi from "joi"; -describe("inviteController - issueInvitation", function () { - let req, res, next, stub; - - beforeEach(function () { - req = { - headers: { authorization: "Bearer token" }, - body: { - email: "test@test.com", - role: ["admin"], - teamId: "123", - }, - db: { requestInviteToken: sinon.stub() }, - settingsService: { getSettings: sinon.stub() }, - emailService: { buildAndSendEmail: sinon.stub() }, - }; - res = { - status: sinon.stub().returnsThis(), - json: sinon.stub(), - }; - next = sinon.stub(); - }); - - afterEach(function () { - sinon.restore(); - }); - - it("should reject with an error if role validation fails", async function () { - stub = sinon.stub(jwt, "decode").callsFake(() => { - return { role: ["bad_role"], firstname: "first_name", teamId: "1" }; - }); - await issueInvitation(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0]).to.be.instanceOf(joi.ValidationError); - expect(next.firstCall.args[0].status).to.equal(422); - stub.restore(); - }); - - it("should reject with an error if body validation fails", async function () { - stub = sinon.stub(jwt, "decode").callsFake(() => { - return { role: ["admin"], firstname: "first_name", teamId: "1" }; - }); - req.body = {}; - await issueInvitation(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].status).to.equal(422); - stub.restore(); - }); - - it("should reject with an error if DB operations fail", async function () { - stub = sinon.stub(jwt, "decode").callsFake(() => { - return { role: ["admin"], firstname: "first_name", teamId: "1" }; - }); - req.db.requestInviteToken.throws(new Error("DB error")); - await issueInvitation(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].message).to.equal("DB error"); - stub.restore(); - }); - - it("should send an invite successfully", async function () { - const token = "token"; - const decodedToken = { - role: "admin", - firstname: "John", - teamId: "team123", - }; - const inviteToken = { token: "inviteToken" }; - const clientHost = "http://localhost"; - - stub = sinon.stub(jwt, "decode").callsFake(() => { - return decodedToken; - }); - req.db.requestInviteToken.resolves(inviteToken); - req.settingsService.getSettings.returns({ clientHost }); - req.emailService.buildAndSendEmail.resolves(); - await issueInvitation(req, res, next); - expect(res.status.calledWith(200)).to.be.true; - expect( - res.json.calledWith({ - success: true, - msg: "Invite sent", - data: inviteToken, - }) - ).to.be.true; - stub.restore(); - }); - - it("should send an email successfully", async function () { - const token = "token"; - const decodedToken = { - role: "admin", - firstname: "John", - teamId: "team123", - }; - const inviteToken = { token: "inviteToken" }; - const clientHost = "http://localhost"; - - stub = sinon.stub(jwt, "decode").callsFake(() => { - return decodedToken; - }); - req.db.requestInviteToken.resolves(inviteToken); - req.settingsService.getSettings.returns({ clientHost }); - req.emailService.buildAndSendEmail.resolves(); - - await issueInvitation(req, res, next); - expect(req.emailService.buildAndSendEmail.calledOnce).to.be.true; - expect( - req.emailService.buildAndSendEmail.calledWith( - "employeeActivationTemplate", - { - name: "John", - link: "http://localhost/register/inviteToken", - }, - "test@test.com", - "Welcome to Uptime Monitor" - ) - ).to.be.true; - stub.restore(); - }); - - it("should continue executing if sending an email fails", async function () { - const token = "token"; - req.emailService.buildAndSendEmail.rejects(new Error("Email error")); - const decodedToken = { - role: "admin", - firstname: "John", - teamId: "team123", - }; - const inviteToken = { token: "inviteToken" }; - const clientHost = "http://localhost"; - - stub = sinon.stub(jwt, "decode").callsFake(() => { - return decodedToken; - }); - req.db.requestInviteToken.resolves(inviteToken); - req.settingsService.getSettings.returns({ clientHost }); - await issueInvitation(req, res, next); - expect(res.status.calledWith(200)).to.be.true; - expect( - res.json.calledWith({ - success: true, - msg: "Invite sent", - data: inviteToken, - }) - ).to.be.true; - stub.restore(); - }); -}); - -describe("inviteController - inviteVerifyController", function () { - let req, res, next; - - beforeEach(function () { - req = { - body: { token: "token" }, - db: { - getInviteToken: sinon.stub(), - }, - }; - res = { - status: sinon.stub().returnsThis(), - json: sinon.stub(), - }; - next = sinon.stub(); - }); - - afterEach(function () { - sinon.restore(); - }); - - it("should reject with an error if body validation fails", async function () { - req.body = {}; - await inviteVerifyController(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].status).to.equal(422); - }); - - it("should reject with an error if DB operations fail", async function () { - req.db.getInviteToken.throws(new Error("DB error")); - await inviteVerifyController(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].message).to.equal("DB error"); - }); - - it("should return 200 and invite data when validation and invite retrieval are successful", async function () { - req.db.getInviteToken.resolves({ invite: "data" }); - await inviteVerifyController(req, res, next); - expect(res.status.calledWith(200)).to.be.true; - expect( - res.json.calledWith({ - status: "success", - msg: "Invite verified", - data: { invite: "data" }, - }) - ).to.be.true; - expect(next.called).to.be.false; - }); -}); diff --git a/server/tests/controllers/maintenanceWindowController.test.js b/server/tests/controllers/maintenanceWindowController.test.js deleted file mode 100755 index 0ea07b389..000000000 --- a/server/tests/controllers/maintenanceWindowController.test.js +++ /dev/null @@ -1,422 +0,0 @@ -import { - createMaintenanceWindows, - getMaintenanceWindowById, - getMaintenanceWindowsByTeamId, - getMaintenanceWindowsByMonitorId, - deleteMaintenanceWindow, - editMaintenanceWindow, -} from "../../controllers/maintenanceWindowController.js"; - -import jwt from "jsonwebtoken"; -import { successMessages } from "../../utils/messages.js"; -import sinon from "sinon"; - -describe("maintenanceWindowController - createMaintenanceWindows", function () { - let req, res, next, stub; - - beforeEach(function () { - req = { - body: { - monitors: ["66ff52e7c5911c61698ac724"], - name: "window", - active: true, - start: "2024-10-11T05:27:13.747Z", - end: "2024-10-11T05:27:14.747Z", - repeat: "123", - }, - headers: { - authorization: "Bearer token", - }, - settingsService: { - getSettings: sinon.stub().returns({ jwtSecret: "jwtSecret" }), - }, - db: { - createMaintenanceWindow: sinon.stub(), - }, - }; - res = { - status: sinon.stub().returnsThis(), - json: sinon.stub(), - }; - next = sinon.stub(); - }); - - afterEach(function () { - sinon.restore(); - }); - - it("should reject with an error if body validation fails", async function () { - stub = sinon.stub(jwt, "verify").callsFake(() => { - return { teamId: "123" }; - }); - req.body = {}; - await createMaintenanceWindows(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].status).to.equal(422); - stub.restore(); - }); - - it("should reject with an error if jwt.verify fails", async function () { - stub = sinon.stub(jwt, "verify").throws(new jwt.JsonWebTokenError()); - await createMaintenanceWindows(req, res, next); - expect(next.firstCall.args[0]).to.be.instanceOf(jwt.JsonWebTokenError); - stub.restore(); - }); - - it("should reject with an error DB operations fail", async function () { - stub = sinon.stub(jwt, "verify").callsFake(() => { - return { teamId: "123" }; - }); - req.db.createMaintenanceWindow.throws(new Error("DB error")); - await createMaintenanceWindows(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].message).to.equal("DB error"); - stub.restore(); - }); - - it("should return success message if all operations are successful", async function () { - stub = sinon.stub(jwt, "verify").callsFake(() => { - return { teamId: "123" }; - }); - await createMaintenanceWindows(req, res, next); - expect(res.status.firstCall.args[0]).to.equal(201); - expect( - res.json.calledOnceWith({ - success: true, - msg: successMessages.MAINTENANCE_WINDOW_CREATE, - }) - ).to.be.true; - stub.restore(); - }); - - it("should return success message if all operations are successful with active set to undefined", async function () { - req.body.active = undefined; - stub = sinon.stub(jwt, "verify").callsFake(() => { - return { teamId: "123" }; - }); - await createMaintenanceWindows(req, res, next); - expect(res.status.firstCall.args[0]).to.equal(201); - expect( - res.json.calledOnceWith({ - success: true, - msg: successMessages.MAINTENANCE_WINDOW_CREATE, - }) - ).to.be.true; - stub.restore(); - }); -}); - -describe("maintenanceWindowController - getMaintenanceWindowById", function () { - let req, res, next; - - beforeEach(function () { - req = { - body: {}, - params: { - id: "123", - }, - headers: { - authorization: "Bearer token", - }, - settingsService: { - getSettings: sinon.stub().returns({ jwtSecret: "jwtSecret" }), - }, - db: { - getMaintenanceWindowById: sinon.stub(), - }, - }; - res = { - status: sinon.stub().returnsThis(), - json: sinon.stub(), - }; - next = sinon.stub(); - }); - - it("should reject if param validation fails", async function () { - req.params = {}; - await getMaintenanceWindowById(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].status).to.equal(422); - }); - - it("should reject if DB operations fail", async function () { - req.db.getMaintenanceWindowById.throws(new Error("DB error")); - await getMaintenanceWindowById(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].message).to.equal("DB error"); - }); - - it("should return success message with data if all operations are successful", async function () { - req.db.getMaintenanceWindowById.returns({ id: "123" }); - await getMaintenanceWindowById(req, res, next); - expect(res.status.firstCall.args[0]).to.equal(200); - expect( - res.json.calledOnceWith({ - success: true, - msg: successMessages.MAINTENANCE_WINDOW_GET_BY_ID, - data: { id: "123" }, - }) - ).to.be.true; - }); -}); - -describe("maintenanceWindowController - getMaintenanceWindowsByTeamId", function () { - let req, res, next, stub; - - beforeEach(function () { - req = { - body: {}, - params: {}, - query: {}, - headers: { - authorization: "Bearer token", - }, - settingsService: { - getSettings: sinon.stub().returns({ jwtSecret: "jwtSecret" }), - }, - db: { - getMaintenanceWindowsByTeamId: sinon.stub(), - }, - }; - res = { - status: sinon.stub().returnsThis(), - json: sinon.stub(), - }; - next = sinon.stub(); - }); - - it("should reject if query validation fails", async function () { - req.query = { - invalid: 1, - }; - await getMaintenanceWindowsByTeamId(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].status).to.equal(422); - }); - - it("should reject if jwt.verify fails", async function () { - stub = sinon.stub(jwt, "verify").throws(new jwt.JsonWebTokenError()); - await getMaintenanceWindowsByTeamId(req, res, next); - expect(next.firstCall.args[0]).to.be.instanceOf(jwt.JsonWebTokenError); - stub.restore(); - }); - - it("should reject with an error if DB operations fail", async function () { - stub = sinon.stub(jwt, "verify").callsFake(() => { - return { teamId: "123" }; - }); - req.db.getMaintenanceWindowsByTeamId.throws(new Error("DB error")); - await getMaintenanceWindowsByTeamId(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].message).to.equal("DB error"); - stub.restore(); - }); - - it("should return success message with data if all operations are successful", async function () { - stub = sinon.stub(jwt, "verify").callsFake(() => { - return { teamId: "123" }; - }); - req.db.getMaintenanceWindowsByTeamId.returns([{ id: "123" }]); - await getMaintenanceWindowsByTeamId(req, res, next); - expect(res.status.firstCall.args[0]).to.equal(200); - expect( - res.json.calledOnceWith({ - success: true, - msg: successMessages.MAINTENANCE_WINDOW_GET_BY_TEAM, - data: [{ id: jwt.verify().teamId }], - }) - ).to.be.true; - stub.restore(); - }); -}); - -describe("maintenanceWindowController - getMaintenanceWindowsByMonitorId", function () { - let req, res, next; - - beforeEach(function () { - req = { - body: {}, - params: { - monitorId: "123", - }, - query: {}, - headers: { - authorization: "Bearer token", - }, - settingsService: { - getSettings: sinon.stub().returns({ jwtSecret: "jwtSecret" }), - }, - db: { - getMaintenanceWindowsByMonitorId: sinon.stub(), - }, - }; - res = { - status: sinon.stub().returnsThis(), - json: sinon.stub(), - }; - next = sinon.stub(); - }); - - afterEach(function () { - sinon.restore(); - }); - - it("should reject if param validation fails", async function () { - req.params = {}; - await getMaintenanceWindowsByMonitorId(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].status).to.equal(422); - }); - - it("should reject with an error if DB operations fail", async function () { - req.db.getMaintenanceWindowsByMonitorId.throws(new Error("DB error")); - await getMaintenanceWindowsByMonitorId(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].message).to.equal("DB error"); - }); - - it("should return success message with data if all operations are successful", async function () { - const data = [{ monitorId: "123" }]; - req.db.getMaintenanceWindowsByMonitorId.returns(data); - await getMaintenanceWindowsByMonitorId(req, res, next); - expect(req.db.getMaintenanceWindowsByMonitorId.calledOnceWith(req.params.monitorId)); - expect(res.status.firstCall.args[0]).to.equal(200); - expect( - res.json.calledOnceWith({ - success: true, - msg: successMessages.MAINTENANCE_WINDOW_GET_BY_MONITOR, - data: data, - }) - ).to.be.true; - }); -}); - -describe("maintenanceWindowController - deleteMaintenanceWindow", function () { - let req, res, next; - - beforeEach(function () { - req = { - body: {}, - params: { - id: "123", - }, - query: {}, - headers: { - authorization: "Bearer token", - }, - settingsService: { - getSettings: sinon.stub().returns({ jwtSecret: "jwtSecret" }), - }, - db: { - deleteMaintenanceWindowById: sinon.stub(), - }, - }; - res = { - status: sinon.stub().returnsThis(), - json: sinon.stub(), - }; - next = sinon.stub(); - }); - - afterEach(function () { - sinon.restore(); - }); - - it("should reject if param validation fails", async function () { - req.params = {}; - await deleteMaintenanceWindow(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].status).to.equal(422); - }); - - it("should reject with an error if DB operations fail", async function () { - req.db.deleteMaintenanceWindowById.throws(new Error("DB error")); - await deleteMaintenanceWindow(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].message).to.equal("DB error"); - }); - - it("should return success message if all operations are successful", async function () { - await deleteMaintenanceWindow(req, res, next); - expect(req.db.deleteMaintenanceWindowById.calledOnceWith(req.params.id)); - expect(res.status.firstCall.args[0]).to.equal(200); - expect( - res.json.calledOnceWith({ - success: true, - msg: successMessages.MAINTENANCE_WINDOW_DELETE, - }) - ).to.be.true; - }); -}); - -describe("maintenanceWindowController - editMaintenanceWindow", function () { - let req, res, next; - - beforeEach(function () { - req = { - body: { - active: true, - name: "test", - }, - params: { - id: "123", - }, - query: {}, - headers: { - authorization: "Bearer token", - }, - settingsService: { - getSettings: sinon.stub().returns({ jwtSecret: "jwtSecret" }), - }, - db: { - editMaintenanceWindowById: sinon.stub(), - }, - }; - res = { - status: sinon.stub().returnsThis(), - json: sinon.stub(), - }; - next = sinon.stub(); - }); - - afterEach(function () { - sinon.restore(); - }); - - it("should reject if param validation fails", async function () { - req.params = {}; - await editMaintenanceWindow(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].status).to.equal(422); - }); - - it("should reject if body validation fails", async function () { - req.body = { invalid: 1 }; - await editMaintenanceWindow(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].status).to.equal(422); - }); - - it("should reject with an error if DB operations fail", async function () { - req.db.editMaintenanceWindowById.throws(new Error("DB error")); - await editMaintenanceWindow(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].message).to.equal("DB error"); - }); - - it("should return success message with data if all operations are successful", async function () { - const data = { id: "123" }; - req.db.editMaintenanceWindowById.returns(data); - - await editMaintenanceWindow(req, res, next); - expect(req.db.editMaintenanceWindowById.calledOnceWith(req.params.id, req.body)); - expect(res.status.firstCall.args[0]).to.equal(200); - expect( - res.json.calledOnceWith({ - success: true, - msg: successMessages.MAINTENANCE_WINDOW_EDIT, - data: data, - }) - ).to.be.true; - }); -}); diff --git a/server/tests/controllers/monitorController.test.js b/server/tests/controllers/monitorController.test.js deleted file mode 100755 index 963862579..000000000 --- a/server/tests/controllers/monitorController.test.js +++ /dev/null @@ -1,1116 +0,0 @@ -import { - getAllMonitors, - getAllMonitorsWithUptimeStats, - getMonitorStatsById, - getMonitorCertificate, - getMonitorById, - getMonitorsAndSummaryByTeamId, - getMonitorsByTeamId, - createMonitor, - checkEndpointResolution, - deleteMonitor, - deleteAllMonitors, - editMonitor, - pauseMonitor, - addDemoMonitors, -} from "../../controllers/monitorController.js"; -import jwt from "jsonwebtoken"; -import sinon from "sinon"; -import { successMessages } from "../../utils/messages.js"; -import logger from "../../utils/logger.js"; -import axios from "axios"; -const SERVICE_NAME = "monitorController"; - -describe("Monitor Controller - getAllMonitors", function () { - let req, res, next; - - beforeEach(function () { - req = { - params: {}, - query: {}, - body: {}, - db: { - getAllMonitors: sinon.stub(), - }, - }; - res = { - status: sinon.stub().returnsThis(), - json: sinon.stub(), - }; - next = sinon.stub(); - }); - - afterEach(function () { - sinon.restore(); - }); - - it("should reject with an error if DB operations fail", async function () { - req.db.getAllMonitors.throws(new Error("DB error")); - await getAllMonitors(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].message).to.equal("DB error"); - }); - - it("should return success message and data if all operations succeed", async function () { - const data = [{ monitor: "data" }]; - req.db.getAllMonitors.returns(data); - await getAllMonitors(req, res, next); - expect(res.status.firstCall.args[0]).to.equal(200); - expect( - res.json.calledOnceWith({ - success: true, - msg: successMessages.MONITOR_GET_ALL, - data: data, - }) - ).to.be.true; - }); -}); -describe("Monitor Controller - getAllMonitorsWithUptimeStats", function () { - let req, res, next; - - beforeEach(function () { - req = { - params: {}, - query: {}, - body: {}, - db: { - getAllMonitorsWithUptimeStats: sinon.stub(), - }, - }; - res = { - status: sinon.stub().returnsThis(), - json: sinon.stub(), - }; - next = sinon.stub(); - }); - - afterEach(function () { - sinon.restore(); - }); - - it("should reject with an error if DB operations fail", async function () { - req.db.getAllMonitorsWithUptimeStats.throws(new Error("DB error")); - await getAllMonitorsWithUptimeStats(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].message).to.equal("DB error"); - }); - - it("should return success message and data if all operations succeed", async function () { - const data = [{ monitor: "data" }]; - req.db.getAllMonitorsWithUptimeStats.returns(data); - await getAllMonitorsWithUptimeStats(req, res, next); - expect(res.status.firstCall.args[0]).to.equal(200); - expect( - res.json.calledOnceWith({ - success: true, - msg: successMessages.MONITOR_GET_ALL, - data: data, - }) - ).to.be.true; - }); -}); - -describe("Monitor Controller - getMonitorStatsById", function () { - let req, res, next; - - beforeEach(function () { - req = { - params: { - monitorId: "123", - }, - query: {}, - body: {}, - db: { - getMonitorStatsById: sinon.stub(), - }, - }; - res = { - status: sinon.stub().returnsThis(), - json: sinon.stub(), - }; - next = sinon.stub(); - }); - - afterEach(function () { - sinon.restore(); - }); - - it("should reject with an error if param validation fails", async function () { - req.params = {}; - await getMonitorStatsById(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].status).to.equal(422); - }); - - it("should reject with an error if query validation fails", async function () { - req.query = { invalid: 1 }; - await getMonitorStatsById(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].status).to.equal(422); - }); - - it("should reject with an error if DB operations fail", async function () { - req.db.getMonitorStatsById.throws(new Error("DB error")); - await getMonitorStatsById(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].message).to.equal("DB error"); - }); - - it("should return success message and data if all operations succeed", async function () { - const data = [{ monitorStats: "data" }]; - req.db.getMonitorStatsById.returns(data); - await getMonitorStatsById(req, res, next); - expect(res.status.firstCall.args[0]).to.equal(200); - expect( - res.json.calledOnceWith({ - success: true, - msg: successMessages.MONITOR_STATS_BY_ID, - data: data, - }) - ).to.be.true; - }); -}); - -describe("Monitor Controller - getMonitorCertificate", function () { - let req, res, next, fetchMonitorCertificate; - - beforeEach(function () { - req = { - params: { - monitorId: "123", - }, - query: {}, - body: {}, - db: { - getMonitorById: sinon.stub(), - }, - }; - res = { - status: sinon.stub().returnsThis(), - json: sinon.stub(), - }; - next = sinon.stub(); - fetchMonitorCertificate = sinon.stub(); - }); - - afterEach(function () { - sinon.restore(); - }); - - it("should reject with an error if param validation fails", async function () { - req.params = {}; - await getMonitorCertificate(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].status).to.equal(422); - }); - - it("should reject with an error if getMonitorById operation fails", async function () { - req.db.getMonitorById.throws(new Error("DB error")); - await getMonitorCertificate(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].message).to.equal("DB error"); - }); - - it("should return success message and data if all operations succeed with a valid cert", async function () { - req.db.getMonitorById.returns({ url: "https://www.google.com" }); - const data = { certificate: "cert", validTo: "2024/08/08" }; - fetchMonitorCertificate.returns(data); - await getMonitorCertificate(req, res, next, fetchMonitorCertificate); - expect(res.status.firstCall.args[0]).to.equal(200); - expect( - res.json.calledOnceWith({ - success: true, - msg: successMessages.MONITOR_CERTIFICATE, - data: { certificateDate: new Date(data.validTo) }, - }) - ).to.be.true; - }); - - it("should return an error if fetchMonitorCertificate fails", async function () { - req.db.getMonitorById.returns({ url: "https://www.google.com" }); - fetchMonitorCertificate.throws(new Error("Certificate error")); - await getMonitorCertificate(req, res, next, fetchMonitorCertificate); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].message).to.equal("Certificate error"); - }); -}); - -describe("Monitor Controller - getMonitorById", function () { - let req, res, next; - - beforeEach(function () { - req = { - params: { - monitorId: "123", - }, - query: {}, - body: {}, - db: { - getMonitorById: sinon.stub(), - }, - }; - res = { - status: sinon.stub().returnsThis(), - json: sinon.stub(), - }; - next = sinon.stub(); - }); - - afterEach(function () { - sinon.restore(); - }); - - it("should reject with an error if param validation fails", async function () { - req.params = {}; - await getMonitorById(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].status).to.equal(422); - }); - - it("should reject with an error if query param validation fails", async function () { - req.query = { invalid: 1 }; - await getMonitorById(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].status).to.equal(422); - }); - - it("should reject with an error if DB operations fail", async function () { - req.db.getMonitorById.throws(new Error("DB error")); - await getMonitorById(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].message).to.equal("DB error"); - }); - - it("should return 404 if a monitor is not found", async function () { - const error = new Error("Monitor not found"); - error.status = 404; - req.db.getMonitorById.throws(error); - await getMonitorById(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].status).to.equal(404); - }); - - it("should return success message and data if all operations succeed", async function () { - const data = { monitor: "data" }; - req.db.getMonitorById.returns(data); - await getMonitorById(req, res, next); - expect(res.status.firstCall.args[0]).to.equal(200); - expect( - res.json.calledOnceWith({ - success: true, - msg: successMessages.MONITOR_GET_BY_ID, - data: data, - }) - ).to.be.true; - }); -}); - -describe("Monitor Controller - getMonitorsAndSummaryByTeamId", function () { - let req, res, next; - - beforeEach(function () { - req = { - params: { - teamId: "123", - }, - query: {}, - body: {}, - db: { - getMonitorsAndSummaryByTeamId: sinon.stub(), - }, - }; - res = { - status: sinon.stub().returnsThis(), - json: sinon.stub(), - }; - next = sinon.stub(); - }); - - afterEach(function () { - sinon.restore(); - }); - - it("should reject with an error if param validation fails", async function () { - req.params = {}; - await getMonitorsAndSummaryByTeamId(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].status).to.equal(422); - }); - - it("should reject with an error if query validation fails", async function () { - req.query = { invalid: 1 }; - await getMonitorsAndSummaryByTeamId(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].status).to.equal(422); - }); - - it("should reject with an error if DB operations fail", async function () { - req.db.getMonitorsAndSummaryByTeamId.throws(new Error("DB error")); - await getMonitorsAndSummaryByTeamId(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].message).to.equal("DB error"); - }); - - it("should return success message and data if all operations succeed", async function () { - const data = { monitors: "data", summary: "data" }; - req.db.getMonitorsAndSummaryByTeamId.returns(data); - await getMonitorsAndSummaryByTeamId(req, res, next); - expect(res.status.firstCall.args[0]).to.equal(200); - expect( - res.json.calledOnceWith({ - success: true, - msg: successMessages.MONITOR_GET_BY_USER_ID(req.params.teamId), - data: data, - }) - ).to.be.true; - }); -}); - -describe("Monitor Controller - getMonitorsByTeamId", function () { - let req, res, next; - - beforeEach(function () { - req = { - params: { - teamId: "123", - }, - query: {}, - body: {}, - db: { - getMonitorsByTeamId: sinon.stub(), - }, - }; - res = { - status: sinon.stub().returnsThis(), - json: sinon.stub(), - }; - next = sinon.stub(); - }); - - afterEach(function () { - sinon.restore(); - }); - - it("should reject with an error if param validation fails", async function () { - req.params = {}; - await getMonitorsByTeamId(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].status).to.equal(422); - }); - - it("should reject with an error if query validation fails", async function () { - req.query = { invalid: 1 }; - await getMonitorsByTeamId(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].status).to.equal(422); - }); - - it("should reject with an error if DB operations fail", async function () { - req.db.getMonitorsByTeamId.throws(new Error("DB error")); - await getMonitorsByTeamId(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].message).to.equal("DB error"); - }); - - it("should return success message and data if all operations succeed", async function () { - const data = { monitors: "data" }; - req.db.getMonitorsByTeamId.returns(data); - await getMonitorsByTeamId(req, res, next); - expect(res.status.firstCall.args[0]).to.equal(200); - expect( - res.json.calledOnceWith({ - success: true, - msg: successMessages.MONITOR_GET_BY_USER_ID(req.params.teamId), - data: data, - }) - ).to.be.true; - }); -}); - -describe("Monitor Controller - createMonitor", function () { - let req, res, next; - - beforeEach(function () { - req = { - params: {}, - query: {}, - body: { - userId: "123", - teamId: "123", - name: "test_monitor", - description: "test_monitor_desc", - type: "http", - url: "https://example.com", - notifications: [{ email: "example@example.com" }], - }, - db: { - createMonitor: sinon.stub(), - createNotification: sinon.stub(), - }, - jobQueue: { - addJob: sinon.stub(), - }, - }; - res = { - status: sinon.stub().returnsThis(), - json: sinon.stub(), - }; - next = sinon.stub(); - }); - - afterEach(function () { - sinon.restore(); - }); - - it("should reject with an error if body validation fails", async function () { - req.body = {}; - await createMonitor(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].status).to.equal(422); - }); - - it("should reject with an error if DB createMonitor operation fail", async function () { - req.db.createMonitor.throws(new Error("DB error")); - await createMonitor(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].message).to.equal("DB error"); - }); - - it("should reject with an error if DB createNotification operation fail", async function () { - req.db.createNotification.throws(new Error("DB error")); - req.db.createMonitor.returns({ _id: "123" }); - await createMonitor(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].message).to.equal("DB error"); - }); - - it("should reject with an error if monitor.save operation fail", async function () { - req.db.createMonitor.returns({ - _id: "123", - save: sinon.stub().throws(new Error("Monitor save error")), - }); - await createMonitor(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].message).to.equal("Monitor save error"); - }); - - it("should throw an error if addJob operation fails", async function () { - req.db.createMonitor.returns({ _id: "123", save: sinon.stub() }); - req.jobQueue.addJob.throws(new Error("Job error")); - await createMonitor(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].message).to.equal("Job error"); - }); - - it("should return success message and data if all operations succeed", async function () { - const monitor = { _id: "123", save: sinon.stub() }; - req.db.createMonitor.returns(monitor); - await createMonitor(req, res, next); - expect(res.status.firstCall.args[0]).to.equal(201); - expect( - res.json.calledOnceWith({ - success: true, - msg: successMessages.MONITOR_CREATE, - data: monitor, - }) - ).to.be.true; - }); -}); - -describe("Monitor Controller - checkEndpointResolution", function () { - let req, res, next, axiosGetStub; - - beforeEach(function () { - req = { query: { monitorURL: "https://example.com" } }; - res = { status: sinon.stub().returnsThis(), json: sinon.stub() }; - next = sinon.stub(); - axiosGetStub = sinon.stub(axios, "get"); - }); - - afterEach(function () { - sinon.restore(); - }); - - it("should resolve the URL successfully", async function () { - axiosGetStub.resolves({ status: 200, statusText: "OK" }); - await checkEndpointResolution(req, res, next); - expect(res.status.calledWith(200)).to.be.true; - expect( - res.json.calledWith({ - success: true, - code: 200, - statusText: "OK", - msg: "URL resolved successfully", - }) - ).to.be.true; - expect(next.called).to.be.false; - }); - - it("should return an error if endpoint resolution fails", async function () { - const axiosError = new Error("resolution failed"); - axiosError.code = "ENOTFOUND"; - axiosGetStub.rejects(axiosError); - await checkEndpointResolution(req, res, next); - expect(next.calledOnce).to.be.true; - const errorPassedToNext = next.getCall(0).args[0]; - expect(errorPassedToNext).to.be.an.instanceOf(Error); - expect(errorPassedToNext.message).to.include("resolution failed"); - expect(errorPassedToNext.code).to.equal("ENOTFOUND"); - expect(errorPassedToNext.status).to.equal(500); - }); - - it("should reject with an error if query validation fails", async function () { - req.query.monitorURL = "invalid-url"; - await checkEndpointResolution(req, res, next); - expect(next.calledOnce).to.be.true; - const error = next.getCall(0).args[0]; - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].status).to.equal(422); - expect(error.message).to.equal('"monitorURL" must be a valid uri'); - }); -}); - -describe("Monitor Controller - deleteMonitor", function () { - let req, res, next; - - beforeEach(function () { - req = { - params: { - monitorId: "123", - }, - query: {}, - body: {}, - db: { - deleteMonitor: sinon.stub(), - deleteChecks: sinon.stub(), - deletePageSpeedChecksByMonitorId: sinon.stub(), - deleteNotificationsByMonitorId: sinon.stub(), - }, - jobQueue: { - deleteJob: sinon.stub(), - }, - }; - res = { - status: sinon.stub().returnsThis(), - json: sinon.stub(), - }; - next = sinon.stub(); - sinon.stub(logger, "error"); - }); - - afterEach(function () { - sinon.restore(); - }); - - it("should reject with an error if param validation fails", async function () { - req.params = {}; - await deleteMonitor(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].status).to.equal(422); - }); - - it("should reject with an error if DB deleteMonitor operation fail", async function () { - req.db.deleteMonitor.throws(new Error("DB error")); - await deleteMonitor(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].message).to.equal("DB error"); - }); - - it("should log an error if deleteJob throws an error", async function () { - const error = new Error("Job error"); - const monitor = { name: "test_monitor", _id: "123" }; - req.db.deleteMonitor.returns(monitor); - req.jobQueue.deleteJob.rejects(error); - await deleteMonitor(req, res, next); - expect(logger.error.calledOnce).to.be.true; - expect(logger.error.firstCall.args[0].message).to.equal(`Error deleting associated records for monitor ${monitor._id} with name ${monitor.name}`); - }); - - it("should log an error if deleteChecks throws an error", async function () { - const error = new Error("Checks error"); - const monitor = { name: "test_monitor", _id: "123" }; - req.db.deleteMonitor.returns(monitor); - req.db.deleteChecks.rejects(error); - await deleteMonitor(req, res, next); - expect(logger.error.calledOnce).to.be.true; - expect(logger.error.firstCall.args[0].message).to.equal(`Error deleting associated records for monitor ${monitor._id} with name ${monitor.name}`); - }); - - it("should log an error if deletePageSpeedChecksByMonitorId throws an error", async function () { - const error = new Error("PageSpeed error"); - const monitor = { name: "test_monitor", _id: "123" }; - req.db.deleteMonitor.returns(monitor); - req.db.deletePageSpeedChecksByMonitorId.rejects(error); - await deleteMonitor(req, res, next); - expect(logger.error.calledOnce).to.be.true; - expect(logger.error.firstCall.args[0].message).to.equal(`Error deleting associated records for monitor ${monitor._id} with name ${monitor.name}`); - }); - - it("should log an error if deleteNotificationsByMonitorId throws an error", async function () { - const error = new Error("Notifications error"); - const monitor = { name: "test_monitor", _id: "123" }; - req.db.deleteMonitor.returns(monitor); - req.db.deleteNotificationsByMonitorId.rejects(error); - await deleteMonitor(req, res, next); - expect(logger.error.calledOnce).to.be.true; - expect(logger.error.firstCall.args[0].message).to.equal(`Error deleting associated records for monitor ${monitor._id} with name ${monitor.name}`); - }); - - it("should return success message if all operations succeed", async function () { - const monitor = { name: "test_monitor", _id: "123" }; - req.db.deleteMonitor.returns(monitor); - await deleteMonitor(req, res, next); - expect(res.status.firstCall.args[0]).to.equal(200); - expect( - res.json.calledOnceWith({ - success: true, - msg: successMessages.MONITOR_DELETE, - }) - ).to.be.true; - }); -}); - -describe("Monitor Controller - deleteAllMonitors", function () { - let req, res, next, stub; - - beforeEach(function () { - stub = sinon.stub(jwt, "verify").callsFake(() => { - return { teamId: "123" }; - }); - req = { - headers: { - authorization: "Bearer token", - }, - params: { - monitorId: "123", - }, - query: {}, - body: {}, - db: { - deleteAllMonitors: sinon.stub(), - deleteChecks: sinon.stub(), - deletePageSpeedChecksByMonitorId: sinon.stub(), - deleteNotificationsByMonitorId: sinon.stub(), - }, - jobQueue: { - deleteJob: sinon.stub(), - }, - settingsService: { - getSettings: sinon.stub(), - }, - }; - res = { - status: sinon.stub().returnsThis(), - json: sinon.stub(), - }; - next = sinon.stub(); - sinon.stub(logger, "error"); - }); - - afterEach(function () { - sinon.restore(); - stub.restore(); - }); - - it("should reject with an error if getTokenFromHeaders throws an error", async function () { - req.headers = {}; - await deleteAllMonitors(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].message).to.equal("No auth headers"); - expect(next.firstCall.args[0].status).to.equal(500); - }); - - it("should reject with an error if token validation fails", async function () { - stub.restore(); - req.settingsService.getSettings.returns({ jwtSecret: "my_secret" }); - await deleteAllMonitors(req, res, next); - expect(next.firstCall.args[0]).to.be.instanceOf(jwt.JsonWebTokenError); - }); - - it("should reject with an error if DB deleteAllMonitors operation fail", async function () { - req.settingsService.getSettings.returns({ jwtSecret: "my_secret" }); - req.db.deleteAllMonitors.throws(new Error("DB error")); - await deleteAllMonitors(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].message).to.equal("DB error"); - }); - - it("should log an error if deleteChecks throws an error", async function () { - const monitors = [{ name: "test_monitor", _id: "123" }]; - req.settingsService.getSettings.returns({ jwtSecret: "my_secret" }); - req.db.deleteAllMonitors.returns({ monitors, deletedCount: 1 }); - const error = new Error("Check error"); - req.db.deleteChecks.rejects(error); - await deleteAllMonitors(req, res, next); - expect(logger.error.calledOnce).to.be.true; - expect(logger.error.firstCall.args[0].message).to.equal( - `Error deleting associated records for monitor ${monitors[0]._id} with name ${monitors[0].name}` - ); - }); - - it("should log an error if deletePageSpeedChecksByMonitorId throws an error", async function () { - const monitors = [{ name: "test_monitor", _id: "123" }]; - req.settingsService.getSettings.returns({ jwtSecret: "my_secret" }); - req.db.deleteAllMonitors.returns({ monitors, deletedCount: 1 }); - const error = new Error("Pagespeed Check error"); - req.db.deletePageSpeedChecksByMonitorId.rejects(error); - await deleteAllMonitors(req, res, next); - expect(logger.error.calledOnce).to.be.true; - expect(logger.error.firstCall.args[0].message).to.equal( - `Error deleting associated records for monitor ${monitors[0]._id} with name ${monitors[0].name}` - ); - }); - - it("should log an error if deleteNotificationsByMonitorId throws an error", async function () { - const monitors = [{ name: "test_monitor", _id: "123" }]; - req.settingsService.getSettings.returns({ jwtSecret: "my_secret" }); - req.db.deleteAllMonitors.returns({ monitors, deletedCount: 1 }); - const error = new Error("Notifications Check error"); - req.db.deleteNotificationsByMonitorId.rejects(error); - await deleteAllMonitors(req, res, next); - expect(logger.error.calledOnce).to.be.true; - expect(logger.error.firstCall.args[0].message).to.equal( - `Error deleting associated records for monitor ${monitors[0]._id} with name ${monitors[0].name}` - ); - }); - - it("should return success message if all operations succeed", async function () { - req.settingsService.getSettings.returns({ jwtSecret: "my_secret" }); - req.db.deleteAllMonitors.returns({ - monitors: [{ name: "test_monitor", _id: "123" }], - deletedCount: 1, - }); - await deleteAllMonitors(req, res, next); - expect(res.status.firstCall.args[0]).to.equal(200); - expect( - res.json.calledOnceWith({ - success: true, - msg: "Deleted 1 monitors", - }) - ).to.be.true; - }); -}); - -describe("Monitor Controller - editMonitor", function () { - let req, res, next; - - beforeEach(function () { - req = { - headers: {}, - params: { - monitorId: "123", - }, - query: {}, - body: { - notifications: [{ email: "example@example.com" }], - }, - db: { - getMonitorById: sinon.stub(), - editMonitor: sinon.stub(), - deleteNotificationsByMonitorId: sinon.stub(), - createNotification: sinon.stub(), - }, - jobQueue: { - deleteJob: sinon.stub(), - addJob: sinon.stub(), - }, - settingsService: { - getSettings: sinon.stub(), - }, - }; - res = { - status: sinon.stub().returnsThis(), - json: sinon.stub(), - }; - next = sinon.stub(); - }); - - afterEach(function () { - sinon.restore(); - }); - - it("should reject with an error if param validation fails", async function () { - req.params = {}; - await editMonitor(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].status).to.equal(422); - }); - - it("should reject with an error if body validation fails", async function () { - req.body = { invalid: 1 }; - await editMonitor(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].status).to.equal(422); - }); - - it("should reject with an error if getMonitorById operation fails", async function () { - req.db.getMonitorById.throws(new Error("DB error")); - await editMonitor(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].message).to.equal("DB error"); - }); - - it("should reject with an error if editMonitor operation fails", async function () { - req.db.getMonitorById.returns({ teamId: "123" }); - req.db.editMonitor.throws(new Error("DB error")); - await editMonitor(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].message).to.equal("DB error"); - }); - - it("should reject with an error if deleteNotificationsByMonitorId operation fails", async function () { - req.db.getMonitorById.returns({ teamId: "123" }); - req.db.editMonitor.returns({ _id: "123" }); - req.db.deleteNotificationsByMonitorId.throws(new Error("DB error")); - await editMonitor(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].message).to.equal("DB error"); - }); - - it("should reject with an error if createNotification operation fails", async function () { - req.db.getMonitorById.returns({ teamId: "123" }); - req.db.editMonitor.returns({ _id: "123" }); - req.db.createNotification.throws(new Error("DB error")); - await editMonitor(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].message).to.equal("DB error"); - }); - - it("should reject with an error if deleteJob operation fails", async function () { - req.db.getMonitorById.returns({ teamId: "123" }); - req.db.editMonitor.returns({ _id: "123" }); - req.jobQueue.deleteJob.throws(new Error("Job error")); - await editMonitor(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].message).to.equal("Job error"); - }); - - it("should reject with an error if addJob operation fails", async function () { - req.db.getMonitorById.returns({ teamId: "123" }); - req.db.editMonitor.returns({ _id: "123" }); - req.jobQueue.addJob.throws(new Error("Add Job error")); - await editMonitor(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].message).to.equal("Add Job error"); - }); - - it("should return success message with data if all operations succeed", async function () { - const monitor = { _id: "123" }; - req.db.getMonitorById.returns({ teamId: "123" }); - req.db.editMonitor.returns(monitor); - await editMonitor(req, res, next); - expect(res.status.firstCall.args[0]).to.equal(200); - expect( - res.json.calledOnceWith({ - success: true, - msg: successMessages.MONITOR_EDIT, - data: monitor, - }) - ).to.be.true; - }); -}); - -describe("Monitor Controller - pauseMonitor", function () { - let req, res, next; - - beforeEach(function () { - req = { - headers: {}, - params: { - monitorId: "123", - }, - query: {}, - body: {}, - db: { - getMonitorById: sinon.stub(), - }, - jobQueue: { - deleteJob: sinon.stub(), - addJob: sinon.stub(), - }, - settingsService: { - getSettings: sinon.stub(), - }, - }; - res = { - status: sinon.stub().returnsThis(), - json: sinon.stub(), - }; - next = sinon.stub(); - }); - - afterEach(function () { - sinon.restore(); - }); - - it("should reject with an error if param validation fails", async function () { - req.params = {}; - await pauseMonitor(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].status).to.equal(422); - }); - - it("should reject with an error if getMonitorById operation fails", async function () { - req.db.getMonitorById.throws(new Error("DB error")); - await pauseMonitor(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].message).to.equal("DB error"); - }); - - it("should reject with an error if deleteJob operation fails", async function () { - const monitor = { _id: req.params.monitorId, isActive: true }; - req.db.getMonitorById.returns(monitor); - req.jobQueue.deleteJob.throws(new Error("Delete Job error")); - await pauseMonitor(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].message).to.equal("Delete Job error"); - }); - - it("should reject with an error if addJob operation fails", async function () { - const monitor = { _id: req.params.monitorId, isActive: false }; - req.db.getMonitorById.returns(monitor); - req.jobQueue.addJob.throws(new Error("Add Job error")); - await pauseMonitor(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].message).to.equal("Add Job error"); - }); - - it("should reject with an error if monitor.save operation fails", async function () { - const monitor = { - _id: req.params.monitorId, - active: false, - save: sinon.stub().throws(new Error("Save error")), - }; - req.db.getMonitorById.returns(monitor); - await pauseMonitor(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].message).to.equal("Save error"); - }); - - it("should return success pause message with data if all operations succeed with inactive monitor", async function () { - const monitor = { - _id: req.params.monitorId, - isActive: false, - save: sinon.stub().resolves(), - }; - req.db.getMonitorById.returns(monitor); - await pauseMonitor(req, res, next); - expect(res.status.firstCall.args[0]).to.equal(200); - expect( - res.json.calledOnceWith({ - success: true, - msg: successMessages.MONITOR_PAUSE, - data: monitor, - }) - ).to.be.true; - }); - - it("should return success resume message with data if all operations succeed with active monitor", async function () { - const monitor = { - _id: req.params.monitorId, - isActive: true, - save: sinon.stub().resolves(), - }; - req.db.getMonitorById.returns(monitor); - await pauseMonitor(req, res, next); - expect(res.status.firstCall.args[0]).to.equal(200); - expect( - res.json.calledOnceWith({ - success: true, - msg: successMessages.MONITOR_RESUME, - data: monitor, - }) - ).to.be.true; - }); -}); - -describe("Monitor Controller - addDemoMonitors", function () { - let req, res, next, stub; - - beforeEach(function () { - stub = sinon.stub(jwt, "verify").callsFake(() => { - return { _id: "123", teamId: "123" }; - }); - req = { - headers: { - authorization: "Bearer token", - }, - params: {}, - query: {}, - body: {}, - db: { - addDemoMonitors: sinon.stub(), - }, - settingsService: { - getSettings: sinon.stub(), - }, - jobQueue: { - addJob: sinon.stub(), - }, - }; - res = { - status: sinon.stub().returnsThis(), - json: sinon.stub(), - }; - next = sinon.stub(); - }); - - afterEach(function () { - sinon.restore(); - stub.restore(); - }); - - it("should reject with an error if getTokenFromHeaders fails", async function () { - req.headers = {}; - await addDemoMonitors(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].message).to.equal("No auth headers"); - expect(next.firstCall.args[0].status).to.equal(500); - }); - - it("should reject with an error if getting settings fails", async function () { - req.settingsService.getSettings.throws(new Error("Settings error")); - await addDemoMonitors(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].message).to.equal("Settings error"); - }); - - it("should reject with an error if JWT validation fails", async function () { - stub.restore(); - req.settingsService.getSettings.returns({ jwtSecret: "my_secret" }); - await addDemoMonitors(req, res, next); - expect(next.firstCall.args[0]).to.be.instanceOf(jwt.JsonWebTokenError); - }); - - it("should reject with an error if addDemoMonitors operation fails", async function () { - req.settingsService.getSettings.returns({ jwtSecret: "my_secret" }); - req.db.addDemoMonitors.throws(new Error("DB error")); - await addDemoMonitors(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].message).to.equal("DB error"); - }); - - it("should reject with an error if addJob operation fails", async function () { - req.settingsService.getSettings.returns({ jwtSecret: "my_secret" }); - req.db.addDemoMonitors.returns([{ _id: "123" }]); - req.jobQueue.addJob.throws(new Error("Add Job error")); - await addDemoMonitors(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].message).to.equal("Add Job error"); - }); - - it("should return success message with data if all operations succeed", async function () { - const monitors = [{ _id: "123" }]; - req.settingsService.getSettings.returns({ jwtSecret: "my_secret" }); - req.db.addDemoMonitors.returns(monitors); - await addDemoMonitors(req, res, next); - expect(res.status.firstCall.args[0]).to.equal(200); - expect( - res.json.calledOnceWith({ - success: true, - msg: successMessages.MONITOR_DEMO_ADDED, - data: monitors.length, - }) - ).to.be.true; - }); -}); diff --git a/server/tests/controllers/queueController.test.js b/server/tests/controllers/queueController.test.js deleted file mode 100755 index 6201bdb5a..000000000 --- a/server/tests/controllers/queueController.test.js +++ /dev/null @@ -1,176 +0,0 @@ -import { afterEach } from "node:test"; -import { getMetrics, getJobs, addJob, obliterateQueue } from "../../controllers/queueController.js"; -import { successMessages } from "../../utils/messages.js"; -import sinon from "sinon"; - -describe("Queue Controller - getMetrics", function () { - let req, res, next; - - beforeEach(function () { - req = { - headers: {}, - params: {}, - body: {}, - db: {}, - jobQueue: { - getMetrics: sinon.stub(), - }, - }; - res = { - status: sinon.stub().returnsThis(), - json: sinon.stub(), - }; - next = sinon.stub(); - }); - - afterEach(() => { - sinon.restore(); - }); - - it("should throw an error if getMetrics throws an error", async function () { - req.jobQueue.getMetrics.throws(new Error("getMetrics error")); - await getMetrics(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].message).to.equal("getMetrics error"); - }); - - it("should return a success message and data if getMetrics is successful", async function () { - const data = { data: "metrics" }; - req.jobQueue.getMetrics.returns(data); - await getMetrics(req, res, next); - expect(res.status.firstCall.args[0]).to.equal(200); - expect(res.json.firstCall.args[0]).to.deep.equal({ - success: true, - msg: successMessages.QUEUE_GET_METRICS, - data, - }); - }); -}); - -describe("Queue Controller - getJobs", function () { - let req, res, next; - - beforeEach(function () { - req = { - headers: {}, - params: {}, - body: {}, - db: {}, - jobQueue: { - getJobStats: sinon.stub(), - }, - }; - res = { - status: sinon.stub().returnsThis(), - json: sinon.stub(), - }; - next = sinon.stub(); - }); - - afterEach(() => { - sinon.restore(); - }); - - it("should reject with an error if getJobs throws an error", async function () { - req.jobQueue.getJobStats.throws(new Error("getJobs error")); - await getJobs(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].message).to.equal("getJobs error"); - }); - - it("should return a success message and data if getJobs is successful", async function () { - const data = { data: "jobs" }; - req.jobQueue.getJobStats.returns(data); - await getJobs(req, res, next); - expect(res.status.firstCall.args[0]).to.equal(200); - expect(res.json.firstCall.args[0]).to.deep.equal({ - success: true, - msg: successMessages.QUEUE_GET_METRICS, - data, - }); - }); -}); - -describe("Queue Controller - addJob", function () { - let req, res, next; - - beforeEach(function () { - req = { - headers: {}, - params: {}, - body: {}, - db: {}, - jobQueue: { - addJob: sinon.stub(), - }, - }; - res = { - status: sinon.stub().returnsThis(), - json: sinon.stub(), - }; - next = sinon.stub(); - }); - - afterEach(() => { - sinon.restore(); - }); - - it("should reject with an error if addJob throws an error", async function () { - req.jobQueue.addJob.throws(new Error("addJob error")); - await addJob(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].message).to.equal("addJob error"); - }); - - it("should return a success message if addJob is successful", async function () { - req.jobQueue.addJob.resolves(); - await addJob(req, res, next); - expect(res.status.firstCall.args[0]).to.equal(200); - expect(res.json.firstCall.args[0]).to.deep.equal({ - success: true, - msg: successMessages.QUEUE_ADD_JOB, - }); - }); -}); - -describe("Queue Controller - obliterateQueue", function () { - let req, res, next; - - beforeEach(function () { - req = { - headers: {}, - params: {}, - body: {}, - db: {}, - jobQueue: { - obliterate: sinon.stub(), - }, - }; - res = { - status: sinon.stub().returnsThis(), - json: sinon.stub(), - }; - next = sinon.stub(); - }); - - afterEach(() => { - sinon.restore(); - }); - - it("should reject with an error if obliterateQueue throws an error", async function () { - req.jobQueue.obliterate.throws(new Error("obliterateQueue error")); - await obliterateQueue(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].message).to.equal("obliterateQueue error"); - }); - - it("should return a success message if obliterateQueue is successful", async function () { - req.jobQueue.obliterate.resolves(); - await obliterateQueue(req, res, next); - expect(res.status.firstCall.args[0]).to.equal(200); - expect(res.json.firstCall.args[0]).to.deep.equal({ - success: true, - msg: successMessages.QUEUE_OBLITERATE, - }); - }); -}); diff --git a/server/tests/controllers/settingsController.test.js b/server/tests/controllers/settingsController.test.js deleted file mode 100755 index 721c41c02..000000000 --- a/server/tests/controllers/settingsController.test.js +++ /dev/null @@ -1,109 +0,0 @@ -import { afterEach } from "node:test"; -import { getAppSettings, updateAppSettings } from "../../controllers/settingsController.js"; - -import { successMessages } from "../../utils/messages.js"; -import sinon from "sinon"; - -describe("Settings Controller - getAppSettings", function () { - let req, res, next; - - beforeEach(function () { - req = { - headers: {}, - params: {}, - body: {}, - db: {}, - settingsService: { - getSettings: sinon.stub(), - }, - }; - res = { - status: sinon.stub().returnsThis(), - json: sinon.stub(), - }; - next = sinon.stub(); - }); - - afterEach(() => { - sinon.restore(); - }); - - it("should throw an error if getSettings throws an error", async function () { - req.settingsService.getSettings.throws(new Error("getSettings error")); - await getAppSettings(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].message).to.equal("getSettings error"); - }); - - it("should return a success message and data if getSettings is successful", async function () { - const data = { data: "settings" }; - req.settingsService.getSettings.returns(data); - await getAppSettings(req, res, next); - expect(res.status.firstCall.args[0]).to.equal(200); - expect(res.json.firstCall.args[0]).to.deep.equal({ - success: true, - msg: successMessages.GET_APP_SETTINGS, - data, - }); - }); -}); - -describe("Settings Controller - updateAppSettings", function () { - let req, res, next; - - beforeEach(function () { - req = { - headers: {}, - params: {}, - body: {}, - db: { - updateAppSettings: sinon.stub(), - }, - settingsService: { - reloadSettings: sinon.stub(), - }, - }; - res = { - status: sinon.stub().returnsThis(), - json: sinon.stub(), - }; - next = sinon.stub(); - }); - - afterEach(() => { - sinon.restore(); - }); - - it("should reject with an error if body validation fails", async function () { - req.body = { invalid: 1 }; - await updateAppSettings(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].status).to.equal(422); - }); - - it("should reject with an error if updateAppSettings throws an error", async function () { - req.db.updateAppSettings.throws(new Error("updateAppSettings error")); - await updateAppSettings(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].message).to.equal("updateAppSettings error"); - }); - - it("should reject with an error if reloadSettings throws an error", async function () { - req.settingsService.reloadSettings.throws(new Error("reloadSettings error")); - await updateAppSettings(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].message).to.equal("reloadSettings error"); - }); - - it("should return a success message and data if updateAppSettings is successful", async function () { - const data = { data: "settings" }; - req.settingsService.reloadSettings.returns(data); - await updateAppSettings(req, res, next); - expect(res.status.firstCall.args[0]).to.equal(200); - expect(res.json.firstCall.args[0]).to.deep.equal({ - success: true, - msg: successMessages.UPDATE_APP_SETTINGS, - data, - }); - }); -}); diff --git a/server/tests/controllers/statusPageController.test.js b/server/tests/controllers/statusPageController.test.js deleted file mode 100755 index cada662e0..000000000 --- a/server/tests/controllers/statusPageController.test.js +++ /dev/null @@ -1,127 +0,0 @@ -import sinon from "sinon"; -import { createStatusPage, getStatusPageByUrl } from "../../controllers/statusPageController.js"; - -describe("statusPageController", function () { - let req, res, next; - - beforeEach(function () { - req = { - params: {}, - body: {}, - db: { - createStatusPage: sinon.stub(), - getStatusPageByUrl: sinon.stub(), - }, - }; - res = { - status: sinon.stub().returnsThis(), - json: sinon.stub(), - }; - next = sinon.stub(); - }); - - afterEach(function () { - sinon.restore(); - }); - - describe("createStatusPage", function () { - beforeEach(function () { - req.body = { - companyName: "Test Company", - url: "123456", - timezone: "America/Toronto", - color: "#000000", - theme: "light", - monitors: ["67309ca673788e808884c8ac", "67309ca673788e808884c8ac"], - }; - }); - - afterEach(function () { - sinon.restore(); - }); - - it("should handle a validation error", async function () { - req.body = { - // Invalid data that will trigger validation error - companyName: "", - url: "", - timezone: "", - color: "invalid", - theme: "invalid", - monitors: ["invalid-id"], - }; - try { - await createStatusPage(req, res, next); - } catch (error) { - expect(error).to.be.an.instanceOf(Error); - expect(error.message).to.equal("Validation error"); - } - }); - - it("should handle a db error", async function () { - const err = new Error("DB error"); - req.db.createStatusPage.throws(err); - - try { - await createStatusPage(req, res, next); - } catch (error) { - expect(error).to.deep.equal(err); - } - }); - - it("should insert a properly formatted status page", async function () { - const result = await createStatusPage(req, res, next); - expect(res.status.firstCall.args[0]).to.equal(200); - expect(res.json.firstCall.args[0].success).to.be.true; - }); - }); - - describe("getStatusPageByUrl", function () { - beforeEach(function () { - req.params = { - url: "123456", - }; - }); - - afterEach(function () { - sinon.restore(); - }); - - it("should handle a validation error", async function () { - req.params = { - url: "", - }; - - try { - await getStatusPageByUrl(req, res, next); - } catch (error) { - expect(error).to.be.an.instanceOf(Error); - expect(error.message).to.equal("Validation error"); - } - }); - - it("should handle a DB error", async function () { - const err = new Error("DB error"); - req.db.getStatusPageByUrl.throws(err); - - try { - await getStatusPageByUrl(req, res, next); - } catch (error) { - expect(error).to.deep.equal(err); - } - }); - - it("should return a status page", async function () { - const statusPage = { - _id: "123456", - companyName: "Test Company", - url: "123456", - }; - req.db.getStatusPageByUrl.resolves(statusPage); - const result = await getStatusPageByUrl(req, res, next); - expect(res.status.firstCall.args[0]).to.equal(200); - expect(res.json.firstCall.args[0].success).to.be.true; - expect(res.json.firstCall.args[0].data).to.deep.equal(statusPage); - }); - }); -}); diff --git a/server/tests/db/checkModule.test.js b/server/tests/db/checkModule.test.js deleted file mode 100755 index 6eef12445..000000000 --- a/server/tests/db/checkModule.test.js +++ /dev/null @@ -1,589 +0,0 @@ -import sinon from "sinon"; -import { - createCheck, - getChecksCount, - getChecks, - getTeamChecks, - deleteChecks, - deleteChecksByTeamId, - updateChecksTTL, -} from "../../db/mongo/modules/checkModule.js"; -import Check from "../../db/models/Check.js"; -import Monitor from "../../db/models/Monitor.js"; -import User from "../../db/models/User.js"; -import logger from "../../utils/logger.js"; - -describe("checkModule", function () { - describe("createCheck", function () { - let checkCountDocumentsStub, checkSaveStub, monitorFindByIdStub, monitorSaveStub; - const mockMonitor = { - _id: "123", - uptimePercentage: 0.5, - status: true, - save: () => this, - }; - const mockCheck = { active: true }; - - beforeEach(function () { - checkSaveStub = sinon.stub(Check.prototype, "save"); - checkCountDocumentsStub = sinon.stub(Check, "countDocuments"); - monitorFindByIdStub = sinon.stub(Monitor, "findById"); - monitorSaveStub = sinon.stub(Monitor.prototype, "save"); - }); - - afterEach(function () { - sinon.restore(); - }); - - it("should return undefined early if no monitor is found", async function () { - monitorFindByIdStub.returns(null); - const check = await createCheck({ monitorId: "123" }); - expect(check).to.be.undefined; - }); - - it("should return a check", async function () { - monitorFindByIdStub.returns(mockMonitor); - checkSaveStub.returns(mockCheck); - monitorSaveStub.returns(mockMonitor); - const check = await createCheck({ monitorId: "123", status: true }); - expect(check).to.deep.equal(mockCheck); - }); - - it("should return a check if status is down", async function () { - mockMonitor.status = false; - monitorFindByIdStub.returns(mockMonitor); - checkSaveStub.returns(mockCheck); - monitorSaveStub.returns(mockMonitor); - const check = await createCheck({ monitorId: "123", status: false }); - expect(check).to.deep.equal(mockCheck); - }); - - it("should return a check if uptimePercentage is undefined", async function () { - mockMonitor.uptimePercentage = undefined; - monitorFindByIdStub.returns(mockMonitor); - checkSaveStub.returns(mockCheck); - monitorSaveStub.returns(mockMonitor); - const check = await createCheck({ monitorId: "123", status: true }); - expect(check).to.deep.equal(mockCheck); - }); - - it("should return a check if uptimePercentage is undefined and status is down", async function () { - mockMonitor.uptimePercentage = undefined; - monitorFindByIdStub.returns(mockMonitor); - checkSaveStub.returns(mockCheck); - monitorSaveStub.returns(mockMonitor); - const check = await createCheck({ monitorId: "123", status: false }); - expect(check).to.deep.equal(mockCheck); - }); - - it("should monitor save error", async function () { - const err = new Error("Save Error"); - monitorSaveStub.throws(err); - try { - await createCheck({ monitorId: "123" }); - } catch (error) { - expect(error).to.deep.equal(err); - } - }); - - it("should handle errors", async function () { - const err = new Error("DB Error"); - checkCountDocumentsStub.throws(err); - try { - await createCheck({ monitorId: "123" }); - } catch (error) { - expect(error).to.deep.equal(err); - } - }); - }); - - describe("getChecksCount", function () { - let checkCountDocumentStub; - - beforeEach(function () { - checkCountDocumentStub = sinon.stub(Check, "countDocuments"); - }); - - afterEach(function () { - checkCountDocumentStub.restore(); - }); - - it("should return count with basic monitorId query", async function () { - const req = { - params: { monitorId: "test123" }, - query: {}, - }; - checkCountDocumentStub.resolves(5); - - const result = await getChecksCount(req); - - expect(result).to.equal(5); - expect(checkCountDocumentStub.calledOnce).to.be.true; - expect(checkCountDocumentStub.firstCall.args[0]).to.deep.equal({ - monitorId: "test123", - }); - }); - - it("should include dateRange in query when provided", async function () { - const req = { - params: { monitorId: "test123" }, - query: { dateRange: "day" }, - }; - checkCountDocumentStub.resolves(3); - - const result = await getChecksCount(req); - - expect(result).to.equal(3); - expect(checkCountDocumentStub.firstCall.args[0]).to.have.property("createdAt"); - expect(checkCountDocumentStub.firstCall.args[0].createdAt).to.have.property("$gte"); - }); - - it('should handle "all" filter correctly', async function () { - const req = { - params: { monitorId: "test123" }, - query: { filter: "all" }, - }; - checkCountDocumentStub.resolves(2); - - const result = await getChecksCount(req); - - expect(result).to.equal(2); - expect(checkCountDocumentStub.firstCall.args[0]).to.deep.equal({ - monitorId: "test123", - status: false, - }); - }); - - it('should handle "down" filter correctly', async function () { - const req = { - params: { monitorId: "test123" }, - query: { filter: "down" }, - }; - checkCountDocumentStub.resolves(2); - - const result = await getChecksCount(req); - - expect(result).to.equal(2); - expect(checkCountDocumentStub.firstCall.args[0]).to.deep.equal({ - monitorId: "test123", - status: false, - }); - }); - - it('should handle "resolve" filter correctly', async function () { - const req = { - params: { monitorId: "test123" }, - query: { filter: "resolve" }, - }; - checkCountDocumentStub.resolves(1); - - const result = await getChecksCount(req); - - expect(result).to.equal(1); - expect(checkCountDocumentStub.firstCall.args[0]).to.deep.equal({ - monitorId: "test123", - status: false, - statusCode: 5000, - }); - }); - - it("should handle unknown filter correctly", async function () { - const req = { - params: { monitorId: "test123" }, - query: { filter: "unknown" }, - }; - checkCountDocumentStub.resolves(1); - - const result = await getChecksCount(req); - - expect(result).to.equal(1); - expect(checkCountDocumentStub.firstCall.args[0]).to.deep.equal({ - monitorId: "test123", - status: false, - }); - }); - - it("should combine dateRange and filter in query", async function () { - const req = { - params: { monitorId: "test123" }, - query: { - dateRange: "week", - filter: "down", - }, - }; - checkCountDocumentStub.resolves(4); - - const result = await getChecksCount(req); - - expect(result).to.equal(4); - expect(checkCountDocumentStub.firstCall.args[0]).to.have.all.keys("monitorId", "createdAt", "status"); - }); - }); - - describe("getChecks", function () { - let checkFindStub, monitorFindStub; - - beforeEach(function () { - checkFindStub = sinon.stub(Check, "find").returns({ - skip: sinon.stub().returns({ - limit: sinon.stub().returns({ - sort: sinon.stub().returns([{ id: 1 }, { id: 2 }]), - }), - }), - }); - }); - - afterEach(function () { - sinon.restore(); - }); - - it("should return checks with basic monitorId query", async function () { - const req = { - params: { monitorId: "test123" }, - query: {}, - }; - - const result = await getChecks(req); - - expect(result).to.deep.equal([{ id: 1 }, { id: 2 }]); - }); - - it("should return checks with limit query", async function () { - const req = { - params: { monitorId: "test123" }, - query: { limit: 10 }, - }; - - const result = await getChecks(req); - - expect(result).to.deep.equal([{ id: 1 }, { id: 2 }]); - }); - - it("should handle pagination correctly", async function () { - const req = { - params: { monitorId: "test123" }, - query: { - page: 2, - rowsPerPage: 10, - }, - }; - - const result = await getChecks(req); - - expect(result).to.deep.equal([{ id: 1 }, { id: 2 }]); - }); - - it("should handle dateRange filter", async function () { - const req = { - params: { monitorId: "test123" }, - query: { dateRange: "week" }, - }; - const result = await getChecks(req); - - expect(result).to.deep.equal([{ id: 1 }, { id: 2 }]); - }); - - it('should handle "all" filter', async function () { - const req = { - params: { monitorId: "test123" }, - query: { filter: "all" }, - }; - - await getChecks(req); - const result = await getChecks(req); - expect(result).to.deep.equal([{ id: 1 }, { id: 2 }]); - }); - - it('should handle "down" filter', async function () { - const req = { - params: { monitorId: "test123" }, - query: { filter: "down" }, - }; - - await getChecks(req); - const result = await getChecks(req); - expect(result).to.deep.equal([{ id: 1 }, { id: 2 }]); - }); - - it('should handle "resolve" filter', async function () { - const req = { - params: { monitorId: "test123" }, - query: { filter: "resolve" }, - }; - - await getChecks(req); - const result = await getChecks(req); - expect(result).to.deep.equal([{ id: 1 }, { id: 2 }]); - }); - - it('should handle "unknown" filter', async function () { - const req = { - params: { monitorId: "test123" }, - query: { filter: "unknown" }, - }; - - await getChecks(req); - const result = await getChecks(req); - expect(result).to.deep.equal([{ id: 1 }, { id: 2 }]); - }); - - it("should handle ascending sort order", async function () { - const req = { - params: { monitorId: "test123" }, - query: { sortOrder: "asc" }, - }; - - await getChecks(req); - const result = await getChecks(req); - expect(result).to.deep.equal([{ id: 1 }, { id: 2 }]); - }); - - it("should handle error case", async function () { - const req = { - params: { monitorId: "test123" }, - query: {}, - }; - - checkFindStub.throws(new Error("Database error")); - - try { - await getChecks(req); - } catch (error) { - expect(error.message).to.equal("Database error"); - expect(error.service).to.equal("checkModule"); - expect(error.method).to.equal("getChecks"); - } - }); - }); - - describe("getTeamChecks", function () { - let checkFindStub, checkCountDocumentsStub, monitorFindStub; - const mockMonitors = [{ _id: "123" }]; - - beforeEach(function () { - monitorFindStub = sinon.stub(Monitor, "find").returns({ - select: sinon.stub().returns(mockMonitors), - }); - checkCountDocumentsStub = sinon.stub(Check, "countDocuments").returns(2); - checkFindStub = sinon.stub(Check, "find").returns({ - skip: sinon.stub().returns({ - limit: sinon.stub().returns({ - sort: sinon.stub().returns({ - select: sinon.stub().returns([{ id: 1 }, { id: 2 }]), - }), - }), - }), - }); - }); - - afterEach(function () { - sinon.restore(); - }); - - it("should return checks with basic monitorId query", async function () { - const req = { - params: { teamId: "test123" }, - query: {}, - }; - - const result = await getTeamChecks(req); - expect(result).to.deep.equal({ checksCount: 2, checks: [{ id: 1 }, { id: 2 }] }); - }); - - it("should handle pagination correctly", async function () { - const req = { - params: { monitorId: "test123" }, - query: { limit: 1, page: 2, rowsPerPage: 10 }, - }; - - const result = await getTeamChecks(req); - expect(result).to.deep.equal({ checksCount: 2, checks: [{ id: 1 }, { id: 2 }] }); - }); - - it("should handle dateRange filter", async function () { - const req = { - params: { monitorId: "test123" }, - query: { dateRange: "week" }, - }; - const result = await getTeamChecks(req); - expect(result).to.deep.equal({ checksCount: 2, checks: [{ id: 1 }, { id: 2 }] }); - }); - - it('should handle "all" filter', async function () { - const req = { - params: { monitorId: "test123" }, - query: { filter: "all" }, - }; - - await getChecks(req); - const result = await getTeamChecks(req); - expect(result).to.deep.equal({ checksCount: 2, checks: [{ id: 1 }, { id: 2 }] }); - }); - - it('should handle "down" filter', async function () { - const req = { - params: { monitorId: "test123" }, - query: { filter: "down" }, - }; - - await getChecks(req); - const result = await getTeamChecks(req); - expect(result).to.deep.equal({ checksCount: 2, checks: [{ id: 1 }, { id: 2 }] }); - }); - - it('should handle "resolve" filter', async function () { - const req = { - params: { monitorId: "test123" }, - query: { filter: "resolve" }, - }; - - await getChecks(req); - const result = await getTeamChecks(req); - expect(result).to.deep.equal({ checksCount: 2, checks: [{ id: 1 }, { id: 2 }] }); - }); - - it('should handle "unknown" filter', async function () { - const req = { - params: { monitorId: "test123" }, - query: { filter: "unknown" }, - }; - - await getChecks(req); - const result = await getTeamChecks(req); - expect(result).to.deep.equal({ checksCount: 2, checks: [{ id: 1 }, { id: 2 }] }); - }); - - it("should handle ascending sort order", async function () { - const req = { - params: { monitorId: "test123" }, - query: { sortOrder: "asc" }, - }; - - await getChecks(req); - const result = await getTeamChecks(req); - expect(result).to.deep.equal({ checksCount: 2, checks: [{ id: 1 }, { id: 2 }] }); - }); - - it("should handle error case", async function () { - const req = { - params: { monitorId: "test123" }, - query: {}, - }; - - checkFindStub.throws(new Error("Database error")); - - try { - await getTeamChecks(req); - } catch (error) { - expect(error.message).to.equal("Database error"); - expect(error.service).to.equal("checkModule"); - expect(error.method).to.equal("getTeamChecks"); - } - }); - }); - - describe("deleteChecks", function () { - let checkDeleteManyStub; - - beforeEach(function () { - checkDeleteManyStub = sinon.stub(Check, "deleteMany").resolves({ deletedCount: 1 }); - }); - - afterEach(function () { - sinon.restore(); - }); - - it("should return a value if a check is deleted", async function () { - const result = await deleteChecks("123"); - expect(result).to.equal(1); - }); - - it("should handle an error", async function () { - checkDeleteManyStub.throws(new Error("Database error")); - try { - await deleteChecks("123"); - } catch (error) { - expect(error.message).to.equal("Database error"); - expect(error.method).to.equal("deleteChecks"); - } - }); - }); - - describe("deleteChecksByTeamId", function () { - let mockMonitors = [{ _id: 123, save: () => this }]; - let monitorFindStub, monitorSaveStub, checkDeleteManyStub; - - beforeEach(function () { - monitorSaveStub = sinon.stub(Monitor.prototype, "save"); - monitorFindStub = sinon.stub(Monitor, "find").returns(mockMonitors); - checkDeleteManyStub = sinon.stub(Check, "deleteMany").resolves({ deletedCount: 1 }); - }); - - afterEach(function () { - sinon.restore(); - }); - - it("should return a deleted count", async function () { - const result = await deleteChecksByTeamId("123"); - expect(result).to.equal(1); - }); - - it("should handle errors", async function () { - const err = new Error("DB Error"); - monitorFindStub.throws(err); - try { - const result = await deleteChecksByTeamId("123"); - } catch (error) { - expect(error).to.deep.equal(err); - } - }); - }); - - describe("updateChecksTTL", function () { - let userUpdateManyStub; - let loggerStub; - - beforeEach(function () { - loggerStub = sinon.stub(logger, "error"); - userUpdateManyStub = sinon.stub(User, "updateMany"); - Check.collection = { dropIndex: sinon.stub(), createIndex: sinon.stub() }; - }); - - afterEach(function () { - sinon.restore(); - }); - - it("should return undefined", async function () { - const result = await updateChecksTTL("123", 10); - expect(result).to.be.undefined; - }); - - it("should log an error if dropIndex throws an error", async function () { - const err = new Error("Drop Index Error"); - Check.collection.dropIndex.throws(err); - await updateChecksTTL("123", 10); - expect(loggerStub.calledOnce).to.be.true; - expect(loggerStub.firstCall.args[0].message).to.equal(err.message); - }); - - it("should throw an error if createIndex throws an error", async function () { - const err = new Error("Create Index Error"); - Check.collection.createIndex.throws(err); - try { - await updateChecksTTL("123", 10); - } catch (error) { - expect(error).to.deep.equal(err); - } - }); - - it("should throw an error if User.updateMany throws an error", async function () { - const err = new Error("Update Many Error"); - userUpdateManyStub.throws(err); - try { - await updateChecksTTL("123", 10); - } catch (error) { - expect(error).to.deep.equal(err); - } - }); - }); -}); diff --git a/server/tests/db/hardwareCheckModule.test.js b/server/tests/db/hardwareCheckModule.test.js deleted file mode 100755 index 20ce197fb..000000000 --- a/server/tests/db/hardwareCheckModule.test.js +++ /dev/null @@ -1,136 +0,0 @@ -import sinon from "sinon"; -import HardwareCheck from "../../db/models/HardwareCheck.js"; -import { createHardwareCheck } from "../../db/mongo/modules/hardwareCheckModule.js"; -import Monitor from "../../db/models/Monitor.js"; -import logger from "../../utils/logger.js"; - -const mockHardwareCheck = { - data: { - cpu: { - physical_core: 4, - logical_core: 8, - frequency: 4800, - current_frequency: 1411, - temperature: [45, 50, 46, 47, 45, 50, 46, 47], - free_percent: 0.8552990910595134, - usage_percent: 0.14470090894048657, - }, - memory: { - total_bytes: 16467628032, - available_bytes: 7895044096, - used_bytes: 6599561216, - usage_percent: 0.4008, - }, - disk: [ - { - read_speed_bytes: null, - write_speed_bytes: null, - total_bytes: 931258499072, - free_bytes: 737097256960, - usage_percent: 0.1661, - }, - ], - host: { - os: "linux", - platform: "ubuntu", - kernel_version: "6.8.0-48-generic", - }, - }, - errors: [ - { - metric: ["cpu.temperature"], - err: "unable to read CPU temperature", - }, - ], -}; - -const mockMonitor = { - _id: "123", - uptimePercentage: 1, - status: true, - save: () => this, -}; - -describe("HardwareCheckModule", function () { - let hardwareCheckSaveStub, hardwareCheckCountDocumentsStub, monitorFindByIdStub, loggerStub; - - beforeEach(function () { - loggerStub = sinon.stub(logger, "error"); - hardwareCheckSaveStub = sinon.stub(HardwareCheck.prototype, "save"); - monitorFindByIdStub = sinon.stub(Monitor, "findById"); - hardwareCheckCountDocumentsStub = sinon.stub(HardwareCheck, "countDocuments"); - }); - - afterEach(function () { - sinon.restore(); - }); - - describe("createHardwareCheck", function () { - it("should return a hardware check", async function () { - hardwareCheckSaveStub.resolves(mockHardwareCheck); - monitorFindByIdStub.resolves(mockMonitor); - hardwareCheckCountDocumentsStub.resolves(1); - const hardwareCheck = await createHardwareCheck({ status: true }); - expect(hardwareCheck).to.exist; - expect(hardwareCheck).to.deep.equal(mockHardwareCheck); - }); - - it("should return a hardware check for a check with status false", async function () { - hardwareCheckSaveStub.resolves(mockHardwareCheck); - monitorFindByIdStub.resolves(mockMonitor); - hardwareCheckCountDocumentsStub.resolves(1); - const hardwareCheck = await createHardwareCheck({ status: false }); - expect(hardwareCheck).to.exist; - expect(hardwareCheck).to.deep.equal(mockHardwareCheck); - }); - - it("should handle an error", async function () { - const err = new Error("test error"); - monitorFindByIdStub.resolves(mockMonitor); - hardwareCheckSaveStub.rejects(err); - try { - await createHardwareCheck({}); - } catch (error) { - expect(error).to.exist; - expect(error).to.deep.equal(err); - } - }); - - it("should log an error if a monitor is not found", async function () { - monitorFindByIdStub.resolves(null); - const res = await createHardwareCheck({}); - expect(loggerStub.calledOnce).to.be.true; - expect(res).to.be.null; - }); - - it("should handle a monitor with undefined uptimePercentage", async function () { - monitorFindByIdStub.resolves({ ...mockMonitor, uptimePercentage: undefined }); - hardwareCheckSaveStub.resolves(mockHardwareCheck); - hardwareCheckCountDocumentsStub.resolves(1); - const res = await createHardwareCheck({}); - expect(res).to.exist; - }); - - it("should handle a monitor with undefined uptimePercentage and true status", async function () { - monitorFindByIdStub.resolves({ - ...mockMonitor, - uptimePercentage: undefined, - }); - hardwareCheckSaveStub.resolves(mockHardwareCheck); - hardwareCheckCountDocumentsStub.resolves(1); - const res = await createHardwareCheck({ status: true }); - expect(res).to.exist; - }); - - it("should handle a monitor with undefined uptimePercentage and false status", async function () { - monitorFindByIdStub.resolves({ - ...mockMonitor, - uptimePercentage: undefined, - }); - hardwareCheckSaveStub.resolves(mockHardwareCheck); - hardwareCheckCountDocumentsStub.resolves(1); - const res = await createHardwareCheck({ status: false }); - expect(res).to.exist; - }); - }); -}); diff --git a/server/tests/db/inviteModule.test.js b/server/tests/db/inviteModule.test.js deleted file mode 100755 index e8913c983..000000000 --- a/server/tests/db/inviteModule.test.js +++ /dev/null @@ -1,103 +0,0 @@ -import sinon from "sinon"; -import InviteToken from "../../db/models/InviteToken.js"; -import { requestInviteToken, getInviteToken, getInviteTokenAndDelete } from "../../db/mongo/modules/inviteModule.js"; -import { errorMessages } from "../../utils/messages.js"; - -describe("Invite Module", function () { - const mockUserData = { - email: "test@test.com", - teamId: "123", - role: ["admin"], - token: "123", - }; - const mockInviteToken = { _id: 123, time: 123 }; - let inviteTokenDeleteManyStub, inviteTokenSaveStub, inviteTokenFindOneStub, inviteTokenFindOneAndDeleteStub; - - beforeEach(function () { - inviteTokenDeleteManyStub = sinon.stub(InviteToken, "deleteMany"); - inviteTokenSaveStub = sinon.stub(InviteToken.prototype, "save"); - inviteTokenFindOneStub = sinon.stub(InviteToken, "findOne"); - inviteTokenFindOneAndDeleteStub = sinon.stub(InviteToken, "findOneAndDelete"); - }); - - afterEach(function () { - sinon.restore(); - }); - - describe("requestInviteToken", function () { - it("should return a new invite token", async function () { - inviteTokenDeleteManyStub.resolves(); - inviteTokenSaveStub.resolves(); - const inviteToken = await requestInviteToken(mockUserData); - expect(inviteToken.email).to.equal(mockUserData.email); - expect(inviteToken.role).to.deep.equal(mockUserData.role); - expect(inviteToken.token).to.exist; - }); - - it("should handle an error", async function () { - const err = new Error("test error"); - inviteTokenDeleteManyStub.rejects(err); - try { - await requestInviteToken(mockUserData); - } catch (error) { - expect(error).to.deep.equal(err); - } - }); - }); - - describe("getInviteToken", function () { - it("should return an invite token", async function () { - inviteTokenFindOneStub.resolves(mockInviteToken); - const inviteToken = await getInviteToken(mockUserData.token); - expect(inviteToken).to.deep.equal(mockInviteToken); - }); - - it("should handle a token not found", async function () { - inviteTokenFindOneStub.resolves(null); - try { - await getInviteToken(mockUserData.token); - } catch (error) { - expect(error.message).to.equal(errorMessages.AUTH_INVITE_NOT_FOUND); - } - }); - - it("should handle DB errors", async function () { - const err = new Error("test error"); - inviteTokenFindOneStub.rejects(err); - try { - await getInviteToken(mockUserData.token); - } catch (error) { - expect(error).to.deep.equal(err); - expect(error.method).to.equal("getInviteToken"); - } - }); - }); - - describe("getInviteTokenAndDelete", function () { - it("should return a deleted invite", async function () { - inviteTokenFindOneAndDeleteStub.resolves(mockInviteToken); - const deletedInvite = await getInviteTokenAndDelete(mockUserData.token); - expect(deletedInvite).to.deep.equal(mockInviteToken); - }); - - it("should handle a token not found", async function () { - inviteTokenFindOneAndDeleteStub.resolves(null); - try { - await getInviteTokenAndDelete(mockUserData.token); - } catch (error) { - expect(error.message).to.equal(errorMessages.AUTH_INVITE_NOT_FOUND); - } - }); - - it("should handle DB errors", async function () { - const err = new Error("test error"); - inviteTokenFindOneAndDeleteStub.rejects(err); - try { - await getInviteTokenAndDelete(mockUserData.token); - } catch (error) { - expect(error).to.deep.equal(err); - expect(error.method).to.equal("getInviteTokenAndDelete"); - } - }); - }); -}); diff --git a/server/tests/db/maintenanceWindowModule.test.js b/server/tests/db/maintenanceWindowModule.test.js deleted file mode 100755 index 576c97f84..000000000 --- a/server/tests/db/maintenanceWindowModule.test.js +++ /dev/null @@ -1,255 +0,0 @@ -import sinon from "sinon"; -import MaintenanceWindow from "../../db/models/MaintenanceWindow.js"; -import { - createMaintenanceWindow, - getMaintenanceWindowById, - getMaintenanceWindowsByTeamId, - getMaintenanceWindowsByMonitorId, - deleteMaintenanceWindowById, - deleteMaintenanceWindowByMonitorId, - deleteMaintenanceWindowByUserId, - editMaintenanceWindowById, -} from "../../db/mongo/modules/maintenanceWindowModule.js"; - -describe("MaintenanceWindow Module", function () { - const mockMaintenanceWindow = { - monitorId: "123", - active: true, - oneTime: true, - start: 1, - end: 20000, - }; - - let mockMaintenanceWindows = [mockMaintenanceWindow]; - let maintenanceWindowSaveStub, - maintenanceWindowFindByIdStub, - maintenanceWindowCountDocumentsStub, - maintenanceWindowFindStub, - maintenanceWindowFindByIdAndDeleteStub, - maintenanceWindowDeleteManyStub, - maintenanceWindowFindByIdAndUpdateStub; - - beforeEach(function () { - maintenanceWindowSaveStub = sinon.stub(MaintenanceWindow.prototype, "save"); - maintenanceWindowFindByIdStub = sinon.stub(MaintenanceWindow, "findById"); - maintenanceWindowCountDocumentsStub = sinon.stub(MaintenanceWindow, "countDocuments"); - maintenanceWindowFindStub = sinon.stub(MaintenanceWindow, "find").returns({ - skip: sinon.stub().returns({ - limit: sinon.stub().returns({ - sort: sinon.stub().returns(mockMaintenanceWindows), - }), - }), - }); - maintenanceWindowFindByIdAndDeleteStub = sinon.stub(MaintenanceWindow, "findByIdAndDelete"); - maintenanceWindowDeleteManyStub = sinon.stub(MaintenanceWindow, "deleteMany"); - maintenanceWindowFindByIdAndUpdateStub = sinon.stub(MaintenanceWindow, "findByIdAndUpdate"); - }); - - afterEach(function () { - sinon.restore(); - }); - - describe("createMaintenanceWindow", function () { - it("should save a new maintenance window", async function () { - maintenanceWindowSaveStub.resolves(mockMaintenanceWindow); - const result = await createMaintenanceWindow(mockMaintenanceWindow); - expect(result).to.deep.equal(mockMaintenanceWindow); - }); - - it("should handle an error", async function () { - const err = new Error("test error"); - maintenanceWindowSaveStub.rejects(err); - try { - await createMaintenanceWindow(mockMaintenanceWindow); - } catch (error) { - expect(error).to.deep.equal(err); - } - }); - }); - - describe("getMaintenanceWindowById", function () { - it("should return a maintenance window", async function () { - maintenanceWindowFindByIdStub.resolves(mockMaintenanceWindow); - const result = await getMaintenanceWindowById(mockMaintenanceWindow.id); - expect(result).to.deep.equal(mockMaintenanceWindow); - }); - - it("should handle an error", async function () { - const err = new Error("test error"); - maintenanceWindowFindByIdStub.rejects(err); - try { - await getMaintenanceWindowById(mockMaintenanceWindow.id); - } catch (error) { - expect(error).to.deep.equal(err); - } - }); - }); - - describe("getMaintenanceWindowsByTeamId", function () { - let query; - - beforeEach(function () { - query = { - active: true, - page: 1, - rowsPerPage: 10, - field: "name", - order: "asc", - }; - }); - - afterEach(function () { - sinon.restore(); - }); - - it("should return a list of maintenance windows and count", async function () { - maintenanceWindowCountDocumentsStub.resolves(1); - const result = await getMaintenanceWindowsByTeamId(mockMaintenanceWindow.teamId, query); - expect(result).to.deep.equal({ - maintenanceWindows: mockMaintenanceWindows, - maintenanceWindowCount: 1, - }); - }); - - it("should return a list of maintenance windows and count with empty query", async function () { - query = undefined; - maintenanceWindowCountDocumentsStub.resolves(1); - const result = await getMaintenanceWindowsByTeamId(mockMaintenanceWindow.teamId, query); - expect(result).to.deep.equal({ - maintenanceWindows: mockMaintenanceWindows, - maintenanceWindowCount: 1, - }); - }); - - it("should return a list of maintenance windows and count with no pagination provided", async function () { - query.page = undefined; - query.rowsPerPage = undefined; - maintenanceWindowCountDocumentsStub.resolves(1); - const result = await getMaintenanceWindowsByTeamId(mockMaintenanceWindow.teamId, query); - expect(result).to.deep.equal({ - maintenanceWindows: mockMaintenanceWindows, - maintenanceWindowCount: 1, - }); - }); - - it("should return a list of maintenance windows and count with field and desc order", async function () { - query.order = "desc"; - maintenanceWindowCountDocumentsStub.resolves(1); - const result = await getMaintenanceWindowsByTeamId(mockMaintenanceWindow.teamId, query); - expect(result).to.deep.equal({ - maintenanceWindows: mockMaintenanceWindows, - maintenanceWindowCount: 1, - }); - }); - - it("should return a list of maintenance windows and count no field", async function () { - query.field = undefined; - maintenanceWindowCountDocumentsStub.resolves(1); - const result = await getMaintenanceWindowsByTeamId(mockMaintenanceWindow.teamId, query); - expect(result).to.deep.equal({ - maintenanceWindows: mockMaintenanceWindows, - maintenanceWindowCount: 1, - }); - }); - - it("should handle an error", async function () { - const err = new Error("test error"); - maintenanceWindowCountDocumentsStub.rejects(err); - try { - await getMaintenanceWindowsByTeamId(mockMaintenanceWindow.teamId, query); - } catch (error) { - expect(error).to.deep.equal(err); - } - }); - }); - - describe("getMaintenanceWindowsByMonitorId", function () { - it("should return a list of maintenance windows", async function () { - maintenanceWindowFindStub.resolves(mockMaintenanceWindows); - const result = await getMaintenanceWindowsByMonitorId(mockMaintenanceWindow.monitorId); - expect(result).to.deep.equal(mockMaintenanceWindows); - }); - - it("should handle an error", async function () { - const err = new Error("test error"); - maintenanceWindowFindStub.rejects(err); - try { - await getMaintenanceWindowsByMonitorId(mockMaintenanceWindow.monitorId); - } catch (error) { - expect(error).to.deep.equal(err); - } - }); - }); - - describe("deleteMaintenanceWindowById", function () { - it("should delete a maintenance window", async function () { - maintenanceWindowFindByIdAndDeleteStub.resolves(mockMaintenanceWindow); - const result = await deleteMaintenanceWindowById(mockMaintenanceWindow.id); - expect(result).to.deep.equal(mockMaintenanceWindow); - }); - - it("should handle an error", async function () { - const err = new Error("test error"); - maintenanceWindowFindByIdAndDeleteStub.rejects(err); - try { - await deleteMaintenanceWindowById(mockMaintenanceWindow.id); - } catch (error) { - expect(error).to.deep.equal(err); - } - }); - }); - - describe("deleteMaintenanceWindowByMonitorId", function () { - it("should return the number of documents deleted", async function () { - maintenanceWindowDeleteManyStub.resolves({ deletedCount: 1 }); - const result = await deleteMaintenanceWindowByMonitorId(mockMaintenanceWindow.monitorId); - expect(result).to.deep.equal({ deletedCount: 1 }); - }); - - it("should handle an error", async function () { - const err = new Error("test error"); - maintenanceWindowDeleteManyStub.rejects(err); - try { - await deleteMaintenanceWindowByMonitorId(mockMaintenanceWindow.monitorId); - } catch (error) { - expect(error).to.deep.equal(err); - } - }); - }); - - describe("deleteMaintenanceWindowByUserId", function () { - it("should return the number of documents deleted", async function () { - maintenanceWindowDeleteManyStub.resolves({ deletedCount: 1 }); - const result = await deleteMaintenanceWindowByUserId(mockMaintenanceWindow.userId); - expect(result).to.deep.equal({ deletedCount: 1 }); - }); - - it("should handle an error", async function () { - const err = new Error("test error"); - maintenanceWindowDeleteManyStub.rejects(err); - try { - await deleteMaintenanceWindowByUserId(mockMaintenanceWindow.userId); - } catch (error) { - expect(error).to.deep.equal(err); - } - }); - }); - - describe("editMaintenanceWindowById", function () { - it("should return the updated maintenance window", async function () { - maintenanceWindowFindByIdAndUpdateStub.resolves(mockMaintenanceWindow); - const result = await editMaintenanceWindowById(mockMaintenanceWindow.id, mockMaintenanceWindow); - expect(result).to.deep.equal(mockMaintenanceWindow); - }); - - it("should handle an error", async function () { - const err = new Error("test error"); - maintenanceWindowFindByIdAndUpdateStub.rejects(err); - try { - await editMaintenanceWindowById(mockMaintenanceWindow.id, mockMaintenanceWindow); - } catch (error) { - expect(error).to.deep.equal(err); - } - }); - }); -}); diff --git a/server/tests/db/monitorModule.test.js b/server/tests/db/monitorModule.test.js deleted file mode 100755 index e59c352cf..000000000 --- a/server/tests/db/monitorModule.test.js +++ /dev/null @@ -1,1865 +0,0 @@ -import sinon from "sinon"; -import Monitor from "../../db/models/Monitor.js"; -import Check from "../../db/models/Check.js"; -import PageSpeedCheck from "../../db/models/PageSpeedCheck.js"; -import HardwareCheck from "../../db/models/HardwareCheck.js"; -import Notification from "../../db/models/Notification.js"; - -import { errorMessages } from "../../utils/messages.js"; -import { - getAllMonitors, - getAllMonitorsWithUptimeStats, - getMonitorStatsById, - getMonitorById, - getMonitorsAndSummaryByTeamId, - getMonitorsByTeamId, - createMonitor, - deleteMonitor, - deleteAllMonitors, - deleteMonitorsByUserId, - editMonitor, - addDemoMonitors, - calculateUptimeDuration, - getLastChecked, - getLatestResponseTime, - getAverageResponseTime, - getUptimePercentage, - getIncidents, - getMonitorChecks, - processChecksForDisplay, - groupChecksByTime, - calculateGroupStats, -} from "../../db/mongo/modules/monitorModule.js"; - -describe("monitorModule", function () { - let monitorFindStub, - monitorFindByIdStub, - monitorFindByIdAndUpdateStub, - monitorFindByIdAndDeleteStub, - monitorDeleteManyStub, - monitorCountStub, - monitorInsertManyStub, - checkFindStub, - pageSpeedCheckFindStub, - hardwareCheckFindStub; - - beforeEach(function () { - monitorFindStub = sinon.stub(Monitor, "find"); - monitorFindByIdStub = sinon.stub(Monitor, "findById"); - monitorFindByIdAndUpdateStub = sinon.stub(Monitor, "findByIdAndUpdate"); - monitorFindByIdAndDeleteStub = sinon.stub(Monitor, "findByIdAndDelete"); - monitorDeleteManyStub = sinon.stub(Monitor, "deleteMany"); - monitorCountStub = sinon.stub(Monitor, "countDocuments"); - - monitorInsertManyStub = sinon.stub(Monitor, "insertMany"); - checkFindStub = sinon.stub(Check, "find").returns({ - sort: sinon.stub(), - }); - pageSpeedCheckFindStub = sinon.stub(PageSpeedCheck, "find").returns({ - sort: sinon.stub(), - }); - hardwareCheckFindStub = sinon.stub(HardwareCheck, "find").returns({ - sort: sinon.stub(), - }); - }); - - afterEach(function () { - sinon.restore(); - }); - - describe("getAllMonitors", function () { - it("should return all monitors", async function () { - const mockMonitors = [ - { _id: "1", name: "Monitor 1", url: "test1.com" }, - { _id: "2", name: "Monitor 2", url: "test2.com" }, - ]; - monitorFindStub.returns(mockMonitors); - const result = await getAllMonitors(); - - expect(result).to.deep.equal(mockMonitors); - expect(monitorFindStub.calledOnce).to.be.true; - expect(monitorFindStub.firstCall.args).to.deep.equal([]); - }); - - it("should handle empty results", async function () { - monitorFindStub.returns([]); - const result = await getAllMonitors(); - expect(result).to.be.an("array").that.is.empty; - }); - - it("should throw error when database fails", async function () { - // Arrange - const error = new Error("Database error"); - error.service = "MonitorModule"; - error.method = "getAllMonitors"; - monitorFindStub.rejects(error); - // Act & Assert - try { - await getAllMonitors(); - expect.fail("Should have thrown an error"); - } catch (err) { - expect(err).to.equal(error); - expect(err.service).to.equal("monitorModule"); - expect(err.method).to.equal("getAllMonitors"); - } - }); - }); - - describe("getAllMonitorsWithUptimeStats", function () { - it("should return monitors with uptime stats for different time periods", async function () { - // Mock data - const mockMonitors = [ - { - _id: "monitor1", - type: "http", - toObject: () => ({ - _id: "monitor1", - type: "http", - name: "Test Monitor", - }), - }, - ]; - - const mockChecks = [{ status: true }, { status: true }, { status: false }, { status: true }]; - - monitorFindStub.resolves(mockMonitors); - checkFindStub.resolves(mockChecks); - - const result = await getAllMonitorsWithUptimeStats(); - - expect(result).to.be.an("array"); - expect(result).to.have.lengthOf(1); - - const monitor = result[0]; - expect(monitor).to.have.property("_id", "monitor1"); - expect(monitor).to.have.property("name", "Test Monitor"); - - // Check uptime percentages exist for all time periods - expect(monitor).to.have.property("1"); - expect(monitor).to.have.property("7"); - expect(monitor).to.have.property("30"); - expect(monitor).to.have.property("90"); - - // Verify uptime percentage calculation (3 successful out of 4 = 75%) - expect(monitor["1"]).to.equal(75); - expect(monitor["7"]).to.equal(75); - expect(monitor["30"]).to.equal(75); - expect(monitor["90"]).to.equal(75); - }); - - it("should return monitors with stats for pagespeed type", async function () { - // Mock data - const mockMonitors = [ - { - _id: "monitor1", - type: "pagespeed", - toObject: () => ({ - _id: "monitor1", - type: "pagespeed", - name: "Test Monitor", - }), - }, - ]; - - const mockChecks = [{ status: true }, { status: true }, { status: false }, { status: true }]; - - monitorFindStub.resolves(mockMonitors); - pageSpeedCheckFindStub.resolves(mockChecks); - - const result = await getAllMonitorsWithUptimeStats(); - - expect(result).to.be.an("array"); - expect(result).to.have.lengthOf(1); - - const monitor = result[0]; - expect(monitor).to.have.property("_id", "monitor1"); - expect(monitor).to.have.property("name", "Test Monitor"); - - // Check uptime percentages exist for all time periods - expect(monitor).to.have.property("1"); - expect(monitor).to.have.property("7"); - expect(monitor).to.have.property("30"); - expect(monitor).to.have.property("90"); - - // Verify uptime percentage calculation (3 successful out of 4 = 75%) - expect(monitor["1"]).to.equal(75); - expect(monitor["7"]).to.equal(75); - expect(monitor["30"]).to.equal(75); - expect(monitor["90"]).to.equal(75); - }); - - it("should return monitors with stats for hardware type", async function () { - // Mock data - const mockMonitors = [ - { - _id: "monitor1", - type: "hardware", - toObject: () => ({ - _id: "monitor1", - type: "hardware", - name: "Test Monitor", - }), - }, - ]; - - const mockChecks = [{ status: true }, { status: true }, { status: false }, { status: true }]; - - monitorFindStub.resolves(mockMonitors); - hardwareCheckFindStub.resolves(mockChecks); - - const result = await getAllMonitorsWithUptimeStats(); - - expect(result).to.be.an("array"); - expect(result).to.have.lengthOf(1); - - const monitor = result[0]; - expect(monitor).to.have.property("_id", "monitor1"); - expect(monitor).to.have.property("name", "Test Monitor"); - - // Check uptime percentages exist for all time periods - expect(monitor).to.have.property("1"); - expect(monitor).to.have.property("7"); - expect(monitor).to.have.property("30"); - expect(monitor).to.have.property("90"); - - // Verify uptime percentage calculation (3 successful out of 4 = 75%) - expect(monitor["1"]).to.equal(75); - expect(monitor["7"]).to.equal(75); - expect(monitor["30"]).to.equal(75); - expect(monitor["90"]).to.equal(75); - }); - - it("should handle errors appropriately", async function () { - // Setup stub to throw error - monitorFindStub.rejects(new Error("Database error")); - - try { - await getAllMonitorsWithUptimeStats(); - } catch (error) { - expect(error).to.be.an("error"); - expect(error.message).to.equal("Database error"); - expect(error.service).to.equal("monitorModule"); - expect(error.method).to.equal("getAllMonitorsWithUptimeStats"); - } - }); - - it("should handle empty monitor list", async function () { - monitorFindStub.resolves([]); - - const result = await getAllMonitorsWithUptimeStats(); - - expect(result).to.be.an("array"); - expect(result).to.have.lengthOf(0); - }); - - it("should handle monitor with no checks", async function () { - const mockMonitors = [ - { - _id: "monitor1", - type: "http", - toObject: () => ({ - _id: "monitor1", - type: "http", - name: "Test Monitor", - }), - }, - ]; - - monitorFindStub.resolves(mockMonitors); - checkFindStub.resolves([]); - - const result = await getAllMonitorsWithUptimeStats(); - - expect(result[0]).to.have.property("1", 0); - expect(result[0]).to.have.property("7", 0); - expect(result[0]).to.have.property("30", 0); - expect(result[0]).to.have.property("90", 0); - }); - }); - - describe("calculateUptimeDuration", function () { - let clock; - const NOW = new Date("2024-01-01T12:00:00Z").getTime(); - - beforeEach(function () { - // Fix the current time - clock = sinon.useFakeTimers(NOW); - }); - - afterEach(function () { - clock.restore(); - }); - - it("should return 0 when checks array is empty", function () { - expect(calculateUptimeDuration([])).to.equal(0); - }); - - it("should return 0 when checks array is null", function () { - expect(calculateUptimeDuration(null)).to.equal(0); - }); - - it("should calculate uptime from last down check to most recent check", function () { - const checks = [ - { status: true, createdAt: "2024-01-01T11:00:00Z" }, // Most recent - { status: true, createdAt: "2024-01-01T10:00:00Z" }, - { status: false, createdAt: "2024-01-01T09:00:00Z" }, // Last down - { status: true, createdAt: "2024-01-01T08:00:00Z" }, - ]; - - // Expected: 2 hours (from 09:00 to 11:00) = 7200000ms - expect(calculateUptimeDuration(checks)).to.equal(7200000); - }); - - it("should calculate uptime from first check when no down checks exist", function () { - const checks = [ - { status: true, createdAt: "2024-01-01T11:00:00Z" }, - { status: true, createdAt: "2024-01-01T10:00:00Z" }, - { status: true, createdAt: "2024-01-01T09:00:00Z" }, - ]; - - // Expected: Current time (12:00) - First check (09:00) = 3 hours = 10800000ms - expect(calculateUptimeDuration(checks)).to.equal(10800000); - }); - }); - - describe("getLastChecked", function () { - let clock; - const NOW = new Date("2024-01-01T12:00:00Z").getTime(); - - beforeEach(function () { - // Fix the current time - clock = sinon.useFakeTimers(NOW); - }); - - afterEach(function () { - clock.restore(); - }); - - it("should return 0 when checks array is empty", function () { - expect(getLastChecked([])).to.equal(0); - }); - - it("should return 0 when checks array is null", function () { - expect(getLastChecked(null)).to.equal(0); - }); - - it("should return time difference between now and most recent check", function () { - const checks = [ - { createdAt: "2024-01-01T11:30:00Z" }, // 30 minutes ago - { createdAt: "2024-01-01T11:00:00Z" }, - { createdAt: "2024-01-01T10:30:00Z" }, - ]; - - // Expected: 30 minutes = 1800000ms - expect(getLastChecked(checks)).to.equal(1800000); - }); - - it("should handle checks from different days", function () { - const checks = [ - { createdAt: "2023-12-31T12:00:00Z" }, // 24 hours ago - { createdAt: "2023-12-30T12:00:00Z" }, - ]; - - // Expected: 24 hours = 86400000ms - expect(getLastChecked(checks)).to.equal(86400000); - }); - }); - - describe("getLatestResponseTime", function () { - it("should return 0 when checks array is empty", function () { - expect(getLatestResponseTime([])).to.equal(0); - }); - - it("should return 0 when checks array is null", function () { - expect(getLatestResponseTime(null)).to.equal(0); - }); - - it("should return response time from most recent check", function () { - const checks = [ - { responseTime: 150, createdAt: "2024-01-01T11:30:00Z" }, // Most recent - { responseTime: 200, createdAt: "2024-01-01T11:00:00Z" }, - { responseTime: 250, createdAt: "2024-01-01T10:30:00Z" }, - ]; - - expect(getLatestResponseTime(checks)).to.equal(150); - }); - - it("should handle missing responseTime in checks", function () { - const checks = [{ createdAt: "2024-01-01T11:30:00Z" }, { responseTime: 200, createdAt: "2024-01-01T11:00:00Z" }]; - - expect(getLatestResponseTime(checks)).to.equal(0); - }); - }); - - describe("getAverageResponseTime", function () { - it("should return 0 when checks array is empty", function () { - expect(getAverageResponseTime([])).to.equal(0); - }); - - it("should return 0 when checks array is null", function () { - expect(getAverageResponseTime(null)).to.equal(0); - }); - - it("should calculate average response time from all checks", function () { - const checks = [ - { responseTime: 100, createdAt: "2024-01-01T11:30:00Z" }, - { responseTime: 200, createdAt: "2024-01-01T11:00:00Z" }, - { responseTime: 300, createdAt: "2024-01-01T10:30:00Z" }, - ]; - - // Average: (100 + 200 + 300) / 3 = 200 - expect(getAverageResponseTime(checks)).to.equal(200); - }); - - it("should handle missing responseTime in some checks", function () { - const checks = [ - { responseTime: 100, createdAt: "2024-01-01T11:30:00Z" }, - { createdAt: "2024-01-01T11:00:00Z" }, - { responseTime: 300, createdAt: "2024-01-01T10:30:00Z" }, - ]; - - // Average: (100 + 300) / 2 = 200 - expect(getAverageResponseTime(checks)).to.equal(200); - }); - - it("should return 0 when no checks have responseTime", function () { - const checks = [{ createdAt: "2024-01-01T11:30:00Z" }, { createdAt: "2024-01-01T11:00:00Z" }]; - - expect(getAverageResponseTime(checks)).to.equal(0); - }); - }); - - describe("getUptimePercentage", function () { - it("should return 0 when checks array is empty", function () { - expect(getUptimePercentage([])).to.equal(0); - }); - - it("should return 0 when checks array is null", function () { - expect(getUptimePercentage(null)).to.equal(0); - }); - - it("should return 100 when all checks are up", function () { - const checks = [{ status: true }, { status: true }, { status: true }]; - expect(getUptimePercentage(checks)).to.equal(100); - }); - - it("should return 0 when all checks are down", function () { - const checks = [{ status: false }, { status: false }, { status: false }]; - expect(getUptimePercentage(checks)).to.equal(0); - }); - - it("should calculate correct percentage for mixed status checks", function () { - const checks = [{ status: true }, { status: false }, { status: true }, { status: true }]; - // 3 up out of 4 total = 75% - expect(getUptimePercentage(checks)).to.equal(75); - }); - - it("should handle undefined status values", function () { - const checks = [{ status: true }, { status: undefined }, { status: true }]; - // 2 up out of 3 total ≈ 66.67% - expect(getUptimePercentage(checks)).to.equal((2 / 3) * 100); - }); - }); - - describe("getIncidents", function () { - it("should return 0 when checks array is empty", function () { - expect(getIncidents([])).to.equal(0); - }); - - it("should return 0 when checks array is null", function () { - expect(getIncidents(null)).to.equal(0); - }); - - it("should return 0 when all checks are up", function () { - const checks = [{ status: true }, { status: true }, { status: true }]; - expect(getIncidents(checks)).to.equal(0); - }); - - it("should count all incidents when all checks are down", function () { - const checks = [{ status: false }, { status: false }, { status: false }]; - expect(getIncidents(checks)).to.equal(3); - }); - - it("should count correct number of incidents for mixed status checks", function () { - const checks = [{ status: true }, { status: false }, { status: true }, { status: false }, { status: true }]; - expect(getIncidents(checks)).to.equal(2); - }); - - it("should handle undefined status values", function () { - const checks = [{ status: true }, { status: undefined }, { status: false }, { status: false }]; - // Only counts explicit false values - expect(getIncidents(checks)).to.equal(2); - }); - }); - - describe("getMonitorChecks", function () { - let mockModel; - - beforeEach(function () { - // Create a mock model with chainable methods - const mockChecks = [ - { monitorId: "123", createdAt: new Date("2024-01-01") }, - { monitorId: "123", createdAt: new Date("2024-01-02") }, - ]; - - mockModel = { - find: sinon.stub().returns({ - sort: sinon.stub().returns(mockChecks), - }), - }; - }); - - afterEach(function () { - sinon.restore(); - }); - - it("should return all checks and date-ranged checks", async function () { - // Arrange - const monitorId = "123"; - const dateRange = { - start: new Date("2024-01-01"), - end: new Date("2024-01-02"), - }; - const sortOrder = -1; - - // Act - const result = await getMonitorChecks(monitorId, mockModel, dateRange, sortOrder); - - // Assert - expect(result).to.have.keys(["checksAll", "checksForDateRange"]); - - // Verify find was called with correct parameters - expect(mockModel.find.firstCall.args[0]).to.deep.equal({ monitorId }); - expect(mockModel.find.secondCall.args[0]).to.deep.equal({ - monitorId, - createdAt: { $gte: dateRange.start, $lte: dateRange.end }, - }); - - // Verify sort was called with correct parameters - const sortCalls = mockModel.find().sort.getCalls(); - sortCalls.forEach((call) => { - expect(call.args[0]).to.deep.equal({ createdAt: sortOrder }); - }); - }); - - it("should handle empty results", async function () { - // Arrange - const emptyModel = { - find: sinon.stub().returns({ - sort: sinon.stub().returns([]), - }), - }; - - // Act - const result = await getMonitorChecks( - "123", - emptyModel, - { - start: new Date(), - end: new Date(), - }, - -1 - ); - - // Assert - expect(result.checksAll).to.be.an("array").that.is.empty; - expect(result.checksForDateRange).to.be.an("array").that.is.empty; - }); - - it("should maintain sort order", async function () { - // Arrange - const sortedChecks = [ - { monitorId: "123", createdAt: new Date("2024-01-02") }, - { monitorId: "123", createdAt: new Date("2024-01-01") }, - ]; - - const sortedModel = { - find: sinon.stub().returns({ - sort: sinon.stub().returns(sortedChecks), - }), - }; - - // Act - const result = await getMonitorChecks( - "123", - sortedModel, - { - start: new Date("2024-01-01"), - end: new Date("2024-01-02"), - }, - -1 - ); - - // Assert - expect(result.checksAll[0].createdAt).to.be.greaterThan(result.checksAll[1].createdAt); - expect(result.checksForDateRange[0].createdAt).to.be.greaterThan(result.checksForDateRange[1].createdAt); - }); - }); - - describe("processChecksForDisplay", function () { - let normalizeStub; - - beforeEach(function () { - normalizeStub = sinon.stub(); - }); - - it("should return original checks when numToDisplay is not provided", function () { - const checks = [1, 2, 3, 4, 5]; - const result = processChecksForDisplay(normalizeStub, checks); - expect(result).to.deep.equal(checks); - }); - - it("should return original checks when numToDisplay is greater than checks length", function () { - const checks = [1, 2, 3]; - const result = processChecksForDisplay(normalizeStub, checks, 5); - expect(result).to.deep.equal(checks); - }); - - it("should filter checks based on numToDisplay", function () { - const checks = [1, 2, 3, 4, 5, 6]; - const result = processChecksForDisplay(normalizeStub, checks, 3); - // Should return [1, 3, 5] as n = ceil(6/3) = 2 - expect(result).to.deep.equal([1, 3, 5]); - }); - - it("should handle empty checks array", function () { - const checks = []; - const result = processChecksForDisplay(normalizeStub, checks, 3); - expect(result).to.be.an("array").that.is.empty; - }); - - it("should call normalizeData when normalize is true", function () { - const checks = [1, 2, 3]; - normalizeStub.returns([10, 20, 30]); - - const result = processChecksForDisplay(normalizeStub, checks, null, true); - - expect(normalizeStub.args[0]).to.deep.equal([checks, 1, 100]); - expect(result).to.deep.equal([10, 20, 30]); - }); - - it("should handle both filtering and normalization", function () { - const checks = [1, 2, 3, 4, 5, 6]; - normalizeStub.returns([10, 30, 50]); - - const result = processChecksForDisplay(normalizeStub, checks, 3, true); - - expect(normalizeStub.args[0][0]).to.deep.equal([1, 3, 5]); - expect(result).to.deep.equal([10, 30, 50]); - }); - }); - - describe("groupChecksByTime", function () { - const mockChecks = [ - { createdAt: "2024-01-15T10:30:45Z" }, - { createdAt: "2024-01-15T10:45:15Z" }, - { createdAt: "2024-01-15T11:15:00Z" }, - { createdAt: "2024-01-16T10:30:00Z" }, - ]; - - it("should group checks by hour when dateRange is 'day'", function () { - const result = groupChecksByTime(mockChecks, "day"); - - // Get timestamps for 10:00 and 11:00 on Jan 15 - const time1 = new Date("2024-01-15T10:00:00Z").getTime(); - const time2 = new Date("2024-01-15T11:00:00Z").getTime(); - const time3 = new Date("2024-01-16T10:00:00Z").getTime(); - - expect(Object.keys(result)).to.have.lengthOf(3); - - expect(result[time1].checks).to.have.lengthOf(2); - expect(result[time2].checks).to.have.lengthOf(1); - expect(result[time3].checks).to.have.lengthOf(1); - }); - - it("should group checks by day when dateRange is not 'day'", function () { - const result = groupChecksByTime(mockChecks, "week"); - - expect(Object.keys(result)).to.have.lengthOf(2); - expect(result["2024-01-15"].checks).to.have.lengthOf(3); - expect(result["2024-01-16"].checks).to.have.lengthOf(1); - }); - - it("should handle empty checks array", function () { - const result = groupChecksByTime([], "day"); - expect(result).to.deep.equal({}); - }); - - it("should handle single check", function () { - const singleCheck = [{ createdAt: "2024-01-15T10:30:45Z" }]; - const result = groupChecksByTime(singleCheck, "day"); - - const expectedTime = new Date("2024-01-15T10:00:00Z").getTime(); - expect(Object.keys(result)).to.have.lengthOf(1); - expect(result[expectedTime].checks).to.have.lengthOf(1); - }); - - it("should skip invalid dates and process valid ones", function () { - const checksWithInvalidDate = [ - { createdAt: "invalid-date" }, - { createdAt: "2024-01-15T10:30:45Z" }, - { createdAt: null }, - { createdAt: undefined }, - { createdAt: "" }, - ]; - - const result = groupChecksByTime(checksWithInvalidDate, "day"); - - const expectedTime = new Date("2024-01-15T10:00:00Z").getTime(); - expect(Object.keys(result)).to.have.lengthOf(1); - expect(result[expectedTime].checks).to.have.lengthOf(1); - expect(result[expectedTime].checks[0].createdAt).to.equal("2024-01-15T10:30:45Z"); - }); - - it("should handle checks in same time group", function () { - const checksInSameHour = [{ createdAt: "2024-01-15T10:15:00Z" }, { createdAt: "2024-01-15T10:45:00Z" }]; - - const result = groupChecksByTime(checksInSameHour, "day"); - - const expectedTime = new Date("2024-01-15T10:00:00Z").getTime(); - expect(Object.keys(result)).to.have.lengthOf(1); - expect(result[expectedTime].checks).to.have.lengthOf(2); - }); - }); - - describe("calculateGroupStats", function () { - // Mock getUptimePercentage function - let uptimePercentageStub; - - beforeEach(function () { - uptimePercentageStub = sinon.stub(); - uptimePercentageStub.returns(95); // Default return value - }); - - it("should calculate stats correctly for a group of checks", function () { - const mockGroup = { - time: "2024-01-15", - checks: [ - { status: true, responseTime: 100 }, - { status: false, responseTime: 200 }, - { status: true, responseTime: 300 }, - ], - }; - - const result = calculateGroupStats(mockGroup, uptimePercentageStub); - - expect(result).to.deep.equal({ - time: "2024-01-15", - uptimePercentage: (2 / 3) * 100, - totalChecks: 3, - totalIncidents: 1, - avgResponseTime: 200, // (100 + 200 + 300) / 3 - }); - }); - - it("should handle empty checks array", function () { - const mockGroup = { - time: "2024-01-15", - checks: [], - }; - - const result = calculateGroupStats(mockGroup, uptimePercentageStub); - - expect(result).to.deep.equal({ - time: "2024-01-15", - uptimePercentage: 0, - totalChecks: 0, - totalIncidents: 0, - avgResponseTime: 0, - }); - }); - - it("should handle missing responseTime values", function () { - const mockGroup = { - time: "2024-01-15", - checks: [{ status: true }, { status: false, responseTime: 200 }, { status: true, responseTime: undefined }], - }; - - const result = calculateGroupStats(mockGroup, uptimePercentageStub); - - expect(result).to.deep.equal({ - time: "2024-01-15", - uptimePercentage: (2 / 3) * 100, - totalChecks: 3, - totalIncidents: 1, - avgResponseTime: 200, // 200 / 1 - }); - }); - - it("should handle all checks with status false", function () { - const mockGroup = { - time: "2024-01-15", - checks: [ - { status: false, responseTime: 100 }, - { status: false, responseTime: 200 }, - { status: false, responseTime: 300 }, - ], - }; - - const result = calculateGroupStats(mockGroup, uptimePercentageStub); - - expect(result).to.deep.equal({ - time: "2024-01-15", - uptimePercentage: 0, - totalChecks: 3, - totalIncidents: 3, - avgResponseTime: 200, - }); - }); - - it("should handle all checks with status true", function () { - const mockGroup = { - time: "2024-01-15", - checks: [ - { status: true, responseTime: 100 }, - { status: true, responseTime: 200 }, - { status: true, responseTime: 300 }, - ], - }; - - const result = calculateGroupStats(mockGroup, uptimePercentageStub); - - expect(result).to.deep.equal({ - time: "2024-01-15", - uptimePercentage: 100, - totalChecks: 3, - totalIncidents: 0, - avgResponseTime: 200, - }); - }); - }); - - describe("getMonitorStatsById", function () { - const now = new Date(); - const oneHourAgo = new Date(now - 3600000); - const twoHoursAgo = new Date(now - 7200000); - - const mockMonitor = { - _id: "monitor123", - type: "http", - name: "Test Monitor", - url: "https://test.com", - toObject: () => ({ - _id: "monitor123", - type: "http", - name: "Test Monitor", - url: "https://test.com", - }), - }; - - const mockMonitorPing = { - _id: "monitor123", - type: "ping", - name: "Test Monitor", - url: "https://test.com", - toObject: () => ({ - _id: "monitor123", - type: "http", - name: "Test Monitor", - url: "https://test.com", - }), - }; - const mockMonitorDocker = { - _id: "monitor123", - type: "docker", - name: "Test Monitor", - url: "https://test.com", - toObject: () => ({ - _id: "monitor123", - type: "http", - name: "Test Monitor", - url: "https://test.com", - }), - }; - - const checkDocs = [ - { - monitorId: "monitor123", - status: true, - responseTime: 100, - createdAt: new Date("2024-01-01T12:00:00Z"), - toObject: function () { - return { - monitorId: this.monitorId, - status: this.status, - responseTime: this.responseTime, - createdAt: this.createdAt, - }; - }, - }, - { - monitorId: "monitor123", - status: true, - responseTime: 150, - createdAt: new Date("2024-01-01T11:00:00Z"), - toObject: function () { - return { - monitorId: this.monitorId, - status: this.status, - responseTime: this.responseTime, - createdAt: this.createdAt, - }; - }, - }, - { - monitorId: "monitor123", - status: false, - responseTime: 200, - createdAt: new Date("2024-01-01T10:00:00Z"), - toObject: function () { - return { - monitorId: this.monitorId, - status: this.status, - responseTime: this.responseTime, - createdAt: this.createdAt, - }; - }, - }, - ]; - const req = { - params: { monitorId: "monitor123" }, - query: { - dateRange: "day", - sortOrder: "desc", - numToDisplay: 10, - normalize: true, - }, - }; - - beforeEach(function () { - checkFindStub.returns({ - sort: () => checkDocs, - }); - monitorFindByIdStub.returns(mockMonitor); - }); - - afterEach(function () { - sinon.restore(); - }); - - it("should return monitor stats with calculated values, sort order desc", async function () { - req.query.sortOrder = "desc"; - const result = await getMonitorStatsById(req); - expect(result).to.include.keys([ - "_id", - "type", - "name", - "url", - "uptimeDuration", - "lastChecked", - "latestResponseTime", - "periodIncidents", - "periodTotalChecks", - "periodAvgResponseTime", - "periodUptime", - "aggregateData", - ]); - expect(result.latestResponseTime).to.equal(100); - expect(result.periodTotalChecks).to.equal(3); - expect(result.periodIncidents).to.equal(1); - expect(result.periodUptime).to.be.a("number"); - expect(result.aggregateData).to.be.an("array"); - }); - - it("should return monitor stats with calculated values, ping type", async function () { - monitorFindByIdStub.returns(mockMonitorPing); - req.query.sortOrder = "desc"; - const result = await getMonitorStatsById(req); - expect(result).to.include.keys([ - "_id", - "type", - "name", - "url", - "uptimeDuration", - "lastChecked", - "latestResponseTime", - "periodIncidents", - "periodTotalChecks", - "periodAvgResponseTime", - "periodUptime", - "aggregateData", - ]); - expect(result.latestResponseTime).to.equal(100); - expect(result.periodTotalChecks).to.equal(3); - expect(result.periodIncidents).to.equal(1); - expect(result.periodUptime).to.be.a("number"); - expect(result.aggregateData).to.be.an("array"); - }); - - it("should return monitor stats with calculated values, docker type", async function () { - monitorFindByIdStub.returns(mockMonitorDocker); - req.query.sortOrder = "desc"; - const result = await getMonitorStatsById(req); - expect(result).to.include.keys([ - "_id", - "type", - "name", - "url", - "uptimeDuration", - "lastChecked", - "latestResponseTime", - "periodIncidents", - "periodTotalChecks", - "periodAvgResponseTime", - "periodUptime", - "aggregateData", - ]); - expect(result.latestResponseTime).to.equal(100); - expect(result.periodTotalChecks).to.equal(3); - expect(result.periodIncidents).to.equal(1); - expect(result.periodUptime).to.be.a("number"); - expect(result.aggregateData).to.be.an("array"); - }); - - it("should return monitor stats with calculated values", async function () { - req.query.sortOrder = "asc"; - const result = await getMonitorStatsById(req); - expect(result).to.include.keys([ - "_id", - "type", - "name", - "url", - "uptimeDuration", - "lastChecked", - "latestResponseTime", - "periodIncidents", - "periodTotalChecks", - "periodAvgResponseTime", - "periodUptime", - "aggregateData", - ]); - expect(result.latestResponseTime).to.equal(100); - expect(result.periodTotalChecks).to.equal(3); - expect(result.periodIncidents).to.equal(1); - expect(result.periodUptime).to.be.a("number"); - expect(result.aggregateData).to.be.an("array"); - }); - - it("should throw error when monitor is not found", async function () { - monitorFindByIdStub.returns(Promise.resolve(null)); - - const req = { - params: { monitorId: "nonexistent" }, - }; - - try { - await getMonitorStatsById(req); - expect.fail("Should have thrown an error"); - } catch (error) { - expect(error).to.be.an("Error"); - expect(error.service).to.equal("monitorModule"); - expect(error.method).to.equal("getMonitorStatsById"); - } - }); - }); - - describe("getMonitorById", function () { - let notificationFindStub; - let monitorSaveStub; - - beforeEach(function () { - // Create stubs - notificationFindStub = sinon.stub(Notification, "find"); - monitorSaveStub = sinon.stub().resolves(); - }); - - afterEach(function () { - sinon.restore(); - }); - - it("should return monitor with notifications when found", async function () { - // Arrange - const monitorId = "123"; - const mockMonitor = { - _id: monitorId, - name: "Test Monitor", - save: monitorSaveStub, - }; - const mockNotifications = [ - { _id: "notif1", message: "Test notification 1" }, - { _id: "notif2", message: "Test notification 2" }, - ]; - - monitorFindByIdStub.resolves(mockMonitor); - notificationFindStub.resolves(mockNotifications); - - const result = await getMonitorById(monitorId); - expect(result._id).to.equal(monitorId); - expect(result.name).to.equal("Test Monitor"); - expect(monitorFindByIdStub.calledWith(monitorId)).to.be.true; - expect(notificationFindStub.calledWith({ monitorId })).to.be.true; - expect(monitorSaveStub.calledOnce).to.be.true; - }); - - it("should throw 404 error when monitor not found", async function () { - // Arrange - const monitorId = "nonexistent"; - monitorFindByIdStub.resolves(null); - - // Act & Assert - try { - await getMonitorById(monitorId); - expect.fail("Should have thrown an error"); - } catch (error) { - expect(error.message).to.equal(errorMessages.DB_FIND_MONITOR_BY_ID(monitorId)); - expect(error.status).to.equal(404); - expect(error.service).to.equal("monitorModule"); - expect(error.method).to.equal("getMonitorById"); - } - }); - - it("should handle database errors properly", async function () { - // Arrange - const monitorId = "123"; - const dbError = new Error("Database connection failed"); - monitorFindByIdStub.rejects(dbError); - - // Act & Assert - try { - await getMonitorById(monitorId); - expect.fail("Should have thrown an error"); - } catch (error) { - expect(error.service).to.equal("monitorModule"); - expect(error.method).to.equal("getMonitorById"); - expect(error.message).to.equal("Database connection failed"); - } - }); - - it("should handle notification fetch errors", async function () { - // Arrange - const monitorId = "123"; - const mockMonitor = { - _id: monitorId, - name: "Test Monitor", - save: monitorSaveStub, - }; - const notificationError = new Error("Notification fetch failed"); - - monitorFindByIdStub.resolves(mockMonitor); - notificationFindStub.rejects(notificationError); - - // Act & Assert - try { - await getMonitorById(monitorId); - expect.fail("Should have thrown an error"); - } catch (error) { - expect(error.service).to.equal("monitorModule"); - expect(error.method).to.equal("getMonitorById"); - expect(error.message).to.equal("Notification fetch failed"); - } - }); - - it("should handle monitor save errors", async function () { - // Arrange - const monitorId = "123"; - const mockMonitor = { - _id: monitorId, - name: "Test Monitor", - save: sinon.stub().rejects(new Error("Save failed")), - }; - const mockNotifications = []; - - monitorFindByIdStub.resolves(mockMonitor); - notificationFindStub.resolves(mockNotifications); - - // Act & Assert - try { - await getMonitorById(monitorId); - expect.fail("Should have thrown an error"); - } catch (error) { - expect(error.service).to.equal("monitorModule"); - expect(error.method).to.equal("getMonitorById"); - expect(error.message).to.equal("Save failed"); - } - }); - }); - - describe("getMonitorsAndSummaryByTeamId", function () { - it("should return monitors and correct summary counts", async function () { - // Arrange - const teamId = "team123"; - const type = "http"; - const mockMonitors = [ - { teamId, type, status: true, isActive: true }, // up - { teamId, type, status: false, isActive: true }, // down - { teamId, type, status: null, isActive: false }, // paused - { teamId, type, status: true, isActive: true }, // up - ]; - monitorFindStub.resolves(mockMonitors); - - // Act - const result = await getMonitorsAndSummaryByTeamId(teamId, type); - - // Assert - expect(result.monitors).to.have.lengthOf(4); - expect(result.monitorCounts).to.deep.equal({ - up: 2, - down: 1, - paused: 1, - total: 4, - }); - expect(monitorFindStub.calledOnceWith({ teamId, type })).to.be.true; - }); - - it("should return empty results for non-existent team", async function () { - // Arrange - monitorFindStub.resolves([]); - - // Act - const result = await getMonitorsAndSummaryByTeamId("nonexistent", "http"); - - // Assert - expect(result.monitors).to.have.lengthOf(0); - expect(result.monitorCounts).to.deep.equal({ - up: 0, - down: 0, - paused: 0, - total: 0, - }); - }); - - it("should handle database errors", async function () { - // Arrange - const error = new Error("Database error"); - error.service = "MonitorModule"; - error.method = "getMonitorsAndSummaryByTeamId"; - monitorFindStub.rejects(error); - - // Act & Assert - try { - await getMonitorsAndSummaryByTeamId("team123", "http"); - expect.fail("Should have thrown an error"); - } catch (err) { - expect(err).to.equal(error); - expect(err.service).to.equal("monitorModule"); - expect(err.method).to.equal("getMonitorsAndSummaryByTeamId"); - } - }); - }); - - describe("getMonitorsByTeamId", function () { - beforeEach(function () { - // Chain stubs for Monitor.find().skip().limit().sort() - - // Stub for CHECK_MODEL_LOOKUP model find - checkFindStub.returns({ - sort: sinon.stub().returns({ - limit: sinon.stub().returns([]), - }), - }); - }); - - afterEach(function () { - sinon.restore(); - }); - - it("should return monitors with basic query parameters", async function () { - const mockMonitors = [ - { _id: "1", type: "http", toObject: () => ({ _id: "1", type: "http" }) }, - { _id: "2", type: "ping", toObject: () => ({ _id: "2", type: "ping" }) }, - ]; - monitorFindStub.returns({ - skip: sinon.stub().returns({ - limit: sinon.stub().returns({ - sort: sinon.stub().returns(mockMonitors), - }), - }), - }); - - const req = { - params: { teamId: "team123" }, - query: { - type: "http", - page: 0, - rowsPerPage: 10, - field: "name", - status: false, - checkOrder: "desc", - }, - }; - - monitorCountStub.resolves(2); - - const result = await getMonitorsByTeamId(req); - - expect(result).to.have.property("monitors"); - expect(result).to.have.property("monitorCount", 2); - }); - - it("should return monitors with basic query parameters", async function () { - const mockMonitors = [ - { _id: "1", type: "http", toObject: () => ({ _id: "1", type: "http" }) }, - { _id: "2", type: "ping", toObject: () => ({ _id: "2", type: "ping" }) }, - ]; - monitorFindStub.returns({ - skip: sinon.stub().returns({ - limit: sinon.stub().returns({ - sort: sinon.stub().returns(mockMonitors), - }), - }), - }); - - const req = { - params: { teamId: "team123" }, - query: { - type: "http", - page: 0, - rowsPerPage: 10, - field: "name", - status: true, - checkOrder: "asc", - }, - }; - - monitorCountStub.resolves(2); - - const result = await getMonitorsByTeamId(req); - - expect(result).to.have.property("monitors"); - expect(result).to.have.property("monitorCount", 2); - }); - - it("should handle type filter with array input", async function () { - const req = { - params: { teamId: "team123" }, - query: { - type: ["http", "ping"], - }, - }; - - monitorFindStub.returns({ - skip: sinon.stub().returns({ - limit: sinon.stub().returns({ - sort: sinon.stub().returns([]), - }), - }), - }); - monitorCountStub.resolves(0); - - await getMonitorsByTeamId(req); - - expect(Monitor.find.firstCall.args[0]).to.deep.equal({ - teamId: "team123", - type: { $in: ["http", "ping"] }, - }); - }); - - it("should handle text search filter", async function () { - const req = { - params: { teamId: "team123" }, - query: { - filter: "search", - }, - }; - - monitorFindStub.returns({ - skip: sinon.stub().returns({ - limit: sinon.stub().returns({ - sort: sinon.stub().returns([]), - }), - }), - }); - monitorCountStub.resolves(0); - - await getMonitorsByTeamId(req); - - expect(Monitor.find.firstCall.args[0]).to.deep.equal({ - teamId: "team123", - $or: [{ name: { $regex: "search", $options: "i" } }, { url: { $regex: "search", $options: "i" } }], - }); - }); - - it("should handle pagination parameters", async function () { - const req = { - params: { teamId: "team123" }, - query: { - page: 2, - rowsPerPage: 5, - }, - }; - - monitorFindStub.returns({ - skip: sinon.stub().returns({ - limit: sinon.stub().returns({ - sort: sinon.stub().returns([]), - }), - }), - }); - monitorCountStub.resolves(0); - - const result = await getMonitorsByTeamId(req); - expect(result).to.deep.equal({ - monitors: [], - monitorCount: 0, - }); - }); - - it("should handle sorting parameters", async function () { - const req = { - params: { teamId: "team123" }, - query: { - field: "name", - order: "asc", - }, - }; - - monitorFindStub.returns({ - skip: sinon.stub().returns({ - limit: sinon.stub().returns({ - sort: sinon.stub().returns([]), - }), - }), - }); - monitorCountStub.resolves(0); - - await getMonitorsByTeamId(req); - - const result = await getMonitorsByTeamId(req); - expect(result).to.deep.equal({ - monitors: [], - monitorCount: 0, - }); - }); - - it("should return early when limit is -1", async function () { - // Arrange - const req = { - params: { teamId: "team123" }, - query: { - limit: "-1", - }, - }; - - const mockMonitors = [ - { _id: "1", type: "http" }, - { _id: "2", type: "ping" }, - ]; - - monitorFindStub.returns({ - skip: sinon.stub().returns({ - limit: sinon.stub().returns({ - sort: sinon.stub().returns(mockMonitors), - }), - }), - }); - - monitorCountStub.resolves(2); - - // Act - const result = await getMonitorsByTeamId(req); - - // Assert - expect(result).to.deep.equal({ - monitors: mockMonitors, - monitorCount: 2, - }); - }); - - it("should normalize checks when normalize parameter is provided", async function () { - const req = { - params: { teamId: "team123" }, - query: { normalize: "true" }, - }; - monitorCountStub.resolves(2); - - const mockMonitors = [ - { _id: "1", type: "http", toObject: () => ({ _id: "1", type: "http" }) }, - { _id: "2", type: "ping", toObject: () => ({ _id: "2", type: "ping" }) }, - ]; - - monitorFindStub.returns({ - skip: sinon.stub().returns({ - limit: sinon.stub().returns({ - sort: sinon.stub().returns(mockMonitors), - }), - }), - }); - - const result = await getMonitorsByTeamId(req); - expect(result.monitorCount).to.equal(2); - expect(result.monitors).to.have.lengthOf(2); - }); - - it("should handle database errors", async function () { - const req = { - params: { teamId: "team123" }, - query: {}, - }; - - const error = new Error("Database error"); - monitorFindStub.returns({ - skip: sinon.stub().returns({ - limit: sinon.stub().returns({ - sort: sinon.stub().throws(error), - }), - }), - }); - - try { - await getMonitorsByTeamId(req); - expect.fail("Should have thrown an error"); - } catch (err) { - expect(err.service).to.equal("monitorModule"); - expect(err.method).to.equal("getMonitorsByTeamId"); - expect(err.message).to.equal("Database error"); - } - }); - }); - - describe("createMonitor", function () { - it("should create a monitor without notifications", async function () { - let monitorSaveStub = sinon.stub(Monitor.prototype, "save").resolves(); - - const req = { - body: { - name: "Test Monitor", - url: "http://test.com", - type: "http", - notifications: ["someNotification"], - }, - }; - - const expectedMonitor = { - name: "Test Monitor", - url: "http://test.com", - type: "http", - notifications: undefined, - save: monitorSaveStub, - }; - - const result = await createMonitor(req); - expect(result.name).to.equal(expectedMonitor.name); - expect(result.url).to.equal(expectedMonitor.url); - }); - - it("should handle database errors", async function () { - const req = { - body: { - name: "Test Monitor", - }, - }; - - try { - await createMonitor(req); - expect.fail("Should have thrown an error"); - } catch (err) { - expect(err.service).to.equal("monitorModule"); - expect(err.method).to.equal("createMonitor"); - } - }); - }); - - describe("deleteMonitor", function () { - it("should delete a monitor successfully", async function () { - const monitorId = "123456789"; - const mockMonitor = { - _id: monitorId, - name: "Test Monitor", - url: "http://test.com", - }; - - const req = { - params: { monitorId }, - }; - - monitorFindByIdAndDeleteStub.resolves(mockMonitor); - - const result = await deleteMonitor(req); - - expect(result).to.deep.equal(mockMonitor); - sinon.assert.calledWith(monitorFindByIdAndDeleteStub, monitorId); - }); - - it("should throw error when monitor not found", async function () { - const monitorId = "nonexistent123"; - const req = { - params: { monitorId }, - }; - - monitorFindByIdAndDeleteStub.resolves(null); - - try { - await deleteMonitor(req); - expect.fail("Should have thrown an error"); - } catch (err) { - expect(err.message).to.equal(errorMessages.DB_FIND_MONITOR_BY_ID(monitorId)); - expect(err.service).to.equal("monitorModule"); - expect(err.method).to.equal("deleteMonitor"); - } - }); - - it("should handle database errors", async function () { - const monitorId = "123456789"; - const req = { - params: { monitorId }, - }; - - const dbError = new Error("Database connection error"); - monitorFindByIdAndDeleteStub.rejects(dbError); - - try { - await deleteMonitor(req); - expect.fail("Should have thrown an error"); - } catch (err) { - expect(err.message).to.equal("Database connection error"); - expect(err.service).to.equal("monitorModule"); - expect(err.method).to.equal("deleteMonitor"); - } - }); - }); - - describe("deleteAllMonitors", function () { - it("should delete all monitors for a team successfully", async function () { - const teamId = "team123"; - const mockMonitors = [ - { _id: "1", name: "Monitor 1", teamId }, - { _id: "2", name: "Monitor 2", teamId }, - ]; - - monitorFindStub.resolves(mockMonitors); - monitorDeleteManyStub.resolves({ deletedCount: 2 }); - - const result = await deleteAllMonitors(teamId); - - expect(result).to.deep.equal({ - monitors: mockMonitors, - deletedCount: 2, - }); - sinon.assert.calledWith(monitorFindStub, { teamId }); - sinon.assert.calledWith(monitorDeleteManyStub, { teamId }); - }); - - it("should return empty array when no monitors found", async function () { - const teamId = "emptyTeam"; - - monitorFindStub.resolves([]); - monitorDeleteManyStub.resolves({ deletedCount: 0 }); - - const result = await deleteAllMonitors(teamId); - - expect(result).to.deep.equal({ - monitors: [], - deletedCount: 0, - }); - sinon.assert.calledWith(monitorFindStub, { teamId }); - sinon.assert.calledWith(monitorDeleteManyStub, { teamId }); - }); - - it("should handle database errors", async function () { - const teamId = "team123"; - const dbError = new Error("Database connection error"); - monitorFindStub.rejects(dbError); - - try { - await deleteAllMonitors(teamId); - } catch (err) { - expect(err.message).to.equal("Database connection error"); - expect(err.service).to.equal("monitorModule"); - expect(err.method).to.equal("deleteAllMonitors"); - } - }); - - it("should handle deleteMany errors", async function () { - const teamId = "team123"; - monitorFindStub.resolves([{ _id: "1", name: "Monitor 1" }]); - monitorDeleteManyStub.rejects(new Error("Delete operation failed")); - - try { - await deleteAllMonitors(teamId); - } catch (err) { - expect(err.message).to.equal("Delete operation failed"); - expect(err.service).to.equal("monitorModule"); - expect(err.method).to.equal("deleteAllMonitors"); - } - }); - }); - - describe("deleteMonitorsByUserId", function () { - beforeEach(function () {}); - - afterEach(function () { - sinon.restore(); - }); - - it("should delete all monitors for a user successfully", async function () { - // Arrange - const userId = "user123"; - const mockResult = { - deletedCount: 3, - acknowledged: true, - }; - - monitorDeleteManyStub.resolves(mockResult); - - // Act - const result = await deleteMonitorsByUserId(userId); - - // Assert - expect(result).to.deep.equal(mockResult); - sinon.assert.calledWith(monitorDeleteManyStub, { userId: userId }); - }); - - it("should return zero deletedCount when no monitors found", async function () { - // Arrange - const userId = "nonexistentUser"; - const mockResult = { - deletedCount: 0, - acknowledged: true, - }; - - monitorDeleteManyStub.resolves(mockResult); - - // Act - const result = await deleteMonitorsByUserId(userId); - - // Assert - expect(result.deletedCount).to.equal(0); - sinon.assert.calledWith(monitorDeleteManyStub, { userId: userId }); - }); - - it("should handle database errors", async function () { - // Arrange - const userId = "user123"; - const dbError = new Error("Database connection error"); - monitorDeleteManyStub.rejects(dbError); - - // Act & Assert - try { - await deleteMonitorsByUserId(userId); - expect.fail("Should have thrown an error"); - } catch (err) { - expect(err.message).to.equal("Database connection error"); - expect(err.service).to.equal("monitorModule"); - expect(err.method).to.equal("deleteMonitorsByUserId"); - } - }); - }); - - describe("editMonitor", function () { - it("should edit a monitor successfully", async function () { - // Arrange - const candidateId = "monitor123"; - const candidateMonitor = { - name: "Updated Monitor", - url: "http://updated.com", - type: "http", - notifications: ["someNotification"], - }; - - const expectedUpdateData = { - name: "Updated Monitor", - url: "http://updated.com", - type: "http", - notifications: undefined, - }; - - const mockUpdatedMonitor = { - _id: candidateId, - ...expectedUpdateData, - }; - - monitorFindByIdAndUpdateStub.resolves(mockUpdatedMonitor); - - // Act - const result = await editMonitor(candidateId, candidateMonitor); - - // Assert - expect(result).to.deep.equal(mockUpdatedMonitor); - sinon.assert.calledWith(monitorFindByIdAndUpdateStub, candidateId, expectedUpdateData, { - new: true, - }); - }); - - it("should return null when monitor not found", async function () { - // Arrange - const candidateId = "nonexistent123"; - const candidateMonitor = { - name: "Updated Monitor", - }; - - monitorFindByIdAndUpdateStub.resolves(null); - - // Act - const result = await editMonitor(candidateId, candidateMonitor); - - // Assert - expect(result).to.be.null; - sinon.assert.calledWith(monitorFindByIdAndUpdateStub, candidateId, { name: "Updated Monitor", notifications: undefined }, { new: true }); - }); - - it("should remove notifications from update data", async function () { - // Arrange - const candidateId = "monitor123"; - const candidateMonitor = { - name: "Updated Monitor", - notifications: ["notification1", "notification2"], - }; - - const expectedUpdateData = { - name: "Updated Monitor", - notifications: undefined, - }; - - monitorFindByIdAndUpdateStub.resolves({ - _id: candidateId, - ...expectedUpdateData, - }); - - // Act - await editMonitor(candidateId, candidateMonitor); - - // Assert - sinon.assert.calledWith(monitorFindByIdAndUpdateStub, candidateId, expectedUpdateData, { - new: true, - }); - }); - - it("should handle database errors", async function () { - // Arrange - const candidateId = "monitor123"; - const candidateMonitor = { - name: "Updated Monitor", - }; - - const dbError = new Error("Database connection error"); - monitorFindByIdAndUpdateStub.rejects(dbError); - - // Act & Assert - try { - await editMonitor(candidateId, candidateMonitor); - expect.fail("Should have thrown an error"); - } catch (err) { - expect(err.message).to.equal("Database connection error"); - expect(err.service).to.equal("monitorModule"); - expect(err.method).to.equal("editMonitor"); - } - }); - }); - - describe("addDemoMonitors", function () { - it("should add demo monitors successfully", async function () { - // Arrange - const userId = "user123"; - const teamId = "team123"; - monitorInsertManyStub.resolves([{ _id: "123" }]); - const result = await addDemoMonitors(userId, teamId); - expect(result).to.deep.equal([{ _id: "123" }]); - }); - - it("should handle database errors", async function () { - const userId = "user123"; - const teamId = "team123"; - - const dbError = new Error("Database connection error"); - monitorInsertManyStub.rejects(dbError); - - try { - await addDemoMonitors(userId, teamId); - } catch (err) { - expect(err.message).to.equal("Database connection error"); - expect(err.service).to.equal("monitorModule"); - expect(err.method).to.equal("addDemoMonitors"); - } - }); - }); -}); diff --git a/server/tests/db/notificationModule.test.js b/server/tests/db/notificationModule.test.js deleted file mode 100755 index fbb97f33a..000000000 --- a/server/tests/db/notificationModule.test.js +++ /dev/null @@ -1,76 +0,0 @@ -import sinon from "sinon"; -import Notification from "../../db/models/Notification.js"; -import { createNotification, getNotificationsByMonitorId, deleteNotificationsByMonitorId } from "../../db/mongo/modules/notificationModule.js"; - -describe("notificationModule", function () { - const mockNotification = { - monitorId: "123", - }; - const mockNotifications = [mockNotification]; - let notificationSaveStub, notificationFindStub, notificationDeleteManyStub; - - beforeEach(function () { - notificationSaveStub = sinon.stub(Notification.prototype, "save").resolves(); - notificationFindStub = sinon.stub(Notification, "find").resolves(); - notificationDeleteManyStub = sinon.stub(Notification, "deleteMany").resolves(); - }); - - afterEach(function () { - sinon.restore(); - }); - - describe("createNotification", function () { - it("should create a new notification", async function () { - const notificationData = { _id: "123", name: "test" }; - notificationSaveStub.resolves(notificationData); - const res = await createNotification(notificationData); - expect(res).to.deep.equal(notificationData); - }); - - it("should handle an error", async function () { - const err = new Error("test error"); - notificationSaveStub.rejects(err); - try { - await createNotification(mockNotification); - } catch (error) { - expect(error).to.deep.equal(err); - } - }); - }); - - describe("getNotificationsByMonitorId", function () { - it("should return notifications by monitor ID", async function () { - notificationFindStub.resolves(mockNotifications); - const res = await getNotificationsByMonitorId(mockNotification.monitorId); - expect(res).to.deep.equal(mockNotifications); - }); - - it("should handle an error", async function () { - const err = new Error("test error"); - notificationFindStub.rejects(err); - try { - await getNotificationsByMonitorId(mockNotification.monitorId); - } catch (error) { - expect(error).to.deep.equal(err); - } - }); - }); - - describe("deleteNotificationsByMonitorId", function () { - it("should delete notifications by monitor ID", async function () { - notificationDeleteManyStub.resolves({ deletedCount: mockNotifications.length }); - const res = await deleteNotificationsByMonitorId(mockNotification.monitorId); - expect(res).to.deep.equal(mockNotifications.length); - }); - - it("should handle an error", async function () { - const err = new Error("test error"); - notificationDeleteManyStub.rejects(err); - try { - await deleteNotificationsByMonitorId(mockNotification.monitorId); - } catch (error) { - expect(error).to.deep.equal(err); - } - }); - }); -}); diff --git a/server/tests/db/pageSpeedCheckModule.test.js b/server/tests/db/pageSpeedCheckModule.test.js deleted file mode 100755 index 45135135d..000000000 --- a/server/tests/db/pageSpeedCheckModule.test.js +++ /dev/null @@ -1,63 +0,0 @@ -import sinon from "sinon"; -import PageSpeedCheck from "../../db/models/PageSpeedCheck.js"; -import { createPageSpeedCheck, deletePageSpeedChecksByMonitorId } from "../../db/mongo/modules/pageSpeedCheckModule.js"; - -const mockPageSpeedCheck = { - monitorId: "monitorId", - bestPractices: 1, - seo: 1, - performance: 1, -}; - -const mockDeletedResult = { deletedCount: 1 }; - -describe("pageSpeedCheckModule", function () { - let pageSpeedCheckSaveStub, pageSpeedCheckDeleteManyStub; - - beforeEach(function () { - pageSpeedCheckSaveStub = sinon.stub(PageSpeedCheck.prototype, "save"); - pageSpeedCheckDeleteManyStub = sinon.stub(PageSpeedCheck, "deleteMany"); - }); - - afterEach(function () { - sinon.restore(); - }); - - describe("createPageSpeedCheck", function () { - it("should return a page speed check", async function () { - pageSpeedCheckSaveStub.resolves(mockPageSpeedCheck); - const pageSpeedCheck = await createPageSpeedCheck(mockPageSpeedCheck); - expect(pageSpeedCheck).to.deep.equal(mockPageSpeedCheck); - }); - - it("should handle an error", async function () { - const err = new Error("test error"); - pageSpeedCheckSaveStub.rejects(err); - try { - await expect(createPageSpeedCheck(mockPageSpeedCheck)); - } catch (error) { - expect(error).to.exist; - expect(error).to.deep.equal(err); - } - }); - }); - - describe("deletePageSpeedChecksByMonitorId", function () { - it("should return the number of deleted checks", async function () { - pageSpeedCheckDeleteManyStub.resolves(mockDeletedResult); - const result = await deletePageSpeedChecksByMonitorId("monitorId"); - expect(result).to.deep.equal(mockDeletedResult.deletedCount); - }); - - it("should handle an error", async function () { - const err = new Error("test error"); - pageSpeedCheckDeleteManyStub.rejects(err); - try { - await expect(deletePageSpeedChecksByMonitorId("monitorId")); - } catch (error) { - expect(error).to.exist; - expect(error).to.deep.equal(err); - } - }); - }); -}); diff --git a/server/tests/db/recoveryModule.test.js b/server/tests/db/recoveryModule.test.js deleted file mode 100755 index 88afa257a..000000000 --- a/server/tests/db/recoveryModule.test.js +++ /dev/null @@ -1,168 +0,0 @@ -import sinon from "sinon"; -import RecoveryToken from "../../db/models/RecoveryToken.js"; -import User from "../../db/models/User.js"; -import { requestRecoveryToken, validateRecoveryToken, resetPassword } from "../../db/mongo/modules/recoveryModule.js"; -import { errorMessages } from "../../utils/messages.js"; - -const mockRecoveryToken = { - email: "test@test.com", - token: "1234567890", -}; - -const mockUser = { - email: "test@test.com", - password: "oldPassword", -}; - -const mockUserWithoutPassword = { - email: "test@test.com", -}; - -// Create a query builder that logs -const createQueryChain = (finalResult, comparePasswordResult = false) => ({ - select: () => ({ - select: async () => { - if (finalResult === mockUser) { - // Return a new object with all required methods - return { - email: "test@test.com", - password: "oldPassword", - comparePassword: sinon.stub().resolves(comparePasswordResult), - save: sinon.stub().resolves(), - }; - } - return finalResult; - }, - }), - // Add methods to the top level too - comparePassword: sinon.stub().resolves(comparePasswordResult), - save: sinon.stub().resolves(), -}); - -describe("recoveryModule", function () { - let deleteManyStub, saveStub, findOneStub, userCompareStub, userSaveStub, userFindOneStub; - let req, res; - - beforeEach(function () { - req = { - body: { email: "test@test.com" }, - }; - deleteManyStub = sinon.stub(RecoveryToken, "deleteMany"); - saveStub = sinon.stub(RecoveryToken.prototype, "save"); - findOneStub = sinon.stub(RecoveryToken, "findOne"); - userCompareStub = sinon.stub(User.prototype, "comparePassword"); - userSaveStub = sinon.stub(User.prototype, "save"); - userFindOneStub = sinon.stub().resolves(); - }); - - afterEach(function () { - sinon.restore(); - }); - - describe("requestRecoveryToken", function () { - it("should return a recovery token", async function () { - deleteManyStub.resolves(); - saveStub.resolves(mockRecoveryToken); - const result = await requestRecoveryToken(req, res); - expect(result.email).to.equal(mockRecoveryToken.email); - }); - - it("should handle an error", async function () { - const err = new Error("Test error"); - deleteManyStub.rejects(err); - try { - await requestRecoveryToken(req, res); - } catch (error) { - expect(error).to.exist; - expect(error).to.deep.equal(err); - } - }); - }); - - describe("validateRecoveryToken", function () { - it("should return a recovery token if found", async function () { - findOneStub.resolves(mockRecoveryToken); - const result = await validateRecoveryToken(req, res); - expect(result).to.deep.equal(mockRecoveryToken); - }); - - it("should thrown an error if a token is not found", async function () { - findOneStub.resolves(null); - try { - await validateRecoveryToken(req, res); - } catch (error) { - expect(error).to.exist; - expect(error.message).to.equal(errorMessages.DB_TOKEN_NOT_FOUND); - } - }); - - it("should handle DB errors", async function () { - const err = new Error("Test error"); - findOneStub.rejects(err); - try { - await validateRecoveryToken(req, res); - } catch (error) { - expect(error).to.exist; - expect(error).to.deep.equal(err); - } - }); - }); - - describe("resetPassword", function () { - beforeEach(function () { - req.body = { - password: "test", - newPassword: "test1", - }; - }); - - afterEach(function () { - sinon.restore(); - }); - - it("should thrown an error if a recovery token is not found", async function () { - findOneStub.resolves(null); - try { - await resetPassword(req, res); - } catch (error) { - expect(error).to.exist; - expect(error.message).to.equal(errorMessages.DB_TOKEN_NOT_FOUND); - } - }); - - it("should throw an error if a user is not found", async function () { - findOneStub.resolves(mockRecoveryToken); - userFindOneStub = sinon.stub(User, "findOne").resolves(null); - try { - await resetPassword(req, res); - } catch (error) { - expect(error).to.exist; - expect(error.message).to.equal(errorMessages.DB_USER_NOT_FOUND); - } - }); - - it("should throw an error if the passwords match", async function () { - findOneStub.resolves(mockRecoveryToken); - saveStub.resolves(); - userFindOneStub = sinon.stub(User, "findOne").returns(createQueryChain(mockUser, true)); - try { - await resetPassword(req, res); - } catch (error) { - expect(error).to.exist; - expect(error.message).to.equal(errorMessages.DB_RESET_PASSWORD_BAD_MATCH); - } - }); - - it("should return a user without password if successful", async function () { - findOneStub.resolves(mockRecoveryToken); - saveStub.resolves(); - userFindOneStub = sinon - .stub(User, "findOne") - .returns(createQueryChain(mockUser)) // First call will resolve to mockUser - .onSecondCall() - .returns(createQueryChain(mockUserWithoutPassword)); - const result = await resetPassword(req, res); - expect(result).to.deep.equal(mockUserWithoutPassword); - }); - }); -}); diff --git a/server/tests/db/settingsModule.test.js b/server/tests/db/settingsModule.test.js deleted file mode 100755 index 9d1a80c3e..000000000 --- a/server/tests/db/settingsModule.test.js +++ /dev/null @@ -1,56 +0,0 @@ -import sinon from "sinon"; -import { getAppSettings, updateAppSettings } from "../../db/mongo/modules/settingsModule.js"; -import AppSettings from "../../db/models/AppSettings.js"; - -const mockAppSettings = { - appName: "Test App", -}; - -describe("SettingsModule", function () { - let appSettingsFindOneStub, appSettingsFindOneAndUpdateStub; - - beforeEach(function () { - appSettingsFindOneStub = sinon.stub(AppSettings, "findOne"); - appSettingsFindOneAndUpdateStub = sinon.stub(AppSettings, "findOneAndUpdate"); - }); - - afterEach(function () { - sinon.restore(); - }); - - describe("getAppSettings", function () { - it("should return app settings", async function () { - appSettingsFindOneStub.resolves(mockAppSettings); - const result = await getAppSettings(); - expect(result).to.deep.equal(mockAppSettings); - }); - - it("should handle an error", async function () { - const err = new Error("Test error"); - appSettingsFindOneStub.throws(err); - try { - await getAppSettings(); - } catch (error) { - expect(error).to.deep.equal(err); - } - }); - }); - - describe("updateAppSettings", function () { - it("should update app settings", async function () { - appSettingsFindOneAndUpdateStub.resolves(mockAppSettings); - const result = await updateAppSettings(mockAppSettings); - expect(result).to.deep.equal(mockAppSettings); - }); - - it("should handle an error", async function () { - const err = new Error("Test error"); - appSettingsFindOneAndUpdateStub.throws(err); - try { - await updateAppSettings(mockAppSettings); - } catch (error) { - expect(error).to.deep.equal(err); - } - }); - }); -}); diff --git a/server/tests/db/statusPageModule.test.js b/server/tests/db/statusPageModule.test.js deleted file mode 100755 index 54e1a287f..000000000 --- a/server/tests/db/statusPageModule.test.js +++ /dev/null @@ -1,70 +0,0 @@ -import sinon from "sinon"; -import { createStatusPage, getStatusPageByUrl } from "../../db/mongo/modules/statusPageModule.js"; -import StatusPage from "../../db/models/StatusPage.js"; -import { errorMessages } from "../../utils/messages.js"; - -describe("statusPageModule", function () { - let statusPageFindOneStub, statusPageSaveStub, statusPageFindStub; - - beforeEach(function () { - statusPageSaveStub = sinon.stub(StatusPage.prototype, "save"); - statusPageFindOneStub = sinon.stub(StatusPage, "findOne"); - statusPageFindStub = sinon.stub(StatusPage, "find"); - }); - - afterEach(function () { - sinon.restore(); - }); - - describe("createStatusPage", function () { - it("should throw an error if a non-unique url is provided", async function () { - statusPageFindOneStub.resolves(true); - try { - await createStatusPage({ url: "test" }); - } catch (error) { - expect(error.status).to.equal(400); - expect(error.message).to.equal(errorMessages.STATUS_PAGE_URL_NOT_UNIQUE); - } - }); - - it("should handle duplicate URL errors", async function () { - const err = new Error("test"); - err.code = 11000; - statusPageSaveStub.rejects(err); - try { - await createStatusPage({ url: "test" }); - } catch (error) { - expect(error).to.deep.equal(err); - } - }); - - it("should return a status page if a unique url is provided", async function () { - statusPageFindOneStub.resolves(null); - statusPageFindStub.resolves([]); - const mockStatusPage = { url: "test" }; - const statusPage = await createStatusPage(mockStatusPage); - expect(statusPage).to.exist; - expect(statusPage.url).to.equal(mockStatusPage.url); - }); - }); - - describe("getStatusPageByUrl", function () { - it("should throw an error if a status page is not found", async function () { - statusPageFindOneStub.resolves(null); - try { - await getStatusPageByUrl("test"); - } catch (error) { - expect(error.status).to.equal(404); - expect(error.message).to.equal(errorMessages.STATUS_PAGE_NOT_FOUND); - } - }); - - it("should return a status page if a status page is found", async function () { - const mockStatusPage = { url: "test" }; - statusPageFindOneStub.resolves(mockStatusPage); - const statusPage = await getStatusPageByUrl(mockStatusPage.url); - expect(statusPage).to.exist; - expect(statusPage).to.deep.equal(mockStatusPage); - }); - }); -}); diff --git a/server/tests/db/userModule.test.js b/server/tests/db/userModule.test.js deleted file mode 100755 index d0f5ecb3e..000000000 --- a/server/tests/db/userModule.test.js +++ /dev/null @@ -1,294 +0,0 @@ -import sinon from "sinon"; -import UserModel from "../../db/models/User.js"; -import TeamModel from "../../db/models/Team.js"; -import { - insertUser, - getUserByEmail, - updateUser, - deleteUser, - deleteTeam, - deleteAllOtherUsers, - getAllUsers, - logoutUser, -} from "../../db/mongo/modules/userModule.js"; -import { errorMessages } from "../../utils/messages.js"; - -const mockUser = { - email: "test@test.com", - password: "password", - role: ["user"], -}; -const mockSuperUser = { - email: "test@test.com", - password: "password", - role: ["superadmin"], -}; -const imageFile = { - image: 1, -}; - -describe("userModule", function () { - let teamSaveStub, - teamFindByIdAndDeleteStub, - userSaveStub, - userFindStub, - userFindOneStub, - userFindByIdAndUpdateStub, - userFindByIdAndDeleteStub, - userDeleteManyStub, - userUpdateOneStub, - generateAvatarImageStub, - parseBooleanStub; - - beforeEach(function () { - teamSaveStub = sinon.stub(TeamModel.prototype, "save"); - teamFindByIdAndDeleteStub = sinon.stub(TeamModel, "findByIdAndDelete"); - userSaveStub = sinon.stub(UserModel.prototype, "save"); - userFindStub = sinon.stub(UserModel, "find"); - userFindOneStub = sinon.stub(UserModel, "findOne"); - userFindByIdAndUpdateStub = sinon.stub(UserModel, "findByIdAndUpdate"); - userFindByIdAndDeleteStub = sinon.stub(UserModel, "findByIdAndDelete"); - userDeleteManyStub = sinon.stub(UserModel, "deleteMany"); - userUpdateOneStub = sinon.stub(UserModel, "updateOne"); - generateAvatarImageStub = sinon.stub().resolves({ image: 2 }); - parseBooleanStub = sinon.stub().returns(true); - }); - - afterEach(function () { - sinon.restore(); - }); - - describe("insertUser", function () { - it("should insert a regular user", async function () { - userSaveStub.resolves(mockUser); - userFindOneStub.returns({ - select: sinon.stub().returns({ - select: sinon.stub().resolves(mockUser), - }), - }); - const result = await insertUser(mockUser, imageFile, generateAvatarImageStub); - expect(result).to.deep.equal(mockUser); - }); - - it("should insert a superadmin user", async function () { - userSaveStub.resolves(mockSuperUser); - userFindOneStub.returns({ - select: sinon.stub().returns({ - select: sinon.stub().resolves(mockSuperUser), - }), - }); - const result = await insertUser(mockSuperUser, imageFile, generateAvatarImageStub); - expect(result).to.deep.equal(mockSuperUser); - }); - - it("should handle an error", async function () { - const err = new Error("test error"); - userSaveStub.rejects(err); - try { - await insertUser(mockUser, imageFile, generateAvatarImageStub); - } catch (error) { - expect(error).to.exist; - expect(error).to.deep.equal(err); - } - }); - - it("should handle a duplicate key error", async function () { - const err = new Error("test error"); - err.code = 11000; - userSaveStub.rejects(err); - try { - await insertUser(mockUser, imageFile, generateAvatarImageStub); - } catch (error) { - expect(error).to.exist; - expect(error).to.deep.equal(err); - } - }); - }); - - describe("getUserByEmail", function () { - it("should return a user", async function () { - userFindOneStub.returns({ - select: sinon.stub().resolves(mockUser), - }); - const result = await getUserByEmail(mockUser.email); - expect(result).to.deep.equal(mockUser); - }); - }); - - describe("getUserByEmail", function () { - it("throw an error if a user is not found", async function () { - userFindOneStub.returns({ - select: sinon.stub().resolves(null), - }); - try { - await getUserByEmail(mockUser.email); - } catch (error) { - expect(error.message).to.equal(errorMessages.DB_USER_NOT_FOUND); - } - }); - }); - - describe("updateUser", function () { - let req, res; - - beforeEach(function () { - req = { - params: { - userId: "testId", - }, - body: { - deleteProfileImage: "false", - email: "test@test.com", - }, - file: { - buffer: "test", - mimetype: "test", - }, - }; - res = {}; - }); - - afterEach(function () {}); - - it("should update a user", async function () { - parseBooleanStub.returns(false); - userFindByIdAndUpdateStub.returns({ - select: sinon.stub().returns({ - select: sinon.stub().resolves(mockUser), - }), - }); - const result = await updateUser(req, res, parseBooleanStub, generateAvatarImageStub); - expect(result).to.deep.equal(mockUser); - }); - - it("should delete a user profile image", async function () { - req.body.deleteProfileImage = "true"; - userFindByIdAndUpdateStub.returns({ - select: sinon.stub().returns({ - select: sinon.stub().resolves(mockUser), - }), - }); - const result = await updateUser(req, res, parseBooleanStub, generateAvatarImageStub); - expect(result).to.deep.equal(mockUser); - }); - - it("should handle an error", async function () { - const err = new Error("test error"); - userFindByIdAndUpdateStub.throws(err); - try { - await updateUser(req, res, parseBooleanStub, generateAvatarImageStub); - } catch (error) { - expect(error).to.exist; - expect(error).to.deep.equal(err); - } - }); - }); - - describe("deleteUser", function () { - it("should return a deleted user", async function () { - userFindByIdAndDeleteStub.resolves(mockUser); - const result = await deleteUser("testId"); - expect(result).to.deep.equal(mockUser); - }); - - it("should throw an error if a user is not found", async function () { - try { - await deleteUser("testId"); - } catch (error) { - expect(error).to.exist; - expect(error.message).to.equal(errorMessages.DB_USER_NOT_FOUND); - } - }); - - it("should handle an error", async function () { - const err = new Error("test error"); - userFindByIdAndDeleteStub.throws(err); - try { - await deleteUser("testId"); - } catch (error) { - expect(error).to.exist; - expect(error).to.deep.equal(err); - } - }); - }); - - describe("deleteTeam", function () { - it("should return true if team deleted", async function () { - teamFindByIdAndDeleteStub.resolves(); - const result = await deleteTeam("testId"); - expect(result).to.equal(true); - }); - - it("should handle an error", async function () { - const err = new Error("test error"); - teamFindByIdAndDeleteStub.throws(err); - try { - await deleteTeam("testId"); - } catch (error) { - expect(error).to.exist; - expect(error).to.deep.equal(err); - } - }); - }); - - describe("deleteAllOtherUsers", function () { - it("should return true if all other users deleted", async function () { - userDeleteManyStub.resolves(true); - const result = await deleteAllOtherUsers(); - expect(result).to.equal(true); - }); - - it("should handle an error", async function () { - const err = new Error("test error"); - userDeleteManyStub.throws(err); - try { - await deleteAllOtherUsers(); - } catch (error) { - expect(error).to.exist; - expect(error).to.deep.equal(err); - } - }); - }); - - describe("getAllUsers", function () { - it("should return all users", async function () { - userFindStub.returns({ - select: sinon.stub().returns({ - select: sinon.stub().resolves([mockUser]), - }), - }); - const result = await getAllUsers(); - expect(result).to.deep.equal([mockUser]); - }); - - it("should handle an error", async function () { - const err = new Error("test error"); - userFindStub.throws(err); - try { - await getAllUsers(); - } catch (error) { - expect(error).to.exist; - expect(error).to.deep.equal(err); - } - }); - }); - - describe("logoutUser", function () { - it("should return true if user logged out", async function () { - userUpdateOneStub.resolves(true); - const result = await logoutUser("testId"); - expect(result).to.equal(true); - }); - - it("should handle an error", async function () { - const err = new Error("test error"); - userUpdateOneStub.throws(err); - try { - await logoutUser("testId"); - } catch (error) { - expect(error).to.exist; - expect(error).to.deep.equal(err); - } - }); - }); -}); diff --git a/server/tests/services/emailService.test.js b/server/tests/services/emailService.test.js deleted file mode 100755 index cca86896a..000000000 --- a/server/tests/services/emailService.test.js +++ /dev/null @@ -1,168 +0,0 @@ -import sinon from "sinon"; -import EmailService from "../../service/emailService.js"; - -describe("EmailService - Constructor", function () { - let settingsServiceMock; - let fsMock; - let pathMock; - let compileMock; - let mjml2htmlMock; - let nodemailerMock; - let loggerMock; - - beforeEach(function () { - settingsServiceMock = { - getSettings: sinon.stub().returns({ - systemEmailHost: "smtp.example.com", - systemEmailPort: 465, - systemEmailAddress: "test@example.com", - systemEmailPassword: "password", - }), - }; - - fsMock = { - readFileSync: sinon.stub().returns(""), - }; - - pathMock = { - join: sinon.stub().callsFake((...args) => args.join("/")), - }; - - compileMock = sinon.stub().returns(() => ""); - - mjml2htmlMock = sinon.stub().returns({ html: "" }); - - nodemailerMock = { - createTransport: sinon.stub().returns({ - sendMail: sinon.stub().resolves({ messageId: "12345" }), - }), - }; - - loggerMock = { - error: sinon.stub(), - }; - }); - - afterEach(function () { - sinon.restore(); - }); - - it("should initialize template loaders and email transporter", function () { - const emailService = new EmailService(settingsServiceMock, fsMock, pathMock, compileMock, mjml2htmlMock, nodemailerMock, loggerMock); - - // Verify that the settingsService is assigned correctly - expect(emailService.settingsService).to.equal(settingsServiceMock); - - // Verify that the template loaders are initialized - expect(emailService.templateLookup.welcomeEmailTemplate).to.be.a("function"); - expect(emailService.templateLookup.employeeActivationTemplate).to.be.a("function"); - - // Verify that the email transporter is initialized - expect(nodemailerMock.createTransport.calledOnce).to.be.true; - const emailConfig = nodemailerMock.createTransport.getCall(0).args[0]; - expect(emailConfig).to.deep.equal({ - host: "smtp.example.com", - port: 465, - secure: true, - auth: { - user: "test@example.com", - pass: "password", - }, - }); - }); - - it("should have undefined templates if FS fails", function () { - fsMock = { - readFileSync: sinon.stub().throws(new Error("File read error")), - }; - const emailService = new EmailService(settingsServiceMock, fsMock, pathMock, compileMock, mjml2htmlMock, nodemailerMock, loggerMock); - expect(loggerMock.error.called).to.be.true; - expect(loggerMock.error.firstCall.args[0].message).to.equal("File read error"); - }); -}); - -describe("EmailService - buildAndSendEmail", function () { - let settingsServiceMock; - let fsMock; - let pathMock; - let compileMock; - let mjml2htmlMock; - let nodemailerMock; - let loggerMock; - let emailService; - - beforeEach(function () { - settingsServiceMock = { - getSettings: sinon.stub().returns({ - systemEmailHost: "smtp.example.com", - systemEmailPort: 465, - systemEmailAddress: "test@example.com", - systemEmailPassword: "password", - }), - }; - - fsMock = { - readFileSync: sinon.stub().returns(""), - }; - - pathMock = { - join: sinon.stub().callsFake((...args) => args.join("/")), - }; - - compileMock = sinon.stub().returns(() => ""); - - mjml2htmlMock = sinon.stub().returns({ html: "" }); - - nodemailerMock = { - createTransport: sinon.stub().returns({ - sendMail: sinon.stub().resolves({ messageId: "12345" }), - }), - }; - - loggerMock = { - error: sinon.stub(), - }; - - emailService = new EmailService(settingsServiceMock, fsMock, pathMock, compileMock, mjml2htmlMock, nodemailerMock, loggerMock); - }); - - afterEach(function () { - sinon.restore(); - }); - - it("should build and send email successfully", async function () { - const messageId = await emailService.buildAndSendEmail("welcomeEmailTemplate", {}, "recipient@example.com", "Welcome"); - - expect(messageId).to.equal("12345"); - expect(nodemailerMock.createTransport().sendMail.calledOnce).to.be.true; - }); - - it("should log error if building HTML fails", async function () { - mjml2htmlMock.throws(new Error("MJML error")); - - const messageId = await emailService.buildAndSendEmail("welcomeEmailTemplate", {}, "recipient@example.com", "Welcome"); - expect(loggerMock.error.calledOnce).to.be.true; - expect(loggerMock.error.getCall(0).args[0].message).to.equal("MJML error"); - }); - - it("should log error if sending email fails", async function () { - nodemailerMock.createTransport().sendMail.rejects(new Error("SMTP error")); - await emailService.buildAndSendEmail("welcomeEmailTemplate", {}, "recipient@example.com", "Welcome"); - expect(loggerMock.error.calledOnce).to.be.true; - expect(loggerMock.error.getCall(0).args[0].message).to.equal("SMTP error"); - }); - - it("should log error if both building HTML and sending email fail", async function () { - mjml2htmlMock.throws(new Error("MJML error")); - nodemailerMock.createTransport().sendMail.rejects(new Error("SMTP error")); - - const messageId = await emailService.buildAndSendEmail("welcomeEmailTemplate", {}, "recipient@example.com", "Welcome"); - - expect(messageId).to.be.undefined; - expect(loggerMock.error.calledTwice).to.be.true; - expect(loggerMock.error.getCall(0).args[0].message).to.equal("MJML error"); - expect(loggerMock.error.getCall(1).args[0].message).to.equal("SMTP error"); - }); - - it("should log an error if buildHtml fails", async function () {}); -}); diff --git a/server/tests/services/jobQueue.test.js b/server/tests/services/jobQueue.test.js deleted file mode 100755 index 155f9119e..000000000 --- a/server/tests/services/jobQueue.test.js +++ /dev/null @@ -1,813 +0,0 @@ -import sinon from "sinon"; -import JobQueue from "../../service/jobQueue.js"; -import { log } from "console"; - -class QueueStub { - constructor(queueName, options) { - this.queueName = queueName; - this.options = options; - this.workers = []; - this.jobs = []; - } - - // Add any methods that are expected to be called on the Queue instance - add(job) { - this.jobs.push(job); - } - - removeRepeatable(id) { - const removedJob = this.jobs.find((job) => job.data._id === id); - this.jobs = this.jobs.filter((job) => job.data._id !== id); - if (removedJob) { - return true; - } - return false; - } - - getRepeatableJobs() { - return this.jobs; - } - async getJobs() { - return this.jobs; - } - - async pause() { - return true; - } - - async obliterate() { - return true; - } -} - -class WorkerStub { - constructor(QUEUE_NAME, workerTask) { - this.queueName = QUEUE_NAME; - this.workerTask = async () => workerTask({ data: { _id: 1 } }); - } - - async close() { - return true; - } -} - -describe("JobQueue", function () { - let settingsService, logger, db, networkService, statusService, notificationService, jobQueue; - - beforeEach(async function () { - settingsService = { getSettings: sinon.stub() }; - statusService = { updateStatus: sinon.stub() }; - notificationService = { handleNotifications: sinon.stub() }; - - logger = { error: sinon.stub(), info: sinon.stub() }; - db = { - getAllMonitors: sinon.stub().returns([]), - getMaintenanceWindowsByMonitorId: sinon.stub().returns([]), - }; - networkService = { getStatus: sinon.stub() }; - jobQueue = await JobQueue.createJobQueue(db, networkService, statusService, notificationService, settingsService, logger, QueueStub, WorkerStub); - }); - - afterEach(function () { - sinon.restore(); - }); - - describe("createJobQueue", function () { - it("should create a new JobQueue and add jobs for active monitors", async function () { - db.getAllMonitors.returns([ - { id: 1, isActive: true }, - { id: 2, isActive: true }, - ]); - const jobQueue = await JobQueue.createJobQueue( - db, - networkService, - statusService, - notificationService, - settingsService, - logger, - QueueStub, - WorkerStub - ); - // There should be double the jobs, as one is meant to be instantly executed - // And one is meant to be enqueued - expect(jobQueue.queue.jobs.length).to.equal(4); - }); - - it("should reject with an error if an error occurs", async function () { - db.getAllMonitors.throws("Error"); - try { - const jobQueue = await JobQueue.createJobQueue( - db, - networkService, - statusService, - notificationService, - settingsService, - logger, - QueueStub, - WorkerStub - ); - } catch (error) { - expect(error.service).to.equal("JobQueue"); - expect(error.method).to.equal("createJobQueue"); - } - }); - - it("should reject with an error if an error occurs, should not overwrite error data", async function () { - const error = new Error("Error"); - error.service = "otherService"; - error.method = "otherMethod"; - db.getAllMonitors.throws(error); - - try { - const jobQueue = await JobQueue.createJobQueue( - db, - networkService, - statusService, - notificationService, - settingsService, - logger, - QueueStub, - WorkerStub - ); - } catch (error) { - expect(error.service).to.equal("otherService"); - expect(error.method).to.equal("otherMethod"); - } - }); - }); - - describe("Constructor", function () { - it("should construct a new JobQueue with default port and host if not provided", async function () { - settingsService.getSettings.returns({}); - - expect(jobQueue.connection.host).to.equal("127.0.0.1"); - expect(jobQueue.connection.port).to.equal(6379); - }); - - it("should construct a new JobQueue with provided port and host", async function () { - settingsService.getSettings.returns({ redisHost: "localhost", redisPort: 1234 }); - const jobQueue = await JobQueue.createJobQueue( - db, - networkService, - statusService, - notificationService, - settingsService, - logger, - QueueStub, - WorkerStub - ); - expect(jobQueue.connection.host).to.equal("localhost"); - expect(jobQueue.connection.port).to.equal(1234); - }); - }); - - describe("isMaintenanceWindow", function () { - it("should throw an error if error occurs", async function () { - db.getMaintenanceWindowsByMonitorId.throws("Error"); - const jobQueue = await JobQueue.createJobQueue( - db, - networkService, - statusService, - notificationService, - settingsService, - logger, - QueueStub, - WorkerStub - ); - try { - jobQueue.isInMaintenanceWindow(1); - } catch (error) { - expect(error.service).to.equal("JobQueue"); - expect(error.method).to.equal("createWorker"); - } - }); - - it("should return true if in maintenance window with no repeat", async function () { - db.getMaintenanceWindowsByMonitorId.returns([ - { - active: true, - start: new Date(Date.now() - 1000).toISOString(), - end: new Date(Date.now() + 1000).toISOString(), - repeat: 0, - }, - ]); - const jobQueue = await JobQueue.createJobQueue( - db, - networkService, - statusService, - notificationService, - settingsService, - logger, - QueueStub, - WorkerStub - ); - const inWindow = await jobQueue.isInMaintenanceWindow(1); - expect(inWindow).to.be.true; - }); - - it("should return true if in maintenance window with repeat", async function () { - db.getMaintenanceWindowsByMonitorId.returns([ - { - active: true, - start: new Date(Date.now() - 10000).toISOString(), - end: new Date(Date.now() - 5000).toISOString(), - repeat: 1000, - }, - ]); - const jobQueue = await JobQueue.createJobQueue( - db, - networkService, - statusService, - notificationService, - settingsService, - logger, - QueueStub, - WorkerStub - ); - const inWindow = await jobQueue.isInMaintenanceWindow(1); - expect(inWindow).to.be.true; - }); - - it("should return false if in end < start", async function () { - db.getMaintenanceWindowsByMonitorId.returns([ - { - active: true, - start: new Date(Date.now() - 5000).toISOString(), - end: new Date(Date.now() - 10000).toISOString(), - repeat: 1000, - }, - ]); - const jobQueue = await JobQueue.createJobQueue( - db, - networkService, - statusService, - notificationService, - settingsService, - logger, - QueueStub, - WorkerStub - ); - const inWindow = await jobQueue.isInMaintenanceWindow(1); - expect(inWindow).to.be.false; - }); - - it("should return false if not in maintenance window", async function () { - db.getMaintenanceWindowsByMonitorId.returns([ - { - active: false, - start: new Date(Date.now() - 5000).toISOString(), - end: new Date(Date.now() - 10000).toISOString(), - repeat: 1000, - }, - ]); - const jobQueue = await JobQueue.createJobQueue( - db, - networkService, - statusService, - notificationService, - settingsService, - logger, - QueueStub, - WorkerStub - ); - const inWindow = await jobQueue.isInMaintenanceWindow(1); - expect(inWindow).to.be.false; - }); - }); - - describe("createJobHandler", function () { - it("resolve to an error if an error is thrown within", async function () { - const jobQueue = await JobQueue.createJobQueue( - db, - networkService, - statusService, - notificationService, - settingsService, - logger, - QueueStub, - WorkerStub - ); - jobQueue.isInMaintenanceWindow = sinon.stub().throws("Error"); - try { - const handler = jobQueue.createJobHandler(); - await handler({ data: { _id: 1 } }); - } catch (error) { - expect(error.service).to.equal("JobQueue"); - expect(error.details).to.equal(`Error processing job 1: Error`); - } - }); - - it("should log info if job is in maintenance window", async function () { - const jobQueue = await JobQueue.createJobQueue( - db, - networkService, - statusService, - notificationService, - settingsService, - logger, - QueueStub, - WorkerStub - ); - jobQueue.isInMaintenanceWindow = sinon.stub().returns(true); - const handler = jobQueue.createJobHandler(); - await handler({ data: { _id: 1 } }); - expect(logger.info.calledOnce).to.be.true; - expect(logger.info.firstCall.args[0].message).to.equal("Monitor 1 is in maintenance window"); - }); - - it("should return if status has not changed", async function () { - const jobQueue = await JobQueue.createJobQueue( - db, - networkService, - statusService, - notificationService, - settingsService, - logger, - QueueStub, - WorkerStub - ); - jobQueue.isInMaintenanceWindow = sinon.stub().returns(false); - statusService.updateStatus = sinon.stub().returns({ statusChanged: false }); - const handler = jobQueue.createJobHandler(); - await handler({ data: { _id: 1 } }); - expect(jobQueue.notificationService.handleNotifications.notCalled).to.be.true; - }); - - it("should return if status has changed, but prevStatus was undefined (monitor paused)", async function () { - const jobQueue = await JobQueue.createJobQueue( - db, - networkService, - statusService, - notificationService, - settingsService, - logger, - QueueStub, - WorkerStub - ); - jobQueue.isInMaintenanceWindow = sinon.stub().returns(false); - statusService.updateStatus = sinon.stub().returns({ statusChanged: true, prevStatus: undefined }); - const handler = jobQueue.createJobHandler(); - await handler({ data: { _id: 1 } }); - expect(jobQueue.notificationService.handleNotifications.notCalled).to.be.true; - }); - - it("should call notification service if status changed and monitor was not paused", async function () { - const jobQueue = await JobQueue.createJobQueue( - db, - networkService, - statusService, - notificationService, - settingsService, - logger, - QueueStub, - WorkerStub - ); - jobQueue.isInMaintenanceWindow = sinon.stub().returns(false); - statusService.updateStatus = sinon.stub().returns({ statusChanged: true, prevStatus: false }); - const handler = jobQueue.createJobHandler(); - await handler({ data: { _id: 1 } }); - expect(jobQueue.notificationService.handleNotifications.calledOnce).to.be.true; - }); - }); - - describe("getWorkerStats", function () { - it("should throw an error if getRepeatable Jobs fails", async function () { - const jobQueue = await JobQueue.createJobQueue( - db, - networkService, - statusService, - notificationService, - settingsService, - logger, - QueueStub, - WorkerStub - ); - jobQueue.queue.getRepeatableJobs = async () => { - throw new Error("Error"); - }; - try { - const stats = await jobQueue.getWorkerStats(); - } catch (error) { - expect(error.service).to.equal("JobQueue"); - expect(error.method).to.equal("getWorkerStats"); - } - }); - - it("should throw an error if getRepeatable Jobs fails but respect existing error data", async function () { - const jobQueue = await JobQueue.createJobQueue( - db, - networkService, - statusService, - notificationService, - settingsService, - logger, - QueueStub, - WorkerStub - ); - jobQueue.queue.getRepeatableJobs = async () => { - const error = new Error("Existing Error"); - error.service = "otherService"; - error.method = "otherMethod"; - throw error; - }; - try { - await jobQueue.getWorkerStats(); - } catch (error) { - expect(error.service).to.equal("otherService"); - expect(error.method).to.equal("otherMethod"); - } - }); - }); - - describe("scaleWorkers", function () { - it("should scale workers to 5 if no workers", async function () { - const jobQueue = await JobQueue.createJobQueue( - db, - networkService, - statusService, - notificationService, - settingsService, - logger, - QueueStub, - WorkerStub - ); - expect(jobQueue.workers.length).to.equal(5); - }); - - it("should scale workers up", async function () { - const jobQueue = await JobQueue.createJobQueue( - db, - networkService, - statusService, - notificationService, - settingsService, - logger, - QueueStub, - WorkerStub - ); - jobQueue.scaleWorkers({ - load: 100, - jobs: Array.from({ length: 100 }, (_, i) => i + 1), - }); - expect(jobQueue.workers.length).to.equal(20); - }); - - it("should scale workers down, even with error of worker.close fails", async function () { - WorkerStub.prototype.close = async () => { - throw new Error("Error"); - }; - const jobQueue = await JobQueue.createJobQueue( - db, - networkService, - statusService, - notificationService, - settingsService, - logger, - QueueStub, - WorkerStub - ); - await jobQueue.scaleWorkers({ - load: 100, - jobs: Array.from({ length: 100 }, (_, i) => i + 1), - }); - - const res = await jobQueue.scaleWorkers({ - load: 0, - jobs: [], - }); - expect(jobQueue.workers.length).to.equal(5); - }); - - it("should scale workers down", async function () { - WorkerStub.prototype.close = async () => { - return true; - }; - const jobQueue = await JobQueue.createJobQueue( - db, - networkService, - statusService, - notificationService, - settingsService, - logger, - QueueStub, - WorkerStub - ); - await jobQueue.scaleWorkers({ - load: 40, - jobs: Array.from({ length: 40 }, (_, i) => i + 1), - }); - - const res = await jobQueue.scaleWorkers({ - load: 0, - jobs: [], - }); - expect(jobQueue.workers.length).to.equal(5); - }); - - it("should return false if scaling doesn't happen", async function () { - const jobQueue = await JobQueue.createJobQueue( - db, - networkService, - statusService, - notificationService, - settingsService, - logger, - QueueStub, - WorkerStub - ); - const res = await jobQueue.scaleWorkers({ load: 5 }); - expect(jobQueue.workers.length).to.equal(5); - expect(res).to.be.false; - }); - }); - - describe("getJobs", function () { - it("should return jobs", async function () { - const jobQueue = await JobQueue.createJobQueue( - db, - networkService, - statusService, - notificationService, - settingsService, - logger, - QueueStub, - WorkerStub - ); - const jobs = await jobQueue.getJobs(); - expect(jobs.length).to.equal(0); - }); - - it("should throw an error if getRepeatableJobs fails", async function () { - const jobQueue = await JobQueue.createJobQueue( - db, - networkService, - statusService, - notificationService, - settingsService, - logger, - QueueStub, - WorkerStub - ); - try { - jobQueue.queue.getRepeatableJobs = async () => { - throw new Error("error"); - }; - - await jobQueue.getJobs(true); - } catch (error) { - expect(error.service).to.equal("JobQueue"); - expect(error.method).to.equal("getJobs"); - } - }); - - it("should throw an error if getRepeatableJobs fails but respect existing error data", async function () { - const jobQueue = await JobQueue.createJobQueue( - db, - networkService, - statusService, - notificationService, - settingsService, - logger, - QueueStub, - WorkerStub - ); - try { - jobQueue.queue.getRepeatableJobs = async () => { - const error = new Error("Existing error"); - error.service = "otherService"; - error.method = "otherMethod"; - throw error; - }; - - await jobQueue.getJobs(true); - } catch (error) { - expect(error.service).to.equal("otherService"); - expect(error.method).to.equal("otherMethod"); - } - }); - }); - - describe("getJobStats", function () { - it("should return job stats for no jobs", async function () { - const jobStats = await jobQueue.getJobStats(); - expect(jobStats).to.deep.equal({ jobs: [], workers: 5 }); - }); - - it("should return job stats for jobs", async function () { - jobQueue.queue.getJobs = async () => { - return [{ data: { url: "test" }, getState: async () => "completed" }]; - }; - const jobStats = await jobQueue.getJobStats(); - expect(jobStats).to.deep.equal({ - jobs: [{ url: "test", state: "completed" }], - workers: 5, - }); - }); - - it("should reject with an error if mapping jobs fails", async function () { - jobQueue.queue.getJobs = async () => { - return [ - { - data: { url: "test" }, - getState: async () => { - throw new Error("Mapping Error"); - }, - }, - ]; - }; - try { - await jobQueue.getJobStats(); - } catch (error) { - expect(error.message).to.equal("Mapping Error"); - expect(error.service).to.equal("JobQueue"); - expect(error.method).to.equal("getJobStats"); - } - }); - - it("should reject with an error if mapping jobs fails but respect existing error data", async function () { - jobQueue.queue.getJobs = async () => { - return [ - { - data: { url: "test" }, - getState: async () => { - const error = new Error("Mapping Error"); - error.service = "otherService"; - error.method = "otherMethod"; - throw error; - }, - }, - ]; - }; - try { - await jobQueue.getJobStats(); - } catch (error) { - expect(error.message).to.equal("Mapping Error"); - expect(error.service).to.equal("otherService"); - expect(error.method).to.equal("otherMethod"); - } - }); - }); - - describe("addJob", function () { - it("should add a job to the queue", async function () { - jobQueue.addJob("test", { url: "test" }); - expect(jobQueue.queue.jobs.length).to.equal(1); - }); - - it("should reject with an error if adding fails", async function () { - jobQueue.queue.add = async () => { - throw new Error("Error adding job"); - }; - try { - await jobQueue.addJob("test", { url: "test" }); - } catch (error) { - expect(error.message).to.equal("Error adding job"); - expect(error.service).to.equal("JobQueue"); - expect(error.method).to.equal("addJob"); - } - }); - - it("should reject with an error if adding fails but respect existing error data", async function () { - jobQueue.queue.add = async () => { - const error = new Error("Error adding job"); - error.service = "otherService"; - error.method = "otherMethod"; - throw error; - }; - try { - await jobQueue.addJob("test", { url: "test" }); - } catch (error) { - expect(error.message).to.equal("Error adding job"); - expect(error.service).to.equal("otherService"); - expect(error.method).to.equal("otherMethod"); - } - }); - }); - - describe("deleteJob", function () { - it("should delete a job from the queue", async function () { - jobQueue.getWorkerStats = sinon.stub().returns({ load: 1, jobs: [{}] }); - jobQueue.scaleWorkers = sinon.stub(); - const monitor = { _id: 1 }; - const job = { data: monitor }; - jobQueue.queue.jobs = [job]; - await jobQueue.deleteJob(monitor); - // expect(jobQueue.queue.jobs.length).to.equal(0); - // expect(logger.info.calledOnce).to.be.true; - // expect(jobQueue.getWorkerStats.calledOnce).to.be.true; - // expect(jobQueue.scaleWorkers.calledOnce).to.be.true; - }); - - it("should log an error if job is not found", async function () { - jobQueue.getWorkerStats = sinon.stub().returns({ load: 1, jobs: [{}] }); - jobQueue.scaleWorkers = sinon.stub(); - const monitor = { _id: 1 }; - const job = { data: monitor }; - jobQueue.queue.jobs = [job]; - await jobQueue.deleteJob({ id_: 2 }); - expect(logger.error.calledOnce).to.be.true; - }); - - it("should reject with an error if removeRepeatable fails", async function () { - jobQueue.queue.removeRepeatable = async () => { - const error = new Error("removeRepeatable error"); - throw error; - }; - - try { - await jobQueue.deleteJob({ _id: 1 }); - } catch (error) { - expect(error.message).to.equal("removeRepeatable error"); - expect(error.service).to.equal("JobQueue"); - expect(error.method).to.equal("deleteJob"); - } - }); - - it("should reject with an error if removeRepeatable fails but respect existing error data", async function () { - jobQueue.queue.removeRepeatable = async () => { - const error = new Error("removeRepeatable error"); - error.service = "otherService"; - error.method = "otherMethod"; - throw error; - }; - - try { - await jobQueue.deleteJob({ _id: 1 }); - } catch (error) { - expect(error.message).to.equal("removeRepeatable error"); - expect(error.service).to.equal("otherService"); - expect(error.method).to.equal("otherMethod"); - } - }); - }); - - describe("getMetrics", function () { - it("should return metrics for the job queue", async function () { - jobQueue.queue.getWaitingCount = async () => 1; - jobQueue.queue.getActiveCount = async () => 2; - jobQueue.queue.getCompletedCount = async () => 3; - jobQueue.queue.getFailedCount = async () => 4; - jobQueue.queue.getDelayedCount = async () => 5; - jobQueue.queue.getRepeatableJobs = async () => [1, 2, 3]; - const metrics = await jobQueue.getMetrics(); - expect(metrics).to.deep.equal({ - waiting: 1, - active: 2, - completed: 3, - failed: 4, - delayed: 5, - repeatableJobs: 3, - }); - }); - - it("should log an error if metrics operations fail", async function () { - jobQueue.queue.getWaitingCount = async () => { - throw new Error("Error"); - }; - await jobQueue.getMetrics(); - expect(logger.error.calledOnce).to.be.true; - expect(logger.error.firstCall.args[0].message).to.equal("Error"); - }); - }); - - describe("obliterate", function () { - it("should return true if obliteration is successful", async function () { - jobQueue.queue.pause = async () => true; - jobQueue.getJobs = async () => [{ key: 1, id: 1 }]; - jobQueue.queue.removeRepeatableByKey = async () => true; - jobQueue.queue.remove = async () => true; - jobQueue.queue.obliterate = async () => true; - const obliteration = await jobQueue.obliterate(); - expect(obliteration).to.be.true; - }); - - it("should throw an error if obliteration fails", async function () { - jobQueue.getMetrics = async () => { - throw new Error("Error"); - }; - - try { - await jobQueue.obliterate(); - } catch (error) { - expect(error.service).to.equal("JobQueue"); - expect(error.method).to.equal("obliterate"); - } - }); - - it("should throw an error if obliteration fails but respect existing error data", async function () { - jobQueue.getMetrics = async () => { - const error = new Error("Error"); - error.service = "otherService"; - error.method = "otherMethod"; - throw error; - }; - - try { - await jobQueue.obliterate(); - } catch (error) { - expect(error.service).to.equal("otherService"); - expect(error.method).to.equal("otherMethod"); - } - }); - }); -}); diff --git a/server/tests/services/networkService.test.js b/server/tests/services/networkService.test.js deleted file mode 100755 index cb48ed9b2..000000000 --- a/server/tests/services/networkService.test.js +++ /dev/null @@ -1,439 +0,0 @@ -import sinon from "sinon"; -import NetworkService from "../../service/networkService.js"; -import { expect } from "chai"; -import http from "http"; -import { errorMessages } from "../../utils/messages.js"; -describe("Network Service", function () { - let axios, ping, Docker, logger, networkService; - - beforeEach(function () { - axios = { - get: sinon.stub().resolves({ - data: { foo: "bar" }, - status: 200, - }), - }; - Docker = class { - listContainers = sinon.stub().resolves([ - { - Names: ["http://test.com"], - Id: "http://test.com", - }, - ]); - getContainer = sinon.stub().returns({ - inspect: sinon.stub().resolves({ State: { Status: "running" } }), - }); - }; - ping = { - promise: { - probe: sinon.stub().resolves({ response: { alive: true }, responseTime: 100, alive: true }), - }, - }; - logger = { error: sinon.stub() }; - networkService = new NetworkService(axios, ping, logger, http, Docker); - }); - - describe("constructor", function () { - it("should create a new NetworkService instance", function () { - const networkService = new NetworkService(); - expect(networkService).to.be.an.instanceOf(NetworkService); - }); - }); - - describe("timeRequest", function () { - it("should time an asynchronous operation", async function () { - const operation = sinon.stub().resolves("success"); - const { response, responseTime } = await networkService.timeRequest(operation); - expect(response).to.equal("success"); - expect(responseTime).to.be.a("number"); - }); - - it("should handle errors if operation throws error", async function () { - const error = new Error("Test error"); - const operation = sinon.stub().throws(error); - const { response, responseTime } = await networkService.timeRequest(operation); - expect(response).to.be.null; - expect(responseTime).to.be.a("number"); - expect(error.message).to.equal("Test error"); - }); - }); - - describe("requestPing", function () { - it("should return a response object if ping successful", async function () { - const pingResult = await networkService.requestPing({ - data: { url: "http://test.com", _id: "123" }, - }); - expect(pingResult.monitorId).to.equal("123"); - expect(pingResult.type).to.equal("ping"); - expect(pingResult.responseTime).to.be.a("number"); - expect(pingResult.status).to.be.true; - }); - - it("should return a response object if ping unsuccessful", async function () { - const error = new Error("Test error"); - networkService.timeRequest = sinon.stub().resolves({ response: null, responseTime: 1, error }); - const pingResult = await networkService.requestPing({ - data: { url: "http://test.com", _id: "123" }, - }); - expect(pingResult.monitorId).to.equal("123"); - expect(pingResult.type).to.equal("ping"); - expect(pingResult.responseTime).to.be.a("number"); - expect(pingResult.status).to.be.false; - expect(pingResult.code).to.equal(networkService.PING_ERROR); - }); - - it("should throw an error if ping cannot resolve", async function () { - const error = new Error("test error"); - networkService.timeRequest = sinon.stub().throws(error); - try { - await networkService.requestPing({ - data: { url: "http://test.com", _id: "123" }, - }); - } catch (error) { - expect(error).to.exist; - expect(error.method).to.equal("requestPing"); - } - }); - }); - - describe("requestHttp", function () { - it("should return a response object if http successful", async function () { - const job = { data: { url: "http://test.com", _id: "123", type: "http" } }; - const httpResult = await networkService.requestHttp(job); - expect(httpResult.monitorId).to.equal("123"); - expect(httpResult.type).to.equal("http"); - expect(httpResult.responseTime).to.be.a("number"); - expect(httpResult.status).to.be.true; - }); - - it("should return a response object if http unsuccessful", async function () { - const error = new Error("Test error"); - error.response = { status: 404 }; - networkService.timeRequest = sinon.stub().resolves({ response: null, responseTime: 1, error }); - const job = { data: { url: "http://test.com", _id: "123", type: "http" } }; - const httpResult = await networkService.requestHttp(job); - expect(httpResult.monitorId).to.equal("123"); - expect(httpResult.type).to.equal("http"); - expect(httpResult.responseTime).to.be.a("number"); - expect(httpResult.status).to.be.false; - expect(httpResult.code).to.equal(404); - }); - - it("should return a response object if http unsuccessful with unknown code", async function () { - const error = new Error("Test error"); - error.response = {}; - networkService.timeRequest = sinon.stub().resolves({ response: null, responseTime: 1, error }); - const job = { data: { url: "http://test.com", _id: "123", type: "http" } }; - const httpResult = await networkService.requestHttp(job); - expect(httpResult.monitorId).to.equal("123"); - expect(httpResult.type).to.equal("http"); - expect(httpResult.responseTime).to.be.a("number"); - expect(httpResult.status).to.be.false; - expect(httpResult.code).to.equal(networkService.NETWORK_ERROR); - }); - - it("should throw an error if an error occurs", async function () { - const error = new Error("test error"); - networkService.timeRequest = sinon.stub().throws(error); - try { - await networkService.requestHttp({ - data: { url: "http://test.com", _id: "123" }, - }); - } catch (error) { - expect(error).to.exist; - expect(error.method).to.equal("requestHttp"); - } - }); - }); - - describe("requestPagespeed", function () { - it("should return a response object if pagespeed successful", async function () { - const job = { data: { url: "http://test.com", _id: "123", type: "pagespeed" } }; - const pagespeedResult = await networkService.requestPagespeed(job); - expect(pagespeedResult.monitorId).to.equal("123"); - expect(pagespeedResult.type).to.equal("pagespeed"); - expect(pagespeedResult.responseTime).to.be.a("number"); - expect(pagespeedResult.status).to.be.true; - }); - - it("should return a response object if pagespeed unsuccessful", async function () { - const error = new Error("Test error"); - error.response = { status: 404 }; - networkService.timeRequest = sinon.stub().resolves({ response: null, responseTime: 1, error }); - const job = { data: { url: "http://test.com", _id: "123", type: "pagespeed" } }; - const pagespeedResult = await networkService.requestPagespeed(job); - expect(pagespeedResult.monitorId).to.equal("123"); - expect(pagespeedResult.type).to.equal("pagespeed"); - expect(pagespeedResult.responseTime).to.be.a("number"); - expect(pagespeedResult.status).to.be.false; - expect(pagespeedResult.code).to.equal(404); - }); - - it("should return a response object if pagespeed unsuccessful with an unknown code", async function () { - const error = new Error("Test error"); - error.response = {}; - networkService.timeRequest = sinon.stub().resolves({ response: null, responseTime: 1, error }); - const job = { data: { url: "http://test.com", _id: "123", type: "pagespeed" } }; - const pagespeedResult = await networkService.requestPagespeed(job); - expect(pagespeedResult.monitorId).to.equal("123"); - expect(pagespeedResult.type).to.equal("pagespeed"); - expect(pagespeedResult.responseTime).to.be.a("number"); - expect(pagespeedResult.status).to.be.false; - expect(pagespeedResult.code).to.equal(networkService.NETWORK_ERROR); - }); - - it("should throw an error if pagespeed cannot resolve", async function () { - const error = new Error("test error"); - networkService.timeRequest = sinon.stub().throws(error); - try { - await networkService.requestPagespeed({ - data: { url: "http://test.com", _id: "123" }, - }); - } catch (error) { - expect(error).to.exist; - expect(error.method).to.equal("requestPagespeed"); - } - }); - }); - - describe("requestHardware", function () { - it("should return a response object if hardware successful", async function () { - const job = { data: { url: "http://test.com", _id: "123", type: "hardware" } }; - const httpResult = await networkService.requestHardware(job); - expect(httpResult.monitorId).to.equal("123"); - expect(httpResult.type).to.equal("hardware"); - expect(httpResult.responseTime).to.be.a("number"); - expect(httpResult.status).to.be.true; - }); - - it("should return a response object if hardware successful and job has a secret", async function () { - const job = { - data: { - url: "http://test.com", - _id: "123", - type: "hardware", - secret: "my_secret", - }, - }; - const httpResult = await networkService.requestHardware(job); - expect(httpResult.monitorId).to.equal("123"); - expect(httpResult.type).to.equal("hardware"); - expect(httpResult.responseTime).to.be.a("number"); - expect(httpResult.status).to.be.true; - }); - - it("should return a response object if hardware unsuccessful", async function () { - const error = new Error("Test error"); - error.response = { status: 404 }; - networkService.timeRequest = sinon.stub().resolves({ response: null, responseTime: 1, error }); - const job = { data: { url: "http://test.com", _id: "123", type: "hardware" } }; - const httpResult = await networkService.requestHardware(job); - expect(httpResult.monitorId).to.equal("123"); - expect(httpResult.type).to.equal("hardware"); - expect(httpResult.responseTime).to.be.a("number"); - expect(httpResult.status).to.be.false; - expect(httpResult.code).to.equal(404); - }); - - it("should return a response object if hardware unsuccessful with unknown code", async function () { - const error = new Error("Test error"); - error.response = {}; - networkService.timeRequest = sinon.stub().resolves({ response: null, responseTime: 1, error }); - const job = { data: { url: "http://test.com", _id: "123", type: "hardware" } }; - const httpResult = await networkService.requestHardware(job); - expect(httpResult.monitorId).to.equal("123"); - expect(httpResult.type).to.equal("hardware"); - expect(httpResult.responseTime).to.be.a("number"); - expect(httpResult.status).to.be.false; - expect(httpResult.code).to.equal(networkService.NETWORK_ERROR); - }); - - it("should throw an error if hardware cannot resolve", async function () { - const error = new Error("test error"); - networkService.timeRequest = sinon.stub().throws(error); - try { - await networkService.requestHardware({ - data: { url: "http://test.com", _id: "123" }, - }); - } catch (error) { - expect(error).to.exist; - expect(error.method).to.equal("requestHardware"); - } - }); - }); - - describe("requestDocker", function () { - it("should return a response object if docker successful", async function () { - const job = { data: { url: "http://test.com", _id: "123", type: "docker" } }; - const dockerResult = await networkService.requestDocker(job); - expect(dockerResult.monitorId).to.equal("123"); - expect(dockerResult.type).to.equal("docker"); - expect(dockerResult.responseTime).to.be.a("number"); - expect(dockerResult.status).to.be.true; - }); - - it("should return a response object with status false if container not running", async function () { - Docker = class { - listContainers = sinon.stub().resolves([ - { - Names: ["/my_container"], - Id: "abc123", - }, - ]); - getContainer = sinon.stub().returns({ - inspect: sinon.stub().resolves({ State: { Status: "stopped" } }), - }); - }; - networkService = new NetworkService(axios, ping, logger, http, Docker); - const job = { data: { url: "abc123", _id: "123", type: "docker" } }; - const dockerResult = await networkService.requestDocker(job); - expect(dockerResult.status).to.be.false; - expect(dockerResult.code).to.equal(200); - }); - - it("should handle an error when fetching the container", async function () { - Docker = class { - listContainers = sinon.stub().resolves([ - { - Names: ["/my_container"], - Id: "abc123", - }, - ]); - getContainer = sinon.stub().returns({ - inspect: sinon.stub().throws(new Error("test error")), - }); - }; - networkService = new NetworkService(axios, ping, logger, http, Docker); - const job = { data: { url: "abc123", _id: "123", type: "docker" } }; - const dockerResult = await networkService.requestDocker(job); - expect(dockerResult.status).to.be.false; - expect(dockerResult.code).to.equal(networkService.NETWORK_ERROR); - }); - - it("should throw an error if operations fail", async function () { - Docker = class { - listContainers = sinon.stub().resolves([ - { - Names: ["/my_container"], - Id: "abc123", - }, - ]); - getContainer = sinon.stub().throws(new Error("test error")); - }; - networkService = new NetworkService(axios, ping, logger, http, Docker); - const job = { data: { url: "abc123", _id: "123", type: "docker" } }; - try { - await networkService.requestDocker(job); - } catch (error) { - expect(error.message).to.equal("test error"); - } - }); - - it("should throw an error if no matching images found", async function () { - Docker = class { - listContainers = sinon.stub().resolves([]); - getContainer = sinon.stub().throws(new Error("test error")); - }; - networkService = new NetworkService(axios, ping, logger, http, Docker); - const job = { data: { url: "abc123", _id: "123", type: "docker" } }; - try { - await networkService.requestDocker(job); - } catch (error) { - expect(error.message).to.equal(errorMessages.DOCKER_NOT_FOUND); - } - }); - }); - - describe("getStatus", function () { - beforeEach(function () { - networkService.requestPing = sinon.stub(); - networkService.requestHttp = sinon.stub(); - networkService.requestPagespeed = sinon.stub(); - networkService.requestHardware = sinon.stub(); - networkService.requestDocker = sinon.stub(); - }); - - afterEach(function () { - sinon.restore(); - }); - - it("should call requestPing if type is ping", async function () { - await networkService.getStatus({ data: { type: "ping" } }); - expect(networkService.requestPing.calledOnce).to.be.true; - expect(networkService.requestDocker.notCalled).to.be.true; - expect(networkService.requestHttp.notCalled).to.be.true; - expect(networkService.requestPagespeed.notCalled).to.be.true; - }); - - it("should call requestHttp if type is http", async function () { - await networkService.getStatus({ data: { type: "http" } }); - expect(networkService.requestPing.notCalled).to.be.true; - expect(networkService.requestDocker.notCalled).to.be.true; - expect(networkService.requestHttp.calledOnce).to.be.true; - expect(networkService.requestPagespeed.notCalled).to.be.true; - }); - - it("should call requestPagespeed if type is pagespeed", async function () { - await networkService.getStatus({ data: { type: "pagespeed" } }); - expect(networkService.requestPing.notCalled).to.be.true; - expect(networkService.requestDocker.notCalled).to.be.true; - expect(networkService.requestHttp.notCalled).to.be.true; - expect(networkService.requestPagespeed.calledOnce).to.be.true; - }); - - it("should call requestHardware if type is hardware", async function () { - await networkService.getStatus({ data: { type: "hardware" } }); - expect(networkService.requestHardware.calledOnce).to.be.true; - expect(networkService.requestDocker.notCalled).to.be.true; - expect(networkService.requestPing.notCalled).to.be.true; - expect(networkService.requestPagespeed.notCalled).to.be.true; - }); - - it("should call requestDocker if type is Docker", async function () { - await networkService.getStatus({ data: { type: "docker" } }); - expect(networkService.requestDocker.calledOnce).to.be.true; - expect(networkService.requestHardware.notCalled).to.be.true; - expect(networkService.requestPing.notCalled).to.be.true; - expect(networkService.requestPagespeed.notCalled).to.be.true; - }); - - it("should throw an error if an unknown type is provided", async function () { - try { - await networkService.getStatus({ data: { type: "unknown" } }); - } catch (error) { - expect(error.service).to.equal("NetworkService"); - expect(error.method).to.equal("getStatus"); - expect(error.message).to.equal("Unsupported type: unknown"); - } - }); - - it("should throw an error if job type is undefined", async function () { - try { - await networkService.getStatus({ data: { type: undefined } }); - } catch (error) { - expect(error.service).to.equal("NetworkService"); - expect(error.method).to.equal("getStatus"); - expect(error.message).to.equal("Unsupported type: unknown"); - } - }); - - it("should throw an error if job is empty", async function () { - try { - await networkService.getStatus({}); - } catch (error) { - expect(error.method).to.equal("getStatus"); - expect(error.message).to.equal("Unsupported type: unknown"); - } - }); - - it("should throw an error if job is null", async function () { - try { - await networkService.getStatus(null); - } catch (error) { - expect(error.service).to.equal("NetworkService"); - expect(error.method).to.equal("getStatus"); - expect(error.message).to.equal("Unsupported type: unknown"); - } - }); - }); -}); diff --git a/server/tests/services/notificationService.test.js b/server/tests/services/notificationService.test.js deleted file mode 100755 index 211cc8b46..000000000 --- a/server/tests/services/notificationService.test.js +++ /dev/null @@ -1,285 +0,0 @@ -import sinon from "sinon"; -import NotificationService from "../../service/notificationService.js"; -import { expect } from "chai"; - -describe("NotificationService", function () { - let emailService, db, logger, notificationService; - - beforeEach(function () { - db = { - getNotificationsByMonitorId: sinon.stub(), - }; - emailService = { - buildAndSendEmail: sinon.stub(), - }; - logger = { - warn: sinon.stub(), - }; - - notificationService = new NotificationService(emailService, db, logger); - }); - - afterEach(function () { - sinon.restore(); - }); - - describe("constructor", function () { - it("should create a new instance of NotificationService", function () { - expect(notificationService).to.be.an.instanceOf(NotificationService); - }); - }); - - describe("sendEmail", function () { - it("should send an email notification with Up Template", async function () { - const networkResponse = { - monitor: { - name: "Test Monitor", - url: "http://test.com", - }, - status: true, - prevStatus: false, - }; - const address = "test@test.com"; - await notificationService.sendEmail(networkResponse, address); - expect(notificationService.emailService.buildAndSendEmail.calledOnce).to.be.true; - expect( - notificationService.emailService.buildAndSendEmail.calledWith("serverIsUpTemplate", { monitor: "Test Monitor", url: "http://test.com" }) - ); - }); - - it("should send an email notification with Down Template", async function () { - const networkResponse = { - monitor: { - name: "Test Monitor", - url: "http://test.com", - }, - status: false, - prevStatus: true, - }; - const address = "test@test.com"; - await notificationService.sendEmail(networkResponse, address); - expect(notificationService.emailService.buildAndSendEmail.calledOnce).to.be.true; - }); - - it("should send an email notification with Up Template", async function () { - const networkResponse = { - monitor: { - name: "Test Monitor", - url: "http://test.com", - }, - status: true, - prevStatus: false, - }; - const address = "test@test.com"; - await notificationService.sendEmail(networkResponse, address); - expect(notificationService.emailService.buildAndSendEmail.calledOnce).to.be.true; - }); - }); - - describe("handleNotifications", function () { - it("should handle notifications based on the network response", async function () { - notificationService.sendEmail = sinon.stub(); - const res = await notificationService.handleNotifications({ - monitor: { - type: "email", - address: "www.google.com", - }, - }); - expect(res).to.be.true; - }); - - it("should handle hardware notifications", async function () { - notificationService.sendEmail = sinon.stub(); - const res = await notificationService.handleNotifications({ - monitor: { - type: "hardware", - address: "www.google.com", - }, - }); - expect(res).to.be.true; - }); - - it("should handle an error when getting notifications", async function () { - const testError = new Error("Test Error"); - notificationService.db.getNotificationsByMonitorId.rejects(testError); - await notificationService.handleNotifications({ monitorId: "123" }); - expect(notificationService.logger.warn.calledOnce).to.be.true; - }); - }); - - describe("sendHardwareEmail", function () { - let networkResponse, address, alerts; - - beforeEach(function () { - networkResponse = { - monitor: { - name: "Test Monitor", - url: "http://test.com", - }, - status: true, - prevStatus: false, - }; - address = "test@test.com"; - alerts = ["test"]; - }); - - afterEach(function () { - sinon.restore(); - }); - - it("should send an email notification with Hardware Template", async function () { - emailService.buildAndSendEmail.resolves(true); - const res = await notificationService.sendHardwareEmail(networkResponse, address, alerts); - expect(res).to.be.true; - }); - - it("should return false if no alerts are provided", async function () { - alerts = []; - emailService.buildAndSendEmail.resolves(true); - const res = await notificationService.sendHardwareEmail(networkResponse, address, alerts); - expect(res).to.be.false; - }); - }); - - describe("handleStatusNotifications", function () { - let networkResponse; - - beforeEach(function () { - networkResponse = { - monitor: { - name: "Test Monitor", - url: "http://test.com", - }, - statusChanged: true, - status: true, - prevStatus: false, - }; - }); - - afterEach(function () { - sinon.restore(); - }); - - it("should handle status notifications", async function () { - db.getNotificationsByMonitorId.resolves([{ type: "email", address: "test@test.com" }]); - const res = await notificationService.handleStatusNotifications(networkResponse); - expect(res).to.be.true; - }); - - it("should return false if status hasn't changed", async function () { - networkResponse.statusChanged = false; - const res = await notificationService.handleStatusNotifications(networkResponse); - expect(res).to.be.false; - }); - - it("should return false if prevStatus is undefined", async function () { - networkResponse.prevStatus = undefined; - const res = await notificationService.handleStatusNotifications(networkResponse); - expect(res).to.be.false; - }); - - it("should handle an error", async function () { - const testError = new Error("Test Error"); - db.getNotificationsByMonitorId.rejects(testError); - try { - await notificationService.handleStatusNotifications(networkResponse); - } catch (error) { - expect(error).to.be.an.instanceOf(Error); - expect(error.message).to.equal("Test Error"); - } - }); - }); - - describe("handleHardwareNotifications", function () { - let networkResponse; - - beforeEach(function () { - networkResponse = { - monitor: { - name: "Test Monitor", - url: "http://test.com", - thresholds: { - usage_cpu: 1, - usage_memory: 1, - usage_disk: 1, - }, - }, - payload: { - data: { - cpu: { - usage_percent: 0.655, - }, - memory: { - usage_percent: 0.783, - }, - disk: [ - { - name: "/dev/sda1", - usage_percent: 0.452, - }, - { - name: "/dev/sdb1", - usage_percent: 0.627, - }, - ], - }, - }, - }; - }); - - afterEach(function () { - sinon.restore(); - }); - - describe("it should return false if no thresholds are set", function () { - it("should return false if no thresholds are set", async function () { - networkResponse.monitor.thresholds = undefined; - const res = await notificationService.handleHardwareNotifications(networkResponse); - expect(res).to.be.false; - }); - - it("should return false if metrics are null", async function () { - networkResponse.payload.data = null; - const res = await notificationService.handleHardwareNotifications(networkResponse); - expect(res).to.be.false; - }); - - it("should return true if request is well formed and thresholds > 0", async function () { - db.getNotificationsByMonitorId.resolves([ - { - type: "email", - address: "test@test.com", - alertThreshold: 1, - cpuAlertThreshold: 1, - memoryAlertThreshold: 1, - diskAlertThreshold: 1, - save: sinon.stub().resolves(), - }, - ]); - const res = await notificationService.handleHardwareNotifications(networkResponse); - expect(res).to.be.true; - }); - - it("should return true if thresholds are exceeded", async function () { - db.getNotificationsByMonitorId.resolves([ - { - type: "email", - address: "test@test.com", - alertThreshold: 1, - cpuAlertThreshold: 1, - memoryAlertThreshold: 1, - diskAlertThreshold: 1, - save: sinon.stub().resolves(), - }, - ]); - networkResponse.monitor.thresholds = { - usage_cpu: 0.01, - usage_memory: 0.01, - usage_disk: 0.01, - }; - const res = await notificationService.handleHardwareNotifications(networkResponse); - expect(res).to.be.true; - }); - }); - }); -}); diff --git a/server/tests/services/settingsService.test.js b/server/tests/services/settingsService.test.js deleted file mode 100755 index 6c9960e5e..000000000 --- a/server/tests/services/settingsService.test.js +++ /dev/null @@ -1,140 +0,0 @@ -import sinon from "sinon"; -import SettingsService from "../../service/settingsService.js"; -import { expect } from "chai"; -import NetworkService from "../../service/networkService.js"; -const SERVICE_NAME = "SettingsService"; - -describe("SettingsService", function () { - let sandbox, mockAppSettings; - - beforeEach(function () { - sandbox = sinon.createSandbox(); - sandbox.stub(process.env, "CLIENT_HOST").value("http://localhost"); - sandbox.stub(process.env, "JWT_SECRET").value("secret"); - sandbox.stub(process.env, "REFRESH_TOKEN_SECRET").value("refreshSecret"); - sandbox.stub(process.env, "DB_TYPE").value("postgres"); - sandbox.stub(process.env, "DB_CONNECTION_STRING").value("postgres://user:pass@localhost/db"); - sandbox.stub(process.env, "REDIS_HOST").value("localhost"); - sandbox.stub(process.env, "REDIS_PORT").value("6379"); - sandbox.stub(process.env, "TOKEN_TTL").value("3600"); - sandbox.stub(process.env, "REFRESH_TOKEN_TTL").value("86400"); - sandbox.stub(process.env, "PAGESPEED_API_KEY").value("apiKey"); - sandbox.stub(process.env, "SYSTEM_EMAIL_HOST").value("smtp.mailtrap.io"); - sandbox.stub(process.env, "SYSTEM_EMAIL_PORT").value("2525"); - sandbox.stub(process.env, "SYSTEM_EMAIL_ADDRESS").value("test@example.com"); - sandbox.stub(process.env, "SYSTEM_EMAIL_PASSWORD").value("password"); - }); - - mockAppSettings = { - settingOne: 123, - settingTwo: 456, - }; - - afterEach(function () { - sandbox.restore(); - sinon.restore(); - }); - - describe("constructor", function () { - it("should construct a new SettingsService", function () { - const settingsService = new SettingsService(mockAppSettings); - expect(settingsService.appSettings).to.equal(mockAppSettings); - }); - }); - - describe("loadSettings", function () { - it("should load settings from DB when environment variables are not set", async function () { - const dbSettings = { logLevel: "debug", apiBaseUrl: "http://localhost" }; - const appSettings = { findOne: sinon.stub().returns(dbSettings) }; - const settingsService = new SettingsService(appSettings); - settingsService.settings = {}; - const result = await settingsService.loadSettings(); - expect(result).to.deep.equal(dbSettings); - }); - - it("should throw an error if settings are not found", async function () { - const appSettings = { findOne: sinon.stub().returns(null) }; - const settingsService = new SettingsService(appSettings); - settingsService.settings = null; - - try { - await settingsService.loadSettings(); - } catch (error) { - expect(error.message).to.equal("Settings not found"); - expect(error.service).to.equal(SERVICE_NAME); - expect(error.method).to.equal("loadSettings"); - } - }); - - it("should add its method and service name to error if not present", async function () { - const appSettings = { findOne: sinon.stub().throws(new Error("Test error")) }; - const settingsService = new SettingsService(appSettings); - try { - await settingsService.loadSettings(); - } catch (error) { - expect(error.message).to.equal("Test error"); - expect(error.service).to.equal(SERVICE_NAME); - expect(error.method).to.equal("loadSettings"); - } - }); - - it("should not add its method and service name to error if present", async function () { - const error = new Error("Test error"); - error.method = "otherMethod"; - error.service = "OTHER_SERVICE"; - const appSettings = { findOne: sinon.stub().throws(error) }; - const settingsService = new SettingsService(appSettings); - try { - await settingsService.loadSettings(); - } catch (error) { - expect(error.message).to.equal("Test error"); - expect(error.service).to.equal("OTHER_SERVICE"); - expect(error.method).to.equal("otherMethod"); - } - }); - - it("should merge DB settings with environment variables", async function () { - const dbSettings = { logLevel: "debug", apiBaseUrl: "http://localhost" }; - const appSettings = { findOne: sinon.stub().returns(dbSettings) }; - const settingsService = new SettingsService(appSettings); - const result = await settingsService.loadSettings(); - expect(result).to.deep.equal(settingsService.settings); - expect(settingsService.settings.logLevel).to.equal("debug"); - expect(settingsService.settings.apiBaseUrl).to.equal("http://localhost"); - }); - }); - - describe("reloadSettings", function () { - it("should call loadSettings", async function () { - const dbSettings = { logLevel: "debug", apiBaseUrl: "http://localhost" }; - const appSettings = { findOne: sinon.stub().returns(dbSettings) }; - const settingsService = new SettingsService(appSettings); - settingsService.settings = {}; - const result = await settingsService.reloadSettings(); - expect(result).to.deep.equal(dbSettings); - }); - }); - - describe("getSettings", function () { - it("should return the current settings", function () { - const dbSettings = { logLevel: "debug", apiBaseUrl: "http://localhost" }; - const appSettings = { findOne: sinon.stub().returns(dbSettings) }; - const settingsService = new SettingsService(appSettings); - settingsService.settings = dbSettings; - const result = settingsService.getSettings(); - expect(result).to.deep.equal(dbSettings); - }); - - it("should throw an error if settings have not been loaded", function () { - const appSettings = { findOne: sinon.stub().returns(null) }; - const settingsService = new SettingsService(appSettings); - settingsService.settings = null; - - try { - settingsService.getSettings(); - } catch (error) { - expect(error.message).to.equal("Settings have not been loaded"); - } - }); - }); -}); diff --git a/server/tests/services/statusService.test.js b/server/tests/services/statusService.test.js deleted file mode 100755 index 289a36884..000000000 --- a/server/tests/services/statusService.test.js +++ /dev/null @@ -1,255 +0,0 @@ -import sinon from "sinon"; -import StatusService from "../../service/statusService.js"; -import { afterEach, describe } from "node:test"; - -describe("StatusService", () => { - let db, logger, statusService; - - beforeEach(function () { - db = { - getMonitorById: sinon.stub(), - createCheck: sinon.stub(), - createPagespeedCheck: sinon.stub(), - }; - logger = { - info: sinon.stub(), - error: sinon.stub(), - }; - statusService = new StatusService(db, logger); - }); - - afterEach(() => { - sinon.restore(); - }); - - describe("constructor", () => { - it("should create an instance of StatusService", function () { - expect(statusService).to.be.an.instanceOf(StatusService); - }); - }); - - describe("getStatusString", () => { - it("should return 'up' if status is true", function () { - expect(statusService.getStatusString(true)).to.equal("up"); - }); - - it("should return 'down' if status is false", function () { - expect(statusService.getStatusString(false)).to.equal("down"); - }); - - it("should return 'unknown' if status is undefined or null", function () { - expect(statusService.getStatusString(undefined)).to.equal("unknown"); - }); - }); - - describe("updateStatus", () => { - beforeEach(function () { - // statusService.insertCheck = sinon.stub().resolves; - }); - - afterEach(() => { - sinon.restore(); - }); - - it("should throw an error if an error occurs", async function () { - const error = new Error("Test error"); - statusService.db.getMonitorById = sinon.stub().throws(error); - try { - await statusService.updateStatus({ monitorId: "test", status: true }); - } catch (error) { - expect(error.message).to.equal("Test error"); - } - // expect(statusService.insertCheck.calledOnce).to.be.true; - }); - - it("should return {statusChanged: false} if status hasn't changed", async function () { - statusService.db.getMonitorById = sinon.stub().returns({ status: true }); - const result = await statusService.updateStatus({ - monitorId: "test", - status: true, - }); - expect(result).to.deep.equal({ statusChanged: false }); - // expect(statusService.insertCheck.calledOnce).to.be.true; - }); - - it("should return {statusChanged: true} if status has changed from down to up", async function () { - statusService.db.getMonitorById = sinon.stub().returns({ status: false, save: sinon.stub() }); - const result = await statusService.updateStatus({ - monitorId: "test", - status: true, - }); - expect(result.statusChanged).to.be.true; - expect(result.monitor.status).to.be.true; - expect(result.prevStatus).to.be.false; - // expect(statusService.insertCheck.calledOnce).to.be.true; - }); - - it("should return {statusChanged: true} if status has changed from up to down", async function () { - statusService.db.getMonitorById = sinon.stub().returns({ status: true, save: sinon.stub() }); - const result = await statusService.updateStatus({ - monitorId: "test", - status: false, - }); - expect(result.statusChanged).to.be.true; - expect(result.monitor.status).to.be.false; - expect(result.prevStatus).to.be.true; - // expect(statusService.insertCheck.calledOnce).to.be.true; - }); - }); - - describe("buildCheck", () => { - it("should build a check object", function () { - const check = statusService.buildCheck({ - monitorId: "test", - type: "test", - status: true, - responseTime: 100, - code: 200, - message: "Test message", - payload: { test: "test" }, - }); - expect(check.monitorId).to.equal("test"); - expect(check.status).to.be.true; - expect(check.statusCode).to.equal(200); - expect(check.responseTime).to.equal(100); - expect(check.message).to.equal("Test message"); - }); - - it("should build a check object for pagespeed type", function () { - const check = statusService.buildCheck({ - monitorId: "test", - type: "pagespeed", - status: true, - responseTime: 100, - code: 200, - message: "Test message", - payload: { - lighthouseResult: { - categories: { - accessibility: { score: 1 }, - "best-practices": { score: 1 }, - performance: { score: 1 }, - seo: { score: 1 }, - }, - audits: { - "cumulative-layout-shift": { score: 1 }, - "speed-index": { score: 1 }, - "first-contentful-paint": { score: 1 }, - "largest-contentful-paint": { score: 1 }, - "total-blocking-time": { score: 1 }, - }, - }, - }, - }); - expect(check.monitorId).to.equal("test"); - expect(check.status).to.be.true; - expect(check.statusCode).to.equal(200); - expect(check.responseTime).to.equal(100); - expect(check.message).to.equal("Test message"); - expect(check.accessibility).to.equal(100); - expect(check.bestPractices).to.equal(100); - expect(check.performance).to.equal(100); - expect(check.seo).to.equal(100); - expect(check.audits).to.deep.equal({ - cls: { score: 1 }, - si: { score: 1 }, - fcp: { score: 1 }, - lcp: { score: 1 }, - tbt: { score: 1 }, - }); - }); - - it("should build a check object for pagespeed type with missing data", function () { - const check = statusService.buildCheck({ - monitorId: "test", - type: "pagespeed", - status: true, - responseTime: 100, - code: 200, - message: "Test message", - payload: { - lighthouseResult: { - categories: {}, - audits: {}, - }, - }, - }); - expect(check.monitorId).to.equal("test"); - expect(check.status).to.be.true; - expect(check.statusCode).to.equal(200); - expect(check.responseTime).to.equal(100); - expect(check.message).to.equal("Test message"); - expect(check.accessibility).to.equal(0); - expect(check.bestPractices).to.equal(0); - expect(check.performance).to.equal(0); - expect(check.seo).to.equal(0); - expect(check.audits).to.deep.equal({ - cls: 0, - si: 0, - fcp: 0, - lcp: 0, - tbt: 0, - }); - }); - - it("should build a check for hardware type", function () { - const check = statusService.buildCheck({ - monitorId: "test", - type: "hardware", - status: true, - responseTime: 100, - code: 200, - message: "Test message", - payload: { data: { cpu: "cpu", memory: "memory", disk: "disk", host: "host" } }, - }); - expect(check.monitorId).to.equal("test"); - expect(check.status).to.be.true; - expect(check.statusCode).to.equal(200); - expect(check.responseTime).to.equal(100); - expect(check.message).to.equal("Test message"); - expect(check.cpu).to.equal("cpu"); - expect(check.memory).to.equal("memory"); - expect(check.disk).to.equal("disk"); - expect(check.host).to.equal("host"); - }); - - it("should build a check for hardware type with missing data", function () { - const check = statusService.buildCheck({ - monitorId: "test", - type: "hardware", - status: true, - responseTime: 100, - code: 200, - message: "Test message", - payload: {}, - }); - expect(check.monitorId).to.equal("test"); - expect(check.status).to.be.true; - expect(check.statusCode).to.equal(200); - expect(check.responseTime).to.equal(100); - expect(check.message).to.equal("Test message"); - expect(check.cpu).to.deep.equal({}); - expect(check.memory).to.deep.equal({}); - expect(check.disk).to.deep.equal({}); - expect(check.host).to.deep.equal({}); - }); - }); - - describe("insertCheck", () => { - it("should log an error if one is thrown", async function () { - const testError = new Error("Test error"); - statusService.db.createCheck = sinon.stub().throws(testError); - try { - await statusService.insertCheck({ monitorId: "test" }); - } catch (error) { - expect(error.message).to.equal(testError.message); - } - expect(statusService.logger.error.calledOnce).to.be.true; - }); - - it("should insert a check into the database", async function () { - await statusService.insertCheck({ monitorId: "test", type: "http" }); - expect(statusService.db.createCheck.calledOnce).to.be.true; - }); - }); -}); diff --git a/server/tests/utils/dataUtils.test.js b/server/tests/utils/dataUtils.test.js deleted file mode 100755 index ec9eabb40..000000000 --- a/server/tests/utils/dataUtils.test.js +++ /dev/null @@ -1,61 +0,0 @@ -import { NormalizeData, calculatePercentile } from "../../utils/dataUtils.js"; -import sinon from "sinon"; - -describe("NormalizeData", function () { - it("should normalize response times when checks length is greater than 1", function () { - const checks = [ - { responseTime: 20, _doc: { id: 1 } }, - { responseTime: 40, _doc: { id: 2 } }, - { responseTime: 60, _doc: { id: 3 } }, - ]; - const rangeMin = 1; - const rangeMax = 100; - - const result = NormalizeData(checks, rangeMin, rangeMax); - - expect(result).to.be.an("array"); - expect(result).to.have.lengthOf(3); - result.forEach((check) => { - expect(check).to.have.property("responseTime").that.is.a("number"); - expect(check).to.have.property("originalResponseTime").that.is.a("number"); - }); - }); - - it("should return checks with original response times when checks length is 1", function () { - const checks = [{ responseTime: 20, _doc: { id: 1 } }]; - const rangeMin = 1; - const rangeMax = 100; - - const result = NormalizeData(checks, rangeMin, rangeMax); - expect(result).to.be.an("array"); - expect(result).to.have.lengthOf(1); - expect(result[0]).to.have.property("originalResponseTime", 20); - }); - - it("should handle edge cases with extreme response times", function () { - const checks = [ - { responseTime: 5, _doc: { id: 1 } }, - { responseTime: 95, _doc: { id: 2 } }, - ]; - const rangeMin = 1; - const rangeMax = 100; - - const result = NormalizeData(checks, rangeMin, rangeMax); - - expect(result).to.be.an("array"); - expect(result).to.have.lengthOf(2); - expect(result[0]).to.have.property("responseTime").that.is.at.least(rangeMin); - expect(result[1]).to.have.property("responseTime").that.is.at.most(rangeMax); - }); -}); - -describe("calculatePercentile", function () { - it("should return the lower value when upper is greater than or equal to the length of the sorted array", function () { - const checks = [{ responseTime: 10 }, { responseTime: 20 }, { responseTime: 30 }, { responseTime: 40 }, { responseTime: 50 }]; - - const percentile = 100; - const result = calculatePercentile(checks, percentile); - const expected = 50; - expect(result).to.equal(expected); - }); -}); diff --git a/server/tests/utils/imageProcessing.test.js b/server/tests/utils/imageProcessing.test.js deleted file mode 100755 index 09330c317..000000000 --- a/server/tests/utils/imageProcessing.test.js +++ /dev/null @@ -1,53 +0,0 @@ -import { expect } from "chai"; -import sinon from "sinon"; -import sharp from "sharp"; -import { GenerateAvatarImage } from "../../utils/imageProcessing.js"; - -describe("imageProcessing - GenerateAvatarImage", function () { - it("should resize the image to 64x64 and return a base64 string", async function () { - const file = { - buffer: Buffer.from("test image buffer"), - }; - - // Stub the sharp function - const toBufferStub = sinon.stub().resolves(Buffer.from("resized image buffer")); - const resizeStub = sinon.stub().returns({ toBuffer: toBufferStub }); - const sharpStub = sinon.stub(sharp.prototype, "resize").returns({ toBuffer: toBufferStub }); - - const result = await GenerateAvatarImage(file); - - // Verify the result - const expected = Buffer.from("resized image buffer").toString("base64"); - expect(result).to.equal(expected); - - // Verify that the sharp function was called with the correct arguments - expect(sharpStub.calledOnceWith({ width: 64, height: 64, fit: "cover" })).to.be.true; - expect(toBufferStub.calledOnce).to.be.true; - - // Restore the stubbed functions - sharpStub.restore(); - }); - - it("should throw an error if resizing fails", async function () { - const file = { - buffer: Buffer.from("test image buffer"), - }; - - // Stub the sharp function to throw an error - const toBufferStub = sinon.stub().rejects(new Error("Resizing failed")); - const resizeStub = sinon.stub().returns({ toBuffer: toBufferStub }); - const sharpStub = sinon.stub(sharp.prototype, "resize").returns({ toBuffer: toBufferStub }); - - try { - await GenerateAvatarImage(file); - // If no error is thrown, fail the test - expect.fail("Expected error to be thrown"); - } catch (error) { - // Verify that the error message is correct - expect(error.message).to.equal("Resizing failed"); - } - - // Restore the stubbed functions - sharpStub.restore(); - }); -}); diff --git a/server/tests/utils/logger.test.js b/server/tests/utils/logger.test.js deleted file mode 100755 index 2d5bd82a9..000000000 --- a/server/tests/utils/logger.test.js +++ /dev/null @@ -1,139 +0,0 @@ -import sinon from "sinon"; -import logger from "../../utils/logger.js"; -import { Logger } from "../../utils/logger.js"; -import winston from "winston"; - -describe("Logger", function () { - let infoStub, warnStub, errorStub; - - beforeEach(function () { - infoStub = sinon.stub(logger.logger, "info"); - warnStub = sinon.stub(logger.logger, "warn"); - errorStub = sinon.stub(logger.logger, "error"); - }); - - afterEach(function () { - sinon.restore(); - }); - - describe("constructor", function () { - let createLoggerStub; - - beforeEach(function () { - createLoggerStub = sinon.stub(winston, "createLogger"); - }); - - afterEach(function () { - sinon.restore(); - }); - - it("should convert message to JSON string if it is an object", function () { - const logMessage = { key: "value" }; - const expectedMessage = JSON.stringify(logMessage, null, 2); - - createLoggerStub.callsFake((config) => { - const consoleTransport = config.transports[0]; - const logEntry = { - level: "info", - message: logMessage, - timestamp: new Date().toISOString(), - }; - const formattedMessage = consoleTransport.format.transform(logEntry); - expect(formattedMessage).to.include(expectedMessage); - return { log: sinon.spy() }; - }); - - const logger = new Logger(); - logger.logger.info(logMessage); - }); - - it("should convert details to JSON string if it is an object", function () { - const logDetails = { key: "value" }; - const expectedDetails = JSON.stringify(logDetails, null, 2); // Removed .s - - createLoggerStub.callsFake((config) => { - const consoleTransport = config.transports[0]; - const logEntry = { - level: "info", - message: "", // Add empty message since it's required - details: logDetails, - timestamp: new Date().toISOString(), - }; - const formattedMessage = consoleTransport.format.transform(logEntry); - expect(formattedMessage).to.include(expectedDetails); - return { info: sinon.spy() }; // Changed to return info method - }); - - const logger = new Logger(); - logger.logger.info("", { details: logDetails }); // Updated to pass details properly - }); - }); - - describe("info", function () { - it("should log an informational message", function () { - const config = { - message: "Info message", - service: "TestService", - method: "TestMethod", - details: { key: "value" }, - }; - - logger.info(config); - - expect(infoStub.calledOnce).to.be.true; - expect( - infoStub.calledWith(config.message, { - service: config.service, - method: config.method, - details: config.details, - }) - ).to.be.true; - }); - }); - - describe("warn", function () { - it("should log a warning message", function () { - const config = { - message: "Warning message", - service: "TestService", - method: "TestMethod", - details: { key: "value" }, - }; - - logger.warn(config); - - expect(warnStub.calledOnce).to.be.true; - expect( - warnStub.calledWith(config.message, { - service: config.service, - method: config.method, - details: config.details, - }) - ).to.be.true; - }); - }); - - describe("error", function () { - it("should log an error message", function () { - const config = { - message: "Error message", - service: "TestService", - method: "TestMethod", - details: { key: "value" }, - stack: "Error stack trace", - }; - - logger.error(config); - - expect(errorStub.calledOnce).to.be.true; - expect( - errorStub.calledWith(config.message, { - service: config.service, - method: config.method, - details: config.details, - stack: config.stack, - }) - ).to.be.true; - }); - }); -}); diff --git a/server/tests/utils/messages.test.js b/server/tests/utils/messages.test.js deleted file mode 100755 index a7e98c477..000000000 --- a/server/tests/utils/messages.test.js +++ /dev/null @@ -1,23 +0,0 @@ -import { errorMessages, successMessages } from "../../utils/messages.js"; -describe("Messages", function () { - describe("messages - errorMessages", function () { - it("should have a DB_FIND_MONITOR_BY_ID function", function () { - const monitorId = "12345"; - expect(errorMessages.DB_FIND_MONITOR_BY_ID(monitorId)).to.equal(`Monitor with id ${monitorId} not found`); - }); - - it("should have a DB_DELETE_CHECKS function", function () { - const monitorId = "12345"; - expect(errorMessages.DB_DELETE_CHECKS(monitorId)).to.equal(`No checks found for monitor with id ${monitorId}`); - }); - }); - - describe("messages - successMessages", function () { - it("should have a MONITOR_GET_BY_USER_ID function", function () { - const userId = "12345"; - expect(successMessages.MONITOR_GET_BY_USER_ID(userId)).to.equal(`Got monitor for ${userId} successfully"`); - }); - - // Add more tests for other success messages as needed - }); -}); diff --git a/server/tests/utils/utils.test.js b/server/tests/utils/utils.test.js deleted file mode 100755 index 2630bb8e6..000000000 --- a/server/tests/utils/utils.test.js +++ /dev/null @@ -1,50 +0,0 @@ -import { ParseBoolean, getTokenFromHeaders } from "../../utils/utils.js"; - -describe("utils - ParseBoolean", function () { - it("should return true", function () { - const result = ParseBoolean("true"); - expect(result).to.be.true; - }); - - it("should return false", function () { - const result = ParseBoolean("false"); - expect(result).to.be.false; - }); - - it("should return false", function () { - const result = ParseBoolean(null); - expect(result).to.be.false; - }); - - it("should return false", function () { - const result = ParseBoolean(undefined); - expect(result).to.be.false; - }); -}); - -describe("utils - getTokenFromHeaders", function () { - it("should throw an error if authorization header is missing", function () { - const headers = {}; - expect(() => getTokenFromHeaders(headers)).to.throw("No auth headers"); - }); - - it("should throw an error if authorization header does not start with Bearer", function () { - const headers = { authorization: "Basic abcdef" }; - expect(() => getTokenFromHeaders(headers)).to.throw("Invalid auth headers"); - }); - - it("should return the token if authorization header is correctly formatted", function () { - const headers = { authorization: "Bearer abcdef" }; - expect(getTokenFromHeaders(headers)).to.equal("abcdef"); - }); - - it("should throw an error if authorization header has more than two parts", function () { - const headers = { authorization: "Bearer abc def" }; - expect(() => getTokenFromHeaders(headers)).to.throw("Invalid auth headers"); - }); - - it("should throw an error if authorization header has less than two parts", function () { - const headers = { authorization: "Bearer" }; - expect(() => getTokenFromHeaders(headers)).to.throw("Invalid auth headers"); - }); -}); diff --git a/server/tsconfig.json b/server/tsconfig.json new file mode 100644 index 000000000..3aa773ab1 --- /dev/null +++ b/server/tsconfig.json @@ -0,0 +1,26 @@ +{ + "compilerOptions": { + "rootDir": "./src", + "outDir": "./dist", + "module": "nodenext", + "target": "esnext", + "types": ["node"], + "sourceMap": true, + "declaration": false, + "declarationMap": false, + "noUncheckedIndexedAccess": true, + "exactOptionalPropertyTypes": true, + "strict": true, + "isolatedModules": true, + "noUncheckedSideEffectImports": true, + "moduleDetection": "force", + "skipLibCheck": true, + "allowSyntheticDefaultImports": true, + "esModuleInterop": true, + "resolveJsonModule": true, + "forceConsistentCasingInFileNames": true, + "allowJs": true, + "checkJs": false + }, + "exclude": ["node_modules", "dist", "**/*.config.js"] +} From f767125acd078252bb3bb1551cda01a944b76f76 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Mon, 22 Sep 2025 13:03:54 -0700 Subject: [PATCH 016/101] tsx, tpyescript --- server/package-lock.json | 17 ++++++++++++++++- server/package.json | 3 ++- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/server/package-lock.json b/server/package-lock.json index e2affbca0..06c1c30b3 100644 --- a/server/package-lock.json +++ b/server/package-lock.json @@ -55,7 +55,8 @@ "nodemon": "3.1.9", "prettier": "^3.3.3", "sinon": "19.0.2", - "tsx": "4.20.5" + "tsx": "4.20.5", + "typescript": "5.9.2" } }, "node_modules/@asamuzakjp/css-color": { @@ -9139,6 +9140,20 @@ "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==", "license": "MIT" }, + "node_modules/typescript": { + "version": "5.9.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.2.tgz", + "integrity": "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==", + "devOptional": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, "node_modules/uglify-js": { "version": "3.19.3", "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.19.3.tgz", diff --git a/server/package.json b/server/package.json index c57c3a4ce..637c785cc 100755 --- a/server/package.json +++ b/server/package.json @@ -64,6 +64,7 @@ "nodemon": "3.1.9", "prettier": "^3.3.3", "sinon": "19.0.2", - "tsx": "4.20.5" + "tsx": "4.20.5", + "typescript": "5.9.2" } } From 23126a92cdcbd856fdbeddcb9b5c46a3b766537f Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Mon, 22 Sep 2025 13:04:45 -0700 Subject: [PATCH 017/101] move v1 controllers to v1 dir --- server/src/config/controllers.js | 24 +++++++++---------- .../{ => v1}/announcementsController.js | 2 +- .../controllers/{ => v1}/authController.js | 2 +- .../controllers/{ => v1}/baseController.js | 2 +- .../controllers/{ => v1}/checkController.js | 2 +- .../controllers/{ => v1}/controllerUtils.js | 0 .../{ => v1}/diagnosticController.js | 0 .../controllers/{ => v1}/inviteController.js | 2 +- .../src/controllers/{ => v1}/logController.js | 0 .../{ => v1}/maintenanceWindowController.js | 2 +- .../controllers/{ => v1}/monitorController.js | 2 +- .../{ => v1}/notificationController.js | 2 +- .../controllers/{ => v1}/queueController.js | 0 .../{ => v1}/settingsController.js | 4 ++-- .../{ => v1}/statusPageController.js | 2 +- server/src/routes/v1/monitorRoute.js | 2 +- 16 files changed, 24 insertions(+), 24 deletions(-) rename server/src/controllers/{ => v1}/announcementsController.js (96%) rename server/src/controllers/{ => v1}/authController.js (99%) rename server/src/controllers/{ => v1}/baseController.js (97%) rename server/src/controllers/{ => v1}/checkController.js (99%) rename server/src/controllers/{ => v1}/controllerUtils.js (100%) rename server/src/controllers/{ => v1}/diagnosticController.js (100%) rename server/src/controllers/{ => v1}/inviteController.js (99%) rename server/src/controllers/{ => v1}/logController.js (100%) rename server/src/controllers/{ => v1}/maintenanceWindowController.js (99%) mode change 100755 => 100644 rename server/src/controllers/{ => v1}/monitorController.js (99%) mode change 100755 => 100644 rename server/src/controllers/{ => v1}/notificationController.js (98%) mode change 100755 => 100644 rename server/src/controllers/{ => v1}/queueController.js (100%) rename server/src/controllers/{ => v1}/settingsController.js (95%) mode change 100755 => 100644 rename server/src/controllers/{ => v1}/statusPageController.js (99%) mode change 100755 => 100644 diff --git a/server/src/config/controllers.js b/server/src/config/controllers.js index 693e35ab8..5c54579eb 100644 --- a/server/src/config/controllers.js +++ b/server/src/config/controllers.js @@ -1,19 +1,19 @@ -import { createCommonDependencies } from "../controllers/baseController.js"; +import { createCommonDependencies } from "../controllers/v1/baseController.js"; // Services // Controllers -import MonitorController from "../controllers/monitorController.js"; -import AuthController from "../controllers/authController.js"; -import SettingsController from "../controllers/settingsController.js"; -import CheckController from "../controllers/checkController.js"; -import InviteController from "../controllers/inviteController.js"; -import MaintenanceWindowController from "../controllers/maintenanceWindowController.js"; -import QueueController from "../controllers/queueController.js"; -import LogController from "../controllers/logController.js"; -import StatusPageController from "../controllers/statusPageController.js"; -import NotificationController from "../controllers/notificationController.js"; -import DiagnosticController from "../controllers/diagnosticController.js"; +import MonitorController from "../controllers/v1/monitorController.js"; +import AuthController from "../controllers/v1/authController.js"; +import SettingsController from "../controllers/v1/settingsController.js"; +import CheckController from "../controllers/v1/checkController.js"; +import InviteController from "../controllers/v1/inviteController.js"; +import MaintenanceWindowController from "../controllers/v1/maintenanceWindowController.js"; +import QueueController from "../controllers/v1/queueController.js"; +import LogController from "../controllers/v1/logController.js"; +import StatusPageController from "../controllers/v1/statusPageController.js"; +import NotificationController from "../controllers/v1/notificationController.js"; +import DiagnosticController from "../controllers/v1/diagnosticController.js"; export const initializeControllers = (services) => { const controllers = {}; diff --git a/server/src/controllers/announcementsController.js b/server/src/controllers/v1/announcementsController.js similarity index 96% rename from server/src/controllers/announcementsController.js rename to server/src/controllers/v1/announcementsController.js index 430859791..0425b44aa 100755 --- a/server/src/controllers/announcementsController.js +++ b/server/src/controllers/v1/announcementsController.js @@ -1,4 +1,4 @@ -import { createAnnouncementValidation } from "../validation/joi.js"; +import { createAnnouncementValidation } from "../../validation/joi.js"; import BaseController from "./baseController.js"; const SERVICE_NAME = "announcementController"; diff --git a/server/src/controllers/authController.js b/server/src/controllers/v1/authController.js similarity index 99% rename from server/src/controllers/authController.js rename to server/src/controllers/v1/authController.js index c186e8423..17ed517eb 100755 --- a/server/src/controllers/authController.js +++ b/server/src/controllers/v1/authController.js @@ -10,7 +10,7 @@ import { editUserByIdParamValidation, editUserByIdBodyValidation, editSuperadminUserByIdBodyValidation, -} from "../validation/joi.js"; +} from "../../validation/joi.js"; const SERVICE_NAME = "authController"; diff --git a/server/src/controllers/baseController.js b/server/src/controllers/v1/baseController.js similarity index 97% rename from server/src/controllers/baseController.js rename to server/src/controllers/v1/baseController.js index 910cf57b4..314905c82 100644 --- a/server/src/controllers/baseController.js +++ b/server/src/controllers/v1/baseController.js @@ -1,4 +1,4 @@ -import { AppError } from "../service/infrastructure/errorService.js"; +import { AppError } from "../../service/infrastructure/errorService.js"; export const createCommonDependencies = (db, errorService, logger, stringService) => { return { diff --git a/server/src/controllers/checkController.js b/server/src/controllers/v1/checkController.js similarity index 99% rename from server/src/controllers/checkController.js rename to server/src/controllers/v1/checkController.js index 4069df3ff..ea293d74d 100755 --- a/server/src/controllers/checkController.js +++ b/server/src/controllers/v1/checkController.js @@ -9,7 +9,7 @@ import { ackCheckBodyValidation, ackAllChecksParamValidation, ackAllChecksBodyValidation, -} from "../validation/joi.js"; +} from "../../validation/joi.js"; const SERVICE_NAME = "checkController"; diff --git a/server/src/controllers/controllerUtils.js b/server/src/controllers/v1/controllerUtils.js similarity index 100% rename from server/src/controllers/controllerUtils.js rename to server/src/controllers/v1/controllerUtils.js diff --git a/server/src/controllers/diagnosticController.js b/server/src/controllers/v1/diagnosticController.js similarity index 100% rename from server/src/controllers/diagnosticController.js rename to server/src/controllers/v1/diagnosticController.js diff --git a/server/src/controllers/inviteController.js b/server/src/controllers/v1/inviteController.js similarity index 99% rename from server/src/controllers/inviteController.js rename to server/src/controllers/v1/inviteController.js index a377234c0..1ec586f31 100755 --- a/server/src/controllers/inviteController.js +++ b/server/src/controllers/v1/inviteController.js @@ -1,4 +1,4 @@ -import { inviteBodyValidation, inviteVerificationBodyValidation } from "../validation/joi.js"; +import { inviteBodyValidation, inviteVerificationBodyValidation } from "../../validation/joi.js"; import BaseController from "./baseController.js"; const SERVICE_NAME = "inviteController"; diff --git a/server/src/controllers/logController.js b/server/src/controllers/v1/logController.js similarity index 100% rename from server/src/controllers/logController.js rename to server/src/controllers/v1/logController.js diff --git a/server/src/controllers/maintenanceWindowController.js b/server/src/controllers/v1/maintenanceWindowController.js old mode 100755 new mode 100644 similarity index 99% rename from server/src/controllers/maintenanceWindowController.js rename to server/src/controllers/v1/maintenanceWindowController.js index 2fdb1ee15..be373ace5 --- a/server/src/controllers/maintenanceWindowController.js +++ b/server/src/controllers/v1/maintenanceWindowController.js @@ -6,7 +6,7 @@ import { getMaintenanceWindowsByMonitorIdParamValidation, getMaintenanceWindowsByTeamIdQueryValidation, deleteMaintenanceWindowByIdParamValidation, -} from "../validation/joi.js"; +} from "../../validation/joi.js"; import BaseController from "./baseController.js"; const SERVICE_NAME = "maintenanceWindowController"; diff --git a/server/src/controllers/monitorController.js b/server/src/controllers/v1/monitorController.js old mode 100755 new mode 100644 similarity index 99% rename from server/src/controllers/monitorController.js rename to server/src/controllers/v1/monitorController.js index 0f40a4a01..462d5a278 --- a/server/src/controllers/monitorController.js +++ b/server/src/controllers/v1/monitorController.js @@ -11,7 +11,7 @@ import { getCertificateParamValidation, getHardwareDetailsByIdParamValidation, getHardwareDetailsByIdQueryValidation, -} from "../validation/joi.js"; +} from "../../validation/joi.js"; import sslChecker from "ssl-checker"; import { fetchMonitorCertificate } from "./controllerUtils.js"; import BaseController from "./baseController.js"; diff --git a/server/src/controllers/notificationController.js b/server/src/controllers/v1/notificationController.js old mode 100755 new mode 100644 similarity index 98% rename from server/src/controllers/notificationController.js rename to server/src/controllers/v1/notificationController.js index e00dd5b6f..37c2b4e60 --- a/server/src/controllers/notificationController.js +++ b/server/src/controllers/v1/notificationController.js @@ -1,4 +1,4 @@ -import { createNotificationBodyValidation } from "../validation/joi.js"; +import { createNotificationBodyValidation } from "../../validation/joi.js"; import BaseController from "./baseController.js"; const SERVICE_NAME = "NotificationController"; diff --git a/server/src/controllers/queueController.js b/server/src/controllers/v1/queueController.js similarity index 100% rename from server/src/controllers/queueController.js rename to server/src/controllers/v1/queueController.js diff --git a/server/src/controllers/settingsController.js b/server/src/controllers/v1/settingsController.js old mode 100755 new mode 100644 similarity index 95% rename from server/src/controllers/settingsController.js rename to server/src/controllers/v1/settingsController.js index 7da724d23..3db73a05a --- a/server/src/controllers/settingsController.js +++ b/server/src/controllers/v1/settingsController.js @@ -1,5 +1,5 @@ -import { updateAppSettingsBodyValidation } from "../validation/joi.js"; -import { sendTestEmailBodyValidation } from "../validation/joi.js"; +import { updateAppSettingsBodyValidation } from "../../validation/joi.js"; +import { sendTestEmailBodyValidation } from "../../validation/joi.js"; import BaseController from "./baseController.js"; const SERVICE_NAME = "SettingsController"; diff --git a/server/src/controllers/statusPageController.js b/server/src/controllers/v1/statusPageController.js old mode 100755 new mode 100644 similarity index 99% rename from server/src/controllers/statusPageController.js rename to server/src/controllers/v1/statusPageController.js index a46688590..de6a1ca7c --- a/server/src/controllers/statusPageController.js +++ b/server/src/controllers/v1/statusPageController.js @@ -1,4 +1,4 @@ -import { createStatusPageBodyValidation, getStatusPageParamValidation, getStatusPageQueryValidation, imageValidation } from "../validation/joi.js"; +import { createStatusPageBodyValidation, getStatusPageParamValidation, getStatusPageQueryValidation, imageValidation } from "../../validation/joi.js"; import BaseController from "./baseController.js"; const SERVICE_NAME = "statusPageController"; diff --git a/server/src/routes/v1/monitorRoute.js b/server/src/routes/v1/monitorRoute.js index f8f219b83..dde72e256 100755 --- a/server/src/routes/v1/monitorRoute.js +++ b/server/src/routes/v1/monitorRoute.js @@ -1,7 +1,7 @@ import { Router } from "express"; import { isAllowed } from "../../middleware/isAllowed.js"; import multer from "multer"; -import { fetchMonitorCertificate } from "../../controllers/controllerUtils.js"; +import { fetchMonitorCertificate } from "../../controllers/v1/controllerUtils.js"; const upload = multer({ storage: multer.memoryStorage(), // Store file in memory as Buffer }); From 0b646ee64f5c6edff0857c2fdaf2812a64edf2bd Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Mon, 22 Sep 2025 13:26:45 -0700 Subject: [PATCH 018/101] update docker files to build --- docker/dev/build_images.sh | 3 +-- docker/dev/server.Dockerfile | 4 +++- docker/dist-arm/server.Dockerfile | 8 ++++--- docker/dist-mono/docker-compose-local.yaml | 27 ++++++++++++++++++++++ docker/dist-mono/server.Dockerfile | 7 ++++-- docker/dist/server.Dockerfile | 4 +++- docker/prod/server.Dockerfile | 2 ++ docker/staging/server.Dockerfile | 4 +++- 8 files changed, 49 insertions(+), 10 deletions(-) create mode 100755 docker/dist-mono/docker-compose-local.yaml diff --git a/docker/dev/build_images.sh b/docker/dev/build_images.sh index e94be9fa5..eb43e31d8 100755 --- a/docker/dev/build_images.sh +++ b/docker/dev/build_images.sh @@ -5,11 +5,10 @@ cd "$(dirname "$0")" cd ../.. # Define service names and their corresponding Dockerfiles in parallel arrays -services=("uptime_client" "uptime_mongo" "uptime_redis" "uptime_server") +services=("uptime_client" "uptime_mongo" "uptime_server") dockerfiles=( "./docker/dev/client.Dockerfile" "./docker/dev/mongoDB.Dockerfile" - "./docker/dev/redis.Dockerfile" "./docker/dev/server.Dockerfile" ) diff --git a/docker/dev/server.Dockerfile b/docker/dev/server.Dockerfile index d24bff63f..2a6ebcb7c 100755 --- a/docker/dev/server.Dockerfile +++ b/docker/dev/server.Dockerfile @@ -13,6 +13,8 @@ RUN npm install COPY ./server/ ./ +RUN npm run build + EXPOSE 52345 -CMD ["node", "src/index.js"] \ No newline at end of file +CMD ["node", "dist/index.js"] \ No newline at end of file diff --git a/docker/dist-arm/server.Dockerfile b/docker/dist-arm/server.Dockerfile index e47db9a74..91ccd427e 100644 --- a/docker/dist-arm/server.Dockerfile +++ b/docker/dist-arm/server.Dockerfile @@ -35,10 +35,12 @@ RUN npm install COPY server ./ -COPY --from=frontend-build /app/client/dist ./public - RUN chmod +x ./scripts/inject-vars.sh +RUN npm run build + +COPY --from=frontend-build /app/client/dist ./public + EXPOSE 52345 -CMD ["sh", "-c", "./scripts/inject-vars.sh && node ./src/index.js"] +CMD ["sh", "-c", "./scripts/inject-vars.sh && node ./dist/index.js"] diff --git a/docker/dist-mono/docker-compose-local.yaml b/docker/dist-mono/docker-compose-local.yaml new file mode 100755 index 000000000..a5616695b --- /dev/null +++ b/docker/dist-mono/docker-compose-local.yaml @@ -0,0 +1,27 @@ +services: + server: + image: mono_server + restart: always + ports: + - "52345:52345" + environment: + - UPTIME_APP_API_BASE_URL=http://localhost:52345/api/v1 + - UPTIME_APP_CLIENT_HOST=http://localhost + - DB_CONNECTION_STRING=mongodb://mongodb:27017/uptime_db + - CLIENT_HOST=http://localhost + - JWT_SECRET=my_secret + depends_on: + - mongodb + mongodb: + image: mono_mongo + restart: always + command: ["mongod", "--quiet", "--bind_ip_all"] + volumes: + - ./mongo/data:/data/db + healthcheck: + test: ["CMD", "mongosh", "--eval", "db.adminCommand('ping')", "--quiet"] + interval: 5s + timeout: 30s + start_period: 0s + start_interval: 1s + retries: 30 diff --git a/docker/dist-mono/server.Dockerfile b/docker/dist-mono/server.Dockerfile index 405f9e6ac..84b7127ed 100644 --- a/docker/dist-mono/server.Dockerfile +++ b/docker/dist-mono/server.Dockerfile @@ -19,12 +19,15 @@ WORKDIR /app/server COPY server ./ -COPY --from=frontend-build /app/client/dist ./public RUN npm install +RUN npm run build + +COPY --from=frontend-build /app/client/dist ./public + RUN chmod +x ./scripts/inject-vars.sh EXPOSE 52345 -CMD ./scripts/inject-vars.sh && node ./src/index.js +CMD ./scripts/inject-vars.sh && node ./dist/index.js diff --git a/docker/dist/server.Dockerfile b/docker/dist/server.Dockerfile index d24bff63f..2a6ebcb7c 100755 --- a/docker/dist/server.Dockerfile +++ b/docker/dist/server.Dockerfile @@ -13,6 +13,8 @@ RUN npm install COPY ./server/ ./ +RUN npm run build + EXPOSE 52345 -CMD ["node", "src/index.js"] \ No newline at end of file +CMD ["node", "dist/index.js"] \ No newline at end of file diff --git a/docker/prod/server.Dockerfile b/docker/prod/server.Dockerfile index b78de2e33..9d9800e7b 100755 --- a/docker/prod/server.Dockerfile +++ b/docker/prod/server.Dockerfile @@ -15,6 +15,8 @@ RUN npm install COPY ./server ./ +RUN npm run build + EXPOSE 52345 CMD ["node", "src/index.js"] \ No newline at end of file diff --git a/docker/staging/server.Dockerfile b/docker/staging/server.Dockerfile index b78de2e33..7341b1e9b 100755 --- a/docker/staging/server.Dockerfile +++ b/docker/staging/server.Dockerfile @@ -15,6 +15,8 @@ RUN npm install COPY ./server ./ +RUN npm run build + EXPOSE 52345 -CMD ["node", "src/index.js"] \ No newline at end of file +CMD ["node", "dist/index.js"] \ No newline at end of file From 11ff99613ae3f0f95143ca2ce64d1712d1758c85 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Mon, 22 Sep 2025 15:56:41 -0700 Subject: [PATCH 019/101] implement auth routes, add user migration --- server/nodemon.json | 4 +- server/package-lock.json | 117 +++++++++++++++ server/package.json | 4 +- server/src/config/controllers.js | 6 + server/src/config/routes.js | 10 ++ server/src/config/services.js | 15 +- server/src/controllers/v2/AuthController.ts | 142 ++++++++++++++++++ server/src/db/models/InviteToken.js | 35 ----- server/src/db/models/auth/Invite.ts | 68 +++++++++ server/src/db/models/auth/Role.ts | 45 ++++++ .../src/db/models/{User.js => auth/User.ts} | 82 ++++++++-- server/src/db/models/index.ts | 7 + .../db/mongo/migration/0002_migrateUsers.ts | 49 ++++++ server/src/db/mongo/migration/index.js | 2 + server/src/db/mongo/modules/inviteModule.js | 12 +- server/src/middleware/v2/VerifyToken.ts | 21 +++ server/src/middleware/verifyTeamAccess.js | 2 + server/src/routes/v2/auth.ts | 31 ++++ server/src/service/v2/business/AuthService.ts | 141 +++++++++++++++++ .../src/service/v2/business/InviteService.ts | 61 ++++++++ server/src/utils/ApiError.ts | 15 ++ server/src/utils/JWTUtils.ts | 22 +++ 22 files changed, 828 insertions(+), 63 deletions(-) create mode 100644 server/src/controllers/v2/AuthController.ts delete mode 100755 server/src/db/models/InviteToken.js create mode 100755 server/src/db/models/auth/Invite.ts create mode 100644 server/src/db/models/auth/Role.ts rename server/src/db/models/{User.js => auth/User.ts} (55%) create mode 100644 server/src/db/models/index.ts create mode 100644 server/src/db/mongo/migration/0002_migrateUsers.ts create mode 100644 server/src/middleware/v2/VerifyToken.ts create mode 100644 server/src/routes/v2/auth.ts create mode 100644 server/src/service/v2/business/AuthService.ts create mode 100644 server/src/service/v2/business/InviteService.ts create mode 100644 server/src/utils/ApiError.ts create mode 100644 server/src/utils/JWTUtils.ts diff --git a/server/nodemon.json b/server/nodemon.json index 88d6ac686..7371b4904 100755 --- a/server/nodemon.json +++ b/server/nodemon.json @@ -1,5 +1,5 @@ { "ignore": ["src/locales/*", "*.log", "node_modules/*"], - "watch": ["src/**/*.js", "*.json"], - "ext": "js,json" + "watch": ["src/**/*.js", "src/**/*.ts", "*.json"], + "ext": "ts,js,json" } diff --git a/server/package-lock.json b/server/package-lock.json index 06c1c30b3..e21b28382 100644 --- a/server/package-lock.json +++ b/server/package-lock.json @@ -45,6 +45,8 @@ }, "devDependencies": { "@eslint/js": "^9.17.0", + "@types/express": "5.0.3", + "@types/jsonwebtoken": "9.0.10", "c8": "10.1.3", "chai": "5.2.0", "eslint": "^9.17.0", @@ -1678,6 +1680,27 @@ "node": ">=10.13.0" } }, + "node_modules/@types/body-parser": { + "version": "1.19.6", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz", + "integrity": "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "node_modules/@types/connect": { + "version": "3.4.38", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", + "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/estree": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", @@ -1685,12 +1708,44 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/express": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/@types/express/-/express-5.0.3.tgz", + "integrity": "sha512-wGA0NX93b19/dZC1J18tKWVIYWyyF2ZjT9vin/NRu0qzzvfVzWjs04iq2rQ3H65vCTQYlRqs3YHfY7zjdV+9Kw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^5.0.0", + "@types/serve-static": "*" + } + }, + "node_modules/@types/express-serve-static-core": { + "version": "5.0.7", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.0.7.tgz", + "integrity": "sha512-R+33OsgWw7rOhD1emjU7dzCDHucJrgJXMA5PYCzJxVil0dsyx5iBEPHqpPfiKNJQb7lZ1vxwoLR4Z87bBUpeGQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, "node_modules/@types/http-cache-semantics": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.4.tgz", "integrity": "sha512-1m0bIFVc7eJWyve9S0RnuRgcQqF/Xd5QsUZAZeQFr1Q3/p9JWoQQEqmVy+DPTNpGXwhgIetAoYF8JSc33q29QA==", "license": "MIT" }, + "node_modules/@types/http-errors": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.5.tgz", + "integrity": "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/istanbul-lib-coverage": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", @@ -1705,6 +1760,31 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/jsonwebtoken": { + "version": "9.0.10", + "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.10.tgz", + "integrity": "sha512-asx5hIG9Qmf/1oStypjanR7iKTv0gXQ1Ov/jfrX6kS/EO0OFni8orbmGCn0672NHR3kXHwpAwR+B368ZGN/2rA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/ms": "*", + "@types/node": "*" + } + }, + "node_modules/@types/mime": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", + "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/ms": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", + "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/node": { "version": "24.3.0", "resolved": "https://registry.npmjs.org/@types/node/-/node-24.3.0.tgz", @@ -1714,12 +1794,49 @@ "undici-types": "~7.10.0" } }, + "node_modules/@types/qs": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.14.0.tgz", + "integrity": "sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/range-parser": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", + "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/relateurl": { "version": "0.2.33", "resolved": "https://registry.npmjs.org/@types/relateurl/-/relateurl-0.2.33.tgz", "integrity": "sha512-bTQCKsVbIdzLqZhLkF5fcJQreE4y1ro4DIyVrlDNSCJRRwHhB8Z+4zXXa8jN6eDvc2HbRsEYgbvrnGvi54EpSw==", "license": "MIT" }, + "node_modules/@types/send": { + "version": "0.17.5", + "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.5.tgz", + "integrity": "sha512-z6F2D3cOStZvuk2SaP6YrwkNO65iTZcwA2ZkSABegdkAh/lf+Aa/YQndZVfmEXT5vgAp6zv06VQ3ejSVjAny4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/mime": "^1", + "@types/node": "*" + } + }, + "node_modules/@types/serve-static": { + "version": "1.15.8", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.8.tgz", + "integrity": "sha512-roei0UY3LhpOJvjbIP6ZZFngyLKl5dskOtDhxY5THRSpO+ZI+nzJ+m5yUMzGrp89YRa7lvknKkMYjqQFGwA7Sg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/http-errors": "*", + "@types/node": "*", + "@types/send": "*" + } + }, "node_modules/@types/triple-beam": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/@types/triple-beam/-/triple-beam-1.3.5.tgz", diff --git a/server/package.json b/server/package.json index 637c785cc..5c655af9d 100755 --- a/server/package.json +++ b/server/package.json @@ -2,7 +2,7 @@ "name": "server", "version": "1.0.0", "description": "", - "main": "index.js", + "main": "dist/index.js", "type": "module", "scripts": { "test": "c8 mocha", @@ -54,6 +54,8 @@ }, "devDependencies": { "@eslint/js": "^9.17.0", + "@types/express": "5.0.3", + "@types/jsonwebtoken": "9.0.10", "c8": "10.1.3", "chai": "5.2.0", "eslint": "^9.17.0", diff --git a/server/src/config/controllers.js b/server/src/config/controllers.js index 5c54579eb..250d04c4b 100644 --- a/server/src/config/controllers.js +++ b/server/src/config/controllers.js @@ -15,6 +15,9 @@ import StatusPageController from "../controllers/v1/statusPageController.js"; import NotificationController from "../controllers/v1/notificationController.js"; import DiagnosticController from "../controllers/v1/diagnosticController.js"; +// V2 +import AuthV2Controller from "../controllers/v2/AuthController.js"; + export const initializeControllers = (services) => { const controllers = {}; const commonDependencies = createCommonDependencies(services.db, services.errorService, services.logger, services.stringService); @@ -62,5 +65,8 @@ export const initializeControllers = (services) => { diagnosticService: services.diagnosticService, }); + // V2 + controllers.authV2Controller = new AuthV2Controller(services.authV2Service, services.inviteV2Service); + return controllers; }; diff --git a/server/src/config/routes.js b/server/src/config/routes.js index 2ec93a2dc..5ef622f37 100644 --- a/server/src/config/routes.js +++ b/server/src/config/routes.js @@ -13,6 +13,8 @@ import LogRoutes from "../routes/v1/logRoutes.js"; import DiagnosticRoutes from "../routes/v1//diagnosticRoute.js"; import NotificationRoutes from "../routes/v1/notificationRoute.js"; +import authV2 from "../routes/v2/auth.js"; + export const setupRoutes = (app, controllers) => { const authRoutes = new AuthRoutes(controllers.authController); const monitorRoutes = new MonitorRoutes(controllers.monitorController); @@ -37,4 +39,12 @@ export const setupRoutes = (app, controllers) => { app.use("/api/v1/status-page", statusPageRoutes.getRouter()); app.use("/api/v1/notifications", verifyJWT, notificationRoutes.getRouter()); app.use("/api/v1/diagnostic", verifyJWT, diagnosticRoutes.getRouter()); + + // ******************* + // V2 Routes + // ******************* + + const v2AuthRoutes = new authV2(controllers.authV2Controller); + + app.use("/api/v2/auth", authApiLimiter, v2AuthRoutes.getRouter()); }; diff --git a/server/src/config/services.js b/server/src/config/services.js index 08c2525f3..b2b4ac430 100644 --- a/server/src/config/services.js +++ b/server/src/config/services.js @@ -48,8 +48,8 @@ import { ParseBoolean } from "../utils/utils.js"; // Models import Check from "../db/models/Check.js"; import Monitor from "../db/models/Monitor.js"; -import User from "../db/models/User.js"; -import InviteToken from "../db/models/InviteToken.js"; +import { User } from "../db/models/auth/User.js"; +import { Invite } from "../db/models/auth/Invite.js"; import StatusPage from "../db/models/StatusPage.js"; import Team from "../db/models/Team.js"; import MaintenanceWindow from "../db/models/MaintenanceWindow.js"; @@ -68,6 +68,10 @@ import NotificationModule from "../db/mongo/modules/notificationModule.js"; import RecoveryModule from "../db/mongo/modules/recoveryModule.js"; import SettingsModule from "../db/mongo/modules/settingsModule.js"; +// v2 +import AuthV2Service from "../service/v2/business/AuthService.js"; +import InviteV2Service from "../service/v2/business/InviteService.js"; + export const initializeServices = async ({ logger, envSettings, settingsService }) => { const serviceRegistry = new ServiceRegistry({ logger }); ServiceRegistry.instance = serviceRegistry; @@ -79,7 +83,7 @@ export const initializeServices = async ({ logger, envSettings, settingsService // Create DB const checkModule = new CheckModule({ logger, Check, Monitor, User }); - const inviteModule = new InviteModule({ InviteToken, crypto, stringService }); + const inviteModule = new InviteModule({ Invite, crypto, stringService }); const statusPageModule = new StatusPageModule({ StatusPage, NormalizeData, stringService }); const userModule = new UserModule({ User, Team, GenerateAvatarImage, ParseBoolean, stringService }); const maintenanceWindowModule = new MaintenanceWindowModule({ MaintenanceWindow }); @@ -208,6 +212,9 @@ export const initializeServices = async ({ logger, envSettings, settingsService games, }); + const authV2Service = new AuthV2Service(); + const inviteV2Service = new InviteV2Service(); + const services = { settingsService, translationService, @@ -227,6 +234,8 @@ export const initializeServices = async ({ logger, envSettings, settingsService monitorService, errorService, logger, + authV2Service, + inviteV2Service, }; Object.values(services).forEach((service) => { diff --git a/server/src/controllers/v2/AuthController.ts b/server/src/controllers/v2/AuthController.ts new file mode 100644 index 000000000..a9d1dd203 --- /dev/null +++ b/server/src/controllers/v2/AuthController.ts @@ -0,0 +1,142 @@ +import { Request, Response, NextFunction } from "express"; +import { encode, decode } from "../../utils/JWTUtils.js"; +import AuthService from "../../service/v2/business/AuthService.js"; +import ApiError from "../../utils/ApiError.js"; +import InviteService from "../../service/v2/business/InviteService.js"; +import { IInvite } from "../../db/models/index.js"; + +export interface IAuthController { + register(req: Request, res: Response, next: NextFunction): Promise; + registerWithInvite(req: Request, res: Response, next: NextFunction): Promise; + login(req: Request, res: Response, next: NextFunction): Promise; + logout(req: Request, res: Response): void; + me(req: Request, res: Response, next: NextFunction): void; +} + +class AuthController implements IAuthController { + private authService: AuthService; + private inviteService: InviteService; + constructor(authService: AuthService, inviteService: InviteService) { + this.authService = authService; + this.inviteService = inviteService; + } + + register = async (req: Request, res: Response, next: NextFunction) => { + try { + const { email, firstName, lastName, password } = req.body; + + if (!email || !firstName || !lastName || !password) { + throw new Error("Email, firstName, lastName, and password are required"); + } + + const result = await this.authService.register({ + email, + firstName, + lastName, + password, + }); + + const token = encode(result); + + res.cookie("token", token, { + httpOnly: true, + secure: process.env.NODE_ENV === "production", + sameSite: "strict", + maxAge: 7 * 24 * 60 * 60 * 1000, // 1 week + }); + + res.status(201).json({ + message: "User created successfully", + }); + } catch (error) { + next(error); + } + }; + + registerWithInvite = async (req: Request, res: Response, next: NextFunction) => { + try { + const token = req.params.token; + if (!token) { + throw new ApiError("Invite token is required", 400); + } + + const invite: IInvite = await this.inviteService.get(token); + + const { firstName, lastName, password } = req.body; + const email = invite?.email; + const roles = invite?.roles; + + if (!email || !firstName || !lastName || !password || !roles || roles.length === 0) { + throw new Error("Email, firstName, lastName, password, and roles are required"); + } + + const result = await this.authService.registerWithInvite({ + email, + firstName, + lastName, + password, + roles, + }); + + if (!result) { + throw new Error("Registration failed"); + } + + await this.inviteService.delete(invite._id.toString()); + + const jwt = encode(result); + + res.cookie("token", jwt, { + httpOnly: true, + secure: process.env.NODE_ENV === "production", + sameSite: "strict", + maxAge: 7 * 24 * 60 * 60 * 1000, // 1 week + }); + + res.status(201).json({ message: "User created successfully" }); + } catch (error) { + next(error); + } + }; + + login = async (req: Request, res: Response, next: NextFunction) => { + try { + const { email, password } = req.body; + // Validation + if (!email || !password) { + throw new ApiError("Email and password are required", 400); + } + const result = await this.authService.login({ email, password }); + + const token = encode(result); + + res.cookie("token", token, { + httpOnly: true, + secure: process.env.NODE_ENV === "production", + sameSite: "strict", + maxAge: 7 * 24 * 60 * 60 * 1000, // 1 week + }); + + res.status(200).json({ + message: "Login successful", + }); + } catch (error) { + next(error); + } + }; + + logout = (req: Request, res: Response) => { + res.clearCookie("token", { + httpOnly: true, + secure: process.env.NODE_ENV === "production", + sameSite: "strict", + }); + res.status(200).json({ message: "Logout successful" }); + }; + + me = (req: Request, res: Response, next: NextFunction) => { + return res.status(200).json({ message: "OK" }); + }; +} + +export default AuthController; diff --git a/server/src/db/models/InviteToken.js b/server/src/db/models/InviteToken.js deleted file mode 100755 index 079600647..000000000 --- a/server/src/db/models/InviteToken.js +++ /dev/null @@ -1,35 +0,0 @@ -import mongoose from "mongoose"; -const InviteTokenSchema = mongoose.Schema( - { - email: { - type: String, - required: true, - unique: true, - }, - teamId: { - type: mongoose.Schema.Types.ObjectId, - ref: "Team", - immutable: true, - required: true, - }, - role: { - type: Array, - required: true, - default: ["user"], - }, - token: { - type: String, - required: true, - }, - expiry: { - type: Date, - default: Date.now, - expires: 3600, - }, - }, - { - timestamps: true, - } -); - -export default mongoose.model("InviteToken", InviteTokenSchema); diff --git a/server/src/db/models/auth/Invite.ts b/server/src/db/models/auth/Invite.ts new file mode 100755 index 000000000..148ff65cb --- /dev/null +++ b/server/src/db/models/auth/Invite.ts @@ -0,0 +1,68 @@ +import mongoose, { Schema, Document, Types } from "mongoose"; + +export interface IInvite extends Document { + // v1 + _id: Types.ObjectId; + email: string; + teamId: Types.ObjectId; + role: string[]; + token: string; + expiry: Date; + createdAt: Date; + updatedAt: Date; + + // v2 + tokenHash: string; + roles: Types.ObjectId[]; + createdBy: Types.ObjectId; + updatedBy: Types.ObjectId; +} + +const InviteSchema = new Schema( + { + email: { + type: String, + required: true, + unique: true, + trim: true, + lowercase: true, + }, + teamId: { + type: mongoose.Schema.Types.ObjectId, + ref: "Team", + immutable: true, + required: true, + }, + role: { + type: [String], + required: true, + default: ["user"], + }, + token: { + type: String, + required: true, + }, + expiry: { + type: Date, + default: Date.now, + expires: 3600, + }, + + // v2 + roles: [ + { + type: Schema.Types.ObjectId, + ref: "Role", + required: true, + }, + ], + tokenHash: { type: String, required: true, unique: true }, + createdBy: { type: Schema.Types.ObjectId, ref: "User", required: true }, + updatedBy: { type: Schema.Types.ObjectId, ref: "User", required: true }, + }, + { + timestamps: true, + } +); + +export const Invite = mongoose.model("Invite", InviteSchema); diff --git a/server/src/db/models/auth/Role.ts b/server/src/db/models/auth/Role.ts new file mode 100644 index 000000000..e7ffb2f7d --- /dev/null +++ b/server/src/db/models/auth/Role.ts @@ -0,0 +1,45 @@ +import mongoose, { Schema, Document, Types } from "mongoose"; + +export interface IRole extends Document { + _id: Types.ObjectId; + name: string; + description?: string; + permissions: string[]; + isActive: boolean; + createdAt: Date; + updatedAt: Date; +} + +const roleSchema = new Schema( + { + name: { + type: String, + required: true, + trim: true, + maxlength: 50, + }, + description: { + type: String, + trim: true, + maxlength: 200, + }, + + permissions: [ + { + type: String, + required: true, + }, + ], + isActive: { + type: Boolean, + default: true, + }, + }, + { + timestamps: true, + } +); + +roleSchema.index({ name: 1 }, { unique: true }); + +export const Role = mongoose.model("Role", roleSchema); diff --git a/server/src/db/models/User.js b/server/src/db/models/auth/User.ts similarity index 55% rename from server/src/db/models/User.js rename to server/src/db/models/auth/User.ts index 9368d98b1..82c058ea2 100755 --- a/server/src/db/models/User.js +++ b/server/src/db/models/auth/User.ts @@ -1,11 +1,47 @@ -import mongoose from "mongoose"; -import bcrypt from "bcryptjs"; -import logger from "../../utils/logger.js"; -import Monitor from "./Monitor.js"; -import Team from "./Team.js"; -import Notification from "./Notification.js"; +import mongoose, { Schema, Document, Types } from "mongoose"; -const UserSchema = mongoose.Schema( +import bcrypt from "bcryptjs"; +import logger from "../../../utils/logger.js"; +import Monitor from "../Monitor.js"; +import Team from "../Team.js"; +import Notification from "../Notification.js"; +import { subscribe } from "diagnostics_channel"; + +export const RoleTypes = ["user", "admin", "superadmin", "demo"] as const; +export type RoleType = (typeof RoleTypes)[number]; + +export interface ITokenizedUser { + sub: string; + roles: string[]; +} + +export interface IUser extends Document { + // V1 + _id: Types.ObjectId; + firstName: string; + lastName: string; + email: string; + password: string; + avatarImage: string; + profileImage: { + data: Buffer; + contentType: string; + }; + isActive: boolean; + isVerified: boolean; + role?: RoleType[]; + teamId: Types.ObjectId; + checkTTL: number; + createdAt: Date; + updatedAt: Date; + comparePassword: (submittedPassword: string) => Promise; + // V2 + roles: Types.ObjectId[]; + lastLoginAt?: Date; + version: number; +} + +const UserSchema = new Schema( { firstName: { type: String, @@ -22,7 +58,7 @@ const UserSchema = mongoose.Schema( }, password: { type: String, - required: true, + required: false, }, avatarImage: { type: String, @@ -41,8 +77,8 @@ const UserSchema = mongoose.Schema( }, role: { type: [String], - default: "user", - enum: ["user", "admin", "superadmin", "demo"], + default: ["user"], + enum: RoleTypes, }, teamId: { type: mongoose.Schema.Types.ObjectId, @@ -52,6 +88,22 @@ const UserSchema = mongoose.Schema( checkTTL: { type: Number, }, + + // v2 + roles: [ + { + type: Schema.Types.ObjectId, + ref: "Role", + default: [], + }, + ], + lastLoginAt: { + type: Date, + }, + version: { + type: Number, + default: 1, + }, }, { timestamps: true, @@ -69,7 +121,7 @@ UserSchema.pre("save", function (next) { UserSchema.pre("findOneAndUpdate", function (next) { const update = this.getUpdate(); - if ("password" in update) { + if (update && "password" in update) { const salt = bcrypt.genSaltSync(10); update.password = bcrypt.hashSync(update.password, salt); } @@ -92,15 +144,13 @@ UserSchema.pre("findOneAndDelete", async function (next) { } next(); } catch (error) { - next(error); + next(error as Error); } }); -UserSchema.methods.comparePassword = async function (submittedPassword) { +UserSchema.methods.comparePassword = async function (submittedPassword: string) { const res = await bcrypt.compare(submittedPassword, this.password); return res; }; -const User = mongoose.model("User", UserSchema); - -export default User; +export const User = mongoose.model("User", UserSchema); diff --git a/server/src/db/models/index.ts b/server/src/db/models/index.ts new file mode 100644 index 000000000..0969eafc6 --- /dev/null +++ b/server/src/db/models/index.ts @@ -0,0 +1,7 @@ +export { User } from "./auth/User.js"; +export type { IUser } from "./auth/User.js"; +export type { ITokenizedUser } from "./auth/User.js"; +export { Role } from "./auth/Role.js"; +export type { IRole } from "./auth/Role.js"; +export type { IInvite } from "./auth/Invite.js"; +export { Invite } from "./auth/Invite.js"; diff --git a/server/src/db/mongo/migration/0002_migrateUsers.ts b/server/src/db/mongo/migration/0002_migrateUsers.ts new file mode 100644 index 000000000..196ac6793 --- /dev/null +++ b/server/src/db/mongo/migration/0002_migrateUsers.ts @@ -0,0 +1,49 @@ +import { User, Role } from "../../models/index.js"; +import { DEFAULT_ROLES } from "../../../service/v2/business/AuthService.js"; +async function migrateUsers() { + try { + // Create roles if they don't exist + const roleCount = await Role.countDocuments(); + let roles; + if (roleCount === 0) { + const rolePromises = DEFAULT_ROLES.map((roleData) => + new Role({ + ...roleData, + }).save() + ); + roles = await Promise.all(rolePromises); + } else { + roles = await Role.find(); + } + + // Migrate users + const users = await User.find({ + $or: [{ version: { $exists: false } }, { version: { $lt: 2 } }], + }); + for (const user of users) { + for (const role of user.role || []) { + if (role === "superadmin") { + const superAdminRole = roles.find((role) => role.name === "SuperAdmin"); + user.roles.push(superAdminRole!._id); + } + if (role === "admin") { + const managerRole = roles.find((role) => role.name === "Manager"); + user.roles.push(managerRole!._id); + } + if (role === "user") { + const memberRole = roles.find((role) => role.name === "Member"); + user.roles.push(memberRole!._id); + } + } + user.version = 2; + await user.save(); + } + + return true; + } catch (err) { + console.error("Migration error:", err); + return false; + } +} + +export { migrateUsers }; diff --git a/server/src/db/mongo/migration/index.js b/server/src/db/mongo/migration/index.js index 4bf56895a..2a1da4148 100644 --- a/server/src/db/mongo/migration/index.js +++ b/server/src/db/mongo/migration/index.js @@ -1,7 +1,9 @@ import { migrateStatusWindowThreshold } from "./0001_migrateStatusWindowThreshold.js"; +import { migrateUsers } from "./0002_migrateUsers.js"; const runMigrations = async () => { await migrateStatusWindowThreshold(); + await migrateUsers(); }; export { runMigrations }; diff --git a/server/src/db/mongo/modules/inviteModule.js b/server/src/db/mongo/modules/inviteModule.js index 444e51efd..513524fe5 100755 --- a/server/src/db/mongo/modules/inviteModule.js +++ b/server/src/db/mongo/modules/inviteModule.js @@ -1,17 +1,17 @@ const SERVICE_NAME = "inviteModule"; class InviteModule { - constructor({ InviteToken, crypto, stringService }) { - this.InviteToken = InviteToken; + constructor({ Invite, crypto, stringService }) { + this.Invite = Invite; this.crypto = crypto; this.stringService = stringService; } requestInviteToken = async (userData) => { try { - await this.InviteToken.deleteMany({ email: userData.email }); + await this.Invite.deleteMany({ email: userData.email }); userData.token = this.crypto.randomBytes(32).toString("hex"); - let inviteToken = new this.InviteToken(userData); + let inviteToken = new this.Invite(userData); await inviteToken.save(); return inviteToken; } catch (error) { @@ -23,7 +23,7 @@ class InviteModule { getInviteToken = async (token) => { try { - const invite = await this.InviteToken.findOne({ + const invite = await this.Invite.findOne({ token, }); if (invite === null) { @@ -38,7 +38,7 @@ class InviteModule { }; getInviteTokenAndDelete = async (token) => { try { - const invite = await this.InviteToken.findOneAndDelete({ + const invite = await this.Invite.findOneAndDelete({ token, }); if (invite === null) { diff --git a/server/src/middleware/v2/VerifyToken.ts b/server/src/middleware/v2/VerifyToken.ts new file mode 100644 index 000000000..7dabd51cc --- /dev/null +++ b/server/src/middleware/v2/VerifyToken.ts @@ -0,0 +1,21 @@ +import { Request, Response, NextFunction } from "express"; +import { decode } from "../../utils/JWTUtils.js"; +import ApiError from "../../utils/ApiError.js"; +const verifyToken = (req: Request, res: Response, next: NextFunction) => { + const token = req.cookies.token; + + if (!token) { + const error = new ApiError("No token provided", 401); + return next(error); + } + + try { + const decoded = decode(token); + req.user = decoded; + next(); + } catch (error) { + next(error); + } +}; + +export { verifyToken }; diff --git a/server/src/middleware/verifyTeamAccess.js b/server/src/middleware/verifyTeamAccess.js index 60647bdc2..a548cbf23 100644 --- a/server/src/middleware/verifyTeamAccess.js +++ b/server/src/middleware/verifyTeamAccess.js @@ -3,6 +3,8 @@ const SERVICE_NAME = "verifyTeamAccess"; const verifyTeamAccess = (Model, paramName) => { return async (req, res, next) => { try { + const user = req.user; + console.log(user); const documentId = req.params[paramName]; const doc = await Model.findById(documentId); diff --git a/server/src/routes/v2/auth.ts b/server/src/routes/v2/auth.ts new file mode 100644 index 000000000..5a1b8ad63 --- /dev/null +++ b/server/src/routes/v2/auth.ts @@ -0,0 +1,31 @@ +import { Router } from "express"; + +import express from "express"; +import AuthController from "../../controllers/v2/AuthController.js"; +import { verifyToken } from "../../middleware/v2/VerifyToken.js"; + +const router = express.Router(); + +class AuthRoutes { + private controller: AuthController; + private router: Router; + constructor(authController: AuthController) { + this.controller = authController; + this.router = Router(); + this.initRoutes(); + } + + initRoutes = () => { + this.router.post("/register", this.controller.register); + this.router.post("/register/invite/:token", this.controller.registerWithInvite); + this.router.post("/login", this.controller.login); + this.router.post("/logout", this.controller.logout); + this.router.get("/me", verifyToken, this.controller.me); + }; + + getRouter() { + return this.router; + } +} + +export default AuthRoutes; diff --git a/server/src/service/v2/business/AuthService.ts b/server/src/service/v2/business/AuthService.ts new file mode 100644 index 000000000..ae22b981d --- /dev/null +++ b/server/src/service/v2/business/AuthService.ts @@ -0,0 +1,141 @@ +import bcrypt from "bcryptjs"; +import { User, Role, ITokenizedUser } from "../../../db/models/index.js"; +import ApiError from "../../../utils/ApiError.js"; +import { Types } from "mongoose"; + +export const DEFAULT_ROLES = [ + { + name: "SuperAdmin", + description: "Super admin with all permissions", + permissions: ["*"], + isSystem: true, + }, + { + name: "Admin", + description: "Admin with full permissions", + permissions: ["monitor.*", "users.*"], + isSystem: true, + }, + { + name: "Manager", + description: "Can manage users", + permissions: ["users.create", "users.update", "monitors.*"], + isSystem: true, + }, + { + name: "Member", + description: "Basic team member", + permissions: ["users.update", "monitors.create", "monitors.view", "monitors.update"], + isSystem: true, + }, +]; + +export type RegisterData = { + email: string; + firstName: string; + lastName: string; + password: string; + roles?: Types.ObjectId[]; // Optional roles for invite-based registration +}; + +export type LoginData = { + email: string; + password: string; +}; + +export type AuthResult = ITokenizedUser; + +export interface IAuthService { + register(signupData: RegisterData): Promise; + registerWithInvite(signupData: RegisterData): Promise; + login(loginData: LoginData): Promise; +} + +class AuthService implements IAuthService { + constructor() {} + + async register(signupData: RegisterData): Promise { + const userCount = await User.countDocuments(); + + if (userCount > 0) { + throw new Error("Registration is closed. Please request an invite."); + } + + const { email, firstName, lastName, password } = signupData; + + // Create all default roles + const rolePromises = DEFAULT_ROLES.map((roleData) => + new Role({ + ...roleData, + }).save() + ); + const roles = await Promise.all(rolePromises); + + // Find admin role and assign to first user + const superAdminRole = roles.find((role) => role.name === "SuperAdmin"); + + const user = new User({ + email, + firstName, + lastName, + password, + roles: [superAdminRole!._id], + }); + + const savedUser = await user.save(); + return { + sub: savedUser._id.toString(), + roles: savedUser.roles.map((role) => role.toString()), + }; + } + + async registerWithInvite(signupData: RegisterData): Promise { + const { email, firstName, lastName, password, roles } = signupData; + + const user = new User({ + email, + firstName, + lastName, + password, + roles: roles || [], + }); + try { + const savedUser = await user.save(); + return { + sub: savedUser._id.toString(), + roles: savedUser.roles.map((role) => role.toString()), + }; + } catch (error: any) { + if (error?.code === 11000) { + const dupError = new ApiError("Email already in use", 409); + dupError.stack = error?.stack; + throw dupError; + } + throw error; + } + } + + async login(loginData: LoginData): Promise { + const { email, password } = loginData; + + // Find user by email + const user = await User.findOne({ email }); + + if (!user) { + throw new Error("Invalid email or password"); + } + + // Check password + const isPasswordValid = await bcrypt.compare(password, user.passwordHash); + if (!isPasswordValid) { + throw new Error("Invalid email or password"); + } + + return { + sub: user._id.toString(), + roles: user.roles.map((role) => role.toString()), + }; + } +} + +export default AuthService; diff --git a/server/src/service/v2/business/InviteService.ts b/server/src/service/v2/business/InviteService.ts new file mode 100644 index 000000000..f8bd9e2db --- /dev/null +++ b/server/src/service/v2/business/InviteService.ts @@ -0,0 +1,61 @@ +import crypto from "node:crypto"; +import { ITokenizedUser, IInvite, Invite } from "../../../db/models/index.js"; +import ApiError from "../../../utils/ApiError.js"; + +export interface IInviteService { + create: (tokenizedUser: ITokenizedUser, invite: IInvite) => Promise<{ token: string }>; + getAll: () => Promise; + get: (tokenHash: string) => Promise; + delete: (id: string) => Promise; +} + +class InviteService implements IInviteService { + constructor() {} + + create = async (tokenizedUser: ITokenizedUser, inviteData: IInvite) => { + const token = crypto.randomBytes(32).toString("hex"); + const tokenHash = crypto.createHash("sha256").update(token).digest("hex"); + try { + const invite = await Invite.create({ + ...inviteData, + tokenHash, + createdBy: tokenizedUser.sub, + updatedBy: tokenizedUser.sub, + }); + if (!invite) { + throw new ApiError("Failed to create invite", 500); + } + return { token }; + } catch (error: any) { + if (error?.code === 11000) { + const dupError = new ApiError("Invite with this email already exists", 409); + dupError.stack = error?.stack; + throw dupError; + } + throw error; + } + }; + + get = async (token: string) => { + const tokenHash = crypto.createHash("sha256").update(token).digest("hex"); + const invite = await Invite.findOne({ tokenHash }); + if (!invite) { + throw new ApiError("Invite not found", 404); + } + return invite; + }; + + getAll = async () => { + return Invite.find(); + }; + + delete = async (id: string) => { + const result = await Invite.deleteOne({ _id: id }); + if (!result.deletedCount) { + throw new ApiError("Invite not found", 404); + } + return result.deletedCount === 1; + }; +} + +export default InviteService; diff --git a/server/src/utils/ApiError.ts b/server/src/utils/ApiError.ts new file mode 100644 index 000000000..313d01c2a --- /dev/null +++ b/server/src/utils/ApiError.ts @@ -0,0 +1,15 @@ +class ApiError extends Error { + public status: number; + + constructor(message: string, status: number = 500) { + super(message); + this.status = status; + this.name = this.constructor.name; + + if (Error.captureStackTrace) { + Error.captureStackTrace(this, this.constructor); + } + } +} + +export default ApiError; diff --git a/server/src/utils/JWTUtils.ts b/server/src/utils/JWTUtils.ts new file mode 100644 index 000000000..4eebb78a0 --- /dev/null +++ b/server/src/utils/JWTUtils.ts @@ -0,0 +1,22 @@ +import jwt from "jsonwebtoken"; +import { AuthResult } from "../service/v2/business/AuthService.js"; + +const encode = (data: AuthResult): string => { + const secret = process.env.JWT_SECRET; + if (!secret) { + throw new Error("JWT_SECRET is not defined"); + } + const token = jwt.sign(data, secret, { expiresIn: "99d" }); + return token; +}; + +const decode = (token: string): AuthResult => { + const secret = process.env.JWT_SECRET; + if (!secret) { + throw new Error("JWT_SECRET is not defined"); + } + const decoded = jwt.verify(token, secret) as AuthResult; + return decoded; +}; + +export { encode, decode }; From 003039400896c85eb4af8c2210de4b4e40b5ed78 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Tue, 23 Sep 2025 11:34:25 -0700 Subject: [PATCH 020/101] add migrations table --- server/src/db/models/index.ts | 2 ++ server/src/db/models/migration/Migration.ts | 34 +++++++++++++++++++ ...s => 0001_migrateStatusWindowThreshold.ts} | 1 + .../db/mongo/migration/0002_migrateUsers.ts | 13 +++++-- server/src/db/mongo/migration/index.js | 9 ----- server/src/db/mongo/migration/index.ts | 25 ++++++++++++++ 6 files changed, 72 insertions(+), 12 deletions(-) create mode 100644 server/src/db/models/migration/Migration.ts rename server/src/db/mongo/migration/{0001_migrateStatusWindowThreshold.js => 0001_migrateStatusWindowThreshold.ts} (90%) delete mode 100644 server/src/db/mongo/migration/index.js create mode 100644 server/src/db/mongo/migration/index.ts diff --git a/server/src/db/models/index.ts b/server/src/db/models/index.ts index 0969eafc6..537633bfd 100644 --- a/server/src/db/models/index.ts +++ b/server/src/db/models/index.ts @@ -5,3 +5,5 @@ export { Role } from "./auth/Role.js"; export type { IRole } from "./auth/Role.js"; export type { IInvite } from "./auth/Invite.js"; export { Invite } from "./auth/Invite.js"; +export { Migration } from "./migration/Migration.js"; +export type { IMigration } from "./migration/Migration.js"; diff --git a/server/src/db/models/migration/Migration.ts b/server/src/db/models/migration/Migration.ts new file mode 100644 index 000000000..cf76bb48e --- /dev/null +++ b/server/src/db/models/migration/Migration.ts @@ -0,0 +1,34 @@ +import mongoose, { Schema, Document, Types } from "mongoose"; + +export interface IMigration extends Document { + _id: Types.ObjectId; + name: string; + runAt: Date; + success: boolean; + createdAt: Date; + updatedAt: Date; +} + +const migrationSchema = new Schema( + { + name: { + type: String, + required: true, + }, + runAt: { + type: Date, + required: false, + }, + success: { + type: Boolean, + required: false, + }, + }, + { + timestamps: true, + } +); + +migrationSchema.index({ name: 1 }, { unique: true }); + +export const Migration = mongoose.model("Migration", migrationSchema); diff --git a/server/src/db/mongo/migration/0001_migrateStatusWindowThreshold.js b/server/src/db/mongo/migration/0001_migrateStatusWindowThreshold.ts similarity index 90% rename from server/src/db/mongo/migration/0001_migrateStatusWindowThreshold.js rename to server/src/db/mongo/migration/0001_migrateStatusWindowThreshold.ts index 8f6988de4..b44487e87 100644 --- a/server/src/db/mongo/migration/0001_migrateStatusWindowThreshold.js +++ b/server/src/db/mongo/migration/0001_migrateStatusWindowThreshold.ts @@ -1,4 +1,5 @@ import Monitor from "../../models/Monitor.js"; +export const MIGRATION_NAME = "0001_migrateStatusWindowThreshold"; async function migrateStatusWindowThreshold() { try { const monitors = await Monitor.find({ statusWindowThreshold: { $lt: 1 } }); diff --git a/server/src/db/mongo/migration/0002_migrateUsers.ts b/server/src/db/mongo/migration/0002_migrateUsers.ts index 196ac6793..a7cdb76fc 100644 --- a/server/src/db/mongo/migration/0002_migrateUsers.ts +++ b/server/src/db/mongo/migration/0002_migrateUsers.ts @@ -1,5 +1,8 @@ import { User, Role } from "../../models/index.js"; +import { Types } from "mongoose"; + import { DEFAULT_ROLES } from "../../../service/v2/business/AuthService.js"; +export const MIGRATION_NAME = "0002_migrateUsers"; async function migrateUsers() { try { // Create roles if they don't exist @@ -21,20 +24,24 @@ async function migrateUsers() { $or: [{ version: { $exists: false } }, { version: { $lt: 2 } }], }); for (const user of users) { + const newRoleIds: Types.ObjectId[] = []; + for (const role of user.role || []) { if (role === "superadmin") { const superAdminRole = roles.find((role) => role.name === "SuperAdmin"); - user.roles.push(superAdminRole!._id); + newRoleIds.push(superAdminRole!._id); } if (role === "admin") { const managerRole = roles.find((role) => role.name === "Manager"); - user.roles.push(managerRole!._id); + newRoleIds.push(managerRole!._id); } if (role === "user") { const memberRole = roles.find((role) => role.name === "Member"); - user.roles.push(memberRole!._id); + newRoleIds.push(memberRole!._id); } } + const merged = [...new Set([...user.roles, ...newRoleIds])]; + user.roles = merged; user.version = 2; await user.save(); } diff --git a/server/src/db/mongo/migration/index.js b/server/src/db/mongo/migration/index.js deleted file mode 100644 index 2a1da4148..000000000 --- a/server/src/db/mongo/migration/index.js +++ /dev/null @@ -1,9 +0,0 @@ -import { migrateStatusWindowThreshold } from "./0001_migrateStatusWindowThreshold.js"; -import { migrateUsers } from "./0002_migrateUsers.js"; - -const runMigrations = async () => { - await migrateStatusWindowThreshold(); - await migrateUsers(); -}; - -export { runMigrations }; diff --git a/server/src/db/mongo/migration/index.ts b/server/src/db/mongo/migration/index.ts new file mode 100644 index 000000000..293e0905f --- /dev/null +++ b/server/src/db/mongo/migration/index.ts @@ -0,0 +1,25 @@ +import { migrateStatusWindowThreshold, MIGRATION_NAME as MIGRATION_0001 } from "./0001_migrateStatusWindowThreshold.js"; +import { migrateUsers, MIGRATION_NAME as MIGRATION_0002 } from "./0002_migrateUsers.js"; +import { Migration, IMigration } from "../../models/index.js"; + +const runMigrations = async () => { + const migrations = [ + { name: MIGRATION_0001, migration: migrateStatusWindowThreshold }, + { name: MIGRATION_0002, migration: migrateUsers }, + ]; + + const migrationNames = migrations.map((m) => m.name); + + const appliedMigrations = await Migration.find({ name: { $in: migrationNames }, success: true }); + + const migrationsToRun = migrations.filter((m) => !appliedMigrations.some((am: IMigration) => am.name === m.name)); + + for (const { name, migration } of migrationsToRun) { + console.log(`Running migration: ${name}`); + const success = await migration(); + await Migration.updateOne({ name }, { $set: { runAt: new Date(), success } }, { upsert: true }); + console.log(`Migration ${name} completed with status: ${success ? "success" : "failure"}`); + } +}; + +export { runMigrations }; From f945a2701151f9b9cd7337f43550973e345af82d Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Tue, 23 Sep 2025 11:59:02 -0700 Subject: [PATCH 021/101] move v1 to v1 dir, add build workflow --- .github/workflows/build-check.yml | 23 +++++++++ server/src/app.js | 10 ++-- server/src/config/routes.js | 4 +- server/src/config/services.js | 36 +++++++------- server/src/controllers/v1/baseController.js | 2 +- server/src/index.js | 2 +- .../src/middleware/{ => v1}/handleErrors.js | 6 +-- server/src/middleware/{ => v1}/isAllowed.js | 6 +-- .../middleware/{ => v1}/languageMiddleware.js | 2 +- server/src/middleware/{ => v1}/rateLimiter.js | 0 .../middleware/{ => v1}/responseHandler.js | 0 .../src/middleware/{ => v1}/sanitization.js | 0 server/src/middleware/{ => v1}/verifyJWT.js | 6 +-- .../middleware/{ => v1}/verifyOwnership.js | 6 +-- .../middleware/{ => v1}/verifySuperAdmin.js | 2 +- .../middleware/{ => v1}/verifyTeamAccess.js | 0 server/src/routes/v1/authRoute.js | 4 +- server/src/routes/v1/checkRoute.js | 2 +- server/src/routes/v1/diagnosticRoute.js | 4 +- server/src/routes/v1/inviteRoute.js | 4 +- server/src/routes/v1/logRoutes.js | 2 +- server/src/routes/v1/monitorRoute.js | 2 +- server/src/routes/v1/queueRoute.js | 2 +- server/src/routes/v1/settingsRoute.js | 2 +- server/src/routes/v1/statusPageRoute.js | 2 +- .../service/{ => v1}/business/checkService.js | 0 .../{ => v1}/business/diagnosticService.js | 0 .../{ => v1}/business/inviteService.js | 0 .../business/maintenanceWindowService.js | 0 .../{ => v1}/business/monitorService.js | 2 +- .../service/{ => v1}/business/userService.js | 0 .../SuperSimpleQueue/SuperSimpleQueue.js | 0 .../SuperSimpleQueueHelper.js | 10 ++-- .../{ => v1}/infrastructure/bufferService.js | 0 .../{ => v1}/infrastructure/emailService.js | 0 .../{ => v1}/infrastructure/errorService.js | 0 .../{ => v1}/infrastructure/networkService.js | 0 .../infrastructure/notificationService.js | 0 .../infrastructure/notificationUtils.js | 0 .../{ => v1}/infrastructure/statusService.js | 2 +- .../{ => v1}/system/serviceRegistry.js | 0 .../{ => v1}/system/settingsService.js | 0 .../service/{ => v1}/system/stringService.js | 0 .../{ => v1}/system/translationService.js | 0 server/src/service/v2/business/AuthService.ts | 47 +++++++++++++++++-- server/src/types/express.d.ts | 10 ++++ 46 files changed, 136 insertions(+), 64 deletions(-) create mode 100644 .github/workflows/build-check.yml rename server/src/middleware/{ => v1}/handleErrors.js (71%) rename server/src/middleware/{ => v1}/isAllowed.js (87%) rename server/src/middleware/{ => v1}/languageMiddleware.js (93%) rename server/src/middleware/{ => v1}/rateLimiter.js (100%) rename server/src/middleware/{ => v1}/responseHandler.js (100%) rename server/src/middleware/{ => v1}/sanitization.js (100%) rename server/src/middleware/{ => v1}/verifyJWT.js (87%) rename server/src/middleware/{ => v1}/verifyOwnership.js (88%) rename server/src/middleware/{ => v1}/verifySuperAdmin.js (97%) rename server/src/middleware/{ => v1}/verifyTeamAccess.js (100%) rename server/src/service/{ => v1}/business/checkService.js (100%) rename server/src/service/{ => v1}/business/diagnosticService.js (100%) rename server/src/service/{ => v1}/business/inviteService.js (100%) rename server/src/service/{ => v1}/business/maintenanceWindowService.js (100%) rename server/src/service/{ => v1}/business/monitorService.js (99%) rename server/src/service/{ => v1}/business/userService.js (100%) rename server/src/service/{ => v1}/infrastructure/SuperSimpleQueue/SuperSimpleQueue.js (100%) rename server/src/service/{ => v1}/infrastructure/SuperSimpleQueue/SuperSimpleQueueHelper.js (90%) rename server/src/service/{ => v1}/infrastructure/bufferService.js (100%) rename server/src/service/{ => v1}/infrastructure/emailService.js (100%) rename server/src/service/{ => v1}/infrastructure/errorService.js (100%) rename server/src/service/{ => v1}/infrastructure/networkService.js (100%) rename server/src/service/{ => v1}/infrastructure/notificationService.js (100%) rename server/src/service/{ => v1}/infrastructure/notificationUtils.js (100%) rename server/src/service/{ => v1}/infrastructure/statusService.js (99%) rename server/src/service/{ => v1}/system/serviceRegistry.js (100%) rename server/src/service/{ => v1}/system/settingsService.js (100%) rename server/src/service/{ => v1}/system/stringService.js (100%) rename server/src/service/{ => v1}/system/translationService.js (100%) create mode 100644 server/src/types/express.d.ts diff --git a/.github/workflows/build-check.yml b/.github/workflows/build-check.yml new file mode 100644 index 000000000..206a652d5 --- /dev/null +++ b/.github/workflows/build-check.yml @@ -0,0 +1,23 @@ +name: Build Check (Server) +on: + pull_request: + workflow_dispatch: + +jobs: + build-server: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Set up Node + uses: actions/setup-node@v4 + with: + node-version: 22 + + - name: Install server dependencies + working-directory: server + run: npm install + + - name: Check server formatting + working-directory: server + run: npm run build diff --git a/server/src/app.js b/server/src/app.js index 9cabda6b6..253baee88 100644 --- a/server/src/app.js +++ b/server/src/app.js @@ -1,16 +1,16 @@ import express from "express"; import path from "path"; -import { responseHandler } from "./middleware/responseHandler.js"; +import { responseHandler } from "./middleware/v1/responseHandler.js"; import cors from "cors"; import helmet from "helmet"; import compression from "compression"; import cookieParser from "cookie-parser"; -import languageMiddleware from "./middleware/languageMiddleware.js"; +import languageMiddleware from "./middleware/v1/languageMiddleware.js"; import swaggerUi from "swagger-ui-express"; -import { handleErrors } from "./middleware/handleErrors.js"; +import { handleErrors } from "./middleware/v1/handleErrors.js"; import { setupRoutes } from "./config/routes.js"; -import { generalApiLimiter } from "./middleware/rateLimiter.js"; -import { sanitizeBody, sanitizeQuery } from "./middleware/sanitization.js"; +import { generalApiLimiter } from "./middleware/v1/rateLimiter.js"; +import { sanitizeBody, sanitizeQuery } from "./middleware/v1/sanitization.js"; export const createApp = ({ services, controllers, envSettings, frontendPath, openApiSpec }) => { const allowedOrigin = envSettings.clientHost; diff --git a/server/src/config/routes.js b/server/src/config/routes.js index 5ef622f37..2068cf021 100644 --- a/server/src/config/routes.js +++ b/server/src/config/routes.js @@ -1,5 +1,5 @@ -import { verifyJWT } from "../middleware/verifyJWT.js"; -import { authApiLimiter } from "../middleware/rateLimiter.js"; +import { verifyJWT } from "../middleware/v1/verifyJWT.js"; +import { authApiLimiter } from "../middleware/v1/rateLimiter.js"; import AuthRoutes from "../routes/v1/authRoute.js"; import InviteRoutes from "../routes/v1//inviteRoute.js"; diff --git a/server/src/config/services.js b/server/src/config/services.js index b2b4ac430..c5ab7fa02 100644 --- a/server/src/config/services.js +++ b/server/src/config/services.js @@ -1,22 +1,22 @@ -import ServiceRegistry from "../service/system/serviceRegistry.js"; -import TranslationService from "../service/system/translationService.js"; -import StringService from "../service/system/stringService.js"; +import ServiceRegistry from "../service/v1/system/serviceRegistry.js"; +import TranslationService from "../service/v1/system/translationService.js"; +import StringService from "../service/v1/system/stringService.js"; import MongoDB from "../db/mongo/MongoDB.js"; -import NetworkService from "../service/infrastructure/networkService.js"; -import EmailService from "../service/infrastructure/emailService.js"; -import BufferService from "../service/infrastructure/bufferService.js"; -import StatusService from "../service/infrastructure/statusService.js"; -import NotificationUtils from "../service/infrastructure/notificationUtils.js"; -import NotificationService from "../service/infrastructure/notificationService.js"; -import ErrorService from "../service/infrastructure/errorService.js"; -import SuperSimpleQueueHelper from "../service/infrastructure/SuperSimpleQueue/SuperSimpleQueueHelper.js"; -import SuperSimpleQueue from "../service/infrastructure/SuperSimpleQueue/SuperSimpleQueue.js"; -import UserService from "../service/business/userService.js"; -import CheckService from "../service/business/checkService.js"; -import DiagnosticService from "../service/business/diagnosticService.js"; -import InviteService from "../service/business/inviteService.js"; -import MaintenanceWindowService from "../service/business/maintenanceWindowService.js"; -import MonitorService from "../service/business/monitorService.js"; +import NetworkService from "../service/v1/infrastructure/networkService.js"; +import EmailService from "../service/v1/infrastructure/emailService.js"; +import BufferService from "../service/v1/infrastructure/bufferService.js"; +import StatusService from "../service/v1/infrastructure/statusService.js"; +import NotificationUtils from "../service/v1/infrastructure/notificationUtils.js"; +import NotificationService from "../service/v1/infrastructure/notificationService.js"; +import ErrorService from "../service/v1/infrastructure/errorService.js"; +import SuperSimpleQueueHelper from "../service/v1/infrastructure/SuperSimpleQueue/SuperSimpleQueueHelper.js"; +import SuperSimpleQueue from "../service/v1/infrastructure/SuperSimpleQueue/SuperSimpleQueue.js"; +import UserService from "../service/v1/business/userService.js"; +import CheckService from "../service/v1/business/checkService.js"; +import DiagnosticService from "../service/v1/business/diagnosticService.js"; +import InviteService from "../service/v1/business/inviteService.js"; +import MaintenanceWindowService from "../service/v1/business/maintenanceWindowService.js"; +import MonitorService from "../service/v1/business/monitorService.js"; import papaparse from "papaparse"; import axios from "axios"; import got from "got"; diff --git a/server/src/controllers/v1/baseController.js b/server/src/controllers/v1/baseController.js index 314905c82..e8612cec5 100644 --- a/server/src/controllers/v1/baseController.js +++ b/server/src/controllers/v1/baseController.js @@ -1,4 +1,4 @@ -import { AppError } from "../../service/infrastructure/errorService.js"; +import { AppError } from "../../service/v1/infrastructure/errorService.js"; export const createCommonDependencies = (db, errorService, logger, stringService) => { return { diff --git a/server/src/index.js b/server/src/index.js index d1ff649eb..a4e477d5f 100755 --- a/server/src/index.js +++ b/server/src/index.js @@ -7,7 +7,7 @@ import path from "path"; import fs from "fs"; import Logger from "./utils/logger.js"; -import SettingsService from "./service/system/settingsService.js"; +import SettingsService from "./service/v1/system/settingsService.js"; import AppSettings from "./db/models/AppSettings.js"; const SERVICE_NAME = "Server"; diff --git a/server/src/middleware/handleErrors.js b/server/src/middleware/v1/handleErrors.js similarity index 71% rename from server/src/middleware/handleErrors.js rename to server/src/middleware/v1/handleErrors.js index b0ded5ab7..43671fbe2 100755 --- a/server/src/middleware/handleErrors.js +++ b/server/src/middleware/v1/handleErrors.js @@ -1,6 +1,6 @@ -import { logger } from "../utils/logger.js"; -import ServiceRegistry from "../service/system/serviceRegistry.js"; -import StringService from "../service/system/stringService.js"; +import { logger } from "../../utils/logger.js"; +import ServiceRegistry from "../../service/v1/system/serviceRegistry.js"; +import StringService from "../../service/v1/system/stringService.js"; const handleErrors = (error, req, res, next) => { const status = error.status || 500; diff --git a/server/src/middleware/isAllowed.js b/server/src/middleware/v1/isAllowed.js similarity index 87% rename from server/src/middleware/isAllowed.js rename to server/src/middleware/v1/isAllowed.js index 5d61974be..cc240f531 100755 --- a/server/src/middleware/isAllowed.js +++ b/server/src/middleware/v1/isAllowed.js @@ -1,9 +1,9 @@ import jwt from "jsonwebtoken"; const TOKEN_PREFIX = "Bearer "; const SERVICE_NAME = "allowedRoles"; -import ServiceRegistry from "../service/system/serviceRegistry.js"; -import StringService from "../service/system/stringService.js"; -import SettingsService from "../service/system/settingsService.js"; +import ServiceRegistry from "../../service/v1/system/serviceRegistry.js"; +import StringService from "../../service/v1/system/stringService.js"; +import SettingsService from "../../service/v1/system/settingsService.js"; const isAllowed = (allowedRoles) => { return (req, res, next) => { diff --git a/server/src/middleware/languageMiddleware.js b/server/src/middleware/v1/languageMiddleware.js similarity index 93% rename from server/src/middleware/languageMiddleware.js rename to server/src/middleware/v1/languageMiddleware.js index 6293dc837..819858a21 100755 --- a/server/src/middleware/languageMiddleware.js +++ b/server/src/middleware/v1/languageMiddleware.js @@ -1,4 +1,4 @@ -import { logger } from "../utils/logger.js"; +import { logger } from "../../utils/logger.js"; const languageMiddleware = (stringService, translationService) => async (req, res, next) => { try { diff --git a/server/src/middleware/rateLimiter.js b/server/src/middleware/v1/rateLimiter.js similarity index 100% rename from server/src/middleware/rateLimiter.js rename to server/src/middleware/v1/rateLimiter.js diff --git a/server/src/middleware/responseHandler.js b/server/src/middleware/v1/responseHandler.js similarity index 100% rename from server/src/middleware/responseHandler.js rename to server/src/middleware/v1/responseHandler.js diff --git a/server/src/middleware/sanitization.js b/server/src/middleware/v1/sanitization.js similarity index 100% rename from server/src/middleware/sanitization.js rename to server/src/middleware/v1/sanitization.js diff --git a/server/src/middleware/verifyJWT.js b/server/src/middleware/v1/verifyJWT.js similarity index 87% rename from server/src/middleware/verifyJWT.js rename to server/src/middleware/v1/verifyJWT.js index d01db0a97..b57391dfd 100755 --- a/server/src/middleware/verifyJWT.js +++ b/server/src/middleware/v1/verifyJWT.js @@ -1,7 +1,7 @@ import jwt from "jsonwebtoken"; -import ServiceRegistry from "../service/system/serviceRegistry.js"; -import SettingsService from "../service/system/settingsService.js"; -import StringService from "../service/system/stringService.js"; +import ServiceRegistry from "../../service/v1/system/serviceRegistry.js"; +import SettingsService from "../../service/v1/system/settingsService.js"; +import StringService from "../../service/v1/system/stringService.js"; const SERVICE_NAME = "verifyJWT"; const TOKEN_PREFIX = "Bearer "; diff --git a/server/src/middleware/verifyOwnership.js b/server/src/middleware/v1/verifyOwnership.js similarity index 88% rename from server/src/middleware/verifyOwnership.js rename to server/src/middleware/v1/verifyOwnership.js index 4431b0c26..741fb6436 100755 --- a/server/src/middleware/verifyOwnership.js +++ b/server/src/middleware/v1/verifyOwnership.js @@ -1,6 +1,6 @@ -import { logger } from "../utils/logger.js"; -import ServiceRegistry from "../service/system/serviceRegistry.js"; -import StringService from "../service/system/stringService.js"; +import { logger } from "../../utils/logger.js"; +import ServiceRegistry from "../../service/v1/system/serviceRegistry.js"; +import StringService from "../../service/v1/system/stringService.js"; import { ObjectId } from "mongodb"; const SERVICE_NAME = "verifyOwnership"; diff --git a/server/src/middleware/verifySuperAdmin.js b/server/src/middleware/v1/verifySuperAdmin.js similarity index 97% rename from server/src/middleware/verifySuperAdmin.js rename to server/src/middleware/v1/verifySuperAdmin.js index a5a93eede..41ac0a3e0 100755 --- a/server/src/middleware/verifySuperAdmin.js +++ b/server/src/middleware/v1/verifySuperAdmin.js @@ -1,5 +1,5 @@ const jwt = require("jsonwebtoken"); -const logger = require("../utils/logger"); +const logger = require("../../utils/logger.js"); const SERVICE_NAME = "verifyAdmin"; const TOKEN_PREFIX = "Bearer "; import ServiceRegistry from "../service/serviceRegistry.js"; diff --git a/server/src/middleware/verifyTeamAccess.js b/server/src/middleware/v1/verifyTeamAccess.js similarity index 100% rename from server/src/middleware/verifyTeamAccess.js rename to server/src/middleware/v1/verifyTeamAccess.js diff --git a/server/src/routes/v1/authRoute.js b/server/src/routes/v1/authRoute.js index f69f94f43..d9b036b05 100755 --- a/server/src/routes/v1/authRoute.js +++ b/server/src/routes/v1/authRoute.js @@ -1,6 +1,6 @@ import { Router } from "express"; -import { verifyJWT } from "../../middleware/verifyJWT.js"; -import { isAllowed } from "../../middleware/isAllowed.js"; +import { verifyJWT } from "../../middleware/v1/verifyJWT.js"; +import { isAllowed } from "../../middleware/v1/isAllowed.js"; import multer from "multer"; const upload = multer(); diff --git a/server/src/routes/v1/checkRoute.js b/server/src/routes/v1/checkRoute.js index 673fe51e0..78f6b8a42 100755 --- a/server/src/routes/v1/checkRoute.js +++ b/server/src/routes/v1/checkRoute.js @@ -1,6 +1,6 @@ import { Router } from "express"; -import { isAllowed } from "../../middleware/isAllowed.js"; +import { isAllowed } from "../../middleware/v1/isAllowed.js"; class CheckRoutes { constructor(checkController) { diff --git a/server/src/routes/v1/diagnosticRoute.js b/server/src/routes/v1/diagnosticRoute.js index 1b7cc06be..2cf481e08 100755 --- a/server/src/routes/v1/diagnosticRoute.js +++ b/server/src/routes/v1/diagnosticRoute.js @@ -1,6 +1,6 @@ import { Router } from "express"; -import { verifyJWT } from "../../middleware/verifyJWT.js"; -import { isAllowed } from "../../middleware/isAllowed.js"; +import { verifyJWT } from "../../middleware/v1/verifyJWT.js"; +import { isAllowed } from "../../middleware/v1/isAllowed.js"; class DiagnosticRoutes { constructor(diagnosticController) { diff --git a/server/src/routes/v1/inviteRoute.js b/server/src/routes/v1/inviteRoute.js index 74fcfeb91..a47550536 100755 --- a/server/src/routes/v1/inviteRoute.js +++ b/server/src/routes/v1/inviteRoute.js @@ -1,6 +1,6 @@ import { Router } from "express"; -import { verifyJWT } from "../../middleware/verifyJWT.js"; -import { isAllowed } from "../../middleware/isAllowed.js"; +import { verifyJWT } from "../../middleware/v1/verifyJWT.js"; +import { isAllowed } from "../../middleware/v1/isAllowed.js"; class InviteRoutes { constructor(inviteController) { diff --git a/server/src/routes/v1/logRoutes.js b/server/src/routes/v1/logRoutes.js index feabe669a..43da8b75a 100755 --- a/server/src/routes/v1/logRoutes.js +++ b/server/src/routes/v1/logRoutes.js @@ -1,5 +1,5 @@ import { Router } from "express"; -import { isAllowed } from "../../middleware/isAllowed.js"; +import { isAllowed } from "../../middleware/v1/isAllowed.js"; class LogRoutes { constructor(logController) { this.router = Router(); diff --git a/server/src/routes/v1/monitorRoute.js b/server/src/routes/v1/monitorRoute.js index dde72e256..107964f6b 100755 --- a/server/src/routes/v1/monitorRoute.js +++ b/server/src/routes/v1/monitorRoute.js @@ -1,5 +1,5 @@ import { Router } from "express"; -import { isAllowed } from "../../middleware/isAllowed.js"; +import { isAllowed } from "../../middleware/v1/isAllowed.js"; import multer from "multer"; import { fetchMonitorCertificate } from "../../controllers/v1/controllerUtils.js"; const upload = multer({ diff --git a/server/src/routes/v1/queueRoute.js b/server/src/routes/v1/queueRoute.js index 59e1cb744..c6cfa05da 100755 --- a/server/src/routes/v1/queueRoute.js +++ b/server/src/routes/v1/queueRoute.js @@ -1,5 +1,5 @@ import { Router } from "express"; -import { isAllowed } from "../../middleware/isAllowed.js"; +import { isAllowed } from "../../middleware/v1/isAllowed.js"; class QueueRoutes { constructor(queueController) { this.router = Router(); diff --git a/server/src/routes/v1/settingsRoute.js b/server/src/routes/v1/settingsRoute.js index 76556e923..e88186af2 100755 --- a/server/src/routes/v1/settingsRoute.js +++ b/server/src/routes/v1/settingsRoute.js @@ -1,5 +1,5 @@ import { Router } from "express"; -import { isAllowed } from "../../middleware/isAllowed.js"; +import { isAllowed } from "../../middleware/v1/isAllowed.js"; class SettingsRoutes { constructor(settingsController) { diff --git a/server/src/routes/v1/statusPageRoute.js b/server/src/routes/v1/statusPageRoute.js index 8300066a8..2c3b0b265 100755 --- a/server/src/routes/v1/statusPageRoute.js +++ b/server/src/routes/v1/statusPageRoute.js @@ -1,5 +1,5 @@ import { Router } from "express"; -import { verifyJWT } from "../../middleware/verifyJWT.js"; +import { verifyJWT } from "../../middleware/v1/verifyJWT.js"; import multer from "multer"; const upload = multer(); diff --git a/server/src/service/business/checkService.js b/server/src/service/v1/business/checkService.js similarity index 100% rename from server/src/service/business/checkService.js rename to server/src/service/v1/business/checkService.js diff --git a/server/src/service/business/diagnosticService.js b/server/src/service/v1/business/diagnosticService.js similarity index 100% rename from server/src/service/business/diagnosticService.js rename to server/src/service/v1/business/diagnosticService.js diff --git a/server/src/service/business/inviteService.js b/server/src/service/v1/business/inviteService.js similarity index 100% rename from server/src/service/business/inviteService.js rename to server/src/service/v1/business/inviteService.js diff --git a/server/src/service/business/maintenanceWindowService.js b/server/src/service/v1/business/maintenanceWindowService.js similarity index 100% rename from server/src/service/business/maintenanceWindowService.js rename to server/src/service/v1/business/maintenanceWindowService.js diff --git a/server/src/service/business/monitorService.js b/server/src/service/v1/business/monitorService.js similarity index 99% rename from server/src/service/business/monitorService.js rename to server/src/service/v1/business/monitorService.js index 637870b71..061234102 100644 --- a/server/src/service/business/monitorService.js +++ b/server/src/service/v1/business/monitorService.js @@ -1,4 +1,4 @@ -import { createMonitorsBodyValidation } from "../../validation/joi.js"; +import { createMonitorsBodyValidation } from "../../../validation/joi.js"; const SERVICE_NAME = "MonitorService"; class MonitorService { diff --git a/server/src/service/business/userService.js b/server/src/service/v1/business/userService.js similarity index 100% rename from server/src/service/business/userService.js rename to server/src/service/v1/business/userService.js diff --git a/server/src/service/infrastructure/SuperSimpleQueue/SuperSimpleQueue.js b/server/src/service/v1/infrastructure/SuperSimpleQueue/SuperSimpleQueue.js similarity index 100% rename from server/src/service/infrastructure/SuperSimpleQueue/SuperSimpleQueue.js rename to server/src/service/v1/infrastructure/SuperSimpleQueue/SuperSimpleQueue.js diff --git a/server/src/service/infrastructure/SuperSimpleQueue/SuperSimpleQueueHelper.js b/server/src/service/v1/infrastructure/SuperSimpleQueue/SuperSimpleQueueHelper.js similarity index 90% rename from server/src/service/infrastructure/SuperSimpleQueue/SuperSimpleQueueHelper.js rename to server/src/service/v1/infrastructure/SuperSimpleQueue/SuperSimpleQueueHelper.js index b348e2534..2833a59f5 100644 --- a/server/src/service/infrastructure/SuperSimpleQueue/SuperSimpleQueueHelper.js +++ b/server/src/service/v1/infrastructure/SuperSimpleQueue/SuperSimpleQueueHelper.js @@ -5,11 +5,11 @@ class SuperSimpleQueueHelper { /** * @param {{ - * db: import("../database").Database, - * logger: import("../logger").Logger, - * networkService: import("../networkService").NetworkService, - * statusService: import("../statusService").StatusService, - * notificationService: import("../notificationService").NotificationService + * db: import("../database.js").Database, + * logger: import("../logger.js").Logger, + * networkService: import("../networkService.js").NetworkService, + * statusService: import("../statusService.js").StatusService, + * notificationService: import("../notificationService.js").NotificationService * }} */ constructor({ db, logger, networkService, statusService, notificationService }) { diff --git a/server/src/service/infrastructure/bufferService.js b/server/src/service/v1/infrastructure/bufferService.js similarity index 100% rename from server/src/service/infrastructure/bufferService.js rename to server/src/service/v1/infrastructure/bufferService.js diff --git a/server/src/service/infrastructure/emailService.js b/server/src/service/v1/infrastructure/emailService.js similarity index 100% rename from server/src/service/infrastructure/emailService.js rename to server/src/service/v1/infrastructure/emailService.js diff --git a/server/src/service/infrastructure/errorService.js b/server/src/service/v1/infrastructure/errorService.js similarity index 100% rename from server/src/service/infrastructure/errorService.js rename to server/src/service/v1/infrastructure/errorService.js diff --git a/server/src/service/infrastructure/networkService.js b/server/src/service/v1/infrastructure/networkService.js similarity index 100% rename from server/src/service/infrastructure/networkService.js rename to server/src/service/v1/infrastructure/networkService.js diff --git a/server/src/service/infrastructure/notificationService.js b/server/src/service/v1/infrastructure/notificationService.js similarity index 100% rename from server/src/service/infrastructure/notificationService.js rename to server/src/service/v1/infrastructure/notificationService.js diff --git a/server/src/service/infrastructure/notificationUtils.js b/server/src/service/v1/infrastructure/notificationUtils.js similarity index 100% rename from server/src/service/infrastructure/notificationUtils.js rename to server/src/service/v1/infrastructure/notificationUtils.js diff --git a/server/src/service/infrastructure/statusService.js b/server/src/service/v1/infrastructure/statusService.js similarity index 99% rename from server/src/service/infrastructure/statusService.js rename to server/src/service/v1/infrastructure/statusService.js index 72fa359d7..983541cd9 100755 --- a/server/src/service/infrastructure/statusService.js +++ b/server/src/service/v1/infrastructure/statusService.js @@ -1,4 +1,4 @@ -import MonitorStats from "../../db/models/MonitorStats.js"; +import MonitorStats from "../../../db/models/MonitorStats.js"; const SERVICE_NAME = "StatusService"; class StatusService { diff --git a/server/src/service/system/serviceRegistry.js b/server/src/service/v1/system/serviceRegistry.js similarity index 100% rename from server/src/service/system/serviceRegistry.js rename to server/src/service/v1/system/serviceRegistry.js diff --git a/server/src/service/system/settingsService.js b/server/src/service/v1/system/settingsService.js similarity index 100% rename from server/src/service/system/settingsService.js rename to server/src/service/v1/system/settingsService.js diff --git a/server/src/service/system/stringService.js b/server/src/service/v1/system/stringService.js similarity index 100% rename from server/src/service/system/stringService.js rename to server/src/service/v1/system/stringService.js diff --git a/server/src/service/system/translationService.js b/server/src/service/v1/system/translationService.js similarity index 100% rename from server/src/service/system/translationService.js rename to server/src/service/v1/system/translationService.js diff --git a/server/src/service/v2/business/AuthService.ts b/server/src/service/v2/business/AuthService.ts index ae22b981d..52f586dbf 100644 --- a/server/src/service/v2/business/AuthService.ts +++ b/server/src/service/v2/business/AuthService.ts @@ -3,6 +3,45 @@ import { User, Role, ITokenizedUser } from "../../../db/models/index.js"; import ApiError from "../../../utils/ApiError.js"; import { Types } from "mongoose"; +export const PERMISSIONS = { + users: { + all: "users.*", + create: "users.create", + view: "users.view", + update: "users.update", + delete: "users.delete", + }, + + monitors: { + all: "monitors.*", + create: "monitors.create", + view: "monitors.view", + update: "monitors.update", + delete: "monitors.delete", + }, + notifications: { + all: "notifications.*", + create: "notifications.create", + view: "notifications.view", + update: "notifications.update", + delete: "notifications.delete", + }, + checks: { + all: "checks.*", + create: "checks.create", + view: "checks.view", + update: "checks.update", + delete: "checks.delete", + }, + statusPages: { + all: "statusPages.*", + create: "statusPages.create", + view: "statusPages.view", + update: "statusPages.update", + delete: "statusPages.delete", + }, +}; + export const DEFAULT_ROLES = [ { name: "SuperAdmin", @@ -13,19 +52,19 @@ export const DEFAULT_ROLES = [ { name: "Admin", description: "Admin with full permissions", - permissions: ["monitor.*", "users.*"], + permissions: [PERMISSIONS.monitors.all, PERMISSIONS.users.all], isSystem: true, }, { name: "Manager", description: "Can manage users", - permissions: ["users.create", "users.update", "monitors.*"], + permissions: [PERMISSIONS.users.create, PERMISSIONS.users.update, PERMISSIONS.monitors.all], isSystem: true, }, { name: "Member", description: "Basic team member", - permissions: ["users.update", "monitors.create", "monitors.view", "monitors.update"], + permissions: [PERMISSIONS.users.update, PERMISSIONS.monitors.create, PERMISSIONS.monitors.view, PERMISSIONS.monitors.update], isSystem: true, }, ]; @@ -126,7 +165,7 @@ class AuthService implements IAuthService { } // Check password - const isPasswordValid = await bcrypt.compare(password, user.passwordHash); + const isPasswordValid = await bcrypt.compare(password, user.password); if (!isPasswordValid) { throw new Error("Invalid email or password"); } diff --git a/server/src/types/express.d.ts b/server/src/types/express.d.ts new file mode 100644 index 000000000..7c78a053c --- /dev/null +++ b/server/src/types/express.d.ts @@ -0,0 +1,10 @@ +import { ITokenizedUser } from "../db/models/index.ts"; + +declare global { + namespace Express { + interface Request { + user?: ITokenizedUser; + resource?: any; + } + } +} From 003d419a6453167b81b6037b8ae6ae3f004f10ca Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Tue, 23 Sep 2025 14:05:16 -0700 Subject: [PATCH 022/101] Revert "Merge pull request #2974 from bluewave-labs/fix/cleanup" This reverts commit 7368cb2567c0e992cfebe05efddcda110ff16e34, reversing changes made to 72882fded9b57b1d0f76a521e12afea9f63135ce. --- .github/workflows/build-check.yml | 23 --------- server/src/app.js | 10 ++-- server/src/config/routes.js | 4 +- server/src/config/services.js | 36 +++++++------- server/src/controllers/v1/baseController.js | 2 +- server/src/index.js | 2 +- .../src/middleware/{v1 => }/handleErrors.js | 6 +-- server/src/middleware/{v1 => }/isAllowed.js | 6 +-- .../middleware/{v1 => }/languageMiddleware.js | 2 +- server/src/middleware/{v1 => }/rateLimiter.js | 0 .../middleware/{v1 => }/responseHandler.js | 0 .../src/middleware/{v1 => }/sanitization.js | 0 server/src/middleware/{v1 => }/verifyJWT.js | 6 +-- .../middleware/{v1 => }/verifyOwnership.js | 6 +-- .../middleware/{v1 => }/verifySuperAdmin.js | 2 +- .../middleware/{v1 => }/verifyTeamAccess.js | 0 server/src/routes/v1/authRoute.js | 4 +- server/src/routes/v1/checkRoute.js | 2 +- server/src/routes/v1/diagnosticRoute.js | 4 +- server/src/routes/v1/inviteRoute.js | 4 +- server/src/routes/v1/logRoutes.js | 2 +- server/src/routes/v1/monitorRoute.js | 2 +- server/src/routes/v1/queueRoute.js | 2 +- server/src/routes/v1/settingsRoute.js | 2 +- server/src/routes/v1/statusPageRoute.js | 2 +- .../service/{v1 => }/business/checkService.js | 0 .../{v1 => }/business/diagnosticService.js | 0 .../{v1 => }/business/inviteService.js | 0 .../business/maintenanceWindowService.js | 0 .../{v1 => }/business/monitorService.js | 2 +- .../service/{v1 => }/business/userService.js | 0 .../SuperSimpleQueue/SuperSimpleQueue.js | 0 .../SuperSimpleQueueHelper.js | 10 ++-- .../{v1 => }/infrastructure/bufferService.js | 0 .../{v1 => }/infrastructure/emailService.js | 0 .../{v1 => }/infrastructure/errorService.js | 0 .../{v1 => }/infrastructure/networkService.js | 0 .../infrastructure/notificationService.js | 0 .../infrastructure/notificationUtils.js | 0 .../{v1 => }/infrastructure/statusService.js | 2 +- .../{v1 => }/system/serviceRegistry.js | 0 .../{v1 => }/system/settingsService.js | 0 .../service/{v1 => }/system/stringService.js | 0 .../{v1 => }/system/translationService.js | 0 server/src/service/v2/business/AuthService.ts | 47 ++----------------- server/src/types/express.d.ts | 10 ---- 46 files changed, 64 insertions(+), 136 deletions(-) delete mode 100644 .github/workflows/build-check.yml rename server/src/middleware/{v1 => }/handleErrors.js (71%) rename server/src/middleware/{v1 => }/isAllowed.js (87%) rename server/src/middleware/{v1 => }/languageMiddleware.js (93%) rename server/src/middleware/{v1 => }/rateLimiter.js (100%) rename server/src/middleware/{v1 => }/responseHandler.js (100%) rename server/src/middleware/{v1 => }/sanitization.js (100%) rename server/src/middleware/{v1 => }/verifyJWT.js (87%) rename server/src/middleware/{v1 => }/verifyOwnership.js (88%) rename server/src/middleware/{v1 => }/verifySuperAdmin.js (97%) rename server/src/middleware/{v1 => }/verifyTeamAccess.js (100%) rename server/src/service/{v1 => }/business/checkService.js (100%) rename server/src/service/{v1 => }/business/diagnosticService.js (100%) rename server/src/service/{v1 => }/business/inviteService.js (100%) rename server/src/service/{v1 => }/business/maintenanceWindowService.js (100%) rename server/src/service/{v1 => }/business/monitorService.js (99%) rename server/src/service/{v1 => }/business/userService.js (100%) rename server/src/service/{v1 => }/infrastructure/SuperSimpleQueue/SuperSimpleQueue.js (100%) rename server/src/service/{v1 => }/infrastructure/SuperSimpleQueue/SuperSimpleQueueHelper.js (90%) rename server/src/service/{v1 => }/infrastructure/bufferService.js (100%) rename server/src/service/{v1 => }/infrastructure/emailService.js (100%) rename server/src/service/{v1 => }/infrastructure/errorService.js (100%) rename server/src/service/{v1 => }/infrastructure/networkService.js (100%) rename server/src/service/{v1 => }/infrastructure/notificationService.js (100%) rename server/src/service/{v1 => }/infrastructure/notificationUtils.js (100%) rename server/src/service/{v1 => }/infrastructure/statusService.js (99%) rename server/src/service/{v1 => }/system/serviceRegistry.js (100%) rename server/src/service/{v1 => }/system/settingsService.js (100%) rename server/src/service/{v1 => }/system/stringService.js (100%) rename server/src/service/{v1 => }/system/translationService.js (100%) delete mode 100644 server/src/types/express.d.ts diff --git a/.github/workflows/build-check.yml b/.github/workflows/build-check.yml deleted file mode 100644 index 206a652d5..000000000 --- a/.github/workflows/build-check.yml +++ /dev/null @@ -1,23 +0,0 @@ -name: Build Check (Server) -on: - pull_request: - workflow_dispatch: - -jobs: - build-server: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - - name: Set up Node - uses: actions/setup-node@v4 - with: - node-version: 22 - - - name: Install server dependencies - working-directory: server - run: npm install - - - name: Check server formatting - working-directory: server - run: npm run build diff --git a/server/src/app.js b/server/src/app.js index 253baee88..9cabda6b6 100644 --- a/server/src/app.js +++ b/server/src/app.js @@ -1,16 +1,16 @@ import express from "express"; import path from "path"; -import { responseHandler } from "./middleware/v1/responseHandler.js"; +import { responseHandler } from "./middleware/responseHandler.js"; import cors from "cors"; import helmet from "helmet"; import compression from "compression"; import cookieParser from "cookie-parser"; -import languageMiddleware from "./middleware/v1/languageMiddleware.js"; +import languageMiddleware from "./middleware/languageMiddleware.js"; import swaggerUi from "swagger-ui-express"; -import { handleErrors } from "./middleware/v1/handleErrors.js"; +import { handleErrors } from "./middleware/handleErrors.js"; import { setupRoutes } from "./config/routes.js"; -import { generalApiLimiter } from "./middleware/v1/rateLimiter.js"; -import { sanitizeBody, sanitizeQuery } from "./middleware/v1/sanitization.js"; +import { generalApiLimiter } from "./middleware/rateLimiter.js"; +import { sanitizeBody, sanitizeQuery } from "./middleware/sanitization.js"; export const createApp = ({ services, controllers, envSettings, frontendPath, openApiSpec }) => { const allowedOrigin = envSettings.clientHost; diff --git a/server/src/config/routes.js b/server/src/config/routes.js index 2068cf021..5ef622f37 100644 --- a/server/src/config/routes.js +++ b/server/src/config/routes.js @@ -1,5 +1,5 @@ -import { verifyJWT } from "../middleware/v1/verifyJWT.js"; -import { authApiLimiter } from "../middleware/v1/rateLimiter.js"; +import { verifyJWT } from "../middleware/verifyJWT.js"; +import { authApiLimiter } from "../middleware/rateLimiter.js"; import AuthRoutes from "../routes/v1/authRoute.js"; import InviteRoutes from "../routes/v1//inviteRoute.js"; diff --git a/server/src/config/services.js b/server/src/config/services.js index c5ab7fa02..b2b4ac430 100644 --- a/server/src/config/services.js +++ b/server/src/config/services.js @@ -1,22 +1,22 @@ -import ServiceRegistry from "../service/v1/system/serviceRegistry.js"; -import TranslationService from "../service/v1/system/translationService.js"; -import StringService from "../service/v1/system/stringService.js"; +import ServiceRegistry from "../service/system/serviceRegistry.js"; +import TranslationService from "../service/system/translationService.js"; +import StringService from "../service/system/stringService.js"; import MongoDB from "../db/mongo/MongoDB.js"; -import NetworkService from "../service/v1/infrastructure/networkService.js"; -import EmailService from "../service/v1/infrastructure/emailService.js"; -import BufferService from "../service/v1/infrastructure/bufferService.js"; -import StatusService from "../service/v1/infrastructure/statusService.js"; -import NotificationUtils from "../service/v1/infrastructure/notificationUtils.js"; -import NotificationService from "../service/v1/infrastructure/notificationService.js"; -import ErrorService from "../service/v1/infrastructure/errorService.js"; -import SuperSimpleQueueHelper from "../service/v1/infrastructure/SuperSimpleQueue/SuperSimpleQueueHelper.js"; -import SuperSimpleQueue from "../service/v1/infrastructure/SuperSimpleQueue/SuperSimpleQueue.js"; -import UserService from "../service/v1/business/userService.js"; -import CheckService from "../service/v1/business/checkService.js"; -import DiagnosticService from "../service/v1/business/diagnosticService.js"; -import InviteService from "../service/v1/business/inviteService.js"; -import MaintenanceWindowService from "../service/v1/business/maintenanceWindowService.js"; -import MonitorService from "../service/v1/business/monitorService.js"; +import NetworkService from "../service/infrastructure/networkService.js"; +import EmailService from "../service/infrastructure/emailService.js"; +import BufferService from "../service/infrastructure/bufferService.js"; +import StatusService from "../service/infrastructure/statusService.js"; +import NotificationUtils from "../service/infrastructure/notificationUtils.js"; +import NotificationService from "../service/infrastructure/notificationService.js"; +import ErrorService from "../service/infrastructure/errorService.js"; +import SuperSimpleQueueHelper from "../service/infrastructure/SuperSimpleQueue/SuperSimpleQueueHelper.js"; +import SuperSimpleQueue from "../service/infrastructure/SuperSimpleQueue/SuperSimpleQueue.js"; +import UserService from "../service/business/userService.js"; +import CheckService from "../service/business/checkService.js"; +import DiagnosticService from "../service/business/diagnosticService.js"; +import InviteService from "../service/business/inviteService.js"; +import MaintenanceWindowService from "../service/business/maintenanceWindowService.js"; +import MonitorService from "../service/business/monitorService.js"; import papaparse from "papaparse"; import axios from "axios"; import got from "got"; diff --git a/server/src/controllers/v1/baseController.js b/server/src/controllers/v1/baseController.js index e8612cec5..314905c82 100644 --- a/server/src/controllers/v1/baseController.js +++ b/server/src/controllers/v1/baseController.js @@ -1,4 +1,4 @@ -import { AppError } from "../../service/v1/infrastructure/errorService.js"; +import { AppError } from "../../service/infrastructure/errorService.js"; export const createCommonDependencies = (db, errorService, logger, stringService) => { return { diff --git a/server/src/index.js b/server/src/index.js index a4e477d5f..d1ff649eb 100755 --- a/server/src/index.js +++ b/server/src/index.js @@ -7,7 +7,7 @@ import path from "path"; import fs from "fs"; import Logger from "./utils/logger.js"; -import SettingsService from "./service/v1/system/settingsService.js"; +import SettingsService from "./service/system/settingsService.js"; import AppSettings from "./db/models/AppSettings.js"; const SERVICE_NAME = "Server"; diff --git a/server/src/middleware/v1/handleErrors.js b/server/src/middleware/handleErrors.js similarity index 71% rename from server/src/middleware/v1/handleErrors.js rename to server/src/middleware/handleErrors.js index 43671fbe2..b0ded5ab7 100755 --- a/server/src/middleware/v1/handleErrors.js +++ b/server/src/middleware/handleErrors.js @@ -1,6 +1,6 @@ -import { logger } from "../../utils/logger.js"; -import ServiceRegistry from "../../service/v1/system/serviceRegistry.js"; -import StringService from "../../service/v1/system/stringService.js"; +import { logger } from "../utils/logger.js"; +import ServiceRegistry from "../service/system/serviceRegistry.js"; +import StringService from "../service/system/stringService.js"; const handleErrors = (error, req, res, next) => { const status = error.status || 500; diff --git a/server/src/middleware/v1/isAllowed.js b/server/src/middleware/isAllowed.js similarity index 87% rename from server/src/middleware/v1/isAllowed.js rename to server/src/middleware/isAllowed.js index cc240f531..5d61974be 100755 --- a/server/src/middleware/v1/isAllowed.js +++ b/server/src/middleware/isAllowed.js @@ -1,9 +1,9 @@ import jwt from "jsonwebtoken"; const TOKEN_PREFIX = "Bearer "; const SERVICE_NAME = "allowedRoles"; -import ServiceRegistry from "../../service/v1/system/serviceRegistry.js"; -import StringService from "../../service/v1/system/stringService.js"; -import SettingsService from "../../service/v1/system/settingsService.js"; +import ServiceRegistry from "../service/system/serviceRegistry.js"; +import StringService from "../service/system/stringService.js"; +import SettingsService from "../service/system/settingsService.js"; const isAllowed = (allowedRoles) => { return (req, res, next) => { diff --git a/server/src/middleware/v1/languageMiddleware.js b/server/src/middleware/languageMiddleware.js similarity index 93% rename from server/src/middleware/v1/languageMiddleware.js rename to server/src/middleware/languageMiddleware.js index 819858a21..6293dc837 100755 --- a/server/src/middleware/v1/languageMiddleware.js +++ b/server/src/middleware/languageMiddleware.js @@ -1,4 +1,4 @@ -import { logger } from "../../utils/logger.js"; +import { logger } from "../utils/logger.js"; const languageMiddleware = (stringService, translationService) => async (req, res, next) => { try { diff --git a/server/src/middleware/v1/rateLimiter.js b/server/src/middleware/rateLimiter.js similarity index 100% rename from server/src/middleware/v1/rateLimiter.js rename to server/src/middleware/rateLimiter.js diff --git a/server/src/middleware/v1/responseHandler.js b/server/src/middleware/responseHandler.js similarity index 100% rename from server/src/middleware/v1/responseHandler.js rename to server/src/middleware/responseHandler.js diff --git a/server/src/middleware/v1/sanitization.js b/server/src/middleware/sanitization.js similarity index 100% rename from server/src/middleware/v1/sanitization.js rename to server/src/middleware/sanitization.js diff --git a/server/src/middleware/v1/verifyJWT.js b/server/src/middleware/verifyJWT.js similarity index 87% rename from server/src/middleware/v1/verifyJWT.js rename to server/src/middleware/verifyJWT.js index b57391dfd..d01db0a97 100755 --- a/server/src/middleware/v1/verifyJWT.js +++ b/server/src/middleware/verifyJWT.js @@ -1,7 +1,7 @@ import jwt from "jsonwebtoken"; -import ServiceRegistry from "../../service/v1/system/serviceRegistry.js"; -import SettingsService from "../../service/v1/system/settingsService.js"; -import StringService from "../../service/v1/system/stringService.js"; +import ServiceRegistry from "../service/system/serviceRegistry.js"; +import SettingsService from "../service/system/settingsService.js"; +import StringService from "../service/system/stringService.js"; const SERVICE_NAME = "verifyJWT"; const TOKEN_PREFIX = "Bearer "; diff --git a/server/src/middleware/v1/verifyOwnership.js b/server/src/middleware/verifyOwnership.js similarity index 88% rename from server/src/middleware/v1/verifyOwnership.js rename to server/src/middleware/verifyOwnership.js index 741fb6436..4431b0c26 100755 --- a/server/src/middleware/v1/verifyOwnership.js +++ b/server/src/middleware/verifyOwnership.js @@ -1,6 +1,6 @@ -import { logger } from "../../utils/logger.js"; -import ServiceRegistry from "../../service/v1/system/serviceRegistry.js"; -import StringService from "../../service/v1/system/stringService.js"; +import { logger } from "../utils/logger.js"; +import ServiceRegistry from "../service/system/serviceRegistry.js"; +import StringService from "../service/system/stringService.js"; import { ObjectId } from "mongodb"; const SERVICE_NAME = "verifyOwnership"; diff --git a/server/src/middleware/v1/verifySuperAdmin.js b/server/src/middleware/verifySuperAdmin.js similarity index 97% rename from server/src/middleware/v1/verifySuperAdmin.js rename to server/src/middleware/verifySuperAdmin.js index 41ac0a3e0..a5a93eede 100755 --- a/server/src/middleware/v1/verifySuperAdmin.js +++ b/server/src/middleware/verifySuperAdmin.js @@ -1,5 +1,5 @@ const jwt = require("jsonwebtoken"); -const logger = require("../../utils/logger.js"); +const logger = require("../utils/logger"); const SERVICE_NAME = "verifyAdmin"; const TOKEN_PREFIX = "Bearer "; import ServiceRegistry from "../service/serviceRegistry.js"; diff --git a/server/src/middleware/v1/verifyTeamAccess.js b/server/src/middleware/verifyTeamAccess.js similarity index 100% rename from server/src/middleware/v1/verifyTeamAccess.js rename to server/src/middleware/verifyTeamAccess.js diff --git a/server/src/routes/v1/authRoute.js b/server/src/routes/v1/authRoute.js index d9b036b05..f69f94f43 100755 --- a/server/src/routes/v1/authRoute.js +++ b/server/src/routes/v1/authRoute.js @@ -1,6 +1,6 @@ import { Router } from "express"; -import { verifyJWT } from "../../middleware/v1/verifyJWT.js"; -import { isAllowed } from "../../middleware/v1/isAllowed.js"; +import { verifyJWT } from "../../middleware/verifyJWT.js"; +import { isAllowed } from "../../middleware/isAllowed.js"; import multer from "multer"; const upload = multer(); diff --git a/server/src/routes/v1/checkRoute.js b/server/src/routes/v1/checkRoute.js index 78f6b8a42..673fe51e0 100755 --- a/server/src/routes/v1/checkRoute.js +++ b/server/src/routes/v1/checkRoute.js @@ -1,6 +1,6 @@ import { Router } from "express"; -import { isAllowed } from "../../middleware/v1/isAllowed.js"; +import { isAllowed } from "../../middleware/isAllowed.js"; class CheckRoutes { constructor(checkController) { diff --git a/server/src/routes/v1/diagnosticRoute.js b/server/src/routes/v1/diagnosticRoute.js index 2cf481e08..1b7cc06be 100755 --- a/server/src/routes/v1/diagnosticRoute.js +++ b/server/src/routes/v1/diagnosticRoute.js @@ -1,6 +1,6 @@ import { Router } from "express"; -import { verifyJWT } from "../../middleware/v1/verifyJWT.js"; -import { isAllowed } from "../../middleware/v1/isAllowed.js"; +import { verifyJWT } from "../../middleware/verifyJWT.js"; +import { isAllowed } from "../../middleware/isAllowed.js"; class DiagnosticRoutes { constructor(diagnosticController) { diff --git a/server/src/routes/v1/inviteRoute.js b/server/src/routes/v1/inviteRoute.js index a47550536..74fcfeb91 100755 --- a/server/src/routes/v1/inviteRoute.js +++ b/server/src/routes/v1/inviteRoute.js @@ -1,6 +1,6 @@ import { Router } from "express"; -import { verifyJWT } from "../../middleware/v1/verifyJWT.js"; -import { isAllowed } from "../../middleware/v1/isAllowed.js"; +import { verifyJWT } from "../../middleware/verifyJWT.js"; +import { isAllowed } from "../../middleware/isAllowed.js"; class InviteRoutes { constructor(inviteController) { diff --git a/server/src/routes/v1/logRoutes.js b/server/src/routes/v1/logRoutes.js index 43da8b75a..feabe669a 100755 --- a/server/src/routes/v1/logRoutes.js +++ b/server/src/routes/v1/logRoutes.js @@ -1,5 +1,5 @@ import { Router } from "express"; -import { isAllowed } from "../../middleware/v1/isAllowed.js"; +import { isAllowed } from "../../middleware/isAllowed.js"; class LogRoutes { constructor(logController) { this.router = Router(); diff --git a/server/src/routes/v1/monitorRoute.js b/server/src/routes/v1/monitorRoute.js index 107964f6b..dde72e256 100755 --- a/server/src/routes/v1/monitorRoute.js +++ b/server/src/routes/v1/monitorRoute.js @@ -1,5 +1,5 @@ import { Router } from "express"; -import { isAllowed } from "../../middleware/v1/isAllowed.js"; +import { isAllowed } from "../../middleware/isAllowed.js"; import multer from "multer"; import { fetchMonitorCertificate } from "../../controllers/v1/controllerUtils.js"; const upload = multer({ diff --git a/server/src/routes/v1/queueRoute.js b/server/src/routes/v1/queueRoute.js index c6cfa05da..59e1cb744 100755 --- a/server/src/routes/v1/queueRoute.js +++ b/server/src/routes/v1/queueRoute.js @@ -1,5 +1,5 @@ import { Router } from "express"; -import { isAllowed } from "../../middleware/v1/isAllowed.js"; +import { isAllowed } from "../../middleware/isAllowed.js"; class QueueRoutes { constructor(queueController) { this.router = Router(); diff --git a/server/src/routes/v1/settingsRoute.js b/server/src/routes/v1/settingsRoute.js index e88186af2..76556e923 100755 --- a/server/src/routes/v1/settingsRoute.js +++ b/server/src/routes/v1/settingsRoute.js @@ -1,5 +1,5 @@ import { Router } from "express"; -import { isAllowed } from "../../middleware/v1/isAllowed.js"; +import { isAllowed } from "../../middleware/isAllowed.js"; class SettingsRoutes { constructor(settingsController) { diff --git a/server/src/routes/v1/statusPageRoute.js b/server/src/routes/v1/statusPageRoute.js index 2c3b0b265..8300066a8 100755 --- a/server/src/routes/v1/statusPageRoute.js +++ b/server/src/routes/v1/statusPageRoute.js @@ -1,5 +1,5 @@ import { Router } from "express"; -import { verifyJWT } from "../../middleware/v1/verifyJWT.js"; +import { verifyJWT } from "../../middleware/verifyJWT.js"; import multer from "multer"; const upload = multer(); diff --git a/server/src/service/v1/business/checkService.js b/server/src/service/business/checkService.js similarity index 100% rename from server/src/service/v1/business/checkService.js rename to server/src/service/business/checkService.js diff --git a/server/src/service/v1/business/diagnosticService.js b/server/src/service/business/diagnosticService.js similarity index 100% rename from server/src/service/v1/business/diagnosticService.js rename to server/src/service/business/diagnosticService.js diff --git a/server/src/service/v1/business/inviteService.js b/server/src/service/business/inviteService.js similarity index 100% rename from server/src/service/v1/business/inviteService.js rename to server/src/service/business/inviteService.js diff --git a/server/src/service/v1/business/maintenanceWindowService.js b/server/src/service/business/maintenanceWindowService.js similarity index 100% rename from server/src/service/v1/business/maintenanceWindowService.js rename to server/src/service/business/maintenanceWindowService.js diff --git a/server/src/service/v1/business/monitorService.js b/server/src/service/business/monitorService.js similarity index 99% rename from server/src/service/v1/business/monitorService.js rename to server/src/service/business/monitorService.js index 061234102..637870b71 100644 --- a/server/src/service/v1/business/monitorService.js +++ b/server/src/service/business/monitorService.js @@ -1,4 +1,4 @@ -import { createMonitorsBodyValidation } from "../../../validation/joi.js"; +import { createMonitorsBodyValidation } from "../../validation/joi.js"; const SERVICE_NAME = "MonitorService"; class MonitorService { diff --git a/server/src/service/v1/business/userService.js b/server/src/service/business/userService.js similarity index 100% rename from server/src/service/v1/business/userService.js rename to server/src/service/business/userService.js diff --git a/server/src/service/v1/infrastructure/SuperSimpleQueue/SuperSimpleQueue.js b/server/src/service/infrastructure/SuperSimpleQueue/SuperSimpleQueue.js similarity index 100% rename from server/src/service/v1/infrastructure/SuperSimpleQueue/SuperSimpleQueue.js rename to server/src/service/infrastructure/SuperSimpleQueue/SuperSimpleQueue.js diff --git a/server/src/service/v1/infrastructure/SuperSimpleQueue/SuperSimpleQueueHelper.js b/server/src/service/infrastructure/SuperSimpleQueue/SuperSimpleQueueHelper.js similarity index 90% rename from server/src/service/v1/infrastructure/SuperSimpleQueue/SuperSimpleQueueHelper.js rename to server/src/service/infrastructure/SuperSimpleQueue/SuperSimpleQueueHelper.js index 2833a59f5..b348e2534 100644 --- a/server/src/service/v1/infrastructure/SuperSimpleQueue/SuperSimpleQueueHelper.js +++ b/server/src/service/infrastructure/SuperSimpleQueue/SuperSimpleQueueHelper.js @@ -5,11 +5,11 @@ class SuperSimpleQueueHelper { /** * @param {{ - * db: import("../database.js").Database, - * logger: import("../logger.js").Logger, - * networkService: import("../networkService.js").NetworkService, - * statusService: import("../statusService.js").StatusService, - * notificationService: import("../notificationService.js").NotificationService + * db: import("../database").Database, + * logger: import("../logger").Logger, + * networkService: import("../networkService").NetworkService, + * statusService: import("../statusService").StatusService, + * notificationService: import("../notificationService").NotificationService * }} */ constructor({ db, logger, networkService, statusService, notificationService }) { diff --git a/server/src/service/v1/infrastructure/bufferService.js b/server/src/service/infrastructure/bufferService.js similarity index 100% rename from server/src/service/v1/infrastructure/bufferService.js rename to server/src/service/infrastructure/bufferService.js diff --git a/server/src/service/v1/infrastructure/emailService.js b/server/src/service/infrastructure/emailService.js similarity index 100% rename from server/src/service/v1/infrastructure/emailService.js rename to server/src/service/infrastructure/emailService.js diff --git a/server/src/service/v1/infrastructure/errorService.js b/server/src/service/infrastructure/errorService.js similarity index 100% rename from server/src/service/v1/infrastructure/errorService.js rename to server/src/service/infrastructure/errorService.js diff --git a/server/src/service/v1/infrastructure/networkService.js b/server/src/service/infrastructure/networkService.js similarity index 100% rename from server/src/service/v1/infrastructure/networkService.js rename to server/src/service/infrastructure/networkService.js diff --git a/server/src/service/v1/infrastructure/notificationService.js b/server/src/service/infrastructure/notificationService.js similarity index 100% rename from server/src/service/v1/infrastructure/notificationService.js rename to server/src/service/infrastructure/notificationService.js diff --git a/server/src/service/v1/infrastructure/notificationUtils.js b/server/src/service/infrastructure/notificationUtils.js similarity index 100% rename from server/src/service/v1/infrastructure/notificationUtils.js rename to server/src/service/infrastructure/notificationUtils.js diff --git a/server/src/service/v1/infrastructure/statusService.js b/server/src/service/infrastructure/statusService.js similarity index 99% rename from server/src/service/v1/infrastructure/statusService.js rename to server/src/service/infrastructure/statusService.js index 983541cd9..72fa359d7 100755 --- a/server/src/service/v1/infrastructure/statusService.js +++ b/server/src/service/infrastructure/statusService.js @@ -1,4 +1,4 @@ -import MonitorStats from "../../../db/models/MonitorStats.js"; +import MonitorStats from "../../db/models/MonitorStats.js"; const SERVICE_NAME = "StatusService"; class StatusService { diff --git a/server/src/service/v1/system/serviceRegistry.js b/server/src/service/system/serviceRegistry.js similarity index 100% rename from server/src/service/v1/system/serviceRegistry.js rename to server/src/service/system/serviceRegistry.js diff --git a/server/src/service/v1/system/settingsService.js b/server/src/service/system/settingsService.js similarity index 100% rename from server/src/service/v1/system/settingsService.js rename to server/src/service/system/settingsService.js diff --git a/server/src/service/v1/system/stringService.js b/server/src/service/system/stringService.js similarity index 100% rename from server/src/service/v1/system/stringService.js rename to server/src/service/system/stringService.js diff --git a/server/src/service/v1/system/translationService.js b/server/src/service/system/translationService.js similarity index 100% rename from server/src/service/v1/system/translationService.js rename to server/src/service/system/translationService.js diff --git a/server/src/service/v2/business/AuthService.ts b/server/src/service/v2/business/AuthService.ts index 52f586dbf..ae22b981d 100644 --- a/server/src/service/v2/business/AuthService.ts +++ b/server/src/service/v2/business/AuthService.ts @@ -3,45 +3,6 @@ import { User, Role, ITokenizedUser } from "../../../db/models/index.js"; import ApiError from "../../../utils/ApiError.js"; import { Types } from "mongoose"; -export const PERMISSIONS = { - users: { - all: "users.*", - create: "users.create", - view: "users.view", - update: "users.update", - delete: "users.delete", - }, - - monitors: { - all: "monitors.*", - create: "monitors.create", - view: "monitors.view", - update: "monitors.update", - delete: "monitors.delete", - }, - notifications: { - all: "notifications.*", - create: "notifications.create", - view: "notifications.view", - update: "notifications.update", - delete: "notifications.delete", - }, - checks: { - all: "checks.*", - create: "checks.create", - view: "checks.view", - update: "checks.update", - delete: "checks.delete", - }, - statusPages: { - all: "statusPages.*", - create: "statusPages.create", - view: "statusPages.view", - update: "statusPages.update", - delete: "statusPages.delete", - }, -}; - export const DEFAULT_ROLES = [ { name: "SuperAdmin", @@ -52,19 +13,19 @@ export const DEFAULT_ROLES = [ { name: "Admin", description: "Admin with full permissions", - permissions: [PERMISSIONS.monitors.all, PERMISSIONS.users.all], + permissions: ["monitor.*", "users.*"], isSystem: true, }, { name: "Manager", description: "Can manage users", - permissions: [PERMISSIONS.users.create, PERMISSIONS.users.update, PERMISSIONS.monitors.all], + permissions: ["users.create", "users.update", "monitors.*"], isSystem: true, }, { name: "Member", description: "Basic team member", - permissions: [PERMISSIONS.users.update, PERMISSIONS.monitors.create, PERMISSIONS.monitors.view, PERMISSIONS.monitors.update], + permissions: ["users.update", "monitors.create", "monitors.view", "monitors.update"], isSystem: true, }, ]; @@ -165,7 +126,7 @@ class AuthService implements IAuthService { } // Check password - const isPasswordValid = await bcrypt.compare(password, user.password); + const isPasswordValid = await bcrypt.compare(password, user.passwordHash); if (!isPasswordValid) { throw new Error("Invalid email or password"); } diff --git a/server/src/types/express.d.ts b/server/src/types/express.d.ts deleted file mode 100644 index 7c78a053c..000000000 --- a/server/src/types/express.d.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { ITokenizedUser } from "../db/models/index.ts"; - -declare global { - namespace Express { - interface Request { - user?: ITokenizedUser; - resource?: any; - } - } -} From 990aed9f2e01c459135d6de835b084d452fe4abe Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Tue, 23 Sep 2025 14:05:23 -0700 Subject: [PATCH 023/101] Revert "Merge pull request #2973 from bluewave-labs/feat/v2/auth" This reverts commit 72882fded9b57b1d0f76a521e12afea9f63135ce, reversing changes made to bfb299cfcaa4dcfaff68daed9f4629bc8fcce554. --- server/nodemon.json | 4 +- server/package-lock.json | 117 --------------- server/package.json | 4 +- server/src/config/controllers.js | 6 - server/src/config/routes.js | 10 -- server/src/config/services.js | 15 +- server/src/controllers/v2/AuthController.ts | 142 ------------------ server/src/db/models/InviteToken.js | 35 +++++ .../src/db/models/{auth/User.ts => User.js} | 80 ++-------- server/src/db/models/auth/Invite.ts | 68 --------- server/src/db/models/auth/Role.ts | 45 ------ server/src/db/models/index.ts | 9 -- server/src/db/models/migration/Migration.ts | 34 ----- ...s => 0001_migrateStatusWindowThreshold.js} | 1 - .../db/mongo/migration/0002_migrateUsers.ts | 56 ------- server/src/db/mongo/migration/index.js | 7 + server/src/db/mongo/migration/index.ts | 25 --- server/src/db/mongo/modules/inviteModule.js | 12 +- server/src/middleware/v2/VerifyToken.ts | 21 --- server/src/middleware/verifyTeamAccess.js | 2 - server/src/routes/v2/auth.ts | 31 ---- server/src/service/v2/business/AuthService.ts | 141 ----------------- .../src/service/v2/business/InviteService.ts | 61 -------- server/src/utils/ApiError.ts | 15 -- server/src/utils/JWTUtils.ts | 22 --- 25 files changed, 69 insertions(+), 894 deletions(-) delete mode 100644 server/src/controllers/v2/AuthController.ts create mode 100755 server/src/db/models/InviteToken.js rename server/src/db/models/{auth/User.ts => User.js} (55%) delete mode 100755 server/src/db/models/auth/Invite.ts delete mode 100644 server/src/db/models/auth/Role.ts delete mode 100644 server/src/db/models/index.ts delete mode 100644 server/src/db/models/migration/Migration.ts rename server/src/db/mongo/migration/{0001_migrateStatusWindowThreshold.ts => 0001_migrateStatusWindowThreshold.js} (90%) delete mode 100644 server/src/db/mongo/migration/0002_migrateUsers.ts create mode 100644 server/src/db/mongo/migration/index.js delete mode 100644 server/src/db/mongo/migration/index.ts delete mode 100644 server/src/middleware/v2/VerifyToken.ts delete mode 100644 server/src/routes/v2/auth.ts delete mode 100644 server/src/service/v2/business/AuthService.ts delete mode 100644 server/src/service/v2/business/InviteService.ts delete mode 100644 server/src/utils/ApiError.ts delete mode 100644 server/src/utils/JWTUtils.ts diff --git a/server/nodemon.json b/server/nodemon.json index 7371b4904..88d6ac686 100755 --- a/server/nodemon.json +++ b/server/nodemon.json @@ -1,5 +1,5 @@ { "ignore": ["src/locales/*", "*.log", "node_modules/*"], - "watch": ["src/**/*.js", "src/**/*.ts", "*.json"], - "ext": "ts,js,json" + "watch": ["src/**/*.js", "*.json"], + "ext": "js,json" } diff --git a/server/package-lock.json b/server/package-lock.json index e21b28382..06c1c30b3 100644 --- a/server/package-lock.json +++ b/server/package-lock.json @@ -45,8 +45,6 @@ }, "devDependencies": { "@eslint/js": "^9.17.0", - "@types/express": "5.0.3", - "@types/jsonwebtoken": "9.0.10", "c8": "10.1.3", "chai": "5.2.0", "eslint": "^9.17.0", @@ -1680,27 +1678,6 @@ "node": ">=10.13.0" } }, - "node_modules/@types/body-parser": { - "version": "1.19.6", - "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz", - "integrity": "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/connect": "*", - "@types/node": "*" - } - }, - "node_modules/@types/connect": { - "version": "3.4.38", - "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", - "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, "node_modules/@types/estree": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", @@ -1708,44 +1685,12 @@ "dev": true, "license": "MIT" }, - "node_modules/@types/express": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/@types/express/-/express-5.0.3.tgz", - "integrity": "sha512-wGA0NX93b19/dZC1J18tKWVIYWyyF2ZjT9vin/NRu0qzzvfVzWjs04iq2rQ3H65vCTQYlRqs3YHfY7zjdV+9Kw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/body-parser": "*", - "@types/express-serve-static-core": "^5.0.0", - "@types/serve-static": "*" - } - }, - "node_modules/@types/express-serve-static-core": { - "version": "5.0.7", - "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.0.7.tgz", - "integrity": "sha512-R+33OsgWw7rOhD1emjU7dzCDHucJrgJXMA5PYCzJxVil0dsyx5iBEPHqpPfiKNJQb7lZ1vxwoLR4Z87bBUpeGQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*", - "@types/qs": "*", - "@types/range-parser": "*", - "@types/send": "*" - } - }, "node_modules/@types/http-cache-semantics": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.4.tgz", "integrity": "sha512-1m0bIFVc7eJWyve9S0RnuRgcQqF/Xd5QsUZAZeQFr1Q3/p9JWoQQEqmVy+DPTNpGXwhgIetAoYF8JSc33q29QA==", "license": "MIT" }, - "node_modules/@types/http-errors": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.5.tgz", - "integrity": "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==", - "dev": true, - "license": "MIT" - }, "node_modules/@types/istanbul-lib-coverage": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", @@ -1760,31 +1705,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@types/jsonwebtoken": { - "version": "9.0.10", - "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.10.tgz", - "integrity": "sha512-asx5hIG9Qmf/1oStypjanR7iKTv0gXQ1Ov/jfrX6kS/EO0OFni8orbmGCn0672NHR3kXHwpAwR+B368ZGN/2rA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/ms": "*", - "@types/node": "*" - } - }, - "node_modules/@types/mime": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", - "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/ms": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", - "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", - "dev": true, - "license": "MIT" - }, "node_modules/@types/node": { "version": "24.3.0", "resolved": "https://registry.npmjs.org/@types/node/-/node-24.3.0.tgz", @@ -1794,49 +1714,12 @@ "undici-types": "~7.10.0" } }, - "node_modules/@types/qs": { - "version": "6.14.0", - "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.14.0.tgz", - "integrity": "sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/range-parser": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", - "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", - "dev": true, - "license": "MIT" - }, "node_modules/@types/relateurl": { "version": "0.2.33", "resolved": "https://registry.npmjs.org/@types/relateurl/-/relateurl-0.2.33.tgz", "integrity": "sha512-bTQCKsVbIdzLqZhLkF5fcJQreE4y1ro4DIyVrlDNSCJRRwHhB8Z+4zXXa8jN6eDvc2HbRsEYgbvrnGvi54EpSw==", "license": "MIT" }, - "node_modules/@types/send": { - "version": "0.17.5", - "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.5.tgz", - "integrity": "sha512-z6F2D3cOStZvuk2SaP6YrwkNO65iTZcwA2ZkSABegdkAh/lf+Aa/YQndZVfmEXT5vgAp6zv06VQ3ejSVjAny4w==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/mime": "^1", - "@types/node": "*" - } - }, - "node_modules/@types/serve-static": { - "version": "1.15.8", - "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.8.tgz", - "integrity": "sha512-roei0UY3LhpOJvjbIP6ZZFngyLKl5dskOtDhxY5THRSpO+ZI+nzJ+m5yUMzGrp89YRa7lvknKkMYjqQFGwA7Sg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/http-errors": "*", - "@types/node": "*", - "@types/send": "*" - } - }, "node_modules/@types/triple-beam": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/@types/triple-beam/-/triple-beam-1.3.5.tgz", diff --git a/server/package.json b/server/package.json index 5c655af9d..637c785cc 100755 --- a/server/package.json +++ b/server/package.json @@ -2,7 +2,7 @@ "name": "server", "version": "1.0.0", "description": "", - "main": "dist/index.js", + "main": "index.js", "type": "module", "scripts": { "test": "c8 mocha", @@ -54,8 +54,6 @@ }, "devDependencies": { "@eslint/js": "^9.17.0", - "@types/express": "5.0.3", - "@types/jsonwebtoken": "9.0.10", "c8": "10.1.3", "chai": "5.2.0", "eslint": "^9.17.0", diff --git a/server/src/config/controllers.js b/server/src/config/controllers.js index 250d04c4b..5c54579eb 100644 --- a/server/src/config/controllers.js +++ b/server/src/config/controllers.js @@ -15,9 +15,6 @@ import StatusPageController from "../controllers/v1/statusPageController.js"; import NotificationController from "../controllers/v1/notificationController.js"; import DiagnosticController from "../controllers/v1/diagnosticController.js"; -// V2 -import AuthV2Controller from "../controllers/v2/AuthController.js"; - export const initializeControllers = (services) => { const controllers = {}; const commonDependencies = createCommonDependencies(services.db, services.errorService, services.logger, services.stringService); @@ -65,8 +62,5 @@ export const initializeControllers = (services) => { diagnosticService: services.diagnosticService, }); - // V2 - controllers.authV2Controller = new AuthV2Controller(services.authV2Service, services.inviteV2Service); - return controllers; }; diff --git a/server/src/config/routes.js b/server/src/config/routes.js index 5ef622f37..2ec93a2dc 100644 --- a/server/src/config/routes.js +++ b/server/src/config/routes.js @@ -13,8 +13,6 @@ import LogRoutes from "../routes/v1/logRoutes.js"; import DiagnosticRoutes from "../routes/v1//diagnosticRoute.js"; import NotificationRoutes from "../routes/v1/notificationRoute.js"; -import authV2 from "../routes/v2/auth.js"; - export const setupRoutes = (app, controllers) => { const authRoutes = new AuthRoutes(controllers.authController); const monitorRoutes = new MonitorRoutes(controllers.monitorController); @@ -39,12 +37,4 @@ export const setupRoutes = (app, controllers) => { app.use("/api/v1/status-page", statusPageRoutes.getRouter()); app.use("/api/v1/notifications", verifyJWT, notificationRoutes.getRouter()); app.use("/api/v1/diagnostic", verifyJWT, diagnosticRoutes.getRouter()); - - // ******************* - // V2 Routes - // ******************* - - const v2AuthRoutes = new authV2(controllers.authV2Controller); - - app.use("/api/v2/auth", authApiLimiter, v2AuthRoutes.getRouter()); }; diff --git a/server/src/config/services.js b/server/src/config/services.js index b2b4ac430..08c2525f3 100644 --- a/server/src/config/services.js +++ b/server/src/config/services.js @@ -48,8 +48,8 @@ import { ParseBoolean } from "../utils/utils.js"; // Models import Check from "../db/models/Check.js"; import Monitor from "../db/models/Monitor.js"; -import { User } from "../db/models/auth/User.js"; -import { Invite } from "../db/models/auth/Invite.js"; +import User from "../db/models/User.js"; +import InviteToken from "../db/models/InviteToken.js"; import StatusPage from "../db/models/StatusPage.js"; import Team from "../db/models/Team.js"; import MaintenanceWindow from "../db/models/MaintenanceWindow.js"; @@ -68,10 +68,6 @@ import NotificationModule from "../db/mongo/modules/notificationModule.js"; import RecoveryModule from "../db/mongo/modules/recoveryModule.js"; import SettingsModule from "../db/mongo/modules/settingsModule.js"; -// v2 -import AuthV2Service from "../service/v2/business/AuthService.js"; -import InviteV2Service from "../service/v2/business/InviteService.js"; - export const initializeServices = async ({ logger, envSettings, settingsService }) => { const serviceRegistry = new ServiceRegistry({ logger }); ServiceRegistry.instance = serviceRegistry; @@ -83,7 +79,7 @@ export const initializeServices = async ({ logger, envSettings, settingsService // Create DB const checkModule = new CheckModule({ logger, Check, Monitor, User }); - const inviteModule = new InviteModule({ Invite, crypto, stringService }); + const inviteModule = new InviteModule({ InviteToken, crypto, stringService }); const statusPageModule = new StatusPageModule({ StatusPage, NormalizeData, stringService }); const userModule = new UserModule({ User, Team, GenerateAvatarImage, ParseBoolean, stringService }); const maintenanceWindowModule = new MaintenanceWindowModule({ MaintenanceWindow }); @@ -212,9 +208,6 @@ export const initializeServices = async ({ logger, envSettings, settingsService games, }); - const authV2Service = new AuthV2Service(); - const inviteV2Service = new InviteV2Service(); - const services = { settingsService, translationService, @@ -234,8 +227,6 @@ export const initializeServices = async ({ logger, envSettings, settingsService monitorService, errorService, logger, - authV2Service, - inviteV2Service, }; Object.values(services).forEach((service) => { diff --git a/server/src/controllers/v2/AuthController.ts b/server/src/controllers/v2/AuthController.ts deleted file mode 100644 index a9d1dd203..000000000 --- a/server/src/controllers/v2/AuthController.ts +++ /dev/null @@ -1,142 +0,0 @@ -import { Request, Response, NextFunction } from "express"; -import { encode, decode } from "../../utils/JWTUtils.js"; -import AuthService from "../../service/v2/business/AuthService.js"; -import ApiError from "../../utils/ApiError.js"; -import InviteService from "../../service/v2/business/InviteService.js"; -import { IInvite } from "../../db/models/index.js"; - -export interface IAuthController { - register(req: Request, res: Response, next: NextFunction): Promise; - registerWithInvite(req: Request, res: Response, next: NextFunction): Promise; - login(req: Request, res: Response, next: NextFunction): Promise; - logout(req: Request, res: Response): void; - me(req: Request, res: Response, next: NextFunction): void; -} - -class AuthController implements IAuthController { - private authService: AuthService; - private inviteService: InviteService; - constructor(authService: AuthService, inviteService: InviteService) { - this.authService = authService; - this.inviteService = inviteService; - } - - register = async (req: Request, res: Response, next: NextFunction) => { - try { - const { email, firstName, lastName, password } = req.body; - - if (!email || !firstName || !lastName || !password) { - throw new Error("Email, firstName, lastName, and password are required"); - } - - const result = await this.authService.register({ - email, - firstName, - lastName, - password, - }); - - const token = encode(result); - - res.cookie("token", token, { - httpOnly: true, - secure: process.env.NODE_ENV === "production", - sameSite: "strict", - maxAge: 7 * 24 * 60 * 60 * 1000, // 1 week - }); - - res.status(201).json({ - message: "User created successfully", - }); - } catch (error) { - next(error); - } - }; - - registerWithInvite = async (req: Request, res: Response, next: NextFunction) => { - try { - const token = req.params.token; - if (!token) { - throw new ApiError("Invite token is required", 400); - } - - const invite: IInvite = await this.inviteService.get(token); - - const { firstName, lastName, password } = req.body; - const email = invite?.email; - const roles = invite?.roles; - - if (!email || !firstName || !lastName || !password || !roles || roles.length === 0) { - throw new Error("Email, firstName, lastName, password, and roles are required"); - } - - const result = await this.authService.registerWithInvite({ - email, - firstName, - lastName, - password, - roles, - }); - - if (!result) { - throw new Error("Registration failed"); - } - - await this.inviteService.delete(invite._id.toString()); - - const jwt = encode(result); - - res.cookie("token", jwt, { - httpOnly: true, - secure: process.env.NODE_ENV === "production", - sameSite: "strict", - maxAge: 7 * 24 * 60 * 60 * 1000, // 1 week - }); - - res.status(201).json({ message: "User created successfully" }); - } catch (error) { - next(error); - } - }; - - login = async (req: Request, res: Response, next: NextFunction) => { - try { - const { email, password } = req.body; - // Validation - if (!email || !password) { - throw new ApiError("Email and password are required", 400); - } - const result = await this.authService.login({ email, password }); - - const token = encode(result); - - res.cookie("token", token, { - httpOnly: true, - secure: process.env.NODE_ENV === "production", - sameSite: "strict", - maxAge: 7 * 24 * 60 * 60 * 1000, // 1 week - }); - - res.status(200).json({ - message: "Login successful", - }); - } catch (error) { - next(error); - } - }; - - logout = (req: Request, res: Response) => { - res.clearCookie("token", { - httpOnly: true, - secure: process.env.NODE_ENV === "production", - sameSite: "strict", - }); - res.status(200).json({ message: "Logout successful" }); - }; - - me = (req: Request, res: Response, next: NextFunction) => { - return res.status(200).json({ message: "OK" }); - }; -} - -export default AuthController; diff --git a/server/src/db/models/InviteToken.js b/server/src/db/models/InviteToken.js new file mode 100755 index 000000000..079600647 --- /dev/null +++ b/server/src/db/models/InviteToken.js @@ -0,0 +1,35 @@ +import mongoose from "mongoose"; +const InviteTokenSchema = mongoose.Schema( + { + email: { + type: String, + required: true, + unique: true, + }, + teamId: { + type: mongoose.Schema.Types.ObjectId, + ref: "Team", + immutable: true, + required: true, + }, + role: { + type: Array, + required: true, + default: ["user"], + }, + token: { + type: String, + required: true, + }, + expiry: { + type: Date, + default: Date.now, + expires: 3600, + }, + }, + { + timestamps: true, + } +); + +export default mongoose.model("InviteToken", InviteTokenSchema); diff --git a/server/src/db/models/auth/User.ts b/server/src/db/models/User.js similarity index 55% rename from server/src/db/models/auth/User.ts rename to server/src/db/models/User.js index 82c058ea2..9368d98b1 100755 --- a/server/src/db/models/auth/User.ts +++ b/server/src/db/models/User.js @@ -1,47 +1,11 @@ -import mongoose, { Schema, Document, Types } from "mongoose"; - +import mongoose from "mongoose"; import bcrypt from "bcryptjs"; -import logger from "../../../utils/logger.js"; -import Monitor from "../Monitor.js"; -import Team from "../Team.js"; -import Notification from "../Notification.js"; -import { subscribe } from "diagnostics_channel"; +import logger from "../../utils/logger.js"; +import Monitor from "./Monitor.js"; +import Team from "./Team.js"; +import Notification from "./Notification.js"; -export const RoleTypes = ["user", "admin", "superadmin", "demo"] as const; -export type RoleType = (typeof RoleTypes)[number]; - -export interface ITokenizedUser { - sub: string; - roles: string[]; -} - -export interface IUser extends Document { - // V1 - _id: Types.ObjectId; - firstName: string; - lastName: string; - email: string; - password: string; - avatarImage: string; - profileImage: { - data: Buffer; - contentType: string; - }; - isActive: boolean; - isVerified: boolean; - role?: RoleType[]; - teamId: Types.ObjectId; - checkTTL: number; - createdAt: Date; - updatedAt: Date; - comparePassword: (submittedPassword: string) => Promise; - // V2 - roles: Types.ObjectId[]; - lastLoginAt?: Date; - version: number; -} - -const UserSchema = new Schema( +const UserSchema = mongoose.Schema( { firstName: { type: String, @@ -58,7 +22,7 @@ const UserSchema = new Schema( }, password: { type: String, - required: false, + required: true, }, avatarImage: { type: String, @@ -77,8 +41,8 @@ const UserSchema = new Schema( }, role: { type: [String], - default: ["user"], - enum: RoleTypes, + default: "user", + enum: ["user", "admin", "superadmin", "demo"], }, teamId: { type: mongoose.Schema.Types.ObjectId, @@ -88,22 +52,6 @@ const UserSchema = new Schema( checkTTL: { type: Number, }, - - // v2 - roles: [ - { - type: Schema.Types.ObjectId, - ref: "Role", - default: [], - }, - ], - lastLoginAt: { - type: Date, - }, - version: { - type: Number, - default: 1, - }, }, { timestamps: true, @@ -121,7 +69,7 @@ UserSchema.pre("save", function (next) { UserSchema.pre("findOneAndUpdate", function (next) { const update = this.getUpdate(); - if (update && "password" in update) { + if ("password" in update) { const salt = bcrypt.genSaltSync(10); update.password = bcrypt.hashSync(update.password, salt); } @@ -144,13 +92,15 @@ UserSchema.pre("findOneAndDelete", async function (next) { } next(); } catch (error) { - next(error as Error); + next(error); } }); -UserSchema.methods.comparePassword = async function (submittedPassword: string) { +UserSchema.methods.comparePassword = async function (submittedPassword) { const res = await bcrypt.compare(submittedPassword, this.password); return res; }; -export const User = mongoose.model("User", UserSchema); +const User = mongoose.model("User", UserSchema); + +export default User; diff --git a/server/src/db/models/auth/Invite.ts b/server/src/db/models/auth/Invite.ts deleted file mode 100755 index 148ff65cb..000000000 --- a/server/src/db/models/auth/Invite.ts +++ /dev/null @@ -1,68 +0,0 @@ -import mongoose, { Schema, Document, Types } from "mongoose"; - -export interface IInvite extends Document { - // v1 - _id: Types.ObjectId; - email: string; - teamId: Types.ObjectId; - role: string[]; - token: string; - expiry: Date; - createdAt: Date; - updatedAt: Date; - - // v2 - tokenHash: string; - roles: Types.ObjectId[]; - createdBy: Types.ObjectId; - updatedBy: Types.ObjectId; -} - -const InviteSchema = new Schema( - { - email: { - type: String, - required: true, - unique: true, - trim: true, - lowercase: true, - }, - teamId: { - type: mongoose.Schema.Types.ObjectId, - ref: "Team", - immutable: true, - required: true, - }, - role: { - type: [String], - required: true, - default: ["user"], - }, - token: { - type: String, - required: true, - }, - expiry: { - type: Date, - default: Date.now, - expires: 3600, - }, - - // v2 - roles: [ - { - type: Schema.Types.ObjectId, - ref: "Role", - required: true, - }, - ], - tokenHash: { type: String, required: true, unique: true }, - createdBy: { type: Schema.Types.ObjectId, ref: "User", required: true }, - updatedBy: { type: Schema.Types.ObjectId, ref: "User", required: true }, - }, - { - timestamps: true, - } -); - -export const Invite = mongoose.model("Invite", InviteSchema); diff --git a/server/src/db/models/auth/Role.ts b/server/src/db/models/auth/Role.ts deleted file mode 100644 index e7ffb2f7d..000000000 --- a/server/src/db/models/auth/Role.ts +++ /dev/null @@ -1,45 +0,0 @@ -import mongoose, { Schema, Document, Types } from "mongoose"; - -export interface IRole extends Document { - _id: Types.ObjectId; - name: string; - description?: string; - permissions: string[]; - isActive: boolean; - createdAt: Date; - updatedAt: Date; -} - -const roleSchema = new Schema( - { - name: { - type: String, - required: true, - trim: true, - maxlength: 50, - }, - description: { - type: String, - trim: true, - maxlength: 200, - }, - - permissions: [ - { - type: String, - required: true, - }, - ], - isActive: { - type: Boolean, - default: true, - }, - }, - { - timestamps: true, - } -); - -roleSchema.index({ name: 1 }, { unique: true }); - -export const Role = mongoose.model("Role", roleSchema); diff --git a/server/src/db/models/index.ts b/server/src/db/models/index.ts deleted file mode 100644 index 537633bfd..000000000 --- a/server/src/db/models/index.ts +++ /dev/null @@ -1,9 +0,0 @@ -export { User } from "./auth/User.js"; -export type { IUser } from "./auth/User.js"; -export type { ITokenizedUser } from "./auth/User.js"; -export { Role } from "./auth/Role.js"; -export type { IRole } from "./auth/Role.js"; -export type { IInvite } from "./auth/Invite.js"; -export { Invite } from "./auth/Invite.js"; -export { Migration } from "./migration/Migration.js"; -export type { IMigration } from "./migration/Migration.js"; diff --git a/server/src/db/models/migration/Migration.ts b/server/src/db/models/migration/Migration.ts deleted file mode 100644 index cf76bb48e..000000000 --- a/server/src/db/models/migration/Migration.ts +++ /dev/null @@ -1,34 +0,0 @@ -import mongoose, { Schema, Document, Types } from "mongoose"; - -export interface IMigration extends Document { - _id: Types.ObjectId; - name: string; - runAt: Date; - success: boolean; - createdAt: Date; - updatedAt: Date; -} - -const migrationSchema = new Schema( - { - name: { - type: String, - required: true, - }, - runAt: { - type: Date, - required: false, - }, - success: { - type: Boolean, - required: false, - }, - }, - { - timestamps: true, - } -); - -migrationSchema.index({ name: 1 }, { unique: true }); - -export const Migration = mongoose.model("Migration", migrationSchema); diff --git a/server/src/db/mongo/migration/0001_migrateStatusWindowThreshold.ts b/server/src/db/mongo/migration/0001_migrateStatusWindowThreshold.js similarity index 90% rename from server/src/db/mongo/migration/0001_migrateStatusWindowThreshold.ts rename to server/src/db/mongo/migration/0001_migrateStatusWindowThreshold.js index b44487e87..8f6988de4 100644 --- a/server/src/db/mongo/migration/0001_migrateStatusWindowThreshold.ts +++ b/server/src/db/mongo/migration/0001_migrateStatusWindowThreshold.js @@ -1,5 +1,4 @@ import Monitor from "../../models/Monitor.js"; -export const MIGRATION_NAME = "0001_migrateStatusWindowThreshold"; async function migrateStatusWindowThreshold() { try { const monitors = await Monitor.find({ statusWindowThreshold: { $lt: 1 } }); diff --git a/server/src/db/mongo/migration/0002_migrateUsers.ts b/server/src/db/mongo/migration/0002_migrateUsers.ts deleted file mode 100644 index a7cdb76fc..000000000 --- a/server/src/db/mongo/migration/0002_migrateUsers.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { User, Role } from "../../models/index.js"; -import { Types } from "mongoose"; - -import { DEFAULT_ROLES } from "../../../service/v2/business/AuthService.js"; -export const MIGRATION_NAME = "0002_migrateUsers"; -async function migrateUsers() { - try { - // Create roles if they don't exist - const roleCount = await Role.countDocuments(); - let roles; - if (roleCount === 0) { - const rolePromises = DEFAULT_ROLES.map((roleData) => - new Role({ - ...roleData, - }).save() - ); - roles = await Promise.all(rolePromises); - } else { - roles = await Role.find(); - } - - // Migrate users - const users = await User.find({ - $or: [{ version: { $exists: false } }, { version: { $lt: 2 } }], - }); - for (const user of users) { - const newRoleIds: Types.ObjectId[] = []; - - for (const role of user.role || []) { - if (role === "superadmin") { - const superAdminRole = roles.find((role) => role.name === "SuperAdmin"); - newRoleIds.push(superAdminRole!._id); - } - if (role === "admin") { - const managerRole = roles.find((role) => role.name === "Manager"); - newRoleIds.push(managerRole!._id); - } - if (role === "user") { - const memberRole = roles.find((role) => role.name === "Member"); - newRoleIds.push(memberRole!._id); - } - } - const merged = [...new Set([...user.roles, ...newRoleIds])]; - user.roles = merged; - user.version = 2; - await user.save(); - } - - return true; - } catch (err) { - console.error("Migration error:", err); - return false; - } -} - -export { migrateUsers }; diff --git a/server/src/db/mongo/migration/index.js b/server/src/db/mongo/migration/index.js new file mode 100644 index 000000000..4bf56895a --- /dev/null +++ b/server/src/db/mongo/migration/index.js @@ -0,0 +1,7 @@ +import { migrateStatusWindowThreshold } from "./0001_migrateStatusWindowThreshold.js"; + +const runMigrations = async () => { + await migrateStatusWindowThreshold(); +}; + +export { runMigrations }; diff --git a/server/src/db/mongo/migration/index.ts b/server/src/db/mongo/migration/index.ts deleted file mode 100644 index 293e0905f..000000000 --- a/server/src/db/mongo/migration/index.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { migrateStatusWindowThreshold, MIGRATION_NAME as MIGRATION_0001 } from "./0001_migrateStatusWindowThreshold.js"; -import { migrateUsers, MIGRATION_NAME as MIGRATION_0002 } from "./0002_migrateUsers.js"; -import { Migration, IMigration } from "../../models/index.js"; - -const runMigrations = async () => { - const migrations = [ - { name: MIGRATION_0001, migration: migrateStatusWindowThreshold }, - { name: MIGRATION_0002, migration: migrateUsers }, - ]; - - const migrationNames = migrations.map((m) => m.name); - - const appliedMigrations = await Migration.find({ name: { $in: migrationNames }, success: true }); - - const migrationsToRun = migrations.filter((m) => !appliedMigrations.some((am: IMigration) => am.name === m.name)); - - for (const { name, migration } of migrationsToRun) { - console.log(`Running migration: ${name}`); - const success = await migration(); - await Migration.updateOne({ name }, { $set: { runAt: new Date(), success } }, { upsert: true }); - console.log(`Migration ${name} completed with status: ${success ? "success" : "failure"}`); - } -}; - -export { runMigrations }; diff --git a/server/src/db/mongo/modules/inviteModule.js b/server/src/db/mongo/modules/inviteModule.js index 513524fe5..444e51efd 100755 --- a/server/src/db/mongo/modules/inviteModule.js +++ b/server/src/db/mongo/modules/inviteModule.js @@ -1,17 +1,17 @@ const SERVICE_NAME = "inviteModule"; class InviteModule { - constructor({ Invite, crypto, stringService }) { - this.Invite = Invite; + constructor({ InviteToken, crypto, stringService }) { + this.InviteToken = InviteToken; this.crypto = crypto; this.stringService = stringService; } requestInviteToken = async (userData) => { try { - await this.Invite.deleteMany({ email: userData.email }); + await this.InviteToken.deleteMany({ email: userData.email }); userData.token = this.crypto.randomBytes(32).toString("hex"); - let inviteToken = new this.Invite(userData); + let inviteToken = new this.InviteToken(userData); await inviteToken.save(); return inviteToken; } catch (error) { @@ -23,7 +23,7 @@ class InviteModule { getInviteToken = async (token) => { try { - const invite = await this.Invite.findOne({ + const invite = await this.InviteToken.findOne({ token, }); if (invite === null) { @@ -38,7 +38,7 @@ class InviteModule { }; getInviteTokenAndDelete = async (token) => { try { - const invite = await this.Invite.findOneAndDelete({ + const invite = await this.InviteToken.findOneAndDelete({ token, }); if (invite === null) { diff --git a/server/src/middleware/v2/VerifyToken.ts b/server/src/middleware/v2/VerifyToken.ts deleted file mode 100644 index 7dabd51cc..000000000 --- a/server/src/middleware/v2/VerifyToken.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { Request, Response, NextFunction } from "express"; -import { decode } from "../../utils/JWTUtils.js"; -import ApiError from "../../utils/ApiError.js"; -const verifyToken = (req: Request, res: Response, next: NextFunction) => { - const token = req.cookies.token; - - if (!token) { - const error = new ApiError("No token provided", 401); - return next(error); - } - - try { - const decoded = decode(token); - req.user = decoded; - next(); - } catch (error) { - next(error); - } -}; - -export { verifyToken }; diff --git a/server/src/middleware/verifyTeamAccess.js b/server/src/middleware/verifyTeamAccess.js index a548cbf23..60647bdc2 100644 --- a/server/src/middleware/verifyTeamAccess.js +++ b/server/src/middleware/verifyTeamAccess.js @@ -3,8 +3,6 @@ const SERVICE_NAME = "verifyTeamAccess"; const verifyTeamAccess = (Model, paramName) => { return async (req, res, next) => { try { - const user = req.user; - console.log(user); const documentId = req.params[paramName]; const doc = await Model.findById(documentId); diff --git a/server/src/routes/v2/auth.ts b/server/src/routes/v2/auth.ts deleted file mode 100644 index 5a1b8ad63..000000000 --- a/server/src/routes/v2/auth.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { Router } from "express"; - -import express from "express"; -import AuthController from "../../controllers/v2/AuthController.js"; -import { verifyToken } from "../../middleware/v2/VerifyToken.js"; - -const router = express.Router(); - -class AuthRoutes { - private controller: AuthController; - private router: Router; - constructor(authController: AuthController) { - this.controller = authController; - this.router = Router(); - this.initRoutes(); - } - - initRoutes = () => { - this.router.post("/register", this.controller.register); - this.router.post("/register/invite/:token", this.controller.registerWithInvite); - this.router.post("/login", this.controller.login); - this.router.post("/logout", this.controller.logout); - this.router.get("/me", verifyToken, this.controller.me); - }; - - getRouter() { - return this.router; - } -} - -export default AuthRoutes; diff --git a/server/src/service/v2/business/AuthService.ts b/server/src/service/v2/business/AuthService.ts deleted file mode 100644 index ae22b981d..000000000 --- a/server/src/service/v2/business/AuthService.ts +++ /dev/null @@ -1,141 +0,0 @@ -import bcrypt from "bcryptjs"; -import { User, Role, ITokenizedUser } from "../../../db/models/index.js"; -import ApiError from "../../../utils/ApiError.js"; -import { Types } from "mongoose"; - -export const DEFAULT_ROLES = [ - { - name: "SuperAdmin", - description: "Super admin with all permissions", - permissions: ["*"], - isSystem: true, - }, - { - name: "Admin", - description: "Admin with full permissions", - permissions: ["monitor.*", "users.*"], - isSystem: true, - }, - { - name: "Manager", - description: "Can manage users", - permissions: ["users.create", "users.update", "monitors.*"], - isSystem: true, - }, - { - name: "Member", - description: "Basic team member", - permissions: ["users.update", "monitors.create", "monitors.view", "monitors.update"], - isSystem: true, - }, -]; - -export type RegisterData = { - email: string; - firstName: string; - lastName: string; - password: string; - roles?: Types.ObjectId[]; // Optional roles for invite-based registration -}; - -export type LoginData = { - email: string; - password: string; -}; - -export type AuthResult = ITokenizedUser; - -export interface IAuthService { - register(signupData: RegisterData): Promise; - registerWithInvite(signupData: RegisterData): Promise; - login(loginData: LoginData): Promise; -} - -class AuthService implements IAuthService { - constructor() {} - - async register(signupData: RegisterData): Promise { - const userCount = await User.countDocuments(); - - if (userCount > 0) { - throw new Error("Registration is closed. Please request an invite."); - } - - const { email, firstName, lastName, password } = signupData; - - // Create all default roles - const rolePromises = DEFAULT_ROLES.map((roleData) => - new Role({ - ...roleData, - }).save() - ); - const roles = await Promise.all(rolePromises); - - // Find admin role and assign to first user - const superAdminRole = roles.find((role) => role.name === "SuperAdmin"); - - const user = new User({ - email, - firstName, - lastName, - password, - roles: [superAdminRole!._id], - }); - - const savedUser = await user.save(); - return { - sub: savedUser._id.toString(), - roles: savedUser.roles.map((role) => role.toString()), - }; - } - - async registerWithInvite(signupData: RegisterData): Promise { - const { email, firstName, lastName, password, roles } = signupData; - - const user = new User({ - email, - firstName, - lastName, - password, - roles: roles || [], - }); - try { - const savedUser = await user.save(); - return { - sub: savedUser._id.toString(), - roles: savedUser.roles.map((role) => role.toString()), - }; - } catch (error: any) { - if (error?.code === 11000) { - const dupError = new ApiError("Email already in use", 409); - dupError.stack = error?.stack; - throw dupError; - } - throw error; - } - } - - async login(loginData: LoginData): Promise { - const { email, password } = loginData; - - // Find user by email - const user = await User.findOne({ email }); - - if (!user) { - throw new Error("Invalid email or password"); - } - - // Check password - const isPasswordValid = await bcrypt.compare(password, user.passwordHash); - if (!isPasswordValid) { - throw new Error("Invalid email or password"); - } - - return { - sub: user._id.toString(), - roles: user.roles.map((role) => role.toString()), - }; - } -} - -export default AuthService; diff --git a/server/src/service/v2/business/InviteService.ts b/server/src/service/v2/business/InviteService.ts deleted file mode 100644 index f8bd9e2db..000000000 --- a/server/src/service/v2/business/InviteService.ts +++ /dev/null @@ -1,61 +0,0 @@ -import crypto from "node:crypto"; -import { ITokenizedUser, IInvite, Invite } from "../../../db/models/index.js"; -import ApiError from "../../../utils/ApiError.js"; - -export interface IInviteService { - create: (tokenizedUser: ITokenizedUser, invite: IInvite) => Promise<{ token: string }>; - getAll: () => Promise; - get: (tokenHash: string) => Promise; - delete: (id: string) => Promise; -} - -class InviteService implements IInviteService { - constructor() {} - - create = async (tokenizedUser: ITokenizedUser, inviteData: IInvite) => { - const token = crypto.randomBytes(32).toString("hex"); - const tokenHash = crypto.createHash("sha256").update(token).digest("hex"); - try { - const invite = await Invite.create({ - ...inviteData, - tokenHash, - createdBy: tokenizedUser.sub, - updatedBy: tokenizedUser.sub, - }); - if (!invite) { - throw new ApiError("Failed to create invite", 500); - } - return { token }; - } catch (error: any) { - if (error?.code === 11000) { - const dupError = new ApiError("Invite with this email already exists", 409); - dupError.stack = error?.stack; - throw dupError; - } - throw error; - } - }; - - get = async (token: string) => { - const tokenHash = crypto.createHash("sha256").update(token).digest("hex"); - const invite = await Invite.findOne({ tokenHash }); - if (!invite) { - throw new ApiError("Invite not found", 404); - } - return invite; - }; - - getAll = async () => { - return Invite.find(); - }; - - delete = async (id: string) => { - const result = await Invite.deleteOne({ _id: id }); - if (!result.deletedCount) { - throw new ApiError("Invite not found", 404); - } - return result.deletedCount === 1; - }; -} - -export default InviteService; diff --git a/server/src/utils/ApiError.ts b/server/src/utils/ApiError.ts deleted file mode 100644 index 313d01c2a..000000000 --- a/server/src/utils/ApiError.ts +++ /dev/null @@ -1,15 +0,0 @@ -class ApiError extends Error { - public status: number; - - constructor(message: string, status: number = 500) { - super(message); - this.status = status; - this.name = this.constructor.name; - - if (Error.captureStackTrace) { - Error.captureStackTrace(this, this.constructor); - } - } -} - -export default ApiError; diff --git a/server/src/utils/JWTUtils.ts b/server/src/utils/JWTUtils.ts deleted file mode 100644 index 4eebb78a0..000000000 --- a/server/src/utils/JWTUtils.ts +++ /dev/null @@ -1,22 +0,0 @@ -import jwt from "jsonwebtoken"; -import { AuthResult } from "../service/v2/business/AuthService.js"; - -const encode = (data: AuthResult): string => { - const secret = process.env.JWT_SECRET; - if (!secret) { - throw new Error("JWT_SECRET is not defined"); - } - const token = jwt.sign(data, secret, { expiresIn: "99d" }); - return token; -}; - -const decode = (token: string): AuthResult => { - const secret = process.env.JWT_SECRET; - if (!secret) { - throw new Error("JWT_SECRET is not defined"); - } - const decoded = jwt.verify(token, secret) as AuthResult; - return decoded; -}; - -export { encode, decode }; From 1383d8b59acb7db9852c410b3838d15cdeb7d693 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Tue, 23 Sep 2025 14:12:17 -0700 Subject: [PATCH 024/101] add workflow --- .github/workflows/check-build.yml | 23 +++++++++++++++++++ ...s => 0001_migrateStatusWindowThreshold.ts} | 0 .../db/mongo/migration/{index.js => index.ts} | 0 server/tsconfig.json | 2 +- 4 files changed, 24 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/check-build.yml rename server/src/db/mongo/migration/{0001_migrateStatusWindowThreshold.js => 0001_migrateStatusWindowThreshold.ts} (100%) rename server/src/db/mongo/migration/{index.js => index.ts} (100%) diff --git a/.github/workflows/check-build.yml b/.github/workflows/check-build.yml new file mode 100644 index 000000000..8417b823f --- /dev/null +++ b/.github/workflows/check-build.yml @@ -0,0 +1,23 @@ +name: Build Check ( Server) +on: + pull_request: + workflow_dispatch: + +jobs: + build-server: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Set up Node + uses: actions/setup-node@v4 + with: + node-version: 22 + + - name: Install server dependencies + working-directory: server + run: npm install + + - name: Check server build + working-directory: server + run: npm run build diff --git a/server/src/db/mongo/migration/0001_migrateStatusWindowThreshold.js b/server/src/db/mongo/migration/0001_migrateStatusWindowThreshold.ts similarity index 100% rename from server/src/db/mongo/migration/0001_migrateStatusWindowThreshold.js rename to server/src/db/mongo/migration/0001_migrateStatusWindowThreshold.ts diff --git a/server/src/db/mongo/migration/index.js b/server/src/db/mongo/migration/index.ts similarity index 100% rename from server/src/db/mongo/migration/index.js rename to server/src/db/mongo/migration/index.ts diff --git a/server/tsconfig.json b/server/tsconfig.json index 3aa773ab1..260ccb964 100644 --- a/server/tsconfig.json +++ b/server/tsconfig.json @@ -22,5 +22,5 @@ "allowJs": true, "checkJs": false }, - "exclude": ["node_modules", "dist", "**/*.config.js"] + "exclude": ["node_modules", "dist/", "**/*.config.js"] } From b3b6f1d834ce64623d3c9a4c9953de79b633db62 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Tue, 23 Sep 2025 14:23:45 -0700 Subject: [PATCH 025/101] move all v1 files to v1 dirs --- server/src/app.js | 10 +- server/src/config/routes.js | 4 +- server/src/config/services.js | 78 +-- server/src/controllers/v1/baseController.js | 2 +- .../src/controllers/v2/MonitorController.ts | 157 ++++++ server/src/db/{mongo => v1}/MongoDB.js | 2 +- .../0001_migrateStatusWindowThreshold.ts | 2 +- .../src/db/{mongo => v1}/migration/index.ts | 0 server/src/db/{ => v1}/models/AppSettings.js | 0 server/src/db/{ => v1}/models/Check.js | 0 server/src/db/{ => v1}/models/InviteToken.js | 0 .../db/{ => v1}/models/MaintenanceWindow.js | 0 server/src/db/{ => v1}/models/Monitor.js | 0 server/src/db/{ => v1}/models/MonitorStats.js | 0 server/src/db/{ => v1}/models/Notification.js | 0 .../src/db/{ => v1}/models/RecoveryToken.js | 0 server/src/db/{ => v1}/models/StatusPage.js | 0 server/src/db/{ => v1}/models/Team.js | 0 server/src/db/{ => v1}/models/User.js | 1 - .../db/{mongo => v1}/modules/checkModule.js | 0 .../modules/checkModuleQueries.js | 0 .../db/{mongo => v1}/modules/inviteModule.js | 0 .../modules/maintenanceWindowModule.js | 0 .../db/{mongo => v1}/modules/monitorModule.js | 0 .../modules/monitorModuleQueries.js | 0 .../modules/notificationModule.js | 0 .../{mongo => v1}/modules/recoveryModule.js | 0 .../{mongo => v1}/modules/settingsModule.js | 0 .../{mongo => v1}/modules/statusPageModule.js | 0 .../db/{mongo => v1}/modules/userModule.js | 0 server/src/db/{mongo => v1}/utils/seedDb.js | 4 +- server/src/index.js | 4 +- .../src/middleware/{ => v1}/handleErrors.js | 6 +- server/src/middleware/{ => v1}/isAllowed.js | 6 +- .../middleware/{ => v1}/languageMiddleware.js | 2 +- server/src/middleware/{ => v1}/rateLimiter.js | 0 .../middleware/{ => v1}/responseHandler.js | 0 .../src/middleware/{ => v1}/sanitization.js | 0 server/src/middleware/{ => v1}/verifyJWT.js | 6 +- .../middleware/{ => v1}/verifyOwnership.js | 6 +- .../middleware/{ => v1}/verifySuperAdmin.js | 2 +- .../middleware/{ => v1}/verifyTeamAccess.js | 0 server/src/routes/v1/authRoute.js | 4 +- server/src/routes/v1/checkRoute.js | 2 +- server/src/routes/v1/diagnosticRoute.js | 4 +- server/src/routes/v1/inviteRoute.js | 4 +- server/src/routes/v1/logRoutes.js | 2 +- .../src/routes/v1/maintenanceWindowRoute.js | 2 +- server/src/routes/v1/monitorRoute.js | 2 +- server/src/routes/v1/queueRoute.js | 2 +- server/src/routes/v1/settingsRoute.js | 2 +- server/src/routes/v1/statusPageRoute.js | 2 +- .../service/{ => v1}/business/checkService.js | 0 .../{ => v1}/business/diagnosticService.js | 0 .../{ => v1}/business/inviteService.js | 0 .../business/maintenanceWindowService.js | 0 .../{ => v1}/business/monitorService.js | 2 +- .../service/{ => v1}/business/userService.js | 0 .../SuperSimpleQueue/SuperSimpleQueue.js | 0 .../SuperSimpleQueueHelper.js | 10 +- .../{ => v1}/infrastructure/bufferService.js | 0 .../{ => v1}/infrastructure/emailService.js | 0 .../{ => v1}/infrastructure/errorService.js | 0 .../{ => v1}/infrastructure/networkService.js | 0 .../infrastructure/notificationService.js | 0 .../infrastructure/notificationUtils.js | 0 .../{ => v1}/infrastructure/statusService.js | 2 +- .../{ => v1}/system/serviceRegistry.js | 0 .../{ => v1}/system/settingsService.js | 0 .../service/{ => v1}/system/stringService.js | 0 .../{ => v1}/system/translationService.js | 0 .../src/service/v2/business/CheckService.ts | 132 +++++ .../src/service/v2/business/MonitorService.ts | 464 ++++++++++++++++++ 73 files changed, 840 insertions(+), 88 deletions(-) create mode 100644 server/src/controllers/v2/MonitorController.ts rename server/src/db/{mongo => v1}/MongoDB.js (97%) rename server/src/db/{mongo => v1}/migration/0001_migrateStatusWindowThreshold.ts (92%) rename server/src/db/{mongo => v1}/migration/index.ts (100%) rename server/src/db/{ => v1}/models/AppSettings.js (100%) rename server/src/db/{ => v1}/models/Check.js (100%) rename server/src/db/{ => v1}/models/InviteToken.js (100%) rename server/src/db/{ => v1}/models/MaintenanceWindow.js (100%) rename server/src/db/{ => v1}/models/Monitor.js (100%) rename server/src/db/{ => v1}/models/MonitorStats.js (100%) rename server/src/db/{ => v1}/models/Notification.js (100%) rename server/src/db/{ => v1}/models/RecoveryToken.js (100%) rename server/src/db/{ => v1}/models/StatusPage.js (100%) rename server/src/db/{ => v1}/models/Team.js (100%) rename server/src/db/{ => v1}/models/User.js (98%) rename server/src/db/{mongo => v1}/modules/checkModule.js (100%) rename server/src/db/{mongo => v1}/modules/checkModuleQueries.js (100%) rename server/src/db/{mongo => v1}/modules/inviteModule.js (100%) rename server/src/db/{mongo => v1}/modules/maintenanceWindowModule.js (100%) rename server/src/db/{mongo => v1}/modules/monitorModule.js (100%) rename server/src/db/{mongo => v1}/modules/monitorModuleQueries.js (100%) rename server/src/db/{mongo => v1}/modules/notificationModule.js (100%) rename server/src/db/{mongo => v1}/modules/recoveryModule.js (100%) rename server/src/db/{mongo => v1}/modules/settingsModule.js (100%) rename server/src/db/{mongo => v1}/modules/statusPageModule.js (100%) rename server/src/db/{mongo => v1}/modules/userModule.js (100%) rename server/src/db/{mongo => v1}/utils/seedDb.js (95%) rename server/src/middleware/{ => v1}/handleErrors.js (71%) rename server/src/middleware/{ => v1}/isAllowed.js (87%) rename server/src/middleware/{ => v1}/languageMiddleware.js (93%) rename server/src/middleware/{ => v1}/rateLimiter.js (100%) rename server/src/middleware/{ => v1}/responseHandler.js (100%) rename server/src/middleware/{ => v1}/sanitization.js (100%) rename server/src/middleware/{ => v1}/verifyJWT.js (87%) rename server/src/middleware/{ => v1}/verifyOwnership.js (88%) rename server/src/middleware/{ => v1}/verifySuperAdmin.js (97%) rename server/src/middleware/{ => v1}/verifyTeamAccess.js (100%) rename server/src/service/{ => v1}/business/checkService.js (100%) rename server/src/service/{ => v1}/business/diagnosticService.js (100%) rename server/src/service/{ => v1}/business/inviteService.js (100%) rename server/src/service/{ => v1}/business/maintenanceWindowService.js (100%) rename server/src/service/{ => v1}/business/monitorService.js (99%) rename server/src/service/{ => v1}/business/userService.js (100%) rename server/src/service/{ => v1}/infrastructure/SuperSimpleQueue/SuperSimpleQueue.js (100%) rename server/src/service/{ => v1}/infrastructure/SuperSimpleQueue/SuperSimpleQueueHelper.js (90%) rename server/src/service/{ => v1}/infrastructure/bufferService.js (100%) rename server/src/service/{ => v1}/infrastructure/emailService.js (100%) rename server/src/service/{ => v1}/infrastructure/errorService.js (100%) rename server/src/service/{ => v1}/infrastructure/networkService.js (100%) rename server/src/service/{ => v1}/infrastructure/notificationService.js (100%) rename server/src/service/{ => v1}/infrastructure/notificationUtils.js (100%) rename server/src/service/{ => v1}/infrastructure/statusService.js (99%) rename server/src/service/{ => v1}/system/serviceRegistry.js (100%) rename server/src/service/{ => v1}/system/settingsService.js (100%) rename server/src/service/{ => v1}/system/stringService.js (100%) rename server/src/service/{ => v1}/system/translationService.js (100%) create mode 100644 server/src/service/v2/business/CheckService.ts create mode 100644 server/src/service/v2/business/MonitorService.ts diff --git a/server/src/app.js b/server/src/app.js index 9cabda6b6..253baee88 100644 --- a/server/src/app.js +++ b/server/src/app.js @@ -1,16 +1,16 @@ import express from "express"; import path from "path"; -import { responseHandler } from "./middleware/responseHandler.js"; +import { responseHandler } from "./middleware/v1/responseHandler.js"; import cors from "cors"; import helmet from "helmet"; import compression from "compression"; import cookieParser from "cookie-parser"; -import languageMiddleware from "./middleware/languageMiddleware.js"; +import languageMiddleware from "./middleware/v1/languageMiddleware.js"; import swaggerUi from "swagger-ui-express"; -import { handleErrors } from "./middleware/handleErrors.js"; +import { handleErrors } from "./middleware/v1/handleErrors.js"; import { setupRoutes } from "./config/routes.js"; -import { generalApiLimiter } from "./middleware/rateLimiter.js"; -import { sanitizeBody, sanitizeQuery } from "./middleware/sanitization.js"; +import { generalApiLimiter } from "./middleware/v1/rateLimiter.js"; +import { sanitizeBody, sanitizeQuery } from "./middleware/v1/sanitization.js"; export const createApp = ({ services, controllers, envSettings, frontendPath, openApiSpec }) => { const allowedOrigin = envSettings.clientHost; diff --git a/server/src/config/routes.js b/server/src/config/routes.js index 2ec93a2dc..40bf8e994 100644 --- a/server/src/config/routes.js +++ b/server/src/config/routes.js @@ -1,5 +1,5 @@ -import { verifyJWT } from "../middleware/verifyJWT.js"; -import { authApiLimiter } from "../middleware/rateLimiter.js"; +import { verifyJWT } from "../middleware/v1/verifyJWT.js"; +import { authApiLimiter } from "../middleware/v1/rateLimiter.js"; import AuthRoutes from "../routes/v1/authRoute.js"; import InviteRoutes from "../routes/v1//inviteRoute.js"; diff --git a/server/src/config/services.js b/server/src/config/services.js index 08c2525f3..22f978071 100644 --- a/server/src/config/services.js +++ b/server/src/config/services.js @@ -1,22 +1,22 @@ -import ServiceRegistry from "../service/system/serviceRegistry.js"; -import TranslationService from "../service/system/translationService.js"; -import StringService from "../service/system/stringService.js"; -import MongoDB from "../db/mongo/MongoDB.js"; -import NetworkService from "../service/infrastructure/networkService.js"; -import EmailService from "../service/infrastructure/emailService.js"; -import BufferService from "../service/infrastructure/bufferService.js"; -import StatusService from "../service/infrastructure/statusService.js"; -import NotificationUtils from "../service/infrastructure/notificationUtils.js"; -import NotificationService from "../service/infrastructure/notificationService.js"; -import ErrorService from "../service/infrastructure/errorService.js"; -import SuperSimpleQueueHelper from "../service/infrastructure/SuperSimpleQueue/SuperSimpleQueueHelper.js"; -import SuperSimpleQueue from "../service/infrastructure/SuperSimpleQueue/SuperSimpleQueue.js"; -import UserService from "../service/business/userService.js"; -import CheckService from "../service/business/checkService.js"; -import DiagnosticService from "../service/business/diagnosticService.js"; -import InviteService from "../service/business/inviteService.js"; -import MaintenanceWindowService from "../service/business/maintenanceWindowService.js"; -import MonitorService from "../service/business/monitorService.js"; +import ServiceRegistry from "../service/v1/system/serviceRegistry.js"; +import TranslationService from "../service/v1/system/translationService.js"; +import StringService from "../service/v1/system/stringService.js"; +import MongoDB from "../db/v1/MongoDB.js"; +import NetworkService from "../service/v1/infrastructure/networkService.js"; +import EmailService from "../service/v1/infrastructure/emailService.js"; +import BufferService from "../service/v1/infrastructure/bufferService.js"; +import StatusService from "../service/v1/infrastructure/statusService.js"; +import NotificationUtils from "../service/v1/infrastructure/notificationUtils.js"; +import NotificationService from "../service/v1/infrastructure/notificationService.js"; +import ErrorService from "../service/v1/infrastructure/errorService.js"; +import SuperSimpleQueueHelper from "../service/v1/infrastructure/SuperSimpleQueue/SuperSimpleQueueHelper.js"; +import SuperSimpleQueue from "../service/v1/infrastructure/SuperSimpleQueue/SuperSimpleQueue.js"; +import UserService from "../service/v1/business/userService.js"; +import CheckService from "../service/v1/business/checkService.js"; +import DiagnosticService from "../service/v1/business/diagnosticService.js"; +import InviteService from "../service/v1/business/inviteService.js"; +import MaintenanceWindowService from "../service/v1/business/maintenanceWindowService.js"; +import MonitorService from "../service/v1/business/monitorService.js"; import papaparse from "papaparse"; import axios from "axios"; import got from "got"; @@ -46,27 +46,27 @@ import { GenerateAvatarImage } from "../utils/imageProcessing.js"; import { ParseBoolean } from "../utils/utils.js"; // Models -import Check from "../db/models/Check.js"; -import Monitor from "../db/models/Monitor.js"; -import User from "../db/models/User.js"; -import InviteToken from "../db/models/InviteToken.js"; -import StatusPage from "../db/models/StatusPage.js"; -import Team from "../db/models/Team.js"; -import MaintenanceWindow from "../db/models/MaintenanceWindow.js"; -import MonitorStats from "../db/models/MonitorStats.js"; -import Notification from "../db/models/Notification.js"; -import RecoveryToken from "../db/models/RecoveryToken.js"; -import AppSettings from "../db/models/AppSettings.js"; +import Check from "../db/v1/models/Check.js"; +import Monitor from "../db/v1/models/Monitor.js"; +import User from "../db/v1/models/User.js"; +import InviteToken from "../db/v1/models/InviteToken.js"; +import StatusPage from "../db/v1/models/StatusPage.js"; +import Team from "../db/v1/models/Team.js"; +import MaintenanceWindow from "../db/v1/models/MaintenanceWindow.js"; +import MonitorStats from "../db/v1/models/MonitorStats.js"; +import Notification from "../db/v1/models/Notification.js"; +import RecoveryToken from "../db/v1/models/RecoveryToken.js"; +import AppSettings from "../db/v1/models/AppSettings.js"; -import InviteModule from "../db/mongo/modules/inviteModule.js"; -import CheckModule from "../db/mongo/modules/checkModule.js"; -import StatusPageModule from "../db/mongo/modules/statusPageModule.js"; -import UserModule from "../db/mongo/modules/userModule.js"; -import MaintenanceWindowModule from "../db/mongo/modules/maintenanceWindowModule.js"; -import MonitorModule from "../db/mongo/modules/monitorModule.js"; -import NotificationModule from "../db/mongo/modules/notificationModule.js"; -import RecoveryModule from "../db/mongo/modules/recoveryModule.js"; -import SettingsModule from "../db/mongo/modules/settingsModule.js"; +import InviteModule from "../db/v1/modules/inviteModule.js"; +import CheckModule from "../db/v1/modules/checkModule.js"; +import StatusPageModule from "../db/v1/modules/statusPageModule.js"; +import UserModule from "../db/v1/modules//userModule.js"; +import MaintenanceWindowModule from "../db/v1/modules/maintenanceWindowModule.js"; +import MonitorModule from "../db/v1/modules/monitorModule.js"; +import NotificationModule from "../db/v1/modules/notificationModule.js"; +import RecoveryModule from "../db/v1/modules/recoveryModule.js"; +import SettingsModule from "../db/v1/modules/settingsModule.js"; export const initializeServices = async ({ logger, envSettings, settingsService }) => { const serviceRegistry = new ServiceRegistry({ logger }); diff --git a/server/src/controllers/v1/baseController.js b/server/src/controllers/v1/baseController.js index 314905c82..e8612cec5 100644 --- a/server/src/controllers/v1/baseController.js +++ b/server/src/controllers/v1/baseController.js @@ -1,4 +1,4 @@ -import { AppError } from "../../service/infrastructure/errorService.js"; +import { AppError } from "../../service/v1/infrastructure/errorService.js"; export const createCommonDependencies = (db, errorService, logger, stringService) => { return { diff --git a/server/src/controllers/v2/MonitorController.ts b/server/src/controllers/v2/MonitorController.ts new file mode 100644 index 000000000..6493b7f44 --- /dev/null +++ b/server/src/controllers/v2/MonitorController.ts @@ -0,0 +1,157 @@ +import { Request, Response, NextFunction } from "express"; +import ApiError from "../../utils/ApiError.js"; +import MonitorService from "../../service/v2/business/MonitorService.js"; +import { MonitorType } from "../../db/v1/models/Monitor.js"; +class MonitorController { + private monitorService: MonitorService; + constructor(monitorService: MonitorService) { + this.monitorService = monitorService; + } + + create = async (req: Request, res: Response, next: NextFunction) => { + try { + const tokenizedUser = req.user; + if (!tokenizedUser) { + return res.status(401).json({ message: "Unauthorized" }); + } + + const monitor = await this.monitorService.create(tokenizedUser, req.body); + res.status(201).json({ + message: "Monitor created successfully", + data: monitor, + }); + } catch (error) { + next(error); + } + }; + + get = async (req: Request, res: Response, next: NextFunction) => { + try { + const tokenizedUser = req.user; + if (!tokenizedUser) { + return res.status(401).json({ message: "Unauthorized" }); + } + + const id = req.params.id; + if (!id) { + throw new ApiError("Monitor ID is required", 400); + } + + const range = req.query.range; + if (!range || typeof range !== "string") throw new ApiError("Range query parameter is required", 400); + + let monitor; + + const status = req.query.status; + if (status && typeof status !== "string") { + throw new ApiError("Status query parameter must be a string", 400); + } + + if (req.query.embedChecks === "true") { + monitor = await this.monitorService.getEmbedChecks(id, range, status); + } else { + monitor = await this.monitorService.get(id); + } + + res.status(200).json({ + message: "Monitor retrieved successfully", + data: monitor, + }); + } catch (error) { + next(error); + } + }; + + getAll = async (req: Request, res: Response, next: NextFunction) => { + try { + const tokenizedUser = req.user; + if (!tokenizedUser) { + return res.status(401).json({ message: "Unauthorized" }); + } + + let monitors; + if (req.query.embedChecks === "true") { + const page = Math.max(1, Number(req.query.page) || 1); + const limit = Math.max(1, Number(req.query.limit) || 10); + const type: MonitorType[] = req.query.type as MonitorType[]; + + monitors = await this.monitorService.getAllEmbedChecks(page, limit, type); + } else { + monitors = await this.monitorService.getAll(); + } + + res.status(200).json({ + message: "Monitors retrieved successfully", + data: monitors, + }); + } catch (error) { + next(error); + } + }; + + toggleActive = async (req: Request, res: Response, next: NextFunction) => { + try { + const tokenizedUser = req.user; + if (!tokenizedUser) { + return res.status(401).json({ message: "Unauthorized" }); + } + + const id = req.params.id; + if (!id) { + throw new ApiError("Monitor ID is required", 400); + } + + const monitor = await this.monitorService.toggleActive(id, tokenizedUser); + res.status(200).json({ + message: "Monitor paused/unpaused successfully", + data: monitor, + }); + } catch (error) { + next(error); + } + }; + + update = async (req: Request, res: Response, next: NextFunction) => { + try { + const tokenizedUser = req.user; + if (!tokenizedUser) { + return res.status(401).json({ message: "Unauthorized" }); + } + + const id = req.params.id; + if (!id) { + throw new ApiError("Monitor ID is required", 400); + } + + const monitor = await this.monitorService.update(tokenizedUser, id, req.body); + res.status(200).json({ + message: "Monitor updated successfully", + data: monitor, + }); + } catch (error) { + next(error); + } + }; + + delete = async (req: Request, res: Response, next: NextFunction) => { + try { + const tokenizedUser = req.user; + if (!tokenizedUser) { + return res.status(401).json({ message: "Unauthorized" }); + } + const id = req.params.id; + if (!id) { + throw new ApiError("Monitor ID is required", 400); + } + await this.monitorService.delete(id); + + res.status(200).json({ + message: "Monitor deleted successfully", + }); + } catch (error) { + next(error); + } + }; +} + +export default MonitorController; diff --git a/server/src/db/mongo/MongoDB.js b/server/src/db/v1/MongoDB.js similarity index 97% rename from server/src/db/mongo/MongoDB.js rename to server/src/db/v1/MongoDB.js index 74fcbd89f..3bdae3525 100755 --- a/server/src/db/mongo/MongoDB.js +++ b/server/src/db/v1/MongoDB.js @@ -1,5 +1,5 @@ import mongoose from "mongoose"; -import AppSettings from "../models/AppSettings.js"; +import AppSettings from "./models/AppSettings.js"; import { runMigrations } from "./migration/index.js"; class MongoDB { static SERVICE_NAME = "MongoDB"; diff --git a/server/src/db/mongo/migration/0001_migrateStatusWindowThreshold.ts b/server/src/db/v1/migration/0001_migrateStatusWindowThreshold.ts similarity index 92% rename from server/src/db/mongo/migration/0001_migrateStatusWindowThreshold.ts rename to server/src/db/v1/migration/0001_migrateStatusWindowThreshold.ts index 8f6988de4..857d36381 100644 --- a/server/src/db/mongo/migration/0001_migrateStatusWindowThreshold.ts +++ b/server/src/db/v1/migration/0001_migrateStatusWindowThreshold.ts @@ -1,4 +1,4 @@ -import Monitor from "../../models/Monitor.js"; +import Monitor from "../models/Monitor.js"; async function migrateStatusWindowThreshold() { try { const monitors = await Monitor.find({ statusWindowThreshold: { $lt: 1 } }); diff --git a/server/src/db/mongo/migration/index.ts b/server/src/db/v1/migration/index.ts similarity index 100% rename from server/src/db/mongo/migration/index.ts rename to server/src/db/v1/migration/index.ts diff --git a/server/src/db/models/AppSettings.js b/server/src/db/v1/models/AppSettings.js similarity index 100% rename from server/src/db/models/AppSettings.js rename to server/src/db/v1/models/AppSettings.js diff --git a/server/src/db/models/Check.js b/server/src/db/v1/models/Check.js similarity index 100% rename from server/src/db/models/Check.js rename to server/src/db/v1/models/Check.js diff --git a/server/src/db/models/InviteToken.js b/server/src/db/v1/models/InviteToken.js similarity index 100% rename from server/src/db/models/InviteToken.js rename to server/src/db/v1/models/InviteToken.js diff --git a/server/src/db/models/MaintenanceWindow.js b/server/src/db/v1/models/MaintenanceWindow.js similarity index 100% rename from server/src/db/models/MaintenanceWindow.js rename to server/src/db/v1/models/MaintenanceWindow.js diff --git a/server/src/db/models/Monitor.js b/server/src/db/v1/models/Monitor.js similarity index 100% rename from server/src/db/models/Monitor.js rename to server/src/db/v1/models/Monitor.js diff --git a/server/src/db/models/MonitorStats.js b/server/src/db/v1/models/MonitorStats.js similarity index 100% rename from server/src/db/models/MonitorStats.js rename to server/src/db/v1/models/MonitorStats.js diff --git a/server/src/db/models/Notification.js b/server/src/db/v1/models/Notification.js similarity index 100% rename from server/src/db/models/Notification.js rename to server/src/db/v1/models/Notification.js diff --git a/server/src/db/models/RecoveryToken.js b/server/src/db/v1/models/RecoveryToken.js similarity index 100% rename from server/src/db/models/RecoveryToken.js rename to server/src/db/v1/models/RecoveryToken.js diff --git a/server/src/db/models/StatusPage.js b/server/src/db/v1/models/StatusPage.js similarity index 100% rename from server/src/db/models/StatusPage.js rename to server/src/db/v1/models/StatusPage.js diff --git a/server/src/db/models/Team.js b/server/src/db/v1/models/Team.js similarity index 100% rename from server/src/db/models/Team.js rename to server/src/db/v1/models/Team.js diff --git a/server/src/db/models/User.js b/server/src/db/v1/models/User.js similarity index 98% rename from server/src/db/models/User.js rename to server/src/db/v1/models/User.js index 9368d98b1..bf3012b26 100755 --- a/server/src/db/models/User.js +++ b/server/src/db/v1/models/User.js @@ -1,6 +1,5 @@ import mongoose from "mongoose"; import bcrypt from "bcryptjs"; -import logger from "../../utils/logger.js"; import Monitor from "./Monitor.js"; import Team from "./Team.js"; import Notification from "./Notification.js"; diff --git a/server/src/db/mongo/modules/checkModule.js b/server/src/db/v1/modules/checkModule.js similarity index 100% rename from server/src/db/mongo/modules/checkModule.js rename to server/src/db/v1/modules/checkModule.js diff --git a/server/src/db/mongo/modules/checkModuleQueries.js b/server/src/db/v1/modules/checkModuleQueries.js similarity index 100% rename from server/src/db/mongo/modules/checkModuleQueries.js rename to server/src/db/v1/modules/checkModuleQueries.js diff --git a/server/src/db/mongo/modules/inviteModule.js b/server/src/db/v1/modules/inviteModule.js similarity index 100% rename from server/src/db/mongo/modules/inviteModule.js rename to server/src/db/v1/modules/inviteModule.js diff --git a/server/src/db/mongo/modules/maintenanceWindowModule.js b/server/src/db/v1/modules/maintenanceWindowModule.js similarity index 100% rename from server/src/db/mongo/modules/maintenanceWindowModule.js rename to server/src/db/v1/modules/maintenanceWindowModule.js diff --git a/server/src/db/mongo/modules/monitorModule.js b/server/src/db/v1/modules/monitorModule.js similarity index 100% rename from server/src/db/mongo/modules/monitorModule.js rename to server/src/db/v1/modules/monitorModule.js diff --git a/server/src/db/mongo/modules/monitorModuleQueries.js b/server/src/db/v1/modules/monitorModuleQueries.js similarity index 100% rename from server/src/db/mongo/modules/monitorModuleQueries.js rename to server/src/db/v1/modules/monitorModuleQueries.js diff --git a/server/src/db/mongo/modules/notificationModule.js b/server/src/db/v1/modules/notificationModule.js similarity index 100% rename from server/src/db/mongo/modules/notificationModule.js rename to server/src/db/v1/modules/notificationModule.js diff --git a/server/src/db/mongo/modules/recoveryModule.js b/server/src/db/v1/modules/recoveryModule.js similarity index 100% rename from server/src/db/mongo/modules/recoveryModule.js rename to server/src/db/v1/modules/recoveryModule.js diff --git a/server/src/db/mongo/modules/settingsModule.js b/server/src/db/v1/modules/settingsModule.js similarity index 100% rename from server/src/db/mongo/modules/settingsModule.js rename to server/src/db/v1/modules/settingsModule.js diff --git a/server/src/db/mongo/modules/statusPageModule.js b/server/src/db/v1/modules/statusPageModule.js similarity index 100% rename from server/src/db/mongo/modules/statusPageModule.js rename to server/src/db/v1/modules/statusPageModule.js diff --git a/server/src/db/mongo/modules/userModule.js b/server/src/db/v1/modules/userModule.js similarity index 100% rename from server/src/db/mongo/modules/userModule.js rename to server/src/db/v1/modules/userModule.js diff --git a/server/src/db/mongo/utils/seedDb.js b/server/src/db/v1/utils/seedDb.js similarity index 95% rename from server/src/db/mongo/utils/seedDb.js rename to server/src/db/v1/utils/seedDb.js index 224744a51..cdfe3675c 100755 --- a/server/src/db/mongo/utils/seedDb.js +++ b/server/src/db/v1/utils/seedDb.js @@ -1,5 +1,5 @@ -import Monitor from "../../models/Monitor.js"; -import Check from "../../models/Check.js"; +import Monitor from "../models/Monitor.js"; +import Check from "../models/Check.js"; import { logger } from "../../../utils/logger.js"; const generateRandomUrl = () => { diff --git a/server/src/index.js b/server/src/index.js index d1ff649eb..e8b756d0e 100755 --- a/server/src/index.js +++ b/server/src/index.js @@ -7,8 +7,8 @@ import path from "path"; import fs from "fs"; import Logger from "./utils/logger.js"; -import SettingsService from "./service/system/settingsService.js"; -import AppSettings from "./db/models/AppSettings.js"; +import SettingsService from "./service/v1/system/settingsService.js"; +import AppSettings from "./db/v1/models/AppSettings.js"; const SERVICE_NAME = "Server"; let logger; diff --git a/server/src/middleware/handleErrors.js b/server/src/middleware/v1/handleErrors.js similarity index 71% rename from server/src/middleware/handleErrors.js rename to server/src/middleware/v1/handleErrors.js index b0ded5ab7..43671fbe2 100755 --- a/server/src/middleware/handleErrors.js +++ b/server/src/middleware/v1/handleErrors.js @@ -1,6 +1,6 @@ -import { logger } from "../utils/logger.js"; -import ServiceRegistry from "../service/system/serviceRegistry.js"; -import StringService from "../service/system/stringService.js"; +import { logger } from "../../utils/logger.js"; +import ServiceRegistry from "../../service/v1/system/serviceRegistry.js"; +import StringService from "../../service/v1/system/stringService.js"; const handleErrors = (error, req, res, next) => { const status = error.status || 500; diff --git a/server/src/middleware/isAllowed.js b/server/src/middleware/v1/isAllowed.js similarity index 87% rename from server/src/middleware/isAllowed.js rename to server/src/middleware/v1/isAllowed.js index 5d61974be..cc240f531 100755 --- a/server/src/middleware/isAllowed.js +++ b/server/src/middleware/v1/isAllowed.js @@ -1,9 +1,9 @@ import jwt from "jsonwebtoken"; const TOKEN_PREFIX = "Bearer "; const SERVICE_NAME = "allowedRoles"; -import ServiceRegistry from "../service/system/serviceRegistry.js"; -import StringService from "../service/system/stringService.js"; -import SettingsService from "../service/system/settingsService.js"; +import ServiceRegistry from "../../service/v1/system/serviceRegistry.js"; +import StringService from "../../service/v1/system/stringService.js"; +import SettingsService from "../../service/v1/system/settingsService.js"; const isAllowed = (allowedRoles) => { return (req, res, next) => { diff --git a/server/src/middleware/languageMiddleware.js b/server/src/middleware/v1/languageMiddleware.js similarity index 93% rename from server/src/middleware/languageMiddleware.js rename to server/src/middleware/v1/languageMiddleware.js index 6293dc837..819858a21 100755 --- a/server/src/middleware/languageMiddleware.js +++ b/server/src/middleware/v1/languageMiddleware.js @@ -1,4 +1,4 @@ -import { logger } from "../utils/logger.js"; +import { logger } from "../../utils/logger.js"; const languageMiddleware = (stringService, translationService) => async (req, res, next) => { try { diff --git a/server/src/middleware/rateLimiter.js b/server/src/middleware/v1/rateLimiter.js similarity index 100% rename from server/src/middleware/rateLimiter.js rename to server/src/middleware/v1/rateLimiter.js diff --git a/server/src/middleware/responseHandler.js b/server/src/middleware/v1/responseHandler.js similarity index 100% rename from server/src/middleware/responseHandler.js rename to server/src/middleware/v1/responseHandler.js diff --git a/server/src/middleware/sanitization.js b/server/src/middleware/v1/sanitization.js similarity index 100% rename from server/src/middleware/sanitization.js rename to server/src/middleware/v1/sanitization.js diff --git a/server/src/middleware/verifyJWT.js b/server/src/middleware/v1/verifyJWT.js similarity index 87% rename from server/src/middleware/verifyJWT.js rename to server/src/middleware/v1/verifyJWT.js index d01db0a97..b57391dfd 100755 --- a/server/src/middleware/verifyJWT.js +++ b/server/src/middleware/v1/verifyJWT.js @@ -1,7 +1,7 @@ import jwt from "jsonwebtoken"; -import ServiceRegistry from "../service/system/serviceRegistry.js"; -import SettingsService from "../service/system/settingsService.js"; -import StringService from "../service/system/stringService.js"; +import ServiceRegistry from "../../service/v1/system/serviceRegistry.js"; +import SettingsService from "../../service/v1/system/settingsService.js"; +import StringService from "../../service/v1/system/stringService.js"; const SERVICE_NAME = "verifyJWT"; const TOKEN_PREFIX = "Bearer "; diff --git a/server/src/middleware/verifyOwnership.js b/server/src/middleware/v1/verifyOwnership.js similarity index 88% rename from server/src/middleware/verifyOwnership.js rename to server/src/middleware/v1/verifyOwnership.js index 4431b0c26..741fb6436 100755 --- a/server/src/middleware/verifyOwnership.js +++ b/server/src/middleware/v1/verifyOwnership.js @@ -1,6 +1,6 @@ -import { logger } from "../utils/logger.js"; -import ServiceRegistry from "../service/system/serviceRegistry.js"; -import StringService from "../service/system/stringService.js"; +import { logger } from "../../utils/logger.js"; +import ServiceRegistry from "../../service/v1/system/serviceRegistry.js"; +import StringService from "../../service/v1/system/stringService.js"; import { ObjectId } from "mongodb"; const SERVICE_NAME = "verifyOwnership"; diff --git a/server/src/middleware/verifySuperAdmin.js b/server/src/middleware/v1/verifySuperAdmin.js similarity index 97% rename from server/src/middleware/verifySuperAdmin.js rename to server/src/middleware/v1/verifySuperAdmin.js index a5a93eede..41ac0a3e0 100755 --- a/server/src/middleware/verifySuperAdmin.js +++ b/server/src/middleware/v1/verifySuperAdmin.js @@ -1,5 +1,5 @@ const jwt = require("jsonwebtoken"); -const logger = require("../utils/logger"); +const logger = require("../../utils/logger.js"); const SERVICE_NAME = "verifyAdmin"; const TOKEN_PREFIX = "Bearer "; import ServiceRegistry from "../service/serviceRegistry.js"; diff --git a/server/src/middleware/verifyTeamAccess.js b/server/src/middleware/v1/verifyTeamAccess.js similarity index 100% rename from server/src/middleware/verifyTeamAccess.js rename to server/src/middleware/v1/verifyTeamAccess.js diff --git a/server/src/routes/v1/authRoute.js b/server/src/routes/v1/authRoute.js index f69f94f43..d9b036b05 100755 --- a/server/src/routes/v1/authRoute.js +++ b/server/src/routes/v1/authRoute.js @@ -1,6 +1,6 @@ import { Router } from "express"; -import { verifyJWT } from "../../middleware/verifyJWT.js"; -import { isAllowed } from "../../middleware/isAllowed.js"; +import { verifyJWT } from "../../middleware/v1/verifyJWT.js"; +import { isAllowed } from "../../middleware/v1/isAllowed.js"; import multer from "multer"; const upload = multer(); diff --git a/server/src/routes/v1/checkRoute.js b/server/src/routes/v1/checkRoute.js index 673fe51e0..78f6b8a42 100755 --- a/server/src/routes/v1/checkRoute.js +++ b/server/src/routes/v1/checkRoute.js @@ -1,6 +1,6 @@ import { Router } from "express"; -import { isAllowed } from "../../middleware/isAllowed.js"; +import { isAllowed } from "../../middleware/v1/isAllowed.js"; class CheckRoutes { constructor(checkController) { diff --git a/server/src/routes/v1/diagnosticRoute.js b/server/src/routes/v1/diagnosticRoute.js index 1b7cc06be..2cf481e08 100755 --- a/server/src/routes/v1/diagnosticRoute.js +++ b/server/src/routes/v1/diagnosticRoute.js @@ -1,6 +1,6 @@ import { Router } from "express"; -import { verifyJWT } from "../../middleware/verifyJWT.js"; -import { isAllowed } from "../../middleware/isAllowed.js"; +import { verifyJWT } from "../../middleware/v1/verifyJWT.js"; +import { isAllowed } from "../../middleware/v1/isAllowed.js"; class DiagnosticRoutes { constructor(diagnosticController) { diff --git a/server/src/routes/v1/inviteRoute.js b/server/src/routes/v1/inviteRoute.js index 74fcfeb91..a47550536 100755 --- a/server/src/routes/v1/inviteRoute.js +++ b/server/src/routes/v1/inviteRoute.js @@ -1,6 +1,6 @@ import { Router } from "express"; -import { verifyJWT } from "../../middleware/verifyJWT.js"; -import { isAllowed } from "../../middleware/isAllowed.js"; +import { verifyJWT } from "../../middleware/v1/verifyJWT.js"; +import { isAllowed } from "../../middleware/v1/isAllowed.js"; class InviteRoutes { constructor(inviteController) { diff --git a/server/src/routes/v1/logRoutes.js b/server/src/routes/v1/logRoutes.js index feabe669a..43da8b75a 100755 --- a/server/src/routes/v1/logRoutes.js +++ b/server/src/routes/v1/logRoutes.js @@ -1,5 +1,5 @@ import { Router } from "express"; -import { isAllowed } from "../../middleware/isAllowed.js"; +import { isAllowed } from "../../middleware/v1/isAllowed.js"; class LogRoutes { constructor(logController) { this.router = Router(); diff --git a/server/src/routes/v1/maintenanceWindowRoute.js b/server/src/routes/v1/maintenanceWindowRoute.js index 622e98f88..3df040994 100755 --- a/server/src/routes/v1/maintenanceWindowRoute.js +++ b/server/src/routes/v1/maintenanceWindowRoute.js @@ -1,5 +1,5 @@ import { Router } from "express"; -import MaintenanceWindow from "../../db/models/MaintenanceWindow.js"; +import MaintenanceWindow from "../../db/v1/models/MaintenanceWindow.js"; class MaintenanceWindowRoutes { constructor(maintenanceWindowController) { this.router = Router(); diff --git a/server/src/routes/v1/monitorRoute.js b/server/src/routes/v1/monitorRoute.js index dde72e256..107964f6b 100755 --- a/server/src/routes/v1/monitorRoute.js +++ b/server/src/routes/v1/monitorRoute.js @@ -1,5 +1,5 @@ import { Router } from "express"; -import { isAllowed } from "../../middleware/isAllowed.js"; +import { isAllowed } from "../../middleware/v1/isAllowed.js"; import multer from "multer"; import { fetchMonitorCertificate } from "../../controllers/v1/controllerUtils.js"; const upload = multer({ diff --git a/server/src/routes/v1/queueRoute.js b/server/src/routes/v1/queueRoute.js index 59e1cb744..c6cfa05da 100755 --- a/server/src/routes/v1/queueRoute.js +++ b/server/src/routes/v1/queueRoute.js @@ -1,5 +1,5 @@ import { Router } from "express"; -import { isAllowed } from "../../middleware/isAllowed.js"; +import { isAllowed } from "../../middleware/v1/isAllowed.js"; class QueueRoutes { constructor(queueController) { this.router = Router(); diff --git a/server/src/routes/v1/settingsRoute.js b/server/src/routes/v1/settingsRoute.js index 76556e923..e88186af2 100755 --- a/server/src/routes/v1/settingsRoute.js +++ b/server/src/routes/v1/settingsRoute.js @@ -1,5 +1,5 @@ import { Router } from "express"; -import { isAllowed } from "../../middleware/isAllowed.js"; +import { isAllowed } from "../../middleware/v1/isAllowed.js"; class SettingsRoutes { constructor(settingsController) { diff --git a/server/src/routes/v1/statusPageRoute.js b/server/src/routes/v1/statusPageRoute.js index 8300066a8..2c3b0b265 100755 --- a/server/src/routes/v1/statusPageRoute.js +++ b/server/src/routes/v1/statusPageRoute.js @@ -1,5 +1,5 @@ import { Router } from "express"; -import { verifyJWT } from "../../middleware/verifyJWT.js"; +import { verifyJWT } from "../../middleware/v1/verifyJWT.js"; import multer from "multer"; const upload = multer(); diff --git a/server/src/service/business/checkService.js b/server/src/service/v1/business/checkService.js similarity index 100% rename from server/src/service/business/checkService.js rename to server/src/service/v1/business/checkService.js diff --git a/server/src/service/business/diagnosticService.js b/server/src/service/v1/business/diagnosticService.js similarity index 100% rename from server/src/service/business/diagnosticService.js rename to server/src/service/v1/business/diagnosticService.js diff --git a/server/src/service/business/inviteService.js b/server/src/service/v1/business/inviteService.js similarity index 100% rename from server/src/service/business/inviteService.js rename to server/src/service/v1/business/inviteService.js diff --git a/server/src/service/business/maintenanceWindowService.js b/server/src/service/v1/business/maintenanceWindowService.js similarity index 100% rename from server/src/service/business/maintenanceWindowService.js rename to server/src/service/v1/business/maintenanceWindowService.js diff --git a/server/src/service/business/monitorService.js b/server/src/service/v1/business/monitorService.js similarity index 99% rename from server/src/service/business/monitorService.js rename to server/src/service/v1/business/monitorService.js index 637870b71..061234102 100644 --- a/server/src/service/business/monitorService.js +++ b/server/src/service/v1/business/monitorService.js @@ -1,4 +1,4 @@ -import { createMonitorsBodyValidation } from "../../validation/joi.js"; +import { createMonitorsBodyValidation } from "../../../validation/joi.js"; const SERVICE_NAME = "MonitorService"; class MonitorService { diff --git a/server/src/service/business/userService.js b/server/src/service/v1/business/userService.js similarity index 100% rename from server/src/service/business/userService.js rename to server/src/service/v1/business/userService.js diff --git a/server/src/service/infrastructure/SuperSimpleQueue/SuperSimpleQueue.js b/server/src/service/v1/infrastructure/SuperSimpleQueue/SuperSimpleQueue.js similarity index 100% rename from server/src/service/infrastructure/SuperSimpleQueue/SuperSimpleQueue.js rename to server/src/service/v1/infrastructure/SuperSimpleQueue/SuperSimpleQueue.js diff --git a/server/src/service/infrastructure/SuperSimpleQueue/SuperSimpleQueueHelper.js b/server/src/service/v1/infrastructure/SuperSimpleQueue/SuperSimpleQueueHelper.js similarity index 90% rename from server/src/service/infrastructure/SuperSimpleQueue/SuperSimpleQueueHelper.js rename to server/src/service/v1/infrastructure/SuperSimpleQueue/SuperSimpleQueueHelper.js index b348e2534..2833a59f5 100644 --- a/server/src/service/infrastructure/SuperSimpleQueue/SuperSimpleQueueHelper.js +++ b/server/src/service/v1/infrastructure/SuperSimpleQueue/SuperSimpleQueueHelper.js @@ -5,11 +5,11 @@ class SuperSimpleQueueHelper { /** * @param {{ - * db: import("../database").Database, - * logger: import("../logger").Logger, - * networkService: import("../networkService").NetworkService, - * statusService: import("../statusService").StatusService, - * notificationService: import("../notificationService").NotificationService + * db: import("../database.js").Database, + * logger: import("../logger.js").Logger, + * networkService: import("../networkService.js").NetworkService, + * statusService: import("../statusService.js").StatusService, + * notificationService: import("../notificationService.js").NotificationService * }} */ constructor({ db, logger, networkService, statusService, notificationService }) { diff --git a/server/src/service/infrastructure/bufferService.js b/server/src/service/v1/infrastructure/bufferService.js similarity index 100% rename from server/src/service/infrastructure/bufferService.js rename to server/src/service/v1/infrastructure/bufferService.js diff --git a/server/src/service/infrastructure/emailService.js b/server/src/service/v1/infrastructure/emailService.js similarity index 100% rename from server/src/service/infrastructure/emailService.js rename to server/src/service/v1/infrastructure/emailService.js diff --git a/server/src/service/infrastructure/errorService.js b/server/src/service/v1/infrastructure/errorService.js similarity index 100% rename from server/src/service/infrastructure/errorService.js rename to server/src/service/v1/infrastructure/errorService.js diff --git a/server/src/service/infrastructure/networkService.js b/server/src/service/v1/infrastructure/networkService.js similarity index 100% rename from server/src/service/infrastructure/networkService.js rename to server/src/service/v1/infrastructure/networkService.js diff --git a/server/src/service/infrastructure/notificationService.js b/server/src/service/v1/infrastructure/notificationService.js similarity index 100% rename from server/src/service/infrastructure/notificationService.js rename to server/src/service/v1/infrastructure/notificationService.js diff --git a/server/src/service/infrastructure/notificationUtils.js b/server/src/service/v1/infrastructure/notificationUtils.js similarity index 100% rename from server/src/service/infrastructure/notificationUtils.js rename to server/src/service/v1/infrastructure/notificationUtils.js diff --git a/server/src/service/infrastructure/statusService.js b/server/src/service/v1/infrastructure/statusService.js similarity index 99% rename from server/src/service/infrastructure/statusService.js rename to server/src/service/v1/infrastructure/statusService.js index 72fa359d7..248d1c910 100755 --- a/server/src/service/infrastructure/statusService.js +++ b/server/src/service/v1/infrastructure/statusService.js @@ -1,4 +1,4 @@ -import MonitorStats from "../../db/models/MonitorStats.js"; +import MonitorStats from "../../../db/v1/models/MonitorStats.js"; const SERVICE_NAME = "StatusService"; class StatusService { diff --git a/server/src/service/system/serviceRegistry.js b/server/src/service/v1/system/serviceRegistry.js similarity index 100% rename from server/src/service/system/serviceRegistry.js rename to server/src/service/v1/system/serviceRegistry.js diff --git a/server/src/service/system/settingsService.js b/server/src/service/v1/system/settingsService.js similarity index 100% rename from server/src/service/system/settingsService.js rename to server/src/service/v1/system/settingsService.js diff --git a/server/src/service/system/stringService.js b/server/src/service/v1/system/stringService.js similarity index 100% rename from server/src/service/system/stringService.js rename to server/src/service/v1/system/stringService.js diff --git a/server/src/service/system/translationService.js b/server/src/service/v1/system/translationService.js similarity index 100% rename from server/src/service/system/translationService.js rename to server/src/service/v1/system/translationService.js diff --git a/server/src/service/v2/business/CheckService.ts b/server/src/service/v2/business/CheckService.ts new file mode 100644 index 000000000..181fe048d --- /dev/null +++ b/server/src/service/v2/business/CheckService.ts @@ -0,0 +1,132 @@ +import { json } from "stream/consumers"; +import { ICheck, Check, Monitor, ISystemInfo, ICaptureInfo } from "../../../db/v1/models/index.js"; +import { MonitorType } from "../../../db/v1/models/Monitor.js"; +import { StatusResponse } from "../infrastructure/NetworkService.js"; +import type { ICapturePayload, ILighthousePayload } from "../infrastructure/NetworkService.js"; +import mongoose from "mongoose"; + +export interface ICheckService { + buildCheck: (statusResponse: StatusResponse, type: MonitorType) => Promise; + cleanupOrphanedChecks: () => Promise; +} + +class CheckService implements ICheckService { + private isCapturePayload = (payload: any): payload is ICapturePayload => { + if (!payload || typeof payload !== "object") return false; + + if (!("data" in payload) || typeof payload.data !== "object") { + return false; + } + + const data = payload.data as Partial; + if (!data.cpu || typeof data.cpu !== "object" || typeof data.cpu.usage_percent !== "number") { + return false; + } + s; + + if (!data.memory || typeof data.memory !== "object" || typeof data.memory.usage_percent !== "number") { + return false; + } + + if (data.disk && !Array.isArray(data.disk)) { + return false; + } + if (data.net && !Array.isArray(data.net)) { + return false; + } + + if (!("capture" in payload) || typeof payload.capture !== "object") return false; + const capture = payload.capture as Record; + if (typeof capture.version !== "string" || typeof capture.mode !== "string") return false; + + return true; + }; + + private isPagespeedPayload = (payload: any): payload is ILighthousePayload => { + if (!payload || typeof payload !== "object") return false; + + if (!("lighthouseResult" in payload) || typeof payload.lighthouseResult !== "object") { + return false; + } + return true; + }; + + private buildBaseCheck = (statusResponse: StatusResponse) => { + const monitorId = new mongoose.Types.ObjectId(statusResponse.monitorId); + const check = new Check({ + monitorId: monitorId, + type: statusResponse?.type, + status: statusResponse?.status, + message: statusResponse?.message, + responseTime: statusResponse?.responseTime, + timings: statusResponse?.timings, + }); + return check; + }; + + private buildInfrastructureCheck = (statusResponse: StatusResponse) => { + if (!this.isCapturePayload(statusResponse.payload)) { + throw new Error("Invalid payload for infrastructure monitor"); + } + const check = this.buildBaseCheck(statusResponse); + check.system = statusResponse.payload.data; + check.capture = statusResponse.payload.capture; + return check; + }; + + private buildPagespeedCheck = (statusResponse: StatusResponse) => { + if (!this.isPagespeedPayload(statusResponse.payload)) { + throw new Error("Invalid payload for pagespeed monitor"); + } + const check = this.buildBaseCheck(statusResponse); + const lighthouseResult = statusResponse?.payload?.lighthouseResult; + check.lighthouse = { + accessibility: lighthouseResult?.categories?.accessibility?.score || 0, + bestPractices: lighthouseResult?.categories?.["best-practices"]?.score || 0, + seo: lighthouseResult?.categories?.seo?.score || 0, + performance: lighthouseResult?.categories?.performance?.score || 0, + audits: { + cls: lighthouseResult?.audits?.["cumulative-layout-shift"] || {}, + si: lighthouseResult?.audits?.["speed-index"] || {}, + fcp: lighthouseResult?.audits?.["first-contentful-paint"] || {}, + lcp: lighthouseResult?.audits?.["largest-contentful-paint"] || {}, + tbt: lighthouseResult?.audits?.["total-blocking-time"] || {}, + }, + }; + return check; + }; + + buildCheck = async (statusResponse: StatusResponse, type: MonitorType): Promise => { + switch (type) { + case "infrastructure": + return this.buildInfrastructureCheck(statusResponse as StatusResponse); + + case "pagespeed": + return this.buildPagespeedCheck(statusResponse as StatusResponse); + case "http": + case "https": + return this.buildBaseCheck(statusResponse); + + case "ping": + return this.buildBaseCheck(statusResponse); + default: + throw new Error(`Unsupported monitor type: ${type}`); + } + }; + + cleanupOrphanedChecks = async () => { + try { + const monitorIds = await Monitor.find().distinct("_id"); + const result = await Check.deleteMany({ + monitorId: { $nin: monitorIds }, + }); + console.log(`Deleted ${result.deletedCount} orphaned Checks.`); + return true; + } catch (error) { + console.error("Error cleaning up orphaned Checks:", error); + return false; + } + }; +} + +export default CheckService; diff --git a/server/src/service/v2/business/MonitorService.ts b/server/src/service/v2/business/MonitorService.ts new file mode 100644 index 000000000..6e48e79e5 --- /dev/null +++ b/server/src/service/v2/business/MonitorService.ts @@ -0,0 +1,464 @@ +import mongoose from "mongoose"; + +import { IMonitor, Monitor, ITokenizedUser, MonitorStats, Check } from "../../../db/v1/models/index.js"; +import ApiError from "../../../utils/ApiError.js"; +import { IJobQueue } from "../infrastructure/JobQueue.js"; +import { MonitorWithChecksResponse } from "../../../types/monitor-response-with-checks.js"; +import { MonitorStatus, MonitorType } from "../../../db/v1/models/monitors/Monitor.js"; +export interface IMonitorService { + create: (tokenizedUser: ITokenizedUser, monitorData: IMonitor) => Promise; + getAll: () => Promise; + getAllEmbedChecks: (page: number, limit: number, type: MonitorType[]) => Promise; + get: (monitorId: string) => Promise; + getEmbedChecks: (monitorId: string, range: string, status?: string) => Promise; + toggleActive: (monitorId: string, tokenizedUser: ITokenizedUser) => Promise; + update: (tokenizedUser: ITokenizedUser, monitorId: string, updateData: Partial) => Promise; + delete: (monitorId: string) => Promise; +} + +class MonitorService implements IMonitorService { + private jobQueue: IJobQueue; + constructor(jobQueue: IJobQueue) { + this.jobQueue = jobQueue; + } + + create = async (tokenizedUser: ITokenizedUser, monitorData: IMonitor) => { + const monitor = await Monitor.create({ + ...monitorData, + createdBy: tokenizedUser.sub, + updatedBy: tokenizedUser.sub, + }); + await MonitorStats.create({ + monitorId: monitor._id, + currentStreakStartedAt: Date.now(), + }); + await this.jobQueue.addJob(monitor); + return monitor; + }; + + getAll = async () => { + return Monitor.find(); + }; + + getAllEmbedChecks = async (page: number, limit: number, type: MonitorType[] = []) => { + const skip = (page - 1) * limit; + let find = {}; + if (type.length > 0) find = { type: { $in: type } }; + const monitors = await Monitor.find(find).skip(skip).limit(limit); + return monitors; + }; + + get = async (monitorId: string) => { + const monitor = await Monitor.findById(monitorId); + if (!monitor) { + throw new ApiError("Monitor not found", 404); + } + return monitor; + }; + + private getStartDate(range: string): Date { + const now = new Date(); + switch (range) { + case "30m": + return new Date(now.getTime() - 30 * 60 * 1000); + case "24h": + return new Date(now.getTime() - 24 * 60 * 60 * 1000); + case "7d": + return new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000); + case "30d": + return new Date(now.getTime() - 30 * 24 * 60 * 60 * 1000); + default: + throw new ApiError("Invalid range parameter", 400); + } + } + + private getDateFormat(range: string): string { + switch (range) { + case "30m": + return "%Y-%m-%dT%H:%M:00Z"; + case "24h": + case "7d": + return "%Y-%m-%dT%H:00:00Z"; + case "30d": + return "%Y-%m-%d"; + default: + throw new ApiError("Invalid range parameter", 400); + } + } + + private getBaseGroup = (dateFormat: string): Record => { + return { + _id: { $dateToString: { format: dateFormat, date: "$createdAt" } }, + count: { $sum: 1 }, + avgResponseTime: { $avg: "$responseTime" }, + }; + }; + + private getBaseProjection = (): object => { + return { status: 1, responseTime: 1, createdAt: 1 }; + }; + + private getPageSpeedGroup = (dateFormat: string): Record => { + return { + _id: { $dateToString: { format: dateFormat, date: "$createdAt" } }, + count: { $sum: 1 }, + avgResponseTime: { $avg: "$responseTime" }, + accessibility: { $avg: "$lighthouse.accessibility" }, + bestPractices: { $avg: "$lighthouse.bestPractices" }, + seo: { $avg: "$lighthouse.seo" }, + performance: { $avg: "$lighthouse.performance" }, + cls: { $avg: "$lighthouse.audits.cls.score" }, + si: { $avg: "$lighthouse.audits.si.score" }, + fcp: { $avg: "$lighthouse.audits.fcp.score" }, + lcp: { $avg: "$lighthouse.audits.lcp.score" }, + tbt: { $avg: "$lighthouse.audits.tbt.score" }, + }; + }; + + private getPageSpeedProjection = (): object => { + const projectStage: any = { status: 1, responseTime: 1, createdAt: 1 }; + projectStage["lighthouse.accessibility"] = 1; + projectStage["lighthouse.seo"] = 1; + projectStage["lighthouse.bestPractices"] = 1; + projectStage["lighthouse.performance"] = 1; + projectStage["lighthouse.audits.cls.score"] = 1; + projectStage["lighthouse.audits.si.score"] = 1; + projectStage["lighthouse.audits.fcp.score"] = 1; + projectStage["lighthouse.audits.lcp.score"] = 1; + projectStage["lighthouse.audits.tbt.score"] = 1; + return projectStage; + }; + + private getInfraGroup = (dateFormat: string): Record => { + return { + _id: { $dateToString: { format: dateFormat, date: "$createdAt" } }, + count: { $sum: 1 }, + avgResponseTime: { $avg: "$responseTime" }, + physicalCores: { $last: "$system.cpu.physical_core" }, + logicalCores: { $last: "$system.cpu.logical_core" }, + frequency: { $avg: "$system.cpu.frequency" }, + currentFrequency: { $last: "$system.cpu.current_frequency" }, + tempsArrays: { $push: "$system.cpu.temperature" }, + freePercent: { $avg: "$system.cpu.free_percent" }, + usedPercent: { $avg: "$system.cpu.usage_percent" }, + total_bytes: { $last: "$system.memory.total_bytes" }, + available_bytes: { $last: "$system.memory.available_bytes" }, + used_bytes: { $last: "$system.memory.used_bytes" }, + memory_usage_percent: { $avg: "$system.memory.usage_percent" }, + disksArray: { $push: "$system.disk" }, + os: { $last: "$system.host.os" }, + platform: { $last: "$system.host.platform" }, + kernel_version: { $last: "$system.host.kernel_version" }, + pretty_name: { $last: "$system.host.pretty_name" }, + netsArray: { $push: "$system.net" }, + }; + }; + + private getInfraProjection = (): object => { + const projectStage: any = { status: 1, responseTime: 1, createdAt: 1 }; + projectStage["system.cpu.physical_core"] = 1; + projectStage["system.cpu.logical_core"] = 1; + projectStage["system.cpu.frequency"] = 1; + projectStage["system.cpu.current_frequency"] = 1; + projectStage["system.cpu.temperature"] = 1; + projectStage["system.cpu.free_percent"] = 1; + projectStage["system.cpu.usage_percent"] = 1; + projectStage["system.memory.total_bytes"] = 1; + projectStage["system.memory.available_bytes"] = 1; + projectStage["system.memory.used_bytes"] = 1; + projectStage["system.memory.usage_percent"] = 1; + projectStage["system.disk"] = 1; + projectStage["system.host.os"] = 1; + projectStage["system.host.platform"] = 1; + projectStage["system.host.kernel_version"] = 1; + projectStage["system.host.pretty_name"] = 1; + projectStage["system.net"] = 1; + return projectStage; + }; + + private getFinalProjection = (type: string): object => { + if (type === "pagespeed") { + return { + _id: 1, + count: 1, + avgResponseTime: 1, + accessibility: "$accessibility", + seo: "$seo", + bestPractices: "$bestPractices", + performance: "$performance", + cls: "$cls", + si: "$si", + fcp: "$fcp", + lcp: "$lcp", + tbt: "$tbt", + }; + } + + if (type === "infrastructure") { + return { + _id: 1, + count: 1, + avgResponseTime: 1, + cpu: { + physicalCores: "$physicalCores", + logicalCores: "$logicalCores", + frequency: "$frequency", + currentFrequency: "$currentFrequency", + temperatures: { + $map: { + input: { + $range: [0, { $size: { $arrayElemAt: ["$tempsArrays", 0] } }], + }, + as: "idx", + in: { + $avg: { + $map: { + input: "$tempsArrays", + as: "arr", + in: { $arrayElemAt: ["$$arr", "$$idx"] }, + }, + }, + }, + }, + }, + freePercent: "$freePercent", + usedPercent: "$usedPercent", + }, + memory: { + total_bytes: "$total_bytes", + available_bytes: "$available_bytes", + used_bytes: "$used_bytes", + usage_percent: "$memory_usage_percent", + }, + disks: { + $map: { + input: { + $range: [0, { $size: { $arrayElemAt: ["$disksArray", 0] } }], + }, + as: "idx", + in: { + $let: { + vars: { + diskGroup: { + $map: { + input: "$disksArray", + as: "diskArr", + in: { $arrayElemAt: ["$$diskArr", "$$idx"] }, + }, + }, + }, + in: { + device: { $arrayElemAt: ["$$diskGroup.device", 0] }, + total_bytes: { $avg: "$$diskGroup.total_bytes" }, + free_bytes: { $avg: "$$diskGroup.free_bytes" }, + used_bytes: { $avg: "$$diskGroup.used_bytes" }, + usage_percent: { $avg: "$$diskGroup.usage_percent" }, + total_inodes: { $avg: "$$diskGroup.total_inodes" }, + free_inodes: { $avg: "$$diskGroup.free_inodes" }, + used_inodes: { $avg: "$$diskGroup.used_inodes" }, + inodes_usage_percent: { + $avg: "$$diskGroup.inodes_usage_percent", + }, + read_bytes: { $avg: "$$diskGroup.read_bytes" }, + write_bytes: { $avg: "$$diskGroup.write_bytes" }, + read_time: { $avg: "$$diskGroup.read_time" }, + write_time: { $avg: "$$diskGroup.write_time" }, + }, + }, + }, + }, + }, + host: { + os: "$os", + platform: "$platform", + kernel_version: "$kernel_version", + pretty_name: "$pretty_name", + }, + net: { + $map: { + input: { + $range: [0, { $size: { $arrayElemAt: ["$netsArray", 0] } }], + }, + as: "idx", + in: { + $let: { + vars: { + netGroup: { + $map: { + input: "$netsArray", + as: "netArr", + in: { $arrayElemAt: ["$$netArr", "$$idx"] }, + }, + }, + }, + in: { + name: { $arrayElemAt: ["$$netGroup.name", 0] }, + bytes_sent: { $avg: "$$netGroup.bytes_sent" }, + bytes_recv: { $avg: "$$netGroup.bytes_recv" }, + packets_sent: { $avg: "$$netGroup.packets_sent" }, + packets_recv: { $avg: "$$netGroup.packets_recv" }, + err_in: { $avg: "$$netGroup.err_in" }, + err_out: { $avg: "$$netGroup.err_out" }, + drop_in: { $avg: "$$netGroup.drop_in" }, + drop_out: { $avg: "$$netGroup.drop_out" }, + fifo_in: { $avg: "$$netGroup.fifo_in" }, + fifo_out: { $avg: "$$netGroup.fifo_out" }, + }, + }, + }, + }, + }, + }; + } + return {}; + }; + + getEmbedChecks = async (monitorId: string, range: string, status: string | undefined): Promise => { + const monitor = await Monitor.findById(monitorId); + if (!monitor) { + throw new ApiError("Monitor not found", 404); + } + const startDate = this.getStartDate(range); + const dateFormat = this.getDateFormat(range); + + // Build match stage + const matchStage: { + monitorId: mongoose.Types.ObjectId; + createdAt: { $gte: Date }; + status?: string; + } = { + monitorId: monitor._id, + createdAt: { $gte: startDate }, + }; + + if (status) { + matchStage.status = status; + } + + let groupClause; + + if (monitor.type === "pagespeed") { + groupClause = this.getPageSpeedGroup(dateFormat); + } else if (monitor.type === "infrastructure") { + groupClause = this.getInfraGroup(dateFormat); + } else { + groupClause = this.getBaseGroup(dateFormat); + } + + let projectStage; + if (monitor.type === "pagespeed") { + projectStage = this.getPageSpeedProjection(); + } else if (monitor.type === "infrastructure") { + projectStage = this.getInfraProjection(); + } else { + projectStage = this.getBaseProjection(); + } + + let finalProjection = {}; + if (monitor.type === "pagespeed" || monitor.type === "infrastructure") { + finalProjection = this.getFinalProjection(monitor.type); + } else { + finalProjection = { _id: 1, count: 1, avgResponseTime: 1 }; + } + + const checks = await Check.aggregate([ + { + $match: matchStage, + }, + { $sort: { createdAt: 1 } }, + { $project: projectStage }, + { $group: groupClause }, + { $sort: { _id: -1 } }, + { + $project: finalProjection, + }, + ]); + + // Get monitor stats + const monitorStats = await MonitorStats.findOne({ + monitorId: monitor._id, + }).lean(); + + if (!monitorStats) { + throw new ApiError("Monitor stats not found", 404); + } + + return { + monitor: monitor.toObject(), + checks, + stats: monitorStats, + }; + }; + + async toggleActive(id: string, tokenizedUser: ITokenizedUser) { + const pendingStatus: MonitorStatus = "initializing"; + const updatedMonitor = await Monitor.findOneAndUpdate( + { _id: id }, + [ + { + $set: { + isActive: { $not: "$isActive" }, + status: pendingStatus, + updatedBy: tokenizedUser.sub, + updatedAt: new Date(), + }, + }, + ], + { new: true } + ); + + if (!updatedMonitor) { + throw new ApiError("Monitor not found", 404); + } + + await this.jobQueue.updateJob(updatedMonitor); + + if (updatedMonitor?.isActive) { + await this.jobQueue.resumeJob(updatedMonitor); + } else { + await this.jobQueue.pauseJob(updatedMonitor); + } + return updatedMonitor; + } + + async update(tokenizedUser: ITokenizedUser, monitorId: string, updateData: Partial) { + const allowedFields: (keyof IMonitor)[] = ["name", "interval", "isActive", "n", "m", "notificationChannels"]; + const safeUpdate: Partial = {}; + + for (const field of allowedFields) { + if (updateData[field] !== undefined) { + (safeUpdate as any)[field] = updateData[field]; + } + } + + const updatedMonitor = await Monitor.findByIdAndUpdate( + monitorId, + { + $set: { + ...safeUpdate, + updatedAt: new Date(), + updatedBy: tokenizedUser.sub, + }, + }, + { new: true, runValidators: true } + ); + + if (!updatedMonitor) { + throw new ApiError("Monitor not found", 404); + } + await this.jobQueue.updateJob(updatedMonitor); + return updatedMonitor; + } + + async delete(monitorId: string) { + const monitor = await Monitor.findById(monitorId); + if (!monitor) { + throw new ApiError("Monitor not found", 404); + } + await monitor.deleteOne(); + await this.jobQueue.deleteJob(monitor); + return true; + } +} + +export default MonitorService; From 059408f098a9307884cc34c7bc4d9d29296b7a7b Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Tue, 23 Sep 2025 14:40:57 -0700 Subject: [PATCH 026/101] add v2 models --- server/src/db/v2/models/auth/Role.ts | 50 +++ server/src/db/v2/models/auth/User.ts | 108 +++++++ server/src/db/v2/models/checks/Check.ts | 297 ++++++++++++++++++ server/src/db/v2/models/index.ts | 33 ++ server/src/db/v2/models/invite/Invite.ts | 43 +++ .../db/v2/models/maintenance/Maintenance.ts | 39 +++ server/src/db/v2/models/monitors/Monitor.ts | 100 ++++++ .../src/db/v2/models/monitors/MonitorStats.ts | 77 +++++ .../NotificationChannel.ts | 50 +++ 9 files changed, 797 insertions(+) create mode 100644 server/src/db/v2/models/auth/Role.ts create mode 100644 server/src/db/v2/models/auth/User.ts create mode 100644 server/src/db/v2/models/checks/Check.ts create mode 100644 server/src/db/v2/models/index.ts create mode 100644 server/src/db/v2/models/invite/Invite.ts create mode 100644 server/src/db/v2/models/maintenance/Maintenance.ts create mode 100644 server/src/db/v2/models/monitors/Monitor.ts create mode 100644 server/src/db/v2/models/monitors/MonitorStats.ts create mode 100644 server/src/db/v2/models/notification-channel/NotificationChannel.ts diff --git a/server/src/db/v2/models/auth/Role.ts b/server/src/db/v2/models/auth/Role.ts new file mode 100644 index 000000000..1031de8b2 --- /dev/null +++ b/server/src/db/v2/models/auth/Role.ts @@ -0,0 +1,50 @@ +import mongoose, { Schema, Document, Types } from "mongoose"; + +export interface IRole extends Document { + _id: Types.ObjectId; + name: string; + description?: string; + permissions: string[]; + isSystem: boolean; + isActive: boolean; + createdAt: Date; + updatedAt: Date; +} + +const roleSchema = new Schema( + { + name: { + type: String, + required: true, + trim: true, + maxlength: 50, + }, + description: { + type: String, + trim: true, + maxlength: 200, + }, + + permissions: [ + { + type: String, + required: true, + }, + ], + isSystem: { + type: Boolean, + default: false, + }, + isActive: { + type: Boolean, + default: true, + }, + }, + { + timestamps: true, + } +); + +roleSchema.index({ name: 1 }, { unique: true }); + +export const Role = mongoose.model("Role_v2", roleSchema); diff --git a/server/src/db/v2/models/auth/User.ts b/server/src/db/v2/models/auth/User.ts new file mode 100644 index 000000000..b72a29e70 --- /dev/null +++ b/server/src/db/v2/models/auth/User.ts @@ -0,0 +1,108 @@ +import mongoose, { Schema, Document, Types } from "mongoose"; + +export interface ITokenizedUser { + sub: string; + roles: string[]; +} +export interface IUser extends Document { + _id: Types.ObjectId; + email: string; + firstName: string; + lastName: string; + passwordHash: string; + roles: Types.ObjectId[]; + profile: { + avatar?: string; + bio?: string; + timezone: string; + locale: string; + }; + preferences: { + theme: "light" | "dark" | "system"; + }; + lastLoginAt?: Date; + isActive: boolean; + isVerified: boolean; + createdAt: Date; + updatedAt: Date; +} + +const userSchema = new Schema( + { + email: { + type: String, + required: true, + unique: true, + trim: true, + lowercase: true, + }, + firstName: { + type: String, + required: true, + trim: true, + maxlength: 50, + }, + lastName: { + type: String, + required: true, + trim: true, + maxlength: 50, + }, + passwordHash: { + type: String, + required: true, + }, + roles: [ + { + type: Schema.Types.ObjectId, + ref: "Role_v2", + }, + ], + profile: { + avatar: { + type: String, + }, + bio: { + type: String, + maxlength: 200, + }, + timezone: { + type: String, + default: "UTC", + }, + locale: { + type: String, + default: "en", + }, + }, + preferences: { + theme: { + type: String, + enum: ["light", "dark", "system"], + default: "system", + }, + }, + lastLoginAt: { + type: Date, + }, + isActive: { + type: Boolean, + default: true, + }, + isVerified: { + type: Boolean, + default: false, + }, + }, + { + timestamps: true, + toJSON: { + transform: function (_doc, ret) { + delete ret.passwordHash; + return ret; + }, + }, + } +); + +export const User = mongoose.model("User_v2", userSchema); diff --git a/server/src/db/v2/models/checks/Check.ts b/server/src/db/v2/models/checks/Check.ts new file mode 100644 index 000000000..83bbc9f91 --- /dev/null +++ b/server/src/db/v2/models/checks/Check.ts @@ -0,0 +1,297 @@ +import mongoose, { Schema, Document, Types } from "mongoose"; +import { MonitorType, MonitorTypes, MonitorStatus, MonitorStatuses } from "../monitors/Monitor.js"; +import type { Response } from "got"; +export type GotTimings = Response["timings"]; +export interface ITimingPhases { + wait: number; + dns: number; + tcp: number; + tls: number; + request: number; + firstByte: number; + download: number; + total: number; +} + +export interface ICpuInfo { + physical_core: number; + logical_core: number; + frequency: number; + current_frequency: number; + temperature: number[]; // per-core temps + free_percent: number; + usage_percent: number; +} + +export interface IMemoryInfo { + total_bytes: number; + available_bytes: number; + used_bytes: number; + usage_percent: number; +} + +export interface IHostInfo { + os?: string; + platform?: string; + kernel_version?: string; + pretty_name?: string; +} + +export interface IDiskInfo { + device: string; + total_bytes: number; + free_bytes: number; + used_bytes: number; + usage_percent: number; + total_inodes?: number; + free_inodes?: number; + used_inodes?: number; + inodes_usage_percent?: number; + read_bytes?: number; + write_bytes?: number; + read_time?: number; + write_time?: number; +} + +export interface INetInfo { + name: string; + bytes_sent: number; + bytes_recv: number; + packets_sent: number; + packets_recv: number; + err_in: number; + err_out: number; + drop_in: number; + drop_out: number; + fifo_in: number; + fifo_out: number; +} + +export interface ICaptureInfo { + version?: string; + mode?: string; +} + +export interface ISystemInfo { + cpu: ICpuInfo; + memory: IMemoryInfo; + disk: IDiskInfo[]; + host: IHostInfo; + net: INetInfo[]; +} + +export interface ILighthouseAudit { + id?: string; + title?: string; + score?: number | null; + displayValue?: string; + numericValue?: number; + numericUnit?: string; +} +export interface ILighthouseCategories { + accessibility?: { score?: number | null }; + "best-practices"?: { score?: number | null }; + seo?: { score?: number | null }; + performance?: { score?: number | null }; +} + +export interface ILighthouseResult { + categories?: ILighthouseCategories; + audits?: Record; +} + +export interface ICheckLighthouseFields { + accessibility: number; + bestPractices: number; + seo: number; + performance: number; + audits: { + cls: ILighthouseAudit; + si: ILighthouseAudit; + fcp: ILighthouseAudit; + lcp: ILighthouseAudit; + tbt: ILighthouseAudit; + }; +} + +export interface ICheck extends Document { + _id: Types.ObjectId; + monitorId: Types.ObjectId; + type: MonitorType; + status: MonitorStatus; + message: string; + responseTime?: number; // in ms + timings?: GotTimings; + httpStatusCode?: number; + errorMessage?: string; + ack: boolean; + ackAt?: Date; + ackBy?: Types.ObjectId; + expiry: Date; + createdAt: Date; + updatedAt: Date; + system?: ISystemInfo; + capture?: ICaptureInfo; + lighthouse?: ICheckLighthouseFields; +} + +const CheckSchema = new Schema( + { + monitorId: { type: Schema.Types.ObjectId, ref: "Monitor_v2", required: true }, + + type: { + type: String, + required: true, + enum: MonitorTypes, + }, + status: { + type: String, + required: true, + enum: MonitorStatuses, + }, + message: { type: String, trim: true }, + responseTime: { type: Number }, + timings: { + start: { type: Date }, + socket: { type: Date }, + lookup: { type: Date }, + connect: { type: Date }, + secureConnect: { type: Date }, + response: { type: Date }, + end: { type: Date }, + phases: { + wait: { type: Number }, + dns: { type: Number }, + tcp: { type: Number }, + tls: { type: Number }, + request: { type: Number }, + firstByte: { type: Number }, + download: { type: Number }, + total: { type: Number }, + }, + }, + system: { + type: { + cpu: { + physical_core: { type: Number }, + logical_core: { type: Number }, + frequency: { type: Number }, + current_frequency: { type: Number }, + temperature: [{ type: Number }], + free_percent: { type: Number }, + usage_percent: { type: Number }, + }, + memory: { + total_bytes: { type: Number }, + available_bytes: { type: Number }, + used_bytes: { type: Number }, + usage_percent: { type: Number }, + }, + disk: [ + { + device: { type: String }, + total_bytes: { type: Number }, + free_bytes: { type: Number }, + used_bytes: { type: Number }, + usage_percent: { type: Number }, + total_inodes: { type: Number }, + free_inodes: { type: Number }, + used_inodes: { type: Number }, + inodes_usage_percent: { type: Number }, + read_bytes: { type: Number }, + write_bytes: { type: Number }, + read_time: { type: Number }, + write_time: { type: Number }, + }, + ], + host: { + os: { type: String }, + platform: { type: String }, + kernel_version: { type: String }, + pretty_name: { type: String }, + }, + net: [ + { + name: { type: String }, + bytes_sent: { type: Number }, + bytes_recv: { type: Number }, + packets_sent: { type: Number }, + packets_recv: { type: Number }, + err_in: { type: Number }, + err_out: { type: Number }, + drop_in: { type: Number }, + drop_out: { type: Number }, + fifo_in: { type: Number }, + fifo_out: { type: Number }, + }, + ], + }, + required: false, + }, + + capture: { + type: { + version: { type: String }, + mode: { type: String }, + }, + required: false, + }, + lighthouse: { + accessibility: { type: Number, required: false }, + bestPractices: { type: Number, required: false }, + seo: { type: Number, required: false }, + performance: { type: Number, required: false }, + audits: { + cls: { + type: Object, + }, + si: { + type: Object, + }, + fcp: { + type: Object, + }, + lcp: { + type: Object, + }, + tbt: { + type: Object, + }, + }, + type: { + accessibility: { type: Number }, + bestPractices: { type: Number }, + seo: { type: Number }, + performance: { type: Number }, + audits: { + cls: { type: Object }, + si: { type: Object }, + fcp: { type: Object }, + lcp: { type: Object }, + tbt: { type: Object }, + }, + }, + required: false, + }, + + httpStatusCode: { type: Number }, + errorMessage: { type: String, trim: true }, + ack: { type: Boolean, required: true, default: false }, + ackAt: { type: Date }, + ackBy: { type: Schema.Types.ObjectId, ref: "User_v2" }, + expiry: { + type: Date, + default: Date.now, + expires: 60 * 60 * 24 * 30, + }, + }, + { timestamps: true } +); + +CheckSchema.index({ monitorId: 1, createdAt: -1 }); +CheckSchema.index({ status: 1 }); +CheckSchema.index({ status: 1, ack: 1 }); +CheckSchema.index({ ack: 1, ackAt: 1 }); +CheckSchema.index({ createdAt: -1 }); + +export const Check = mongoose.model("Check_v2", CheckSchema); diff --git a/server/src/db/v2/models/index.ts b/server/src/db/v2/models/index.ts new file mode 100644 index 000000000..fbc05c10f --- /dev/null +++ b/server/src/db/v2/models/index.ts @@ -0,0 +1,33 @@ +export { User } from "./auth/User.js"; +export type { IUser } from "./auth/User.js"; +export type { ITokenizedUser } from "./auth/User.js"; +export { Role } from "./auth/Role.js"; +export type { IRole } from "./auth/Role.js"; +export { connectDatabase, disconnectDatabase } from "../index.js"; +export { Monitor } from "./monitors/Monitor.js"; +export { MonitorStatuses } from "./monitors/Monitor.js"; +export type { IMonitor } from "./monitors/Monitor.js"; +export { Check } from "./checks/Check.js"; +export type { + ICheck, + ISystemInfo, + ICaptureInfo, + INetInfo, + IDiskInfo, + IHostInfo, + IMemoryInfo, + ICpuInfo, + ILighthouseAudit, + ITimingPhases, + ILighthouseCategories, + ILighthouseResult, + ICheckLighthouseFields, +} from "./checks/Check.js"; +export type { IMonitorStats } from "./monitors/MonitorStats.js"; +export { MonitorStats } from "./monitors/MonitorStats.js"; +export type { INotificationChannel } from "./notification-channel/NotificationChannel.js"; +export { NotificationChannel } from "./notification-channel/NotificationChannel.js"; +export type { IMaintenance } from "./maintenance/Maintenance.js"; +export { Maintenance } from "./maintenance/Maintenance.js"; +export type { IInvite } from "./invite/Invite.js"; +export { Invite } from "./invite/Invite.js"; diff --git a/server/src/db/v2/models/invite/Invite.ts b/server/src/db/v2/models/invite/Invite.ts new file mode 100644 index 000000000..2bc569900 --- /dev/null +++ b/server/src/db/v2/models/invite/Invite.ts @@ -0,0 +1,43 @@ +import mongoose, { Schema, Document, Types } from "mongoose"; + +export interface IInvite extends Document { + _id: Types.ObjectId; + email: string; + tokenHash: string; + roles: Types.ObjectId[]; + createdBy: Types.ObjectId; + updatedBy: Types.ObjectId; + expiry: Date; + createdAt: Date; + updatedAt: Date; +} + +const InviteSchema = new Schema( + { + email: { + type: String, + required: true, + unique: true, + trim: true, + lowercase: true, + }, + roles: [ + { + type: Schema.Types.ObjectId, + ref: "Role_v2", + required: true, + }, + ], + tokenHash: { type: String, required: true, unique: true }, + expiry: { + type: Date, + default: Date.now, + expires: 60 * 60 * 24, + }, + createdBy: { type: Schema.Types.ObjectId, ref: "User_v2", required: true }, + updatedBy: { type: Schema.Types.ObjectId, ref: "User_v2", required: true }, + }, + { timestamps: true } +); + +export const Invite = mongoose.model("Invite_v2", InviteSchema); diff --git a/server/src/db/v2/models/maintenance/Maintenance.ts b/server/src/db/v2/models/maintenance/Maintenance.ts new file mode 100644 index 000000000..c2ce33a2b --- /dev/null +++ b/server/src/db/v2/models/maintenance/Maintenance.ts @@ -0,0 +1,39 @@ +import mongoose, { Schema, Document, Types } from "mongoose"; + +export interface IMaintenance extends Document { + _id: Types.ObjectId; + name: string; + isActive: boolean; + monitors: Types.ObjectId[]; + startTime: Date; + endTime: Date; + createdBy: Types.ObjectId; + updatedBy: Types.ObjectId; + createdAt: Date; + updatedAt: Date; +} + +const MaintenanceSchema = new Schema( + { + name: { type: String, required: true, trim: true }, + isActive: { type: Boolean, required: true, default: true }, + monitors: [ + { + type: Schema.Types.ObjectId, + ref: "Monitor_v2", + required: true, + }, + ], + startTime: { type: Date, required: true }, + endTime: { type: Date, required: true }, + createdBy: { type: Schema.Types.ObjectId, ref: "User_v2", required: true }, + updatedBy: { type: Schema.Types.ObjectId, ref: "User_v2", required: true }, + }, + { timestamps: true } +); + +MaintenanceSchema.index({ isActive: 1 }); +MaintenanceSchema.index({ startTime: 1 }); +MaintenanceSchema.index({ endTime: 1 }); + +export const Maintenance = mongoose.model("Maintenance_v2", MaintenanceSchema); diff --git a/server/src/db/v2/models/monitors/Monitor.ts b/server/src/db/v2/models/monitors/Monitor.ts new file mode 100644 index 000000000..055d36a1b --- /dev/null +++ b/server/src/db/v2/models/monitors/Monitor.ts @@ -0,0 +1,100 @@ +import mongoose, { Schema, Document, Types } from "mongoose"; +import { Check, MonitorStats } from "../index.js"; + +export const MonitorTypes = ["http", "https", "ping", "infrastructure", "pagespeed"] as const; +export type MonitorType = (typeof MonitorTypes)[number]; + +export const MonitorStatuses = ["up", "down", "paused", "initializing"] as const; +export type MonitorStatus = (typeof MonitorStatuses)[number]; +export interface IMonitor extends Document { + _id: Types.ObjectId; + name: string; + url: string; + secret?: string; + type: MonitorType; + interval: number; // in ms + isActive: boolean; + status: MonitorStatus; + n: number; // Number of consecutive successes required to change status + lastCheckedAt?: Date; + latestChecks: { + status: MonitorStatus; + responseTime: number; + checkedAt: Date; + }[]; + notificationChannels?: Types.ObjectId[]; + createdBy: Types.ObjectId; + updatedBy: Types.ObjectId; + createdAt: Date; + updatedAt: Date; +} + +const MonitorSchema = new Schema( + { + name: { type: String, required: true, trim: true, maxlength: 100 }, + url: { type: String, required: true, trim: true }, + secret: { type: String, required: false }, + type: { + type: String, + required: true, + enum: MonitorTypes, + }, + interval: { type: Number, required: true, default: 60000 }, + isActive: { type: Boolean, required: true, default: true }, + status: { + type: String, + required: true, + default: "initializing", + enum: MonitorStatuses, + }, + n: { type: Number, required: true, default: 1 }, + lastCheckedAt: { type: Date }, + latestChecks: { + type: [ + { + status: { + type: String, + required: true, + enum: MonitorStatuses, + }, + responseTime: { type: Number, required: true }, + checkedAt: { type: Date, required: true }, + }, + ], + default: [], + }, + notificationChannels: [ + { + type: Schema.Types.ObjectId, + ref: "NotificationChannel_v2", + required: false, + default: [], + }, + ], + + createdBy: { type: Schema.Types.ObjectId, ref: "User_v2", required: true }, + updatedBy: { type: Schema.Types.ObjectId, ref: "User_v2", required: true }, + }, + { timestamps: true } +); + +MonitorSchema.pre("deleteOne", { document: true, query: false }, async function (next) { + try { + const monitorId = this._id; + await Check.deleteMany({ monitorId }); + await MonitorStats.deleteMany({ monitorId }); + next(); + } catch (error: any) { + next(error); + } +}); + +MonitorSchema.index({ isActive: 1 }); +MonitorSchema.index({ status: 1 }); +MonitorSchema.index({ type: 1 }); +MonitorSchema.index({ lastCheckedAt: 1 }); +MonitorSchema.index({ isActive: 1, status: 1 }); +MonitorSchema.index({ createdBy: 1 }); +MonitorSchema.index({ updatedBy: 1 }); + +export const Monitor = mongoose.model("Monitor_v2", MonitorSchema); diff --git a/server/src/db/v2/models/monitors/MonitorStats.ts b/server/src/db/v2/models/monitors/MonitorStats.ts new file mode 100644 index 000000000..56e76f9ab --- /dev/null +++ b/server/src/db/v2/models/monitors/MonitorStats.ts @@ -0,0 +1,77 @@ +import mongoose, { Schema, Document, Types } from "mongoose"; +import { MonitorStatus, MonitorStatuses } from "./Monitor.js"; + +export interface IMonitorStats extends mongoose.Document { + monitorId: mongoose.Types.ObjectId; + avgResponseTime: number; + maxResponseTime: number; + totalChecks: number; + totalUpChecks: number; + totalDownChecks: number; + uptimePercentage: number; + lastCheckTimestamp: number; + lastResponseTime: number; + timeOfLastFailure: number; + currentStreak: number; + currentStreakStatus: MonitorStatus; + currentStreakStartedAt: number; + createdAt: Date; + updatedAt: Date; +} + +const MonitorStatsSchema = new Schema( + { + monitorId: { + type: mongoose.Schema.Types.ObjectId, + ref: "Monitor_v2", + immutable: true, + index: true, + }, + avgResponseTime: { + type: Number, + default: 0, + }, + maxResponseTime: { + type: Number, + default: 0, + }, + lastResponseTime: { + type: Number, + default: 0, + }, + totalChecks: { + type: Number, + default: 0, + }, + totalUpChecks: { + type: Number, + default: 0, + }, + totalDownChecks: { + type: Number, + default: 0, + }, + uptimePercentage: { + type: Number, + default: 0, + }, + lastCheckTimestamp: { + type: Number, + default: 0, + }, + timeOfLastFailure: { + type: Number, + default: 0, + }, + currentStreak: { type: Number, required: false, default: 0 }, + currentStreakStatus: { + type: String, + required: false, + enum: MonitorStatuses, + }, + currentStreakStartedAt: { type: Number, required: false }, + }, + { timestamps: true } +); + +export const MonitorStats = mongoose.model("MonitorStats_v2", MonitorStatsSchema); diff --git a/server/src/db/v2/models/notification-channel/NotificationChannel.ts b/server/src/db/v2/models/notification-channel/NotificationChannel.ts new file mode 100644 index 000000000..e77639608 --- /dev/null +++ b/server/src/db/v2/models/notification-channel/NotificationChannel.ts @@ -0,0 +1,50 @@ +import mongoose, { Schema, Document, Types } from "mongoose"; +export const ChannelTypes = ["email", "slack", "discord", "webhook"] as const; +export type ChannelType = (typeof ChannelTypes)[number]; + +export interface INotificationChannelConfig { + url?: string; // For webhook, slack, discord + emailAddress?: string; // For email +} + +export interface INotificationChannel { + _id: Types.ObjectId; + name: string; + type: ChannelType; + config: INotificationChannelConfig; + isActive: boolean; + createdBy: Types.ObjectId; + updatedBy: Types.ObjectId; + createdAt: Date; + updatedAt: Date; +} + +const NotificationChannelConfigSchema = new Schema( + { + url: { type: String, required: false }, + emailAddress: { type: String, required: false }, + }, + { _id: false, strict: "throw" } +); + +const NotificationChannelSchema = new Schema( + { + name: { type: String, required: true, trim: true }, + type: { + type: String, + required: true, + enum: ChannelTypes, + }, + config: { type: NotificationChannelConfigSchema, required: true }, + isActive: { type: Boolean, required: true, default: true }, + createdBy: { type: Schema.Types.ObjectId, ref: "User_v2", required: true }, + updatedBy: { type: Schema.Types.ObjectId, ref: "User_v2", required: true }, + }, + { timestamps: true } +); + +NotificationChannelSchema.index({ isActive: 1 }); +NotificationChannelSchema.index({ type: 1 }); +NotificationChannelSchema.index({ type: 1, isActive: 1 }); + +export const NotificationChannel = mongoose.model("NotificationChannel_v2", NotificationChannelSchema); From c0ff162ed925de8f70280ce40e043a4dfea50915 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Tue, 23 Sep 2025 14:46:32 -0700 Subject: [PATCH 027/101] remove v2 files --- .../src/controllers/v2/MonitorController.ts | 157 ------ server/src/db/v2/index.ts | 25 + server/src/db/v2/models/auth/User.ts | 6 - .../src/service/v2/business/CheckService.ts | 132 ----- .../src/service/v2/business/MonitorService.ts | 464 ------------------ 5 files changed, 25 insertions(+), 759 deletions(-) delete mode 100644 server/src/controllers/v2/MonitorController.ts create mode 100644 server/src/db/v2/index.ts delete mode 100644 server/src/service/v2/business/CheckService.ts delete mode 100644 server/src/service/v2/business/MonitorService.ts diff --git a/server/src/controllers/v2/MonitorController.ts b/server/src/controllers/v2/MonitorController.ts deleted file mode 100644 index 6493b7f44..000000000 --- a/server/src/controllers/v2/MonitorController.ts +++ /dev/null @@ -1,157 +0,0 @@ -import { Request, Response, NextFunction } from "express"; -import ApiError from "../../utils/ApiError.js"; -import MonitorService from "../../service/v2/business/MonitorService.js"; -import { MonitorType } from "../../db/v1/models/Monitor.js"; -class MonitorController { - private monitorService: MonitorService; - constructor(monitorService: MonitorService) { - this.monitorService = monitorService; - } - - create = async (req: Request, res: Response, next: NextFunction) => { - try { - const tokenizedUser = req.user; - if (!tokenizedUser) { - return res.status(401).json({ message: "Unauthorized" }); - } - - const monitor = await this.monitorService.create(tokenizedUser, req.body); - res.status(201).json({ - message: "Monitor created successfully", - data: monitor, - }); - } catch (error) { - next(error); - } - }; - - get = async (req: Request, res: Response, next: NextFunction) => { - try { - const tokenizedUser = req.user; - if (!tokenizedUser) { - return res.status(401).json({ message: "Unauthorized" }); - } - - const id = req.params.id; - if (!id) { - throw new ApiError("Monitor ID is required", 400); - } - - const range = req.query.range; - if (!range || typeof range !== "string") throw new ApiError("Range query parameter is required", 400); - - let monitor; - - const status = req.query.status; - if (status && typeof status !== "string") { - throw new ApiError("Status query parameter must be a string", 400); - } - - if (req.query.embedChecks === "true") { - monitor = await this.monitorService.getEmbedChecks(id, range, status); - } else { - monitor = await this.monitorService.get(id); - } - - res.status(200).json({ - message: "Monitor retrieved successfully", - data: monitor, - }); - } catch (error) { - next(error); - } - }; - - getAll = async (req: Request, res: Response, next: NextFunction) => { - try { - const tokenizedUser = req.user; - if (!tokenizedUser) { - return res.status(401).json({ message: "Unauthorized" }); - } - - let monitors; - if (req.query.embedChecks === "true") { - const page = Math.max(1, Number(req.query.page) || 1); - const limit = Math.max(1, Number(req.query.limit) || 10); - const type: MonitorType[] = req.query.type as MonitorType[]; - - monitors = await this.monitorService.getAllEmbedChecks(page, limit, type); - } else { - monitors = await this.monitorService.getAll(); - } - - res.status(200).json({ - message: "Monitors retrieved successfully", - data: monitors, - }); - } catch (error) { - next(error); - } - }; - - toggleActive = async (req: Request, res: Response, next: NextFunction) => { - try { - const tokenizedUser = req.user; - if (!tokenizedUser) { - return res.status(401).json({ message: "Unauthorized" }); - } - - const id = req.params.id; - if (!id) { - throw new ApiError("Monitor ID is required", 400); - } - - const monitor = await this.monitorService.toggleActive(id, tokenizedUser); - res.status(200).json({ - message: "Monitor paused/unpaused successfully", - data: monitor, - }); - } catch (error) { - next(error); - } - }; - - update = async (req: Request, res: Response, next: NextFunction) => { - try { - const tokenizedUser = req.user; - if (!tokenizedUser) { - return res.status(401).json({ message: "Unauthorized" }); - } - - const id = req.params.id; - if (!id) { - throw new ApiError("Monitor ID is required", 400); - } - - const monitor = await this.monitorService.update(tokenizedUser, id, req.body); - res.status(200).json({ - message: "Monitor updated successfully", - data: monitor, - }); - } catch (error) { - next(error); - } - }; - - delete = async (req: Request, res: Response, next: NextFunction) => { - try { - const tokenizedUser = req.user; - if (!tokenizedUser) { - return res.status(401).json({ message: "Unauthorized" }); - } - const id = req.params.id; - if (!id) { - throw new ApiError("Monitor ID is required", 400); - } - await this.monitorService.delete(id); - - res.status(200).json({ - message: "Monitor deleted successfully", - }); - } catch (error) { - next(error); - } - }; -} - -export default MonitorController; diff --git a/server/src/db/v2/index.ts b/server/src/db/v2/index.ts new file mode 100644 index 000000000..2cd194283 --- /dev/null +++ b/server/src/db/v2/index.ts @@ -0,0 +1,25 @@ +import mongoose from "mongoose"; + +const MONGODB_URI = process.env.MONGODB_URI || "mongodb://localhost:27017/checkmate"; + +export const connectDatabase = async (): Promise => { + try { + await mongoose.connect(MONGODB_URI); + console.log("Connected to MongoDB"); + return true; + } catch (error) { + console.error("MongoDB connection error:", error); + process.exit(1); + } +}; + +export const disconnectDatabase = async (): Promise => { + try { + await mongoose.disconnect(); + console.log("Disconnected from MongoDB"); + return true; + } catch (error) { + console.error("MongoDB disconnection error:", error); + return false; + } +}; diff --git a/server/src/db/v2/models/auth/User.ts b/server/src/db/v2/models/auth/User.ts index b72a29e70..25b9794c7 100644 --- a/server/src/db/v2/models/auth/User.ts +++ b/server/src/db/v2/models/auth/User.ts @@ -96,12 +96,6 @@ const userSchema = new Schema( }, { timestamps: true, - toJSON: { - transform: function (_doc, ret) { - delete ret.passwordHash; - return ret; - }, - }, } ); diff --git a/server/src/service/v2/business/CheckService.ts b/server/src/service/v2/business/CheckService.ts deleted file mode 100644 index 181fe048d..000000000 --- a/server/src/service/v2/business/CheckService.ts +++ /dev/null @@ -1,132 +0,0 @@ -import { json } from "stream/consumers"; -import { ICheck, Check, Monitor, ISystemInfo, ICaptureInfo } from "../../../db/v1/models/index.js"; -import { MonitorType } from "../../../db/v1/models/Monitor.js"; -import { StatusResponse } from "../infrastructure/NetworkService.js"; -import type { ICapturePayload, ILighthousePayload } from "../infrastructure/NetworkService.js"; -import mongoose from "mongoose"; - -export interface ICheckService { - buildCheck: (statusResponse: StatusResponse, type: MonitorType) => Promise; - cleanupOrphanedChecks: () => Promise; -} - -class CheckService implements ICheckService { - private isCapturePayload = (payload: any): payload is ICapturePayload => { - if (!payload || typeof payload !== "object") return false; - - if (!("data" in payload) || typeof payload.data !== "object") { - return false; - } - - const data = payload.data as Partial; - if (!data.cpu || typeof data.cpu !== "object" || typeof data.cpu.usage_percent !== "number") { - return false; - } - s; - - if (!data.memory || typeof data.memory !== "object" || typeof data.memory.usage_percent !== "number") { - return false; - } - - if (data.disk && !Array.isArray(data.disk)) { - return false; - } - if (data.net && !Array.isArray(data.net)) { - return false; - } - - if (!("capture" in payload) || typeof payload.capture !== "object") return false; - const capture = payload.capture as Record; - if (typeof capture.version !== "string" || typeof capture.mode !== "string") return false; - - return true; - }; - - private isPagespeedPayload = (payload: any): payload is ILighthousePayload => { - if (!payload || typeof payload !== "object") return false; - - if (!("lighthouseResult" in payload) || typeof payload.lighthouseResult !== "object") { - return false; - } - return true; - }; - - private buildBaseCheck = (statusResponse: StatusResponse) => { - const monitorId = new mongoose.Types.ObjectId(statusResponse.monitorId); - const check = new Check({ - monitorId: monitorId, - type: statusResponse?.type, - status: statusResponse?.status, - message: statusResponse?.message, - responseTime: statusResponse?.responseTime, - timings: statusResponse?.timings, - }); - return check; - }; - - private buildInfrastructureCheck = (statusResponse: StatusResponse) => { - if (!this.isCapturePayload(statusResponse.payload)) { - throw new Error("Invalid payload for infrastructure monitor"); - } - const check = this.buildBaseCheck(statusResponse); - check.system = statusResponse.payload.data; - check.capture = statusResponse.payload.capture; - return check; - }; - - private buildPagespeedCheck = (statusResponse: StatusResponse) => { - if (!this.isPagespeedPayload(statusResponse.payload)) { - throw new Error("Invalid payload for pagespeed monitor"); - } - const check = this.buildBaseCheck(statusResponse); - const lighthouseResult = statusResponse?.payload?.lighthouseResult; - check.lighthouse = { - accessibility: lighthouseResult?.categories?.accessibility?.score || 0, - bestPractices: lighthouseResult?.categories?.["best-practices"]?.score || 0, - seo: lighthouseResult?.categories?.seo?.score || 0, - performance: lighthouseResult?.categories?.performance?.score || 0, - audits: { - cls: lighthouseResult?.audits?.["cumulative-layout-shift"] || {}, - si: lighthouseResult?.audits?.["speed-index"] || {}, - fcp: lighthouseResult?.audits?.["first-contentful-paint"] || {}, - lcp: lighthouseResult?.audits?.["largest-contentful-paint"] || {}, - tbt: lighthouseResult?.audits?.["total-blocking-time"] || {}, - }, - }; - return check; - }; - - buildCheck = async (statusResponse: StatusResponse, type: MonitorType): Promise => { - switch (type) { - case "infrastructure": - return this.buildInfrastructureCheck(statusResponse as StatusResponse); - - case "pagespeed": - return this.buildPagespeedCheck(statusResponse as StatusResponse); - case "http": - case "https": - return this.buildBaseCheck(statusResponse); - - case "ping": - return this.buildBaseCheck(statusResponse); - default: - throw new Error(`Unsupported monitor type: ${type}`); - } - }; - - cleanupOrphanedChecks = async () => { - try { - const monitorIds = await Monitor.find().distinct("_id"); - const result = await Check.deleteMany({ - monitorId: { $nin: monitorIds }, - }); - console.log(`Deleted ${result.deletedCount} orphaned Checks.`); - return true; - } catch (error) { - console.error("Error cleaning up orphaned Checks:", error); - return false; - } - }; -} - -export default CheckService; diff --git a/server/src/service/v2/business/MonitorService.ts b/server/src/service/v2/business/MonitorService.ts deleted file mode 100644 index 6e48e79e5..000000000 --- a/server/src/service/v2/business/MonitorService.ts +++ /dev/null @@ -1,464 +0,0 @@ -import mongoose from "mongoose"; - -import { IMonitor, Monitor, ITokenizedUser, MonitorStats, Check } from "../../../db/v1/models/index.js"; -import ApiError from "../../../utils/ApiError.js"; -import { IJobQueue } from "../infrastructure/JobQueue.js"; -import { MonitorWithChecksResponse } from "../../../types/monitor-response-with-checks.js"; -import { MonitorStatus, MonitorType } from "../../../db/v1/models/monitors/Monitor.js"; -export interface IMonitorService { - create: (tokenizedUser: ITokenizedUser, monitorData: IMonitor) => Promise; - getAll: () => Promise; - getAllEmbedChecks: (page: number, limit: number, type: MonitorType[]) => Promise; - get: (monitorId: string) => Promise; - getEmbedChecks: (monitorId: string, range: string, status?: string) => Promise; - toggleActive: (monitorId: string, tokenizedUser: ITokenizedUser) => Promise; - update: (tokenizedUser: ITokenizedUser, monitorId: string, updateData: Partial) => Promise; - delete: (monitorId: string) => Promise; -} - -class MonitorService implements IMonitorService { - private jobQueue: IJobQueue; - constructor(jobQueue: IJobQueue) { - this.jobQueue = jobQueue; - } - - create = async (tokenizedUser: ITokenizedUser, monitorData: IMonitor) => { - const monitor = await Monitor.create({ - ...monitorData, - createdBy: tokenizedUser.sub, - updatedBy: tokenizedUser.sub, - }); - await MonitorStats.create({ - monitorId: monitor._id, - currentStreakStartedAt: Date.now(), - }); - await this.jobQueue.addJob(monitor); - return monitor; - }; - - getAll = async () => { - return Monitor.find(); - }; - - getAllEmbedChecks = async (page: number, limit: number, type: MonitorType[] = []) => { - const skip = (page - 1) * limit; - let find = {}; - if (type.length > 0) find = { type: { $in: type } }; - const monitors = await Monitor.find(find).skip(skip).limit(limit); - return monitors; - }; - - get = async (monitorId: string) => { - const monitor = await Monitor.findById(monitorId); - if (!monitor) { - throw new ApiError("Monitor not found", 404); - } - return monitor; - }; - - private getStartDate(range: string): Date { - const now = new Date(); - switch (range) { - case "30m": - return new Date(now.getTime() - 30 * 60 * 1000); - case "24h": - return new Date(now.getTime() - 24 * 60 * 60 * 1000); - case "7d": - return new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000); - case "30d": - return new Date(now.getTime() - 30 * 24 * 60 * 60 * 1000); - default: - throw new ApiError("Invalid range parameter", 400); - } - } - - private getDateFormat(range: string): string { - switch (range) { - case "30m": - return "%Y-%m-%dT%H:%M:00Z"; - case "24h": - case "7d": - return "%Y-%m-%dT%H:00:00Z"; - case "30d": - return "%Y-%m-%d"; - default: - throw new ApiError("Invalid range parameter", 400); - } - } - - private getBaseGroup = (dateFormat: string): Record => { - return { - _id: { $dateToString: { format: dateFormat, date: "$createdAt" } }, - count: { $sum: 1 }, - avgResponseTime: { $avg: "$responseTime" }, - }; - }; - - private getBaseProjection = (): object => { - return { status: 1, responseTime: 1, createdAt: 1 }; - }; - - private getPageSpeedGroup = (dateFormat: string): Record => { - return { - _id: { $dateToString: { format: dateFormat, date: "$createdAt" } }, - count: { $sum: 1 }, - avgResponseTime: { $avg: "$responseTime" }, - accessibility: { $avg: "$lighthouse.accessibility" }, - bestPractices: { $avg: "$lighthouse.bestPractices" }, - seo: { $avg: "$lighthouse.seo" }, - performance: { $avg: "$lighthouse.performance" }, - cls: { $avg: "$lighthouse.audits.cls.score" }, - si: { $avg: "$lighthouse.audits.si.score" }, - fcp: { $avg: "$lighthouse.audits.fcp.score" }, - lcp: { $avg: "$lighthouse.audits.lcp.score" }, - tbt: { $avg: "$lighthouse.audits.tbt.score" }, - }; - }; - - private getPageSpeedProjection = (): object => { - const projectStage: any = { status: 1, responseTime: 1, createdAt: 1 }; - projectStage["lighthouse.accessibility"] = 1; - projectStage["lighthouse.seo"] = 1; - projectStage["lighthouse.bestPractices"] = 1; - projectStage["lighthouse.performance"] = 1; - projectStage["lighthouse.audits.cls.score"] = 1; - projectStage["lighthouse.audits.si.score"] = 1; - projectStage["lighthouse.audits.fcp.score"] = 1; - projectStage["lighthouse.audits.lcp.score"] = 1; - projectStage["lighthouse.audits.tbt.score"] = 1; - return projectStage; - }; - - private getInfraGroup = (dateFormat: string): Record => { - return { - _id: { $dateToString: { format: dateFormat, date: "$createdAt" } }, - count: { $sum: 1 }, - avgResponseTime: { $avg: "$responseTime" }, - physicalCores: { $last: "$system.cpu.physical_core" }, - logicalCores: { $last: "$system.cpu.logical_core" }, - frequency: { $avg: "$system.cpu.frequency" }, - currentFrequency: { $last: "$system.cpu.current_frequency" }, - tempsArrays: { $push: "$system.cpu.temperature" }, - freePercent: { $avg: "$system.cpu.free_percent" }, - usedPercent: { $avg: "$system.cpu.usage_percent" }, - total_bytes: { $last: "$system.memory.total_bytes" }, - available_bytes: { $last: "$system.memory.available_bytes" }, - used_bytes: { $last: "$system.memory.used_bytes" }, - memory_usage_percent: { $avg: "$system.memory.usage_percent" }, - disksArray: { $push: "$system.disk" }, - os: { $last: "$system.host.os" }, - platform: { $last: "$system.host.platform" }, - kernel_version: { $last: "$system.host.kernel_version" }, - pretty_name: { $last: "$system.host.pretty_name" }, - netsArray: { $push: "$system.net" }, - }; - }; - - private getInfraProjection = (): object => { - const projectStage: any = { status: 1, responseTime: 1, createdAt: 1 }; - projectStage["system.cpu.physical_core"] = 1; - projectStage["system.cpu.logical_core"] = 1; - projectStage["system.cpu.frequency"] = 1; - projectStage["system.cpu.current_frequency"] = 1; - projectStage["system.cpu.temperature"] = 1; - projectStage["system.cpu.free_percent"] = 1; - projectStage["system.cpu.usage_percent"] = 1; - projectStage["system.memory.total_bytes"] = 1; - projectStage["system.memory.available_bytes"] = 1; - projectStage["system.memory.used_bytes"] = 1; - projectStage["system.memory.usage_percent"] = 1; - projectStage["system.disk"] = 1; - projectStage["system.host.os"] = 1; - projectStage["system.host.platform"] = 1; - projectStage["system.host.kernel_version"] = 1; - projectStage["system.host.pretty_name"] = 1; - projectStage["system.net"] = 1; - return projectStage; - }; - - private getFinalProjection = (type: string): object => { - if (type === "pagespeed") { - return { - _id: 1, - count: 1, - avgResponseTime: 1, - accessibility: "$accessibility", - seo: "$seo", - bestPractices: "$bestPractices", - performance: "$performance", - cls: "$cls", - si: "$si", - fcp: "$fcp", - lcp: "$lcp", - tbt: "$tbt", - }; - } - - if (type === "infrastructure") { - return { - _id: 1, - count: 1, - avgResponseTime: 1, - cpu: { - physicalCores: "$physicalCores", - logicalCores: "$logicalCores", - frequency: "$frequency", - currentFrequency: "$currentFrequency", - temperatures: { - $map: { - input: { - $range: [0, { $size: { $arrayElemAt: ["$tempsArrays", 0] } }], - }, - as: "idx", - in: { - $avg: { - $map: { - input: "$tempsArrays", - as: "arr", - in: { $arrayElemAt: ["$$arr", "$$idx"] }, - }, - }, - }, - }, - }, - freePercent: "$freePercent", - usedPercent: "$usedPercent", - }, - memory: { - total_bytes: "$total_bytes", - available_bytes: "$available_bytes", - used_bytes: "$used_bytes", - usage_percent: "$memory_usage_percent", - }, - disks: { - $map: { - input: { - $range: [0, { $size: { $arrayElemAt: ["$disksArray", 0] } }], - }, - as: "idx", - in: { - $let: { - vars: { - diskGroup: { - $map: { - input: "$disksArray", - as: "diskArr", - in: { $arrayElemAt: ["$$diskArr", "$$idx"] }, - }, - }, - }, - in: { - device: { $arrayElemAt: ["$$diskGroup.device", 0] }, - total_bytes: { $avg: "$$diskGroup.total_bytes" }, - free_bytes: { $avg: "$$diskGroup.free_bytes" }, - used_bytes: { $avg: "$$diskGroup.used_bytes" }, - usage_percent: { $avg: "$$diskGroup.usage_percent" }, - total_inodes: { $avg: "$$diskGroup.total_inodes" }, - free_inodes: { $avg: "$$diskGroup.free_inodes" }, - used_inodes: { $avg: "$$diskGroup.used_inodes" }, - inodes_usage_percent: { - $avg: "$$diskGroup.inodes_usage_percent", - }, - read_bytes: { $avg: "$$diskGroup.read_bytes" }, - write_bytes: { $avg: "$$diskGroup.write_bytes" }, - read_time: { $avg: "$$diskGroup.read_time" }, - write_time: { $avg: "$$diskGroup.write_time" }, - }, - }, - }, - }, - }, - host: { - os: "$os", - platform: "$platform", - kernel_version: "$kernel_version", - pretty_name: "$pretty_name", - }, - net: { - $map: { - input: { - $range: [0, { $size: { $arrayElemAt: ["$netsArray", 0] } }], - }, - as: "idx", - in: { - $let: { - vars: { - netGroup: { - $map: { - input: "$netsArray", - as: "netArr", - in: { $arrayElemAt: ["$$netArr", "$$idx"] }, - }, - }, - }, - in: { - name: { $arrayElemAt: ["$$netGroup.name", 0] }, - bytes_sent: { $avg: "$$netGroup.bytes_sent" }, - bytes_recv: { $avg: "$$netGroup.bytes_recv" }, - packets_sent: { $avg: "$$netGroup.packets_sent" }, - packets_recv: { $avg: "$$netGroup.packets_recv" }, - err_in: { $avg: "$$netGroup.err_in" }, - err_out: { $avg: "$$netGroup.err_out" }, - drop_in: { $avg: "$$netGroup.drop_in" }, - drop_out: { $avg: "$$netGroup.drop_out" }, - fifo_in: { $avg: "$$netGroup.fifo_in" }, - fifo_out: { $avg: "$$netGroup.fifo_out" }, - }, - }, - }, - }, - }, - }; - } - return {}; - }; - - getEmbedChecks = async (monitorId: string, range: string, status: string | undefined): Promise => { - const monitor = await Monitor.findById(monitorId); - if (!monitor) { - throw new ApiError("Monitor not found", 404); - } - const startDate = this.getStartDate(range); - const dateFormat = this.getDateFormat(range); - - // Build match stage - const matchStage: { - monitorId: mongoose.Types.ObjectId; - createdAt: { $gte: Date }; - status?: string; - } = { - monitorId: monitor._id, - createdAt: { $gte: startDate }, - }; - - if (status) { - matchStage.status = status; - } - - let groupClause; - - if (monitor.type === "pagespeed") { - groupClause = this.getPageSpeedGroup(dateFormat); - } else if (monitor.type === "infrastructure") { - groupClause = this.getInfraGroup(dateFormat); - } else { - groupClause = this.getBaseGroup(dateFormat); - } - - let projectStage; - if (monitor.type === "pagespeed") { - projectStage = this.getPageSpeedProjection(); - } else if (monitor.type === "infrastructure") { - projectStage = this.getInfraProjection(); - } else { - projectStage = this.getBaseProjection(); - } - - let finalProjection = {}; - if (monitor.type === "pagespeed" || monitor.type === "infrastructure") { - finalProjection = this.getFinalProjection(monitor.type); - } else { - finalProjection = { _id: 1, count: 1, avgResponseTime: 1 }; - } - - const checks = await Check.aggregate([ - { - $match: matchStage, - }, - { $sort: { createdAt: 1 } }, - { $project: projectStage }, - { $group: groupClause }, - { $sort: { _id: -1 } }, - { - $project: finalProjection, - }, - ]); - - // Get monitor stats - const monitorStats = await MonitorStats.findOne({ - monitorId: monitor._id, - }).lean(); - - if (!monitorStats) { - throw new ApiError("Monitor stats not found", 404); - } - - return { - monitor: monitor.toObject(), - checks, - stats: monitorStats, - }; - }; - - async toggleActive(id: string, tokenizedUser: ITokenizedUser) { - const pendingStatus: MonitorStatus = "initializing"; - const updatedMonitor = await Monitor.findOneAndUpdate( - { _id: id }, - [ - { - $set: { - isActive: { $not: "$isActive" }, - status: pendingStatus, - updatedBy: tokenizedUser.sub, - updatedAt: new Date(), - }, - }, - ], - { new: true } - ); - - if (!updatedMonitor) { - throw new ApiError("Monitor not found", 404); - } - - await this.jobQueue.updateJob(updatedMonitor); - - if (updatedMonitor?.isActive) { - await this.jobQueue.resumeJob(updatedMonitor); - } else { - await this.jobQueue.pauseJob(updatedMonitor); - } - return updatedMonitor; - } - - async update(tokenizedUser: ITokenizedUser, monitorId: string, updateData: Partial) { - const allowedFields: (keyof IMonitor)[] = ["name", "interval", "isActive", "n", "m", "notificationChannels"]; - const safeUpdate: Partial = {}; - - for (const field of allowedFields) { - if (updateData[field] !== undefined) { - (safeUpdate as any)[field] = updateData[field]; - } - } - - const updatedMonitor = await Monitor.findByIdAndUpdate( - monitorId, - { - $set: { - ...safeUpdate, - updatedAt: new Date(), - updatedBy: tokenizedUser.sub, - }, - }, - { new: true, runValidators: true } - ); - - if (!updatedMonitor) { - throw new ApiError("Monitor not found", 404); - } - await this.jobQueue.updateJob(updatedMonitor); - return updatedMonitor; - } - - async delete(monitorId: string) { - const monitor = await Monitor.findById(monitorId); - if (!monitor) { - throw new ApiError("Monitor not found", 404); - } - await monitor.deleteOne(); - await this.jobQueue.deleteJob(monitor); - return true; - } -} - -export default MonitorService; From 2d12fbdff11c2c3fae0600258e4544d0da044487 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Tue, 23 Sep 2025 14:56:02 -0700 Subject: [PATCH 028/101] fix notification schema --- server/src/db/v2/models/monitors/Monitor.ts | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/server/src/db/v2/models/monitors/Monitor.ts b/server/src/db/v2/models/monitors/Monitor.ts index 055d36a1b..52cbae9d8 100644 --- a/server/src/db/v2/models/monitors/Monitor.ts +++ b/server/src/db/v2/models/monitors/Monitor.ts @@ -63,14 +63,10 @@ const MonitorSchema = new Schema( ], default: [], }, - notificationChannels: [ - { - type: Schema.Types.ObjectId, - ref: "NotificationChannel_v2", - required: false, - default: [], - }, - ], + notificationChannels: { + type: [{ type: Schema.Types.ObjectId, ref: "NotificationChannel_v2" }], + default: [], + }, createdBy: { type: Schema.Types.ObjectId, ref: "User_v2", required: true }, updatedBy: { type: Schema.Types.ObjectId, ref: "User_v2", required: true }, From 68f4adfbfef319683fb675d6d2df1203ad50448d Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Tue, 23 Sep 2025 15:22:08 -0700 Subject: [PATCH 029/101] add v2 services --- server/package-lock.json | 1392 +++++++++++++++++ server/package.json | 2 + server/src/config/index.ts | 25 + server/src/service/v2/business/AuthService.ts | 206 +++ .../src/service/v2/business/CheckService.ts | 132 ++ .../src/service/v2/business/InviteService.ts | 61 + .../service/v2/business/MaintenanceService.ts | 141 ++ .../v2/business/MonitorStatsService.ts | 25 + .../v2/business/NotificationChannelService.ts | 100 ++ .../src/service/v2/business/QueueService.ts | 23 + server/src/service/v2/business/UserService.ts | 13 + .../service/v2/infrastructure/JobGenerator.ts | 80 + .../src/service/v2/infrastructure/JobQueue.ts | 227 +++ .../v2/infrastructure/NetworkService.ts | 193 +++ .../v2/infrastructure/NotificationService.ts | 58 + .../NotificationServices/Discord.ts | 73 + .../NotificationServices/Email.ts | 65 + .../NotificationServices/IMessageService.ts | 15 + .../NotificationServices/Slack.ts | 116 ++ .../NotificationServices/Webhook.ts | 43 + .../NotificationServices/index.ts | 5 + .../v2/infrastructure/StatusService.ts | 100 ++ server/src/utils/ApiError.ts | 15 + server/src/utils/JWTUtils.ts | 22 + 24 files changed, 3132 insertions(+) create mode 100644 server/src/config/index.ts create mode 100644 server/src/service/v2/business/AuthService.ts create mode 100644 server/src/service/v2/business/CheckService.ts create mode 100644 server/src/service/v2/business/InviteService.ts create mode 100644 server/src/service/v2/business/MaintenanceService.ts create mode 100644 server/src/service/v2/business/MonitorStatsService.ts create mode 100644 server/src/service/v2/business/NotificationChannelService.ts create mode 100644 server/src/service/v2/business/QueueService.ts create mode 100644 server/src/service/v2/business/UserService.ts create mode 100644 server/src/service/v2/infrastructure/JobGenerator.ts create mode 100644 server/src/service/v2/infrastructure/JobQueue.ts create mode 100644 server/src/service/v2/infrastructure/NetworkService.ts create mode 100644 server/src/service/v2/infrastructure/NotificationService.ts create mode 100644 server/src/service/v2/infrastructure/NotificationServices/Discord.ts create mode 100644 server/src/service/v2/infrastructure/NotificationServices/Email.ts create mode 100644 server/src/service/v2/infrastructure/NotificationServices/IMessageService.ts create mode 100644 server/src/service/v2/infrastructure/NotificationServices/Slack.ts create mode 100644 server/src/service/v2/infrastructure/NotificationServices/Webhook.ts create mode 100644 server/src/service/v2/infrastructure/NotificationServices/index.ts create mode 100644 server/src/service/v2/infrastructure/StatusService.ts create mode 100644 server/src/utils/ApiError.ts create mode 100644 server/src/utils/JWTUtils.ts diff --git a/server/package-lock.json b/server/package-lock.json index 06c1c30b3..92d258433 100644 --- a/server/package-lock.json +++ b/server/package-lock.json @@ -45,6 +45,8 @@ }, "devDependencies": { "@eslint/js": "^9.17.0", + "@types/nodemailer": "7.0.1", + "@types/ping": "0.4.4", "c8": "10.1.3", "chai": "5.2.0", "eslint": "^9.17.0", @@ -72,6 +74,751 @@ "lru-cache": "^10.4.3" } }, + "node_modules/@aws-crypto/sha256-browser": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-browser/-/sha256-browser-5.2.0.tgz", + "integrity": "sha512-AXfN/lGotSQwu6HNcEsIASo7kWXZ5HYWvfOmSNKDsEqC4OashTp8alTmaz+F7TC2L083SFv5RdB+qU3Vs1kZqw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-js": "^5.2.0", + "@aws-crypto/supports-web-crypto": "^5.2.0", + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "@aws-sdk/util-locate-window": "^3.0.0", + "@smithy/util-utf8": "^2.0.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/is-array-buffer": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", + "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/util-buffer-from": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", + "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/is-array-buffer": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/util-utf8": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", + "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/sha256-js": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-js/-/sha256-js-5.2.0.tgz", + "integrity": "sha512-FFQQyu7edu4ufvIZ+OadFpHHOt+eSTBaYaki44c+akjg7qZg9oOQeLlk77F6tSYqjDAFClrHJk9tMf0HdVyOvA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-crypto/supports-web-crypto": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/supports-web-crypto/-/supports-web-crypto-5.2.0.tgz", + "integrity": "sha512-iAvUotm021kM33eCdNfwIN//F77/IADDSs58i+MDaOqFrVjZo9bAal0NK7HurRuWLLpF1iLX7gbWrjHjeo+YFg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-crypto/util": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/util/-/util-5.2.0.tgz", + "integrity": "sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.222.0", + "@smithy/util-utf8": "^2.0.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-crypto/util/node_modules/@smithy/is-array-buffer": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", + "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/util/node_modules/@smithy/util-buffer-from": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", + "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/is-array-buffer": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/util/node_modules/@smithy/util-utf8": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", + "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-sesv2": { + "version": "3.895.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sesv2/-/client-sesv2-3.895.0.tgz", + "integrity": "sha512-tYfgwoG4n8LEc0NHVriNNS+YqeDMJHHcUzJxJJ7Nols5Y3EJzv76QRvu2rdLa9XoxvTSFGXJSuqrd8s0ElbCQQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.894.0", + "@aws-sdk/credential-provider-node": "3.895.0", + "@aws-sdk/middleware-host-header": "3.893.0", + "@aws-sdk/middleware-logger": "3.893.0", + "@aws-sdk/middleware-recursion-detection": "3.893.0", + "@aws-sdk/middleware-user-agent": "3.895.0", + "@aws-sdk/region-config-resolver": "3.893.0", + "@aws-sdk/signature-v4-multi-region": "3.894.0", + "@aws-sdk/types": "3.893.0", + "@aws-sdk/util-endpoints": "3.895.0", + "@aws-sdk/util-user-agent-browser": "3.893.0", + "@aws-sdk/util-user-agent-node": "3.895.0", + "@smithy/config-resolver": "^4.2.2", + "@smithy/core": "^3.11.1", + "@smithy/fetch-http-handler": "^5.2.1", + "@smithy/hash-node": "^4.1.1", + "@smithy/invalid-dependency": "^4.1.1", + "@smithy/middleware-content-length": "^4.1.1", + "@smithy/middleware-endpoint": "^4.2.3", + "@smithy/middleware-retry": "^4.2.4", + "@smithy/middleware-serde": "^4.1.1", + "@smithy/middleware-stack": "^4.1.1", + "@smithy/node-config-provider": "^4.2.2", + "@smithy/node-http-handler": "^4.2.1", + "@smithy/protocol-http": "^5.2.1", + "@smithy/smithy-client": "^4.6.3", + "@smithy/types": "^4.5.0", + "@smithy/url-parser": "^4.1.1", + "@smithy/util-base64": "^4.1.0", + "@smithy/util-body-length-browser": "^4.1.0", + "@smithy/util-body-length-node": "^4.1.0", + "@smithy/util-defaults-mode-browser": "^4.1.3", + "@smithy/util-defaults-mode-node": "^4.1.3", + "@smithy/util-endpoints": "^3.1.2", + "@smithy/util-middleware": "^4.1.1", + "@smithy/util-retry": "^4.1.2", + "@smithy/util-utf8": "^4.1.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-sso": { + "version": "3.895.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.895.0.tgz", + "integrity": "sha512-AQHk6iJrwce/NwZa5/Njy0ZGoHdxWCajkgufhXk53L0kRiC3vUPPWEV1m1F3etQWhaUsatcO2xtRuKvLpe4zgA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.894.0", + "@aws-sdk/middleware-host-header": "3.893.0", + "@aws-sdk/middleware-logger": "3.893.0", + "@aws-sdk/middleware-recursion-detection": "3.893.0", + "@aws-sdk/middleware-user-agent": "3.895.0", + "@aws-sdk/region-config-resolver": "3.893.0", + "@aws-sdk/types": "3.893.0", + "@aws-sdk/util-endpoints": "3.895.0", + "@aws-sdk/util-user-agent-browser": "3.893.0", + "@aws-sdk/util-user-agent-node": "3.895.0", + "@smithy/config-resolver": "^4.2.2", + "@smithy/core": "^3.11.1", + "@smithy/fetch-http-handler": "^5.2.1", + "@smithy/hash-node": "^4.1.1", + "@smithy/invalid-dependency": "^4.1.1", + "@smithy/middleware-content-length": "^4.1.1", + "@smithy/middleware-endpoint": "^4.2.3", + "@smithy/middleware-retry": "^4.2.4", + "@smithy/middleware-serde": "^4.1.1", + "@smithy/middleware-stack": "^4.1.1", + "@smithy/node-config-provider": "^4.2.2", + "@smithy/node-http-handler": "^4.2.1", + "@smithy/protocol-http": "^5.2.1", + "@smithy/smithy-client": "^4.6.3", + "@smithy/types": "^4.5.0", + "@smithy/url-parser": "^4.1.1", + "@smithy/util-base64": "^4.1.0", + "@smithy/util-body-length-browser": "^4.1.0", + "@smithy/util-body-length-node": "^4.1.0", + "@smithy/util-defaults-mode-browser": "^4.1.3", + "@smithy/util-defaults-mode-node": "^4.1.3", + "@smithy/util-endpoints": "^3.1.2", + "@smithy/util-middleware": "^4.1.1", + "@smithy/util-retry": "^4.1.2", + "@smithy/util-utf8": "^4.1.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/core": { + "version": "3.894.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.894.0.tgz", + "integrity": "sha512-7zbO31NV2FaocmMtWOg/fuTk3PC2Ji2AC0Fi2KqrppEDIcwLlTTuT9w/rdu/93Pz+wyUhCxWnDc0tPbwtCLs+A==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.893.0", + "@aws-sdk/xml-builder": "3.894.0", + "@smithy/core": "^3.11.1", + "@smithy/node-config-provider": "^4.2.2", + "@smithy/property-provider": "^4.1.1", + "@smithy/protocol-http": "^5.2.1", + "@smithy/signature-v4": "^5.2.1", + "@smithy/smithy-client": "^4.6.3", + "@smithy/types": "^4.5.0", + "@smithy/util-base64": "^4.1.0", + "@smithy/util-body-length-browser": "^4.1.0", + "@smithy/util-middleware": "^4.1.1", + "@smithy/util-utf8": "^4.1.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-env": { + "version": "3.894.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.894.0.tgz", + "integrity": "sha512-2aiQJIRWOuROPPISKgzQnH/HqSfucdk5z5VMemVH3Mm2EYOrzBwmmiiFpmSMN3ST+sE8c7gusqycUchP+KfALQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.894.0", + "@aws-sdk/types": "3.893.0", + "@smithy/property-provider": "^4.1.1", + "@smithy/types": "^4.5.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-http": { + "version": "3.894.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.894.0.tgz", + "integrity": "sha512-Z5QQpqFRflszrT+lUq6+ORuu4jRDcpgCUSoTtlhczidMqfdOSckKmK3chZEfmUUJPSwoFQZ7EiVTsX3c886fBg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.894.0", + "@aws-sdk/types": "3.893.0", + "@smithy/fetch-http-handler": "^5.2.1", + "@smithy/node-http-handler": "^4.2.1", + "@smithy/property-provider": "^4.1.1", + "@smithy/protocol-http": "^5.2.1", + "@smithy/smithy-client": "^4.6.3", + "@smithy/types": "^4.5.0", + "@smithy/util-stream": "^4.3.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-ini": { + "version": "3.895.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.895.0.tgz", + "integrity": "sha512-uIh7N4IN/yIk+qYMAkVpVkjhB90SGKSfaXEVcnmxzBDG6e5304HKT0esqoCVZvtFfLKasjm2TOpalM5l3fi/dA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.894.0", + "@aws-sdk/credential-provider-env": "3.894.0", + "@aws-sdk/credential-provider-http": "3.894.0", + "@aws-sdk/credential-provider-process": "3.894.0", + "@aws-sdk/credential-provider-sso": "3.895.0", + "@aws-sdk/credential-provider-web-identity": "3.895.0", + "@aws-sdk/nested-clients": "3.895.0", + "@aws-sdk/types": "3.893.0", + "@smithy/credential-provider-imds": "^4.1.2", + "@smithy/property-provider": "^4.1.1", + "@smithy/shared-ini-file-loader": "^4.2.0", + "@smithy/types": "^4.5.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-node": { + "version": "3.895.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.895.0.tgz", + "integrity": "sha512-7xsBCmkBUz+2sNqNsDJ1uyQsBvwhNFzwFt8wX39WrFJTpTQh3uNQ5g8QH21BbkKqIFKCLdvgHgwt3Ub5RGVuPA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/credential-provider-env": "3.894.0", + "@aws-sdk/credential-provider-http": "3.894.0", + "@aws-sdk/credential-provider-ini": "3.895.0", + "@aws-sdk/credential-provider-process": "3.894.0", + "@aws-sdk/credential-provider-sso": "3.895.0", + "@aws-sdk/credential-provider-web-identity": "3.895.0", + "@aws-sdk/types": "3.893.0", + "@smithy/credential-provider-imds": "^4.1.2", + "@smithy/property-provider": "^4.1.1", + "@smithy/shared-ini-file-loader": "^4.2.0", + "@smithy/types": "^4.5.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-process": { + "version": "3.894.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.894.0.tgz", + "integrity": "sha512-VU74GNsj+SsO+pl4d+JimlQ7+AcderZaC6bFndQssQdFZ5NRad8yFNz5Xbec8CPJr+z/VAwHib6431F5nYF46g==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.894.0", + "@aws-sdk/types": "3.893.0", + "@smithy/property-provider": "^4.1.1", + "@smithy/shared-ini-file-loader": "^4.2.0", + "@smithy/types": "^4.5.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-sso": { + "version": "3.895.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.895.0.tgz", + "integrity": "sha512-bZCcHUZGz+XlCaK0KEOHGHkMtlwIvnpxJvlZtSCVaBdX/IgouxaB42fxChflxSMRWF45ygdezfky4i17f6vC4w==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/client-sso": "3.895.0", + "@aws-sdk/core": "3.894.0", + "@aws-sdk/token-providers": "3.895.0", + "@aws-sdk/types": "3.893.0", + "@smithy/property-provider": "^4.1.1", + "@smithy/shared-ini-file-loader": "^4.2.0", + "@smithy/types": "^4.5.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-web-identity": { + "version": "3.895.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.895.0.tgz", + "integrity": "sha512-tKbXbOp2xrL02fxKvB7ko1E4Uvyy5TF9qi5pT2MVWNnfSsBlUM80aJ6tyUPKWXdUTdAlPrU3XcwgQl/DnnRa9A==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.894.0", + "@aws-sdk/nested-clients": "3.895.0", + "@aws-sdk/types": "3.893.0", + "@smithy/property-provider": "^4.1.1", + "@smithy/shared-ini-file-loader": "^4.2.0", + "@smithy/types": "^4.5.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-host-header": { + "version": "3.893.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.893.0.tgz", + "integrity": "sha512-qL5xYRt80ahDfj9nDYLhpCNkDinEXvjLe/Qen/Y/u12+djrR2MB4DRa6mzBCkLkdXDtf0WAoW2EZsNCfGrmOEQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.893.0", + "@smithy/protocol-http": "^5.2.1", + "@smithy/types": "^4.5.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-logger": { + "version": "3.893.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.893.0.tgz", + "integrity": "sha512-ZqzMecjju5zkBquSIfVfCORI/3Mge21nUY4nWaGQy+NUXehqCGG4W7AiVpiHGOcY2cGJa7xeEkYcr2E2U9U0AA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.893.0", + "@smithy/types": "^4.5.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-recursion-detection": { + "version": "3.893.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.893.0.tgz", + "integrity": "sha512-H7Zotd9zUHQAr/wr3bcWHULYhEeoQrF54artgsoUGIf/9emv6LzY89QUccKIxYd6oHKNTrTyXm9F0ZZrzXNxlg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.893.0", + "@aws/lambda-invoke-store": "^0.0.1", + "@smithy/protocol-http": "^5.2.1", + "@smithy/types": "^4.5.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-sdk-s3": { + "version": "3.894.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.894.0.tgz", + "integrity": "sha512-0C3lTVdTuv5CkJ4LulpA7FmGFSKrGUKxnFZ6+qGjYjNzbdiHXfq0TyEBiDmVqDkoV2k4AT2H/m0Xw//rTkcNEQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.894.0", + "@aws-sdk/types": "3.893.0", + "@aws-sdk/util-arn-parser": "3.893.0", + "@smithy/core": "^3.11.1", + "@smithy/node-config-provider": "^4.2.2", + "@smithy/protocol-http": "^5.2.1", + "@smithy/signature-v4": "^5.2.1", + "@smithy/smithy-client": "^4.6.3", + "@smithy/types": "^4.5.0", + "@smithy/util-config-provider": "^4.1.0", + "@smithy/util-middleware": "^4.1.1", + "@smithy/util-stream": "^4.3.2", + "@smithy/util-utf8": "^4.1.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-user-agent": { + "version": "3.895.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.895.0.tgz", + "integrity": "sha512-JUqQW2RPp4I95wZ/Im9fTiaX3DF55oJgeoiNlLdHkQZPSNNS/pT1WMWMReSvJdcfSNU3xSUaLtI+h4mQjQUDbQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.894.0", + "@aws-sdk/types": "3.893.0", + "@aws-sdk/util-endpoints": "3.895.0", + "@smithy/core": "^3.11.1", + "@smithy/protocol-http": "^5.2.1", + "@smithy/types": "^4.5.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/nested-clients": { + "version": "3.895.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.895.0.tgz", + "integrity": "sha512-8w1ihfYgvds6kfal/qJXQQrHRsKYh2nujSyzWMo2TMKMze9WPZA93G4mRbRtKtbSuQ66mVWePH8Cksq35ABu2Q==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.894.0", + "@aws-sdk/middleware-host-header": "3.893.0", + "@aws-sdk/middleware-logger": "3.893.0", + "@aws-sdk/middleware-recursion-detection": "3.893.0", + "@aws-sdk/middleware-user-agent": "3.895.0", + "@aws-sdk/region-config-resolver": "3.893.0", + "@aws-sdk/types": "3.893.0", + "@aws-sdk/util-endpoints": "3.895.0", + "@aws-sdk/util-user-agent-browser": "3.893.0", + "@aws-sdk/util-user-agent-node": "3.895.0", + "@smithy/config-resolver": "^4.2.2", + "@smithy/core": "^3.11.1", + "@smithy/fetch-http-handler": "^5.2.1", + "@smithy/hash-node": "^4.1.1", + "@smithy/invalid-dependency": "^4.1.1", + "@smithy/middleware-content-length": "^4.1.1", + "@smithy/middleware-endpoint": "^4.2.3", + "@smithy/middleware-retry": "^4.2.4", + "@smithy/middleware-serde": "^4.1.1", + "@smithy/middleware-stack": "^4.1.1", + "@smithy/node-config-provider": "^4.2.2", + "@smithy/node-http-handler": "^4.2.1", + "@smithy/protocol-http": "^5.2.1", + "@smithy/smithy-client": "^4.6.3", + "@smithy/types": "^4.5.0", + "@smithy/url-parser": "^4.1.1", + "@smithy/util-base64": "^4.1.0", + "@smithy/util-body-length-browser": "^4.1.0", + "@smithy/util-body-length-node": "^4.1.0", + "@smithy/util-defaults-mode-browser": "^4.1.3", + "@smithy/util-defaults-mode-node": "^4.1.3", + "@smithy/util-endpoints": "^3.1.2", + "@smithy/util-middleware": "^4.1.1", + "@smithy/util-retry": "^4.1.2", + "@smithy/util-utf8": "^4.1.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/region-config-resolver": { + "version": "3.893.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.893.0.tgz", + "integrity": "sha512-/cJvh3Zsa+Of0Zbg7vl9wp/kZtdb40yk/2+XcroAMVPO9hPvmS9r/UOm6tO7FeX4TtkRFwWaQJiTZTgSdsPY+Q==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.893.0", + "@smithy/node-config-provider": "^4.2.2", + "@smithy/types": "^4.5.0", + "@smithy/util-config-provider": "^4.1.0", + "@smithy/util-middleware": "^4.1.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/signature-v4-multi-region": { + "version": "3.894.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.894.0.tgz", + "integrity": "sha512-Te5b3fSbatkZrh3eYNmpOadZFKsCLNSwiolQKQeEeKHxdnqORwYXa+0ypcTHle6ukic+tFRRd9n3NuMVo9uiVg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/middleware-sdk-s3": "3.894.0", + "@aws-sdk/types": "3.893.0", + "@smithy/protocol-http": "^5.2.1", + "@smithy/signature-v4": "^5.2.1", + "@smithy/types": "^4.5.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/token-providers": { + "version": "3.895.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.895.0.tgz", + "integrity": "sha512-vJqrEHFFGRZ3ok5T+jII00sa2DQ3HdVkTBIfM0DcrcPssqDV18VKdA767qiBdIEN/cygjdBg8Ri/cuq6ER9BeQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.894.0", + "@aws-sdk/nested-clients": "3.895.0", + "@aws-sdk/types": "3.893.0", + "@smithy/property-provider": "^4.1.1", + "@smithy/shared-ini-file-loader": "^4.2.0", + "@smithy/types": "^4.5.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/types": { + "version": "3.893.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.893.0.tgz", + "integrity": "sha512-Aht1nn5SnA0N+Tjv0dzhAY7CQbxVtmq1bBR6xI0MhG7p2XYVh1wXuKTzrldEvQWwA3odOYunAfT9aBiKZx9qIg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.5.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/util-arn-parser": { + "version": "3.893.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-arn-parser/-/util-arn-parser-3.893.0.tgz", + "integrity": "sha512-u8H4f2Zsi19DGnwj5FSZzDMhytYF/bCh37vAtBsn3cNDL3YG578X5oc+wSX54pM3tOxS+NY7tvOAo52SW7koUA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/util-endpoints": { + "version": "3.895.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.895.0.tgz", + "integrity": "sha512-MhxBvWbwxmKknuggO2NeMwOVkHOYL98pZ+1ZRI5YwckoCL3AvISMnPJgfN60ww6AIXHGpkp+HhpFdKOe8RHSEg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.893.0", + "@smithy/types": "^4.5.0", + "@smithy/url-parser": "^4.1.1", + "@smithy/util-endpoints": "^3.1.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/util-locate-window": { + "version": "3.893.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-locate-window/-/util-locate-window-3.893.0.tgz", + "integrity": "sha512-T89pFfgat6c8nMmpI8eKjBcDcgJq36+m9oiXbcUzeU55MP9ZuGgBomGjGnHaEyF36jenW9gmg3NfZDm0AO2XPg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/util-user-agent-browser": { + "version": "3.893.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.893.0.tgz", + "integrity": "sha512-PE9NtbDBW6Kgl1bG6A5fF3EPo168tnkj8TgMcT0sg4xYBWsBpq0bpJZRh+Jm5Bkwiw9IgTCLjEU7mR6xWaMB9w==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.893.0", + "@smithy/types": "^4.5.0", + "bowser": "^2.11.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-sdk/util-user-agent-node": { + "version": "3.895.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.895.0.tgz", + "integrity": "sha512-lLRC7BAFOPtJk4cZC0Q0MZBMCGF109QpGnug3L3n/2TJW02Sinz9lzA0ykBpYXe9j60LjIYSENCg+F4DZE5vxg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/middleware-user-agent": "3.895.0", + "@aws-sdk/types": "3.893.0", + "@smithy/node-config-provider": "^4.2.2", + "@smithy/types": "^4.5.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "aws-crt": ">=1.0.0" + }, + "peerDependenciesMeta": { + "aws-crt": { + "optional": true + } + } + }, + "node_modules/@aws-sdk/xml-builder": { + "version": "3.894.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.894.0.tgz", + "integrity": "sha512-E6EAMc9dT1a2DOdo4zyOf3fp5+NJ2wI+mcm7RaW1baFIWDwcb99PpvWoV7YEiK7oaBDshuOEGWKUSYXdW+JYgA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.5.0", + "fast-xml-parser": "5.2.5", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/xml-builder/node_modules/fast-xml-parser": { + "version": "5.2.5", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.2.5.tgz", + "integrity": "sha512-pfX9uG9Ki0yekDHx2SiuRIyFdyAr1kMIMitPvb0YBo8SUfKvia7w7FIyd/l6av85pFYRhZscS75MwMnbvY+hcQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", + "dependencies": { + "strnum": "^2.1.0" + }, + "bin": { + "fxparser": "src/cli/cli.js" + } + }, + "node_modules/@aws/lambda-invoke-store": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/@aws/lambda-invoke-store/-/lambda-invoke-store-0.0.1.tgz", + "integrity": "sha512-ORHRQ2tmvnBXc8t/X9Z8IcSbBA4xTLKuN873FopzklHMeqBst7YG0d+AX97inkvDX+NChYtSr+qGfcqGFaI8Zw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.0.0" + } + }, "node_modules/@babel/code-frame": { "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", @@ -1655,6 +2402,626 @@ "dev": true, "license": "(Unlicense OR Apache-2.0)" }, + "node_modules/@smithy/abort-controller": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-4.1.1.tgz", + "integrity": "sha512-vkzula+IwRvPR6oKQhMYioM3A/oX/lFCZiwuxkQbRhqJS2S4YRY2k7k/SyR2jMf3607HLtbEwlRxi0ndXHMjRg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.5.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/config-resolver": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-4.2.2.tgz", + "integrity": "sha512-IT6MatgBWagLybZl1xQcURXRICvqz1z3APSCAI9IqdvfCkrA7RaQIEfgC6G/KvfxnDfQUDqFV+ZlixcuFznGBQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/node-config-provider": "^4.2.2", + "@smithy/types": "^4.5.0", + "@smithy/util-config-provider": "^4.1.0", + "@smithy/util-middleware": "^4.1.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/core": { + "version": "3.12.0", + "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.12.0.tgz", + "integrity": "sha512-zJeAgogZfbwlPGL93y4Z/XNeIN37YCreRUd6YMIRvaq+6RnBK8PPYYIQ85Is/GglPh3kNImD5riDCXbVSDpCiQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/middleware-serde": "^4.1.1", + "@smithy/protocol-http": "^5.2.1", + "@smithy/types": "^4.5.0", + "@smithy/util-base64": "^4.1.0", + "@smithy/util-body-length-browser": "^4.1.0", + "@smithy/util-middleware": "^4.1.1", + "@smithy/util-stream": "^4.3.2", + "@smithy/util-utf8": "^4.1.0", + "@smithy/uuid": "^1.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/credential-provider-imds": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.1.2.tgz", + "integrity": "sha512-JlYNq8TShnqCLg0h+afqe2wLAwZpuoSgOyzhYvTgbiKBWRov+uUve+vrZEQO6lkdLOWPh7gK5dtb9dS+KGendg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/node-config-provider": "^4.2.2", + "@smithy/property-provider": "^4.1.1", + "@smithy/types": "^4.5.0", + "@smithy/url-parser": "^4.1.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/fetch-http-handler": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.2.1.tgz", + "integrity": "sha512-5/3wxKNtV3wO/hk1is+CZUhL8a1yy/U+9u9LKQ9kZTkMsHaQjJhc3stFfiujtMnkITjzWfndGA2f7g9Uh9vKng==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/protocol-http": "^5.2.1", + "@smithy/querystring-builder": "^4.1.1", + "@smithy/types": "^4.5.0", + "@smithy/util-base64": "^4.1.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/hash-node": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-4.1.1.tgz", + "integrity": "sha512-H9DIU9WBLhYrvPs9v4sYvnZ1PiAI0oc8CgNQUJ1rpN3pP7QADbTOUjchI2FB764Ub0DstH5xbTqcMJu1pnVqxA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.5.0", + "@smithy/util-buffer-from": "^4.1.0", + "@smithy/util-utf8": "^4.1.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/invalid-dependency": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-4.1.1.tgz", + "integrity": "sha512-1AqLyFlfrrDkyES8uhINRlJXmHA2FkG+3DY8X+rmLSqmFwk3DJnvhyGzyByPyewh2jbmV+TYQBEfngQax8IFGg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.5.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/is-array-buffer": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-4.1.0.tgz", + "integrity": "sha512-ePTYUOV54wMogio+he4pBybe8fwg4sDvEVDBU8ZlHOZXbXK3/C0XfJgUCu6qAZcawv05ZhZzODGUerFBPsPUDQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-content-length": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-4.1.1.tgz", + "integrity": "sha512-9wlfBBgTsRvC2JxLJxv4xDGNBrZuio3AgSl0lSFX7fneW2cGskXTYpFxCdRYD2+5yzmsiTuaAJD1Wp7gWt9y9w==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/protocol-http": "^5.2.1", + "@smithy/types": "^4.5.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-endpoint": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-4.2.4.tgz", + "integrity": "sha512-FZ4hzupOmthm8Q8ujYrd0I+/MHwVMuSTdkDtIQE0xVuvJt9pLT6Q+b0p4/t+slDyrpcf+Wj7SN+ZqT5OryaaZg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.12.0", + "@smithy/middleware-serde": "^4.1.1", + "@smithy/node-config-provider": "^4.2.2", + "@smithy/shared-ini-file-loader": "^4.2.0", + "@smithy/types": "^4.5.0", + "@smithy/url-parser": "^4.1.1", + "@smithy/util-middleware": "^4.1.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-retry": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-4.3.0.tgz", + "integrity": "sha512-qhEX9745fAxZvtLM4bQJAVC98elWjiMO2OiHl1s6p7hUzS4QfZO1gXUYNwEK8m0J6NoCD5W52ggWxbIDHI0XSg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/node-config-provider": "^4.2.2", + "@smithy/protocol-http": "^5.2.1", + "@smithy/service-error-classification": "^4.1.2", + "@smithy/smithy-client": "^4.6.4", + "@smithy/types": "^4.5.0", + "@smithy/util-middleware": "^4.1.1", + "@smithy/util-retry": "^4.1.2", + "@smithy/uuid": "^1.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-serde": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-4.1.1.tgz", + "integrity": "sha512-lh48uQdbCoj619kRouev5XbWhCwRKLmphAif16c4J6JgJ4uXjub1PI6RL38d3BLliUvSso6klyB/LTNpWSNIyg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/protocol-http": "^5.2.1", + "@smithy/types": "^4.5.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-stack": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-4.1.1.tgz", + "integrity": "sha512-ygRnniqNcDhHzs6QAPIdia26M7e7z9gpkIMUe/pK0RsrQ7i5MblwxY8078/QCnGq6AmlUUWgljK2HlelsKIb/A==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.5.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/node-config-provider": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-4.2.2.tgz", + "integrity": "sha512-SYGTKyPvyCfEzIN5rD8q/bYaOPZprYUPD2f5g9M7OjaYupWOoQFYJ5ho+0wvxIRf471i2SR4GoiZ2r94Jq9h6A==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/property-provider": "^4.1.1", + "@smithy/shared-ini-file-loader": "^4.2.0", + "@smithy/types": "^4.5.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/node-http-handler": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.2.1.tgz", + "integrity": "sha512-REyybygHlxo3TJICPF89N2pMQSf+p+tBJqpVe1+77Cfi9HBPReNjTgtZ1Vg73exq24vkqJskKDpfF74reXjxfw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/abort-controller": "^4.1.1", + "@smithy/protocol-http": "^5.2.1", + "@smithy/querystring-builder": "^4.1.1", + "@smithy/types": "^4.5.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/property-provider": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-4.1.1.tgz", + "integrity": "sha512-gm3ZS7DHxUbzC2wr8MUCsAabyiXY0gaj3ROWnhSx/9sPMc6eYLMM4rX81w1zsMaObj2Lq3PZtNCC1J6lpEY7zg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.5.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/protocol-http": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.2.1.tgz", + "integrity": "sha512-T8SlkLYCwfT/6m33SIU/JOVGNwoelkrvGjFKDSDtVvAXj/9gOT78JVJEas5a+ETjOu4SVvpCstKgd0PxSu/aHw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.5.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/querystring-builder": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-4.1.1.tgz", + "integrity": "sha512-J9b55bfimP4z/Jg1gNo+AT84hr90p716/nvxDkPGCD4W70MPms0h8KF50RDRgBGZeL83/u59DWNqJv6tEP/DHA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.5.0", + "@smithy/util-uri-escape": "^4.1.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/querystring-parser": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-4.1.1.tgz", + "integrity": "sha512-63TEp92YFz0oQ7Pj9IuI3IgnprP92LrZtRAkE3c6wLWJxfy/yOPRt39IOKerVr0JS770olzl0kGafXlAXZ1vng==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.5.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/service-error-classification": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-4.1.2.tgz", + "integrity": "sha512-Kqd8wyfmBWHZNppZSMfrQFpc3M9Y/kjyN8n8P4DqJJtuwgK1H914R471HTw7+RL+T7+kI1f1gOnL7Vb5z9+NgQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.5.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/shared-ini-file-loader": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-4.2.0.tgz", + "integrity": "sha512-OQTfmIEp2LLuWdxa8nEEPhZmiOREO6bcB6pjs0AySf4yiZhl6kMOfqmcwcY8BaBPX+0Tb+tG7/Ia/6mwpoZ7Pw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.5.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/signature-v4": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.2.1.tgz", + "integrity": "sha512-M9rZhWQLjlQVCCR37cSjHfhriGRN+FQ8UfgrYNufv66TJgk+acaggShl3KS5U/ssxivvZLlnj7QH2CUOKlxPyA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/is-array-buffer": "^4.1.0", + "@smithy/protocol-http": "^5.2.1", + "@smithy/types": "^4.5.0", + "@smithy/util-hex-encoding": "^4.1.0", + "@smithy/util-middleware": "^4.1.1", + "@smithy/util-uri-escape": "^4.1.0", + "@smithy/util-utf8": "^4.1.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/smithy-client": { + "version": "4.6.4", + "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-4.6.4.tgz", + "integrity": "sha512-qL7O3VDyfzCSN9r+sdbQXGhaHtrfSJL30En6Jboj0I3bobf2g1/T0eP2L4qxqrEW26gWhJ4THI4ElVVLjYyBHg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.12.0", + "@smithy/middleware-endpoint": "^4.2.4", + "@smithy/middleware-stack": "^4.1.1", + "@smithy/protocol-http": "^5.2.1", + "@smithy/types": "^4.5.0", + "@smithy/util-stream": "^4.3.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/types": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.5.0.tgz", + "integrity": "sha512-RkUpIOsVlAwUIZXO1dsz8Zm+N72LClFfsNqf173catVlvRZiwPy0x2u0JLEA4byreOPKDZPGjmPDylMoP8ZJRg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/url-parser": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-4.1.1.tgz", + "integrity": "sha512-bx32FUpkhcaKlEoOMbScvc93isaSiRM75pQ5IgIBaMkT7qMlIibpPRONyx/0CvrXHzJLpOn/u6YiDX2hcvs7Dg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/querystring-parser": "^4.1.1", + "@smithy/types": "^4.5.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-base64": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-4.1.0.tgz", + "integrity": "sha512-RUGd4wNb8GeW7xk+AY5ghGnIwM96V0l2uzvs/uVHf+tIuVX2WSvynk5CxNoBCsM2rQRSZElAo9rt3G5mJ/gktQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^4.1.0", + "@smithy/util-utf8": "^4.1.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-body-length-browser": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@smithy/util-body-length-browser/-/util-body-length-browser-4.1.0.tgz", + "integrity": "sha512-V2E2Iez+bo6bUMOTENPr6eEmepdY8Hbs+Uc1vkDKgKNA/brTJqOW/ai3JO1BGj9GbCeLqw90pbbH7HFQyFotGQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-body-length-node": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@smithy/util-body-length-node/-/util-body-length-node-4.1.0.tgz", + "integrity": "sha512-BOI5dYjheZdgR9XiEM3HJcEMCXSoqbzu7CzIgYrx0UtmvtC3tC2iDGpJLsSRFffUpy8ymsg2ARMP5fR8mtuUQQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-buffer-from": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-4.1.0.tgz", + "integrity": "sha512-N6yXcjfe/E+xKEccWEKzK6M+crMrlwaCepKja0pNnlSkm6SjAeLKKA++er5Ba0I17gvKfN/ThV+ZOx/CntKTVw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/is-array-buffer": "^4.1.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-config-provider": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@smithy/util-config-provider/-/util-config-provider-4.1.0.tgz", + "integrity": "sha512-swXz2vMjrP1ZusZWVTB/ai5gK+J8U0BWvP10v9fpcFvg+Xi/87LHvHfst2IgCs1i0v4qFZfGwCmeD/KNCdJZbQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-defaults-mode-browser": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-4.1.4.tgz", + "integrity": "sha512-mLDJ1s4eA3vwOGaQOEPlg5LB4LdZUUMpB5UMOMofeGhWqiS7WR7dTpLiNi9zVn+YziKUd3Af5NLfxDs7NJqmIw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/property-provider": "^4.1.1", + "@smithy/smithy-client": "^4.6.4", + "@smithy/types": "^4.5.0", + "bowser": "^2.11.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-defaults-mode-node": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-4.1.4.tgz", + "integrity": "sha512-pjX2iMTcOASaSanAd7bu6i3fcMMezr3NTr8Rh64etB0uHRZi+Aw86DoCxPESjY4UTIuA06hhqtTtw95o//imYA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/config-resolver": "^4.2.2", + "@smithy/credential-provider-imds": "^4.1.2", + "@smithy/node-config-provider": "^4.2.2", + "@smithy/property-provider": "^4.1.1", + "@smithy/smithy-client": "^4.6.4", + "@smithy/types": "^4.5.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-endpoints": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-3.1.2.tgz", + "integrity": "sha512-+AJsaaEGb5ySvf1SKMRrPZdYHRYSzMkCoK16jWnIMpREAnflVspMIDeCVSZJuj+5muZfgGpNpijE3mUNtjv01Q==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/node-config-provider": "^4.2.2", + "@smithy/types": "^4.5.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-hex-encoding": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-4.1.0.tgz", + "integrity": "sha512-1LcueNN5GYC4tr8mo14yVYbh/Ur8jHhWOxniZXii+1+ePiIbsLZ5fEI0QQGtbRRP5mOhmooos+rLmVASGGoq5w==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-middleware": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-4.1.1.tgz", + "integrity": "sha512-CGmZ72mL29VMfESz7S6dekqzCh8ZISj3B+w0g1hZFXaOjGTVaSqfAEFAq8EGp8fUL+Q2l8aqNmt8U1tglTikeg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.5.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-retry": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-4.1.2.tgz", + "integrity": "sha512-NCgr1d0/EdeP6U5PSZ9Uv5SMR5XRRYoVr1kRVtKZxWL3tixEL3UatrPIMFZSKwHlCcp2zPLDvMubVDULRqeunA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/service-error-classification": "^4.1.2", + "@smithy/types": "^4.5.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-stream": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-4.3.2.tgz", + "integrity": "sha512-Ka+FA2UCC/Q1dEqUanCdpqwxOFdf5Dg2VXtPtB1qxLcSGh5C1HdzklIt18xL504Wiy9nNUKwDMRTVCbKGoK69g==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/fetch-http-handler": "^5.2.1", + "@smithy/node-http-handler": "^4.2.1", + "@smithy/types": "^4.5.0", + "@smithy/util-base64": "^4.1.0", + "@smithy/util-buffer-from": "^4.1.0", + "@smithy/util-hex-encoding": "^4.1.0", + "@smithy/util-utf8": "^4.1.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-uri-escape": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-4.1.0.tgz", + "integrity": "sha512-b0EFQkq35K5NHUYxU72JuoheM6+pytEVUGlTwiFxWFpmddA+Bpz3LgsPRIpBk8lnPE47yT7AF2Egc3jVnKLuPg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-utf8": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.1.0.tgz", + "integrity": "sha512-mEu1/UIXAdNYuBcyEPbjScKi/+MQVXNIuY/7Cm5XLIWe319kDrT5SizBE95jqtmEXoDbGoZxKLCMttdZdqTZKQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^4.1.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/uuid": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@smithy/uuid/-/uuid-1.0.0.tgz", + "integrity": "sha512-OlA/yZHh0ekYFnbUkmYBDQPE6fGfdrvgz39ktp8Xf+FA6BfxLejPTMDOG0Nfk5/rDySAz1dRbFf24zaAFYVXlQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, "node_modules/@szmarczak/http-timer": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-5.0.1.tgz", @@ -1714,6 +3081,24 @@ "undici-types": "~7.10.0" } }, + "node_modules/@types/nodemailer": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/@types/nodemailer/-/nodemailer-7.0.1.tgz", + "integrity": "sha512-UfHAghPmGZVzaL8x9y+mKZMWyHC399+iq0MOmya5tIyenWX3lcdSb60vOmp0DocR6gCDTYTozv/ULQnREyyjkg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@aws-sdk/client-sesv2": "^3.839.0", + "@types/node": "*" + } + }, + "node_modules/@types/ping": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/@types/ping/-/ping-0.4.4.tgz", + "integrity": "sha512-ifvo6w2f5eJYlXm+HiVx67iJe8WZp87sfa683nlqED5Vnt9Z93onkokNoWqOG21EaE8fMxyKPobE+mkPEyxsdw==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/relateurl": { "version": "0.2.33", "resolved": "https://registry.npmjs.org/@types/relateurl/-/relateurl-0.2.33.tgz", @@ -2073,6 +3458,13 @@ "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", "license": "ISC" }, + "node_modules/bowser": { + "version": "2.12.1", + "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.12.1.tgz", + "integrity": "sha512-z4rE2Gxh7tvshQ4hluIT7XcFrgLIQaw9X3A+kTTRdovCz5PMukm/0QC/BKSYPj3omF5Qfypn9O/c5kgpmvYUCw==", + "dev": true, + "license": "MIT" + }, "node_modules/brace-expansion": { "version": "1.1.12", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", diff --git a/server/package.json b/server/package.json index 637c785cc..2bee352e0 100755 --- a/server/package.json +++ b/server/package.json @@ -54,6 +54,8 @@ }, "devDependencies": { "@eslint/js": "^9.17.0", + "@types/nodemailer": "7.0.1", + "@types/ping": "0.4.4", "c8": "10.1.3", "chai": "5.2.0", "eslint": "^9.17.0", diff --git a/server/src/config/index.ts b/server/src/config/index.ts new file mode 100644 index 000000000..7ace6f99c --- /dev/null +++ b/server/src/config/index.ts @@ -0,0 +1,25 @@ +import dotenv from "dotenv"; + +export interface IEevConfig { + NODE_ENV: string; + JWT_SECRET: string; + PORT: number; + PAGESPEED_API_KEY: string; + SMTP_HOST: string; + SMTP_PORT: number; + SMTP_USER: string; + SMTP_PASS: string; +} + +dotenv.config(); + +export const config: IEevConfig = { + NODE_ENV: process.env.NODE_ENV || "development", + JWT_SECRET: process.env.JWT_SECRET || "your_jwt_secret", + PORT: process.env.PORT ? parseInt(process.env.PORT, 10) : 3000, + PAGESPEED_API_KEY: process.env.PAGESPEED_API_KEY || "", + SMTP_HOST: process.env.SMTP_HOST || "smtp.example.com", + SMTP_PORT: process.env.SMTP_PORT ? parseInt(process.env.SMTP_PORT, 10) : 587, + SMTP_USER: process.env.SMTP_USER || "user@example.com", + SMTP_PASS: process.env.SMTP_PASS || "your_smtp_password", +}; diff --git a/server/src/service/v2/business/AuthService.ts b/server/src/service/v2/business/AuthService.ts new file mode 100644 index 000000000..cf49a71c6 --- /dev/null +++ b/server/src/service/v2/business/AuthService.ts @@ -0,0 +1,206 @@ +import bcrypt from "bcryptjs"; +import { User, Role, ITokenizedUser, Monitor, Check, NotificationChannel } from "../../../db/v2/models/index.js"; +import ApiError from "../../../utils/ApiError.js"; +import { Types } from "mongoose"; +import { IJobQueue } from "../infrastructure/JobQueue.js"; + +export const PERMISSIONS = { + users: { + all: "users.*", + create: "users.create", + view: "users.view", + update: "users.update", + delete: "users.delete", + }, + + monitors: { + all: "monitors.*", + create: "monitors.create", + view: "monitors.view", + update: "monitors.update", + delete: "monitors.delete", + }, + notifications: { + all: "notifications.*", + create: "notifications.create", + view: "notifications.view", + update: "notifications.update", + delete: "notifications.delete", + }, + checks: { + all: "checks.*", + create: "checks.create", + view: "checks.view", + update: "checks.update", + delete: "checks.delete", + }, + statusPages: { + all: "statusPages.*", + create: "statusPages.create", + view: "statusPages.view", + update: "statusPages.update", + delete: "statusPages.delete", + }, +}; + +const DEFAULT_ROLES = [ + { + name: "SuperAdmin", + description: "Super admin with all permissions", + permissions: ["*"], + isSystem: true, + }, + { + name: "Admin", + description: "Admin with full permissions", + permissions: [PERMISSIONS.monitors.all, PERMISSIONS.users.all], + isSystem: true, + }, + { + name: "Manager", + description: "Can manage users", + permissions: [PERMISSIONS.users.create, PERMISSIONS.users.update, PERMISSIONS.monitors.all], + isSystem: true, + }, + { + name: "Member", + description: "Basic team member", + permissions: [PERMISSIONS.users.update, PERMISSIONS.monitors.create, PERMISSIONS.monitors.view, PERMISSIONS.monitors.update], + isSystem: true, + }, +]; + +export type RegisterData = { + email: string; + firstName: string; + lastName: string; + password: string; + roles?: Types.ObjectId[]; // Optional roles for invite-based registration +}; + +export type LoginData = { + email: string; + password: string; +}; + +export type AuthResult = ITokenizedUser; + +export interface IAuthService { + register(signupData: RegisterData): Promise; + registerWithInvite(signupData: RegisterData): Promise; + login(loginData: LoginData): Promise; + cleanup(): Promise; + cleanMonitors(): Promise; +} + +class AuthService implements IAuthService { + private jobQueue: IJobQueue; + constructor(jobQueue: IJobQueue) { + this.jobQueue = jobQueue; + } + + async register(signupData: RegisterData): Promise { + const userCount = await User.countDocuments(); + + if (userCount > 0) { + throw new Error("Registration is closed. Please request an invite."); + } + + const { email, firstName, lastName, password } = signupData; + + // Create all default roles + const rolePromises = DEFAULT_ROLES.map((roleData) => + new Role({ + ...roleData, + }).save() + ); + const roles = await Promise.all(rolePromises); + + // Hash password and create user + const saltRounds = 12; + const passwordHash = await bcrypt.hash(password, saltRounds); + + // Find admin role and assign to first user + const superAdminRole = roles.find((role) => role.name === "SuperAdmin"); + + const user = new User({ + email, + firstName, + lastName, + passwordHash, + roles: [superAdminRole!._id], + }); + + const savedUser = await user.save(); + return { + sub: savedUser._id.toString(), + roles: savedUser.roles.map((role) => role.toString()), + }; + } + + async registerWithInvite(signupData: RegisterData): Promise { + const { email, firstName, lastName, password, roles } = signupData; + + const saltRounds = 12; + const passwordHash = await bcrypt.hash(password, saltRounds); + const user = new User({ + email, + firstName, + lastName, + passwordHash, + roles: roles || [], + }); + try { + const savedUser = await user.save(); + return { + sub: savedUser._id.toString(), + roles: savedUser.roles.map((role) => role.toString()), + }; + } catch (error: any) { + if (error?.code === 11000) { + const dupError = new ApiError("Email already in use", 409); + dupError.stack = error?.stack; + throw dupError; + } + throw error; + } + } + + async login(loginData: LoginData): Promise { + const { email, password } = loginData; + + // Find user by email + const user = await User.findOne({ email }); + + if (!user) { + throw new Error("Invalid email or password"); + } + + // Check password + const isPasswordValid = await bcrypt.compare(password, user.passwordHash); + if (!isPasswordValid) { + throw new Error("Invalid email or password"); + } + + return { + sub: user._id.toString(), + roles: user.roles.map((role) => role.toString()), + }; + } + + async cleanup() { + await User.deleteMany({}); + await Role.deleteMany({}); + await Monitor.deleteMany({}); + await Check.deleteMany({}); + await NotificationChannel.deleteMany({}); + await this.jobQueue.flush(); + } + + async cleanMonitors() { + await Monitor.deleteMany({}); + await Check.deleteMany({}); + } +} + +export default AuthService; diff --git a/server/src/service/v2/business/CheckService.ts b/server/src/service/v2/business/CheckService.ts new file mode 100644 index 000000000..5c68891a9 --- /dev/null +++ b/server/src/service/v2/business/CheckService.ts @@ -0,0 +1,132 @@ +import { json } from "stream/consumers"; +import { ICheck, Check, Monitor } from "../../../db/v2/models/index.js"; +import type { ISystemInfo, ICaptureInfo } from "../../../db/v2/models/index.js"; +import { MonitorType } from "../../../db/v2/models/monitors/Monitor.js"; +import { StatusResponse } from "../infrastructure/NetworkService.js"; +import type { ICapturePayload, ILighthousePayload } from "../infrastructure/NetworkService.js"; +import mongoose from "mongoose"; + +export interface ICheckService { + buildCheck: (statusResponse: StatusResponse, type: MonitorType) => Promise; + cleanupOrphanedChecks: () => Promise; +} + +class CheckService implements ICheckService { + private isCapturePayload = (payload: any): payload is ICapturePayload => { + if (!payload || typeof payload !== "object") return false; + + if (!("data" in payload) || typeof payload.data !== "object") { + return false; + } + + const data = payload.data as Partial; + if (!data.cpu || typeof data.cpu !== "object" || typeof data.cpu.usage_percent !== "number") { + return false; + } + + if (!data.memory || typeof data.memory !== "object" || typeof data.memory.usage_percent !== "number") { + return false; + } + + if (data.disk && !Array.isArray(data.disk)) { + return false; + } + if (data.net && !Array.isArray(data.net)) { + return false; + } + + if (!("capture" in payload) || typeof payload.capture !== "object") return false; + const capture = payload.capture as Record; + if (typeof capture.version !== "string" || typeof capture.mode !== "string") return false; + + return true; + }; + + private isPagespeedPayload = (payload: any): payload is ILighthousePayload => { + if (!payload || typeof payload !== "object") return false; + + if (!("lighthouseResult" in payload) || typeof payload.lighthouseResult !== "object") { + return false; + } + return true; + }; + + private buildBaseCheck = (statusResponse: StatusResponse) => { + const monitorId = new mongoose.Types.ObjectId(statusResponse.monitorId); + const check = new Check({ + monitorId: monitorId, + type: statusResponse?.type, + status: statusResponse?.status, + message: statusResponse?.message, + responseTime: statusResponse?.responseTime, + timings: statusResponse?.timings, + }); + return check; + }; + + private buildInfrastructureCheck = (statusResponse: StatusResponse) => { + if (!this.isCapturePayload(statusResponse.payload)) { + throw new Error("Invalid payload for infrastructure monitor"); + } + const check = this.buildBaseCheck(statusResponse); + check.system = statusResponse.payload.data; + check.capture = statusResponse.payload.capture; + return check; + }; + + private buildPagespeedCheck = (statusResponse: StatusResponse) => { + if (!this.isPagespeedPayload(statusResponse.payload)) { + throw new Error("Invalid payload for pagespeed monitor"); + } + const check = this.buildBaseCheck(statusResponse); + const lighthouseResult = statusResponse?.payload?.lighthouseResult; + check.lighthouse = { + accessibility: lighthouseResult?.categories?.accessibility?.score || 0, + bestPractices: lighthouseResult?.categories?.["best-practices"]?.score || 0, + seo: lighthouseResult?.categories?.seo?.score || 0, + performance: lighthouseResult?.categories?.performance?.score || 0, + audits: { + cls: lighthouseResult?.audits?.["cumulative-layout-shift"] || {}, + si: lighthouseResult?.audits?.["speed-index"] || {}, + fcp: lighthouseResult?.audits?.["first-contentful-paint"] || {}, + lcp: lighthouseResult?.audits?.["largest-contentful-paint"] || {}, + tbt: lighthouseResult?.audits?.["total-blocking-time"] || {}, + }, + }; + return check; + }; + + buildCheck = async (statusResponse: StatusResponse, type: MonitorType): Promise => { + switch (type) { + case "infrastructure": + return this.buildInfrastructureCheck(statusResponse as StatusResponse); + + case "pagespeed": + return this.buildPagespeedCheck(statusResponse as StatusResponse); + case "http": + case "https": + return this.buildBaseCheck(statusResponse); + + case "ping": + return this.buildBaseCheck(statusResponse); + default: + throw new Error(`Unsupported monitor type: ${type}`); + } + }; + + cleanupOrphanedChecks = async () => { + try { + const monitorIds = await Monitor.find().distinct("_id"); + const result = await Check.deleteMany({ + monitorId: { $nin: monitorIds }, + }); + console.log(`Deleted ${result.deletedCount} orphaned Checks.`); + return true; + } catch (error) { + console.error("Error cleaning up orphaned Checks:", error); + return false; + } + }; +} + +export default CheckService; diff --git a/server/src/service/v2/business/InviteService.ts b/server/src/service/v2/business/InviteService.ts new file mode 100644 index 000000000..1c954d75f --- /dev/null +++ b/server/src/service/v2/business/InviteService.ts @@ -0,0 +1,61 @@ +import crypto from "node:crypto"; +import { ITokenizedUser, IInvite, Invite } from "../../../db/v2/models/index.js"; +import ApiError from "../../../utils/ApiError.js"; + +export interface IInviteService { + create: (tokenizedUser: ITokenizedUser, invite: IInvite) => Promise<{ token: string }>; + getAll: () => Promise; + get: (tokenHash: string) => Promise; + delete: (id: string) => Promise; +} + +class InviteService implements IInviteService { + constructor() {} + + create = async (tokenizedUser: ITokenizedUser, inviteData: IInvite) => { + const token = crypto.randomBytes(32).toString("hex"); + const tokenHash = crypto.createHash("sha256").update(token).digest("hex"); + try { + const invite = await Invite.create({ + ...inviteData, + tokenHash, + createdBy: tokenizedUser.sub, + updatedBy: tokenizedUser.sub, + }); + if (!invite) { + throw new ApiError("Failed to create invite", 500); + } + return { token }; + } catch (error: any) { + if (error?.code === 11000) { + const dupError = new ApiError("Invite with this email already exists", 409); + dupError.stack = error?.stack; + throw dupError; + } + throw error; + } + }; + + get = async (token: string) => { + const tokenHash = crypto.createHash("sha256").update(token).digest("hex"); + const invite = await Invite.findOne({ tokenHash }); + if (!invite) { + throw new ApiError("Invite not found", 404); + } + return invite; + }; + + getAll = async () => { + return Invite.find(); + }; + + delete = async (id: string) => { + const result = await Invite.deleteOne({ _id: id }); + if (!result.deletedCount) { + throw new ApiError("Invite not found", 404); + } + return result.deletedCount === 1; + }; +} + +export default InviteService; diff --git a/server/src/service/v2/business/MaintenanceService.ts b/server/src/service/v2/business/MaintenanceService.ts new file mode 100644 index 000000000..ef8c8152f --- /dev/null +++ b/server/src/service/v2/business/MaintenanceService.ts @@ -0,0 +1,141 @@ +import { ITokenizedUser, IMaintenance, Maintenance } from "../../../db/v2/models/index.js"; +import ApiError from "../../../utils/ApiError.js"; + +export interface IMaintenanceService { + create: ( + tokenizedUser: ITokenizedUser, + + maintenance: IMaintenance + ) => Promise; + getAll: () => Promise; + get: (id: string) => Promise; + toggleActive: (tokenizedUser: ITokenizedUser, id: string) => Promise; + update: (tokenizedUser: ITokenizedUser, id: string, updateData: Partial) => Promise; + delete: (id: string) => Promise; + isInMaintenance: (monitorId: string) => Promise; +} + +type MaintenanceCache = Map; + +class MaintenanceService implements IMaintenanceService { + private maintenanceCache: MaintenanceCache; + private lastRefresh: number; + private CACHE_TTL_MS = 60 * 1000; + + constructor() { + this.maintenanceCache = new Map(); + this.lastRefresh = 0; + } + + create = async (tokenizedUser: ITokenizedUser, maintenanceData: IMaintenance) => { + const maintenance = await Maintenance.create({ + ...maintenanceData, + createdBy: tokenizedUser.sub, + updatedBy: tokenizedUser.sub, + }); + return maintenance; + }; + + get = async (id: string) => { + const maintenance = await Maintenance.findById(id); + if (!maintenance) { + throw new ApiError("Maintenance not found", 404); + } + return maintenance; + }; + + getAll = async () => { + return Maintenance.find(); + }; + + toggleActive = async (tokenizedUser: ITokenizedUser, id: string) => { + const updatedMaintenance = await Maintenance.findOneAndUpdate( + { _id: id }, + [ + { + $set: { + isActive: { $not: "$isActive" }, + updatedBy: tokenizedUser.sub, + updatedAt: new Date(), + }, + }, + ], + { new: true } + ); + if (!updatedMaintenance) { + throw new ApiError("Maintenance not found", 404); + } + return updatedMaintenance; + }; + + update = async (tokenizedUser: ITokenizedUser, id: string, updateData: Partial) => { + const allowedFields: (keyof IMaintenance)[] = ["name", "monitors", "startTime", "endTime", "isActive"]; + const safeUpdate: Partial = {}; + for (const field of allowedFields) { + if (updateData[field] !== undefined) { + (safeUpdate as any)[field] = updateData[field]; + } + } + + const updatedMaintenance = await Maintenance.findByIdAndUpdate( + id, + { + $set: { + ...safeUpdate, + updatedAt: new Date(), + updatedBy: tokenizedUser.sub, + }, + }, + { new: true, runValidators: true } + ); + + if (!updatedMaintenance) { + throw new ApiError("Failed to update maintenance", 500); + } + + return updatedMaintenance; + }; + + delete = async (id: string) => { + const result = await Maintenance.deleteOne({ _id: id }); + if (!result.deletedCount) { + throw new ApiError("Maintenance not found", 404); + } + return result.deletedCount === 1; + }; + + private refreshCache = async () => { + const now = new Date(); + + const activeMaintenances = await Maintenance.find({ + isActive: true, + startTime: { $lte: now }, + endTime: { $gte: now }, + }).lean(); + + // Reset cache + const newCache = new Map(); + + for (const m of activeMaintenances) { + for (const monitorId of m.monitors) { + const key = monitorId.toString(); + if (!newCache.has(key)) newCache.set(key, []); + newCache.get(key)!.push(m); + } + } + + this.maintenanceCache = newCache; + this.lastRefresh = Date.now(); + }; + + isInMaintenance = async (monitorId: string) => { + const now = Date.now(); + if (now - this.lastRefresh > this.CACHE_TTL_MS) { + await this.refreshCache(); + } + const maintenances = this.maintenanceCache.get(monitorId) || []; + return maintenances.length > 0; + }; +} + +export default MaintenanceService; diff --git a/server/src/service/v2/business/MonitorStatsService.ts b/server/src/service/v2/business/MonitorStatsService.ts new file mode 100644 index 000000000..dc7d4acb3 --- /dev/null +++ b/server/src/service/v2/business/MonitorStatsService.ts @@ -0,0 +1,25 @@ +import { MonitorStats } from "../../../db/v2/models/index.js"; +import { Monitor } from "../../../db/v2/models/index.js"; +export interface IMonitorStatsService { + cleanupOrphanedMonitorStats: () => Promise; +} + +class MonitorStatsService implements IMonitorStatsService { + constructor() {} + + async cleanupOrphanedMonitorStats() { + try { + const monitorIds = await Monitor.find().distinct("_id"); + const result = await MonitorStats.deleteMany({ + monitorId: { $nin: monitorIds }, + }); + console.log(`Deleted ${result.deletedCount} orphaned MonitorStats.`); + return true; + } catch (error) { + console.error("Error cleaning up orphaned MonitorStats:", error); + return false; + } + } +} + +export default MonitorStatsService; diff --git a/server/src/service/v2/business/NotificationChannelService.ts b/server/src/service/v2/business/NotificationChannelService.ts new file mode 100644 index 000000000..35eae5b12 --- /dev/null +++ b/server/src/service/v2/business/NotificationChannelService.ts @@ -0,0 +1,100 @@ +import { ITokenizedUser, INotificationChannel, NotificationChannel, Monitor } from "../../../db/v2/models/index.js"; +import ApiError from "../../../utils/ApiError.js"; + +export interface INotificationChannelService { + create: ( + tokenizedUser: ITokenizedUser, + + notificationChannel: INotificationChannel + ) => Promise; + getAll: () => Promise; + get: (id: string) => Promise; + toggleActive: (tokenizedUser: ITokenizedUser, id: string) => Promise; + update: (tokenizedUser: ITokenizedUser, id: string, updateData: Partial) => Promise; + delete: (id: string) => Promise; +} + +class NotificationChannelService implements INotificationChannelService { + constructor() {} + + create = async (tokenizedUser: ITokenizedUser, notificationChannelData: INotificationChannel) => { + const notificationChannel = await NotificationChannel.create({ + ...notificationChannelData, + createdBy: tokenizedUser.sub, + updatedBy: tokenizedUser.sub, + }); + return notificationChannel; + }; + + get = async (id: string) => { + const channel = await NotificationChannel.findById(id); + if (!channel) { + throw new ApiError("Notification channel not found", 404); + } + return channel; + }; + + getAll = async () => { + return NotificationChannel.find(); + }; + + toggleActive = async (tokenizedUser: ITokenizedUser, id: string) => { + const updatedChannel = await NotificationChannel.findOneAndUpdate( + { _id: id }, + [ + { + $set: { + isActive: { $not: "$isActive" }, + updatedBy: tokenizedUser.sub, + updatedAt: new Date(), + }, + }, + ], + { new: true } + ); + if (!updatedChannel) { + throw new ApiError("Notification channel not found", 404); + } + return updatedChannel; + }; + + update = async (tokenizedUser: ITokenizedUser, id: string, updateData: Partial) => { + const allowedFields: (keyof INotificationChannel)[] = ["name", "config", "isActive"]; + const safeUpdate: Partial = {}; + for (const field of allowedFields) { + if (updateData[field] !== undefined) { + (safeUpdate as any)[field] = updateData[field]; + } + } + + const updatedChannel = await NotificationChannel.findByIdAndUpdate( + id, + { + $set: { + ...safeUpdate, + updatedAt: new Date(), + updatedBy: tokenizedUser.sub, + }, + }, + { new: true, runValidators: true } + ); + + if (!updatedChannel) { + throw new ApiError("Failed to update notification channel", 500); + } + + return updatedChannel; + }; + + delete = async (id: string) => { + const result = await NotificationChannel.deleteOne({ _id: id }); + if (!result.deletedCount) { + throw new ApiError("Notification channel not found", 404); + } + + await Monitor.updateMany({ notificationChannels: id }, { $pull: { notificationChannels: id } }); + return result.deletedCount === 1; + }; +} + +export default NotificationChannelService; diff --git a/server/src/service/v2/business/QueueService.ts b/server/src/service/v2/business/QueueService.ts new file mode 100644 index 000000000..20069072e --- /dev/null +++ b/server/src/service/v2/business/QueueService.ts @@ -0,0 +1,23 @@ +import { IJobQueue } from "../infrastructure/JobQueue.js"; + +class QueueService { + private jobQueue: IJobQueue; + + constructor(jobQueue: IJobQueue) { + this.jobQueue = jobQueue; + } + + async getMetrics() { + return await this.jobQueue.getMetrics(); + } + + async getJobs() { + return await this.jobQueue.getJobs(); + } + + async flush() { + return await this.jobQueue.flush(); + } +} + +export default QueueService; diff --git a/server/src/service/v2/business/UserService.ts b/server/src/service/v2/business/UserService.ts new file mode 100644 index 000000000..46d221759 --- /dev/null +++ b/server/src/service/v2/business/UserService.ts @@ -0,0 +1,13 @@ +import { IUser, User } from "../../../db/v2/models/index.js"; + +export interface IUserService { + getAllUsers(): Promise; +} + +class UserService implements IUserService { + async getAllUsers(): Promise { + return await User.find(); + } +} + +export default UserService; diff --git a/server/src/service/v2/infrastructure/JobGenerator.ts b/server/src/service/v2/infrastructure/JobGenerator.ts new file mode 100644 index 000000000..4f2beaf71 --- /dev/null +++ b/server/src/service/v2/infrastructure/JobGenerator.ts @@ -0,0 +1,80 @@ +import { IMonitor } from "../../../db/v2/models/index.js"; +import { INetworkService } from "./NetworkService.js"; +import { ICheckService } from "../business/CheckService.js"; +import { IMonitorStatsService } from "../business/MonitorStatsService.js"; +import { IStatusService } from "./StatusService.js"; +import { INotificationService } from "./NotificationService.js"; +import { IMaintenanceService } from "../business/MaintenanceService.js"; +import ApiError from "../../../utils/ApiError.js"; + +export interface IJobGenerator { + generateJob: () => (Monitor: IMonitor) => Promise; + generateCleanupJob: () => () => Promise; +} + +class JobGenerator implements IJobGenerator { + private networkService: INetworkService; + private checkService: ICheckService; + private monitorStatsService: IMonitorStatsService; + private statusService: IStatusService; + private notificationService: INotificationService; + private maintenanceService: IMaintenanceService; + + constructor( + networkService: INetworkService, + checkService: ICheckService, + monitorStatsService: IMonitorStatsService, + statusService: IStatusService, + notificationService: INotificationService, + maintenanceService: IMaintenanceService + ) { + this.networkService = networkService; + this.checkService = checkService; + this.monitorStatsService = monitorStatsService; + this.statusService = statusService; + this.notificationService = notificationService; + this.maintenanceService = maintenanceService; + } + + generateJob = () => { + return async (monitor: IMonitor) => { + try { + const monitorId = monitor._id.toString(); + if (!monitorId) { + throw new ApiError("No monitorID for creating job", 400); + } + + // Check for active maintenance window, if found, skip the check + const isInMaintenance = await this.maintenanceService.isInMaintenance(monitorId); + if (isInMaintenance) { + return; + } + + const status = await this.networkService.requestStatus(monitor); + const check = await this.checkService.buildCheck(status, monitor.type); + await check.save(); + const [updatedMonitor, statusChanged] = await this.statusService.updateMonitorStatus(monitor, status); + + if (statusChanged) { + await this.notificationService.handleNotifications(updatedMonitor); + } + await this.statusService.updateMonitorStats(updatedMonitor, status, statusChanged); + } catch (error) { + throw error; + } + }; + }; + + generateCleanupJob = () => { + return async () => { + try { + await this.checkService.cleanupOrphanedChecks(); + await this.monitorStatsService.cleanupOrphanedMonitorStats(); + } catch (error) { + throw error; + } + }; + }; +} + +export default JobGenerator; diff --git a/server/src/service/v2/infrastructure/JobQueue.ts b/server/src/service/v2/infrastructure/JobQueue.ts new file mode 100644 index 000000000..af415d3f7 --- /dev/null +++ b/server/src/service/v2/infrastructure/JobQueue.ts @@ -0,0 +1,227 @@ +import { IJob } from "super-simple-scheduler/dist/job/job.js"; +import { Monitor, IMonitor } from "../../../db/v2/models/index.js"; +import Scheduler from "super-simple-scheduler"; +import { IJobGenerator } from "./JobGenerator.js"; +export interface IJobMetrics { + jobs: number; + activeJobs: number; + failingJobs: number; + jobsWithFailures: Array<{ + monitorId: string | number; + monitorUrl: string | null; + monitorType: string | null; + failedAt: number | null; + failCount: number | null; + failReason: string | null; + }>; + totalRuns: number; + totalFailures: number; +} + +export interface IJobData extends IJob { + lastRunTook: number | null; +} + +export interface IJobQueue { + init: () => Promise; + addJob: (monitor: IMonitor) => Promise; + pauseJob: (monitor: IMonitor) => Promise; + resumeJob: (monitor: IMonitor) => Promise; + updateJob: (monitor: IMonitor) => Promise; + deleteJob: (monitor: IMonitor) => Promise; + getMetrics: () => Promise; + getJobs: () => Promise; + flush: () => Promise; + shutdown: () => Promise; +} + +export default class JobQueue implements IJobQueue { + private scheduler: Scheduler; + private static instance: JobQueue | null = null; + private jobGenerator: any; + constructor() { + this.scheduler = new Scheduler({ + logLevel: "debug", + }); + } + + static async create(jobGenerator: IJobGenerator) { + if (!JobQueue.instance) { + const instance = new JobQueue(); + instance.jobGenerator = jobGenerator; + await instance.init(); + JobQueue.instance = instance; + } + return JobQueue.instance; + } + + static getInstance(): JobQueue | null { + return JobQueue.instance; + } + + init = async () => { + try { + this.scheduler.start(); + // Add template and jobs + this.scheduler.addTemplate("monitor-job", this.jobGenerator.generateJob()); + + // Add a cleanup job + this.scheduler.addTemplate("cleanup-job", this.jobGenerator.generateCleanupJob()); + await this.scheduler.addJob({ + id: "cleanup-orphaned-checks", + template: "cleanup-job", + repeat: 24 * 60 * 60 * 1000, // 24 hours + active: true, + }); + + const monitors = await Monitor.find(); + for (const monitor of monitors) { + this.addJob(monitor); + } + + return true; + } catch (error) { + console.error(error); + return false; + } + }; + + addJob = async (monitor: IMonitor) => { + try { + return await this.scheduler?.addJob({ + id: monitor._id.toString(), + template: "monitor-job", + repeat: monitor.interval, + active: monitor.isActive, + data: monitor, + }); + } catch (error) { + console.error(error); + return false; + } + }; + + pauseJob = async (monitor: IMonitor) => { + try { + return await this.scheduler?.pauseJob(monitor._id.toString()); + } catch (error) { + console.error(error); + return false; + } + }; + + resumeJob = async (monitor: IMonitor) => { + try { + return await this.scheduler.resumeJob(monitor._id.toString()); + } catch (error) { + console.error(error); + return false; + } + }; + + updateJob = async (monitor: IMonitor) => { + try { + return await this.scheduler.updateJob(monitor._id.toString(), { + repeat: monitor.interval, + data: monitor, + }); + } catch (error) { + console.error(error); + return false; + } + }; + + deleteJob = async (monitor: IMonitor) => { + try { + this.scheduler?.removeJob(monitor._id.toString()); + return true; + } catch (error) { + console.error(error); + return false; + } + }; + + getMetrics = async (): Promise => { + try { + const jobs = await this.scheduler.getJobs(); + const metrics: IJobMetrics = jobs.reduce( + (acc, job) => { + if (!job.data) return acc; + + acc.totalRuns += job.runCount || 0; + acc.totalFailures += job.failCount || 0; + acc.jobs++; + + // Check if job is currently failing (has recent failures) + const hasFailures = job.failCount && job.failCount > 0; + const isCurrentlyFailing = hasFailures && job.lastFailedAt && (!job.lastRunAt || job.lastFailedAt > job.lastRunAt); + + if (isCurrentlyFailing) { + acc.failingJobs++; + } + + if (job.lockedAt) { + acc.activeJobs++; + } + + if (hasFailures) { + acc.jobsWithFailures.push({ + monitorId: job.id, + monitorUrl: job.data?.url || null, + monitorType: job.data?.type || null, + failedAt: job.lastFailedAt || null, + failCount: job.failCount || null, + failReason: job.lastFailReason || null, + }); + } + return acc; + }, + { + jobs: 0, + activeJobs: 0, + failingJobs: 0, + jobsWithFailures: [], + totalRuns: 0, + totalFailures: 0, + } + ); + return metrics; + } catch (error) { + console.error(error); + return null; + } + }; + + getJobs = async (): Promise => { + try { + const jobs = await this.scheduler.getJobs(); + return jobs.map((job) => { + return { + ...job, + lastRunTook: job.lockedAt || !job.lastFinishedAt || !job.lastRunAt ? null : job.lastFinishedAt - job.lastRunAt, + }; + }); + } catch (error) { + console.error(error); + return null; + } + }; + + flush = async () => { + try { + return await this.scheduler.flushJobs(); + } catch (error) { + console.error(error); + return false; + } + }; + + shutdown = async () => { + try { + return await this.scheduler.stop(); + } catch (error) { + console.error(error); + return false; + } + }; +} diff --git a/server/src/service/v2/infrastructure/NetworkService.ts b/server/src/service/v2/infrastructure/NetworkService.ts new file mode 100644 index 000000000..90657bd2d --- /dev/null +++ b/server/src/service/v2/infrastructure/NetworkService.ts @@ -0,0 +1,193 @@ +import { Got, HTTPError } from "got"; +import ping from "ping"; +import { IMonitor } from "../../../db/v2/models/index.js"; +import { GotTimings } from "../../../db/v2/models/checks/Check.js"; +import type { Response } from "got"; +import type { ISystemInfo, ICaptureInfo, ILighthouseResult } from "../../../db/v2/models/index.js"; +import { MonitorType, MonitorStatus } from "../../../db/v2/models/monitors/Monitor.js"; +import ApiError from "../../../utils/ApiError.js"; +import { config } from "../../../config/index.js"; +export interface INetworkService { + requestHttp: (monitor: IMonitor) => Promise; + requestInfrastructure: (monitor: IMonitor) => Promise; + requestStatus: (monitor: IMonitor) => Promise; + requestPagespeed: (monitor: IMonitor) => Promise; + requestPing: (monitor: IMonitor) => Promise; +} + +export interface ICapturePayload { + data: ISystemInfo; + capture: ICaptureInfo; +} + +export interface ILighthousePayload { + lighthouseResult: ILighthouseResult; +} + +export interface StatusResponse { + monitorId: string; + type: MonitorType; + code?: number; + status: MonitorStatus; + message: string; + responseTime: number; + timings?: GotTimings; + payload?: TPayload; +} + +class NetworkService implements INetworkService { + private got: Got; + private NETWORK_ERROR: number; + constructor(got: Got) { + this.got = got; + this.NETWORK_ERROR = 5000; + } + + private buildStatusResponse = (monitor: IMonitor, response: Response | null, error: any | null): StatusResponse => { + if (error) { + const statusResponse: StatusResponse = { + monitorId: monitor._id.toString(), + type: monitor.type, + status: "down" as MonitorStatus, + code: this.NETWORK_ERROR, + message: error.message || "Network error", + responseTime: 0, + timings: { phases: {} } as GotTimings, + }; + if (error instanceof HTTPError) { + statusResponse.code = error?.response?.statusCode || this.NETWORK_ERROR; + statusResponse.message = error.message || "HTTP error"; + statusResponse.responseTime = error.timings?.phases?.total || 0; + statusResponse.timings = error.timings; + } + return statusResponse; + } + + const statusResponse: StatusResponse = { + monitorId: monitor._id.toString(), + type: monitor.type, + code: response?.statusCode || this.NETWORK_ERROR, + status: response?.ok === true ? "up" : "down", + message: response?.statusMessage || "", + responseTime: response?.timings?.phases?.total || 0, + timings: response?.timings || ({ phases: {} } as GotTimings), + }; + + return statusResponse; + }; + + requestHttp = async (monitor: IMonitor) => { + try { + const url = monitor.url; + if (!url) { + throw new Error("No URL provided"); + } + + try { + const response: Response = await this.got(url); + return this.buildStatusResponse(monitor, response, null); + } catch (error) { + return this.buildStatusResponse(monitor, null, error); + } + } catch (error) { + throw error; + } + }; + + requestInfrastructure = async (monitor: IMonitor) => { + const url = monitor.url; + if (!url) { + throw new Error("No URL provided"); + } + const secret = monitor.secret; + if (!secret) { + throw new Error("No secret provided for infrastructure monitor"); + } + + let statusResponse: StatusResponse; + try { + const response: Response | null = await this.got(url, { + headers: { Authorization: `Bearer ${secret}` }, + responseType: "json", + }); + + statusResponse = this.buildStatusResponse(monitor, response, null); + if (!response?.body) { + throw new ApiError("No payload received from infrastructure monitor", 500); + } + statusResponse.payload = response?.body; + return statusResponse; + } catch (error) { + statusResponse = this.buildStatusResponse(monitor, null, error); + } + return statusResponse; + }; + + requestPagespeed = async (monitor: IMonitor) => { + const apiKey = config.PAGESPEED_API_KEY; + if (!apiKey) { + throw new Error("No API key provided for pagespeed monitor"); + } + const url = monitor.url; + if (!url) { + throw new Error("No URL provided"); + } + + let statusResponse: StatusResponse; + + try { + const response: Response = await this.got(url); + statusResponse = this.buildStatusResponse(monitor, response, null) as StatusResponse; + } catch (error) { + statusResponse = this.buildStatusResponse(monitor, null, error); + } + + const pagespeedUrl = `https://pagespeedonline.googleapis.com/pagespeedonline/v5/runPagespeed?url=${url}&category=seo&category=accessibility&category=best-practices&category=performance&key=${apiKey}`; + const pagespeedResponse = await this.got(pagespeedUrl, { + responseType: "json", + }); + const payload = pagespeedResponse.body; + if (payload) { + statusResponse.payload = payload; + return statusResponse; + } else { + throw new ApiError("No payload received from pagespeed monitor", 500); + } + }; + + requestPing = async (monitor: IMonitor) => { + const response = await ping.promise.probe(monitor.url); + const status = response?.alive === true ? "up" : "down"; + + const rawTime = typeof response?.time === "string" ? parseFloat(response.time) : Number(response?.time); + const responseTime = Number.isFinite(rawTime) ? rawTime : 0; + + return { + monitorId: monitor._id.toString(), + type: monitor.type, + status: status as MonitorStatus, + message: "Ping successful", + responseTime, + timings: { phases: {} } as GotTimings, + }; + }; + + requestStatus = async (monitor: IMonitor) => { + switch (monitor?.type) { + case "http": + return await this.requestHttp(monitor); // uses GOT + case "https": + return await this.requestHttp(monitor); // uses GOT + case "infrastructure": + return await this.requestInfrastructure(monitor); // uses GOT + case "pagespeed": + return await this.requestPagespeed(monitor); // uses GOT + case "ping": + return await this.requestPing(monitor); // uses PING + default: + throw new Error("Not implemented"); + } + }; +} + +export default NetworkService; diff --git a/server/src/service/v2/infrastructure/NotificationService.ts b/server/src/service/v2/infrastructure/NotificationService.ts new file mode 100644 index 000000000..4195958bd --- /dev/null +++ b/server/src/service/v2/infrastructure/NotificationService.ts @@ -0,0 +1,58 @@ +import UserService from "../business/UserService.js"; +import { IMonitor, NotificationChannel } from "../../../db/v2/models/index.js"; +import { EmailService, SlackService, DiscordService, WebhookService } from "./NotificationServices/index.js"; +export interface INotificationService { + handleNotifications: (monitor: IMonitor) => Promise; +} + +class NotificationService implements INotificationService { + private emailService: EmailService; + private slackService: SlackService; + private discordService: DiscordService; + private webhookService: WebhookService; + private userService: UserService; + + constructor(userService: UserService) { + this.userService = userService; + this.emailService = new EmailService(userService); + this.slackService = new SlackService(); + this.discordService = new DiscordService(); + this.webhookService = new WebhookService(); + } + + handleNotifications = async (monitor: IMonitor) => { + const notificationIds = monitor.notificationChannels || []; + + if (notificationIds.length === 0) { + return; + } + + const notificationChannels = await NotificationChannel.find({ + _id: { $in: notificationIds }, + }); + + for (const channel of notificationChannels) { + // Implement sending logic based on channel.type and channel.config + let service; + switch (channel.type) { + case "email": + await this.emailService.sendMessage(this.emailService.buildAlert(monitor), channel); + break; + case "slack": + await this.slackService.sendMessage(this.slackService.buildAlert(monitor), channel); + break; + case "discord": + await this.discordService.sendMessage(this.discordService.buildAlert(monitor), channel); + break; + case "webhook": + await this.webhookService.sendMessage(this.webhookService.buildAlert(monitor), channel); + break; + default: + console.warn(`Unknown notification channel type: ${channel.type}`); + } + } + return; + }; +} + +export default NotificationService; diff --git a/server/src/service/v2/infrastructure/NotificationServices/Discord.ts b/server/src/service/v2/infrastructure/NotificationServices/Discord.ts new file mode 100644 index 000000000..c9ec30700 --- /dev/null +++ b/server/src/service/v2/infrastructure/NotificationServices/Discord.ts @@ -0,0 +1,73 @@ +import { IMonitor, INotificationChannel } from "../../../../db/v2/models/index.js"; +import { IAlert, IMessageService } from "./IMessageService.js"; +import got from "got"; +import ApiError from "../../../../utils/ApiError.js"; +class DiscordService implements IMessageService { + constructor() {} + + private toDiscordEmbeds = (alert: IAlert) => { + return { + color: alert.status === "up" ? 65280 : 16711680, + title: `Monitor name: ${alert.name}`, + description: `Status: **${alert.status}**`, + fields: [ + { + name: "Url", + value: alert.url, + }, + { + name: "Checked at", + value: alert.checkTime ? alert.checkTime.toISOString() : "N/A", + }, + { name: "Alert time", value: alert.alertTime.toISOString() }, + ...(alert.details + ? Object.entries(alert.details).map(([key, value]) => ({ + name: key, + value, + })) + : []), + ], + }; + }; + + buildAlert = (monitor: IMonitor) => { + const name = monitor?.name || "Unnamed monitor"; + const monitorStatus = monitor?.status || "unknown status"; + const url = monitor?.url || "no URL"; + const checkTime = monitor?.lastCheckedAt || null; + const alertTime = new Date(); + return { + name, + url, + status: monitorStatus, + checkTime, + alertTime, + }; + }; + + sendMessage = async (alert: IAlert, channel: INotificationChannel) => { + const notificationUrl = channel?.config?.url; + if (!notificationUrl) { + throw new ApiError("Webhook URL not configured", 400); + } + + try { + const payload = { + content: "Status Alert", + embeds: [this.toDiscordEmbeds(alert)], + }; + await got.post(notificationUrl, { json: payload }); + } catch (error) { + console.warn("Failed to send Discord message", error); + return false; + } + + return true; + }; + + testMessage = async () => { + return true; + }; +} + +export default DiscordService; diff --git a/server/src/service/v2/infrastructure/NotificationServices/Email.ts b/server/src/service/v2/infrastructure/NotificationServices/Email.ts new file mode 100644 index 000000000..d1443ce08 --- /dev/null +++ b/server/src/service/v2/infrastructure/NotificationServices/Email.ts @@ -0,0 +1,65 @@ +import { IMonitor, INotificationChannel } from "../../../../db/v2/models/index.js"; +import { IMessageService, IAlert } from "./IMessageService.js"; +import nodemailer, { Transporter } from "nodemailer"; +import { config } from "../../../../config/index.js"; +import UserService from "../../business/UserService.js"; +import ApiError from "../../../../utils/ApiError.js"; +class EmailService implements IMessageService { + private transporter: Transporter; + private userService: UserService; + + constructor(userService: UserService) { + this.userService = userService; + this.transporter = nodemailer.createTransport({ + host: config.SMTP_HOST, + port: config.SMTP_PORT, + secure: config.SMTP_PORT === 465, + auth: { + user: config.SMTP_USER, + pass: config.SMTP_PASS, + }, + }); + } + + buildAlert = (monitor: IMonitor) => { + const name = monitor?.name || "Unnamed monitor"; + const monitorStatus = monitor?.status || "unknown status"; + const url = monitor?.url || "no URL"; + const checkTime = monitor?.lastCheckedAt || null; + const alertTime = new Date(); + return { + name, + url, + status: monitorStatus, + checkTime, + alertTime, + }; + }; + + sendMessage = async (alert: string | IAlert, channel: INotificationChannel) => { + try { + const users = await this.userService.getAllUsers(); + const emails = users.map((u) => u.email).join(","); + + if (!emails || emails.length === 0) { + throw new ApiError("No user emails found", 500); + } + + await this.transporter.sendMail({ + from: `"Checkmate" <${config.SMTP_USER}>`, + to: emails, + subject: "Monitor Alert", + text: JSON.stringify(alert, null, 2), + }); + return true; + } catch (error) { + return false; + } + }; + + testMessage = async () => { + return true; + }; +} + +export default EmailService; diff --git a/server/src/service/v2/infrastructure/NotificationServices/IMessageService.ts b/server/src/service/v2/infrastructure/NotificationServices/IMessageService.ts new file mode 100644 index 000000000..dbafc59fb --- /dev/null +++ b/server/src/service/v2/infrastructure/NotificationServices/IMessageService.ts @@ -0,0 +1,15 @@ +import { IMonitor, INotificationChannel } from "../../../db/v2/models/index.js"; + +export interface IAlert { + name: string; + url: string; + status: string; + details?: Record; + checkTime: Date | null; + alertTime: Date; +} +export interface IMessageService { + buildAlert: (monitor: IMonitor) => IAlert; + sendMessage: (alert: IAlert, channel: INotificationChannel) => Promise; + testMessage: (message: string, channel: INotificationChannel) => Promise; +} diff --git a/server/src/service/v2/infrastructure/NotificationServices/Slack.ts b/server/src/service/v2/infrastructure/NotificationServices/Slack.ts new file mode 100644 index 000000000..50e2021e4 --- /dev/null +++ b/server/src/service/v2/infrastructure/NotificationServices/Slack.ts @@ -0,0 +1,116 @@ +import { IMonitor, INotificationChannel } from "../../../../db/v2/models/index.js"; +import { IAlert, IMessageService } from "./IMessageService.js"; +import got from "got"; + +class SlackService implements IMessageService { + constructor() {} + + private toSlackBlocks = (alert: IAlert) => { + return [ + { + type: "header", + text: { + type: "plain_text", + text: "Status Alert", + }, + }, + { + type: "section", + text: { + type: "mrkdwn", + text: `*Monitor name:* ${alert.name}`, + }, + }, + + { + type: "section", + text: { + type: "mrkdwn", + text: `*Status:* ${alert.status}`, + }, + }, + + { + type: "section", + text: { + type: "mrkdwn", + text: `*URL:* ${alert.url}`, + }, + }, + { + type: "section", + text: { + type: "mrkdwn", + text: `*Checked at:* ${alert?.checkTime?.toISOString() || "N/A"}`, + }, + }, + + { + type: "divider", + }, + + ...(alert.details + ? Object.entries(alert.details).map(([key, value]) => ({ + type: "section", + fields: [ + { + type: "mrkdwn", + text: `*${key}:* ${value}`, + }, + ], + })) + : []), + + { + type: "context", + elements: [ + { + type: "mrkdwn", + text: `*Alert generated at:* ${alert?.alertTime?.toISOString() || "N/A"}`, + }, + ], + }, + ]; + }; + + buildAlert = (monitor: IMonitor) => { + const name = monitor?.name || "Unnamed monitor"; + const monitorStatus = monitor?.status || "unknown status"; + const url = monitor?.url || "no URL"; + const checkTime = monitor?.lastCheckedAt || null; + const alertTime = new Date(); + return { + name, + url, + status: monitorStatus, + checkTime, + alertTime, + }; + }; + + sendMessage = async (alert: IAlert, channel: INotificationChannel) => { + const notificationUrl = channel?.config?.url; + if (!notificationUrl) { + throw new Error("Webhook URL not configured"); + } + + try { + const payload = { + text: "Status Alert", + blocks: this.toSlackBlocks(alert), + }; + await got.post(notificationUrl, { json: payload }); + } catch (error) { + console.warn("Error sending Slack message:", error); + return false; + } + + return true; + }; + + testMessage = async () => { + return true; + }; +} + +export default SlackService; diff --git a/server/src/service/v2/infrastructure/NotificationServices/Webhook.ts b/server/src/service/v2/infrastructure/NotificationServices/Webhook.ts new file mode 100644 index 000000000..b84b2b220 --- /dev/null +++ b/server/src/service/v2/infrastructure/NotificationServices/Webhook.ts @@ -0,0 +1,43 @@ +import { IMonitor, INotificationChannel } from "../../../../db/v2/models/index.js"; +import { IAlert, IMessageService } from "./IMessageService.js"; +import ApiError from "../../../../utils/ApiError.js"; +import got from "got"; +class WebhookService implements IMessageService { + constructor() {} + + buildAlert = (monitor: IMonitor) => { + const name = monitor?.name || "Unnamed monitor"; + const monitorStatus = monitor?.status || "unknown status"; + const url = monitor?.url || "no URL"; + const checkTime = monitor?.lastCheckedAt || null; + const alertTime = new Date(); + return { + name, + url, + status: monitorStatus, + checkTime, + alertTime, + }; + }; + + sendMessage = async (alert: IAlert, channel: INotificationChannel) => { + const notificationUrl = channel?.config?.url; + if (!notificationUrl) { + throw new ApiError("Webhook URL not configured", 400); + } + try { + await got.post(notificationUrl, { json: { ...alert } }); + } catch (error) { + console.warn("Failed to send webhook notification:", error); + return false; + } + + return true; + }; + + testMessage = async () => { + return true; + }; +} + +export default WebhookService; diff --git a/server/src/service/v2/infrastructure/NotificationServices/index.ts b/server/src/service/v2/infrastructure/NotificationServices/index.ts new file mode 100644 index 000000000..2b949f716 --- /dev/null +++ b/server/src/service/v2/infrastructure/NotificationServices/index.ts @@ -0,0 +1,5 @@ +export { default as DiscordService } from "./Discord.js"; +export { default as EmailService } from "./Email.js"; +export { default as SlackService } from "./Slack.js"; +export { default as WebhookService } from "./Webhook.js"; +export * from "./IMessageService.js"; diff --git a/server/src/service/v2/infrastructure/StatusService.ts b/server/src/service/v2/infrastructure/StatusService.ts new file mode 100644 index 000000000..87b0df0af --- /dev/null +++ b/server/src/service/v2/infrastructure/StatusService.ts @@ -0,0 +1,100 @@ +import { IMonitor, IMonitorStats, MonitorStats } from "../../../db/v2/models/index.js"; +import { StatusResponse } from "./NetworkService.js"; +import ApiError from "../../../utils/ApiError.js"; + +const MAX_LATEST_CHECKS = 25; +export interface IStatusService { + updateMonitorStatus: (monitor: IMonitor, status: StatusResponse) => Promise; + + calculateAvgResponseTime: (stats: IMonitorStats, statusResponse: StatusResponse) => number; + + updateMonitorStats: (monitor: IMonitor, status: StatusResponse, statusChanged: boolean) => Promise; +} + +export type StatusChangeResult = [updatedMonitor: IMonitor, statusChanged: boolean]; + +class StatusService implements IStatusService { + updateMonitorStatus = async (monitor: IMonitor, statusResponse: StatusResponse): Promise => { + const newStatus = statusResponse.status; + monitor.lastCheckedAt = new Date(); + + // Store latest checks for display + monitor.latestChecks = monitor.latestChecks || []; + monitor.latestChecks.push({ + status: newStatus, + responseTime: statusResponse.responseTime, + checkedAt: monitor.lastCheckedAt, + }); + while (monitor.latestChecks.length > MAX_LATEST_CHECKS) { + monitor.latestChecks.shift(); + } + + // Update monitor status + if (monitor.status === "initializing") { + monitor.status = newStatus; + return [await monitor.save(), true]; + } else { + const { n } = monitor; + const latestChecks = monitor.latestChecks.slice(-n); + // Return early if not enough statuses to evaluate + if (latestChecks.length < n) { + return [await monitor.save(), false]; + } + + // If all different than current status, update status + const allDifferent = latestChecks.every((check) => check.status !== monitor.status); + if (allDifferent && monitor.status !== newStatus) { + monitor.status = newStatus; + } + return [await monitor.save(), allDifferent]; + } + }; + + calculateAvgResponseTime = (stats: IMonitorStats, statusResponse: StatusResponse): number => { + let avgResponseTime = stats.avgResponseTime; + // Set initial + if (avgResponseTime === 0) { + avgResponseTime = statusResponse.responseTime; + } else { + avgResponseTime = (avgResponseTime * (stats.totalChecks - 1) + statusResponse.responseTime) / stats.totalChecks; + } + return avgResponseTime; + }; + + updateMonitorStats = async (monitor: IMonitor, statusResponse: StatusResponse, statusChanged: boolean) => { + const stats = await MonitorStats.findOne({ monitorId: monitor._id }); + if (!stats) { + throw new ApiError("MonitorStats not found", 500); + } + + // Update check counts + stats.totalChecks += 1; + stats.totalUpChecks += statusResponse.status === "up" ? 1 : 0; + stats.totalDownChecks += statusResponse.status === "down" ? 1 : 0; + + // Update streak + if (!statusChanged) { + stats.currentStreak += 1; + } else { + stats.currentStreak = 1; + stats.currentStreakStatus = statusResponse.status; + stats.currentStreakStartedAt = Date.now(); + } + + // Update time stamps + stats.lastCheckTimestamp = Date.now(); + stats.timeOfLastFailure = statusResponse.status === "down" ? Date.now() : stats.timeOfLastFailure; + + // Update stats that need updated check counts + stats.avgResponseTime = this.calculateAvgResponseTime(stats, statusResponse); + stats.uptimePercentage = stats.totalUpChecks / stats.totalChecks; + + // Other + stats.lastResponseTime = statusResponse.responseTime; + stats.maxResponseTime = Math.max(stats.maxResponseTime, statusResponse.responseTime); + + return await stats.save(); + }; +} + +export default StatusService; diff --git a/server/src/utils/ApiError.ts b/server/src/utils/ApiError.ts new file mode 100644 index 000000000..313d01c2a --- /dev/null +++ b/server/src/utils/ApiError.ts @@ -0,0 +1,15 @@ +class ApiError extends Error { + public status: number; + + constructor(message: string, status: number = 500) { + super(message); + this.status = status; + this.name = this.constructor.name; + + if (Error.captureStackTrace) { + Error.captureStackTrace(this, this.constructor); + } + } +} + +export default ApiError; diff --git a/server/src/utils/JWTUtils.ts b/server/src/utils/JWTUtils.ts new file mode 100644 index 000000000..4eebb78a0 --- /dev/null +++ b/server/src/utils/JWTUtils.ts @@ -0,0 +1,22 @@ +import jwt from "jsonwebtoken"; +import { AuthResult } from "../service/v2/business/AuthService.js"; + +const encode = (data: AuthResult): string => { + const secret = process.env.JWT_SECRET; + if (!secret) { + throw new Error("JWT_SECRET is not defined"); + } + const token = jwt.sign(data, secret, { expiresIn: "99d" }); + return token; +}; + +const decode = (token: string): AuthResult => { + const secret = process.env.JWT_SECRET; + if (!secret) { + throw new Error("JWT_SECRET is not defined"); + } + const decoded = jwt.verify(token, secret) as AuthResult; + return decoded; +}; + +export { encode, decode }; From 66e5441bdf6d8cea0af92d94d074705473cb5a5c Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Tue, 23 Sep 2025 15:23:20 -0700 Subject: [PATCH 030/101] fix import path --- .../v2/infrastructure/NotificationServices/IMessageService.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/src/service/v2/infrastructure/NotificationServices/IMessageService.ts b/server/src/service/v2/infrastructure/NotificationServices/IMessageService.ts index dbafc59fb..f00a20549 100644 --- a/server/src/service/v2/infrastructure/NotificationServices/IMessageService.ts +++ b/server/src/service/v2/infrastructure/NotificationServices/IMessageService.ts @@ -1,4 +1,4 @@ -import { IMonitor, INotificationChannel } from "../../../db/v2/models/index.js"; +import { IMonitor, INotificationChannel } from "../../../../db/v2/models/index.js"; export interface IAlert { name: string; From 76f4e974db348a2e6248f1824719e0157baa37d6 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Wed, 24 Sep 2025 14:20:30 -0700 Subject: [PATCH 031/101] dev types --- server/package-lock.json | 19 +++++++++++++++++++ server/package.json | 1 + server/src/routes/v2/auth.ts | 0 3 files changed, 20 insertions(+) create mode 100644 server/src/routes/v2/auth.ts diff --git a/server/package-lock.json b/server/package-lock.json index 92d258433..2a522ae64 100644 --- a/server/package-lock.json +++ b/server/package-lock.json @@ -45,6 +45,7 @@ }, "devDependencies": { "@eslint/js": "^9.17.0", + "@types/jsonwebtoken": "9.0.10", "@types/nodemailer": "7.0.1", "@types/ping": "0.4.4", "c8": "10.1.3", @@ -3072,6 +3073,24 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/jsonwebtoken": { + "version": "9.0.10", + "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.10.tgz", + "integrity": "sha512-asx5hIG9Qmf/1oStypjanR7iKTv0gXQ1Ov/jfrX6kS/EO0OFni8orbmGCn0672NHR3kXHwpAwR+B368ZGN/2rA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/ms": "*", + "@types/node": "*" + } + }, + "node_modules/@types/ms": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", + "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/node": { "version": "24.3.0", "resolved": "https://registry.npmjs.org/@types/node/-/node-24.3.0.tgz", diff --git a/server/package.json b/server/package.json index 2bee352e0..2fdcf58cb 100755 --- a/server/package.json +++ b/server/package.json @@ -54,6 +54,7 @@ }, "devDependencies": { "@eslint/js": "^9.17.0", + "@types/jsonwebtoken": "9.0.10", "@types/nodemailer": "7.0.1", "@types/ping": "0.4.4", "c8": "10.1.3", diff --git a/server/src/routes/v2/auth.ts b/server/src/routes/v2/auth.ts new file mode 100644 index 000000000..e69de29bb From 6d0c56094837a2597cb55f776e7f96d642730e4a Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Wed, 24 Sep 2025 15:55:09 -0700 Subject: [PATCH 032/101] add v2 routes, controllers --- server/src/config/controllers.js | 18 +- server/src/config/routes.js | 24 + server/src/config/services.js | 73 ++- server/src/controllers/v2/AuthController.ts | 150 ++++++ server/src/controllers/v2/InviteController.ts | 62 +++ .../controllers/v2/MaintenanceController.ts | 96 ++++ .../src/controllers/v2/MonitorController.ts | 157 ++++++ .../v2/NotificationChannelController.ts | 96 ++++ server/src/controllers/v2/QueueController.ts | 29 ++ server/src/middleware/v2/VerifyPermissions.ts | 77 +++ server/src/middleware/v2/VerifyToken.ts | 21 + server/src/routes/v2/auth.ts | 33 ++ server/src/routes/v2/invite.ts | 30 ++ server/src/routes/v2/maintenance.ts | 34 ++ server/src/routes/v2/monitors.ts | 34 ++ server/src/routes/v2/notificationChannels.ts | 34 ++ server/src/routes/v2/queue.ts | 24 + server/src/service/v2/business/AuthService.ts | 4 + .../src/service/v2/business/CheckService.ts | 3 + .../src/service/v2/business/InviteService.ts | 2 + .../service/v2/business/MaintenanceService.ts | 3 + .../src/service/v2/business/MonitorService.ts | 468 ++++++++++++++++++ .../v2/business/MonitorStatsService.ts | 3 + .../v2/business/NotificationChannelService.ts | 4 + .../src/service/v2/business/QueueService.ts | 3 + server/src/service/v2/business/UserService.ts | 2 + .../service/v2/infrastructure/JobGenerator.ts | 2 + .../src/service/v2/infrastructure/JobQueue.ts | 4 + .../v2/infrastructure/NetworkService.ts | 6 +- .../v2/infrastructure/NotificationService.ts | 3 + .../NotificationServices/Discord.ts | 3 + .../NotificationServices/Email.ts | 3 + .../NotificationServices/Slack.ts | 2 + .../NotificationServices/Webhook.ts | 4 + .../v2/infrastructure/StatusService.ts | 2 + server/src/types/express.d.ts | 10 + server/src/types/index.ts | 1 + .../src/types/monitor-response-with-checks.ts | 11 + 38 files changed, 1532 insertions(+), 3 deletions(-) create mode 100644 server/src/controllers/v2/AuthController.ts create mode 100644 server/src/controllers/v2/InviteController.ts create mode 100644 server/src/controllers/v2/MaintenanceController.ts create mode 100644 server/src/controllers/v2/MonitorController.ts create mode 100644 server/src/controllers/v2/NotificationChannelController.ts create mode 100644 server/src/controllers/v2/QueueController.ts create mode 100644 server/src/middleware/v2/VerifyPermissions.ts create mode 100644 server/src/middleware/v2/VerifyToken.ts create mode 100644 server/src/routes/v2/invite.ts create mode 100644 server/src/routes/v2/maintenance.ts create mode 100644 server/src/routes/v2/monitors.ts create mode 100644 server/src/routes/v2/notificationChannels.ts create mode 100644 server/src/routes/v2/queue.ts create mode 100644 server/src/service/v2/business/MonitorService.ts create mode 100644 server/src/types/express.d.ts create mode 100644 server/src/types/index.ts create mode 100644 server/src/types/monitor-response-with-checks.ts diff --git a/server/src/config/controllers.js b/server/src/config/controllers.js index 5c54579eb..78cd7091b 100644 --- a/server/src/config/controllers.js +++ b/server/src/config/controllers.js @@ -2,7 +2,7 @@ import { createCommonDependencies } from "../controllers/v1/baseController.js"; // Services -// Controllers +// V1 Controllers import MonitorController from "../controllers/v1/monitorController.js"; import AuthController from "../controllers/v1/authController.js"; import SettingsController from "../controllers/v1/settingsController.js"; @@ -15,10 +15,18 @@ import StatusPageController from "../controllers/v1/statusPageController.js"; import NotificationController from "../controllers/v1/notificationController.js"; import DiagnosticController from "../controllers/v1/diagnosticController.js"; +// V2 Controllers +import AuthControllerV2 from "../controllers/v2/AuthController.js"; +import InviteControllerV2 from "../controllers/v2/InviteController.js"; +import MaintenanceControllerV2 from "../controllers/v2/MaintenanceController.js"; +import MonitorControllerV2 from "../controllers/v2/MonitorController.js"; +import NotificationChannelControllerV2 from "../controllers/v2/NotificationChannelController.js"; +import QueueControllerV2 from "../controllers/v2/QueueController.js"; export const initializeControllers = (services) => { const controllers = {}; const commonDependencies = createCommonDependencies(services.db, services.errorService, services.logger, services.stringService); + // V1 controllers.authController = new AuthController(commonDependencies, { settingsService: services.settingsService, emailService: services.emailService, @@ -62,5 +70,13 @@ export const initializeControllers = (services) => { diagnosticService: services.diagnosticService, }); + //V2 + controllers.authControllerV2 = new AuthControllerV2(services.authServiceV2, services.inviteServiceV2); + controllers.inviteControllerV2 = new InviteControllerV2(services.inviteServiceV2); + controllers.maintenanceControllerV2 = new MaintenanceControllerV2(services.maintenanceServiceV2); + controllers.monitorControllerV2 = new MonitorControllerV2(services.monitorServiceV2); + controllers.notificationChannelControllerV2 = new NotificationChannelControllerV2(services.notificationChannelServiceV2); + controllers.queueControllerV2 = new QueueControllerV2(services.jobQueueV2); + return controllers; }; diff --git a/server/src/config/routes.js b/server/src/config/routes.js index 40bf8e994..13b369262 100644 --- a/server/src/config/routes.js +++ b/server/src/config/routes.js @@ -13,7 +13,16 @@ import LogRoutes from "../routes/v1/logRoutes.js"; import DiagnosticRoutes from "../routes/v1//diagnosticRoute.js"; import NotificationRoutes from "../routes/v1/notificationRoute.js"; +//V2 +import AuthRoutesV2 from "../routes/v2/auth.js"; +import InviteRoutesV2 from "../routes/v2/invite.js"; +import MaintenanceRoutesV2 from "../routes/v2/maintenance.js"; +import MonitorRoutesV2 from "../routes/v2/monitors.js"; +import NotificationChannelRoutesV2 from "../routes/v2/notificationChannels.js"; +import QueueRoutesV2 from "../routes/v2/queue.js"; + export const setupRoutes = (app, controllers) => { + // V1 const authRoutes = new AuthRoutes(controllers.authController); const monitorRoutes = new MonitorRoutes(controllers.monitorController); const settingsRoutes = new SettingsRoutes(controllers.settingsController); @@ -37,4 +46,19 @@ export const setupRoutes = (app, controllers) => { app.use("/api/v1/status-page", statusPageRoutes.getRouter()); app.use("/api/v1/notifications", verifyJWT, notificationRoutes.getRouter()); app.use("/api/v1/diagnostic", verifyJWT, diagnosticRoutes.getRouter()); + + // V2 + const authRoutesV2 = new AuthRoutesV2(controllers.authControllerV2); + const inviteRoutesV2 = new InviteRoutesV2(controllers.inviteControllerV2); + const maintenanceRoutesV2 = new MaintenanceRoutesV2(controllers.maintenanceControllerV2); + const monitorRoutesV2 = new MonitorRoutesV2(controllers.monitorControllerV2); + const notificationChannelRoutesV2 = new NotificationChannelRoutesV2(controllers.notificationChannelControllerV2); + const queueRoutesV2 = new QueueRoutesV2(controllers.queueControllerV2); + + app.use("/api/v2/auth", authApiLimiter, authRoutesV2.getRouter()); + app.use("/api/v2/invite", inviteRoutesV2.getRouter()); + app.use("/api/v2/maintenance", maintenanceRoutesV2.getRouter()); + app.use("/api/v2/monitors", monitorRoutesV2.getRouter()); + app.use("/api/v2/notification-channels", notificationChannelRoutesV2.getRouter()); + app.use("/api/v2/queue", queueRoutesV2.getRouter()); }; diff --git a/server/src/config/services.js b/server/src/config/services.js index 22f978071..ec07c5e15 100644 --- a/server/src/config/services.js +++ b/server/src/config/services.js @@ -61,13 +61,35 @@ import AppSettings from "../db/v1/models/AppSettings.js"; import InviteModule from "../db/v1/modules/inviteModule.js"; import CheckModule from "../db/v1/modules/checkModule.js"; import StatusPageModule from "../db/v1/modules/statusPageModule.js"; -import UserModule from "../db/v1/modules//userModule.js"; +import UserModule from "../db/v1/modules/userModule.js"; import MaintenanceWindowModule from "../db/v1/modules/maintenanceWindowModule.js"; import MonitorModule from "../db/v1/modules/monitorModule.js"; import NotificationModule from "../db/v1/modules/notificationModule.js"; import RecoveryModule from "../db/v1/modules/recoveryModule.js"; import SettingsModule from "../db/v1/modules/settingsModule.js"; +// V2 Business +import AuthServiceV2 from "../service/v2/business/AuthService.js"; +import CheckServiceV2 from "../service/v2/business/CheckService.js"; +import InviteServiceV2 from "../service/v2/business/InviteService.js"; +import MaintenanceServiceV2 from "../service/v2/business/MaintenanceService.js"; +import MonitorServiceV2 from "../service/v2/business/MonitorService.js"; +import MonitorStatsServiceV2 from "../service/v2/business/MonitorStatsService.js"; +import NotificationChannelServiceV2 from "../service/v2/business/NotificationChannelService.js"; +import QueueServiceV2 from "../service/v2/business/QueueService.js"; +import UserServiceV2 from "../service/v2/business/UserService.js"; + +// V2 Infra +import DiscordServiceV2 from "../service/v2/infrastructure/NotificationServices/Discord.js"; +import EmailServiceV2 from "../service/v2/infrastructure/NotificationServices/Email.js"; +import SlackServiceV2 from "../service/v2/infrastructure/NotificationServices/Slack.js"; +import WebhookServiceV2 from "../service/v2/infrastructure/NotificationServices/Webhook.js"; +import JobGeneratorV2 from "../service/v2/infrastructure/JobGenerator.js"; +import JobQueueV2 from "../service/v2/infrastructure/JobQueue.js"; +import NetworkServiceV2 from "../service/v2/infrastructure/NetworkService.js"; +import NotificationServiceV2 from "../service/v2/infrastructure/NotificationService.js"; +import StatusServiceV2 from "../service/v2/infrastructure/StatusService.js"; + export const initializeServices = async ({ logger, envSettings, settingsService }) => { const serviceRegistry = new ServiceRegistry({ logger }); ServiceRegistry.instance = serviceRegistry; @@ -208,7 +230,37 @@ export const initializeServices = async ({ logger, envSettings, settingsService games, }); + // V2 Services + const jobQueueV2 = new JobQueueV2(); + const authServiceV2 = new AuthServiceV2(jobQueueV2); + const checkServiceV2 = new CheckServiceV2(); + const inviteServiceV2 = new InviteServiceV2(); + const maintenanceServiceV2 = new MaintenanceServiceV2(); + const monitorServiceV2 = new MonitorServiceV2(jobQueueV2); + const monitorStatsServiceV2 = new MonitorStatsServiceV2(); + const notificationChannelServiceV2 = new NotificationChannelServiceV2(); + const queueServiceV2 = new QueueServiceV2(jobQueueV2); + const userServiceV2 = new UserServiceV2(); + + // V2 Infra + const discordServiceV2 = new DiscordServiceV2(); + const emailServiceV2 = new EmailServiceV2(userServiceV2); + const slackServiceV2 = new SlackServiceV2(); + const webhookServiceV2 = new WebhookServiceV2(); + const networkServiceV2 = new NetworkServiceV2(); + const statusServiceV2 = new StatusServiceV2(); + const notificationServiceV2 = new NotificationServiceV2(userServiceV2); + const jobGeneratorV2 = new JobGeneratorV2( + networkServiceV2, + checkServiceV2, + monitorStatsServiceV2, + statusServiceV2, + notificationServiceV2, + maintenanceServiceV2 + ); + const services = { + //v1 settingsService, translationService, stringService, @@ -227,6 +279,25 @@ export const initializeServices = async ({ logger, envSettings, settingsService monitorService, errorService, logger, + //v2 + jobQueueV2, + authServiceV2, + checkServiceV2, + inviteServiceV2, + maintenanceServiceV2, + monitorServiceV2, + monitorStatsServiceV2, + notificationChannelServiceV2, + queueServiceV2, + userServiceV2, + discordServiceV2, + emailServiceV2, + slackServiceV2, + webhookServiceV2, + networkServiceV2, + statusServiceV2, + notificationServiceV2, + jobGeneratorV2, }; Object.values(services).forEach((service) => { diff --git a/server/src/controllers/v2/AuthController.ts b/server/src/controllers/v2/AuthController.ts new file mode 100644 index 000000000..3afed5312 --- /dev/null +++ b/server/src/controllers/v2/AuthController.ts @@ -0,0 +1,150 @@ +import { Request, Response, NextFunction } from "express"; +import { encode, decode } from "../../utils/JWTUtils.js"; +import AuthService from "../../service/v2/business/AuthService.js"; +import ApiError from "../../utils/ApiError.js"; +import InviteService from "../../service/v2/business/InviteService.js"; +import { IInvite } from "../../db/v2/models/index.js"; + +class AuthController { + private authService: AuthService; + private inviteService: InviteService; + constructor(authService: AuthService, inviteService: InviteService) { + this.authService = authService; + this.inviteService = inviteService; + } + + register = async (req: Request, res: Response, next: NextFunction) => { + try { + const { email, firstName, lastName, password } = req.body; + + if (!email || !firstName || !lastName || !password) { + throw new Error("Email, firstName, lastName, and password are required"); + } + + const result = await this.authService.register({ + email, + firstName, + lastName, + password, + }); + + const token = encode(result); + + res.cookie("token", token, { + httpOnly: true, + secure: process.env.NODE_ENV === "production", + sameSite: "strict", + maxAge: 7 * 24 * 60 * 60 * 1000, // 1 week + }); + + res.status(201).json({ + message: "User created successfully", + }); + } catch (error) { + next(error); + } + }; + + registerWithInvite = async (req: Request, res: Response, next: NextFunction) => { + try { + const token = req.params.token; + if (!token) { + throw new ApiError("Invite token is required", 400); + } + + const invite: IInvite = await this.inviteService.get(token); + + const { firstName, lastName, password } = req.body; + const email = invite?.email; + const roles = invite?.roles; + + if (!email || !firstName || !lastName || !password || !roles || roles.length === 0) { + throw new Error("Email, firstName, lastName, password, and roles are required"); + } + + const result = await this.authService.registerWithInvite({ + email, + firstName, + lastName, + password, + roles, + }); + + if (!result) { + throw new Error("Registration failed"); + } + + await this.inviteService.delete(invite._id.toString()); + + const jwt = encode(result); + + res.cookie("token", jwt, { + httpOnly: true, + secure: process.env.NODE_ENV === "production", + sameSite: "strict", + maxAge: 7 * 24 * 60 * 60 * 1000, // 1 week + }); + + res.status(201).json({ message: "User created successfully" }); + } catch (error) { + next(error); + } + }; + + login = async (req: Request, res: Response, next: NextFunction) => { + try { + const { email, password } = req.body; + // Validation + if (!email || !password) { + return res.status(400).json({ message: "Email and password are required" }); + } + const result = await this.authService.login({ email, password }); + + const token = encode(result); + + res.cookie("token", token, { + httpOnly: true, + secure: process.env.NODE_ENV === "production", + sameSite: "strict", + maxAge: 7 * 24 * 60 * 60 * 1000, // 1 week + }); + + res.status(200).json({ + message: "Login successful", + }); + } catch (error) { + next(error); + } + }; + + logout = (req: Request, res: Response) => { + res.clearCookie("token", { + httpOnly: true, + secure: process.env.NODE_ENV === "production", + sameSite: "strict", + }); + res.status(200).json({ message: "Logout successful" }); + }; + + me = (req: Request, res: Response, next: NextFunction) => { + return res.status(200).json({ message: "OK" }); + }; + + cleanup = async (req: Request, res: Response) => { + try { + await this.authService.cleanup(); + res.status(200).json({ message: "Cleanup successful" }); + } catch (error) {} + }; + + cleanMonitors = async (req: Request, res: Response) => { + try { + await this.authService.cleanMonitors(); + res.status(200).json({ message: "Monitors cleanup successful" }); + } catch (error) { + res.status(500).json({ message: "Internal server error" }); + } + }; +} + +export default AuthController; diff --git a/server/src/controllers/v2/InviteController.ts b/server/src/controllers/v2/InviteController.ts new file mode 100644 index 000000000..4d72bf8d4 --- /dev/null +++ b/server/src/controllers/v2/InviteController.ts @@ -0,0 +1,62 @@ +import { Request, Response, NextFunction } from "express"; +import InviteService from "../../service/v2/business/InviteService.js"; + +class InviteController { + private inviteService: InviteService; + constructor(inviteService: InviteService) { + this.inviteService = inviteService; + } + + create = async (req: Request, res: Response, next: NextFunction) => { + try { + const tokenizedUser = req.user; + if (!tokenizedUser) { + return res.status(401).json({ message: "Unauthorized" }); + } + const invite = await this.inviteService.create(tokenizedUser, req.body); + res.status(201).json({ message: "OK", data: invite }); + } catch (error: any) { + next(error); + } + }; + + getAll = async (req: Request, res: Response, next: NextFunction) => { + try { + const invites = await this.inviteService.getAll(); + res.status(200).json({ + message: "OK", + data: invites, + }); + } catch (error) { + next(error); + } + }; + + get = async (req: Request, res: Response, next: NextFunction) => { + try { + const token = req.params.token; + if (!token) { + return res.status(400).json({ message: "Token parameter is required" }); + } + const invite = await this.inviteService.get(token); + res.status(200).json({ message: "OK", data: invite }); + } catch (error) { + next(error); + } + }; + + delete = async (req: Request, res: Response, next: NextFunction) => { + try { + const id = req.params.id; + if (!id) { + return res.status(400).json({ message: "ID parameter is required" }); + } + await this.inviteService.delete(id); + res.status(204).json({ message: "OK" }); + } catch (error: any) { + next(error); + } + }; +} + +export default InviteController; diff --git a/server/src/controllers/v2/MaintenanceController.ts b/server/src/controllers/v2/MaintenanceController.ts new file mode 100644 index 000000000..7c6be11d5 --- /dev/null +++ b/server/src/controllers/v2/MaintenanceController.ts @@ -0,0 +1,96 @@ +import { Request, Response, NextFunction } from "express"; +import MaintenanceService from "../../service/v2/business/MaintenanceService.js"; + +class MaintenanceController { + private maintenanceService: MaintenanceService; + constructor(maintenanceService: MaintenanceService) { + this.maintenanceService = maintenanceService; + } + + create = async (req: Request, res: Response, next: NextFunction) => { + try { + const tokenizedUser = req.user; + if (!tokenizedUser) { + return res.status(401).json({ message: "Unauthorized" }); + } + const maintenance = await this.maintenanceService.create(tokenizedUser, req.body); + res.status(201).json({ message: "OK", data: maintenance }); + } catch (error) { + next(error); + } + }; + + getAll = async (req: Request, res: Response, next: NextFunction) => { + try { + const maintenances = await this.maintenanceService.getAll(); + res.status(200).json({ + message: "OK", + data: maintenances, + }); + } catch (error) { + next(error); + } + }; + + toggleActive = async (req: Request, res: Response, next: NextFunction) => { + try { + const tokenizedUser = req.user; + if (!tokenizedUser) { + return res.status(401).json({ message: "Unauthorized" }); + } + const id = req.params.id; + if (!id) { + return res.status(400).json({ message: "ID parameter is required" }); + } + const maintenance = await this.maintenanceService.toggleActive(tokenizedUser, id); + res.status(200).json({ message: "OK", data: maintenance }); + } catch (error) { + next(error); + } + }; + + update = async (req: Request, res: Response, next: NextFunction) => { + try { + const tokenizedUser = req.user; + if (!tokenizedUser) { + return res.status(401).json({ message: "Unauthorized" }); + } + const id = req.params.id; + if (!id) { + return res.status(400).json({ message: "ID parameter is required" }); + } + const updatedMaintenance = await this.maintenanceService.update(tokenizedUser, id, req.body); + res.status(200).json({ message: "OK", data: updatedMaintenance }); + } catch (error) { + next(error); + } + }; + + get = async (req: Request, res: Response, next: NextFunction) => { + try { + const id = req.params.id; + if (!id) { + return res.status(400).json({ message: "ID parameter is required" }); + } + const maintenance = await this.maintenanceService.get(id); + res.status(200).json({ message: "OK", data: maintenance }); + } catch (error) { + next(error); + } + }; + + delete = async (req: Request, res: Response, next: NextFunction) => { + try { + const id = req.params.id; + if (!id) { + return res.status(400).json({ message: "ID parameter is required" }); + } + await this.maintenanceService.delete(id); + res.status(204).json({ message: "OK" }); + } catch (error) { + next(error); + } + }; +} + +export default MaintenanceController; diff --git a/server/src/controllers/v2/MonitorController.ts b/server/src/controllers/v2/MonitorController.ts new file mode 100644 index 000000000..c9a4ce7da --- /dev/null +++ b/server/src/controllers/v2/MonitorController.ts @@ -0,0 +1,157 @@ +import { Request, Response, NextFunction } from "express"; +import ApiError from "../../utils/ApiError.js"; +import MonitorService from "../../service/v2/business/MonitorService.js"; +import { MonitorType } from "../../db/v2/models/monitors/Monitor.js"; +class MonitorController { + private monitorService: MonitorService; + constructor(monitorService: MonitorService) { + this.monitorService = monitorService; + } + + create = async (req: Request, res: Response, next: NextFunction) => { + try { + const tokenizedUser = req.user; + if (!tokenizedUser) { + return res.status(401).json({ message: "Unauthorized" }); + } + + const monitor = await this.monitorService.create(tokenizedUser, req.body); + res.status(201).json({ + message: "Monitor created successfully", + data: monitor, + }); + } catch (error) { + next(error); + } + }; + + get = async (req: Request, res: Response, next: NextFunction) => { + try { + const tokenizedUser = req.user; + if (!tokenizedUser) { + return res.status(401).json({ message: "Unauthorized" }); + } + + const id = req.params.id; + if (!id) { + throw new ApiError("Monitor ID is required", 400); + } + + const range = req.query.range; + if (!range || typeof range !== "string") throw new ApiError("Range query parameter is required", 400); + + let monitor; + + const status = req.query.status; + if (status && typeof status !== "string") { + throw new ApiError("Status query parameter must be a string", 400); + } + + if (req.query.embedChecks === "true") { + monitor = await this.monitorService.getEmbedChecks(id, range, status); + } else { + monitor = await this.monitorService.get(id); + } + + res.status(200).json({ + message: "Monitor retrieved successfully", + data: monitor, + }); + } catch (error) { + next(error); + } + }; + + getAll = async (req: Request, res: Response, next: NextFunction) => { + try { + const tokenizedUser = req.user; + if (!tokenizedUser) { + return res.status(401).json({ message: "Unauthorized" }); + } + + let monitors; + if (req.query.embedChecks === "true") { + const page = Math.max(1, Number(req.query.page) || 1); + const limit = Math.max(1, Number(req.query.limit) || 10); + const type: MonitorType[] = req.query.type as MonitorType[]; + + monitors = await this.monitorService.getAllEmbedChecks(page, limit, type); + } else { + monitors = await this.monitorService.getAll(); + } + + res.status(200).json({ + message: "Monitors retrieved successfully", + data: monitors, + }); + } catch (error) { + next(error); + } + }; + + toggleActive = async (req: Request, res: Response, next: NextFunction) => { + try { + const tokenizedUser = req.user; + if (!tokenizedUser) { + return res.status(401).json({ message: "Unauthorized" }); + } + + const id = req.params.id; + if (!id) { + throw new ApiError("Monitor ID is required", 400); + } + + const monitor = await this.monitorService.toggleActive(id, tokenizedUser); + res.status(200).json({ + message: "Monitor paused/unpaused successfully", + data: monitor, + }); + } catch (error) { + next(error); + } + }; + + update = async (req: Request, res: Response, next: NextFunction) => { + try { + const tokenizedUser = req.user; + if (!tokenizedUser) { + return res.status(401).json({ message: "Unauthorized" }); + } + + const id = req.params.id; + if (!id) { + throw new ApiError("Monitor ID is required", 400); + } + + const monitor = await this.monitorService.update(tokenizedUser, id, req.body); + res.status(200).json({ + message: "Monitor updated successfully", + data: monitor, + }); + } catch (error) { + next(error); + } + }; + + delete = async (req: Request, res: Response, next: NextFunction) => { + try { + const tokenizedUser = req.user; + if (!tokenizedUser) { + return res.status(401).json({ message: "Unauthorized" }); + } + const id = req.params.id; + if (!id) { + throw new ApiError("Monitor ID is required", 400); + } + await this.monitorService.delete(id); + + res.status(200).json({ + message: "Monitor deleted successfully", + }); + } catch (error) { + next(error); + } + }; +} + +export default MonitorController; diff --git a/server/src/controllers/v2/NotificationChannelController.ts b/server/src/controllers/v2/NotificationChannelController.ts new file mode 100644 index 000000000..e13f572f4 --- /dev/null +++ b/server/src/controllers/v2/NotificationChannelController.ts @@ -0,0 +1,96 @@ +import { Request, Response, NextFunction } from "express"; +import NotificationService from "../../service/v2/business/NotificationChannelService.js"; + +class NotificationChannelController { + private notificationService: NotificationService; + constructor(notificationService: NotificationService) { + this.notificationService = notificationService; + } + + create = async (req: Request, res: Response, next: NextFunction) => { + try { + const tokenizedUser = req.user; + if (!tokenizedUser) { + return res.status(401).json({ message: "Unauthorized" }); + } + const channel = await this.notificationService.create(tokenizedUser, req.body); + res.status(201).json({ message: "OK", data: channel }); + } catch (error) { + next(error); + } + }; + + getAll = async (req: Request, res: Response, next: NextFunction) => { + try { + const notificationChannels = await this.notificationService.getAll(); + res.status(200).json({ + message: "OK", + data: notificationChannels, + }); + } catch (error) { + next(error); + } + }; + + toggleActive = async (req: Request, res: Response, next: NextFunction) => { + try { + const tokenizedUser = req.user; + if (!tokenizedUser) { + return res.status(401).json({ message: "Unauthorized" }); + } + const id = req.params.id; + if (!id) { + return res.status(400).json({ message: "ID parameter is required" }); + } + const notificationChannel = await this.notificationService.toggleActive(tokenizedUser, id); + res.status(200).json({ message: "OK", data: notificationChannel }); + } catch (error) { + next(error); + } + }; + + update = async (req: Request, res: Response, next: NextFunction) => { + try { + const tokenizedUser = req.user; + if (!tokenizedUser) { + return res.status(401).json({ message: "Unauthorized" }); + } + const id = req.params.id; + if (!id) { + return res.status(400).json({ message: "ID parameter is required" }); + } + const updatedChannel = await this.notificationService.update(tokenizedUser, id, req.body); + res.status(200).json({ message: "OK", data: updatedChannel }); + } catch (error) { + next(error); + } + }; + + get = async (req: Request, res: Response, next: NextFunction) => { + try { + const id = req.params.id; + if (!id) { + return res.status(400).json({ message: "ID parameter is required" }); + } + const notificationChannel = await this.notificationService.get(id); + res.status(200).json({ message: "OK", data: notificationChannel }); + } catch (error) { + next(error); + } + }; + + delete = async (req: Request, res: Response, next: NextFunction) => { + try { + const id = req.params.id; + if (!id) { + return res.status(400).json({ message: "ID parameter is required" }); + } + await this.notificationService.delete(id); + res.status(204).json({ message: "OK" }); + } catch (error) { + next(error); + } + }; +} + +export default NotificationChannelController; diff --git a/server/src/controllers/v2/QueueController.ts b/server/src/controllers/v2/QueueController.ts new file mode 100644 index 000000000..510703894 --- /dev/null +++ b/server/src/controllers/v2/QueueController.ts @@ -0,0 +1,29 @@ +import { Request, Response, NextFunction } from "express"; +import QueueService from "../../service/v2/business/QueueService.js"; +class QueueController { + private queueService: QueueService; + constructor(queueService: QueueService) { + this.queueService = queueService; + } + + getJobs = async (req: Request, res: Response, next: NextFunction) => { + try { + const jobs = await this.queueService.getJobs(); + res.status(200).json({ message: "ok", data: jobs }); + } catch (error) { + next(error); + } + }; + + getMetrics = async (req: Request, res: Response, next: NextFunction) => { + const metrics = await this.queueService.getMetrics(); + res.status(200).json({ message: "ok", data: metrics }); + }; + + flush = async (req: Request, res: Response, next: NextFunction) => { + const result = await this.queueService.flush(); + res.status(200).json({ message: "ok", flushed: result }); + }; +} + +export default QueueController; diff --git a/server/src/middleware/v2/VerifyPermissions.ts b/server/src/middleware/v2/VerifyPermissions.ts new file mode 100644 index 000000000..64d573e00 --- /dev/null +++ b/server/src/middleware/v2/VerifyPermissions.ts @@ -0,0 +1,77 @@ +import { Request, Response, NextFunction } from "express"; +import ApiError from "../../utils/ApiError.js"; +import { User, IUser, Role, IRole } from "../../db/v2/models/index.js"; + +const rolesCache = new Map(); +// const CACHE_TTL = 30 * 60 * 1000; // 30 minutes +const CACHE_TTL = 1; // 30 minutes +const MAX_CACHE_SIZE = 1000; + +const getCachedRoles = async (userId: string) => { + if (rolesCache.size >= MAX_CACHE_SIZE) { + const oldestKey = rolesCache.keys().next().value; + if (!oldestKey) return null; + rolesCache.delete(oldestKey); + } + + const cached = rolesCache.get(userId); + + if (cached && Date.now() - cached.timestamp < CACHE_TTL) { + return cached.roles; + } + + const user: IUser | null = await User.findById(userId); + if (!user) { + return null; + } + + const roles = await Role.find({ _id: { $in: user.roles } }); + rolesCache.set(userId, { roles, timestamp: Date.now() }); + return roles; +}; + +const hasPermission = (roles: IRole[], requiredPermissions: string[]) => { + const userPermissions = [...new Set(roles.flatMap((role) => role.permissions))]; + + if (userPermissions.includes("*")) return true; + + const matches = (requiredPermission: string, userPermission: string) => { + if (userPermission === requiredPermission) return true; + if (userPermission.endsWith(".*")) { + const prefix = userPermission.slice(0, -2); + return requiredPermission.startsWith(prefix + "."); + } + return false; + }; + + return requiredPermissions.every((requiredPermission) => { + return userPermissions.some((userPermission) => matches(requiredPermission, userPermission)); + }); +}; + +const verifyPermission = (resourceActions: string[]) => { + return async (req: Request, res: Response, next: NextFunction) => { + const tokenizedUser = req.user; + + if (!tokenizedUser) { + throw new ApiError("No user", 400); + } + + const userId = tokenizedUser.sub; + if (!userId) { + throw new ApiError("No user ID", 400); + } + + const userRoles = await getCachedRoles(userId); + if (!userRoles) { + throw new ApiError("User roles not found", 400); + } + const allowed = hasPermission(userRoles, resourceActions); + if (!allowed) { + throw new ApiError("Insufficient permissions", 403); + } + next(); + }; +}; + +export { verifyPermission }; diff --git a/server/src/middleware/v2/VerifyToken.ts b/server/src/middleware/v2/VerifyToken.ts new file mode 100644 index 000000000..7dabd51cc --- /dev/null +++ b/server/src/middleware/v2/VerifyToken.ts @@ -0,0 +1,21 @@ +import { Request, Response, NextFunction } from "express"; +import { decode } from "../../utils/JWTUtils.js"; +import ApiError from "../../utils/ApiError.js"; +const verifyToken = (req: Request, res: Response, next: NextFunction) => { + const token = req.cookies.token; + + if (!token) { + const error = new ApiError("No token provided", 401); + return next(error); + } + + try { + const decoded = decode(token); + req.user = decoded; + next(); + } catch (error) { + next(error); + } +}; + +export { verifyToken }; diff --git a/server/src/routes/v2/auth.ts b/server/src/routes/v2/auth.ts index e69de29bb..919d0fd76 100644 --- a/server/src/routes/v2/auth.ts +++ b/server/src/routes/v2/auth.ts @@ -0,0 +1,33 @@ +import { Router } from "express"; + +import express from "express"; +import AuthController from "../../controllers/v2/AuthController.js"; +import { verifyToken } from "../../middleware/v2/VerifyToken.js"; + +const router = express.Router(); + +class AuthRoutes { + private controller: AuthController; + private router: Router; + constructor(authController: AuthController) { + this.controller = authController; + this.router = Router(); + this.initRoutes(); + } + + initRoutes = () => { + this.router.post("/register", this.controller.register); + this.router.post("/register/invite/:token", this.controller.registerWithInvite); + this.router.post("/login", this.controller.login); + this.router.post("/logout", this.controller.logout); + this.router.get("/me", verifyToken, this.controller.me); + this.router.post("/cleanup", this.controller.cleanup); + this.router.post("/cleanup-monitors", this.controller.cleanMonitors); + }; + + getRouter() { + return this.router; + } +} + +export default AuthRoutes; diff --git a/server/src/routes/v2/invite.ts b/server/src/routes/v2/invite.ts new file mode 100644 index 000000000..8066d0c77 --- /dev/null +++ b/server/src/routes/v2/invite.ts @@ -0,0 +1,30 @@ +import { Router } from "express"; +import InviteController from "../../controllers/v2/InviteController.js"; +import { verifyToken } from "../../middleware/v2/VerifyToken.js"; +import { verifyPermission } from "../../middleware/v2/VerifyPermissions.js"; + +class InviteRoutes { + private router; + private controller; + constructor(inviteController: InviteController) { + this.router = Router(); + this.controller = inviteController; + this.initRoutes(); + } + + initRoutes = () => { + this.router.post("/", verifyToken, verifyPermission(["invite.create"]), this.controller.create); + + this.router.get("/", verifyToken, verifyPermission(["invite.view"]), this.controller.getAll); + + this.router.get("/:token", verifyToken, verifyPermission(["invite.view"]), this.controller.get); + + this.router.delete("/:id", verifyToken, verifyPermission(["invite.delete"]), this.controller.delete); + }; + + getRouter() { + return this.router; + } +} + +export default InviteRoutes; diff --git a/server/src/routes/v2/maintenance.ts b/server/src/routes/v2/maintenance.ts new file mode 100644 index 000000000..62bc5f1cf --- /dev/null +++ b/server/src/routes/v2/maintenance.ts @@ -0,0 +1,34 @@ +import { Router } from "express"; +import MaintenanceController from "../../controllers/v2/MaintenanceController.js"; +import { verifyToken } from "../../middleware/v2/VerifyToken.js"; +import { verifyPermission } from "../../middleware/v2/VerifyPermissions.js"; + +class MaintenanceRoutes { + private router; + private controller; + constructor(maintenanceController: MaintenanceController) { + this.router = Router(); + this.controller = maintenanceController; + this.initRoutes(); + } + + initRoutes = () => { + this.router.post("/", verifyToken, verifyPermission(["maintenance.create"]), this.controller.create); + + this.router.get("/", verifyToken, verifyPermission(["maintenance.view"]), this.controller.getAll); + + this.router.patch("/:id/active", verifyToken, verifyPermission(["maintenance.update"]), this.controller.toggleActive); + + this.router.patch("/:id", verifyToken, verifyPermission(["maintenance.update"]), this.controller.update); + + this.router.get("/:id", verifyToken, verifyPermission(["maintenance.view"]), this.controller.get); + + this.router.delete("/:id", verifyToken, verifyPermission(["maintenance.delete"]), this.controller.delete); + }; + + getRouter() { + return this.router; + } +} + +export default MaintenanceRoutes; diff --git a/server/src/routes/v2/monitors.ts b/server/src/routes/v2/monitors.ts new file mode 100644 index 000000000..eb218f4c6 --- /dev/null +++ b/server/src/routes/v2/monitors.ts @@ -0,0 +1,34 @@ +import { Router } from "express"; +import MonitorController from "../../controllers/v2/MonitorController.js"; +import { verifyToken } from "../../middleware/v2/VerifyToken.js"; +import { verifyPermission } from "../../middleware/v2/VerifyPermissions.js"; + +class MonitorRoutes { + private router; + private controller; + constructor(monitorController: MonitorController) { + this.router = Router(); + this.controller = monitorController; + this.initRoutes(); + } + + initRoutes = () => { + this.router.post("/", verifyToken, verifyPermission(["monitors.create"]), this.controller.create); + + this.router.get("/", verifyToken, verifyPermission(["monitors.view"]), this.controller.getAll); + + this.router.patch("/:id/active", verifyToken, verifyPermission(["monitors.update"]), this.controller.toggleActive); + + this.router.patch("/:id", verifyToken, verifyPermission(["monitors.update"]), this.controller.update); + + this.router.get("/:id", verifyToken, verifyPermission(["monitors.view"]), this.controller.get); + + this.router.delete("/:id", verifyToken, verifyPermission(["monitors.delete"]), this.controller.delete); + }; + + getRouter() { + return this.router; + } +} + +export default MonitorRoutes; diff --git a/server/src/routes/v2/notificationChannels.ts b/server/src/routes/v2/notificationChannels.ts new file mode 100644 index 000000000..1db32902a --- /dev/null +++ b/server/src/routes/v2/notificationChannels.ts @@ -0,0 +1,34 @@ +import { Router } from "express"; +import NotificationController from "../../controllers/v2/NotificationChannelController.js"; +import { verifyToken } from "../../middleware/v2/VerifyToken.js"; +import { verifyPermission } from "../../middleware/v2/VerifyPermissions.js"; + +class NotificationChannelRoutes { + private router; + private controller; + constructor(notificationController: NotificationController) { + this.router = Router(); + this.controller = notificationController; + this.initRoutes(); + } + + initRoutes = () => { + this.router.post("/", verifyToken, verifyPermission(["notifications.create"]), this.controller.create); + + this.router.get("/", verifyToken, verifyPermission(["notifications.view"]), this.controller.getAll); + + this.router.patch("/:id/active", verifyToken, verifyPermission(["notifications.update"]), this.controller.toggleActive); + + this.router.patch("/:id", verifyToken, verifyPermission(["notifications.update"]), this.controller.update); + + this.router.get("/:id", verifyToken, verifyPermission(["notifications.view"]), this.controller.get); + + this.router.delete("/:id", verifyToken, verifyPermission(["notifications.delete"]), this.controller.delete); + }; + + getRouter() { + return this.router; + } +} + +export default NotificationChannelRoutes; diff --git a/server/src/routes/v2/queue.ts b/server/src/routes/v2/queue.ts new file mode 100644 index 000000000..428776e61 --- /dev/null +++ b/server/src/routes/v2/queue.ts @@ -0,0 +1,24 @@ +import QueueController from "../../controllers/v2/QueueController.js"; +import { Router } from "express"; + +class QueueRoutes { + private router; + private controller; + constructor(queueController: QueueController) { + this.router = Router(); + this.controller = queueController; + this.initRoutes(); + } + + initRoutes() { + this.router.get("/jobs", this.controller.getJobs); + this.router.get("/metrics", this.controller.getMetrics); + this.router.post("/flush", this.controller.flush); + } + + getRouter() { + return this.router; + } +} + +export default QueueRoutes; diff --git a/server/src/service/v2/business/AuthService.ts b/server/src/service/v2/business/AuthService.ts index cf49a71c6..67ece226e 100644 --- a/server/src/service/v2/business/AuthService.ts +++ b/server/src/service/v2/business/AuthService.ts @@ -4,6 +4,8 @@ import ApiError from "../../../utils/ApiError.js"; import { Types } from "mongoose"; import { IJobQueue } from "../infrastructure/JobQueue.js"; +const SERVICE_NAME = "AuthServiceV2"; + export const PERMISSIONS = { users: { all: "users.*", @@ -94,6 +96,8 @@ export interface IAuthService { } class AuthService implements IAuthService { + static SERVICE_NAME = SERVICE_NAME; + private jobQueue: IJobQueue; constructor(jobQueue: IJobQueue) { this.jobQueue = jobQueue; diff --git a/server/src/service/v2/business/CheckService.ts b/server/src/service/v2/business/CheckService.ts index 5c68891a9..7187fb5a3 100644 --- a/server/src/service/v2/business/CheckService.ts +++ b/server/src/service/v2/business/CheckService.ts @@ -6,12 +6,15 @@ import { StatusResponse } from "../infrastructure/NetworkService.js"; import type { ICapturePayload, ILighthousePayload } from "../infrastructure/NetworkService.js"; import mongoose from "mongoose"; +const SERVICE_NAME = "CheckServiceV2"; export interface ICheckService { buildCheck: (statusResponse: StatusResponse, type: MonitorType) => Promise; cleanupOrphanedChecks: () => Promise; } class CheckService implements ICheckService { + static SERVICE_NAME = SERVICE_NAME; + private isCapturePayload = (payload: any): payload is ICapturePayload => { if (!payload || typeof payload !== "object") return false; diff --git a/server/src/service/v2/business/InviteService.ts b/server/src/service/v2/business/InviteService.ts index 1c954d75f..b06b2a2c2 100644 --- a/server/src/service/v2/business/InviteService.ts +++ b/server/src/service/v2/business/InviteService.ts @@ -2,6 +2,7 @@ import crypto from "node:crypto"; import { ITokenizedUser, IInvite, Invite } from "../../../db/v2/models/index.js"; import ApiError from "../../../utils/ApiError.js"; +const SERVICE_NAME = "InviteServiceV2"; export interface IInviteService { create: (tokenizedUser: ITokenizedUser, invite: IInvite) => Promise<{ token: string }>; getAll: () => Promise; @@ -10,6 +11,7 @@ export interface IInviteService { } class InviteService implements IInviteService { + static SERVICE_NAME = SERVICE_NAME; constructor() {} create = async (tokenizedUser: ITokenizedUser, inviteData: IInvite) => { diff --git a/server/src/service/v2/business/MaintenanceService.ts b/server/src/service/v2/business/MaintenanceService.ts index ef8c8152f..55b2d787b 100644 --- a/server/src/service/v2/business/MaintenanceService.ts +++ b/server/src/service/v2/business/MaintenanceService.ts @@ -1,6 +1,8 @@ import { ITokenizedUser, IMaintenance, Maintenance } from "../../../db/v2/models/index.js"; import ApiError from "../../../utils/ApiError.js"; +const SERVICE_NAME = "MaintenanceServiceV2"; + export interface IMaintenanceService { create: ( tokenizedUser: ITokenizedUser, @@ -18,6 +20,7 @@ export interface IMaintenanceService { type MaintenanceCache = Map; class MaintenanceService implements IMaintenanceService { + static SERVICE_NAME = SERVICE_NAME; private maintenanceCache: MaintenanceCache; private lastRefresh: number; private CACHE_TTL_MS = 60 * 1000; diff --git a/server/src/service/v2/business/MonitorService.ts b/server/src/service/v2/business/MonitorService.ts new file mode 100644 index 000000000..61fc8b114 --- /dev/null +++ b/server/src/service/v2/business/MonitorService.ts @@ -0,0 +1,468 @@ +import mongoose from "mongoose"; + +import { IMonitor, Monitor, ITokenizedUser, MonitorStats, Check } from "../../../db/v2/models/index.js"; +import ApiError from "../../../utils/ApiError.js"; +import { IJobQueue } from "../infrastructure/JobQueue.js"; +import { MonitorWithChecksResponse } from "../../../types/index.js"; +import { MonitorStatus, MonitorType } from "../../../db/v2/models/monitors/Monitor.js"; + +const SERVICE_NAME = "MonitorServiceV2"; + +export interface IMonitorService { + create: (tokenizedUser: ITokenizedUser, monitorData: IMonitor) => Promise; + getAll: () => Promise; + getAllEmbedChecks: (page: number, limit: number, type: MonitorType[]) => Promise; + get: (monitorId: string) => Promise; + getEmbedChecks: (monitorId: string, range: string, status?: string) => Promise; + toggleActive: (monitorId: string, tokenizedUser: ITokenizedUser) => Promise; + update: (tokenizedUser: ITokenizedUser, monitorId: string, updateData: Partial) => Promise; + delete: (monitorId: string) => Promise; +} + +class MonitorService implements IMonitorService { + static SERVICE_NAME = SERVICE_NAME; + private jobQueue: IJobQueue; + constructor(jobQueue: IJobQueue) { + this.jobQueue = jobQueue; + } + + create = async (tokenizedUser: ITokenizedUser, monitorData: IMonitor) => { + const monitor = await Monitor.create({ + ...monitorData, + createdBy: tokenizedUser.sub, + updatedBy: tokenizedUser.sub, + }); + await MonitorStats.create({ + monitorId: monitor._id, + currentStreakStartedAt: Date.now(), + }); + await this.jobQueue.addJob(monitor); + return monitor; + }; + + getAll = async () => { + return Monitor.find(); + }; + + getAllEmbedChecks = async (page: number, limit: number, type: MonitorType[] = []) => { + const skip = (page - 1) * limit; + let find = {}; + if (type.length > 0) find = { type: { $in: type } }; + const monitors = await Monitor.find(find).skip(skip).limit(limit); + return monitors; + }; + + get = async (monitorId: string) => { + const monitor = await Monitor.findById(monitorId); + if (!monitor) { + throw new ApiError("Monitor not found", 404); + } + return monitor; + }; + + private getStartDate(range: string): Date { + const now = new Date(); + switch (range) { + case "30m": + return new Date(now.getTime() - 30 * 60 * 1000); + case "24h": + return new Date(now.getTime() - 24 * 60 * 60 * 1000); + case "7d": + return new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000); + case "30d": + return new Date(now.getTime() - 30 * 24 * 60 * 60 * 1000); + default: + throw new ApiError("Invalid range parameter", 400); + } + } + + private getDateFormat(range: string): string { + switch (range) { + case "30m": + return "%Y-%m-%dT%H:%M:00Z"; + case "24h": + case "7d": + return "%Y-%m-%dT%H:00:00Z"; + case "30d": + return "%Y-%m-%d"; + default: + throw new ApiError("Invalid range parameter", 400); + } + } + + private getBaseGroup = (dateFormat: string): Record => { + return { + _id: { $dateToString: { format: dateFormat, date: "$createdAt" } }, + count: { $sum: 1 }, + avgResponseTime: { $avg: "$responseTime" }, + }; + }; + + private getBaseProjection = (): object => { + return { status: 1, responseTime: 1, createdAt: 1 }; + }; + + private getPageSpeedGroup = (dateFormat: string): Record => { + return { + _id: { $dateToString: { format: dateFormat, date: "$createdAt" } }, + count: { $sum: 1 }, + avgResponseTime: { $avg: "$responseTime" }, + accessibility: { $avg: "$lighthouse.accessibility" }, + bestPractices: { $avg: "$lighthouse.bestPractices" }, + seo: { $avg: "$lighthouse.seo" }, + performance: { $avg: "$lighthouse.performance" }, + cls: { $avg: "$lighthouse.audits.cls.score" }, + si: { $avg: "$lighthouse.audits.si.score" }, + fcp: { $avg: "$lighthouse.audits.fcp.score" }, + lcp: { $avg: "$lighthouse.audits.lcp.score" }, + tbt: { $avg: "$lighthouse.audits.tbt.score" }, + }; + }; + + private getPageSpeedProjection = (): object => { + const projectStage: any = { status: 1, responseTime: 1, createdAt: 1 }; + projectStage["lighthouse.accessibility"] = 1; + projectStage["lighthouse.seo"] = 1; + projectStage["lighthouse.bestPractices"] = 1; + projectStage["lighthouse.performance"] = 1; + projectStage["lighthouse.audits.cls.score"] = 1; + projectStage["lighthouse.audits.si.score"] = 1; + projectStage["lighthouse.audits.fcp.score"] = 1; + projectStage["lighthouse.audits.lcp.score"] = 1; + projectStage["lighthouse.audits.tbt.score"] = 1; + return projectStage; + }; + + private getInfraGroup = (dateFormat: string): Record => { + return { + _id: { $dateToString: { format: dateFormat, date: "$createdAt" } }, + count: { $sum: 1 }, + avgResponseTime: { $avg: "$responseTime" }, + physicalCores: { $last: "$system.cpu.physical_core" }, + logicalCores: { $last: "$system.cpu.logical_core" }, + frequency: { $avg: "$system.cpu.frequency" }, + currentFrequency: { $last: "$system.cpu.current_frequency" }, + tempsArrays: { $push: "$system.cpu.temperature" }, + freePercent: { $avg: "$system.cpu.free_percent" }, + usedPercent: { $avg: "$system.cpu.usage_percent" }, + total_bytes: { $last: "$system.memory.total_bytes" }, + available_bytes: { $last: "$system.memory.available_bytes" }, + used_bytes: { $last: "$system.memory.used_bytes" }, + memory_usage_percent: { $avg: "$system.memory.usage_percent" }, + disksArray: { $push: "$system.disk" }, + os: { $last: "$system.host.os" }, + platform: { $last: "$system.host.platform" }, + kernel_version: { $last: "$system.host.kernel_version" }, + pretty_name: { $last: "$system.host.pretty_name" }, + netsArray: { $push: "$system.net" }, + }; + }; + + private getInfraProjection = (): object => { + const projectStage: any = { status: 1, responseTime: 1, createdAt: 1 }; + projectStage["system.cpu.physical_core"] = 1; + projectStage["system.cpu.logical_core"] = 1; + projectStage["system.cpu.frequency"] = 1; + projectStage["system.cpu.current_frequency"] = 1; + projectStage["system.cpu.temperature"] = 1; + projectStage["system.cpu.free_percent"] = 1; + projectStage["system.cpu.usage_percent"] = 1; + projectStage["system.memory.total_bytes"] = 1; + projectStage["system.memory.available_bytes"] = 1; + projectStage["system.memory.used_bytes"] = 1; + projectStage["system.memory.usage_percent"] = 1; + projectStage["system.disk"] = 1; + projectStage["system.host.os"] = 1; + projectStage["system.host.platform"] = 1; + projectStage["system.host.kernel_version"] = 1; + projectStage["system.host.pretty_name"] = 1; + projectStage["system.net"] = 1; + return projectStage; + }; + + private getFinalProjection = (type: string): object => { + if (type === "pagespeed") { + return { + _id: 1, + count: 1, + avgResponseTime: 1, + accessibility: "$accessibility", + seo: "$seo", + bestPractices: "$bestPractices", + performance: "$performance", + cls: "$cls", + si: "$si", + fcp: "$fcp", + lcp: "$lcp", + tbt: "$tbt", + }; + } + + if (type === "infrastructure") { + return { + _id: 1, + count: 1, + avgResponseTime: 1, + cpu: { + physicalCores: "$physicalCores", + logicalCores: "$logicalCores", + frequency: "$frequency", + currentFrequency: "$currentFrequency", + temperatures: { + $map: { + input: { + $range: [0, { $size: { $arrayElemAt: ["$tempsArrays", 0] } }], + }, + as: "idx", + in: { + $avg: { + $map: { + input: "$tempsArrays", + as: "arr", + in: { $arrayElemAt: ["$$arr", "$$idx"] }, + }, + }, + }, + }, + }, + freePercent: "$freePercent", + usedPercent: "$usedPercent", + }, + memory: { + total_bytes: "$total_bytes", + available_bytes: "$available_bytes", + used_bytes: "$used_bytes", + usage_percent: "$memory_usage_percent", + }, + disks: { + $map: { + input: { + $range: [0, { $size: { $arrayElemAt: ["$disksArray", 0] } }], + }, + as: "idx", + in: { + $let: { + vars: { + diskGroup: { + $map: { + input: "$disksArray", + as: "diskArr", + in: { $arrayElemAt: ["$$diskArr", "$$idx"] }, + }, + }, + }, + in: { + device: { $arrayElemAt: ["$$diskGroup.device", 0] }, + total_bytes: { $avg: "$$diskGroup.total_bytes" }, + free_bytes: { $avg: "$$diskGroup.free_bytes" }, + used_bytes: { $avg: "$$diskGroup.used_bytes" }, + usage_percent: { $avg: "$$diskGroup.usage_percent" }, + total_inodes: { $avg: "$$diskGroup.total_inodes" }, + free_inodes: { $avg: "$$diskGroup.free_inodes" }, + used_inodes: { $avg: "$$diskGroup.used_inodes" }, + inodes_usage_percent: { + $avg: "$$diskGroup.inodes_usage_percent", + }, + read_bytes: { $avg: "$$diskGroup.read_bytes" }, + write_bytes: { $avg: "$$diskGroup.write_bytes" }, + read_time: { $avg: "$$diskGroup.read_time" }, + write_time: { $avg: "$$diskGroup.write_time" }, + }, + }, + }, + }, + }, + host: { + os: "$os", + platform: "$platform", + kernel_version: "$kernel_version", + pretty_name: "$pretty_name", + }, + net: { + $map: { + input: { + $range: [0, { $size: { $arrayElemAt: ["$netsArray", 0] } }], + }, + as: "idx", + in: { + $let: { + vars: { + netGroup: { + $map: { + input: "$netsArray", + as: "netArr", + in: { $arrayElemAt: ["$$netArr", "$$idx"] }, + }, + }, + }, + in: { + name: { $arrayElemAt: ["$$netGroup.name", 0] }, + bytes_sent: { $avg: "$$netGroup.bytes_sent" }, + bytes_recv: { $avg: "$$netGroup.bytes_recv" }, + packets_sent: { $avg: "$$netGroup.packets_sent" }, + packets_recv: { $avg: "$$netGroup.packets_recv" }, + err_in: { $avg: "$$netGroup.err_in" }, + err_out: { $avg: "$$netGroup.err_out" }, + drop_in: { $avg: "$$netGroup.drop_in" }, + drop_out: { $avg: "$$netGroup.drop_out" }, + fifo_in: { $avg: "$$netGroup.fifo_in" }, + fifo_out: { $avg: "$$netGroup.fifo_out" }, + }, + }, + }, + }, + }, + }; + } + return {}; + }; + + getEmbedChecks = async (monitorId: string, range: string, status: string | undefined): Promise => { + const monitor = await Monitor.findById(monitorId); + if (!monitor) { + throw new ApiError("Monitor not found", 404); + } + const startDate = this.getStartDate(range); + const dateFormat = this.getDateFormat(range); + + // Build match stage + const matchStage: { + monitorId: mongoose.Types.ObjectId; + createdAt: { $gte: Date }; + status?: string; + } = { + monitorId: monitor._id, + createdAt: { $gte: startDate }, + }; + + if (status) { + matchStage.status = status; + } + + let groupClause; + + if (monitor.type === "pagespeed") { + groupClause = this.getPageSpeedGroup(dateFormat); + } else if (monitor.type === "infrastructure") { + groupClause = this.getInfraGroup(dateFormat); + } else { + groupClause = this.getBaseGroup(dateFormat); + } + + let projectStage; + if (monitor.type === "pagespeed") { + projectStage = this.getPageSpeedProjection(); + } else if (monitor.type === "infrastructure") { + projectStage = this.getInfraProjection(); + } else { + projectStage = this.getBaseProjection(); + } + + let finalProjection = {}; + if (monitor.type === "pagespeed" || monitor.type === "infrastructure") { + finalProjection = this.getFinalProjection(monitor.type); + } else { + finalProjection = { _id: 1, count: 1, avgResponseTime: 1 }; + } + + const checks = await Check.aggregate([ + { + $match: matchStage, + }, + { $sort: { createdAt: 1 } }, + { $project: projectStage }, + { $group: groupClause }, + { $sort: { _id: -1 } }, + { + $project: finalProjection, + }, + ]); + + // Get monitor stats + const monitorStats = await MonitorStats.findOne({ + monitorId: monitor._id, + }).lean(); + + if (!monitorStats) { + throw new ApiError("Monitor stats not found", 404); + } + + return { + monitor: monitor.toObject(), + checks, + stats: monitorStats, + }; + }; + + async toggleActive(id: string, tokenizedUser: ITokenizedUser) { + const pendingStatus: MonitorStatus = "initializing"; + const updatedMonitor = await Monitor.findOneAndUpdate( + { _id: id }, + [ + { + $set: { + isActive: { $not: "$isActive" }, + status: pendingStatus, + updatedBy: tokenizedUser.sub, + updatedAt: new Date(), + }, + }, + ], + { new: true } + ); + + if (!updatedMonitor) { + throw new ApiError("Monitor not found", 404); + } + + await this.jobQueue.updateJob(updatedMonitor); + + if (updatedMonitor?.isActive) { + await this.jobQueue.resumeJob(updatedMonitor); + } else { + await this.jobQueue.pauseJob(updatedMonitor); + } + return updatedMonitor; + } + + async update(tokenizedUser: ITokenizedUser, monitorId: string, updateData: Partial) { + const allowedFields: (keyof IMonitor)[] = ["name", "interval", "isActive", "n", "notificationChannels"]; + const safeUpdate: Partial = {}; + + for (const field of allowedFields) { + if (updateData[field] !== undefined) { + (safeUpdate as any)[field] = updateData[field]; + } + } + + const updatedMonitor = await Monitor.findByIdAndUpdate( + monitorId, + { + $set: { + ...safeUpdate, + updatedAt: new Date(), + updatedBy: tokenizedUser.sub, + }, + }, + { new: true, runValidators: true } + ); + + if (!updatedMonitor) { + throw new ApiError("Monitor not found", 404); + } + await this.jobQueue.updateJob(updatedMonitor); + return updatedMonitor; + } + + async delete(monitorId: string) { + const monitor = await Monitor.findById(monitorId); + if (!monitor) { + throw new ApiError("Monitor not found", 404); + } + await monitor.deleteOne(); + await this.jobQueue.deleteJob(monitor); + return true; + } +} + +export default MonitorService; diff --git a/server/src/service/v2/business/MonitorStatsService.ts b/server/src/service/v2/business/MonitorStatsService.ts index dc7d4acb3..089b43ec4 100644 --- a/server/src/service/v2/business/MonitorStatsService.ts +++ b/server/src/service/v2/business/MonitorStatsService.ts @@ -1,10 +1,13 @@ import { MonitorStats } from "../../../db/v2/models/index.js"; import { Monitor } from "../../../db/v2/models/index.js"; + +const SERVICE_NAME = "MonitorStatsServiceV2"; export interface IMonitorStatsService { cleanupOrphanedMonitorStats: () => Promise; } class MonitorStatsService implements IMonitorStatsService { + static SERVICE_NAME = SERVICE_NAME; constructor() {} async cleanupOrphanedMonitorStats() { diff --git a/server/src/service/v2/business/NotificationChannelService.ts b/server/src/service/v2/business/NotificationChannelService.ts index 35eae5b12..112efa691 100644 --- a/server/src/service/v2/business/NotificationChannelService.ts +++ b/server/src/service/v2/business/NotificationChannelService.ts @@ -1,6 +1,8 @@ import { ITokenizedUser, INotificationChannel, NotificationChannel, Monitor } from "../../../db/v2/models/index.js"; import ApiError from "../../../utils/ApiError.js"; +const SERVICE_NAME = "NotificationChannelServiceV2"; + export interface INotificationChannelService { create: ( tokenizedUser: ITokenizedUser, @@ -15,6 +17,8 @@ export interface INotificationChannelService { } class NotificationChannelService implements INotificationChannelService { + static SERVICE_NAME = SERVICE_NAME; + constructor() {} create = async (tokenizedUser: ITokenizedUser, notificationChannelData: INotificationChannel) => { diff --git a/server/src/service/v2/business/QueueService.ts b/server/src/service/v2/business/QueueService.ts index 20069072e..620544ba7 100644 --- a/server/src/service/v2/business/QueueService.ts +++ b/server/src/service/v2/business/QueueService.ts @@ -1,6 +1,9 @@ import { IJobQueue } from "../infrastructure/JobQueue.js"; +const SERVICE_NAME = "QueueServiceV2"; + class QueueService { + static SERVICE_NAME = SERVICE_NAME; private jobQueue: IJobQueue; constructor(jobQueue: IJobQueue) { diff --git a/server/src/service/v2/business/UserService.ts b/server/src/service/v2/business/UserService.ts index 46d221759..09c50dcbc 100644 --- a/server/src/service/v2/business/UserService.ts +++ b/server/src/service/v2/business/UserService.ts @@ -1,10 +1,12 @@ import { IUser, User } from "../../../db/v2/models/index.js"; +const SERVICE_NAME = "UserServiceV2"; export interface IUserService { getAllUsers(): Promise; } class UserService implements IUserService { + static SERVICE_NAME = SERVICE_NAME; async getAllUsers(): Promise { return await User.find(); } diff --git a/server/src/service/v2/infrastructure/JobGenerator.ts b/server/src/service/v2/infrastructure/JobGenerator.ts index 4f2beaf71..69b716920 100644 --- a/server/src/service/v2/infrastructure/JobGenerator.ts +++ b/server/src/service/v2/infrastructure/JobGenerator.ts @@ -7,12 +7,14 @@ import { INotificationService } from "./NotificationService.js"; import { IMaintenanceService } from "../business/MaintenanceService.js"; import ApiError from "../../../utils/ApiError.js"; +const SERVICE_NAME = "JobGeneratorV2"; export interface IJobGenerator { generateJob: () => (Monitor: IMonitor) => Promise; generateCleanupJob: () => () => Promise; } class JobGenerator implements IJobGenerator { + static SERVICE_NAME = SERVICE_NAME; private networkService: INetworkService; private checkService: ICheckService; private monitorStatsService: IMonitorStatsService; diff --git a/server/src/service/v2/infrastructure/JobQueue.ts b/server/src/service/v2/infrastructure/JobQueue.ts index af415d3f7..85479d8e5 100644 --- a/server/src/service/v2/infrastructure/JobQueue.ts +++ b/server/src/service/v2/infrastructure/JobQueue.ts @@ -2,6 +2,8 @@ import { IJob } from "super-simple-scheduler/dist/job/job.js"; import { Monitor, IMonitor } from "../../../db/v2/models/index.js"; import Scheduler from "super-simple-scheduler"; import { IJobGenerator } from "./JobGenerator.js"; + +const SERVICE_NAME = "JobQueueV2"; export interface IJobMetrics { jobs: number; activeJobs: number; @@ -36,6 +38,8 @@ export interface IJobQueue { } export default class JobQueue implements IJobQueue { + static SERVICE_NAME = SERVICE_NAME; + private scheduler: Scheduler; private static instance: JobQueue | null = null; private jobGenerator: any; diff --git a/server/src/service/v2/infrastructure/NetworkService.ts b/server/src/service/v2/infrastructure/NetworkService.ts index 90657bd2d..ab8427d84 100644 --- a/server/src/service/v2/infrastructure/NetworkService.ts +++ b/server/src/service/v2/infrastructure/NetworkService.ts @@ -1,4 +1,5 @@ import { Got, HTTPError } from "got"; +import got from "got"; import ping from "ping"; import { IMonitor } from "../../../db/v2/models/index.js"; import { GotTimings } from "../../../db/v2/models/checks/Check.js"; @@ -7,6 +8,8 @@ import type { ISystemInfo, ICaptureInfo, ILighthouseResult } from "../../../db/v import { MonitorType, MonitorStatus } from "../../../db/v2/models/monitors/Monitor.js"; import ApiError from "../../../utils/ApiError.js"; import { config } from "../../../config/index.js"; + +const SERVICE_NAME = "NetworkServiceV2"; export interface INetworkService { requestHttp: (monitor: IMonitor) => Promise; requestInfrastructure: (monitor: IMonitor) => Promise; @@ -36,9 +39,10 @@ export interface StatusResponse { } class NetworkService implements INetworkService { + static SERVICE_NAME = SERVICE_NAME; private got: Got; private NETWORK_ERROR: number; - constructor(got: Got) { + constructor() { this.got = got; this.NETWORK_ERROR = 5000; } diff --git a/server/src/service/v2/infrastructure/NotificationService.ts b/server/src/service/v2/infrastructure/NotificationService.ts index 4195958bd..2cc5e75d8 100644 --- a/server/src/service/v2/infrastructure/NotificationService.ts +++ b/server/src/service/v2/infrastructure/NotificationService.ts @@ -1,11 +1,14 @@ import UserService from "../business/UserService.js"; import { IMonitor, NotificationChannel } from "../../../db/v2/models/index.js"; import { EmailService, SlackService, DiscordService, WebhookService } from "./NotificationServices/index.js"; + +const SERVICE_NAME = "NotificationServiceV2"; export interface INotificationService { handleNotifications: (monitor: IMonitor) => Promise; } class NotificationService implements INotificationService { + static SERVICE_NAME = SERVICE_NAME; private emailService: EmailService; private slackService: SlackService; private discordService: DiscordService; diff --git a/server/src/service/v2/infrastructure/NotificationServices/Discord.ts b/server/src/service/v2/infrastructure/NotificationServices/Discord.ts index c9ec30700..c3a5d375a 100644 --- a/server/src/service/v2/infrastructure/NotificationServices/Discord.ts +++ b/server/src/service/v2/infrastructure/NotificationServices/Discord.ts @@ -2,7 +2,10 @@ import { IMonitor, INotificationChannel } from "../../../../db/v2/models/index.j import { IAlert, IMessageService } from "./IMessageService.js"; import got from "got"; import ApiError from "../../../../utils/ApiError.js"; + +const SERVICE_NAME = "DiscordServiceV2"; class DiscordService implements IMessageService { + static SERVICE_NAME = SERVICE_NAME; constructor() {} private toDiscordEmbeds = (alert: IAlert) => { diff --git a/server/src/service/v2/infrastructure/NotificationServices/Email.ts b/server/src/service/v2/infrastructure/NotificationServices/Email.ts index d1443ce08..4bed9a156 100644 --- a/server/src/service/v2/infrastructure/NotificationServices/Email.ts +++ b/server/src/service/v2/infrastructure/NotificationServices/Email.ts @@ -4,7 +4,10 @@ import nodemailer, { Transporter } from "nodemailer"; import { config } from "../../../../config/index.js"; import UserService from "../../business/UserService.js"; import ApiError from "../../../../utils/ApiError.js"; + +const SERVICE_NAME = "EmailServiceV2"; class EmailService implements IMessageService { + static SERVICE_NAME = SERVICE_NAME; private transporter: Transporter; private userService: UserService; diff --git a/server/src/service/v2/infrastructure/NotificationServices/Slack.ts b/server/src/service/v2/infrastructure/NotificationServices/Slack.ts index 50e2021e4..0be56ca56 100644 --- a/server/src/service/v2/infrastructure/NotificationServices/Slack.ts +++ b/server/src/service/v2/infrastructure/NotificationServices/Slack.ts @@ -2,7 +2,9 @@ import { IMonitor, INotificationChannel } from "../../../../db/v2/models/index.j import { IAlert, IMessageService } from "./IMessageService.js"; import got from "got"; +const SERVICE_NAME = "SlackServiceV2"; class SlackService implements IMessageService { + static SERVICE_NAME = SERVICE_NAME; constructor() {} private toSlackBlocks = (alert: IAlert) => { diff --git a/server/src/service/v2/infrastructure/NotificationServices/Webhook.ts b/server/src/service/v2/infrastructure/NotificationServices/Webhook.ts index b84b2b220..5dd89b16d 100644 --- a/server/src/service/v2/infrastructure/NotificationServices/Webhook.ts +++ b/server/src/service/v2/infrastructure/NotificationServices/Webhook.ts @@ -2,7 +2,11 @@ import { IMonitor, INotificationChannel } from "../../../../db/v2/models/index.j import { IAlert, IMessageService } from "./IMessageService.js"; import ApiError from "../../../../utils/ApiError.js"; import got from "got"; + +const SERVICE_NAME = "WebhookServiceV2"; class WebhookService implements IMessageService { + static SERVICE_NAME = SERVICE_NAME; + constructor() {} buildAlert = (monitor: IMonitor) => { diff --git a/server/src/service/v2/infrastructure/StatusService.ts b/server/src/service/v2/infrastructure/StatusService.ts index 87b0df0af..f2881b160 100644 --- a/server/src/service/v2/infrastructure/StatusService.ts +++ b/server/src/service/v2/infrastructure/StatusService.ts @@ -2,6 +2,7 @@ import { IMonitor, IMonitorStats, MonitorStats } from "../../../db/v2/models/ind import { StatusResponse } from "./NetworkService.js"; import ApiError from "../../../utils/ApiError.js"; +const SERVICE_NAME = "StatusServiceV2"; const MAX_LATEST_CHECKS = 25; export interface IStatusService { updateMonitorStatus: (monitor: IMonitor, status: StatusResponse) => Promise; @@ -14,6 +15,7 @@ export interface IStatusService { export type StatusChangeResult = [updatedMonitor: IMonitor, statusChanged: boolean]; class StatusService implements IStatusService { + static SERVICE_NAME = SERVICE_NAME; updateMonitorStatus = async (monitor: IMonitor, statusResponse: StatusResponse): Promise => { const newStatus = statusResponse.status; monitor.lastCheckedAt = new Date(); diff --git a/server/src/types/express.d.ts b/server/src/types/express.d.ts new file mode 100644 index 000000000..7c78a053c --- /dev/null +++ b/server/src/types/express.d.ts @@ -0,0 +1,10 @@ +import { ITokenizedUser } from "../db/models/index.ts"; + +declare global { + namespace Express { + interface Request { + user?: ITokenizedUser; + resource?: any; + } + } +} diff --git a/server/src/types/index.ts b/server/src/types/index.ts new file mode 100644 index 000000000..6e708b934 --- /dev/null +++ b/server/src/types/index.ts @@ -0,0 +1 @@ +export type { MonitorWithChecksResponse } from "./monitor-response-with-checks.js"; diff --git a/server/src/types/monitor-response-with-checks.ts b/server/src/types/monitor-response-with-checks.ts new file mode 100644 index 000000000..660ce0035 --- /dev/null +++ b/server/src/types/monitor-response-with-checks.ts @@ -0,0 +1,11 @@ +import { IMonitor, IMonitorStats } from "../db/v2/models/index.js"; + +export interface MonitorWithChecksResponse { + monitor: IMonitor; + checks: Array<{ + _id: string; + count: number; + avgResponseTime: number; + }>; + stats: IMonitorStats; +} From 811a6bf2225fb9808a5d34a333d453386f03bf4a Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Thu, 25 Sep 2025 09:38:41 -0700 Subject: [PATCH 033/101] create and start v2 queue --- server/src/config/services.js | 10 ++++------ server/src/service/v1/infrastructure/bufferService.js | 10 +++++----- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/server/src/config/services.js b/server/src/config/services.js index ec07c5e15..c1679868d 100644 --- a/server/src/config/services.js +++ b/server/src/config/services.js @@ -231,18 +231,12 @@ export const initializeServices = async ({ logger, envSettings, settingsService }); // V2 Services - const jobQueueV2 = new JobQueueV2(); - const authServiceV2 = new AuthServiceV2(jobQueueV2); const checkServiceV2 = new CheckServiceV2(); const inviteServiceV2 = new InviteServiceV2(); const maintenanceServiceV2 = new MaintenanceServiceV2(); - const monitorServiceV2 = new MonitorServiceV2(jobQueueV2); const monitorStatsServiceV2 = new MonitorStatsServiceV2(); const notificationChannelServiceV2 = new NotificationChannelServiceV2(); - const queueServiceV2 = new QueueServiceV2(jobQueueV2); const userServiceV2 = new UserServiceV2(); - - // V2 Infra const discordServiceV2 = new DiscordServiceV2(); const emailServiceV2 = new EmailServiceV2(userServiceV2); const slackServiceV2 = new SlackServiceV2(); @@ -258,6 +252,10 @@ export const initializeServices = async ({ logger, envSettings, settingsService notificationServiceV2, maintenanceServiceV2 ); + const jobQueueV2 = await JobQueueV2.create(jobGeneratorV2); + const authServiceV2 = new AuthServiceV2(jobQueueV2); + const monitorServiceV2 = new MonitorServiceV2(jobQueueV2); + const queueServiceV2 = new QueueServiceV2(jobQueueV2); const services = { //v1 diff --git a/server/src/service/v1/infrastructure/bufferService.js b/server/src/service/v1/infrastructure/bufferService.js index ed48cc654..459d25af3 100755 --- a/server/src/service/v1/infrastructure/bufferService.js +++ b/server/src/service/v1/infrastructure/bufferService.js @@ -64,11 +64,11 @@ class BufferService { }); } this.buffer = []; - this.logger.debug({ - message: `Flushed ${items} items`, - service: this.SERVICE_NAME, - method: "flushBuffer", - }); + // this.logger.debug({ + // message: `Flushed ${items} items`, + // service: this.SERVICE_NAME, + // method: "flushBuffer", + // }); items = 0; } } From 5957479d03e0ad84c8dca84eae95d64fffe2bc9e Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Thu, 25 Sep 2025 09:46:11 -0700 Subject: [PATCH 034/101] express type --- server/package-lock.json | 98 ++++++++++++++++++++++++++++++++++++++++ server/package.json | 1 + 2 files changed, 99 insertions(+) diff --git a/server/package-lock.json b/server/package-lock.json index 2a522ae64..8c9eac34c 100644 --- a/server/package-lock.json +++ b/server/package-lock.json @@ -45,6 +45,7 @@ }, "devDependencies": { "@eslint/js": "^9.17.0", + "@types/express": "5.0.3", "@types/jsonwebtoken": "9.0.10", "@types/nodemailer": "7.0.1", "@types/ping": "0.4.4", @@ -3046,6 +3047,27 @@ "node": ">=10.13.0" } }, + "node_modules/@types/body-parser": { + "version": "1.19.6", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz", + "integrity": "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "node_modules/@types/connect": { + "version": "3.4.38", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", + "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/estree": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", @@ -3053,12 +3075,44 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/express": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/@types/express/-/express-5.0.3.tgz", + "integrity": "sha512-wGA0NX93b19/dZC1J18tKWVIYWyyF2ZjT9vin/NRu0qzzvfVzWjs04iq2rQ3H65vCTQYlRqs3YHfY7zjdV+9Kw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^5.0.0", + "@types/serve-static": "*" + } + }, + "node_modules/@types/express-serve-static-core": { + "version": "5.0.7", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.0.7.tgz", + "integrity": "sha512-R+33OsgWw7rOhD1emjU7dzCDHucJrgJXMA5PYCzJxVil0dsyx5iBEPHqpPfiKNJQb7lZ1vxwoLR4Z87bBUpeGQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, "node_modules/@types/http-cache-semantics": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.4.tgz", "integrity": "sha512-1m0bIFVc7eJWyve9S0RnuRgcQqF/Xd5QsUZAZeQFr1Q3/p9JWoQQEqmVy+DPTNpGXwhgIetAoYF8JSc33q29QA==", "license": "MIT" }, + "node_modules/@types/http-errors": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.5.tgz", + "integrity": "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/istanbul-lib-coverage": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", @@ -3084,6 +3138,13 @@ "@types/node": "*" } }, + "node_modules/@types/mime": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", + "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/ms": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", @@ -3118,12 +3179,49 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/qs": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.14.0.tgz", + "integrity": "sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/range-parser": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", + "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/relateurl": { "version": "0.2.33", "resolved": "https://registry.npmjs.org/@types/relateurl/-/relateurl-0.2.33.tgz", "integrity": "sha512-bTQCKsVbIdzLqZhLkF5fcJQreE4y1ro4DIyVrlDNSCJRRwHhB8Z+4zXXa8jN6eDvc2HbRsEYgbvrnGvi54EpSw==", "license": "MIT" }, + "node_modules/@types/send": { + "version": "0.17.5", + "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.5.tgz", + "integrity": "sha512-z6F2D3cOStZvuk2SaP6YrwkNO65iTZcwA2ZkSABegdkAh/lf+Aa/YQndZVfmEXT5vgAp6zv06VQ3ejSVjAny4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/mime": "^1", + "@types/node": "*" + } + }, + "node_modules/@types/serve-static": { + "version": "1.15.8", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.8.tgz", + "integrity": "sha512-roei0UY3LhpOJvjbIP6ZZFngyLKl5dskOtDhxY5THRSpO+ZI+nzJ+m5yUMzGrp89YRa7lvknKkMYjqQFGwA7Sg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/http-errors": "*", + "@types/node": "*", + "@types/send": "*" + } + }, "node_modules/@types/triple-beam": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/@types/triple-beam/-/triple-beam-1.3.5.tgz", diff --git a/server/package.json b/server/package.json index 2fdcf58cb..3bd79722a 100755 --- a/server/package.json +++ b/server/package.json @@ -54,6 +54,7 @@ }, "devDependencies": { "@eslint/js": "^9.17.0", + "@types/express": "5.0.3", "@types/jsonwebtoken": "9.0.10", "@types/nodemailer": "7.0.1", "@types/ping": "0.4.4", From 4d89213e1c46228521d9e918cff3bb06887881b4 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Thu, 25 Sep 2025 10:11:41 -0700 Subject: [PATCH 035/101] Add typescript config, move to v1 dir --- client/package.json | 4 +- client/src/Components/ActionsMenu/index.jsx | 2 +- .../src/Components/MonitorActions/index.jsx | 2 +- .../MonitorDetailsControlHeader/index.jsx | 6 +- .../MonitorDetailsControlHeader/status.jsx | 2 +- .../src/Components/PageStateWrapper/index.jsx | 2 +- client/src/Components/StatBox/index.jsx | 2 +- client/src/Hooks/{ => v1}/checkHooks.js | 4 +- client/src/Hooks/{ => v1}/inviteHooks.js | 2 +- client/src/Hooks/{ => v1}/logHooks.js | 4 +- client/src/Hooks/{ => v1}/monitorHooks.js | 6 +- client/src/Hooks/{ => v1}/settingsHooks.js | 4 +- client/src/Hooks/{ => v1}/useIsAdmin.js | 0 client/src/Hooks/{ => v1}/useMonitorUtils.js | 0 client/src/Hooks/{ => v1}/useNotifications.js | 6 +- client/src/Hooks/{ => v1}/useSendTestEmail.js | 4 +- client/src/Hooks/{ => v1}/userHooks.js | 4 +- client/src/Pages/{ => v1}/About/index.jsx | 4 +- .../Account/EditUser/hooks/editUser.js | 6 +- .../Pages/{ => v1}/Account/EditUser/index.jsx | 14 ++--- .../components/AddMemberMenu/index.jsx | 0 .../AddTeamMember/hooks/useAddTeamMember.jsx | 2 +- .../components/AddTeamMember/index.jsx | 18 +++--- .../Account/components/PasswordPanel.jsx | 14 ++--- .../Account/components/ProfilePanel.jsx | 18 +++--- .../Account/components/RoleTable/index.jsx | 4 +- .../{ => v1}/Account/components/TeamPanel.jsx | 22 +++---- client/src/Pages/{ => v1}/Account/index.jsx | 8 +-- client/src/Pages/{ => v1}/Auth/CheckEmail.jsx | 12 ++-- .../Pages/{ => v1}/Auth/ForgotPassword.jsx | 16 ++--- .../Auth/Login/hooks/useLoadingSubmit.jsx | 0 .../Auth/Login/hooks/useLoginForm.jsx | 0 .../Auth/Login/hooks/useLoginSubmit.jsx | 4 +- .../Auth/Login/hooks/useValidateLoginForm.jsx | 2 +- .../src/Pages/{ => v1}/Auth/Login/index.jsx | 16 ++--- .../{ => v1}/Auth/NewPasswordConfirmed.jsx | 10 ++-- .../Pages/{ => v1}/Auth/Register/index.jsx | 14 ++--- .../Pages/{ => v1}/Auth/SetNewPassword.jsx | 22 +++---- .../{ => v1}/Auth/components/AuthHeader.jsx | 6 +- .../Auth/components/AuthPageWrapper.jsx | 6 +- .../Auth/components/PasswordTooltip.jsx | 2 +- .../Auth/hooks/usePasswordFeedback.jsx | 2 +- .../Auth/hooks/useValidatePassword.jsx | 2 +- client/src/Pages/{ => v1}/Auth/index.css | 0 .../Components/IncidentTable/index.jsx | 22 +++---- .../Components/OptionsHeader/index.jsx | 4 +- .../Components/OptionsHeader/skeleton.jsx | 0 .../Components/StatusBoxes/StatusBox.jsx | 10 ++-- .../Components/StatusBoxes/index.jsx | 4 +- .../Components/StatusBoxes/skeleton.jsx | 0 client/src/Pages/{ => v1}/Incidents/index.jsx | 18 +++--- .../Create/Components/CustomAlertsSection.jsx | 6 +- .../Components/CustomThreshold/index.jsx | 4 +- .../Components/MonitorActionButtons.jsx | 2 +- .../Create/Components/MonitorStatusHeader.jsx | 4 +- .../hooks/useInfrastructureMonitorForm.jsx | 0 .../Create/hooks/useInfrastructureSubmit.jsx | 2 +- .../hooks/useValidateInfrastructureForm.jsx | 4 +- .../{ => v1}/Infrastructure/Create/index.jsx | 32 +++++----- .../AreaChartBoxes/InfraAreaChart.jsx | 6 +- .../Components/AreaChartBoxes/index.jsx | 8 +-- .../Components/AreaChartBoxes/skeleton.jsx | 0 .../Components/BaseContainer/index.jsx | 2 +- .../Details/Components/GaugeBoxes/Gauge.jsx | 4 +- .../Details/Components/GaugeBoxes/index.jsx | 6 +- .../Components/GaugeBoxes/skeleton.jsx | 0 .../Components/NetworkStats/NetworkCharts.jsx | 6 +- .../NetworkStats/NetworkStatBoxes.jsx | 6 +- .../Details/Components/NetworkStats/index.jsx | 8 +-- .../Components/NetworkStats/skeleton.jsx | 0 .../Details/Components/StatusBoxes/index.jsx | 8 +-- .../Details/Hooks/useHardwareUtils.jsx | 0 .../{ => v1}/Infrastructure/Details/index.jsx | 22 +++---- .../Monitors/Components/Filters/index.jsx | 2 +- .../Components/MonitorsTable/index.jsx | 16 ++--- .../Components/MonitorsTableMenu/index.jsx | 10 ++-- .../Infrastructure/Monitors/index.jsx | 22 +++---- .../src/Pages/{ => v1}/Integrations/index.css | 0 .../src/Pages/{ => v1}/Integrations/index.jsx | 6 +- .../Diagnostics/components/gauges/index.jsx | 4 +- .../Diagnostics/components/stats/index.jsx | 4 +- .../Pages/{ => v1}/Logs/Diagnostics/index.jsx | 12 ++-- .../{ => v1}/Logs/Diagnostics/utils/utils.js | 0 client/src/Pages/{ => v1}/Logs/Logs/index.jsx | 4 +- .../Queue/components/FailedJobTable/index.jsx | 4 +- .../Logs/Queue/components/JobTable/index.jsx | 6 +- .../Logs/Queue/components/Metrics/index.jsx | 4 +- .../Queue/components/MetricsTable/index.jsx | 2 +- .../src/Pages/{ => v1}/Logs/Queue/index.jsx | 8 +-- client/src/Pages/{ => v1}/Logs/index.jsx | 8 +-- .../Components/ConfigSelect/index.jsx | 2 +- .../Components/MonitorList/index.jsx | 0 .../Components/MonitorsConfig/index.jsx | 4 +- .../hooks/useMaintenanceActions.jsx | 4 +- .../hooks/useMaintenanceData.jsx | 4 +- .../Maintenance/CreateMaintenance/index.css | 0 .../Maintenance/CreateMaintenance/index.jsx | 26 ++++---- .../MaintenanceTable/ActionsMenu/index.jsx | 10 ++-- .../Maintenance/MaintenanceTable/index.jsx | 12 ++-- .../src/Pages/{ => v1}/Maintenance/index.css | 0 .../src/Pages/{ => v1}/Maintenance/index.jsx | 8 +-- client/src/Pages/{ => v1}/NotFound/index.jsx | 2 +- .../Notifications/components/ActionMenu.jsx | 0 .../{ => v1}/Notifications/create/index.jsx | 18 +++--- .../Pages/{ => v1}/Notifications/index.jsx | 10 ++-- .../src/Pages/{ => v1}/Notifications/utils.js | 0 .../Pages/{ => v1}/PageSpeed/Create/index.css | 0 .../Pages/{ => v1}/PageSpeed/Create/index.jsx | 32 +++++----- .../{ => v1}/PageSpeed/Create/skeleton.jsx | 0 .../Details/Components/Charts/AreaChart.jsx | 2 +- .../Components/Charts/AreaChartLegend.jsx | 6 +- .../Details/Components/Charts/PieChart.jsx | 0 .../Components/Charts/PieChartLegend.jsx | 4 +- .../Components/PageSpeedAreaChart/index.jsx | 10 ++-- .../PageSpeedAreaChart/skeleton.jsx | 0 .../Components/PageSpeedStatusBoxes/index.jsx | 6 +- .../Components/PerformanceReport/index.jsx | 10 ++-- .../Components/PerformanceReport/skeleton.jsx | 0 .../{ => v1}/PageSpeed/Details/index.jsx | 18 +++--- .../Monitors/Components/Card/index.jsx | 10 ++-- .../Monitors/Components/MonitorGrid/index.jsx | 2 +- .../{ => v1}/PageSpeed/Monitors/index.jsx | 18 +++--- .../src/Pages/{ => v1}/ServerUnreachable.jsx | 14 ++--- .../Pages/{ => v1}/Settings/SettingsAbout.jsx | 4 +- .../Settings/SettingsDemoMonitors.jsx | 4 +- .../Pages/{ => v1}/Settings/SettingsEmail.jsx | 12 ++-- .../Settings/SettingsGlobalThresholds.jsx | 4 +- .../{ => v1}/Settings/SettingsPagespeed.jsx | 6 +- .../Pages/{ => v1}/Settings/SettingsStats.jsx | 6 +- .../{ => v1}/Settings/SettingsTimeZone.jsx | 6 +- .../Pages/{ => v1}/Settings/SettingsUI.jsx | 4 +- .../Pages/{ => v1}/Settings/SettingsURL.jsx | 4 +- client/src/Pages/{ => v1}/Settings/index.jsx | 30 +++++----- .../Create/Components/MonitorList/index.jsx | 0 .../Create/Components/Progress/index.jsx | 4 +- .../Create/Components/Skeleton/index.jsx | 0 .../Create/Components/Tabs/ConfigStack.jsx | 2 +- .../Create/Components/Tabs/Content.jsx | 8 +-- .../Create/Components/Tabs/Settings.jsx | 16 ++--- .../Create/Components/Tabs/index.jsx | 6 +- .../Create/Hooks/useCreateStatusPage.jsx | 4 +- .../Create/Hooks/useMonitorsFetch.jsx | 4 +- .../{ => v1}/StatusPage/Create/index.jsx | 24 ++++---- .../Status/Components/AdminLink/index.jsx | 0 .../Components/ControlsHeader/index.jsx | 6 +- .../Status/Components/MonitorsList/index.jsx | 8 +-- .../Status/Components/Skeleton/index.jsx | 0 .../Status/Components/StatusBar/index.jsx | 0 .../Status/Hooks/useStatusPageDelete.jsx | 4 +- .../Status/Hooks/useStatusPageFetch.jsx | 6 +- .../{ => v1}/StatusPage/Status/index.jsx | 20 +++---- .../Components/StatusPagesTable/index.jsx | 4 +- .../StatusPages/Hooks/useStatusPagesFetch.jsx | 4 +- .../{ => v1}/StatusPage/StatusPages/index.jsx | 12 ++-- .../{ => v1}/Uptime/BulkImport/Upload.jsx | 0 .../{ => v1}/Uptime/BulkImport/index.jsx | 10 ++-- .../Pages/{ => v1}/Uptime/Create/index.css | 0 .../Pages/{ => v1}/Uptime/Create/index.jsx | 32 +++++----- .../Pages/{ => v1}/Uptime/Create/skeleton.jsx | 0 .../Details/Components/ChartBoxes/index.jsx | 18 +++--- .../Components/ChartBoxes/skeleton.jsx | 0 .../Components/Charts/CustomLabels.jsx | 2 +- .../Components/Charts/DownBarChart.jsx | 2 +- .../Components/Charts/ResponseGaugeChart.jsx | 0 .../Components/Charts/ResponseTimeChart.jsx | 8 +-- .../Charts/ResponseTimeChartSkeleton.jsx | 0 .../Details/Components/Charts/UpBarChart.jsx | 2 +- .../Components/ResponseTable/index.jsx | 14 ++--- .../Components/ResponseTable/skeleton.jsx | 0 .../Components/UptimeStatusBoxes/index.jsx | 8 +-- .../Details/Hooks/useCertificateFetch.jsx | 6 +- .../Pages/{ => v1}/Uptime/Details/index.jsx | 24 ++++---- .../Monitors/Components/Filter/index.jsx | 2 +- .../Components/LoadingSpinner/index.jsx | 0 .../Components/SearchComponent/index.jsx | 4 +- .../Monitors/Components/Skeleton/index.jsx | 0 .../Monitors/Components/StatusBoxes/index.jsx | 4 +- .../Components/StatusBoxes/skeleton.jsx | 0 .../Components/StatusBoxes/statusBox.jsx | 6 +- .../Components/UptimeDataTable/index.jsx | 16 ++--- .../Uptime/Monitors/Hooks/useDebounce.jsx | 0 .../Pages/{ => v1}/Uptime/Monitors/index.jsx | 26 ++++---- client/src/Routes/index.jsx | 60 +++++++++---------- client/tsconfig.app.json | 32 ++++++++++ client/tsconfig.json | 14 +++++ client/tsconfig.node.json | 27 +++++++++ client/{vite.config.js => vite.config.ts} | 6 +- 187 files changed, 708 insertions(+), 637 deletions(-) rename client/src/Hooks/{ => v1}/checkHooks.js (97%) rename client/src/Hooks/{ => v1}/inviteHooks.js (97%) rename client/src/Hooks/{ => v1}/logHooks.js (96%) rename client/src/Hooks/{ => v1}/monitorHooks.js (98%) rename client/src/Hooks/{ => v1}/settingsHooks.js (95%) rename client/src/Hooks/{ => v1}/useIsAdmin.js (100%) rename client/src/Hooks/{ => v1}/useMonitorUtils.js (100%) rename client/src/Hooks/{ => v1}/useNotifications.js (96%) rename client/src/Hooks/{ => v1}/useSendTestEmail.js (93%) rename client/src/Hooks/{ => v1}/userHooks.js (92%) rename client/src/Pages/{ => v1}/About/index.jsx (67%) rename client/src/Pages/{ => v1}/Account/EditUser/hooks/editUser.js (91%) rename client/src/Pages/{ => v1}/Account/EditUser/index.jsx (86%) rename client/src/Pages/{ => v1}/Account/components/AddMemberMenu/index.jsx (100%) rename client/src/Pages/{ => v1}/Account/components/AddTeamMember/hooks/useAddTeamMember.jsx (93%) rename client/src/Pages/{ => v1}/Account/components/AddTeamMember/index.jsx (91%) rename client/src/Pages/{ => v1}/Account/components/PasswordPanel.jsx (92%) rename client/src/Pages/{ => v1}/Account/components/ProfilePanel.jsx (94%) rename client/src/Pages/{ => v1}/Account/components/RoleTable/index.jsx (87%) rename client/src/Pages/{ => v1}/Account/components/TeamPanel.jsx (92%) rename client/src/Pages/{ => v1}/Account/index.jsx (91%) rename client/src/Pages/{ => v1}/Auth/CheckEmail.jsx (93%) rename client/src/Pages/{ => v1}/Auth/ForgotPassword.jsx (91%) rename client/src/Pages/{ => v1}/Auth/Login/hooks/useLoadingSubmit.jsx (100%) rename client/src/Pages/{ => v1}/Auth/Login/hooks/useLoginForm.jsx (100%) rename client/src/Pages/{ => v1}/Auth/Login/hooks/useLoginSubmit.jsx (87%) rename client/src/Pages/{ => v1}/Auth/Login/hooks/useValidateLoginForm.jsx (90%) rename client/src/Pages/{ => v1}/Auth/Login/index.jsx (82%) rename client/src/Pages/{ => v1}/Auth/NewPasswordConfirmed.jsx (90%) rename client/src/Pages/{ => v1}/Auth/Register/index.jsx (94%) rename client/src/Pages/{ => v1}/Auth/SetNewPassword.jsx (91%) rename client/src/Pages/{ => v1}/Auth/components/AuthHeader.jsx (81%) rename client/src/Pages/{ => v1}/Auth/components/AuthPageWrapper.jsx (92%) rename client/src/Pages/{ => v1}/Auth/components/PasswordTooltip.jsx (98%) rename client/src/Pages/{ => v1}/Auth/hooks/usePasswordFeedback.jsx (96%) rename client/src/Pages/{ => v1}/Auth/hooks/useValidatePassword.jsx (96%) rename client/src/Pages/{ => v1}/Auth/index.css (100%) rename client/src/Pages/{ => v1}/Incidents/Components/IncidentTable/index.jsx (88%) rename client/src/Pages/{ => v1}/Incidents/Components/OptionsHeader/index.jsx (96%) rename client/src/Pages/{ => v1}/Incidents/Components/OptionsHeader/skeleton.jsx (100%) rename client/src/Pages/{ => v1}/Incidents/Components/StatusBoxes/StatusBox.jsx (87%) rename client/src/Pages/{ => v1}/Incidents/Components/StatusBoxes/index.jsx (92%) rename client/src/Pages/{ => v1}/Incidents/Components/StatusBoxes/skeleton.jsx (100%) rename client/src/Pages/{ => v1}/Incidents/index.jsx (81%) rename client/src/Pages/{ => v1}/Infrastructure/Create/Components/CustomAlertsSection.jsx (91%) rename client/src/Pages/{ => v1}/Infrastructure/Create/Components/CustomThreshold/index.jsx (95%) rename client/src/Pages/{ => v1}/Infrastructure/Create/Components/MonitorActionButtons.jsx (96%) rename client/src/Pages/{ => v1}/Infrastructure/Create/Components/MonitorStatusHeader.jsx (91%) rename client/src/Pages/{ => v1}/Infrastructure/Create/hooks/useInfrastructureMonitorForm.jsx (100%) rename client/src/Pages/{ => v1}/Infrastructure/Create/hooks/useInfrastructureSubmit.jsx (98%) rename client/src/Pages/{ => v1}/Infrastructure/Create/hooks/useValidateInfrastructureForm.jsx (85%) rename client/src/Pages/{ => v1}/Infrastructure/Create/index.jsx (92%) rename client/src/Pages/{ => v1}/Infrastructure/Details/Components/AreaChartBoxes/InfraAreaChart.jsx (79%) rename client/src/Pages/{ => v1}/Infrastructure/Details/Components/AreaChartBoxes/index.jsx (93%) rename client/src/Pages/{ => v1}/Infrastructure/Details/Components/AreaChartBoxes/skeleton.jsx (100%) rename client/src/Pages/{ => v1}/Infrastructure/Details/Components/BaseContainer/index.jsx (94%) rename client/src/Pages/{ => v1}/Infrastructure/Details/Components/GaugeBoxes/Gauge.jsx (93%) rename client/src/Pages/{ => v1}/Infrastructure/Details/Components/GaugeBoxes/index.jsx (93%) rename client/src/Pages/{ => v1}/Infrastructure/Details/Components/GaugeBoxes/skeleton.jsx (100%) rename client/src/Pages/{ => v1}/Infrastructure/Details/Components/NetworkStats/NetworkCharts.jsx (93%) rename client/src/Pages/{ => v1}/Infrastructure/Details/Components/NetworkStats/NetworkStatBoxes.jsx (90%) rename client/src/Pages/{ => v1}/Infrastructure/Details/Components/NetworkStats/index.jsx (90%) rename client/src/Pages/{ => v1}/Infrastructure/Details/Components/NetworkStats/skeleton.jsx (100%) rename client/src/Pages/{ => v1}/Infrastructure/Details/Components/StatusBoxes/index.jsx (90%) rename client/src/Pages/{ => v1}/Infrastructure/Details/Hooks/useHardwareUtils.jsx (100%) rename client/src/Pages/{ => v1}/Infrastructure/Details/index.jsx (79%) rename client/src/Pages/{ => v1}/Infrastructure/Monitors/Components/Filters/index.jsx (96%) rename client/src/Pages/{ => v1}/Infrastructure/Monitors/Components/MonitorsTable/index.jsx (86%) rename client/src/Pages/{ => v1}/Infrastructure/Monitors/Components/MonitorsTableMenu/index.jsx (93%) rename client/src/Pages/{ => v1}/Infrastructure/Monitors/index.jsx (80%) rename client/src/Pages/{ => v1}/Integrations/index.css (100%) rename client/src/Pages/{ => v1}/Integrations/index.jsx (94%) rename client/src/Pages/{ => v1}/Logs/Diagnostics/components/gauges/index.jsx (96%) rename client/src/Pages/{ => v1}/Logs/Diagnostics/components/stats/index.jsx (94%) rename client/src/Pages/{ => v1}/Logs/Diagnostics/index.jsx (82%) rename client/src/Pages/{ => v1}/Logs/Diagnostics/utils/utils.js (100%) rename client/src/Pages/{ => v1}/Logs/Logs/index.jsx (94%) rename client/src/Pages/{ => v1}/Logs/Queue/components/FailedJobTable/index.jsx (92%) rename client/src/Pages/{ => v1}/Logs/Queue/components/JobTable/index.jsx (92%) rename client/src/Pages/{ => v1}/Logs/Queue/components/Metrics/index.jsx (84%) rename client/src/Pages/{ => v1}/Logs/Queue/components/MetricsTable/index.jsx (94%) rename client/src/Pages/{ => v1}/Logs/Queue/index.jsx (86%) rename client/src/Pages/{ => v1}/Logs/index.jsx (84%) rename client/src/Pages/{ => v1}/Maintenance/CreateMaintenance/Components/ConfigSelect/index.jsx (92%) rename client/src/Pages/{ => v1}/Maintenance/CreateMaintenance/Components/MonitorList/index.jsx (100%) rename client/src/Pages/{ => v1}/Maintenance/CreateMaintenance/Components/MonitorsConfig/index.jsx (93%) rename client/src/Pages/{ => v1}/Maintenance/CreateMaintenance/hooks/useMaintenanceActions.jsx (93%) rename client/src/Pages/{ => v1}/Maintenance/CreateMaintenance/hooks/useMaintenanceData.jsx (95%) rename client/src/Pages/{ => v1}/Maintenance/CreateMaintenance/index.css (100%) rename client/src/Pages/{ => v1}/Maintenance/CreateMaintenance/index.jsx (93%) rename client/src/Pages/{ => v1}/Maintenance/MaintenanceTable/ActionsMenu/index.jsx (91%) rename client/src/Pages/{ => v1}/Maintenance/MaintenanceTable/index.jsx (92%) rename client/src/Pages/{ => v1}/Maintenance/index.css (100%) rename client/src/Pages/{ => v1}/Maintenance/index.jsx (91%) rename client/src/Pages/{ => v1}/NotFound/index.jsx (96%) rename client/src/Pages/{ => v1}/Notifications/components/ActionMenu.jsx (100%) rename client/src/Pages/{ => v1}/Notifications/create/index.jsx (92%) rename client/src/Pages/{ => v1}/Notifications/index.jsx (89%) rename client/src/Pages/{ => v1}/Notifications/utils.js (100%) rename client/src/Pages/{ => v1}/PageSpeed/Create/index.css (100%) rename client/src/Pages/{ => v1}/PageSpeed/Create/index.jsx (91%) rename client/src/Pages/{ => v1}/PageSpeed/Create/skeleton.jsx (100%) rename client/src/Pages/{ => v1}/PageSpeed/Details/Components/Charts/AreaChart.jsx (99%) rename client/src/Pages/{ => v1}/PageSpeed/Details/Components/Charts/AreaChartLegend.jsx (84%) rename client/src/Pages/{ => v1}/PageSpeed/Details/Components/Charts/PieChart.jsx (100%) rename client/src/Pages/{ => v1}/PageSpeed/Details/Components/Charts/PieChartLegend.jsx (92%) rename client/src/Pages/{ => v1}/PageSpeed/Details/Components/PageSpeedAreaChart/index.jsx (75%) rename client/src/Pages/{ => v1}/PageSpeed/Details/Components/PageSpeedAreaChart/skeleton.jsx (100%) rename client/src/Pages/{ => v1}/PageSpeed/Details/Components/PageSpeedStatusBoxes/index.jsx (76%) rename client/src/Pages/{ => v1}/PageSpeed/Details/Components/PerformanceReport/index.jsx (78%) rename client/src/Pages/{ => v1}/PageSpeed/Details/Components/PerformanceReport/skeleton.jsx (100%) rename client/src/Pages/{ => v1}/PageSpeed/Details/index.jsx (81%) rename client/src/Pages/{ => v1}/PageSpeed/Monitors/Components/Card/index.jsx (95%) rename client/src/Pages/{ => v1}/PageSpeed/Monitors/Components/MonitorGrid/index.jsx (88%) rename client/src/Pages/{ => v1}/PageSpeed/Monitors/index.jsx (69%) rename client/src/Pages/{ => v1}/ServerUnreachable.jsx (90%) rename client/src/Pages/{ => v1}/Settings/SettingsAbout.jsx (88%) rename client/src/Pages/{ => v1}/Settings/SettingsDemoMonitors.jsx (95%) rename client/src/Pages/{ => v1}/Settings/SettingsEmail.jsx (94%) rename client/src/Pages/{ => v1}/Settings/SettingsGlobalThresholds.jsx (94%) rename client/src/Pages/{ => v1}/Settings/SettingsPagespeed.jsx (91%) rename client/src/Pages/{ => v1}/Settings/SettingsStats.jsx (92%) rename client/src/Pages/{ => v1}/Settings/SettingsTimeZone.jsx (90%) rename client/src/Pages/{ => v1}/Settings/SettingsUI.jsx (91%) rename client/src/Pages/{ => v1}/Settings/SettingsURL.jsx (90%) rename client/src/Pages/{ => v1}/Settings/index.jsx (89%) rename client/src/Pages/{ => v1}/StatusPage/Create/Components/MonitorList/index.jsx (100%) rename client/src/Pages/{ => v1}/StatusPage/Create/Components/Progress/index.jsx (83%) rename client/src/Pages/{ => v1}/StatusPage/Create/Components/Skeleton/index.jsx (100%) rename client/src/Pages/{ => v1}/StatusPage/Create/Components/Tabs/ConfigStack.jsx (91%) rename client/src/Pages/{ => v1}/StatusPage/Create/Components/Tabs/Content.jsx (90%) rename client/src/Pages/{ => v1}/StatusPage/Create/Components/Tabs/Settings.jsx (87%) rename client/src/Pages/{ => v1}/StatusPage/Create/Components/Tabs/index.jsx (93%) rename client/src/Pages/{ => v1}/StatusPage/Create/Hooks/useCreateStatusPage.jsx (83%) rename client/src/Pages/{ => v1}/StatusPage/Create/Hooks/useMonitorsFetch.jsx (88%) rename client/src/Pages/{ => v1}/StatusPage/Create/index.jsx (92%) rename client/src/Pages/{ => v1}/StatusPage/Status/Components/AdminLink/index.jsx (100%) rename client/src/Pages/{ => v1}/StatusPage/Status/Components/ControlsHeader/index.jsx (92%) rename client/src/Pages/{ => v1}/StatusPage/Status/Components/MonitorsList/index.jsx (82%) rename client/src/Pages/{ => v1}/StatusPage/Status/Components/Skeleton/index.jsx (100%) rename client/src/Pages/{ => v1}/StatusPage/Status/Components/StatusBar/index.jsx (100%) rename client/src/Pages/{ => v1}/StatusPage/Status/Hooks/useStatusPageDelete.jsx (89%) rename client/src/Pages/{ => v1}/StatusPage/Status/Hooks/useStatusPageFetch.jsx (87%) rename client/src/Pages/{ => v1}/StatusPage/Status/index.jsx (85%) rename client/src/Pages/{ => v1}/StatusPage/StatusPages/Components/StatusPagesTable/index.jsx (93%) rename client/src/Pages/{ => v1}/StatusPage/StatusPages/Hooks/useStatusPagesFetch.jsx (86%) rename client/src/Pages/{ => v1}/StatusPage/StatusPages/index.jsx (70%) rename client/src/Pages/{ => v1}/Uptime/BulkImport/Upload.jsx (100%) rename client/src/Pages/{ => v1}/Uptime/BulkImport/index.jsx (89%) rename client/src/Pages/{ => v1}/Uptime/Create/index.css (100%) rename client/src/Pages/{ => v1}/Uptime/Create/index.jsx (94%) rename client/src/Pages/{ => v1}/Uptime/Create/skeleton.jsx (100%) rename client/src/Pages/{ => v1}/Uptime/Details/Components/ChartBoxes/index.jsx (86%) rename client/src/Pages/{ => v1}/Uptime/Details/Components/ChartBoxes/skeleton.jsx (100%) rename client/src/Pages/{ => v1}/Uptime/Details/Components/Charts/CustomLabels.jsx (93%) rename client/src/Pages/{ => v1}/Uptime/Details/Components/Charts/DownBarChart.jsx (97%) rename client/src/Pages/{ => v1}/Uptime/Details/Components/Charts/ResponseGaugeChart.jsx (100%) rename client/src/Pages/{ => v1}/Uptime/Details/Components/Charts/ResponseTimeChart.jsx (60%) rename client/src/Pages/{ => v1}/Uptime/Details/Components/Charts/ResponseTimeChartSkeleton.jsx (100%) rename client/src/Pages/{ => v1}/Uptime/Details/Components/Charts/UpBarChart.jsx (98%) rename client/src/Pages/{ => v1}/Uptime/Details/Components/ResponseTable/index.jsx (76%) rename client/src/Pages/{ => v1}/Uptime/Details/Components/ResponseTable/skeleton.jsx (100%) rename client/src/Pages/{ => v1}/Uptime/Details/Components/UptimeStatusBoxes/index.jsx (85%) rename client/src/Pages/{ => v1}/Uptime/Details/Hooks/useCertificateFetch.jsx (84%) rename client/src/Pages/{ => v1}/Uptime/Details/index.jsx (83%) rename client/src/Pages/{ => v1}/Uptime/Monitors/Components/Filter/index.jsx (98%) rename client/src/Pages/{ => v1}/Uptime/Monitors/Components/LoadingSpinner/index.jsx (100%) rename client/src/Pages/{ => v1}/Uptime/Monitors/Components/SearchComponent/index.jsx (89%) rename client/src/Pages/{ => v1}/Uptime/Monitors/Components/Skeleton/index.jsx (100%) rename client/src/Pages/{ => v1}/Uptime/Monitors/Components/StatusBoxes/index.jsx (91%) rename client/src/Pages/{ => v1}/Uptime/Monitors/Components/StatusBoxes/skeleton.jsx (100%) rename client/src/Pages/{ => v1}/Uptime/Monitors/Components/StatusBoxes/statusBox.jsx (90%) rename client/src/Pages/{ => v1}/Uptime/Monitors/Components/UptimeDataTable/index.jsx (90%) rename client/src/Pages/{ => v1}/Uptime/Monitors/Hooks/useDebounce.jsx (100%) rename client/src/Pages/{ => v1}/Uptime/Monitors/index.jsx (86%) create mode 100644 client/tsconfig.app.json create mode 100644 client/tsconfig.json create mode 100644 client/tsconfig.node.json rename client/{vite.config.js => vite.config.ts} (63%) diff --git a/client/package.json b/client/package.json index 3108f7121..9ebc99647 100644 --- a/client/package.json +++ b/client/package.json @@ -5,8 +5,8 @@ "type": "module", "scripts": { "dev": "vite", - "build": "vite build", - "build-dev": "vite build --mode development", + "build": "tsc -b && vite build", + "build-dev": "tsc -b && vite build --mode development", "lint": "eslint . --ext js,jsx --report-unused-disable-directives --max-warnings 0", "preview": "vite preview", "format": "prettier --write .", diff --git a/client/src/Components/ActionsMenu/index.jsx b/client/src/Components/ActionsMenu/index.jsx index 1b590a471..78d86e1b2 100644 --- a/client/src/Components/ActionsMenu/index.jsx +++ b/client/src/Components/ActionsMenu/index.jsx @@ -12,7 +12,7 @@ import { useNavigate } from "react-router-dom"; import { createToast } from "../../Utils/toastUtils"; import PropTypes from "prop-types"; -import { usePauseMonitor, useDeleteMonitor } from "../../Hooks/monitorHooks"; +import { usePauseMonitor, useDeleteMonitor } from "../../Hooks/v1/monitorHooks.js"; const ActionsMenu = ({ monitor, diff --git a/client/src/Components/MonitorActions/index.jsx b/client/src/Components/MonitorActions/index.jsx index ac5d89a47..9e88e26c5 100644 --- a/client/src/Components/MonitorActions/index.jsx +++ b/client/src/Components/MonitorActions/index.jsx @@ -11,7 +11,7 @@ import MenuList from "@mui/material/MenuList"; import { useNavigate } from "react-router-dom"; import { useTranslation } from "react-i18next"; import { createToast } from "../../Utils/toastUtils"; -import { useExportMonitors } from "../../Hooks/monitorHooks"; +import { useExportMonitors } from "../../Hooks/v1/monitorHooks.js"; const MonitorActions = ({ isLoading }) => { const [open, setOpen] = React.useState(false); diff --git a/client/src/Components/MonitorDetailsControlHeader/index.jsx b/client/src/Components/MonitorDetailsControlHeader/index.jsx index 4674a1fb7..cd56394f4 100644 --- a/client/src/Components/MonitorDetailsControlHeader/index.jsx +++ b/client/src/Components/MonitorDetailsControlHeader/index.jsx @@ -12,10 +12,10 @@ import EmailIcon from "@mui/icons-material/Email"; import PropTypes from "prop-types"; import { useNavigate } from "react-router-dom"; import { useTheme } from "@mui/material/styles"; -import { usePauseMonitor } from "../../Hooks/monitorHooks"; -import { useSendTestEmail } from "../../Hooks/useSendTestEmail"; +import { usePauseMonitor } from "../../Hooks/v1/monitorHooks.js"; +import { useSendTestEmail } from "../../Hooks/v1/useSendTestEmail.js"; import { useTranslation } from "react-i18next"; -import { useTestAllNotifications } from "../../Hooks/useNotifications"; +import { useTestAllNotifications } from "../../Hooks/v1/useNotifications.js"; /** * MonitorDetailsControlHeader component displays the control header for monitor details. * It includes status display, pause/resume button, and a configure button for admins. diff --git a/client/src/Components/MonitorDetailsControlHeader/status.jsx b/client/src/Components/MonitorDetailsControlHeader/status.jsx index 65671a609..2cf749861 100644 --- a/client/src/Components/MonitorDetailsControlHeader/status.jsx +++ b/client/src/Components/MonitorDetailsControlHeader/status.jsx @@ -7,7 +7,7 @@ import Dot from "../../Components/Dot"; import { formatDurationRounded } from "../../Utils/timeUtils"; import PropTypes from "prop-types"; import { useTheme } from "@emotion/react"; -import { useMonitorUtils } from "../../Hooks/useMonitorUtils"; +import { useMonitorUtils } from "../../Hooks/v1/useMonitorUtils.js"; import { formatMonitorUrl } from "../../Utils/utils"; /** * Status component displays the status information of a monitor. diff --git a/client/src/Components/PageStateWrapper/index.jsx b/client/src/Components/PageStateWrapper/index.jsx index f971b2c9c..c335fcbb3 100644 --- a/client/src/Components/PageStateWrapper/index.jsx +++ b/client/src/Components/PageStateWrapper/index.jsx @@ -3,7 +3,7 @@ import Fallback from "../Fallback"; import { Typography } from "@mui/material"; import { useTheme } from "@emotion/react"; import { useTranslation } from "react-i18next"; -import { useIsAdmin } from "../../Hooks/useIsAdmin"; +import { useIsAdmin } from "../../Hooks/v1/useIsAdmin.js"; import PropTypes from "prop-types"; const PageStateWrapper = ({ networkError, diff --git a/client/src/Components/StatBox/index.jsx b/client/src/Components/StatBox/index.jsx index 7d093de60..15eb61f74 100644 --- a/client/src/Components/StatBox/index.jsx +++ b/client/src/Components/StatBox/index.jsx @@ -2,7 +2,7 @@ import { Stack, Typography } from "@mui/material"; import Image from "../Image"; import { useTheme } from "@mui/material/styles"; import PropTypes from "prop-types"; -import { useMonitorUtils } from "../../Hooks/useMonitorUtils"; +import { useMonitorUtils } from "../../Hooks/v1/useMonitorUtils.js"; /** * StatBox Component diff --git a/client/src/Hooks/checkHooks.js b/client/src/Hooks/v1/checkHooks.js similarity index 97% rename from client/src/Hooks/checkHooks.js rename to client/src/Hooks/v1/checkHooks.js index bdb1a73b7..96fd1fe1c 100644 --- a/client/src/Hooks/checkHooks.js +++ b/client/src/Hooks/v1/checkHooks.js @@ -1,6 +1,6 @@ import { useState, useEffect } from "react"; -import { networkService } from "../main"; -import { createToast } from "../Utils/toastUtils"; +import { networkService } from "../../main.jsx"; +import { createToast } from "../../Utils/toastUtils.jsx"; import { useTranslation } from "react-i18next"; const useFetchChecksTeam = ({ diff --git a/client/src/Hooks/inviteHooks.js b/client/src/Hooks/v1/inviteHooks.js similarity index 97% rename from client/src/Hooks/inviteHooks.js rename to client/src/Hooks/v1/inviteHooks.js index fb577272a..b6a0851af 100644 --- a/client/src/Hooks/inviteHooks.js +++ b/client/src/Hooks/v1/inviteHooks.js @@ -1,5 +1,5 @@ import { useState } from "react"; -import { networkService } from "../main"; +import { networkService } from "../../main.jsx"; import { useTranslation } from "react-i18next"; const CLIENT_HOST = import.meta.env.VITE_APP_CLIENT_HOST; diff --git a/client/src/Hooks/logHooks.js b/client/src/Hooks/v1/logHooks.js similarity index 96% rename from client/src/Hooks/logHooks.js rename to client/src/Hooks/v1/logHooks.js index 989870eea..ff4d13872 100644 --- a/client/src/Hooks/logHooks.js +++ b/client/src/Hooks/v1/logHooks.js @@ -1,6 +1,6 @@ import { useState, useEffect } from "react"; -import { networkService } from "../main"; -import { createToast } from "../Utils/toastUtils"; +import { networkService } from "../../main.jsx"; +import { createToast } from "../../Utils/toastUtils.jsx"; import { useTranslation } from "react-i18next"; const useFetchLogs = () => { diff --git a/client/src/Hooks/monitorHooks.js b/client/src/Hooks/v1/monitorHooks.js similarity index 98% rename from client/src/Hooks/monitorHooks.js rename to client/src/Hooks/v1/monitorHooks.js index eb1e7acdc..e6b4b5261 100644 --- a/client/src/Hooks/monitorHooks.js +++ b/client/src/Hooks/v1/monitorHooks.js @@ -1,8 +1,8 @@ import { useEffect, useState } from "react"; -import { networkService } from "../main"; -import { createToast } from "../Utils/toastUtils"; +import { networkService } from "../../main.jsx"; +import { createToast } from "../../Utils/toastUtils.jsx"; import { useTheme } from "@emotion/react"; -import { useMonitorUtils } from "./useMonitorUtils"; +import { useMonitorUtils } from "./useMonitorUtils.js"; import { useNavigate } from "react-router-dom"; import { useTranslation } from "react-i18next"; diff --git a/client/src/Hooks/settingsHooks.js b/client/src/Hooks/v1/settingsHooks.js similarity index 95% rename from client/src/Hooks/settingsHooks.js rename to client/src/Hooks/v1/settingsHooks.js index 98a21c1fa..787e46ff9 100644 --- a/client/src/Hooks/settingsHooks.js +++ b/client/src/Hooks/v1/settingsHooks.js @@ -1,6 +1,6 @@ import { useState, useEffect } from "react"; -import { networkService } from "../main"; -import { createToast } from "../Utils/toastUtils"; +import { networkService } from "../../main.jsx"; +import { createToast } from "../../Utils/toastUtils.jsx"; import { useTranslation } from "react-i18next"; const useFetchSettings = ({ setSettingsData, setIsApiKeySet, setIsEmailPasswordSet }) => { diff --git a/client/src/Hooks/useIsAdmin.js b/client/src/Hooks/v1/useIsAdmin.js similarity index 100% rename from client/src/Hooks/useIsAdmin.js rename to client/src/Hooks/v1/useIsAdmin.js diff --git a/client/src/Hooks/useMonitorUtils.js b/client/src/Hooks/v1/useMonitorUtils.js similarity index 100% rename from client/src/Hooks/useMonitorUtils.js rename to client/src/Hooks/v1/useMonitorUtils.js diff --git a/client/src/Hooks/useNotifications.js b/client/src/Hooks/v1/useNotifications.js similarity index 96% rename from client/src/Hooks/useNotifications.js rename to client/src/Hooks/v1/useNotifications.js index 539725958..d1b4a6652 100644 --- a/client/src/Hooks/useNotifications.js +++ b/client/src/Hooks/v1/useNotifications.js @@ -1,9 +1,9 @@ import { useState, useEffect, useCallback } from "react"; -import { createToast } from "../Utils/toastUtils"; -import { networkService } from "../main"; +import { createToast } from "../../Utils/toastUtils.jsx"; +import { networkService } from "../../main.jsx"; import { useNavigate } from "react-router-dom"; import { useTranslation } from "react-i18next"; -import { NOTIFICATION_TYPES } from "../Pages/Notifications/utils"; +import { NOTIFICATION_TYPES } from "../../Pages/v1/Notifications/utils.js"; const useCreateNotification = () => { const navigate = useNavigate(); diff --git a/client/src/Hooks/useSendTestEmail.js b/client/src/Hooks/v1/useSendTestEmail.js similarity index 93% rename from client/src/Hooks/useSendTestEmail.js rename to client/src/Hooks/v1/useSendTestEmail.js index e280b1f99..0be77954a 100644 --- a/client/src/Hooks/useSendTestEmail.js +++ b/client/src/Hooks/v1/useSendTestEmail.js @@ -1,7 +1,7 @@ import { useState } from "react"; -import { networkService } from "../main"; +import { networkService } from "../../main.jsx"; import { useSelector } from "react-redux"; -import { createToast } from "../Utils/toastUtils"; +import { createToast } from "../../Utils/toastUtils.jsx"; import { useTranslation } from "react-i18next"; const useSendTestEmail = () => { diff --git a/client/src/Hooks/userHooks.js b/client/src/Hooks/v1/userHooks.js similarity index 92% rename from client/src/Hooks/userHooks.js rename to client/src/Hooks/v1/userHooks.js index 8be32a082..71b156594 100644 --- a/client/src/Hooks/userHooks.js +++ b/client/src/Hooks/v1/userHooks.js @@ -1,6 +1,6 @@ import { useEffect, useState, useCallback } from "react"; -import { networkService } from "../main"; -import { createToast } from "../Utils/toastUtils"; +import { networkService } from "../../main.jsx"; +import { createToast } from "../../Utils/toastUtils.jsx"; import { useTranslation } from "react-i18next"; export const useGetUser = (userId) => { diff --git a/client/src/Pages/About/index.jsx b/client/src/Pages/v1/About/index.jsx similarity index 67% rename from client/src/Pages/About/index.jsx rename to client/src/Pages/v1/About/index.jsx index 97040b2e3..0e68089d3 100644 --- a/client/src/Pages/About/index.jsx +++ b/client/src/Pages/v1/About/index.jsx @@ -1,6 +1,6 @@ import React from "react"; -import AppBar from "../../Components/Common/AppBar"; -import Footer from "../../Components/Common/Footer"; +import AppBar from "../../../Components/Common/AppBar.jsx"; +import Footer from "../../../Components/Common/Footer.jsx"; import { useTranslation } from "react-i18next"; const AboutUs = () => { diff --git a/client/src/Pages/Account/EditUser/hooks/editUser.js b/client/src/Pages/v1/Account/EditUser/hooks/editUser.js similarity index 91% rename from client/src/Pages/Account/EditUser/hooks/editUser.js rename to client/src/Pages/v1/Account/EditUser/hooks/editUser.js index 2ffa914a0..00660aead 100644 --- a/client/src/Pages/Account/EditUser/hooks/editUser.js +++ b/client/src/Pages/v1/Account/EditUser/hooks/editUser.js @@ -1,8 +1,8 @@ import { useState, useEffect } from "react"; -import { ROLES } from "../../../../Utils/roleUtils"; -import { editUserValidation } from "../../../../Validation/validation"; +import { ROLES } from "../../../../../Utils/roleUtils.js"; +import { editUserValidation } from "../../../../../Validation/validation.js"; import Joi from "joi"; -import { createToast } from "../../../../Utils/toastUtils"; +import { createToast } from "../../../../../Utils/toastUtils.jsx"; import { useTranslation } from "react-i18next"; export const useEditUserForm = (user) => { const [searchInput, setSearchInput] = useState(""); diff --git a/client/src/Pages/Account/EditUser/index.jsx b/client/src/Pages/v1/Account/EditUser/index.jsx similarity index 86% rename from client/src/Pages/Account/EditUser/index.jsx rename to client/src/Pages/v1/Account/EditUser/index.jsx index e1eeead9d..658de5144 100644 --- a/client/src/Pages/Account/EditUser/index.jsx +++ b/client/src/Pages/v1/Account/EditUser/index.jsx @@ -2,19 +2,19 @@ import Stack from "@mui/material/Stack"; import Box from "@mui/material/Box"; import Typography from "@mui/material/Typography"; -import Breadcrumbs from "../../../Components/Breadcrumbs"; -import TextInput from "../../../Components/Inputs/TextInput"; -import Search from "../../../Components/Inputs/Search"; +import Breadcrumbs from "../../../../Components/Breadcrumbs/index.jsx"; +import TextInput from "../../../../Components/Inputs/TextInput/index.jsx"; +import Search from "../../../../Components/Inputs/Search/index.jsx"; import Button from "@mui/material/Button"; -import RoleTable from "../components/RoleTable"; +import RoleTable from "../components/RoleTable/index.jsx"; // Utils import { useParams } from "react-router-dom"; import { useTheme } from "@emotion/react"; import { useTranslation } from "react-i18next"; -import { useGetUser, useEditUser } from "../../../Hooks/userHooks"; -import { EDITABLE_ROLES, ROLES } from "../../../Utils/roleUtils"; -import { useEditUserForm, useValidateEditUserForm } from "./hooks/editUser"; +import { useGetUser, useEditUser } from "../../../../Hooks/v1/userHooks.js"; +import { EDITABLE_ROLES, ROLES } from "../../../../Utils/roleUtils.js"; +import { useEditUserForm, useValidateEditUserForm } from "./hooks/editUser.js"; const EditUser = () => { const { userId } = useParams(); diff --git a/client/src/Pages/Account/components/AddMemberMenu/index.jsx b/client/src/Pages/v1/Account/components/AddMemberMenu/index.jsx similarity index 100% rename from client/src/Pages/Account/components/AddMemberMenu/index.jsx rename to client/src/Pages/v1/Account/components/AddMemberMenu/index.jsx diff --git a/client/src/Pages/Account/components/AddTeamMember/hooks/useAddTeamMember.jsx b/client/src/Pages/v1/Account/components/AddTeamMember/hooks/useAddTeamMember.jsx similarity index 93% rename from client/src/Pages/Account/components/AddTeamMember/hooks/useAddTeamMember.jsx rename to client/src/Pages/v1/Account/components/AddTeamMember/hooks/useAddTeamMember.jsx index e42c504b2..58db019a3 100644 --- a/client/src/Pages/Account/components/AddTeamMember/hooks/useAddTeamMember.jsx +++ b/client/src/Pages/v1/Account/components/AddTeamMember/hooks/useAddTeamMember.jsx @@ -1,5 +1,5 @@ import { useState } from "react"; -import { newOrChangedCredentials } from "../../../../../Validation/validation"; +import { newOrChangedCredentials } from "../../../../../../Validation/validation.js"; import { useTranslation } from "react-i18next"; const useAddTeamMember = () => { const { t } = useTranslation(); diff --git a/client/src/Pages/Account/components/AddTeamMember/index.jsx b/client/src/Pages/v1/Account/components/AddTeamMember/index.jsx similarity index 91% rename from client/src/Pages/Account/components/AddTeamMember/index.jsx rename to client/src/Pages/v1/Account/components/AddTeamMember/index.jsx index 2df8fb226..e17ed433b 100644 --- a/client/src/Pages/Account/components/AddTeamMember/index.jsx +++ b/client/src/Pages/v1/Account/components/AddTeamMember/index.jsx @@ -1,16 +1,16 @@ import { Button, Stack } from "@mui/material"; -import { GenericDialog } from "../../../../Components/Dialog/genericDialog"; -import TextInput from "../../../../Components/Inputs/TextInput"; -import Select from "../../../../Components/Inputs/Select"; -import { useGetInviteToken } from "../../../../Hooks/inviteHooks"; +import { GenericDialog } from "../../../../../Components/Dialog/genericDialog.jsx"; +import TextInput from "../../../../../Components/Inputs/TextInput/index.jsx"; +import Select from "../../../../../Components/Inputs/Select/index.jsx"; +import { useGetInviteToken } from "../../../../../Hooks/v1/inviteHooks.js"; import { useTheme } from "@emotion/react"; import { useTranslation } from "react-i18next"; -import { createToast } from "../../../../Utils/toastUtils"; +import { createToast } from "../../../../../Utils/toastUtils.jsx"; import { useState } from "react"; -import PasswordTooltip from "../../../Auth/components/PasswordTooltip"; -import useAddTeamMember from "./hooks/useAddTeamMember"; -import usePasswordFeedback from "../../../Auth/hooks/usePasswordFeedback"; -import { PasswordEndAdornment } from "../../../../Components/Inputs/TextInput/Adornments"; +import PasswordTooltip from "../../../Auth/components/PasswordTooltip.jsx"; +import useAddTeamMember from "./hooks/useAddTeamMember.jsx"; +import usePasswordFeedback from "../../../Auth/hooks/usePasswordFeedback.jsx"; +import { PasswordEndAdornment } from "../../../../../Components/Inputs/TextInput/Adornments/index.jsx"; import PropTypes from "prop-types"; const INITIAL_FORM_STATE = { diff --git a/client/src/Pages/Account/components/PasswordPanel.jsx b/client/src/Pages/v1/Account/components/PasswordPanel.jsx similarity index 92% rename from client/src/Pages/Account/components/PasswordPanel.jsx rename to client/src/Pages/v1/Account/components/PasswordPanel.jsx index dabd67706..11961bf58 100644 --- a/client/src/Pages/Account/components/PasswordPanel.jsx +++ b/client/src/Pages/v1/Account/components/PasswordPanel.jsx @@ -2,14 +2,14 @@ import TabPanel from "@mui/lab/TabPanel"; import { useState } from "react"; import { useTheme } from "@emotion/react"; import { Box, Stack, Typography, Button } from "@mui/material"; -import { PasswordEndAdornment } from "../../../Components/Inputs/TextInput/Adornments"; -import TextInput from "../../../Components/Inputs/TextInput"; -import { newOrChangedCredentials } from "../../../Validation/validation"; -import Alert from "../../../Components/Alert"; -import { update } from "../../../Features/Auth/authSlice"; +import { PasswordEndAdornment } from "../../../../Components/Inputs/TextInput/Adornments/index.jsx"; +import TextInput from "../../../../Components/Inputs/TextInput/index.jsx"; +import { newOrChangedCredentials } from "../../../../Validation/validation.js"; +import Alert from "../../../../Components/Alert/index.jsx"; +import { update } from "../../../../Features/Auth/authSlice.js"; import { useDispatch, useSelector } from "react-redux"; -import { createToast } from "../../../Utils/toastUtils"; -import { getTouchedFieldErrors } from "../../../Validation/error"; +import { createToast } from "../../../../Utils/toastUtils.jsx"; +import { getTouchedFieldErrors } from "../../../../Validation/error.js"; import { useTranslation } from "react-i18next"; const defaultPasswordsState = { diff --git a/client/src/Pages/Account/components/ProfilePanel.jsx b/client/src/Pages/v1/Account/components/ProfilePanel.jsx similarity index 94% rename from client/src/Pages/Account/components/ProfilePanel.jsx rename to client/src/Pages/v1/Account/components/ProfilePanel.jsx index 7c73e7aa3..8e607f4bd 100644 --- a/client/src/Pages/Account/components/ProfilePanel.jsx +++ b/client/src/Pages/v1/Account/components/ProfilePanel.jsx @@ -2,16 +2,16 @@ import { useTheme } from "@emotion/react"; import { useState } from "react"; import TabPanel from "@mui/lab/TabPanel"; import { Box, Button, Divider, Stack, Typography } from "@mui/material"; -import Avatar from "../../../Components/Avatar"; -import TextInput from "../../../Components/Inputs/TextInput"; -import ImageUpload from "../../../Components/Inputs/ImageUpload"; -import { newOrChangedCredentials } from "../../../Validation/validation"; +import Avatar from "../../../../Components/Avatar/index.jsx"; +import TextInput from "../../../../Components/Inputs/TextInput/index.jsx"; +import ImageUpload from "../../../../Components/Inputs/ImageUpload/index.jsx"; +import { newOrChangedCredentials } from "../../../../Validation/validation.js"; import { useDispatch, useSelector } from "react-redux"; -import { clearAuthState, deleteUser, update } from "../../../Features/Auth/authSlice"; -import { createToast } from "../../../Utils/toastUtils"; -import { logger } from "../../../Utils/Logger"; -import { GenericDialog } from "../../../Components/Dialog/genericDialog"; -import Dialog from "../../../Components/Dialog"; +import { clearAuthState, deleteUser, update } from "../../../../Features/Auth/authSlice.js"; +import { createToast } from "../../../../Utils/toastUtils.jsx"; +import { logger } from "../../../../Utils/Logger.js"; +import { GenericDialog } from "../../../../Components/Dialog/genericDialog.jsx"; +import Dialog from "../../../../Components/Dialog/index.jsx"; import { useTranslation } from "react-i18next"; /** diff --git a/client/src/Pages/Account/components/RoleTable/index.jsx b/client/src/Pages/v1/Account/components/RoleTable/index.jsx similarity index 87% rename from client/src/Pages/Account/components/RoleTable/index.jsx rename to client/src/Pages/v1/Account/components/RoleTable/index.jsx index 9d6ba3a16..c42be7bcd 100644 --- a/client/src/Pages/Account/components/RoleTable/index.jsx +++ b/client/src/Pages/v1/Account/components/RoleTable/index.jsx @@ -1,9 +1,9 @@ import Typography from "@mui/material/Typography"; -import DataTable from "../../../../Components/Table"; +import DataTable from "../../../../../Components/Table/index.jsx"; import DeleteOutlineRoundedIcon from "@mui/icons-material/DeleteOutlineRounded"; import { useTranslation } from "react-i18next"; -import { ROLES } from "../../../../Utils/roleUtils"; +import { ROLES } from "../../../../../Utils/roleUtils.js"; const RoleTable = ({ roles, handleDeleteRole }) => { const { t } = useTranslation(); diff --git a/client/src/Pages/Account/components/TeamPanel.jsx b/client/src/Pages/v1/Account/components/TeamPanel.jsx similarity index 92% rename from client/src/Pages/Account/components/TeamPanel.jsx rename to client/src/Pages/v1/Account/components/TeamPanel.jsx index fc1768642..8964cd277 100644 --- a/client/src/Pages/Account/components/TeamPanel.jsx +++ b/client/src/Pages/v1/Account/components/TeamPanel.jsx @@ -3,18 +3,18 @@ import TabPanel from "@mui/lab/TabPanel"; import { Button, ButtonGroup, Stack, Typography } from "@mui/material"; import { useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; -import TextInput from "../../../Components/Inputs/TextInput"; -import { newOrChangedCredentials } from "../../../Validation/validation"; -import { networkService } from "../../../main"; -import { createToast } from "../../../Utils/toastUtils"; -import Select from "../../../Components/Inputs/Select"; -import { GenericDialog } from "../../../Components/Dialog/genericDialog"; -import AddTeamMember from "../components/AddTeamMember"; -import DataTable from "../../../Components/Table"; -import { useGetInviteToken } from "../../../Hooks/inviteHooks"; +import TextInput from "../../../../Components/Inputs/TextInput/index.jsx"; +import { newOrChangedCredentials } from "../../../../Validation/validation.js"; +import { networkService } from "../../../../main.jsx"; +import { createToast } from "../../../../Utils/toastUtils.jsx"; +import Select from "../../../../Components/Inputs/Select/index.jsx"; +import { GenericDialog } from "../../../../Components/Dialog/genericDialog.jsx"; +import AddTeamMember from "./AddTeamMember/index.jsx"; +import DataTable from "../../../../Components/Table/index.jsx"; +import { useGetInviteToken } from "../../../../Hooks/v1/inviteHooks.js"; import { useNavigate } from "react-router-dom"; -import { useIsSuperAdmin } from "../../../Hooks/useIsAdmin"; -import AddMemberMenu from "./AddMemberMenu"; +import { useIsSuperAdmin } from "../../../../Hooks/v1/useIsAdmin.js"; +import AddMemberMenu from "./AddMemberMenu/index.jsx"; /** * TeamPanel component manages the organization and team members, * providing functionalities like renaming the organization, managing team members, diff --git a/client/src/Pages/Account/index.jsx b/client/src/Pages/v1/Account/index.jsx similarity index 91% rename from client/src/Pages/Account/index.jsx rename to client/src/Pages/v1/Account/index.jsx index 37c64aca6..a496858b7 100644 --- a/client/src/Pages/Account/index.jsx +++ b/client/src/Pages/v1/Account/index.jsx @@ -3,11 +3,11 @@ import PropTypes from "prop-types"; import { useNavigate } from "react-router-dom"; import { useSelector } from "react-redux"; import { Box, Tab, useTheme } from "@mui/material"; -import CustomTabList from "../../Components/Tab"; +import CustomTabList from "../../../Components/Tab/index.jsx"; import TabContext from "@mui/lab/TabContext"; -import ProfilePanel from "./components/ProfilePanel"; -import PasswordPanel from "./components/PasswordPanel"; -import TeamPanel from "./components/TeamPanel"; +import ProfilePanel from "./components/ProfilePanel.jsx"; +import PasswordPanel from "./components/PasswordPanel.jsx"; +import TeamPanel from "./components/TeamPanel.jsx"; import { useTranslation } from "react-i18next"; /** diff --git a/client/src/Pages/Auth/CheckEmail.jsx b/client/src/Pages/v1/Auth/CheckEmail.jsx similarity index 93% rename from client/src/Pages/Auth/CheckEmail.jsx rename to client/src/Pages/v1/Auth/CheckEmail.jsx index e1f5e43ed..95217bcb5 100644 --- a/client/src/Pages/Auth/CheckEmail.jsx +++ b/client/src/Pages/v1/Auth/CheckEmail.jsx @@ -3,13 +3,13 @@ import { useEffect, useState } from "react"; import { useTheme } from "@emotion/react"; import { useDispatch } from "react-redux"; import { useNavigate } from "react-router-dom"; -import { createToast } from "../../Utils/toastUtils"; -import { forgotPassword } from "../../Features/Auth/authSlice"; +import { createToast } from "../../../Utils/toastUtils.jsx"; +import { forgotPassword } from "../../../Features/Auth/authSlice.js"; import { Trans, 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"; -import IconBox from "../../Components/IconBox"; +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"; +import IconBox from "../../../Components/IconBox/index.jsx"; import "./index.css"; const CheckEmail = () => { diff --git a/client/src/Pages/Auth/ForgotPassword.jsx b/client/src/Pages/v1/Auth/ForgotPassword.jsx similarity index 91% rename from client/src/Pages/Auth/ForgotPassword.jsx rename to client/src/Pages/v1/Auth/ForgotPassword.jsx index 2a887756e..9836f5d77 100644 --- a/client/src/Pages/Auth/ForgotPassword.jsx +++ b/client/src/Pages/v1/Auth/ForgotPassword.jsx @@ -1,16 +1,16 @@ import { Box, Stack, Typography, Button } from "@mui/material"; import { useTheme } from "@emotion/react"; -import { createToast } from "../../Utils/toastUtils"; +import { createToast } from "../../../Utils/toastUtils.jsx"; import { useDispatch, useSelector } from "react-redux"; -import { forgotPassword } from "../../Features/Auth/authSlice"; +import { forgotPassword } from "../../../Features/Auth/authSlice.js"; import { useEffect, useState } from "react"; -import { newOrChangedCredentials } from "../../Validation/validation"; +import { newOrChangedCredentials } from "../../../Validation/validation.js"; import { useNavigate } from "react-router-dom"; -import TextInput from "../../Components/Inputs/TextInput"; -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 TextInput from "../../../Components/Inputs/TextInput/index.jsx"; +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/index.jsx"; import { Trans, useTranslation } from "react-i18next"; import "./index.css"; diff --git a/client/src/Pages/Auth/Login/hooks/useLoadingSubmit.jsx b/client/src/Pages/v1/Auth/Login/hooks/useLoadingSubmit.jsx similarity index 100% rename from client/src/Pages/Auth/Login/hooks/useLoadingSubmit.jsx rename to client/src/Pages/v1/Auth/Login/hooks/useLoadingSubmit.jsx diff --git a/client/src/Pages/Auth/Login/hooks/useLoginForm.jsx b/client/src/Pages/v1/Auth/Login/hooks/useLoginForm.jsx similarity index 100% rename from client/src/Pages/Auth/Login/hooks/useLoginForm.jsx rename to client/src/Pages/v1/Auth/Login/hooks/useLoginForm.jsx diff --git a/client/src/Pages/Auth/Login/hooks/useLoginSubmit.jsx b/client/src/Pages/v1/Auth/Login/hooks/useLoginSubmit.jsx similarity index 87% rename from client/src/Pages/Auth/Login/hooks/useLoginSubmit.jsx rename to client/src/Pages/v1/Auth/Login/hooks/useLoginSubmit.jsx index 6bc5716ff..0eed9bd07 100644 --- a/client/src/Pages/Auth/Login/hooks/useLoginSubmit.jsx +++ b/client/src/Pages/v1/Auth/Login/hooks/useLoginSubmit.jsx @@ -1,7 +1,7 @@ import { useDispatch } from "react-redux"; -import { login } from "../../../../Features/Auth/authSlice"; +import { login } from "../../../../../Features/Auth/authSlice.js"; import { useNavigate } from "react-router-dom"; -import { createToast } from "../../../../Utils/toastUtils"; +import { createToast } from "../../../../../Utils/toastUtils.jsx"; import { useTranslation } from "react-i18next"; const useLoginSubmit = () => { diff --git a/client/src/Pages/Auth/Login/hooks/useValidateLoginForm.jsx b/client/src/Pages/v1/Auth/Login/hooks/useValidateLoginForm.jsx similarity index 90% rename from client/src/Pages/Auth/Login/hooks/useValidateLoginForm.jsx rename to client/src/Pages/v1/Auth/Login/hooks/useValidateLoginForm.jsx index e5d045be8..ca9bef879 100644 --- a/client/src/Pages/Auth/Login/hooks/useValidateLoginForm.jsx +++ b/client/src/Pages/v1/Auth/Login/hooks/useValidateLoginForm.jsx @@ -1,5 +1,5 @@ import { useState } from "react"; -import { loginCredentials } from "../../../../Validation/validation"; +import { loginCredentials } from "../../../../../Validation/validation.js"; const useValidateLoginForm = () => { const [errors, setErrors] = useState({ diff --git a/client/src/Pages/Auth/Login/index.jsx b/client/src/Pages/v1/Auth/Login/index.jsx similarity index 82% rename from client/src/Pages/Auth/Login/index.jsx rename to client/src/Pages/v1/Auth/Login/index.jsx index d06ecef6d..901edcd8b 100644 --- a/client/src/Pages/Auth/Login/index.jsx +++ b/client/src/Pages/v1/Auth/Login/index.jsx @@ -1,17 +1,17 @@ // Components import Stack from "@mui/material/Stack"; import Button from "@mui/material/Button"; -import TextInput from "../../../Components/Inputs/TextInput"; -import { PasswordEndAdornment } from "../../../Components/Inputs/TextInput/Adornments"; -import TextLink from "../../../Components/TextLink"; -import AuthPageWrapper from "../components/AuthPageWrapper"; +import TextInput from "../../../../Components/Inputs/TextInput/index.jsx"; +import { PasswordEndAdornment } from "../../../../Components/Inputs/TextInput/Adornments/index.jsx"; +import TextLink from "../../../../Components/TextLink/index.jsx"; +import AuthPageWrapper from "../components/AuthPageWrapper.jsx"; // Utils import { useTheme } from "@mui/material/styles"; import { useTranslation } from "react-i18next"; -import useLoginForm from "./hooks/useLoginForm"; -import useValidateLoginForm from "./hooks/useValidateLoginForm"; -import useLoginSubmit from "./hooks/useLoginSubmit"; -import useLoadingSubmit from "./hooks/useLoadingSubmit"; +import useLoginForm from "./hooks/useLoginForm.jsx"; +import useValidateLoginForm from "./hooks/useValidateLoginForm.jsx"; +import useLoginSubmit from "./hooks/useLoginSubmit.jsx"; +import useLoadingSubmit from "./hooks/useLoadingSubmit.jsx"; const Login = () => { // Hooks diff --git a/client/src/Pages/Auth/NewPasswordConfirmed.jsx b/client/src/Pages/v1/Auth/NewPasswordConfirmed.jsx similarity index 90% rename from client/src/Pages/Auth/NewPasswordConfirmed.jsx rename to client/src/Pages/v1/Auth/NewPasswordConfirmed.jsx index 7ace77abc..2bede1ae4 100644 --- a/client/src/Pages/Auth/NewPasswordConfirmed.jsx +++ b/client/src/Pages/v1/Auth/NewPasswordConfirmed.jsx @@ -2,11 +2,11 @@ import { Box, Button, Stack, Typography } from "@mui/material"; import { useTheme } from "@emotion/react"; import { useNavigate } from "react-router-dom"; import { useDispatch } from "react-redux"; -import { clearAuthState } from "../../Features/Auth/authSlice"; -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 { clearAuthState } from "../../../Features/Auth/authSlice.js"; +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/index.jsx"; import { Trans, useTranslation } from "react-i18next"; import "./index.css"; diff --git a/client/src/Pages/Auth/Register/index.jsx b/client/src/Pages/v1/Auth/Register/index.jsx similarity index 94% rename from client/src/Pages/Auth/Register/index.jsx rename to client/src/Pages/v1/Auth/Register/index.jsx index 2b45d2d15..a903602d4 100644 --- a/client/src/Pages/Auth/Register/index.jsx +++ b/client/src/Pages/v1/Auth/Register/index.jsx @@ -1,10 +1,10 @@ // Components import Stack from "@mui/material/Stack"; import Typography from "@mui/material/Typography"; -import TextInput from "../../../Components/Inputs/TextInput"; +import TextInput from "../../../../Components/Inputs/TextInput/index.jsx"; import Button from "@mui/material/Button"; import Box from "@mui/material/Box"; -import PasswordTooltip from "../components/PasswordTooltip"; +import PasswordTooltip from "../components/PasswordTooltip.jsx"; // Utils import { useTheme } from "@emotion/react"; @@ -13,11 +13,11 @@ import { useState, useEffect } from "react"; import { useNavigate } from "react-router-dom"; import { useDispatch, useSelector } from "react-redux"; import { useParams } from "react-router-dom"; -import { networkService } from "../../../main"; -import { newOrChangedCredentials } from "../../../Validation/validation"; -import { register } from "../../../Features/Auth/authSlice"; -import AuthPageWrapper from "../components/AuthPageWrapper"; -import { createToast } from "../../../Utils/toastUtils"; +import { networkService } from "../../../../main.jsx"; +import { newOrChangedCredentials } from "../../../../Validation/validation.js"; +import { register } from "../../../../Features/Auth/authSlice.js"; +import AuthPageWrapper from "../components/AuthPageWrapper.jsx"; +import { createToast } from "../../../../Utils/toastUtils.jsx"; import PropTypes from "prop-types"; const getFeedbackStatus = (form, errors, field, criteria) => { diff --git a/client/src/Pages/Auth/SetNewPassword.jsx b/client/src/Pages/v1/Auth/SetNewPassword.jsx similarity index 91% rename from client/src/Pages/Auth/SetNewPassword.jsx rename to client/src/Pages/v1/Auth/SetNewPassword.jsx index b4899f47c..e9df89d88 100644 --- a/client/src/Pages/Auth/SetNewPassword.jsx +++ b/client/src/Pages/v1/Auth/SetNewPassword.jsx @@ -4,18 +4,18 @@ import { useNavigate } from "react-router-dom"; import { useParams } from "react-router-dom"; import { useTheme } from "@emotion/react"; import { Box, Stack, Typography, Button } from "@mui/material"; -import { setNewPassword } from "../../Features/Auth/authSlice"; -import { createToast } from "../../Utils/toastUtils"; -import { newOrChangedCredentials } from "../../Validation/validation"; -import Check from "../../Components/Check/Check"; -import TextInput from "../../Components/Inputs/TextInput"; -import { PasswordEndAdornment } from "../../Components/Inputs/TextInput/Adornments"; -import IconBox from "../../Components/IconBox"; -import LockIcon from "../../assets/icons/lock.svg?react"; -import Logo from "../../assets/icons/checkmate-icon.svg?react"; -import Background from "../../assets/Images/background-grid.svg?react"; +import { setNewPassword } from "../../../Features/Auth/authSlice.js"; +import { createToast } from "../../../Utils/toastUtils.jsx"; +import { newOrChangedCredentials } from "../../../Validation/validation.js"; +import Check from "../../../Components/Check/Check.jsx"; +import TextInput from "../../../Components/Inputs/TextInput/index.jsx"; +import { PasswordEndAdornment } from "../../../Components/Inputs/TextInput/Adornments/index.jsx"; +import IconBox from "../../../Components/IconBox/index.jsx"; +import LockIcon from "../../../assets/icons/lock.svg?react"; +import Logo from "../../../assets/icons/checkmate-icon.svg?react"; +import Background from "../../../assets/Images/background-grid.svg?react"; import "./index.css"; -import { useValidatePassword } from "./hooks/useValidatePassword"; +import { useValidatePassword } from "./hooks/useValidatePassword.jsx"; import { Trans, useTranslation } from "react-i18next"; const SetNewPassword = () => { diff --git a/client/src/Pages/Auth/components/AuthHeader.jsx b/client/src/Pages/v1/Auth/components/AuthHeader.jsx similarity index 81% rename from client/src/Pages/Auth/components/AuthHeader.jsx rename to client/src/Pages/v1/Auth/components/AuthHeader.jsx index ce454c065..067c47d92 100644 --- a/client/src/Pages/Auth/components/AuthHeader.jsx +++ b/client/src/Pages/v1/Auth/components/AuthHeader.jsx @@ -1,9 +1,9 @@ // Components import Stack from "@mui/material/Stack"; import Typography from "@mui/material/Typography"; -import Logo from "../../../assets/icons/checkmate-icon.svg?react"; -import LanguageSelector from "../../../Components/LanguageSelector"; -import ThemeSwitch from "../../../Components/ThemeSwitch"; +import Logo from "../../../../assets/icons/checkmate-icon.svg?react"; +import LanguageSelector from "../../../../Components/LanguageSelector.jsx"; +import ThemeSwitch from "../../../../Components/ThemeSwitch/index.jsx"; // Utils import { useTheme } from "@mui/material/styles"; diff --git a/client/src/Pages/Auth/components/AuthPageWrapper.jsx b/client/src/Pages/v1/Auth/components/AuthPageWrapper.jsx similarity index 92% rename from client/src/Pages/Auth/components/AuthPageWrapper.jsx rename to client/src/Pages/v1/Auth/components/AuthPageWrapper.jsx index b83be19c5..7e358e6ab 100644 --- a/client/src/Pages/Auth/components/AuthPageWrapper.jsx +++ b/client/src/Pages/v1/Auth/components/AuthPageWrapper.jsx @@ -1,10 +1,10 @@ -import Background from "../../../assets/Images/background-grid.svg?react"; +import Background from "../../../../assets/Images/background-grid.svg?react"; import Stack from "@mui/material/Stack"; import Box from "@mui/material/Box"; import Typography from "@mui/material/Typography"; -import AuthHeader from "../components/AuthHeader"; +import AuthHeader from "./AuthHeader.jsx"; import { useTheme } from "@mui/material/styles"; -import Logo from "../../../assets/icons/checkmate-icon.svg?react"; +import Logo from "../../../../assets/icons/checkmate-icon.svg?react"; import PropTypes from "prop-types"; const AuthPageWrapper = ({ children, heading, welcome }) => { diff --git a/client/src/Pages/Auth/components/PasswordTooltip.jsx b/client/src/Pages/v1/Auth/components/PasswordTooltip.jsx similarity index 98% rename from client/src/Pages/Auth/components/PasswordTooltip.jsx rename to client/src/Pages/v1/Auth/components/PasswordTooltip.jsx index bc2f28ec7..ff3c2f385 100644 --- a/client/src/Pages/Auth/components/PasswordTooltip.jsx +++ b/client/src/Pages/v1/Auth/components/PasswordTooltip.jsx @@ -1,4 +1,4 @@ -import Check from "../../../Components/Check/Check"; +import Check from "../../../../Components/Check/Check.jsx"; import Stack from "@mui/material/Stack"; import { Tooltip, useTheme } from "@mui/material"; import { useTranslation } from "react-i18next"; diff --git a/client/src/Pages/Auth/hooks/usePasswordFeedback.jsx b/client/src/Pages/v1/Auth/hooks/usePasswordFeedback.jsx similarity index 96% rename from client/src/Pages/Auth/hooks/usePasswordFeedback.jsx rename to client/src/Pages/v1/Auth/hooks/usePasswordFeedback.jsx index 4159e6166..3c199e25f 100644 --- a/client/src/Pages/Auth/hooks/usePasswordFeedback.jsx +++ b/client/src/Pages/v1/Auth/hooks/usePasswordFeedback.jsx @@ -1,5 +1,5 @@ import { useState } from "react"; -import { newOrChangedCredentials } from "../../../Validation/validation"; +import { newOrChangedCredentials } from "../../../../Validation/validation.js"; const usePasswordFeedback = () => { const [feedback, setFeedback] = useState({}); diff --git a/client/src/Pages/Auth/hooks/useValidatePassword.jsx b/client/src/Pages/v1/Auth/hooks/useValidatePassword.jsx similarity index 96% rename from client/src/Pages/Auth/hooks/useValidatePassword.jsx rename to client/src/Pages/v1/Auth/hooks/useValidatePassword.jsx index 102d8d73e..231b8eeb8 100644 --- a/client/src/Pages/Auth/hooks/useValidatePassword.jsx +++ b/client/src/Pages/v1/Auth/hooks/useValidatePassword.jsx @@ -1,5 +1,5 @@ import { useMemo, useState } from "react"; -import { newOrChangedCredentials } from "../../../Validation/validation"; +import { newOrChangedCredentials } from "../../../../Validation/validation.js"; const getFeedbackStatus = (form, errors, field, criteria) => { const fieldErrors = errors[field]; diff --git a/client/src/Pages/Auth/index.css b/client/src/Pages/v1/Auth/index.css similarity index 100% rename from client/src/Pages/Auth/index.css rename to client/src/Pages/v1/Auth/index.css diff --git a/client/src/Pages/Incidents/Components/IncidentTable/index.jsx b/client/src/Pages/v1/Incidents/Components/IncidentTable/index.jsx similarity index 88% rename from client/src/Pages/Incidents/Components/IncidentTable/index.jsx rename to client/src/Pages/v1/Incidents/Components/IncidentTable/index.jsx index df9174290..e204d7167 100644 --- a/client/src/Pages/Incidents/Components/IncidentTable/index.jsx +++ b/client/src/Pages/v1/Incidents/Components/IncidentTable/index.jsx @@ -1,26 +1,26 @@ //Components import Stack from "@mui/material/Stack"; -import DataTable from "../../../../Components/Table"; +import DataTable from "../../../../../Components/Table/index.jsx"; import Table from "@mui/material/Table"; import TableBody from "@mui/material/TableBody"; import TableRow from "@mui/material/TableRow"; import TableCell from "@mui/material/TableCell"; -import TableSkeleton from "../../../../Components/Table/skeleton"; -import Pagination from "../../../../Components/Table/TablePagination"; -import { StatusLabel } from "../../../../Components/Label"; -import { HttpStatusLabel } from "../../../../Components/HttpStatusLabel"; -import GenericFallback from "../../../../Components/GenericFallback"; -import NetworkError from "../../../../Components/GenericFallback/NetworkError"; +import TableSkeleton from "../../../../../Components/Table/skeleton.jsx"; +import Pagination from "../../../../../Components/Table/TablePagination/index.jsx"; +import { StatusLabel } from "../../../../../Components/Label/index.jsx"; +import { HttpStatusLabel } from "../../../../../Components/HttpStatusLabel/index.jsx"; +import GenericFallback from "../../../../../Components/GenericFallback/index.jsx"; +import NetworkError from "../../../../../Components/GenericFallback/NetworkError.jsx"; //Utils -import { formatDateWithTz } from "../../../../Utils/timeUtils"; +import { formatDateWithTz } from "../../../../../Utils/timeUtils.js"; import { useSelector } from "react-redux"; import { useState } from "react"; import PropTypes from "prop-types"; import { useTranslation } from "react-i18next"; -import { useFetchChecksTeam } from "../../../../Hooks/checkHooks"; -import { useFetchChecksByMonitor } from "../../../../Hooks/checkHooks"; -import { useResolveIncident } from "../../../../Hooks/checkHooks"; +import { useFetchChecksTeam } from "../../../../../Hooks/v1/checkHooks.js"; +import { useFetchChecksByMonitor } from "../../../../../Hooks/v1/checkHooks.js"; +import { useResolveIncident } from "../../../../../Hooks/v1/checkHooks.js"; import { Button, Typography, useTheme } from "@mui/material"; import { lighten } from "@mui/material/styles"; diff --git a/client/src/Pages/Incidents/Components/OptionsHeader/index.jsx b/client/src/Pages/v1/Incidents/Components/OptionsHeader/index.jsx similarity index 96% rename from client/src/Pages/Incidents/Components/OptionsHeader/index.jsx rename to client/src/Pages/v1/Incidents/Components/OptionsHeader/index.jsx index 259880223..b1bde1175 100644 --- a/client/src/Pages/Incidents/Components/OptionsHeader/index.jsx +++ b/client/src/Pages/v1/Incidents/Components/OptionsHeader/index.jsx @@ -1,11 +1,11 @@ // Components import { Stack, Typography, Button, ButtonGroup } from "@mui/material"; -import Select from "../../../../Components/Inputs/Select"; +import Select from "../../../../../Components/Inputs/Select/index.jsx"; import PropTypes from "prop-types"; //Utils import { useTheme } from "@emotion/react"; -import SkeletonLayout from "./skeleton"; +import SkeletonLayout from "./skeleton.jsx"; import { useTranslation } from "react-i18next"; const OptionsHeader = ({ diff --git a/client/src/Pages/Incidents/Components/OptionsHeader/skeleton.jsx b/client/src/Pages/v1/Incidents/Components/OptionsHeader/skeleton.jsx similarity index 100% rename from client/src/Pages/Incidents/Components/OptionsHeader/skeleton.jsx rename to client/src/Pages/v1/Incidents/Components/OptionsHeader/skeleton.jsx diff --git a/client/src/Pages/Incidents/Components/StatusBoxes/StatusBox.jsx b/client/src/Pages/v1/Incidents/Components/StatusBoxes/StatusBox.jsx similarity index 87% rename from client/src/Pages/Incidents/Components/StatusBoxes/StatusBox.jsx rename to client/src/Pages/v1/Incidents/Components/StatusBoxes/StatusBox.jsx index 7b20e3d51..cd7078e6c 100644 --- a/client/src/Pages/Incidents/Components/StatusBoxes/StatusBox.jsx +++ b/client/src/Pages/v1/Incidents/Components/StatusBoxes/StatusBox.jsx @@ -1,11 +1,11 @@ import PropTypes from "prop-types"; import { useTheme } from "@emotion/react"; import { Box, Stack, Typography } from "@mui/material"; -import Background from "../../../../assets/Images/background-grid.svg?react"; -import AlertIcon from "../../../../assets/icons/alert-icon.svg?react"; -import CheckIcon from "../../../../assets/icons/check-icon.svg?react"; -import CloseIcon from "../../../../assets/icons/close-icon.svg?react"; -import WarningIcon from "../../../../assets/icons/warning-icon.svg?react"; +import Background from "../../../../../assets/Images/background-grid.svg?react"; +import AlertIcon from "../../../../../assets/icons/alert-icon.svg?react"; +import CheckIcon from "../../../../../assets/icons/check-icon.svg?react"; +import CloseIcon from "../../../../../assets/icons/close-icon.svg?react"; +import WarningIcon from "../../../../../assets/icons/warning-icon.svg?react"; const StatusBox = ({ title, value, status }) => { const theme = useTheme(); diff --git a/client/src/Pages/Incidents/Components/StatusBoxes/index.jsx b/client/src/Pages/v1/Incidents/Components/StatusBoxes/index.jsx similarity index 92% rename from client/src/Pages/Incidents/Components/StatusBoxes/index.jsx rename to client/src/Pages/v1/Incidents/Components/StatusBoxes/index.jsx index 6ba2f1820..3bcd3fd3a 100644 --- a/client/src/Pages/Incidents/Components/StatusBoxes/index.jsx +++ b/client/src/Pages/v1/Incidents/Components/StatusBoxes/index.jsx @@ -1,9 +1,9 @@ import PropTypes from "prop-types"; import { Stack } from "@mui/material"; -import StatusBox from "./StatusBox"; +import StatusBox from "./StatusBox.jsx"; import { useTheme } from "@emotion/react"; import { useTranslation } from "react-i18next"; -import SkeletonLayout from "./skeleton"; +import SkeletonLayout from "./skeleton.jsx"; const StatusBoxes = ({ isLoading, summary }) => { const theme = useTheme(); diff --git a/client/src/Pages/Incidents/Components/StatusBoxes/skeleton.jsx b/client/src/Pages/v1/Incidents/Components/StatusBoxes/skeleton.jsx similarity index 100% rename from client/src/Pages/Incidents/Components/StatusBoxes/skeleton.jsx rename to client/src/Pages/v1/Incidents/Components/StatusBoxes/skeleton.jsx diff --git a/client/src/Pages/Incidents/index.jsx b/client/src/Pages/v1/Incidents/index.jsx similarity index 81% rename from client/src/Pages/Incidents/index.jsx rename to client/src/Pages/v1/Incidents/index.jsx index 7696c453b..b9281f97c 100644 --- a/client/src/Pages/Incidents/index.jsx +++ b/client/src/Pages/v1/Incidents/index.jsx @@ -1,19 +1,19 @@ // Components import { Stack } from "@mui/material"; -import Breadcrumbs from "../../Components/Breadcrumbs"; -import GenericFallback from "../../Components/GenericFallback"; -import IncidentTable from "./Components/IncidentTable"; -import OptionsHeader from "./Components/OptionsHeader"; -import StatusBoxes from "./Components/StatusBoxes"; +import Breadcrumbs from "../../../Components/Breadcrumbs/index.jsx"; +import GenericFallback from "../../../Components/GenericFallback/index.jsx"; +import IncidentTable from "./Components/IncidentTable/index.jsx"; +import OptionsHeader from "./Components/OptionsHeader/index.jsx"; +import StatusBoxes from "./Components/StatusBoxes/index.jsx"; import { Box, Button } from "@mui/material"; //Utils import { useTheme } from "@emotion/react"; -import { useFetchMonitorsByTeamId } from "../../Hooks/monitorHooks"; -import { useFetchChecksSummaryByTeamId } from "../../Hooks/checkHooks"; -import { useAcknowledgeChecks } from "../../Hooks/checkHooks"; +import { useFetchMonitorsByTeamId } from "../../../Hooks/v1/monitorHooks.js"; +import { useFetchChecksSummaryByTeamId } from "../../../Hooks/v1/checkHooks.js"; +import { useAcknowledgeChecks } from "../../../Hooks/v1/checkHooks.js"; import { useState, useEffect } from "react"; -import NetworkError from "../../Components/GenericFallback/NetworkError"; +import NetworkError from "../../../Components/GenericFallback/NetworkError.jsx"; import { useTranslation } from "react-i18next"; import { useParams } from "react-router-dom"; diff --git a/client/src/Pages/Infrastructure/Create/Components/CustomAlertsSection.jsx b/client/src/Pages/v1/Infrastructure/Create/Components/CustomAlertsSection.jsx similarity index 91% rename from client/src/Pages/Infrastructure/Create/Components/CustomAlertsSection.jsx rename to client/src/Pages/v1/Infrastructure/Create/Components/CustomAlertsSection.jsx index 5a6f5e981..299331a79 100644 --- a/client/src/Pages/Infrastructure/Create/Components/CustomAlertsSection.jsx +++ b/client/src/Pages/v1/Infrastructure/Create/Components/CustomAlertsSection.jsx @@ -1,7 +1,7 @@ -import ConfigBox from "../../../../Components/ConfigBox"; +import ConfigBox from "../../../../../Components/ConfigBox/index.jsx"; import { Box, Stack, Typography } from "@mui/material"; -import { CustomThreshold } from "../Components/CustomThreshold"; -import { capitalizeFirstLetter } from "../../../../Utils/stringUtils"; +import { CustomThreshold } from "./CustomThreshold/index.jsx"; +import { capitalizeFirstLetter } from "../../../../../Utils/stringUtils.js"; import { useTheme } from "@emotion/react"; import { useTranslation } from "react-i18next"; import PropTypes from "prop-types"; diff --git a/client/src/Pages/Infrastructure/Create/Components/CustomThreshold/index.jsx b/client/src/Pages/v1/Infrastructure/Create/Components/CustomThreshold/index.jsx similarity index 95% rename from client/src/Pages/Infrastructure/Create/Components/CustomThreshold/index.jsx rename to client/src/Pages/v1/Infrastructure/Create/Components/CustomThreshold/index.jsx index 865698f44..4edc0b766 100644 --- a/client/src/Pages/Infrastructure/Create/Components/CustomThreshold/index.jsx +++ b/client/src/Pages/v1/Infrastructure/Create/Components/CustomThreshold/index.jsx @@ -1,6 +1,6 @@ import { Box, Stack, Typography } from "@mui/material"; -import TextInput from "../../../../../Components/Inputs/TextInput"; -import Checkbox from "../../../../../Components/Inputs/Checkbox"; +import TextInput from "../../../../../../Components/Inputs/TextInput/index.jsx"; +import Checkbox from "../../../../../../Components/Inputs/Checkbox/index.jsx"; import { useTheme } from "@emotion/react"; import PropTypes from "prop-types"; diff --git a/client/src/Pages/Infrastructure/Create/Components/MonitorActionButtons.jsx b/client/src/Pages/v1/Infrastructure/Create/Components/MonitorActionButtons.jsx similarity index 96% rename from client/src/Pages/Infrastructure/Create/Components/MonitorActionButtons.jsx rename to client/src/Pages/v1/Infrastructure/Create/Components/MonitorActionButtons.jsx index becda3e3a..16df8ad01 100644 --- a/client/src/Pages/Infrastructure/Create/Components/MonitorActionButtons.jsx +++ b/client/src/Pages/v1/Infrastructure/Create/Components/MonitorActionButtons.jsx @@ -4,7 +4,7 @@ import { useTheme } from "@emotion/react"; import { useTranslation } from "react-i18next"; import PauseCircleOutlineIcon from "@mui/icons-material/PauseCircleOutline"; import PlayCircleOutlineRoundedIcon from "@mui/icons-material/PlayCircleOutlineRounded"; -import Dialog from "../../../../Components/Dialog"; +import Dialog from "../../../../../Components/Dialog/index.jsx"; import PropTypes from "prop-types"; const MonitorActionButtons = ({ monitor, isBusy, handlePause, handleRemove }) => { diff --git a/client/src/Pages/Infrastructure/Create/Components/MonitorStatusHeader.jsx b/client/src/Pages/v1/Infrastructure/Create/Components/MonitorStatusHeader.jsx similarity index 91% rename from client/src/Pages/Infrastructure/Create/Components/MonitorStatusHeader.jsx rename to client/src/Pages/v1/Infrastructure/Create/Components/MonitorStatusHeader.jsx index 2ed48bf64..4313c34c0 100644 --- a/client/src/Pages/Infrastructure/Create/Components/MonitorStatusHeader.jsx +++ b/client/src/Pages/v1/Infrastructure/Create/Components/MonitorStatusHeader.jsx @@ -1,8 +1,8 @@ import { Box, Stack, Tooltip, Typography } from "@mui/material"; -import { useMonitorUtils } from "../../../../Hooks/useMonitorUtils"; +import { useMonitorUtils } from "../../../../../Hooks/v1/useMonitorUtils.js"; import { useTheme } from "@emotion/react"; import { useTranslation } from "react-i18next"; -import PulseDot from "../../../../Components/Animated/PulseDot"; +import PulseDot from "../../../../../Components/Animated/PulseDot.jsx"; import PropTypes from "prop-types"; const MonitorStatusHeader = ({ monitor, infrastructureMonitor }) => { const theme = useTheme(); diff --git a/client/src/Pages/Infrastructure/Create/hooks/useInfrastructureMonitorForm.jsx b/client/src/Pages/v1/Infrastructure/Create/hooks/useInfrastructureMonitorForm.jsx similarity index 100% rename from client/src/Pages/Infrastructure/Create/hooks/useInfrastructureMonitorForm.jsx rename to client/src/Pages/v1/Infrastructure/Create/hooks/useInfrastructureMonitorForm.jsx diff --git a/client/src/Pages/Infrastructure/Create/hooks/useInfrastructureSubmit.jsx b/client/src/Pages/v1/Infrastructure/Create/hooks/useInfrastructureSubmit.jsx similarity index 98% rename from client/src/Pages/Infrastructure/Create/hooks/useInfrastructureSubmit.jsx rename to client/src/Pages/v1/Infrastructure/Create/hooks/useInfrastructureSubmit.jsx index 9b3ee0d04..0fd5c0081 100644 --- a/client/src/Pages/Infrastructure/Create/hooks/useInfrastructureSubmit.jsx +++ b/client/src/Pages/v1/Infrastructure/Create/hooks/useInfrastructureSubmit.jsx @@ -1,4 +1,4 @@ -import { useCreateMonitor, useUpdateMonitor } from "../../../../Hooks/monitorHooks"; +import { useCreateMonitor, useUpdateMonitor } from "../../../../../Hooks/v1/monitorHooks.js"; const useInfrastructureSubmit = () => { const [createMonitor, isCreating] = useCreateMonitor(); const [updateMonitor, isUpdating] = useUpdateMonitor(); diff --git a/client/src/Pages/Infrastructure/Create/hooks/useValidateInfrastructureForm.jsx b/client/src/Pages/v1/Infrastructure/Create/hooks/useValidateInfrastructureForm.jsx similarity index 85% rename from client/src/Pages/Infrastructure/Create/hooks/useValidateInfrastructureForm.jsx rename to client/src/Pages/v1/Infrastructure/Create/hooks/useValidateInfrastructureForm.jsx index 080f441c4..73097d0d8 100644 --- a/client/src/Pages/Infrastructure/Create/hooks/useValidateInfrastructureForm.jsx +++ b/client/src/Pages/v1/Infrastructure/Create/hooks/useValidateInfrastructureForm.jsx @@ -1,6 +1,6 @@ import { useState } from "react"; -import { infrastructureMonitorValidation } from "../../../../Validation/validation"; -import { createToast } from "../../../../Utils/toastUtils"; +import { infrastructureMonitorValidation } from "../../../../../Validation/validation.js"; +import { createToast } from "../../../../../Utils/toastUtils.jsx"; const useValidateInfrastructureForm = () => { const [errors, setErrors] = useState({}); diff --git a/client/src/Pages/Infrastructure/Create/index.jsx b/client/src/Pages/v1/Infrastructure/Create/index.jsx similarity index 92% rename from client/src/Pages/Infrastructure/Create/index.jsx rename to client/src/Pages/v1/Infrastructure/Create/index.jsx index a9d16c926..06ecdff6c 100644 --- a/client/src/Pages/Infrastructure/Create/index.jsx +++ b/client/src/Pages/v1/Infrastructure/Create/index.jsx @@ -1,18 +1,18 @@ //Components -import Breadcrumbs from "../../../Components/Breadcrumbs"; -import ConfigBox from "../../../Components/ConfigBox"; -import FieldWrapper from "../../../Components/Inputs/FieldWrapper"; -import Link from "../../../Components/Link"; -import Select from "../../../Components/Inputs/Select"; -import TextInput from "../../../Components/Inputs/TextInput"; +import Breadcrumbs from "../../../../Components/Breadcrumbs/index.jsx"; +import ConfigBox from "../../../../Components/ConfigBox/index.jsx"; +import FieldWrapper from "../../../../Components/Inputs/FieldWrapper/index.jsx"; +import Link from "../../../../Components/Link/index.jsx"; +import Select from "../../../../Components/Inputs/Select/index.jsx"; +import TextInput from "../../../../Components/Inputs/TextInput/index.jsx"; import { Box, Stack, Typography, Button, ButtonGroup } from "@mui/material"; -import { HttpAdornment } from "../../../Components/Inputs/TextInput/Adornments"; -import MonitorStatusHeader from "./Components/MonitorStatusHeader"; -import MonitorActionButtons from "./Components/MonitorActionButtons"; -import CustomAlertsSection from "./Components/CustomAlertsSection"; +import { HttpAdornment } from "../../../../Components/Inputs/TextInput/Adornments/index.jsx"; +import MonitorStatusHeader from "./Components/MonitorStatusHeader.jsx"; +import MonitorActionButtons from "./Components/MonitorActionButtons.jsx"; +import CustomAlertsSection from "./Components/CustomAlertsSection.jsx"; // Utils -import NotificationsConfig from "../../../Components/NotificationConfig"; -import { useGetNotificationsByTeamId } from "../../../Hooks/useNotifications"; +import NotificationsConfig from "../../../../Components/NotificationConfig/index.jsx"; +import { useGetNotificationsByTeamId } from "../../../../Hooks/v1/useNotifications.js"; import { useParams } from "react-router-dom"; import { useState, useEffect } from "react"; import { useTheme } from "@emotion/react"; @@ -22,10 +22,10 @@ import { useFetchGlobalSettings, useFetchHardwareMonitorById, usePauseMonitor, -} from "../../../Hooks/monitorHooks"; -import useInfrastructureMonitorForm from "./hooks/useInfrastructureMonitorForm"; -import useValidateInfrastructureForm from "./hooks/useValidateInfrastructureForm"; -import useInfrastructureSubmit from "./hooks/useInfrastructureSubmit"; +} from "../../../../Hooks/v1/monitorHooks.js"; +import useInfrastructureMonitorForm from "./hooks/useInfrastructureMonitorForm.jsx"; +import useValidateInfrastructureForm from "./hooks/useValidateInfrastructureForm.jsx"; +import useInfrastructureSubmit from "./hooks/useInfrastructureSubmit.jsx"; const CreateInfrastructureMonitor = () => { const { monitorId } = useParams(); diff --git a/client/src/Pages/Infrastructure/Details/Components/AreaChartBoxes/InfraAreaChart.jsx b/client/src/Pages/v1/Infrastructure/Details/Components/AreaChartBoxes/InfraAreaChart.jsx similarity index 79% rename from client/src/Pages/Infrastructure/Details/Components/AreaChartBoxes/InfraAreaChart.jsx rename to client/src/Pages/v1/Infrastructure/Details/Components/AreaChartBoxes/InfraAreaChart.jsx index 9544bcf2a..47f33a5e6 100644 --- a/client/src/Pages/Infrastructure/Details/Components/AreaChartBoxes/InfraAreaChart.jsx +++ b/client/src/Pages/v1/Infrastructure/Details/Components/AreaChartBoxes/InfraAreaChart.jsx @@ -1,10 +1,10 @@ // Components import { Typography } from "@mui/material"; -import BaseContainer from "../BaseContainer"; -import AreaChart from "../../../../../Components/Charts/AreaChart"; +import BaseContainer from "../BaseContainer/index.jsx"; +import AreaChart from "../../../../../../Components/Charts/AreaChart/index.jsx"; // Utils import { useTheme } from "@emotion/react"; -import { useHardwareUtils } from "../../Hooks/useHardwareUtils"; +import { useHardwareUtils } from "../../Hooks/useHardwareUtils.jsx"; const InfraAreaChart = ({ config }) => { const theme = useTheme(); const { getDimensions } = useHardwareUtils(); diff --git a/client/src/Pages/Infrastructure/Details/Components/AreaChartBoxes/index.jsx b/client/src/Pages/v1/Infrastructure/Details/Components/AreaChartBoxes/index.jsx similarity index 93% rename from client/src/Pages/Infrastructure/Details/Components/AreaChartBoxes/index.jsx rename to client/src/Pages/v1/Infrastructure/Details/Components/AreaChartBoxes/index.jsx index 0eb3aa36d..3dfb4edd8 100644 --- a/client/src/Pages/Infrastructure/Details/Components/AreaChartBoxes/index.jsx +++ b/client/src/Pages/v1/Infrastructure/Details/Components/AreaChartBoxes/index.jsx @@ -1,7 +1,7 @@ // Components import { Stack } from "@mui/material"; -import InfraAreaChart from "./InfraAreaChart"; -import SkeletonLayout from "./skeleton"; +import InfraAreaChart from "./InfraAreaChart.jsx"; +import SkeletonLayout from "./skeleton.jsx"; // Utils import { @@ -9,9 +9,9 @@ import { TzTick, InfrastructureTooltip, TemperatureTooltip, -} from "../../../../../Components/Charts/Utils/chartUtils"; +} from "../../../../../../Components/Charts/Utils/chartUtils.jsx"; import { useTheme } from "@emotion/react"; -import { useHardwareUtils } from "../../Hooks/useHardwareUtils"; +import { useHardwareUtils } from "../../Hooks/useHardwareUtils.jsx"; import { useTranslation } from "react-i18next"; const AreaChartBoxes = ({ shouldRender, monitor, dateRange }) => { const theme = useTheme(); diff --git a/client/src/Pages/Infrastructure/Details/Components/AreaChartBoxes/skeleton.jsx b/client/src/Pages/v1/Infrastructure/Details/Components/AreaChartBoxes/skeleton.jsx similarity index 100% rename from client/src/Pages/Infrastructure/Details/Components/AreaChartBoxes/skeleton.jsx rename to client/src/Pages/v1/Infrastructure/Details/Components/AreaChartBoxes/skeleton.jsx diff --git a/client/src/Pages/Infrastructure/Details/Components/BaseContainer/index.jsx b/client/src/Pages/v1/Infrastructure/Details/Components/BaseContainer/index.jsx similarity index 94% rename from client/src/Pages/Infrastructure/Details/Components/BaseContainer/index.jsx rename to client/src/Pages/v1/Infrastructure/Details/Components/BaseContainer/index.jsx index 246769751..dd73d1a65 100644 --- a/client/src/Pages/Infrastructure/Details/Components/BaseContainer/index.jsx +++ b/client/src/Pages/v1/Infrastructure/Details/Components/BaseContainer/index.jsx @@ -11,7 +11,7 @@ import { Box } from "@mui/material"; // Utils import { useTheme } from "@emotion/react"; -import { useHardwareUtils } from "../../Hooks/useHardwareUtils"; +import { useHardwareUtils } from "../../Hooks/useHardwareUtils.jsx"; import PropTypes from "prop-types"; const BaseContainer = ({ children, sx = {} }) => { diff --git a/client/src/Pages/Infrastructure/Details/Components/GaugeBoxes/Gauge.jsx b/client/src/Pages/v1/Infrastructure/Details/Components/GaugeBoxes/Gauge.jsx similarity index 93% rename from client/src/Pages/Infrastructure/Details/Components/GaugeBoxes/Gauge.jsx rename to client/src/Pages/v1/Infrastructure/Details/Components/GaugeBoxes/Gauge.jsx index 70d36bd00..5c55119ad 100644 --- a/client/src/Pages/Infrastructure/Details/Components/GaugeBoxes/Gauge.jsx +++ b/client/src/Pages/v1/Infrastructure/Details/Components/GaugeBoxes/Gauge.jsx @@ -1,6 +1,6 @@ // Components -import CustomGauge from "../../../../../Components/Charts/CustomGauge"; -import BaseContainer from "../BaseContainer"; +import CustomGauge from "../../../../../../Components/Charts/CustomGauge/index.jsx"; +import BaseContainer from "../BaseContainer/index.jsx"; import { Stack, Typography, Box } from "@mui/material"; // Utils import { useTheme } from "@emotion/react"; diff --git a/client/src/Pages/Infrastructure/Details/Components/GaugeBoxes/index.jsx b/client/src/Pages/v1/Infrastructure/Details/Components/GaugeBoxes/index.jsx similarity index 93% rename from client/src/Pages/Infrastructure/Details/Components/GaugeBoxes/index.jsx rename to client/src/Pages/v1/Infrastructure/Details/Components/GaugeBoxes/index.jsx index 3c1c68f8b..432881e7e 100644 --- a/client/src/Pages/Infrastructure/Details/Components/GaugeBoxes/index.jsx +++ b/client/src/Pages/v1/Infrastructure/Details/Components/GaugeBoxes/index.jsx @@ -1,11 +1,11 @@ // Components import { Stack } from "@mui/material"; -import Gauge from "./Gauge"; -import SkeletonLayout from "./skeleton"; +import Gauge from "./Gauge.jsx"; +import SkeletonLayout from "./skeleton.jsx"; import PropTypes from "prop-types"; // Utils -import { useHardwareUtils } from "../../Hooks/useHardwareUtils"; +import { useHardwareUtils } from "../../Hooks/useHardwareUtils.jsx"; import { useTheme } from "@emotion/react"; import { useTranslation } from "react-i18next"; diff --git a/client/src/Pages/Infrastructure/Details/Components/GaugeBoxes/skeleton.jsx b/client/src/Pages/v1/Infrastructure/Details/Components/GaugeBoxes/skeleton.jsx similarity index 100% rename from client/src/Pages/Infrastructure/Details/Components/GaugeBoxes/skeleton.jsx rename to client/src/Pages/v1/Infrastructure/Details/Components/GaugeBoxes/skeleton.jsx diff --git a/client/src/Pages/Infrastructure/Details/Components/NetworkStats/NetworkCharts.jsx b/client/src/Pages/v1/Infrastructure/Details/Components/NetworkStats/NetworkCharts.jsx similarity index 93% rename from client/src/Pages/Infrastructure/Details/Components/NetworkStats/NetworkCharts.jsx rename to client/src/Pages/v1/Infrastructure/Details/Components/NetworkStats/NetworkCharts.jsx index 9157a3432..5cfdba350 100644 --- a/client/src/Pages/Infrastructure/Details/Components/NetworkStats/NetworkCharts.jsx +++ b/client/src/Pages/v1/Infrastructure/Details/Components/NetworkStats/NetworkCharts.jsx @@ -1,15 +1,15 @@ import PropTypes from "prop-types"; import { Stack, Typography } from "@mui/material"; -import InfraAreaChart from "../../../../../Pages/Infrastructure/Details/Components/AreaChartBoxes/InfraAreaChart"; +import InfraAreaChart from "../AreaChartBoxes/InfraAreaChart.jsx"; import { TzTick, InfrastructureTooltip, NetworkTick, -} from "../../../../../Components/Charts/Utils/chartUtils"; +} from "../../../../../../Components/Charts/Utils/chartUtils.jsx"; import { useTheme } from "@emotion/react"; import { useTranslation } from "react-i18next"; -import { useHardwareUtils } from "../../Hooks/useHardwareUtils"; +import { useHardwareUtils } from "../../Hooks/useHardwareUtils.jsx"; const NetworkCharts = ({ ethernetData, dateRange }) => { const theme = useTheme(); diff --git a/client/src/Pages/Infrastructure/Details/Components/NetworkStats/NetworkStatBoxes.jsx b/client/src/Pages/v1/Infrastructure/Details/Components/NetworkStats/NetworkStatBoxes.jsx similarity index 90% rename from client/src/Pages/Infrastructure/Details/Components/NetworkStats/NetworkStatBoxes.jsx rename to client/src/Pages/v1/Infrastructure/Details/Components/NetworkStats/NetworkStatBoxes.jsx index 98ba90dae..52ccf0805 100644 --- a/client/src/Pages/Infrastructure/Details/Components/NetworkStats/NetworkStatBoxes.jsx +++ b/client/src/Pages/v1/Infrastructure/Details/Components/NetworkStats/NetworkStatBoxes.jsx @@ -1,9 +1,9 @@ import PropTypes from "prop-types"; -import StatusBoxes from "../../../../../Components/StatusBoxes"; -import StatBox from "../../../../../Components/StatBox"; +import StatusBoxes from "../../../../../../Components/StatusBoxes/index.jsx"; +import StatBox from "../../../../../../Components/StatBox/index.jsx"; import { Typography } from "@mui/material"; import { useTranslation } from "react-i18next"; -import { useHardwareUtils } from "../../Hooks/useHardwareUtils"; +import { useHardwareUtils } from "../../Hooks/useHardwareUtils.jsx"; function formatNumber(num) { return num != null ? num.toLocaleString() : "0"; diff --git a/client/src/Pages/Infrastructure/Details/Components/NetworkStats/index.jsx b/client/src/Pages/v1/Infrastructure/Details/Components/NetworkStats/index.jsx similarity index 90% rename from client/src/Pages/Infrastructure/Details/Components/NetworkStats/index.jsx rename to client/src/Pages/v1/Infrastructure/Details/Components/NetworkStats/index.jsx index 41d2b0a87..e0d6986e1 100644 --- a/client/src/Pages/Infrastructure/Details/Components/NetworkStats/index.jsx +++ b/client/src/Pages/v1/Infrastructure/Details/Components/NetworkStats/index.jsx @@ -3,10 +3,10 @@ import { useState, useEffect } from "react"; import { Box } from "@mui/material"; import { useTranslation } from "react-i18next"; import { useTheme } from "@emotion/react"; -import Select from "../../../../../Components/Inputs/Select"; -import NetworkStatBoxes from "./NetworkStatBoxes"; -import NetworkCharts from "./NetworkCharts"; -import MonitorTimeFrameHeader from "../../../../../Components/MonitorTimeFrameHeader"; +import Select from "../../../../../../Components/Inputs/Select/index.jsx"; +import NetworkStatBoxes from "./NetworkStatBoxes.jsx"; +import NetworkCharts from "./NetworkCharts.jsx"; +import MonitorTimeFrameHeader from "../../../../../../Components/MonitorTimeFrameHeader/index.jsx"; const getAvailableInterfaces = (net) => { return (net || []).map((iface) => iface.name).filter(Boolean); diff --git a/client/src/Pages/Infrastructure/Details/Components/NetworkStats/skeleton.jsx b/client/src/Pages/v1/Infrastructure/Details/Components/NetworkStats/skeleton.jsx similarity index 100% rename from client/src/Pages/Infrastructure/Details/Components/NetworkStats/skeleton.jsx rename to client/src/Pages/v1/Infrastructure/Details/Components/NetworkStats/skeleton.jsx diff --git a/client/src/Pages/Infrastructure/Details/Components/StatusBoxes/index.jsx b/client/src/Pages/v1/Infrastructure/Details/Components/StatusBoxes/index.jsx similarity index 90% rename from client/src/Pages/Infrastructure/Details/Components/StatusBoxes/index.jsx rename to client/src/Pages/v1/Infrastructure/Details/Components/StatusBoxes/index.jsx index 6c11e7c82..47862449e 100644 --- a/client/src/Pages/Infrastructure/Details/Components/StatusBoxes/index.jsx +++ b/client/src/Pages/v1/Infrastructure/Details/Components/StatusBoxes/index.jsx @@ -1,11 +1,11 @@ // Components import { Stack, Typography } from "@mui/material"; -import StatusBoxes from "../../../../../Components/StatusBoxes"; -import StatBox from "../../../../../Components/StatBox"; +import StatusBoxes from "../../../../../../Components/StatusBoxes/index.jsx"; +import StatBox from "../../../../../../Components/StatBox/index.jsx"; //Utils -import { useMonitorUtils } from "../../../../../Hooks/useMonitorUtils"; -import { useHardwareUtils } from "../../Hooks/useHardwareUtils"; +import { useMonitorUtils } from "../../../../../../Hooks/v1/useMonitorUtils.js"; +import { useHardwareUtils } from "../../Hooks/useHardwareUtils.jsx"; import { useTranslation } from "react-i18next"; const InfraStatBoxes = ({ shouldRender, monitor }) => { diff --git a/client/src/Pages/Infrastructure/Details/Hooks/useHardwareUtils.jsx b/client/src/Pages/v1/Infrastructure/Details/Hooks/useHardwareUtils.jsx similarity index 100% rename from client/src/Pages/Infrastructure/Details/Hooks/useHardwareUtils.jsx rename to client/src/Pages/v1/Infrastructure/Details/Hooks/useHardwareUtils.jsx diff --git a/client/src/Pages/Infrastructure/Details/index.jsx b/client/src/Pages/v1/Infrastructure/Details/index.jsx similarity index 79% rename from client/src/Pages/Infrastructure/Details/index.jsx rename to client/src/Pages/v1/Infrastructure/Details/index.jsx index a16f3906b..04691d985 100644 --- a/client/src/Pages/Infrastructure/Details/index.jsx +++ b/client/src/Pages/v1/Infrastructure/Details/index.jsx @@ -1,20 +1,20 @@ // Components import { Stack, Typography, Tab } from "@mui/material"; -import Breadcrumbs from "../../../Components/Breadcrumbs"; -import MonitorDetailsControlHeader from "../../../Components/MonitorDetailsControlHeader"; -import MonitorTimeFrameHeader from "../../../Components/MonitorTimeFrameHeader"; -import StatusBoxes from "./Components/StatusBoxes"; -import GaugeBoxes from "./Components/GaugeBoxes"; -import AreaChartBoxes from "./Components/AreaChartBoxes"; -import GenericFallback from "../../../Components/GenericFallback"; -import NetworkStats from "./Components/NetworkStats"; -import CustomTabList from "../../../Components/Tab"; +import Breadcrumbs from "../../../../Components/Breadcrumbs/index.jsx"; +import MonitorDetailsControlHeader from "../../../../Components/MonitorDetailsControlHeader/index.jsx"; +import MonitorTimeFrameHeader from "../../../../Components/MonitorTimeFrameHeader/index.jsx"; +import StatusBoxes from "./Components/StatusBoxes/index.jsx"; +import GaugeBoxes from "./Components/GaugeBoxes/index.jsx"; +import AreaChartBoxes from "./Components/AreaChartBoxes/index.jsx"; +import GenericFallback from "../../../../Components/GenericFallback/index.jsx"; +import NetworkStats from "./Components/NetworkStats/index.jsx"; +import CustomTabList from "../../../../Components/Tab/index.jsx"; import TabContext from "@mui/lab/TabContext"; // Utils import { useTheme } from "@emotion/react"; -import { useIsAdmin } from "../../../Hooks/useIsAdmin"; -import { useFetchHardwareMonitorById } from "../../../Hooks/monitorHooks"; +import { useIsAdmin } from "../../../../Hooks/v1/useIsAdmin.js"; +import { useFetchHardwareMonitorById } from "../../../../Hooks/v1/monitorHooks.js"; import { useState } from "react"; import { useTranslation } from "react-i18next"; import { useParams } from "react-router-dom"; diff --git a/client/src/Pages/Infrastructure/Monitors/Components/Filters/index.jsx b/client/src/Pages/v1/Infrastructure/Monitors/Components/Filters/index.jsx similarity index 96% rename from client/src/Pages/Infrastructure/Monitors/Components/Filters/index.jsx rename to client/src/Pages/v1/Infrastructure/Monitors/Components/Filters/index.jsx index 3c2511c2f..bef632e88 100644 --- a/client/src/Pages/Infrastructure/Monitors/Components/Filters/index.jsx +++ b/client/src/Pages/v1/Infrastructure/Monitors/Components/Filters/index.jsx @@ -1,6 +1,6 @@ import { useTheme } from "@emotion/react"; import PropTypes from "prop-types"; -import FilterHeader from "../../../../../Components/FilterHeader"; +import FilterHeader from "../../../../../../Components/FilterHeader/index.jsx"; import { useMemo } from "react"; import { Box, Button } from "@mui/material"; import ClearIcon from "@mui/icons-material/Clear"; diff --git a/client/src/Pages/Infrastructure/Monitors/Components/MonitorsTable/index.jsx b/client/src/Pages/v1/Infrastructure/Monitors/Components/MonitorsTable/index.jsx similarity index 86% rename from client/src/Pages/Infrastructure/Monitors/Components/MonitorsTable/index.jsx rename to client/src/Pages/v1/Infrastructure/Monitors/Components/MonitorsTable/index.jsx index 7ee603ef7..801a9bc6a 100644 --- a/client/src/Pages/Infrastructure/Monitors/Components/MonitorsTable/index.jsx +++ b/client/src/Pages/v1/Infrastructure/Monitors/Components/MonitorsTable/index.jsx @@ -1,18 +1,18 @@ // Components import { Box } from "@mui/material"; -import DataTable from "../../../../../Components/Table"; -import Host from "../../../../../Components/Host"; -import { StatusLabel } from "../../../../../Components/Label"; +import DataTable from "../../../../../../Components/Table/index.jsx"; +import Host from "../../../../../../Components/Host/index.jsx"; +import { StatusLabel } from "../../../../../../Components/Label/index.jsx"; import { Stack } from "@mui/material"; -import { InfrastructureMenu } from "../MonitorsTableMenu"; -import LoadingSpinner from "../../../../Uptime/Monitors/Components/LoadingSpinner"; +import { InfrastructureMenu } from "../MonitorsTableMenu/index.jsx"; +import LoadingSpinner from "../../../../Uptime/Monitors/Components/LoadingSpinner/index.jsx"; // Assets -import CPUChipIcon from "../../../../../assets/icons/cpu-chip.svg?react"; -import CustomGauge from "../../../../../Components/Charts/CustomGauge"; +import CPUChipIcon from "../../../../../../assets/icons/cpu-chip.svg?react"; +import CustomGauge from "../../../../../../Components/Charts/CustomGauge/index.jsx"; // Utils import { useTheme } from "@emotion/react"; -import { useMonitorUtils } from "../../../../../Hooks/useMonitorUtils"; +import { useMonitorUtils } from "../../../../../../Hooks/v1/useMonitorUtils.js"; import { useNavigate } from "react-router-dom"; import PropTypes from "prop-types"; import { useTranslation } from "react-i18next"; diff --git a/client/src/Pages/Infrastructure/Monitors/Components/MonitorsTableMenu/index.jsx b/client/src/Pages/v1/Infrastructure/Monitors/Components/MonitorsTableMenu/index.jsx similarity index 93% rename from client/src/Pages/Infrastructure/Monitors/Components/MonitorsTableMenu/index.jsx rename to client/src/Pages/v1/Infrastructure/Monitors/Components/MonitorsTableMenu/index.jsx index 147f1c297..c2270c691 100644 --- a/client/src/Pages/Infrastructure/Monitors/Components/MonitorsTableMenu/index.jsx +++ b/client/src/Pages/v1/Infrastructure/Monitors/Components/MonitorsTableMenu/index.jsx @@ -3,13 +3,13 @@ import { useRef, useState } from "react"; import { useTheme } from "@emotion/react"; import { useNavigate } from "react-router-dom"; -import { createToast } from "../../../../../Utils/toastUtils"; +import { createToast } from "../../../../../../Utils/toastUtils.jsx"; import { IconButton, Menu, MenuItem } from "@mui/material"; -import Settings from "../../../../../assets/icons/settings-bold.svg?react"; +import Settings from "../../../../../../assets/icons/settings-bold.svg?react"; import PropTypes from "prop-types"; -import Dialog from "../../../../../Components/Dialog"; -import { networkService } from "../../../../../Utils/NetworkService.js"; -import { usePauseMonitor } from "../../../../../Hooks/monitorHooks"; +import Dialog from "../../../../../../Components/Dialog/index.jsx"; +import { networkService } from "../../../../../../Utils/NetworkService.js"; +import { usePauseMonitor } from "../../../../../../Hooks/v1/monitorHooks.js"; import { useTranslation } from "react-i18next"; /** diff --git a/client/src/Pages/Infrastructure/Monitors/index.jsx b/client/src/Pages/v1/Infrastructure/Monitors/index.jsx similarity index 80% rename from client/src/Pages/Infrastructure/Monitors/index.jsx rename to client/src/Pages/v1/Infrastructure/Monitors/index.jsx index bfd2270fe..7fbfa1167 100644 --- a/client/src/Pages/Infrastructure/Monitors/index.jsx +++ b/client/src/Pages/v1/Infrastructure/Monitors/index.jsx @@ -1,21 +1,21 @@ // Components import { Stack } from "@mui/material"; -import Breadcrumbs from "../../../Components/Breadcrumbs"; -import MonitorCountHeader from "../../../Components/MonitorCountHeader"; -import MonitorCreateHeader from "../../../Components/MonitorCreateHeader"; -import MonitorsTable from "./Components/MonitorsTable"; -import Pagination from "../../..//Components/Table/TablePagination"; -import PageStateWrapper from "../../../Components/PageStateWrapper"; -import Filter from "./Components/Filters"; -import SearchComponent from "../../Uptime/Monitors/Components/SearchComponent"; +import Breadcrumbs from "../../../../Components/Breadcrumbs/index.jsx"; +import MonitorCountHeader from "../../../../Components/MonitorCountHeader/index.jsx"; +import MonitorCreateHeader from "../../../../Components/MonitorCreateHeader/index.jsx"; +import MonitorsTable from "./Components/MonitorsTable/index.jsx"; +import Pagination from "../../../../Components/Table/TablePagination/index.jsx"; +import PageStateWrapper from "../../../../Components/PageStateWrapper/index.jsx"; +import Filter from "./Components/Filters/index.jsx"; +import SearchComponent from "../../Uptime/Monitors/Components/SearchComponent/index.jsx"; // Utils import { useTheme } from "@emotion/react"; import { useEffect, useState } from "react"; -import { useIsAdmin } from "../../../Hooks/useIsAdmin"; +import { useIsAdmin } from "../../../../Hooks/v1/useIsAdmin.js"; import { useTranslation } from "react-i18next"; -import { useFetchMonitorsByTeamId } from "../../../Hooks/monitorHooks"; +import { useFetchMonitorsByTeamId } from "../../../../Hooks/v1/monitorHooks.js"; import { useDispatch, useSelector } from "react-redux"; -import { setRowsPerPage } from "../../../Features/UI/uiSlice"; +import { setRowsPerPage } from "../../../../Features/UI/uiSlice.js"; // Constants const TYPES = ["hardware"]; const BREADCRUMBS = [{ name: `infrastructure`, path: "/infrastructure" }]; diff --git a/client/src/Pages/Integrations/index.css b/client/src/Pages/v1/Integrations/index.css similarity index 100% rename from client/src/Pages/Integrations/index.css rename to client/src/Pages/v1/Integrations/index.css diff --git a/client/src/Pages/Integrations/index.jsx b/client/src/Pages/v1/Integrations/index.jsx similarity index 94% rename from client/src/Pages/Integrations/index.jsx rename to client/src/Pages/v1/Integrations/index.jsx index fe0faa5cf..dde6b52ee 100644 --- a/client/src/Pages/Integrations/index.jsx +++ b/client/src/Pages/v1/Integrations/index.jsx @@ -1,9 +1,9 @@ import PropTypes from "prop-types"; import { Stack, Typography, Grid, Button } from "@mui/material"; import { useTheme } from "@emotion/react"; -import Discord from "../../assets/icons/discord-icon.svg?react"; -import Slack from "../../assets/icons/slack-icon.svg?react"; -import Zapier from "../../assets/icons/zapier-icon.svg?react"; +import Discord from "../../../assets/icons/discord-icon.svg?react"; +import Slack from "../../../assets/icons/slack-icon.svg?react"; +import Zapier from "../../../assets/icons/zapier-icon.svg?react"; import { useTranslation } from "react-i18next"; import "./index.css"; diff --git a/client/src/Pages/Logs/Diagnostics/components/gauges/index.jsx b/client/src/Pages/v1/Logs/Diagnostics/components/gauges/index.jsx similarity index 96% rename from client/src/Pages/Logs/Diagnostics/components/gauges/index.jsx rename to client/src/Pages/v1/Logs/Diagnostics/components/gauges/index.jsx index 5bb227b7c..082be2587 100644 --- a/client/src/Pages/Logs/Diagnostics/components/gauges/index.jsx +++ b/client/src/Pages/v1/Logs/Diagnostics/components/gauges/index.jsx @@ -1,11 +1,11 @@ import Stack from "@mui/material/Stack"; -import CustomGauge from "../../../../../Components/Charts/CustomGauge"; +import CustomGauge from "../../../../../../Components/Charts/CustomGauge/index.jsx"; import Typography from "@mui/material/Typography"; // Utils import { useTheme } from "@emotion/react"; import PropTypes from "prop-types"; -import { getPercentage, formatBytes } from "../../utils/utils"; +import { getPercentage, formatBytes } from "../../utils/utils.js"; import { useTranslation } from "react-i18next"; import { Box } from "@mui/material"; diff --git a/client/src/Pages/Logs/Diagnostics/components/stats/index.jsx b/client/src/Pages/v1/Logs/Diagnostics/components/stats/index.jsx similarity index 94% rename from client/src/Pages/Logs/Diagnostics/components/stats/index.jsx rename to client/src/Pages/v1/Logs/Diagnostics/components/stats/index.jsx index 103d1c698..b99f34506 100644 --- a/client/src/Pages/Logs/Diagnostics/components/stats/index.jsx +++ b/client/src/Pages/v1/Logs/Diagnostics/components/stats/index.jsx @@ -7,8 +7,8 @@ import CircularProgress from "@mui/material/CircularProgress"; import { useTheme } from "@emotion/react"; import PropTypes from "prop-types"; -import { getHumanReadableDuration } from "../../../../../Utils/timeUtils"; -import { formatBytes } from "../../utils/utils"; +import { getHumanReadableDuration } from "../../../../../../Utils/timeUtils.js"; +import { formatBytes } from "../../utils/utils.js"; import { useTranslation } from "react-i18next"; const StatsCard = ({ title, value, unit = "", isLoading }) => { diff --git a/client/src/Pages/Logs/Diagnostics/index.jsx b/client/src/Pages/v1/Logs/Diagnostics/index.jsx similarity index 82% rename from client/src/Pages/Logs/Diagnostics/index.jsx rename to client/src/Pages/v1/Logs/Diagnostics/index.jsx index 72ee47f48..d7a1a8a7c 100644 --- a/client/src/Pages/Logs/Diagnostics/index.jsx +++ b/client/src/Pages/v1/Logs/Diagnostics/index.jsx @@ -1,14 +1,14 @@ import Stack from "@mui/material/Stack"; import Box from "@mui/material/Box"; -import Gauges from "./components/gauges"; +import Gauges from "./components/gauges/index.jsx"; import Button from "@mui/material/Button"; -import StatBox from "../../../Components/StatBox"; -import StatusBoxes from "../../../Components/StatusBoxes"; +import StatBox from "../../../../Components/StatBox/index.jsx"; +import StatusBoxes from "../../../../Components/StatusBoxes/index.jsx"; import { useTheme } from "@emotion/react"; import { useTranslation } from "react-i18next"; -import { useFetchDiagnostics } from "../../../Hooks/logHooks"; -import { getHumanReadableDuration } from "../../../Utils/timeUtils"; -import { formatBytes, getPercentage } from "./utils/utils"; +import { useFetchDiagnostics } from "../../../../Hooks/v1/logHooks.js"; +import { getHumanReadableDuration } from "../../../../Utils/timeUtils.js"; +import { formatBytes, getPercentage } from "./utils/utils.js"; const Diagnostics = () => { // Local state diff --git a/client/src/Pages/Logs/Diagnostics/utils/utils.js b/client/src/Pages/v1/Logs/Diagnostics/utils/utils.js similarity index 100% rename from client/src/Pages/Logs/Diagnostics/utils/utils.js rename to client/src/Pages/v1/Logs/Diagnostics/utils/utils.js diff --git a/client/src/Pages/Logs/Logs/index.jsx b/client/src/Pages/v1/Logs/Logs/index.jsx similarity index 94% rename from client/src/Pages/Logs/Logs/index.jsx rename to client/src/Pages/v1/Logs/Logs/index.jsx index b44b3fd1a..5c7ad9fa9 100644 --- a/client/src/Pages/Logs/Logs/index.jsx +++ b/client/src/Pages/v1/Logs/Logs/index.jsx @@ -1,10 +1,10 @@ import Stack from "@mui/material/Stack"; import Box from "@mui/material/Box"; -import Select from "../../../Components/Inputs/Select"; +import Select from "../../../../Components/Inputs/Select/index.jsx"; import Typography from "@mui/material/Typography"; import Divider from "@mui/material/Divider"; -import { useFetchLogs } from "../../../Hooks/logHooks"; +import { useFetchLogs } from "../../../../Hooks/v1/logHooks.js"; import { useTheme } from "@emotion/react"; import { useState } from "react"; import { useTranslation } from "react-i18next"; diff --git a/client/src/Pages/Logs/Queue/components/FailedJobTable/index.jsx b/client/src/Pages/v1/Logs/Queue/components/FailedJobTable/index.jsx similarity index 92% rename from client/src/Pages/Logs/Queue/components/FailedJobTable/index.jsx rename to client/src/Pages/v1/Logs/Queue/components/FailedJobTable/index.jsx index 94fc2f5c3..46be9520c 100644 --- a/client/src/Pages/Logs/Queue/components/FailedJobTable/index.jsx +++ b/client/src/Pages/v1/Logs/Queue/components/FailedJobTable/index.jsx @@ -1,10 +1,10 @@ import Stack from "@mui/material/Stack"; import Typography from "@mui/material/Typography"; -import DataTable from "../../../../../Components/Table"; +import DataTable from "../../../../../../Components/Table/index.jsx"; import { useNavigate } from "react-router-dom"; import { useTheme } from "@emotion/react"; -import { TypeToPathMap } from "../../../../../Utils/monitorUtils"; +import { TypeToPathMap } from "../../../../../../Utils/monitorUtils.js"; import PropTypes from "prop-types"; import { useTranslation } from "react-i18next"; diff --git a/client/src/Pages/Logs/Queue/components/JobTable/index.jsx b/client/src/Pages/v1/Logs/Queue/components/JobTable/index.jsx similarity index 92% rename from client/src/Pages/Logs/Queue/components/JobTable/index.jsx rename to client/src/Pages/v1/Logs/Queue/components/JobTable/index.jsx index fab85ec5d..4ac4069bd 100644 --- a/client/src/Pages/Logs/Queue/components/JobTable/index.jsx +++ b/client/src/Pages/v1/Logs/Queue/components/JobTable/index.jsx @@ -1,13 +1,13 @@ import Stack from "@mui/material/Stack"; -import DataTable from "../../../../../Components/Table"; +import DataTable from "../../../../../../Components/Table/index.jsx"; import Typography from "@mui/material/Typography"; // Utils import PropTypes from "prop-types"; import { useTheme } from "@emotion/react"; import { useNavigate } from "react-router-dom"; -import { TypeToPathMap } from "../../../../../Utils/monitorUtils"; +import { TypeToPathMap } from "../../../../../../Utils/monitorUtils.js"; import { useTranslation } from "react-i18next"; -import { createHeaderFactory } from "../../../../../Components/Table/TableUtils"; +import { createHeaderFactory } from "../../../../../../Components/Table/TableUtils.js"; const JobTable = ({ jobs = [] }) => { const theme = useTheme(); diff --git a/client/src/Pages/Logs/Queue/components/Metrics/index.jsx b/client/src/Pages/v1/Logs/Queue/components/Metrics/index.jsx similarity index 84% rename from client/src/Pages/Logs/Queue/components/Metrics/index.jsx rename to client/src/Pages/v1/Logs/Queue/components/Metrics/index.jsx index b0112fa99..dd9fdcc6a 100644 --- a/client/src/Pages/Logs/Queue/components/Metrics/index.jsx +++ b/client/src/Pages/v1/Logs/Queue/components/Metrics/index.jsx @@ -1,6 +1,6 @@ import Stack from "@mui/material/Stack"; -import StatBox from "../../../../../Components/StatBox"; -import StatusBoxes from "../../../../../Components/StatusBoxes"; +import StatBox from "../../../../../../Components/StatBox/index.jsx"; +import StatusBoxes from "../../../../../../Components/StatusBoxes/index.jsx"; import { useTranslation } from "react-i18next"; import { useTheme } from "@emotion/react"; diff --git a/client/src/Pages/Logs/Queue/components/MetricsTable/index.jsx b/client/src/Pages/v1/Logs/Queue/components/MetricsTable/index.jsx similarity index 94% rename from client/src/Pages/Logs/Queue/components/MetricsTable/index.jsx rename to client/src/Pages/v1/Logs/Queue/components/MetricsTable/index.jsx index 204b90035..58f13fd5d 100644 --- a/client/src/Pages/Logs/Queue/components/MetricsTable/index.jsx +++ b/client/src/Pages/v1/Logs/Queue/components/MetricsTable/index.jsx @@ -1,6 +1,6 @@ import Stack from "@mui/material/Stack"; import Typography from "@mui/material/Typography"; -import DataTable from "../../../../../Components/Table"; +import DataTable from "../../../../../../Components/Table/index.jsx"; // Utils import { useTranslation } from "react-i18next"; diff --git a/client/src/Pages/Logs/Queue/index.jsx b/client/src/Pages/v1/Logs/Queue/index.jsx similarity index 86% rename from client/src/Pages/Logs/Queue/index.jsx rename to client/src/Pages/v1/Logs/Queue/index.jsx index fde9073fc..10eca4d3b 100644 --- a/client/src/Pages/Logs/Queue/index.jsx +++ b/client/src/Pages/v1/Logs/Queue/index.jsx @@ -1,8 +1,8 @@ // Components import Stack from "@mui/material/Stack"; -import JobTable from "./components/JobTable"; -import Metrics from "./components/Metrics"; -import FailedJobTable from "./components/FailedJobTable"; +import JobTable from "./components/JobTable/index.jsx"; +import Metrics from "./components/Metrics/index.jsx"; +import FailedJobTable from "./components/FailedJobTable/index.jsx"; import ButtonGroup from "@mui/material/ButtonGroup"; import Button from "@mui/material/Button"; import Typography from "@mui/material/Typography"; @@ -10,7 +10,7 @@ import Divider from "@mui/material/Divider"; // Utils import { useState } from "react"; -import { useFetchQueueData, useFlushQueue } from "../../../Hooks/logHooks"; +import { useFetchQueueData, useFlushQueue } from "../../../../Hooks/v1/logHooks.js"; import { useTranslation } from "react-i18next"; import { useTheme } from "@emotion/react"; diff --git a/client/src/Pages/Logs/index.jsx b/client/src/Pages/v1/Logs/index.jsx similarity index 84% rename from client/src/Pages/Logs/index.jsx rename to client/src/Pages/v1/Logs/index.jsx index 79c89f282..c6dfe4d99 100644 --- a/client/src/Pages/Logs/index.jsx +++ b/client/src/Pages/v1/Logs/index.jsx @@ -1,10 +1,10 @@ import Stack from "@mui/material/Stack"; -import Breadcrumbs from "../../Components/Breadcrumbs"; +import Breadcrumbs from "../../../Components/Breadcrumbs/index.jsx"; import Tabs from "@mui/material/Tabs"; import Tab from "@mui/material/Tab"; -import Queue from "./Queue"; -import LogsComponent from "./Logs"; -import Diagnostics from "./Diagnostics"; +import Queue from "./Queue/index.jsx"; +import LogsComponent from "./Logs/index.jsx"; +import Diagnostics from "./Diagnostics/index.jsx"; import { useTheme } from "@emotion/react"; import { useTranslation } from "react-i18next"; diff --git a/client/src/Pages/Maintenance/CreateMaintenance/Components/ConfigSelect/index.jsx b/client/src/Pages/v1/Maintenance/CreateMaintenance/Components/ConfigSelect/index.jsx similarity index 92% rename from client/src/Pages/Maintenance/CreateMaintenance/Components/ConfigSelect/index.jsx rename to client/src/Pages/v1/Maintenance/CreateMaintenance/Components/ConfigSelect/index.jsx index 1627ef3d5..b43bcbfc7 100644 --- a/client/src/Pages/Maintenance/CreateMaintenance/Components/ConfigSelect/index.jsx +++ b/client/src/Pages/v1/Maintenance/CreateMaintenance/Components/ConfigSelect/index.jsx @@ -1,4 +1,4 @@ -import Select from "../../../../../Components/Inputs/Select"; +import Select from "../../../../../../Components/Inputs/Select/index.jsx"; import PropTypes from "prop-types"; const ConfigSelect = ({ configSelection, valueSelect, onChange, ...props }) => { const getValueById = (config, id) => { diff --git a/client/src/Pages/Maintenance/CreateMaintenance/Components/MonitorList/index.jsx b/client/src/Pages/v1/Maintenance/CreateMaintenance/Components/MonitorList/index.jsx similarity index 100% rename from client/src/Pages/Maintenance/CreateMaintenance/Components/MonitorList/index.jsx rename to client/src/Pages/v1/Maintenance/CreateMaintenance/Components/MonitorList/index.jsx diff --git a/client/src/Pages/Maintenance/CreateMaintenance/Components/MonitorsConfig/index.jsx b/client/src/Pages/v1/Maintenance/CreateMaintenance/Components/MonitorsConfig/index.jsx similarity index 93% rename from client/src/Pages/Maintenance/CreateMaintenance/Components/MonitorsConfig/index.jsx rename to client/src/Pages/v1/Maintenance/CreateMaintenance/Components/MonitorsConfig/index.jsx index e792734ff..91e6a1531 100644 --- a/client/src/Pages/Maintenance/CreateMaintenance/Components/MonitorsConfig/index.jsx +++ b/client/src/Pages/v1/Maintenance/CreateMaintenance/Components/MonitorsConfig/index.jsx @@ -1,7 +1,7 @@ import PropTypes from "prop-types"; -import Search from "../../../../../Components/Inputs/Search"; +import Search from "../../../../../../Components/Inputs/Search/index.jsx"; import { useState } from "react"; -import ConfigRow from "../../../../../Components/ConfigRow"; +import ConfigRow from "../../../../../../Components/ConfigRow/index.jsx"; /** * MonitorsConfig is a component that allows users to select and apply a maintenance window to a list of monitors. diff --git a/client/src/Pages/Maintenance/CreateMaintenance/hooks/useMaintenanceActions.jsx b/client/src/Pages/v1/Maintenance/CreateMaintenance/hooks/useMaintenanceActions.jsx similarity index 93% rename from client/src/Pages/Maintenance/CreateMaintenance/hooks/useMaintenanceActions.jsx rename to client/src/Pages/v1/Maintenance/CreateMaintenance/hooks/useMaintenanceActions.jsx index 84333a14f..a75cbe206 100644 --- a/client/src/Pages/Maintenance/CreateMaintenance/hooks/useMaintenanceActions.jsx +++ b/client/src/Pages/v1/Maintenance/CreateMaintenance/hooks/useMaintenanceActions.jsx @@ -1,4 +1,4 @@ -import { networkService } from "../../../../main"; +import { networkService } from "../../../../../main.jsx"; import dayjs from "dayjs"; import { MS_PER_SECOND, @@ -6,7 +6,7 @@ import { MS_PER_HOUR, MS_PER_DAY, MS_PER_WEEK, -} from "../../../../Utils/timeUtils"; +} from "../../../../../Utils/timeUtils.js"; const useMaintenanceActions = () => { const MS_LOOKUP = { diff --git a/client/src/Pages/Maintenance/CreateMaintenance/hooks/useMaintenanceData.jsx b/client/src/Pages/v1/Maintenance/CreateMaintenance/hooks/useMaintenanceData.jsx similarity index 95% rename from client/src/Pages/Maintenance/CreateMaintenance/hooks/useMaintenanceData.jsx rename to client/src/Pages/v1/Maintenance/CreateMaintenance/hooks/useMaintenanceData.jsx index 8ba9b5e80..d21906e41 100644 --- a/client/src/Pages/Maintenance/CreateMaintenance/hooks/useMaintenanceData.jsx +++ b/client/src/Pages/v1/Maintenance/CreateMaintenance/hooks/useMaintenanceData.jsx @@ -1,4 +1,4 @@ -import { networkService } from "../../../../main"; +import { networkService } from "../../../../../main.jsx"; import dayjs from "dayjs"; import { MS_PER_SECOND, @@ -6,7 +6,7 @@ import { MS_PER_HOUR, MS_PER_DAY, MS_PER_WEEK, -} from "../../../../Utils/timeUtils"; +} from "../../../../../Utils/timeUtils.js"; const useMaintenanceData = () => { const REVERSE_REPEAT_LOOKUP = { 0: "none", diff --git a/client/src/Pages/Maintenance/CreateMaintenance/index.css b/client/src/Pages/v1/Maintenance/CreateMaintenance/index.css similarity index 100% rename from client/src/Pages/Maintenance/CreateMaintenance/index.css rename to client/src/Pages/v1/Maintenance/CreateMaintenance/index.css diff --git a/client/src/Pages/Maintenance/CreateMaintenance/index.jsx b/client/src/Pages/v1/Maintenance/CreateMaintenance/index.jsx similarity index 93% rename from client/src/Pages/Maintenance/CreateMaintenance/index.jsx rename to client/src/Pages/v1/Maintenance/CreateMaintenance/index.jsx index 9dbf9d4aa..da3e72d8a 100644 --- a/client/src/Pages/Maintenance/CreateMaintenance/index.jsx +++ b/client/src/Pages/v1/Maintenance/CreateMaintenance/index.jsx @@ -2,27 +2,27 @@ import { Box, Button, Stack, Typography } from "@mui/material"; import { useSelector } from "react-redux"; import { useTheme } from "@emotion/react"; import { useEffect, useState } from "react"; -import ConfigBox from "../../../Components/ConfigBox"; +import ConfigBox from "../../../../Components/ConfigBox/index.jsx"; import { AdapterDayjs } from "@mui/x-date-pickers/AdapterDayjs"; import { LocalizationProvider } from "@mui/x-date-pickers"; import { DatePicker } from "@mui/x-date-pickers/DatePicker"; import { MobileTimePicker } from "@mui/x-date-pickers/MobileTimePicker"; -import { maintenanceWindowValidation } from "../../../Validation/validation"; -import { createToast } from "../../../Utils/toastUtils"; -import MonitorList from "./Components/MonitorList"; -import ConfigSelect from "./Components/ConfigSelect"; -import useMaintenanceData from "./hooks/useMaintenanceData"; -import useMaintenanceActions from "./hooks/useMaintenanceActions"; +import { maintenanceWindowValidation } from "../../../../Validation/validation.js"; +import { createToast } from "../../../../Utils/toastUtils.jsx"; +import MonitorList from "./Components/MonitorList/index.jsx"; +import ConfigSelect from "./Components/ConfigSelect/index.jsx"; +import useMaintenanceData from "./hooks/useMaintenanceData.jsx"; +import useMaintenanceActions from "./hooks/useMaintenanceActions.jsx"; -import TextInput from "../../../Components/Inputs/TextInput"; -import Breadcrumbs from "../../../Components/Breadcrumbs"; -import CalendarIcon from "../../../assets/icons/calendar.svg?react"; +import TextInput from "../../../../Components/Inputs/TextInput/index.jsx"; +import Breadcrumbs from "../../../../Components/Breadcrumbs/index.jsx"; +import CalendarIcon from "../../../../assets/icons/calendar.svg?react"; import "./index.css"; -import Search from "../../../Components/Inputs/Search"; -import { logger } from "../../../Utils/Logger"; +import Search from "../../../../Components/Inputs/Search/index.jsx"; +import { logger } from "../../../../Utils/Logger.js"; import { useNavigate, useParams } from "react-router-dom"; -import { buildErrors, hasValidationErrors } from "../../../Validation/error"; +import { buildErrors, hasValidationErrors } from "../../../../Validation/error.js"; import { useTranslation } from "react-i18next"; import dayjs from "dayjs"; diff --git a/client/src/Pages/Maintenance/MaintenanceTable/ActionsMenu/index.jsx b/client/src/Pages/v1/Maintenance/MaintenanceTable/ActionsMenu/index.jsx similarity index 91% rename from client/src/Pages/Maintenance/MaintenanceTable/ActionsMenu/index.jsx rename to client/src/Pages/v1/Maintenance/MaintenanceTable/ActionsMenu/index.jsx index aa1c48433..cc51697f7 100644 --- a/client/src/Pages/Maintenance/MaintenanceTable/ActionsMenu/index.jsx +++ b/client/src/Pages/v1/Maintenance/MaintenanceTable/ActionsMenu/index.jsx @@ -3,14 +3,14 @@ import { useTheme } from "@emotion/react"; import { useNavigate } from "react-router-dom"; import { useSelector } from "react-redux"; import { IconButton, Menu, MenuItem } from "@mui/material"; -import { logger } from "../../../../Utils/Logger"; -import Settings from "../../../../assets/icons/settings-bold.svg?react"; +import { logger } from "../../../../../Utils/Logger.js"; +import Settings from "../../../../../assets/icons/settings-bold.svg?react"; import PropTypes from "prop-types"; -import { networkService } from "../../../../main"; -import { createToast } from "../../../../Utils/toastUtils"; +import { networkService } from "../../../../../main.jsx"; +import { createToast } from "../../../../../Utils/toastUtils.jsx"; import { useTranslation } from "react-i18next"; -import Dialog from "../../../../Components/Dialog"; +import Dialog from "../../../../../Components/Dialog/index.jsx"; const ActionsMenu = ({ /* isAdmin, */ maintenanceWindow, updateCallback }) => { maintenanceWindow; diff --git a/client/src/Pages/Maintenance/MaintenanceTable/index.jsx b/client/src/Pages/v1/Maintenance/MaintenanceTable/index.jsx similarity index 92% rename from client/src/Pages/Maintenance/MaintenanceTable/index.jsx rename to client/src/Pages/v1/Maintenance/MaintenanceTable/index.jsx index bb5842e9f..fb18cd380 100644 --- a/client/src/Pages/Maintenance/MaintenanceTable/index.jsx +++ b/client/src/Pages/v1/Maintenance/MaintenanceTable/index.jsx @@ -1,15 +1,15 @@ import PropTypes from "prop-types"; import { Box } from "@mui/material"; -import DataTable from "../../../Components/Table"; -import Pagination from "../../../Components/Table/TablePagination"; +import DataTable from "../../../../Components/Table/index.jsx"; +import Pagination from "../../../../Components/Table/TablePagination/index.jsx"; import ArrowDownwardRoundedIcon from "@mui/icons-material/ArrowDownwardRounded"; import ArrowUpwardRoundedIcon from "@mui/icons-material/ArrowUpwardRounded"; -import ActionsMenu from "./ActionsMenu"; +import ActionsMenu from "./ActionsMenu/index.jsx"; import { memo } from "react"; import { useDispatch, useSelector } from "react-redux"; -import { formatDurationRounded } from "../../../Utils/timeUtils"; -import { StatusLabel } from "../../../Components/Label"; -import { setRowsPerPage } from "../../../Features/UI/uiSlice"; +import { formatDurationRounded } from "../../../../Utils/timeUtils.js"; +import { StatusLabel } from "../../../../Components/Label/index.jsx"; +import { setRowsPerPage } from "../../../../Features/UI/uiSlice.js"; import { useTranslation } from "react-i18next"; import { useTheme } from "@emotion/react"; import { useNavigate } from "react-router-dom"; diff --git a/client/src/Pages/Maintenance/index.css b/client/src/Pages/v1/Maintenance/index.css similarity index 100% rename from client/src/Pages/Maintenance/index.css rename to client/src/Pages/v1/Maintenance/index.css diff --git a/client/src/Pages/Maintenance/index.jsx b/client/src/Pages/v1/Maintenance/index.jsx similarity index 91% rename from client/src/Pages/Maintenance/index.jsx rename to client/src/Pages/v1/Maintenance/index.jsx index ee142f524..a993c153f 100644 --- a/client/src/Pages/Maintenance/index.jsx +++ b/client/src/Pages/v1/Maintenance/index.jsx @@ -2,13 +2,13 @@ import { Stack, Button } from "@mui/material"; import { useTheme } from "@emotion/react"; import { useState, useEffect } from "react"; import "./index.css"; -import MaintenanceTable from "./MaintenanceTable"; +import MaintenanceTable from "./MaintenanceTable/index.jsx"; import { useSelector } from "react-redux"; -import { networkService } from "../../main"; -import Breadcrumbs from "../../Components/Breadcrumbs"; +import { networkService } from "../../../main.jsx"; +import Breadcrumbs from "../../../Components/Breadcrumbs/index.jsx"; import { useNavigate } from "react-router-dom"; import { useTranslation } from "react-i18next"; -import PageStateWrapper from "../../Components/PageStateWrapper"; +import PageStateWrapper from "../../../Components/PageStateWrapper/index.jsx"; const Maintenance = () => { const theme = useTheme(); diff --git a/client/src/Pages/NotFound/index.jsx b/client/src/Pages/v1/NotFound/index.jsx similarity index 96% rename from client/src/Pages/NotFound/index.jsx rename to client/src/Pages/v1/NotFound/index.jsx index fdae92cb5..48825c9f6 100644 --- a/client/src/Pages/NotFound/index.jsx +++ b/client/src/Pages/v1/NotFound/index.jsx @@ -1,5 +1,5 @@ import PropTypes from "prop-types"; -import NotFoundSvg from "../../../src/assets/Images/sushi_404.svg"; +import NotFoundSvg from "../../../assets/Images/sushi_404.svg"; import { Button, Stack, Typography } from "@mui/material"; import { useNavigate } from "react-router-dom"; import { useTheme } from "@emotion/react"; diff --git a/client/src/Pages/Notifications/components/ActionMenu.jsx b/client/src/Pages/v1/Notifications/components/ActionMenu.jsx similarity index 100% rename from client/src/Pages/Notifications/components/ActionMenu.jsx rename to client/src/Pages/v1/Notifications/components/ActionMenu.jsx diff --git a/client/src/Pages/Notifications/create/index.jsx b/client/src/Pages/v1/Notifications/create/index.jsx similarity index 92% rename from client/src/Pages/Notifications/create/index.jsx rename to client/src/Pages/v1/Notifications/create/index.jsx index 6d8afe665..0d541736d 100644 --- a/client/src/Pages/Notifications/create/index.jsx +++ b/client/src/Pages/v1/Notifications/create/index.jsx @@ -1,13 +1,13 @@ // Components import Stack from "@mui/material/Stack"; import Typography from "@mui/material/Typography"; -import Breadcrumbs from "../../../Components/Breadcrumbs"; +import Breadcrumbs from "../../../../Components/Breadcrumbs/index.jsx"; import Button from "@mui/material/Button"; -import ConfigBox from "../../../Components/ConfigBox"; +import ConfigBox from "../../../../Components/ConfigBox/index.jsx"; import Box from "@mui/material/Box"; -import Select from "../../../Components/Inputs/Select"; -import TextInput from "../../../Components/Inputs/TextInput"; -import Dialog from "../../../Components/Dialog"; +import Select from "../../../../Components/Inputs/Select/index.jsx"; +import TextInput from "../../../../Components/Inputs/TextInput/index.jsx"; +import Dialog from "../../../../Components/Dialog/index.jsx"; // Utils import { useState } from "react"; @@ -18,9 +18,9 @@ import { useEditNotification, useTestNotification, useDeleteNotification, -} from "../../../Hooks/useNotifications"; -import { notificationValidation } from "../../../Validation/validation"; -import { createToast } from "../../../Utils/toastUtils"; +} from "../../../../Hooks/v1/useNotifications.js"; +import { notificationValidation } from "../../../../Validation/validation.js"; +import { createToast } from "../../../../Utils/toastUtils.jsx"; import { useTranslation } from "react-i18next"; import { useParams, useNavigate } from "react-router-dom"; import { @@ -29,7 +29,7 @@ import { DESCRIPTION_MAP, LABEL_MAP, PLACEHOLDER_MAP, -} from "../utils"; +} from "../utils.js"; // Setup diff --git a/client/src/Pages/Notifications/index.jsx b/client/src/Pages/v1/Notifications/index.jsx similarity index 89% rename from client/src/Pages/Notifications/index.jsx rename to client/src/Pages/v1/Notifications/index.jsx index 910fbf1fc..5649224b6 100644 --- a/client/src/Pages/Notifications/index.jsx +++ b/client/src/Pages/v1/Notifications/index.jsx @@ -1,11 +1,11 @@ // Components import Stack from "@mui/material/Stack"; import Typography from "@mui/material/Typography"; -import Breadcrumbs from "../../Components/Breadcrumbs"; +import Breadcrumbs from "../../../Components/Breadcrumbs/index.jsx"; import Button from "@mui/material/Button"; -import DataTable from "../../Components/Table"; -import ActionMenu from "./components/ActionMenu"; -import PageStateWrapper from "../../Components/PageStateWrapper"; +import DataTable from "../../../Components/Table/index.jsx"; +import ActionMenu from "./components/ActionMenu.jsx"; +import PageStateWrapper from "../../../Components/PageStateWrapper/index.jsx"; // Utils import { useState } from "react"; @@ -14,7 +14,7 @@ import { useNavigate } from "react-router-dom"; import { useGetNotificationsByTeamId, useDeleteNotification, -} from "../../Hooks/useNotifications"; +} from "../../../Hooks/v1/useNotifications.js"; import { useTranslation } from "react-i18next"; const Notifications = () => { diff --git a/client/src/Pages/Notifications/utils.js b/client/src/Pages/v1/Notifications/utils.js similarity index 100% rename from client/src/Pages/Notifications/utils.js rename to client/src/Pages/v1/Notifications/utils.js diff --git a/client/src/Pages/PageSpeed/Create/index.css b/client/src/Pages/v1/PageSpeed/Create/index.css similarity index 100% rename from client/src/Pages/PageSpeed/Create/index.css rename to client/src/Pages/v1/PageSpeed/Create/index.css diff --git a/client/src/Pages/PageSpeed/Create/index.jsx b/client/src/Pages/v1/PageSpeed/Create/index.jsx similarity index 91% rename from client/src/Pages/PageSpeed/Create/index.jsx rename to client/src/Pages/v1/PageSpeed/Create/index.jsx index 46bc7f977..7cc244848 100644 --- a/client/src/Pages/PageSpeed/Create/index.jsx +++ b/client/src/Pages/v1/PageSpeed/Create/index.jsx @@ -1,37 +1,37 @@ // Components import { Box, Stack, Tooltip, Typography, Button, ButtonGroup } from "@mui/material"; -import ConfigBox from "../../../Components/ConfigBox"; -import Select from "../../../Components/Inputs/Select"; -import TextInput from "../../../Components/Inputs/TextInput"; +import ConfigBox from "../../../../Components/ConfigBox/index.jsx"; +import Select from "../../../../Components/Inputs/Select/index.jsx"; +import TextInput from "../../../../Components/Inputs/TextInput/index.jsx"; import PauseCircleOutlineIcon from "@mui/icons-material/PauseCircleOutline"; -import Breadcrumbs from "../../../Components/Breadcrumbs"; -import PulseDot from "../../../Components/Animated/PulseDot"; +import Breadcrumbs from "../../../../Components/Breadcrumbs/index.jsx"; +import PulseDot from "../../../../Components/Animated/PulseDot.jsx"; import PlayCircleOutlineRoundedIcon from "@mui/icons-material/PlayCircleOutlineRounded"; -import SkeletonLayout from "./skeleton"; -import NotificationsConfig from "../../../Components/NotificationConfig"; -import Dialog from "../../../Components/Dialog"; -import { HttpAdornment } from "../../../Components/Inputs/TextInput/Adornments"; -import Radio from "../../../Components/Inputs/Radio"; +import SkeletonLayout from "./skeleton.jsx"; +import NotificationsConfig from "../../../../Components/NotificationConfig/index.jsx"; +import Dialog from "../../../../Components/Dialog/index.jsx"; +import { HttpAdornment } from "../../../../Components/Inputs/TextInput/Adornments/index.jsx"; +import Radio from "../../../../Components/Inputs/Radio/index.jsx"; // Utils import { useState, useEffect } from "react"; import { useSelector } from "react-redux"; -import { monitorValidation } from "../../../Validation/validation"; -import { parseDomainName } from "../../../Utils/monitorUtils"; +import { monitorValidation } from "../../../../Validation/validation.js"; +import { parseDomainName } from "../../../../Utils/monitorUtils.js"; import { useTranslation } from "react-i18next"; -import { useGetNotificationsByTeamId } from "../../../Hooks/useNotifications"; +import { useGetNotificationsByTeamId } from "../../../../Hooks/v1/useNotifications.js"; import { useTheme } from "@emotion/react"; -import { createToast } from "../../../Utils/toastUtils"; +import { createToast } from "../../../../Utils/toastUtils.jsx"; import { useParams } from "react-router"; -import { useMonitorUtils } from "../../../Hooks/useMonitorUtils"; +import { useMonitorUtils } from "../../../../Hooks/v1/useMonitorUtils.js"; import { useCreateMonitor, useFetchMonitorById, useDeleteMonitor, useUpdateMonitor, usePauseMonitor, -} from "../../../Hooks/monitorHooks"; +} from "../../../../Hooks/v1/monitorHooks.js"; const PageSpeedSetup = () => { const { monitorId } = useParams(); diff --git a/client/src/Pages/PageSpeed/Create/skeleton.jsx b/client/src/Pages/v1/PageSpeed/Create/skeleton.jsx similarity index 100% rename from client/src/Pages/PageSpeed/Create/skeleton.jsx rename to client/src/Pages/v1/PageSpeed/Create/skeleton.jsx diff --git a/client/src/Pages/PageSpeed/Details/Components/Charts/AreaChart.jsx b/client/src/Pages/v1/PageSpeed/Details/Components/Charts/AreaChart.jsx similarity index 99% rename from client/src/Pages/PageSpeed/Details/Components/Charts/AreaChart.jsx rename to client/src/Pages/v1/PageSpeed/Details/Components/Charts/AreaChart.jsx index 40fec0ab6..5c79cc06c 100644 --- a/client/src/Pages/PageSpeed/Details/Components/Charts/AreaChart.jsx +++ b/client/src/Pages/v1/PageSpeed/Details/Components/Charts/AreaChart.jsx @@ -11,7 +11,7 @@ import { import { useTheme } from "@emotion/react"; import { useMemo, useState } from "react"; import { Box, Stack, Typography } from "@mui/material"; -import { formatDateWithTz } from "../../../../../Utils/timeUtils"; +import { formatDateWithTz } from "../../../../../../Utils/timeUtils.js"; import { useSelector } from "react-redux"; const config = { diff --git a/client/src/Pages/PageSpeed/Details/Components/Charts/AreaChartLegend.jsx b/client/src/Pages/v1/PageSpeed/Details/Components/Charts/AreaChartLegend.jsx similarity index 84% rename from client/src/Pages/PageSpeed/Details/Components/Charts/AreaChartLegend.jsx rename to client/src/Pages/v1/PageSpeed/Details/Components/Charts/AreaChartLegend.jsx index 72811fa2a..cb6f18bd2 100644 --- a/client/src/Pages/PageSpeed/Details/Components/Charts/AreaChartLegend.jsx +++ b/client/src/Pages/v1/PageSpeed/Details/Components/Charts/AreaChartLegend.jsx @@ -1,7 +1,7 @@ import { Box, Typography, Divider } from "@mui/material"; -import Checkbox from "../../../../../Components/Inputs/Checkbox"; -import MetricsIcon from "../../../../../assets/icons/ruler-icon.svg?react"; -import LegendBox from "../../../../../Components/Charts/LegendBox"; +import Checkbox from "../../../../../../Components/Inputs/Checkbox/index.jsx"; +import MetricsIcon from "../../../../../../assets/icons/ruler-icon.svg?react"; +import LegendBox from "../../../../../../Components/Charts/LegendBox/index.jsx"; import { useTranslation } from "react-i18next"; import { useTheme } from "@emotion/react"; diff --git a/client/src/Pages/PageSpeed/Details/Components/Charts/PieChart.jsx b/client/src/Pages/v1/PageSpeed/Details/Components/Charts/PieChart.jsx similarity index 100% rename from client/src/Pages/PageSpeed/Details/Components/Charts/PieChart.jsx rename to client/src/Pages/v1/PageSpeed/Details/Components/Charts/PieChart.jsx diff --git a/client/src/Pages/PageSpeed/Details/Components/Charts/PieChartLegend.jsx b/client/src/Pages/v1/PageSpeed/Details/Components/Charts/PieChartLegend.jsx similarity index 92% rename from client/src/Pages/PageSpeed/Details/Components/Charts/PieChartLegend.jsx rename to client/src/Pages/v1/PageSpeed/Details/Components/Charts/PieChartLegend.jsx index 18ca0733c..fc4b190d8 100644 --- a/client/src/Pages/PageSpeed/Details/Components/Charts/PieChartLegend.jsx +++ b/client/src/Pages/v1/PageSpeed/Details/Components/Charts/PieChartLegend.jsx @@ -1,7 +1,7 @@ import { Stack, Box, Typography } from "@mui/material"; import { useTheme } from "@emotion/react"; -import LegendBox from "../../../../../Components/Charts/LegendBox"; -import SpeedometerIcon from "../../../../../assets/icons/speedometer-icon.svg?react"; +import LegendBox from "../../../../../../Components/Charts/LegendBox/index.jsx"; +import SpeedometerIcon from "../../../../../../assets/icons/speedometer-icon.svg?react"; import PropTypes from "prop-types"; const PieChartLegend = ({ audits }) => { diff --git a/client/src/Pages/PageSpeed/Details/Components/PageSpeedAreaChart/index.jsx b/client/src/Pages/v1/PageSpeed/Details/Components/PageSpeedAreaChart/index.jsx similarity index 75% rename from client/src/Pages/PageSpeed/Details/Components/PageSpeedAreaChart/index.jsx rename to client/src/Pages/v1/PageSpeed/Details/Components/PageSpeedAreaChart/index.jsx index 582648e23..bb45117fe 100644 --- a/client/src/Pages/PageSpeed/Details/Components/PageSpeedAreaChart/index.jsx +++ b/client/src/Pages/v1/PageSpeed/Details/Components/PageSpeedAreaChart/index.jsx @@ -1,8 +1,8 @@ -import ChartBox from "../../../../../Components/Charts/ChartBox"; -import AreaChart from "../Charts/AreaChart"; -import AreaChartLegend from "../Charts/AreaChartLegend"; -import SkeletonLayout from "./skeleton"; -import ScoreIcon from "../../../../../assets/icons/monitor-graph-line.svg?react"; +import ChartBox from "../../../../../../Components/Charts/ChartBox/index.jsx"; +import AreaChart from "../Charts/AreaChart.jsx"; +import AreaChartLegend from "../Charts/AreaChartLegend.jsx"; +import SkeletonLayout from "./skeleton.jsx"; +import ScoreIcon from "../../../../../../assets/icons/monitor-graph-line.svg?react"; import { Stack } from "@mui/material"; import PropTypes from "prop-types"; diff --git a/client/src/Pages/PageSpeed/Details/Components/PageSpeedAreaChart/skeleton.jsx b/client/src/Pages/v1/PageSpeed/Details/Components/PageSpeedAreaChart/skeleton.jsx similarity index 100% rename from client/src/Pages/PageSpeed/Details/Components/PageSpeedAreaChart/skeleton.jsx rename to client/src/Pages/v1/PageSpeed/Details/Components/PageSpeedAreaChart/skeleton.jsx diff --git a/client/src/Pages/PageSpeed/Details/Components/PageSpeedStatusBoxes/index.jsx b/client/src/Pages/v1/PageSpeed/Details/Components/PageSpeedStatusBoxes/index.jsx similarity index 76% rename from client/src/Pages/PageSpeed/Details/Components/PageSpeedStatusBoxes/index.jsx rename to client/src/Pages/v1/PageSpeed/Details/Components/PageSpeedStatusBoxes/index.jsx index cb79198e9..0811ae707 100644 --- a/client/src/Pages/PageSpeed/Details/Components/PageSpeedStatusBoxes/index.jsx +++ b/client/src/Pages/v1/PageSpeed/Details/Components/PageSpeedStatusBoxes/index.jsx @@ -1,7 +1,7 @@ -import StatusBoxes from "../../../../../Components/StatusBoxes"; -import StatBox from "../../../../../Components/StatBox"; +import StatusBoxes from "../../../../../../Components/StatusBoxes/index.jsx"; +import StatBox from "../../../../../../Components/StatBox/index.jsx"; import { Typography } from "@mui/material"; -import { getHumanReadableDuration } from "../../../../../Utils/timeUtils"; +import { getHumanReadableDuration } from "../../../../../../Utils/timeUtils.js"; import { useTranslation } from "react-i18next"; const PageSpeedStatusBoxes = ({ shouldRender, monitor }) => { diff --git a/client/src/Pages/PageSpeed/Details/Components/PerformanceReport/index.jsx b/client/src/Pages/v1/PageSpeed/Details/Components/PerformanceReport/index.jsx similarity index 78% rename from client/src/Pages/PageSpeed/Details/Components/PerformanceReport/index.jsx rename to client/src/Pages/v1/PageSpeed/Details/Components/PerformanceReport/index.jsx index 2f809ebbb..ce4704144 100644 --- a/client/src/Pages/PageSpeed/Details/Components/PerformanceReport/index.jsx +++ b/client/src/Pages/v1/PageSpeed/Details/Components/PerformanceReport/index.jsx @@ -1,10 +1,10 @@ -import ChartBox from "../../../../../Components/Charts/ChartBox"; -import PerformanceIcon from "../../../../../assets/icons/performance-report.svg?react"; -import PieChart from "../Charts/PieChart"; +import ChartBox from "../../../../../../Components/Charts/ChartBox/index.jsx"; +import PerformanceIcon from "../../../../../../assets/icons/performance-report.svg?react"; +import PieChart from "../Charts/PieChart.jsx"; import { Typography } from "@mui/material"; import { useTheme } from "@emotion/react"; -import PieChartLegend from "../Charts/PieChartLegend"; -import SkeletonLayout from "./skeleton"; +import PieChartLegend from "../Charts/PieChartLegend.jsx"; +import SkeletonLayout from "./skeleton.jsx"; import { useTranslation } from "react-i18next"; const PerformanceReport = ({ shouldRender, audits }) => { diff --git a/client/src/Pages/PageSpeed/Details/Components/PerformanceReport/skeleton.jsx b/client/src/Pages/v1/PageSpeed/Details/Components/PerformanceReport/skeleton.jsx similarity index 100% rename from client/src/Pages/PageSpeed/Details/Components/PerformanceReport/skeleton.jsx rename to client/src/Pages/v1/PageSpeed/Details/Components/PerformanceReport/skeleton.jsx diff --git a/client/src/Pages/PageSpeed/Details/index.jsx b/client/src/Pages/v1/PageSpeed/Details/index.jsx similarity index 81% rename from client/src/Pages/PageSpeed/Details/index.jsx rename to client/src/Pages/v1/PageSpeed/Details/index.jsx index cfc799b36..cf9652bce 100644 --- a/client/src/Pages/PageSpeed/Details/index.jsx +++ b/client/src/Pages/v1/PageSpeed/Details/index.jsx @@ -1,17 +1,17 @@ // Components import { Stack, Typography } from "@mui/material"; -import Breadcrumbs from "../../../Components/Breadcrumbs"; -import MonitorTimeFrameHeader from "../../../Components/MonitorTimeFrameHeader"; -import PageSpeedStatusBoxes from "./Components/PageSpeedStatusBoxes"; -import MonitorDetailsControlHeader from "../../../Components/MonitorDetailsControlHeader"; -import PageSpeedAreaChart from "./Components/PageSpeedAreaChart"; -import PerformanceReport from "./Components/PerformanceReport"; -import GenericFallback from "../../../Components/GenericFallback"; +import Breadcrumbs from "../../../../Components/Breadcrumbs/index.jsx"; +import MonitorTimeFrameHeader from "../../../../Components/MonitorTimeFrameHeader/index.jsx"; +import PageSpeedStatusBoxes from "./Components/PageSpeedStatusBoxes/index.jsx"; +import MonitorDetailsControlHeader from "../../../../Components/MonitorDetailsControlHeader/index.jsx"; +import PageSpeedAreaChart from "./Components/PageSpeedAreaChart/index.jsx"; +import PerformanceReport from "./Components/PerformanceReport/index.jsx"; +import GenericFallback from "../../../../Components/GenericFallback/index.jsx"; // Utils import { useTheme } from "@emotion/react"; -import { useIsAdmin } from "../../../Hooks/useIsAdmin"; +import { useIsAdmin } from "../../../../Hooks/v1/useIsAdmin.js"; import { useParams } from "react-router-dom"; -import { useFetchStatsByMonitorId } from "../../../Hooks/monitorHooks"; +import { useFetchStatsByMonitorId } from "../../../../Hooks/v1/monitorHooks.js"; import { useState } from "react"; import { useTranslation } from "react-i18next"; // Constants diff --git a/client/src/Pages/PageSpeed/Monitors/Components/Card/index.jsx b/client/src/Pages/v1/PageSpeed/Monitors/Components/Card/index.jsx similarity index 95% rename from client/src/Pages/PageSpeed/Monitors/Components/Card/index.jsx rename to client/src/Pages/v1/PageSpeed/Monitors/Components/Card/index.jsx index 94d6b137b..db1f6e712 100644 --- a/client/src/Pages/PageSpeed/Monitors/Components/Card/index.jsx +++ b/client/src/Pages/v1/PageSpeed/Monitors/Components/Card/index.jsx @@ -1,17 +1,17 @@ import PropTypes from "prop-types"; // import PageSpeedIcon from "../../../../assets/icons/page-speed.svg?react"; -import PageSpeedIcon from "../../../../../assets/icons/page-speed.svg?react"; -import { StatusLabel } from "../../../../../Components/Label"; +import PageSpeedIcon from "../../../../../../assets/icons/page-speed.svg?react"; +import { StatusLabel } from "../../../../../../Components/Label/index.jsx"; import { Box, Grid, Stack, Typography } from "@mui/material"; import { useNavigate } from "react-router-dom"; import { useTheme } from "@emotion/react"; import { Area, AreaChart, CartesianGrid, ResponsiveContainer, Tooltip } from "recharts"; import { useSelector } from "react-redux"; -import { formatDateWithTz, formatDurationSplit } from "../../../../../Utils/timeUtils"; -import { useMonitorUtils } from "../../../../../Hooks/useMonitorUtils"; +import { formatDateWithTz, formatDurationSplit } from "../../../../../../Utils/timeUtils.js"; +import { useMonitorUtils } from "../../../../../../Hooks/v1/useMonitorUtils.js"; import { useState } from "react"; import { useTranslation } from "react-i18next"; -import IconBox from "../../../../../Components/IconBox"; +import IconBox from "../../../../../../Components/IconBox/index.jsx"; /** * CustomToolTip displays a tooltip with formatted date and score information. * @param {Object} props diff --git a/client/src/Pages/PageSpeed/Monitors/Components/MonitorGrid/index.jsx b/client/src/Pages/v1/PageSpeed/Monitors/Components/MonitorGrid/index.jsx similarity index 88% rename from client/src/Pages/PageSpeed/Monitors/Components/MonitorGrid/index.jsx rename to client/src/Pages/v1/PageSpeed/Monitors/Components/MonitorGrid/index.jsx index 6266ebdc9..6b0a800fb 100644 --- a/client/src/Pages/PageSpeed/Monitors/Components/MonitorGrid/index.jsx +++ b/client/src/Pages/v1/PageSpeed/Monitors/Components/MonitorGrid/index.jsx @@ -1,5 +1,5 @@ import { Grid, Grid2 } from "@mui/material"; -import Card from "../Card"; +import Card from "../Card/index.jsx"; const MonitorGrid = ({ shouldRender, monitors }) => { return ( diff --git a/client/src/Pages/PageSpeed/Monitors/index.jsx b/client/src/Pages/v1/PageSpeed/Monitors/index.jsx similarity index 69% rename from client/src/Pages/PageSpeed/Monitors/index.jsx rename to client/src/Pages/v1/PageSpeed/Monitors/index.jsx index b5b13ba6e..e568e8d82 100644 --- a/client/src/Pages/PageSpeed/Monitors/index.jsx +++ b/client/src/Pages/v1/PageSpeed/Monitors/index.jsx @@ -1,18 +1,18 @@ // Components import { useState } from "react"; -import Breadcrumbs from "../../../Components/Breadcrumbs"; +import Breadcrumbs from "../../../../Components/Breadcrumbs/index.jsx"; import { Stack } from "@mui/material"; -import CreateMonitorHeader from "../../../Components/MonitorCreateHeader"; -import MonitorCountHeader from "../../../Components/MonitorCountHeader"; -import MonitorGrid from "./Components/MonitorGrid"; -import PageStateWrapper from "../../../Components/PageStateWrapper"; -import FallbackPageSpeedWarning from "../../../Components/Fallback/FallbackPageSpeedWarning"; +import CreateMonitorHeader from "../../../../Components/MonitorCreateHeader/index.jsx"; +import MonitorCountHeader from "../../../../Components/MonitorCountHeader/index.jsx"; +import MonitorGrid from "./Components/MonitorGrid/index.jsx"; +import PageStateWrapper from "../../../../Components/PageStateWrapper/index.jsx"; +import FallbackPageSpeedWarning from "../../../../Components/Fallback/FallbackPageSpeedWarning.jsx"; // Utils import { useTheme } from "@emotion/react"; -import { useIsAdmin } from "../../../Hooks/useIsAdmin"; -import { useFetchMonitorsByTeamId } from "../../../Hooks/monitorHooks"; -import { useFetchSettings } from "../../../Hooks/settingsHooks"; +import { useIsAdmin } from "../../../../Hooks/v1/useIsAdmin.js"; +import { useFetchMonitorsByTeamId } from "../../../../Hooks/v1/monitorHooks.js"; +import { useFetchSettings } from "../../../../Hooks/v1/settingsHooks.js"; // Constants const BREADCRUMBS = [{ name: `pagespeed`, path: "/pagespeed" }]; const TYPES = ["pagespeed"]; diff --git a/client/src/Pages/ServerUnreachable.jsx b/client/src/Pages/v1/ServerUnreachable.jsx similarity index 90% rename from client/src/Pages/ServerUnreachable.jsx rename to client/src/Pages/v1/ServerUnreachable.jsx index 466633144..0db697543 100644 --- a/client/src/Pages/ServerUnreachable.jsx +++ b/client/src/Pages/v1/ServerUnreachable.jsx @@ -2,14 +2,14 @@ import React, { useState } from "react"; import { Box, Typography, Button, Stack } from "@mui/material"; import { useTheme } from "@emotion/react"; import { useNavigate } from "react-router-dom"; -import { networkService } from "../Utils/NetworkService"; -import Alert from "../Components/Alert"; -import { createToast } from "../Utils/toastUtils"; +import { networkService } from "../../Utils/NetworkService.js"; +import Alert from "../../Components/Alert/index.jsx"; +import { createToast } from "../../Utils/toastUtils.jsx"; import { useTranslation } from "react-i18next"; -import Background from "../assets/Images/background-grid.svg?react"; -import Logo from "../assets/icons/checkmate-icon.svg?react"; -import ThemeSwitch from "../Components/ThemeSwitch"; -import LanguageSelector from "../Components/LanguageSelector"; +import Background from "../../assets/Images/background-grid.svg?react"; +import Logo from "../../assets/icons/checkmate-icon.svg?react"; +import ThemeSwitch from "../../Components/ThemeSwitch/index.jsx"; +import LanguageSelector from "../../Components/LanguageSelector.jsx"; const ServerUnreachable = () => { const theme = useTheme(); diff --git a/client/src/Pages/Settings/SettingsAbout.jsx b/client/src/Pages/v1/Settings/SettingsAbout.jsx similarity index 88% rename from client/src/Pages/Settings/SettingsAbout.jsx rename to client/src/Pages/v1/Settings/SettingsAbout.jsx index a80351393..3d5f5224e 100644 --- a/client/src/Pages/Settings/SettingsAbout.jsx +++ b/client/src/Pages/v1/Settings/SettingsAbout.jsx @@ -1,10 +1,10 @@ import Box from "@mui/material/Box"; import Typography from "@mui/material/Typography"; -import ConfigBox from "../../Components/ConfigBox"; +import ConfigBox from "../../../Components/ConfigBox/index.jsx"; // Utils import { useTheme } from "@emotion/react"; import { useTranslation } from "react-i18next"; -import Link from "../../Components/Link"; +import Link from "../../../Components/Link/index.jsx"; const SettingsAbout = () => { const theme = useTheme(); diff --git a/client/src/Pages/Settings/SettingsDemoMonitors.jsx b/client/src/Pages/v1/Settings/SettingsDemoMonitors.jsx similarity index 95% rename from client/src/Pages/Settings/SettingsDemoMonitors.jsx rename to client/src/Pages/v1/Settings/SettingsDemoMonitors.jsx index 13e93421e..96f907970 100644 --- a/client/src/Pages/Settings/SettingsDemoMonitors.jsx +++ b/client/src/Pages/v1/Settings/SettingsDemoMonitors.jsx @@ -1,12 +1,12 @@ import Box from "@mui/material/Box"; import Typography from "@mui/material/Typography"; import Button from "@mui/material/Button"; -import ConfigBox from "../../Components/ConfigBox"; +import ConfigBox from "../../../Components/ConfigBox/index.jsx"; // Utils import { useTheme } from "@emotion/react"; import { PropTypes } from "prop-types"; import { useTranslation } from "react-i18next"; -import Dialog from "../../Components/Dialog"; +import Dialog from "../../../Components/Dialog/index.jsx"; import { useState } from "react"; const SettingsDemoMonitors = ({ isAdmin, HEADER_SX, handleChange, isLoading }) => { diff --git a/client/src/Pages/Settings/SettingsEmail.jsx b/client/src/Pages/v1/Settings/SettingsEmail.jsx similarity index 94% rename from client/src/Pages/Settings/SettingsEmail.jsx rename to client/src/Pages/v1/Settings/SettingsEmail.jsx index 1f86b1984..c2389b277 100644 --- a/client/src/Pages/Settings/SettingsEmail.jsx +++ b/client/src/Pages/v1/Settings/SettingsEmail.jsx @@ -1,19 +1,19 @@ import Box from "@mui/material/Box"; import Typography from "@mui/material/Typography"; -import ConfigBox from "../../Components/ConfigBox"; -import TextInput from "../../Components/Inputs/TextInput"; +import ConfigBox from "../../../Components/ConfigBox/index.jsx"; +import TextInput from "../../../Components/Inputs/TextInput/index.jsx"; import Button from "@mui/material/Button"; import Stack from "@mui/material/Stack"; import { Switch } from "@mui/material"; -import TextLink from "../../Components/TextLink"; +import TextLink from "../../../Components/TextLink/index.jsx"; // Utils import { useTheme } from "@emotion/react"; import { PropTypes } from "prop-types"; import { useState } from "react"; import { useTranslation } from "react-i18next"; -import { PasswordEndAdornment } from "../../Components/Inputs/TextInput/Adornments"; -import { useSendTestEmail } from "../../Hooks/useSendTestEmail"; -import { createToast } from "../../Utils/toastUtils"; +import { PasswordEndAdornment } from "../../../Components/Inputs/TextInput/Adornments/index.jsx"; +import { useSendTestEmail } from "../../../Hooks/v1/useSendTestEmail.js"; +import { createToast } from "../../../Utils/toastUtils.jsx"; const SettingsEmail = ({ isAdmin, diff --git a/client/src/Pages/Settings/SettingsGlobalThresholds.jsx b/client/src/Pages/v1/Settings/SettingsGlobalThresholds.jsx similarity index 94% rename from client/src/Pages/Settings/SettingsGlobalThresholds.jsx rename to client/src/Pages/v1/Settings/SettingsGlobalThresholds.jsx index acc101ab4..e238c6475 100644 --- a/client/src/Pages/Settings/SettingsGlobalThresholds.jsx +++ b/client/src/Pages/v1/Settings/SettingsGlobalThresholds.jsx @@ -1,8 +1,8 @@ import Box from "@mui/material/Box"; import Stack from "@mui/material/Stack"; import Typography from "@mui/material/Typography"; -import ConfigBox from "../../Components/ConfigBox"; -import TextInput from "../../Components/Inputs/TextInput"; +import ConfigBox from "../../../Components/ConfigBox/index.jsx"; +import TextInput from "../../../Components/Inputs/TextInput/index.jsx"; import { useTheme } from "@emotion/react"; import { PropTypes } from "prop-types"; diff --git a/client/src/Pages/Settings/SettingsPagespeed.jsx b/client/src/Pages/v1/Settings/SettingsPagespeed.jsx similarity index 91% rename from client/src/Pages/Settings/SettingsPagespeed.jsx rename to client/src/Pages/v1/Settings/SettingsPagespeed.jsx index 157c75dac..83b2e23c7 100644 --- a/client/src/Pages/Settings/SettingsPagespeed.jsx +++ b/client/src/Pages/v1/Settings/SettingsPagespeed.jsx @@ -2,9 +2,9 @@ import Box from "@mui/material/Box"; import Stack from "@mui/material/Stack"; import Typography from "@mui/material/Typography"; import Button from "@mui/material/Button"; -import ConfigBox from "../../Components/ConfigBox"; -import TextInput from "../../Components/Inputs/TextInput"; -import { PasswordEndAdornment } from "../../Components/Inputs/TextInput/Adornments"; +import ConfigBox from "../../../Components/ConfigBox/index.jsx"; +import TextInput from "../../../Components/Inputs/TextInput/index.jsx"; +import { PasswordEndAdornment } from "../../../Components/Inputs/TextInput/Adornments/index.jsx"; // Utils import { useTheme } from "@emotion/react"; import { PropTypes } from "prop-types"; diff --git a/client/src/Pages/Settings/SettingsStats.jsx b/client/src/Pages/v1/Settings/SettingsStats.jsx similarity index 92% rename from client/src/Pages/Settings/SettingsStats.jsx rename to client/src/Pages/v1/Settings/SettingsStats.jsx index 740ac3a37..52f739ecf 100644 --- a/client/src/Pages/Settings/SettingsStats.jsx +++ b/client/src/Pages/v1/Settings/SettingsStats.jsx @@ -2,9 +2,9 @@ import Box from "@mui/material/Box"; import Typography from "@mui/material/Typography"; import Button from "@mui/material/Button"; import Stack from "@mui/material/Stack"; -import ConfigBox from "../../Components/ConfigBox"; -import TextInput from "../../Components/Inputs/TextInput"; -import Dialog from "../../Components/Dialog"; +import ConfigBox from "../../../Components/ConfigBox/index.jsx"; +import TextInput from "../../../Components/Inputs/TextInput/index.jsx"; +import Dialog from "../../../Components/Dialog/index.jsx"; // Utils import { useTheme } from "@emotion/react"; diff --git a/client/src/Pages/Settings/SettingsTimeZone.jsx b/client/src/Pages/v1/Settings/SettingsTimeZone.jsx similarity index 90% rename from client/src/Pages/Settings/SettingsTimeZone.jsx rename to client/src/Pages/v1/Settings/SettingsTimeZone.jsx index ee5cf48de..59b2008f4 100644 --- a/client/src/Pages/Settings/SettingsTimeZone.jsx +++ b/client/src/Pages/v1/Settings/SettingsTimeZone.jsx @@ -1,9 +1,9 @@ import Box from "@mui/material/Box"; import Stack from "@mui/material/Stack"; import Typography from "@mui/material/Typography"; -import ConfigBox from "../../Components/ConfigBox"; -import Search from "../../Components/Inputs/Search"; -import timezones from "../../Utils/timezones.json"; +import ConfigBox from "../../../Components/ConfigBox/index.jsx"; +import Search from "../../../Components/Inputs/Search/index.jsx"; +import timezones from "../../../Utils/timezones.json"; // Utils import { useTheme } from "@emotion/react"; diff --git a/client/src/Pages/Settings/SettingsUI.jsx b/client/src/Pages/v1/Settings/SettingsUI.jsx similarity index 91% rename from client/src/Pages/Settings/SettingsUI.jsx rename to client/src/Pages/v1/Settings/SettingsUI.jsx index aab5f863d..91f4cea3c 100644 --- a/client/src/Pages/Settings/SettingsUI.jsx +++ b/client/src/Pages/v1/Settings/SettingsUI.jsx @@ -1,8 +1,8 @@ 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"; +import ConfigBox from "../../../Components/ConfigBox/index.jsx"; +import Select from "../../../Components/Inputs/Select/index.jsx"; // Utils import { useTheme } from "@emotion/react"; diff --git a/client/src/Pages/Settings/SettingsURL.jsx b/client/src/Pages/v1/Settings/SettingsURL.jsx similarity index 90% rename from client/src/Pages/Settings/SettingsURL.jsx rename to client/src/Pages/v1/Settings/SettingsURL.jsx index 24c49cad1..105658d4f 100644 --- a/client/src/Pages/Settings/SettingsURL.jsx +++ b/client/src/Pages/v1/Settings/SettingsURL.jsx @@ -1,8 +1,8 @@ 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"; +import ConfigBox from "../../../Components/ConfigBox/index.jsx"; +import Select from "../../../Components/Inputs/Select/index.jsx"; // Utils import { useTheme } from "@emotion/react"; diff --git a/client/src/Pages/Settings/index.jsx b/client/src/Pages/v1/Settings/index.jsx similarity index 89% rename from client/src/Pages/Settings/index.jsx rename to client/src/Pages/v1/Settings/index.jsx index 92f58b4c0..d27685ed6 100644 --- a/client/src/Pages/Settings/index.jsx +++ b/client/src/Pages/v1/Settings/index.jsx @@ -1,31 +1,31 @@ import Stack from "@mui/material/Stack"; import Typography from "@mui/material/Typography"; -import Breadcrumbs from "../../Components/Breadcrumbs"; -import SettingsTimeZone from "./SettingsTimeZone"; -import SettingsUI from "./SettingsUI"; -import SettingsURL from "./SettingsURL"; -import SettingsPagespeed from "./SettingsPagespeed"; -import SettingsDemoMonitors from "./SettingsDemoMonitors"; -import SettingsAbout from "./SettingsAbout"; -import SettingsEmail from "./SettingsEmail"; -import SettingsGlobalThresholds from "./SettingsGlobalThresholds"; +import Breadcrumbs from "../../../Components/Breadcrumbs/index.jsx"; +import SettingsTimeZone from "./SettingsTimeZone.jsx"; +import SettingsUI from "./SettingsUI.jsx"; +import SettingsURL from "./SettingsURL.jsx"; +import SettingsPagespeed from "./SettingsPagespeed.jsx"; +import SettingsDemoMonitors from "./SettingsDemoMonitors.jsx"; +import SettingsAbout from "./SettingsAbout.jsx"; +import SettingsEmail from "./SettingsEmail.jsx"; +import SettingsGlobalThresholds from "./SettingsGlobalThresholds.jsx"; import Button from "@mui/material/Button"; // Utils -import { settingsValidation } from "../../Validation/validation"; +import { settingsValidation } from "../../../Validation/validation.js"; import { useState } from "react"; import { useTheme } from "@emotion/react"; import { useTranslation } from "react-i18next"; import { useSelector, useDispatch } from "react-redux"; -import { setTimezone, setMode, setLanguage, setShowURL } from "../../Features/UI/uiSlice"; -import SettingsStats from "./SettingsStats"; +import { setTimezone, setMode, setLanguage, setShowURL } from "../../../Features/UI/uiSlice.js"; +import SettingsStats from "./SettingsStats.jsx"; -import { useFetchSettings, useSaveSettings } from "../../Hooks/settingsHooks"; -import { useIsAdmin } from "../../Hooks/useIsAdmin"; +import { useFetchSettings, useSaveSettings } from "../../../Hooks/v1/settingsHooks.js"; +import { useIsAdmin } from "../../../Hooks/v1/useIsAdmin.js"; import { useAddDemoMonitors, useDeleteAllMonitors, useDeleteMonitorStats, -} from "../../Hooks/monitorHooks"; +} from "../../../Hooks/v1/monitorHooks.js"; // Constants const BREADCRUMBS = [{ name: `Settings`, path: "/settings" }]; diff --git a/client/src/Pages/StatusPage/Create/Components/MonitorList/index.jsx b/client/src/Pages/v1/StatusPage/Create/Components/MonitorList/index.jsx similarity index 100% rename from client/src/Pages/StatusPage/Create/Components/MonitorList/index.jsx rename to client/src/Pages/v1/StatusPage/Create/Components/MonitorList/index.jsx diff --git a/client/src/Pages/StatusPage/Create/Components/Progress/index.jsx b/client/src/Pages/v1/StatusPage/Create/Components/Progress/index.jsx similarity index 83% rename from client/src/Pages/StatusPage/Create/Components/Progress/index.jsx rename to client/src/Pages/v1/StatusPage/Create/Components/Progress/index.jsx index 14c4ce0f5..c9f7d5f8f 100644 --- a/client/src/Pages/StatusPage/Create/Components/Progress/index.jsx +++ b/client/src/Pages/v1/StatusPage/Create/Components/Progress/index.jsx @@ -1,8 +1,8 @@ import { Button, Box } from "@mui/material"; -import ProgressUpload from "../../../../../Components/ProgressBars"; +import ProgressUpload from "../../../../../../Components/ProgressBars/index.jsx"; import ImageIcon from "@mui/icons-material/Image"; import { useTranslation } from "react-i18next"; -import { formatBytes } from "../../../../../Utils/fileUtils"; +import { formatBytes } from "../../../../../../Utils/fileUtils.js"; const Progress = ({ isLoading, progressValue, logo, logoType, removeLogo, errors }) => { const { t } = useTranslation(); if (isLoading) { diff --git a/client/src/Pages/StatusPage/Create/Components/Skeleton/index.jsx b/client/src/Pages/v1/StatusPage/Create/Components/Skeleton/index.jsx similarity index 100% rename from client/src/Pages/StatusPage/Create/Components/Skeleton/index.jsx rename to client/src/Pages/v1/StatusPage/Create/Components/Skeleton/index.jsx diff --git a/client/src/Pages/StatusPage/Create/Components/Tabs/ConfigStack.jsx b/client/src/Pages/v1/StatusPage/Create/Components/Tabs/ConfigStack.jsx similarity index 91% rename from client/src/Pages/StatusPage/Create/Components/Tabs/ConfigStack.jsx rename to client/src/Pages/v1/StatusPage/Create/Components/Tabs/ConfigStack.jsx index e313f0e85..fb5278c4e 100644 --- a/client/src/Pages/StatusPage/Create/Components/Tabs/ConfigStack.jsx +++ b/client/src/Pages/v1/StatusPage/Create/Components/Tabs/ConfigStack.jsx @@ -1,5 +1,5 @@ import { Stack, Typography } from "@mui/material"; -import ConfigBox from "../../../../../Components/ConfigBox"; +import ConfigBox from "../../../../../../Components/ConfigBox/index.jsx"; import PropTypes from "prop-types"; import { useTheme } from "@emotion/react"; diff --git a/client/src/Pages/StatusPage/Create/Components/Tabs/Content.jsx b/client/src/Pages/v1/StatusPage/Create/Components/Tabs/Content.jsx similarity index 90% rename from client/src/Pages/StatusPage/Create/Components/Tabs/Content.jsx rename to client/src/Pages/v1/StatusPage/Create/Components/Tabs/Content.jsx index 327a2fb0a..23e713c5a 100644 --- a/client/src/Pages/StatusPage/Create/Components/Tabs/Content.jsx +++ b/client/src/Pages/v1/StatusPage/Create/Components/Tabs/Content.jsx @@ -1,14 +1,14 @@ // Components import { Stack, Typography } from "@mui/material"; import { TabPanel } from "@mui/lab"; -import MonitorList from "../MonitorList"; -import Search from "../../../../../Components/Inputs/Search"; -import Checkbox from "../../../../../Components/Inputs/Checkbox"; +import MonitorList from "../MonitorList/index.jsx"; +import Search from "../../../../../../Components/Inputs/Search/index.jsx"; +import Checkbox from "../../../../../../Components/Inputs/Checkbox/index.jsx"; // Utils import { useState } from "react"; import { useTheme } from "@emotion/react"; import { useTranslation } from "react-i18next"; -import ConfigStack from "./ConfigStack"; +import ConfigStack from "./ConfigStack.jsx"; const Content = ({ tabValue, form, diff --git a/client/src/Pages/StatusPage/Create/Components/Tabs/Settings.jsx b/client/src/Pages/v1/StatusPage/Create/Components/Tabs/Settings.jsx similarity index 87% rename from client/src/Pages/StatusPage/Create/Components/Tabs/Settings.jsx rename to client/src/Pages/v1/StatusPage/Create/Components/Tabs/Settings.jsx index dd4345158..a4612e3da 100644 --- a/client/src/Pages/StatusPage/Create/Components/Tabs/Settings.jsx +++ b/client/src/Pages/v1/StatusPage/Create/Components/Tabs/Settings.jsx @@ -1,17 +1,17 @@ // Components import { Stack, Typography } from "@mui/material"; import { TabPanel } from "@mui/lab"; -import ConfigBox from "../../../../../Components/ConfigBox"; -import Checkbox from "../../../../../Components/Inputs/Checkbox"; -import TextInput from "../../../../../Components/Inputs/TextInput"; -import Search from "../../../../../Components/Inputs/Search"; -import ImageUpload from "../../../../../Components/Inputs/ImageUpload"; -import ColorPicker from "../../../../../Components/Inputs/ColorPicker"; -import Progress from "../Progress"; +import ConfigBox from "../../../../../../Components/ConfigBox/index.jsx"; +import Checkbox from "../../../../../../Components/Inputs/Checkbox/index.jsx"; +import TextInput from "../../../../../../Components/Inputs/TextInput/index.jsx"; +import Search from "../../../../../../Components/Inputs/Search/index.jsx"; +import ImageUpload from "../../../../../../Components/Inputs/ImageUpload/index.jsx"; +import ColorPicker from "../../../../../../Components/Inputs/ColorPicker/index.jsx"; +import Progress from "../Progress/index.jsx"; // Utils import { useTheme } from "@emotion/react"; -import timezones from "../../../../../Utils/timezones.json"; +import timezones from "../../../../../../Utils/timezones.json"; import PropTypes from "prop-types"; import { useTranslation } from "react-i18next"; import { useMemo, useState, useCallback } from "react"; diff --git a/client/src/Pages/StatusPage/Create/Components/Tabs/index.jsx b/client/src/Pages/v1/StatusPage/Create/Components/Tabs/index.jsx similarity index 93% rename from client/src/Pages/StatusPage/Create/Components/Tabs/index.jsx rename to client/src/Pages/v1/StatusPage/Create/Components/Tabs/index.jsx index 60d86e1c9..971b8d85c 100644 --- a/client/src/Pages/StatusPage/Create/Components/Tabs/index.jsx +++ b/client/src/Pages/v1/StatusPage/Create/Components/Tabs/index.jsx @@ -1,12 +1,12 @@ // Components import { TabContext } from "@mui/lab"; import { Tab } from "@mui/material"; -import Settings from "./Settings"; -import Content from "./Content"; +import Settings from "./Settings.jsx"; +import Content from "./Content.jsx"; // Utils import PropTypes from "prop-types"; -import CustomTabList from "../../../../../Components/Tab"; +import CustomTabList from "../../../../../../Components/Tab/index.jsx"; const Tabs = ({ isCreate, diff --git a/client/src/Pages/StatusPage/Create/Hooks/useCreateStatusPage.jsx b/client/src/Pages/v1/StatusPage/Create/Hooks/useCreateStatusPage.jsx similarity index 83% rename from client/src/Pages/StatusPage/Create/Hooks/useCreateStatusPage.jsx rename to client/src/Pages/v1/StatusPage/Create/Hooks/useCreateStatusPage.jsx index a2ac5f55d..808598052 100644 --- a/client/src/Pages/StatusPage/Create/Hooks/useCreateStatusPage.jsx +++ b/client/src/Pages/v1/StatusPage/Create/Hooks/useCreateStatusPage.jsx @@ -1,6 +1,6 @@ import { useState } from "react"; -import { networkService } from "../../../../main"; -import { createToast } from "../../../../Utils/toastUtils"; +import { networkService } from "../../../../../main.jsx"; +import { createToast } from "../../../../../Utils/toastUtils.jsx"; const useCreateStatusPage = (isCreate, url) => { const [isLoading, setIsLoading] = useState(false); diff --git a/client/src/Pages/StatusPage/Create/Hooks/useMonitorsFetch.jsx b/client/src/Pages/v1/StatusPage/Create/Hooks/useMonitorsFetch.jsx similarity index 88% rename from client/src/Pages/StatusPage/Create/Hooks/useMonitorsFetch.jsx rename to client/src/Pages/v1/StatusPage/Create/Hooks/useMonitorsFetch.jsx index f380bed9a..2a7db202a 100644 --- a/client/src/Pages/StatusPage/Create/Hooks/useMonitorsFetch.jsx +++ b/client/src/Pages/v1/StatusPage/Create/Hooks/useMonitorsFetch.jsx @@ -1,7 +1,7 @@ import { useEffect, useState } from "react"; -import { networkService } from "../../../../main"; +import { networkService } from "../../../../../main.jsx"; import { useSelector } from "react-redux"; -import { createToast } from "../../../../Utils/toastUtils"; +import { createToast } from "../../../../../Utils/toastUtils.jsx"; const useMonitorsFetch = () => { const { user } = useSelector((state) => state.auth); diff --git a/client/src/Pages/StatusPage/Create/index.jsx b/client/src/Pages/v1/StatusPage/Create/index.jsx similarity index 92% rename from client/src/Pages/StatusPage/Create/index.jsx rename to client/src/Pages/v1/StatusPage/Create/index.jsx index d4cbc0fd5..674a158ac 100644 --- a/client/src/Pages/StatusPage/Create/index.jsx +++ b/client/src/Pages/v1/StatusPage/Create/index.jsx @@ -1,23 +1,23 @@ // Components import { Stack, Button, Typography } from "@mui/material"; -import Tabs from "./Components/Tabs"; -import GenericFallback from "../../../Components/GenericFallback"; -import SkeletonLayout from "./Components/Skeleton"; -import Dialog from "../../../Components/Dialog"; -import Breadcrumbs from "../../../Components/Breadcrumbs"; +import Tabs from "./Components/Tabs/index.jsx"; +import GenericFallback from "../../../../Components/GenericFallback/index.jsx"; +import SkeletonLayout from "./Components/Skeleton/index.jsx"; +import Dialog from "../../../../Components/Dialog/index.jsx"; +import Breadcrumbs from "../../../../Components/Breadcrumbs/index.jsx"; //Utils import { useTheme } from "@emotion/react"; import { useState, useEffect, useRef, useCallback } from "react"; -import { statusPageValidation } from "../../../Validation/validation"; -import { buildErrors } from "../../../Validation/error"; -import { useMonitorsFetch } from "./Hooks/useMonitorsFetch"; -import { useCreateStatusPage } from "./Hooks/useCreateStatusPage"; -import { createToast } from "../../../Utils/toastUtils"; +import { statusPageValidation } from "../../../../Validation/validation.js"; +import { buildErrors } from "../../../../Validation/error.js"; +import { useMonitorsFetch } from "./Hooks/useMonitorsFetch.jsx"; +import { useCreateStatusPage } from "./Hooks/useCreateStatusPage.jsx"; +import { createToast } from "../../../../Utils/toastUtils.jsx"; import { useNavigate } from "react-router-dom"; -import { useStatusPageFetch } from "../Status/Hooks/useStatusPageFetch"; +import { useStatusPageFetch } from "../Status/Hooks/useStatusPageFetch.jsx"; import { useParams } from "react-router-dom"; import { useTranslation } from "react-i18next"; -import { useStatusPageDelete } from "../Status/Hooks/useStatusPageDelete"; +import { useStatusPageDelete } from "../Status/Hooks/useStatusPageDelete.jsx"; //Constants const ERROR_TAB_MAPPING = [ ["companyName", "url", "timezone", "color", "isPublished", "logo"], diff --git a/client/src/Pages/StatusPage/Status/Components/AdminLink/index.jsx b/client/src/Pages/v1/StatusPage/Status/Components/AdminLink/index.jsx similarity index 100% rename from client/src/Pages/StatusPage/Status/Components/AdminLink/index.jsx rename to client/src/Pages/v1/StatusPage/Status/Components/AdminLink/index.jsx diff --git a/client/src/Pages/StatusPage/Status/Components/ControlsHeader/index.jsx b/client/src/Pages/v1/StatusPage/Status/Components/ControlsHeader/index.jsx similarity index 92% rename from client/src/Pages/StatusPage/Status/Components/ControlsHeader/index.jsx rename to client/src/Pages/v1/StatusPage/Status/Components/ControlsHeader/index.jsx index beac3b0d2..fa5ed71f1 100644 --- a/client/src/Pages/StatusPage/Status/Components/ControlsHeader/index.jsx +++ b/client/src/Pages/v1/StatusPage/Status/Components/ControlsHeader/index.jsx @@ -1,8 +1,8 @@ // Components import { Box, Stack, Typography, Button } from "@mui/material"; -import Image from "../../../../../Components/Image"; -import SettingsIcon from "../../../../../assets/icons/settings-bold.svg?react"; -import ThemeSwitch from "../../../../../Components/ThemeSwitch"; +import Image from "../../../../../../Components/Image/index.jsx"; +import SettingsIcon from "../../../../../../assets/icons/settings-bold.svg?react"; +import ThemeSwitch from "../../../../../../Components/ThemeSwitch/index.jsx"; import ArrowOutwardIcon from "@mui/icons-material/ArrowOutward"; //Utils import { useTheme } from "@mui/material/styles"; diff --git a/client/src/Pages/StatusPage/Status/Components/MonitorsList/index.jsx b/client/src/Pages/v1/StatusPage/Status/Components/MonitorsList/index.jsx similarity index 82% rename from client/src/Pages/StatusPage/Status/Components/MonitorsList/index.jsx rename to client/src/Pages/v1/StatusPage/Status/Components/MonitorsList/index.jsx index 545974c57..fd04dae66 100644 --- a/client/src/Pages/StatusPage/Status/Components/MonitorsList/index.jsx +++ b/client/src/Pages/v1/StatusPage/Status/Components/MonitorsList/index.jsx @@ -1,12 +1,12 @@ // Components import { Stack, Box } from "@mui/material"; -import Host from "../../../../../Components/Host"; -import StatusPageBarChart from "../../../../../Components/Charts/StatusPageBarChart"; -import { StatusLabel } from "../../../../../Components/Label"; +import Host from "../../../../../../Components/Host/index.jsx"; +import StatusPageBarChart from "../../../../../../Components/Charts/StatusPageBarChart/index.jsx"; +import { StatusLabel } from "../../../../../../Components/Label/index.jsx"; //Utils import { useTheme } from "@mui/material/styles"; -import { useMonitorUtils } from "../../../../../Hooks/useMonitorUtils"; +import { useMonitorUtils } from "../../../../../../Hooks/v1/useMonitorUtils.js"; import PropTypes from "prop-types"; import { useSelector } from "react-redux"; diff --git a/client/src/Pages/StatusPage/Status/Components/Skeleton/index.jsx b/client/src/Pages/v1/StatusPage/Status/Components/Skeleton/index.jsx similarity index 100% rename from client/src/Pages/StatusPage/Status/Components/Skeleton/index.jsx rename to client/src/Pages/v1/StatusPage/Status/Components/Skeleton/index.jsx diff --git a/client/src/Pages/StatusPage/Status/Components/StatusBar/index.jsx b/client/src/Pages/v1/StatusPage/Status/Components/StatusBar/index.jsx similarity index 100% rename from client/src/Pages/StatusPage/Status/Components/StatusBar/index.jsx rename to client/src/Pages/v1/StatusPage/Status/Components/StatusBar/index.jsx diff --git a/client/src/Pages/StatusPage/Status/Hooks/useStatusPageDelete.jsx b/client/src/Pages/v1/StatusPage/Status/Hooks/useStatusPageDelete.jsx similarity index 89% rename from client/src/Pages/StatusPage/Status/Hooks/useStatusPageDelete.jsx rename to client/src/Pages/v1/StatusPage/Status/Hooks/useStatusPageDelete.jsx index 2c39c70b3..038a771ba 100644 --- a/client/src/Pages/StatusPage/Status/Hooks/useStatusPageDelete.jsx +++ b/client/src/Pages/v1/StatusPage/Status/Hooks/useStatusPageDelete.jsx @@ -1,6 +1,6 @@ import { useState } from "react"; -import { networkService } from "../../../../Utils/NetworkService"; -import { createToast } from "../../../../Utils/toastUtils"; +import { networkService } from "../../../../../Utils/NetworkService.js"; +import { createToast } from "../../../../../Utils/toastUtils.jsx"; import { useTranslation } from "react-i18next"; /** diff --git a/client/src/Pages/StatusPage/Status/Hooks/useStatusPageFetch.jsx b/client/src/Pages/v1/StatusPage/Status/Hooks/useStatusPageFetch.jsx similarity index 87% rename from client/src/Pages/StatusPage/Status/Hooks/useStatusPageFetch.jsx rename to client/src/Pages/v1/StatusPage/Status/Hooks/useStatusPageFetch.jsx index ae9c7b579..506082887 100644 --- a/client/src/Pages/StatusPage/Status/Hooks/useStatusPageFetch.jsx +++ b/client/src/Pages/v1/StatusPage/Status/Hooks/useStatusPageFetch.jsx @@ -1,9 +1,9 @@ import { useEffect, useState, useCallback } from "react"; -import { networkService } from "../../../../main"; +import { networkService } from "../../../../../main.jsx"; import { useSelector } from "react-redux"; -import { createToast } from "../../../../Utils/toastUtils"; +import { createToast } from "../../../../../Utils/toastUtils.jsx"; import { useTheme } from "@emotion/react"; -import { useMonitorUtils } from "../../../../Hooks/useMonitorUtils"; +import { useMonitorUtils } from "../../../../../Hooks/v1/useMonitorUtils.js"; const useStatusPageFetch = (isCreate = false, url) => { const [isLoading, setIsLoading] = useState(true); diff --git a/client/src/Pages/StatusPage/Status/index.jsx b/client/src/Pages/v1/StatusPage/Status/index.jsx similarity index 85% rename from client/src/Pages/StatusPage/Status/index.jsx rename to client/src/Pages/v1/StatusPage/Status/index.jsx index a77f2a380..48fc44fc9 100644 --- a/client/src/Pages/StatusPage/Status/index.jsx +++ b/client/src/Pages/v1/StatusPage/Status/index.jsx @@ -1,18 +1,18 @@ // Components import { Typography, Stack } from "@mui/material"; -import GenericFallback from "../../../Components/GenericFallback"; -import AdminLink from "./Components/AdminLink"; -import ControlsHeader from "./Components/ControlsHeader"; -import SkeletonLayout from "./Components/Skeleton"; -import StatusBar from "./Components/StatusBar"; -import MonitorsList from "./Components/MonitorsList"; -import Breadcrumbs from "../../../Components/Breadcrumbs/index.jsx"; -import TextLink from "../../../Components/TextLink"; +import GenericFallback from "../../../../Components/GenericFallback/index.jsx"; +import AdminLink from "./Components/AdminLink/index.jsx"; +import ControlsHeader from "./Components/ControlsHeader/index.jsx"; +import SkeletonLayout from "./Components/Skeleton/index.jsx"; +import StatusBar from "./Components/StatusBar/index.jsx"; +import MonitorsList from "./Components/MonitorsList/index.jsx"; +import Breadcrumbs from "../../../../Components/Breadcrumbs/index.jsx"; +import TextLink from "../../../../Components/TextLink/index.jsx"; // Utils -import { useStatusPageFetch } from "./Hooks/useStatusPageFetch"; +import { useStatusPageFetch } from "./Hooks/useStatusPageFetch.jsx"; import { useTheme } from "@emotion/react"; -import { useIsAdmin } from "../../../Hooks/useIsAdmin"; +import { useIsAdmin } from "../../../../Hooks/v1/useIsAdmin.js"; import { useLocation } from "react-router-dom"; import { useParams } from "react-router-dom"; import { useTranslation } from "react-i18next"; diff --git a/client/src/Pages/StatusPage/StatusPages/Components/StatusPagesTable/index.jsx b/client/src/Pages/v1/StatusPage/StatusPages/Components/StatusPagesTable/index.jsx similarity index 93% rename from client/src/Pages/StatusPage/StatusPages/Components/StatusPagesTable/index.jsx rename to client/src/Pages/v1/StatusPage/StatusPages/Components/StatusPagesTable/index.jsx index 04d7a6cfb..d8d68fb2e 100644 --- a/client/src/Pages/StatusPage/StatusPages/Components/StatusPagesTable/index.jsx +++ b/client/src/Pages/v1/StatusPage/StatusPages/Components/StatusPagesTable/index.jsx @@ -1,7 +1,7 @@ -import DataTable from "../../../../../Components/Table"; +import DataTable from "../../../../../../Components/Table/index.jsx"; import { useTheme } from "@emotion/react"; import { useNavigate } from "react-router-dom"; -import { StatusLabel } from "../../../../../Components/Label"; +import { StatusLabel } from "../../../../../../Components/Label/index.jsx"; import ArrowOutwardIcon from "@mui/icons-material/ArrowOutward"; import { Stack, Typography } from "@mui/material"; import { useTranslation } from "react-i18next"; diff --git a/client/src/Pages/StatusPage/StatusPages/Hooks/useStatusPagesFetch.jsx b/client/src/Pages/v1/StatusPage/StatusPages/Hooks/useStatusPagesFetch.jsx similarity index 86% rename from client/src/Pages/StatusPage/StatusPages/Hooks/useStatusPagesFetch.jsx rename to client/src/Pages/v1/StatusPage/StatusPages/Hooks/useStatusPagesFetch.jsx index b236368c8..89df1b84d 100644 --- a/client/src/Pages/StatusPage/StatusPages/Hooks/useStatusPagesFetch.jsx +++ b/client/src/Pages/v1/StatusPage/StatusPages/Hooks/useStatusPagesFetch.jsx @@ -1,7 +1,7 @@ import { useState, useEffect } from "react"; -import { networkService } from "../../../../main"; +import { networkService } from "../../../../../main.jsx"; import { useSelector } from "react-redux"; -import { createToast } from "../../../../Utils/toastUtils"; +import { createToast } from "../../../../../Utils/toastUtils.jsx"; const useStatusPagesFetch = () => { const { user } = useSelector((state) => state.auth); diff --git a/client/src/Pages/StatusPage/StatusPages/index.jsx b/client/src/Pages/v1/StatusPage/StatusPages/index.jsx similarity index 70% rename from client/src/Pages/StatusPage/StatusPages/index.jsx rename to client/src/Pages/v1/StatusPage/StatusPages/index.jsx index fa26c2b81..8e0c89ce3 100644 --- a/client/src/Pages/StatusPage/StatusPages/index.jsx +++ b/client/src/Pages/v1/StatusPage/StatusPages/index.jsx @@ -1,13 +1,13 @@ // Components import { Stack } from "@mui/material"; -import Breadcrumbs from "../../../Components/Breadcrumbs"; -import MonitorCreateHeader from "../../../Components/MonitorCreateHeader"; -import StatusPagesTable from "./Components/StatusPagesTable"; -import PageStateWrapper from "../../../Components/PageStateWrapper"; +import Breadcrumbs from "../../../../Components/Breadcrumbs/index.jsx"; +import MonitorCreateHeader from "../../../../Components/MonitorCreateHeader/index.jsx"; +import StatusPagesTable from "./Components/StatusPagesTable/index.jsx"; +import PageStateWrapper from "../../../../Components/PageStateWrapper/index.jsx"; // Utils import { useTheme } from "@emotion/react"; -import { useStatusPagesFetch } from "./Hooks/useStatusPagesFetch"; -import { useIsAdmin } from "../../../Hooks/useIsAdmin"; +import { useStatusPagesFetch } from "./Hooks/useStatusPagesFetch.jsx"; +import { useIsAdmin } from "../../../../Hooks/v1/useIsAdmin.js"; const BREADCRUMBS = [{ name: `Status Pages`, path: "" }]; const StatusPages = () => { diff --git a/client/src/Pages/Uptime/BulkImport/Upload.jsx b/client/src/Pages/v1/Uptime/BulkImport/Upload.jsx similarity index 100% rename from client/src/Pages/Uptime/BulkImport/Upload.jsx rename to client/src/Pages/v1/Uptime/BulkImport/Upload.jsx diff --git a/client/src/Pages/Uptime/BulkImport/index.jsx b/client/src/Pages/v1/Uptime/BulkImport/index.jsx similarity index 89% rename from client/src/Pages/Uptime/BulkImport/index.jsx rename to client/src/Pages/v1/Uptime/BulkImport/index.jsx index 9490b4eab..412ba0f97 100644 --- a/client/src/Pages/Uptime/BulkImport/index.jsx +++ b/client/src/Pages/v1/Uptime/BulkImport/index.jsx @@ -5,14 +5,14 @@ import { useState } from "react"; import { Box, Stack, Typography, Button, Link } from "@mui/material"; //Components -import { createToast } from "../../../Utils/toastUtils"; -import Breadcrumbs from "../../../Components/Breadcrumbs"; -import ConfigBox from "../../../Components/ConfigBox"; -import UploadFile from "./Upload"; +import { createToast } from "../../../../Utils/toastUtils.jsx"; +import Breadcrumbs from "../../../../Components/Breadcrumbs/index.jsx"; +import ConfigBox from "../../../../Components/ConfigBox/index.jsx"; +import UploadFile from "./Upload.jsx"; import { useSelector } from "react-redux"; import { useNavigate } from "react-router-dom"; import { Trans, useTranslation } from "react-i18next"; -import { useCreateBulkMonitors } from "../../../Hooks/monitorHooks"; +import { useCreateBulkMonitors } from "../../../../Hooks/v1/monitorHooks.js"; const BulkImport = () => { const theme = useTheme(); diff --git a/client/src/Pages/Uptime/Create/index.css b/client/src/Pages/v1/Uptime/Create/index.css similarity index 100% rename from client/src/Pages/Uptime/Create/index.css rename to client/src/Pages/v1/Uptime/Create/index.css diff --git a/client/src/Pages/Uptime/Create/index.jsx b/client/src/Pages/v1/Uptime/Create/index.jsx similarity index 94% rename from client/src/Pages/Uptime/Create/index.jsx rename to client/src/Pages/v1/Uptime/Create/index.jsx index 3f905fa11..6bb2a3b33 100644 --- a/client/src/Pages/Uptime/Create/index.jsx +++ b/client/src/Pages/v1/Uptime/Create/index.jsx @@ -9,31 +9,31 @@ import { Tooltip, Typography, } from "@mui/material"; -import Breadcrumbs from "../../../Components/Breadcrumbs"; -import TextInput from "../../../Components/Inputs/TextInput"; -import { HttpAdornment } from "../../../Components/Inputs/TextInput/Adornments"; -import Radio from "../../../Components/Inputs/Radio"; -import Select from "../../../Components/Inputs/Select"; -import ConfigBox from "../../../Components/ConfigBox"; -import NotificationsConfig from "../../../Components/NotificationConfig"; -import Checkbox from "../../../Components/Inputs/Checkbox"; -import Dialog from "../../../Components/Dialog"; -import PulseDot from "../../../Components/Animated/PulseDot"; -import SkeletonLayout from "./skeleton"; +import Breadcrumbs from "../../../../Components/Breadcrumbs/index.jsx"; +import TextInput from "../../../../Components/Inputs/TextInput/index.jsx"; +import { HttpAdornment } from "../../../../Components/Inputs/TextInput/Adornments/index.jsx"; +import Radio from "../../../../Components/Inputs/Radio/index.jsx"; +import Select from "../../../../Components/Inputs/Select/index.jsx"; +import ConfigBox from "../../../../Components/ConfigBox/index.jsx"; +import NotificationsConfig from "../../../../Components/NotificationConfig/index.jsx"; +import Checkbox from "../../../../Components/Inputs/Checkbox/index.jsx"; +import Dialog from "../../../../Components/Dialog/index.jsx"; +import PulseDot from "../../../../Components/Animated/PulseDot.jsx"; +import SkeletonLayout from "./skeleton.jsx"; // Utils import PropTypes from "prop-types"; import { useTheme } from "@emotion/react"; import { useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; -import { monitorValidation } from "../../../Validation/validation"; -import { createToast } from "../../../Utils/toastUtils"; +import { monitorValidation } from "../../../../Validation/validation.js"; +import { createToast } from "../../../../Utils/toastUtils.jsx"; import { PauseOutlined as PauseOutlinedIcon, PlayArrowOutlined as PlayArrowOutlinedIcon, } from "@mui/icons-material"; -import { useMonitorUtils } from "../../../Hooks/useMonitorUtils"; -import { useGetNotificationsByTeamId } from "../../../Hooks/useNotifications"; +import { useMonitorUtils } from "../../../../Hooks/v1/useMonitorUtils.js"; +import { useGetNotificationsByTeamId } from "../../../../Hooks/v1/useNotifications.js"; import { useParams } from "react-router-dom"; import { useCreateMonitor, @@ -42,7 +42,7 @@ import { usePauseMonitor, useFetchMonitorById, useFetchMonitorGames, -} from "../../../Hooks/monitorHooks"; +} from "../../../../Hooks/v1/monitorHooks.js"; /** * Create page renders monitor creation or configuration views. diff --git a/client/src/Pages/Uptime/Create/skeleton.jsx b/client/src/Pages/v1/Uptime/Create/skeleton.jsx similarity index 100% rename from client/src/Pages/Uptime/Create/skeleton.jsx rename to client/src/Pages/v1/Uptime/Create/skeleton.jsx diff --git a/client/src/Pages/Uptime/Details/Components/ChartBoxes/index.jsx b/client/src/Pages/v1/Uptime/Details/Components/ChartBoxes/index.jsx similarity index 86% rename from client/src/Pages/Uptime/Details/Components/ChartBoxes/index.jsx rename to client/src/Pages/v1/Uptime/Details/Components/ChartBoxes/index.jsx index b60a4dd04..c6b76c289 100644 --- a/client/src/Pages/Uptime/Details/Components/ChartBoxes/index.jsx +++ b/client/src/Pages/v1/Uptime/Details/Components/ChartBoxes/index.jsx @@ -1,15 +1,15 @@ // Components import { Stack, Typography, Box } from "@mui/material"; -import ChartBox from "../../../../../Components/Charts/ChartBox"; -import UptimeIcon from "../../../../../assets/icons/uptime-icon.svg?react"; -import IncidentsIcon from "../../../../../assets/icons/incidents.svg?react"; -import AverageResponseIcon from "../../../../../assets/icons/average-response-icon.svg?react"; -import UpBarChart from "../Charts/UpBarChart"; -import DownBarChart from "../Charts/DownBarChart"; -import ResponseGaugeChart from "../Charts/ResponseGaugeChart"; -import SkeletonLayout from "./skeleton"; +import ChartBox from "../../../../../../Components/Charts/ChartBox/index.jsx"; +import UptimeIcon from "../../../../../../assets/icons/uptime-icon.svg?react"; +import IncidentsIcon from "../../../../../../assets/icons/incidents.svg?react"; +import AverageResponseIcon from "../../../../../../assets/icons/average-response-icon.svg?react"; +import UpBarChart from "../Charts/UpBarChart.jsx"; +import DownBarChart from "../Charts/DownBarChart.jsx"; +import ResponseGaugeChart from "../Charts/ResponseGaugeChart.jsx"; +import SkeletonLayout from "./skeleton.jsx"; // Utils -import { formatDateWithTz } from "../../../../../Utils/timeUtils"; +import { formatDateWithTz } from "../../../../../../Utils/timeUtils.js"; import PropTypes from "prop-types"; import { useTheme } from "@emotion/react"; import { useState } from "react"; diff --git a/client/src/Pages/Uptime/Details/Components/ChartBoxes/skeleton.jsx b/client/src/Pages/v1/Uptime/Details/Components/ChartBoxes/skeleton.jsx similarity index 100% rename from client/src/Pages/Uptime/Details/Components/ChartBoxes/skeleton.jsx rename to client/src/Pages/v1/Uptime/Details/Components/ChartBoxes/skeleton.jsx diff --git a/client/src/Pages/Uptime/Details/Components/Charts/CustomLabels.jsx b/client/src/Pages/v1/Uptime/Details/Components/Charts/CustomLabels.jsx similarity index 93% rename from client/src/Pages/Uptime/Details/Components/Charts/CustomLabels.jsx rename to client/src/Pages/v1/Uptime/Details/Components/Charts/CustomLabels.jsx index 8df33953f..c9c1ff58d 100644 --- a/client/src/Pages/Uptime/Details/Components/Charts/CustomLabels.jsx +++ b/client/src/Pages/v1/Uptime/Details/Components/Charts/CustomLabels.jsx @@ -1,6 +1,6 @@ import PropTypes from "prop-types"; import { useSelector } from "react-redux"; -import { formatDateWithTz } from "../../../../../Utils/timeUtils"; +import { formatDateWithTz } from "../../../../../../Utils/timeUtils.js"; const CustomLabels = ({ x, width, height, firstDataPoint, lastDataPoint, type }) => { const uiTimezone = useSelector((state) => state.ui.timezone); diff --git a/client/src/Pages/Uptime/Details/Components/Charts/DownBarChart.jsx b/client/src/Pages/v1/Uptime/Details/Components/Charts/DownBarChart.jsx similarity index 97% rename from client/src/Pages/Uptime/Details/Components/Charts/DownBarChart.jsx rename to client/src/Pages/v1/Uptime/Details/Components/Charts/DownBarChart.jsx index 662abc831..c5e08ecca 100644 --- a/client/src/Pages/Uptime/Details/Components/Charts/DownBarChart.jsx +++ b/client/src/Pages/v1/Uptime/Details/Components/Charts/DownBarChart.jsx @@ -2,7 +2,7 @@ import { memo, useState } from "react"; import { useTheme } from "@mui/material"; import { ResponsiveContainer, BarChart, XAxis, Bar, Cell } from "recharts"; import PropTypes from "prop-types"; -import CustomLabels from "./CustomLabels"; +import CustomLabels from "./CustomLabels.jsx"; const DownBarChart = memo(({ groupedDownChecks = [], type, onBarHover }) => { const theme = useTheme(); diff --git a/client/src/Pages/Uptime/Details/Components/Charts/ResponseGaugeChart.jsx b/client/src/Pages/v1/Uptime/Details/Components/Charts/ResponseGaugeChart.jsx similarity index 100% rename from client/src/Pages/Uptime/Details/Components/Charts/ResponseGaugeChart.jsx rename to client/src/Pages/v1/Uptime/Details/Components/Charts/ResponseGaugeChart.jsx diff --git a/client/src/Pages/Uptime/Details/Components/Charts/ResponseTimeChart.jsx b/client/src/Pages/v1/Uptime/Details/Components/Charts/ResponseTimeChart.jsx similarity index 60% rename from client/src/Pages/Uptime/Details/Components/Charts/ResponseTimeChart.jsx rename to client/src/Pages/v1/Uptime/Details/Components/Charts/ResponseTimeChart.jsx index e2d8dea80..f306dfd76 100644 --- a/client/src/Pages/Uptime/Details/Components/Charts/ResponseTimeChart.jsx +++ b/client/src/Pages/v1/Uptime/Details/Components/Charts/ResponseTimeChart.jsx @@ -1,7 +1,7 @@ -import ChartBox from "../../../../../Components/Charts/ChartBox"; -import MonitorDetailsAreaChart from "../../../../../Components/Charts/MonitorDetailsAreaChart"; -import ResponseTimeIcon from "../../../../../assets/icons/response-time-icon.svg?react"; -import SkeletonLayout from "./ResponseTimeChartSkeleton"; +import ChartBox from "../../../../../../Components/Charts/ChartBox/index.jsx"; +import MonitorDetailsAreaChart from "../../../../../../Components/Charts/MonitorDetailsAreaChart/index.jsx"; +import ResponseTimeIcon from "../../../../../../assets/icons/response-time-icon.svg?react"; +import SkeletonLayout from "./ResponseTimeChartSkeleton.jsx"; import PropTypes from "prop-types"; const ResponseTImeChart = ({ isLoading = false, groupedChecks = [], dateRange }) => { diff --git a/client/src/Pages/Uptime/Details/Components/Charts/ResponseTimeChartSkeleton.jsx b/client/src/Pages/v1/Uptime/Details/Components/Charts/ResponseTimeChartSkeleton.jsx similarity index 100% rename from client/src/Pages/Uptime/Details/Components/Charts/ResponseTimeChartSkeleton.jsx rename to client/src/Pages/v1/Uptime/Details/Components/Charts/ResponseTimeChartSkeleton.jsx diff --git a/client/src/Pages/Uptime/Details/Components/Charts/UpBarChart.jsx b/client/src/Pages/v1/Uptime/Details/Components/Charts/UpBarChart.jsx similarity index 98% rename from client/src/Pages/Uptime/Details/Components/Charts/UpBarChart.jsx rename to client/src/Pages/v1/Uptime/Details/Components/Charts/UpBarChart.jsx index 317147b74..8f1af8912 100644 --- a/client/src/Pages/Uptime/Details/Components/Charts/UpBarChart.jsx +++ b/client/src/Pages/v1/Uptime/Details/Components/Charts/UpBarChart.jsx @@ -2,7 +2,7 @@ import { memo, useState } from "react"; import { useTheme } from "@mui/material"; import { ResponsiveContainer, BarChart, XAxis, Bar, Cell } from "recharts"; import PropTypes from "prop-types"; -import CustomLabels from "./CustomLabels"; +import CustomLabels from "./CustomLabels.jsx"; const getThemeColor = (responseTime) => { if (responseTime < 200) { diff --git a/client/src/Pages/Uptime/Details/Components/ResponseTable/index.jsx b/client/src/Pages/v1/Uptime/Details/Components/ResponseTable/index.jsx similarity index 76% rename from client/src/Pages/Uptime/Details/Components/ResponseTable/index.jsx rename to client/src/Pages/v1/Uptime/Details/Components/ResponseTable/index.jsx index 4d482aa07..cc5745a26 100644 --- a/client/src/Pages/Uptime/Details/Components/ResponseTable/index.jsx +++ b/client/src/Pages/v1/Uptime/Details/Components/ResponseTable/index.jsx @@ -1,12 +1,12 @@ -import ChartBox from "../../../../../Components/Charts/ChartBox"; +import ChartBox from "../../../../../../Components/Charts/ChartBox/index.jsx"; import PropTypes from "prop-types"; -import HistoryIcon from "../../../../../assets/icons/history-icon.svg?react"; -import Table from "../../../../../Components/Table"; -import TablePagination from "../../../../../Components/Table/TablePagination"; -import { StatusLabel } from "../../../../../Components/Label"; +import HistoryIcon from "../../../../../../assets/icons/history-icon.svg?react"; +import Table from "../../../../../../Components/Table/index.jsx"; +import TablePagination from "../../../../../../Components/Table/TablePagination/index.jsx"; +import { StatusLabel } from "../../../../../../Components/Label/index.jsx"; import { useTranslation } from "react-i18next"; -import { formatDateWithTz } from "../../../../../Utils/timeUtils"; -import SkeletonLayout from "./skeleton"; +import { formatDateWithTz } from "../../../../../../Utils/timeUtils.js"; +import SkeletonLayout from "./skeleton.jsx"; const ResponseTable = ({ isLoading = false, checks = [], diff --git a/client/src/Pages/Uptime/Details/Components/ResponseTable/skeleton.jsx b/client/src/Pages/v1/Uptime/Details/Components/ResponseTable/skeleton.jsx similarity index 100% rename from client/src/Pages/Uptime/Details/Components/ResponseTable/skeleton.jsx rename to client/src/Pages/v1/Uptime/Details/Components/ResponseTable/skeleton.jsx diff --git a/client/src/Pages/Uptime/Details/Components/UptimeStatusBoxes/index.jsx b/client/src/Pages/v1/Uptime/Details/Components/UptimeStatusBoxes/index.jsx similarity index 85% rename from client/src/Pages/Uptime/Details/Components/UptimeStatusBoxes/index.jsx rename to client/src/Pages/v1/Uptime/Details/Components/UptimeStatusBoxes/index.jsx index abeb251fe..f0988466c 100644 --- a/client/src/Pages/Uptime/Details/Components/UptimeStatusBoxes/index.jsx +++ b/client/src/Pages/v1/Uptime/Details/Components/UptimeStatusBoxes/index.jsx @@ -1,11 +1,11 @@ -import StatusBoxes from "../../../../../Components/StatusBoxes"; -import StatBox from "../../../../../Components/StatBox"; +import StatusBoxes from "../../../../../../Components/StatusBoxes/index.jsx"; +import StatBox from "../../../../../../Components/StatBox/index.jsx"; import PropTypes from "prop-types"; -import { getHumanReadableDuration } from "../../../../../Utils/timeUtils"; +import { getHumanReadableDuration } from "../../../../../../Utils/timeUtils.js"; import { useTheme } from "@mui/material/styles"; import { Typography } from "@mui/material"; -import { useMonitorUtils } from "../../../../../Hooks/useMonitorUtils"; +import { useMonitorUtils } from "../../../../../../Hooks/v1/useMonitorUtils.js"; const UptimeStatusBoxes = ({ isLoading = false, diff --git a/client/src/Pages/Uptime/Details/Hooks/useCertificateFetch.jsx b/client/src/Pages/v1/Uptime/Details/Hooks/useCertificateFetch.jsx similarity index 84% rename from client/src/Pages/Uptime/Details/Hooks/useCertificateFetch.jsx rename to client/src/Pages/v1/Uptime/Details/Hooks/useCertificateFetch.jsx index ad0db6f70..f85111f97 100644 --- a/client/src/Pages/Uptime/Details/Hooks/useCertificateFetch.jsx +++ b/client/src/Pages/v1/Uptime/Details/Hooks/useCertificateFetch.jsx @@ -1,7 +1,7 @@ -import { logger } from "../../../../Utils/Logger"; +import { logger } from "../../../../../Utils/Logger.js"; import { useEffect, useState } from "react"; -import { networkService } from "../../../../main"; -import { formatDateWithTz } from "../../../../Utils/timeUtils"; +import { networkService } from "../../../../../main.jsx"; +import { formatDateWithTz } from "../../../../../Utils/timeUtils.js"; const useCertificateFetch = ({ monitor, diff --git a/client/src/Pages/Uptime/Details/index.jsx b/client/src/Pages/v1/Uptime/Details/index.jsx similarity index 83% rename from client/src/Pages/Uptime/Details/index.jsx rename to client/src/Pages/v1/Uptime/Details/index.jsx index c72a5756f..4e773dc2b 100644 --- a/client/src/Pages/Uptime/Details/index.jsx +++ b/client/src/Pages/v1/Uptime/Details/index.jsx @@ -1,12 +1,12 @@ // Components -import Breadcrumbs from "../../../Components/Breadcrumbs"; -import MonitorDetailsControlHeader from "../../../Components/MonitorDetailsControlHeader"; -import MonitorTimeFrameHeader from "../../../Components/MonitorTimeFrameHeader"; -import ChartBoxes from "./Components/ChartBoxes"; -import ResponseTimeChart from "./Components/Charts/ResponseTimeChart"; -import ResponseTable from "./Components/ResponseTable"; -import UptimeStatusBoxes from "./Components/UptimeStatusBoxes"; -import GenericFallback from "../../../Components/GenericFallback"; +import Breadcrumbs from "../../../../Components/Breadcrumbs/index.jsx"; +import MonitorDetailsControlHeader from "../../../../Components/MonitorDetailsControlHeader/index.jsx"; +import MonitorTimeFrameHeader from "../../../../Components/MonitorTimeFrameHeader/index.jsx"; +import ChartBoxes from "./Components/ChartBoxes/index.jsx"; +import ResponseTimeChart from "./Components/Charts/ResponseTimeChart.jsx"; +import ResponseTable from "./Components/ResponseTable/index.jsx"; +import UptimeStatusBoxes from "./Components/UptimeStatusBoxes/index.jsx"; +import GenericFallback from "../../../../Components/GenericFallback/index.jsx"; import Stack from "@mui/material/Stack"; import Typography from "@mui/material/Typography"; @@ -15,10 +15,10 @@ import { useState } from "react"; import { useParams } from "react-router-dom"; import { useSelector } from "react-redux"; import { useTheme } from "@emotion/react"; -import { useIsAdmin } from "../../../Hooks/useIsAdmin"; -import { useFetchUptimeMonitorById } from "../../../Hooks/monitorHooks"; -import useCertificateFetch from "./Hooks/useCertificateFetch"; -import { useFetchChecksByMonitor } from "../../../Hooks/checkHooks"; +import { useIsAdmin } from "../../../../Hooks/v1/useIsAdmin.js"; +import { useFetchUptimeMonitorById } from "../../../../Hooks/v1/monitorHooks.js"; +import useCertificateFetch from "./Hooks/useCertificateFetch.jsx"; +import { useFetchChecksByMonitor } from "../../../../Hooks/v1/checkHooks.js"; import { useTranslation } from "react-i18next"; // Constants diff --git a/client/src/Pages/Uptime/Monitors/Components/Filter/index.jsx b/client/src/Pages/v1/Uptime/Monitors/Components/Filter/index.jsx similarity index 98% rename from client/src/Pages/Uptime/Monitors/Components/Filter/index.jsx rename to client/src/Pages/v1/Uptime/Monitors/Components/Filter/index.jsx index 0efb06b6c..85c4a8540 100644 --- a/client/src/Pages/Uptime/Monitors/Components/Filter/index.jsx +++ b/client/src/Pages/v1/Uptime/Monitors/Components/Filter/index.jsx @@ -1,6 +1,6 @@ import { useTheme } from "@emotion/react"; import PropTypes from "prop-types"; -import FilterHeader from "../../../../../Components/FilterHeader"; +import FilterHeader from "../../../../../../Components/FilterHeader/index.jsx"; import { useMemo } from "react"; import { Box, Button } from "@mui/material"; import ClearIcon from "@mui/icons-material/Clear"; diff --git a/client/src/Pages/Uptime/Monitors/Components/LoadingSpinner/index.jsx b/client/src/Pages/v1/Uptime/Monitors/Components/LoadingSpinner/index.jsx similarity index 100% rename from client/src/Pages/Uptime/Monitors/Components/LoadingSpinner/index.jsx rename to client/src/Pages/v1/Uptime/Monitors/Components/LoadingSpinner/index.jsx diff --git a/client/src/Pages/Uptime/Monitors/Components/SearchComponent/index.jsx b/client/src/Pages/v1/Uptime/Monitors/Components/SearchComponent/index.jsx similarity index 89% rename from client/src/Pages/Uptime/Monitors/Components/SearchComponent/index.jsx rename to client/src/Pages/v1/Uptime/Monitors/Components/SearchComponent/index.jsx index 0c6eec854..3339102a2 100644 --- a/client/src/Pages/Uptime/Monitors/Components/SearchComponent/index.jsx +++ b/client/src/Pages/v1/Uptime/Monitors/Components/SearchComponent/index.jsx @@ -1,7 +1,7 @@ import { useState } from "react"; -import Search from "../../../../../Components/Inputs/Search"; +import Search from "../../../../../../Components/Inputs/Search/index.jsx"; import { Box } from "@mui/material"; -import useDebounce from "../../Hooks/useDebounce"; +import useDebounce from "../../Hooks/useDebounce.jsx"; import { useEffect, useRef } from "react"; import PropTypes from "prop-types"; diff --git a/client/src/Pages/Uptime/Monitors/Components/Skeleton/index.jsx b/client/src/Pages/v1/Uptime/Monitors/Components/Skeleton/index.jsx similarity index 100% rename from client/src/Pages/Uptime/Monitors/Components/Skeleton/index.jsx rename to client/src/Pages/v1/Uptime/Monitors/Components/Skeleton/index.jsx diff --git a/client/src/Pages/Uptime/Monitors/Components/StatusBoxes/index.jsx b/client/src/Pages/v1/Uptime/Monitors/Components/StatusBoxes/index.jsx similarity index 91% rename from client/src/Pages/Uptime/Monitors/Components/StatusBoxes/index.jsx rename to client/src/Pages/v1/Uptime/Monitors/Components/StatusBoxes/index.jsx index 9cd29c4ca..5cba9e3b1 100644 --- a/client/src/Pages/Uptime/Monitors/Components/StatusBoxes/index.jsx +++ b/client/src/Pages/v1/Uptime/Monitors/Components/StatusBoxes/index.jsx @@ -1,9 +1,9 @@ import PropTypes from "prop-types"; import { Stack } from "@mui/material"; -import StatusBox from "./statusBox"; +import StatusBox from "./statusBox.jsx"; import { useTheme } from "@emotion/react"; import { useTranslation } from "react-i18next"; -import SkeletonLayout from "./skeleton"; +import SkeletonLayout from "./skeleton.jsx"; const StatusBoxes = ({ shouldRender, monitorsSummary }) => { const theme = useTheme(); diff --git a/client/src/Pages/Uptime/Monitors/Components/StatusBoxes/skeleton.jsx b/client/src/Pages/v1/Uptime/Monitors/Components/StatusBoxes/skeleton.jsx similarity index 100% rename from client/src/Pages/Uptime/Monitors/Components/StatusBoxes/skeleton.jsx rename to client/src/Pages/v1/Uptime/Monitors/Components/StatusBoxes/skeleton.jsx diff --git a/client/src/Pages/Uptime/Monitors/Components/StatusBoxes/statusBox.jsx b/client/src/Pages/v1/Uptime/Monitors/Components/StatusBoxes/statusBox.jsx similarity index 90% rename from client/src/Pages/Uptime/Monitors/Components/StatusBoxes/statusBox.jsx rename to client/src/Pages/v1/Uptime/Monitors/Components/StatusBoxes/statusBox.jsx index 78d555409..c745e7935 100644 --- a/client/src/Pages/Uptime/Monitors/Components/StatusBoxes/statusBox.jsx +++ b/client/src/Pages/v1/Uptime/Monitors/Components/StatusBoxes/statusBox.jsx @@ -1,9 +1,9 @@ import PropTypes from "prop-types"; import { useTheme } from "@emotion/react"; import { Box, Stack, Typography } from "@mui/material"; -import Arrow from "../../../../../assets/icons/top-right-arrow.svg?react"; -import Background from "../../../../../assets/Images/background-grid.svg?react"; -import ClockSnooze from "../../../../../assets/icons/clock-snooze.svg?react"; +import Arrow from "../../../../../../assets/icons/top-right-arrow.svg?react"; +import Background from "../../../../../../assets/Images/background-grid.svg?react"; +import ClockSnooze from "../../../../../../assets/icons/clock-snooze.svg?react"; const StatusBox = ({ title, value, status }) => { const theme = useTheme(); diff --git a/client/src/Pages/Uptime/Monitors/Components/UptimeDataTable/index.jsx b/client/src/Pages/v1/Uptime/Monitors/Components/UptimeDataTable/index.jsx similarity index 90% rename from client/src/Pages/Uptime/Monitors/Components/UptimeDataTable/index.jsx rename to client/src/Pages/v1/Uptime/Monitors/Components/UptimeDataTable/index.jsx index 63a40f85a..575647eab 100644 --- a/client/src/Pages/Uptime/Monitors/Components/UptimeDataTable/index.jsx +++ b/client/src/Pages/v1/Uptime/Monitors/Components/UptimeDataTable/index.jsx @@ -1,19 +1,19 @@ // Components import { Box, Stack } from "@mui/material"; -import DataTable from "../../../../../Components/Table"; +import DataTable from "../../../../../../Components/Table/index.jsx"; import ArrowDownwardRoundedIcon from "@mui/icons-material/ArrowDownwardRounded"; import ArrowUpwardRoundedIcon from "@mui/icons-material/ArrowUpwardRounded"; -import Host from "../../../../../Components/Host"; -import { StatusLabel } from "../../../../../Components/Label"; -import BarChart from "../../../../../Components/Charts/BarChart"; -import ActionsMenu from "../../../../../Components/ActionsMenu"; +import Host from "../../../../../../Components/Host/index.jsx"; +import { StatusLabel } from "../../../../../../Components/Label/index.jsx"; +import BarChart from "../../../../../../Components/Charts/BarChart/index.jsx"; +import ActionsMenu from "../../../../../../Components/ActionsMenu/index.jsx"; -import LoadingSpinner from "../LoadingSpinner"; -import TableSkeleton from "../../../../../Components/Table/skeleton"; +import LoadingSpinner from "../LoadingSpinner/index.jsx"; +import TableSkeleton from "../../../../../../Components/Table/skeleton.jsx"; // Utils import { useTheme } from "@emotion/react"; -import { useMonitorUtils } from "../../../../../Hooks/useMonitorUtils"; +import { useMonitorUtils } from "../../../../../../Hooks/v1/useMonitorUtils.js"; import { useNavigate } from "react-router-dom"; import PropTypes from "prop-types"; import { useTranslation } from "react-i18next"; diff --git a/client/src/Pages/Uptime/Monitors/Hooks/useDebounce.jsx b/client/src/Pages/v1/Uptime/Monitors/Hooks/useDebounce.jsx similarity index 100% rename from client/src/Pages/Uptime/Monitors/Hooks/useDebounce.jsx rename to client/src/Pages/v1/Uptime/Monitors/Hooks/useDebounce.jsx diff --git a/client/src/Pages/Uptime/Monitors/index.jsx b/client/src/Pages/v1/Uptime/Monitors/index.jsx similarity index 86% rename from client/src/Pages/Uptime/Monitors/index.jsx rename to client/src/Pages/v1/Uptime/Monitors/index.jsx index 7f28c1cd7..dae0bc2d5 100644 --- a/client/src/Pages/Uptime/Monitors/index.jsx +++ b/client/src/Pages/v1/Uptime/Monitors/index.jsx @@ -4,32 +4,32 @@ // 2a.List of monitors must have the total number of monitors that match. // Components -import Breadcrumbs from "../../../Components/Breadcrumbs"; -import Greeting from "../../../Utils/greeting"; -import StatusBoxes from "./Components/StatusBoxes"; -import UptimeDataTable from "./Components/UptimeDataTable"; -import Pagination from "../../../Components/Table/TablePagination"; -import CreateMonitorHeader from "../../../Components/MonitorCreateHeader"; -import SearchComponent from "./Components/SearchComponent"; -import Filter from "./Components/Filter"; -import PageStateWrapper from "../../../Components/PageStateWrapper"; +import Breadcrumbs from "../../../../Components/Breadcrumbs/index.jsx"; +import Greeting from "../../../../Utils/greeting.jsx"; +import StatusBoxes from "./Components/StatusBoxes/index.jsx"; +import UptimeDataTable from "./Components/UptimeDataTable/index.jsx"; +import Pagination from "../../../../Components/Table/TablePagination/index.jsx"; +import CreateMonitorHeader from "../../../../Components/MonitorCreateHeader/index.jsx"; +import SearchComponent from "./Components/SearchComponent/index.jsx"; +import Filter from "./Components/Filter/index.jsx"; +import PageStateWrapper from "../../../../Components/PageStateWrapper/index.jsx"; -import MonitorCountHeader from "../../../Components/MonitorCountHeader"; +import MonitorCountHeader from "../../../../Components/MonitorCountHeader/index.jsx"; // MUI Components import { Stack, Box, Button } from "@mui/material"; // Utils import { useState, useCallback, useEffect } from "react"; -import { useIsAdmin } from "../../../Hooks/useIsAdmin"; +import { useIsAdmin } from "../../../../Hooks/v1/useIsAdmin.js"; import { useTheme } from "@emotion/react"; import { useNavigate } from "react-router-dom"; import { useSelector, useDispatch } from "react-redux"; -import { setRowsPerPage } from "../../../Features/UI/uiSlice"; +import { setRowsPerPage } from "../../../../Features/UI/uiSlice.js"; import PropTypes from "prop-types"; import { useFetchMonitorsWithSummary, useFetchMonitorsWithChecks, -} from "../../../Hooks/monitorHooks"; +} from "../../../../Hooks/v1/monitorHooks.js"; import { useTranslation } from "react-i18next"; const TYPES = ["http", "ping", "docker", "port", "game"]; diff --git a/client/src/Routes/index.jsx b/client/src/Routes/index.jsx index 7ddcd7e3c..0de847cb7 100644 --- a/client/src/Routes/index.jsx +++ b/client/src/Routes/index.jsx @@ -1,57 +1,57 @@ import { Navigate, Route, Routes as LibRoutes } from "react-router"; import HomeLayout from "../Components/Layouts/HomeLayout"; -import NotFound from "../Pages/NotFound"; +import NotFound from "../Pages/v1/NotFound"; // Auth -import AuthLogin from "../Pages/Auth/Login"; -import AuthRegister from "../Pages/Auth/Register/"; -import AuthForgotPassword from "../Pages/Auth/ForgotPassword"; -import AuthCheckEmail from "../Pages/Auth/CheckEmail"; -import AuthSetNewPassword from "../Pages/Auth/SetNewPassword"; -import AuthNewPasswordConfirmed from "../Pages/Auth/NewPasswordConfirmed"; +import AuthLogin from "../Pages/v1/Auth/Login"; +import AuthRegister from "../Pages/v1/Auth/Register/"; +import AuthForgotPassword from "../Pages/v1/Auth/ForgotPassword"; +import AuthCheckEmail from "../Pages/v1/Auth/CheckEmail"; +import AuthSetNewPassword from "../Pages/v1/Auth/SetNewPassword"; +import AuthNewPasswordConfirmed from "../Pages/v1/Auth/NewPasswordConfirmed"; // Uptime -import Uptime from "../Pages/Uptime/Monitors"; -import UptimeDetails from "../Pages/Uptime/Details"; -import UptimeCreate from "../Pages/Uptime/Create"; +import Uptime from "../Pages/v1/Uptime/Monitors"; +import UptimeDetails from "../Pages/v1/Uptime/Details"; +import UptimeCreate from "../Pages/v1/Uptime/Create"; // PageSpeed -import PageSpeed from "../Pages/PageSpeed/Monitors"; -import PageSpeedDetails from "../Pages/PageSpeed/Details"; -import PageSpeedCreate from "../Pages/PageSpeed/Create"; +import PageSpeed from "../Pages/v1/PageSpeed/Monitors"; +import PageSpeedDetails from "../Pages/v1/PageSpeed/Details"; +import PageSpeedCreate from "../Pages/v1/PageSpeed/Create"; // Infrastructure -import Infrastructure from "../Pages/Infrastructure/Monitors"; -import InfrastructureCreate from "../Pages/Infrastructure/Create"; -import InfrastructureDetails from "../Pages/Infrastructure/Details"; +import Infrastructure from "../Pages/v1/Infrastructure/Monitors"; +import InfrastructureCreate from "../Pages/v1/Infrastructure/Create"; +import InfrastructureDetails from "../Pages/v1/Infrastructure/Details"; // Server Status -import ServerUnreachable from "../Pages/ServerUnreachable"; +import ServerUnreachable from "../Pages/v1/ServerUnreachable.jsx"; // Incidents -import Incidents from "../Pages/Incidents"; +import Incidents from "../Pages/v1/Incidents"; // Status pages -import CreateStatus from "../Pages/StatusPage/Create"; -import StatusPages from "../Pages/StatusPage/StatusPages"; -import Status from "../Pages/StatusPage/Status"; +import CreateStatus from "../Pages/v1/StatusPage/Create"; +import StatusPages from "../Pages/v1/StatusPage/StatusPages"; +import Status from "../Pages/v1/StatusPage/Status"; -import Notifications from "../Pages/Notifications"; -import CreateNotifications from "../Pages/Notifications/create"; +import Notifications from "../Pages/v1/Notifications"; +import CreateNotifications from "../Pages/v1/Notifications/create"; // Settings -import Account from "../Pages/Account"; -import EditUser from "../Pages/Account/EditUser"; -import Settings from "../Pages/Settings"; +import Account from "../Pages/v1/Account"; +import EditUser from "../Pages/v1/Account/EditUser"; +import Settings from "../Pages/v1/Settings"; -import Maintenance from "../Pages/Maintenance"; +import Maintenance from "../Pages/v1/Maintenance"; import ProtectedRoute from "../Components/ProtectedRoute"; import RoleProtectedRoute from "../Components/RoleProtectedRoute"; -import CreateNewMaintenanceWindow from "../Pages/Maintenance/CreateMaintenance"; +import CreateNewMaintenanceWindow from "../Pages/v1/Maintenance/CreateMaintenance"; import withAdminCheck from "../Components/HOC/withAdminCheck"; -import BulkImport from "../Pages/Uptime/BulkImport"; -import Logs from "../Pages/Logs"; +import BulkImport from "../Pages/v1/Uptime/BulkImport"; +import Logs from "../Pages/v1/Logs"; const Routes = () => { const AdminCheckedRegister = withAdminCheck(AuthRegister); diff --git a/client/tsconfig.app.json b/client/tsconfig.app.json new file mode 100644 index 000000000..f44839e9f --- /dev/null +++ b/client/tsconfig.app.json @@ -0,0 +1,32 @@ +{ + "compilerOptions": { + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", + "target": "ES2022", + "useDefineForClassFields": true, + "lib": ["ES2022", "DOM", "DOM.Iterable"], + "module": "ESNext", + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, + "moduleDetection": "force", + "noEmit": true, + "jsx": "react-jsx", + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "erasableSyntaxOnly": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedSideEffectImports": true, + "paths": { + "@/*": ["./src/*"] + }, + "allowJs": true, + "checkJs": false + }, + "include": ["src"] +} diff --git a/client/tsconfig.json b/client/tsconfig.json new file mode 100644 index 000000000..a1a0f47d0 --- /dev/null +++ b/client/tsconfig.json @@ -0,0 +1,14 @@ +{ + "files": [], + "references": [{ "path": "./tsconfig.app.json" }, { "path": "./tsconfig.node.json" }], + "compilerOptions": { + "module": "ESNext", + "moduleResolution": "Bundler", + "skipLibCheck": true, + "paths": { + "@/*": ["./src/*"] + }, + "allowJs": true, + "checkJs": false + } +} diff --git a/client/tsconfig.node.json b/client/tsconfig.node.json new file mode 100644 index 000000000..bbd18d2b2 --- /dev/null +++ b/client/tsconfig.node.json @@ -0,0 +1,27 @@ +{ + "compilerOptions": { + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", + "target": "ES2023", + "lib": ["ES2023"], + "module": "ESNext", + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, + "moduleDetection": "force", + "noEmit": true, + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "erasableSyntaxOnly": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedSideEffectImports": true, + "allowJs": true, + "checkJs": false + }, + "include": ["vite.config.ts"] +} diff --git a/client/vite.config.js b/client/vite.config.ts similarity index 63% rename from client/vite.config.js rename to client/vite.config.ts index 07b5de748..66630e88c 100644 --- a/client/vite.config.js +++ b/client/vite.config.ts @@ -1,10 +1,8 @@ -import { defineConfig, loadEnv } from "vite"; +import { defineConfig } from "vite"; import react from "@vitejs/plugin-react"; import svgr from "vite-plugin-svgr"; -import { execSync } from "child_process"; -export default defineConfig(({ mode }) => { - const env = loadEnv(mode, process.cwd(), ""); +export default defineConfig(({}) => { let version = "3.2.0"; return { From 17b84cbe8ac03e26b38447098ebfbd55a2197a79 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Thu, 25 Sep 2025 10:19:08 -0700 Subject: [PATCH 036/101] format --- client/src/Pages/v1/Account/components/ProfilePanel.jsx | 6 +++++- .../Create/hooks/useInfrastructureSubmit.jsx | 5 ++++- .../Pages/v1/PageSpeed/Monitors/Components/Card/index.jsx | 5 ++++- client/src/Pages/v1/Settings/index.jsx | 7 ++++++- 4 files changed, 19 insertions(+), 4 deletions(-) diff --git a/client/src/Pages/v1/Account/components/ProfilePanel.jsx b/client/src/Pages/v1/Account/components/ProfilePanel.jsx index 8e607f4bd..1ce1d34da 100644 --- a/client/src/Pages/v1/Account/components/ProfilePanel.jsx +++ b/client/src/Pages/v1/Account/components/ProfilePanel.jsx @@ -7,7 +7,11 @@ import TextInput from "../../../../Components/Inputs/TextInput/index.jsx"; import ImageUpload from "../../../../Components/Inputs/ImageUpload/index.jsx"; import { newOrChangedCredentials } from "../../../../Validation/validation.js"; import { useDispatch, useSelector } from "react-redux"; -import { clearAuthState, deleteUser, update } from "../../../../Features/Auth/authSlice.js"; +import { + clearAuthState, + deleteUser, + update, +} from "../../../../Features/Auth/authSlice.js"; import { createToast } from "../../../../Utils/toastUtils.jsx"; import { logger } from "../../../../Utils/Logger.js"; import { GenericDialog } from "../../../../Components/Dialog/genericDialog.jsx"; diff --git a/client/src/Pages/v1/Infrastructure/Create/hooks/useInfrastructureSubmit.jsx b/client/src/Pages/v1/Infrastructure/Create/hooks/useInfrastructureSubmit.jsx index 0fd5c0081..8a97486c7 100644 --- a/client/src/Pages/v1/Infrastructure/Create/hooks/useInfrastructureSubmit.jsx +++ b/client/src/Pages/v1/Infrastructure/Create/hooks/useInfrastructureSubmit.jsx @@ -1,4 +1,7 @@ -import { useCreateMonitor, useUpdateMonitor } from "../../../../../Hooks/v1/monitorHooks.js"; +import { + useCreateMonitor, + useUpdateMonitor, +} from "../../../../../Hooks/v1/monitorHooks.js"; const useInfrastructureSubmit = () => { const [createMonitor, isCreating] = useCreateMonitor(); const [updateMonitor, isUpdating] = useUpdateMonitor(); diff --git a/client/src/Pages/v1/PageSpeed/Monitors/Components/Card/index.jsx b/client/src/Pages/v1/PageSpeed/Monitors/Components/Card/index.jsx index db1f6e712..5857a7a7e 100644 --- a/client/src/Pages/v1/PageSpeed/Monitors/Components/Card/index.jsx +++ b/client/src/Pages/v1/PageSpeed/Monitors/Components/Card/index.jsx @@ -7,7 +7,10 @@ import { useNavigate } from "react-router-dom"; import { useTheme } from "@emotion/react"; import { Area, AreaChart, CartesianGrid, ResponsiveContainer, Tooltip } from "recharts"; import { useSelector } from "react-redux"; -import { formatDateWithTz, formatDurationSplit } from "../../../../../../Utils/timeUtils.js"; +import { + formatDateWithTz, + formatDurationSplit, +} from "../../../../../../Utils/timeUtils.js"; import { useMonitorUtils } from "../../../../../../Hooks/v1/useMonitorUtils.js"; import { useState } from "react"; import { useTranslation } from "react-i18next"; diff --git a/client/src/Pages/v1/Settings/index.jsx b/client/src/Pages/v1/Settings/index.jsx index d27685ed6..d1788eddf 100644 --- a/client/src/Pages/v1/Settings/index.jsx +++ b/client/src/Pages/v1/Settings/index.jsx @@ -16,7 +16,12 @@ import { useState } from "react"; import { useTheme } from "@emotion/react"; import { useTranslation } from "react-i18next"; import { useSelector, useDispatch } from "react-redux"; -import { setTimezone, setMode, setLanguage, setShowURL } from "../../../Features/UI/uiSlice.js"; +import { + setTimezone, + setMode, + setLanguage, + setShowURL, +} from "../../../Features/UI/uiSlice.js"; import SettingsStats from "./SettingsStats.jsx"; import { useFetchSettings, useSaveSettings } from "../../../Hooks/v1/settingsHooks.js"; From e29f7af85894c57d2e4f34a274b69acd621ca264 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Thu, 25 Sep 2025 10:21:31 -0700 Subject: [PATCH 037/101] add client build check --- .github/workflows/check-build.yml | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/.github/workflows/check-build.yml b/.github/workflows/check-build.yml index 8417b823f..5ce8c9c26 100644 --- a/.github/workflows/check-build.yml +++ b/.github/workflows/check-build.yml @@ -4,6 +4,23 @@ on: workflow_dispatch: jobs: + build-client: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Set up Node + uses: actions/setup-node@v4 + with: + node-version: 22 + + - name: Install client dependencies + working-directory: client + run: npm install + + - name: Check client build + working-directory: client + run: npm run build build-server: runs-on: ubuntu-latest steps: From 28ef465b3a5bd19b5eabd72002baa02bc3f3ffcc Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Thu, 25 Sep 2025 10:22:47 -0700 Subject: [PATCH 038/101] update action name --- .github/workflows/check-build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/check-build.yml b/.github/workflows/check-build.yml index 5ce8c9c26..4d2de7041 100644 --- a/.github/workflows/check-build.yml +++ b/.github/workflows/check-build.yml @@ -1,4 +1,4 @@ -name: Build Check ( Server) +name: Build Check (Client & Server) on: pull_request: workflow_dispatch: From cb1e10f0a95fd671ad8fc227dccd754adcd6d655 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Thu, 25 Sep 2025 16:06:52 -0700 Subject: [PATCH 039/101] add v2 login page --- client/package-lock.json | 65 +++++++++- client/package.json | 6 +- .../Components/Inputs/TextInput/indexV2.tsx | 6 + client/src/Hooks/v2/UseApi.tsx | 52 ++++++++ client/src/Pages/v2/Auth/Login.tsx | 113 ++++++++++++++++++ client/src/Routes/index.jsx | 15 ++- client/src/Utils/ApiClient.ts | 21 ++++ client/src/Utils/Theme/constants.js | 1 - client/src/Utils/Theme/v2/palette.ts | 82 +++++++++++++ client/src/Utils/Theme/v2/theme.ts | 52 ++++++++ client/src/types/env.d.ts | 9 ++ client/src/types/mui.d.ts | 20 ++++ client/vite.config.ts | 6 + server/src/app.js | 2 +- 14 files changed, 445 insertions(+), 5 deletions(-) create mode 100644 client/src/Components/Inputs/TextInput/indexV2.tsx create mode 100644 client/src/Hooks/v2/UseApi.tsx create mode 100644 client/src/Pages/v2/Auth/Login.tsx create mode 100644 client/src/Utils/ApiClient.ts create mode 100644 client/src/Utils/Theme/v2/palette.ts create mode 100644 client/src/Utils/Theme/v2/theme.ts create mode 100644 client/src/types/env.d.ts create mode 100644 client/src/types/mui.d.ts diff --git a/client/package-lock.json b/client/package-lock.json index ee1ab1bc1..de297943a 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -11,6 +11,7 @@ "@emotion/react": "^11.13.3", "@emotion/styled": "^11.13.0", "@hello-pangea/dnd": "^18.0.0", + "@hookform/resolvers": "5.2.2", "@mui/icons-material": "6.4.11", "@mui/lab": "6.0.0-dev.240424162023-9968b4889d", "@mui/material": "6.4.11", @@ -26,6 +27,7 @@ "mui-color-input": "^6.0.0", "react": "18.3.1", "react-dom": "^18.2.0", + "react-hook-form": "7.63.0", "react-i18next": "^15.4.0", "react-icons": "5.5.0", "react-redux": "9.2.0", @@ -34,7 +36,9 @@ "react-toastify": "^10.0.5", "recharts": "2.15.2", "redux-persist": "6.0.0", - "vite-plugin-svgr": "^4.2.0" + "swr": "2.3.6", + "vite-plugin-svgr": "^4.2.0", + "zod": "4.1.11" }, "devDependencies": { "@types/react": "^18.2.66", @@ -653,6 +657,18 @@ "react-dom": "^18.0.0 || ^19.0.0" } }, + "node_modules/@hookform/resolvers": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/@hookform/resolvers/-/resolvers-5.2.2.tgz", + "integrity": "sha512-A/IxlMLShx3KjV/HeTcTfaMxdwy690+L/ZADoeaTltLx+CVuzkeVIPuybK3jrRfw7YZnmdKsVVHAlEPIAEUNlA==", + "license": "MIT", + "dependencies": { + "@standard-schema/utils": "^0.3.0" + }, + "peerDependencies": { + "react-hook-form": "^7.55.0" + } + }, "node_modules/@humanwhocodes/config-array": { "version": "0.13.0", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz", @@ -2991,6 +3007,15 @@ "node": ">=0.4.0" } }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/doctrine": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", @@ -5343,6 +5368,22 @@ "react": "^18.3.1" } }, + "node_modules/react-hook-form": { + "version": "7.63.0", + "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.63.0.tgz", + "integrity": "sha512-ZwueDMvUeucovM2VjkCf7zIHcs1aAlDimZu2Hvel5C5907gUzMpm4xCrQXtRzCvsBqFjonB4m3x4LzCFI1ZKWA==", + "license": "MIT", + "engines": { + "node": ">=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/react-hook-form" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17 || ^18 || ^19" + } + }, "node_modules/react-i18next": { "version": "15.5.1", "resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-15.5.1.tgz", @@ -6141,6 +6182,19 @@ "integrity": "sha512-e4hG1hRwoOdRb37cIMSgzNsxyzKfayW6VOflrwvR+/bzrkyxY/31WkbgnQpgtrNp1SdpJvpUAGTa/ZoiPNDuRQ==", "license": "MIT" }, + "node_modules/swr": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/swr/-/swr-2.3.6.tgz", + "integrity": "sha512-wfHRmHWk/isGNMwlLGlZX5Gzz/uTgo0o2IRuTMcf4CPuPFJZlq0rDaKUx+ozB5nBOReNV1kiOyzMfj+MBMikLw==", + "license": "MIT", + "dependencies": { + "dequal": "^2.0.3", + "use-sync-external-store": "^1.4.0" + }, + "peerDependencies": { + "react": "^16.11.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/text-segmentation": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/text-segmentation/-/text-segmentation-1.0.3.tgz", @@ -6639,6 +6693,15 @@ "funding": { "url": "https://github.com/sponsors/sindresorhus" } + }, + "node_modules/zod": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.1.11.tgz", + "integrity": "sha512-WPsqwxITS2tzx1bzhIKsEs19ABD5vmCVa4xBo2tq/SrV4RNZtfws1EnCWQXM6yh8bD08a1idvkB5MZSBiZsjwg==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } } } } diff --git a/client/package.json b/client/package.json index 9ebc99647..03d0cf6bc 100644 --- a/client/package.json +++ b/client/package.json @@ -16,6 +16,7 @@ "@emotion/react": "^11.13.3", "@emotion/styled": "^11.13.0", "@hello-pangea/dnd": "^18.0.0", + "@hookform/resolvers": "5.2.2", "@mui/icons-material": "6.4.11", "@mui/lab": "6.0.0-dev.240424162023-9968b4889d", "@mui/material": "6.4.11", @@ -31,6 +32,7 @@ "mui-color-input": "^6.0.0", "react": "18.3.1", "react-dom": "^18.2.0", + "react-hook-form": "7.63.0", "react-i18next": "^15.4.0", "react-icons": "5.5.0", "react-redux": "9.2.0", @@ -39,7 +41,9 @@ "react-toastify": "^10.0.5", "recharts": "2.15.2", "redux-persist": "6.0.0", - "vite-plugin-svgr": "^4.2.0" + "swr": "2.3.6", + "vite-plugin-svgr": "^4.2.0", + "zod": "4.1.11" }, "unusedDepencies": { "@solana/wallet-adapter-base": "0.9.25", diff --git a/client/src/Components/Inputs/TextInput/indexV2.tsx b/client/src/Components/Inputs/TextInput/indexV2.tsx new file mode 100644 index 000000000..bc1f144b6 --- /dev/null +++ b/client/src/Components/Inputs/TextInput/indexV2.tsx @@ -0,0 +1,6 @@ +import TextField from "@mui/material/TextField"; +import Box from "@mui/material/Box"; +import type { TextFieldProps } from "@mui/material"; +export const TextInput = (props: TextFieldProps) => { + return ; +}; diff --git a/client/src/Hooks/v2/UseApi.tsx b/client/src/Hooks/v2/UseApi.tsx new file mode 100644 index 000000000..fdb4e8a24 --- /dev/null +++ b/client/src/Hooks/v2/UseApi.tsx @@ -0,0 +1,52 @@ +import { useState } from "react"; +import useSWR from "swr"; +import type { SWRConfiguration } from "swr"; +import type { AxiosRequestConfig } from "axios"; +import { get, post } from "@/Utils/ApiClient"; // your axios wrapper + +// Generic fetcher for GET requests +const fetcher = async (url: string, config?: AxiosRequestConfig) => { + const res = await get(url, config); + return res.data; +}; + +export const useGet = ( + url: string, + axiosConfig?: AxiosRequestConfig, + swrConfig?: SWRConfiguration +) => { + const { data, error, isLoading, mutate } = useSWR( + url, + (url) => fetcher(url, axiosConfig), + swrConfig + ); + + return { + response: data ?? null, + loading: isLoading, + error: error?.message ?? null, + refetch: mutate, + }; +}; + +export const usePost = (endpoint: string) => { + const [loading, setLoading] = useState(false); + const [error, setError] = useState(null); + + const postFn = async (body: B, config?: AxiosRequestConfig): Promise => { + setLoading(true); + setError(null); + + try { + const res = await post(endpoint, body, config); + return res.data; + } catch (err: any) { + setError(err?.message ?? "Unknown error"); + return null; + } finally { + setLoading(false); + } + }; + + return { post: postFn, loading, error }; +}; diff --git a/client/src/Pages/v2/Auth/Login.tsx b/client/src/Pages/v2/Auth/Login.tsx new file mode 100644 index 000000000..d1d4ec014 --- /dev/null +++ b/client/src/Pages/v2/Auth/Login.tsx @@ -0,0 +1,113 @@ +import { zodResolver } from "@hookform/resolvers/zod"; +import { z } from "zod"; +import { useTranslation } from "react-i18next"; +import { useForm, Controller } from "react-hook-form"; +import { useTheme } from "@mui/material/styles"; + +import { TextInput } from "@/Components/Inputs/TextInput/indexV2.tsx"; +import Button from "@mui/material/Button"; +import Stack from "@mui/material/Stack"; +import { usePost } from "@/Hooks/v2/UseApi"; +const schema = z.object({ + email: z.email("Invalid email address"), + password: z.string().min(6, "Password must be at least 6 characters"), +}); + +type FormData = z.infer; +type LoginData = { + username: string; + password: string; +}; + +const Login = () => { + const { t } = useTranslation(); + const theme = useTheme(); + const { post, loading, error } = usePost("/auth/login"); + + const { + handleSubmit, + control, + formState: { errors }, + } = useForm({ + resolver: zodResolver(schema), // ⮅ïļ connect Zod + defaultValues: { + email: "", + password: "", + }, + }); + + const onSubmit = async (data: FormData) => { + const result = await post(data); + if (result) { + console.log("Login successful:", result); + } else { + console.error("Login failed:", error); + } + }; + + return ( + + + ( + + )} + /> + ( + + )} + /> + + + + ); +}; + +export default Login; diff --git a/client/src/Routes/index.jsx b/client/src/Routes/index.jsx index 0de847cb7..06a4b1920 100644 --- a/client/src/Routes/index.jsx +++ b/client/src/Routes/index.jsx @@ -1,9 +1,12 @@ +import { useSelector } from "react-redux"; +import { lightTheme, darkTheme } from "@/Utils/Theme/v2/theme"; import { Navigate, Route, Routes as LibRoutes } from "react-router"; +import { ThemeProvider } from "@emotion/react"; import HomeLayout from "../Components/Layouts/HomeLayout"; import NotFound from "../Pages/v1/NotFound"; - // Auth import AuthLogin from "../Pages/v1/Auth/Login"; +import AuthLoginV2 from "@/Pages/v2/Auth/Login"; import AuthRegister from "../Pages/v1/Auth/Register/"; import AuthForgotPassword from "../Pages/v1/Auth/ForgotPassword"; import AuthCheckEmail from "../Pages/v1/Auth/CheckEmail"; @@ -54,6 +57,8 @@ import BulkImport from "../Pages/v1/Uptime/BulkImport"; import Logs from "../Pages/v1/Logs"; const Routes = () => { + const mode = useSelector((state) => state.ui.mode); + const v2Theme = mode === "light" ? lightTheme : darkTheme; const AdminCheckedRegister = withAdminCheck(AuthRegister); return ( @@ -214,6 +219,14 @@ const Routes = () => { path="/login" element={} /> + + + + } + /> ( + url: string, + config: AxiosRequestConfig = {} +): Promise> => api.get(url, config); + +export const post = ( + url: string, + data: any, + config: AxiosRequestConfig = {} +): Promise> => api.post(url, data, config); + +export default api; diff --git a/client/src/Utils/Theme/constants.js b/client/src/Utils/Theme/constants.js index 9df6665d0..45217fdeb 100644 --- a/client/src/Utils/Theme/constants.js +++ b/client/src/Utils/Theme/constants.js @@ -100,7 +100,6 @@ const newColors = { blueGray900: "#515151", blueBlueWave: "#1570EF", lightBlueWave: "#CDE2FF", - /* I changed green 100 and green 700. Need to change red and warning as well, and refactor the object following the structure */ green100: "#67cd78", green200: "#4B9B77", green400: "#079455", diff --git a/client/src/Utils/Theme/v2/palette.ts b/client/src/Utils/Theme/v2/palette.ts new file mode 100644 index 000000000..2875f750d --- /dev/null +++ b/client/src/Utils/Theme/v2/palette.ts @@ -0,0 +1,82 @@ +import { lighten, darken } from "@mui/material/styles"; + +const typographyBase = 13; + +export const typographyLevels = { + base: typographyBase, + xs: `${(typographyBase - 4) / 16}rem`, + s: `${(typographyBase - 2) / 16}rem`, + m: `${typographyBase / 16}rem`, + l: `${(typographyBase + 2) / 16}rem`, + xl: `${(typographyBase + 10) / 16}rem`, +}; + +const colors = { + offWhite: "#FEFEFE", + offBlack: "#131315", + gray0: "#FDFDFD", + gray10: "#F4F4FF", + gray50: "#F9F9F9", + gray100: "#F3F3F3", + gray200: "#EFEFEF", + gray250: "#DADADA", + gray500: "#A2A3A3", + gray900: "#1c1c1c", + blueGray50: "#E8F0FE", + blueGray500: "#475467", + blueGray600: "#344054", + blueGray800: "#1C2130", + blueGray900: "#515151", + blueBlueWave: "#1570EF", + lightBlueWave: "#CDE2FF", + green100: "#67cd78", + green200: "#4B9B77", + green400: "#079455", + green700: "#026513", + orange100: "#FD8F22", + orange200: "#D69A5D", + orange600: "#9B734B", + orange700: "#884605", + red100: "#F27C7C", + red400: "#D92020", + red600: "#9B4B4B", + red700: "#980303", +}; + +export const lightPalette = { + accent: { + main: colors.blueBlueWave, + light: lighten(colors.blueBlueWave, 0.2), + dark: darken(colors.blueBlueWave, 0.2), + contrastText: colors.offWhite, + }, + primary: { + main: colors.offWhite, + contrastText: colors.blueGray800, + contrastTextSecondary: colors.blueGray600, + }, + secondary: { + main: colors.gray200, + light: colors.lightBlueWave, + contrastText: colors.blueGray600, + }, +}; + +export const darkPalette = { + accent: { + main: colors.blueBlueWave, + light: lighten(colors.blueBlueWave, 0.2), + dark: darken(colors.blueBlueWave, 0.2), + contrastText: colors.offWhite, + }, + primary: { + main: colors.offBlack, + contrastText: colors.blueGray50, + contrastTextSecondary: colors.gray200, + }, + secondary: { + main: "#313131", + light: colors.lightBlueWave, + contrastText: colors.gray200, + }, +}; diff --git a/client/src/Utils/Theme/v2/theme.ts b/client/src/Utils/Theme/v2/theme.ts new file mode 100644 index 000000000..ce81ea2fd --- /dev/null +++ b/client/src/Utils/Theme/v2/theme.ts @@ -0,0 +1,52 @@ +import { createTheme } from "@mui/material"; +import { lightPalette, darkPalette, typographyLevels } from "./palette"; +const fontFamilyPrimary = '"Inter" , sans-serif'; + +export const theme = (mode: string, palette: any) => + createTheme({ + spacing: 2, + palette: { + mode: mode, + ...palette, + }, + typography: { + fontFamily: fontFamilyPrimary, + fontSize: typographyLevels.base, + }, + + components: { + MuiFormLabel: { + styleOverrides: { + root: ({ theme }) => ({ + fontSize: typographyLevels.base, + "&.Mui-focused": { + color: theme.palette.secondary.contrastText, + }, + }), + }, + }, + MuiInputLabel: { + styleOverrides: { + root: ({ theme }) => ({ + top: `-${theme.spacing(4)}`, + "&.MuiInputLabel-shrink": { + top: 0, + }, + }), + }, + }, + MuiTextField: { + styleOverrides: { + root: ({ theme }) => ({ + "& .MuiOutlinedInput-root": { + height: 34, + fontSize: typographyLevels.base, + }, + }), + }, + }, + }, + }); + +export const lightTheme = createTheme(theme("light", lightPalette)); +export const darkTheme = createTheme(theme("dark", darkPalette)); diff --git a/client/src/types/env.d.ts b/client/src/types/env.d.ts new file mode 100644 index 000000000..e2bce23dd --- /dev/null +++ b/client/src/types/env.d.ts @@ -0,0 +1,9 @@ +/// + +interface ImportMetaEnv { + readonly VITE_APP_API_V2_BASE_URL?: string; +} + +interface ImportMeta { + readonly env: ImportMetaEnv; +} diff --git a/client/src/types/mui.d.ts b/client/src/types/mui.d.ts new file mode 100644 index 000000000..851ffea70 --- /dev/null +++ b/client/src/types/mui.d.ts @@ -0,0 +1,20 @@ +import "@mui/material/Button"; + +declare module "@mui/material/styles" { + interface Palette { + accent: Palette["primary"]; + } + interface PaletteOptions { + accent?: PaletteOptions["primary"]; + } + + interface PaletteColor { + contrastTextSecondary?: string; + } +} + +declare module "@mui/material/Button" { + interface ButtonPropsColorOverrides { + accent: true; + } +} diff --git a/client/vite.config.ts b/client/vite.config.ts index 66630e88c..a99c1a254 100644 --- a/client/vite.config.ts +++ b/client/vite.config.ts @@ -1,6 +1,7 @@ import { defineConfig } from "vite"; import react from "@vitejs/plugin-react"; import svgr from "vite-plugin-svgr"; +import path from "path"; export default defineConfig(({}) => { let version = "3.2.0"; @@ -8,6 +9,11 @@ export default defineConfig(({}) => { return { base: "/", plugins: [svgr(), react()], + resolve: { + alias: { + "@": path.resolve(__dirname, "src"), + }, + }, optimizeDeps: { include: ["@mui/material/Tooltip", "@emotion/styled"], }, diff --git a/server/src/app.js b/server/src/app.js index 253baee88..4578de896 100644 --- a/server/src/app.js +++ b/server/src/app.js @@ -27,7 +27,7 @@ export const createApp = ({ services, controllers, envSettings, frontendPath, op cors({ origin: allowedOrigin, methods: "GET,HEAD,PUT,PATCH,POST,DELETE,OPTIONS", - allowedHeaders: "*", + allowedHeaders: ["Content-Type", "Authorization"], credentials: true, }) ); From 0c5b9a863b5531417c2fce6b25df4ac9ea400b77 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Fri, 26 Sep 2025 14:16:59 -0700 Subject: [PATCH 040/101] add loading --- client/src/Pages/v2/Auth/Login.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/client/src/Pages/v2/Auth/Login.tsx b/client/src/Pages/v2/Auth/Login.tsx index d1d4ec014..6a76fdf76 100644 --- a/client/src/Pages/v2/Auth/Login.tsx +++ b/client/src/Pages/v2/Auth/Login.tsx @@ -99,6 +99,7 @@ const Login = () => { /> + {error && {error}} + + + ); +}; + +export default Register; diff --git a/client/src/Routes/index.jsx b/client/src/Routes/index.jsx index 06a4b1920..39959bc1c 100644 --- a/client/src/Routes/index.jsx +++ b/client/src/Routes/index.jsx @@ -8,6 +8,7 @@ import NotFound from "../Pages/v1/NotFound"; import AuthLogin from "../Pages/v1/Auth/Login"; import AuthLoginV2 from "@/Pages/v2/Auth/Login"; import AuthRegister from "../Pages/v1/Auth/Register/"; +import AuthRegisterV2 from "@/Pages/v2/Auth/Register"; import AuthForgotPassword from "../Pages/v1/Auth/ForgotPassword"; import AuthCheckEmail from "../Pages/v1/Auth/CheckEmail"; import AuthSetNewPassword from "../Pages/v1/Auth/SetNewPassword"; @@ -232,6 +233,14 @@ const Routes = () => { path="/register" element={} /> + + + + } + /> Date: Fri, 26 Sep 2025 15:31:55 -0700 Subject: [PATCH 047/101] move v2 routes to v2 router --- client/src/Routes/index.jsx | 27 +++++++-------------------- client/src/Routes/v2router.tsx | 29 +++++++++++++++++++++++++++++ 2 files changed, 36 insertions(+), 20 deletions(-) create mode 100644 client/src/Routes/v2router.tsx diff --git a/client/src/Routes/index.jsx b/client/src/Routes/index.jsx index 39959bc1c..2e4fd9c50 100644 --- a/client/src/Routes/index.jsx +++ b/client/src/Routes/index.jsx @@ -1,14 +1,11 @@ import { useSelector } from "react-redux"; import { lightTheme, darkTheme } from "@/Utils/Theme/v2/theme"; import { Navigate, Route, Routes as LibRoutes } from "react-router"; -import { ThemeProvider } from "@emotion/react"; import HomeLayout from "../Components/Layouts/HomeLayout"; import NotFound from "../Pages/v1/NotFound"; // Auth import AuthLogin from "../Pages/v1/Auth/Login"; -import AuthLoginV2 from "@/Pages/v2/Auth/Login"; import AuthRegister from "../Pages/v1/Auth/Register/"; -import AuthRegisterV2 from "@/Pages/v2/Auth/Register"; import AuthForgotPassword from "../Pages/v1/Auth/ForgotPassword"; import AuthCheckEmail from "../Pages/v1/Auth/CheckEmail"; import AuthSetNewPassword from "../Pages/v1/Auth/SetNewPassword"; @@ -57,12 +54,18 @@ import withAdminCheck from "../Components/HOC/withAdminCheck"; import BulkImport from "../Pages/v1/Uptime/BulkImport"; import Logs from "../Pages/v1/Logs"; +import V2Routes from "@/Routes/v2router"; + const Routes = () => { const mode = useSelector((state) => state.ui.mode); - const v2Theme = mode === "light" ? lightTheme : darkTheme; const AdminCheckedRegister = withAdminCheck(AuthRegister); return ( + } + /> + { path="/login" element={} /> - - - - } - /> } /> - - - - } - /> { + const v2Theme = mode === "light" ? lightTheme : darkTheme; + + return ( + + + } + /> + } + /> + + + ); +}; + +export default V2Routes; From 1d5f2427ed692658b9d86111eec95334fc087fbf Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Fri, 26 Sep 2025 16:01:05 -0700 Subject: [PATCH 048/101] add root layout --- .../src/Components/v2/Layouts/RootLayout.tsx | 50 +++++++++++++++++++ client/src/Routes/index.jsx | 27 +++------- client/src/Routes/v2router.tsx | 39 +++++++++++++++ 3 files changed, 96 insertions(+), 20 deletions(-) create mode 100644 client/src/Components/v2/Layouts/RootLayout.tsx create mode 100644 client/src/Routes/v2router.tsx diff --git a/client/src/Components/v2/Layouts/RootLayout.tsx b/client/src/Components/v2/Layouts/RootLayout.tsx new file mode 100644 index 000000000..5f78600f4 --- /dev/null +++ b/client/src/Components/v2/Layouts/RootLayout.tsx @@ -0,0 +1,50 @@ +import { useState, useEffect } from "react"; +import useMediaQuery from "@mui/material/useMediaQuery"; +import { useTheme } from "@emotion/react"; + +import { Outlet } from "react-router"; +import Stack from "@mui/material/Stack"; +import Box from "@mui/material/Box"; + +const COLLAPSED_WIDTH = 50; +const EXPANDED_WIDTH = 250; + +const SideBar = () => { + const theme = useTheme(); + const isSmall = useMediaQuery(theme.breakpoints.down("md")); + + const [collapsed, setCollapsed] = useState(false); + useEffect(() => { + setCollapsed(isSmall); + }, [isSmall]); + return ( + + setCollapsed(!collapsed)} + > + Sidebar Content + + + ); +}; + +const RootLayout = ({ mode = "light" }) => { + return ( + + + + + ); +}; + +export default RootLayout; diff --git a/client/src/Routes/index.jsx b/client/src/Routes/index.jsx index 39959bc1c..2e4fd9c50 100644 --- a/client/src/Routes/index.jsx +++ b/client/src/Routes/index.jsx @@ -1,14 +1,11 @@ import { useSelector } from "react-redux"; import { lightTheme, darkTheme } from "@/Utils/Theme/v2/theme"; import { Navigate, Route, Routes as LibRoutes } from "react-router"; -import { ThemeProvider } from "@emotion/react"; import HomeLayout from "../Components/Layouts/HomeLayout"; import NotFound from "../Pages/v1/NotFound"; // Auth import AuthLogin from "../Pages/v1/Auth/Login"; -import AuthLoginV2 from "@/Pages/v2/Auth/Login"; import AuthRegister from "../Pages/v1/Auth/Register/"; -import AuthRegisterV2 from "@/Pages/v2/Auth/Register"; import AuthForgotPassword from "../Pages/v1/Auth/ForgotPassword"; import AuthCheckEmail from "../Pages/v1/Auth/CheckEmail"; import AuthSetNewPassword from "../Pages/v1/Auth/SetNewPassword"; @@ -57,12 +54,18 @@ import withAdminCheck from "../Components/HOC/withAdminCheck"; import BulkImport from "../Pages/v1/Uptime/BulkImport"; import Logs from "../Pages/v1/Logs"; +import V2Routes from "@/Routes/v2router"; + const Routes = () => { const mode = useSelector((state) => state.ui.mode); - const v2Theme = mode === "light" ? lightTheme : darkTheme; const AdminCheckedRegister = withAdminCheck(AuthRegister); return ( + } + /> + { path="/login" element={} /> - - - - } - /> } /> - - - - } - /> { + const v2Theme = mode === "light" ? lightTheme : darkTheme; + + return ( + + + } + /> + } + /> + } + > + Test Page} + /> + + + + ); +}; + +export default V2Routes; From 4d254b004dd8440ec08bbfc01f04ffcbd229e190 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Sat, 27 Sep 2025 14:38:14 -0700 Subject: [PATCH 049/101] add gitignore --- .gitignore | 1 + client/src/Components/v2/Layouts/RootLayout.tsx | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 172702b40..997dff9c5 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ +.idea .vscode .VSCodeCounter \ No newline at end of file diff --git a/client/src/Components/v2/Layouts/RootLayout.tsx b/client/src/Components/v2/Layouts/RootLayout.tsx index 5f78600f4..4082242ef 100644 --- a/client/src/Components/v2/Layouts/RootLayout.tsx +++ b/client/src/Components/v2/Layouts/RootLayout.tsx @@ -1,6 +1,6 @@ import { useState, useEffect } from "react"; import useMediaQuery from "@mui/material/useMediaQuery"; -import { useTheme } from "@emotion/react"; +import { useTheme } from "@mui/material/styles"; import { Outlet } from "react-router"; import Stack from "@mui/material/Stack"; From 09fbc6b0b267406ffa6cf256cc2a09fdbe6b4294 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Sat, 27 Sep 2025 14:48:05 -0700 Subject: [PATCH 050/101] remove unused mode --- client/src/Components/v2/Layouts/RootLayout.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/Components/v2/Layouts/RootLayout.tsx b/client/src/Components/v2/Layouts/RootLayout.tsx index 4082242ef..be2f42e30 100644 --- a/client/src/Components/v2/Layouts/RootLayout.tsx +++ b/client/src/Components/v2/Layouts/RootLayout.tsx @@ -35,7 +35,7 @@ const SideBar = () => { ); }; -const RootLayout = ({ mode = "light" }) => { +const RootLayout = () => { return ( Date: Sat, 27 Sep 2025 14:54:55 -0700 Subject: [PATCH 051/101] move components to v1 and v2 dirs --- client/src/App.jsx | 2 +- .../Components/{ => v1}/ActionsMenu/index.jsx | 8 ++-- .../src/Components/{ => v1}/Alert/index.css | 0 .../src/Components/{ => v1}/Alert/index.jsx | 0 .../Components/{ => v1}/Animated/PulseDot.jsx | 0 .../Components/{ => v1}/ArrowLeft/index.jsx | 6 +-- .../Components/{ => v1}/ArrowRight/index.jsx | 4 +- .../src/Components/{ => v1}/Avatar/index.css | 0 .../src/Components/{ => v1}/Avatar/index.jsx | 0 .../Components/{ => v1}/Breadcrumbs/index.css | 0 .../Components/{ => v1}/Breadcrumbs/index.jsx | 2 +- .../{ => v1}/Buttons/RoundGradientButton.jsx | 0 .../{ => v1}/Charts/AreaChart/index.jsx | 2 +- .../{ => v1}/Charts/BarChart/index.css | 0 .../{ => v1}/Charts/BarChart/index.jsx | 2 +- .../{ => v1}/Charts/ChartBox/EmptyView.jsx | 2 +- .../{ => v1}/Charts/ChartBox/index.jsx | 4 +- .../{ => v1}/Charts/CustomGauge/index.css | 0 .../{ => v1}/Charts/CustomGauge/index.jsx | 0 .../Charts/DePINStatusPageBarChart/index.jsx | 2 +- .../{ => v1}/Charts/LegendBox/index.jsx | 2 +- .../Charts/MonitorDetailsAreaChart/index.css | 0 .../Charts/MonitorDetailsAreaChart/index.jsx | 4 +- .../Charts/StatusPageBarChart/index.jsx | 2 +- .../Charts/Utils/chartUtilFunctions.js | 0 .../{ => v1}/Charts/Utils/chartUtils.jsx | 4 +- .../{ => v1}/Charts/Utils/gradientUtils.jsx | 0 .../src/Components/{ => v1}/Check/Check.jsx | 4 +- .../src/Components/{ => v1}/Check/check.css | 0 .../{ => v1}/CircularCount/index.jsx | 0 .../src/Components/{ => v1}/Common/AppBar.jsx | 2 +- .../src/Components/{ => v1}/Common/Footer.jsx | 0 .../Components/{ => v1}/ConfigBox/index.jsx | 0 .../Components/{ => v1}/ConfigRow/index.jsx | 2 +- .../{ => v1}/Dialog/genericDialog.jsx | 0 .../src/Components/{ => v1}/Dialog/index.jsx | 2 +- client/src/Components/{ => v1}/Dot/index.jsx | 0 .../Fallback/FallBackActionButtons.jsx | 0 .../{ => v1}/Fallback/FallbackBackground.jsx | 6 +-- .../{ => v1}/Fallback/FallbackCheckList.jsx | 2 +- .../{ => v1}/Fallback/FallbackContainer.jsx | 0 .../Fallback/FallbackPageSpeedWarning.jsx | 2 +- .../{ => v1}/Fallback/FallbackTitle.jsx | 0 .../Components/{ => v1}/Fallback/index.css | 0 .../Components/{ => v1}/Fallback/index.jsx | 10 ++--- .../{ => v1}/FilterHeader/index.jsx | 0 .../{ => v1}/GenericFallback/NetworkError.jsx | 0 .../{ => v1}/GenericFallback/index.jsx | 6 +-- .../{ => v1}/HOC/withAdminCheck.jsx | 4 +- .../src/Components/{ => v1}/Heading/index.jsx | 0 client/src/Components/{ => v1}/Host/index.jsx | 2 +- .../{ => v1}/HttpStatusLabel/index.jsx | 2 +- .../Components/{ => v1}/I18nLoader/index.jsx | 2 +- .../src/Components/{ => v1}/IconBox/index.jsx | 0 .../src/Components/{ => v1}/Image/index.jsx | 0 .../src/Components/{ => v1}/InfoBox/index.jsx | 4 +- .../{ => v1}/Inputs/Checkbox/index.css | 0 .../{ => v1}/Inputs/Checkbox/index.jsx | 4 +- .../{ => v1}/Inputs/ColorPicker/index.jsx | 0 .../{ => v1}/Inputs/FieldWrapper/index.jsx | 0 .../{ => v1}/Inputs/ImageUpload/index.jsx | 4 +- .../{ => v1}/Inputs/Radio/index.css | 0 .../{ => v1}/Inputs/Radio/index.jsx | 2 +- .../{ => v1}/Inputs/Search/index.jsx | 4 +- .../{ => v1}/Inputs/Select/index.css | 0 .../{ => v1}/Inputs/Select/index.jsx | 2 +- .../Inputs/TextInput/Adornments/index.jsx | 2 +- .../{ => v1}/Inputs/TextInput/index.jsx | 2 +- .../src/Components/{ => v1}/Label/index.css | 0 .../src/Components/{ => v1}/Label/index.jsx | 0 .../{ => v1}/Layouts/AppLayout/index.jsx | 2 +- .../{ => v1}/Layouts/HomeLayout/index.css | 0 .../{ => v1}/Layouts/HomeLayout/index.jsx | 2 +- client/src/Components/{ => v1}/Link/index.jsx | 0 client/src/Components/{ => v1}/Link/link.css | 0 .../{ => v1}/MonitorActions/index.jsx | 4 +- .../{ => v1}/MonitorCountHeader/index.jsx | 2 +- .../{ => v1}/MonitorCountHeader/skeleton.jsx | 0 .../{ => v1}/MonitorCreateHeader/index.jsx | 2 +- .../{ => v1}/MonitorCreateHeader/skeleton.jsx | 0 .../MonitorDetailsControlHeader/index.jsx | 10 ++--- .../MonitorDetailsControlHeader/skeleton.jsx | 0 .../MonitorDetailsControlHeader/status.jsx | 10 ++--- .../{ => v1}/MonitorTimeFrameHeader/index.jsx | 2 +- .../MonitorTimeFrameHeader/skeleton.jsx | 0 .../{ => v1}/NotificationConfig/index.jsx | 2 +- .../NotificationIntegrationModal.jsx | 6 +-- .../Components/TabComponent.jsx | 4 +- .../Components/TabPanel.jsx | 0 .../Hooks/useNotification.js | 4 +- .../{ => v1}/PageStateWrapper/index.jsx | 6 +-- .../{ => v1}/ProgressBars/index.css | 0 .../{ => v1}/ProgressBars/index.jsx | 0 .../{ => v1}/ProgressStepper/index.css | 0 .../{ => v1}/ProgressStepper/index.jsx | 0 .../{ => v1}/ProtectedRoute/index.jsx | 0 .../{ => v1}/RoleProtectedRoute/index.jsx | 0 .../{ => v1}/ShareComponent/index.jsx | 0 .../Sidebar/components/authFooter.jsx | 8 ++-- .../Sidebar/components/collapseButton.jsx | 6 +-- .../{ => v1}/Sidebar/components/logo.jsx | 0 .../{ => v1}/Sidebar/components/navItem.jsx | 0 .../src/Components/{ => v1}/Sidebar/index.jsx | 42 +++++++++---------- .../{ => v1}/Skeletons/FullPage/index.jsx | 0 .../{ => v1}/StandardContainer/index.jsx | 0 .../Components/{ => v1}/StarPrompt/index.jsx | 2 +- .../src/Components/{ => v1}/StatBox/index.jsx | 4 +- .../Components/{ => v1}/StatusBoxes/index.jsx | 2 +- .../{ => v1}/StatusBoxes/skeleton.jsx | 0 .../Components/{ => v1}/Subheader/index.jsx | 0 client/src/Components/{ => v1}/Tab/index.jsx | 0 .../Table/TablePagination/Actions/index.jsx | 4 +- .../{ => v1}/Table/TablePagination/index.jsx | 4 +- .../Components/{ => v1}/Table/TableUtils.js | 0 .../src/Components/{ => v1}/Table/index.jsx | 2 +- .../Components/{ => v1}/Table/skeleton.jsx | 0 .../Components/{ => v1}/TextLink/index.jsx | 0 .../{ => v1}/ThemeSwitch/SunAndMoonIcon.jsx | 0 .../Components/{ => v1}/ThemeSwitch/index.css | 0 .../Components/{ => v1}/ThemeSwitch/index.jsx | 4 +- client/src/Components/{ => v1}/Toast/body.jsx | 0 .../src/Components/{ => v1}/Toast/index.jsx | 2 +- .../indexV2.tsx => v2/Inputs/TextInput.tsx} | 0 client/src/Pages/v1/About/index.jsx | 4 +- .../src/Pages/v1/Account/EditUser/index.jsx | 6 +-- .../components/AddTeamMember/index.jsx | 8 ++-- .../v1/Account/components/PasswordPanel.jsx | 6 +-- .../v1/Account/components/ProfilePanel.jsx | 10 ++--- .../v1/Account/components/RoleTable/index.jsx | 2 +- .../Pages/v1/Account/components/TeamPanel.jsx | 8 ++-- client/src/Pages/v1/Account/index.jsx | 2 +- client/src/Pages/v1/Auth/CheckEmail.jsx | 2 +- client/src/Pages/v1/Auth/ForgotPassword.jsx | 4 +- client/src/Pages/v1/Auth/Login/index.jsx | 6 +-- .../Pages/v1/Auth/NewPasswordConfirmed.jsx | 2 +- client/src/Pages/v1/Auth/Register/index.jsx | 2 +- client/src/Pages/v1/Auth/SetNewPassword.jsx | 8 ++-- .../Pages/v1/Auth/components/AuthHeader.jsx | 2 +- .../v1/Auth/components/PasswordTooltip.jsx | 2 +- .../Components/IncidentTable/index.jsx | 14 +++---- .../Components/OptionsHeader/index.jsx | 2 +- client/src/Pages/v1/Incidents/index.jsx | 6 +-- .../Create/Components/CustomAlertsSection.jsx | 2 +- .../Components/CustomThreshold/index.jsx | 4 +- .../Components/MonitorActionButtons.jsx | 2 +- .../Create/Components/MonitorStatusHeader.jsx | 2 +- .../Pages/v1/Infrastructure/Create/index.jsx | 16 +++---- .../AreaChartBoxes/InfraAreaChart.jsx | 2 +- .../Components/AreaChartBoxes/index.jsx | 2 +- .../Details/Components/GaugeBoxes/Gauge.jsx | 2 +- .../Components/NetworkStats/NetworkCharts.jsx | 2 +- .../NetworkStats/NetworkStatBoxes.jsx | 4 +- .../Details/Components/NetworkStats/index.jsx | 4 +- .../Details/Components/StatusBoxes/index.jsx | 4 +- .../Pages/v1/Infrastructure/Details/index.jsx | 10 ++--- .../Monitors/Components/Filters/index.jsx | 2 +- .../Components/MonitorsTable/index.jsx | 8 ++-- .../Components/MonitorsTableMenu/index.jsx | 2 +- .../v1/Infrastructure/Monitors/index.jsx | 10 ++--- .../Diagnostics/components/gauges/index.jsx | 2 +- .../src/Pages/v1/Logs/Diagnostics/index.jsx | 4 +- client/src/Pages/v1/Logs/Logs/index.jsx | 2 +- .../Queue/components/FailedJobTable/index.jsx | 2 +- .../Logs/Queue/components/JobTable/index.jsx | 4 +- .../Logs/Queue/components/Metrics/index.jsx | 4 +- .../Queue/components/MetricsTable/index.jsx | 2 +- client/src/Pages/v1/Logs/index.jsx | 2 +- .../Components/ConfigSelect/index.jsx | 2 +- .../Components/MonitorsConfig/index.jsx | 4 +- .../Maintenance/CreateMaintenance/index.jsx | 8 ++-- .../MaintenanceTable/ActionsMenu/index.jsx | 2 +- .../v1/Maintenance/MaintenanceTable/index.jsx | 6 +-- client/src/Pages/v1/Maintenance/index.jsx | 4 +- .../Pages/v1/Notifications/create/index.jsx | 10 ++--- client/src/Pages/v1/Notifications/index.jsx | 6 +-- .../src/Pages/v1/PageSpeed/Create/index.jsx | 18 ++++---- .../Components/Charts/AreaChartLegend.jsx | 4 +- .../Components/Charts/PieChartLegend.jsx | 2 +- .../Components/PageSpeedAreaChart/index.jsx | 2 +- .../Components/PageSpeedStatusBoxes/index.jsx | 4 +- .../Components/PerformanceReport/index.jsx | 2 +- .../src/Pages/v1/PageSpeed/Details/index.jsx | 8 ++-- .../Monitors/Components/Card/index.jsx | 4 +- .../src/Pages/v1/PageSpeed/Monitors/index.jsx | 10 ++--- client/src/Pages/v1/ServerUnreachable.jsx | 4 +- .../src/Pages/v1/Settings/SettingsAbout.jsx | 4 +- .../v1/Settings/SettingsDemoMonitors.jsx | 4 +- .../src/Pages/v1/Settings/SettingsEmail.jsx | 8 ++-- .../v1/Settings/SettingsGlobalThresholds.jsx | 4 +- .../Pages/v1/Settings/SettingsPagespeed.jsx | 6 +-- .../src/Pages/v1/Settings/SettingsStats.jsx | 6 +-- .../Pages/v1/Settings/SettingsTimeZone.jsx | 4 +- client/src/Pages/v1/Settings/SettingsUI.jsx | 4 +- client/src/Pages/v1/Settings/SettingsURL.jsx | 4 +- client/src/Pages/v1/Settings/index.jsx | 2 +- .../Create/Components/Progress/index.jsx | 2 +- .../Create/Components/Tabs/ConfigStack.jsx | 2 +- .../Create/Components/Tabs/Content.jsx | 4 +- .../Create/Components/Tabs/Settings.jsx | 12 +++--- .../Create/Components/Tabs/index.jsx | 2 +- .../src/Pages/v1/StatusPage/Create/index.jsx | 6 +-- .../Components/ControlsHeader/index.jsx | 4 +- .../Status/Components/MonitorsList/index.jsx | 6 +-- .../src/Pages/v1/StatusPage/Status/index.jsx | 6 +-- .../Components/StatusPagesTable/index.jsx | 4 +- .../Pages/v1/StatusPage/StatusPages/index.jsx | 6 +-- .../src/Pages/v1/Uptime/BulkImport/index.jsx | 4 +- client/src/Pages/v1/Uptime/Create/index.jsx | 20 ++++----- .../Details/Components/ChartBoxes/index.jsx | 2 +- .../Components/Charts/ResponseTimeChart.jsx | 4 +- .../Components/ResponseTable/index.jsx | 8 ++-- .../Components/UptimeStatusBoxes/index.jsx | 4 +- client/src/Pages/v1/Uptime/Details/index.jsx | 8 ++-- .../Monitors/Components/Filter/index.jsx | 2 +- .../Components/SearchComponent/index.jsx | 2 +- .../Components/UptimeDataTable/index.jsx | 12 +++--- client/src/Pages/v1/Uptime/Monitors/index.jsx | 10 ++--- client/src/Pages/v2/Auth/Login.tsx | 2 +- client/src/Pages/v2/Auth/Register.tsx | 2 +- client/src/Routes/index.jsx | 8 ++-- client/src/Utils/toastUtils.jsx | 2 +- client/src/main.jsx | 2 +- 222 files changed, 370 insertions(+), 370 deletions(-) rename client/src/Components/{ => v1}/ActionsMenu/index.jsx (94%) rename client/src/Components/{ => v1}/Alert/index.css (100%) rename client/src/Components/{ => v1}/Alert/index.jsx (100%) rename client/src/Components/{ => v1}/Animated/PulseDot.jsx (100%) rename client/src/Components/{ => v1}/ArrowLeft/index.jsx (70%) rename client/src/Components/{ => v1}/ArrowRight/index.jsx (73%) rename client/src/Components/{ => v1}/Avatar/index.css (100%) rename client/src/Components/{ => v1}/Avatar/index.jsx (100%) rename client/src/Components/{ => v1}/Breadcrumbs/index.css (100%) rename client/src/Components/{ => v1}/Breadcrumbs/index.jsx (97%) rename client/src/Components/{ => v1}/Buttons/RoundGradientButton.jsx (100%) rename client/src/Components/{ => v1}/Charts/AreaChart/index.jsx (98%) rename client/src/Components/{ => v1}/Charts/BarChart/index.css (100%) rename client/src/Components/{ => v1}/Charts/BarChart/index.jsx (98%) rename client/src/Components/{ => v1}/Charts/ChartBox/EmptyView.jsx (98%) rename client/src/Components/{ => v1}/Charts/ChartBox/index.jsx (95%) rename client/src/Components/{ => v1}/Charts/CustomGauge/index.css (100%) rename client/src/Components/{ => v1}/Charts/CustomGauge/index.jsx (100%) rename client/src/Components/{ => v1}/Charts/DePINStatusPageBarChart/index.jsx (98%) rename client/src/Components/{ => v1}/Charts/LegendBox/index.jsx (95%) rename client/src/Components/{ => v1}/Charts/MonitorDetailsAreaChart/index.css (100%) rename client/src/Components/{ => v1}/Charts/MonitorDetailsAreaChart/index.jsx (97%) rename client/src/Components/{ => v1}/Charts/StatusPageBarChart/index.jsx (98%) rename client/src/Components/{ => v1}/Charts/Utils/chartUtilFunctions.js (100%) rename client/src/Components/{ => v1}/Charts/Utils/chartUtils.jsx (98%) rename client/src/Components/{ => v1}/Charts/Utils/gradientUtils.jsx (100%) rename client/src/Components/{ => v1}/Check/Check.jsx (93%) rename client/src/Components/{ => v1}/Check/check.css (100%) rename client/src/Components/{ => v1}/CircularCount/index.jsx (100%) rename client/src/Components/{ => v1}/Common/AppBar.jsx (99%) rename client/src/Components/{ => v1}/Common/Footer.jsx (100%) rename client/src/Components/{ => v1}/ConfigBox/index.jsx (100%) rename client/src/Components/{ => v1}/ConfigRow/index.jsx (96%) rename client/src/Components/{ => v1}/Dialog/genericDialog.jsx (100%) rename client/src/Components/{ => v1}/Dialog/index.jsx (95%) rename client/src/Components/{ => v1}/Dot/index.jsx (100%) rename client/src/Components/{ => v1}/Fallback/FallBackActionButtons.jsx (100%) rename client/src/Components/{ => v1}/Fallback/FallbackBackground.jsx (78%) rename client/src/Components/{ => v1}/Fallback/FallbackCheckList.jsx (95%) rename client/src/Components/{ => v1}/Fallback/FallbackContainer.jsx (100%) rename client/src/Components/{ => v1}/Fallback/FallbackPageSpeedWarning.jsx (97%) rename client/src/Components/{ => v1}/Fallback/FallbackTitle.jsx (100%) rename client/src/Components/{ => v1}/Fallback/index.css (100%) rename client/src/Components/{ => v1}/Fallback/index.jsx (87%) rename client/src/Components/{ => v1}/FilterHeader/index.jsx (100%) rename client/src/Components/{ => v1}/GenericFallback/NetworkError.jsx (100%) rename client/src/Components/{ => v1}/GenericFallback/index.jsx (89%) rename client/src/Components/{ => v1}/HOC/withAdminCheck.jsx (91%) rename client/src/Components/{ => v1}/Heading/index.jsx (100%) rename client/src/Components/{ => v1}/Host/index.jsx (97%) rename client/src/Components/{ => v1}/HttpStatusLabel/index.jsx (96%) rename client/src/Components/{ => v1}/I18nLoader/index.jsx (88%) rename client/src/Components/{ => v1}/IconBox/index.jsx (100%) rename client/src/Components/{ => v1}/Image/index.jsx (100%) rename client/src/Components/{ => v1}/InfoBox/index.jsx (88%) rename client/src/Components/{ => v1}/Inputs/Checkbox/index.css (100%) rename client/src/Components/{ => v1}/Inputs/Checkbox/index.jsx (95%) rename client/src/Components/{ => v1}/Inputs/ColorPicker/index.jsx (100%) rename client/src/Components/{ => v1}/Inputs/FieldWrapper/index.jsx (100%) rename client/src/Components/{ => v1}/Inputs/ImageUpload/index.jsx (98%) rename client/src/Components/{ => v1}/Inputs/Radio/index.css (100%) rename client/src/Components/{ => v1}/Inputs/Radio/index.jsx (96%) rename client/src/Components/{ => v1}/Inputs/Search/index.jsx (98%) rename client/src/Components/{ => v1}/Inputs/Select/index.css (100%) rename client/src/Components/{ => v1}/Inputs/Select/index.jsx (98%) rename client/src/Components/{ => v1}/Inputs/TextInput/Adornments/index.jsx (96%) rename client/src/Components/{ => v1}/Inputs/TextInput/index.jsx (98%) rename client/src/Components/{ => v1}/Label/index.css (100%) rename client/src/Components/{ => v1}/Label/index.jsx (100%) rename client/src/Components/{ => v1}/Layouts/AppLayout/index.jsx (91%) rename client/src/Components/{ => v1}/Layouts/HomeLayout/index.css (100%) rename client/src/Components/{ => v1}/Layouts/HomeLayout/index.jsx (88%) rename client/src/Components/{ => v1}/Link/index.jsx (100%) rename client/src/Components/{ => v1}/Link/link.css (100%) rename client/src/Components/{ => v1}/MonitorActions/index.jsx (95%) rename client/src/Components/{ => v1}/MonitorCountHeader/index.jsx (95%) rename client/src/Components/{ => v1}/MonitorCountHeader/skeleton.jsx (100%) rename client/src/Components/{ => v1}/MonitorCreateHeader/index.jsx (94%) rename client/src/Components/{ => v1}/MonitorCreateHeader/skeleton.jsx (100%) rename client/src/Components/{ => v1}/MonitorDetailsControlHeader/index.jsx (92%) rename client/src/Components/{ => v1}/MonitorDetailsControlHeader/skeleton.jsx (100%) rename client/src/Components/{ => v1}/MonitorDetailsControlHeader/status.jsx (83%) rename client/src/Components/{ => v1}/MonitorTimeFrameHeader/index.jsx (97%) rename client/src/Components/{ => v1}/MonitorTimeFrameHeader/skeleton.jsx (100%) rename client/src/Components/{ => v1}/NotificationConfig/index.jsx (98%) rename client/src/Components/{ => v1}/NotificationIntegrationModal/Components/NotificationIntegrationModal.jsx (98%) rename client/src/Components/{ => v1}/NotificationIntegrationModal/Components/TabComponent.jsx (95%) rename client/src/Components/{ => v1}/NotificationIntegrationModal/Components/TabPanel.jsx (100%) rename client/src/Components/{ => v1}/NotificationIntegrationModal/Hooks/useNotification.js (96%) rename client/src/Components/{ => v1}/PageStateWrapper/index.jsx (90%) rename client/src/Components/{ => v1}/ProgressBars/index.css (100%) rename client/src/Components/{ => v1}/ProgressBars/index.jsx (100%) rename client/src/Components/{ => v1}/ProgressStepper/index.css (100%) rename client/src/Components/{ => v1}/ProgressStepper/index.jsx (100%) rename client/src/Components/{ => v1}/ProtectedRoute/index.jsx (100%) rename client/src/Components/{ => v1}/RoleProtectedRoute/index.jsx (100%) rename client/src/Components/{ => v1}/ShareComponent/index.jsx (100%) rename client/src/Components/{ => v1}/Sidebar/components/authFooter.jsx (96%) rename client/src/Components/{ => v1}/Sidebar/components/collapseButton.jsx (88%) rename client/src/Components/{ => v1}/Sidebar/components/logo.jsx (100%) rename client/src/Components/{ => v1}/Sidebar/components/navItem.jsx (100%) rename client/src/Components/{ => v1}/Sidebar/index.jsx (75%) rename client/src/Components/{ => v1}/Skeletons/FullPage/index.jsx (100%) rename client/src/Components/{ => v1}/StandardContainer/index.jsx (100%) rename client/src/Components/{ => v1}/StarPrompt/index.jsx (97%) rename client/src/Components/{ => v1}/StatBox/index.jsx (97%) rename client/src/Components/{ => v1}/StatusBoxes/index.jsx (93%) rename client/src/Components/{ => v1}/StatusBoxes/skeleton.jsx (100%) rename client/src/Components/{ => v1}/Subheader/index.jsx (100%) rename client/src/Components/{ => v1}/Tab/index.jsx (100%) rename client/src/Components/{ => v1}/Table/TablePagination/Actions/index.jsx (95%) rename client/src/Components/{ => v1}/Table/TablePagination/index.jsx (96%) rename client/src/Components/{ => v1}/Table/TableUtils.js (100%) rename client/src/Components/{ => v1}/Table/index.jsx (99%) rename client/src/Components/{ => v1}/Table/skeleton.jsx (100%) rename client/src/Components/{ => v1}/TextLink/index.jsx (100%) rename client/src/Components/{ => v1}/ThemeSwitch/SunAndMoonIcon.jsx (100%) rename client/src/Components/{ => v1}/ThemeSwitch/index.css (100%) rename client/src/Components/{ => v1}/ThemeSwitch/index.jsx (91%) rename client/src/Components/{ => v1}/Toast/body.jsx (100%) rename client/src/Components/{ => v1}/Toast/index.jsx (98%) rename client/src/Components/{Inputs/TextInput/indexV2.tsx => v2/Inputs/TextInput.tsx} (100%) diff --git a/client/src/App.jsx b/client/src/App.jsx index b599890f1..04f0d5b65 100644 --- a/client/src/App.jsx +++ b/client/src/App.jsx @@ -10,7 +10,7 @@ import { logger } from "./Utils/Logger"; // Import the logger import { networkService } from "./main"; import { Routes } from "./Routes"; import WalletProvider from "./Components/WalletProvider"; -import AppLayout from "./Components/Layouts/AppLayout"; +import AppLayout from "@/Components/v1/Layouts/AppLayout"; function App() { const mode = useSelector((state) => state.ui.mode); diff --git a/client/src/Components/ActionsMenu/index.jsx b/client/src/Components/v1/ActionsMenu/index.jsx similarity index 94% rename from client/src/Components/ActionsMenu/index.jsx rename to client/src/Components/v1/ActionsMenu/index.jsx index 78d86e1b2..229c7c875 100644 --- a/client/src/Components/ActionsMenu/index.jsx +++ b/client/src/Components/v1/ActionsMenu/index.jsx @@ -2,17 +2,17 @@ import IconButton from "@mui/material/IconButton"; import Menu from "@mui/material/Menu"; import MenuItem from "@mui/material/MenuItem"; -import Settings from "../../assets/icons/settings-bold.svg?react"; -import Dialog from "../../Components/Dialog"; +import Settings from "../../../assets/icons/settings-bold.svg?react"; +import Dialog from "../Dialog/index.jsx"; // Utils import { useState } from "react"; import { useTheme } from "@emotion/react"; import { useNavigate } from "react-router-dom"; -import { createToast } from "../../Utils/toastUtils"; +import { createToast } from "../../../Utils/toastUtils.jsx"; import PropTypes from "prop-types"; -import { usePauseMonitor, useDeleteMonitor } from "../../Hooks/v1/monitorHooks.js"; +import { usePauseMonitor, useDeleteMonitor } from "../../../Hooks/v1/monitorHooks.js"; const ActionsMenu = ({ monitor, diff --git a/client/src/Components/Alert/index.css b/client/src/Components/v1/Alert/index.css similarity index 100% rename from client/src/Components/Alert/index.css rename to client/src/Components/v1/Alert/index.css diff --git a/client/src/Components/Alert/index.jsx b/client/src/Components/v1/Alert/index.jsx similarity index 100% rename from client/src/Components/Alert/index.jsx rename to client/src/Components/v1/Alert/index.jsx diff --git a/client/src/Components/Animated/PulseDot.jsx b/client/src/Components/v1/Animated/PulseDot.jsx similarity index 100% rename from client/src/Components/Animated/PulseDot.jsx rename to client/src/Components/v1/Animated/PulseDot.jsx diff --git a/client/src/Components/ArrowLeft/index.jsx b/client/src/Components/v1/ArrowLeft/index.jsx similarity index 70% rename from client/src/Components/ArrowLeft/index.jsx rename to client/src/Components/v1/ArrowLeft/index.jsx index 7909441f3..a327d6bb7 100644 --- a/client/src/Components/ArrowLeft/index.jsx +++ b/client/src/Components/v1/ArrowLeft/index.jsx @@ -1,6 +1,6 @@ -import LeftArrow from "../../assets/icons/left-arrow.svg?react"; -import LeftArrowDouble from "../../assets/icons/left-arrow-double.svg?react"; -import LeftArrowLong from "../../assets/icons/left-arrow-long.svg?react"; +import LeftArrow from "../../../assets/icons/left-arrow.svg?react"; +import LeftArrowDouble from "../../../assets/icons/left-arrow-double.svg?react"; +import LeftArrowLong from "../../../assets/icons/left-arrow-long.svg?react"; import PropTypes from "prop-types"; const ArrowLeft = ({ type, color = "#667085", ...props }) => { diff --git a/client/src/Components/ArrowRight/index.jsx b/client/src/Components/v1/ArrowRight/index.jsx similarity index 73% rename from client/src/Components/ArrowRight/index.jsx rename to client/src/Components/v1/ArrowRight/index.jsx index 335bc19f2..08a2639b0 100644 --- a/client/src/Components/ArrowRight/index.jsx +++ b/client/src/Components/v1/ArrowRight/index.jsx @@ -1,5 +1,5 @@ -import RightArrow from "../../assets/icons/right-arrow.svg?react"; -import RightArrowDouble from "../../assets/icons/right-arrow-double.svg?react"; +import RightArrow from "../../../assets/icons/right-arrow.svg?react"; +import RightArrowDouble from "../../../assets/icons/right-arrow-double.svg?react"; import PropTypes from "prop-types"; const ArrowRight = ({ type, color = "#667085", ...props }) => { diff --git a/client/src/Components/Avatar/index.css b/client/src/Components/v1/Avatar/index.css similarity index 100% rename from client/src/Components/Avatar/index.css rename to client/src/Components/v1/Avatar/index.css diff --git a/client/src/Components/Avatar/index.jsx b/client/src/Components/v1/Avatar/index.jsx similarity index 100% rename from client/src/Components/Avatar/index.jsx rename to client/src/Components/v1/Avatar/index.jsx diff --git a/client/src/Components/Breadcrumbs/index.css b/client/src/Components/v1/Breadcrumbs/index.css similarity index 100% rename from client/src/Components/Breadcrumbs/index.css rename to client/src/Components/v1/Breadcrumbs/index.css diff --git a/client/src/Components/Breadcrumbs/index.jsx b/client/src/Components/v1/Breadcrumbs/index.jsx similarity index 97% rename from client/src/Components/Breadcrumbs/index.jsx rename to client/src/Components/v1/Breadcrumbs/index.jsx index f1af89790..34e5d5e95 100644 --- a/client/src/Components/Breadcrumbs/index.jsx +++ b/client/src/Components/v1/Breadcrumbs/index.jsx @@ -2,7 +2,7 @@ import PropTypes from "prop-types"; import { Box, Breadcrumbs as MUIBreadcrumbs } from "@mui/material"; import { useTheme } from "@emotion/react"; import { useNavigate } from "react-router-dom"; -import ArrowRight from "../ArrowRight"; +import ArrowRight from "../ArrowRight/index.jsx"; import "./index.css"; /** diff --git a/client/src/Components/Buttons/RoundGradientButton.jsx b/client/src/Components/v1/Buttons/RoundGradientButton.jsx similarity index 100% rename from client/src/Components/Buttons/RoundGradientButton.jsx rename to client/src/Components/v1/Buttons/RoundGradientButton.jsx diff --git a/client/src/Components/Charts/AreaChart/index.jsx b/client/src/Components/v1/Charts/AreaChart/index.jsx similarity index 98% rename from client/src/Components/Charts/AreaChart/index.jsx rename to client/src/Components/v1/Charts/AreaChart/index.jsx index e1f1832e5..dd5a900d6 100644 --- a/client/src/Components/Charts/AreaChart/index.jsx +++ b/client/src/Components/v1/Charts/AreaChart/index.jsx @@ -61,7 +61,7 @@ import { Tooltip, ResponsiveContainer, } from "recharts"; -import { createGradient } from "../Utils/gradientUtils"; +import { createGradient } from "../Utils/gradientUtils.jsx"; import PropTypes from "prop-types"; import { useTheme } from "@mui/material"; import { useId } from "react"; diff --git a/client/src/Components/Charts/BarChart/index.css b/client/src/Components/v1/Charts/BarChart/index.css similarity index 100% rename from client/src/Components/Charts/BarChart/index.css rename to client/src/Components/v1/Charts/BarChart/index.css diff --git a/client/src/Components/Charts/BarChart/index.jsx b/client/src/Components/v1/Charts/BarChart/index.jsx similarity index 98% rename from client/src/Components/Charts/BarChart/index.jsx rename to client/src/Components/v1/Charts/BarChart/index.jsx index 375773b94..2bf8e1798 100644 --- a/client/src/Components/Charts/BarChart/index.jsx +++ b/client/src/Components/v1/Charts/BarChart/index.jsx @@ -1,6 +1,6 @@ import { useTheme } from "@emotion/react"; import { Box, Stack, Tooltip, Typography } from "@mui/material"; -import { formatDateWithTz } from "../../../Utils/timeUtils"; +import { formatDateWithTz } from "../../../../Utils/timeUtils.js"; import { useEffect, useState } from "react"; import "./index.css"; import { useSelector } from "react-redux"; diff --git a/client/src/Components/Charts/ChartBox/EmptyView.jsx b/client/src/Components/v1/Charts/ChartBox/EmptyView.jsx similarity index 98% rename from client/src/Components/Charts/ChartBox/EmptyView.jsx rename to client/src/Components/v1/Charts/ChartBox/EmptyView.jsx index 296197a8c..c1b60632d 100644 --- a/client/src/Components/Charts/ChartBox/EmptyView.jsx +++ b/client/src/Components/v1/Charts/ChartBox/EmptyView.jsx @@ -2,7 +2,7 @@ import { Typography, Stack } from "@mui/material"; import PropTypes from "prop-types"; import { useTheme } from "@emotion/react"; -import IconBox from "../../IconBox"; +import IconBox from "../../IconBox/index.jsx"; /** * `EmptyView` is a functional React component that displays an empty state view with an optional icon, header, and message. diff --git a/client/src/Components/Charts/ChartBox/index.jsx b/client/src/Components/v1/Charts/ChartBox/index.jsx similarity index 95% rename from client/src/Components/Charts/ChartBox/index.jsx rename to client/src/Components/v1/Charts/ChartBox/index.jsx index 2c13ece87..be2faf8ed 100644 --- a/client/src/Components/Charts/ChartBox/index.jsx +++ b/client/src/Components/v1/Charts/ChartBox/index.jsx @@ -1,7 +1,7 @@ import { Stack, Typography } from "@mui/material"; import { useTheme } from "@emotion/react"; -import IconBox from "../../IconBox"; -import EmptyView from "./EmptyView"; +import IconBox from "../../IconBox/index.jsx"; +import EmptyView from "./EmptyView.jsx"; import PropTypes from "prop-types"; const ChartBox = ({ diff --git a/client/src/Components/Charts/CustomGauge/index.css b/client/src/Components/v1/Charts/CustomGauge/index.css similarity index 100% rename from client/src/Components/Charts/CustomGauge/index.css rename to client/src/Components/v1/Charts/CustomGauge/index.css diff --git a/client/src/Components/Charts/CustomGauge/index.jsx b/client/src/Components/v1/Charts/CustomGauge/index.jsx similarity index 100% rename from client/src/Components/Charts/CustomGauge/index.jsx rename to client/src/Components/v1/Charts/CustomGauge/index.jsx diff --git a/client/src/Components/Charts/DePINStatusPageBarChart/index.jsx b/client/src/Components/v1/Charts/DePINStatusPageBarChart/index.jsx similarity index 98% rename from client/src/Components/Charts/DePINStatusPageBarChart/index.jsx rename to client/src/Components/v1/Charts/DePINStatusPageBarChart/index.jsx index 65c6278f1..b37990e46 100644 --- a/client/src/Components/Charts/DePINStatusPageBarChart/index.jsx +++ b/client/src/Components/v1/Charts/DePINStatusPageBarChart/index.jsx @@ -5,7 +5,7 @@ import { Stack, Box, Tooltip, Typography } from "@mui/material"; import { useTheme } from "@emotion/react"; import { useState, useEffect } from "react"; import PropTypes from "prop-types"; -import { formatDateWithTz } from "../../../Utils/timeUtils"; +import { formatDateWithTz } from "../../../../Utils/timeUtils.js"; import { useSelector } from "react-redux"; const PlaceholderCheck = ({ daysToShow }) => { diff --git a/client/src/Components/Charts/LegendBox/index.jsx b/client/src/Components/v1/Charts/LegendBox/index.jsx similarity index 95% rename from client/src/Components/Charts/LegendBox/index.jsx rename to client/src/Components/v1/Charts/LegendBox/index.jsx index ac14669e3..f19e09105 100644 --- a/client/src/Components/Charts/LegendBox/index.jsx +++ b/client/src/Components/v1/Charts/LegendBox/index.jsx @@ -1,6 +1,6 @@ import { Stack, Typography } from "@mui/material"; import { useTheme } from "@emotion/react"; -import IconBox from "../../IconBox"; +import IconBox from "../../IconBox/index.jsx"; import PropTypes from "prop-types"; const LegendBox = ({ children, icon, header, sx }) => { diff --git a/client/src/Components/Charts/MonitorDetailsAreaChart/index.css b/client/src/Components/v1/Charts/MonitorDetailsAreaChart/index.css similarity index 100% rename from client/src/Components/Charts/MonitorDetailsAreaChart/index.css rename to client/src/Components/v1/Charts/MonitorDetailsAreaChart/index.css diff --git a/client/src/Components/Charts/MonitorDetailsAreaChart/index.jsx b/client/src/Components/v1/Charts/MonitorDetailsAreaChart/index.jsx similarity index 97% rename from client/src/Components/Charts/MonitorDetailsAreaChart/index.jsx rename to client/src/Components/v1/Charts/MonitorDetailsAreaChart/index.jsx index a1ead3cf9..a9ef04a53 100644 --- a/client/src/Components/Charts/MonitorDetailsAreaChart/index.jsx +++ b/client/src/Components/v1/Charts/MonitorDetailsAreaChart/index.jsx @@ -12,11 +12,11 @@ import { Box, Stack, Typography } from "@mui/material"; import { useTheme } from "@emotion/react"; import { useMemo, useState } from "react"; import { useSelector } from "react-redux"; -import { formatDateWithTz } from "../../../Utils/timeUtils"; +import { formatDateWithTz } from "../../../../Utils/timeUtils.js"; import { tooltipDateFormatLookup, tickDateFormatLookup, -} from "../Utils/chartUtilFunctions"; +} from "../Utils/chartUtilFunctions.js"; import "./index.css"; const CustomToolTip = ({ active, payload, label, dateRange }) => { diff --git a/client/src/Components/Charts/StatusPageBarChart/index.jsx b/client/src/Components/v1/Charts/StatusPageBarChart/index.jsx similarity index 98% rename from client/src/Components/Charts/StatusPageBarChart/index.jsx rename to client/src/Components/v1/Charts/StatusPageBarChart/index.jsx index eab0c7d1d..7da4d58ba 100644 --- a/client/src/Components/Charts/StatusPageBarChart/index.jsx +++ b/client/src/Components/v1/Charts/StatusPageBarChart/index.jsx @@ -1,6 +1,6 @@ import { useTheme } from "@emotion/react"; import { Box, Stack, Tooltip, Typography } from "@mui/material"; -import { formatDateWithTz } from "../../../Utils/timeUtils"; +import { formatDateWithTz } from "../../../../Utils/timeUtils.js"; import { useEffect, useState, forwardRef } from "react"; import { useSelector } from "react-redux"; import PropTypes from "prop-types"; diff --git a/client/src/Components/Charts/Utils/chartUtilFunctions.js b/client/src/Components/v1/Charts/Utils/chartUtilFunctions.js similarity index 100% rename from client/src/Components/Charts/Utils/chartUtilFunctions.js rename to client/src/Components/v1/Charts/Utils/chartUtilFunctions.js diff --git a/client/src/Components/Charts/Utils/chartUtils.jsx b/client/src/Components/v1/Charts/Utils/chartUtils.jsx similarity index 98% rename from client/src/Components/Charts/Utils/chartUtils.jsx rename to client/src/Components/v1/Charts/Utils/chartUtils.jsx index e24881c1c..1bab80595 100644 --- a/client/src/Components/Charts/Utils/chartUtils.jsx +++ b/client/src/Components/v1/Charts/Utils/chartUtils.jsx @@ -2,9 +2,9 @@ import PropTypes from "prop-types"; import { useSelector } from "react-redux"; import { useTheme } from "@mui/material"; import { Text } from "recharts"; -import { formatDateWithTz } from "../../../Utils/timeUtils"; +import { formatDateWithTz } from "../../../../Utils/timeUtils.js"; import { Box, Stack, Typography } from "@mui/material"; -import { tickDateFormatLookup, tooltipDateFormatLookup } from "./chartUtilFunctions"; +import { tickDateFormatLookup, tooltipDateFormatLookup } from "./chartUtilFunctions.js"; /** * Custom tick component for rendering time with timezone. * diff --git a/client/src/Components/Charts/Utils/gradientUtils.jsx b/client/src/Components/v1/Charts/Utils/gradientUtils.jsx similarity index 100% rename from client/src/Components/Charts/Utils/gradientUtils.jsx rename to client/src/Components/v1/Charts/Utils/gradientUtils.jsx diff --git a/client/src/Components/Check/Check.jsx b/client/src/Components/v1/Check/Check.jsx similarity index 93% rename from client/src/Components/Check/Check.jsx rename to client/src/Components/v1/Check/Check.jsx index 02b9fd78a..53971316b 100644 --- a/client/src/Components/Check/Check.jsx +++ b/client/src/Components/v1/Check/Check.jsx @@ -1,7 +1,7 @@ import "./check.css"; import PropTypes from "prop-types"; -import CheckGrey from "../../assets/icons/check.svg?react"; -import CheckOutlined from "../../assets/icons/check-outlined.svg?react"; +import CheckGrey from "../../../assets/icons/check.svg?react"; +import CheckOutlined from "../../../assets/icons/check-outlined.svg?react"; import { Box, Stack, Typography } from "@mui/material"; import { useTheme } from "@emotion/react"; diff --git a/client/src/Components/Check/check.css b/client/src/Components/v1/Check/check.css similarity index 100% rename from client/src/Components/Check/check.css rename to client/src/Components/v1/Check/check.css diff --git a/client/src/Components/CircularCount/index.jsx b/client/src/Components/v1/CircularCount/index.jsx similarity index 100% rename from client/src/Components/CircularCount/index.jsx rename to client/src/Components/v1/CircularCount/index.jsx diff --git a/client/src/Components/Common/AppBar.jsx b/client/src/Components/v1/Common/AppBar.jsx similarity index 99% rename from client/src/Components/Common/AppBar.jsx rename to client/src/Components/v1/Common/AppBar.jsx index de5f0992e..1d5a38ddd 100644 --- a/client/src/Components/Common/AppBar.jsx +++ b/client/src/Components/v1/Common/AppBar.jsx @@ -10,7 +10,7 @@ import MenuItem from "@mui/material/MenuItem"; import Drawer from "@mui/material/Drawer"; import MenuIcon from "@mui/icons-material/Menu"; import CloseRoundedIcon from "@mui/icons-material/CloseRounded"; -import ThemeSwitch from "../ThemeSwitch"; +import ThemeSwitch from "../ThemeSwitch/index.jsx"; import { useTheme } from "@mui/material/styles"; import { useSelector } from "react-redux"; import { useLocation, useNavigate } from "react-router-dom"; diff --git a/client/src/Components/Common/Footer.jsx b/client/src/Components/v1/Common/Footer.jsx similarity index 100% rename from client/src/Components/Common/Footer.jsx rename to client/src/Components/v1/Common/Footer.jsx diff --git a/client/src/Components/ConfigBox/index.jsx b/client/src/Components/v1/ConfigBox/index.jsx similarity index 100% rename from client/src/Components/ConfigBox/index.jsx rename to client/src/Components/v1/ConfigBox/index.jsx diff --git a/client/src/Components/ConfigRow/index.jsx b/client/src/Components/v1/ConfigRow/index.jsx similarity index 96% rename from client/src/Components/ConfigRow/index.jsx rename to client/src/Components/v1/ConfigRow/index.jsx index 217085b6d..12fea923e 100644 --- a/client/src/Components/ConfigRow/index.jsx +++ b/client/src/Components/v1/ConfigRow/index.jsx @@ -1,7 +1,7 @@ import { Box, Stack, Typography } from "@mui/material"; import PropTypes from "prop-types"; import { useTheme } from "@emotion/react"; -import ConfigBox from "../ConfigBox"; +import ConfigBox from "../ConfigBox/index.jsx"; /** * ConfigRow is a styled container used to layout content in a row format with specific padding, border, and spacing. diff --git a/client/src/Components/Dialog/genericDialog.jsx b/client/src/Components/v1/Dialog/genericDialog.jsx similarity index 100% rename from client/src/Components/Dialog/genericDialog.jsx rename to client/src/Components/v1/Dialog/genericDialog.jsx diff --git a/client/src/Components/Dialog/index.jsx b/client/src/Components/v1/Dialog/index.jsx similarity index 95% rename from client/src/Components/Dialog/index.jsx rename to client/src/Components/v1/Dialog/index.jsx index 678eb8721..b8cf07439 100644 --- a/client/src/Components/Dialog/index.jsx +++ b/client/src/Components/v1/Dialog/index.jsx @@ -1,6 +1,6 @@ import PropTypes from "prop-types"; import { Button, Stack } from "@mui/material"; -import { GenericDialog } from "./genericDialog"; +import { GenericDialog } from "./genericDialog.jsx"; import { useTheme } from "@emotion/react"; import { useTranslation } from "react-i18next"; diff --git a/client/src/Components/Dot/index.jsx b/client/src/Components/v1/Dot/index.jsx similarity index 100% rename from client/src/Components/Dot/index.jsx rename to client/src/Components/v1/Dot/index.jsx diff --git a/client/src/Components/Fallback/FallBackActionButtons.jsx b/client/src/Components/v1/Fallback/FallBackActionButtons.jsx similarity index 100% rename from client/src/Components/Fallback/FallBackActionButtons.jsx rename to client/src/Components/v1/Fallback/FallBackActionButtons.jsx diff --git a/client/src/Components/Fallback/FallbackBackground.jsx b/client/src/Components/v1/Fallback/FallbackBackground.jsx similarity index 78% rename from client/src/Components/Fallback/FallbackBackground.jsx rename to client/src/Components/v1/Fallback/FallbackBackground.jsx index fe46f4064..2182082fd 100644 --- a/client/src/Components/Fallback/FallbackBackground.jsx +++ b/client/src/Components/v1/Fallback/FallbackBackground.jsx @@ -1,8 +1,8 @@ import { useTheme } from "@emotion/react"; import Box from "@mui/material/Box"; -import Background from "../../assets/Images/background-grid.svg?react"; -import OutputAnimation from "../../assets/Animations/output.gif"; -import DarkmodeOutput from "../../assets/Animations/darkmodeOutput.gif"; +import Background from "../../../assets/Images/background-grid.svg?react"; +import OutputAnimation from "../../../assets/Animations/output.gif"; +import DarkmodeOutput from "../../../assets/Animations/darkmodeOutput.gif"; import { useSelector } from "react-redux"; const FallbackBackground = () => { const theme = useTheme(); diff --git a/client/src/Components/Fallback/FallbackCheckList.jsx b/client/src/Components/v1/Fallback/FallbackCheckList.jsx similarity index 95% rename from client/src/Components/Fallback/FallbackCheckList.jsx rename to client/src/Components/v1/Fallback/FallbackCheckList.jsx index 4d13dead4..befb95185 100644 --- a/client/src/Components/Fallback/FallbackCheckList.jsx +++ b/client/src/Components/v1/Fallback/FallbackCheckList.jsx @@ -2,7 +2,7 @@ import PropTypes from "prop-types"; import { useTheme } from "@mui/material/styles"; import Box from "@mui/material/Box"; import Stack from "@mui/material/Stack"; -import Check from "../Check/Check"; +import Check from "../Check/Check.jsx"; const FallbackCheckList = ({ checks, type }) => { const theme = useTheme(); return ( diff --git a/client/src/Components/Fallback/FallbackContainer.jsx b/client/src/Components/v1/Fallback/FallbackContainer.jsx similarity index 100% rename from client/src/Components/Fallback/FallbackContainer.jsx rename to client/src/Components/v1/Fallback/FallbackContainer.jsx diff --git a/client/src/Components/Fallback/FallbackPageSpeedWarning.jsx b/client/src/Components/v1/Fallback/FallbackPageSpeedWarning.jsx similarity index 97% rename from client/src/Components/Fallback/FallbackPageSpeedWarning.jsx rename to client/src/Components/v1/Fallback/FallbackPageSpeedWarning.jsx index bcec7300d..2cf52bd88 100644 --- a/client/src/Components/Fallback/FallbackPageSpeedWarning.jsx +++ b/client/src/Components/v1/Fallback/FallbackPageSpeedWarning.jsx @@ -3,7 +3,7 @@ import { useTheme } from "@mui/material/styles"; import Link from "@mui/material/Link"; import { Link as RouterLink } from "react-router-dom"; import { useTranslation } from "react-i18next"; -import Alert from "../Alert"; +import Alert from "../Alert/index.jsx"; import PropTypes from "prop-types"; const renderWarningMessage = (t) => { diff --git a/client/src/Components/Fallback/FallbackTitle.jsx b/client/src/Components/v1/Fallback/FallbackTitle.jsx similarity index 100% rename from client/src/Components/Fallback/FallbackTitle.jsx rename to client/src/Components/v1/Fallback/FallbackTitle.jsx diff --git a/client/src/Components/Fallback/index.css b/client/src/Components/v1/Fallback/index.css similarity index 100% rename from client/src/Components/Fallback/index.css rename to client/src/Components/v1/Fallback/index.css diff --git a/client/src/Components/Fallback/index.jsx b/client/src/Components/v1/Fallback/index.jsx similarity index 87% rename from client/src/Components/Fallback/index.jsx rename to client/src/Components/v1/Fallback/index.jsx index 5fbb958e7..40113f882 100644 --- a/client/src/Components/Fallback/index.jsx +++ b/client/src/Components/v1/Fallback/index.jsx @@ -2,11 +2,11 @@ import PropTypes from "prop-types"; import { useTheme } from "@emotion/react"; import { Box, Stack } from "@mui/material"; import "./index.css"; -import FallbackTitle from "./FallbackTitle"; -import FallbackCheckList from "./FallbackCheckList"; -import FallbackActionButtons from "./FallBackActionButtons"; -import FallbackBackground from "./FallbackBackground"; -import FallbackContainer from "./FallbackContainer"; +import FallbackTitle from "./FallbackTitle.jsx"; +import FallbackCheckList from "./FallbackCheckList.jsx"; +import FallbackActionButtons from "./FallBackActionButtons.jsx"; +import FallbackBackground from "./FallbackBackground.jsx"; +import FallbackContainer from "./FallbackContainer.jsx"; /** * Fallback component to display a fallback UI with a title, a list of checks, and a navigation button. * diff --git a/client/src/Components/FilterHeader/index.jsx b/client/src/Components/v1/FilterHeader/index.jsx similarity index 100% rename from client/src/Components/FilterHeader/index.jsx rename to client/src/Components/v1/FilterHeader/index.jsx diff --git a/client/src/Components/GenericFallback/NetworkError.jsx b/client/src/Components/v1/GenericFallback/NetworkError.jsx similarity index 100% rename from client/src/Components/GenericFallback/NetworkError.jsx rename to client/src/Components/v1/GenericFallback/NetworkError.jsx diff --git a/client/src/Components/GenericFallback/index.jsx b/client/src/Components/v1/GenericFallback/index.jsx similarity index 89% rename from client/src/Components/GenericFallback/index.jsx rename to client/src/Components/v1/GenericFallback/index.jsx index 7f6d258ea..137cdd1da 100644 --- a/client/src/Components/GenericFallback/index.jsx +++ b/client/src/Components/v1/GenericFallback/index.jsx @@ -1,8 +1,8 @@ import { useTheme } from "@emotion/react"; import { Box, Stack } from "@mui/material"; -import OutputAnimation from "../../assets/Animations/output.gif"; -import DarkmodeOutput from "../../assets/Animations/darkmodeOutput.gif"; -import Background from "../../assets/Images/background-grid.svg?react"; +import OutputAnimation from "../../../assets/Animations/output.gif"; +import DarkmodeOutput from "../../../assets/Animations/darkmodeOutput.gif"; +import Background from "../../../assets/Images/background-grid.svg?react"; import { useSelector } from "react-redux"; /** diff --git a/client/src/Components/HOC/withAdminCheck.jsx b/client/src/Components/v1/HOC/withAdminCheck.jsx similarity index 91% rename from client/src/Components/HOC/withAdminCheck.jsx rename to client/src/Components/v1/HOC/withAdminCheck.jsx index 458330a1f..8646c99b6 100644 --- a/client/src/Components/HOC/withAdminCheck.jsx +++ b/client/src/Components/v1/HOC/withAdminCheck.jsx @@ -1,8 +1,8 @@ import { useEffect, useState } from "react"; import { useNavigate } from "react-router-dom"; -import { logger } from "../../Utils/Logger"; -import { networkService } from "../../main"; +import { logger } from "../../../Utils/Logger.js"; +import { networkService } from "../../../main.jsx"; const withAdminCheck = (WrappedComponent) => { const WithAdminCheck = (props) => { diff --git a/client/src/Components/Heading/index.jsx b/client/src/Components/v1/Heading/index.jsx similarity index 100% rename from client/src/Components/Heading/index.jsx rename to client/src/Components/v1/Heading/index.jsx diff --git a/client/src/Components/Host/index.jsx b/client/src/Components/v1/Host/index.jsx similarity index 97% rename from client/src/Components/Host/index.jsx rename to client/src/Components/v1/Host/index.jsx index d1cf382b2..772f5b61c 100644 --- a/client/src/Components/Host/index.jsx +++ b/client/src/Components/v1/Host/index.jsx @@ -1,7 +1,7 @@ import { Stack, Typography } from "@mui/material"; import PropTypes from "prop-types"; import { useTheme } from "@emotion/react"; -import Dot from "../Dot"; +import Dot from "../Dot/index.jsx"; /** * Host component. * This subcomponent receives a params object and displays the host details. diff --git a/client/src/Components/HttpStatusLabel/index.jsx b/client/src/Components/v1/HttpStatusLabel/index.jsx similarity index 96% rename from client/src/Components/HttpStatusLabel/index.jsx rename to client/src/Components/v1/HttpStatusLabel/index.jsx index 52676ae7c..2253e323e 100644 --- a/client/src/Components/HttpStatusLabel/index.jsx +++ b/client/src/Components/v1/HttpStatusLabel/index.jsx @@ -1,6 +1,6 @@ import PropTypes from "prop-types"; import { useTheme } from "@mui/material"; -import { BaseLabel } from "../Label"; +import { BaseLabel } from "../Label/index.jsx"; /** * @component diff --git a/client/src/Components/I18nLoader/index.jsx b/client/src/Components/v1/I18nLoader/index.jsx similarity index 88% rename from client/src/Components/I18nLoader/index.jsx rename to client/src/Components/v1/I18nLoader/index.jsx index 7c3cbfb85..2a6a4966d 100644 --- a/client/src/Components/I18nLoader/index.jsx +++ b/client/src/Components/v1/I18nLoader/index.jsx @@ -1,4 +1,4 @@ -import i18n from "../../Utils/i18n"; +import i18n from "../../../Utils/i18n.js"; import { useSelector } from "react-redux"; import { useEffect } from "react"; const I18nLoader = () => { diff --git a/client/src/Components/IconBox/index.jsx b/client/src/Components/v1/IconBox/index.jsx similarity index 100% rename from client/src/Components/IconBox/index.jsx rename to client/src/Components/v1/IconBox/index.jsx diff --git a/client/src/Components/Image/index.jsx b/client/src/Components/v1/Image/index.jsx similarity index 100% rename from client/src/Components/Image/index.jsx rename to client/src/Components/v1/Image/index.jsx diff --git a/client/src/Components/InfoBox/index.jsx b/client/src/Components/v1/InfoBox/index.jsx similarity index 88% rename from client/src/Components/InfoBox/index.jsx rename to client/src/Components/v1/InfoBox/index.jsx index 6797d6c9c..96e67975d 100644 --- a/client/src/Components/InfoBox/index.jsx +++ b/client/src/Components/v1/InfoBox/index.jsx @@ -1,6 +1,6 @@ -import { RowContainer } from "../StandardContainer"; +import { RowContainer } from "../StandardContainer/index.jsx"; import { Stack, Typography } from "@mui/material"; -import Image from "../Image"; +import Image from "../Image/index.jsx"; import { useTheme } from "@mui/material/styles"; diff --git a/client/src/Components/Inputs/Checkbox/index.css b/client/src/Components/v1/Inputs/Checkbox/index.css similarity index 100% rename from client/src/Components/Inputs/Checkbox/index.css rename to client/src/Components/v1/Inputs/Checkbox/index.css diff --git a/client/src/Components/Inputs/Checkbox/index.jsx b/client/src/Components/v1/Inputs/Checkbox/index.jsx similarity index 95% rename from client/src/Components/Inputs/Checkbox/index.jsx rename to client/src/Components/v1/Inputs/Checkbox/index.jsx index 1497c44fb..08af49d84 100644 --- a/client/src/Components/Inputs/Checkbox/index.jsx +++ b/client/src/Components/v1/Inputs/Checkbox/index.jsx @@ -1,8 +1,8 @@ import PropTypes from "prop-types"; import { FormControlLabel, Checkbox as MuiCheckbox } from "@mui/material"; import { useTheme } from "@emotion/react"; -import CheckboxOutline from "../../../assets/icons/checkbox-outline.svg?react"; -import CheckboxFilled from "../../../assets/icons/checkbox-filled.svg?react"; +import CheckboxOutline from "../../../../assets/icons/checkbox-outline.svg?react"; +import CheckboxFilled from "../../../../assets/icons/checkbox-filled.svg?react"; import "./index.css"; /** diff --git a/client/src/Components/Inputs/ColorPicker/index.jsx b/client/src/Components/v1/Inputs/ColorPicker/index.jsx similarity index 100% rename from client/src/Components/Inputs/ColorPicker/index.jsx rename to client/src/Components/v1/Inputs/ColorPicker/index.jsx diff --git a/client/src/Components/Inputs/FieldWrapper/index.jsx b/client/src/Components/v1/Inputs/FieldWrapper/index.jsx similarity index 100% rename from client/src/Components/Inputs/FieldWrapper/index.jsx rename to client/src/Components/v1/Inputs/FieldWrapper/index.jsx diff --git a/client/src/Components/Inputs/ImageUpload/index.jsx b/client/src/Components/v1/Inputs/ImageUpload/index.jsx similarity index 98% rename from client/src/Components/Inputs/ImageUpload/index.jsx rename to client/src/Components/v1/Inputs/ImageUpload/index.jsx index a81c89393..e7415bf8f 100644 --- a/client/src/Components/Inputs/ImageUpload/index.jsx +++ b/client/src/Components/v1/Inputs/ImageUpload/index.jsx @@ -1,10 +1,10 @@ // Components import { Box, Stack, Typography } from "@mui/material"; import CloudUploadIcon from "@mui/icons-material/CloudUpload"; -import Image from "../../../Components/Image"; +import Image from "../../Image/index.jsx"; import TextField from "@mui/material/TextField"; import IconButton from "@mui/material/IconButton"; -import ProgressUpload from "../../ProgressBars"; +import ProgressUpload from "../../ProgressBars/index.jsx"; import ImageIcon from "@mui/icons-material/Image"; // Utils diff --git a/client/src/Components/Inputs/Radio/index.css b/client/src/Components/v1/Inputs/Radio/index.css similarity index 100% rename from client/src/Components/Inputs/Radio/index.css rename to client/src/Components/v1/Inputs/Radio/index.css diff --git a/client/src/Components/Inputs/Radio/index.jsx b/client/src/Components/v1/Inputs/Radio/index.jsx similarity index 96% rename from client/src/Components/Inputs/Radio/index.jsx rename to client/src/Components/v1/Inputs/Radio/index.jsx index 4c687de1e..575c1af74 100644 --- a/client/src/Components/Inputs/Radio/index.jsx +++ b/client/src/Components/v1/Inputs/Radio/index.jsx @@ -1,7 +1,7 @@ import PropTypes from "prop-types"; import { FormControlLabel, Radio as MUIRadio, Typography } from "@mui/material"; import { useTheme } from "@emotion/react"; -import RadioChecked from "../../../assets/icons/radio-checked.svg?react"; +import RadioChecked from "../../../../assets/icons/radio-checked.svg?react"; import "./index.css"; /** diff --git a/client/src/Components/Inputs/Search/index.jsx b/client/src/Components/v1/Inputs/Search/index.jsx similarity index 98% rename from client/src/Components/Inputs/Search/index.jsx rename to client/src/Components/v1/Inputs/Search/index.jsx index 7202cc2eb..7a3771d47 100644 --- a/client/src/Components/Inputs/Search/index.jsx +++ b/client/src/Components/v1/Inputs/Search/index.jsx @@ -8,10 +8,10 @@ import { Checkbox, } from "@mui/material"; import { useTheme } from "@emotion/react"; -import SearchIcon from "../../../assets/icons/search.svg?react"; +import SearchIcon from "../../../../assets/icons/search.svg?react"; import React, { useEffect } from "react"; import { useTranslation } from "react-i18next"; -import FieldWrapper from "../FieldWrapper"; +import FieldWrapper from "../FieldWrapper/index.jsx"; /** * Search component using Material UI's Autocomplete. diff --git a/client/src/Components/Inputs/Select/index.css b/client/src/Components/v1/Inputs/Select/index.css similarity index 100% rename from client/src/Components/Inputs/Select/index.css rename to client/src/Components/v1/Inputs/Select/index.css diff --git a/client/src/Components/Inputs/Select/index.jsx b/client/src/Components/v1/Inputs/Select/index.jsx similarity index 98% rename from client/src/Components/Inputs/Select/index.jsx rename to client/src/Components/v1/Inputs/Select/index.jsx index c5c488f91..37ce30dc1 100644 --- a/client/src/Components/Inputs/Select/index.jsx +++ b/client/src/Components/v1/Inputs/Select/index.jsx @@ -2,7 +2,7 @@ import PropTypes from "prop-types"; import { useTheme } from "@emotion/react"; import { MenuItem, Select as MuiSelect, Stack, Typography } from "@mui/material"; import KeyboardArrowDownIcon from "@mui/icons-material/KeyboardArrowDown"; -import FieldWrapper from "../FieldWrapper"; +import FieldWrapper from "../FieldWrapper/index.jsx"; import "./index.css"; diff --git a/client/src/Components/Inputs/TextInput/Adornments/index.jsx b/client/src/Components/v1/Inputs/TextInput/Adornments/index.jsx similarity index 96% rename from client/src/Components/Inputs/TextInput/Adornments/index.jsx rename to client/src/Components/v1/Inputs/TextInput/Adornments/index.jsx index d6af2a89e..4e4bbd2af 100644 --- a/client/src/Components/Inputs/TextInput/Adornments/index.jsx +++ b/client/src/Components/v1/Inputs/TextInput/Adornments/index.jsx @@ -4,7 +4,7 @@ import PropTypes from "prop-types"; import VisibilityOff from "@mui/icons-material/VisibilityOff"; import Visibility from "@mui/icons-material/Visibility"; import ReorderRoundedIcon from "@mui/icons-material/ReorderRounded"; -import DeleteIcon from "../../../../assets/icons/trash-bin.svg?react"; +import DeleteIcon from "../../../../../assets/icons/trash-bin.svg?react"; export const HttpAdornment = ({ https }) => { const theme = useTheme(); diff --git a/client/src/Components/Inputs/TextInput/index.jsx b/client/src/Components/v1/Inputs/TextInput/index.jsx similarity index 98% rename from client/src/Components/Inputs/TextInput/index.jsx rename to client/src/Components/v1/Inputs/TextInput/index.jsx index 469791704..7203e5f50 100644 --- a/client/src/Components/Inputs/TextInput/index.jsx +++ b/client/src/Components/v1/Inputs/TextInput/index.jsx @@ -2,7 +2,7 @@ import { Stack, TextField, Typography } from "@mui/material"; import { useTheme } from "@emotion/react"; import { forwardRef, useState, cloneElement } from "react"; import PropTypes from "prop-types"; -import FieldWrapper from "../FieldWrapper"; +import FieldWrapper from "../FieldWrapper/index.jsx"; const getSx = (theme, type, maxWidth) => { const sx = { diff --git a/client/src/Components/Label/index.css b/client/src/Components/v1/Label/index.css similarity index 100% rename from client/src/Components/Label/index.css rename to client/src/Components/v1/Label/index.css diff --git a/client/src/Components/Label/index.jsx b/client/src/Components/v1/Label/index.jsx similarity index 100% rename from client/src/Components/Label/index.jsx rename to client/src/Components/v1/Label/index.jsx diff --git a/client/src/Components/Layouts/AppLayout/index.jsx b/client/src/Components/v1/Layouts/AppLayout/index.jsx similarity index 91% rename from client/src/Components/Layouts/AppLayout/index.jsx rename to client/src/Components/v1/Layouts/AppLayout/index.jsx index d13f6d879..0dbfb1e7a 100644 --- a/client/src/Components/Layouts/AppLayout/index.jsx +++ b/client/src/Components/v1/Layouts/AppLayout/index.jsx @@ -1,7 +1,7 @@ import Box from "@mui/material/Box"; import PropTypes from "prop-types"; import { useTheme } from "@emotion/react"; -import BackgroundSVG from "../../../assets/Images/background.svg"; +import BackgroundSVG from "../../../../assets/Images/background.svg"; import { useSelector } from "react-redux"; const AppLayout = ({ children }) => { diff --git a/client/src/Components/Layouts/HomeLayout/index.css b/client/src/Components/v1/Layouts/HomeLayout/index.css similarity index 100% rename from client/src/Components/Layouts/HomeLayout/index.css rename to client/src/Components/v1/Layouts/HomeLayout/index.css diff --git a/client/src/Components/Layouts/HomeLayout/index.jsx b/client/src/Components/v1/Layouts/HomeLayout/index.jsx similarity index 88% rename from client/src/Components/Layouts/HomeLayout/index.jsx rename to client/src/Components/v1/Layouts/HomeLayout/index.jsx index 55b612522..3e63aff37 100644 --- a/client/src/Components/Layouts/HomeLayout/index.jsx +++ b/client/src/Components/v1/Layouts/HomeLayout/index.jsx @@ -1,4 +1,4 @@ -import Sidebar from "../../Sidebar"; +import Sidebar from "../../Sidebar/index.jsx"; import { Outlet } from "react-router"; import { Stack } from "@mui/material"; diff --git a/client/src/Components/Link/index.jsx b/client/src/Components/v1/Link/index.jsx similarity index 100% rename from client/src/Components/Link/index.jsx rename to client/src/Components/v1/Link/index.jsx diff --git a/client/src/Components/Link/link.css b/client/src/Components/v1/Link/link.css similarity index 100% rename from client/src/Components/Link/link.css rename to client/src/Components/v1/Link/link.css diff --git a/client/src/Components/MonitorActions/index.jsx b/client/src/Components/v1/MonitorActions/index.jsx similarity index 95% rename from client/src/Components/MonitorActions/index.jsx rename to client/src/Components/v1/MonitorActions/index.jsx index 9e88e26c5..23cebc972 100644 --- a/client/src/Components/MonitorActions/index.jsx +++ b/client/src/Components/v1/MonitorActions/index.jsx @@ -10,8 +10,8 @@ import MenuItem from "@mui/material/MenuItem"; import MenuList from "@mui/material/MenuList"; import { useNavigate } from "react-router-dom"; import { useTranslation } from "react-i18next"; -import { createToast } from "../../Utils/toastUtils"; -import { useExportMonitors } from "../../Hooks/v1/monitorHooks.js"; +import { createToast } from "../../../Utils/toastUtils.jsx"; +import { useExportMonitors } from "../../../Hooks/v1/monitorHooks.js"; const MonitorActions = ({ isLoading }) => { const [open, setOpen] = React.useState(false); diff --git a/client/src/Components/MonitorCountHeader/index.jsx b/client/src/Components/v1/MonitorCountHeader/index.jsx similarity index 95% rename from client/src/Components/MonitorCountHeader/index.jsx rename to client/src/Components/v1/MonitorCountHeader/index.jsx index 7610a9d5c..eba644d26 100644 --- a/client/src/Components/MonitorCountHeader/index.jsx +++ b/client/src/Components/v1/MonitorCountHeader/index.jsx @@ -1,7 +1,7 @@ import { Stack, Typography } from "@mui/material"; import PropTypes from "prop-types"; import { useTheme } from "@emotion/react"; -import SkeletonLayout from "./skeleton"; +import SkeletonLayout from "./skeleton.jsx"; const MonitorCountHeader = ({ isLoading = false, diff --git a/client/src/Components/MonitorCountHeader/skeleton.jsx b/client/src/Components/v1/MonitorCountHeader/skeleton.jsx similarity index 100% rename from client/src/Components/MonitorCountHeader/skeleton.jsx rename to client/src/Components/v1/MonitorCountHeader/skeleton.jsx diff --git a/client/src/Components/MonitorCreateHeader/index.jsx b/client/src/Components/v1/MonitorCreateHeader/index.jsx similarity index 94% rename from client/src/Components/MonitorCreateHeader/index.jsx rename to client/src/Components/v1/MonitorCreateHeader/index.jsx index b3b50681c..90c12f5ec 100644 --- a/client/src/Components/MonitorCreateHeader/index.jsx +++ b/client/src/Components/v1/MonitorCreateHeader/index.jsx @@ -3,7 +3,7 @@ import { useNavigate } from "react-router-dom"; import PropTypes from "prop-types"; import { useTranslation } from "react-i18next"; import { useTheme } from "@emotion/react"; -import MonitorActions from "../MonitorActions"; +import MonitorActions from "../MonitorActions/index.jsx"; const CreateMonitorHeader = ({ isAdmin, label, isLoading = true, path, bulkPath }) => { const navigate = useNavigate(); diff --git a/client/src/Components/MonitorCreateHeader/skeleton.jsx b/client/src/Components/v1/MonitorCreateHeader/skeleton.jsx similarity index 100% rename from client/src/Components/MonitorCreateHeader/skeleton.jsx rename to client/src/Components/v1/MonitorCreateHeader/skeleton.jsx diff --git a/client/src/Components/MonitorDetailsControlHeader/index.jsx b/client/src/Components/v1/MonitorDetailsControlHeader/index.jsx similarity index 92% rename from client/src/Components/MonitorDetailsControlHeader/index.jsx rename to client/src/Components/v1/MonitorDetailsControlHeader/index.jsx index cd56394f4..ae565f3bb 100644 --- a/client/src/Components/MonitorDetailsControlHeader/index.jsx +++ b/client/src/Components/v1/MonitorDetailsControlHeader/index.jsx @@ -1,6 +1,6 @@ import Stack from "@mui/material/Stack"; -import Status from "./status"; -import Skeleton from "./skeleton"; +import Status from "./status.jsx"; +import Skeleton from "./skeleton.jsx"; import Button from "@mui/material/Button"; import { Tooltip } from "@mui/material"; import SettingsOutlinedIcon from "@mui/icons-material/SettingsOutlined"; @@ -12,10 +12,10 @@ import EmailIcon from "@mui/icons-material/Email"; import PropTypes from "prop-types"; import { useNavigate } from "react-router-dom"; import { useTheme } from "@mui/material/styles"; -import { usePauseMonitor } from "../../Hooks/v1/monitorHooks.js"; -import { useSendTestEmail } from "../../Hooks/v1/useSendTestEmail.js"; +import { usePauseMonitor } from "../../../Hooks/v1/monitorHooks.js"; +import { useSendTestEmail } from "../../../Hooks/v1/useSendTestEmail.js"; import { useTranslation } from "react-i18next"; -import { useTestAllNotifications } from "../../Hooks/v1/useNotifications.js"; +import { useTestAllNotifications } from "../../../Hooks/v1/useNotifications.js"; /** * MonitorDetailsControlHeader component displays the control header for monitor details. * It includes status display, pause/resume button, and a configure button for admins. diff --git a/client/src/Components/MonitorDetailsControlHeader/skeleton.jsx b/client/src/Components/v1/MonitorDetailsControlHeader/skeleton.jsx similarity index 100% rename from client/src/Components/MonitorDetailsControlHeader/skeleton.jsx rename to client/src/Components/v1/MonitorDetailsControlHeader/skeleton.jsx diff --git a/client/src/Components/MonitorDetailsControlHeader/status.jsx b/client/src/Components/v1/MonitorDetailsControlHeader/status.jsx similarity index 83% rename from client/src/Components/MonitorDetailsControlHeader/status.jsx rename to client/src/Components/v1/MonitorDetailsControlHeader/status.jsx index 2cf749861..d9b7afc40 100644 --- a/client/src/Components/MonitorDetailsControlHeader/status.jsx +++ b/client/src/Components/v1/MonitorDetailsControlHeader/status.jsx @@ -1,14 +1,14 @@ // Components import Stack from "@mui/material/Stack"; -import PulseDot from "../../Components/Animated/PulseDot"; +import PulseDot from "../Animated/PulseDot.jsx"; import Typography from "@mui/material/Typography"; -import Dot from "../../Components/Dot"; +import Dot from "../Dot/index.jsx"; // Utils -import { formatDurationRounded } from "../../Utils/timeUtils"; +import { formatDurationRounded } from "../../../Utils/timeUtils.js"; import PropTypes from "prop-types"; import { useTheme } from "@emotion/react"; -import { useMonitorUtils } from "../../Hooks/v1/useMonitorUtils.js"; -import { formatMonitorUrl } from "../../Utils/utils"; +import { useMonitorUtils } from "../../../Hooks/v1/useMonitorUtils.js"; +import { formatMonitorUrl } from "../../../Utils/utils.js"; /** * Status component displays the status information of a monitor. * It includes the monitor's name, URL, and check interval. diff --git a/client/src/Components/MonitorTimeFrameHeader/index.jsx b/client/src/Components/v1/MonitorTimeFrameHeader/index.jsx similarity index 97% rename from client/src/Components/MonitorTimeFrameHeader/index.jsx rename to client/src/Components/v1/MonitorTimeFrameHeader/index.jsx index 3958d507c..9d57edc41 100644 --- a/client/src/Components/MonitorTimeFrameHeader/index.jsx +++ b/client/src/Components/v1/MonitorTimeFrameHeader/index.jsx @@ -1,6 +1,6 @@ import { Stack, Typography, Button, ButtonGroup } from "@mui/material"; import { useTheme } from "@emotion/react"; -import SkeletonLayout from "./skeleton"; +import SkeletonLayout from "./skeleton.jsx"; import PropTypes from "prop-types"; const MonitorTimeFrameHeader = ({ diff --git a/client/src/Components/MonitorTimeFrameHeader/skeleton.jsx b/client/src/Components/v1/MonitorTimeFrameHeader/skeleton.jsx similarity index 100% rename from client/src/Components/MonitorTimeFrameHeader/skeleton.jsx rename to client/src/Components/v1/MonitorTimeFrameHeader/skeleton.jsx diff --git a/client/src/Components/NotificationConfig/index.jsx b/client/src/Components/v1/NotificationConfig/index.jsx similarity index 98% rename from client/src/Components/NotificationConfig/index.jsx rename to client/src/Components/v1/NotificationConfig/index.jsx index c37889eff..58096881e 100644 --- a/client/src/Components/NotificationConfig/index.jsx +++ b/client/src/Components/v1/NotificationConfig/index.jsx @@ -3,7 +3,7 @@ import Stack from "@mui/material/Stack"; import Typography from "@mui/material/Typography"; import Divider from "@mui/material/Divider"; import DeleteOutlineRoundedIcon from "@mui/icons-material/DeleteOutlineRounded"; -import Search from "../Inputs/Search"; +import Search from "../Inputs/Search/index.jsx"; // Utils import { useState, useEffect } from "react"; diff --git a/client/src/Components/NotificationIntegrationModal/Components/NotificationIntegrationModal.jsx b/client/src/Components/v1/NotificationIntegrationModal/Components/NotificationIntegrationModal.jsx similarity index 98% rename from client/src/Components/NotificationIntegrationModal/Components/NotificationIntegrationModal.jsx rename to client/src/Components/v1/NotificationIntegrationModal/Components/NotificationIntegrationModal.jsx index dc94ee731..2aae996f6 100644 --- a/client/src/Components/NotificationIntegrationModal/Components/NotificationIntegrationModal.jsx +++ b/client/src/Components/v1/NotificationIntegrationModal/Components/NotificationIntegrationModal.jsx @@ -14,9 +14,9 @@ import { Stack, } from "@mui/material"; import { useTheme } from "@emotion/react"; -import TabPanel from "./TabPanel"; -import TabComponent from "./TabComponent"; -import useNotifications from "../Hooks/useNotification"; +import TabPanel from "./TabPanel.jsx"; +import TabComponent from "./TabComponent.jsx"; +import useNotifications from "../Hooks/useNotification.js"; // Define constants for notification types to avoid magic values const NOTIFICATION_TYPES = { diff --git a/client/src/Components/NotificationIntegrationModal/Components/TabComponent.jsx b/client/src/Components/v1/NotificationIntegrationModal/Components/TabComponent.jsx similarity index 95% rename from client/src/Components/NotificationIntegrationModal/Components/TabComponent.jsx rename to client/src/Components/v1/NotificationIntegrationModal/Components/TabComponent.jsx index 571379c7e..87fb2c301 100644 --- a/client/src/Components/NotificationIntegrationModal/Components/TabComponent.jsx +++ b/client/src/Components/v1/NotificationIntegrationModal/Components/TabComponent.jsx @@ -2,8 +2,8 @@ import React from "react"; import { Typography, Box, Button, CircularProgress } from "@mui/material"; import { useTranslation } from "react-i18next"; import { useTheme } from "@emotion/react"; -import TextInput from "../../../Components/Inputs/TextInput"; -import Checkbox from "../../../Components/Inputs/Checkbox"; +import TextInput from "../../Inputs/TextInput/index.jsx"; +import Checkbox from "../../Inputs/Checkbox/index.jsx"; const TabComponent = ({ type, diff --git a/client/src/Components/NotificationIntegrationModal/Components/TabPanel.jsx b/client/src/Components/v1/NotificationIntegrationModal/Components/TabPanel.jsx similarity index 100% rename from client/src/Components/NotificationIntegrationModal/Components/TabPanel.jsx rename to client/src/Components/v1/NotificationIntegrationModal/Components/TabPanel.jsx diff --git a/client/src/Components/NotificationIntegrationModal/Hooks/useNotification.js b/client/src/Components/v1/NotificationIntegrationModal/Hooks/useNotification.js similarity index 96% rename from client/src/Components/NotificationIntegrationModal/Hooks/useNotification.js rename to client/src/Components/v1/NotificationIntegrationModal/Hooks/useNotification.js index cdbc11598..c0712fa5f 100644 --- a/client/src/Components/NotificationIntegrationModal/Hooks/useNotification.js +++ b/client/src/Components/v1/NotificationIntegrationModal/Hooks/useNotification.js @@ -1,7 +1,7 @@ import { useState } from "react"; import { useTranslation } from "react-i18next"; -import { networkService } from "../../../Utils/NetworkService"; -import { createToast } from "../../../Utils/toastUtils"; +import { networkService } from "../../../../Utils/NetworkService.js"; +import { createToast } from "../../../../Utils/toastUtils.jsx"; // Define constants for notification types to avoid magic values const NOTIFICATION_TYPES = { diff --git a/client/src/Components/PageStateWrapper/index.jsx b/client/src/Components/v1/PageStateWrapper/index.jsx similarity index 90% rename from client/src/Components/PageStateWrapper/index.jsx rename to client/src/Components/v1/PageStateWrapper/index.jsx index c335fcbb3..ac97704df 100644 --- a/client/src/Components/PageStateWrapper/index.jsx +++ b/client/src/Components/v1/PageStateWrapper/index.jsx @@ -1,9 +1,9 @@ -import GenericFallback from "../GenericFallback"; -import Fallback from "../Fallback"; +import GenericFallback from "../GenericFallback/index.jsx"; +import Fallback from "../Fallback/index.jsx"; import { Typography } from "@mui/material"; import { useTheme } from "@emotion/react"; import { useTranslation } from "react-i18next"; -import { useIsAdmin } from "../../Hooks/v1/useIsAdmin.js"; +import { useIsAdmin } from "../../../Hooks/v1/useIsAdmin.js"; import PropTypes from "prop-types"; const PageStateWrapper = ({ networkError, diff --git a/client/src/Components/ProgressBars/index.css b/client/src/Components/v1/ProgressBars/index.css similarity index 100% rename from client/src/Components/ProgressBars/index.css rename to client/src/Components/v1/ProgressBars/index.css diff --git a/client/src/Components/ProgressBars/index.jsx b/client/src/Components/v1/ProgressBars/index.jsx similarity index 100% rename from client/src/Components/ProgressBars/index.jsx rename to client/src/Components/v1/ProgressBars/index.jsx diff --git a/client/src/Components/ProgressStepper/index.css b/client/src/Components/v1/ProgressStepper/index.css similarity index 100% rename from client/src/Components/ProgressStepper/index.css rename to client/src/Components/v1/ProgressStepper/index.css diff --git a/client/src/Components/ProgressStepper/index.jsx b/client/src/Components/v1/ProgressStepper/index.jsx similarity index 100% rename from client/src/Components/ProgressStepper/index.jsx rename to client/src/Components/v1/ProgressStepper/index.jsx diff --git a/client/src/Components/ProtectedRoute/index.jsx b/client/src/Components/v1/ProtectedRoute/index.jsx similarity index 100% rename from client/src/Components/ProtectedRoute/index.jsx rename to client/src/Components/v1/ProtectedRoute/index.jsx diff --git a/client/src/Components/RoleProtectedRoute/index.jsx b/client/src/Components/v1/RoleProtectedRoute/index.jsx similarity index 100% rename from client/src/Components/RoleProtectedRoute/index.jsx rename to client/src/Components/v1/RoleProtectedRoute/index.jsx diff --git a/client/src/Components/ShareComponent/index.jsx b/client/src/Components/v1/ShareComponent/index.jsx similarity index 100% rename from client/src/Components/ShareComponent/index.jsx rename to client/src/Components/v1/ShareComponent/index.jsx diff --git a/client/src/Components/Sidebar/components/authFooter.jsx b/client/src/Components/v1/Sidebar/components/authFooter.jsx similarity index 96% rename from client/src/Components/Sidebar/components/authFooter.jsx rename to client/src/Components/v1/Sidebar/components/authFooter.jsx index e94aeddee..2c33a2402 100644 --- a/client/src/Components/Sidebar/components/authFooter.jsx +++ b/client/src/Components/v1/Sidebar/components/authFooter.jsx @@ -3,19 +3,19 @@ import Box from "@mui/material/Box"; import Typography from "@mui/material/Typography"; import Tooltip from "@mui/material/Tooltip"; import IconButton from "@mui/material/IconButton"; -import Avatar from "../../Avatar"; +import Avatar from "../../Avatar/index.jsx"; import Menu from "@mui/material/Menu"; import MenuItem from "@mui/material/MenuItem"; import Divider from "@mui/material/Divider"; -import DotsVertical from "../../../assets/icons/dots-vertical.svg?react"; -import LogoutSvg from "../../../assets/icons/logout.svg?react"; +import DotsVertical from "../../../../assets/icons/dots-vertical.svg?react"; +import LogoutSvg from "../../../../assets/icons/logout.svg?react"; import { useTheme } from "@emotion/react"; import { useSelector } from "react-redux"; import { useTranslation } from "react-i18next"; import { useState } from "react"; import { useNavigate } from "react-router"; -import { clearAuthState } from "../../../Features/Auth/authSlice"; +import { clearAuthState } from "../../../../Features/Auth/authSlice.js"; import { useDispatch } from "react-redux"; import PropTypes from "prop-types"; diff --git a/client/src/Components/Sidebar/components/collapseButton.jsx b/client/src/Components/v1/Sidebar/components/collapseButton.jsx similarity index 88% rename from client/src/Components/Sidebar/components/collapseButton.jsx rename to client/src/Components/v1/Sidebar/components/collapseButton.jsx index e7c382e5a..e3a9c91f5 100644 --- a/client/src/Components/Sidebar/components/collapseButton.jsx +++ b/client/src/Components/v1/Sidebar/components/collapseButton.jsx @@ -1,9 +1,9 @@ import IconButton from "@mui/material/IconButton"; -import ArrowRight from "../../ArrowRight"; -import ArrowLeft from "../../ArrowLeft"; +import ArrowRight from "../../ArrowRight/index.jsx"; +import ArrowLeft from "../../ArrowLeft/index.jsx"; import { useTheme } from "@mui/material/styles"; import { useDispatch } from "react-redux"; -import { toggleSidebar } from "../../../Features/UI/uiSlice"; +import { toggleSidebar } from "../../../../Features/UI/uiSlice.js"; import PropTypes from "prop-types"; const CollapseButton = ({ collapsed }) => { diff --git a/client/src/Components/Sidebar/components/logo.jsx b/client/src/Components/v1/Sidebar/components/logo.jsx similarity index 100% rename from client/src/Components/Sidebar/components/logo.jsx rename to client/src/Components/v1/Sidebar/components/logo.jsx diff --git a/client/src/Components/Sidebar/components/navItem.jsx b/client/src/Components/v1/Sidebar/components/navItem.jsx similarity index 100% rename from client/src/Components/Sidebar/components/navItem.jsx rename to client/src/Components/v1/Sidebar/components/navItem.jsx diff --git a/client/src/Components/Sidebar/index.jsx b/client/src/Components/v1/Sidebar/index.jsx similarity index 75% rename from client/src/Components/Sidebar/index.jsx rename to client/src/Components/v1/Sidebar/index.jsx index 6b7b6577c..bd7400fa6 100644 --- a/client/src/Components/Sidebar/index.jsx +++ b/client/src/Components/v1/Sidebar/index.jsx @@ -1,29 +1,29 @@ import Stack from "@mui/material/Stack"; import List from "@mui/material/List"; -import Logo from "./components/logo"; -import CollapseButton from "./components/collapseButton"; +import Logo from "./components/logo.jsx"; +import CollapseButton from "./components/collapseButton.jsx"; import Divider from "@mui/material/Divider"; -import NavItem from "./components/navItem"; -import AuthFooter from "./components/authFooter"; +import NavItem from "./components/navItem.jsx"; +import AuthFooter from "./components/authFooter.jsx"; -import StarPrompt from "../StarPrompt"; -import LockSvg from "../../assets/icons/lock.svg?react"; -import UserSvg from "../../assets/icons/user.svg?react"; -import TeamSvg from "../../assets/icons/user-two.svg?react"; -import Support from "../../assets/icons/support.svg?react"; -import Maintenance from "../../assets/icons/maintenance.svg?react"; -import Monitors from "../../assets/icons/monitors.svg?react"; -import Incidents from "../../assets/icons/incidents.svg?react"; -import Integrations from "../../assets/icons/integrations.svg?react"; -import PageSpeed from "../../assets/icons/page-speed.svg?react"; -import Settings from "../../assets/icons/settings.svg?react"; -import ChangeLog from "../../assets/icons/changeLog.svg?react"; -import Docs from "../../assets/icons/docs.svg?react"; -import StatusPages from "../../assets/icons/status-pages.svg?react"; -import Discussions from "../../assets/icons/discussions.svg?react"; -import Notifications from "../../assets/icons/notifications.svg?react"; -import Logs from "../../assets/icons/logs.svg?react"; +import StarPrompt from "../StarPrompt/index.jsx"; +import LockSvg from "../../../assets/icons/lock.svg?react"; +import UserSvg from "../../../assets/icons/user.svg?react"; +import TeamSvg from "../../../assets/icons/user-two.svg?react"; +import Support from "../../../assets/icons/support.svg?react"; +import Maintenance from "../../../assets/icons/maintenance.svg?react"; +import Monitors from "../../../assets/icons/monitors.svg?react"; +import Incidents from "../../../assets/icons/incidents.svg?react"; +import Integrations from "../../../assets/icons/integrations.svg?react"; +import PageSpeed from "../../../assets/icons/page-speed.svg?react"; +import Settings from "../../../assets/icons/settings.svg?react"; +import ChangeLog from "../../../assets/icons/changeLog.svg?react"; +import Docs from "../../../assets/icons/docs.svg?react"; +import StatusPages from "../../../assets/icons/status-pages.svg?react"; +import Discussions from "../../../assets/icons/discussions.svg?react"; +import Notifications from "../../../assets/icons/notifications.svg?react"; +import Logs from "../../../assets/icons/logs.svg?react"; // Utils import { useTheme } from "@mui/material/styles"; diff --git a/client/src/Components/Skeletons/FullPage/index.jsx b/client/src/Components/v1/Skeletons/FullPage/index.jsx similarity index 100% rename from client/src/Components/Skeletons/FullPage/index.jsx rename to client/src/Components/v1/Skeletons/FullPage/index.jsx diff --git a/client/src/Components/StandardContainer/index.jsx b/client/src/Components/v1/StandardContainer/index.jsx similarity index 100% rename from client/src/Components/StandardContainer/index.jsx rename to client/src/Components/v1/StandardContainer/index.jsx diff --git a/client/src/Components/StarPrompt/index.jsx b/client/src/Components/v1/StarPrompt/index.jsx similarity index 97% rename from client/src/Components/StarPrompt/index.jsx rename to client/src/Components/v1/StarPrompt/index.jsx index f5f130387..6151544c1 100644 --- a/client/src/Components/StarPrompt/index.jsx +++ b/client/src/Components/v1/StarPrompt/index.jsx @@ -4,7 +4,7 @@ import CloseIcon from "@mui/icons-material/Close"; import { useTheme } from "@emotion/react"; import { useSelector, useDispatch } from "react-redux"; import { useTranslation } from "react-i18next"; -import { setStarPromptOpen } from "../../Features/UI/uiSlice"; +import { setStarPromptOpen } from "../../../Features/UI/uiSlice.js"; const StarPrompt = ({ repoUrl = "https://github.com/bluewave-labs/checkmate" }) => { const theme = useTheme(); diff --git a/client/src/Components/StatBox/index.jsx b/client/src/Components/v1/StatBox/index.jsx similarity index 97% rename from client/src/Components/StatBox/index.jsx rename to client/src/Components/v1/StatBox/index.jsx index 15eb61f74..1b5f1cf6f 100644 --- a/client/src/Components/StatBox/index.jsx +++ b/client/src/Components/v1/StatBox/index.jsx @@ -1,8 +1,8 @@ import { Stack, Typography } from "@mui/material"; -import Image from "../Image"; +import Image from "../Image/index.jsx"; import { useTheme } from "@mui/material/styles"; import PropTypes from "prop-types"; -import { useMonitorUtils } from "../../Hooks/v1/useMonitorUtils.js"; +import { useMonitorUtils } from "../../../Hooks/v1/useMonitorUtils.js"; /** * StatBox Component diff --git a/client/src/Components/StatusBoxes/index.jsx b/client/src/Components/v1/StatusBoxes/index.jsx similarity index 93% rename from client/src/Components/StatusBoxes/index.jsx rename to client/src/Components/v1/StatusBoxes/index.jsx index d97bb6211..a78337ba7 100644 --- a/client/src/Components/StatusBoxes/index.jsx +++ b/client/src/Components/v1/StatusBoxes/index.jsx @@ -1,6 +1,6 @@ // Components import { Stack } from "@mui/material"; -import SkeletonLayout from "./skeleton"; +import SkeletonLayout from "./skeleton.jsx"; // Utils import { useTheme } from "@mui/material/styles"; import PropTypes from "prop-types"; diff --git a/client/src/Components/StatusBoxes/skeleton.jsx b/client/src/Components/v1/StatusBoxes/skeleton.jsx similarity index 100% rename from client/src/Components/StatusBoxes/skeleton.jsx rename to client/src/Components/v1/StatusBoxes/skeleton.jsx diff --git a/client/src/Components/Subheader/index.jsx b/client/src/Components/v1/Subheader/index.jsx similarity index 100% rename from client/src/Components/Subheader/index.jsx rename to client/src/Components/v1/Subheader/index.jsx diff --git a/client/src/Components/Tab/index.jsx b/client/src/Components/v1/Tab/index.jsx similarity index 100% rename from client/src/Components/Tab/index.jsx rename to client/src/Components/v1/Tab/index.jsx diff --git a/client/src/Components/Table/TablePagination/Actions/index.jsx b/client/src/Components/v1/Table/TablePagination/Actions/index.jsx similarity index 95% rename from client/src/Components/Table/TablePagination/Actions/index.jsx rename to client/src/Components/v1/Table/TablePagination/Actions/index.jsx index dec72446b..0df62878e 100644 --- a/client/src/Components/Table/TablePagination/Actions/index.jsx +++ b/client/src/Components/v1/Table/TablePagination/Actions/index.jsx @@ -1,7 +1,7 @@ import PropTypes from "prop-types"; import { Box, Button } from "@mui/material"; -import LeftArrow from "../../../ArrowLeft"; -import RightArrow from "../../../ArrowRight"; +import LeftArrow from "../../../ArrowLeft/index.jsx"; +import RightArrow from "../../../ArrowRight/index.jsx"; import { useTheme } from "@emotion/react"; TablePaginationActions.propTypes = { diff --git a/client/src/Components/Table/TablePagination/index.jsx b/client/src/Components/v1/Table/TablePagination/index.jsx similarity index 96% rename from client/src/Components/Table/TablePagination/index.jsx rename to client/src/Components/v1/Table/TablePagination/index.jsx index 44b127d45..8a5af0cd5 100644 --- a/client/src/Components/Table/TablePagination/index.jsx +++ b/client/src/Components/v1/Table/TablePagination/index.jsx @@ -1,8 +1,8 @@ import PropTypes from "prop-types"; import { useTheme } from "@emotion/react"; import { Stack, TablePagination, Typography } from "@mui/material"; -import { TablePaginationActions } from "./Actions"; -import SelectorVertical from "../../../assets/icons/selector-vertical.svg?react"; +import { TablePaginationActions } from "./Actions/index.jsx"; +import SelectorVertical from "../../../../assets/icons/selector-vertical.svg?react"; Pagination.propTypes = { paginationLabel: PropTypes.string, // Label for the pagination. diff --git a/client/src/Components/Table/TableUtils.js b/client/src/Components/v1/Table/TableUtils.js similarity index 100% rename from client/src/Components/Table/TableUtils.js rename to client/src/Components/v1/Table/TableUtils.js diff --git a/client/src/Components/Table/index.jsx b/client/src/Components/v1/Table/index.jsx similarity index 99% rename from client/src/Components/Table/index.jsx rename to client/src/Components/v1/Table/index.jsx index bfafb5387..2113a2215 100644 --- a/client/src/Components/Table/index.jsx +++ b/client/src/Components/v1/Table/index.jsx @@ -8,7 +8,7 @@ import { TableRow, } from "@mui/material"; import Tooltip from "@mui/material/Tooltip"; -import SkeletonLayout from "./skeleton"; +import SkeletonLayout from "./skeleton.jsx"; import PropTypes from "prop-types"; import { useTheme } from "@emotion/react"; diff --git a/client/src/Components/Table/skeleton.jsx b/client/src/Components/v1/Table/skeleton.jsx similarity index 100% rename from client/src/Components/Table/skeleton.jsx rename to client/src/Components/v1/Table/skeleton.jsx diff --git a/client/src/Components/TextLink/index.jsx b/client/src/Components/v1/TextLink/index.jsx similarity index 100% rename from client/src/Components/TextLink/index.jsx rename to client/src/Components/v1/TextLink/index.jsx diff --git a/client/src/Components/ThemeSwitch/SunAndMoonIcon.jsx b/client/src/Components/v1/ThemeSwitch/SunAndMoonIcon.jsx similarity index 100% rename from client/src/Components/ThemeSwitch/SunAndMoonIcon.jsx rename to client/src/Components/v1/ThemeSwitch/SunAndMoonIcon.jsx diff --git a/client/src/Components/ThemeSwitch/index.css b/client/src/Components/v1/ThemeSwitch/index.css similarity index 100% rename from client/src/Components/ThemeSwitch/index.css rename to client/src/Components/v1/ThemeSwitch/index.css diff --git a/client/src/Components/ThemeSwitch/index.jsx b/client/src/Components/v1/ThemeSwitch/index.jsx similarity index 91% rename from client/src/Components/ThemeSwitch/index.jsx rename to client/src/Components/v1/ThemeSwitch/index.jsx index 5efb17419..60763da90 100644 --- a/client/src/Components/ThemeSwitch/index.jsx +++ b/client/src/Components/v1/ThemeSwitch/index.jsx @@ -10,9 +10,9 @@ */ import { IconButton } from "@mui/material"; -import SunAndMoonIcon from "./SunAndMoonIcon"; +import SunAndMoonIcon from "./SunAndMoonIcon.jsx"; import { useDispatch, useSelector } from "react-redux"; -import { setMode } from "../../Features/UI/uiSlice"; +import { setMode } from "../../../Features/UI/uiSlice.js"; import "./index.css"; import { useTranslation } from "react-i18next"; diff --git a/client/src/Components/Toast/body.jsx b/client/src/Components/v1/Toast/body.jsx similarity index 100% rename from client/src/Components/Toast/body.jsx rename to client/src/Components/v1/Toast/body.jsx diff --git a/client/src/Components/Toast/index.jsx b/client/src/Components/v1/Toast/index.jsx similarity index 98% rename from client/src/Components/Toast/index.jsx rename to client/src/Components/v1/Toast/index.jsx index 36df0e631..5d66fca3f 100644 --- a/client/src/Components/Toast/index.jsx +++ b/client/src/Components/v1/Toast/index.jsx @@ -2,7 +2,7 @@ import Stack from "@mui/material/Stack"; import IconButton from "@mui/material/IconButton"; import Typography from "@mui/material/Typography"; import Button from "@mui/material/Button"; -import ToastBody from "./body"; +import ToastBody from "./body.jsx"; import InfoOutlinedIcon from "@mui/icons-material/InfoOutlined"; import ErrorOutlineOutlinedIcon from "@mui/icons-material/ErrorOutlineOutlined"; import WarningAmberOutlinedIcon from "@mui/icons-material/WarningAmberOutlined"; diff --git a/client/src/Components/Inputs/TextInput/indexV2.tsx b/client/src/Components/v2/Inputs/TextInput.tsx similarity index 100% rename from client/src/Components/Inputs/TextInput/indexV2.tsx rename to client/src/Components/v2/Inputs/TextInput.tsx diff --git a/client/src/Pages/v1/About/index.jsx b/client/src/Pages/v1/About/index.jsx index 0e68089d3..ff7bb6c6b 100644 --- a/client/src/Pages/v1/About/index.jsx +++ b/client/src/Pages/v1/About/index.jsx @@ -1,6 +1,6 @@ import React from "react"; -import AppBar from "../../../Components/Common/AppBar.jsx"; -import Footer from "../../../Components/Common/Footer.jsx"; +import AppBar from "@/Components/v1/Common/AppBar.jsx"; +import Footer from "@/Components/v1/Common/Footer.jsx"; import { useTranslation } from "react-i18next"; const AboutUs = () => { diff --git a/client/src/Pages/v1/Account/EditUser/index.jsx b/client/src/Pages/v1/Account/EditUser/index.jsx index 658de5144..e2e976916 100644 --- a/client/src/Pages/v1/Account/EditUser/index.jsx +++ b/client/src/Pages/v1/Account/EditUser/index.jsx @@ -2,9 +2,9 @@ import Stack from "@mui/material/Stack"; import Box from "@mui/material/Box"; import Typography from "@mui/material/Typography"; -import Breadcrumbs from "../../../../Components/Breadcrumbs/index.jsx"; -import TextInput from "../../../../Components/Inputs/TextInput/index.jsx"; -import Search from "../../../../Components/Inputs/Search/index.jsx"; +import Breadcrumbs from "@/Components/v1/Breadcrumbs/index.jsx"; +import TextInput from "@/Components/v1/Inputs/TextInput/index.jsx"; +import Search from "@/Components/v1/Inputs/Search/index.jsx"; import Button from "@mui/material/Button"; import RoleTable from "../components/RoleTable/index.jsx"; diff --git a/client/src/Pages/v1/Account/components/AddTeamMember/index.jsx b/client/src/Pages/v1/Account/components/AddTeamMember/index.jsx index e17ed433b..219658b63 100644 --- a/client/src/Pages/v1/Account/components/AddTeamMember/index.jsx +++ b/client/src/Pages/v1/Account/components/AddTeamMember/index.jsx @@ -1,7 +1,7 @@ import { Button, Stack } from "@mui/material"; -import { GenericDialog } from "../../../../../Components/Dialog/genericDialog.jsx"; -import TextInput from "../../../../../Components/Inputs/TextInput/index.jsx"; -import Select from "../../../../../Components/Inputs/Select/index.jsx"; +import { GenericDialog } from "@/Components/v1/Dialog/genericDialog.jsx"; +import TextInput from "@/Components/v1/Inputs/TextInput/index.jsx"; +import Select from "@/Components/v1/Inputs/Select/index.jsx"; import { useGetInviteToken } from "../../../../../Hooks/v1/inviteHooks.js"; import { useTheme } from "@emotion/react"; import { useTranslation } from "react-i18next"; @@ -10,7 +10,7 @@ import { useState } from "react"; import PasswordTooltip from "../../../Auth/components/PasswordTooltip.jsx"; import useAddTeamMember from "./hooks/useAddTeamMember.jsx"; import usePasswordFeedback from "../../../Auth/hooks/usePasswordFeedback.jsx"; -import { PasswordEndAdornment } from "../../../../../Components/Inputs/TextInput/Adornments/index.jsx"; +import { PasswordEndAdornment } from "@/Components/v1/Inputs/TextInput/Adornments/index.jsx"; import PropTypes from "prop-types"; const INITIAL_FORM_STATE = { diff --git a/client/src/Pages/v1/Account/components/PasswordPanel.jsx b/client/src/Pages/v1/Account/components/PasswordPanel.jsx index 11961bf58..ca2af9205 100644 --- a/client/src/Pages/v1/Account/components/PasswordPanel.jsx +++ b/client/src/Pages/v1/Account/components/PasswordPanel.jsx @@ -2,10 +2,10 @@ import TabPanel from "@mui/lab/TabPanel"; import { useState } from "react"; import { useTheme } from "@emotion/react"; import { Box, Stack, Typography, Button } from "@mui/material"; -import { PasswordEndAdornment } from "../../../../Components/Inputs/TextInput/Adornments/index.jsx"; -import TextInput from "../../../../Components/Inputs/TextInput/index.jsx"; +import { PasswordEndAdornment } from "@/Components/v1/Inputs/TextInput/Adornments/index.jsx"; +import TextInput from "@/Components/v1/Inputs/TextInput/index.jsx"; import { newOrChangedCredentials } from "../../../../Validation/validation.js"; -import Alert from "../../../../Components/Alert/index.jsx"; +import Alert from "@/Components/v1/Alert/index.jsx"; import { update } from "../../../../Features/Auth/authSlice.js"; import { useDispatch, useSelector } from "react-redux"; import { createToast } from "../../../../Utils/toastUtils.jsx"; diff --git a/client/src/Pages/v1/Account/components/ProfilePanel.jsx b/client/src/Pages/v1/Account/components/ProfilePanel.jsx index 1ce1d34da..2035861a2 100644 --- a/client/src/Pages/v1/Account/components/ProfilePanel.jsx +++ b/client/src/Pages/v1/Account/components/ProfilePanel.jsx @@ -2,9 +2,9 @@ import { useTheme } from "@emotion/react"; import { useState } from "react"; import TabPanel from "@mui/lab/TabPanel"; import { Box, Button, Divider, Stack, Typography } from "@mui/material"; -import Avatar from "../../../../Components/Avatar/index.jsx"; -import TextInput from "../../../../Components/Inputs/TextInput/index.jsx"; -import ImageUpload from "../../../../Components/Inputs/ImageUpload/index.jsx"; +import Avatar from "@/Components/v1/Avatar/index.jsx"; +import TextInput from "@/Components/v1/Inputs/TextInput/index.jsx"; +import ImageUpload from "@/Components/v1/Inputs/ImageUpload/index.jsx"; import { newOrChangedCredentials } from "../../../../Validation/validation.js"; import { useDispatch, useSelector } from "react-redux"; import { @@ -14,8 +14,8 @@ import { } from "../../../../Features/Auth/authSlice.js"; import { createToast } from "../../../../Utils/toastUtils.jsx"; import { logger } from "../../../../Utils/Logger.js"; -import { GenericDialog } from "../../../../Components/Dialog/genericDialog.jsx"; -import Dialog from "../../../../Components/Dialog/index.jsx"; +import { GenericDialog } from "@/Components/v1/Dialog/genericDialog.jsx"; +import Dialog from "@/Components/v1/Dialog/index.jsx"; import { useTranslation } from "react-i18next"; /** diff --git a/client/src/Pages/v1/Account/components/RoleTable/index.jsx b/client/src/Pages/v1/Account/components/RoleTable/index.jsx index c42be7bcd..02dcaaf0d 100644 --- a/client/src/Pages/v1/Account/components/RoleTable/index.jsx +++ b/client/src/Pages/v1/Account/components/RoleTable/index.jsx @@ -1,5 +1,5 @@ import Typography from "@mui/material/Typography"; -import DataTable from "../../../../../Components/Table/index.jsx"; +import DataTable from "@/Components/v1/Table/index.jsx"; import DeleteOutlineRoundedIcon from "@mui/icons-material/DeleteOutlineRounded"; import { useTranslation } from "react-i18next"; diff --git a/client/src/Pages/v1/Account/components/TeamPanel.jsx b/client/src/Pages/v1/Account/components/TeamPanel.jsx index 8964cd277..02918eb30 100644 --- a/client/src/Pages/v1/Account/components/TeamPanel.jsx +++ b/client/src/Pages/v1/Account/components/TeamPanel.jsx @@ -3,14 +3,14 @@ import TabPanel from "@mui/lab/TabPanel"; import { Button, ButtonGroup, Stack, Typography } from "@mui/material"; import { useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; -import TextInput from "../../../../Components/Inputs/TextInput/index.jsx"; +import TextInput from "@/Components/v1/Inputs/TextInput/index.jsx"; import { newOrChangedCredentials } from "../../../../Validation/validation.js"; import { networkService } from "../../../../main.jsx"; import { createToast } from "../../../../Utils/toastUtils.jsx"; -import Select from "../../../../Components/Inputs/Select/index.jsx"; -import { GenericDialog } from "../../../../Components/Dialog/genericDialog.jsx"; +import Select from "@/Components/v1/Inputs/Select/index.jsx"; +import { GenericDialog } from "@/Components/v1/Dialog/genericDialog.jsx"; import AddTeamMember from "./AddTeamMember/index.jsx"; -import DataTable from "../../../../Components/Table/index.jsx"; +import DataTable from "@/Components/v1/Table/index.jsx"; import { useGetInviteToken } from "../../../../Hooks/v1/inviteHooks.js"; import { useNavigate } from "react-router-dom"; import { useIsSuperAdmin } from "../../../../Hooks/v1/useIsAdmin.js"; diff --git a/client/src/Pages/v1/Account/index.jsx b/client/src/Pages/v1/Account/index.jsx index a496858b7..ae16ff9d7 100644 --- a/client/src/Pages/v1/Account/index.jsx +++ b/client/src/Pages/v1/Account/index.jsx @@ -3,7 +3,7 @@ import PropTypes from "prop-types"; import { useNavigate } from "react-router-dom"; import { useSelector } from "react-redux"; import { Box, Tab, useTheme } from "@mui/material"; -import CustomTabList from "../../../Components/Tab/index.jsx"; +import CustomTabList from "@/Components/v1/Tab/index.jsx"; import TabContext from "@mui/lab/TabContext"; import ProfilePanel from "./components/ProfilePanel.jsx"; import PasswordPanel from "./components/PasswordPanel.jsx"; diff --git a/client/src/Pages/v1/Auth/CheckEmail.jsx b/client/src/Pages/v1/Auth/CheckEmail.jsx index 95217bcb5..c12a84804 100644 --- a/client/src/Pages/v1/Auth/CheckEmail.jsx +++ b/client/src/Pages/v1/Auth/CheckEmail.jsx @@ -9,7 +9,7 @@ import { Trans, 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"; -import IconBox from "../../../Components/IconBox/index.jsx"; +import IconBox from "@/Components/v1/IconBox/index.jsx"; import "./index.css"; const CheckEmail = () => { diff --git a/client/src/Pages/v1/Auth/ForgotPassword.jsx b/client/src/Pages/v1/Auth/ForgotPassword.jsx index 9836f5d77..8cbd85d8a 100644 --- a/client/src/Pages/v1/Auth/ForgotPassword.jsx +++ b/client/src/Pages/v1/Auth/ForgotPassword.jsx @@ -6,11 +6,11 @@ import { forgotPassword } from "../../../Features/Auth/authSlice.js"; import { useEffect, useState } from "react"; import { newOrChangedCredentials } from "../../../Validation/validation.js"; import { useNavigate } from "react-router-dom"; -import TextInput from "../../../Components/Inputs/TextInput/index.jsx"; +import TextInput from "@/Components/v1/Inputs/TextInput/index.jsx"; 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/index.jsx"; +import IconBox from "@/Components/v1/IconBox/index.jsx"; import { Trans, useTranslation } from "react-i18next"; import "./index.css"; diff --git a/client/src/Pages/v1/Auth/Login/index.jsx b/client/src/Pages/v1/Auth/Login/index.jsx index 901edcd8b..7f5dcb5e4 100644 --- a/client/src/Pages/v1/Auth/Login/index.jsx +++ b/client/src/Pages/v1/Auth/Login/index.jsx @@ -1,9 +1,9 @@ // Components import Stack from "@mui/material/Stack"; import Button from "@mui/material/Button"; -import TextInput from "../../../../Components/Inputs/TextInput/index.jsx"; -import { PasswordEndAdornment } from "../../../../Components/Inputs/TextInput/Adornments/index.jsx"; -import TextLink from "../../../../Components/TextLink/index.jsx"; +import TextInput from "@/Components/v1/Inputs/TextInput/index.jsx"; +import { PasswordEndAdornment } from "@/Components/v1/Inputs/TextInput/Adornments/index.jsx"; +import TextLink from "@/Components/v1/TextLink/index.jsx"; import AuthPageWrapper from "../components/AuthPageWrapper.jsx"; // Utils import { useTheme } from "@mui/material/styles"; diff --git a/client/src/Pages/v1/Auth/NewPasswordConfirmed.jsx b/client/src/Pages/v1/Auth/NewPasswordConfirmed.jsx index 2bede1ae4..629494098 100644 --- a/client/src/Pages/v1/Auth/NewPasswordConfirmed.jsx +++ b/client/src/Pages/v1/Auth/NewPasswordConfirmed.jsx @@ -6,7 +6,7 @@ import { clearAuthState } from "../../../Features/Auth/authSlice.js"; 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/index.jsx"; +import IconBox from "@/Components/v1/IconBox/index.jsx"; import { Trans, useTranslation } from "react-i18next"; import "./index.css"; diff --git a/client/src/Pages/v1/Auth/Register/index.jsx b/client/src/Pages/v1/Auth/Register/index.jsx index a903602d4..d6515b85c 100644 --- a/client/src/Pages/v1/Auth/Register/index.jsx +++ b/client/src/Pages/v1/Auth/Register/index.jsx @@ -1,7 +1,7 @@ // Components import Stack from "@mui/material/Stack"; import Typography from "@mui/material/Typography"; -import TextInput from "../../../../Components/Inputs/TextInput/index.jsx"; +import TextInput from "@/Components/v1/Inputs/TextInput/index.jsx"; import Button from "@mui/material/Button"; import Box from "@mui/material/Box"; import PasswordTooltip from "../components/PasswordTooltip.jsx"; diff --git a/client/src/Pages/v1/Auth/SetNewPassword.jsx b/client/src/Pages/v1/Auth/SetNewPassword.jsx index e9df89d88..45f3351f3 100644 --- a/client/src/Pages/v1/Auth/SetNewPassword.jsx +++ b/client/src/Pages/v1/Auth/SetNewPassword.jsx @@ -7,10 +7,10 @@ import { Box, Stack, Typography, Button } from "@mui/material"; import { setNewPassword } from "../../../Features/Auth/authSlice.js"; import { createToast } from "../../../Utils/toastUtils.jsx"; import { newOrChangedCredentials } from "../../../Validation/validation.js"; -import Check from "../../../Components/Check/Check.jsx"; -import TextInput from "../../../Components/Inputs/TextInput/index.jsx"; -import { PasswordEndAdornment } from "../../../Components/Inputs/TextInput/Adornments/index.jsx"; -import IconBox from "../../../Components/IconBox/index.jsx"; +import Check from "@/Components/v1/Check/Check.jsx"; +import TextInput from "@/Components/v1/Inputs/TextInput/index.jsx"; +import { PasswordEndAdornment } from "@/Components/v1/Inputs/TextInput/Adornments/index.jsx"; +import IconBox from "@/Components/v1/IconBox/index.jsx"; import LockIcon from "../../../assets/icons/lock.svg?react"; import Logo from "../../../assets/icons/checkmate-icon.svg?react"; import Background from "../../../assets/Images/background-grid.svg?react"; diff --git a/client/src/Pages/v1/Auth/components/AuthHeader.jsx b/client/src/Pages/v1/Auth/components/AuthHeader.jsx index 067c47d92..a34e83d6a 100644 --- a/client/src/Pages/v1/Auth/components/AuthHeader.jsx +++ b/client/src/Pages/v1/Auth/components/AuthHeader.jsx @@ -3,7 +3,7 @@ import Stack from "@mui/material/Stack"; import Typography from "@mui/material/Typography"; import Logo from "../../../../assets/icons/checkmate-icon.svg?react"; import LanguageSelector from "../../../../Components/LanguageSelector.jsx"; -import ThemeSwitch from "../../../../Components/ThemeSwitch/index.jsx"; +import ThemeSwitch from "@/Components/v1/ThemeSwitch/index.jsx"; // Utils import { useTheme } from "@mui/material/styles"; diff --git a/client/src/Pages/v1/Auth/components/PasswordTooltip.jsx b/client/src/Pages/v1/Auth/components/PasswordTooltip.jsx index ff3c2f385..2881eb001 100644 --- a/client/src/Pages/v1/Auth/components/PasswordTooltip.jsx +++ b/client/src/Pages/v1/Auth/components/PasswordTooltip.jsx @@ -1,4 +1,4 @@ -import Check from "../../../../Components/Check/Check.jsx"; +import Check from "@/Components/v1/Check/Check.jsx"; import Stack from "@mui/material/Stack"; import { Tooltip, useTheme } from "@mui/material"; import { useTranslation } from "react-i18next"; diff --git a/client/src/Pages/v1/Incidents/Components/IncidentTable/index.jsx b/client/src/Pages/v1/Incidents/Components/IncidentTable/index.jsx index e204d7167..df457c0e2 100644 --- a/client/src/Pages/v1/Incidents/Components/IncidentTable/index.jsx +++ b/client/src/Pages/v1/Incidents/Components/IncidentTable/index.jsx @@ -1,16 +1,16 @@ //Components import Stack from "@mui/material/Stack"; -import DataTable from "../../../../../Components/Table/index.jsx"; +import DataTable from "@/Components/v1/Table/index.jsx"; import Table from "@mui/material/Table"; import TableBody from "@mui/material/TableBody"; import TableRow from "@mui/material/TableRow"; import TableCell from "@mui/material/TableCell"; -import TableSkeleton from "../../../../../Components/Table/skeleton.jsx"; -import Pagination from "../../../../../Components/Table/TablePagination/index.jsx"; -import { StatusLabel } from "../../../../../Components/Label/index.jsx"; -import { HttpStatusLabel } from "../../../../../Components/HttpStatusLabel/index.jsx"; -import GenericFallback from "../../../../../Components/GenericFallback/index.jsx"; -import NetworkError from "../../../../../Components/GenericFallback/NetworkError.jsx"; +import TableSkeleton from "@/Components/v1/Table/skeleton.jsx"; +import Pagination from "@/Components/v1/Table/TablePagination/index.jsx"; +import { StatusLabel } from "@/Components/v1/Label/index.jsx"; +import { HttpStatusLabel } from "@/Components/v1/HttpStatusLabel/index.jsx"; +import GenericFallback from "@/Components/v1/GenericFallback/index.jsx"; +import NetworkError from "@/Components/v1/GenericFallback/NetworkError.jsx"; //Utils import { formatDateWithTz } from "../../../../../Utils/timeUtils.js"; diff --git a/client/src/Pages/v1/Incidents/Components/OptionsHeader/index.jsx b/client/src/Pages/v1/Incidents/Components/OptionsHeader/index.jsx index b1bde1175..c16f54f18 100644 --- a/client/src/Pages/v1/Incidents/Components/OptionsHeader/index.jsx +++ b/client/src/Pages/v1/Incidents/Components/OptionsHeader/index.jsx @@ -1,6 +1,6 @@ // Components import { Stack, Typography, Button, ButtonGroup } from "@mui/material"; -import Select from "../../../../../Components/Inputs/Select/index.jsx"; +import Select from "@/Components/v1/Inputs/Select/index.jsx"; import PropTypes from "prop-types"; //Utils diff --git a/client/src/Pages/v1/Incidents/index.jsx b/client/src/Pages/v1/Incidents/index.jsx index b9281f97c..3c0d7f60f 100644 --- a/client/src/Pages/v1/Incidents/index.jsx +++ b/client/src/Pages/v1/Incidents/index.jsx @@ -1,7 +1,7 @@ // Components import { Stack } from "@mui/material"; -import Breadcrumbs from "../../../Components/Breadcrumbs/index.jsx"; -import GenericFallback from "../../../Components/GenericFallback/index.jsx"; +import Breadcrumbs from "@/Components/v1/Breadcrumbs/index.jsx"; +import GenericFallback from "@/Components/v1/GenericFallback/index.jsx"; import IncidentTable from "./Components/IncidentTable/index.jsx"; import OptionsHeader from "./Components/OptionsHeader/index.jsx"; import StatusBoxes from "./Components/StatusBoxes/index.jsx"; @@ -13,7 +13,7 @@ import { useFetchMonitorsByTeamId } from "../../../Hooks/v1/monitorHooks.js"; import { useFetchChecksSummaryByTeamId } from "../../../Hooks/v1/checkHooks.js"; import { useAcknowledgeChecks } from "../../../Hooks/v1/checkHooks.js"; import { useState, useEffect } from "react"; -import NetworkError from "../../../Components/GenericFallback/NetworkError.jsx"; +import NetworkError from "@/Components/v1/GenericFallback/NetworkError.jsx"; import { useTranslation } from "react-i18next"; import { useParams } from "react-router-dom"; diff --git a/client/src/Pages/v1/Infrastructure/Create/Components/CustomAlertsSection.jsx b/client/src/Pages/v1/Infrastructure/Create/Components/CustomAlertsSection.jsx index 299331a79..5fe1d8984 100644 --- a/client/src/Pages/v1/Infrastructure/Create/Components/CustomAlertsSection.jsx +++ b/client/src/Pages/v1/Infrastructure/Create/Components/CustomAlertsSection.jsx @@ -1,4 +1,4 @@ -import ConfigBox from "../../../../../Components/ConfigBox/index.jsx"; +import ConfigBox from "@/Components/v1/ConfigBox/index.jsx"; import { Box, Stack, Typography } from "@mui/material"; import { CustomThreshold } from "./CustomThreshold/index.jsx"; import { capitalizeFirstLetter } from "../../../../../Utils/stringUtils.js"; diff --git a/client/src/Pages/v1/Infrastructure/Create/Components/CustomThreshold/index.jsx b/client/src/Pages/v1/Infrastructure/Create/Components/CustomThreshold/index.jsx index 4edc0b766..d783f7985 100644 --- a/client/src/Pages/v1/Infrastructure/Create/Components/CustomThreshold/index.jsx +++ b/client/src/Pages/v1/Infrastructure/Create/Components/CustomThreshold/index.jsx @@ -1,6 +1,6 @@ import { Box, Stack, Typography } from "@mui/material"; -import TextInput from "../../../../../../Components/Inputs/TextInput/index.jsx"; -import Checkbox from "../../../../../../Components/Inputs/Checkbox/index.jsx"; +import TextInput from "@/Components/v1/Inputs/TextInput/index.jsx"; +import Checkbox from "@/Components/v1/Inputs/Checkbox/index.jsx"; import { useTheme } from "@emotion/react"; import PropTypes from "prop-types"; diff --git a/client/src/Pages/v1/Infrastructure/Create/Components/MonitorActionButtons.jsx b/client/src/Pages/v1/Infrastructure/Create/Components/MonitorActionButtons.jsx index 16df8ad01..871fe0d7b 100644 --- a/client/src/Pages/v1/Infrastructure/Create/Components/MonitorActionButtons.jsx +++ b/client/src/Pages/v1/Infrastructure/Create/Components/MonitorActionButtons.jsx @@ -4,7 +4,7 @@ import { useTheme } from "@emotion/react"; import { useTranslation } from "react-i18next"; import PauseCircleOutlineIcon from "@mui/icons-material/PauseCircleOutline"; import PlayCircleOutlineRoundedIcon from "@mui/icons-material/PlayCircleOutlineRounded"; -import Dialog from "../../../../../Components/Dialog/index.jsx"; +import Dialog from "@/Components/v1/Dialog/index.jsx"; import PropTypes from "prop-types"; const MonitorActionButtons = ({ monitor, isBusy, handlePause, handleRemove }) => { diff --git a/client/src/Pages/v1/Infrastructure/Create/Components/MonitorStatusHeader.jsx b/client/src/Pages/v1/Infrastructure/Create/Components/MonitorStatusHeader.jsx index 4313c34c0..570f38a7e 100644 --- a/client/src/Pages/v1/Infrastructure/Create/Components/MonitorStatusHeader.jsx +++ b/client/src/Pages/v1/Infrastructure/Create/Components/MonitorStatusHeader.jsx @@ -2,7 +2,7 @@ import { Box, Stack, Tooltip, Typography } from "@mui/material"; import { useMonitorUtils } from "../../../../../Hooks/v1/useMonitorUtils.js"; import { useTheme } from "@emotion/react"; import { useTranslation } from "react-i18next"; -import PulseDot from "../../../../../Components/Animated/PulseDot.jsx"; +import PulseDot from "@/Components/v1/Animated/PulseDot.jsx"; import PropTypes from "prop-types"; const MonitorStatusHeader = ({ monitor, infrastructureMonitor }) => { const theme = useTheme(); diff --git a/client/src/Pages/v1/Infrastructure/Create/index.jsx b/client/src/Pages/v1/Infrastructure/Create/index.jsx index 06ecdff6c..d4fd859ec 100644 --- a/client/src/Pages/v1/Infrastructure/Create/index.jsx +++ b/client/src/Pages/v1/Infrastructure/Create/index.jsx @@ -1,17 +1,17 @@ //Components -import Breadcrumbs from "../../../../Components/Breadcrumbs/index.jsx"; -import ConfigBox from "../../../../Components/ConfigBox/index.jsx"; -import FieldWrapper from "../../../../Components/Inputs/FieldWrapper/index.jsx"; -import Link from "../../../../Components/Link/index.jsx"; -import Select from "../../../../Components/Inputs/Select/index.jsx"; -import TextInput from "../../../../Components/Inputs/TextInput/index.jsx"; +import Breadcrumbs from "@/Components/v1/Breadcrumbs/index.jsx"; +import ConfigBox from "@/Components/v1/ConfigBox/index.jsx"; +import FieldWrapper from "@/Components/v1/Inputs/FieldWrapper/index.jsx"; +import Link from "@/Components/v1/Link/index.jsx"; +import Select from "@/Components/v1/Inputs/Select/index.jsx"; +import TextInput from "@/Components/v1/Inputs/TextInput/index.jsx"; import { Box, Stack, Typography, Button, ButtonGroup } from "@mui/material"; -import { HttpAdornment } from "../../../../Components/Inputs/TextInput/Adornments/index.jsx"; +import { HttpAdornment } from "@/Components/v1/Inputs/TextInput/Adornments/index.jsx"; import MonitorStatusHeader from "./Components/MonitorStatusHeader.jsx"; import MonitorActionButtons from "./Components/MonitorActionButtons.jsx"; import CustomAlertsSection from "./Components/CustomAlertsSection.jsx"; // Utils -import NotificationsConfig from "../../../../Components/NotificationConfig/index.jsx"; +import NotificationsConfig from "@/Components/v1/NotificationConfig/index.jsx"; import { useGetNotificationsByTeamId } from "../../../../Hooks/v1/useNotifications.js"; import { useParams } from "react-router-dom"; import { useState, useEffect } from "react"; diff --git a/client/src/Pages/v1/Infrastructure/Details/Components/AreaChartBoxes/InfraAreaChart.jsx b/client/src/Pages/v1/Infrastructure/Details/Components/AreaChartBoxes/InfraAreaChart.jsx index 47f33a5e6..ee100bb0e 100644 --- a/client/src/Pages/v1/Infrastructure/Details/Components/AreaChartBoxes/InfraAreaChart.jsx +++ b/client/src/Pages/v1/Infrastructure/Details/Components/AreaChartBoxes/InfraAreaChart.jsx @@ -1,7 +1,7 @@ // Components import { Typography } from "@mui/material"; import BaseContainer from "../BaseContainer/index.jsx"; -import AreaChart from "../../../../../../Components/Charts/AreaChart/index.jsx"; +import AreaChart from "@/Components/v1/Charts/AreaChart/index.jsx"; // Utils import { useTheme } from "@emotion/react"; import { useHardwareUtils } from "../../Hooks/useHardwareUtils.jsx"; diff --git a/client/src/Pages/v1/Infrastructure/Details/Components/AreaChartBoxes/index.jsx b/client/src/Pages/v1/Infrastructure/Details/Components/AreaChartBoxes/index.jsx index 3dfb4edd8..3465683d9 100644 --- a/client/src/Pages/v1/Infrastructure/Details/Components/AreaChartBoxes/index.jsx +++ b/client/src/Pages/v1/Infrastructure/Details/Components/AreaChartBoxes/index.jsx @@ -9,7 +9,7 @@ import { TzTick, InfrastructureTooltip, TemperatureTooltip, -} from "../../../../../../Components/Charts/Utils/chartUtils.jsx"; +} from "@/Components/v1/Charts/Utils/chartUtils.jsx"; import { useTheme } from "@emotion/react"; import { useHardwareUtils } from "../../Hooks/useHardwareUtils.jsx"; import { useTranslation } from "react-i18next"; diff --git a/client/src/Pages/v1/Infrastructure/Details/Components/GaugeBoxes/Gauge.jsx b/client/src/Pages/v1/Infrastructure/Details/Components/GaugeBoxes/Gauge.jsx index 5c55119ad..cd61b7109 100644 --- a/client/src/Pages/v1/Infrastructure/Details/Components/GaugeBoxes/Gauge.jsx +++ b/client/src/Pages/v1/Infrastructure/Details/Components/GaugeBoxes/Gauge.jsx @@ -1,5 +1,5 @@ // Components -import CustomGauge from "../../../../../../Components/Charts/CustomGauge/index.jsx"; +import CustomGauge from "@/Components/v1/Charts/CustomGauge/index.jsx"; import BaseContainer from "../BaseContainer/index.jsx"; import { Stack, Typography, Box } from "@mui/material"; // Utils diff --git a/client/src/Pages/v1/Infrastructure/Details/Components/NetworkStats/NetworkCharts.jsx b/client/src/Pages/v1/Infrastructure/Details/Components/NetworkStats/NetworkCharts.jsx index 5cfdba350..d9f93311c 100644 --- a/client/src/Pages/v1/Infrastructure/Details/Components/NetworkStats/NetworkCharts.jsx +++ b/client/src/Pages/v1/Infrastructure/Details/Components/NetworkStats/NetworkCharts.jsx @@ -6,7 +6,7 @@ import { TzTick, InfrastructureTooltip, NetworkTick, -} from "../../../../../../Components/Charts/Utils/chartUtils.jsx"; +} from "@/Components/v1/Charts/Utils/chartUtils.jsx"; import { useTheme } from "@emotion/react"; import { useTranslation } from "react-i18next"; import { useHardwareUtils } from "../../Hooks/useHardwareUtils.jsx"; diff --git a/client/src/Pages/v1/Infrastructure/Details/Components/NetworkStats/NetworkStatBoxes.jsx b/client/src/Pages/v1/Infrastructure/Details/Components/NetworkStats/NetworkStatBoxes.jsx index 52ccf0805..0747dfcd1 100644 --- a/client/src/Pages/v1/Infrastructure/Details/Components/NetworkStats/NetworkStatBoxes.jsx +++ b/client/src/Pages/v1/Infrastructure/Details/Components/NetworkStats/NetworkStatBoxes.jsx @@ -1,6 +1,6 @@ import PropTypes from "prop-types"; -import StatusBoxes from "../../../../../../Components/StatusBoxes/index.jsx"; -import StatBox from "../../../../../../Components/StatBox/index.jsx"; +import StatusBoxes from "@/Components/v1/StatusBoxes/index.jsx"; +import StatBox from "@/Components/v1/StatBox/index.jsx"; import { Typography } from "@mui/material"; import { useTranslation } from "react-i18next"; import { useHardwareUtils } from "../../Hooks/useHardwareUtils.jsx"; diff --git a/client/src/Pages/v1/Infrastructure/Details/Components/NetworkStats/index.jsx b/client/src/Pages/v1/Infrastructure/Details/Components/NetworkStats/index.jsx index e0d6986e1..78db333e6 100644 --- a/client/src/Pages/v1/Infrastructure/Details/Components/NetworkStats/index.jsx +++ b/client/src/Pages/v1/Infrastructure/Details/Components/NetworkStats/index.jsx @@ -3,10 +3,10 @@ import { useState, useEffect } from "react"; import { Box } from "@mui/material"; import { useTranslation } from "react-i18next"; import { useTheme } from "@emotion/react"; -import Select from "../../../../../../Components/Inputs/Select/index.jsx"; +import Select from "@/Components/v1/Inputs/Select/index.jsx"; import NetworkStatBoxes from "./NetworkStatBoxes.jsx"; import NetworkCharts from "./NetworkCharts.jsx"; -import MonitorTimeFrameHeader from "../../../../../../Components/MonitorTimeFrameHeader/index.jsx"; +import MonitorTimeFrameHeader from "@/Components/v1/MonitorTimeFrameHeader/index.jsx"; const getAvailableInterfaces = (net) => { return (net || []).map((iface) => iface.name).filter(Boolean); diff --git a/client/src/Pages/v1/Infrastructure/Details/Components/StatusBoxes/index.jsx b/client/src/Pages/v1/Infrastructure/Details/Components/StatusBoxes/index.jsx index 47862449e..7f09e82d5 100644 --- a/client/src/Pages/v1/Infrastructure/Details/Components/StatusBoxes/index.jsx +++ b/client/src/Pages/v1/Infrastructure/Details/Components/StatusBoxes/index.jsx @@ -1,7 +1,7 @@ // Components import { Stack, Typography } from "@mui/material"; -import StatusBoxes from "../../../../../../Components/StatusBoxes/index.jsx"; -import StatBox from "../../../../../../Components/StatBox/index.jsx"; +import StatusBoxes from "@/Components/v1/StatusBoxes/index.jsx"; +import StatBox from "@/Components/v1/StatBox/index.jsx"; //Utils import { useMonitorUtils } from "../../../../../../Hooks/v1/useMonitorUtils.js"; diff --git a/client/src/Pages/v1/Infrastructure/Details/index.jsx b/client/src/Pages/v1/Infrastructure/Details/index.jsx index 04691d985..f9c51c27d 100644 --- a/client/src/Pages/v1/Infrastructure/Details/index.jsx +++ b/client/src/Pages/v1/Infrastructure/Details/index.jsx @@ -1,14 +1,14 @@ // Components import { Stack, Typography, Tab } from "@mui/material"; -import Breadcrumbs from "../../../../Components/Breadcrumbs/index.jsx"; -import MonitorDetailsControlHeader from "../../../../Components/MonitorDetailsControlHeader/index.jsx"; -import MonitorTimeFrameHeader from "../../../../Components/MonitorTimeFrameHeader/index.jsx"; +import Breadcrumbs from "@/Components/v1/Breadcrumbs/index.jsx"; +import MonitorDetailsControlHeader from "@/Components/v1/MonitorDetailsControlHeader/index.jsx"; +import MonitorTimeFrameHeader from "@/Components/v1/MonitorTimeFrameHeader/index.jsx"; import StatusBoxes from "./Components/StatusBoxes/index.jsx"; import GaugeBoxes from "./Components/GaugeBoxes/index.jsx"; import AreaChartBoxes from "./Components/AreaChartBoxes/index.jsx"; -import GenericFallback from "../../../../Components/GenericFallback/index.jsx"; +import GenericFallback from "@/Components/v1/GenericFallback/index.jsx"; import NetworkStats from "./Components/NetworkStats/index.jsx"; -import CustomTabList from "../../../../Components/Tab/index.jsx"; +import CustomTabList from "@/Components/v1/Tab/index.jsx"; import TabContext from "@mui/lab/TabContext"; // Utils diff --git a/client/src/Pages/v1/Infrastructure/Monitors/Components/Filters/index.jsx b/client/src/Pages/v1/Infrastructure/Monitors/Components/Filters/index.jsx index bef632e88..70bf502af 100644 --- a/client/src/Pages/v1/Infrastructure/Monitors/Components/Filters/index.jsx +++ b/client/src/Pages/v1/Infrastructure/Monitors/Components/Filters/index.jsx @@ -1,6 +1,6 @@ import { useTheme } from "@emotion/react"; import PropTypes from "prop-types"; -import FilterHeader from "../../../../../../Components/FilterHeader/index.jsx"; +import FilterHeader from "@/Components/v1/FilterHeader/index.jsx"; import { useMemo } from "react"; import { Box, Button } from "@mui/material"; import ClearIcon from "@mui/icons-material/Clear"; diff --git a/client/src/Pages/v1/Infrastructure/Monitors/Components/MonitorsTable/index.jsx b/client/src/Pages/v1/Infrastructure/Monitors/Components/MonitorsTable/index.jsx index 801a9bc6a..cc2cd5f98 100644 --- a/client/src/Pages/v1/Infrastructure/Monitors/Components/MonitorsTable/index.jsx +++ b/client/src/Pages/v1/Infrastructure/Monitors/Components/MonitorsTable/index.jsx @@ -1,14 +1,14 @@ // Components import { Box } from "@mui/material"; -import DataTable from "../../../../../../Components/Table/index.jsx"; -import Host from "../../../../../../Components/Host/index.jsx"; -import { StatusLabel } from "../../../../../../Components/Label/index.jsx"; +import DataTable from "@/Components/v1/Table/index.jsx"; +import Host from "@/Components/v1/Host/index.jsx"; +import { StatusLabel } from "@/Components/v1/Label/index.jsx"; import { Stack } from "@mui/material"; import { InfrastructureMenu } from "../MonitorsTableMenu/index.jsx"; import LoadingSpinner from "../../../../Uptime/Monitors/Components/LoadingSpinner/index.jsx"; // Assets import CPUChipIcon from "../../../../../../assets/icons/cpu-chip.svg?react"; -import CustomGauge from "../../../../../../Components/Charts/CustomGauge/index.jsx"; +import CustomGauge from "@/Components/v1/Charts/CustomGauge/index.jsx"; // Utils import { useTheme } from "@emotion/react"; diff --git a/client/src/Pages/v1/Infrastructure/Monitors/Components/MonitorsTableMenu/index.jsx b/client/src/Pages/v1/Infrastructure/Monitors/Components/MonitorsTableMenu/index.jsx index c2270c691..d3af34d00 100644 --- a/client/src/Pages/v1/Infrastructure/Monitors/Components/MonitorsTableMenu/index.jsx +++ b/client/src/Pages/v1/Infrastructure/Monitors/Components/MonitorsTableMenu/index.jsx @@ -7,7 +7,7 @@ import { createToast } from "../../../../../../Utils/toastUtils.jsx"; import { IconButton, Menu, MenuItem } from "@mui/material"; import Settings from "../../../../../../assets/icons/settings-bold.svg?react"; import PropTypes from "prop-types"; -import Dialog from "../../../../../../Components/Dialog/index.jsx"; +import Dialog from "@/Components/v1/Dialog/index.jsx"; import { networkService } from "../../../../../../Utils/NetworkService.js"; import { usePauseMonitor } from "../../../../../../Hooks/v1/monitorHooks.js"; import { useTranslation } from "react-i18next"; diff --git a/client/src/Pages/v1/Infrastructure/Monitors/index.jsx b/client/src/Pages/v1/Infrastructure/Monitors/index.jsx index 7fbfa1167..6c3a657ab 100644 --- a/client/src/Pages/v1/Infrastructure/Monitors/index.jsx +++ b/client/src/Pages/v1/Infrastructure/Monitors/index.jsx @@ -1,11 +1,11 @@ // Components import { Stack } from "@mui/material"; -import Breadcrumbs from "../../../../Components/Breadcrumbs/index.jsx"; -import MonitorCountHeader from "../../../../Components/MonitorCountHeader/index.jsx"; -import MonitorCreateHeader from "../../../../Components/MonitorCreateHeader/index.jsx"; +import Breadcrumbs from "@/Components/v1/Breadcrumbs/index.jsx"; +import MonitorCountHeader from "@/Components/v1/MonitorCountHeader/index.jsx"; +import MonitorCreateHeader from "@/Components/v1/MonitorCreateHeader/index.jsx"; import MonitorsTable from "./Components/MonitorsTable/index.jsx"; -import Pagination from "../../../../Components/Table/TablePagination/index.jsx"; -import PageStateWrapper from "../../../../Components/PageStateWrapper/index.jsx"; +import Pagination from "@/Components/v1/Table/TablePagination/index.jsx"; +import PageStateWrapper from "@/Components/v1/PageStateWrapper/index.jsx"; import Filter from "./Components/Filters/index.jsx"; import SearchComponent from "../../Uptime/Monitors/Components/SearchComponent/index.jsx"; // Utils diff --git a/client/src/Pages/v1/Logs/Diagnostics/components/gauges/index.jsx b/client/src/Pages/v1/Logs/Diagnostics/components/gauges/index.jsx index 082be2587..b38837cf2 100644 --- a/client/src/Pages/v1/Logs/Diagnostics/components/gauges/index.jsx +++ b/client/src/Pages/v1/Logs/Diagnostics/components/gauges/index.jsx @@ -1,5 +1,5 @@ import Stack from "@mui/material/Stack"; -import CustomGauge from "../../../../../../Components/Charts/CustomGauge/index.jsx"; +import CustomGauge from "@/Components/v1/Charts/CustomGauge/index.jsx"; import Typography from "@mui/material/Typography"; // Utils diff --git a/client/src/Pages/v1/Logs/Diagnostics/index.jsx b/client/src/Pages/v1/Logs/Diagnostics/index.jsx index d7a1a8a7c..54feee516 100644 --- a/client/src/Pages/v1/Logs/Diagnostics/index.jsx +++ b/client/src/Pages/v1/Logs/Diagnostics/index.jsx @@ -2,8 +2,8 @@ import Stack from "@mui/material/Stack"; import Box from "@mui/material/Box"; import Gauges from "./components/gauges/index.jsx"; import Button from "@mui/material/Button"; -import StatBox from "../../../../Components/StatBox/index.jsx"; -import StatusBoxes from "../../../../Components/StatusBoxes/index.jsx"; +import StatBox from "@/Components/v1/StatBox/index.jsx"; +import StatusBoxes from "@/Components/v1/StatusBoxes/index.jsx"; import { useTheme } from "@emotion/react"; import { useTranslation } from "react-i18next"; import { useFetchDiagnostics } from "../../../../Hooks/v1/logHooks.js"; diff --git a/client/src/Pages/v1/Logs/Logs/index.jsx b/client/src/Pages/v1/Logs/Logs/index.jsx index 5c7ad9fa9..64c4cb97a 100644 --- a/client/src/Pages/v1/Logs/Logs/index.jsx +++ b/client/src/Pages/v1/Logs/Logs/index.jsx @@ -1,6 +1,6 @@ import Stack from "@mui/material/Stack"; import Box from "@mui/material/Box"; -import Select from "../../../../Components/Inputs/Select/index.jsx"; +import Select from "@/Components/v1/Inputs/Select/index.jsx"; import Typography from "@mui/material/Typography"; import Divider from "@mui/material/Divider"; diff --git a/client/src/Pages/v1/Logs/Queue/components/FailedJobTable/index.jsx b/client/src/Pages/v1/Logs/Queue/components/FailedJobTable/index.jsx index 46be9520c..743f52639 100644 --- a/client/src/Pages/v1/Logs/Queue/components/FailedJobTable/index.jsx +++ b/client/src/Pages/v1/Logs/Queue/components/FailedJobTable/index.jsx @@ -1,6 +1,6 @@ import Stack from "@mui/material/Stack"; import Typography from "@mui/material/Typography"; -import DataTable from "../../../../../../Components/Table/index.jsx"; +import DataTable from "@/Components/v1/Table/index.jsx"; import { useNavigate } from "react-router-dom"; import { useTheme } from "@emotion/react"; diff --git a/client/src/Pages/v1/Logs/Queue/components/JobTable/index.jsx b/client/src/Pages/v1/Logs/Queue/components/JobTable/index.jsx index 4ac4069bd..7d309e845 100644 --- a/client/src/Pages/v1/Logs/Queue/components/JobTable/index.jsx +++ b/client/src/Pages/v1/Logs/Queue/components/JobTable/index.jsx @@ -1,5 +1,5 @@ import Stack from "@mui/material/Stack"; -import DataTable from "../../../../../../Components/Table/index.jsx"; +import DataTable from "@/Components/v1/Table/index.jsx"; import Typography from "@mui/material/Typography"; // Utils import PropTypes from "prop-types"; @@ -7,7 +7,7 @@ import { useTheme } from "@emotion/react"; import { useNavigate } from "react-router-dom"; import { TypeToPathMap } from "../../../../../../Utils/monitorUtils.js"; import { useTranslation } from "react-i18next"; -import { createHeaderFactory } from "../../../../../../Components/Table/TableUtils.js"; +import { createHeaderFactory } from "@/Components/v1/Table/TableUtils.js"; const JobTable = ({ jobs = [] }) => { const theme = useTheme(); diff --git a/client/src/Pages/v1/Logs/Queue/components/Metrics/index.jsx b/client/src/Pages/v1/Logs/Queue/components/Metrics/index.jsx index dd9fdcc6a..c4f4389c3 100644 --- a/client/src/Pages/v1/Logs/Queue/components/Metrics/index.jsx +++ b/client/src/Pages/v1/Logs/Queue/components/Metrics/index.jsx @@ -1,6 +1,6 @@ import Stack from "@mui/material/Stack"; -import StatBox from "../../../../../../Components/StatBox/index.jsx"; -import StatusBoxes from "../../../../../../Components/StatusBoxes/index.jsx"; +import StatBox from "@/Components/v1/StatBox/index.jsx"; +import StatusBoxes from "@/Components/v1/StatusBoxes/index.jsx"; import { useTranslation } from "react-i18next"; import { useTheme } from "@emotion/react"; diff --git a/client/src/Pages/v1/Logs/Queue/components/MetricsTable/index.jsx b/client/src/Pages/v1/Logs/Queue/components/MetricsTable/index.jsx index 58f13fd5d..d96f37a20 100644 --- a/client/src/Pages/v1/Logs/Queue/components/MetricsTable/index.jsx +++ b/client/src/Pages/v1/Logs/Queue/components/MetricsTable/index.jsx @@ -1,6 +1,6 @@ import Stack from "@mui/material/Stack"; import Typography from "@mui/material/Typography"; -import DataTable from "../../../../../../Components/Table/index.jsx"; +import DataTable from "@/Components/v1/Table/index.jsx"; // Utils import { useTranslation } from "react-i18next"; diff --git a/client/src/Pages/v1/Logs/index.jsx b/client/src/Pages/v1/Logs/index.jsx index c6dfe4d99..e5bdf623d 100644 --- a/client/src/Pages/v1/Logs/index.jsx +++ b/client/src/Pages/v1/Logs/index.jsx @@ -1,5 +1,5 @@ import Stack from "@mui/material/Stack"; -import Breadcrumbs from "../../../Components/Breadcrumbs/index.jsx"; +import Breadcrumbs from "@/Components/v1/Breadcrumbs/index.jsx"; import Tabs from "@mui/material/Tabs"; import Tab from "@mui/material/Tab"; import Queue from "./Queue/index.jsx"; diff --git a/client/src/Pages/v1/Maintenance/CreateMaintenance/Components/ConfigSelect/index.jsx b/client/src/Pages/v1/Maintenance/CreateMaintenance/Components/ConfigSelect/index.jsx index b43bcbfc7..8d4c0ae53 100644 --- a/client/src/Pages/v1/Maintenance/CreateMaintenance/Components/ConfigSelect/index.jsx +++ b/client/src/Pages/v1/Maintenance/CreateMaintenance/Components/ConfigSelect/index.jsx @@ -1,4 +1,4 @@ -import Select from "../../../../../../Components/Inputs/Select/index.jsx"; +import Select from "@/Components/v1/Inputs/Select/index.jsx"; import PropTypes from "prop-types"; const ConfigSelect = ({ configSelection, valueSelect, onChange, ...props }) => { const getValueById = (config, id) => { diff --git a/client/src/Pages/v1/Maintenance/CreateMaintenance/Components/MonitorsConfig/index.jsx b/client/src/Pages/v1/Maintenance/CreateMaintenance/Components/MonitorsConfig/index.jsx index 91e6a1531..14920d81c 100644 --- a/client/src/Pages/v1/Maintenance/CreateMaintenance/Components/MonitorsConfig/index.jsx +++ b/client/src/Pages/v1/Maintenance/CreateMaintenance/Components/MonitorsConfig/index.jsx @@ -1,7 +1,7 @@ import PropTypes from "prop-types"; -import Search from "../../../../../../Components/Inputs/Search/index.jsx"; +import Search from "@/Components/v1/Inputs/Search/index.jsx"; import { useState } from "react"; -import ConfigRow from "../../../../../../Components/ConfigRow/index.jsx"; +import ConfigRow from "@/Components/v1/ConfigRow/index.jsx"; /** * MonitorsConfig is a component that allows users to select and apply a maintenance window to a list of monitors. diff --git a/client/src/Pages/v1/Maintenance/CreateMaintenance/index.jsx b/client/src/Pages/v1/Maintenance/CreateMaintenance/index.jsx index da3e72d8a..d0e7b03cd 100644 --- a/client/src/Pages/v1/Maintenance/CreateMaintenance/index.jsx +++ b/client/src/Pages/v1/Maintenance/CreateMaintenance/index.jsx @@ -2,7 +2,7 @@ import { Box, Button, Stack, Typography } from "@mui/material"; import { useSelector } from "react-redux"; import { useTheme } from "@emotion/react"; import { useEffect, useState } from "react"; -import ConfigBox from "../../../../Components/ConfigBox/index.jsx"; +import ConfigBox from "@/Components/v1/ConfigBox/index.jsx"; import { AdapterDayjs } from "@mui/x-date-pickers/AdapterDayjs"; import { LocalizationProvider } from "@mui/x-date-pickers"; import { DatePicker } from "@mui/x-date-pickers/DatePicker"; @@ -14,11 +14,11 @@ import ConfigSelect from "./Components/ConfigSelect/index.jsx"; import useMaintenanceData from "./hooks/useMaintenanceData.jsx"; import useMaintenanceActions from "./hooks/useMaintenanceActions.jsx"; -import TextInput from "../../../../Components/Inputs/TextInput/index.jsx"; -import Breadcrumbs from "../../../../Components/Breadcrumbs/index.jsx"; +import TextInput from "@/Components/v1/Inputs/TextInput/index.jsx"; +import Breadcrumbs from "@/Components/v1/Breadcrumbs/index.jsx"; import CalendarIcon from "../../../../assets/icons/calendar.svg?react"; import "./index.css"; -import Search from "../../../../Components/Inputs/Search/index.jsx"; +import Search from "@/Components/v1/Inputs/Search/index.jsx"; import { logger } from "../../../../Utils/Logger.js"; import { useNavigate, useParams } from "react-router-dom"; diff --git a/client/src/Pages/v1/Maintenance/MaintenanceTable/ActionsMenu/index.jsx b/client/src/Pages/v1/Maintenance/MaintenanceTable/ActionsMenu/index.jsx index cc51697f7..b2654da49 100644 --- a/client/src/Pages/v1/Maintenance/MaintenanceTable/ActionsMenu/index.jsx +++ b/client/src/Pages/v1/Maintenance/MaintenanceTable/ActionsMenu/index.jsx @@ -10,7 +10,7 @@ import { networkService } from "../../../../../main.jsx"; import { createToast } from "../../../../../Utils/toastUtils.jsx"; import { useTranslation } from "react-i18next"; -import Dialog from "../../../../../Components/Dialog/index.jsx"; +import Dialog from "@/Components/v1/Dialog/index.jsx"; const ActionsMenu = ({ /* isAdmin, */ maintenanceWindow, updateCallback }) => { maintenanceWindow; diff --git a/client/src/Pages/v1/Maintenance/MaintenanceTable/index.jsx b/client/src/Pages/v1/Maintenance/MaintenanceTable/index.jsx index fb18cd380..f1d44f944 100644 --- a/client/src/Pages/v1/Maintenance/MaintenanceTable/index.jsx +++ b/client/src/Pages/v1/Maintenance/MaintenanceTable/index.jsx @@ -1,14 +1,14 @@ import PropTypes from "prop-types"; import { Box } from "@mui/material"; -import DataTable from "../../../../Components/Table/index.jsx"; -import Pagination from "../../../../Components/Table/TablePagination/index.jsx"; +import DataTable from "@/Components/v1/Table/index.jsx"; +import Pagination from "@/Components/v1/Table/TablePagination/index.jsx"; import ArrowDownwardRoundedIcon from "@mui/icons-material/ArrowDownwardRounded"; import ArrowUpwardRoundedIcon from "@mui/icons-material/ArrowUpwardRounded"; import ActionsMenu from "./ActionsMenu/index.jsx"; import { memo } from "react"; import { useDispatch, useSelector } from "react-redux"; import { formatDurationRounded } from "../../../../Utils/timeUtils.js"; -import { StatusLabel } from "../../../../Components/Label/index.jsx"; +import { StatusLabel } from "@/Components/v1/Label/index.jsx"; import { setRowsPerPage } from "../../../../Features/UI/uiSlice.js"; import { useTranslation } from "react-i18next"; import { useTheme } from "@emotion/react"; diff --git a/client/src/Pages/v1/Maintenance/index.jsx b/client/src/Pages/v1/Maintenance/index.jsx index a993c153f..cba0e9f18 100644 --- a/client/src/Pages/v1/Maintenance/index.jsx +++ b/client/src/Pages/v1/Maintenance/index.jsx @@ -5,10 +5,10 @@ import "./index.css"; import MaintenanceTable from "./MaintenanceTable/index.jsx"; import { useSelector } from "react-redux"; import { networkService } from "../../../main.jsx"; -import Breadcrumbs from "../../../Components/Breadcrumbs/index.jsx"; +import Breadcrumbs from "@/Components/v1/Breadcrumbs/index.jsx"; import { useNavigate } from "react-router-dom"; import { useTranslation } from "react-i18next"; -import PageStateWrapper from "../../../Components/PageStateWrapper/index.jsx"; +import PageStateWrapper from "@/Components/v1/PageStateWrapper/index.jsx"; const Maintenance = () => { const theme = useTheme(); diff --git a/client/src/Pages/v1/Notifications/create/index.jsx b/client/src/Pages/v1/Notifications/create/index.jsx index 0d541736d..eb2b04aa6 100644 --- a/client/src/Pages/v1/Notifications/create/index.jsx +++ b/client/src/Pages/v1/Notifications/create/index.jsx @@ -1,13 +1,13 @@ // Components import Stack from "@mui/material/Stack"; import Typography from "@mui/material/Typography"; -import Breadcrumbs from "../../../../Components/Breadcrumbs/index.jsx"; +import Breadcrumbs from "@/Components/v1/Breadcrumbs/index.jsx"; import Button from "@mui/material/Button"; -import ConfigBox from "../../../../Components/ConfigBox/index.jsx"; +import ConfigBox from "@/Components/v1/ConfigBox/index.jsx"; import Box from "@mui/material/Box"; -import Select from "../../../../Components/Inputs/Select/index.jsx"; -import TextInput from "../../../../Components/Inputs/TextInput/index.jsx"; -import Dialog from "../../../../Components/Dialog/index.jsx"; +import Select from "@/Components/v1/Inputs/Select/index.jsx"; +import TextInput from "@/Components/v1/Inputs/TextInput/index.jsx"; +import Dialog from "@/Components/v1/Dialog/index.jsx"; // Utils import { useState } from "react"; diff --git a/client/src/Pages/v1/Notifications/index.jsx b/client/src/Pages/v1/Notifications/index.jsx index 5649224b6..78ec6e9bb 100644 --- a/client/src/Pages/v1/Notifications/index.jsx +++ b/client/src/Pages/v1/Notifications/index.jsx @@ -1,11 +1,11 @@ // Components import Stack from "@mui/material/Stack"; import Typography from "@mui/material/Typography"; -import Breadcrumbs from "../../../Components/Breadcrumbs/index.jsx"; +import Breadcrumbs from "@/Components/v1/Breadcrumbs/index.jsx"; import Button from "@mui/material/Button"; -import DataTable from "../../../Components/Table/index.jsx"; +import DataTable from "@/Components/v1/Table/index.jsx"; import ActionMenu from "./components/ActionMenu.jsx"; -import PageStateWrapper from "../../../Components/PageStateWrapper/index.jsx"; +import PageStateWrapper from "@/Components/v1/PageStateWrapper/index.jsx"; // Utils import { useState } from "react"; diff --git a/client/src/Pages/v1/PageSpeed/Create/index.jsx b/client/src/Pages/v1/PageSpeed/Create/index.jsx index 7cc244848..cb856bd37 100644 --- a/client/src/Pages/v1/PageSpeed/Create/index.jsx +++ b/client/src/Pages/v1/PageSpeed/Create/index.jsx @@ -1,17 +1,17 @@ // Components import { Box, Stack, Tooltip, Typography, Button, ButtonGroup } from "@mui/material"; -import ConfigBox from "../../../../Components/ConfigBox/index.jsx"; -import Select from "../../../../Components/Inputs/Select/index.jsx"; -import TextInput from "../../../../Components/Inputs/TextInput/index.jsx"; +import ConfigBox from "@/Components/v1/ConfigBox/index.jsx"; +import Select from "@/Components/v1/Inputs/Select/index.jsx"; +import TextInput from "@/Components/v1/Inputs/TextInput/index.jsx"; import PauseCircleOutlineIcon from "@mui/icons-material/PauseCircleOutline"; -import Breadcrumbs from "../../../../Components/Breadcrumbs/index.jsx"; -import PulseDot from "../../../../Components/Animated/PulseDot.jsx"; +import Breadcrumbs from "@/Components/v1/Breadcrumbs/index.jsx"; +import PulseDot from "@/Components/v1/Animated/PulseDot.jsx"; import PlayCircleOutlineRoundedIcon from "@mui/icons-material/PlayCircleOutlineRounded"; import SkeletonLayout from "./skeleton.jsx"; -import NotificationsConfig from "../../../../Components/NotificationConfig/index.jsx"; -import Dialog from "../../../../Components/Dialog/index.jsx"; -import { HttpAdornment } from "../../../../Components/Inputs/TextInput/Adornments/index.jsx"; -import Radio from "../../../../Components/Inputs/Radio/index.jsx"; +import NotificationsConfig from "@/Components/v1/NotificationConfig/index.jsx"; +import Dialog from "@/Components/v1/Dialog/index.jsx"; +import { HttpAdornment } from "@/Components/v1/Inputs/TextInput/Adornments/index.jsx"; +import Radio from "@/Components/v1/Inputs/Radio/index.jsx"; // Utils import { useState, useEffect } from "react"; diff --git a/client/src/Pages/v1/PageSpeed/Details/Components/Charts/AreaChartLegend.jsx b/client/src/Pages/v1/PageSpeed/Details/Components/Charts/AreaChartLegend.jsx index cb6f18bd2..c0da1600f 100644 --- a/client/src/Pages/v1/PageSpeed/Details/Components/Charts/AreaChartLegend.jsx +++ b/client/src/Pages/v1/PageSpeed/Details/Components/Charts/AreaChartLegend.jsx @@ -1,7 +1,7 @@ import { Box, Typography, Divider } from "@mui/material"; -import Checkbox from "../../../../../../Components/Inputs/Checkbox/index.jsx"; +import Checkbox from "@/Components/v1/Inputs/Checkbox/index.jsx"; import MetricsIcon from "../../../../../../assets/icons/ruler-icon.svg?react"; -import LegendBox from "../../../../../../Components/Charts/LegendBox/index.jsx"; +import LegendBox from "@/Components/v1/Charts/LegendBox/index.jsx"; import { useTranslation } from "react-i18next"; import { useTheme } from "@emotion/react"; diff --git a/client/src/Pages/v1/PageSpeed/Details/Components/Charts/PieChartLegend.jsx b/client/src/Pages/v1/PageSpeed/Details/Components/Charts/PieChartLegend.jsx index fc4b190d8..1297bd699 100644 --- a/client/src/Pages/v1/PageSpeed/Details/Components/Charts/PieChartLegend.jsx +++ b/client/src/Pages/v1/PageSpeed/Details/Components/Charts/PieChartLegend.jsx @@ -1,6 +1,6 @@ import { Stack, Box, Typography } from "@mui/material"; import { useTheme } from "@emotion/react"; -import LegendBox from "../../../../../../Components/Charts/LegendBox/index.jsx"; +import LegendBox from "@/Components/v1/Charts/LegendBox/index.jsx"; import SpeedometerIcon from "../../../../../../assets/icons/speedometer-icon.svg?react"; import PropTypes from "prop-types"; diff --git a/client/src/Pages/v1/PageSpeed/Details/Components/PageSpeedAreaChart/index.jsx b/client/src/Pages/v1/PageSpeed/Details/Components/PageSpeedAreaChart/index.jsx index bb45117fe..1281a396e 100644 --- a/client/src/Pages/v1/PageSpeed/Details/Components/PageSpeedAreaChart/index.jsx +++ b/client/src/Pages/v1/PageSpeed/Details/Components/PageSpeedAreaChart/index.jsx @@ -1,4 +1,4 @@ -import ChartBox from "../../../../../../Components/Charts/ChartBox/index.jsx"; +import ChartBox from "@/Components/v1/Charts/ChartBox/index.jsx"; import AreaChart from "../Charts/AreaChart.jsx"; import AreaChartLegend from "../Charts/AreaChartLegend.jsx"; import SkeletonLayout from "./skeleton.jsx"; diff --git a/client/src/Pages/v1/PageSpeed/Details/Components/PageSpeedStatusBoxes/index.jsx b/client/src/Pages/v1/PageSpeed/Details/Components/PageSpeedStatusBoxes/index.jsx index 0811ae707..ff265317c 100644 --- a/client/src/Pages/v1/PageSpeed/Details/Components/PageSpeedStatusBoxes/index.jsx +++ b/client/src/Pages/v1/PageSpeed/Details/Components/PageSpeedStatusBoxes/index.jsx @@ -1,5 +1,5 @@ -import StatusBoxes from "../../../../../../Components/StatusBoxes/index.jsx"; -import StatBox from "../../../../../../Components/StatBox/index.jsx"; +import StatusBoxes from "@/Components/v1/StatusBoxes/index.jsx"; +import StatBox from "@/Components/v1/StatBox/index.jsx"; import { Typography } from "@mui/material"; import { getHumanReadableDuration } from "../../../../../../Utils/timeUtils.js"; import { useTranslation } from "react-i18next"; diff --git a/client/src/Pages/v1/PageSpeed/Details/Components/PerformanceReport/index.jsx b/client/src/Pages/v1/PageSpeed/Details/Components/PerformanceReport/index.jsx index ce4704144..27f9e885b 100644 --- a/client/src/Pages/v1/PageSpeed/Details/Components/PerformanceReport/index.jsx +++ b/client/src/Pages/v1/PageSpeed/Details/Components/PerformanceReport/index.jsx @@ -1,4 +1,4 @@ -import ChartBox from "../../../../../../Components/Charts/ChartBox/index.jsx"; +import ChartBox from "@/Components/v1/Charts/ChartBox/index.jsx"; import PerformanceIcon from "../../../../../../assets/icons/performance-report.svg?react"; import PieChart from "../Charts/PieChart.jsx"; import { Typography } from "@mui/material"; diff --git a/client/src/Pages/v1/PageSpeed/Details/index.jsx b/client/src/Pages/v1/PageSpeed/Details/index.jsx index cf9652bce..836f0ca37 100644 --- a/client/src/Pages/v1/PageSpeed/Details/index.jsx +++ b/client/src/Pages/v1/PageSpeed/Details/index.jsx @@ -1,12 +1,12 @@ // Components import { Stack, Typography } from "@mui/material"; -import Breadcrumbs from "../../../../Components/Breadcrumbs/index.jsx"; -import MonitorTimeFrameHeader from "../../../../Components/MonitorTimeFrameHeader/index.jsx"; +import Breadcrumbs from "@/Components/v1/Breadcrumbs/index.jsx"; +import MonitorTimeFrameHeader from "@/Components/v1/MonitorTimeFrameHeader/index.jsx"; import PageSpeedStatusBoxes from "./Components/PageSpeedStatusBoxes/index.jsx"; -import MonitorDetailsControlHeader from "../../../../Components/MonitorDetailsControlHeader/index.jsx"; +import MonitorDetailsControlHeader from "@/Components/v1/MonitorDetailsControlHeader/index.jsx"; import PageSpeedAreaChart from "./Components/PageSpeedAreaChart/index.jsx"; import PerformanceReport from "./Components/PerformanceReport/index.jsx"; -import GenericFallback from "../../../../Components/GenericFallback/index.jsx"; +import GenericFallback from "@/Components/v1/GenericFallback/index.jsx"; // Utils import { useTheme } from "@emotion/react"; import { useIsAdmin } from "../../../../Hooks/v1/useIsAdmin.js"; diff --git a/client/src/Pages/v1/PageSpeed/Monitors/Components/Card/index.jsx b/client/src/Pages/v1/PageSpeed/Monitors/Components/Card/index.jsx index 5857a7a7e..4e551526b 100644 --- a/client/src/Pages/v1/PageSpeed/Monitors/Components/Card/index.jsx +++ b/client/src/Pages/v1/PageSpeed/Monitors/Components/Card/index.jsx @@ -1,7 +1,7 @@ import PropTypes from "prop-types"; // import PageSpeedIcon from "../../../../assets/icons/page-speed.svg?react"; import PageSpeedIcon from "../../../../../../assets/icons/page-speed.svg?react"; -import { StatusLabel } from "../../../../../../Components/Label/index.jsx"; +import { StatusLabel } from "@/Components/v1/Label/index.jsx"; import { Box, Grid, Stack, Typography } from "@mui/material"; import { useNavigate } from "react-router-dom"; import { useTheme } from "@emotion/react"; @@ -14,7 +14,7 @@ import { import { useMonitorUtils } from "../../../../../../Hooks/v1/useMonitorUtils.js"; import { useState } from "react"; import { useTranslation } from "react-i18next"; -import IconBox from "../../../../../../Components/IconBox/index.jsx"; +import IconBox from "@/Components/v1/IconBox/index.jsx"; /** * CustomToolTip displays a tooltip with formatted date and score information. * @param {Object} props diff --git a/client/src/Pages/v1/PageSpeed/Monitors/index.jsx b/client/src/Pages/v1/PageSpeed/Monitors/index.jsx index e568e8d82..7485a72c9 100644 --- a/client/src/Pages/v1/PageSpeed/Monitors/index.jsx +++ b/client/src/Pages/v1/PageSpeed/Monitors/index.jsx @@ -1,12 +1,12 @@ // Components import { useState } from "react"; -import Breadcrumbs from "../../../../Components/Breadcrumbs/index.jsx"; +import Breadcrumbs from "@/Components/v1/Breadcrumbs/index.jsx"; import { Stack } from "@mui/material"; -import CreateMonitorHeader from "../../../../Components/MonitorCreateHeader/index.jsx"; -import MonitorCountHeader from "../../../../Components/MonitorCountHeader/index.jsx"; +import CreateMonitorHeader from "@/Components/v1/MonitorCreateHeader/index.jsx"; +import MonitorCountHeader from "@/Components/v1/MonitorCountHeader/index.jsx"; import MonitorGrid from "./Components/MonitorGrid/index.jsx"; -import PageStateWrapper from "../../../../Components/PageStateWrapper/index.jsx"; -import FallbackPageSpeedWarning from "../../../../Components/Fallback/FallbackPageSpeedWarning.jsx"; +import PageStateWrapper from "@/Components/v1/PageStateWrapper/index.jsx"; +import FallbackPageSpeedWarning from "@/Components/v1/Fallback/FallbackPageSpeedWarning.jsx"; // Utils import { useTheme } from "@emotion/react"; diff --git a/client/src/Pages/v1/ServerUnreachable.jsx b/client/src/Pages/v1/ServerUnreachable.jsx index 0db697543..8230f473b 100644 --- a/client/src/Pages/v1/ServerUnreachable.jsx +++ b/client/src/Pages/v1/ServerUnreachable.jsx @@ -3,12 +3,12 @@ import { Box, Typography, Button, Stack } from "@mui/material"; import { useTheme } from "@emotion/react"; import { useNavigate } from "react-router-dom"; import { networkService } from "../../Utils/NetworkService.js"; -import Alert from "../../Components/Alert/index.jsx"; +import Alert from "@/Components/v1/Alert/index.jsx"; import { createToast } from "../../Utils/toastUtils.jsx"; import { useTranslation } from "react-i18next"; import Background from "../../assets/Images/background-grid.svg?react"; import Logo from "../../assets/icons/checkmate-icon.svg?react"; -import ThemeSwitch from "../../Components/ThemeSwitch/index.jsx"; +import ThemeSwitch from "@/Components/v1/ThemeSwitch/index.jsx"; import LanguageSelector from "../../Components/LanguageSelector.jsx"; const ServerUnreachable = () => { diff --git a/client/src/Pages/v1/Settings/SettingsAbout.jsx b/client/src/Pages/v1/Settings/SettingsAbout.jsx index 3d5f5224e..6a8125af3 100644 --- a/client/src/Pages/v1/Settings/SettingsAbout.jsx +++ b/client/src/Pages/v1/Settings/SettingsAbout.jsx @@ -1,10 +1,10 @@ import Box from "@mui/material/Box"; import Typography from "@mui/material/Typography"; -import ConfigBox from "../../../Components/ConfigBox/index.jsx"; +import ConfigBox from "@/Components/v1/ConfigBox/index.jsx"; // Utils import { useTheme } from "@emotion/react"; import { useTranslation } from "react-i18next"; -import Link from "../../../Components/Link/index.jsx"; +import Link from "@/Components/v1/Link/index.jsx"; const SettingsAbout = () => { const theme = useTheme(); diff --git a/client/src/Pages/v1/Settings/SettingsDemoMonitors.jsx b/client/src/Pages/v1/Settings/SettingsDemoMonitors.jsx index 96f907970..47e699851 100644 --- a/client/src/Pages/v1/Settings/SettingsDemoMonitors.jsx +++ b/client/src/Pages/v1/Settings/SettingsDemoMonitors.jsx @@ -1,12 +1,12 @@ import Box from "@mui/material/Box"; import Typography from "@mui/material/Typography"; import Button from "@mui/material/Button"; -import ConfigBox from "../../../Components/ConfigBox/index.jsx"; +import ConfigBox from "@/Components/v1/ConfigBox/index.jsx"; // Utils import { useTheme } from "@emotion/react"; import { PropTypes } from "prop-types"; import { useTranslation } from "react-i18next"; -import Dialog from "../../../Components/Dialog/index.jsx"; +import Dialog from "@/Components/v1/Dialog/index.jsx"; import { useState } from "react"; const SettingsDemoMonitors = ({ isAdmin, HEADER_SX, handleChange, isLoading }) => { diff --git a/client/src/Pages/v1/Settings/SettingsEmail.jsx b/client/src/Pages/v1/Settings/SettingsEmail.jsx index c2389b277..7b5ad175e 100644 --- a/client/src/Pages/v1/Settings/SettingsEmail.jsx +++ b/client/src/Pages/v1/Settings/SettingsEmail.jsx @@ -1,17 +1,17 @@ import Box from "@mui/material/Box"; import Typography from "@mui/material/Typography"; -import ConfigBox from "../../../Components/ConfigBox/index.jsx"; -import TextInput from "../../../Components/Inputs/TextInput/index.jsx"; +import ConfigBox from "@/Components/v1/ConfigBox/index.jsx"; +import TextInput from "@/Components/v1/Inputs/TextInput/index.jsx"; import Button from "@mui/material/Button"; import Stack from "@mui/material/Stack"; import { Switch } from "@mui/material"; -import TextLink from "../../../Components/TextLink/index.jsx"; +import TextLink from "@/Components/v1/TextLink/index.jsx"; // Utils import { useTheme } from "@emotion/react"; import { PropTypes } from "prop-types"; import { useState } from "react"; import { useTranslation } from "react-i18next"; -import { PasswordEndAdornment } from "../../../Components/Inputs/TextInput/Adornments/index.jsx"; +import { PasswordEndAdornment } from "@/Components/v1/Inputs/TextInput/Adornments/index.jsx"; import { useSendTestEmail } from "../../../Hooks/v1/useSendTestEmail.js"; import { createToast } from "../../../Utils/toastUtils.jsx"; diff --git a/client/src/Pages/v1/Settings/SettingsGlobalThresholds.jsx b/client/src/Pages/v1/Settings/SettingsGlobalThresholds.jsx index e238c6475..ea58478e2 100644 --- a/client/src/Pages/v1/Settings/SettingsGlobalThresholds.jsx +++ b/client/src/Pages/v1/Settings/SettingsGlobalThresholds.jsx @@ -1,8 +1,8 @@ import Box from "@mui/material/Box"; import Stack from "@mui/material/Stack"; import Typography from "@mui/material/Typography"; -import ConfigBox from "../../../Components/ConfigBox/index.jsx"; -import TextInput from "../../../Components/Inputs/TextInput/index.jsx"; +import ConfigBox from "@/Components/v1/ConfigBox/index.jsx"; +import TextInput from "@/Components/v1/Inputs/TextInput/index.jsx"; import { useTheme } from "@emotion/react"; import { PropTypes } from "prop-types"; diff --git a/client/src/Pages/v1/Settings/SettingsPagespeed.jsx b/client/src/Pages/v1/Settings/SettingsPagespeed.jsx index 83b2e23c7..b0d5d6441 100644 --- a/client/src/Pages/v1/Settings/SettingsPagespeed.jsx +++ b/client/src/Pages/v1/Settings/SettingsPagespeed.jsx @@ -2,9 +2,9 @@ import Box from "@mui/material/Box"; import Stack from "@mui/material/Stack"; import Typography from "@mui/material/Typography"; import Button from "@mui/material/Button"; -import ConfigBox from "../../../Components/ConfigBox/index.jsx"; -import TextInput from "../../../Components/Inputs/TextInput/index.jsx"; -import { PasswordEndAdornment } from "../../../Components/Inputs/TextInput/Adornments/index.jsx"; +import ConfigBox from "@/Components/v1/ConfigBox/index.jsx"; +import TextInput from "@/Components/v1/Inputs/TextInput/index.jsx"; +import { PasswordEndAdornment } from "@/Components/v1/Inputs/TextInput/Adornments/index.jsx"; // Utils import { useTheme } from "@emotion/react"; import { PropTypes } from "prop-types"; diff --git a/client/src/Pages/v1/Settings/SettingsStats.jsx b/client/src/Pages/v1/Settings/SettingsStats.jsx index 52f739ecf..b68006ab2 100644 --- a/client/src/Pages/v1/Settings/SettingsStats.jsx +++ b/client/src/Pages/v1/Settings/SettingsStats.jsx @@ -2,9 +2,9 @@ import Box from "@mui/material/Box"; import Typography from "@mui/material/Typography"; import Button from "@mui/material/Button"; import Stack from "@mui/material/Stack"; -import ConfigBox from "../../../Components/ConfigBox/index.jsx"; -import TextInput from "../../../Components/Inputs/TextInput/index.jsx"; -import Dialog from "../../../Components/Dialog/index.jsx"; +import ConfigBox from "@/Components/v1/ConfigBox/index.jsx"; +import TextInput from "@/Components/v1/Inputs/TextInput/index.jsx"; +import Dialog from "@/Components/v1/Dialog/index.jsx"; // Utils import { useTheme } from "@emotion/react"; diff --git a/client/src/Pages/v1/Settings/SettingsTimeZone.jsx b/client/src/Pages/v1/Settings/SettingsTimeZone.jsx index 59b2008f4..f684c871d 100644 --- a/client/src/Pages/v1/Settings/SettingsTimeZone.jsx +++ b/client/src/Pages/v1/Settings/SettingsTimeZone.jsx @@ -1,8 +1,8 @@ import Box from "@mui/material/Box"; import Stack from "@mui/material/Stack"; import Typography from "@mui/material/Typography"; -import ConfigBox from "../../../Components/ConfigBox/index.jsx"; -import Search from "../../../Components/Inputs/Search/index.jsx"; +import ConfigBox from "@/Components/v1/ConfigBox/index.jsx"; +import Search from "@/Components/v1/Inputs/Search/index.jsx"; import timezones from "../../../Utils/timezones.json"; // Utils diff --git a/client/src/Pages/v1/Settings/SettingsUI.jsx b/client/src/Pages/v1/Settings/SettingsUI.jsx index 91f4cea3c..7129e775b 100644 --- a/client/src/Pages/v1/Settings/SettingsUI.jsx +++ b/client/src/Pages/v1/Settings/SettingsUI.jsx @@ -1,8 +1,8 @@ import Box from "@mui/material/Box"; import Stack from "@mui/material/Stack"; import Typography from "@mui/material/Typography"; -import ConfigBox from "../../../Components/ConfigBox/index.jsx"; -import Select from "../../../Components/Inputs/Select/index.jsx"; +import ConfigBox from "@/Components/v1/ConfigBox/index.jsx"; +import Select from "@/Components/v1/Inputs/Select/index.jsx"; // Utils import { useTheme } from "@emotion/react"; diff --git a/client/src/Pages/v1/Settings/SettingsURL.jsx b/client/src/Pages/v1/Settings/SettingsURL.jsx index 105658d4f..c52dcf317 100644 --- a/client/src/Pages/v1/Settings/SettingsURL.jsx +++ b/client/src/Pages/v1/Settings/SettingsURL.jsx @@ -1,8 +1,8 @@ import Box from "@mui/material/Box"; import Stack from "@mui/material/Stack"; import Typography from "@mui/material/Typography"; -import ConfigBox from "../../../Components/ConfigBox/index.jsx"; -import Select from "../../../Components/Inputs/Select/index.jsx"; +import ConfigBox from "@/Components/v1/ConfigBox/index.jsx"; +import Select from "@/Components/v1/Inputs/Select/index.jsx"; // Utils import { useTheme } from "@emotion/react"; diff --git a/client/src/Pages/v1/Settings/index.jsx b/client/src/Pages/v1/Settings/index.jsx index d1788eddf..2732e331a 100644 --- a/client/src/Pages/v1/Settings/index.jsx +++ b/client/src/Pages/v1/Settings/index.jsx @@ -1,6 +1,6 @@ import Stack from "@mui/material/Stack"; import Typography from "@mui/material/Typography"; -import Breadcrumbs from "../../../Components/Breadcrumbs/index.jsx"; +import Breadcrumbs from "@/Components/v1/Breadcrumbs/index.jsx"; import SettingsTimeZone from "./SettingsTimeZone.jsx"; import SettingsUI from "./SettingsUI.jsx"; import SettingsURL from "./SettingsURL.jsx"; diff --git a/client/src/Pages/v1/StatusPage/Create/Components/Progress/index.jsx b/client/src/Pages/v1/StatusPage/Create/Components/Progress/index.jsx index c9f7d5f8f..232a12f1d 100644 --- a/client/src/Pages/v1/StatusPage/Create/Components/Progress/index.jsx +++ b/client/src/Pages/v1/StatusPage/Create/Components/Progress/index.jsx @@ -1,5 +1,5 @@ import { Button, Box } from "@mui/material"; -import ProgressUpload from "../../../../../../Components/ProgressBars/index.jsx"; +import ProgressUpload from "@/Components/v1/ProgressBars/index.jsx"; import ImageIcon from "@mui/icons-material/Image"; import { useTranslation } from "react-i18next"; import { formatBytes } from "../../../../../../Utils/fileUtils.js"; diff --git a/client/src/Pages/v1/StatusPage/Create/Components/Tabs/ConfigStack.jsx b/client/src/Pages/v1/StatusPage/Create/Components/Tabs/ConfigStack.jsx index fb5278c4e..f31c456ef 100644 --- a/client/src/Pages/v1/StatusPage/Create/Components/Tabs/ConfigStack.jsx +++ b/client/src/Pages/v1/StatusPage/Create/Components/Tabs/ConfigStack.jsx @@ -1,5 +1,5 @@ import { Stack, Typography } from "@mui/material"; -import ConfigBox from "../../../../../../Components/ConfigBox/index.jsx"; +import ConfigBox from "@/Components/v1/ConfigBox/index.jsx"; import PropTypes from "prop-types"; import { useTheme } from "@emotion/react"; diff --git a/client/src/Pages/v1/StatusPage/Create/Components/Tabs/Content.jsx b/client/src/Pages/v1/StatusPage/Create/Components/Tabs/Content.jsx index 23e713c5a..d11825e69 100644 --- a/client/src/Pages/v1/StatusPage/Create/Components/Tabs/Content.jsx +++ b/client/src/Pages/v1/StatusPage/Create/Components/Tabs/Content.jsx @@ -2,8 +2,8 @@ import { Stack, Typography } from "@mui/material"; import { TabPanel } from "@mui/lab"; import MonitorList from "../MonitorList/index.jsx"; -import Search from "../../../../../../Components/Inputs/Search/index.jsx"; -import Checkbox from "../../../../../../Components/Inputs/Checkbox/index.jsx"; +import Search from "@/Components/v1/Inputs/Search/index.jsx"; +import Checkbox from "@/Components/v1/Inputs/Checkbox/index.jsx"; // Utils import { useState } from "react"; import { useTheme } from "@emotion/react"; diff --git a/client/src/Pages/v1/StatusPage/Create/Components/Tabs/Settings.jsx b/client/src/Pages/v1/StatusPage/Create/Components/Tabs/Settings.jsx index a4612e3da..a668a9db8 100644 --- a/client/src/Pages/v1/StatusPage/Create/Components/Tabs/Settings.jsx +++ b/client/src/Pages/v1/StatusPage/Create/Components/Tabs/Settings.jsx @@ -1,12 +1,12 @@ // Components import { Stack, Typography } from "@mui/material"; import { TabPanel } from "@mui/lab"; -import ConfigBox from "../../../../../../Components/ConfigBox/index.jsx"; -import Checkbox from "../../../../../../Components/Inputs/Checkbox/index.jsx"; -import TextInput from "../../../../../../Components/Inputs/TextInput/index.jsx"; -import Search from "../../../../../../Components/Inputs/Search/index.jsx"; -import ImageUpload from "../../../../../../Components/Inputs/ImageUpload/index.jsx"; -import ColorPicker from "../../../../../../Components/Inputs/ColorPicker/index.jsx"; +import ConfigBox from "@/Components/v1/ConfigBox/index.jsx"; +import Checkbox from "@/Components/v1/Inputs/Checkbox/index.jsx"; +import TextInput from "@/Components/v1/Inputs/TextInput/index.jsx"; +import Search from "@/Components/v1/Inputs/Search/index.jsx"; +import ImageUpload from "@/Components/v1/Inputs/ImageUpload/index.jsx"; +import ColorPicker from "@/Components/v1/Inputs/ColorPicker/index.jsx"; import Progress from "../Progress/index.jsx"; // Utils diff --git a/client/src/Pages/v1/StatusPage/Create/Components/Tabs/index.jsx b/client/src/Pages/v1/StatusPage/Create/Components/Tabs/index.jsx index 971b8d85c..e8669c474 100644 --- a/client/src/Pages/v1/StatusPage/Create/Components/Tabs/index.jsx +++ b/client/src/Pages/v1/StatusPage/Create/Components/Tabs/index.jsx @@ -6,7 +6,7 @@ import Content from "./Content.jsx"; // Utils import PropTypes from "prop-types"; -import CustomTabList from "../../../../../../Components/Tab/index.jsx"; +import CustomTabList from "@/Components/v1/Tab/index.jsx"; const Tabs = ({ isCreate, diff --git a/client/src/Pages/v1/StatusPage/Create/index.jsx b/client/src/Pages/v1/StatusPage/Create/index.jsx index 674a158ac..d9fafed70 100644 --- a/client/src/Pages/v1/StatusPage/Create/index.jsx +++ b/client/src/Pages/v1/StatusPage/Create/index.jsx @@ -1,10 +1,10 @@ // Components import { Stack, Button, Typography } from "@mui/material"; import Tabs from "./Components/Tabs/index.jsx"; -import GenericFallback from "../../../../Components/GenericFallback/index.jsx"; +import GenericFallback from "@/Components/v1/GenericFallback/index.jsx"; import SkeletonLayout from "./Components/Skeleton/index.jsx"; -import Dialog from "../../../../Components/Dialog/index.jsx"; -import Breadcrumbs from "../../../../Components/Breadcrumbs/index.jsx"; +import Dialog from "@/Components/v1/Dialog/index.jsx"; +import Breadcrumbs from "@/Components/v1/Breadcrumbs/index.jsx"; //Utils import { useTheme } from "@emotion/react"; import { useState, useEffect, useRef, useCallback } from "react"; diff --git a/client/src/Pages/v1/StatusPage/Status/Components/ControlsHeader/index.jsx b/client/src/Pages/v1/StatusPage/Status/Components/ControlsHeader/index.jsx index fa5ed71f1..c56aa40cc 100644 --- a/client/src/Pages/v1/StatusPage/Status/Components/ControlsHeader/index.jsx +++ b/client/src/Pages/v1/StatusPage/Status/Components/ControlsHeader/index.jsx @@ -1,8 +1,8 @@ // Components import { Box, Stack, Typography, Button } from "@mui/material"; -import Image from "../../../../../../Components/Image/index.jsx"; +import Image from "@/Components/v1/Image/index.jsx"; import SettingsIcon from "../../../../../../assets/icons/settings-bold.svg?react"; -import ThemeSwitch from "../../../../../../Components/ThemeSwitch/index.jsx"; +import ThemeSwitch from "@/Components/v1/ThemeSwitch/index.jsx"; import ArrowOutwardIcon from "@mui/icons-material/ArrowOutward"; //Utils import { useTheme } from "@mui/material/styles"; diff --git a/client/src/Pages/v1/StatusPage/Status/Components/MonitorsList/index.jsx b/client/src/Pages/v1/StatusPage/Status/Components/MonitorsList/index.jsx index fd04dae66..59ad4d522 100644 --- a/client/src/Pages/v1/StatusPage/Status/Components/MonitorsList/index.jsx +++ b/client/src/Pages/v1/StatusPage/Status/Components/MonitorsList/index.jsx @@ -1,8 +1,8 @@ // Components import { Stack, Box } from "@mui/material"; -import Host from "../../../../../../Components/Host/index.jsx"; -import StatusPageBarChart from "../../../../../../Components/Charts/StatusPageBarChart/index.jsx"; -import { StatusLabel } from "../../../../../../Components/Label/index.jsx"; +import Host from "@/Components/v1/Host/index.jsx"; +import StatusPageBarChart from "@/Components/v1/Charts/StatusPageBarChart/index.jsx"; +import { StatusLabel } from "@/Components/v1/Label/index.jsx"; //Utils import { useTheme } from "@mui/material/styles"; diff --git a/client/src/Pages/v1/StatusPage/Status/index.jsx b/client/src/Pages/v1/StatusPage/Status/index.jsx index 48fc44fc9..e700bbbd9 100644 --- a/client/src/Pages/v1/StatusPage/Status/index.jsx +++ b/client/src/Pages/v1/StatusPage/Status/index.jsx @@ -1,13 +1,13 @@ // Components import { Typography, Stack } from "@mui/material"; -import GenericFallback from "../../../../Components/GenericFallback/index.jsx"; +import GenericFallback from "@/Components/v1/GenericFallback/index.jsx"; import AdminLink from "./Components/AdminLink/index.jsx"; import ControlsHeader from "./Components/ControlsHeader/index.jsx"; import SkeletonLayout from "./Components/Skeleton/index.jsx"; import StatusBar from "./Components/StatusBar/index.jsx"; import MonitorsList from "./Components/MonitorsList/index.jsx"; -import Breadcrumbs from "../../../../Components/Breadcrumbs/index.jsx"; -import TextLink from "../../../../Components/TextLink/index.jsx"; +import Breadcrumbs from "@/Components/v1/Breadcrumbs/index.jsx"; +import TextLink from "@/Components/v1/TextLink/index.jsx"; // Utils import { useStatusPageFetch } from "./Hooks/useStatusPageFetch.jsx"; diff --git a/client/src/Pages/v1/StatusPage/StatusPages/Components/StatusPagesTable/index.jsx b/client/src/Pages/v1/StatusPage/StatusPages/Components/StatusPagesTable/index.jsx index d8d68fb2e..1afe25409 100644 --- a/client/src/Pages/v1/StatusPage/StatusPages/Components/StatusPagesTable/index.jsx +++ b/client/src/Pages/v1/StatusPage/StatusPages/Components/StatusPagesTable/index.jsx @@ -1,7 +1,7 @@ -import DataTable from "../../../../../../Components/Table/index.jsx"; +import DataTable from "@/Components/v1/Table/index.jsx"; import { useTheme } from "@emotion/react"; import { useNavigate } from "react-router-dom"; -import { StatusLabel } from "../../../../../../Components/Label/index.jsx"; +import { StatusLabel } from "@/Components/v1/Label/index.jsx"; import ArrowOutwardIcon from "@mui/icons-material/ArrowOutward"; import { Stack, Typography } from "@mui/material"; import { useTranslation } from "react-i18next"; diff --git a/client/src/Pages/v1/StatusPage/StatusPages/index.jsx b/client/src/Pages/v1/StatusPage/StatusPages/index.jsx index 8e0c89ce3..3c60fe950 100644 --- a/client/src/Pages/v1/StatusPage/StatusPages/index.jsx +++ b/client/src/Pages/v1/StatusPage/StatusPages/index.jsx @@ -1,9 +1,9 @@ // Components import { Stack } from "@mui/material"; -import Breadcrumbs from "../../../../Components/Breadcrumbs/index.jsx"; -import MonitorCreateHeader from "../../../../Components/MonitorCreateHeader/index.jsx"; +import Breadcrumbs from "@/Components/v1/Breadcrumbs/index.jsx"; +import MonitorCreateHeader from "@/Components/v1/MonitorCreateHeader/index.jsx"; import StatusPagesTable from "./Components/StatusPagesTable/index.jsx"; -import PageStateWrapper from "../../../../Components/PageStateWrapper/index.jsx"; +import PageStateWrapper from "@/Components/v1/PageStateWrapper/index.jsx"; // Utils import { useTheme } from "@emotion/react"; import { useStatusPagesFetch } from "./Hooks/useStatusPagesFetch.jsx"; diff --git a/client/src/Pages/v1/Uptime/BulkImport/index.jsx b/client/src/Pages/v1/Uptime/BulkImport/index.jsx index 412ba0f97..9f3efcd67 100644 --- a/client/src/Pages/v1/Uptime/BulkImport/index.jsx +++ b/client/src/Pages/v1/Uptime/BulkImport/index.jsx @@ -6,8 +6,8 @@ import { Box, Stack, Typography, Button, Link } from "@mui/material"; //Components import { createToast } from "../../../../Utils/toastUtils.jsx"; -import Breadcrumbs from "../../../../Components/Breadcrumbs/index.jsx"; -import ConfigBox from "../../../../Components/ConfigBox/index.jsx"; +import Breadcrumbs from "@/Components/v1/Breadcrumbs/index.jsx"; +import ConfigBox from "@/Components/v1/ConfigBox/index.jsx"; import UploadFile from "./Upload.jsx"; import { useSelector } from "react-redux"; import { useNavigate } from "react-router-dom"; diff --git a/client/src/Pages/v1/Uptime/Create/index.jsx b/client/src/Pages/v1/Uptime/Create/index.jsx index 6bb2a3b33..202f64d85 100644 --- a/client/src/Pages/v1/Uptime/Create/index.jsx +++ b/client/src/Pages/v1/Uptime/Create/index.jsx @@ -9,16 +9,16 @@ import { Tooltip, Typography, } from "@mui/material"; -import Breadcrumbs from "../../../../Components/Breadcrumbs/index.jsx"; -import TextInput from "../../../../Components/Inputs/TextInput/index.jsx"; -import { HttpAdornment } from "../../../../Components/Inputs/TextInput/Adornments/index.jsx"; -import Radio from "../../../../Components/Inputs/Radio/index.jsx"; -import Select from "../../../../Components/Inputs/Select/index.jsx"; -import ConfigBox from "../../../../Components/ConfigBox/index.jsx"; -import NotificationsConfig from "../../../../Components/NotificationConfig/index.jsx"; -import Checkbox from "../../../../Components/Inputs/Checkbox/index.jsx"; -import Dialog from "../../../../Components/Dialog/index.jsx"; -import PulseDot from "../../../../Components/Animated/PulseDot.jsx"; +import Breadcrumbs from "@/Components/v1/Breadcrumbs/index.jsx"; +import TextInput from "@/Components/v1/Inputs/TextInput/index.jsx"; +import { HttpAdornment } from "@/Components/v1/Inputs/TextInput/Adornments/index.jsx"; +import Radio from "@/Components/v1/Inputs/Radio/index.jsx"; +import Select from "@/Components/v1/Inputs/Select/index.jsx"; +import ConfigBox from "@/Components/v1/ConfigBox/index.jsx"; +import NotificationsConfig from "@/Components/v1/NotificationConfig/index.jsx"; +import Checkbox from "@/Components/v1/Inputs/Checkbox/index.jsx"; +import Dialog from "@/Components/v1/Dialog/index.jsx"; +import PulseDot from "@/Components/v1/Animated/PulseDot.jsx"; import SkeletonLayout from "./skeleton.jsx"; // Utils diff --git a/client/src/Pages/v1/Uptime/Details/Components/ChartBoxes/index.jsx b/client/src/Pages/v1/Uptime/Details/Components/ChartBoxes/index.jsx index c6b76c289..45f35a42e 100644 --- a/client/src/Pages/v1/Uptime/Details/Components/ChartBoxes/index.jsx +++ b/client/src/Pages/v1/Uptime/Details/Components/ChartBoxes/index.jsx @@ -1,6 +1,6 @@ // Components import { Stack, Typography, Box } from "@mui/material"; -import ChartBox from "../../../../../../Components/Charts/ChartBox/index.jsx"; +import ChartBox from "@/Components/v1/Charts/ChartBox/index.jsx"; import UptimeIcon from "../../../../../../assets/icons/uptime-icon.svg?react"; import IncidentsIcon from "../../../../../../assets/icons/incidents.svg?react"; import AverageResponseIcon from "../../../../../../assets/icons/average-response-icon.svg?react"; diff --git a/client/src/Pages/v1/Uptime/Details/Components/Charts/ResponseTimeChart.jsx b/client/src/Pages/v1/Uptime/Details/Components/Charts/ResponseTimeChart.jsx index f306dfd76..daa7b4f53 100644 --- a/client/src/Pages/v1/Uptime/Details/Components/Charts/ResponseTimeChart.jsx +++ b/client/src/Pages/v1/Uptime/Details/Components/Charts/ResponseTimeChart.jsx @@ -1,5 +1,5 @@ -import ChartBox from "../../../../../../Components/Charts/ChartBox/index.jsx"; -import MonitorDetailsAreaChart from "../../../../../../Components/Charts/MonitorDetailsAreaChart/index.jsx"; +import ChartBox from "@/Components/v1/Charts/ChartBox/index.jsx"; +import MonitorDetailsAreaChart from "@/Components/v1/Charts/MonitorDetailsAreaChart/index.jsx"; import ResponseTimeIcon from "../../../../../../assets/icons/response-time-icon.svg?react"; import SkeletonLayout from "./ResponseTimeChartSkeleton.jsx"; import PropTypes from "prop-types"; diff --git a/client/src/Pages/v1/Uptime/Details/Components/ResponseTable/index.jsx b/client/src/Pages/v1/Uptime/Details/Components/ResponseTable/index.jsx index cc5745a26..948489ac1 100644 --- a/client/src/Pages/v1/Uptime/Details/Components/ResponseTable/index.jsx +++ b/client/src/Pages/v1/Uptime/Details/Components/ResponseTable/index.jsx @@ -1,9 +1,9 @@ -import ChartBox from "../../../../../../Components/Charts/ChartBox/index.jsx"; +import ChartBox from "@/Components/v1/Charts/ChartBox/index.jsx"; import PropTypes from "prop-types"; import HistoryIcon from "../../../../../../assets/icons/history-icon.svg?react"; -import Table from "../../../../../../Components/Table/index.jsx"; -import TablePagination from "../../../../../../Components/Table/TablePagination/index.jsx"; -import { StatusLabel } from "../../../../../../Components/Label/index.jsx"; +import Table from "@/Components/v1/Table/index.jsx"; +import TablePagination from "@/Components/v1/Table/TablePagination/index.jsx"; +import { StatusLabel } from "@/Components/v1/Label/index.jsx"; import { useTranslation } from "react-i18next"; import { formatDateWithTz } from "../../../../../../Utils/timeUtils.js"; import SkeletonLayout from "./skeleton.jsx"; diff --git a/client/src/Pages/v1/Uptime/Details/Components/UptimeStatusBoxes/index.jsx b/client/src/Pages/v1/Uptime/Details/Components/UptimeStatusBoxes/index.jsx index f0988466c..a7b971839 100644 --- a/client/src/Pages/v1/Uptime/Details/Components/UptimeStatusBoxes/index.jsx +++ b/client/src/Pages/v1/Uptime/Details/Components/UptimeStatusBoxes/index.jsx @@ -1,5 +1,5 @@ -import StatusBoxes from "../../../../../../Components/StatusBoxes/index.jsx"; -import StatBox from "../../../../../../Components/StatBox/index.jsx"; +import StatusBoxes from "@/Components/v1/StatusBoxes/index.jsx"; +import StatBox from "@/Components/v1/StatBox/index.jsx"; import PropTypes from "prop-types"; import { getHumanReadableDuration } from "../../../../../../Utils/timeUtils.js"; diff --git a/client/src/Pages/v1/Uptime/Details/index.jsx b/client/src/Pages/v1/Uptime/Details/index.jsx index 4e773dc2b..f5e283495 100644 --- a/client/src/Pages/v1/Uptime/Details/index.jsx +++ b/client/src/Pages/v1/Uptime/Details/index.jsx @@ -1,12 +1,12 @@ // Components -import Breadcrumbs from "../../../../Components/Breadcrumbs/index.jsx"; -import MonitorDetailsControlHeader from "../../../../Components/MonitorDetailsControlHeader/index.jsx"; -import MonitorTimeFrameHeader from "../../../../Components/MonitorTimeFrameHeader/index.jsx"; +import Breadcrumbs from "@/Components/v1/Breadcrumbs/index.jsx"; +import MonitorDetailsControlHeader from "@/Components/v1/MonitorDetailsControlHeader/index.jsx"; +import MonitorTimeFrameHeader from "@/Components/v1/MonitorTimeFrameHeader/index.jsx"; import ChartBoxes from "./Components/ChartBoxes/index.jsx"; import ResponseTimeChart from "./Components/Charts/ResponseTimeChart.jsx"; import ResponseTable from "./Components/ResponseTable/index.jsx"; import UptimeStatusBoxes from "./Components/UptimeStatusBoxes/index.jsx"; -import GenericFallback from "../../../../Components/GenericFallback/index.jsx"; +import GenericFallback from "@/Components/v1/GenericFallback/index.jsx"; import Stack from "@mui/material/Stack"; import Typography from "@mui/material/Typography"; diff --git a/client/src/Pages/v1/Uptime/Monitors/Components/Filter/index.jsx b/client/src/Pages/v1/Uptime/Monitors/Components/Filter/index.jsx index 85c4a8540..b9a6824c1 100644 --- a/client/src/Pages/v1/Uptime/Monitors/Components/Filter/index.jsx +++ b/client/src/Pages/v1/Uptime/Monitors/Components/Filter/index.jsx @@ -1,6 +1,6 @@ import { useTheme } from "@emotion/react"; import PropTypes from "prop-types"; -import FilterHeader from "../../../../../../Components/FilterHeader/index.jsx"; +import FilterHeader from "@/Components/v1/FilterHeader/index.jsx"; import { useMemo } from "react"; import { Box, Button } from "@mui/material"; import ClearIcon from "@mui/icons-material/Clear"; diff --git a/client/src/Pages/v1/Uptime/Monitors/Components/SearchComponent/index.jsx b/client/src/Pages/v1/Uptime/Monitors/Components/SearchComponent/index.jsx index 3339102a2..64c546a8a 100644 --- a/client/src/Pages/v1/Uptime/Monitors/Components/SearchComponent/index.jsx +++ b/client/src/Pages/v1/Uptime/Monitors/Components/SearchComponent/index.jsx @@ -1,5 +1,5 @@ import { useState } from "react"; -import Search from "../../../../../../Components/Inputs/Search/index.jsx"; +import Search from "@/Components/v1/Inputs/Search/index.jsx"; import { Box } from "@mui/material"; import useDebounce from "../../Hooks/useDebounce.jsx"; import { useEffect, useRef } from "react"; diff --git a/client/src/Pages/v1/Uptime/Monitors/Components/UptimeDataTable/index.jsx b/client/src/Pages/v1/Uptime/Monitors/Components/UptimeDataTable/index.jsx index 575647eab..9cf2d247d 100644 --- a/client/src/Pages/v1/Uptime/Monitors/Components/UptimeDataTable/index.jsx +++ b/client/src/Pages/v1/Uptime/Monitors/Components/UptimeDataTable/index.jsx @@ -1,15 +1,15 @@ // Components import { Box, Stack } from "@mui/material"; -import DataTable from "../../../../../../Components/Table/index.jsx"; +import DataTable from "@/Components/v1/Table/index.jsx"; import ArrowDownwardRoundedIcon from "@mui/icons-material/ArrowDownwardRounded"; import ArrowUpwardRoundedIcon from "@mui/icons-material/ArrowUpwardRounded"; -import Host from "../../../../../../Components/Host/index.jsx"; -import { StatusLabel } from "../../../../../../Components/Label/index.jsx"; -import BarChart from "../../../../../../Components/Charts/BarChart/index.jsx"; -import ActionsMenu from "../../../../../../Components/ActionsMenu/index.jsx"; +import Host from "@/Components/v1/Host/index.jsx"; +import { StatusLabel } from "@/Components/v1/Label/index.jsx"; +import BarChart from "@/Components/v1/Charts/BarChart/index.jsx"; +import ActionsMenu from "@/Components/v1/ActionsMenu/index.jsx"; import LoadingSpinner from "../LoadingSpinner/index.jsx"; -import TableSkeleton from "../../../../../../Components/Table/skeleton.jsx"; +import TableSkeleton from "@/Components/v1/Table/skeleton.jsx"; // Utils import { useTheme } from "@emotion/react"; diff --git a/client/src/Pages/v1/Uptime/Monitors/index.jsx b/client/src/Pages/v1/Uptime/Monitors/index.jsx index dae0bc2d5..cacfc23b4 100644 --- a/client/src/Pages/v1/Uptime/Monitors/index.jsx +++ b/client/src/Pages/v1/Uptime/Monitors/index.jsx @@ -4,17 +4,17 @@ // 2a.List of monitors must have the total number of monitors that match. // Components -import Breadcrumbs from "../../../../Components/Breadcrumbs/index.jsx"; +import Breadcrumbs from "@/Components/v1/Breadcrumbs/index.jsx"; import Greeting from "../../../../Utils/greeting.jsx"; import StatusBoxes from "./Components/StatusBoxes/index.jsx"; import UptimeDataTable from "./Components/UptimeDataTable/index.jsx"; -import Pagination from "../../../../Components/Table/TablePagination/index.jsx"; -import CreateMonitorHeader from "../../../../Components/MonitorCreateHeader/index.jsx"; +import Pagination from "@/Components/v1/Table/TablePagination/index.jsx"; +import CreateMonitorHeader from "@/Components/v1/MonitorCreateHeader/index.jsx"; import SearchComponent from "./Components/SearchComponent/index.jsx"; import Filter from "./Components/Filter/index.jsx"; -import PageStateWrapper from "../../../../Components/PageStateWrapper/index.jsx"; +import PageStateWrapper from "@/Components/v1/PageStateWrapper/index.jsx"; -import MonitorCountHeader from "../../../../Components/MonitorCountHeader/index.jsx"; +import MonitorCountHeader from "@/Components/v1/MonitorCountHeader/index.jsx"; // MUI Components import { Stack, Box, Button } from "@mui/material"; diff --git a/client/src/Pages/v2/Auth/Login.tsx b/client/src/Pages/v2/Auth/Login.tsx index 58eec01dc..fd4a08e93 100644 --- a/client/src/Pages/v2/Auth/Login.tsx +++ b/client/src/Pages/v2/Auth/Login.tsx @@ -4,7 +4,7 @@ import { useTranslation } from "react-i18next"; import { useForm, Controller } from "react-hook-form"; import { useTheme } from "@mui/material/styles"; -import { TextInput } from "@/Components/Inputs/TextInput/indexV2.tsx"; +import { TextInput } from "@/Components/v2/Inputs/TextInput"; import Button from "@mui/material/Button"; import Stack from "@mui/material/Stack"; import { usePost } from "@/Hooks/v2/UseApi"; diff --git a/client/src/Pages/v2/Auth/Register.tsx b/client/src/Pages/v2/Auth/Register.tsx index a9043a2c9..2a02c401f 100644 --- a/client/src/Pages/v2/Auth/Register.tsx +++ b/client/src/Pages/v2/Auth/Register.tsx @@ -5,7 +5,7 @@ import { useForm, Controller } from "react-hook-form"; import { useTheme } from "@mui/material/styles"; import Typography from "@mui/material/Typography"; -import { TextInput } from "@/Components/Inputs/TextInput/indexV2.tsx"; +import { TextInput } from "@/Components/v2/Inputs/TextInput"; import Button from "@mui/material/Button"; import Stack from "@mui/material/Stack"; import { usePost } from "@/Hooks/v2/UseApi"; diff --git a/client/src/Routes/index.jsx b/client/src/Routes/index.jsx index 2e4fd9c50..90dcaa3f5 100644 --- a/client/src/Routes/index.jsx +++ b/client/src/Routes/index.jsx @@ -1,7 +1,7 @@ import { useSelector } from "react-redux"; import { lightTheme, darkTheme } from "@/Utils/Theme/v2/theme"; import { Navigate, Route, Routes as LibRoutes } from "react-router"; -import HomeLayout from "../Components/Layouts/HomeLayout"; +import HomeLayout from "@/Components/v1/Layouts/HomeLayout"; import NotFound from "../Pages/v1/NotFound"; // Auth import AuthLogin from "../Pages/v1/Auth/Login"; @@ -47,10 +47,10 @@ import Settings from "../Pages/v1/Settings"; import Maintenance from "../Pages/v1/Maintenance"; -import ProtectedRoute from "../Components/ProtectedRoute"; -import RoleProtectedRoute from "../Components/RoleProtectedRoute"; +import ProtectedRoute from "../Components/v1/ProtectedRoute"; +import RoleProtectedRoute from "../Components/v1/RoleProtectedRoute"; import CreateNewMaintenanceWindow from "../Pages/v1/Maintenance/CreateMaintenance"; -import withAdminCheck from "../Components/HOC/withAdminCheck"; +import withAdminCheck from "@/Components/v1/HOC/withAdminCheck"; import BulkImport from "../Pages/v1/Uptime/BulkImport"; import Logs from "../Pages/v1/Logs"; diff --git a/client/src/Utils/toastUtils.jsx b/client/src/Utils/toastUtils.jsx index 21cf13e2b..41b2d5482 100644 --- a/client/src/Utils/toastUtils.jsx +++ b/client/src/Utils/toastUtils.jsx @@ -1,6 +1,6 @@ import PropTypes from "prop-types"; import { toast, Slide } from "react-toastify"; -import Toast from "../Components/Toast"; +import Toast from "../Components/v1/Toast"; /** * @param {object} props * @param {'info' | 'error' | 'warning'} - The variant of the alert (e.g., "info", "error"). diff --git a/client/src/main.jsx b/client/src/main.jsx index 01ecefcc6..dd42587af 100644 --- a/client/src/main.jsx +++ b/client/src/main.jsx @@ -8,7 +8,7 @@ import { PersistGate } from "redux-persist/integration/react"; import NetworkServiceProvider from "./Utils/NetworkServiceProvider.jsx"; import { networkService } from "./Utils/NetworkService"; export { networkService }; -import I18nLoader from "./Components/I18nLoader"; +import I18nLoader from "./Components/v1/I18nLoader"; ReactDOM.createRoot(document.getElementById("root")).render( From b57ed47592153337df4fc81ea64d6a43cc935606 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Sat, 27 Sep 2025 14:57:55 -0700 Subject: [PATCH 052/101] remove mode prop --- client/src/Routes/v2router.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/Routes/v2router.tsx b/client/src/Routes/v2router.tsx index d450854f9..941550026 100644 --- a/client/src/Routes/v2router.tsx +++ b/client/src/Routes/v2router.tsx @@ -22,7 +22,7 @@ const V2Routes = ({ mode = "light" }) => { /> } + element={} > Date: Sat, 27 Sep 2025 15:47:56 -0700 Subject: [PATCH 053/101] Add collapse button and logo --- client/src/Components/v2/Arrows/ArrowLeft.tsx | 36 +++++++++++ .../src/Components/v2/Arrows/ArrowRight.tsx | 28 +++++++++ .../src/Components/v2/Layouts/RootLayout.tsx | 35 +---------- .../v2/Layouts/Sidebar/CollapseButton.tsx | 50 ++++++++++++++++ .../Components/v2/Layouts/Sidebar/Logo.tsx | 59 +++++++++++++++++++ .../Components/v2/Layouts/Sidebar/index.tsx | 40 +++++++++++++ client/src/Features/UI/uiSlice.js | 5 ++ client/src/Utils/Theme/v2/palette.ts | 10 ++++ client/src/Utils/Theme/v2/theme.ts | 3 + client/src/types/env.d.ts | 8 +++ client/src/types/mui.d.ts | 3 + 11 files changed, 243 insertions(+), 34 deletions(-) create mode 100644 client/src/Components/v2/Arrows/ArrowLeft.tsx create mode 100644 client/src/Components/v2/Arrows/ArrowRight.tsx create mode 100644 client/src/Components/v2/Layouts/Sidebar/CollapseButton.tsx create mode 100644 client/src/Components/v2/Layouts/Sidebar/Logo.tsx create mode 100644 client/src/Components/v2/Layouts/Sidebar/index.tsx diff --git a/client/src/Components/v2/Arrows/ArrowLeft.tsx b/client/src/Components/v2/Arrows/ArrowLeft.tsx new file mode 100644 index 000000000..01d1342c8 --- /dev/null +++ b/client/src/Components/v2/Arrows/ArrowLeft.tsx @@ -0,0 +1,36 @@ +import LeftArrow from "@/assets/icons/left-arrow.svg?react"; +import LeftArrowDouble from "@/assets/icons/left-arrow-double.svg?react"; +import LeftArrowLong from "@/assets/icons/left-arrow-long.svg?react"; + +export const ArrowLeft = ({ + type, + color = "#667085", + ...props +}: { + type?: string; + color?: string | undefined; + [key: string]: any; +}) => { + if (type === "double") { + return ( + + ); + } else if (type === "long") { + return ( + + ); + } else { + return ( + + ); + } +}; diff --git a/client/src/Components/v2/Arrows/ArrowRight.tsx b/client/src/Components/v2/Arrows/ArrowRight.tsx new file mode 100644 index 000000000..c73165f16 --- /dev/null +++ b/client/src/Components/v2/Arrows/ArrowRight.tsx @@ -0,0 +1,28 @@ +import RightArrow from "@/assets/icons/right-arrow.svg?react"; +import RightArrowDouble from "@/assets/icons/right-arrow-double.svg?react"; + +export const ArrowRight = ({ + type, + color = "#667085", + ...props +}: { + type?: string; + color?: string | undefined; + [key: string]: any; +}) => { + if (type === "double") { + return ( + + ); + } else { + return ( + + ); + } +}; diff --git a/client/src/Components/v2/Layouts/RootLayout.tsx b/client/src/Components/v2/Layouts/RootLayout.tsx index be2f42e30..dbc6f2e5e 100644 --- a/client/src/Components/v2/Layouts/RootLayout.tsx +++ b/client/src/Components/v2/Layouts/RootLayout.tsx @@ -1,39 +1,6 @@ -import { useState, useEffect } from "react"; -import useMediaQuery from "@mui/material/useMediaQuery"; -import { useTheme } from "@mui/material/styles"; - import { Outlet } from "react-router"; import Stack from "@mui/material/Stack"; -import Box from "@mui/material/Box"; - -const COLLAPSED_WIDTH = 50; -const EXPANDED_WIDTH = 250; - -const SideBar = () => { - const theme = useTheme(); - const isSmall = useMediaQuery(theme.breakpoints.down("md")); - - const [collapsed, setCollapsed] = useState(false); - useEffect(() => { - setCollapsed(isSmall); - }, [isSmall]); - return ( - - setCollapsed(!collapsed)} - > - Sidebar Content - - - ); -}; +import { SideBar } from "@/Components/v2/Layouts/Sidebar"; const RootLayout = () => { return ( diff --git a/client/src/Components/v2/Layouts/Sidebar/CollapseButton.tsx b/client/src/Components/v2/Layouts/Sidebar/CollapseButton.tsx new file mode 100644 index 000000000..564c41591 --- /dev/null +++ b/client/src/Components/v2/Layouts/Sidebar/CollapseButton.tsx @@ -0,0 +1,50 @@ +import IconButton from "@mui/material/IconButton"; +import { ArrowRight } from "@/Components/v2/Arrows/ArrowRight"; +import { ArrowLeft } from "@/Components/v2/Arrows/ArrowLeft"; +import { useTheme } from "@mui/material/styles"; +import { useDispatch } from "react-redux"; +import { toggleSidebar } from "../../../../Features/UI/uiSlice.js"; + +export const CollapseButton = ({ collapsed }: { collapsed: boolean }) => { + const theme = useTheme(); + const dispatch = useDispatch(); + const arrowIcon = collapsed ? ( + + ) : ( + + ); + + return ( + { + dispatch(toggleSidebar()); + }} + > + {arrowIcon} + + ); +}; diff --git a/client/src/Components/v2/Layouts/Sidebar/Logo.tsx b/client/src/Components/v2/Layouts/Sidebar/Logo.tsx new file mode 100644 index 000000000..6d190b99d --- /dev/null +++ b/client/src/Components/v2/Layouts/Sidebar/Logo.tsx @@ -0,0 +1,59 @@ +import Stack from "@mui/material/Stack"; +import Box from "@mui/material/Box"; +import Typography from "@mui/material/Typography"; +import { useTheme } from "@mui/material/styles"; +import { useNavigate } from "react-router"; +import { useTranslation } from "react-i18next"; + +export const Logo = ({ collapsed }: { collapsed: boolean }) => { + const { t } = useTranslation(); + const theme = useTheme(); + const navigate = useNavigate(); + + return ( + navigate("/")} + sx={{ cursor: "pointer" }} + > + + C + + + {" "} + + {t("common.appName")} + + + + ); +}; diff --git a/client/src/Components/v2/Layouts/Sidebar/index.tsx b/client/src/Components/v2/Layouts/Sidebar/index.tsx new file mode 100644 index 000000000..43975adfc --- /dev/null +++ b/client/src/Components/v2/Layouts/Sidebar/index.tsx @@ -0,0 +1,40 @@ +import { useEffect } from "react"; +import { useSelector, useDispatch } from "react-redux"; +import { setCollapsed } from "@/Features/UI/uiSlice"; +import useMediaQuery from "@mui/material/useMediaQuery"; +import { useTheme } from "@mui/material/styles"; + +import { CollapseButton } from "@/Components/v2/Layouts/Sidebar/CollapseButton"; +import Stack from "@mui/material/Stack"; +import { Logo } from "@/Components/v2/Layouts/Sidebar/Logo"; + +export const COLLAPSED_WIDTH = 64; +export const EXPANDED_WIDTH = 250; + +export const SideBar = () => { + const theme = useTheme(); + const isSmall = useMediaQuery(theme.breakpoints.down("md")); + const dispatch = useDispatch(); + const collapsed = useSelector((state: any) => state.ui.sidebar.collapsed); + + useEffect(() => { + dispatch(setCollapsed({ collapsed: isSmall })); + }, [isSmall]); + + return ( + + + + + ); +}; diff --git a/client/src/Features/UI/uiSlice.js b/client/src/Features/UI/uiSlice.js index 45ba267b0..63f7ae90e 100644 --- a/client/src/Features/UI/uiSlice.js +++ b/client/src/Features/UI/uiSlice.js @@ -47,6 +47,10 @@ const uiSlice = createSlice({ toggleSidebar: (state) => { state.sidebar.collapsed = !state.sidebar.collapsed; }, + setCollapsed: (state, action) => { + const { collapsed } = action.payload; + state.sidebar.collapsed = collapsed; + }, setMode: (state, action) => { state.mode = action.payload; }, @@ -73,6 +77,7 @@ export default uiSlice.reducer; export const { setRowsPerPage, toggleSidebar, + setCollapsed, setMode, setShowURL, setGreeting, diff --git a/client/src/Utils/Theme/v2/palette.ts b/client/src/Utils/Theme/v2/palette.ts index 2875f750d..2e41a159c 100644 --- a/client/src/Utils/Theme/v2/palette.ts +++ b/client/src/Utils/Theme/v2/palette.ts @@ -54,12 +54,17 @@ export const lightPalette = { main: colors.offWhite, contrastText: colors.blueGray800, contrastTextSecondary: colors.blueGray600, + lowContrast: colors.gray250, }, secondary: { main: colors.gray200, light: colors.lightBlueWave, contrastText: colors.blueGray600, }, + tertiary: { + main: colors.gray100, + contrastText: colors.blueGray800, + }, }; export const darkPalette = { @@ -73,10 +78,15 @@ export const darkPalette = { main: colors.offBlack, contrastText: colors.blueGray50, contrastTextSecondary: colors.gray200, + lowContrast: colors.blueGray600, }, secondary: { main: "#313131", light: colors.lightBlueWave, contrastText: colors.gray200, }, + tertiary: { + main: colors.blueGray800, + contrastText: colors.gray100, + }, }; diff --git a/client/src/Utils/Theme/v2/theme.ts b/client/src/Utils/Theme/v2/theme.ts index 55d2b9bb9..77bab3e70 100644 --- a/client/src/Utils/Theme/v2/theme.ts +++ b/client/src/Utils/Theme/v2/theme.ts @@ -67,6 +67,9 @@ export const theme = (mode: string, palette: any) => }, }, }, + shape: { + borderRadius: 2, + }, }); export const lightTheme = createTheme(theme("light", lightPalette)); diff --git a/client/src/types/env.d.ts b/client/src/types/env.d.ts index e2bce23dd..74a2a29e7 100644 --- a/client/src/types/env.d.ts +++ b/client/src/types/env.d.ts @@ -7,3 +7,11 @@ interface ImportMetaEnv { interface ImportMeta { readonly env: ImportMetaEnv; } + +declare module "*.svg?react" { + import * as React from "react"; + const ReactComponent: React.FunctionComponent< + React.SVGProps & { title?: string } + >; + export default ReactComponent; +} diff --git a/client/src/types/mui.d.ts b/client/src/types/mui.d.ts index 851ffea70..5f5c1c38b 100644 --- a/client/src/types/mui.d.ts +++ b/client/src/types/mui.d.ts @@ -3,13 +3,16 @@ import "@mui/material/Button"; declare module "@mui/material/styles" { interface Palette { accent: Palette["primary"]; + tertiary: Palette["primary"]; } interface PaletteOptions { accent?: PaletteOptions["primary"]; + tertiary?: PaletteOptions["primary"]; } interface PaletteColor { contrastTextSecondary?: string; + lowContrast?: string; } } From 7f2318da01a21f47216f666ebdd8192b48fe6b2a Mon Sep 17 00:00:00 2001 From: ariretiarno Date: Tue, 30 Sep 2025 11:28:22 +0700 Subject: [PATCH 054/101] helm chart --- .../templates/client-deployment.yaml | 8 ++-- .../checkmate/templates/client-ingress.yaml | 9 ++++- .../checkmate/templates/client-service.yaml | 4 +- .../helm/checkmate/templates/mongodb-pvc.yaml | 10 ----- .../checkmate/templates/mongodb-service.yaml | 4 +- ...loyment.yaml => mongodb-statefulsets.yaml} | 21 ++++++---- .../checkmate/templates/redis-deployment.yaml | 26 ------------- .../helm/checkmate/templates/redis-pvc.yaml | 10 ----- .../checkmate/templates/redis-service.yaml | 6 ++- .../templates/redis-statefulsets.yaml | 33 ++++++++++++++++ .../templates/server-deployment.yaml | 6 +-- .../checkmate/templates/server-ingress.yaml | 21 ++++++---- .../checkmate/templates/server-service.yaml | 4 +- charts/helm/checkmate/values.yaml | 38 +++++++++++-------- 14 files changed, 107 insertions(+), 93 deletions(-) delete mode 100644 charts/helm/checkmate/templates/mongodb-pvc.yaml rename charts/helm/checkmate/templates/{mongodb-deployment.yaml => mongodb-statefulsets.yaml} (54%) delete mode 100644 charts/helm/checkmate/templates/redis-deployment.yaml delete mode 100644 charts/helm/checkmate/templates/redis-pvc.yaml create mode 100644 charts/helm/checkmate/templates/redis-statefulsets.yaml diff --git a/charts/helm/checkmate/templates/client-deployment.yaml b/charts/helm/checkmate/templates/client-deployment.yaml index 6c1f70fd7..b6e13fc7d 100644 --- a/charts/helm/checkmate/templates/client-deployment.yaml +++ b/charts/helm/checkmate/templates/client-deployment.yaml @@ -1,16 +1,16 @@ apiVersion: apps/v1 kind: Deployment metadata: - name: client + name: checkmate-client spec: replicas: 1 selector: matchLabels: - app: client + app: checkmate-client template: metadata: labels: - app: client + app: checkmate-client spec: containers: - name: client @@ -20,3 +20,5 @@ spec: env: - name: UPTIME_APP_API_BASE_URL value: "{{ .Values.server.protocol }}://{{ .Values.server.ingress.host }}/api/v1" + - name: UPTIME_APP_CLIENT_HOST + value: "http://{{ .Values.client.ingress.host }}" diff --git a/charts/helm/checkmate/templates/client-ingress.yaml b/charts/helm/checkmate/templates/client-ingress.yaml index da059a863..014aa53b3 100644 --- a/charts/helm/checkmate/templates/client-ingress.yaml +++ b/charts/helm/checkmate/templates/client-ingress.yaml @@ -2,10 +2,15 @@ apiVersion: networking.k8s.io/v1 kind: Ingress metadata: - name: client-ingress + name: checkmate-client-ingress + {{- if .Values.client.ingress.annotations }} annotations: - nginx.ingress.kubernetes.io/rewrite-target: / + {{- range $key, $value := .Values.client.ingress.annotations }} + {{ $key }}: {{ $value | quote }} + {{- end }} + {{- end }} spec: + ingressClassName: {{ .Values.client.ingress.className }} rules: - host: {{ .Values.client.ingress.host }} http: diff --git a/charts/helm/checkmate/templates/client-service.yaml b/charts/helm/checkmate/templates/client-service.yaml index afb88188c..b56eaeedf 100644 --- a/charts/helm/checkmate/templates/client-service.yaml +++ b/charts/helm/checkmate/templates/client-service.yaml @@ -1,9 +1,9 @@ apiVersion: v1 kind: Service metadata: - name: client + name: checkmate-client spec: selector: - app: client + app: checkmate-client ports: - port: {{ .Values.client.port }} diff --git a/charts/helm/checkmate/templates/mongodb-pvc.yaml b/charts/helm/checkmate/templates/mongodb-pvc.yaml deleted file mode 100644 index 435b662e6..000000000 --- a/charts/helm/checkmate/templates/mongodb-pvc.yaml +++ /dev/null @@ -1,10 +0,0 @@ -apiVersion: v1 -kind: PersistentVolumeClaim -metadata: - name: mongodb-pvc -spec: - accessModes: - - ReadWriteOnce - resources: - requests: - storage: {{ .Values.persistence.mongodbSize }} \ No newline at end of file diff --git a/charts/helm/checkmate/templates/mongodb-service.yaml b/charts/helm/checkmate/templates/mongodb-service.yaml index 2911dc3eb..b96fe1a82 100644 --- a/charts/helm/checkmate/templates/mongodb-service.yaml +++ b/charts/helm/checkmate/templates/mongodb-service.yaml @@ -1,9 +1,9 @@ apiVersion: v1 kind: Service metadata: - name: mongodb + name: checkmate-mongodb spec: selector: - app: mongodb + app: checkmate-mongodb ports: - port: {{ .Values.mongodb.port }} \ No newline at end of file diff --git a/charts/helm/checkmate/templates/mongodb-deployment.yaml b/charts/helm/checkmate/templates/mongodb-statefulsets.yaml similarity index 54% rename from charts/helm/checkmate/templates/mongodb-deployment.yaml rename to charts/helm/checkmate/templates/mongodb-statefulsets.yaml index 4c906fe09..4b490d7d7 100644 --- a/charts/helm/checkmate/templates/mongodb-deployment.yaml +++ b/charts/helm/checkmate/templates/mongodb-statefulsets.yaml @@ -1,16 +1,16 @@ apiVersion: apps/v1 -kind: Deployment +kind: StatefulSet metadata: - name: mongodb + name: checkmate-mongodb spec: replicas: 1 selector: matchLabels: - app: mongodb + app: checkmate-mongodb template: metadata: labels: - app: mongodb + app: checkmate-mongodb spec: containers: - name: mongodb @@ -21,7 +21,12 @@ spec: volumeMounts: - name: mongodb-data mountPath: /data/db - volumes: - - name: mongodb-data - persistentVolumeClaim: - claimName: mongodb-pvc \ No newline at end of file + volumeClaimTemplates: + - metadata: + name: checkmate-mongo-persistent-storage + spec: + storageClassName: "gp3" + accessModes: ["ReadWriteOnce"] + resources: + requests: + storage: {{ .Values.persistence.mongodbSize }} \ No newline at end of file diff --git a/charts/helm/checkmate/templates/redis-deployment.yaml b/charts/helm/checkmate/templates/redis-deployment.yaml deleted file mode 100644 index cc9ffdaf9..000000000 --- a/charts/helm/checkmate/templates/redis-deployment.yaml +++ /dev/null @@ -1,26 +0,0 @@ -apiVersion: apps/v1 -kind: Deployment -metadata: - name: redis -spec: - replicas: 1 - selector: - matchLabels: - app: redis - template: - metadata: - labels: - app: redis - spec: - containers: - - name: redis - image: {{ .Values.redis.image }} - ports: - - containerPort: {{ .Values.redis.port }} - volumeMounts: - - name: redis-data - mountPath: /data - volumes: - - name: redis-data - persistentVolumeClaim: - claimName: redis-pvc diff --git a/charts/helm/checkmate/templates/redis-pvc.yaml b/charts/helm/checkmate/templates/redis-pvc.yaml deleted file mode 100644 index 2f0d81960..000000000 --- a/charts/helm/checkmate/templates/redis-pvc.yaml +++ /dev/null @@ -1,10 +0,0 @@ -apiVersion: v1 -kind: PersistentVolumeClaim -metadata: - name: redis-pvc -spec: - accessModes: - - ReadWriteOnce - resources: - requests: - storage: {{ .Values.persistence.redisSize }} \ No newline at end of file diff --git a/charts/helm/checkmate/templates/redis-service.yaml b/charts/helm/checkmate/templates/redis-service.yaml index f29fa46de..2720350da 100644 --- a/charts/helm/checkmate/templates/redis-service.yaml +++ b/charts/helm/checkmate/templates/redis-service.yaml @@ -1,9 +1,11 @@ +{{- if .Values.redis.enabled }} apiVersion: v1 kind: Service metadata: - name: redis + name: checkmate-redis spec: selector: - app: redis + app: checkmate-redis ports: - port: {{ .Values.redis.port }} +{{- end }} diff --git a/charts/helm/checkmate/templates/redis-statefulsets.yaml b/charts/helm/checkmate/templates/redis-statefulsets.yaml new file mode 100644 index 000000000..252361730 --- /dev/null +++ b/charts/helm/checkmate/templates/redis-statefulsets.yaml @@ -0,0 +1,33 @@ +{{- if .Values.redis.enabled }} +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: checkmate-redis +spec: + replicas: 1 + selector: + matchLabels: + app: checkmate-redis + template: + metadata: + labels: + app: checkmate-redis + spec: + containers: + - name: redis + image: {{ .Values.redis.image }} + ports: + - containerPort: {{ .Values.redis.port }} + volumeMounts: + - name: redis-data + mountPath: /data + volumeClaimTemplates: + - metadata: + name: checkmate-redis-persistent-storage + spec: + storageClassName: "gp3" + accessModes: ["ReadWriteOnce"] + resources: + requests: + storage: {{ .Values.persistence.redisSize }} +{{- end }} \ No newline at end of file diff --git a/charts/helm/checkmate/templates/server-deployment.yaml b/charts/helm/checkmate/templates/server-deployment.yaml index 6f21f86f3..19affe8d8 100644 --- a/charts/helm/checkmate/templates/server-deployment.yaml +++ b/charts/helm/checkmate/templates/server-deployment.yaml @@ -1,16 +1,16 @@ apiVersion: apps/v1 kind: Deployment metadata: - name: server + name: checkmate-server spec: replicas: 1 selector: matchLabels: - app: server + app: checkmate-server template: metadata: labels: - app: server + app: checkmate-server spec: containers: - name: server diff --git a/charts/helm/checkmate/templates/server-ingress.yaml b/charts/helm/checkmate/templates/server-ingress.yaml index c1982d451..3755c94b8 100644 --- a/charts/helm/checkmate/templates/server-ingress.yaml +++ b/charts/helm/checkmate/templates/server-ingress.yaml @@ -2,15 +2,22 @@ apiVersion: networking.k8s.io/v1 kind: Ingress metadata: - name: server-ingress + name: checkmate-server-ingress + {{- if .Values.server.ingress.annotations }} annotations: - nginx.ingress.kubernetes.io/rewrite-target: / - nginx.ingress.kubernetes.io/enable-cors: "true" - nginx.ingress.kubernetes.io/cors-allow-origin: "http://{{ .Values.client.ingress.host }},https://{{ .Values.client.ingress.host }}" - nginx.ingress.kubernetes.io/cors-allow-methods: "GET, PUT, POST, DELETE, PATCH, OPTIONS" - nginx.ingress.kubernetes.io/cors-allow-headers: "DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization" - nginx.ingress.kubernetes.io/cors-allow-credentials: "true" + {{- range $key, $value := .Values.server.ingress.annotations }} + {{ $key }}: {{ $value | quote }} + {{- end }} + {{- end }} + {{/*#annotations: + #nginx.ingress.kubernetes.io/rewrite-target: / + #nginx.ingress.kubernetes.io/enable-cors: "true" + #nginx.ingress.kubernetes.io/cors-allow-origin: "http://{{ .Values.client.ingress.host }},https://{{ .Values.client.ingress.host }}" + #nginx.ingress.kubernetes.io/cors-allow-methods: "GET, PUT, POST, DELETE, PATCH, OPTIONS" + #nginx.ingress.kubernetes.io/cors-allow-headers: "DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization" + #nginx.ingress.kubernetes.io/cors-allow-credentials: "true"*/}} spec: + ingressClassName: {{ .Values.server.ingress.className }} rules: - host: {{ .Values.server.ingress.host }} http: diff --git a/charts/helm/checkmate/templates/server-service.yaml b/charts/helm/checkmate/templates/server-service.yaml index 64494ff90..d8a7fbe86 100644 --- a/charts/helm/checkmate/templates/server-service.yaml +++ b/charts/helm/checkmate/templates/server-service.yaml @@ -1,9 +1,9 @@ apiVersion: v1 kind: Service metadata: - name: server + name: checkmate-server spec: selector: - app: server + app: checkmate-server ports: - port: {{ .Values.server.port }} diff --git a/charts/helm/checkmate/values.yaml b/charts/helm/checkmate/values.yaml index 79c77a49e..1840cf909 100644 --- a/charts/helm/checkmate/values.yaml +++ b/charts/helm/checkmate/values.yaml @@ -1,40 +1,46 @@ client: - image: ghcr.io/bluewave-labs/checkmate-client:v2.3 + image: ghcr.io/bluewave-labs/checkmate-client:v3.2.0 port: 80 ingress: enabled: true host: change_me + className: nginx + annotations: {} server: - image: ghcr.io/bluewave-labs/checkmate-backend:v2.3 + image: ghcr.io/bluewave-labs/checkmate-backend:v3.2.0 port: 52345 - protocol: change_me + protocol: http ingress: enabled: true host: change_me + className: nginx + annotations: {} redis: + enabled: false image: redis:7.2 port: 6379 mongodb: - image: mongo:7.0 + image: ghcr.io/bluewave-labs/checkmate-mongo:v3.2.0 port: 27017 secrets: JWT_SECRET: change_me - REFRESH_TOKEN_SECRET: change_me - SYSTEM_EMAIL_ADDRESS: test@example.com - SYSTEM_EMAIL_PASSWORD: change_me - SYSTEM_EMAIL_HOST: smtp.example.com - SYSTEM_EMAIL_PORT: "587" - PAGESPEED_API_KEY: change_me - DB_CONNECTION_STRING: mongodb://mongodb:27017/uptime_db - REDIS_HOST: redis - REDIS_PORT: "6379" - DB_TYPE: MongoDB - TOKEN_TTL: 99d - REFRESH_TOKEN_TTL: 99d +# REFRESH_TOKEN_SECRET: change_me +# SYSTEM_EMAIL_ADDRESS: test@example.com +# SYSTEM_EMAIL_PASSWORD: change_me +# SYSTEM_EMAIL_HOST: smtp.example.com +# SYSTEM_EMAIL_PORT: "587" +# PAGESPEED_API_KEY: change_me + DB_CONNECTION_STRING: mongodb://checkmate-mongodb.namespace.svc:27017/uptime_db + CLIENT_HOST: change_me +# REDIS_HOST: redis +# REDIS_PORT: "6379" +# DB_TYPE: MongoDB +# TOKEN_TTL: 99d +# REFRESH_TOKEN_TTL: 99d persistence: mongodbSize: 5Gi From c6cf7a46391a9608156c385e0f626d922e6b2e90 Mon Sep 17 00:00:00 2001 From: ariretiarno Date: Tue, 30 Sep 2025 11:30:21 +0700 Subject: [PATCH 055/101] helm chart --- charts/helm/checkmate/templates/secrets.yaml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/charts/helm/checkmate/templates/secrets.yaml b/charts/helm/checkmate/templates/secrets.yaml index 0a3f1ace5..af104e7d1 100644 --- a/charts/helm/checkmate/templates/secrets.yaml +++ b/charts/helm/checkmate/templates/secrets.yaml @@ -4,10 +4,6 @@ {{- fail "secrets.JWT_SECRET must be overridden and cannot be 'change_me'" }} {{- end }} -{{- if or (not $secrets.REFRESH_TOKEN_SECRET) (eq $secrets.REFRESH_TOKEN_SECRET "change_me") }} - {{- fail "secrets.REFRESH_TOKEN_SECRET must be overridden and cannot be 'change_me'" }} -{{- end }} - apiVersion: v1 kind: Secret metadata: From 4744c05f07669a0110ae634d26f756c38d88c633 Mon Sep 17 00:00:00 2001 From: ariretiarno Date: Tue, 30 Sep 2025 11:35:14 +0700 Subject: [PATCH 056/101] pvc --- charts/helm/checkmate/templates/mongodb-statefulsets.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/charts/helm/checkmate/templates/mongodb-statefulsets.yaml b/charts/helm/checkmate/templates/mongodb-statefulsets.yaml index 4b490d7d7..1ffd1c228 100644 --- a/charts/helm/checkmate/templates/mongodb-statefulsets.yaml +++ b/charts/helm/checkmate/templates/mongodb-statefulsets.yaml @@ -19,7 +19,7 @@ spec: - containerPort: {{ .Values.mongodb.port }} command: ["mongod", "--quiet", "--bind_ip_all"] volumeMounts: - - name: mongodb-data + - name: checkmate-mongo-persistent-storage mountPath: /data/db volumeClaimTemplates: - metadata: From 7a352cdcb34a962798dae4f03c1a073809b5addf Mon Sep 17 00:00:00 2001 From: ariretiarno Date: Wed, 1 Oct 2025 09:16:45 +0700 Subject: [PATCH 057/101] client host --- charts/helm/checkmate/server-nginx-cm.yaml | 41 +++++++++++++++++++ .../templates/server-deployment.yaml | 3 ++ 2 files changed, 44 insertions(+) create mode 100644 charts/helm/checkmate/server-nginx-cm.yaml diff --git a/charts/helm/checkmate/server-nginx-cm.yaml b/charts/helm/checkmate/server-nginx-cm.yaml new file mode 100644 index 000000000..64eca82b7 --- /dev/null +++ b/charts/helm/checkmate/server-nginx-cm.yaml @@ -0,0 +1,41 @@ +apiVersion: apps/v1 +kind: ConfigMap +metadata: + name: checkmate-server-nginx-cm +data: + default.conf: | + server { + listen 80; + listen [::]:80; + + server_name checkmate-demo.bluewavelabs.ca; + server_tokens off; + + location /.well-known/acme-challenge/ { + root /var/www/certbot; + } + + location / { + root /usr/share/nginx/html; + index index.html index.htm; + try_files $uri $uri/ /index.html; + } + + # location /api/ { + # proxy_pass http://server:5000/api/; + # proxy_http_version 1.1; + # proxy_set_header Host $host; + # proxy_set_header X-Real-IP $remote_addr; + # proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + # proxy_set_header X-Forwarded-Proto $scheme; + # } + + location /api-docs/ { + proxy_pass http://server:5000/api-docs/; + proxy_http_version 1.1; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } + } \ No newline at end of file diff --git a/charts/helm/checkmate/templates/server-deployment.yaml b/charts/helm/checkmate/templates/server-deployment.yaml index 19affe8d8..856e16922 100644 --- a/charts/helm/checkmate/templates/server-deployment.yaml +++ b/charts/helm/checkmate/templates/server-deployment.yaml @@ -20,3 +20,6 @@ spec: envFrom: - secretRef: name: checkmate-secrets + env: + - name: CLIENT_HOST + value: {{ .Values.client.ingress.host }} From d63731fcedcab31c58a94121e4d93dcfe21559ab Mon Sep 17 00:00:00 2001 From: ariretiarno Date: Wed, 1 Oct 2025 09:22:42 +0700 Subject: [PATCH 058/101] client host --- charts/helm/checkmate/templates/server-deployment.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/charts/helm/checkmate/templates/server-deployment.yaml b/charts/helm/checkmate/templates/server-deployment.yaml index 856e16922..8c9403916 100644 --- a/charts/helm/checkmate/templates/server-deployment.yaml +++ b/charts/helm/checkmate/templates/server-deployment.yaml @@ -22,4 +22,4 @@ spec: name: checkmate-secrets env: - name: CLIENT_HOST - value: {{ .Values.client.ingress.host }} + value: "http://{{ .Values.client.ingress.host }}" From 8d48b2cc8a8c24e1c9c5af4f512708fa70dfe463 Mon Sep 17 00:00:00 2001 From: ariretiarno Date: Wed, 1 Oct 2025 09:33:32 +0700 Subject: [PATCH 059/101] client host --- charts/helm/checkmate/templates/client-deployment.yaml | 8 ++++++++ charts/helm/checkmate/templates/client-ingress.yaml | 2 +- charts/helm/checkmate/templates/server-ingress.yaml | 2 +- .../helm/checkmate/{ => templates}/server-nginx-cm.yaml | 6 +++--- 4 files changed, 13 insertions(+), 5 deletions(-) rename charts/helm/checkmate/{ => templates}/server-nginx-cm.yaml (86%) diff --git a/charts/helm/checkmate/templates/client-deployment.yaml b/charts/helm/checkmate/templates/client-deployment.yaml index b6e13fc7d..a5d487dc8 100644 --- a/charts/helm/checkmate/templates/client-deployment.yaml +++ b/charts/helm/checkmate/templates/client-deployment.yaml @@ -22,3 +22,11 @@ spec: value: "{{ .Values.server.protocol }}://{{ .Values.server.ingress.host }}/api/v1" - name: UPTIME_APP_CLIENT_HOST value: "http://{{ .Values.client.ingress.host }}" + volumeMounts: + - name: config-volume + mountPath: /etc/nginx/conf.d/default.conf + subPath: default.conf + volumes: + - name: config-volume + configMap: + name: checkmate-server-nginx-cm diff --git a/charts/helm/checkmate/templates/client-ingress.yaml b/charts/helm/checkmate/templates/client-ingress.yaml index 014aa53b3..89e001e15 100644 --- a/charts/helm/checkmate/templates/client-ingress.yaml +++ b/charts/helm/checkmate/templates/client-ingress.yaml @@ -19,7 +19,7 @@ spec: pathType: Prefix backend: service: - name: client + name: checkmate-client port: number: {{ .Values.client.port }} {{- end }} diff --git a/charts/helm/checkmate/templates/server-ingress.yaml b/charts/helm/checkmate/templates/server-ingress.yaml index 3755c94b8..0d7ca867b 100644 --- a/charts/helm/checkmate/templates/server-ingress.yaml +++ b/charts/helm/checkmate/templates/server-ingress.yaml @@ -26,7 +26,7 @@ spec: pathType: Prefix backend: service: - name: server + name: checkmate-server port: number: {{ .Values.server.port }} {{- end }} diff --git a/charts/helm/checkmate/server-nginx-cm.yaml b/charts/helm/checkmate/templates/server-nginx-cm.yaml similarity index 86% rename from charts/helm/checkmate/server-nginx-cm.yaml rename to charts/helm/checkmate/templates/server-nginx-cm.yaml index 64eca82b7..84da55299 100644 --- a/charts/helm/checkmate/server-nginx-cm.yaml +++ b/charts/helm/checkmate/templates/server-nginx-cm.yaml @@ -1,4 +1,4 @@ -apiVersion: apps/v1 +apiVersion: v1 kind: ConfigMap metadata: name: checkmate-server-nginx-cm @@ -22,7 +22,7 @@ data: } # location /api/ { - # proxy_pass http://server:5000/api/; + # proxy_pass http://{{ .Values.server.ingress.host }}:5000/api/; # proxy_http_version 1.1; # proxy_set_header Host $host; # proxy_set_header X-Real-IP $remote_addr; @@ -31,7 +31,7 @@ data: # } location /api-docs/ { - proxy_pass http://server:5000/api-docs/; + proxy_pass http://{{ .Values.server.ingress.host }}:5000/api-docs/; proxy_http_version 1.1; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; From 468a67f937353580e10c5ef370f403de6bf70285 Mon Sep 17 00:00:00 2001 From: karenvicent Date: Wed, 1 Oct 2025 11:34:51 -0400 Subject: [PATCH 060/101] git stash push -m "work in progress" --- client/src/Hooks/v1/userHooks.js | 26 +++- .../components/ChangePasswordModal/index.jsx | 140 ++++++++++++++++++ .../src/Pages/v1/Account/EditUser/index.jsx | 19 ++- client/src/Utils/NetworkService.js | 10 ++ client/src/locales/en.json | 8 +- server/src/controllers/v1/authController.js | 23 +++ server/src/routes/v1/authRoute.js | 1 + server/src/service/v1/business/userService.js | 5 + server/src/validation/joi.js | 5 + 9 files changed, 230 insertions(+), 7 deletions(-) create mode 100644 client/src/Pages/Account/components/ChangePasswordModal/index.jsx diff --git a/client/src/Hooks/v1/userHooks.js b/client/src/Hooks/v1/userHooks.js index 71b156594..f9c454450 100644 --- a/client/src/Hooks/v1/userHooks.js +++ b/client/src/Hooks/v1/userHooks.js @@ -7,7 +7,6 @@ export const useGetUser = (userId) => { const [user, setUser] = useState(undefined); const [isLoading, setIsLoading] = useState(false); const [error, setError] = useState(null); - const fetchUser = useCallback(async () => { try { setIsLoading(true); @@ -54,8 +53,29 @@ export const useEditUser = (userId) => { setIsLoading(false); } }, - [userId] + [userId, t] ); - return [editUser, isLoading, error]; + const changePassword = useCallback( + async (passwordForm) => { + try { + setIsLoading(true); + + await networkService.changePasswordByAdmin({ userId, passwordForm }); + createToast({ + body: t("teamPanel.changeTeamPassword.success"), + }); + } catch (error) { + createToast({ + body: error.message, + }); + setError(error); + } finally { + setIsLoading(false); + } + }, + [userId, t] + ); + + return [editUser, isLoading, error, changePassword]; }; diff --git a/client/src/Pages/Account/components/ChangePasswordModal/index.jsx b/client/src/Pages/Account/components/ChangePasswordModal/index.jsx new file mode 100644 index 000000000..375595b29 --- /dev/null +++ b/client/src/Pages/Account/components/ChangePasswordModal/index.jsx @@ -0,0 +1,140 @@ +import { useState } from "react"; +import { Button, Stack } from "@mui/material"; +import { GenericDialog } from "../../../../Components/Dialog/genericDialog"; +import TextInput from "../../../../Components/Inputs/TextInput"; +import PasswordTooltip from "../../../Auth/components/PasswordTooltip"; +import { useTheme } from "@emotion/react"; +import { useTranslation } from "react-i18next"; +import { createToast } from "../../../../Utils/toastUtils"; +import { PasswordEndAdornment } from "../../../../Components/Inputs/TextInput/Adornments"; +import usePasswordFeedback from "../../../Auth/hooks/usePasswordFeedback"; + +const ChangePasswordModal = ({ isSaving, isLoading, userId, changePassword, email }) => { + const INITIAL_FORM_STATE = { + password: "", + confirm: "", + }; + console.log("email", email); + const theme = useTheme(); + const { t } = useTranslation(); + const { feedback, handlePasswordFeedback } = usePasswordFeedback(); + const [form, setForm] = useState(INITIAL_FORM_STATE); + const [isChangePasswordOpen, setIsChangePasswordOpen] = useState(false); + const [errors, setErrors] = useState({}); + const [isLoadingSubmit, setIsLoadingSubmit] = useState(false); + const closeChangePasswordModal = () => { + setIsChangePasswordOpen(false); + setForm(INITIAL_FORM_STATE); + }; + + const onChange = (e) => { + let { name, value } = e.target; + const updatedForm = { ...form, [name]: value }; + //validateFields(name, value, updatedForm); + setForm(updatedForm); + + handlePasswordFeedback(updatedForm, name, value, form, errors, setErrors); + }; + const isFormValid = !errors.password && !errors.confirm; + const onsubmitChangePassword = async (event) => { + event.preventDefault(); + if (!isFormValid) return; + const newPasswordForm = { + password: form.password, + newPassword: form.confirm, + email: email, + }; + try { + setIsLoadingSubmit(true); + await changePassword(newPasswordForm); + closeChangePasswordModal(); + } catch (error) { + const errorMsg = error.response?.data?.msg || error.message || "unknownError"; + createToast({ + type: "error", + body: t(errorMsg), + }); + } finally { + setIsLoadingSubmit(false); + } + }; + return ( + <> + + + + } + sx={{ mb: theme.spacing(5) }} + /> + + } + sx={{ mb: theme.spacing(5) }} + /> + + + + + + + + ); +}; +export default ChangePasswordModal; diff --git a/client/src/Pages/v1/Account/EditUser/index.jsx b/client/src/Pages/v1/Account/EditUser/index.jsx index e2e976916..9fe307cf2 100644 --- a/client/src/Pages/v1/Account/EditUser/index.jsx +++ b/client/src/Pages/v1/Account/EditUser/index.jsx @@ -7,6 +7,7 @@ import TextInput from "@/Components/v1/Inputs/TextInput/index.jsx"; import Search from "@/Components/v1/Inputs/Search/index.jsx"; import Button from "@mui/material/Button"; import RoleTable from "../components/RoleTable/index.jsx"; +import ChangePasswordModal from "../components/ChangePasswordModal"; // Utils import { useParams } from "react-router-dom"; @@ -26,7 +27,7 @@ const EditUser = () => { ]; const [user, isLoading, error] = useGetUser(userId); - const [editUser, isSaving, saveError] = useEditUser(userId); + const [editUser, isSaving, saveError, changePassword] = useEditUser(userId); const [ form, setForm, @@ -104,7 +105,12 @@ const EditUser = () => { roles={form?.role} handleDeleteRole={handleDeleteRole} /> - + - + + ); diff --git a/client/src/Utils/NetworkService.js b/client/src/Utils/NetworkService.js index dce0faaf5..7401994bb 100644 --- a/client/src/Utils/NetworkService.js +++ b/client/src/Utils/NetworkService.js @@ -1136,6 +1136,16 @@ class NetworkService { }, }); } + + async changePasswordByAdmin(config) { + const { userId, passwordForm } = config; + console.log("Password form data:", passwordForm); + return this.axiosInstance.put(`auth/users/${userId}/password`, passwordForm, { + headers: { + "Content-Type": "application/json", + }, + }); + } } export default NetworkService; diff --git a/client/src/locales/en.json b/client/src/locales/en.json index d89ebc44e..642c8e0f6 100644 --- a/client/src/locales/en.json +++ b/client/src/locales/en.json @@ -425,7 +425,13 @@ } } }, - "role": "Role" + "role": "Role", + "changeTeamPassword": { + "changePasswordMenu": "Reset Password", + "title": "Reset team member password", + "description": "Create a new password for this team member. You will need to share the password with them securely.", + "success": "Password successfully reset. Make sure to provide the credentials to the member in a secure way." + } }, "monitorState": { "paused": "Paused", diff --git a/server/src/controllers/v1/authController.js b/server/src/controllers/v1/authController.js index 17ed517eb..b84aaacd6 100755 --- a/server/src/controllers/v1/authController.js +++ b/server/src/controllers/v1/authController.js @@ -456,6 +456,29 @@ class AuthController extends BaseController { SERVICE_NAME, "editUserById" ); + editUserPasswordById = this.asyncHandler( + async (req, res) => { + console.log("editUserPasswordById called"); + const roles = req?.user?.role; + if (!roles.includes("superadmin")) { + console.log("entro a error editUserPasswordById"); + throw createError("Unauthorized", 403); + } + + const userId = req.params.userId; + console.log("userId param:", userId); + const updates = { ...req.body }; + console.log("newPassword body:", req.body); + await editUserByIdParamValidation.validateAsync(req.params); + console.log("params validated"); + //await editUserPasswordByIdBodyValidation.validateAsync(req.body); + console.log("body validated"); + await this.userService.setPasswordByUserId( userId, updates ); + return res.success({ msg: "Password reset successfully" }); + }, + SERVICE_NAME, "editUserPasswordById" + ) + } export default AuthController; diff --git a/server/src/routes/v1/authRoute.js b/server/src/routes/v1/authRoute.js index d9b036b05..63b4c4201 100755 --- a/server/src/routes/v1/authRoute.js +++ b/server/src/routes/v1/authRoute.js @@ -25,6 +25,7 @@ class AuthRoutes { this.router.get("/users", verifyJWT, isAllowed(["admin", "superadmin"]), this.authController.getAllUsers); this.router.get("/users/:userId", verifyJWT, isAllowed(["superadmin"]), this.authController.getUserById); this.router.put("/users/:userId", verifyJWT, isAllowed(["superadmin"]), this.authController.editUserById); + this.router.put("/users/:userId/password", verifyJWT, isAllowed(["superadmin"]), this.authController.editUserPasswordById); this.router.put("/user", verifyJWT, upload.single("profileImage"), this.authController.editUser); this.router.delete("/user", verifyJWT, this.authController.deleteUser); diff --git a/server/src/service/v1/business/userService.js b/server/src/service/v1/business/userService.js index 0a965bced..6c036a5c8 100644 --- a/server/src/service/v1/business/userService.js +++ b/server/src/service/v1/business/userService.js @@ -211,5 +211,10 @@ class UserService { editUserById = async (userId, user) => { await this.db.userModule.editUserById(userId, user); }; + setPasswordByUserId = async (userId, updates) => { + const updatedUser = await this.db.userModule.updateUser({ userId: userId, user: updates, file: null }); + return updatedUser; + } + } export default UserService; diff --git a/server/src/validation/joi.js b/server/src/validation/joi.js index fa8dccde2..209135769 100755 --- a/server/src/validation/joi.js +++ b/server/src/validation/joi.js @@ -670,6 +670,10 @@ const editSuperadminUserByIdBodyValidation = joi.object({ .required(), }); +const editUserPasswordByIdBodyValidation = joi.object({ + newPassword: joi.string().min(8).required().pattern(passwordPattern), +}); + export { roleValidatior, loginValidation, @@ -739,4 +743,5 @@ export { editUserByIdParamValidation, editUserByIdBodyValidation, editSuperadminUserByIdBodyValidation, + editUserPasswordByIdBodyValidation, }; From 3ae2f857d4169f13cec49166dae0dd42ed9776ed Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Wed, 1 Oct 2025 13:55:32 -0700 Subject: [PATCH 061/101] add v2 sidebar --- .../v2/Layouts/Sidebar/BottomControls.tsx | 16 +++ .../Components/v2/Layouts/Sidebar/Menu.tsx | 58 ++++++++++ .../Components/v2/Layouts/Sidebar/NavItem.tsx | 103 ++++++++++++++++++ .../Components/v2/Layouts/Sidebar/index.tsx | 65 ++++++++++- client/src/Features/Auth/v2AuthSlice.ts | 19 ++++ client/src/Pages/v2/Auth/Login.tsx | 11 +- client/src/Routes/v2router.tsx | 6 +- client/src/Utils/Theme/v2/palette.ts | 2 + client/src/store.js | 4 +- client/src/types/mui.d.ts | 1 + 10 files changed, 279 insertions(+), 6 deletions(-) create mode 100644 client/src/Components/v2/Layouts/Sidebar/BottomControls.tsx create mode 100644 client/src/Components/v2/Layouts/Sidebar/Menu.tsx create mode 100644 client/src/Components/v2/Layouts/Sidebar/NavItem.tsx create mode 100644 client/src/Features/Auth/v2AuthSlice.ts diff --git a/client/src/Components/v2/Layouts/Sidebar/BottomControls.tsx b/client/src/Components/v2/Layouts/Sidebar/BottomControls.tsx new file mode 100644 index 000000000..a07ccde6c --- /dev/null +++ b/client/src/Components/v2/Layouts/Sidebar/BottomControls.tsx @@ -0,0 +1,16 @@ +import { useTheme } from "@mui/material/styles"; + +import Stack from "@mui/material/Stack"; + +export const BottomControls = ({}) => { + const theme = useTheme(); + return ( + + ); +}; diff --git a/client/src/Components/v2/Layouts/Sidebar/Menu.tsx b/client/src/Components/v2/Layouts/Sidebar/Menu.tsx new file mode 100644 index 000000000..5b3ed705e --- /dev/null +++ b/client/src/Components/v2/Layouts/Sidebar/Menu.tsx @@ -0,0 +1,58 @@ +import Notifications from "@/assets/icons/notifications.svg?react"; +import Monitors from "@/assets/icons/monitors.svg?react"; +import PageSpeed from "@/assets/icons/page-speed.svg?react"; +import Integrations from "@/assets/icons/integrations.svg?react"; +import Incidents from "@/assets/icons/incidents.svg?react"; +import StatusPages from "@/assets/icons/status-pages.svg?react"; +import Maintenance from "@/assets/icons/maintenance.svg?react"; +import Logs from "@/assets/icons/logs.svg?react"; +import Settings from "@/assets/icons/settings.svg?react"; +import Support from "@/assets/icons/support.svg?react"; +import Discussions from "@/assets/icons/discussions.svg?react"; +import Docs from "@/assets/icons/docs.svg?react"; +import ChangeLog from "@/assets/icons/changeLog.svg?react"; + +export const getMenu = (t: Function) => [ + { name: t("menu.uptime"), path: "v2/uptime", icon: }, + { name: t("menu.pagespeed"), path: "pagespeed", icon: }, + + { name: t("menu.infrastructure"), path: "infrastructure", icon: }, + { + name: t("menu.notifications"), + path: "notifications", + icon: , + }, + { name: t("menu.incidents"), path: "incidents", icon: }, + + { name: t("menu.statusPages"), path: "status", icon: }, + { name: t("menu.maintenance"), path: "maintenance", icon: }, + { name: t("menu.logs"), path: "logs", icon: }, + + { + name: t("menu.settings"), + icon: , + path: "settings", + }, +]; + +export const getBottomMenu = (t: Function) => [ + { name: t("menu.support"), path: "support", icon: , url: "invite" }, + { + name: t("menu.discussions"), + path: "discussions", + icon: , + url: "https://github.com/bluewave-labs/checkmate/discussions", + }, + { + name: t("menu.docs"), + path: "docs", + icon: , + url: "https://bluewavelabs.gitbook.io/checkmate", + }, + { + name: t("menu.changelog"), + path: "changelog", + icon: , + url: "https://github.com/bluewave-labs/checkmate/releases", + }, +]; diff --git a/client/src/Components/v2/Layouts/Sidebar/NavItem.tsx b/client/src/Components/v2/Layouts/Sidebar/NavItem.tsx new file mode 100644 index 000000000..8f7f5d68c --- /dev/null +++ b/client/src/Components/v2/Layouts/Sidebar/NavItem.tsx @@ -0,0 +1,103 @@ +import Tooltip from "@mui/material/Tooltip"; +import ListItemButton from "@mui/material/ListItemButton"; +import ListItemIcon from "@mui/material/ListItemIcon"; +import Box from "@mui/material/Box"; +import Typography from "@mui/material/Typography"; +import { useTheme } from "@mui/material/styles"; + +export interface NavData { + name: string; + icon: JSX.Element; +} + +export const NavItem = ({ + item, + collapsed, + selected, + onClick, +}: { + item: NavData; + collapsed: boolean; + selected: boolean; + onClick: (event: React.MouseEvent) => void; +}) => { + const theme = useTheme(); + const iconStroke = selected + ? theme.palette.primary.contrastText + : theme.palette.primary.contrastTextTertiary; + + const buttonBgColor = selected ? theme.palette.secondary.main : "transparent"; + const buttonBgHoverColor = selected + ? theme.palette.secondary.main + : theme.palette.tertiary.main; + const fontWeight = selected ? 600 : 400; + return ( + + + + {item.icon} + + + + {item.name} + + + + + ); +}; diff --git a/client/src/Components/v2/Layouts/Sidebar/index.tsx b/client/src/Components/v2/Layouts/Sidebar/index.tsx index 43975adfc..b05716aee 100644 --- a/client/src/Components/v2/Layouts/Sidebar/index.tsx +++ b/client/src/Components/v2/Layouts/Sidebar/index.tsx @@ -3,10 +3,17 @@ import { useSelector, useDispatch } from "react-redux"; import { setCollapsed } from "@/Features/UI/uiSlice"; import useMediaQuery from "@mui/material/useMediaQuery"; import { useTheme } from "@mui/material/styles"; +import { useTranslation } from "react-i18next"; +import { useNavigate, useLocation } from "react-router-dom"; import { CollapseButton } from "@/Components/v2/Layouts/Sidebar/CollapseButton"; import Stack from "@mui/material/Stack"; +import List from "@mui/material/List"; +import Divider from "@mui/material/Divider"; import { Logo } from "@/Components/v2/Layouts/Sidebar/Logo"; +import { getMenu, getBottomMenu } from "@/Components/v2/Layouts/Sidebar/Menu"; +import { NavItem } from "@/Components/v2/Layouts/Sidebar/NavItem"; +import { BottomControls } from "@/Components/v2/Layouts/Sidebar/BottomControls"; export const COLLAPSED_WIDTH = 64; export const EXPANDED_WIDTH = 250; @@ -16,6 +23,11 @@ export const SideBar = () => { const isSmall = useMediaQuery(theme.breakpoints.down("md")); const dispatch = useDispatch(); const collapsed = useSelector((state: any) => state.ui.sidebar.collapsed); + const { t } = useTranslation(); + const navigate = useNavigate(); + const location = useLocation(); + const menu = getMenu(t); + const bottomMenu = getBottomMenu(t); useEffect(() => { dispatch(setCollapsed({ collapsed: isSmall })); @@ -23,6 +35,7 @@ export const SideBar = () => { return ( { borderRight={`1px solid ${theme.palette.primary.lowContrast}`} width={collapsed ? COLLAPSED_WIDTH : EXPANDED_WIDTH} sx={{ - transition: "width 0.3s ease", + transition: "width 650ms cubic-bezier(0.36, -0.01, 0, 0.77)", }} > + + {menu.map((item) => { + const selected = location.pathname.startsWith(`/${item.path}`); + return ( + navigate(`/${item.path}`)} + /> + ); + })} + + + {bottomMenu.map((item) => { + const selected = location.pathname.startsWith(`/${item.path}`); + + return ( + { + if (item.url) { + window.open(item.url, "_blank", "noreferrer"); + } else { + navigate(`/${item.path}`); + } + }} + /> + ); + })} + + + ); }; diff --git a/client/src/Features/Auth/v2AuthSlice.ts b/client/src/Features/Auth/v2AuthSlice.ts new file mode 100644 index 000000000..28a18b659 --- /dev/null +++ b/client/src/Features/Auth/v2AuthSlice.ts @@ -0,0 +1,19 @@ +import { createSlice } from "@reduxjs/toolkit"; + +const initialState = { + isAuthenticated: false, +}; + +const v2AuthSlice = createSlice({ + name: "v2Auth", + initialState, + reducers: { + setIsAuthenticated: (state, action) => { + const { authenticated } = action.payload; + state.isAuthenticated = authenticated; + }, + }, +}); + +export default v2AuthSlice.reducer; +export const { setIsAuthenticated } = v2AuthSlice.actions; diff --git a/client/src/Pages/v2/Auth/Login.tsx b/client/src/Pages/v2/Auth/Login.tsx index fd4a08e93..2a5629917 100644 --- a/client/src/Pages/v2/Auth/Login.tsx +++ b/client/src/Pages/v2/Auth/Login.tsx @@ -3,7 +3,9 @@ import { z } from "zod"; import { useTranslation } from "react-i18next"; import { useForm, Controller } from "react-hook-form"; import { useTheme } from "@mui/material/styles"; - +import { useDispatch } from "react-redux"; +import { setIsAuthenticated } from "@/Features/Auth/v2AuthSlice"; +import { useNavigate } from "react-router"; import { TextInput } from "@/Components/v2/Inputs/TextInput"; import Button from "@mui/material/Button"; import Stack from "@mui/material/Stack"; @@ -19,8 +21,10 @@ type FormData = z.infer; const Login = () => { const { t } = useTranslation(); + const dispatch = useDispatch(); const theme = useTheme(); const { post, loading, error } = usePost("/auth/login"); + const navigate = useNavigate(); const { handleSubmit, @@ -37,9 +41,10 @@ const Login = () => { const onSubmit = async (data: FormData) => { const result = await post(data); if (result) { - console.log(result.message); + dispatch(setIsAuthenticated({ authenticated: true })); + navigate("/v2/uptime"); } else { - console.error("Login failed:", error); + dispatch(setIsAuthenticated({ authenticated: false })); } }; diff --git a/client/src/Routes/v2router.tsx b/client/src/Routes/v2router.tsx index 941550026..12d3eb595 100644 --- a/client/src/Routes/v2router.tsx +++ b/client/src/Routes/v2router.tsx @@ -25,7 +25,11 @@ const V2Routes = ({ mode = "light" }) => { element={} > Uptime} + /> + Test Page} /> diff --git a/client/src/Utils/Theme/v2/palette.ts b/client/src/Utils/Theme/v2/palette.ts index 2e41a159c..855f7b6b8 100644 --- a/client/src/Utils/Theme/v2/palette.ts +++ b/client/src/Utils/Theme/v2/palette.ts @@ -54,6 +54,7 @@ export const lightPalette = { main: colors.offWhite, contrastText: colors.blueGray800, contrastTextSecondary: colors.blueGray600, + contrastTextTertiary: colors.blueGray500, lowContrast: colors.gray250, }, secondary: { @@ -78,6 +79,7 @@ export const darkPalette = { main: colors.offBlack, contrastText: colors.blueGray50, contrastTextSecondary: colors.gray200, + contrastTextTertiary: colors.gray500, lowContrast: colors.blueGray600, }, secondary: { diff --git a/client/src/store.js b/client/src/store.js index 69aa3d50b..565d68519 100644 --- a/client/src/store.js +++ b/client/src/store.js @@ -1,6 +1,7 @@ import { configureStore, combineReducers } from "@reduxjs/toolkit"; import authReducer from "./Features/Auth/authSlice"; +import v2AuthReducer from "./Features/Auth/v2AuthSlice"; import uiReducer from "./Features/UI/uiSlice"; import storage from "redux-persist/lib/storage"; import { persistReducer, persistStore, createTransform } from "redux-persist"; @@ -19,12 +20,13 @@ const authTransform = createTransform( const persistConfig = { key: "root", storage, - whitelist: ["auth", "ui"], + whitelist: ["auth", "v2Auth", "ui"], transforms: [authTransform], }; const rootReducer = combineReducers({ auth: authReducer, + v2Auth: v2AuthReducer, ui: uiReducer, }); diff --git a/client/src/types/mui.d.ts b/client/src/types/mui.d.ts index 5f5c1c38b..84bcd799f 100644 --- a/client/src/types/mui.d.ts +++ b/client/src/types/mui.d.ts @@ -12,6 +12,7 @@ declare module "@mui/material/styles" { interface PaletteColor { contrastTextSecondary?: string; + contrastTextTertiary?: string; lowContrast?: string; } } From 735054b4e15a5fbd024522f56019c4723053631e Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Wed, 1 Oct 2025 13:57:04 -0700 Subject: [PATCH 062/101] remove unused error --- client/src/Pages/v2/Auth/Login.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/Pages/v2/Auth/Login.tsx b/client/src/Pages/v2/Auth/Login.tsx index 2a5629917..a41750c27 100644 --- a/client/src/Pages/v2/Auth/Login.tsx +++ b/client/src/Pages/v2/Auth/Login.tsx @@ -23,7 +23,7 @@ const Login = () => { const { t } = useTranslation(); const dispatch = useDispatch(); const theme = useTheme(); - const { post, loading, error } = usePost("/auth/login"); + const { post, loading } = usePost("/auth/login"); const navigate = useNavigate(); const { From 851d722746cde4445c50225f24e4b9e6d50b0ef9 Mon Sep 17 00:00:00 2001 From: Vivek Kavala Date: Thu, 2 Oct 2025 12:46:51 +0530 Subject: [PATCH 063/101] fix: remove manual response time calculation in favor of library-native timing The manual timing calculations were adding Node.js processing overhead and were less accurate than the native timing provided by the respective libraries (ping, got, etc.). Now using response.time from each library for more accurate network response time measurements. --- .../v1/infrastructure/networkService.js | 25 +++++++++++-------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/server/src/service/v1/infrastructure/networkService.js b/server/src/service/v1/infrastructure/networkService.js index 47ef6878f..676a56da8 100644 --- a/server/src/service/v1/infrastructure/networkService.js +++ b/server/src/service/v1/infrastructure/networkService.js @@ -33,11 +33,11 @@ class NetworkService { const start = process.hrtime.bigint(); try { const response = await operation(); - const elapsedMs = Math.round(Number(process.hrtime.bigint() - start) / 1_000_000); - return { response, responseTime: elapsedMs }; + // const elapsedMs = Math.round(Number(process.hrtime.bigint() - start) / 1_000_000); + return { response }; } catch (error) { - const elapsedMs = Math.round(Number(process.hrtime.bigint() - start) / 1_000_000); - return { response: null, responseTime: elapsedMs, error }; + // const elapsedMs = Math.round(Number(process.hrtime.bigint() - start) / 1_000_000); + return { response: null, error }; } } @@ -70,7 +70,12 @@ class NetworkService { throw new Error("Monitor URL is required"); } - const { response, responseTime, error } = await this.timeRequest(() => this.ping.promise.probe(monitor.url)); + const rawUrl = monitor.url; + const sanitizedHost = rawUrl + .replace(/^https?:\/\//, "") + .replace(/\/.*$/, "") + .replace(/:.*/, ""); + const { response, error } = await this.timeRequest(() => this.ping.promise.probe(sanitizedHost)); if (!response) { if (error) { @@ -84,7 +89,7 @@ class NetworkService { type: "ping", status: response.alive, code: 200, - responseTime, + responseTime: response.time, message: "Success", payload: response, }; @@ -345,9 +350,9 @@ class NetworkService { } const container = docker.getContainer(targetContainer.Id); - const { response, responseTime, error } = await this.timeRequest(() => container.inspect()); + const { response, error } = await this.timeRequest(() => container.inspect()); - dockerResponse.responseTime = responseTime; + dockerResponse.responseTime = response.time; dockerResponse.status = response?.State?.Status === "running" ? true : false; dockerResponse.code = 200; dockerResponse.message = "Docker container status fetched successfully"; @@ -370,7 +375,7 @@ class NetworkService { async requestPort(monitor) { try { const { url, port } = monitor; - const { response, responseTime, error } = await this.timeRequest(async () => { + const { response, error } = await this.timeRequest(async () => { return new Promise((resolve, reject) => { const socket = this.net.createConnection( { @@ -403,7 +408,7 @@ class NetworkService { message: this.stringService.portSuccess, monitorId: monitor._id, type: monitor.type, - responseTime, + responseTime: response.time, }; if (error) { From 080a33fac71cc5305e5c99f2d8f52ccf713c4f04 Mon Sep 17 00:00:00 2001 From: jithendra Kavala Date: Thu, 2 Oct 2025 13:51:30 +0530 Subject: [PATCH 064/101] docs: improve local development setup instructions in CONTRIBUTING.md --- CONTRIBUTING.md | 184 ++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 172 insertions(+), 12 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 702436b38..848b7d2f7 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -6,7 +6,7 @@ We truly appreciate all kinds of contributions — code, ideas, translations or Before you start, please take a moment to read the relevant section. It helps us review and accept contributions faster, and makes the whole process smoother for everyone. 💚 -PS: **We work closely with contributors on our [Discord channel](https://discord.com/invite/NAb6H3UTjK)**. You’ll find community members, core maintainers, and first-timers helping each other out. +PS: **We work closely with contributors on our [Discord channel](https://discord.com/invite/NAb6H3UTjK)**. You'll find community members, core maintainers, and first-timers helping each other out. --- @@ -32,12 +32,12 @@ PS: **We work closely with contributors on our [Discord channel](https://discord ### Get help or ask a question? -Ask anything in our [Discord server](https://discord.com/invite/NAb6H3UTjK) — we’re friendly and happy to help. [Our core contributors](https://github.com/bluewave-labs/checkmate?tab=readme-ov-file#-contributing) are active and ready to support you. You can also use [GitHub Discussions](https://github.com/bluewave-labs/Checkmate/discussions) section to ask your questions. +Ask anything in our [Discord server](https://discord.com/invite/NAb6H3UTjK) — we're friendly and happy to help. [Our core contributors](https://github.com/bluewave-labs/checkmate?tab=readme-ov-file#-contributing) are active and ready to support you. You can also use [GitHub Discussions](https://github.com/bluewave-labs/Checkmate/discussions) section to ask your questions. ### Report a bug? 1. Search [existing issues](https://github.com/bluewave-labs/checkmate/issues). -2. If it’s not listed, open a **new issue**. +2. If it's not listed, open a **new issue**. 3. Include as much detail as possible: what happened, what you expected, and steps to reproduce. Logs and screenshots help. ### Suggest a new feature? @@ -48,19 +48,181 @@ Ask anything in our [Discord server](https://discord.com/invite/NAb6H3UTjK) — ### Set up Checkmate locally? -Frontend & backend: +#### Prerequisites + +- Node.js (with npm) +- Docker (for MongoDB and Redis) +- Git + +#### Step 1: Clone the Repository + +```bash +git clone https://github.com/bluewave-labs/Checkmate.git +cd Checkmate +``` + +#### Step 2: Set Up Docker Containers (MongoDB & Redis) + +Navigate to the Docker dev directory: + +```bash +cd server/docker/dev +``` + +Build the Docker images: + +```bash +./build_images.sh +``` + +Run Redis container: + +```bash +docker run -d -p 6379:6379 -v $(pwd)/redis/data:/data --name uptime_redis uptime_redis +``` + +Run MongoDB container: + +```bash +docker run -d -p 27017:27017 -v $(pwd)/mongo/data:/data/db --name uptime_database_mongo uptime_database_mongo +``` + +Navigate back to the root directory: + +```bash +cd ../../.. +``` + +#### Step 3: Set Up the Backend (Server) + +Navigate to the server directory: + +```bash +cd server +``` + +Install dependencies: + ```bash npm install +``` + +Create a `.env` file in the `server` directory with the following minimum required configuration: + +```env +CLIENT_HOST="http://localhost:5173" +JWT_SECRET="my_secret_key_change_this" +DB_CONNECTION_STRING="mongodb://localhost:27017/uptime_db" +TOKEN_TTL="99d" +ORIGIN="localhost" +LOG_LEVEL="debug" +``` + +**Environment Variables Explained:** + +- `CLIENT_HOST`: Frontend URL (default: http://localhost:5173) +- `JWT_SECRET`: Secret key for JWT tokens (change to something secure) +- `DB_CONNECTION_STRING`: MongoDB connection URL +- `ORIGIN`: Origin for CORS purposes +- `TOKEN_TTL`: Token time to live (in vercel/ms format) +- `LOG_LEVEL`: Debug level (debug, info, warn, error) + +Start the backend server: + +```bash npm run dev ``` -By default, the frontend expects the backend on `http://localhost:3001`. Update configs if needed. +The server will run at `http://localhost:52345`. + +#### Step 4: Set Up the Frontend (Client) + +Open a new terminal window and navigate to the client directory from the root: + +```bash +cd client +``` + +Install dependencies: + +```bash +npm install +``` + +Create a `.env` file in the `client` directory: + +```env +VITE_APP_API_BASE_URL="http://localhost:52345/api/v1" +VITE_APP_LOG_LEVEL="debug" +``` + +**Environment Variables Explained:** + +- `VITE_APP_API_BASE_URL`: Backend API URL +- `VITE_APP_LOG_LEVEL`: Log level (none, error, warn, debug, info) + +Start the frontend: + +```bash +npm run dev +``` + +The client will run at `http://localhost:5173`. + +#### Step 5: Access the Application + +Open your browser and navigate to: + +- **Frontend**: http://localhost:5173 +- **Backend API**: http://localhost:52345 +- **API Documentation**: http://localhost:52345/api-docs + +#### Managing Docker Containers + +Stop containers: + +```bash +docker stop uptime_redis uptime_database_mongo +``` + +Start containers: + +```bash +docker start uptime_redis uptime_database_mongo +``` + +Remove containers (if needed): + +```bash +docker rm uptime_redis uptime_database_mongo +``` + +#### Troubleshooting + +**Port already in use:** + +- Check if another service is using ports 5173, 52345, 27017, or 6379 +- Stop the conflicting service or change the port in `.env` files + +**MongoDB/Redis connection issues:** + +- Verify containers are running: `docker ps` +- Check container logs: `docker logs uptime_database_mongo` or `docker logs uptime_redis` + +**Module not found errors:** + +- Ensure you ran `npm install` in both `client` and `server` directories + +**Need more help?** + +- Check the [full documentation](https://docs.checkmate.so) +- Ask on [Discord](https://discord.com/invite/NAb6H3UTjK) ### Start contributing code? 1. Pick or open an issue (check `good-first-issue`s first) 2. (optional but highly suggested) Read a detailed structure of [Checkmate](https://deepwiki.com/bluewave-labs/Checkmate) if you would like to deep dive into the architecture. -3. Ask to be assigned. If there is alrady someone assigned and it's been more than 7 days, you can raise the flag and ask to be assigned as well. +3. Ask to be assigned. If there is already someone assigned and it's been more than 7 days, you can raise the flag and ask to be assigned as well. 4. Create a branch from `develop`. 5. Write your code. 6. Run and test locally. @@ -75,6 +237,7 @@ Docs live in [checkmate-documentation](https://github.com/bluewave-labs/checkmat ### Help with translations? We use [PoEditor](https://poeditor.com) for translations. You can: + - [Sign up and join your language team](https://poeditor.com/join/project/lRUoGZFCsJ). - Translate UI strings. - Ask questions on Discord in the relevant #translations channel. @@ -84,6 +247,7 @@ Make sure all new UI strings in code use `t('key')`. ### Submit a pull request? Follow the [pull request checklist](#pull-request-checklist). Your PR should: + - Be focused on one issue. - Be tested locally. - Use our linting and translation rules. @@ -123,7 +287,6 @@ If one or more of these are missing, we may ask you to update your pull request ## Branching model - - Code contributions should go to the `develop` branch. - `master` is used for stable releases. - Use descriptive branch names, like `fix/login-error` or `feat/add-alerts`. @@ -137,13 +300,10 @@ If one or more of these are missing, we may ask you to update your pull request ## Thank you -Thanks for making Checkmate better. We mean it. Whether it’s your first pull request or your 50th, we’re excited to build with you. +Thanks for making Checkmate better. We mean it. Whether it's your first pull request or your 50th, we're excited to build with you. - -PS: feel free to introduce yourself on [Discord](https://discord.gg/YOUR-DISCORD-LINK) and say hi. +PS: feel free to introduce yourself on [Discord](https://discord.gg/NAb6H3UTjK) and say hi. -- Checkmate team Also make sure you read the [document about how to make a good pull request](/PULLREQUESTS.md). - - From a1c04ba35eacf1e57e230390c7783b1e6fcc7666 Mon Sep 17 00:00:00 2001 From: a-y-a-n-das Date: Thu, 2 Oct 2025 20:43:00 +0530 Subject: [PATCH 065/101] fixed the format --- server/src/db/v1/modules/monitorModule.js | 1138 ++++++++++----------- 1 file changed, 569 insertions(+), 569 deletions(-) diff --git a/server/src/db/v1/modules/monitorModule.js b/server/src/db/v1/modules/monitorModule.js index 55cf37799..f795a7f2e 100755 --- a/server/src/db/v1/modules/monitorModule.js +++ b/server/src/db/v1/modules/monitorModule.js @@ -1,578 +1,578 @@ import { - buildUptimeDetailsPipeline, - buildHardwareDetailsPipeline, - buildMonitorSummaryByTeamIdPipeline, - buildMonitorsByTeamIdPipeline, - buildMonitorsAndSummaryByTeamIdPipeline, - buildMonitorsWithChecksByTeamIdPipeline, - buildFilteredMonitorsByTeamIdPipeline, + buildUptimeDetailsPipeline, + buildHardwareDetailsPipeline, + buildMonitorSummaryByTeamIdPipeline, + buildMonitorsByTeamIdPipeline, + buildMonitorsAndSummaryByTeamIdPipeline, + buildMonitorsWithChecksByTeamIdPipeline, + buildFilteredMonitorsByTeamIdPipeline, } from "./monitorModuleQueries.js"; const SERVICE_NAME = "monitorModule"; class MonitorModule { - constructor({ Monitor, MonitorStats, Check, stringService, fs, path, fileURLToPath, ObjectId, NormalizeData, NormalizeDataUptimeDetails }) { - this.Monitor = Monitor; - this.MonitorStats = MonitorStats; - this.Check = Check; - this.stringService = stringService; - this.fs = fs; - this.path = path; - this.fileURLToPath = fileURLToPath; - this.ObjectId = ObjectId; - this.NormalizeData = NormalizeData; - this.NormalizeDataUptimeDetails = NormalizeDataUptimeDetails; - - const __filename = fileURLToPath(import.meta.url); - const __dirname = path.dirname(__filename); - - this.demoMonitorsPath = path.resolve(__dirname, "../../../utils/demoMonitors.json"); - } - - // Helper - calculateUptimeDuration = (checks) => { - if (!checks || checks.length === 0) { - return 0; - } - const latestCheck = new Date(checks[0].createdAt); - let latestDownCheck = 0; - - for (let i = checks.length - 1; i >= 0; i--) { - if (checks[i].status === false) { - latestDownCheck = new Date(checks[i].createdAt); - break; - } - } - - // If no down check is found, uptime is from the last check to now - if (latestDownCheck === 0) { - return Date.now() - new Date(checks[checks.length - 1].createdAt); - } - - // Otherwise the uptime is from the last check to the last down check - return latestCheck - latestDownCheck; - }; - - // Helper - getLastChecked = (checks) => { - if (!checks || checks.length === 0) { - return 0; // Handle case when no checks are available - } - // Data is sorted newest->oldest, so last check is the most recent - return new Date() - new Date(checks[0].createdAt); - }; - getLatestResponseTime = (checks) => { - if (!checks || checks.length === 0) { - return 0; - } - - return checks[0]?.responseTime ?? 0; - }; - - // Helper - getAverageResponseTime = (checks) => { - if (!checks || checks.length === 0) { - return 0; - } - - const validChecks = checks.filter((check) => typeof check.responseTime === "number"); - if (validChecks.length === 0) { - return 0; - } - const aggResponseTime = validChecks.reduce((sum, check) => { - return sum + check.responseTime; - }, 0); - return aggResponseTime / validChecks.length; - }; - - // Helper - getUptimePercentage = (checks) => { - if (!checks || checks.length === 0) { - return 0; - } - const upCount = checks.reduce((count, check) => { - return check.status === true ? count + 1 : count; - }, 0); - return (upCount / checks.length) * 100; - }; - - // Helper - getIncidents = (checks) => { - if (!checks || checks.length === 0) { - return 0; // Handle case when no checks are available - } - return checks.reduce((acc, check) => { - return check.status === false ? (acc += 1) : acc; - }, 0); - }; - - // Helper - getDateRange = (dateRange) => { - const startDates = { - recent: new Date(new Date().setHours(new Date().getHours() - 2)), - day: new Date(new Date().setDate(new Date().getDate() - 1)), - week: new Date(new Date().setDate(new Date().getDate() - 7)), - month: new Date(new Date().setMonth(new Date().getMonth() - 1)), - all: new Date(0), - }; - return { - start: startDates[dateRange], - end: new Date(), - }; - }; - - //Helper - getMonitorChecks = async (monitorId, dateRange, sortOrder) => { - const indexSpec = { - monitorId: 1, - updatedAt: sortOrder, // This will be 1 or -1 - }; - - const [checksAll, checksForDateRange] = await Promise.all([ - this.Check.find({ monitorId }).sort({ createdAt: sortOrder }).hint(indexSpec).lean(), - this.Check.find({ - monitorId, - createdAt: { $gte: dateRange.start, $lte: dateRange.end }, - }) - .hint(indexSpec) - .lean(), - ]); - - return { checksAll, checksForDateRange }; - }; - - // Helper - processChecksForDisplay = (normalizeData, checks, numToDisplay, normalize) => { - let processedChecks = checks; - if (numToDisplay && checks.length > numToDisplay) { - const n = Math.ceil(checks.length / numToDisplay); - processedChecks = checks.filter((_, index) => index % n === 0); - } - return normalize ? normalizeData(processedChecks, 1, 100) : processedChecks; - }; - - // Helper - groupChecksByTime = (checks, dateRange) => { - return checks.reduce((acc, check) => { - // Validate the date - const checkDate = new Date(check.createdAt); - if (Number.isNaN(checkDate.getTime()) || checkDate.getTime() === 0) { - return acc; - } - - const time = dateRange === "day" ? checkDate.setMinutes(0, 0, 0) : checkDate.toISOString().split("T")[0]; - - if (!acc[time]) { - acc[time] = { time, checks: [] }; - } - acc[time].checks.push(check); - return acc; - }, {}); - }; - - // Helper - calculateGroupStats = (group) => { - const totalChecks = group.checks.length; - - const checksWithResponseTime = group.checks.filter((check) => typeof check.responseTime === "number" && !Number.isNaN(check.responseTime)); - - return { - time: group.time, - uptimePercentage: this.getUptimePercentage(group.checks), - totalChecks, - totalIncidents: group.checks.filter((check) => !check.status).length, - avgResponseTime: - checksWithResponseTime.length > 0 - ? checksWithResponseTime.reduce((sum, check) => sum + check.responseTime, 0) / checksWithResponseTime.length - : 0, - }; - }; - - getAllMonitors = async () => { - try { - const monitors = await this.Monitor.find(); - return monitors; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "getAllMonitors"; - throw error; - } - }; - - getMonitorById = async (monitorId) => { - try { - const monitor = await this.Monitor.findById(monitorId); - if (monitor === null || monitor === undefined) { - const error = new Error(this.stringService.getDbFindMonitorById(monitorId)); - error.status = 404; - throw error; - } - - return monitor; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "getMonitorById"; - throw error; - } - }; - - getMonitorsByIds = async (monitorIds) => { - try { - const objectIds = monitorIds.map((id) => new this.ObjectId(id)); - return await this.Monitor.find({ _id: { $in: objectIds } }, { _id: 1, teamId: 1 }).lean(); - } catch (error) { - error.service = SERVICE_NAME; - error.method = "getMonitorsByIds"; - throw error; - } - }; - getUptimeDetailsById = async ({ monitorId, dateRange }) => { - try { - const dates = this.getDateRange(dateRange); - const formatLookup = { - recent: "%Y-%m-%dT%H:%M:00Z", - day: "%Y-%m-%dT%H:00:00Z", - week: "%Y-%m-%dT00:00:00Z", - month: "%Y-%m-%dT00:00:00Z", - }; - - const dateString = formatLookup[dateRange]; - - const results = await this.Check.aggregate(buildUptimeDetailsPipeline(monitorId, dates, dateString)); - - const monitorData = results[0]; - - monitorData.groupedUpChecks = this.NormalizeDataUptimeDetails(monitorData.groupedUpChecks, 10, 100); - - monitorData.groupedDownChecks = this.NormalizeDataUptimeDetails(monitorData.groupedDownChecks, 10, 100); - - const normalizedGroupChecks = this.NormalizeDataUptimeDetails(monitorData.groupedChecks, 10, 100); - - monitorData.groupedChecks = normalizedGroupChecks; - const monitorStats = await this.MonitorStats.findOne({ monitorId }); - return { monitorData, monitorStats }; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "getUptimeDetailsById"; - throw error; - } - }; - - getMonitorStatsById = async ({ monitorId, sortOrder, dateRange, numToDisplay, normalize }) => { - try { - // Get monitor, if we can't find it, abort with error - const monitor = await this.Monitor.findById(monitorId); - if (monitor === null || monitor === undefined) { - throw new Error(this.stringService.getDbFindMonitorById(monitorId)); - } - - // Get query params - const sort = sortOrder === "asc" ? 1 : -1; - - // Get Checks for monitor in date range requested - const dates = this.getDateRange(dateRange); - const { checksAll, checksForDateRange } = await this.getMonitorChecks(monitorId, dates, sort); - - // Build monitor stats - const monitorStats = { - ...monitor.toObject(), - uptimeDuration: this.calculateUptimeDuration(checksAll), - lastChecked: this.getLastChecked(checksAll), - latestResponseTime: this.getLatestResponseTime(checksAll), - periodIncidents: this.getIncidents(checksForDateRange), - periodTotalChecks: checksForDateRange.length, - checks: this.processChecksForDisplay(this.NormalizeData, checksForDateRange, numToDisplay, normalize), - }; - - if (monitor.type === "http" || monitor.type === "ping" || monitor.type === "docker" || monitor.type === "port" || monitor.type === "game") { - // HTTP/PING Specific stats - monitorStats.periodAvgResponseTime = this.getAverageResponseTime(checksForDateRange); - monitorStats.periodUptime = this.getUptimePercentage(checksForDateRange); - const groupedChecks = this.groupChecksByTime(checksForDateRange, dateRange); - monitorStats.aggregateData = Object.values(groupedChecks).map(this.calculateGroupStats); - } - - return monitorStats; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "getMonitorStatsById"; - throw error; - } - }; - - getHardwareDetailsById = async ({ monitorId, dateRange }) => { - try { - const monitor = await this.Monitor.findById(monitorId); - const dates = this.getDateRange(dateRange); - - const formatLookup = { - recent: "%Y-%m-%dT%H:%M:00Z", - day: "%Y-%m-%dT%H:00:00Z", - week: "%Y-%m-%dT00:00:00Z", - month: "%Y-%m-%dT00:00:00Z", - }; - const dateString = formatLookup[dateRange]; - - const hardwareStats = await this.Check.aggregate(buildHardwareDetailsPipeline(monitor, dates, dateString)); - - const stats = hardwareStats[0]; - - return { - ...monitor.toObject(), - stats, - }; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "getHardwareDetailsById"; - throw error; - } - }; - - getMonitorsByTeamId = async ({ limit, type, page, rowsPerPage, filter, field, order, teamId }) => { - limit = parseInt(limit); - page = parseInt(page); - rowsPerPage = parseInt(rowsPerPage); - if (field === undefined) { - field = "name"; - order = "asc"; - } - // Build match stage - const matchStage = { teamId: new this.ObjectId(teamId) }; - if (type !== undefined) { - matchStage.type = Array.isArray(type) ? { $in: type } : type; - } - - const summaryResult = await this.Monitor.aggregate(buildMonitorSummaryByTeamIdPipeline({ matchStage })); - const summary = summaryResult[0]; - - const monitors = await this.Monitor.aggregate(buildMonitorsByTeamIdPipeline({ matchStage, field, order })); - - const filteredMonitors = await this.Monitor.aggregate( - buildFilteredMonitorsByTeamIdPipeline({ - matchStage, - filter, - page, - rowsPerPage, - field, - order, - limit, - type, - }) - ); - - const normalizedFilteredMonitors = filteredMonitors.map((monitor) => { - if (!monitor.checks) { - return monitor; - } - monitor.checks = this.NormalizeData(monitor.checks, 10, 100); - return monitor; - }); - - return { summary, monitors, filteredMonitors: normalizedFilteredMonitors }; - }; - - getMonitorsAndSummaryByTeamId = async ({ type, explain, teamId }) => { - try { - const matchStage = { teamId: new this.ObjectId(teamId) }; - if (type !== undefined) { - matchStage.type = Array.isArray(type) ? { $in: type } : type; - } - - if (explain === true) { - return this.Monitor.aggregate(buildMonitorsAndSummaryByTeamIdPipeline({ matchStage })).explain("executionStats"); - } - - const queryResult = await this.Monitor.aggregate(buildMonitorsAndSummaryByTeamIdPipeline({ matchStage })); - const { monitors, summary } = queryResult?.[0] ?? {}; - return { monitors, summary }; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "getMonitorsAndSummaryByTeamId"; - throw error; - } - }; - - getMonitorsWithChecksByTeamId = async ({ limit, type, page, rowsPerPage, filter, field, order, teamId, explain }) => { - try { - limit = parseInt(limit); - page = parseInt(page); - rowsPerPage = parseInt(rowsPerPage); - if (field === undefined) { - field = "name"; - order = "asc"; - } - // Build match stage - const matchStage = { teamId: new this.ObjectId(teamId) }; - if (type !== undefined) { - matchStage.type = Array.isArray(type) ? { $in: type } : type; - } - - if (explain === true) { - return this.Monitor.aggregate( - buildMonitorsWithChecksByTeamIdPipeline({ - matchStage, - filter, - page, - rowsPerPage, - field, - order, - limit, - type, - }) - ).explain("executionStats"); - } - - const queryResult = await this.Monitor.aggregate( - buildMonitorsWithChecksByTeamIdPipeline({ - matchStage, - filter, - page, - rowsPerPage, - field, - order, - limit, - type, - }) - ); - const monitors = queryResult[0]?.monitors; - const count = queryResult[0]?.count; - const normalizedFilteredMonitors = monitors.map((monitor) => { - if (!monitor.checks) { - return monitor; - } - monitor.checks = this.NormalizeData(monitor.checks, 10, 100); - return monitor; - }); - return { count, monitors: normalizedFilteredMonitors }; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "getMonitorsWithChecksByTeamId"; - throw error; - } - }; - createMonitor = async ({ body, teamId, userId }) => { - try { - const monitor = new this.Monitor({ ...body, teamId, userId }); - const saved = await monitor.save(); - return saved; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "createMonitor"; - throw error; - } - }; - - createBulkMonitors = async (req) => { - try { - const monitors = req.map((item) => new this.Monitor({ ...item, notifications: undefined })); - await this.Monitor.bulkSave(monitors); - return monitors; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "createBulkMonitors"; - throw error; - } - }; - - deleteMonitor = async ({ teamId, monitorId }) => { - try { - const deletedMonitor = await this.Monitor.findOneAndDelete({ _id: monitorId, teamId }); - - if (!deletedMonitor) { - throw new Error(this.stringService.getDbFindMonitorById(monitorId)); - } - - return deletedMonitor; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "deleteMonitor"; - throw error; - } - }; - - deleteAllMonitors = async (teamId) => { - try { - const monitors = await this.Monitor.find({ teamId }); - const { deletedCount } = await this.Monitor.deleteMany({ teamId }); - - return { monitors, deletedCount }; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "deleteAllMonitors"; - throw error; - } - }; - - deleteMonitorsByUserId = async (userId) => { - try { - const result = await this.Monitor.deleteMany({ userId: userId }); - return result; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "deleteMonitorsByUserId"; - throw error; - } - }; - - editMonitor = async ({ monitorId, body }) => { - try { - const editedMonitor = await this.Monitor.findByIdAndUpdate(monitorId, body, { - new: true, - }); - return editedMonitor; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "editMonitor"; - throw error; - } - }; - - addDemoMonitors = async (userId, teamId) => { - try { - const demoMonitors = JSON.parse(this.fs.readFileSync(this.demoMonitorsPath, "utf8")); - - const demoMonitorsToInsert = demoMonitors.map((monitor) => { - return { - userId, - teamId, - name: monitor.name, - description: monitor.name, - type: "http", - url: monitor.url, - interval: 60000, - }; - }); - const insertedMonitors = await this.Monitor.insertMany(demoMonitorsToInsert); - return insertedMonitors; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "addDemoMonitors"; - throw error; - } - }; - - pauseMonitor = async ({ monitorId }) => { - try { - const monitor = await this.Monitor.findOneAndUpdate( - { _id: monitorId }, - [ - { - $set: { - isActive: { $not: "$isActive" }, - status: "$$REMOVE", - }, - }, - ], - { new: true } - ); - - return monitor; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "pauseMonitor"; - throw error; - } - }; + constructor({ Monitor, MonitorStats, Check, stringService, fs, path, fileURLToPath, ObjectId, NormalizeData, NormalizeDataUptimeDetails }) { + this.Monitor = Monitor; + this.MonitorStats = MonitorStats; + this.Check = Check; + this.stringService = stringService; + this.fs = fs; + this.path = path; + this.fileURLToPath = fileURLToPath; + this.ObjectId = ObjectId; + this.NormalizeData = NormalizeData; + this.NormalizeDataUptimeDetails = NormalizeDataUptimeDetails; + + const __filename = fileURLToPath(import.meta.url); + const __dirname = path.dirname(__filename); + + this.demoMonitorsPath = path.resolve(__dirname, "../../../utils/demoMonitors.json"); + } + + // Helper + calculateUptimeDuration = (checks) => { + if (!checks || checks.length === 0) { + return 0; + } + const latestCheck = new Date(checks[0].createdAt); + let latestDownCheck = 0; + + for (let i = checks.length - 1; i >= 0; i--) { + if (checks[i].status === false) { + latestDownCheck = new Date(checks[i].createdAt); + break; + } + } + + // If no down check is found, uptime is from the last check to now + if (latestDownCheck === 0) { + return Date.now() - new Date(checks[checks.length - 1].createdAt); + } + + // Otherwise the uptime is from the last check to the last down check + return latestCheck - latestDownCheck; + }; + + // Helper + getLastChecked = (checks) => { + if (!checks || checks.length === 0) { + return 0; // Handle case when no checks are available + } + // Data is sorted newest->oldest, so last check is the most recent + return new Date() - new Date(checks[0].createdAt); + }; + getLatestResponseTime = (checks) => { + if (!checks || checks.length === 0) { + return 0; + } + + return checks[0]?.responseTime ?? 0; + }; + + // Helper + getAverageResponseTime = (checks) => { + if (!checks || checks.length === 0) { + return 0; + } + + const validChecks = checks.filter((check) => typeof check.responseTime === "number"); + if (validChecks.length === 0) { + return 0; + } + const aggResponseTime = validChecks.reduce((sum, check) => { + return sum + check.responseTime; + }, 0); + return aggResponseTime / validChecks.length; + }; + + // Helper + getUptimePercentage = (checks) => { + if (!checks || checks.length === 0) { + return 0; + } + const upCount = checks.reduce((count, check) => { + return check.status === true ? count + 1 : count; + }, 0); + return (upCount / checks.length) * 100; + }; + + // Helper + getIncidents = (checks) => { + if (!checks || checks.length === 0) { + return 0; // Handle case when no checks are available + } + return checks.reduce((acc, check) => { + return check.status === false ? (acc += 1) : acc; + }, 0); + }; + + // Helper + getDateRange = (dateRange) => { + const startDates = { + recent: new Date(new Date().setHours(new Date().getHours() - 2)), + day: new Date(new Date().setDate(new Date().getDate() - 1)), + week: new Date(new Date().setDate(new Date().getDate() - 7)), + month: new Date(new Date().setMonth(new Date().getMonth() - 1)), + all: new Date(0), + }; + return { + start: startDates[dateRange], + end: new Date(), + }; + }; + + //Helper + getMonitorChecks = async (monitorId, dateRange, sortOrder) => { + const indexSpec = { + monitorId: 1, + updatedAt: sortOrder, // This will be 1 or -1 + }; + + const [checksAll, checksForDateRange] = await Promise.all([ + this.Check.find({ monitorId }).sort({ createdAt: sortOrder }).hint(indexSpec).lean(), + this.Check.find({ + monitorId, + createdAt: { $gte: dateRange.start, $lte: dateRange.end }, + }) + .hint(indexSpec) + .lean(), + ]); + + return { checksAll, checksForDateRange }; + }; + + // Helper + processChecksForDisplay = (normalizeData, checks, numToDisplay, normalize) => { + let processedChecks = checks; + if (numToDisplay && checks.length > numToDisplay) { + const n = Math.ceil(checks.length / numToDisplay); + processedChecks = checks.filter((_, index) => index % n === 0); + } + return normalize ? normalizeData(processedChecks, 1, 100) : processedChecks; + }; + + // Helper + groupChecksByTime = (checks, dateRange) => { + return checks.reduce((acc, check) => { + // Validate the date + const checkDate = new Date(check.createdAt); + if (Number.isNaN(checkDate.getTime()) || checkDate.getTime() === 0) { + return acc; + } + + const time = dateRange === "day" ? checkDate.setMinutes(0, 0, 0) : checkDate.toISOString().split("T")[0]; + + if (!acc[time]) { + acc[time] = { time, checks: [] }; + } + acc[time].checks.push(check); + return acc; + }, {}); + }; + + // Helper + calculateGroupStats = (group) => { + const totalChecks = group.checks.length; + + const checksWithResponseTime = group.checks.filter((check) => typeof check.responseTime === "number" && !Number.isNaN(check.responseTime)); + + return { + time: group.time, + uptimePercentage: this.getUptimePercentage(group.checks), + totalChecks, + totalIncidents: group.checks.filter((check) => !check.status).length, + avgResponseTime: + checksWithResponseTime.length > 0 + ? checksWithResponseTime.reduce((sum, check) => sum + check.responseTime, 0) / checksWithResponseTime.length + : 0, + }; + }; + + getAllMonitors = async () => { + try { + const monitors = await this.Monitor.find(); + return monitors; + } catch (error) { + error.service = SERVICE_NAME; + error.method = "getAllMonitors"; + throw error; + } + }; + + getMonitorById = async (monitorId) => { + try { + const monitor = await this.Monitor.findById(monitorId); + if (monitor === null || monitor === undefined) { + const error = new Error(this.stringService.getDbFindMonitorById(monitorId)); + error.status = 404; + throw error; + } + + return monitor; + } catch (error) { + error.service = SERVICE_NAME; + error.method = "getMonitorById"; + throw error; + } + }; + + getMonitorsByIds = async (monitorIds) => { + try { + const objectIds = monitorIds.map((id) => new this.ObjectId(id)); + return await this.Monitor.find({ _id: { $in: objectIds } }, { _id: 1, teamId: 1 }).lean(); + } catch (error) { + error.service = SERVICE_NAME; + error.method = "getMonitorsByIds"; + throw error; + } + }; + getUptimeDetailsById = async ({ monitorId, dateRange }) => { + try { + const dates = this.getDateRange(dateRange); + const formatLookup = { + recent: "%Y-%m-%dT%H:%M:00Z", + day: "%Y-%m-%dT%H:00:00Z", + week: "%Y-%m-%dT00:00:00Z", + month: "%Y-%m-%dT00:00:00Z", + }; + + const dateString = formatLookup[dateRange]; + + const results = await this.Check.aggregate(buildUptimeDetailsPipeline(monitorId, dates, dateString)); + + const monitorData = results[0]; + + monitorData.groupedUpChecks = this.NormalizeDataUptimeDetails(monitorData.groupedUpChecks, 10, 100); + + monitorData.groupedDownChecks = this.NormalizeDataUptimeDetails(monitorData.groupedDownChecks, 10, 100); + + const normalizedGroupChecks = this.NormalizeDataUptimeDetails(monitorData.groupedChecks, 10, 100); + + monitorData.groupedChecks = normalizedGroupChecks; + const monitorStats = await this.MonitorStats.findOne({ monitorId }); + return { monitorData, monitorStats }; + } catch (error) { + error.service = SERVICE_NAME; + error.method = "getUptimeDetailsById"; + throw error; + } + }; + + getMonitorStatsById = async ({ monitorId, sortOrder, dateRange, numToDisplay, normalize }) => { + try { + // Get monitor, if we can't find it, abort with error + const monitor = await this.Monitor.findById(monitorId); + if (monitor === null || monitor === undefined) { + throw new Error(this.stringService.getDbFindMonitorById(monitorId)); + } + + // Get query params + const sort = sortOrder === "asc" ? 1 : -1; + + // Get Checks for monitor in date range requested + const dates = this.getDateRange(dateRange); + const { checksAll, checksForDateRange } = await this.getMonitorChecks(monitorId, dates, sort); + + // Build monitor stats + const monitorStats = { + ...monitor.toObject(), + uptimeDuration: this.calculateUptimeDuration(checksAll), + lastChecked: this.getLastChecked(checksAll), + latestResponseTime: this.getLatestResponseTime(checksAll), + periodIncidents: this.getIncidents(checksForDateRange), + periodTotalChecks: checksForDateRange.length, + checks: this.processChecksForDisplay(this.NormalizeData, checksForDateRange, numToDisplay, normalize), + }; + + if (monitor.type === "http" || monitor.type === "ping" || monitor.type === "docker" || monitor.type === "port" || monitor.type === "game") { + // HTTP/PING Specific stats + monitorStats.periodAvgResponseTime = this.getAverageResponseTime(checksForDateRange); + monitorStats.periodUptime = this.getUptimePercentage(checksForDateRange); + const groupedChecks = this.groupChecksByTime(checksForDateRange, dateRange); + monitorStats.aggregateData = Object.values(groupedChecks).map(this.calculateGroupStats); + } + + return monitorStats; + } catch (error) { + error.service = SERVICE_NAME; + error.method = "getMonitorStatsById"; + throw error; + } + }; + + getHardwareDetailsById = async ({ monitorId, dateRange }) => { + try { + const monitor = await this.Monitor.findById(monitorId); + const dates = this.getDateRange(dateRange); + + const formatLookup = { + recent: "%Y-%m-%dT%H:%M:00Z", + day: "%Y-%m-%dT%H:00:00Z", + week: "%Y-%m-%dT00:00:00Z", + month: "%Y-%m-%dT00:00:00Z", + }; + const dateString = formatLookup[dateRange]; + + const hardwareStats = await this.Check.aggregate(buildHardwareDetailsPipeline(monitor, dates, dateString)); + + const stats = hardwareStats[0]; + + return { + ...monitor.toObject(), + stats, + }; + } catch (error) { + error.service = SERVICE_NAME; + error.method = "getHardwareDetailsById"; + throw error; + } + }; + + getMonitorsByTeamId = async ({ limit, type, page, rowsPerPage, filter, field, order, teamId }) => { + limit = parseInt(limit); + page = parseInt(page); + rowsPerPage = parseInt(rowsPerPage); + if (field === undefined) { + field = "name"; + order = "asc"; + } + // Build match stage + const matchStage = { teamId: new this.ObjectId(teamId) }; + if (type !== undefined) { + matchStage.type = Array.isArray(type) ? { $in: type } : type; + } + + const summaryResult = await this.Monitor.aggregate(buildMonitorSummaryByTeamIdPipeline({ matchStage })); + const summary = summaryResult[0]; + + const monitors = await this.Monitor.aggregate(buildMonitorsByTeamIdPipeline({ matchStage, field, order })); + + const filteredMonitors = await this.Monitor.aggregate( + buildFilteredMonitorsByTeamIdPipeline({ + matchStage, + filter, + page, + rowsPerPage, + field, + order, + limit, + type, + }) + ); + + const normalizedFilteredMonitors = filteredMonitors.map((monitor) => { + if (!monitor.checks) { + return monitor; + } + monitor.checks = this.NormalizeData(monitor.checks, 10, 100); + return monitor; + }); + + return { summary, monitors, filteredMonitors: normalizedFilteredMonitors }; + }; + + getMonitorsAndSummaryByTeamId = async ({ type, explain, teamId }) => { + try { + const matchStage = { teamId: new this.ObjectId(teamId) }; + if (type !== undefined) { + matchStage.type = Array.isArray(type) ? { $in: type } : type; + } + + if (explain === true) { + return this.Monitor.aggregate(buildMonitorsAndSummaryByTeamIdPipeline({ matchStage })).explain("executionStats"); + } + + const queryResult = await this.Monitor.aggregate(buildMonitorsAndSummaryByTeamIdPipeline({ matchStage })); + const { monitors, summary } = queryResult?.[0] ?? {}; + return { monitors, summary }; + } catch (error) { + error.service = SERVICE_NAME; + error.method = "getMonitorsAndSummaryByTeamId"; + throw error; + } + }; + + getMonitorsWithChecksByTeamId = async ({ limit, type, page, rowsPerPage, filter, field, order, teamId, explain }) => { + try { + limit = parseInt(limit); + page = parseInt(page); + rowsPerPage = parseInt(rowsPerPage); + if (field === undefined) { + field = "name"; + order = "asc"; + } + // Build match stage + const matchStage = { teamId: new this.ObjectId(teamId) }; + if (type !== undefined) { + matchStage.type = Array.isArray(type) ? { $in: type } : type; + } + + if (explain === true) { + return this.Monitor.aggregate( + buildMonitorsWithChecksByTeamIdPipeline({ + matchStage, + filter, + page, + rowsPerPage, + field, + order, + limit, + type, + }) + ).explain("executionStats"); + } + + const queryResult = await this.Monitor.aggregate( + buildMonitorsWithChecksByTeamIdPipeline({ + matchStage, + filter, + page, + rowsPerPage, + field, + order, + limit, + type, + }) + ); + const monitors = queryResult[0]?.monitors; + const count = queryResult[0]?.count; + const normalizedFilteredMonitors = monitors.map((monitor) => { + if (!monitor.checks) { + return monitor; + } + monitor.checks = this.NormalizeData(monitor.checks, 10, 100); + return monitor; + }); + return { count, monitors: normalizedFilteredMonitors }; + } catch (error) { + error.service = SERVICE_NAME; + error.method = "getMonitorsWithChecksByTeamId"; + throw error; + } + }; + createMonitor = async ({ body, teamId, userId }) => { + try { + const monitor = new this.Monitor({ ...body, teamId, userId }); + const saved = await monitor.save(); + return saved; + } catch (error) { + error.service = SERVICE_NAME; + error.method = "createMonitor"; + throw error; + } + }; + + createBulkMonitors = async (req) => { + try { + const monitors = req.map((item) => new this.Monitor({ ...item, notifications: undefined })); + await this.Monitor.bulkSave(monitors); + return monitors; + } catch (error) { + error.service = SERVICE_NAME; + error.method = "createBulkMonitors"; + throw error; + } + }; + + deleteMonitor = async ({ teamId, monitorId }) => { + try { + const deletedMonitor = await this.Monitor.findOneAndDelete({ _id: monitorId, teamId }); + + if (!deletedMonitor) { + throw new Error(this.stringService.getDbFindMonitorById(monitorId)); + } + + return deletedMonitor; + } catch (error) { + error.service = SERVICE_NAME; + error.method = "deleteMonitor"; + throw error; + } + }; + + deleteAllMonitors = async (teamId) => { + try { + const monitors = await this.Monitor.find({ teamId }); + const { deletedCount } = await this.Monitor.deleteMany({ teamId }); + + return { monitors, deletedCount }; + } catch (error) { + error.service = SERVICE_NAME; + error.method = "deleteAllMonitors"; + throw error; + } + }; + + deleteMonitorsByUserId = async (userId) => { + try { + const result = await this.Monitor.deleteMany({ userId: userId }); + return result; + } catch (error) { + error.service = SERVICE_NAME; + error.method = "deleteMonitorsByUserId"; + throw error; + } + }; + + editMonitor = async ({ monitorId, body }) => { + try { + const editedMonitor = await this.Monitor.findByIdAndUpdate(monitorId, body, { + new: true, + }); + return editedMonitor; + } catch (error) { + error.service = SERVICE_NAME; + error.method = "editMonitor"; + throw error; + } + }; + + addDemoMonitors = async (userId, teamId) => { + try { + const demoMonitors = JSON.parse(this.fs.readFileSync(this.demoMonitorsPath, "utf8")); + + const demoMonitorsToInsert = demoMonitors.map((monitor) => { + return { + userId, + teamId, + name: monitor.name, + description: monitor.name, + type: "http", + url: monitor.url, + interval: 60000, + }; + }); + const insertedMonitors = await this.Monitor.insertMany(demoMonitorsToInsert); + return insertedMonitors; + } catch (error) { + error.service = SERVICE_NAME; + error.method = "addDemoMonitors"; + throw error; + } + }; + + pauseMonitor = async ({ monitorId }) => { + try { + const monitor = await this.Monitor.findOneAndUpdate( + { _id: monitorId }, + [ + { + $set: { + isActive: { $not: "$isActive" }, + status: "$$REMOVE", + }, + }, + ], + { new: true } + ); + + return monitor; + } catch (error) { + error.service = SERVICE_NAME; + error.method = "pauseMonitor"; + throw error; + } + }; } export default MonitorModule; From 4317d1a47e352d99d5d224416a54ba9f22a41049 Mon Sep 17 00:00:00 2001 From: karenvicent Date: Thu, 2 Oct 2025 15:19:42 -0400 Subject: [PATCH 066/101] chace password by superadmin --- .../components/ChangePasswordModal/index.jsx | 25 +++++++---- .../src/Pages/v1/Account/EditUser/index.jsx | 25 ++++++----- client/src/Utils/NetworkService.js | 1 - server/src/controllers/v1/authController.js | 45 +++++++++---------- server/src/service/v1/business/userService.js | 3 +- server/src/validation/joi.js | 2 + 6 files changed, 54 insertions(+), 47 deletions(-) diff --git a/client/src/Pages/Account/components/ChangePasswordModal/index.jsx b/client/src/Pages/Account/components/ChangePasswordModal/index.jsx index 375595b29..17984c3ae 100644 --- a/client/src/Pages/Account/components/ChangePasswordModal/index.jsx +++ b/client/src/Pages/Account/components/ChangePasswordModal/index.jsx @@ -1,20 +1,20 @@ import { useState } from "react"; import { Button, Stack } from "@mui/material"; -import { GenericDialog } from "../../../../Components/Dialog/genericDialog"; -import TextInput from "../../../../Components/Inputs/TextInput"; -import PasswordTooltip from "../../../Auth/components/PasswordTooltip"; +import { GenericDialog } from "@/Components/v1/Dialog/genericDialog"; +import TextInput from "@/Components/v1/Inputs/TextInput"; +import PasswordTooltip from "@/Pages/v1/Auth/components/PasswordTooltip"; import { useTheme } from "@emotion/react"; import { useTranslation } from "react-i18next"; import { createToast } from "../../../../Utils/toastUtils"; -import { PasswordEndAdornment } from "../../../../Components/Inputs/TextInput/Adornments"; -import usePasswordFeedback from "../../../Auth/hooks/usePasswordFeedback"; +import { PasswordEndAdornment } from "@/Components/v1/Inputs/TextInput/Adornments"; +import usePasswordFeedback from "@/Pages/v1/Auth/hooks/usePasswordFeedback"; +import PropTypes from "prop-types"; -const ChangePasswordModal = ({ isSaving, isLoading, userId, changePassword, email }) => { +const ChangePasswordModal = ({ isSaving, isLoading, changePassword, email }) => { const INITIAL_FORM_STATE = { password: "", confirm: "", }; - console.log("email", email); const theme = useTheme(); const { t } = useTranslation(); const { feedback, handlePasswordFeedback } = usePasswordFeedback(); @@ -30,7 +30,6 @@ const ChangePasswordModal = ({ isSaving, isLoading, userId, changePassword, emai const onChange = (e) => { let { name, value } = e.target; const updatedForm = { ...form, [name]: value }; - //validateFields(name, value, updatedForm); setForm(updatedForm); handlePasswordFeedback(updatedForm, name, value, form, errors, setErrors); @@ -130,11 +129,19 @@ const ChangePasswordModal = ({ isSaving, isLoading, userId, changePassword, emai onClick={onsubmitChangePassword} disabled={isLoadingSubmit || !isFormValid} > - Save + {t("save")} ); }; + +ChangePasswordModal.propTypes = { + isSaving: PropTypes.bool.isRequired, + isLoading: PropTypes.bool.isRequired, + changePassword: PropTypes.func.isRequired, + email: PropTypes.string.isRequired, +}; + export default ChangePasswordModal; diff --git a/client/src/Pages/v1/Account/EditUser/index.jsx b/client/src/Pages/v1/Account/EditUser/index.jsx index 9fe307cf2..e11716683 100644 --- a/client/src/Pages/v1/Account/EditUser/index.jsx +++ b/client/src/Pages/v1/Account/EditUser/index.jsx @@ -7,8 +7,7 @@ import TextInput from "@/Components/v1/Inputs/TextInput/index.jsx"; import Search from "@/Components/v1/Inputs/Search/index.jsx"; import Button from "@mui/material/Button"; import RoleTable from "../components/RoleTable/index.jsx"; -import ChangePasswordModal from "../components/ChangePasswordModal"; - +import ChangePasswordModal from "@/Pages/Account/components/ChangePasswordModal/index.jsx"; // Utils import { useParams } from "react-router-dom"; import { useTheme } from "@emotion/react"; @@ -16,9 +15,12 @@ import { useTranslation } from "react-i18next"; import { useGetUser, useEditUser } from "../../../../Hooks/v1/userHooks.js"; import { EDITABLE_ROLES, ROLES } from "../../../../Utils/roleUtils.js"; import { useEditUserForm, useValidateEditUserForm } from "./hooks/editUser.js"; +import { useSelector } from "react-redux"; const EditUser = () => { + const { user } = useSelector((state) => state.auth); const { userId } = useParams(); + const isSameUser = user?._id === userId; const theme = useTheme(); const { t } = useTranslation(); const BREADCRUMBS = [ @@ -26,7 +28,7 @@ const EditUser = () => { { name: t("editUserPage.title"), path: "" }, ]; - const [user, isLoading, error] = useGetUser(userId); + const [userToEdit, isLoading, error] = useGetUser(userId); const [editUser, isSaving, saveError, changePassword] = useEditUser(userId); const [ form, @@ -35,7 +37,7 @@ const EditUser = () => { handleDeleteRole, searchInput, handleSearchInput, - ] = useEditUserForm(user); + ] = useEditUserForm(userToEdit); const [errors, validateForm, validateField] = useValidateEditUserForm(); const onChange = (e) => { @@ -119,13 +121,14 @@ const EditUser = () => { > {t("editUserPage.form.save")} - + {!isSameUser && ( + + )} diff --git a/client/src/Utils/NetworkService.js b/client/src/Utils/NetworkService.js index 7401994bb..6372be646 100644 --- a/client/src/Utils/NetworkService.js +++ b/client/src/Utils/NetworkService.js @@ -1139,7 +1139,6 @@ class NetworkService { async changePasswordByAdmin(config) { const { userId, passwordForm } = config; - console.log("Password form data:", passwordForm); return this.axiosInstance.put(`auth/users/${userId}/password`, passwordForm, { headers: { "Content-Type": "application/json", diff --git a/server/src/controllers/v1/authController.js b/server/src/controllers/v1/authController.js index b84aaacd6..600c6730f 100755 --- a/server/src/controllers/v1/authController.js +++ b/server/src/controllers/v1/authController.js @@ -10,10 +10,12 @@ import { editUserByIdParamValidation, editUserByIdBodyValidation, editSuperadminUserByIdBodyValidation, + editUserPasswordByIdBodyValidation, } from "../../validation/joi.js"; +import ErrorService from "../../service/v1/infrastructure/errorService.js"; const SERVICE_NAME = "authController"; - +const errorService = new ErrorService(); /** * Authentication Controller * @@ -436,7 +438,7 @@ class AuthController extends BaseController { async (req, res) => { const roles = req?.user?.role; if (!roles.includes("superadmin")) { - throw createError("Unauthorized", 403); + throw errorService.createError("Unauthorized", 403); } const userId = req.params.userId; @@ -456,29 +458,24 @@ class AuthController extends BaseController { SERVICE_NAME, "editUserById" ); - editUserPasswordById = this.asyncHandler( - async (req, res) => { - console.log("editUserPasswordById called"); - const roles = req?.user?.role; - if (!roles.includes("superadmin")) { - console.log("entro a error editUserPasswordById"); - throw createError("Unauthorized", 403); - } - - const userId = req.params.userId; - console.log("userId param:", userId); - const updates = { ...req.body }; - console.log("newPassword body:", req.body); - await editUserByIdParamValidation.validateAsync(req.params); - console.log("params validated"); - //await editUserPasswordByIdBodyValidation.validateAsync(req.body); - console.log("body validated"); - await this.userService.setPasswordByUserId( userId, updates ); - return res.success({ msg: "Password reset successfully" }); - }, - SERVICE_NAME, "editUserPasswordById" - ) + editUserPasswordById = this.asyncHandler( + async (req, res) => { + const roles = req?.user?.role; + if (!roles.includes("superadmin")) { + throw errorService.createError("Unauthorized", 403); + } + const userId = req.params.userId; + const updates = { ...req.body }; + console.log("body", req.body); + await editUserByIdParamValidation.validateAsync(req.params); + await editUserPasswordByIdBodyValidation.validateAsync(req.body); + await this.userService.setPasswordByUserId(userId, updates); + return res.success({ msg: "Password reset successfully" }); + }, + SERVICE_NAME, + "editUserPasswordById" + ); } export default AuthController; diff --git a/server/src/service/v1/business/userService.js b/server/src/service/v1/business/userService.js index 6c036a5c8..c81d9135e 100644 --- a/server/src/service/v1/business/userService.js +++ b/server/src/service/v1/business/userService.js @@ -214,7 +214,6 @@ class UserService { setPasswordByUserId = async (userId, updates) => { const updatedUser = await this.db.userModule.updateUser({ userId: userId, user: updates, file: null }); return updatedUser; - } - + }; } export default UserService; diff --git a/server/src/validation/joi.js b/server/src/validation/joi.js index 209135769..4005e2ebe 100755 --- a/server/src/validation/joi.js +++ b/server/src/validation/joi.js @@ -671,7 +671,9 @@ const editSuperadminUserByIdBodyValidation = joi.object({ }); const editUserPasswordByIdBodyValidation = joi.object({ + email: joi.string().required(), newPassword: joi.string().min(8).required().pattern(passwordPattern), + password: joi.string().min(8).required().pattern(passwordPattern), }); export { From 5159d43cad589f1b717c9625c9bb0bddd47dbfd7 Mon Sep 17 00:00:00 2001 From: karenvicent Date: Thu, 2 Oct 2025 15:40:21 -0400 Subject: [PATCH 067/101] fix save button validation --- .../Pages/Account/components/ChangePasswordModal/index.jsx | 7 ++++++- server/src/controllers/v1/authController.js | 1 - 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/client/src/Pages/Account/components/ChangePasswordModal/index.jsx b/client/src/Pages/Account/components/ChangePasswordModal/index.jsx index 17984c3ae..b3f81eebb 100644 --- a/client/src/Pages/Account/components/ChangePasswordModal/index.jsx +++ b/client/src/Pages/Account/components/ChangePasswordModal/index.jsx @@ -34,7 +34,11 @@ const ChangePasswordModal = ({ isSaving, isLoading, changePassword, email }) => handlePasswordFeedback(updatedForm, name, value, form, errors, setErrors); }; - const isFormValid = !errors.password && !errors.confirm; + const isFormValid = + form.password.length > 1 && + form.confirm.length > 1 && + !errors.password && + !errors.confirm; const onsubmitChangePassword = async (event) => { event.preventDefault(); if (!isFormValid) return; @@ -57,6 +61,7 @@ const ChangePasswordModal = ({ isSaving, isLoading, changePassword, email }) => setIsLoadingSubmit(false); } }; + return ( <> + ( + + )} + /> + ( + + )} + /> + + + + - + ); }; diff --git a/client/src/Pages/v2/Auth/Register.tsx b/client/src/Pages/v2/Auth/Register.tsx index 2a02c401f..d4565c66c 100644 --- a/client/src/Pages/v2/Auth/Register.tsx +++ b/client/src/Pages/v2/Auth/Register.tsx @@ -1,14 +1,17 @@ +import { AuthBasePage } from "@/Components/v2/Auth"; +import { TextInput } from "@/Components/v2/Inputs"; +import { Button } from "@/Components/v2/Inputs"; +import Typography from "@mui/material/Typography"; +import Stack from "@mui/material/Stack"; + +import type { ApiResponse } from "@/Hooks/v2/UseApi"; import { zodResolver } from "@hookform/resolvers/zod"; import { z } from "zod"; import { useTranslation } from "react-i18next"; import { useForm, Controller } from "react-hook-form"; import { useTheme } from "@mui/material/styles"; -import Typography from "@mui/material/Typography"; - -import { TextInput } from "@/Components/v2/Inputs/TextInput"; -import Button from "@mui/material/Button"; -import Stack from "@mui/material/Stack"; import { usePost } from "@/Hooks/v2/UseApi"; +import { useNavigate } from "react-router"; const schema = z .object({ @@ -30,14 +33,15 @@ type FormData = z.infer; const Register = () => { const { t } = useTranslation(); const theme = useTheme(); - const { post, loading, error } = usePost("/auth/register"); + const navigate = useNavigate(); + const { post, loading, error } = usePost(); const { handleSubmit, control, formState: { errors }, } = useForm({ - resolver: zodResolver(schema), // ⮅ïļ connect Zod + resolver: zodResolver(schema), defaultValues: { email: "", password: "", @@ -45,124 +49,128 @@ const Register = () => { }); const onSubmit = async (data: FormData) => { - const result = await post(data); + const result = await post("/auth/register", data); if (result) { - console.log(result); + navigate("/v2/uptime"); } else { console.error("Login failed:", error); } }; return ( - - ( - - )} - /> - ( - - )} - /> - ( - - )} - /> - ( - - )} - /> - ( - - )} - /> - - {error && {error}} + ( + + )} + /> + ( + + )} + /> + ( + + )} + /> + ( + + )} + /> + ( + + )} + /> + + {error && {error}} + - + ); }; diff --git a/client/src/Pages/v2/Uptime/Create.tsx b/client/src/Pages/v2/Uptime/Create.tsx index a135ec84c..38b1511f4 100644 --- a/client/src/Pages/v2/Uptime/Create.tsx +++ b/client/src/Pages/v2/Uptime/Create.tsx @@ -44,7 +44,7 @@ const UptimeCreatePage = () => { mode: "onChange", }); const { response } = useGet("/notification-channels"); - const { post, loading, error } = usePost("/monitors"); + const { post, loading, error } = usePost(); const selectedType = useWatch({ control, name: "type", @@ -58,7 +58,7 @@ const UptimeCreatePage = () => { let interval = humanInterval(data.interval); if (!interval) interval = 60000; const submitData = { ...data, interval }; - const result = await post(submitData); + const result = await post("/monitors", submitData); if (result) { console.log(result); } else { From fb90f193b1509dcbcee4bee01d04794fc2178b94 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Thu, 9 Oct 2025 12:44:27 -0700 Subject: [PATCH 098/101] add fallbacks --- .../Components/v2/DesignElements/BasePage.tsx | 58 ++++++- .../v2/DesignElements/BulletPointCheck.tsx | 45 +++++ .../Components/v2/DesignElements/Fallback.tsx | 158 ++++++++++++++++++ .../Components/v2/DesignElements/index.tsx | 4 +- client/src/Pages/v2/Uptime/UptimeMonitors.tsx | 22 +-- 5 files changed, 275 insertions(+), 12 deletions(-) create mode 100644 client/src/Components/v2/DesignElements/BulletPointCheck.tsx create mode 100644 client/src/Components/v2/DesignElements/Fallback.tsx diff --git a/client/src/Components/v2/DesignElements/BasePage.tsx b/client/src/Components/v2/DesignElements/BasePage.tsx index ad516f2c6..f458cbb0a 100644 --- a/client/src/Components/v2/DesignElements/BasePage.tsx +++ b/client/src/Components/v2/DesignElements/BasePage.tsx @@ -1,7 +1,9 @@ import Stack from "@mui/material/Stack"; +import { ErrorFallback, EmptyFallback } from "./Fallback"; + import type { StackProps } from "@mui/material/Stack"; import { useTheme } from "@mui/material/styles"; - +import { useTranslation } from "react-i18next"; interface BasePageProps extends StackProps { children: React.ReactNode; } @@ -22,3 +24,57 @@ export const BasePage: React.FC = ({ ); }; + +interface BasePageWithStatesProps extends StackProps { + loading: boolean; + error: any; + items: any[]; + page: string; + actionLink?: string; + children: React.ReactNode; +} + +const isEmpty = (items: any[]) => { + if (!items) return true; + if (Array.isArray(items) && items.length === 0) return true; + return false; +}; + +export const BasePageWithStates: React.FC = ({ + loading, + error, + items, + page, + actionLink, + children, + ...props +}: BasePageWithStatesProps) => { + const { t } = useTranslation(); + + if (loading) { + return null; + } + + if (error) { + return ( + + ); + } + + if (isEmpty(items)) { + return ( + + ); + } + + return {children}; +}; diff --git a/client/src/Components/v2/DesignElements/BulletPointCheck.tsx b/client/src/Components/v2/DesignElements/BulletPointCheck.tsx new file mode 100644 index 000000000..03147e011 --- /dev/null +++ b/client/src/Components/v2/DesignElements/BulletPointCheck.tsx @@ -0,0 +1,45 @@ +import Stack from "@mui/material/Stack"; +import Typography from "@mui/material/Typography"; +import CheckOutlined from "@/assets/icons/check-outlined.svg?react"; +import { useTheme } from "@mui/material/styles"; + +export const BulletPointCheck = ({ + text, + variant = "info", +}: { + text: string; + noHighlightText?: string; + variant?: "success" | "error" | "info"; +}) => { + const theme = useTheme(); + const colors: Record = { + success: theme.palette.success.main, + error: theme.palette.error.main, + info: theme.palette.primary.contrastTextSecondary, + }; + + return ( + + + + {text} + + + ); +}; diff --git a/client/src/Components/v2/DesignElements/Fallback.tsx b/client/src/Components/v2/DesignElements/Fallback.tsx new file mode 100644 index 000000000..f7b48a113 --- /dev/null +++ b/client/src/Components/v2/DesignElements/Fallback.tsx @@ -0,0 +1,158 @@ +import Box from "@mui/material/Box"; +import Stack from "@mui/material/Stack"; +import OutputAnimation from "@/assets/Animations/output.gif"; +import DarkmodeOutput from "@/assets/Animations/darkmodeOutput.gif"; +import Typography from "@mui/material/Typography"; +import { BulletPointCheck } from "@/Components/v2/DesignElements"; +import { Button } from "@/Components/v2/Inputs"; + +import { useNavigate } from "react-router"; +import { useMediaQuery } from "@mui/material"; +import { useTheme } from "@mui/material/styles"; +import { useSelector } from "react-redux"; + +import type { BoxProps } from "@mui/material"; + +interface BaseFallbackProps extends BoxProps { + children: React.ReactNode; +} + +export const BaseFallback: React.FC = ({ children, ...props }) => { + const theme = useTheme(); + const mode = useSelector((state: any) => state.ui.mode); + const isSmall = useMediaQuery(theme.breakpoints.down("md")); + + return ( + + + + + + {children} + + + + ); +}; + +export const ErrorFallback = ({ + title, + subtitle, +}: { + title: string; + subtitle: string; +}) => { + const theme = useTheme(); + return ( + + + {title} + + {subtitle} + + ); +}; + +export const EmptyFallback = ({ + page, + title, + bullets, + actionButtonText, + actionLink, +}: { + page: string; + title: string; + bullets: any; + actionButtonText: string; + actionLink: string; +}) => { + const theme = useTheme(); + const navigate = useNavigate(); + return ( + + + + {title} + + + {bullets?.map((bullet: string, index: number) => ( + + ))} + + + + + + + ); +}; diff --git a/client/src/Components/v2/DesignElements/index.tsx b/client/src/Components/v2/DesignElements/index.tsx index 6ba0a19ac..3574daffb 100644 --- a/client/src/Components/v2/DesignElements/index.tsx +++ b/client/src/Components/v2/DesignElements/index.tsx @@ -1,7 +1,9 @@ export { SplitBox as HorizontalSplitBox, ConfigBox } from "./SplitBox"; -export { BasePage } from "./BasePage"; +export { BasePage, BasePageWithStates } from "./BasePage"; export { BGBox, UpStatusBox, DownStatusBox, PausedStatusBox } from "./StatusBox"; export { DataTable as Table, Pagination } from "./Table"; export { GradientBox, StatBox } from "./StatBox"; export { BaseBox } from "./BaseBox"; export { StatusLabel } from "./StatusLabel"; +export { BaseFallback, ErrorFallback, EmptyFallback } from "./Fallback"; +export { BulletPointCheck } from "./BulletPointCheck"; diff --git a/client/src/Pages/v2/Uptime/UptimeMonitors.tsx b/client/src/Pages/v2/Uptime/UptimeMonitors.tsx index 57865d929..e5a151eb1 100644 --- a/client/src/Pages/v2/Uptime/UptimeMonitors.tsx +++ b/client/src/Pages/v2/Uptime/UptimeMonitors.tsx @@ -1,5 +1,5 @@ import { - BasePage, + BasePageWithStates, UpStatusBox, DownStatusBox, PausedStatusBox, @@ -18,10 +18,10 @@ const UptimeMonitors = () => { const theme = useTheme(); const isSmall = useMediaQuery(theme.breakpoints.down("md")); - const { response, loading, refetch } = useGet( + const { response, isValidating, error, refetch } = useGet( "/monitors?embedChecks=true", {}, - { refreshInterval: 30000 } + { refreshInterval: 30000, keepPreviousData: true } ); const monitors: IMonitor[] = response?.data ?? ([] as IMonitor[]); @@ -43,14 +43,16 @@ const UptimeMonitors = () => { } ); - if (monitors.length === 0 && !loading) { - return "No monitors found"; - } - return ( - + { monitors={monitors} refetch={refetch} /> - + ); }; From ab3e6cdff92bb5ad071f2371e4602a39d1dabeb7 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Thu, 9 Oct 2025 13:53:34 -0700 Subject: [PATCH 099/101] normalzie on FE --- .../v2/DesignElements/StatusLabel.tsx | 10 ++++- .../Components/v2/Layouts/Sidebar/Menu.tsx | 32 ++++++++-------- .../v2/Monitors/ChartResponseTime.tsx | 14 ++++--- .../v2/Monitors/HistogramResponseTime.tsx | 28 ++++---------- .../v2/Monitors/HistogramStatus.tsx | 22 ++++++----- client/src/Pages/v2/Uptime/MonitorTable.tsx | 7 +++- client/src/Types/Check.ts | 1 + client/src/Utils/DataUtils.ts | 37 +++++++++++++++++++ 8 files changed, 97 insertions(+), 54 deletions(-) create mode 100644 client/src/Utils/DataUtils.ts diff --git a/client/src/Components/v2/DesignElements/StatusLabel.tsx b/client/src/Components/v2/DesignElements/StatusLabel.tsx index 69b788e96..d601785f9 100644 --- a/client/src/Components/v2/DesignElements/StatusLabel.tsx +++ b/client/src/Components/v2/DesignElements/StatusLabel.tsx @@ -5,7 +5,13 @@ import type { MonitorStatus } from "@/Types/Monitor"; import { getStatusPalette } from "@/Utils/MonitorUtils"; import { useTheme } from "@mui/material/styles"; -export const StatusLabel = ({ status }: { status: MonitorStatus }) => { +export const StatusLabel = ({ + status, + isActive, +}: { + status: MonitorStatus; + isActive: boolean; +}) => { const theme = useTheme(); const palette = getStatusPalette(status); const transformedText = status.charAt(0).toUpperCase() + status.slice(1).toLowerCase(); @@ -29,7 +35,7 @@ export const StatusLabel = ({ status }: { status: MonitorStatus }) => { borderRadius="50%" marginRight="5px" /> - {transformedText} + {isActive === false ? "Paused" : transformedText} ); }; diff --git a/client/src/Components/v2/Layouts/Sidebar/Menu.tsx b/client/src/Components/v2/Layouts/Sidebar/Menu.tsx index 5b3ed705e..9ca555a92 100644 --- a/client/src/Components/v2/Layouts/Sidebar/Menu.tsx +++ b/client/src/Components/v2/Layouts/Sidebar/Menu.tsx @@ -14,25 +14,25 @@ import ChangeLog from "@/assets/icons/changeLog.svg?react"; export const getMenu = (t: Function) => [ { name: t("menu.uptime"), path: "v2/uptime", icon: }, - { name: t("menu.pagespeed"), path: "pagespeed", icon: }, + // { name: t("menu.pagespeed"), path: "pagespeed", icon: }, - { name: t("menu.infrastructure"), path: "infrastructure", icon: }, - { - name: t("menu.notifications"), - path: "notifications", - icon: , - }, - { name: t("menu.incidents"), path: "incidents", icon: }, + // { name: t("menu.infrastructure"), path: "infrastructure", icon: }, + // { + // name: t("menu.notifications"), + // path: "notifications", + // icon: , + // }, + // { name: t("menu.incidents"), path: "incidents", icon: }, - { name: t("menu.statusPages"), path: "status", icon: }, - { name: t("menu.maintenance"), path: "maintenance", icon: }, - { name: t("menu.logs"), path: "logs", icon: }, + // { name: t("menu.statusPages"), path: "status", icon: }, + // { name: t("menu.maintenance"), path: "maintenance", icon: }, + // { name: t("menu.logs"), path: "logs", icon: }, - { - name: t("menu.settings"), - icon: , - path: "settings", - }, + // { + // name: t("menu.settings"), + // icon: , + // path: "settings", + // }, ]; export const getBottomMenu = (t: Function) => [ diff --git a/client/src/Components/v2/Monitors/ChartResponseTime.tsx b/client/src/Components/v2/Monitors/ChartResponseTime.tsx index 97aef314d..efa00488a 100644 --- a/client/src/Components/v2/Monitors/ChartResponseTime.tsx +++ b/client/src/Components/v2/Monitors/ChartResponseTime.tsx @@ -1,6 +1,7 @@ import { BaseChart } from "./HistogramStatus"; import { BaseBox } from "../DesignElements"; import ResponseTimeIcon from "@/assets/icons/response-time-icon.svg?react"; +import { normalizeResponseTimes } from "@/Utils/DataUtils"; import { AreaChart, Area, @@ -18,7 +19,7 @@ import { tooltipDateFormatLookup, } from "@/Utils/TimeUtils"; import { useTheme } from "@mui/material/styles"; -import type { GroupedCheck } from "@/Types/Check"; +import type { Check, GroupedCheck } from "@/Types/Check"; import { useSelector } from "react-redux"; type XTickProps = { @@ -68,7 +69,7 @@ const ResponseTimeToolTip: React.FC = ({ if (!active) return null; const format = tooltipDateFormatLookup(range); - const responseTime = Math.floor(payload?.[0]?.value || 0); + const responseTime = Math.floor(payload?.[0]?.payload?.avgResponseTime || 0); return ( {formatDateWithTz(label, format, uiTimezone)} @@ -86,7 +87,10 @@ export const ChartResponseTime = ({ }) => { const theme = useTheme(); const uiTimezone = useSelector((state: any) => state.ui.timezone); - + const normalized = normalizeResponseTimes( + checks, + "avgResponseTime" + ); return ( } @@ -96,7 +100,7 @@ export const ChartResponseTime = ({ width="100%" height={300} > - + diff --git a/client/src/Components/v2/Monitors/HistogramResponseTime.tsx b/client/src/Components/v2/Monitors/HistogramResponseTime.tsx index d19ddbf53..1925ac93c 100644 --- a/client/src/Components/v2/Monitors/HistogramResponseTime.tsx +++ b/client/src/Components/v2/Monitors/HistogramResponseTime.tsx @@ -3,24 +3,20 @@ import Box from "@mui/material/Box"; import { useTheme } from "@mui/material/styles"; import type { Check } from "@/Types/Check"; import { HistogramResponseTimeTooltip } from "@/Components/v2/Monitors/HistogramResponseTimeTooltip"; +import { normalizeResponseTimes } from "@/Utils/DataUtils"; export const HistogramResponseTime = ({ checks }: { checks: Check[] }) => { + const normalChecks = normalizeResponseTimes(checks, "responseTime"); let data = Array(); - data = checks.map((c) => Math.max(c.responseTime, 1)); - - const logResponses = data.map((r) => Math.log10(r)); - const logMin = Math.min(...logResponses); - const logMax = Math.max(...logResponses); - - if (!checks) { + if (!normalChecks || normalChecks.length === 0) { return null; } - if (checks.length !== 25) { - const placeholders = Array(25 - checks.length).fill("placeholder"); - data = [...checks, ...placeholders]; + if (normalChecks.length !== 25) { + const placeholders = Array(25 - normalChecks.length).fill("placeholder"); + data = [...normalChecks, ...placeholders]; } else { - data = checks; + data = normalChecks; } const theme = useTheme(); @@ -38,14 +34,6 @@ export const HistogramResponseTime = ({ checks }: { checks: Check[] }) => { }} > {data.map((check, index) => { - const safeResponse = Math.max(check.responseTime, 1); - const logValue = Math.log10(safeResponse); - const minHeight = 10; - const barHeight = - logMax === logMin - ? 100 - : Math.max(minHeight, ((logValue - logMin) / (logMax - logMin)) * 100); - if (check === "placeholder") { return ( { position="absolute" bottom={0} width="100%" - height={`${barHeight}%`} + height={`${check.normalResponseTime}%`} bgcolor={ check.status ? theme.palette.success.lowContrast diff --git a/client/src/Components/v2/Monitors/HistogramStatus.tsx b/client/src/Components/v2/Monitors/HistogramStatus.tsx index aeacf31d2..2e10a8042 100644 --- a/client/src/Components/v2/Monitors/HistogramStatus.tsx +++ b/client/src/Components/v2/Monitors/HistogramStatus.tsx @@ -9,6 +9,7 @@ import IncidentsIcon from "@/assets/icons/incidents.svg?react"; import type { GroupedCheck } from "@/Types/Check"; import type { MonitorStatus } from "@/Types/Monitor"; +import { normalizeResponseTimes } from "@/Utils/DataUtils"; import { useState } from "react"; import { formatDateWithTz } from "@/Utils/TimeUtils"; import { useSelector } from "react-redux"; @@ -122,8 +123,9 @@ export const HistogramStatus = ({ const theme = useTheme(); const [idx, setIdx] = useState(null); const dateFormat = range === "1d" || range === "2h" ? "MMM D, h A" : "MMM D"; + const normalChecks = normalizeResponseTimes(checks, "avgResponseTime"); - if (checks.length === 0) { + if (normalChecks.length === 0) { return ( { + const totalChecks = normalChecks.reduce((count, check) => { return count + check.count; }, 0); @@ -161,12 +163,12 @@ export const HistogramStatus = ({ Total checks {idx ? ( - {checks[idx].count} + {normalChecks[idx].count} - {formatDateWithTz(checks[idx]._id, dateFormat, uiTimezone)} + {formatDateWithTz(normalChecks[idx]._id, dateFormat, uiTimezone)} ) : ( @@ -178,26 +180,26 @@ export const HistogramStatus = ({ width="100%" height={155} > - + } /> - {checks?.map((groupedCheck, idx) => { - const fillColor = getResponseTimeColor(groupedCheck.avgResponseTime); + {normalChecks?.map((groupedCheck, idx) => { + const fillColor = getResponseTimeColor(groupedCheck.normalResponseTime); return ( setIdx(idx)} diff --git a/client/src/Pages/v2/Uptime/MonitorTable.tsx b/client/src/Pages/v2/Uptime/MonitorTable.tsx index a7b1d203f..209784309 100644 --- a/client/src/Pages/v2/Uptime/MonitorTable.tsx +++ b/client/src/Pages/v2/Uptime/MonitorTable.tsx @@ -104,7 +104,12 @@ export const MonitorTable = ({ id: "status", content: t("status"), render: (row) => { - return ; + return ( + + ); }, }, { diff --git a/client/src/Types/Check.ts b/client/src/Types/Check.ts index 542507a4b..cccc56f3d 100644 --- a/client/src/Types/Check.ts +++ b/client/src/Types/Check.ts @@ -27,6 +27,7 @@ export interface Check { status: string; message: string; responseTime: number; + normalResponseTime?: number; httpStatusCode: number; ack: boolean; expiry: string; diff --git a/client/src/Utils/DataUtils.ts b/client/src/Utils/DataUtils.ts new file mode 100644 index 000000000..3878d7d6a --- /dev/null +++ b/client/src/Utils/DataUtils.ts @@ -0,0 +1,37 @@ +const MIN_OUT = 10; +const MAX_OUT = 100; + +export const normalizeResponseTimes = , K extends keyof T>( + checks: T[], + key: K +): (T & { normalResponseTime: number })[] => { + if (!Array.isArray(checks) || checks.length === 0) + return checks as (T & { + normalResponseTime: number; + })[]; + + if (checks.length === 1) { + return [ + { + ...checks[0], + normalResponseTime: 50, + }, + ]; + } + + const { min, max } = checks.reduce( + (acc, check) => { + if (check[key] > acc.max) acc.max = check[key]; + if (check[key] < acc.min) acc.min = check[key]; + return acc; + }, + { max: -Infinity, min: Infinity } + ); + + const range = max - min || 1; + + return checks.map((check) => ({ + ...check, + normalResponseTime: MIN_OUT + ((check[key] - min) * (MAX_OUT - MIN_OUT)) / range, + })); +}; From 78b5cc9c6f81367f8935971201737741066eb65e Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Thu, 9 Oct 2025 13:55:39 -0700 Subject: [PATCH 100/101] ts --- .../Components/v2/DesignElements/StatusLabel.tsx | 2 +- .../src/Components/v2/Layouts/Sidebar/Menu.tsx | 16 ++++++++-------- .../Components/v2/Monitors/ChartResponseTime.tsx | 2 +- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/client/src/Components/v2/DesignElements/StatusLabel.tsx b/client/src/Components/v2/DesignElements/StatusLabel.tsx index d601785f9..a314c09b0 100644 --- a/client/src/Components/v2/DesignElements/StatusLabel.tsx +++ b/client/src/Components/v2/DesignElements/StatusLabel.tsx @@ -10,7 +10,7 @@ export const StatusLabel = ({ isActive, }: { status: MonitorStatus; - isActive: boolean; + isActive?: boolean; }) => { const theme = useTheme(); const palette = getStatusPalette(status); diff --git a/client/src/Components/v2/Layouts/Sidebar/Menu.tsx b/client/src/Components/v2/Layouts/Sidebar/Menu.tsx index 9ca555a92..f0f99f33a 100644 --- a/client/src/Components/v2/Layouts/Sidebar/Menu.tsx +++ b/client/src/Components/v2/Layouts/Sidebar/Menu.tsx @@ -1,12 +1,12 @@ -import Notifications from "@/assets/icons/notifications.svg?react"; +// import Notifications from "@/assets/icons/notifications.svg?react"; import Monitors from "@/assets/icons/monitors.svg?react"; -import PageSpeed from "@/assets/icons/page-speed.svg?react"; -import Integrations from "@/assets/icons/integrations.svg?react"; -import Incidents from "@/assets/icons/incidents.svg?react"; -import StatusPages from "@/assets/icons/status-pages.svg?react"; -import Maintenance from "@/assets/icons/maintenance.svg?react"; -import Logs from "@/assets/icons/logs.svg?react"; -import Settings from "@/assets/icons/settings.svg?react"; +// import PageSpeed from "@/assets/icons/page-speed.svg?react"; +// import Integrations from "@/assets/icons/integrations.svg?react"; +// import Incidents from "@/assets/icons/incidents.svg?react"; +// import StatusPages from "@/assets/icons/status-pages.svg?react"; +// import Maintenance from "@/assets/icons/maintenance.svg?react"; +// import Logs from "@/assets/icons/logs.svg?react"; +// import Settings from "@/assets/icons/settings.svg?react"; import Support from "@/assets/icons/support.svg?react"; import Discussions from "@/assets/icons/discussions.svg?react"; import Docs from "@/assets/icons/docs.svg?react"; diff --git a/client/src/Components/v2/Monitors/ChartResponseTime.tsx b/client/src/Components/v2/Monitors/ChartResponseTime.tsx index efa00488a..28f40f254 100644 --- a/client/src/Components/v2/Monitors/ChartResponseTime.tsx +++ b/client/src/Components/v2/Monitors/ChartResponseTime.tsx @@ -19,7 +19,7 @@ import { tooltipDateFormatLookup, } from "@/Utils/TimeUtils"; import { useTheme } from "@mui/material/styles"; -import type { Check, GroupedCheck } from "@/Types/Check"; +import type { GroupedCheck } from "@/Types/Check"; import { useSelector } from "react-redux"; type XTickProps = { From 5018c56d0ddcdb58ba2b3c0b39e0a4f9bd6514b1 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Thu, 9 Oct 2025 14:00:04 -0700 Subject: [PATCH 101/101] stikcy sidebar --- client/src/Components/v2/Layouts/RootLayout.tsx | 1 - client/src/Components/v2/Layouts/Sidebar/index.tsx | 3 +++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/client/src/Components/v2/Layouts/RootLayout.tsx b/client/src/Components/v2/Layouts/RootLayout.tsx index 87c186228..4237494b1 100644 --- a/client/src/Components/v2/Layouts/RootLayout.tsx +++ b/client/src/Components/v2/Layouts/RootLayout.tsx @@ -6,7 +6,6 @@ const RootLayout = () => { const theme = useTheme(); return ( diff --git a/client/src/Components/v2/Layouts/Sidebar/index.tsx b/client/src/Components/v2/Layouts/Sidebar/index.tsx index b05716aee..a17b9fa13 100644 --- a/client/src/Components/v2/Layouts/Sidebar/index.tsx +++ b/client/src/Components/v2/Layouts/Sidebar/index.tsx @@ -37,6 +37,9 @@ export const SideBar = () => {