From 4a093e2b97dff0fde711c891a4aee6e55d478945 Mon Sep 17 00:00:00 2001
From: Alex Holliday
+ 🇺🇸 English |
+ 🇸🇦 العربية |
+ 🇪🇸 Català |
+ 🇨🇿 Čeština |
+ 🇩🇪 Deutsch |
+ 🇪🇸 Español |
+ 🇫🇮 Suomi |
+ 🇫🇷 Français |
+ 🇮🇹 Italiano |
+ 🇯🇵 日本語 |
+ 🇧🇷 Português (Brasil) |
+ 🇷🇺 Русский |
+ 🇹🇭 ไทย |
+ 🇹🇷 Türkçe |
+ 🇺🇦 Українська |
+ 🇻🇳 Tiếng Việt |
+ 🇨🇳 简体中文 |
+ 🇹🇼 繁體中文
+ Un'applicazione open source per il monitoraggio di uptime e infrastruttura
+
+
+Checkmate
+
+
+
+
+
+
+Questo repository contiene sia il frontend che il backend di Checkmate, uno strumento di monitoraggio open source e self-hosted per tenere sotto controllo l'hardware dei server, l'uptime, i tempi di risposta e gli incidenti in tempo reale, con visualizzazioni accattivanti. Checkmate verifica regolarmente se un server o un sito web è raggiungibile e funziona in modo ottimale, fornendo avvisi e report in tempo reale su disponibilità, tempi di inattività e tempi di risposta dei servizi monitorati.
+
+Checkmate dispone anche di un agente, chiamato [Capture](https://github.com/bluewave-labs/capture), per raccogliere dati dai server remoti. Anche se Capture non è necessario per far funzionare Checkmate, offre informazioni aggiuntive su CPU, RAM, disco e temperatura dei tuoi server. Capture può essere eseguito su Linux, Windows, Mac, Raspberry Pi o qualsiasi altro dispositivo in grado di eseguire Go.
+
+Checkmate è stato sottoposto a stress test con oltre 1000 monitor attivi senza problemi particolari né colli di bottiglia nelle prestazioni.
+
+## 📚 Indice
+
+- [📦 Demo](#demo)
+- [🔗 Guida utente](#users-guide)
+- [🛠️ Installazione](#installation)
+- [🚀 Prestazioni](#performance)
+- [💚 Domande e idee](#questions--ideas)
+- [🧩 Funzionalità](#features)
+- [🏗️ Screenshot](#screenshots)
+- [🏗️ Tech stack](#tech-stack)
+- [🔗 Alcuni link](#a-few-links)
+- [🤝 Come contribuire](#contributing)
+
+
+
+## Demo
+
+Puoi vedere l'ultima build di [Checkmate](https://demo.checkmate.so/) in azione.
+
+Il nome utente è demouser@demo.com e la password è Demouser1! (una nota: aggiorniamo il server demo di tanto in tanto, quindi se non dovesse funzionare, scrivici sul canale Discussions).
+
+
+## Guida utente
+
+Le istruzioni per l'uso sono disponibili [qui](https://checkmate.so/docs).
+
+## Prerequisiti
+- [Docker](https://www.docker.com/) installato
+- [Git](https://git-scm.com/) installato
+
+
+## Installazione
+
+Consulta le istruzioni di installazione nel [portale della documentazione di Checkmate](https://checkmate.so/docs).
+
+In alternativa, puoi usare [Coolify](https://coolify.io/), [Elestio](https://elest.io/open-source/checkmate), [K8s](./charts/helm/checkmate/INSTALLATION.md), [Sive Host](https://sive.host) (Sudafrica), [Cloudzy](https://cloudzy.com/marketplace/checkmate) o [Pikapods](https://www.pikapods.com/) per creare rapidamente un'istanza di Checkmate. Se desideri monitorare l'infrastruttura dei tuoi server, ti servirà l'[agente Capture](https://github.com/bluewave-labs/capture). Anche il repository di Capture contiene le istruzioni di installazione.
+
+### Usare una CA personalizzata
+
+Se hai bisogno di monitorare endpoint HTTPS interni con certificati emessi da Autorità di Certificazione (CA) private (come Smallstep), consulta la nostra [Guida all'attendibilità delle CA personalizzate](../custom-ca-trust.md) per le opzioni di configurazione di Docker.
+
+Per ulteriore documentazione, consulta la [cartella docs](../).
+
+
+## Prestazioni
+
+Grazie a numerose ottimizzazioni, Checkmate opera con un consumo di memoria estremamente ridotto, richiedendo risorse minime di memoria e CPU. Ecco l'utilizzo di memoria di un'istanza Node.js in esecuzione su un server che monitora 323 server ogni minuto:
+
+
+
+Puoi vedere il consumo di memoria di MongoDB e Redis sullo stesso server (398Mb e 15Mb) per lo stesso numero di server:
+
+
+
+
+## Domande e idee
+
+Se hai domande, suggerimenti o commenti, hai diverse opzioni:
+
+- [Canale Discord](https://discord.gg/NAb6H3UTjK) (preferito)
+- [GitHub Discussions](https://github.com/bluewave-labs/Checkmate/discussions) (controlliamo di tanto in tanto)
+
+Non esitare a fare domande o a condividere le tue idee: ci farebbe piacere sentirti!
+
+
+## Funzionalità
+
+- Completamente open source, distribuibile sui tuoi server o su dispositivi domestici (es. Raspberry Pi 4 o 5)
+- Diverse opzioni di monitoraggio: Uptime, Docker, Ping, SSL, Port, Game server
+- Monitoraggio della velocità delle pagine
+- Monitoraggio dell'infrastruttura (memoria, utilizzo del disco, prestazioni della CPU, rete, ecc.) - richiede l'agente [Capture](https://github.com/bluewave-labs/capture)
+ - Monitoraggio selettivo dei dischi con selezione del mountpoint
+- Incidenti a colpo d'occhio
+- Pagine di stato con 4 temi curati
+- Notifiche via E-mail, Webhook, Discord, Slack, PagerDuty, Matrix, Microsoft Teams, Telegram, Pushover, Twilio (SMS)
+- Manutenzione programmata
+- Monitoraggio tramite query JSON
+- Supporto multilingua per arabo, cinese (semplificato), cinese (tradizionale, Taiwan), ceco, inglese, finlandese, francese, tedesco, giapponese, portoghese (Brasile), russo, spagnolo, thailandese, turco, ucraino e vietnamita
+
+
+## Ciclo di vita del monitor
+
+1. Un monitor esegue un controllo (HTTP / ping / port / hardware tramite l'agente Capture)
+2. Il risultato viene memorizzato (successo/errore + tempo di risposta)
+3. I risultati dei controlli recenti vengono valutati rispetto alla soglia di cambio stato configurata per il monitor
+4. Se la soglia di cambio stato del monitor viene raggiunta e lo stato attuale è diverso da quello precedente, lo stato del monitor cambia (es. `initializing`, `up`, `down`, `breached`)
+5. Al cambio di stato: viene creato o risolto un incidente, a seconda dello stato attuale del monitor
+6. Le notifiche vengono attivate in base alla configurazione
+
+
+## Screenshot
+
+
+
+
+
+
+
.json` file directly.
+2. Keep the JSON key structure identical to `en.json` — only translate the string values.
+3. Open a PR against `develop`. No language-selector code changes are needed.
+
+**To add a new language:**
+
+1. Add `client/src/locales/.json` with the same key structure as `en.json`, translated. If you can't translate every key, leave unfamiliar strings in English rather than removing keys — i18next falls back to the key path when a value is missing.
+2. Add the display name to `languageNames` in `client/src/Components/inputs/LanguageSelector.tsx` (e.g. `it: "Italiano"`). The selector picks up any language present in `resources` automatically.
+3. Open a PR against `develop`.
+
+If translating a whole file is more than you want to take on, open an issue asking us to add the language and we'll generate an initial machine-translated file that you (or others) can refine over time.
+
+**Note:** an older workflow that synced translations with PoEditor is still in the repo but no longer authoritative. Please treat the `.json` files in `client/src/locales/` as the source of truth.
+
### Submit a pull request?
Follow the [pull request checklist](#pull-request-checklist). Your PR should:
diff --git a/client/src/Components/inputs/LanguageSelector.tsx b/client/src/Components/inputs/LanguageSelector.tsx
index a83f3e0fa..116f45069 100644
--- a/client/src/Components/inputs/LanguageSelector.tsx
+++ b/client/src/Components/inputs/LanguageSelector.tsx
@@ -13,6 +13,7 @@ export const languageNames: Record = {
ca: "Català",
es: "Español",
fr: "Français",
+ it: "Italiano",
"pt-BR": "Português (BR)",
cs: "Čeština",
fi: "Suomi",
diff --git a/client/src/locales/it.json b/client/src/locales/it.json
new file mode 100644
index 000000000..4620284f7
--- /dev/null
+++ b/client/src/locales/it.json
@@ -0,0 +1,1662 @@
+{
+ "common": {
+ "auth": {
+ "roles": {
+ "admin": "Amministratore",
+ "demo": "Demo",
+ "superadmin": "Super amministratore",
+ "user": "Utente"
+ }
+ },
+ "alerts": {
+ "pageSpeedApiKey": {
+ "content": "Attenzione: non hai ancora aggiunto una chiave API di Google PageSpeed. Vai su Impostazioni per aggiungerne una. Senza di essa, il monitor PageSpeed non funzionerà."
+ }
+ },
+ "appName": "Checkmate",
+ "statusCode": {
+ "down": "Non attivo",
+ "networkError": {
+ "timeout": "Il server non ha risposto in tempo (richiesta scaduta).",
+ "dns": "Impossibile risolvere l'hostname (risoluzione DNS fallita).",
+ "refused": "Il server ha rifiutato la connessione (porta chiusa o servizio non in ascolto).",
+ "tls": "Handshake TLS o SSL fallito (problema di certificato o crittografia).",
+ "reset": "La connessione è stata reimpostata prima di ricevere una risposta.",
+ "unknown": "Nessuna risposta HTTP ricevuta dal target."
+ }
+ },
+ "breadcrumbs": {
+ "details": "Dettagli",
+ "home": "Home"
+ },
+ "buttons": {
+ "addMember": "Aggiungi membro",
+ "cancel": "Annulla",
+ "close": "Chiudi",
+ "configure": "Configura",
+ "confirm": "Conferma",
+ "create": "Crea",
+ "delete": "Elimina",
+ "generateToken": "Genera token",
+ "incidents": "Incidenti",
+ "inviteMember": "Invita membro",
+ "pause": "Sospendi",
+ "resume": "Riprendi",
+ "save": "Salva",
+ "next": "Avanti",
+ "back": "Indietro",
+ "sendInvite": "Invia invito",
+ "test": "Test",
+ "testNotifications": "Testa notifiche",
+ "toggleTheme": "Cambia tema chiaro e scuro",
+ "flushQueue": "Svuota coda",
+ "notFound": "Vai alla dashboard principale",
+ "resetPassword": "Reimposta password",
+ "reset": "Reimposta",
+ "clear": "Cancella",
+ "addDemo": "Aggiungi monitor demo",
+ "removeMonitors": "Rimuovi monitor",
+ "sendTestEmail": "Invia email di test",
+ "exportToJSON": "Esporta in JSON",
+ "importFromJSON": "Importa da JSON",
+ "clearFilters": "Cancella filtri",
+ "removeUser": "Rimuovi utente"
+ },
+ "charts": {
+ "labels": {
+ "averageResponseTime": "Tempo di risposta medio",
+ "downtime": "Downtime",
+ "high": "Alto",
+ "low": "Basso",
+ "uptime": "Uptime"
+ },
+ "histogram": {
+ "avg": "Media: {{value}} ms",
+ "max": "Max: {{value}} ms"
+ }
+ },
+ "dialogs": {
+ "delete": {
+ "description": "Questa azione non può essere annullata.",
+ "title": "Sei sicuro di voler eliminare questo elemento?"
+ }
+ },
+ "labels": {
+ "active": "Attivo",
+ "running": "In esecuzione",
+ "paused": "Sospeso",
+ "idle": "Inattivo",
+ "na": "N/A",
+ "resolved": "Risolto",
+ "responseTime": "Tempo di risposta"
+ },
+ "form": {
+ "role": {
+ "option": {
+ "role": {
+ "label": "Ruoli"
+ }
+ }
+ },
+ "name": {
+ "option": {
+ "firstName": {
+ "label": "Nome",
+ "placeholder": "Inserisci il tuo nome"
+ },
+ "lastName": {
+ "label": "Cognome",
+ "placeholder": "Inserisci il tuo cognome"
+ }
+ }
+ },
+ "email": {
+ "option": {
+ "email": {
+ "label": "Email",
+ "placeholder": "you@example.com"
+ }
+ }
+ }
+ },
+ "table": {
+ "empty": "Nessun elemento da mostrare",
+ "headers": {
+ "actions": "Azioni",
+ "dateTime": "Data e ora",
+ "message": "Messaggio",
+ "monitor": "Monitor",
+ "monitorId": "ID monitor",
+ "name": "Nome",
+ "status": "Stato",
+ "type": "Tipo",
+ "url": "URL",
+ "interval": "Intervallo",
+ "active": "Attivo",
+ "responseTime": "Tempo di risposta",
+ "tags": "Tag"
+ }
+ }
+ },
+ "components": {
+ "pageHeader": {
+ "links": {
+ "support": "Supporto",
+ "discussions": "Discussioni",
+ "docs": "Documentazione",
+ "changelog": "Changelog"
+ }
+ },
+ "imageUpload": {
+ "clickToUpload": "Clicca per caricare",
+ "dragAndDrop": "o trascina e rilascia",
+ "supportedFormats": "Formati supportati",
+ "maxSize": "Dimensione massima",
+ "orDragAndDrop": "o trascina e rilascia",
+ "errors": {
+ "invalidFileSize": "La dimensione del file è troppo grande!",
+ "invalidFileFormat": "Formato file non supportato!"
+ }
+ },
+ "headerStatusPageControls": {
+ "publicLink": "Link pubblico"
+ },
+ "headerTimeRange": {
+ "labels": {
+ "day": "Giorno",
+ "month": "Mese",
+ "recent": "Recente",
+ "week": "Settimana"
+ },
+ "description": {
+ "recent": "Mostra le statistiche delle ultime 2 ore.",
+ "day": "Mostra le statistiche delle ultime 24 ore.",
+ "week": "Mostra le statistiche degli ultimi 7 giorni.",
+ "month": "Mostra le statistiche degli ultimi 30 giorni."
+ }
+ },
+ "offlineBanner": {
+ "serverUnreachable": "Impossibile raggiungere il server",
+ "retry": "Riprova",
+ "retrying": "Nuovo tentativo...",
+ "reconnected": "Connessione ripristinata"
+ },
+ "sidebar": {
+ "menu": {
+ "uptime": "Uptime",
+ "pagespeed": "Pagespeed",
+ "infrastructure": "Infrastruttura",
+ "notifications": "Notifiche",
+ "tags": "Tag",
+ "checks": "Verifiche",
+ "incidents": "Incidenti",
+ "statusPages": "Pagine di stato",
+ "maintenance": "Manutenzione",
+ "logs": "Log",
+ "settings": "Impostazioni"
+ },
+ "bottomMenu": {
+ "support": "Supporto",
+ "discussions": "Discussioni",
+ "docs": "Docs",
+ "changelog": "Changelog"
+ },
+ "accountMenu": {
+ "profile": "Profilo",
+ "password": "Password",
+ "team": "Team"
+ },
+ "starPrompt": {
+ "title": "Metti una stella a Checkmate",
+ "description": "Scopri le ultime release e aiuta a far crescere la community su GitHub"
+ },
+ "authFooter": {
+ "navControls": "Controlli",
+ "logOut": "Esci",
+ "roles": {
+ "superAdmin": "Super amministratore",
+ "admin": "Amministratore",
+ "user": "Utente",
+ "demoUser": "Utente demo"
+ }
+ }
+ },
+ "starPrompt": {
+ "title": "Metti una stella a Checkmate",
+ "description": "Scopri le ultime release e aiuta a far crescere la community su GitHub"
+ }
+ },
+ "pages": {
+ "notFound": {
+ "title": "Oh no! Hai fatto cadere il tuo sushi!",
+ "subtitle": "L'URL non esiste, oppure non hai accesso ad esso."
+ },
+ "account": {
+ "tabs": {
+ "profile": "Profilo",
+ "password": "Password",
+ "team": "Team"
+ },
+ "form": {
+ "name": {
+ "title": "Nome",
+ "description": "Aggiorna le tue informazioni personali"
+ },
+ "photo": {
+ "title": "Foto profilo",
+ "description": "Carica una foto profilo"
+ },
+ "currentPassword": {
+ "title": "Password attuale",
+ "description": "Inserisci la tua password attuale per verificare la tua identità",
+ "option": {
+ "label": "Password attuale",
+ "placeholder": "Inserisci la password attuale"
+ }
+ },
+ "newPassword": {
+ "title": "Nuova password",
+ "description": "Scegli una password sicura con almeno 8 caratteri",
+ "option": {
+ "newPassword": {
+ "label": "Nuova password",
+ "placeholder": "Inserisci la nuova password"
+ },
+ "confirm": {
+ "label": "Conferma password",
+ "placeholder": "Conferma la nuova password"
+ }
+ }
+ },
+ "deleteAccount": {
+ "title": "Elimina account",
+ "description": "Questa azione è permanente e non può essere annullata"
+ }
+ },
+ "team": {
+ "fallback": {
+ "title": "Nessun membro del team ancora",
+ "description": "Invita compagni di team via email o aggiungili direttamente. Una volta iscritti, appariranno qui con il loro ruolo."
+ },
+ "filter": {
+ "placeholder": "Filtra per ruolo",
+ "all": "Tutti i ruoli",
+ "admin": "Amministratore",
+ "member": "Membro"
+ },
+ "table": {
+ "headers": {
+ "email": "Email",
+ "role": "Ruolo",
+ "created": "Creato il"
+ }
+ },
+ "addMember": {
+ "title": "Registra un nuovo membro del team",
+ "description": "Crea un nuovo account utente. Condividi le credenziali in modo sicuro dopo la creazione."
+ },
+ "invite": {
+ "title": "Invita un membro del team",
+ "description": "Invia un invito per unirsi al tuo team",
+ "email": {
+ "label": "Indirizzo email",
+ "placeholder": "Inserisci l'indirizzo email"
+ },
+ "role": {
+ "label": "Ruolo",
+ "placeholder": "Seleziona un ruolo"
+ },
+ "linkLabel": "Link di invito"
+ }
+ },
+ "header": {
+ "title": "Account",
+ "description": "Gestisci il tuo profilo, la password, il team e le preferenze dell'area di lavoro."
+ }
+ },
+ "auth": {
+ "brandPanel": {
+ "eyebrow": "Monitoraggio infrastrutturale open source",
+ "tagline": "Tieni d'occhio ogni mossa dei tuoi server.",
+ "description": "Uptime in tempo reale, tempo di risposta e tracciamento degli incidenti - self-hosted e tuo per sempre."
+ },
+ "common": {
+ "passwordRules": {
+ "length": {
+ "beginning": "Deve essere lunga almeno",
+ "highlighted": "8 caratteri"
+ },
+ "lowercase": {
+ "beginning": "Deve contenere almeno",
+ "highlighted": "un carattere minuscolo"
+ },
+ "match": {
+ "beginning": "Le password",
+ "highlighted": "devono corrispondere"
+ },
+ "number": {
+ "beginning": "Deve contenere almeno",
+ "highlighted": "un numero"
+ },
+ "special": {
+ "beginning": "Deve contenere almeno",
+ "highlighted": "un carattere speciale (!?@#$%^&*()-_=+[]{}|;:'\",./\\~`)"
+ },
+ "uppercase": {
+ "beginning": "Deve contenere almeno",
+ "highlighted": "un carattere maiuscolo"
+ }
+ },
+ "form": {
+ "option": {
+ "email": {
+ "label": "Email",
+ "placeholder": "me@example.com"
+ },
+ "password": {
+ "label": "Password",
+ "placeholder": "********"
+ },
+ "confirmPassword": {
+ "label": "Conferma password"
+ }
+ }
+ }
+ },
+ "login": {
+ "title": "Bentornato su Checkmate!",
+ "subtitle": "Accedi per continuare",
+ "submit": "Accedi",
+ "links": {
+ "forgotPassword": {
+ "text": "Password dimenticata?",
+ "linkText": "Reimposta password"
+ },
+ "register": {
+ "text": "Non hai un account?",
+ "linkText": "Registrati qui"
+ }
+ }
+ },
+ "register": {
+ "title": "Bentornato su Checkmate!",
+ "subtitle": "Registrati per iniziare",
+ "setupNotice": "Questa pagina è disponibile solo durante la configurazione iniziale. Una volta registrato un super amministratore, altri utenti possono essere invitati dalle impostazioni del team.",
+ "submit": "Registrati"
+ },
+ "forgotPassword": {
+ "title": "Password dimenticata?",
+ "subtitle": "Nessun problema, ti invieremo le istruzioni per il ripristino.",
+ "submit": "Richiedi recupero",
+ "links": {
+ "login": {
+ "text": "Torna al",
+ "linkText": "login"
+ }
+ }
+ },
+ "setNewPassword": {
+ "title": "Reimposta la tua password",
+ "subtitle": "La tua nuova password deve essere diversa dalle password usate in precedenza."
+ }
+ },
+ "checks": {
+ "fallback": {
+ "title": "Ancora nessuna verifica",
+ "description": "Una volta creato un monitor, ogni controllo di stato e tempo di risposta apparirà qui come una verifica."
+ },
+ "selects": {
+ "monitor": {
+ "all": "Tutti i monitor"
+ },
+ "status": {
+ "all": "Tutti",
+ "down": "Non attivo",
+ "up": "Attivo"
+ }
+ },
+ "table": {
+ "empty": "Nessuna verifica non attiva in questo intervallo di tempo",
+ "headers": {
+ "statusCode": "Codice di stato",
+ "location": "Posizione"
+ }
+ },
+ "header": {
+ "title": "Verifiche",
+ "description": "Ogni controllo che Checkmate esegue sui tuoi monitor appare qui, con codice di stato e tempo di risposta."
+ }
+ },
+ "common": {
+ "monitors": {
+ "actions": {
+ "configure": "Configura",
+ "delete": "Elimina",
+ "generateToken": "Genera token",
+ "details": "Dettagli",
+ "incidents": "Incidenti",
+ "inviteMember": "Invita membro",
+ "openSite": "Apri sito",
+ "pause": "Sospendi",
+ "resume": "Riprendi",
+ "bulkSelected_one": "{{count}} monitor selezionato",
+ "bulkSelected_other": "{{count}} monitor selezionati",
+ "selectAll": "Seleziona tutti i monitor",
+ "selectMonitor": "Seleziona {{name}}"
+ },
+ "bulkPause": {
+ "alreadyPaused_one": "Il monitor selezionato è già sospeso",
+ "alreadyPaused_other": "Tutti i monitor selezionati sono già sospesi",
+ "alreadyRunning_one": "Il monitor selezionato è già in esecuzione",
+ "alreadyRunning_other": "Tutti i monitor selezionati sono già in esecuzione",
+ "paused_one": "{{count}} monitor sospeso",
+ "paused_other": "{{count}} monitor sospesi",
+ "resumed_one": "{{count}} monitor ripreso",
+ "resumed_other": "{{count}} monitor ripresi",
+ "partialFailure": "{{successCount}} monitor aggiornati, ma {{failedCount}} non sono stati sincronizzati con la coda dei job"
+ },
+ "statBoxes": {
+ "activeFor": "Attivo da",
+ "certificateExpiry": "Scadenza certificato",
+ "lastCheck": "Ultima verifica",
+ "lastResponseTime": "Ultimo tempo di risposta",
+ "serviceIsDown": "Il servizio è non attivo"
+ },
+ "status": {
+ "down": "non attivo",
+ "breached": "soglia superata",
+ "initializing": "inizializzazione",
+ "maintenance": "manutenzione",
+ "paused": "sospeso",
+ "total": "totale",
+ "up": "attivo"
+ },
+ "monitorTypes": {
+ "optionDocker": "Docker",
+ "optionGame": "Game",
+ "optionHttp": "HTTP(S)",
+ "optionPing": "Ping",
+ "optionPort": "Porta",
+ "optionPagespeed": "PageSpeed",
+ "optionHardware": "Infrastruttura",
+ "optionGrpc": "gRPC",
+ "optionWebSocket": "WebSocket",
+ "optionDns": "DNS"
+ }
+ }
+ },
+ "createMonitor": {
+ "form": {
+ "advanced": {
+ "description": "Impostazioni opzionali per casi d'uso avanzati",
+ "option": {
+ "method": {
+ "label": "Metodo di richiesta",
+ "description": {
+ "GET": "GET recupera la risposta completa incluso il body. Il matching avanzato è completamente supportato",
+ "HEAD": "HEAD recupera solo gli header della risposta per ridurre il carico sul target. Supporta il matching del codice di stato ma non il matching avanzato poiché non c'è un body."
+ }
+ },
+ "advancedMatching": {
+ "label": "Usa matching avanzato"
+ },
+ "expectedValue": {
+ "label": "Valore atteso"
+ },
+ "jsonPath": {
+ "description": "Questa espressione verrà valutata sui dati JSON della risposta e il risultato sarà usato per il matching con il valore atteso. Consulta jmespath.org per la documentazione del linguaggio di query.",
+ "label": "Espressione JSONPath"
+ },
+ "matchMethod": {
+ "label": "Metodo di matching",
+ "equal": "Uguale",
+ "include": "Include",
+ "regex": "Regex"
+ },
+ "customUpCodes": {
+ "label": "Codici di stato accettati",
+ "description": "Aggiungi qualsiasi codice non-2xx che debba essere considerato riuscito.",
+ "removeAriaLabel": "Rimuovi codice di stato"
+ }
+ },
+ "title": "Impostazioni avanzate"
+ },
+ "frequency": {
+ "description": "Con quale frequenza vuoi verificare lo stato di questo monitor?",
+ "option": {
+ "frequency": {
+ "label": "Frequenza di verifica",
+ "value": {
+ "fifteenMinutes": "15 minuti",
+ "fifteenSeconds": "15 secondi",
+ "fiveMinutes": "5 minuti",
+ "fourMinutes": "4 minuti",
+ "oneMinute": "1 minuto",
+ "tenMinutes": "10 minuti",
+ "thirtyMinutes": "30 minuti",
+ "thirtySeconds": "30 secondi",
+ "threeMinutes": "3 minuti",
+ "twoMinutes": "2 minuti"
+ }
+ }
+ },
+ "title": "Frequenza di verifica"
+ },
+ "general": {
+ "option": {
+ "container": {
+ "label": "Nome/ID container",
+ "placeholder": "my-app o abcd1234"
+ },
+ "host": {
+ "label": "Host",
+ "placeholder": "192.168.1.100 o example.com"
+ },
+ "name": {
+ "label": "Nome visualizzato",
+ "placeholder": "es. Il mio sito web"
+ },
+ "secret": {
+ "label": "Segreto di autorizzazione",
+ "placeholder": "Inserisci la tua chiave segreta"
+ },
+ "url": {
+ "label": "URL",
+ "placeholder": "https://www.google.com"
+ },
+ "game": {
+ "label": "Scegli gioco",
+ "placeholder": "Seleziona un gioco"
+ },
+ "port": {
+ "label": "Porta da monitorare",
+ "placeholder": 80
+ },
+ "grpcServiceName": {
+ "label": "Nome servizio",
+ "placeholder": "es. my.service.v1 (lascia vuoto per lo stato generale)"
+ },
+ "wsUrl": {
+ "label": "URL WebSocket",
+ "placeholder": "wss://example.com/socket"
+ },
+ "strategy": {
+ "label": "Strategia",
+ "desktop": "Desktop",
+ "mobile": "Mobile"
+ },
+ "captureUrl": {
+ "label": "Endpoint Capture",
+ "placeholder": "http://192.168.1.10:5001/api/v1/metrics"
+ },
+ "dnsServer": {
+ "label": "Server DNS",
+ "placeholder": "8.8.8.8"
+ },
+ "dnsRecordType": {
+ "label": "Tipo di record DNS",
+ "value": {
+ "A": "A",
+ "AAAA": "AAAA",
+ "CNAME": "CNAME",
+ "MX": "MX",
+ "NS": "NS",
+ "TXT": "TXT"
+ }
+ }
+ },
+ "title": "Impostazioni generali",
+ "description": {
+ "http": "Inserisci l'URL o l'IP da monitorare (es. https://example.com/ o 192.168.1.100) e aggiungi un nome visualizzato chiaro che apparirà nella dashboard.",
+ "ping": "Inserisci l'indirizzo IP o l'hostname da pingare (es. 192.168.1.100 o example.com) e aggiungi un nome visualizzato chiaro che apparirà nella dashboard.",
+ "port": "Inserisci l'URL o l'IP del server, il numero di porta e un nome visualizzato chiaro che apparirà nella dashboard.",
+ "docker": "Inserisci il nome o l'ID del container Docker. Puoi usare il nome del container (es. my-app) o l'ID del container (ID completo di 64 caratteri o ID breve).",
+ "game": "Inserisci l'indirizzo IP o l'hostname e il numero di porta da pingare (es. 192.168.1.100 o example.com) e scegli il tipo di gioco. Alcuni giochi richiedono l'abilitazione delle query del server nel loro file di configurazione — controlla la documentazione del tuo game server. Consulta la lista dei giochi supportati per i dettagli del protocollo.",
+ "pagespeed": "Traccia le prestazioni di caricamento della pagina, i Core Web Vitals e i punteggi di ottimizzazione per il tuo sito web.",
+ "grpc": "Inserisci l'hostname e la porta del server gRPC, specifica opzionalmente un nome di servizio per la verifica di stato e aggiungi un nome visualizzato.",
+ "websocket": "Inserisci l'URL WebSocket da monitorare (es. wss://example.com/socket) e aggiungi un nome visualizzato.",
+ "hardware": "Monitora CPU, memoria, utilizzo del disco e temperatura per la tua infrastruttura. L'URL dell'endpoint Capture deve includere il percorso completo /api/v1/metrics (es. http://192.168.1.10:5001/api/v1/metrics). Per il segreto di autorizzazione, incolla solo la stringa del token — non includere 'Bearer'.",
+ "dns": "Monitora se uno specifico server DNS sta rispondendo."
+ }
+ },
+ "ignoreTls": {
+ "description": "Configura la validazione del certificato TLS/SSL per le connessioni HTTPS.",
+ "option": {
+ "tls": {
+ "label": "Ignora errori TLS/SSL"
+ }
+ },
+ "title": "Impostazioni TLS/SSL"
+ },
+ "incidents": {
+ "description": "Viene usata una finestra scorrevole per determinare quando un monitor va non attivo. Lo stato di un monitor cambierà solo quando la percentuale di verifiche nella finestra scorrevole raggiunge il valore specificato.",
+ "option": {
+ "checks": {
+ "label": "Numero di verifiche nella finestra scorrevole"
+ },
+ "percentage": {
+ "label": "Quale percentuale di verifiche nella finestra scorrevole deve fallire/riuscire prima che lo stato del monitor cambi?"
+ }
+ },
+ "title": "Incidenti"
+ },
+ "notifications": {
+ "description": "Seleziona i canali di notifica che vuoi utilizzare",
+ "title": "Notifiche"
+ },
+ "tags": {
+ "description": "Seleziona i tag che vuoi applicare a questo monitor",
+ "title": "Tag"
+ },
+ "type": {
+ "description": "Seleziona il tipo di verifica da eseguire",
+ "optionDockerDescription": "Usa Docker per monitorare se un container è in esecuzione.",
+ "optionGameDescription": "Monitora se uno specifico game server è online.",
+ "optionGrpcDescription": "Monitora i servizi gRPC usando il protocollo standard Health Checking.",
+ "optionWebSocketDescription": "Monitora gli endpoint WebSocket per lo stato della connessione e il tempo di risposta.",
+ "optionHttpDescription": "Usa HTTP(S) per monitorare il tuo sito web o endpoint API.",
+ "optionPingDescription": "Usa ICMP Ping per monitorare se un server è online.",
+ "optionPortDescription": "Monitora se una specifica porta su un server è aperta.",
+ "optionDnsDescription": "Monitora se uno specifico server DNS sta rispondendo.",
+ "title": "Tipo"
+ },
+ "thresholds": {
+ "title": "Soglie di allerta",
+ "description": "Definisci le soglie alle quali devono essere attivati gli avvisi per questo monitor hardware.",
+ "option": {
+ "cpuThreshold": {
+ "label": "Soglia allerta CPU (%)"
+ },
+ "memoryThreshold": {
+ "label": "Soglia allerta memoria (%)"
+ },
+ "diskThreshold": {
+ "label": "Soglia allerta disco (%)"
+ },
+ "tempThreshold": {
+ "label": "Soglia allerta temperatura (°C)"
+ }
+ }
+ },
+ "geoChecks": {
+ "title": "Verifiche geo-distribuite",
+ "description": "Esegui verifiche da più posizioni geografiche per monitorare disponibilità e prestazioni globali.",
+ "option": {
+ "enabled": {
+ "label": "Abilita verifiche geo-distribuite"
+ },
+ "locations": {
+ "label": "Posizioni",
+ "placeholder": "Seleziona posizioni",
+ "options": {
+ "EU": "Europa",
+ "NA": "Nord America",
+ "AS": "Asia",
+ "SA": "Sud America",
+ "AF": "Africa",
+ "OC": "Oceania"
+ }
+ },
+ "interval": {
+ "label": "Intervallo di verifica",
+ "value": {
+ "fiveMinutes": "5 minuti",
+ "tenMinutes": "10 minuti",
+ "fifteenMinutes": "15 minuti",
+ "thirtyMinutes": "30 minuti"
+ }
+ }
+ }
+ },
+ "url": {
+ "title": "Monitor IP/URL sulla pagina di stato",
+ "description": "Visualizza l'indirizzo IP o l'URL del monitor sulla pagina di stato pubblica. Se disabilitato, verrà mostrato solo il nome del monitor per proteggere le informazioni sensibili.",
+ "option": {
+ "showURL": {
+ "label": "Visualizza IP/URL sulla pagina di stato",
+ "enabled": "Abilitato",
+ "disabled": "Disabilitato"
+ }
+ }
+ },
+ "stats": {
+ "title": "Cronologia monitor",
+ "description": "Cancella tutta la cronologia di monitoraggio e le statistiche per il tuo team. Questa azione è irreversibile.",
+ "buttonText": "Cancella tutte le statistiche",
+ "dialog": {
+ "title": "Cancellare tutta la cronologia di monitoraggio?",
+ "description": "Questa operazione eliminerà permanentemente tutta la cronologia di monitoraggio, le statistiche e i dati di verifica per il tuo team. Questa azione non può essere annullata.",
+ "confirm": "Sì, cancella tutte le statistiche"
+ }
+ }
+ }
+ },
+ "editUser": {
+ "form": {
+ "roles": {
+ "title": "Ruoli",
+ "description": "Assegna ruoli all'utente. È possibile selezionare più ruoli."
+ }
+ },
+ "dialog": {
+ "removeUser": {
+ "title": "Rimuovi utente",
+ "content": "Sei sicuro di voler rimuovere {{name}} dal tuo team? Questa azione non può essere annullata."
+ }
+ }
+ },
+ "incidents": {
+ "fallback": {
+ "title": "Nessun incidente — tutti i sistemi sono in salute",
+ "description": "Quando un monitor va non attivo o si riprende, l'incidente apparirà qui con tempistiche, dettagli del monitor e cronologia di risoluzione."
+ },
+ "dialog": {
+ "resolveIncident": {
+ "title": "Risolvi incidente",
+ "option": {
+ "comment": {
+ "label": "Commento (opzionale)",
+ "placeholder": "Aggiungi un commento sulla risoluzione..."
+ }
+ }
+ },
+ "details": {
+ "analysis": "Analisi incidente",
+ "comment": "Commento:",
+ "detailsLabel": "Dettagli",
+ "downtime": "Downtime:",
+ "message": "Messaggio:",
+ "monitor": "Monitor:",
+ "overview": "Panoramica",
+ "resolutionDetails": "Dettagli risoluzione",
+ "resolutionType": "Tipo:",
+ "resolutionTypes": {
+ "automatic": "Automatica",
+ "manual": "Manuale"
+ },
+ "resolve": "Risolvi incidente",
+ "resolvedAt": "Risolto il:",
+ "resolvedBy": "Risolto da:",
+ "startedAt": "Iniziato il:",
+ "status": "Stato:",
+ "statusCode": "Codice di stato:",
+ "timeline": "Cronologia",
+ "title": "Dettagli incidente",
+ "url": "URL:"
+ }
+ },
+ "filters": {
+ "allMonitors": "Tutti i monitor",
+ "monitor": "Monitor",
+ "resolutionType": "Tipo di risoluzione",
+ "resolutionTypes": {
+ "manual": "Manuale",
+ "automatic": "Automatica",
+ "all": "Tutti"
+ }
+ },
+ "summaryCard": {
+ "activeIncidents": {
+ "title": "Incidenti attivi",
+ "active_zero": "Nessun incidente attivo",
+ "active_one": "{{count}} incidente attivo",
+ "active_other": "{{count}} incidenti attivi"
+ },
+ "incidentStats": {
+ "avgResolutionTime": "Tempo medio di risoluzione",
+ "mostAffectedMonitor": "Monitor più colpito",
+ "title": "Statistiche incidenti",
+ "totalIncidents": "Incidenti totali"
+ },
+ "latestIncidents": {
+ "title": "Ultimi incidenti"
+ }
+ },
+ "table": {
+ "actions": {
+ "details": "Dettagli",
+ "goToMonitor": "Vai al monitor",
+ "resolveManually": "Risolvi manualmente"
+ },
+ "activeIncidents": "Incidenti attivi",
+ "headers": {
+ "endTime": "Ora di fine",
+ "resolutionType": "Tipo di risoluzione",
+ "startTime": "Ora di inizio",
+ "statusCode": "Codice di stato"
+ },
+ "resolvedIncidents": "Incidenti risolti",
+ "status": {
+ "active": "Attivo",
+ "resolved": "Risolto"
+ }
+ },
+ "header": {
+ "title": "Incidenti",
+ "description": "Quando i monitor vanno non attivi o si riprendono, gli incidenti appaiono qui con tempistiche, dettagli del monitor e cronologia di risoluzione."
+ }
+ },
+ "infrastructure": {
+ "charts": {
+ "labels": {
+ "cpu": "Utilizzo CPU",
+ "disk": "Utilizzo disco",
+ "memory": "Utilizzo memoria",
+ "netBytesRecv": "{{name}} - Byte ricevuti",
+ "netBytesSent": "{{name}} - Byte inviati",
+ "loopbackOnlyNotice": "Capture sta segnalando solo interfacce loopback o bridge di container (lo, lo0, docker0). Questo è normale quando Capture viene eseguito all'interno di un container che può vedere solo il proprio namespace di rete — esegui Capture direttamente sull'host per vedere l'attività di rete reale.",
+ "temp": "Temp"
+ }
+ },
+ "gauges": {
+ "cpu": {
+ "lowerLabel": "Frequenza massima",
+ "title": "Utilizzo CPU",
+ "upperLabel": "Frequenza attuale"
+ },
+ "disk": {
+ "lowerLabel": "Libero",
+ "title": "Utilizzo disco {{idx}}",
+ "upperLabel": "Utilizzato"
+ },
+ "memory": {
+ "lowerLabel": "Libero",
+ "title": "Utilizzo memoria",
+ "upperLabel": "Utilizzato"
+ }
+ },
+ "statBoxes": {
+ "avgCpuTemperature": "Temperatura media CPU",
+ "cpuFrequency": "Frequenza CPU",
+ "cpuLogical": "CPU (logica)",
+ "cpuPhysical": "CPU (fisica)",
+ "disk": "Disco",
+ "memory": "Memoria",
+ "os": "OS"
+ },
+ "table": {
+ "headers": {
+ "cpu": "CPU",
+ "disk": "Disco",
+ "memory": "Memoria"
+ }
+ },
+ "tabs": {
+ "labels": {
+ "network": "Rete",
+ "overview": "Panoramica"
+ }
+ },
+ "fallback": {
+ "actionButton": "Crea un monitor",
+ "description": "Traccia le prestazioni del server, identifica i colli di bottiglia e garantisci l'affidabilità con il monitoraggio infrastrutturale in tempo reale.",
+ "title": "Nessun monitor infrastrutturale ancora"
+ },
+ "header": {
+ "title": "Monitor infrastruttura",
+ "description": "Traccia le prestazioni del server, identifica i colli di bottiglia e garantisci l'affidabilità con il monitoraggio infrastrutturale in tempo reale."
+ }
+ },
+ "tags": {
+ "header": {
+ "title": "Tag",
+ "description": "Organizza i tuoi monitor con i tag"
+ },
+ "fallback": {
+ "title": "Ancora nessun tag",
+ "description": "Organizza i tuoi monitor con i tag. Una volta creato un tag e assegnato a un monitor, potrai filtrare per quel tag in tutta l'app e vedere i dettagli del tag qui.",
+ "actionButton": "Crea un tag"
+ },
+ "table": {
+ "headers": {
+ "color": "Colore",
+ "appearance": "Aspetto"
+ }
+ },
+ "form": {
+ "name": {
+ "title": "Nome tag",
+ "description": "Scegli un nome per il tuo tag",
+ "optionName": "Nome tag",
+ "placeholder": "es. Gruppo 1"
+ },
+ "color": {
+ "title": "Colore tag",
+ "description": "Scegli un colore per il tuo tag",
+ "optionName": "Colore tag",
+ "placeholder": "es. #13715B"
+ }
+ }
+ },
+ "logs": {
+ "fallback": {
+ "title": "Ancora nessun log",
+ "description": "Attività del server, avvisi ed errori appariranno qui man mano che Checkmate viene eseguito."
+ },
+ "queueFallback": {
+ "title": "Nessun job nella coda",
+ "description": "Le verifiche del monitor pianificate o in esecuzione appariranno qui. Una coda vuota significa che tutto è aggiornato."
+ },
+ "tabs": {
+ "diagnostics": "Diagnostica",
+ "logs": "Log",
+ "queue": "Coda job"
+ },
+ "logLevelSelect": {
+ "label": "Livello di log"
+ },
+ "workers": "Worker",
+ "workersExplainers": "Mostra i processi worker. Gli ID worker sono troncati, passa il mouse sopra per l'ID completo",
+ "jobQueue": "Coda job",
+ "jobQueueExplainer": "Le righe evidenziate sono job attualmente in elaborazione da parte di un worker.",
+ "failedJobs": "Job falliti",
+ "failedJobsExplainer": "Errori recenti per monitor — orario dell'ultimo errore, conteggio errori e motivo.",
+ "noLogs": "Nessun log trovato",
+ "metrics": {
+ "jobs": "Job",
+ "activeJobs": "Job attivi",
+ "failingJobs": "Job in errore",
+ "totalRuns": "Esecuzioni totali",
+ "totalFailures": "Errori totali"
+ },
+ "diagnostics": {
+ "sections": {
+ "runtimeStats": {
+ "title": "Statistiche runtime",
+ "description": "Istantanea del processo Node.js e della macchina host. Aggiornata ogni 5 secondi."
+ },
+ "memoryCpu": {
+ "title": "Memoria e CPU",
+ "description": "Come il processo sta utilizzando la memoria allocata e la CPU dell'host."
+ },
+ "mongoDBStats": {
+ "title": "Statistiche MongoDB",
+ "description": "Istantanea delle metriche MongoDB"
+ },
+ "mongoDBCollectionStats": {
+ "title": "Statistiche collezioni MongoDB",
+ "description": "Statistiche per le collezioni MongoDB"
+ }
+ },
+ "stats": {
+ "eventLoopDelay": "Ritardo event loop",
+ "uptime": "Uptime",
+ "usedHeapSize": "Dimensione heap utilizzata",
+ "totalHeapSize": "Dimensione heap totale",
+ "osMemoryLimit": "Limite memoria OS"
+ },
+ "mongoDBStats": {
+ "readyState": "Stato di prontezza",
+ "readsPerSecond": "Letture/s",
+ "insertsPerSecond": "Inserimenti/s",
+ "updatesPerSecond": "Aggiornamenti/s",
+ "deletesPerSecond": "Eliminazioni/s",
+ "writesPerSecond": "Scritture totali/s",
+ "host": "Host",
+ "port": "Porta",
+ "dbName": "Nome database",
+ "storageSize": "Dimensione archiviazione"
+ },
+ "mongoDBCollectionStats": {
+ "documents": "Documenti: {{count}}",
+ "bucketCount": "Conteggio bucket: {{count}}",
+ "storageSize": "Dimensione archiviazione: {{size}}",
+ "indexSize": "Dimensione indice: {{size}}",
+ "totalSize": "Dimensione totale: {{size}}",
+ "dbUsageTooltip": "{{percentage}}% dell'archiviazione DB"
+ },
+ "gauges": {
+ "heapAllocation": "Allocazione heap",
+ "heapUsage": "Utilizzo heap",
+ "heapUtilization": "Utilizzo heap",
+ "instantCpuUsage": "Utilizzo CPU istantaneo",
+ "availableMemoryPercentage": "% di memoria disponibile",
+ "allocatedPercentage": "% allocata",
+ "usedSPercentage": "% di 1s utilizzato dalla CPU",
+ "total": "Totale",
+ "used": "Utilizzato"
+ }
+ },
+ "table": {
+ "headers": {
+ "host": "Host",
+ "processId": "ID processo",
+ "workerId": "ID worker",
+ "workerMode": "Modalità worker",
+ "lastSeenAt": "Ultima visto il",
+ "state": "Stato",
+ "timestamp": "Timestamp",
+ "level": "Livello",
+ "service": "Servizio",
+ "method": "Metodo",
+ "monitorId": "ID monitor",
+ "runCount": "Conteggio esecuzioni",
+ "failCount": "Conteggio",
+ "nextScheduledAt": "Prossima esecuzione il",
+ "lockedBy": "Bloccato da",
+ "lastFinishedAt": "Ultimo completamento",
+ "lastFailedAt": "Ultimo errore",
+ "failReason": "Motivo errore"
+ }
+ },
+ "header": {
+ "title": "Log e coda",
+ "description": "Ispeziona i log del server, la coda dei job del monitor e la diagnostica runtime in un unico posto."
+ }
+ },
+ "maintenanceWindow": {
+ "fallback": {
+ "actionButton": "Crea una finestra di manutenzione",
+ "description": "Pianifica downtime programmati in modo che gli avvisi vengano soppressi e il tuo team e gli utenti sappiano cosa aspettarsi.",
+ "title": "Nessuna finestra di manutenzione pianificata"
+ },
+ "table": {
+ "headers": {
+ "nextWindow": "Prossima finestra",
+ "repeat": "Ripeti"
+ }
+ },
+ "form": {
+ "general": {
+ "title": "Impostazioni generali",
+ "description": "Imposta un nome e un'opzione di ripetizione per la tua finestra di manutenzione.",
+ "option": {
+ "name": {
+ "label": "Nome",
+ "placeholder": "es. Manutenzione settimanale"
+ },
+ "repeat": {
+ "label": "Ripeti"
+ }
+ }
+ },
+ "startDate": {
+ "title": "Data di inizio",
+ "description": "Seleziona la data di inizio per la tua finestra di manutenzione.",
+ "option": {
+ "startDate": {
+ "label": "Data di inizio"
+ }
+ }
+ },
+ "startTime": {
+ "title": "Ora di inizio",
+ "description": "Imposta l'ora di inizio e la durata per la tua finestra di manutenzione. Tutti i valori sono in UTC",
+ "option": {
+ "duration": {
+ "label": "Durata"
+ },
+ "startTime": {
+ "label": "Ora di inizio"
+ }
+ },
+ "monitors": {
+ "title": "Monitor",
+ "description": "Monitor a cui deve essere applicata la finestra di manutenzione",
+ "option": {
+ "addMonitors": {
+ "label": "Aggiungi monitor"
+ }
+ }
+ }
+ }
+ },
+ "header": {
+ "title": "Finestre di manutenzione",
+ "description": "Pianifica downtime programmati in modo che gli avvisi vengano soppressi e il tuo team e gli utenti sappiano cosa aspettarsi."
+ }
+ },
+ "notifications": {
+ "fallback": {
+ "actionButton": "Crea un canale",
+ "description": "Connetti Slack, Discord, email, webhook e altro ancora affinché le persone giuste siano informate degli incidenti nel momento in cui si verificano.",
+ "title": "Nessun canale di notifica ancora"
+ },
+ "form": {
+ "accessToken": {
+ "optionAccessToken": "Token di accesso",
+ "placeholder": "syt_YWxleF9ob2xsaWRheQ_VmtScmV0U2VjcmV0S2V5_abc123"
+ },
+ "address": {
+ "description": "L'indirizzo a cui verranno inviate le notifiche.",
+ "optionAddress": "Indirizzo",
+ "placeholderEmail": "example@example.com",
+ "placeholderWebhook": "https://your-server.com/webhook",
+ "title": "Indirizzo"
+ },
+ "homeServer": {
+ "optionHomeServer": "Home server",
+ "placeholder": "example.com"
+ },
+ "matrix": {
+ "description": "Configura la connessione al tuo homeserver Matrix per le notifiche.",
+ "title": "Configurazione Matrix"
+ },
+ "notificationName": {
+ "description": "Un nome descrittivo per il canale di notifica",
+ "optionName": "Nome canale",
+ "placeholder": "es. Avvisi produzione",
+ "title": "Nome canale"
+ },
+ "pagerDuty": {
+ "description": "La tua chiave di integrazione PagerDuty per ricevere avvisi.",
+ "optionIntegrationKey": "Chiave di integrazione",
+ "placeholder": "a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6",
+ "title": "Chiave di integrazione"
+ },
+ "roomId": {
+ "optionRoomId": "ID stanza",
+ "placeholder": "!abcdefg:matrix.org"
+ },
+ "type": {
+ "description": "Seleziona il tipo di canale di notifica da creare.",
+ "optionType": "Tipo",
+ "title": "Tipo di canale"
+ },
+ "telegram": {
+ "title": "Configurazione Telegram",
+ "description": "Configura il tuo bot Telegram per inviare notifiche.",
+ "optionBotToken": "Token bot",
+ "placeholderBotToken": "123456789:ABCdefGhIJKlmNoPQRsTUVwxyZ",
+ "optionChatId": "ID chat",
+ "placeholderChatId": "-1001234567890"
+ },
+ "pushover": {
+ "title": "Configurazione Pushover",
+ "description": "Configura Pushover per ricevere notifiche push sui tuoi dispositivi.",
+ "optionAppToken": "Token API applicazione",
+ "placeholderAppToken": "azGDORePK8gMaC0QOYAMyEEuzJnyUi",
+ "optionUserKey": "Chiave utente",
+ "placeholderUserKey": "uQiRzpo4DXghDmr9QzzfQu27cmVRsG"
+ },
+ "twilio": {
+ "title": "Configurazione SMS Twilio",
+ "description": "Configura Twilio per inviare notifiche SMS a un numero di telefono.",
+ "optionAccountSid": "Account SID",
+ "placeholderAccountSid": "ACxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
+ "optionAuthToken": "Auth token",
+ "placeholderAuthToken": "your_auth_token",
+ "optionFromNumber": "Numero mittente (Twilio)",
+ "placeholderFromNumber": "+15551234567",
+ "optionToNumber": "Numero destinatario",
+ "placeholderToNumber": "+15559876543"
+ },
+ "ntfy": {
+ "title": "Configurazione ntfy",
+ "description": "Configura un server e un topic ntfy per le notifiche push.",
+ "optionServerUrl": "URL server",
+ "placeholderServerUrl": "https://ntfy.sh",
+ "optionTopic": "Topic",
+ "placeholderTopic": "checkmate-alerts"
+ }
+ },
+ "table": {
+ "headers": {
+ "destination": "Destinazione"
+ }
+ },
+ "header": {
+ "title": "Notifiche",
+ "description": "Connetti Slack, Discord, email, webhook e altro ancora affinché le persone giuste siano informate degli incidenti nel momento in cui si verificano."
+ }
+ },
+ "pageSpeed": {
+ "strategy": {
+ "desktop": "Desktop",
+ "mobile": "Mobile"
+ },
+ "charts": {
+ "common": {
+ "cls": "Cumulative Layout Shift (CLS)",
+ "fcp": "First Contentful Paint (FCP)",
+ "lcp": "Largest Contentful Paint (LCP)",
+ "si": "Speed Index (SI)",
+ "tbt": "Total Blocking Time (TBT)"
+ },
+ "legend": {
+ "title": "Report PageSpeed",
+ "weight": "Peso"
+ },
+ "pie": {
+ "title": "Report prestazioni"
+ }
+ },
+ "table": {
+ "headers": {
+ "pageSpeedScore": "Punteggio PageSpeed"
+ }
+ },
+ "fallback": {
+ "actionButton": "Crea un monitor",
+ "description": "Traccia i Core Web Vitals e i punteggi Lighthouse nel tempo per individuare le regressioni prima degli utenti.",
+ "title": "Nessun monitor PageSpeed ancora"
+ },
+ "header": {
+ "title": "Monitor PageSpeed",
+ "description": "Traccia i Core Web Vitals e i punteggi Lighthouse nel tempo per individuare le regressioni prima degli utenti."
+ }
+ },
+ "settings": {
+ "form": {
+ "timezone": {
+ "title": "Fuso orario di visualizzazione",
+ "description": "Seleziona il fuso orario usato per visualizzare date e orari in tutta l'applicazione.",
+ "option": {
+ "timezone": {
+ "label": "Fuso orario di visualizzazione"
+ }
+ }
+ },
+ "ui": {
+ "title": "Aspetto",
+ "description": "Passa dalla modalità chiara a quella scura, cambia lingua o personalizza il tipo di visualizzazione dei grafici.",
+ "option": {
+ "theme": {
+ "label": "Modalità tema",
+ "light": "Chiaro",
+ "dark": "Scuro"
+ },
+ "language": {
+ "label": "Lingua"
+ },
+ "chartType": {
+ "label": "Tipo di grafico",
+ "histogram": "Istogramma",
+ "heatmap": "Heatmap"
+ }
+ }
+ },
+ "pagespeed": {
+ "title": "Chiave API di Google PageSpeed",
+ "description": "Inserisci la tua chiave API di Google PageSpeed per abilitare il monitoraggio Google PageSpeed. Clicca su Reimposta per aggiornare la chiave.",
+ "option": {
+ "apiKey": {
+ "label": "Chiave API PageSpeed",
+ "labelSet": "La chiave API è impostata. Clicca su Reimposta per modificarla.",
+ "placeholder": "Inserisci la tua chiave API di Google PageSpeed"
+ }
+ }
+ },
+ "url": {
+ "title": "Monitor IP/URL sulla pagina di stato",
+ "description": "Visualizza l'indirizzo IP o l'URL del monitor sulla pagina di stato pubblica. Se disabilitato, verrà mostrato solo il nome del monitor per proteggere le informazioni sensibili.",
+ "option": {
+ "showURL": {
+ "label": "Visualizza IP/URL sulla pagina di stato",
+ "enabled": "Abilitato",
+ "disabled": "Disabilitato"
+ }
+ }
+ },
+ "stats": {
+ "title": "Cronologia monitor",
+ "description": "Cancella tutta la cronologia di monitoraggio e le statistiche per il tuo team. Questa azione è irreversibile.",
+ "option": {
+ "clear": {
+ "label": "Cancella tutte le statistiche. Questa azione è irreversibile."
+ }
+ },
+ "dialog": {
+ "title": "Cancellare tutta la cronologia di monitoraggio?",
+ "description": "Non può essere annullato"
+ }
+ },
+ "retention": {
+ "title": "Conservazione verifiche",
+ "description": "Imposta per quanto tempo i dati delle verifiche vengono conservati prima di essere puliti automaticamente.",
+ "option": {
+ "days": {
+ "label": "Periodo di conservazione (giorni)",
+ "unlimited": "Illimitato"
+ }
+ }
+ },
+ "thresholds": {
+ "title": "Soglie globali",
+ "description": "Imposta soglie globali di CPU, memoria, disco e temperatura per il monitoraggio infrastrutturale. Queste si applicano a tutti i monitor hardware a meno che non vengano sovrascritte.",
+ "option": {
+ "cpu": {
+ "label": "Soglia CPU (%)",
+ "placeholder": "1 - 100"
+ },
+ "memory": {
+ "label": "Soglia memoria (%)",
+ "placeholder": "1 - 100"
+ },
+ "disk": {
+ "label": "Soglia disco (%)",
+ "placeholder": "1 - 100"
+ },
+ "temperature": {
+ "label": "Soglia temperatura (°C)",
+ "placeholder": "1 - 150"
+ }
+ }
+ },
+ "email": {
+ "title": "Impostazioni email",
+ "description": "Configura le impostazioni email per il tuo sistema. Vengono utilizzate per inviare notifiche e avvisi.",
+ "descriptionTransport": "Questo costruisce un trasporto SMTP per NodeMailer",
+ "descriptionTransportLink": "Vedi le specifiche qui",
+ "option": {
+ "host": {
+ "label": "Host email - Hostname o indirizzo IP a cui connettersi",
+ "placeholder": "smtp.gmail.com"
+ },
+ "port": {
+ "label": "Porta email - Porta a cui connettersi",
+ "placeholder": "587"
+ },
+ "address": {
+ "label": "Indirizzo email - Usato per l'autenticazione",
+ "placeholder": "you@example.com"
+ },
+ "displayName": {
+ "label": "Nome mittente (opzionale) - Nome descrittivo mostrato nel campo Da",
+ "placeholder": "Notifiche Checkmate"
+ },
+ "user": {
+ "label": "Utente email - Username per l'autenticazione, sovrascrive l'indirizzo email se specificato",
+ "placeholder": "Lascia vuoto se non richiesto"
+ },
+ "password": {
+ "label": "Password email - Password per l'autenticazione",
+ "labelSet": "La password è impostata. Clicca su Reimposta per modificarla.",
+ "placeholder": "Inserisci la tua password"
+ },
+ "tlsServername": {
+ "label": "Servername TLS - Hostname opzionale per la validazione TLS quando l'host è un IP",
+ "placeholder": "example.com"
+ },
+ "connectionHost": {
+ "label": "Host di connessione email - Hostname da usare nel saluto HELO/EHLO",
+ "placeholder": "localhost"
+ },
+ "secure": {
+ "label": "Usa SSL (consigliato): Cripta la connessione usando SSL/TLS"
+ },
+ "pool": {
+ "label": "Abilita pooling delle connessioni: Riutilizza le connessioni esistenti per migliorare le prestazioni"
+ },
+ "ignoreTLS": {
+ "label": "Disabilita STARTTLS: Non usare TLS anche se il server lo supporta"
+ },
+ "requireTLS": {
+ "label": "Forza STARTTLS: Richiedi l'aggiornamento TLS, fallisce se non supportato"
+ },
+ "rejectUnauthorized": {
+ "label": "Rifiuta certificati non validi: Rifiuta connessioni con certificati auto-firmati o non fidati"
+ }
+ }
+ },
+ "demoMonitors": {
+ "title": "Monitor demo",
+ "description": "Aggiungi monitor di esempio a scopo dimostrativo."
+ },
+ "removeMonitors": {
+ "title": "Reset del sistema",
+ "description": "Rimuovi tutti i monitor dal tuo sistema.",
+ "dialog": {
+ "title": "Rimuovere tutti i monitor?",
+ "description": "Questa operazione non può essere annullata.",
+ "paragraph": "Anche tutta la cronologia delle verifiche, gli incidenti, le finestre di manutenzione pianificate e qualsiasi pagina di stato che faccia riferimento a questi monitor verranno rimossi. Esporta prima i tuoi monitor per mantenere un backup dei loro dati."
+ }
+ },
+ "exportMonitors": {
+ "title": "Esporta monitor"
+ },
+ "importExportMonitors": {
+ "title": "Importa / Esporta monitor",
+ "description": "Importa o esporta i dati dei tuoi monitor come file JSON per backup o trasferimento."
+ },
+ "about": {
+ "title": "Informazioni",
+ "developedBy": "Sviluppato da Bluewave Labs"
+ },
+ "validation": {
+ "errorMessage": "Correggi i seguenti errori di validazione:"
+ }
+ },
+ "header": {
+ "title": "Impostazioni",
+ "description": "Configura SMTP, integrazioni, conservazione e comportamento globale dell'app."
+ }
+ },
+ "statusPages": {
+ "deleteSuccess": "Pagina di stato eliminata con successo",
+ "fallback": {
+ "title": "Nessuna pagina di stato ancora",
+ "description": "Pubblica una pagina pubblica che mostra l'uptime in tempo reale e la cronologia degli incidenti ai tuoi clienti e stakeholder.",
+ "actionButton": "Crea una pagina di stato"
+ },
+ "monitorsList": {
+ "chartTypeHeatmap": "Heatmap",
+ "chartTypeHistogram": "Istogramma",
+ "noData": "Nessun dato disponibile",
+ "status": {
+ "up": "Operativo",
+ "down": "Non attivo",
+ "breached": "Soglia superata",
+ "maintenance": "Manutenzione",
+ "paused": "Sospeso",
+ "initializing": "Inizializzazione"
+ },
+ "chart": {
+ "avg": "media {{value}}ms",
+ "max": "max {{value}}ms",
+ "downTooltip": "Non attivo",
+ "heatmapAria": "Heatmap del tempo di risposta"
+ },
+ "infrastructure": {
+ "title": "Infrastruttura",
+ "cpu": "CPU",
+ "memory": "RAM",
+ "memoryText": "Memoria",
+ "disk": "Disco",
+ "usage": "Utilizzo",
+ "used": "Utilizzato",
+ "total": "Totale"
+ },
+ "uptime": {
+ "title": "Uptime",
+ "responseTime": "Tempo di risposta"
+ }
+ },
+ "statusBar": {
+ "monitoringSummary": "Monitoraggio di {{count}} servizio · Aggiornamenti ogni 10 secondi",
+ "monitoringSummary_other": "Monitoraggio di {{count}} servizi · Aggiornamenti ogni 10 secondi",
+ "allUp": "Tutti i sistemi operativi",
+ "allBreached": "Tutti i sistemi hanno superato le soglie",
+ "allMaintenance": "Tutti i sistemi sono in manutenzione",
+ "allDown": "Tutti i sistemi sono non attivi",
+ "allPaused": "Tutti i sistemi sono sospesi",
+ "allInitializing": "Tutti i sistemi sono in inizializzazione",
+ "breachedAndDown": "Alcuni sistemi hanno superato le soglie e sono non attivi",
+ "breached": "Alcuni sistemi hanno superato le soglie",
+ "degraded": "Alcuni sistemi stanno riscontrando problemi",
+ "maintenanceAndDown": "Alcuni sistemi sono non attivi durante la manutenzione",
+ "maintenance": "Alcuni sistemi sono in manutenzione",
+ "partiallyPaused": "Alcuni sistemi sono sospesi",
+ "initializing": "Alcuni sistemi sono in inizializzazione",
+ "noMonitors": "Nessun monitor è stato configurato",
+ "unknown": "Impossibile determinare lo stato del sistema"
+ },
+ "table": {
+ "headers": {
+ "name": "Nome pagina di stato",
+ "url": "URL pubblico"
+ },
+ "published": "Pubblicata",
+ "unpublished": "Non pubblicata"
+ },
+ "title": "Pagine di stato",
+ "details": {
+ "empty": {
+ "title": "Ancora nulla qui",
+ "addMonitor": "Aggiungi un monitor per iniziare"
+ }
+ },
+ "footer": {
+ "poweredBy": "Powered by"
+ },
+ "editorial": {
+ "reportTitle": "Report di servizio",
+ "allUp": "Tutti i sistemi funzionano normalmente."
+ },
+ "form": {
+ "access": {
+ "title": "Accesso",
+ "description": "Se la tua pagina di stato è pronta, puoi contrassegnarla come pubblicata.",
+ "option": {
+ "published": {
+ "name": "Pubblicata e visibile al pubblico"
+ }
+ }
+ },
+ "basicInfo": {
+ "title": "Informazioni di base",
+ "description": "Definisci il nome dell'azienda e il sottodominio a cui punta la tua pagina di stato.",
+ "option": {
+ "name": {
+ "label": "Nome azienda",
+ "placeholder": "Acme Inc."
+ },
+ "url": {
+ "label": "Indirizzo della tua pagina di stato",
+ "placeholder": "my-status-page"
+ },
+ "customDomain": {
+ "label": "Dominio personalizzato",
+ "placeholder": "status.example.com",
+ "helper": "Opzionale. Punta un record DNS a questa istanza Checkmate per servire la pagina di stato sul tuo dominio."
+ }
+ }
+ },
+ "monitors": {
+ "title": "Monitor",
+ "description": "Seleziona i monitor da visualizzare sulla tua pagina di stato.",
+ "noMonitors": "Nessun monitor selezionato",
+ "option": {
+ "monitors": {
+ "label": "Seleziona monitor",
+ "placeholder": "Cerca e seleziona monitor..."
+ }
+ }
+ },
+ "timezone": {
+ "title": "Fuso orario",
+ "description": "Seleziona il fuso orario in cui verrà visualizzata la tua pagina di stato.",
+ "option": {
+ "timezone": {
+ "label": "Fuso orario",
+ "placeholder": "Seleziona fuso orario..."
+ }
+ }
+ },
+ "appearance": {
+ "title": "Aspetto",
+ "description": "Definisci l'aspetto predefinito della tua pagina di stato pubblica.",
+ "option": {
+ "logo": {
+ "label": "Logo"
+ },
+ "color": {
+ "label": "Colore del brand"
+ }
+ }
+ },
+ "theme": {
+ "title": "Tema pagina",
+ "description": "Scegli lo stile visivo per la tua pagina di stato pubblica.",
+ "label": "Tema",
+ "options": {
+ "refined": {
+ "name": "Raffinato",
+ "description": "Pulito, professionale, vicino al classico look di Checkmate."
+ },
+ "modern": {
+ "name": "Moderno",
+ "description": "Livello SaaS con un punto di stato pulsante e schede in dissolvenza morbida."
+ },
+ "bold": {
+ "name": "Bold",
+ "description": "Caratteri grandi e spaziatura decisa. Ottimo per pagine focalizzate sul brand."
+ },
+ "editorial": {
+ "name": "Editoriale",
+ "description": "Intestazioni con serif e divisori sottili. Distintivo e sobrio."
+ },
+ "minimal": {
+ "name": "Minimale",
+ "description": "Piatto, focalizzato e compatto, con solo l'essenziale."
+ }
+ }
+ },
+ "themeMode": {
+ "label": "Modalità tema",
+ "auto": "Auto (segue il dispositivo del visitatore)",
+ "light": "Solo chiaro",
+ "dark": "Solo scuro"
+ },
+ "customCSS": {
+ "title": "CSS personalizzato",
+ "description": "Aggiungi CSS personalizzato per rifinire l'aspetto della tua pagina di stato pubblica.",
+ "option": {
+ "customCSS": {
+ "label": "CSS personalizzato",
+ "placeholder": "footer { display: none; }"
+ }
+ }
+ },
+ "features": {
+ "title": "Funzionalità",
+ "description": "Configura quali informazioni verranno visualizzate sulla tua pagina di stato.",
+ "option": {
+ "showCharts": {
+ "label": "Mostra grafici del tempo di risposta"
+ },
+ "showUptimePercentage": {
+ "label": "Mostra percentuale di uptime"
+ },
+ "showAdminLoginLink": {
+ "label": "Mostra link login amministratore"
+ },
+ "showInfrastructure": {
+ "label": "Mostra metriche infrastrutturali"
+ }
+ }
+ }
+ },
+ "header": {
+ "title": "Pagine di stato",
+ "description": "Pubblica pagine pubbliche che mostrano l'uptime in tempo reale e la cronologia degli incidenti ai tuoi clienti e stakeholder."
+ },
+ "preview": {
+ "title": "Anteprima pagina di stato"
+ }
+ },
+ "uptime": {
+ "filters": {
+ "search": {
+ "placeholder": "Cerca monitor..."
+ },
+ "activeTags": "Tag attivi"
+ },
+ "table": {
+ "headers": {
+ "responseTime": "Tempo di risposta",
+ "uptime": "Uptime %"
+ }
+ },
+ "fallback": {
+ "actionButton": "Crea un monitor",
+ "description": "Controlla endpoint HTTP, ping, container e porte — e ricevi avvisi nel momento in cui qualcosa va non attivo.",
+ "title": "Nessun monitor uptime ancora"
+ },
+ "header": {
+ "title": "Monitor uptime",
+ "description": "Controlla endpoint HTTP, ping, container e porte — e ricevi avvisi nel momento in cui qualcosa va non attivo."
+ }
+ }
+ }
+}
From 242e853bf00d12909740220fdb378d40c8b294c5 Mon Sep 17 00:00:00 2001
From: gorkem-bwl
Date: Mon, 13 Jul 2026 16:18:24 +0300
Subject: [PATCH 21/84] =?UTF-8?q?i18n(it):=20review=20pass=20=E2=80=94=20f?=
=?UTF-8?q?ix=20grammar,=20awkward=20phrasing,=20inconsistencies?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
91 leaves refined after a native-Italian style pass.
## Categories of fixes
- Grammar / gender agreement — e.g. `logs.table.headers.lastSeenAt`
"Ultima visto il" → "Visto l'ultima volta"; heap gauge subject
agreement in `logs.diagnostics.gauges.allocatedPercentage`.
- Awkward phrasing / calques — `notFound.subtitle` drops stilted
"ad esso"; `incidents.fallback.title` "in salute" → "funzionano
correttamente" (was reading as if for a person, not systems);
`settings.form.removeMonitors.title` "Reset del sistema" →
"Reimposta il sistema"; "con successo" → "correttamente" on
status-page delete confirmations.
- Consistency — paused monitor status unified on "In pausa" across
`common.labels.paused`, monitor status labels, status-bar keys, and
the `bulkPause.*_one/other` copies. Button verbs
"Sospendi"/"Riprendi" kept for brevity; status noun stays "In pausa".
- Mistranslations — `buttons.testNotifications` "Testa" → "Prova";
`buttons.toggleTheme` "Cambia" → "Alterna"; Twilio "Auth token" now
translated; Mongoose `readyState` clarified to "Stato di connessione".
- Register — trimmed "Ancora" from several empty-state titles so they
read like natural Italian empty states.
## Verification
874 leaves in EN and IT, 0 missing / 0 extra, all placeholders and
inline tags preserved, no empty values.
---
client/src/locales/it.json | 142 ++++++++++++++++++-------------------
1 file changed, 71 insertions(+), 71 deletions(-)
diff --git a/client/src/locales/it.json b/client/src/locales/it.json
index 4620284f7..cabeb94ef 100644
--- a/client/src/locales/it.json
+++ b/client/src/locales/it.json
@@ -46,9 +46,9 @@
"next": "Avanti",
"back": "Indietro",
"sendInvite": "Invia invito",
- "test": "Test",
- "testNotifications": "Testa notifiche",
- "toggleTheme": "Cambia tema chiaro e scuro",
+ "test": "Prova",
+ "testNotifications": "Prova notifiche",
+ "toggleTheme": "Alterna tema chiaro e scuro",
"flushQueue": "Svuota coda",
"notFound": "Vai alla dashboard principale",
"resetPassword": "Reimposta password",
@@ -78,13 +78,13 @@
"dialogs": {
"delete": {
"description": "Questa azione non può essere annullata.",
- "title": "Sei sicuro di voler eliminare questo elemento?"
+ "title": "Sei sicuro di voler eliminarlo?"
}
},
"labels": {
"active": "Attivo",
"running": "In esecuzione",
- "paused": "Sospeso",
+ "paused": "In pausa",
"idle": "Inattivo",
"na": "N/A",
"resolved": "Risolto",
@@ -120,7 +120,7 @@
}
},
"table": {
- "empty": "Nessun elemento da mostrare",
+ "empty": "Non c'è ancora nulla da mostrare",
"headers": {
"actions": "Azioni",
"dateTime": "Data e ora",
@@ -229,7 +229,7 @@
"pages": {
"notFound": {
"title": "Oh no! Hai fatto cadere il tuo sushi!",
- "subtitle": "L'URL non esiste, oppure non hai accesso ad esso."
+ "subtitle": "L'URL non esiste, oppure non hai accesso."
},
"account": {
"tabs": {
@@ -275,7 +275,7 @@
},
"team": {
"fallback": {
- "title": "Nessun membro del team ancora",
+ "title": "Nessun membro del team",
"description": "Invita compagni di team via email o aggiungili direttamente. Una volta iscritti, appariranno qui con il loro ruolo."
},
"filter": {
@@ -318,7 +318,7 @@
"brandPanel": {
"eyebrow": "Monitoraggio infrastrutturale open source",
"tagline": "Tieni d'occhio ogni mossa dei tuoi server.",
- "description": "Uptime in tempo reale, tempo di risposta e tracciamento degli incidenti - self-hosted e tuo per sempre."
+ "description": "Uptime in tempo reale, tempo di risposta e tracciamento degli incidenti — self-hosted e tuo per sempre."
},
"common": {
"passwordRules": {
@@ -381,7 +381,7 @@
"register": {
"title": "Bentornato su Checkmate!",
"subtitle": "Registrati per iniziare",
- "setupNotice": "Questa pagina è disponibile solo durante la configurazione iniziale. Una volta registrato un super amministratore, altri utenti possono essere invitati dalle impostazioni del team.",
+ "setupNotice": "Questa pagina è disponibile solo durante la configurazione iniziale. Una volta registrato un super amministratore, gli altri utenti possono essere invitati dalle impostazioni del team.",
"submit": "Registrati"
},
"forgotPassword": {
@@ -402,8 +402,8 @@
},
"checks": {
"fallback": {
- "title": "Ancora nessuna verifica",
- "description": "Una volta creato un monitor, ogni controllo di stato e tempo di risposta apparirà qui come una verifica."
+ "title": "Nessuna verifica",
+ "description": "Una volta creato un monitor, ogni sonda di stato e tempo di risposta apparirà qui come una verifica."
},
"selects": {
"monitor": {
@@ -416,7 +416,7 @@
}
},
"table": {
- "empty": "Nessuna verifica non attiva in questo intervallo di tempo",
+ "empty": "Nessuna verifica fallita in questo intervallo di tempo",
"headers": {
"statusCode": "Codice di stato",
"location": "Posizione"
@@ -424,7 +424,7 @@
},
"header": {
"title": "Verifiche",
- "description": "Ogni controllo che Checkmate esegue sui tuoi monitor appare qui, con codice di stato e tempo di risposta."
+ "description": "Ogni sonda che Checkmate esegue sui tuoi monitor arriva qui, con codice di stato e tempo di risposta."
}
},
"common": {
@@ -445,29 +445,29 @@
"selectMonitor": "Seleziona {{name}}"
},
"bulkPause": {
- "alreadyPaused_one": "Il monitor selezionato è già sospeso",
- "alreadyPaused_other": "Tutti i monitor selezionati sono già sospesi",
+ "alreadyPaused_one": "Il monitor selezionato è già in pausa",
+ "alreadyPaused_other": "Tutti i monitor selezionati sono già in pausa",
"alreadyRunning_one": "Il monitor selezionato è già in esecuzione",
"alreadyRunning_other": "Tutti i monitor selezionati sono già in esecuzione",
- "paused_one": "{{count}} monitor sospeso",
- "paused_other": "{{count}} monitor sospesi",
+ "paused_one": "{{count}} monitor messo in pausa",
+ "paused_other": "{{count}} monitor messi in pausa",
"resumed_one": "{{count}} monitor ripreso",
"resumed_other": "{{count}} monitor ripresi",
- "partialFailure": "{{successCount}} monitor aggiornati, ma {{failedCount}} non sono stati sincronizzati con la coda dei job"
+ "partialFailure": "{{successCount}} monitor aggiornati, ma {{failedCount}} non sono riusciti a sincronizzarsi con la coda dei job"
},
"statBoxes": {
"activeFor": "Attivo da",
"certificateExpiry": "Scadenza certificato",
"lastCheck": "Ultima verifica",
"lastResponseTime": "Ultimo tempo di risposta",
- "serviceIsDown": "Il servizio è non attivo"
+ "serviceIsDown": "Servizio non attivo"
},
"status": {
"down": "non attivo",
"breached": "soglia superata",
"initializing": "inizializzazione",
"maintenance": "manutenzione",
- "paused": "sospeso",
+ "paused": "in pausa",
"total": "totale",
"up": "attivo"
},
@@ -493,8 +493,8 @@
"method": {
"label": "Metodo di richiesta",
"description": {
- "GET": "GET recupera la risposta completa incluso il body. Il matching avanzato è completamente supportato",
- "HEAD": "HEAD recupera solo gli header della risposta per ridurre il carico sul target. Supporta il matching del codice di stato ma non il matching avanzato poiché non c'è un body."
+ "GET": "GET recupera la risposta completa, incluso il corpo. Il matching avanzato è completamente supportato.",
+ "HEAD": "HEAD recupera solo gli header della risposta, riducendo il carico sul target. Supporta il matching sul codice di stato ma non il matching avanzato, poiché non c'è un corpo."
}
},
"advancedMatching": {
@@ -611,7 +611,7 @@
"ping": "Inserisci l'indirizzo IP o l'hostname da pingare (es. 192.168.1.100 o example.com) e aggiungi un nome visualizzato chiaro che apparirà nella dashboard.",
"port": "Inserisci l'URL o l'IP del server, il numero di porta e un nome visualizzato chiaro che apparirà nella dashboard.",
"docker": "Inserisci il nome o l'ID del container Docker. Puoi usare il nome del container (es. my-app) o l'ID del container (ID completo di 64 caratteri o ID breve).",
- "game": "Inserisci l'indirizzo IP o l'hostname e il numero di porta da pingare (es. 192.168.1.100 o example.com) e scegli il tipo di gioco. Alcuni giochi richiedono l'abilitazione delle query del server nel loro file di configurazione — controlla la documentazione del tuo game server. Consulta la lista dei giochi supportati per i dettagli del protocollo.",
+ "game": "Inserisci l'indirizzo IP o l'hostname e il numero di porta da pingare (es. 192.168.1.100 o example.com) e scegli il tipo di gioco. Alcuni giochi richiedono l'abilitazione delle query del server nel loro file di configurazione — consulta la documentazione del tuo server di gioco. Consulta la lista dei giochi supportati per i dettagli del protocollo.",
"pagespeed": "Traccia le prestazioni di caricamento della pagina, i Core Web Vitals e i punteggi di ottimizzazione per il tuo sito web.",
"grpc": "Inserisci l'hostname e la porta del server gRPC, specifica opzionalmente un nome di servizio per la verifica di stato e aggiungi un nome visualizzato.",
"websocket": "Inserisci l'URL WebSocket da monitorare (es. wss://example.com/socket) e aggiungi un nome visualizzato.",
@@ -629,7 +629,7 @@
"title": "Impostazioni TLS/SSL"
},
"incidents": {
- "description": "Viene usata una finestra scorrevole per determinare quando un monitor va non attivo. Lo stato di un monitor cambierà solo quando la percentuale di verifiche nella finestra scorrevole raggiunge il valore specificato.",
+ "description": "Per determinare quando un monitor va giù viene utilizzata una finestra scorrevole. Lo stato di un monitor cambia solo quando la percentuale di verifiche nella finestra scorrevole raggiunge il valore specificato.",
"option": {
"checks": {
"label": "Numero di verifiche nella finestra scorrevole"
@@ -747,8 +747,8 @@
},
"incidents": {
"fallback": {
- "title": "Nessun incidente — tutti i sistemi sono in salute",
- "description": "Quando un monitor va non attivo o si riprende, l'incidente apparirà qui con tempistiche, dettagli del monitor e cronologia di risoluzione."
+ "title": "Nessun incidente — tutti i sistemi funzionano correttamente",
+ "description": "Quando un monitor va giù o si ripristina, l'incidente apparirà qui con tempistiche, dettagli del monitor e cronologia della risoluzione."
},
"dialog": {
"resolveIncident": {
@@ -833,7 +833,7 @@
},
"header": {
"title": "Incidenti",
- "description": "Quando i monitor vanno non attivi o si riprendono, gli incidenti appaiono qui con tempistiche, dettagli del monitor e cronologia di risoluzione."
+ "description": "Quando i monitor vanno giù o si ripristinano, gli incidenti appaiono qui con tempistiche, dettagli del monitor e cronologia della risoluzione."
}
},
"infrastructure": {
@@ -890,7 +890,7 @@
"fallback": {
"actionButton": "Crea un monitor",
"description": "Traccia le prestazioni del server, identifica i colli di bottiglia e garantisci l'affidabilità con il monitoraggio infrastrutturale in tempo reale.",
- "title": "Nessun monitor infrastrutturale ancora"
+ "title": "Nessun monitor infrastruttura"
},
"header": {
"title": "Monitor infrastruttura",
@@ -903,7 +903,7 @@
"description": "Organizza i tuoi monitor con i tag"
},
"fallback": {
- "title": "Ancora nessun tag",
+ "title": "Nessun tag",
"description": "Organizza i tuoi monitor con i tag. Una volta creato un tag e assegnato a un monitor, potrai filtrare per quel tag in tutta l'app e vedere i dettagli del tag qui.",
"actionButton": "Crea un tag"
},
@@ -930,7 +930,7 @@
},
"logs": {
"fallback": {
- "title": "Ancora nessun log",
+ "title": "Nessun log",
"description": "Attività del server, avvisi ed errori appariranno qui man mano che Checkmate viene eseguito."
},
"queueFallback": {
@@ -946,7 +946,7 @@
"label": "Livello di log"
},
"workers": "Worker",
- "workersExplainers": "Mostra i processi worker. Gli ID worker sono troncati, passa il mouse sopra per l'ID completo",
+ "workersExplainers": "Mostra i processi worker. Gli ID worker sono troncati; passa il mouse sopra per vedere l'ID completo.",
"jobQueue": "Coda job",
"jobQueueExplainer": "Le righe evidenziate sono job attualmente in elaborazione da parte di un worker.",
"failedJobs": "Job falliti",
@@ -955,7 +955,7 @@
"metrics": {
"jobs": "Job",
"activeJobs": "Job attivi",
- "failingJobs": "Job in errore",
+ "failingJobs": "Job non riusciti",
"totalRuns": "Esecuzioni totali",
"totalFailures": "Errori totali"
},
@@ -986,7 +986,7 @@
"osMemoryLimit": "Limite memoria OS"
},
"mongoDBStats": {
- "readyState": "Stato di prontezza",
+ "readyState": "Stato di connessione",
"readsPerSecond": "Letture/s",
"insertsPerSecond": "Inserimenti/s",
"updatesPerSecond": "Aggiornamenti/s",
@@ -1010,9 +1010,9 @@
"heapUsage": "Utilizzo heap",
"heapUtilization": "Utilizzo heap",
"instantCpuUsage": "Utilizzo CPU istantaneo",
- "availableMemoryPercentage": "% di memoria disponibile",
- "allocatedPercentage": "% allocata",
- "usedSPercentage": "% di 1s utilizzato dalla CPU",
+ "availableMemoryPercentage": "% memoria disponibile",
+ "allocatedPercentage": "% allocato",
+ "usedSPercentage": "% di 1s usato dalla CPU",
"total": "Totale",
"used": "Utilizzato"
}
@@ -1023,16 +1023,16 @@
"processId": "ID processo",
"workerId": "ID worker",
"workerMode": "Modalità worker",
- "lastSeenAt": "Ultima visto il",
+ "lastSeenAt": "Visto l'ultima volta",
"state": "Stato",
"timestamp": "Timestamp",
"level": "Livello",
"service": "Servizio",
"method": "Metodo",
"monitorId": "ID monitor",
- "runCount": "Conteggio esecuzioni",
- "failCount": "Conteggio",
- "nextScheduledAt": "Prossima esecuzione il",
+ "runCount": "Esecuzioni",
+ "failCount": "Errori",
+ "nextScheduledAt": "Prossima esecuzione",
"lockedBy": "Bloccato da",
"lastFinishedAt": "Ultimo completamento",
"lastFailedAt": "Ultimo errore",
@@ -1110,7 +1110,7 @@
"fallback": {
"actionButton": "Crea un canale",
"description": "Connetti Slack, Discord, email, webhook e altro ancora affinché le persone giuste siano informate degli incidenti nel momento in cui si verificano.",
- "title": "Nessun canale di notifica ancora"
+ "title": "Nessun canale di notifica"
},
"form": {
"accessToken": {
@@ -1172,9 +1172,9 @@
"twilio": {
"title": "Configurazione SMS Twilio",
"description": "Configura Twilio per inviare notifiche SMS a un numero di telefono.",
- "optionAccountSid": "Account SID",
+ "optionAccountSid": "SID account",
"placeholderAccountSid": "ACxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
- "optionAuthToken": "Auth token",
+ "optionAuthToken": "Token di autenticazione",
"placeholderAuthToken": "your_auth_token",
"optionFromNumber": "Numero mittente (Twilio)",
"placeholderFromNumber": "+15551234567",
@@ -1229,7 +1229,7 @@
"fallback": {
"actionButton": "Crea un monitor",
"description": "Traccia i Core Web Vitals e i punteggi Lighthouse nel tempo per individuare le regressioni prima degli utenti.",
- "title": "Nessun monitor PageSpeed ancora"
+ "title": "Nessun monitor PageSpeed"
},
"header": {
"title": "Monitor PageSpeed",
@@ -1249,7 +1249,7 @@
},
"ui": {
"title": "Aspetto",
- "description": "Passa dalla modalità chiara a quella scura, cambia lingua o personalizza il tipo di visualizzazione dei grafici.",
+ "description": "Passa tra modalità chiara e scura, cambia lingua o personalizza il tipo di grafico.",
"option": {
"theme": {
"label": "Modalità tema",
@@ -1298,12 +1298,12 @@
},
"dialog": {
"title": "Cancellare tutta la cronologia di monitoraggio?",
- "description": "Non può essere annullato"
+ "description": "Questa azione non può essere annullata."
}
},
"retention": {
- "title": "Conservazione verifiche",
- "description": "Imposta per quanto tempo i dati delle verifiche vengono conservati prima di essere puliti automaticamente.",
+ "title": "Conservazione delle verifiche",
+ "description": "Imposta per quanto tempo i dati delle verifiche vengono conservati prima di essere eliminati automaticamente.",
"option": {
"days": {
"label": "Periodo di conservazione (giorni)",
@@ -1313,7 +1313,7 @@
},
"thresholds": {
"title": "Soglie globali",
- "description": "Imposta soglie globali di CPU, memoria, disco e temperatura per il monitoraggio infrastrutturale. Queste si applicano a tutti i monitor hardware a meno che non vengano sovrascritte.",
+ "description": "Imposta soglie globali di CPU, memoria, disco e temperatura per il monitoraggio infrastrutturale. Si applicano a tutti i monitor hardware a meno che non vengano sovrascritte.",
"option": {
"cpu": {
"label": "Soglia CPU (%)",
@@ -1336,8 +1336,8 @@
"email": {
"title": "Impostazioni email",
"description": "Configura le impostazioni email per il tuo sistema. Vengono utilizzate per inviare notifiche e avvisi.",
- "descriptionTransport": "Questo costruisce un trasporto SMTP per NodeMailer",
- "descriptionTransportLink": "Vedi le specifiche qui",
+ "descriptionTransport": "Costruisce un trasporto SMTP per NodeMailer",
+ "descriptionTransportLink": "Consulta le specifiche qui",
"option": {
"host": {
"label": "Host email - Hostname o indirizzo IP a cui connettersi",
@@ -1373,19 +1373,19 @@
"placeholder": "localhost"
},
"secure": {
- "label": "Usa SSL (consigliato): Cripta la connessione usando SSL/TLS"
+ "label": "Usa SSL (consigliato): cripta la connessione con SSL/TLS"
},
"pool": {
- "label": "Abilita pooling delle connessioni: Riutilizza le connessioni esistenti per migliorare le prestazioni"
+ "label": "Abilita il pooling delle connessioni: riutilizza le connessioni esistenti per migliorare le prestazioni"
},
"ignoreTLS": {
- "label": "Disabilita STARTTLS: Non usare TLS anche se il server lo supporta"
+ "label": "Disabilita STARTTLS: non usare TLS anche se il server lo supporta"
},
"requireTLS": {
- "label": "Forza STARTTLS: Richiedi l'aggiornamento TLS, fallisce se non supportato"
+ "label": "Forza STARTTLS: richiedi l'aggiornamento a TLS, fallisce se non supportato"
},
"rejectUnauthorized": {
- "label": "Rifiuta certificati non validi: Rifiuta connessioni con certificati auto-firmati o non fidati"
+ "label": "Rifiuta certificati non validi: rifiuta connessioni con certificati auto-firmati o non fidati"
}
}
},
@@ -1394,12 +1394,12 @@
"description": "Aggiungi monitor di esempio a scopo dimostrativo."
},
"removeMonitors": {
- "title": "Reset del sistema",
+ "title": "Reimposta il sistema",
"description": "Rimuovi tutti i monitor dal tuo sistema.",
"dialog": {
"title": "Rimuovere tutti i monitor?",
"description": "Questa operazione non può essere annullata.",
- "paragraph": "Anche tutta la cronologia delle verifiche, gli incidenti, le finestre di manutenzione pianificate e qualsiasi pagina di stato che faccia riferimento a questi monitor verranno rimossi. Esporta prima i tuoi monitor per mantenere un backup dei loro dati."
+ "paragraph": "Verranno rimossi anche tutta la cronologia delle verifiche, gli incidenti, le finestre di manutenzione pianificate e qualsiasi pagina di stato che facesse riferimento a questi monitor. Esporta prima i tuoi monitor per conservare un backup dei loro dati."
}
},
"exportMonitors": {
@@ -1423,7 +1423,7 @@
}
},
"statusPages": {
- "deleteSuccess": "Pagina di stato eliminata con successo",
+ "deleteSuccess": "Pagina di stato eliminata correttamente",
"fallback": {
"title": "Nessuna pagina di stato ancora",
"description": "Pubblica una pagina pubblica che mostra l'uptime in tempo reale e la cronologia degli incidenti ai tuoi clienti e stakeholder.",
@@ -1438,7 +1438,7 @@
"down": "Non attivo",
"breached": "Soglia superata",
"maintenance": "Manutenzione",
- "paused": "Sospeso",
+ "paused": "In pausa",
"initializing": "Inizializzazione"
},
"chart": {
@@ -1463,20 +1463,20 @@
}
},
"statusBar": {
- "monitoringSummary": "Monitoraggio di {{count}} servizio · Aggiornamenti ogni 10 secondi",
- "monitoringSummary_other": "Monitoraggio di {{count}} servizi · Aggiornamenti ogni 10 secondi",
+ "monitoringSummary": "Monitoraggio di {{count}} servizio · aggiornamenti ogni 10 secondi",
+ "monitoringSummary_other": "Monitoraggio di {{count}} servizi · aggiornamenti ogni 10 secondi",
"allUp": "Tutti i sistemi operativi",
"allBreached": "Tutti i sistemi hanno superato le soglie",
"allMaintenance": "Tutti i sistemi sono in manutenzione",
"allDown": "Tutti i sistemi sono non attivi",
- "allPaused": "Tutti i sistemi sono sospesi",
+ "allPaused": "Tutti i sistemi sono in pausa",
"allInitializing": "Tutti i sistemi sono in inizializzazione",
"breachedAndDown": "Alcuni sistemi hanno superato le soglie e sono non attivi",
"breached": "Alcuni sistemi hanno superato le soglie",
"degraded": "Alcuni sistemi stanno riscontrando problemi",
"maintenanceAndDown": "Alcuni sistemi sono non attivi durante la manutenzione",
"maintenance": "Alcuni sistemi sono in manutenzione",
- "partiallyPaused": "Alcuni sistemi sono sospesi",
+ "partiallyPaused": "Alcuni sistemi sono in pausa",
"initializing": "Alcuni sistemi sono in inizializzazione",
"noMonitors": "Nessun monitor è stato configurato",
"unknown": "Impossibile determinare lo stato del sistema"
@@ -1500,7 +1500,7 @@
"poweredBy": "Powered by"
},
"editorial": {
- "reportTitle": "Report di servizio",
+ "reportTitle": "Rapporto di servizio",
"allUp": "Tutti i sistemi funzionano normalmente."
},
"form": {
@@ -1528,7 +1528,7 @@
"customDomain": {
"label": "Dominio personalizzato",
"placeholder": "status.example.com",
- "helper": "Opzionale. Punta un record DNS a questa istanza Checkmate per servire la pagina di stato sul tuo dominio."
+ "helper": "Opzionale. Punta un record DNS a questa istanza di Checkmate per servire la pagina di stato sul tuo dominio."
}
}
},
@@ -1579,7 +1579,7 @@
"description": "Livello SaaS con un punto di stato pulsante e schede in dissolvenza morbida."
},
"bold": {
- "name": "Bold",
+ "name": "Deciso",
"description": "Caratteri grandi e spaziatura decisa. Ottimo per pagine focalizzate sul brand."
},
"editorial": {
@@ -1650,12 +1650,12 @@
},
"fallback": {
"actionButton": "Crea un monitor",
- "description": "Controlla endpoint HTTP, ping, container e porte — e ricevi avvisi nel momento in cui qualcosa va non attivo.",
- "title": "Nessun monitor uptime ancora"
+ "description": "Controlla endpoint HTTP, ping, container e porte — e ricevi un avviso nel momento in cui qualcosa va giù.",
+ "title": "Nessun monitor uptime"
},
"header": {
"title": "Monitor uptime",
- "description": "Controlla endpoint HTTP, ping, container e porte — e ricevi avvisi nel momento in cui qualcosa va non attivo."
+ "description": "Controlla endpoint HTTP, ping, container e porte — e ricevi un avviso nel momento in cui qualcosa va giù."
}
}
}
From 5d55750b7b0381abeb16732e1671b06823dd17eb Mon Sep 17 00:00:00 2001
From: Alex Holliday
Date: Mon, 13 Jul 2026 09:38:55 -0700
Subject: [PATCH 22/84] update docs
---
README.md | 21 +++++++++++++++++++++
docs/hosting/checkmate-so/README.md | 4 +++-
2 files changed, 24 insertions(+), 1 deletion(-)
diff --git a/README.md b/README.md
index 05eb688ff..2841149ca 100644
--- a/README.md
+++ b/README.md
@@ -88,6 +88,27 @@ JWT_SECRET="$(openssl rand -hex 32)" docker compose up -d
Then open http://localhost:52345. If the app is reached at another origin (domain or LAN IP), set `CLIENT_HOST` accordingly. To build the image yourself, run `docker build -f docker/Dockerfile -t checkmate .` from a checkout. For TLS, put any reverse proxy (Caddy, Traefik, nginx) in front of port 52345.
+### Configuration
+
+The image is configured entirely through environment variables on the server container:
+
+| Variable | Required | Description |
+|---|---|---|
+| `DB_CONNECTION_STRING` | Yes | MongoDB connection string, e.g. `mongodb://mongodb:27017/uptime_db` |
+| `JWT_SECRET` | Yes | Secret used to sign auth tokens; generate one with `openssl rand -hex 32` |
+| `CLIENT_HOST` | Yes | The URL users reach the app at, e.g. `https://checkmate.example.com`; used for CORS and for links in notifications and emails |
+| `LOG_LEVEL` | No | Server log level: `error`, `warn`, `info`, or `debug` (default `debug`) |
+
+The web client needs no configuration by default: it calls the API on the same origin it was served from (`/api/v1`). For setups where the defaults don't apply — for example, the API is reached through a different origin than the page — the server renders overrides into the client at runtime via these optional variables:
+
+| Variable | Description |
+|---|---|
+| `CLIENT_CONFIG_API_BASE_URL` | Full base URL the client calls the API at, e.g. `https://api.example.com/api/v1`; defaults to same-origin `/api/v1` |
+| `CLIENT_CONFIG_CLIENT_HOST` | Origin used when the client builds absolute links (invites, status pages); defaults to the browser's current origin |
+| `CLIENT_CONFIG_LOG_LEVEL` | Browser console log level: `error`, `warn`, `info`, or `debug` (default `error`) |
+
+> **Upgrading from an older image?** The `UPTIME_APP_*` variables (`UPTIME_APP_API_BASE_URL`, `UPTIME_APP_CLIENT_HOST`, `UPTIME_APP_LOG_LEVEL`) are no longer read. In most setups no replacement is needed — the same-origin defaults cover them; if you pointed the client at a different origin, use the `CLIENT_CONFIG_*` equivalents above. The `checkmate-client`, `checkmate-backend`, `checkmate-mongo`, and `checkmate-backend-mono-multiarch` images are no longer updated — switch to `ghcr.io/bluewave-labs/checkmate`, keeping your existing MongoDB service and data volume.
+
See full installation instructions in the [Checkmate documentation portal](https://checkmate.so/docs).
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), [Cloudzy](https://cloudzy.com/marketplace/checkmate) 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.
diff --git a/docs/hosting/checkmate-so/README.md b/docs/hosting/checkmate-so/README.md
index 2a7e9f648..0c06f6948 100644
--- a/docs/hosting/checkmate-so/README.md
+++ b/docs/hosting/checkmate-so/README.md
@@ -29,7 +29,9 @@ Kept next to this compose file on the host:
- `server-prod.env` / `server-staging.env` — Checkmate server env
(`DB_CONNECTION_STRING`, `JWT_SECRET`, `CLIENT_HOST=https://demo.checkmate.so`
- etc.). The old `UPTIME_APP_*` client vars are no-ops now and can be removed.
+ etc.). The old `UPTIME_APP_*` client vars are no-ops now and can be removed;
+ if a client override is ever needed, use the `CLIENT_CONFIG_*` vars (see the
+ Configuration section of the root README).
- `mongo-prod.env` / `mongo-staging.env` — Mongo credentials.
- `mongo/data-prod` / `mongo/data-staging` — Mongo data directories.
From c1bcd9b206364660c75b43aa11058d9bfb7ec73d Mon Sep 17 00:00:00 2001
From: Alex Holliday
Date: Mon, 13 Jul 2026 09:47:45 -0700
Subject: [PATCH 23/84] add note to helm chart about migration
---
charts/helm/checkmate/Chart.yaml | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/charts/helm/checkmate/Chart.yaml b/charts/helm/checkmate/Chart.yaml
index c9433f690..286f1c908 100644
--- a/charts/helm/checkmate/Chart.yaml
+++ b/charts/helm/checkmate/Chart.yaml
@@ -3,4 +3,8 @@ name: checkmate-chart
description: A Helm chart for Checkmate App
type: application
version: 0.2.0
+# DO NOT bump appVersion past v3.8.1 until this chart is migrated to the all-in-one
+# image (ghcr.io/bluewave-labs/checkmate). The checkmate-client / checkmate-backend /
+# checkmate-mongo images this chart references are frozen at v3.8.1 — later versions
+# are never published, so a bumped chart will fail to pull.
appVersion: "v3.8.1" # "v" prefix: published image tags are v-prefixed (ghcr.io/.../checkmate-*:v3.8.1)
From 800080fd0633c28a352dfd16f75204e6c509cd9d Mon Sep 17 00:00:00 2001
From: Alex Holliday
Date: Mon, 13 Jul 2026 09:48:02 -0700
Subject: [PATCH 24/84] add log warnings about legacy env vars
---
server/src/config/envValidation.ts | 9 +++++++++
1 file changed, 9 insertions(+)
diff --git a/server/src/config/envValidation.ts b/server/src/config/envValidation.ts
index 0bfaa01ce..a769a28c7 100644
--- a/server/src/config/envValidation.ts
+++ b/server/src/config/envValidation.ts
@@ -69,6 +69,15 @@ export const validateEnv = (logger: ILogger): ValidatedEnv => {
process.exit(1);
}
+ const legacyClientVars = Object.keys(process.env).filter((key) => key.startsWith("UPTIME_APP_"));
+ if (legacyClientVars.length > 0) {
+ logger.warn({
+ message: `${legacyClientVars.join(", ")} no longer configure the client and will be ignored. The client defaults to the origin it is served from; to override, use CLIENT_CONFIG_API_BASE_URL, CLIENT_CONFIG_CLIENT_HOST, or CLIENT_CONFIG_LOG_LEVEL.`,
+ method: "validateEnv",
+ service: "Server",
+ });
+ }
+
logger.info({
message: "Environment variables validated successfully",
method: "validateEnv",
From 9f79feec8ecdb5d4b67f61b4dd9aa17a6295baa5 Mon Sep 17 00:00:00 2001
From: Alex Holliday
Date: Mon, 13 Jul 2026 10:10:28 -0700
Subject: [PATCH 25/84] add img.shields.io to csp directive
---
server/src/app.ts | 1 +
1 file changed, 1 insertion(+)
diff --git a/server/src/app.ts b/server/src/app.ts
index f195d8140..12e552406 100644
--- a/server/src/app.ts
+++ b/server/src/app.ts
@@ -76,6 +76,7 @@ export const createApp = ({
directives: {
upgradeInsecureRequests: null,
"script-src": ["'self'", "'unsafe-inline'", "'unsafe-eval'"],
+ "img-src": ["'self'", "data:", "https://img.shields.io"],
"object-src": ["'none'"],
"base-uri": ["'self'"],
},
From fd4edb72278c8d68bbb32c8b956f773828b7e1d0 Mon Sep 17 00:00:00 2001
From: edobusy
Date: Tue, 14 Jul 2026 13:43:37 +1000
Subject: [PATCH 26/84] fix(docs): correct K8s install link path in translated
READMEs
The translated READMEs in docs/translations/ link to the Kubernetes
installation guide with a path copied from the root README
(./charts/helm/checkmate/INSTALLATION.md). Because these files live two
levels below the repo root, that path resolves to
docs/translations/charts/..., which does not exist, so the link is
broken in all 17 translations.
Fix the path to ../../charts/helm/checkmate/INSTALLATION.md so it
resolves to the repo-root charts/ directory. The root README.md is
correct as-is and is left unchanged.
---
docs/translations/README.ar.md | 2 +-
docs/translations/README.ca.md | 2 +-
docs/translations/README.cs.md | 2 +-
docs/translations/README.de.md | 2 +-
docs/translations/README.es.md | 2 +-
docs/translations/README.fi.md | 2 +-
docs/translations/README.fr.md | 2 +-
docs/translations/README.it.md | 2 +-
docs/translations/README.ja.md | 2 +-
docs/translations/README.pt-BR.md | 2 +-
docs/translations/README.ru.md | 2 +-
docs/translations/README.th.md | 2 +-
docs/translations/README.tr.md | 2 +-
docs/translations/README.uk.md | 2 +-
docs/translations/README.vi.md | 2 +-
docs/translations/README.zh-CN.md | 2 +-
docs/translations/README.zh-TW.md | 2 +-
17 files changed, 17 insertions(+), 17 deletions(-)
diff --git a/docs/translations/README.ar.md b/docs/translations/README.ar.md
index 4997fc381..8ff16e140 100644
--- a/docs/translations/README.ar.md
+++ b/docs/translations/README.ar.md
@@ -87,7 +87,7 @@
اطّلع على تعليمات التثبيت في [بوابة وثائق Checkmate](https://checkmate.so/docs).
-بدلاً من ذلك، يمكنك استخدام [Coolify](https://coolify.io/)، أو [Elestio](https://elest.io/open-source/checkmate)، أو [K8s](./charts/helm/checkmate/INSTALLATION.md)، أو [Sive Host](https://sive.host) (جنوب أفريقيا)، أو [Cloudzy](https://cloudzy.com/marketplace/checkmate)، أو [Pikapods](https://www.pikapods.com/) لتشغيل نسخة من Checkmate بسرعة. إن أردت مراقبة البنية التحتية لخوادمك، ستحتاج إلى [وكيل Capture](https://github.com/bluewave-labs/capture). يحتوي مستودع Capture أيضًا على تعليمات التثبيت.
+بدلاً من ذلك، يمكنك استخدام [Coolify](https://coolify.io/)، أو [Elestio](https://elest.io/open-source/checkmate)، أو [K8s](../../charts/helm/checkmate/INSTALLATION.md)، أو [Sive Host](https://sive.host) (جنوب أفريقيا)، أو [Cloudzy](https://cloudzy.com/marketplace/checkmate)، أو [Pikapods](https://www.pikapods.com/) لتشغيل نسخة من Checkmate بسرعة. إن أردت مراقبة البنية التحتية لخوادمك، ستحتاج إلى [وكيل Capture](https://github.com/bluewave-labs/capture). يحتوي مستودع Capture أيضًا على تعليمات التثبيت.
### استخدام شهادة CA مخصصة
diff --git a/docs/translations/README.ca.md b/docs/translations/README.ca.md
index 3552d6c4c..29e7e4107 100644
--- a/docs/translations/README.ca.md
+++ b/docs/translations/README.ca.md
@@ -85,7 +85,7 @@ Pots trobar les instruccions d'ús [aquí](https://checkmate.so/docs).
Consulta les instruccions d'instal·lació al [portal de documentació de Checkmate](https://checkmate.so/docs).
-Alternativament, també pots utilitzar [Coolify](https://coolify.io/), [Elestio](https://elest.io/open-source/checkmate), [K8s](./charts/helm/checkmate/INSTALLATION.md), [Sive Host](https://sive.host) (Sud-àfrica), [Cloudzy](https://cloudzy.com/marketplace/checkmate) o [Pikapods](https://www.pikapods.com/) per posar en marxa ràpidament una instància de Checkmate. Si vols monitoritzar la infraestructura de servidors, necessitaràs l'[agent Capture](https://github.com/bluewave-labs/capture). El repositori de Capture també conté les instruccions d'instal·lació.
+Alternativament, també pots utilitzar [Coolify](https://coolify.io/), [Elestio](https://elest.io/open-source/checkmate), [K8s](../../charts/helm/checkmate/INSTALLATION.md), [Sive Host](https://sive.host) (Sud-àfrica), [Cloudzy](https://cloudzy.com/marketplace/checkmate) o [Pikapods](https://www.pikapods.com/) per posar en marxa ràpidament una instància de Checkmate. Si vols monitoritzar la infraestructura de servidors, necessitaràs l'[agent Capture](https://github.com/bluewave-labs/capture). El repositori de Capture també conté les instruccions d'instal·lació.
### Utilitzar una CA personalitzada
diff --git a/docs/translations/README.cs.md b/docs/translations/README.cs.md
index 13dd5d8b8..d662f0b70 100644
--- a/docs/translations/README.cs.md
+++ b/docs/translations/README.cs.md
@@ -85,7 +85,7 @@ Pokyny k použití najdete [zde](https://checkmate.so/docs).
Pokyny k instalaci najdete v [dokumentačním portálu Checkmate](https://checkmate.so/docs).
-Případně můžete použít [Coolify](https://coolify.io/), [Elestio](https://elest.io/open-source/checkmate), [K8s](./charts/helm/checkmate/INSTALLATION.md), [Sive Host](https://sive.host) (Jižní Afrika), [Cloudzy](https://cloudzy.com/marketplace/checkmate) nebo [Pikapods](https://www.pikapods.com/) pro rychlé spuštění instance Checkmate. Pokud chcete monitorovat infrastrukturu serverů, budete potřebovat [agenta Capture](https://github.com/bluewave-labs/capture). Repozitář Capture obsahuje také pokyny k instalaci.
+Případně můžete použít [Coolify](https://coolify.io/), [Elestio](https://elest.io/open-source/checkmate), [K8s](../../charts/helm/checkmate/INSTALLATION.md), [Sive Host](https://sive.host) (Jižní Afrika), [Cloudzy](https://cloudzy.com/marketplace/checkmate) nebo [Pikapods](https://www.pikapods.com/) pro rychlé spuštění instance Checkmate. Pokud chcete monitorovat infrastrukturu serverů, budete potřebovat [agenta Capture](https://github.com/bluewave-labs/capture). Repozitář Capture obsahuje také pokyny k instalaci.
### Použití vlastní CA
diff --git a/docs/translations/README.de.md b/docs/translations/README.de.md
index 21f72165e..faee4e44c 100644
--- a/docs/translations/README.de.md
+++ b/docs/translations/README.de.md
@@ -82,7 +82,7 @@ Die Nutzungsanleitung findest du [hier](https://checkmate.so/docs).
Die Installationsanleitung findest du im [Dokumentationsportal von Checkmate](https://checkmate.so/docs).
-Alternativ kannst du [Coolify](https://coolify.io/), [Elestio](https://elest.io/open-source/checkmate), [K8s](./charts/helm/checkmate/INSTALLATION.md), [Sive Host](https://sive.host) (Südafrika), [Cloudzy](https://cloudzy.com/marketplace/checkmate) oder [Pikapods](https://www.pikapods.com/) nutzen, um schnell eine Checkmate-Instanz hochzuziehen. Für das Monitoring deiner Server-Infrastruktur brauchst du den [Capture-Agenten](https://github.com/bluewave-labs/capture); dessen Repository enthält ebenfalls eine Installationsanleitung.
+Alternativ kannst du [Coolify](https://coolify.io/), [Elestio](https://elest.io/open-source/checkmate), [K8s](../../charts/helm/checkmate/INSTALLATION.md), [Sive Host](https://sive.host) (Südafrika), [Cloudzy](https://cloudzy.com/marketplace/checkmate) oder [Pikapods](https://www.pikapods.com/) nutzen, um schnell eine Checkmate-Instanz hochzuziehen. Für das Monitoring deiner Server-Infrastruktur brauchst du den [Capture-Agenten](https://github.com/bluewave-labs/capture); dessen Repository enthält ebenfalls eine Installationsanleitung.
### Eigene CA verwenden
diff --git a/docs/translations/README.es.md b/docs/translations/README.es.md
index cbd8f4258..5dc04e269 100644
--- a/docs/translations/README.es.md
+++ b/docs/translations/README.es.md
@@ -82,7 +82,7 @@ Las instrucciones de uso están disponibles [aquí](https://checkmate.so/docs).
Consulta las instrucciones de instalación en el [portal de documentación de Checkmate](https://checkmate.so/docs).
-También puedes utilizar [Coolify](https://coolify.io/), [Elestio](https://elest.io/open-source/checkmate), [K8s](./charts/helm/checkmate/INSTALLATION.md), [Sive Host](https://sive.host) (Sudáfrica), [Cloudzy](https://cloudzy.com/marketplace/checkmate) o [Pikapods](https://www.pikapods.com/) para levantar rápidamente una instancia de Checkmate. Si quieres monitorizar la infraestructura de tus servidores, necesitarás el [agente Capture](https://github.com/bluewave-labs/capture). El repositorio de Capture también incluye las instrucciones de instalación.
+También puedes utilizar [Coolify](https://coolify.io/), [Elestio](https://elest.io/open-source/checkmate), [K8s](../../charts/helm/checkmate/INSTALLATION.md), [Sive Host](https://sive.host) (Sudáfrica), [Cloudzy](https://cloudzy.com/marketplace/checkmate) o [Pikapods](https://www.pikapods.com/) para levantar rápidamente una instancia de Checkmate. Si quieres monitorizar la infraestructura de tus servidores, necesitarás el [agente Capture](https://github.com/bluewave-labs/capture). El repositorio de Capture también incluye las instrucciones de instalación.
### Uso de una CA personalizada
diff --git a/docs/translations/README.fi.md b/docs/translations/README.fi.md
index cbfcb76cb..877130120 100644
--- a/docs/translations/README.fi.md
+++ b/docs/translations/README.fi.md
@@ -85,7 +85,7 @@ Käyttöohjeet löytyvät [täältä](https://checkmate.so/docs).
Asennusohjeet löytyvät [Checkmaten dokumentaatioportaalista](https://checkmate.so/docs).
-Vaihtoehtoisesti voit käyttää [Coolifya](https://coolify.io/), [Elestiota](https://elest.io/open-source/checkmate), [K8s:ää](./charts/helm/checkmate/INSTALLATION.md), [Sive Hostia](https://sive.host) (Etelä-Afrikka), [Cloudzya](https://cloudzy.com/marketplace/checkmate) tai [Pikapodsia](https://www.pikapods.com/) Checkmate-instanssin nopeaan käynnistämiseen. Jos haluat valvoa palvelininfrastruktuuriasi, tarvitset [Capture-agentin](https://github.com/bluewave-labs/capture). Capturen repositorio sisältää myös sen asennusohjeet.
+Vaihtoehtoisesti voit käyttää [Coolifya](https://coolify.io/), [Elestiota](https://elest.io/open-source/checkmate), [K8s:ää](../../charts/helm/checkmate/INSTALLATION.md), [Sive Hostia](https://sive.host) (Etelä-Afrikka), [Cloudzya](https://cloudzy.com/marketplace/checkmate) tai [Pikapodsia](https://www.pikapods.com/) Checkmate-instanssin nopeaan käynnistämiseen. Jos haluat valvoa palvelininfrastruktuuriasi, tarvitset [Capture-agentin](https://github.com/bluewave-labs/capture). Capturen repositorio sisältää myös sen asennusohjeet.
### Mukautetun CA:n käyttö
diff --git a/docs/translations/README.fr.md b/docs/translations/README.fr.md
index 209fc3dcc..e78a6cf3c 100644
--- a/docs/translations/README.fr.md
+++ b/docs/translations/README.fr.md
@@ -82,7 +82,7 @@ Les instructions d'utilisation sont disponibles [ici](https://checkmate.so/docs)
Consultez les instructions d'installation dans le [portail de documentation Checkmate](https://checkmate.so/docs).
-Vous pouvez également utiliser [Coolify](https://coolify.io/), [Elestio](https://elest.io/open-source/checkmate), [K8s](./charts/helm/checkmate/INSTALLATION.md), [Sive Host](https://sive.host) (Afrique du Sud), [Cloudzy](https://cloudzy.com/marketplace/checkmate) ou [Pikapods](https://www.pikapods.com/) pour démarrer rapidement une instance Checkmate. Pour surveiller votre infrastructure serveur, vous aurez besoin de l'[agent Capture](https://github.com/bluewave-labs/capture). Le dépôt Capture contient également les instructions d'installation.
+Vous pouvez également utiliser [Coolify](https://coolify.io/), [Elestio](https://elest.io/open-source/checkmate), [K8s](../../charts/helm/checkmate/INSTALLATION.md), [Sive Host](https://sive.host) (Afrique du Sud), [Cloudzy](https://cloudzy.com/marketplace/checkmate) ou [Pikapods](https://www.pikapods.com/) pour démarrer rapidement une instance Checkmate. Pour surveiller votre infrastructure serveur, vous aurez besoin de l'[agent Capture](https://github.com/bluewave-labs/capture). Le dépôt Capture contient également les instructions d'installation.
### Utiliser une autorité de certification personnalisée
diff --git a/docs/translations/README.it.md b/docs/translations/README.it.md
index f97cb8420..1f03bd682 100644
--- a/docs/translations/README.it.md
+++ b/docs/translations/README.it.md
@@ -82,7 +82,7 @@ Le istruzioni per l'uso sono disponibili [qui](https://checkmate.so/docs).
Consulta le istruzioni di installazione nel [portale della documentazione di Checkmate](https://checkmate.so/docs).
-In alternativa, puoi usare [Coolify](https://coolify.io/), [Elestio](https://elest.io/open-source/checkmate), [K8s](./charts/helm/checkmate/INSTALLATION.md), [Sive Host](https://sive.host) (Sudafrica), [Cloudzy](https://cloudzy.com/marketplace/checkmate) o [Pikapods](https://www.pikapods.com/) per creare rapidamente un'istanza di Checkmate. Se desideri monitorare l'infrastruttura dei tuoi server, ti servirà l'[agente Capture](https://github.com/bluewave-labs/capture). Anche il repository di Capture contiene le istruzioni di installazione.
+In alternativa, puoi usare [Coolify](https://coolify.io/), [Elestio](https://elest.io/open-source/checkmate), [K8s](../../charts/helm/checkmate/INSTALLATION.md), [Sive Host](https://sive.host) (Sudafrica), [Cloudzy](https://cloudzy.com/marketplace/checkmate) o [Pikapods](https://www.pikapods.com/) per creare rapidamente un'istanza di Checkmate. Se desideri monitorare l'infrastruttura dei tuoi server, ti servirà l'[agente Capture](https://github.com/bluewave-labs/capture). Anche il repository di Capture contiene le istruzioni di installazione.
### Usare una CA personalizzata
diff --git a/docs/translations/README.ja.md b/docs/translations/README.ja.md
index 6534a3e3a..bbb446a51 100644
--- a/docs/translations/README.ja.md
+++ b/docs/translations/README.ja.md
@@ -82,7 +82,7 @@ Checkmate は 1000 以上のアクティブなモニターでストレステス
インストール手順は [Checkmate ドキュメントポータル](https://checkmate.so/docs) を参照してください。
-代わりに、[Coolify](https://coolify.io/)、[Elestio](https://elest.io/open-source/checkmate)、[K8s](./charts/helm/checkmate/INSTALLATION.md)、[Sive Host](https://sive.host)(南アフリカ)、[Cloudzy](https://cloudzy.com/marketplace/checkmate)、[Pikapods](https://www.pikapods.com/) を使って Checkmate インスタンスをすばやく立ち上げることもできます。サーバーインフラを監視したい場合は、[Capture エージェント](https://github.com/bluewave-labs/capture) が必要です。Capture リポジトリにもインストール手順が記載されています。
+代わりに、[Coolify](https://coolify.io/)、[Elestio](https://elest.io/open-source/checkmate)、[K8s](../../charts/helm/checkmate/INSTALLATION.md)、[Sive Host](https://sive.host)(南アフリカ)、[Cloudzy](https://cloudzy.com/marketplace/checkmate)、[Pikapods](https://www.pikapods.com/) を使って Checkmate インスタンスをすばやく立ち上げることもできます。サーバーインフラを監視したい場合は、[Capture エージェント](https://github.com/bluewave-labs/capture) が必要です。Capture リポジトリにもインストール手順が記載されています。
### カスタム CA の利用
diff --git a/docs/translations/README.pt-BR.md b/docs/translations/README.pt-BR.md
index 6ddbe8367..ee35ca4f7 100644
--- a/docs/translations/README.pt-BR.md
+++ b/docs/translations/README.pt-BR.md
@@ -82,7 +82,7 @@ As instruções de uso podem ser encontradas [aqui](https://checkmate.so/docs).
Veja as instruções de instalação no [portal de documentação do Checkmate](https://checkmate.so/docs).
-Alternativamente, você pode usar [Coolify](https://coolify.io/), [Elestio](https://elest.io/open-source/checkmate), [K8s](./charts/helm/checkmate/INSTALLATION.md), [Sive Host](https://sive.host) (África do Sul), [Cloudzy](https://cloudzy.com/marketplace/checkmate) ou [Pikapods](https://www.pikapods.com/) para subir rapidamente uma instância do Checkmate. Se quiser monitorar sua infraestrutura de servidores, você precisará do [agente Capture](https://github.com/bluewave-labs/capture). O repositório do Capture também contém as instruções de instalação.
+Alternativamente, você pode usar [Coolify](https://coolify.io/), [Elestio](https://elest.io/open-source/checkmate), [K8s](../../charts/helm/checkmate/INSTALLATION.md), [Sive Host](https://sive.host) (África do Sul), [Cloudzy](https://cloudzy.com/marketplace/checkmate) ou [Pikapods](https://www.pikapods.com/) para subir rapidamente uma instância do Checkmate. Se quiser monitorar sua infraestrutura de servidores, você precisará do [agente Capture](https://github.com/bluewave-labs/capture). O repositório do Capture também contém as instruções de instalação.
### Usando uma CA personalizada
diff --git a/docs/translations/README.ru.md b/docs/translations/README.ru.md
index 1e22d0c9c..d9b5dcb1c 100644
--- a/docs/translations/README.ru.md
+++ b/docs/translations/README.ru.md
@@ -82,7 +82,7 @@ Checkmate прошёл нагрузочное тестирование с 1000+
Инструкции по установке смотрите на [портале документации Checkmate](https://checkmate.so/docs).
-В качестве альтернативы вы можете использовать [Coolify](https://coolify.io/), [Elestio](https://elest.io/open-source/checkmate), [K8s](./charts/helm/checkmate/INSTALLATION.md), [Sive Host](https://sive.host) (ЮАР), [Cloudzy](https://cloudzy.com/marketplace/checkmate) или [Pikapods](https://www.pikapods.com/), чтобы быстро поднять инстанс Checkmate. Если вы хотите мониторить серверную инфраструктуру, потребуется агент [Capture](https://github.com/bluewave-labs/capture). В репозитории Capture также есть инструкции по установке.
+В качестве альтернативы вы можете использовать [Coolify](https://coolify.io/), [Elestio](https://elest.io/open-source/checkmate), [K8s](../../charts/helm/checkmate/INSTALLATION.md), [Sive Host](https://sive.host) (ЮАР), [Cloudzy](https://cloudzy.com/marketplace/checkmate) или [Pikapods](https://www.pikapods.com/), чтобы быстро поднять инстанс Checkmate. Если вы хотите мониторить серверную инфраструктуру, потребуется агент [Capture](https://github.com/bluewave-labs/capture). В репозитории Capture также есть инструкции по установке.
### Использование собственного УЦ
diff --git a/docs/translations/README.th.md b/docs/translations/README.th.md
index 6e02f662e..61b4953ca 100644
--- a/docs/translations/README.th.md
+++ b/docs/translations/README.th.md
@@ -85,7 +85,7 @@ Checkmate ผ่านการทดสอบโหลดสำหรับม
ดูวิธีการติดตั้งใน [พอร์ทัลเอกสารของ Checkmate](https://checkmate.so/docs)
-อีกทางเลือกหนึ่ง คุณสามารถใช้ [Coolify](https://coolify.io/), [Elestio](https://elest.io/open-source/checkmate), [K8s](./charts/helm/checkmate/INSTALLATION.md), [Sive Host](https://sive.host) (แอฟริกาใต้), [Cloudzy](https://cloudzy.com/marketplace/checkmate) หรือ [Pikapods](https://www.pikapods.com/) เพื่อเปิดอินสแตนซ์ Checkmate ได้อย่างรวดเร็ว หากต้องการมอนิเตอร์โครงสร้างพื้นฐานเซิร์ฟเวอร์ คุณจะต้องใช้ [เอเจนต์ Capture](https://github.com/bluewave-labs/capture) รีโพ Capture ยังมีวิธีการติดตั้งด้วย
+อีกทางเลือกหนึ่ง คุณสามารถใช้ [Coolify](https://coolify.io/), [Elestio](https://elest.io/open-source/checkmate), [K8s](../../charts/helm/checkmate/INSTALLATION.md), [Sive Host](https://sive.host) (แอฟริกาใต้), [Cloudzy](https://cloudzy.com/marketplace/checkmate) หรือ [Pikapods](https://www.pikapods.com/) เพื่อเปิดอินสแตนซ์ Checkmate ได้อย่างรวดเร็ว หากต้องการมอนิเตอร์โครงสร้างพื้นฐานเซิร์ฟเวอร์ คุณจะต้องใช้ [เอเจนต์ Capture](https://github.com/bluewave-labs/capture) รีโพ Capture ยังมีวิธีการติดตั้งด้วย
### การใช้ Custom CA
diff --git a/docs/translations/README.tr.md b/docs/translations/README.tr.md
index 1e0b58416..7e41aec43 100644
--- a/docs/translations/README.tr.md
+++ b/docs/translations/README.tr.md
@@ -82,7 +82,7 @@ Kullanım talimatlarını [burada](https://checkmate.so/docs) bulabilirsiniz.
Kurulum talimatları için [Checkmate dokümantasyon portalına](https://checkmate.so/docs) bakın.
-Alternatif olarak hızlıca bir Checkmate örneği çalıştırmak için [Coolify](https://coolify.io/), [Elestio](https://elest.io/open-source/checkmate), [K8s](./charts/helm/checkmate/INSTALLATION.md), [Sive Host](https://sive.host) (Güney Afrika), [Cloudzy](https://cloudzy.com/marketplace/checkmate) veya [Pikapods](https://www.pikapods.com/)'u kullanabilirsiniz. Sunucu altyapınızı izlemek isterseniz [Capture ajanına](https://github.com/bluewave-labs/capture) ihtiyacınız olacak. Capture deposu da kurulum talimatlarını içerir.
+Alternatif olarak hızlıca bir Checkmate örneği çalıştırmak için [Coolify](https://coolify.io/), [Elestio](https://elest.io/open-source/checkmate), [K8s](../../charts/helm/checkmate/INSTALLATION.md), [Sive Host](https://sive.host) (Güney Afrika), [Cloudzy](https://cloudzy.com/marketplace/checkmate) veya [Pikapods](https://www.pikapods.com/)'u kullanabilirsiniz. Sunucu altyapınızı izlemek isterseniz [Capture ajanına](https://github.com/bluewave-labs/capture) ihtiyacınız olacak. Capture deposu da kurulum talimatlarını içerir.
### Özel CA Kullanımı
diff --git a/docs/translations/README.uk.md b/docs/translations/README.uk.md
index 798cfd271..7322afac7 100644
--- a/docs/translations/README.uk.md
+++ b/docs/translations/README.uk.md
@@ -85,7 +85,7 @@ Checkmate пройшов навантажувальне тестування з
Інструкції зі встановлення дивіться у [порталі документації Checkmate](https://checkmate.so/docs).
-Альтернативно, для швидкого розгортання Checkmate можна скористатися [Coolify](https://coolify.io/), [Elestio](https://elest.io/open-source/checkmate), [K8s](./charts/helm/checkmate/INSTALLATION.md), [Sive Host](https://sive.host) (Південна Африка), [Cloudzy](https://cloudzy.com/marketplace/checkmate) або [Pikapods](https://www.pikapods.com/). Якщо хочете моніторити серверну інфраструктуру, знадобиться агент [Capture](https://github.com/bluewave-labs/capture). У репозиторії Capture також є інструкції зі встановлення.
+Альтернативно, для швидкого розгортання Checkmate можна скористатися [Coolify](https://coolify.io/), [Elestio](https://elest.io/open-source/checkmate), [K8s](../../charts/helm/checkmate/INSTALLATION.md), [Sive Host](https://sive.host) (Південна Африка), [Cloudzy](https://cloudzy.com/marketplace/checkmate) або [Pikapods](https://www.pikapods.com/). Якщо хочете моніторити серверну інфраструктуру, знадобиться агент [Capture](https://github.com/bluewave-labs/capture). У репозиторії Capture також є інструкції зі встановлення.
### Використання власного УЦ
diff --git a/docs/translations/README.vi.md b/docs/translations/README.vi.md
index f800c77a6..36bbac7ca 100644
--- a/docs/translations/README.vi.md
+++ b/docs/translations/README.vi.md
@@ -85,7 +85,7 @@ Có thể tìm thấy hướng dẫn sử dụng [tại đây](https://checkmate
Xem hướng dẫn cài đặt trong [cổng tài liệu của Checkmate](https://checkmate.so/docs).
-Hoặc bạn có thể dùng [Coolify](https://coolify.io/), [Elestio](https://elest.io/open-source/checkmate), [K8s](./charts/helm/checkmate/INSTALLATION.md), [Sive Host](https://sive.host) (Nam Phi), [Cloudzy](https://cloudzy.com/marketplace/checkmate) hoặc [Pikapods](https://www.pikapods.com/) để dựng nhanh một instance Checkmate. Nếu muốn giám sát hạ tầng máy chủ, bạn sẽ cần [agent Capture](https://github.com/bluewave-labs/capture). Kho Capture cũng chứa hướng dẫn cài đặt.
+Hoặc bạn có thể dùng [Coolify](https://coolify.io/), [Elestio](https://elest.io/open-source/checkmate), [K8s](../../charts/helm/checkmate/INSTALLATION.md), [Sive Host](https://sive.host) (Nam Phi), [Cloudzy](https://cloudzy.com/marketplace/checkmate) hoặc [Pikapods](https://www.pikapods.com/) để dựng nhanh một instance Checkmate. Nếu muốn giám sát hạ tầng máy chủ, bạn sẽ cần [agent Capture](https://github.com/bluewave-labs/capture). Kho Capture cũng chứa hướng dẫn cài đặt.
### Sử dụng CA tuỳ chỉnh
diff --git a/docs/translations/README.zh-CN.md b/docs/translations/README.zh-CN.md
index eb1e3c6f6..c3916509a 100644
--- a/docs/translations/README.zh-CN.md
+++ b/docs/translations/README.zh-CN.md
@@ -82,7 +82,7 @@ Checkmate 已经在 1000+ 个活跃监控项的环境下完成压力测试,未
请参阅 [Checkmate 文档门户](https://checkmate.so/docs)中的安装说明。
-你也可以使用 [Coolify](https://coolify.io/)、[Elestio](https://elest.io/open-source/checkmate)、[K8s](./charts/helm/checkmate/INSTALLATION.md)、[Sive Host](https://sive.host)(南非)、[Cloudzy](https://cloudzy.com/marketplace/checkmate) 或 [Pikapods](https://www.pikapods.com/) 快速启动一个 Checkmate 实例。如果想监控服务器基础设施,你需要 [Capture 代理](https://github.com/bluewave-labs/capture),Capture 仓库中也提供了安装说明。
+你也可以使用 [Coolify](https://coolify.io/)、[Elestio](https://elest.io/open-source/checkmate)、[K8s](../../charts/helm/checkmate/INSTALLATION.md)、[Sive Host](https://sive.host)(南非)、[Cloudzy](https://cloudzy.com/marketplace/checkmate) 或 [Pikapods](https://www.pikapods.com/) 快速启动一个 Checkmate 实例。如果想监控服务器基础设施,你需要 [Capture 代理](https://github.com/bluewave-labs/capture),Capture 仓库中也提供了安装说明。
### 使用自定义 CA
diff --git a/docs/translations/README.zh-TW.md b/docs/translations/README.zh-TW.md
index c79944190..cbf57b9fe 100644
--- a/docs/translations/README.zh-TW.md
+++ b/docs/translations/README.zh-TW.md
@@ -82,7 +82,7 @@ Checkmate 已在 1000+ 個活躍監控項目下進行過壓力測試,未發生
安裝說明請參閱 [Checkmate 文件入口](https://checkmate.so/docs)。
-你也可以使用 [Coolify](https://coolify.io/)、[Elestio](https://elest.io/open-source/checkmate)、[K8s](./charts/helm/checkmate/INSTALLATION.md)、[Sive Host](https://sive.host)(南非)、[Cloudzy](https://cloudzy.com/marketplace/checkmate) 或 [Pikapods](https://www.pikapods.com/) 來快速啟動一個 Checkmate 實例。若要監控伺服器基礎架構,需要 [Capture 代理程式](https://github.com/bluewave-labs/capture),Capture 儲存庫中也提供了安裝說明。
+你也可以使用 [Coolify](https://coolify.io/)、[Elestio](https://elest.io/open-source/checkmate)、[K8s](../../charts/helm/checkmate/INSTALLATION.md)、[Sive Host](https://sive.host)(南非)、[Cloudzy](https://cloudzy.com/marketplace/checkmate) 或 [Pikapods](https://www.pikapods.com/) 來快速啟動一個 Checkmate 實例。若要監控伺服器基礎架構,需要 [Capture 代理程式](https://github.com/bluewave-labs/capture),Capture 儲存庫中也提供了安裝說明。
### 使用自訂 CA
From 107b5cbd9672cbe01a66d23b2ed33542acb74d41 Mon Sep 17 00:00:00 2001
From: Alex Holliday
Date: Tue, 14 Jul 2026 09:24:14 -0700
Subject: [PATCH 27/84] delete incidents on all monitors deleted
---
server/src/domain/monitors/monitor.service.ts | 1 +
1 file changed, 1 insertion(+)
diff --git a/server/src/domain/monitors/monitor.service.ts b/server/src/domain/monitors/monitor.service.ts
index 455a1daff..1402cc453 100644
--- a/server/src/domain/monitors/monitor.service.ts
+++ b/server/src/domain/monitors/monitor.service.ts
@@ -555,6 +555,7 @@ export class MonitorService implements IMonitorService {
await this.geoChecksRepository.deleteByMonitorId(monitor.id);
await this.statusPagesRepository.removeMonitorFromStatusPages(monitor.id);
await this.monitorStatsRepository.deleteByMonitorId(monitor.id);
+ await this.incidentsRepository.deleteByMonitorId(monitor.id, teamId);
} catch (error: unknown) {
this.logger.warn({
message: `Error deleting associated records for monitor ${monitor.id} with name ${monitor.name}`,
From b088a8c878bee3f970af371811399549bf14bd68 Mon Sep 17 00:00:00 2001
From: Alex Holliday
Date: Tue, 14 Jul 2026 10:17:38 -0700
Subject: [PATCH 28/84] remove toEntityWithChecks
---
.../monitors/monitor.repository.mongo.ts | 70 +------------------
1 file changed, 2 insertions(+), 68 deletions(-)
diff --git a/server/src/domain/monitors/monitor.repository.mongo.ts b/server/src/domain/monitors/monitor.repository.mongo.ts
index cba542c96..6db51fb38 100644
--- a/server/src/domain/monitors/monitor.repository.mongo.ts
+++ b/server/src/domain/monitors/monitor.repository.mongo.ts
@@ -249,7 +249,7 @@ class MongoMonitorsRepository implements IMonitorsRepository {
];
const documents = await MonitorModel.aggregate(pipeline);
- return documents.map((doc) => this.toEntityWithChecks(doc));
+ return documents.map((doc) => this.toEntity(doc));
};
findMonitorCountByTeamIdAndType = async (teamId: string, config?: TeamQueryConfig): Promise => {
@@ -525,6 +525,7 @@ class MongoMonitorsRepository implements IMonitorsRepository {
};
const toDateString = (value: Date | string): string => {
+ if (!value) return "";
return value instanceof Date ? value.toISOString() : value;
};
@@ -581,73 +582,6 @@ class MongoMonitorsRepository implements IMonitorsRepository {
};
};
- private toEntityWithChecks = (doc: MonitorDocument): Monitor => {
- const toStringId = (value: unknown): string => {
- if (value instanceof mongoose.Types.ObjectId) {
- return value.toString();
- }
- return value?.toString() ?? "";
- };
-
- const toDateString = (value: Date | string): string => {
- if (!value) return "";
- return value instanceof Date ? value.toISOString() : value;
- };
-
- const notificationIds = (doc.notifications ?? []).map((notification: unknown) => toStringId(notification));
- const tagIds = (doc.tags ?? []).map((tag: unknown) => toStringId(tag));
-
- return {
- id: toStringId(doc._id),
- userId: toStringId(doc.userId),
- teamId: toStringId(doc.teamId),
- name: doc.name,
- description: doc.description ?? undefined,
- method: doc.method ?? "GET",
- status: doc.status ?? "initializing",
- statusWindow: doc.statusWindow ?? [],
- statusWindowSize: doc.statusWindowSize,
- statusWindowThreshold: doc.statusWindowThreshold,
- type: doc.type,
- ignoreTlsErrors: doc.ignoreTlsErrors,
- useAdvancedMatching: doc.useAdvancedMatching ?? false,
- jsonPath: doc.jsonPath ?? undefined,
- expectedValue: doc.expectedValue ?? undefined,
- matchMethod: doc.matchMethod ?? undefined,
- url: doc.url,
- port: doc.port ?? undefined,
- isActive: doc.isActive,
- interval: doc.interval,
- uptimePercentage: doc.uptimePercentage ?? undefined,
- notifications: notificationIds,
- tags: tagIds,
- customUpCodes: doc.customUpCodes ?? [],
- secret: doc.secret ?? undefined,
- cpuAlertThreshold: doc.cpuAlertThreshold,
- cpuAlertCounter: doc.cpuAlertCounter,
- memoryAlertThreshold: doc.memoryAlertThreshold,
- memoryAlertCounter: doc.memoryAlertCounter,
- diskAlertThreshold: doc.diskAlertThreshold,
- diskAlertCounter: doc.diskAlertCounter,
- tempAlertThreshold: doc.tempAlertThreshold,
- tempAlertCounter: doc.tempAlertCounter,
- selectedDisks: doc.selectedDisks ?? [],
- gameId: doc.gameId ?? undefined,
- grpcServiceName: doc.grpcServiceName ?? undefined,
- strategy: doc.strategy ?? undefined,
- group: doc.group ?? null,
- recentChecks: (doc.recentChecks ?? []).map((check: CheckSnapshotDocument) => this.toCheckSnapshot(check)),
- geoCheckEnabled: doc.geoCheckEnabled ?? false,
- geoCheckLocations: doc.geoCheckLocations ?? [],
- geoCheckInterval: doc.geoCheckInterval ?? 300000,
- dnsServer: doc.dnsServer ?? undefined,
- dnsRecordType: doc.dnsRecordType ?? undefined,
- createdAt: toDateString(doc.createdAt),
- updatedAt: toDateString(doc.updatedAt),
- lastEvaluatedAt: doc.lastEvaluatedAt,
- };
- };
-
private toCheckSnapshot = (doc: CheckSnapshotDocument): CheckSnapshot => {
const toDateString = (value: Date | string): string => {
return value instanceof Date ? value.toISOString() : value;
From 70f10a73d8669b17c82e5a28f8ef83c8d07300c8 Mon Sep 17 00:00:00 2001
From: Alex Holliday
Date: Tue, 14 Jul 2026 10:46:25 -0700
Subject: [PATCH 29/84] remove duplicated code
---
.../monitors/monitor.repository.mongo.ts | 127 ++++--------------
1 file changed, 25 insertions(+), 102 deletions(-)
diff --git a/server/src/domain/monitors/monitor.repository.mongo.ts b/server/src/domain/monitors/monitor.repository.mongo.ts
index 6db51fb38..e7ccf56e9 100644
--- a/server/src/domain/monitors/monitor.repository.mongo.ts
+++ b/server/src/domain/monitors/monitor.repository.mongo.ts
@@ -48,19 +48,19 @@ class MongoMonitorsRepository implements IMonitorsRepository {
return this.mapDocuments(monitors);
};
- findByTeamId = async (teamId: string, config: TeamQueryConfig): Promise => {
- const { page = 0, rowsPerPage = 0, filter, field = "createdAt", order = "desc", type, tags } = config ?? {};
+ private queryBuilder = (config: TeamQueryConfig, teamId: string): FilterQuery => {
+ const { filter, field = "createdAt", type, tags } = config ?? {};
- const query: Record = {
- teamId: new mongoose.Types.ObjectId(teamId),
- };
+ const query: FilterQuery = { teamId: new mongoose.Types.ObjectId(teamId) };
if (type !== undefined) {
query.type = Array.isArray(type) ? { $in: type } : type;
}
if (tags !== undefined) {
- query.tags = Array.isArray(tags) ? { $in: tags } : tags;
+ // Convert to ObjectIds: aggregation $match does no schema casting, so raw strings never match the ObjectId refs
+ const tagIds = (Array.isArray(tags) ? tags : [tags]).map((tag) => new mongoose.Types.ObjectId(tag));
+ query.tags = { $in: tagIds };
}
if (filter !== undefined) {
@@ -81,48 +81,28 @@ class MongoMonitorsRepository implements IMonitorsRepository {
break;
}
}
+ return query;
+ };
+ private uptimeStatsLookupStages: PipelineStage[] = [
+ { $lookup: { from: "monitorstats", localField: "_id", foreignField: "monitorId", as: "stats" } },
+ { $addFields: { uptimePercentage: { $arrayElemAt: ["$stats.uptimePercentage", 0] } } },
+ { $project: { stats: 0 } },
+ ];
+
+ findByTeamId = async (teamId: string, config: TeamQueryConfig): Promise => {
+ const { page = 0, rowsPerPage = 0, field = "createdAt", order = "desc" } = config ?? {};
+ const query = this.queryBuilder(config, teamId);
const sort = { [field]: order === "asc" ? 1 : -1 } as const;
const skip = Math.max(page, 0) * rowsPerPage;
-
const documents = await MonitorModel.find(query).sort(sort).skip(skip).limit(rowsPerPage);
-
return this.mapDocuments(documents);
};
findByTeamIdWithStats = async (teamId: string, config: TeamQueryConfig): Promise => {
- const { page = 0, rowsPerPage = 0, filter, field = "createdAt", order = "desc", type, tags } = config ?? {};
+ const { page = 0, rowsPerPage = 0, field = "createdAt", order = "desc" } = config ?? {};
- const query: Record = {
- teamId: new mongoose.Types.ObjectId(teamId),
- };
-
- if (type !== undefined) {
- query.type = Array.isArray(type) ? { $in: type } : type;
- }
-
- if (tags !== undefined) {
- query.tags = Array.isArray(tags) ? { $in: tags } : tags;
- }
-
- if (filter !== undefined) {
- switch (field) {
- case "name":
- query.$or = [{ name: { $regex: filter, $options: "i" } }, { url: { $regex: filter, $options: "i" } }];
- break;
- case "isActive":
- query.isActive = filter === "true";
- break;
- case "status":
- query.status = filter;
- break;
- case "type":
- query.type = filter;
- break;
- default:
- break;
- }
- }
+ const query = this.queryBuilder(config, teamId);
const sort = { [field]: order === "asc" ? 1 : -1 } as const;
const skip = Math.max(page, 0) * rowsPerPage;
@@ -131,24 +111,7 @@ class MongoMonitorsRepository implements IMonitorsRepository {
{ $match: query },
{ $sort: sort },
...(rowsPerPage ? [{ $skip: skip }, { $limit: rowsPerPage }] : []),
- {
- $lookup: {
- from: "monitorstats",
- localField: "_id",
- foreignField: "monitorId",
- as: "stats",
- },
- },
- {
- $addFields: {
- uptimePercentage: { $arrayElemAt: ["$stats.uptimePercentage", 0] },
- },
- },
- {
- $project: {
- stats: 0,
- },
- },
+ ...this.uptimeStatsLookupStages,
];
const documents = await MonitorModel.aggregate(pipeline);
@@ -162,27 +125,7 @@ class MongoMonitorsRepository implements IMonitorsRepository {
const objectIds = monitorIds.map((id) => new mongoose.Types.ObjectId(id));
- const pipeline: PipelineStage[] = [
- { $match: { _id: { $in: objectIds } } },
- {
- $lookup: {
- from: "monitorstats",
- localField: "_id",
- foreignField: "monitorId",
- as: "stats",
- },
- },
- {
- $addFields: {
- uptimePercentage: { $arrayElemAt: ["$stats.uptimePercentage", 0] },
- },
- },
- {
- $project: {
- stats: 0,
- },
- },
- ];
+ const pipeline: PipelineStage[] = [{ $match: { _id: { $in: objectIds } } }, ...this.uptimeStatsLookupStages];
const documents = await MonitorModel.aggregate(pipeline);
return documents.map((doc) => this.toEntity(doc));
@@ -252,21 +195,8 @@ class MongoMonitorsRepository implements IMonitorsRepository {
return documents.map((doc) => this.toEntity(doc));
};
- findMonitorCountByTeamIdAndType = async (teamId: string, config?: TeamQueryConfig): Promise => {
- const { type, tags } = config ?? {};
-
- const query: FilterQuery = {
- teamId: new mongoose.Types.ObjectId(teamId),
- };
-
- if (type !== undefined) {
- query.type = Array.isArray(type) ? { $in: type } : type;
- }
-
- if (tags !== undefined) {
- query.tags = Array.isArray(tags) ? { $in: tags } : tags;
- }
-
+ findMonitorCountByTeamIdAndType = async (teamId: string, config: TeamQueryConfig): Promise => {
+ const query = this.queryBuilder(config, teamId);
const count = await MonitorModel.countDocuments(query);
return count;
};
@@ -394,15 +324,8 @@ class MongoMonitorsRepository implements IMonitorsRepository {
return { monitors: this.mapDocuments(monitors), deletedCount };
};
- findMonitorsSummaryByTeamId = async (teamId: string, config?: SummaryConfig): Promise => {
- const match: FilterQuery = { teamId: new mongoose.Types.ObjectId(teamId) };
- if (config?.type !== undefined) {
- match.type = Array.isArray(config.type) ? { $in: config.type } : config.type;
- }
- if (config?.tags !== undefined) {
- const tagIds = (Array.isArray(config.tags) ? config.tags : [config.tags]).map((tag) => new mongoose.Types.ObjectId(tag));
- match.tags = { $in: tagIds };
- }
+ findMonitorsSummaryByTeamId = async (teamId: string, config: SummaryConfig): Promise => {
+ const match = this.queryBuilder(config, teamId);
const pipeline = [
{ $match: match },
{
From 0810fcbb889489365034f6a98327fe4f8d90ebda Mon Sep 17 00:00:00 2001
From: Alex Holliday
Date: Wed, 15 Jul 2026 09:18:35 -0700
Subject: [PATCH 30/84] refactor hardware aggregation pipeline
---
.../checks/check.hardware.aggregation.ts | 137 ++++++++++++++
.../domain/checks/check.repository.mongo.ts | 171 +-----------------
2 files changed, 142 insertions(+), 166 deletions(-)
create mode 100644 server/src/domain/checks/check.hardware.aggregation.ts
diff --git a/server/src/domain/checks/check.hardware.aggregation.ts b/server/src/domain/checks/check.hardware.aggregation.ts
new file mode 100644
index 000000000..176817315
--- /dev/null
+++ b/server/src/domain/checks/check.hardware.aggregation.ts
@@ -0,0 +1,137 @@
+import { CheckModel } from "@/domain/checks/check.model.js";
+import mongoose from "mongoose";
+
+type DateRange = { start: Date; end: Date };
+type HardwareUpChecks = { totalChecks: number };
+
+const NET_RATE_FIELDS = [
+ ["bytesSentPerSecond", "bytes_sent"],
+ ["deltaBytesRecv", "bytes_recv"],
+ ["deltaPacketsSent", "packets_sent"],
+ ["deltaPacketsRecv", "packets_recv"],
+ ["deltaErrIn", "err_in"],
+ ["deltaErrOut", "err_out"],
+ ["deltaDropIn", "drop_in"],
+ ["deltaDropOut", "drop_out"],
+] as const;
+
+const netRatePerSecond = (sourceField: string) => ({
+ $let: {
+ vars: {
+ tDiff: {
+ $divide: [{ $subtract: [{ $last: "$createdAts" }, { $first: "$createdAts" }] }, 1000],
+ },
+ f: {
+ $arrayElemAt: [
+ {
+ $map: {
+ input: { $first: "$net" },
+ as: "i",
+ in: `$$i.${sourceField}`,
+ },
+ },
+ "$$nIdx",
+ ],
+ },
+ l: {
+ $arrayElemAt: [
+ {
+ $map: {
+ input: { $last: "$net" },
+ as: "i",
+ in: `$$i.${sourceField}`,
+ },
+ },
+ "$$nIdx",
+ ],
+ },
+ },
+ in: {
+ $cond: [{ $gt: ["$$tDiff", 0] }, { $divide: [{ $subtract: ["$$l", "$$f"] }, "$$tDiff"] }, 0],
+ },
+ },
+});
+
+export const getHardwareTotalChecks = async (monitorId: string, dates: DateRange): Promise => {
+ return await CheckModel.countDocuments({
+ "metadata.monitorId": new mongoose.Types.ObjectId(monitorId),
+ "metadata.type": "hardware",
+ createdAt: { $gte: dates.start, $lte: dates.end },
+ });
+};
+
+export const getHardwareUpChecks = async (monitorId: string, dates: DateRange): Promise => {
+ const count = await CheckModel.countDocuments({
+ "metadata.monitorId": new mongoose.Types.ObjectId(monitorId),
+ "metadata.type": "hardware",
+ createdAt: { $gte: dates.start, $lte: dates.end },
+ status: true,
+ });
+ return { totalChecks: count };
+};
+
+export const getHardwareStats = async (monitorId: string, dates: DateRange, dateString: string) => {
+ const netProjection = {
+ name: { $arrayElemAt: ["$sampleDoc.net.name", "$$nIdx"] },
+ ...Object.fromEntries(NET_RATE_FIELDS.map(([outputKey, sourceField]) => [outputKey, netRatePerSecond(sourceField)])),
+ };
+
+ return await CheckModel.aggregate([
+ {
+ $match: {
+ "metadata.monitorId": new mongoose.Types.ObjectId(monitorId),
+ "metadata.type": "hardware",
+ createdAt: { $gte: dates.start, $lte: dates.end },
+ },
+ },
+ { $sort: { createdAt: 1 } },
+ {
+ $group: {
+ _id: { $dateToString: { format: dateString, date: "$createdAt" } },
+ avgCpuUsage: { $avg: "$cpu.usage_percent" },
+ avgMemoryUsage: { $avg: "$memory.usage_percent" },
+ avgTemperatures: { $push: { $ifNull: ["$cpu.temperature", [0]] } },
+ disks: { $push: "$disk" },
+ net: { $push: "$net" },
+ createdAts: { $push: "$createdAt" },
+ sampleDoc: { $first: "$$ROOT" },
+ },
+ },
+ {
+ $project: {
+ _id: 1,
+ avgCpuUsage: 1,
+ avgMemoryUsage: 1,
+ avgTemperature: {
+ $map: {
+ input: { $range: [0, { $size: { $ifNull: [{ $arrayElemAt: ["$avgTemperatures", 0] }, [0]] } }] },
+ as: "idx",
+ in: { $avg: { $map: { input: "$avgTemperatures", as: "t", in: { $arrayElemAt: ["$$t", "$$idx"] } } } },
+ },
+ },
+ disks: {
+ $map: {
+ input: { $range: [0, { $size: { $ifNull: ["$sampleDoc.disk", []] } }] },
+ as: "dIdx",
+ in: {
+ name: { $concat: ["disk", { $toString: "$$dIdx" }] },
+ readSpeed: { $avg: { $map: { input: "$disks", as: "dA", in: { $arrayElemAt: ["$$dA.read_bytes", "$$dIdx"] } } } },
+ writeSpeed: { $avg: { $map: { input: "$disks", as: "dA", in: { $arrayElemAt: ["$$dA.write_bytes", "$$dIdx"] } } } },
+ totalBytes: { $avg: { $map: { input: "$disks", as: "dA", in: { $arrayElemAt: ["$$dA.total_bytes", "$$dIdx"] } } } },
+ freeBytes: { $avg: { $map: { input: "$disks", as: "dA", in: { $arrayElemAt: ["$$dA.free_bytes", "$$dIdx"] } } } },
+ usagePercent: { $avg: { $map: { input: "$disks", as: "dA", in: { $arrayElemAt: ["$$dA.usage_percent", "$$dIdx"] } } } },
+ },
+ },
+ },
+ net: {
+ $map: {
+ input: { $range: [0, { $size: { $ifNull: ["$sampleDoc.net", []] } }] },
+ as: "nIdx",
+ in: netProjection,
+ },
+ },
+ },
+ },
+ { $sort: { _id: 1 } },
+ ]);
+};
diff --git a/server/src/domain/checks/check.repository.mongo.ts b/server/src/domain/checks/check.repository.mongo.ts
index 4f9ad6f4c..bfa8aa467 100644
--- a/server/src/domain/checks/check.repository.mongo.ts
+++ b/server/src/domain/checks/check.repository.mongo.ts
@@ -18,11 +18,11 @@ import mongoose from "mongoose";
import { getDateForRange } from "@/utils/dataUtils.js";
import { ILogger } from "@/utils/logger.js";
+import { getHardwareUpChecks, getHardwareStats, getHardwareTotalChecks } from "@/domain/checks/check.hardware.aggregation.js";
+
const SERVICE_NAME = "StatusService";
export type LatestChecksMap = Record;
-type DateRange = { start: Date; end: Date };
-type HardwareUpChecks = { totalChecks: number };
class MongoChecksRepository implements IChecksRepository {
static SERVICE_NAME = SERVICE_NAME;
@@ -540,9 +540,9 @@ class MongoChecksRepository implements IChecksRepository {
const monitorId = monitorObjectId.toHexString();
const dates = { start: startDate, end: endDate };
const [aggregateDataDoc, upChecksDoc, hardwareMetrics] = await Promise.all([
- this.getHardwareTotalChecks(monitorId, dates),
- this.getHardwareUpChecks(monitorId, dates),
- this.getHardwareStats(monitorId, dates, dateString),
+ getHardwareTotalChecks(monitorId, dates),
+ getHardwareUpChecks(monitorId, dates),
+ getHardwareStats(monitorId, dates, dateString),
]);
const aggregateData = {
@@ -635,167 +635,6 @@ class MongoChecksRepository implements IChecksRepository {
groupedChecks: result?.groupedChecks ?? [],
};
};
-
- private getHardwareTotalChecks = async (monitorId: string, dates: DateRange): Promise => {
- return await CheckModel.countDocuments({
- "metadata.monitorId": new mongoose.Types.ObjectId(monitorId),
- "metadata.type": "hardware",
- createdAt: { $gte: dates.start, $lte: dates.end },
- });
- };
-
- private getHardwareUpChecks = async (monitorId: string, dates: DateRange): Promise => {
- const count = await CheckModel.countDocuments({
- "metadata.monitorId": new mongoose.Types.ObjectId(monitorId),
- "metadata.type": "hardware",
- createdAt: { $gte: dates.start, $lte: dates.end },
- status: true,
- });
- return { totalChecks: count };
- };
-
- private getHardwareStats = async (monitorId: string, dates: DateRange, dateString: string) => {
- return await CheckModel.aggregate([
- {
- $match: {
- "metadata.monitorId": new mongoose.Types.ObjectId(monitorId),
- "metadata.type": "hardware",
- createdAt: { $gte: dates.start, $lte: dates.end },
- },
- },
- { $sort: { createdAt: 1 } },
- {
- $group: {
- _id: { $dateToString: { format: dateString, date: "$createdAt" } },
- avgCpuUsage: { $avg: "$cpu.usage_percent" },
- avgMemoryUsage: { $avg: "$memory.usage_percent" },
- avgTemperatures: { $push: { $ifNull: ["$cpu.temperature", [0]] } },
- disks: { $push: "$disk" },
- net: { $push: "$net" },
- createdAts: { $push: "$createdAt" },
- sampleDoc: { $first: "$$ROOT" },
- },
- },
- {
- $project: {
- _id: 1,
- avgCpuUsage: 1,
- avgMemoryUsage: 1,
- avgTemperature: {
- $map: {
- input: { $range: [0, { $size: { $ifNull: [{ $arrayElemAt: ["$avgTemperatures", 0] }, [0]] } }] },
- as: "idx",
- in: { $avg: { $map: { input: "$avgTemperatures", as: "t", in: { $arrayElemAt: ["$$t", "$$idx"] } } } },
- },
- },
- disks: {
- $map: {
- input: { $range: [0, { $size: { $ifNull: ["$sampleDoc.disk", []] } }] },
- as: "dIdx",
- in: {
- name: { $concat: ["disk", { $toString: "$$dIdx" }] },
- readSpeed: { $avg: { $map: { input: "$disks", as: "dA", in: { $arrayElemAt: ["$$dA.read_bytes", "$$dIdx"] } } } },
- writeSpeed: { $avg: { $map: { input: "$disks", as: "dA", in: { $arrayElemAt: ["$$dA.write_bytes", "$$dIdx"] } } } },
- totalBytes: { $avg: { $map: { input: "$disks", as: "dA", in: { $arrayElemAt: ["$$dA.total_bytes", "$$dIdx"] } } } },
- freeBytes: { $avg: { $map: { input: "$disks", as: "dA", in: { $arrayElemAt: ["$$dA.free_bytes", "$$dIdx"] } } } },
- usagePercent: { $avg: { $map: { input: "$disks", as: "dA", in: { $arrayElemAt: ["$$dA.usage_percent", "$$dIdx"] } } } },
- },
- },
- },
- net: {
- $map: {
- input: { $range: [0, { $size: { $ifNull: ["$sampleDoc.net", []] } }] },
- as: "nIdx",
- in: {
- name: { $arrayElemAt: ["$sampleDoc.net.name", "$$nIdx"] },
- bytesSentPerSecond: {
- $let: {
- vars: {
- tDiff: { $divide: [{ $subtract: [{ $last: "$createdAts" }, { $first: "$createdAts" }] }, 1000] },
- f: { $arrayElemAt: [{ $map: { input: { $first: "$net" }, as: "i", in: "$$i.bytes_sent" } }, "$$nIdx"] },
- l: { $arrayElemAt: [{ $map: { input: { $last: "$net" }, as: "i", in: "$$i.bytes_sent" } }, "$$nIdx"] },
- },
- in: { $cond: [{ $gt: ["$$tDiff", 0] }, { $divide: [{ $subtract: ["$$l", "$$f"] }, "$$tDiff"] }, 0] },
- },
- },
- deltaBytesRecv: {
- $let: {
- vars: {
- tDiff: { $divide: [{ $subtract: [{ $last: "$createdAts" }, { $first: "$createdAts" }] }, 1000] },
- f: { $arrayElemAt: [{ $map: { input: { $first: "$net" }, as: "i", in: "$$i.bytes_recv" } }, "$$nIdx"] },
- l: { $arrayElemAt: [{ $map: { input: { $last: "$net" }, as: "i", in: "$$i.bytes_recv" } }, "$$nIdx"] },
- },
- in: { $cond: [{ $gt: ["$$tDiff", 0] }, { $divide: [{ $subtract: ["$$l", "$$f"] }, "$$tDiff"] }, 0] },
- },
- },
- deltaPacketsSent: {
- $let: {
- vars: {
- tDiff: { $divide: [{ $subtract: [{ $last: "$createdAts" }, { $first: "$createdAts" }] }, 1000] },
- f: { $arrayElemAt: [{ $map: { input: { $first: "$net" }, as: "i", in: "$$i.packets_sent" } }, "$$nIdx"] },
- l: { $arrayElemAt: [{ $map: { input: { $last: "$net" }, as: "i", in: "$$i.packets_sent" } }, "$$nIdx"] },
- },
- in: { $cond: [{ $gt: ["$$tDiff", 0] }, { $divide: [{ $subtract: ["$$l", "$$f"] }, "$$tDiff"] }, 0] },
- },
- },
- deltaPacketsRecv: {
- $let: {
- vars: {
- tDiff: { $divide: [{ $subtract: [{ $last: "$createdAts" }, { $first: "$createdAts" }] }, 1000] },
- f: { $arrayElemAt: [{ $map: { input: { $first: "$net" }, as: "i", in: "$$i.packets_recv" } }, "$$nIdx"] },
- l: { $arrayElemAt: [{ $map: { input: { $last: "$net" }, as: "i", in: "$$i.packets_recv" } }, "$$nIdx"] },
- },
- in: { $cond: [{ $gt: ["$$tDiff", 0] }, { $divide: [{ $subtract: ["$$l", "$$f"] }, "$$tDiff"] }, 0] },
- },
- },
- deltaErrIn: {
- $let: {
- vars: {
- tDiff: { $divide: [{ $subtract: [{ $last: "$createdAts" }, { $first: "$createdAts" }] }, 1000] },
- f: { $arrayElemAt: [{ $map: { input: { $first: "$net" }, as: "i", in: "$$i.err_in" } }, "$$nIdx"] },
- l: { $arrayElemAt: [{ $map: { input: { $last: "$net" }, as: "i", in: "$$i.err_in" } }, "$$nIdx"] },
- },
- in: { $cond: [{ $gt: ["$$tDiff", 0] }, { $divide: [{ $subtract: ["$$l", "$$f"] }, "$$tDiff"] }, 0] },
- },
- },
- deltaErrOut: {
- $let: {
- vars: {
- tDiff: { $divide: [{ $subtract: [{ $last: "$createdAts" }, { $first: "$createdAts" }] }, 1000] },
- f: { $arrayElemAt: [{ $map: { input: { $first: "$net" }, as: "i", in: "$$i.err_out" } }, "$$nIdx"] },
- l: { $arrayElemAt: [{ $map: { input: { $last: "$net" }, as: "i", in: "$$i.err_out" } }, "$$nIdx"] },
- },
- in: { $cond: [{ $gt: ["$$tDiff", 0] }, { $divide: [{ $subtract: ["$$l", "$$f"] }, "$$tDiff"] }, 0] },
- },
- },
- deltaDropIn: {
- $let: {
- vars: {
- tDiff: { $divide: [{ $subtract: [{ $last: "$createdAts" }, { $first: "$createdAts" }] }, 1000] },
- f: { $arrayElemAt: [{ $map: { input: { $first: "$net" }, as: "i", in: "$$i.drop_in" } }, "$$nIdx"] },
- l: { $arrayElemAt: [{ $map: { input: { $last: "$net" }, as: "i", in: "$$i.drop_in" } }, "$$nIdx"] },
- },
- in: { $cond: [{ $gt: ["$$tDiff", 0] }, { $divide: [{ $subtract: ["$$l", "$$f"] }, "$$tDiff"] }, 0] },
- },
- },
- deltaDropOut: {
- $let: {
- vars: {
- tDiff: { $divide: [{ $subtract: [{ $last: "$createdAts" }, { $first: "$createdAts" }] }, 1000] },
- f: { $arrayElemAt: [{ $map: { input: { $first: "$net" }, as: "i", in: "$$i.drop_out" } }, "$$nIdx"] },
- l: { $arrayElemAt: [{ $map: { input: { $last: "$net" }, as: "i", in: "$$i.drop_out" } }, "$$nIdx"] },
- },
- in: { $cond: [{ $gt: ["$$tDiff", 0] }, { $divide: [{ $subtract: ["$$l", "$$f"] }, "$$tDiff"] }, 0] },
- },
- },
- },
- },
- },
- },
- },
- { $sort: { _id: 1 } },
- ]);
- };
}
export default MongoChecksRepository;
From 51203909233c69e78e0a4eed7082d83c1368ce7f Mon Sep 17 00:00:00 2001
From: Alex Holliday
Date: Wed, 15 Jul 2026 09:23:16 -0700
Subject: [PATCH 31/84] aggregation->aggregations
---
...k.hardware.aggregation.ts => check.hardware.aggregations.ts} | 0
server/src/domain/checks/check.repository.mongo.ts | 2 +-
2 files changed, 1 insertion(+), 1 deletion(-)
rename server/src/domain/checks/{check.hardware.aggregation.ts => check.hardware.aggregations.ts} (100%)
diff --git a/server/src/domain/checks/check.hardware.aggregation.ts b/server/src/domain/checks/check.hardware.aggregations.ts
similarity index 100%
rename from server/src/domain/checks/check.hardware.aggregation.ts
rename to server/src/domain/checks/check.hardware.aggregations.ts
diff --git a/server/src/domain/checks/check.repository.mongo.ts b/server/src/domain/checks/check.repository.mongo.ts
index bfa8aa467..3e8d1c52a 100644
--- a/server/src/domain/checks/check.repository.mongo.ts
+++ b/server/src/domain/checks/check.repository.mongo.ts
@@ -18,7 +18,7 @@ import mongoose from "mongoose";
import { getDateForRange } from "@/utils/dataUtils.js";
import { ILogger } from "@/utils/logger.js";
-import { getHardwareUpChecks, getHardwareStats, getHardwareTotalChecks } from "@/domain/checks/check.hardware.aggregation.js";
+import { getHardwareUpChecks, getHardwareStats, getHardwareTotalChecks } from "@/domain/checks/check.hardware.aggregations.js";
const SERVICE_NAME = "StatusService";
From bd13e9d9c801965a7d5c057cb6f34dfc2c117994 Mon Sep 17 00:00:00 2001
From: Alex Holliday
Date: Wed, 15 Jul 2026 10:02:36 -0700
Subject: [PATCH 32/84] add catchAsync to handle errors, remove all try/catch
blocks from contorllers
---
server/src/api/controllers/authController.ts | 415 ++++-----
server/src/api/controllers/checkController.ts | 173 ++--
.../api/controllers/diagnosticController.ts | 25 +-
.../src/api/controllers/geoCheckController.ts | 47 +-
.../src/api/controllers/incidentController.ts | 147 ++--
.../src/api/controllers/inviteController.ts | 101 +--
server/src/api/controllers/logController.ts | 25 +-
.../maintenanceWindowController.ts | 201 ++---
.../src/api/controllers/monitorController.ts | 791 ++++++++----------
.../api/controllers/notificationController.ts | 229 +++--
server/src/api/controllers/queueController.ts | 97 +--
.../src/api/controllers/settingsController.ts | 163 ++--
.../api/controllers/statusPageController.ts | 219 +++--
server/src/api/controllers/tagController.ts | 131 ++-
server/src/utils/catchAsync.ts | 14 +
15 files changed, 1243 insertions(+), 1535 deletions(-)
create mode 100644 server/src/utils/catchAsync.ts
diff --git a/server/src/api/controllers/authController.ts b/server/src/api/controllers/authController.ts
index 45f806a8c..1eb992529 100755
--- a/server/src/api/controllers/authController.ts
+++ b/server/src/api/controllers/authController.ts
@@ -1,4 +1,5 @@
-import { Request, Response, NextFunction } from "express";
+import { Request, Response, RequestHandler } from "express";
+import { catchAsync } from "@/utils/catchAsync.js";
import { AppError } from "@/utils/AppError.js";
import { requireTeamId, requireUserEmail, requireUserId, requireUserRoles } from "@/api/controllers/controllerUtils.js";
@@ -24,20 +25,20 @@ import { IUserService } from "@/domain/users/user.service.js";
const SERVICE_NAME = "authController";
export interface IAuthController {
- registerUser(req: Request, res: Response, next: NextFunction): Promise;
- createUser(req: Request, res: Response, next: NextFunction): Promise;
- loginUser(req: Request, res: Response, next: NextFunction): Promise;
- editUser(req: Request, res: Response, next: NextFunction): Promise;
- checkSuperadminExists(req: Request, res: Response, next: NextFunction): Promise;
- requestRecovery(req: Request, res: Response, next: NextFunction): Promise;
- validateRecovery(req: Request, res: Response, next: NextFunction): Promise;
- resetPassword(req: Request, res: Response, next: NextFunction): Promise;
- deleteUser(req: Request, res: Response, next: NextFunction): Promise;
- deleteUserById(req: Request, res: Response, next: NextFunction): Promise;
- getAllUsers(req: Request, res: Response, next: NextFunction): Promise;
- getUserById(req: Request, res: Response, next: NextFunction): Promise;
- editUserById(req: Request, res: Response, next: NextFunction): Promise;
- editUserPasswordById(req: Request, res: Response, next: NextFunction): Promise;
+ registerUser: RequestHandler;
+ createUser: RequestHandler;
+ loginUser: RequestHandler;
+ editUser: RequestHandler;
+ checkSuperadminExists: RequestHandler;
+ requestRecovery: RequestHandler;
+ validateRecovery: RequestHandler;
+ resetPassword: RequestHandler;
+ deleteUser: RequestHandler;
+ deleteUserById: RequestHandler;
+ getAllUsers: RequestHandler;
+ getUserById: RequestHandler;
+ editUserById: RequestHandler;
+ editUserPasswordById: RequestHandler;
}
class AuthController implements IAuthController {
@@ -53,239 +54,183 @@ class AuthController implements IAuthController {
return AuthController.SERVICE_NAME;
}
- registerUser = async (req: Request, res: Response, next: NextFunction) => {
- try {
- const newUser = req.body.user;
- const newUserToken = req.body.token;
- if (newUser?.email) {
- const newUserEmail = requireUserEmail(newUser.email);
- newUser.email = newUserEmail.toLowerCase();
- }
- const validatedBody = registrationBodyValidation.parse(newUser);
-
- const { user, token } = await this.userService.registerUser(validatedBody, newUserToken, req?.file ?? null);
- res.status(200).json({
- success: true,
- msg: "User registered successfully",
- data: { user, token },
- });
- } catch (error) {
- next(error);
+ registerUser = catchAsync(async (req: Request, res: Response) => {
+ const newUser = req.body.user;
+ const newUserToken = req.body.token;
+ if (newUser?.email) {
+ const newUserEmail = requireUserEmail(newUser.email);
+ newUser.email = newUserEmail.toLowerCase();
}
- };
+ const validatedBody = registrationBodyValidation.parse(newUser);
- createUser = async (req: Request, res: Response, next: NextFunction) => {
- try {
- const userData = req.body;
- if (userData?.email) {
- userData.email = userData.email.toLowerCase();
- }
- const validatedBody = createUserBodyValidation.parse(userData);
+ const { user, token } = await this.userService.registerUser(validatedBody, newUserToken, req?.file ?? null);
+ res.status(200).json({
+ success: true,
+ msg: "User registered successfully",
+ data: { user, token },
+ });
+ });
- const teamId = requireTeamId(req.user?.teamId);
- const actorRoles = requireUserRoles(req.user?.role);
- const newUser = await this.userService.createUser(validatedBody, teamId, actorRoles, req?.file ?? null);
- res.status(201).json({
- success: true,
- msg: "User created successfully",
- data: newUser,
- });
- } catch (error) {
- next(error);
+ createUser = catchAsync(async (req: Request, res: Response) => {
+ const userData = req.body;
+ if (userData?.email) {
+ userData.email = userData.email.toLowerCase();
}
- };
+ const validatedBody = createUserBodyValidation.parse(userData);
- loginUser = async (req: Request, res: Response, next: NextFunction) => {
- try {
- if (req.body?.email) {
- req.body.email = req.body.email?.toLowerCase();
- }
- loginValidation.parse(req.body);
- const { user, token } = await this.userService.loginUser(req.body.email, req.body.password);
+ const teamId = requireTeamId(req.user?.teamId);
+ const actorRoles = requireUserRoles(req.user?.role);
+ const newUser = await this.userService.createUser(validatedBody, teamId, actorRoles, req?.file ?? null);
+ res.status(201).json({
+ success: true,
+ msg: "User created successfully",
+ data: newUser,
+ });
+ });
- return res.status(200).json({
- success: true,
- msg: "User logged in successfully",
- data: {
- user,
- token,
- },
- });
- } catch (error) {
- next(error);
+ loginUser = catchAsync(async (req: Request, res: Response) => {
+ if (req.body?.email) {
+ req.body.email = req.body.email?.toLowerCase();
}
- };
+ loginValidation.parse(req.body);
+ const { user, token } = await this.userService.loginUser(req.body.email, req.body.password);
- editUser = async (req: Request, res: Response, next: NextFunction) => {
- try {
- const validatedBody = editUserBodyValidation.parse(req.body);
- const userId = requireUserId(req.user?.id);
- const userEmail = requireUserEmail(req.user?.email);
- const updatedUser = await this.userService.editUser(validatedBody, req?.file ?? null, userId, userEmail);
+ return res.status(200).json({
+ success: true,
+ msg: "User logged in successfully",
+ data: {
+ user,
+ token,
+ },
+ });
+ });
- res.status(200).json({
- success: true,
- msg: "User updated successfully",
- data: updatedUser,
- });
- } catch (error) {
- next(error);
+ editUser = catchAsync(async (req: Request, res: Response) => {
+ const validatedBody = editUserBodyValidation.parse(req.body);
+ const userId = requireUserId(req.user?.id);
+ const userEmail = requireUserEmail(req.user?.email);
+ const updatedUser = await this.userService.editUser(validatedBody, req?.file ?? null, userId, userEmail);
+
+ res.status(200).json({
+ success: true,
+ msg: "User updated successfully",
+ data: updatedUser,
+ });
+ });
+
+ checkSuperadminExists = catchAsync(async (req: Request, res: Response) => {
+ const superAdminExists = await this.userService.checkSuperadminExists();
+ return res.status(200).json({
+ success: true,
+ msg: "Superadmin existence checked successfully",
+ data: superAdminExists,
+ });
+ });
+
+ requestRecovery = catchAsync(async (req: Request, res: Response) => {
+ recoveryValidation.parse(req.body);
+ const email = req?.body?.email;
+ const msgId = await this.userService.requestRecovery(email);
+ return res.status(200).json({
+ success: true,
+ msg: "Password recovery email sent successfully",
+ data: msgId,
+ });
+ });
+
+ validateRecovery = catchAsync(async (req: Request, res: Response) => {
+ recoveryTokenBodyValidation.parse(req.body);
+ await this.userService.validateRecovery(req.body.recoveryToken);
+ return res.status(200).json({
+ success: true,
+ msg: "Recovery token is valid",
+ });
+ });
+
+ resetPassword = catchAsync(async (req: Request, res: Response) => {
+ newPasswordValidation.parse(req.body);
+ const { user, token } = await this.userService.resetPassword(req.body.password, req.body.recoveryToken);
+ return res.status(200).json({
+ success: true,
+ msg: "Password has been reset successfully",
+ data: { user, token },
+ });
+ });
+
+ deleteUser = catchAsync(async (req: Request, res: Response) => {
+ const userId = requireUserId(req.user?.id);
+ const teamId = requireTeamId(req.user?.teamId);
+ const roles = requireUserRoles(req.user?.role);
+
+ await this.userService.deleteUser({
+ userId,
+ teamId,
+ roles,
+ });
+ return res.status(200).json({
+ success: true,
+ msg: "User deleted successfully",
+ });
+ });
+
+ deleteUserById = catchAsync(async (req: Request, res: Response) => {
+ const validatedParams = getUserByIdParamValidation.parse(req.params);
+ const targetUserId = validatedParams.userId;
+ const actorId = requireUserId(req.user?.id);
+ const actorTeamId = requireTeamId(req.user?.teamId);
+ const actorRoles = requireUserRoles(req.user?.role);
+ await this.userService.deleteUserById({ actorId, actorTeamId, actorRoles, targetUserId });
+ return res.status(200).json({
+ success: true,
+ msg: "User removed successfully",
+ });
+ });
+
+ getAllUsers = catchAsync(async (req: Request, res: Response) => {
+ const allUsers = await this.userService.getAllUsers();
+ return res.status(200).json({
+ success: true,
+ msg: "Users retrieved successfully",
+ data: allUsers,
+ });
+ });
+
+ getUserById = catchAsync(async (req: Request, res: Response) => {
+ const validatedParams = getUserByIdParamValidation.parse(req.params);
+ const actorRoles = requireUserRoles(req.user?.role);
+ const user = await this.userService.getUserById(actorRoles, validatedParams.userId);
+
+ return res.status(200).json({ success: true, msg: "ok", data: user });
+ });
+
+ editUserById = catchAsync(async (req: Request, res: Response) => {
+ const actorRoles = requireUserRoles(req.user?.role);
+ const actorId = requireUserId(req.user?.id);
+
+ if (!actorRoles.includes("superadmin")) {
+ throw new AppError({ message: "Unauthorized", status: 403 });
}
- };
- checkSuperadminExists = async (req: Request, res: Response, next: NextFunction) => {
- try {
- const superAdminExists = await this.userService.checkSuperadminExists();
- return res.status(200).json({
- success: true,
- msg: "Superadmin existence checked successfully",
- data: superAdminExists,
- });
- } catch (error) {
- next(error);
+ const validatedParams = editUserByIdParamValidation.parse(req.params);
+ // If this is superadmin self edit, allow "superadmin" role
+ const validatedBody =
+ validatedParams.userId === actorId ? editSuperadminUserByIdBodyValidation.parse(req.body) : editUserByIdBodyValidation.parse(req.body);
+
+ await this.userService.editUserById(validatedParams.userId, validatedBody);
+ return res.status(200).json({ success: true, msg: "ok" });
+ });
+
+ editUserPasswordById = catchAsync(async (req: Request, res: Response) => {
+ const actorRoles = requireUserRoles(req.user?.role);
+ if (!actorRoles.includes("superadmin")) {
+ throw new AppError({ message: "Unauthorized", status: 403 });
}
- };
- requestRecovery = async (req: Request, res: Response, next: NextFunction) => {
- try {
- recoveryValidation.parse(req.body);
- const email = req?.body?.email;
- const msgId = await this.userService.requestRecovery(email);
- return res.status(200).json({
- success: true,
- msg: "Password recovery email sent successfully",
- data: msgId,
- });
- } catch (error) {
- next(error);
- }
- };
-
- validateRecovery = async (req: Request, res: Response, next: NextFunction) => {
- try {
- recoveryTokenBodyValidation.parse(req.body);
- await this.userService.validateRecovery(req.body.recoveryToken);
- return res.status(200).json({
- success: true,
- msg: "Recovery token is valid",
- });
- } catch (error) {
- next(error);
- }
- };
-
- resetPassword = async (req: Request, res: Response, next: NextFunction) => {
- try {
- newPasswordValidation.parse(req.body);
- const { user, token } = await this.userService.resetPassword(req.body.password, req.body.recoveryToken);
- return res.status(200).json({
- success: true,
- msg: "Password has been reset successfully",
- data: { user, token },
- });
- } catch (error) {
- next(error);
- }
- };
-
- deleteUser = async (req: Request, res: Response, next: NextFunction) => {
- try {
- const userId = requireUserId(req.user?.id);
- const teamId = requireTeamId(req.user?.teamId);
- const roles = requireUserRoles(req.user?.role);
-
- await this.userService.deleteUser({
- userId,
- teamId,
- roles,
- });
- return res.status(200).json({
- success: true,
- msg: "User deleted successfully",
- });
- } catch (error) {
- next(error);
- }
- };
-
- deleteUserById = async (req: Request, res: Response, next: NextFunction) => {
- try {
- const validatedParams = getUserByIdParamValidation.parse(req.params);
- const targetUserId = validatedParams.userId;
- const actorId = requireUserId(req.user?.id);
- const actorTeamId = requireTeamId(req.user?.teamId);
- const actorRoles = requireUserRoles(req.user?.role);
- await this.userService.deleteUserById({ actorId, actorTeamId, actorRoles, targetUserId });
- return res.status(200).json({
- success: true,
- msg: "User removed successfully",
- });
- } catch (error) {
- next(error);
- }
- };
-
- getAllUsers = async (req: Request, res: Response, next: NextFunction) => {
- try {
- const allUsers = await this.userService.getAllUsers();
- return res.status(200).json({
- success: true,
- msg: "Users retrieved successfully",
- data: allUsers,
- });
- } catch (error) {
- next(error);
- }
- };
-
- getUserById = async (req: Request, res: Response, next: NextFunction) => {
- try {
- const validatedParams = getUserByIdParamValidation.parse(req.params);
- const actorRoles = requireUserRoles(req.user?.role);
- const user = await this.userService.getUserById(actorRoles, validatedParams.userId);
-
- return res.status(200).json({ success: true, msg: "ok", data: user });
- } catch (error) {
- next(error);
- }
- };
-
- editUserById = async (req: Request, res: Response, next: NextFunction) => {
- try {
- const actorRoles = requireUserRoles(req.user?.role);
- const actorId = requireUserId(req.user?.id);
-
- if (!actorRoles.includes("superadmin")) {
- throw new AppError({ message: "Unauthorized", status: 403 });
- }
-
- const validatedParams = editUserByIdParamValidation.parse(req.params);
- // If this is superadmin self edit, allow "superadmin" role
- const validatedBody =
- validatedParams.userId === actorId ? editSuperadminUserByIdBodyValidation.parse(req.body) : editUserByIdBodyValidation.parse(req.body);
-
- await this.userService.editUserById(validatedParams.userId, validatedBody);
- return res.status(200).json({ success: true, msg: "ok" });
- } catch (error) {
- next(error);
- }
- };
-
- editUserPasswordById = async (req: Request, res: Response, next: NextFunction) => {
- try {
- const actorRoles = requireUserRoles(req.user?.role);
- if (!actorRoles.includes("superadmin")) {
- throw new AppError({ message: "Unauthorized", status: 403 });
- }
-
- const validatedParams = editUserByIdParamValidation.parse(req.params);
- const validatedBody = editUserPasswordByIdBodyValidation.parse(req.body);
- await this.userService.setPasswordByUserId(validatedParams.userId, validatedBody.password);
- return res.status(200).json({ success: true, msg: "Password reset successfully" });
- } catch (error) {
- next(error);
- }
- };
+ const validatedParams = editUserByIdParamValidation.parse(req.params);
+ const validatedBody = editUserPasswordByIdBodyValidation.parse(req.body);
+ await this.userService.setPasswordByUserId(validatedParams.userId, validatedBody.password);
+ return res.status(200).json({ success: true, msg: "Password reset successfully" });
+ });
}
export default AuthController;
diff --git a/server/src/api/controllers/checkController.ts b/server/src/api/controllers/checkController.ts
index 23fbe2227..ccc91921d 100755
--- a/server/src/api/controllers/checkController.ts
+++ b/server/src/api/controllers/checkController.ts
@@ -1,4 +1,5 @@
-import { Request, Response, NextFunction } from "express";
+import { Request, Response, RequestHandler } from "express";
+import { catchAsync } from "@/utils/catchAsync.js";
import {
getChecksParamValidation,
@@ -14,11 +15,11 @@ const SERVICE_NAME = "checkController";
export interface ICheckController {
serviceName: string;
- getChecksByMonitor: (req: Request, res: Response, next: NextFunction) => Promise;
- getChecksByTeam: (req: Request, res: Response, next: NextFunction) => Promise;
- getChecksSummaryByTeamId: (req: Request, res: Response, next: NextFunction) => Promise;
- deleteChecks: (req: Request, res: Response, next: NextFunction) => Promise;
- deleteChecksByTeamId: (req: Request, res: Response, next: NextFunction) => Promise;
+ getChecksByMonitor: RequestHandler;
+ getChecksByTeam: RequestHandler;
+ getChecksSummaryByTeamId: RequestHandler;
+ deleteChecks: RequestHandler;
+ deleteChecksByTeamId: RequestHandler;
}
class CheckController implements ICheckController {
@@ -33,109 +34,89 @@ class CheckController implements ICheckController {
return CheckController.SERVICE_NAME;
}
- getChecksByMonitor = async (req: Request, res: Response, next: NextFunction) => {
- try {
- const validatedParams = getChecksParamValidation.parse(req.params);
- const validatedQuery = getChecksQueryValidation.parse(req.query);
+ getChecksByMonitor = catchAsync(async (req: Request, res: Response) => {
+ const validatedParams = getChecksParamValidation.parse(req.params);
+ const validatedQuery = getChecksQueryValidation.parse(req.query);
- const teamId = requireTeamId(req.user?.teamId);
+ const teamId = requireTeamId(req.user?.teamId);
- const result = await this.checkService.getChecksByMonitor({
- monitorId: validatedParams.monitorId,
- teamId,
- sortOrder: validatedQuery.sortOrder,
- dateRange: validatedQuery.dateRange,
- filter: validatedQuery.filter,
- page: validatedQuery.page,
- rowsPerPage: validatedQuery.rowsPerPage,
- status: validatedQuery.status,
- });
+ const result = await this.checkService.getChecksByMonitor({
+ monitorId: validatedParams.monitorId,
+ teamId,
+ sortOrder: validatedQuery.sortOrder,
+ dateRange: validatedQuery.dateRange,
+ filter: validatedQuery.filter,
+ page: validatedQuery.page,
+ rowsPerPage: validatedQuery.rowsPerPage,
+ status: validatedQuery.status,
+ });
- return res.status(200).json({
- success: true,
- msg: "Checks retrieved successfully",
- data: result,
- });
- } catch (error) {
- next(error);
- }
- };
+ return res.status(200).json({
+ success: true,
+ msg: "Checks retrieved successfully",
+ data: result,
+ });
+ });
- getChecksByTeam = async (req: Request, res: Response, next: NextFunction) => {
- try {
- const validatedQuery = getTeamChecksQueryValidation.parse(req.query);
- const teamId = requireTeamId(req.user?.teamId);
+ getChecksByTeam = catchAsync(async (req: Request, res: Response) => {
+ const validatedQuery = getTeamChecksQueryValidation.parse(req.query);
+ const teamId = requireTeamId(req.user?.teamId);
- const checkData = await this.checkService.getChecksByTeam({
- teamId,
- sortOrder: validatedQuery.sortOrder,
- dateRange: validatedQuery.dateRange,
- page: validatedQuery.page,
- rowsPerPage: validatedQuery.rowsPerPage,
- filter: validatedQuery.filter,
- });
- return res.status(200).json({
- success: true,
- msg: "Team checks retrieved successfully",
- data: checkData,
- });
- } catch (error) {
- next(error);
- }
- };
+ const checkData = await this.checkService.getChecksByTeam({
+ teamId,
+ sortOrder: validatedQuery.sortOrder,
+ dateRange: validatedQuery.dateRange,
+ page: validatedQuery.page,
+ rowsPerPage: validatedQuery.rowsPerPage,
+ filter: validatedQuery.filter,
+ });
+ return res.status(200).json({
+ success: true,
+ msg: "Team checks retrieved successfully",
+ data: checkData,
+ });
+ });
- getChecksSummaryByTeamId = async (req: Request, res: Response, next: NextFunction) => {
- try {
- const validatedQuery = getChecksSummaryByTeamIdQueryValidation.parse(req.query);
- const teamId = requireTeamId(req.user?.teamId);
- const dateRange = validatedQuery.dateRange ?? "hour";
+ getChecksSummaryByTeamId = catchAsync(async (req: Request, res: Response) => {
+ const validatedQuery = getChecksSummaryByTeamIdQueryValidation.parse(req.query);
+ const teamId = requireTeamId(req.user?.teamId);
+ const dateRange = validatedQuery.dateRange ?? "hour";
- const summary = await this.checkService.getChecksSummaryByTeamId({ teamId, dateRange });
- return res.status(200).json({
- success: true,
- msg: "Checks summary retrieved successfully",
- data: summary,
- });
- } catch (error) {
- next(error);
- }
- };
+ const summary = await this.checkService.getChecksSummaryByTeamId({ teamId, dateRange });
+ return res.status(200).json({
+ success: true,
+ msg: "Checks summary retrieved successfully",
+ data: summary,
+ });
+ });
- deleteChecks = async (req: Request, res: Response, next: NextFunction) => {
- try {
- const validatedParams = deleteChecksParamValidation.parse(req.params);
- const teamId = requireTeamId(req.user?.teamId);
+ deleteChecks = catchAsync(async (req: Request, res: Response) => {
+ const validatedParams = deleteChecksParamValidation.parse(req.params);
+ const teamId = requireTeamId(req.user?.teamId);
- const deletedCount = await this.checkService.deleteChecks({
- monitorId: validatedParams.monitorId,
- teamId,
- });
+ const deletedCount = await this.checkService.deleteChecks({
+ monitorId: validatedParams.monitorId,
+ teamId,
+ });
- return res.status(200).json({
- success: true,
- msg: "Checks deleted successfully",
- data: { deletedCount },
- });
- } catch (error) {
- next(error);
- }
- };
+ return res.status(200).json({
+ success: true,
+ msg: "Checks deleted successfully",
+ data: { deletedCount },
+ });
+ });
- deleteChecksByTeamId = async (req: Request, res: Response, next: NextFunction) => {
- try {
- const teamId = requireTeamId(req.user?.teamId);
+ deleteChecksByTeamId = catchAsync(async (req: Request, res: Response) => {
+ const teamId = requireTeamId(req.user?.teamId);
- const deletedCount = await this.checkService.deleteChecksByTeamId({ teamId });
+ const deletedCount = await this.checkService.deleteChecksByTeamId({ teamId });
- return res.status(200).json({
- success: true,
- msg: "Checks deleted successfully",
- data: { deletedCount },
- });
- } catch (error) {
- next(error);
- }
- };
+ return res.status(200).json({
+ success: true,
+ msg: "Checks deleted successfully",
+ data: { deletedCount },
+ });
+ });
}
export default CheckController;
diff --git a/server/src/api/controllers/diagnosticController.ts b/server/src/api/controllers/diagnosticController.ts
index efc383e1a..0864f7237 100755
--- a/server/src/api/controllers/diagnosticController.ts
+++ b/server/src/api/controllers/diagnosticController.ts
@@ -1,10 +1,11 @@
import { IDiagnosticService } from "@/domain/diagnostics/diagnostic.service.js";
-import { Request, Response, NextFunction } from "express";
+import { Request, Response, RequestHandler } from "express";
+import { catchAsync } from "@/utils/catchAsync.js";
const SERVICE_NAME = "diagnosticController";
export interface IDiagnosticController {
- getSystemStats: (req: Request, res: Response, next: NextFunction) => Promise;
+ getSystemStats: RequestHandler;
}
class DiagnosticController implements IDiagnosticController {
@@ -20,18 +21,14 @@ class DiagnosticController implements IDiagnosticController {
return DiagnosticController.SERVICE_NAME;
}
- getSystemStats = async (req: Request, res: Response, next: NextFunction) => {
- try {
- const diagnostics = await this.diagnosticService.getSystemStats();
- return res.status(200).json({
- success: true,
- msg: "OK",
- data: diagnostics,
- });
- } catch (error) {
- next(error);
- }
- };
+ getSystemStats = catchAsync(async (req: Request, res: Response) => {
+ const diagnostics = await this.diagnosticService.getSystemStats();
+ return res.status(200).json({
+ success: true,
+ msg: "OK",
+ data: diagnostics,
+ });
+ });
}
export default DiagnosticController;
diff --git a/server/src/api/controllers/geoCheckController.ts b/server/src/api/controllers/geoCheckController.ts
index a50176188..81082b6ed 100644
--- a/server/src/api/controllers/geoCheckController.ts
+++ b/server/src/api/controllers/geoCheckController.ts
@@ -1,4 +1,5 @@
-import { Request, Response, NextFunction } from "express";
+import { Request, Response, RequestHandler } from "express";
+import { catchAsync } from "@/utils/catchAsync.js";
import { getChecksParamValidation, getChecksQueryValidation } from "@/api/validation/checkValidation.js";
import type { IGeoChecksService } from "@/domain/geo-checks/geo-check.service.js";
import { requireTeamId } from "./controllerUtils.js";
@@ -6,7 +7,7 @@ import { requireTeamId } from "./controllerUtils.js";
const SERVICE_NAME = "geoCheckController";
export interface IGeoCheckController {
- getGeoChecksByMonitor: (req: Request, res: Response, next: NextFunction) => Promise;
+ getGeoChecksByMonitor: RequestHandler;
}
class GeoCheckController implements IGeoCheckController {
static SERVICE_NAME = SERVICE_NAME;
@@ -20,32 +21,28 @@ class GeoCheckController implements IGeoCheckController {
return GeoCheckController.SERVICE_NAME;
}
- getGeoChecksByMonitor = async (req: Request, res: Response, next: NextFunction) => {
- try {
- const validatedParams = getChecksParamValidation.parse(req.params);
- const validatedQuery = getChecksQueryValidation.parse(req.query);
+ getGeoChecksByMonitor = catchAsync(async (req: Request, res: Response) => {
+ const validatedParams = getChecksParamValidation.parse(req.params);
+ const validatedQuery = getChecksQueryValidation.parse(req.query);
- const teamId = requireTeamId(req.user?.teamId);
+ const teamId = requireTeamId(req.user?.teamId);
- const result = await this.geoChecksService.getGeoChecksByMonitor({
- monitorId: validatedParams.monitorId,
- teamId: teamId,
- sortOrder: validatedQuery.sortOrder,
- dateRange: validatedQuery.dateRange,
- page: validatedQuery.page,
- rowsPerPage: validatedQuery.rowsPerPage,
- continent: validatedQuery.continent,
- });
+ const result = await this.geoChecksService.getGeoChecksByMonitor({
+ monitorId: validatedParams.monitorId,
+ teamId: teamId,
+ sortOrder: validatedQuery.sortOrder,
+ dateRange: validatedQuery.dateRange,
+ page: validatedQuery.page,
+ rowsPerPage: validatedQuery.rowsPerPage,
+ continent: validatedQuery.continent,
+ });
- return res.status(200).json({
- success: true,
- msg: "Geo checks retrieved successfully",
- data: result,
- });
- } catch (error) {
- next(error);
- }
- };
+ return res.status(200).json({
+ success: true,
+ msg: "Geo checks retrieved successfully",
+ data: result,
+ });
+ });
}
export default GeoCheckController;
diff --git a/server/src/api/controllers/incidentController.ts b/server/src/api/controllers/incidentController.ts
index 6358d2a89..f1998f16f 100644
--- a/server/src/api/controllers/incidentController.ts
+++ b/server/src/api/controllers/incidentController.ts
@@ -1,5 +1,6 @@
import { AppError } from "@/utils/AppError.js";
-import { Request, Response, NextFunction } from "express";
+import { Request, Response, RequestHandler } from "express";
+import { catchAsync } from "@/utils/catchAsync.js";
import { requireTeamId, requireUserId, requireUserEmail, extractString } from "./controllerUtils.js";
import { IIncidentService } from "@/domain/incidents/incident.service.js";
import { getIncidentsByTeamQueryValidation, getIncidentSummaryQueryValidation } from "@/api/validation/incidentValidation.js";
@@ -7,10 +8,10 @@ import { getIncidentsByTeamQueryValidation, getIncidentSummaryQueryValidation }
const SERVICE_NAME = "IncidentController";
export interface IIncidentController {
- getIncidentsByTeam: (req: Request, res: Response, next: NextFunction) => Promise;
- getIncidentSummary: (req: Request, res: Response, next: NextFunction) => Promise;
- getIncidentById: (req: Request, res: Response, next: NextFunction) => Promise;
- resolveIncidentManually: (req: Request, res: Response, next: NextFunction) => Promise;
+ getIncidentsByTeam: RequestHandler;
+ getIncidentSummary: RequestHandler;
+ getIncidentById: RequestHandler;
+ resolveIncidentManually: RequestHandler;
}
class IncidentController implements IIncidentController {
private incidentService: IIncidentService;
@@ -18,90 +19,74 @@ class IncidentController implements IIncidentController {
this.incidentService = incidentService;
}
- getIncidentsByTeam = async (req: Request, res: Response, next: NextFunction) => {
- try {
- const validatedQuery = getIncidentsByTeamQueryValidation.parse(req.query);
+ getIncidentsByTeam = catchAsync(async (req: Request, res: Response) => {
+ const validatedQuery = getIncidentsByTeamQueryValidation.parse(req.query);
- const teamId = requireTeamId(req.user?.teamId);
- const result = await this.incidentService.getIncidentsByTeam(
- teamId,
- validatedQuery.sortOrder,
- validatedQuery.dateRange,
- validatedQuery.page,
- validatedQuery.rowsPerPage,
- validatedQuery.status,
- validatedQuery.monitorId,
- validatedQuery.resolutionType
- );
+ const teamId = requireTeamId(req.user?.teamId);
+ const result = await this.incidentService.getIncidentsByTeam(
+ teamId,
+ validatedQuery.sortOrder,
+ validatedQuery.dateRange,
+ validatedQuery.page,
+ validatedQuery.rowsPerPage,
+ validatedQuery.status,
+ validatedQuery.monitorId,
+ validatedQuery.resolutionType
+ );
- return res.status(200).json({
- success: true,
- msg: "Incidents retrieved successfully",
- data: result,
- });
- } catch (error) {
- next(error);
+ return res.status(200).json({
+ success: true,
+ msg: "Incidents retrieved successfully",
+ data: result,
+ });
+ });
+
+ getIncidentSummary = catchAsync(async (req: Request, res: Response) => {
+ const teamId = requireTeamId(req.user?.teamId);
+ const validatedQuery = getIncidentSummaryQueryValidation.parse(req.query);
+
+ const summary = await this.incidentService.getIncidentSummary(teamId, validatedQuery.limit);
+ return res.status(200).json({
+ success: true,
+ msg: "Incident summary retrieved successfully",
+ data: summary,
+ });
+ });
+
+ getIncidentById = catchAsync(async (req: Request, res: Response) => {
+ const teamId = requireTeamId(req.user?.teamId);
+ const incidentId = req.params.incidentId as string;
+ if (!incidentId) {
+ throw new AppError({ message: "Incident ID is required", service: SERVICE_NAME, status: 400 });
}
- };
- getIncidentSummary = async (req: Request, res: Response, next: NextFunction) => {
- try {
- const teamId = requireTeamId(req.user?.teamId);
- const validatedQuery = getIncidentSummaryQueryValidation.parse(req.query);
+ const incident = await this.incidentService.getIncidentById(incidentId, teamId);
- const summary = await this.incidentService.getIncidentSummary(teamId, validatedQuery.limit);
- return res.status(200).json({
- success: true,
- msg: "Incident summary retrieved successfully",
- data: summary,
- });
- } catch (error) {
- next(error);
+ return res.status(200).json({
+ success: true,
+ msg: "Incident retrieved successfully",
+ data: incident,
+ });
+ });
+
+ resolveIncidentManually = catchAsync(async (req: Request, res: Response) => {
+ const teamId = requireTeamId(req.user?.teamId);
+ const userId = requireUserId(req.user?.id);
+ const userEmail = requireUserEmail(req.user?.email);
+ const incidentId = extractString(req.params?.incidentId);
+ if (!incidentId) {
+ throw new AppError({ message: "Incident ID is required", service: SERVICE_NAME, status: 400 });
}
- };
- getIncidentById = async (req: Request, res: Response, next: NextFunction) => {
- try {
- const teamId = requireTeamId(req.user?.teamId);
- const incidentId = req.params.incidentId as string;
- if (!incidentId) {
- throw new AppError({ message: "Incident ID is required", service: SERVICE_NAME, status: 400 });
- }
+ const comment = extractString(req.body?.comment);
+ const resolvedIncident = await this.incidentService.resolveIncident(incidentId, userId, teamId, comment, userEmail);
- const incident = await this.incidentService.getIncidentById(incidentId, teamId);
-
- return res.status(200).json({
- success: true,
- msg: "Incident retrieved successfully",
- data: incident,
- });
- } catch (error) {
- next(error);
- }
- };
-
- resolveIncidentManually = async (req: Request, res: Response, next: NextFunction) => {
- try {
- const teamId = requireTeamId(req.user?.teamId);
- const userId = requireUserId(req.user?.id);
- const userEmail = requireUserEmail(req.user?.email);
- const incidentId = extractString(req.params?.incidentId);
- if (!incidentId) {
- throw new AppError({ message: "Incident ID is required", service: SERVICE_NAME, status: 400 });
- }
-
- const comment = extractString(req.body?.comment);
- const resolvedIncident = await this.incidentService.resolveIncident(incidentId, userId, teamId, comment, userEmail);
-
- return res.status(200).json({
- success: true,
- msg: "Incident resolved successfully",
- data: resolvedIncident,
- });
- } catch (error) {
- next(error);
- }
- };
+ return res.status(200).json({
+ success: true,
+ msg: "Incident resolved successfully",
+ data: resolvedIncident,
+ });
+ });
}
export default IncidentController;
diff --git a/server/src/api/controllers/inviteController.ts b/server/src/api/controllers/inviteController.ts
index c77385f7b..0efc1e0a7 100755
--- a/server/src/api/controllers/inviteController.ts
+++ b/server/src/api/controllers/inviteController.ts
@@ -1,13 +1,14 @@
-import { Request, Response, NextFunction } from "express";
+import { Request, Response, RequestHandler } from "express";
+import { catchAsync } from "@/utils/catchAsync.js";
import { inviteBodyValidation, inviteVerificationBodyValidation } from "@/api/validation/authValidation.js";
import { requireFirstName, requireTeamId, requireUserRoles } from "@/api/controllers/controllerUtils.js";
import { IInviteService } from "@/domain/invites/invite.service.js";
const SERVICE_NAME = "inviteController";
export interface IInviteController {
- getInviteToken: (req: Request, res: Response, next: NextFunction) => Promise;
- sendInviteEmail: (req: Request, res: Response, next: NextFunction) => Promise;
- verifyInviteToken: (req: Request, res: Response, next: NextFunction) => Promise;
+ getInviteToken: RequestHandler;
+ sendInviteEmail: RequestHandler;
+ verifyInviteToken: RequestHandler;
}
class InviteController implements IInviteController {
@@ -21,62 +22,50 @@ class InviteController implements IInviteController {
return InviteController.SERVICE_NAME;
}
- getInviteToken = async (req: Request, res: Response, next: NextFunction) => {
- try {
- const teamId = requireTeamId(req.user?.teamId);
- const userRoles = requireUserRoles(req.user?.role);
- const invite = req.body;
- invite.teamId = teamId;
- inviteBodyValidation.parse(invite);
- const inviteToken = await this.inviteService.getInviteToken({ invite, teamId, userRoles });
- return res.status(200).json({
- success: true,
- msg: "Invite token generated successfully",
- data: inviteToken,
- });
- } catch (error) {
- next(error);
- }
- };
+ getInviteToken = catchAsync(async (req: Request, res: Response) => {
+ const teamId = requireTeamId(req.user?.teamId);
+ const userRoles = requireUserRoles(req.user?.role);
+ const invite = req.body;
+ invite.teamId = teamId;
+ inviteBodyValidation.parse(invite);
+ const inviteToken = await this.inviteService.getInviteToken({ invite, teamId, userRoles });
+ return res.status(200).json({
+ success: true,
+ msg: "Invite token generated successfully",
+ data: inviteToken,
+ });
+ });
- sendInviteEmail = async (req: Request, res: Response, next: NextFunction) => {
- try {
- const teamId = requireTeamId(req.user?.teamId);
- const userRoles = requireUserRoles(req.user?.role);
- const firstName = requireFirstName(req.user?.firstName);
+ sendInviteEmail = catchAsync(async (req: Request, res: Response) => {
+ const teamId = requireTeamId(req.user?.teamId);
+ const userRoles = requireUserRoles(req.user?.role);
+ const firstName = requireFirstName(req.user?.firstName);
- const inviteRequest = req.body;
- inviteRequest.teamId = teamId;
- inviteBodyValidation.parse(inviteRequest);
+ const inviteRequest = req.body;
+ inviteRequest.teamId = teamId;
+ inviteBodyValidation.parse(inviteRequest);
- const inviteToken = await this.inviteService.sendInviteEmail({
- invite: inviteRequest,
- firstName,
- userRoles,
- });
- return res.status(200).json({
- success: true,
- msg: "Invite issued successfully",
- data: inviteToken,
- });
- } catch (error) {
- next(error);
- }
- };
+ const inviteToken = await this.inviteService.sendInviteEmail({
+ invite: inviteRequest,
+ firstName,
+ userRoles,
+ });
+ return res.status(200).json({
+ success: true,
+ msg: "Invite issued successfully",
+ data: inviteToken,
+ });
+ });
- verifyInviteToken = async (req: Request, res: Response, next: NextFunction) => {
- try {
- inviteVerificationBodyValidation.parse(req.body);
- const invite = await this.inviteService.verifyInviteToken({ inviteToken: req?.body?.token });
- return res.status(200).json({
- success: true,
- msg: "Invite verified successfully",
- data: invite,
- });
- } catch (error) {
- next(error);
- }
- };
+ verifyInviteToken = catchAsync(async (req: Request, res: Response) => {
+ inviteVerificationBodyValidation.parse(req.body);
+ const invite = await this.inviteService.verifyInviteToken({ inviteToken: req?.body?.token });
+ return res.status(200).json({
+ success: true,
+ msg: "Invite verified successfully",
+ data: invite,
+ });
+ });
}
export default InviteController;
diff --git a/server/src/api/controllers/logController.ts b/server/src/api/controllers/logController.ts
index 7d221c6c5..f0449bbf2 100644
--- a/server/src/api/controllers/logController.ts
+++ b/server/src/api/controllers/logController.ts
@@ -1,10 +1,11 @@
import { ILogger } from "@/utils/logger.js";
-import { Request, Response, NextFunction } from "express";
+import { Request, Response, RequestHandler } from "express";
+import { catchAsync } from "@/utils/catchAsync.js";
const SERVICE_NAME = "LogController";
export interface ILogController {
- getLogs: (req: Request, res: Response, next: NextFunction) => Promise;
+ getLogs: RequestHandler;
}
class LogController {
@@ -17,17 +18,13 @@ class LogController {
return LogController.SERVICE_NAME;
}
- getLogs = async (req: Request, res: Response, next: NextFunction) => {
- try {
- const logs = this.logger.getLogs();
- res.status(200).json({
- success: true,
- msg: "Logs fetched successfully",
- data: logs,
- });
- } catch (error) {
- next(error);
- }
- };
+ getLogs = catchAsync(async (req: Request, res: Response) => {
+ const logs = this.logger.getLogs();
+ res.status(200).json({
+ success: true,
+ msg: "Logs fetched successfully",
+ data: logs,
+ });
+ });
}
export default LogController;
diff --git a/server/src/api/controllers/maintenanceWindowController.ts b/server/src/api/controllers/maintenanceWindowController.ts
index ddbf4deca..49fa605ec 100644
--- a/server/src/api/controllers/maintenanceWindowController.ts
+++ b/server/src/api/controllers/maintenanceWindowController.ts
@@ -1,4 +1,5 @@
-import { Request, Response, NextFunction } from "express";
+import { Request, Response, RequestHandler } from "express";
+import { catchAsync } from "@/utils/catchAsync.js";
import {
createMaintenanceWindowBodyValidation,
editMaintenanceWindowByIdParamValidation,
@@ -12,12 +13,12 @@ import { requireTeamId } from "@/api/controllers/controllerUtils.js";
import { IMaintenanceWindowService } from "@/domain/maintenance-windows/maintenance-window.service.js";
export interface IMaintenanceWindowController {
- createMaintenanceWindows: (req: Request, res: Response, next: NextFunction) => Promise;
- getMaintenanceWindowById: (req: Request, res: Response, next: NextFunction) => Promise;
- getMaintenanceWindowsByTeamId: (req: Request, res: Response, next: NextFunction) => Promise;
- getMaintenanceWindowsByMonitorId: (req: Request, res: Response, next: NextFunction) => Promise;
- deleteMaintenanceWindow: (req: Request, res: Response, next: NextFunction) => Promise;
- editMaintenanceWindow: (req: Request, res: Response, next: NextFunction) => Promise;
+ createMaintenanceWindows: RequestHandler;
+ getMaintenanceWindowById: RequestHandler;
+ getMaintenanceWindowsByTeamId: RequestHandler;
+ getMaintenanceWindowsByMonitorId: RequestHandler;
+ deleteMaintenanceWindow: RequestHandler;
+ editMaintenanceWindow: RequestHandler;
}
class MaintenanceWindowController implements IMaintenanceWindowController {
private maintenanceWindowService: IMaintenanceWindowService;
@@ -25,131 +26,107 @@ class MaintenanceWindowController implements IMaintenanceWindowController {
this.maintenanceWindowService = maintenanceWindowService;
}
- createMaintenanceWindows = async (req: Request, res: Response, next: NextFunction) => {
- try {
- const validatedBody = createMaintenanceWindowBodyValidation.parse(req.body);
- const teamId = requireTeamId(req?.user?.teamId);
+ createMaintenanceWindows = catchAsync(async (req: Request, res: Response) => {
+ const validatedBody = createMaintenanceWindowBodyValidation.parse(req.body);
+ const teamId = requireTeamId(req?.user?.teamId);
- const monitorIDs = validatedBody.monitors;
- const name = validatedBody.name;
- const active = validatedBody.active ?? true;
- const duration = validatedBody.duration;
- const durationUnit = validatedBody.durationUnit;
- const repeat = validatedBody.repeat;
- const start = validatedBody.start;
- const end = validatedBody.end;
+ const monitorIDs = validatedBody.monitors;
+ const name = validatedBody.name;
+ const active = validatedBody.active ?? true;
+ const duration = validatedBody.duration;
+ const durationUnit = validatedBody.durationUnit;
+ const repeat = validatedBody.repeat;
+ const start = validatedBody.start;
+ const end = validatedBody.end;
- await this.maintenanceWindowService.createMaintenanceWindow({ teamId, monitorIDs, name, active, duration, durationUnit, repeat, start, end });
+ await this.maintenanceWindowService.createMaintenanceWindow({ teamId, monitorIDs, name, active, duration, durationUnit, repeat, start, end });
- return res.status(200).json({
- success: true,
- msg: "Maintenance window created successfully",
- });
- } catch (error) {
- next(error);
- }
- };
- getMaintenanceWindowById = async (req: Request, res: Response, next: NextFunction) => {
- try {
- const validatedParams = getMaintenanceWindowByIdParamValidation.parse(req.params);
+ return res.status(200).json({
+ success: true,
+ msg: "Maintenance window created successfully",
+ });
+ });
+ getMaintenanceWindowById = catchAsync(async (req: Request, res: Response) => {
+ const validatedParams = getMaintenanceWindowByIdParamValidation.parse(req.params);
- const teamId = requireTeamId(req.user?.teamId);
+ const teamId = requireTeamId(req.user?.teamId);
- const maintenanceWindow = await this.maintenanceWindowService.getMaintenanceWindowById({ id: validatedParams.id, teamId });
+ const maintenanceWindow = await this.maintenanceWindowService.getMaintenanceWindowById({ id: validatedParams.id, teamId });
- return res.status(200).json({
- success: true,
- msg: "Maintenance window fetched successfully",
- data: maintenanceWindow,
- });
- } catch (error) {
- next(error);
- }
- };
+ return res.status(200).json({
+ success: true,
+ msg: "Maintenance window fetched successfully",
+ data: maintenanceWindow,
+ });
+ });
- getMaintenanceWindowsByTeamId = async (req: Request, res: Response, next: NextFunction) => {
- try {
- const validatedQuery = getMaintenanceWindowsByTeamIdQueryValidation.parse(req.query);
+ getMaintenanceWindowsByTeamId = catchAsync(async (req: Request, res: Response) => {
+ const validatedQuery = getMaintenanceWindowsByTeamIdQueryValidation.parse(req.query);
- const teamId = requireTeamId(req?.user?.teamId);
+ const teamId = requireTeamId(req?.user?.teamId);
- const maintenanceWindows = await this.maintenanceWindowService.getMaintenanceWindowsByTeamId({
- teamId,
- active: validatedQuery.active,
- page: validatedQuery.page,
- rowsPerPage: validatedQuery.rowsPerPage,
- field: validatedQuery.field,
- order: validatedQuery.order,
- });
+ const maintenanceWindows = await this.maintenanceWindowService.getMaintenanceWindowsByTeamId({
+ teamId,
+ active: validatedQuery.active,
+ page: validatedQuery.page,
+ rowsPerPage: validatedQuery.rowsPerPage,
+ field: validatedQuery.field,
+ order: validatedQuery.order,
+ });
- return res.status(200).json({
- success: true,
- msg: "Maintenance windows fetched successfully",
- data: maintenanceWindows,
- });
- } catch (error) {
- next(error);
- }
- };
+ return res.status(200).json({
+ success: true,
+ msg: "Maintenance windows fetched successfully",
+ data: maintenanceWindows,
+ });
+ });
- getMaintenanceWindowsByMonitorId = async (req: Request, res: Response, next: NextFunction) => {
- try {
- const validatedParams = getMaintenanceWindowsByMonitorIdParamValidation.parse(req.params);
+ getMaintenanceWindowsByMonitorId = catchAsync(async (req: Request, res: Response) => {
+ const validatedParams = getMaintenanceWindowsByMonitorIdParamValidation.parse(req.params);
- const teamId = requireTeamId(req?.user?.teamId);
+ const teamId = requireTeamId(req?.user?.teamId);
- const maintenanceWindows = await this.maintenanceWindowService.getMaintenanceWindowsByMonitorId({
- monitorId: validatedParams.monitorId,
- teamId,
- });
+ const maintenanceWindows = await this.maintenanceWindowService.getMaintenanceWindowsByMonitorId({
+ monitorId: validatedParams.monitorId,
+ teamId,
+ });
- return res.status(200).json({
- success: true,
- msg: "Maintenance windows fetched successfully",
- data: maintenanceWindows,
- });
- } catch (error) {
- next(error);
- }
- };
- deleteMaintenanceWindow = async (req: Request, res: Response, next: NextFunction) => {
- try {
- const validatedParams = deleteMaintenanceWindowByIdParamValidation.parse(req.params);
+ return res.status(200).json({
+ success: true,
+ msg: "Maintenance windows fetched successfully",
+ data: maintenanceWindows,
+ });
+ });
+ deleteMaintenanceWindow = catchAsync(async (req: Request, res: Response) => {
+ const validatedParams = deleteMaintenanceWindowByIdParamValidation.parse(req.params);
- const teamId = requireTeamId(req?.user?.teamId);
+ const teamId = requireTeamId(req?.user?.teamId);
- await this.maintenanceWindowService.deleteMaintenanceWindow({ id: validatedParams.id, teamId });
+ await this.maintenanceWindowService.deleteMaintenanceWindow({ id: validatedParams.id, teamId });
- return res.status(200).json({
- success: true,
- msg: "Maintenance window deleted successfully",
- });
- } catch (error) {
- next(error);
- }
- };
+ return res.status(200).json({
+ success: true,
+ msg: "Maintenance window deleted successfully",
+ });
+ });
- editMaintenanceWindow = async (req: Request, res: Response, next: NextFunction) => {
- try {
- const validatedParams = editMaintenanceWindowByIdParamValidation.parse(req.params);
- const validatedBody = editMaintenanceByIdWindowBodyValidation.parse(req.body);
+ editMaintenanceWindow = catchAsync(async (req: Request, res: Response) => {
+ const validatedParams = editMaintenanceWindowByIdParamValidation.parse(req.params);
+ const validatedBody = editMaintenanceByIdWindowBodyValidation.parse(req.body);
- const teamId = requireTeamId(req.user?.teamId);
+ const teamId = requireTeamId(req.user?.teamId);
- const editedMaintenanceWindow = await this.maintenanceWindowService.editMaintenanceWindow({
- id: validatedParams.id,
- body: validatedBody,
- teamId,
- });
- return res.status(200).json({
- success: true,
- msg: "Maintenance window edited successfully",
- data: editedMaintenanceWindow,
- });
- } catch (error) {
- next(error);
- }
- };
+ const editedMaintenanceWindow = await this.maintenanceWindowService.editMaintenanceWindow({
+ id: validatedParams.id,
+ body: validatedBody,
+ teamId,
+ });
+ return res.status(200).json({
+ success: true,
+ msg: "Maintenance window edited successfully",
+ data: editedMaintenanceWindow,
+ });
+ });
}
export default MaintenanceWindowController;
diff --git a/server/src/api/controllers/monitorController.ts b/server/src/api/controllers/monitorController.ts
index a0cb073d2..e0f820a62 100644
--- a/server/src/api/controllers/monitorController.ts
+++ b/server/src/api/controllers/monitorController.ts
@@ -1,4 +1,5 @@
-import { Request, Response, NextFunction } from "express";
+import { Request, Response, RequestHandler } from "express";
+import { catchAsync } from "@/utils/catchAsync.js";
import { updateNotificationsValidation } from "@/api/validation/notificationValidation.js";
import {
getMonitorByIdParamValidation,
@@ -25,26 +26,26 @@ import { INotificationsService } from "@/domain/notifications/notification.servi
const SERVICE_NAME = "monitorController";
export interface IMonitorController {
- getMonitorCertificate: (req: Request, res: Response, next: NextFunction) => Promise;
- getUptimeDetailsById: (req: Request, res: Response, next: NextFunction) => Promise;
- getHardwareDetailsById: (req: Request, res: Response, next: NextFunction) => Promise;
- getPageSpeedDetailsById: (req: Request, res: Response, next: NextFunction) => Promise;
- getGeoChecksByMonitorId: (req: Request, res: Response, next: NextFunction) => Promise;
- getMonitorById: (req: Request, res: Response, next: NextFunction) => Promise;
- createMonitor: (req: Request, res: Response, next: NextFunction) => Promise;
- importMonitorsFromJSON: (req: Request, res: Response, next: NextFunction) => Promise;
- deleteMonitor: (req: Request, res: Response, next: NextFunction) => Promise;
- deleteAllMonitors: (req: Request, res: Response, next: NextFunction) => Promise;
- editMonitor: (req: Request, res: Response, next: NextFunction) => Promise;
- pauseMonitor: (req: Request, res: Response, next: NextFunction) => Promise;
- bulkPauseMonitors: (req: Request, res: Response, next: NextFunction) => Promise;
- addDemoMonitors: (req: Request, res: Response, next: NextFunction) => Promise;
- getMonitorsByTeamId: (req: Request, res: Response, next: NextFunction) => Promise;
- getMonitorsWithChecksByTeamId: (req: Request, res: Response, next: NextFunction) => Promise;
- exportMonitorsToJSON: (req: Request, res: Response, next: NextFunction) => Promise;
- getAllGames: (req: Request, res: Response, next: NextFunction) => Promise;
- getGroupsByTeamId: (req: Request, res: Response, next: NextFunction) => Promise;
- updateNotifications: (req: Request, res: Response, next: NextFunction) => Promise;
+ getMonitorCertificate: RequestHandler;
+ getUptimeDetailsById: RequestHandler;
+ getHardwareDetailsById: RequestHandler;
+ getPageSpeedDetailsById: RequestHandler;
+ getGeoChecksByMonitorId: RequestHandler;
+ getMonitorById: RequestHandler;
+ createMonitor: RequestHandler;
+ importMonitorsFromJSON: RequestHandler;
+ deleteMonitor: RequestHandler;
+ deleteAllMonitors: RequestHandler;
+ editMonitor: RequestHandler;
+ pauseMonitor: RequestHandler;
+ bulkPauseMonitors: RequestHandler;
+ addDemoMonitors: RequestHandler;
+ getMonitorsByTeamId: RequestHandler;
+ getMonitorsWithChecksByTeamId: RequestHandler;
+ exportMonitorsToJSON: RequestHandler;
+ getAllGames: RequestHandler;
+ getGroupsByTeamId: RequestHandler;
+ updateNotifications: RequestHandler;
}
class MonitorController implements IMonitorController {
static SERVICE_NAME = SERVICE_NAME;
@@ -61,430 +62,350 @@ class MonitorController implements IMonitorController {
return MonitorController.SERVICE_NAME;
}
- getMonitorCertificate = async (req: Request, res: Response, next: NextFunction) => {
- try {
- const validatedParams = getCertificateParamValidation.parse(req.params);
- const teamId = requireTeamId(req.user?.teamId);
- const monitor = await this.monitorService.getMonitorById({ teamId, monitorId: validatedParams.monitorId });
- const certificate = await fetchMonitorCertificate(sslChecker, monitor);
+ getMonitorCertificate = catchAsync(async (req: Request, res: Response) => {
+ const validatedParams = getCertificateParamValidation.parse(req.params);
+ const teamId = requireTeamId(req.user?.teamId);
+ const monitor = await this.monitorService.getMonitorById({ teamId, monitorId: validatedParams.monitorId });
+ const certificate = await fetchMonitorCertificate(sslChecker, monitor);
- return res.status(200).json({
- success: true,
- msg: "SSL certificate retrieved successfully",
- data: {
- certificateDate: new Date(certificate.validTo),
- },
- });
- } catch (error) {
- next(error);
+ return res.status(200).json({
+ success: true,
+ msg: "SSL certificate retrieved successfully",
+ data: {
+ certificateDate: new Date(certificate.validTo),
+ },
+ });
+ });
+
+ getUptimeDetailsById = catchAsync(async (req: Request, res: Response) => {
+ const validatedParams = getUptimeDetailsByIdParamValidation.parse(req.params);
+ const validatedQuery = getUptimeDetailsByIdQueryValidation.parse(req.query);
+
+ const monitorId = validatedParams.monitorId;
+ const dateRange = validatedQuery.dateRange;
+
+ const teamId = requireTeamId(req.user?.teamId);
+
+ const data = await this.monitorService.getUptimeDetailsById({
+ teamId,
+ monitorId,
+ dateRange,
+ });
+ return res.status(200).json({
+ success: true,
+ msg: "Uptime details retrieved successfully",
+ data: data,
+ });
+ });
+
+ getHardwareDetailsById = catchAsync(async (req: Request, res: Response) => {
+ const validatedParams = getHardwareDetailsByIdParamValidation.parse(req.params);
+ const validatedQuery = getHardwareDetailsByIdQueryValidation.parse(req.query);
+
+ const monitorId = validatedParams.monitorId;
+ const dateRange = validatedQuery.dateRange || "recent";
+ const teamId = requireTeamId(req.user?.teamId);
+
+ const data = await this.monitorService.getHardwareDetailsById({
+ teamId,
+ monitorId,
+ dateRange,
+ });
+
+ return res.status(200).json({
+ success: true,
+ msg: "Hardware details retrieved successfully",
+ data: data,
+ });
+ });
+ getPageSpeedDetailsById = catchAsync(async (req: Request, res: Response) => {
+ const validatedParams = getHardwareDetailsByIdParamValidation.parse(req.params);
+ const validatedQuery = getHardwareDetailsByIdQueryValidation.parse(req.query);
+
+ const monitorId = validatedParams.monitorId;
+ const dateRange = validatedQuery.dateRange || "recent";
+ const teamId = requireTeamId(req.user?.teamId);
+
+ const data = await this.monitorService.getPageSpeedDetailsById({
+ teamId,
+ monitorId,
+ dateRange,
+ });
+
+ return res.status(200).json({
+ success: true,
+ msg: "Page speed details retrieved successfully",
+ data,
+ });
+ });
+
+ getGeoChecksByMonitorId = catchAsync(async (req: Request, res: Response) => {
+ const validatedParams = getMonitorByIdParamValidation.parse(req.params);
+ const validatedQuery = getMonitorByIdQueryValidation.parse(req.query);
+
+ const monitorId = validatedParams.monitorId;
+ const dateRange = validatedQuery.dateRange || "recent";
+ const continentParam = validatedQuery.continent;
+ const continents = continentParam ? (Array.isArray(continentParam) ? continentParam : [continentParam]) : undefined;
+ const teamId = requireTeamId(req.user?.teamId);
+
+ const data = await this.monitorService.getGeoChecksByMonitorId({
+ teamId,
+ monitorId,
+ dateRange,
+ continents,
+ });
+
+ return res.status(200).json({
+ success: true,
+ msg: "Geo checks retrieved successfully",
+ data,
+ });
+ });
+
+ getMonitorById = catchAsync(async (req: Request, res: Response) => {
+ const validatedParams = getMonitorByIdParamValidation.parse(req.params);
+
+ const teamId = requireTeamId(req.user?.teamId);
+ const monitorId = validatedParams.monitorId;
+
+ const monitor = await this.monitorService.getMonitorById({ teamId, monitorId });
+
+ return res.status(200).json({
+ success: true,
+ msg: "Monitor retrieved successfully",
+ data: monitor,
+ });
+ });
+
+ createMonitor = catchAsync(async (req: Request, res: Response) => {
+ const validatedBody = createMonitorBodyValidation.parse(req.body);
+
+ const userId = requireUserId(req.user?.id);
+ const teamId = requireTeamId(req.user?.teamId);
+
+ const monitor = await this.monitorService.createMonitor(teamId, userId, validatedBody);
+
+ return res.status(200).json({
+ success: true,
+ msg: "Monitor created successfully",
+ data: monitor,
+ });
+ });
+
+ importMonitorsFromJSON = catchAsync(async (req: Request, res: Response) => {
+ const teamId = requireTeamId(req.user?.teamId);
+ const userId = requireUserId(req.user?.id);
+ const validatedBody = importMonitorsBodyValidation.parse(req.body);
+ const monitors = validatedBody.monitors;
+
+ const result = await this.monitorService.importMonitorsFromJSON({ teamId, userId, monitors });
+
+ return res.status(200).json({
+ success: true,
+ msg: `Successfully imported ${result.imported} monitor(s)`,
+ data: result,
+ });
+ });
+
+ deleteMonitor = catchAsync(async (req: Request, res: Response) => {
+ const validatedParams = getMonitorByIdParamValidation.parse(req.params);
+ const teamId = requireTeamId(req.user?.teamId);
+ const monitorId = validatedParams.monitorId;
+
+ const deletedMonitor = await this.monitorService.deleteMonitor({ teamId, monitorId });
+
+ return res.status(200).json({
+ success: true,
+ msg: "Monitor deleted successfully",
+ data: deletedMonitor,
+ });
+ });
+
+ deleteAllMonitors = catchAsync(async (req: Request, res: Response) => {
+ const teamId = requireTeamId(req.user?.teamId);
+ const deletedCount = await this.monitorService.deleteAllMonitors({ teamId });
+
+ return res.status(200).json({
+ success: true,
+ msg: `Deleted ${deletedCount} monitors`,
+ });
+ });
+
+ editMonitor = catchAsync(async (req: Request, res: Response) => {
+ const validatedParams = getMonitorByIdParamValidation.parse(req.params);
+ const validatedBody = editMonitorBodyValidation.parse(req.body);
+ const monitorId = validatedParams.monitorId;
+ const teamId = requireTeamId(req.user?.teamId);
+
+ const editedMonitor = await this.monitorService.editMonitor({ teamId, monitorId, body: validatedBody });
+
+ return res.status(200).json({
+ success: true,
+ msg: "Monitor edited successfully",
+ data: editedMonitor,
+ });
+ });
+
+ pauseMonitor = catchAsync(async (req: Request, res: Response) => {
+ const validatedParams = pauseMonitorParamValidation.parse(req.params);
+
+ const monitorId = validatedParams.monitorId;
+ const teamId = requireTeamId(req.user?.teamId);
+
+ const monitor = await this.monitorService.pauseMonitor({ teamId, monitorId });
+
+ return res.status(200).json({
+ success: true,
+ msg: monitor.isActive ? "Monitor resumed successfully" : "Monitor paused successfully",
+ data: monitor,
+ });
+ });
+
+ bulkPauseMonitors = catchAsync(async (req: Request, res: Response) => {
+ const validatedBody = bulkPauseMonitorBodyValidation.parse(req.body);
+
+ const { monitorIds, pause } = validatedBody;
+ const teamId = requireTeamId(req.user?.teamId);
+
+ const { monitors, failedCount } = await this.monitorService.bulkPauseMonitors({ teamId, monitorIds, pause });
+
+ const action = pause ? "paused" : "resumed";
+ const monitorStr = monitors.length === 1 ? "monitor" : "monitors";
+
+ let msg = `${monitors.length} ${monitorStr} ${action} successfully`;
+ if (failedCount > 0) {
+ msg = `${monitors.length} ${monitorStr} ${action} in database, but ${failedCount} failed to sync with the job queue. Please check logs.`;
}
- };
- getUptimeDetailsById = async (req: Request, res: Response, next: NextFunction) => {
- try {
- const validatedParams = getUptimeDetailsByIdParamValidation.parse(req.params);
- const validatedQuery = getUptimeDetailsByIdQueryValidation.parse(req.query);
+ return res.status(200).json({
+ success: true,
+ msg,
+ data: { monitors, failedCount },
+ });
+ });
- const monitorId = validatedParams.monitorId;
- const dateRange = validatedQuery.dateRange;
+ addDemoMonitors = catchAsync(async (req: Request, res: Response) => {
+ const id = requireUserId(req.user?.id);
+ const teamId = requireTeamId(req.user?.teamId);
+ const demoMonitors = await this.monitorService.addDemoMonitors({ userId: id, teamId });
- const teamId = requireTeamId(req.user?.teamId);
+ return res.status(200).json({
+ success: true,
+ msg: "Demo monitors added successfully",
+ data: demoMonitors?.length ?? 0,
+ });
+ });
- const data = await this.monitorService.getUptimeDetailsById({
- teamId,
- monitorId,
- dateRange,
+ getMonitorsByTeamId = catchAsync(async (req: Request, res: Response) => {
+ const validatedQuery = getMonitorsByTeamIdQueryValidation.parse(req.query);
+
+ const teamId = requireTeamId(req.user?.teamId);
+ const type = validatedQuery.type;
+ const tags = validatedQuery.tags;
+ const filter = validatedQuery.filter;
+
+ const monitors = await this.monitorService.getMonitorsByTeamId({ teamId, type, tags, filter });
+
+ return res.status(200).json({
+ success: true,
+ msg: "Monitors retrieved successfully",
+ data: monitors,
+ });
+ });
+
+ getMonitorsWithChecksByTeamId = catchAsync(async (req: Request, res: Response) => {
+ const validatedQuery = getMonitorsWithChecksQueryValidation.parse(req.query);
+
+ const limit = validatedQuery.limit;
+ const page = validatedQuery.page;
+ const rowsPerPage = validatedQuery.rowsPerPage;
+ const filter = validatedQuery.filter;
+ const field = validatedQuery.field;
+ const order = validatedQuery.order;
+ const type = validatedQuery.type;
+ const tags = validatedQuery.tags;
+ const teamId = requireTeamId(req.user?.teamId);
+
+ const monitors = await this.monitorService.getMonitorsWithChecksByTeamId({
+ teamId,
+ limit,
+ type,
+ tags,
+ page,
+ rowsPerPage,
+ filter,
+ field,
+ order,
+ });
+
+ return res.status(200).json({
+ msg: "Monitors retrieved successfully",
+ data: monitors,
+ });
+ });
+
+ exportMonitorsToJSON = catchAsync(async (req: Request, res: Response) => {
+ const teamId = requireTeamId(req.user?.teamId);
+ const json = await this.monitorService.exportMonitorsToJSON({ teamId });
+
+ return res.status(200).json({
+ success: true,
+ msg: "Monitors exported successfully",
+ data: json,
+ });
+ });
+
+ getAllGames = catchAsync(async (req: Request, res: Response) => {
+ const games = this.monitorService.getAllGames();
+ return res.status(200).json({
+ success: true,
+ msg: "Supported games retrieved successfully",
+ data: games,
+ });
+ });
+
+ getGroupsByTeamId = catchAsync(async (req: Request, res: Response) => {
+ const teamId = requireTeamId(req.user?.teamId);
+ const groups = await this.monitorService.getGroupsByTeamId({ teamId });
+
+ return res.status(200).json({
+ msg: "Groups retrieved successfully",
+ data: groups,
+ });
+ });
+
+ updateNotifications = catchAsync(async (req: Request, res: Response) => {
+ updateNotificationsValidation.parse(req.body);
+
+ const teamId = requireTeamId(req.user?.teamId);
+ const { monitorIds, notificationIds, action } = req.body;
+
+ // Verify all requested notification IDs actually belong to this team
+ const teamNotifications = await this.notificationsService.findNotificationsByTeamId(teamId);
+ const validNotificationIds = teamNotifications.map((n) => n.id);
+
+ const invalidIds = notificationIds.filter((id: string) => !validNotificationIds.includes(id));
+ if (invalidIds.length > 0) {
+ throw new AppError({
+ message: `The following notification IDs are invalid or do not belong to your team: ${invalidIds.join(", ")}`,
+ status: 403,
});
- return res.status(200).json({
- success: true,
- msg: "Uptime details retrieved successfully",
- data: data,
- });
- } catch (error) {
- next(error);
}
- };
- getHardwareDetailsById = async (req: Request, res: Response, next: NextFunction) => {
- try {
- const validatedParams = getHardwareDetailsByIdParamValidation.parse(req.params);
- const validatedQuery = getHardwareDetailsByIdQueryValidation.parse(req.query);
+ const modifiedCount = await this.monitorService.updateNotifications({
+ teamId,
+ monitorIds,
+ notificationIds,
+ action,
+ });
- const monitorId = validatedParams.monitorId;
- const dateRange = validatedQuery.dateRange || "recent";
- const teamId = requireTeamId(req.user?.teamId);
-
- const data = await this.monitorService.getHardwareDetailsById({
- teamId,
- monitorId,
- dateRange,
- });
-
- return res.status(200).json({
- success: true,
- msg: "Hardware details retrieved successfully",
- data: data,
- });
- } catch (error) {
- next(error);
- }
- };
- getPageSpeedDetailsById = async (req: Request, res: Response, next: NextFunction) => {
- try {
- const validatedParams = getHardwareDetailsByIdParamValidation.parse(req.params);
- const validatedQuery = getHardwareDetailsByIdQueryValidation.parse(req.query);
-
- const monitorId = validatedParams.monitorId;
- const dateRange = validatedQuery.dateRange || "recent";
- const teamId = requireTeamId(req.user?.teamId);
-
- const data = await this.monitorService.getPageSpeedDetailsById({
- teamId,
- monitorId,
- dateRange,
- });
-
- return res.status(200).json({
- success: true,
- msg: "Page speed details retrieved successfully",
- data,
- });
- } catch (error) {
- next(error);
- }
- };
-
- getGeoChecksByMonitorId = async (req: Request, res: Response, next: NextFunction) => {
- try {
- const validatedParams = getMonitorByIdParamValidation.parse(req.params);
- const validatedQuery = getMonitorByIdQueryValidation.parse(req.query);
-
- const monitorId = validatedParams.monitorId;
- const dateRange = validatedQuery.dateRange || "recent";
- const continentParam = validatedQuery.continent;
- const continents = continentParam ? (Array.isArray(continentParam) ? continentParam : [continentParam]) : undefined;
- const teamId = requireTeamId(req.user?.teamId);
-
- const data = await this.monitorService.getGeoChecksByMonitorId({
- teamId,
- monitorId,
- dateRange,
- continents,
- });
-
- return res.status(200).json({
- success: true,
- msg: "Geo checks retrieved successfully",
- data,
- });
- } catch (error) {
- next(error);
- }
- };
-
- getMonitorById = async (req: Request, res: Response, next: NextFunction) => {
- try {
- const validatedParams = getMonitorByIdParamValidation.parse(req.params);
-
- const teamId = requireTeamId(req.user?.teamId);
- const monitorId = validatedParams.monitorId;
-
- const monitor = await this.monitorService.getMonitorById({ teamId, monitorId });
-
- return res.status(200).json({
- success: true,
- msg: "Monitor retrieved successfully",
- data: monitor,
- });
- } catch (error) {
- next(error);
- }
- };
-
- createMonitor = async (req: Request, res: Response, next: NextFunction) => {
- try {
- const validatedBody = createMonitorBodyValidation.parse(req.body);
-
- const userId = requireUserId(req.user?.id);
- const teamId = requireTeamId(req.user?.teamId);
-
- const monitor = await this.monitorService.createMonitor(teamId, userId, validatedBody);
-
- return res.status(200).json({
- success: true,
- msg: "Monitor created successfully",
- data: monitor,
- });
- } catch (error) {
- next(error);
- }
- };
-
- importMonitorsFromJSON = async (req: Request, res: Response, next: NextFunction) => {
- try {
- const teamId = requireTeamId(req.user?.teamId);
- const userId = requireUserId(req.user?.id);
- const validatedBody = importMonitorsBodyValidation.parse(req.body);
- const monitors = validatedBody.monitors;
-
- const result = await this.monitorService.importMonitorsFromJSON({ teamId, userId, monitors });
-
- return res.status(200).json({
- success: true,
- msg: `Successfully imported ${result.imported} monitor(s)`,
- data: result,
- });
- } catch (error) {
- next(error);
- }
- };
-
- deleteMonitor = async (req: Request, res: Response, next: NextFunction) => {
- try {
- const validatedParams = getMonitorByIdParamValidation.parse(req.params);
- const teamId = requireTeamId(req.user?.teamId);
- const monitorId = validatedParams.monitorId;
-
- const deletedMonitor = await this.monitorService.deleteMonitor({ teamId, monitorId });
-
- return res.status(200).json({
- success: true,
- msg: "Monitor deleted successfully",
- data: deletedMonitor,
- });
- } catch (error) {
- next(error);
- }
- };
-
- deleteAllMonitors = async (req: Request, res: Response, next: NextFunction) => {
- try {
- const teamId = requireTeamId(req.user?.teamId);
- const deletedCount = await this.monitorService.deleteAllMonitors({ teamId });
-
- return res.status(200).json({
- success: true,
- msg: `Deleted ${deletedCount} monitors`,
- });
- } catch (error) {
- next(error);
- }
- };
-
- editMonitor = async (req: Request, res: Response, next: NextFunction) => {
- try {
- const validatedParams = getMonitorByIdParamValidation.parse(req.params);
- const validatedBody = editMonitorBodyValidation.parse(req.body);
- const monitorId = validatedParams.monitorId;
- const teamId = requireTeamId(req.user?.teamId);
-
- const editedMonitor = await this.monitorService.editMonitor({ teamId, monitorId, body: validatedBody });
-
- return res.status(200).json({
- success: true,
- msg: "Monitor edited successfully",
- data: editedMonitor,
- });
- } catch (error) {
- next(error);
- }
- };
-
- pauseMonitor = async (req: Request, res: Response, next: NextFunction) => {
- try {
- const validatedParams = pauseMonitorParamValidation.parse(req.params);
-
- const monitorId = validatedParams.monitorId;
- const teamId = requireTeamId(req.user?.teamId);
-
- const monitor = await this.monitorService.pauseMonitor({ teamId, monitorId });
-
- return res.status(200).json({
- success: true,
- msg: monitor.isActive ? "Monitor resumed successfully" : "Monitor paused successfully",
- data: monitor,
- });
- } catch (error) {
- next(error);
- }
- };
-
- bulkPauseMonitors = async (req: Request, res: Response, next: NextFunction) => {
- try {
- const validatedBody = bulkPauseMonitorBodyValidation.parse(req.body);
-
- const { monitorIds, pause } = validatedBody;
- const teamId = requireTeamId(req.user?.teamId);
-
- const { monitors, failedCount } = await this.monitorService.bulkPauseMonitors({ teamId, monitorIds, pause });
-
- const action = pause ? "paused" : "resumed";
- const monitorStr = monitors.length === 1 ? "monitor" : "monitors";
-
- let msg = `${monitors.length} ${monitorStr} ${action} successfully`;
- if (failedCount > 0) {
- msg = `${monitors.length} ${monitorStr} ${action} in database, but ${failedCount} failed to sync with the job queue. Please check logs.`;
- }
-
- return res.status(200).json({
- success: true,
- msg,
- data: { monitors, failedCount },
- });
- } catch (error) {
- next(error);
- }
- };
-
- addDemoMonitors = async (req: Request, res: Response, next: NextFunction) => {
- try {
- const id = requireUserId(req.user?.id);
- const teamId = requireTeamId(req.user?.teamId);
- const demoMonitors = await this.monitorService.addDemoMonitors({ userId: id, teamId });
-
- return res.status(200).json({
- success: true,
- msg: "Demo monitors added successfully",
- data: demoMonitors?.length ?? 0,
- });
- } catch (error) {
- next(error);
- }
- };
-
- getMonitorsByTeamId = async (req: Request, res: Response, next: NextFunction) => {
- try {
- const validatedQuery = getMonitorsByTeamIdQueryValidation.parse(req.query);
-
- const teamId = requireTeamId(req.user?.teamId);
- const type = validatedQuery.type;
- const tags = validatedQuery.tags;
- const filter = validatedQuery.filter;
-
- const monitors = await this.monitorService.getMonitorsByTeamId({ teamId, type, tags, filter });
-
- return res.status(200).json({
- success: true,
- msg: "Monitors retrieved successfully",
- data: monitors,
- });
- } catch (error) {
- next(error);
- }
- };
-
- getMonitorsWithChecksByTeamId = async (req: Request, res: Response, next: NextFunction) => {
- try {
- const validatedQuery = getMonitorsWithChecksQueryValidation.parse(req.query);
-
- const limit = validatedQuery.limit;
- const page = validatedQuery.page;
- const rowsPerPage = validatedQuery.rowsPerPage;
- const filter = validatedQuery.filter;
- const field = validatedQuery.field;
- const order = validatedQuery.order;
- const type = validatedQuery.type;
- const tags = validatedQuery.tags;
- const teamId = requireTeamId(req.user?.teamId);
-
- const monitors = await this.monitorService.getMonitorsWithChecksByTeamId({
- teamId,
- limit,
- type,
- tags,
- page,
- rowsPerPage,
- filter,
- field,
- order,
- });
-
- return res.status(200).json({
- msg: "Monitors retrieved successfully",
- data: monitors,
- });
- } catch (error) {
- next(error);
- }
- };
-
- exportMonitorsToJSON = async (req: Request, res: Response, next: NextFunction) => {
- try {
- const teamId = requireTeamId(req.user?.teamId);
- const json = await this.monitorService.exportMonitorsToJSON({ teamId });
-
- return res.status(200).json({
- success: true,
- msg: "Monitors exported successfully",
- data: json,
- });
- } catch (error) {
- next(error);
- }
- };
-
- getAllGames = async (req: Request, res: Response, next: NextFunction) => {
- try {
- const games = this.monitorService.getAllGames();
- return res.status(200).json({
- success: true,
- msg: "Supported games retrieved successfully",
- data: games,
- });
- } catch (error) {
- next(error);
- }
- };
-
- getGroupsByTeamId = async (req: Request, res: Response, next: NextFunction) => {
- try {
- const teamId = requireTeamId(req.user?.teamId);
- const groups = await this.monitorService.getGroupsByTeamId({ teamId });
-
- return res.status(200).json({
- msg: "Groups retrieved successfully",
- data: groups,
- });
- } catch (error) {
- next(error);
- }
- };
-
- updateNotifications = async (req: Request, res: Response, next: NextFunction) => {
- try {
- updateNotificationsValidation.parse(req.body);
-
- const teamId = requireTeamId(req.user?.teamId);
- const { monitorIds, notificationIds, action } = req.body;
-
- // Verify all requested notification IDs actually belong to this team
- const teamNotifications = await this.notificationsService.findNotificationsByTeamId(teamId);
- const validNotificationIds = teamNotifications.map((n) => n.id);
-
- const invalidIds = notificationIds.filter((id: string) => !validNotificationIds.includes(id));
- if (invalidIds.length > 0) {
- throw new AppError({
- message: `The following notification IDs are invalid or do not belong to your team: ${invalidIds.join(", ")}`,
- status: 403,
- });
- }
-
- const modifiedCount = await this.monitorService.updateNotifications({
- teamId,
- monitorIds,
- notificationIds,
- action,
- });
-
- return res.status(200).json({
- success: true,
- msg: `Notifications updated successfully on ${modifiedCount} monitor(s)`,
- data: { modifiedCount },
- });
- } catch (error) {
- next(error);
- }
- };
+ return res.status(200).json({
+ success: true,
+ msg: `Notifications updated successfully on ${modifiedCount} monitor(s)`,
+ data: { modifiedCount },
+ });
+ });
}
export default MonitorController;
diff --git a/server/src/api/controllers/notificationController.ts b/server/src/api/controllers/notificationController.ts
index 3aecfcad8..53dd7645c 100644
--- a/server/src/api/controllers/notificationController.ts
+++ b/server/src/api/controllers/notificationController.ts
@@ -1,4 +1,5 @@
-import { Request, Response, NextFunction } from "express";
+import { Request, Response, RequestHandler } from "express";
+import { catchAsync } from "@/utils/catchAsync.js";
import {
createNotificationBodyValidation,
@@ -16,13 +17,13 @@ import { IMonitorsRepository } from "@/domain/monitors/monitor.repository.interf
const SERVICE_NAME = "NotificationController";
export interface INotificationController {
- testNotification: (req: Request, res: Response, next: NextFunction) => Promise;
- createNotification: (req: Request, res: Response, next: NextFunction) => Promise;
- getNotificationsByTeamId: (req: Request, res: Response, next: NextFunction) => Promise;
- deleteNotification: (req: Request, res: Response, next: NextFunction) => Promise;
- getNotificationById: (req: Request, res: Response, next: NextFunction) => Promise;
- editNotification: (req: Request, res: Response, next: NextFunction) => Promise;
- testAllNotifications: (req: Request, res: Response, next: NextFunction) => Promise;
+ testNotification: RequestHandler;
+ createNotification: RequestHandler;
+ getNotificationsByTeamId: RequestHandler;
+ deleteNotification: RequestHandler;
+ getNotificationById: RequestHandler;
+ editNotification: RequestHandler;
+ testAllNotifications: RequestHandler;
}
class NotificationController implements INotificationController {
private notificationsService: INotificationsService;
@@ -32,132 +33,104 @@ class NotificationController implements INotificationController {
this.monitorsRepository = monitorsRepository;
}
- testNotification = async (req: Request, res: Response, next: NextFunction) => {
- try {
- const notification = testNotificationBodyValidation.parse(req.body);
- const success = await this.notificationsService.sendTestNotification(notification);
+ testNotification = catchAsync(async (req: Request, res: Response) => {
+ const notification = testNotificationBodyValidation.parse(req.body);
+ const success = await this.notificationsService.sendTestNotification(notification);
- return res.status(200).json({
- success,
- msg: success ? "Notification sent successfully" : "Notification could not be sent — check the destination details.",
- details: { service: SERVICE_NAME },
- });
- } catch (error) {
- next(error);
+ return res.status(200).json({
+ success,
+ msg: success ? "Notification sent successfully" : "Notification could not be sent — check the destination details.",
+ details: { service: SERVICE_NAME },
+ });
+ });
+
+ createNotification = catchAsync(async (req: Request, res: Response) => {
+ const validatedBody = createNotificationBodyValidation.parse(req.body);
+
+ const teamId = requireTeamId(req.user?.teamId);
+ const userId = requireUserId(req.user?.id);
+
+ const notification = await this.notificationsService.createNotification(validatedBody, userId, teamId);
+ return res.status(200).json({
+ success: true,
+ msg: "Notification created successfully",
+ data: notification,
+ });
+ });
+
+ getNotificationsByTeamId = catchAsync(async (req: Request, res: Response) => {
+ const teamId = requireTeamId(req.user?.teamId);
+ const notifications = await this.notificationsService.findNotificationsByTeamId(teamId);
+
+ return res.status(200).json({
+ success: true,
+ msg: "Notifications fetched successfully",
+ data: notifications,
+ });
+ });
+
+ deleteNotification = catchAsync(async (req: Request, res: Response) => {
+ const teamId = requireTeamId(req.user?.teamId);
+ const validatedParams = deleteNotificationParamValidation.parse(req.params);
+
+ await this.notificationsService.deleteById(validatedParams.id, teamId);
+ return res.status(200).json({
+ success: true,
+ msg: "Notification deleted successfully",
+ });
+ });
+
+ getNotificationById = catchAsync(async (req: Request, res: Response) => {
+ const teamId = requireTeamId(req.user?.teamId);
+ const validatedParams = getNotificationByIdParamValidation.parse(req.params);
+
+ const notification = await this.notificationsService.findById(validatedParams.id, teamId);
+
+ return res.status(200).json({
+ success: true,
+ msg: "Notification fetched successfully",
+ data: notification,
+ });
+ });
+
+ editNotification = catchAsync(async (req: Request, res: Response) => {
+ const validatedBody = createNotificationBodyValidation.parse(req.body);
+ const validatedParams = editNotificationParamValidation.parse(req.params);
+
+ const teamId = requireTeamId(req.user?.teamId);
+ const notificationId = validatedParams.id;
+
+ const editedNotification = await this.notificationsService.updateById(notificationId, teamId, validatedBody);
+ return res.status(200).json({
+ success: true,
+ msg: "Notification updated successfully",
+ data: editedNotification,
+ });
+ });
+
+ testAllNotifications = catchAsync(async (req: Request, res: Response) => {
+ const validatedBody = testAllNotificationsBodyValidation.parse(req.body);
+
+ const teamId = requireTeamId(req.user?.teamId);
+
+ const monitor = await this.monitorsRepository.findById(validatedBody.monitorId, teamId);
+ const notifications = monitor.notifications || [];
+
+ if (notifications.length === 0) {
+ throw new AppError({ message: "No notifications", status: 400 });
}
- };
- createNotification = async (req: Request, res: Response, next: NextFunction) => {
- try {
- const validatedBody = createNotificationBodyValidation.parse(req.body);
+ const result = await this.notificationsService.testAllNotifications(notifications);
- const teamId = requireTeamId(req.user?.teamId);
- const userId = requireUserId(req.user?.id);
-
- const notification = await this.notificationsService.createNotification(validatedBody, userId, teamId);
- return res.status(200).json({
- success: true,
- msg: "Notification created successfully",
- data: notification,
- });
- } catch (error) {
- next(error);
+ if (!result) {
+ throw new AppError({ message: "Failed to send all notifications", status: 500 });
}
- };
- getNotificationsByTeamId = async (req: Request, res: Response, next: NextFunction) => {
- try {
- const teamId = requireTeamId(req.user?.teamId);
- const notifications = await this.notificationsService.findNotificationsByTeamId(teamId);
-
- return res.status(200).json({
- success: true,
- msg: "Notifications fetched successfully",
- data: notifications,
- });
- } catch (error) {
- next(error);
- }
- };
-
- deleteNotification = async (req: Request, res: Response, next: NextFunction) => {
- try {
- const teamId = requireTeamId(req.user?.teamId);
- const validatedParams = deleteNotificationParamValidation.parse(req.params);
-
- await this.notificationsService.deleteById(validatedParams.id, teamId);
- return res.status(200).json({
- success: true,
- msg: "Notification deleted successfully",
- });
- } catch (error) {
- next(error);
- }
- };
-
- getNotificationById = async (req: Request, res: Response, next: NextFunction) => {
- try {
- const teamId = requireTeamId(req.user?.teamId);
- const validatedParams = getNotificationByIdParamValidation.parse(req.params);
-
- const notification = await this.notificationsService.findById(validatedParams.id, teamId);
-
- return res.status(200).json({
- success: true,
- msg: "Notification fetched successfully",
- data: notification,
- });
- } catch (error) {
- next(error);
- }
- };
-
- editNotification = async (req: Request, res: Response, next: NextFunction) => {
- try {
- const validatedBody = createNotificationBodyValidation.parse(req.body);
- const validatedParams = editNotificationParamValidation.parse(req.params);
-
- const teamId = requireTeamId(req.user?.teamId);
- const notificationId = validatedParams.id;
-
- const editedNotification = await this.notificationsService.updateById(notificationId, teamId, validatedBody);
- return res.status(200).json({
- success: true,
- msg: "Notification updated successfully",
- data: editedNotification,
- });
- } catch (error) {
- next(error);
- }
- };
-
- testAllNotifications = async (req: Request, res: Response, next: NextFunction) => {
- try {
- const validatedBody = testAllNotificationsBodyValidation.parse(req.body);
-
- const teamId = requireTeamId(req.user?.teamId);
-
- const monitor = await this.monitorsRepository.findById(validatedBody.monitorId, teamId);
- const notifications = monitor.notifications || [];
-
- if (notifications.length === 0) {
- throw new AppError({ message: "No notifications", status: 400 });
- }
-
- const result = await this.notificationsService.testAllNotifications(notifications);
-
- if (!result) {
- throw new AppError({ message: "Failed to send all notifications", status: 500 });
- }
-
- return res.status(200).json({
- success: true,
- msg: "All notifications sent successfully",
- });
- } catch (error) {
- next(error);
- }
- };
+ return res.status(200).json({
+ success: true,
+ msg: "All notifications sent successfully",
+ });
+ });
}
export default NotificationController;
diff --git a/server/src/api/controllers/queueController.ts b/server/src/api/controllers/queueController.ts
index 0a21761ab..72c1463f9 100755
--- a/server/src/api/controllers/queueController.ts
+++ b/server/src/api/controllers/queueController.ts
@@ -1,14 +1,15 @@
import { IJobScheduler } from "@/worker/worker.interface.js";
import { getQueueJobsQueryValidation } from "@/api/validation/queueValidation.js";
-import { Request, Response, NextFunction } from "express";
+import { Request, Response, RequestHandler } from "express";
+import { catchAsync } from "@/utils/catchAsync.js";
const SERVICE_NAME = "JobQueueController";
export interface IJobQueueController {
- getMetrics(req: Request, res: Response, next: NextFunction): Promise;
- getJobs(req: Request, res: Response, next: NextFunction): Promise;
- getAllMetrics(req: Request, res: Response, next: NextFunction): Promise;
- flushQueue(req: Request, res: Response, next: NextFunction): Promise;
+ getMetrics: RequestHandler;
+ getJobs: RequestHandler;
+ getAllMetrics: RequestHandler;
+ flushQueue: RequestHandler;
}
class JobQueueController implements IJobQueueController {
@@ -19,59 +20,43 @@ class JobQueueController implements IJobQueueController {
return JobQueueController.SERVICE_NAME;
}
- getMetrics = async (req: Request, res: Response, next: NextFunction) => {
- try {
- const metrics = await this.scheduler.getMetrics();
- res.status(200).json({
- success: true,
- msg: "Queue metrics fetched successfully",
- data: metrics,
- });
- } catch (error) {
- next(error);
- }
- };
+ getMetrics = catchAsync(async (req: Request, res: Response) => {
+ const metrics = await this.scheduler.getMetrics();
+ res.status(200).json({
+ success: true,
+ msg: "Queue metrics fetched successfully",
+ data: metrics,
+ });
+ });
- getJobs = async (req: Request, res: Response, next: NextFunction) => {
- try {
- const pagination = getQueueJobsQueryValidation.parse(req.query);
- const { jobs, count } = await this.scheduler.getJobs(pagination);
- return res.status(200).json({
- success: true,
- msg: "Queue jobs fetched successfully",
- data: { jobs, count },
- });
- } catch (error) {
- next(error);
- }
- };
+ getJobs = catchAsync(async (req: Request, res: Response) => {
+ const pagination = getQueueJobsQueryValidation.parse(req.query);
+ const { jobs, count } = await this.scheduler.getJobs(pagination);
+ return res.status(200).json({
+ success: true,
+ msg: "Queue jobs fetched successfully",
+ data: { jobs, count },
+ });
+ });
- getAllMetrics = async (req: Request, res: Response, next: NextFunction) => {
- try {
- const pagination = getQueueJobsQueryValidation.parse(req.query);
- const { jobs, count } = await this.scheduler.getJobs(pagination);
- const metrics = await this.scheduler.getMetrics();
- return res.status(200).json({
- success: true,
- msg: "Queue metrics fetched successfully",
- data: { jobs, count, metrics },
- });
- } catch (error) {
- next(error);
- }
- };
+ getAllMetrics = catchAsync(async (req: Request, res: Response) => {
+ const pagination = getQueueJobsQueryValidation.parse(req.query);
+ const { jobs, count } = await this.scheduler.getJobs(pagination);
+ const metrics = await this.scheduler.getMetrics();
+ return res.status(200).json({
+ success: true,
+ msg: "Queue metrics fetched successfully",
+ data: { jobs, count, metrics },
+ });
+ });
- flushQueue = async (req: Request, res: Response, next: NextFunction) => {
- try {
- const result = await this.scheduler.flushQueues();
- return res.status(200).json({
- success: true,
- msg: "Queue flushed successfully",
- data: result,
- });
- } catch (error) {
- next(error);
- }
- };
+ flushQueue = catchAsync(async (req: Request, res: Response) => {
+ const result = await this.scheduler.flushQueues();
+ return res.status(200).json({
+ success: true,
+ msg: "Queue flushed successfully",
+ data: result,
+ });
+ });
}
export default JobQueueController;
diff --git a/server/src/api/controllers/settingsController.ts b/server/src/api/controllers/settingsController.ts
index 4acfdf552..06085c3f8 100644
--- a/server/src/api/controllers/settingsController.ts
+++ b/server/src/api/controllers/settingsController.ts
@@ -1,4 +1,5 @@
-import { Request, Response, NextFunction } from "express";
+import { Request, Response, RequestHandler } from "express";
+import { catchAsync } from "@/utils/catchAsync.js";
import { updateAppSettingsBodyValidation } from "@/api/validation/settingsValidation.js";
import { sendTestEmailBodyValidation } from "@/api/validation/notificationValidation.js";
import { AppError } from "@/utils/AppError.js";
@@ -10,9 +11,9 @@ const SERVICE_NAME = "SettingsController";
export interface ISettingsController {
serviceName: string;
- getAppSettings(req: Request, res: Response, next: NextFunction): Promise;
- updateAppSettings(req: Request, res: Response, next: NextFunction): Promise;
- sendTestEmail(req: Request, res: Response, next: NextFunction): Promise;
+ getAppSettings: RequestHandler;
+ updateAppSettings: RequestHandler;
+ sendTestEmail: RequestHandler;
}
class SettingsController implements ISettingsController {
@@ -50,94 +51,82 @@ class SettingsController implements ISettingsController {
return returnSettings;
};
- getAppSettings = async (req: Request, res: Response, next: NextFunction) => {
- try {
- const dbSettings = await this.settingsService.getDBSettings();
+ getAppSettings = catchAsync(async (req: Request, res: Response) => {
+ const dbSettings = await this.settingsService.getDBSettings();
- const returnSettings = this.buildAppSettings(dbSettings);
- return res.status(200).json({
- success: true,
- msg: "App settings fetched successfully",
- data: returnSettings,
- });
- } catch (error) {
- next(error);
+ const returnSettings = this.buildAppSettings(dbSettings);
+ return res.status(200).json({
+ success: true,
+ msg: "App settings fetched successfully",
+ data: returnSettings,
+ });
+ });
+
+ updateAppSettings = catchAsync(async (req: Request, res: Response) => {
+ const validatedBody = updateAppSettingsBodyValidation.parse(req.body);
+
+ const updatedSettings = await this.settingsService.updateDbSettings(validatedBody);
+ const returnSettings = this.buildAppSettings(updatedSettings);
+ return res.status(200).json({
+ success: true,
+ msg: "App settings updated successfully",
+ data: returnSettings,
+ });
+ });
+
+ sendTestEmail = catchAsync(async (req: Request, res: Response) => {
+ sendTestEmailBodyValidation.parse(req.body);
+
+ const {
+ to,
+ systemEmailHost,
+ systemEmailPort,
+ systemEmailAddress,
+ systemEmailDisplayName,
+ systemEmailPassword,
+ systemEmailUser,
+ systemEmailConnectionHost,
+ systemEmailSecure,
+ systemEmailPool,
+ systemEmailIgnoreTLS,
+ systemEmailRequireTLS,
+ systemEmailRejectUnauthorized,
+ systemEmailTLSServername,
+ } = req.body;
+
+ const subject = "This is a test email from Checkmate";
+ const context = { testName: "Monitoring System" };
+
+ const html = await this.emailService.buildEmail("testEmailTemplate", context);
+ if (!html) {
+ throw new AppError({ message: "Failed to build email template.", status: 500 });
}
- };
+ const messageId = await this.emailService.sendEmail(to, subject, html, {
+ systemEmailHost,
+ systemEmailPort,
+ systemEmailUser,
+ systemEmailAddress,
+ systemEmailDisplayName,
+ systemEmailPassword,
+ systemEmailConnectionHost,
+ systemEmailSecure,
+ systemEmailPool,
+ systemEmailIgnoreTLS,
+ systemEmailRequireTLS,
+ systemEmailRejectUnauthorized,
+ systemEmailTLSServername,
+ });
- updateAppSettings = async (req: Request, res: Response, next: NextFunction) => {
- try {
- const validatedBody = updateAppSettingsBodyValidation.parse(req.body);
-
- const updatedSettings = await this.settingsService.updateDbSettings(validatedBody);
- const returnSettings = this.buildAppSettings(updatedSettings);
- return res.status(200).json({
- success: true,
- msg: "App settings updated successfully",
- data: returnSettings,
- });
- } catch (error) {
- next(error);
+ if (!messageId) {
+ throw new AppError({ message: "Failed to send test email.", status: 500 });
}
- };
- sendTestEmail = async (req: Request, res: Response, next: NextFunction) => {
- try {
- sendTestEmailBodyValidation.parse(req.body);
-
- const {
- to,
- systemEmailHost,
- systemEmailPort,
- systemEmailAddress,
- systemEmailDisplayName,
- systemEmailPassword,
- systemEmailUser,
- systemEmailConnectionHost,
- systemEmailSecure,
- systemEmailPool,
- systemEmailIgnoreTLS,
- systemEmailRequireTLS,
- systemEmailRejectUnauthorized,
- systemEmailTLSServername,
- } = req.body;
-
- const subject = "This is a test email from Checkmate";
- const context = { testName: "Monitoring System" };
-
- const html = await this.emailService.buildEmail("testEmailTemplate", context);
- if (!html) {
- throw new AppError({ message: "Failed to build email template.", status: 500 });
- }
- const messageId = await this.emailService.sendEmail(to, subject, html, {
- systemEmailHost,
- systemEmailPort,
- systemEmailUser,
- systemEmailAddress,
- systemEmailDisplayName,
- systemEmailPassword,
- systemEmailConnectionHost,
- systemEmailSecure,
- systemEmailPool,
- systemEmailIgnoreTLS,
- systemEmailRequireTLS,
- systemEmailRejectUnauthorized,
- systemEmailTLSServername,
- });
-
- if (!messageId) {
- throw new AppError({ message: "Failed to send test email.", status: 500 });
- }
-
- return res.status(200).json({
- success: true,
- msg: "Test email sent successfully",
- data: { messageId },
- });
- } catch (error) {
- next(error);
- }
- };
+ return res.status(200).json({
+ success: true,
+ msg: "Test email sent successfully",
+ data: { messageId },
+ });
+ });
}
export default SettingsController;
diff --git a/server/src/api/controllers/statusPageController.ts b/server/src/api/controllers/statusPageController.ts
index d820e5eed..f75c5e7fb 100644
--- a/server/src/api/controllers/statusPageController.ts
+++ b/server/src/api/controllers/statusPageController.ts
@@ -1,4 +1,5 @@
-import { Request, Response, NextFunction } from "express";
+import { Request, Response, RequestHandler } from "express";
+import { catchAsync } from "@/utils/catchAsync.js";
import {
createStatusPageBodyValidation,
@@ -16,12 +17,12 @@ const SERVICE_NAME = "statusPageController";
export interface IStatusPageController {
readonly serviceName: string;
- createStatusPage(req: Request, res: Response, next: NextFunction): Promise;
- updateStatusPage(req: Request, res: Response, next: NextFunction): Promise;
- getStatusPageByUrl(req: Request, res: Response, next: NextFunction): Promise;
- resolveStatusPageByDomain(req: Request, res: Response, next: NextFunction): Promise;
- getStatusPagesByTeamId(req: Request, res: Response, next: NextFunction): Promise;
- deleteStatusPage(req: Request, res: Response, next: NextFunction): Promise;
+ createStatusPage: RequestHandler;
+ updateStatusPage: RequestHandler;
+ getStatusPageByUrl: RequestHandler;
+ resolveStatusPageByDomain: RequestHandler;
+ getStatusPagesByTeamId: RequestHandler;
+ deleteStatusPage: RequestHandler;
}
class StatusPageController implements IStatusPageController {
@@ -35,132 +36,108 @@ class StatusPageController implements IStatusPageController {
return StatusPageController.SERVICE_NAME;
}
- createStatusPage = async (req: Request, res: Response, next: NextFunction) => {
- try {
- createStatusPageBodyValidation.parse(req.body);
- if (req.file) {
- imageValidation.parse(req.file);
- }
-
- const teamId = requireTeamId(req?.user?.teamId);
- const userId = requireUserId(req?.user?.id);
- const statusPage = await this.statusPageService.createStatusPage(userId, teamId, req.file, req.body);
-
- return res.status(200).json({
- success: true,
- msg: "Status page created successfully",
- data: statusPage,
- });
- } catch (error) {
- next(error);
+ createStatusPage = catchAsync(async (req: Request, res: Response) => {
+ createStatusPageBodyValidation.parse(req.body);
+ if (req.file) {
+ imageValidation.parse(req.file);
}
- };
- updateStatusPage = async (req: Request, res: Response, next: NextFunction) => {
- try {
- createStatusPageBodyValidation.parse(req.body);
- if (req.file) {
- imageValidation.parse(req.file);
- }
+ const teamId = requireTeamId(req?.user?.teamId);
+ const userId = requireUserId(req?.user?.id);
+ const statusPage = await this.statusPageService.createStatusPage(userId, teamId, req.file, req.body);
- const teamId = requireTeamId(req?.user?.teamId);
- const statusPageId = req.params.id as string;
- if (!statusPageId) {
- throw new AppError({ message: "Status page ID is required", status: 400 });
- }
- const statusPage = await this.statusPageService.updateStatusPage(statusPageId, teamId, req.file, req.body);
- if (statusPage === null) {
- throw new AppError({ message: "Status page not found", status: 404 });
- }
- res.status(200).json({
- success: true,
- msg: "Status page updated successfully",
- data: statusPage,
- });
- } catch (error) {
- next(error);
+ return res.status(200).json({
+ success: true,
+ msg: "Status page created successfully",
+ data: statusPage,
+ });
+ });
+
+ updateStatusPage = catchAsync(async (req: Request, res: Response) => {
+ createStatusPageBodyValidation.parse(req.body);
+ if (req.file) {
+ imageValidation.parse(req.file);
}
- };
- getStatusPageByUrl = async (req: Request, res: Response, next: NextFunction) => {
- try {
- getStatusPageParamValidation.parse(req.params);
- getStatusPageQueryValidation.parse(req.query);
-
- if (!req.params.url) {
- throw new AppError({ message: "Status page URL is required", status: 400 });
- }
-
- const statusPage = await this.statusPageService.getStatusPageByUrl(req.params.url as string);
- const data = await this.statusPageService.getPublicStatusPagePayload(statusPage, req.user?.teamId);
-
- return res.status(200).json({
- success: true,
- msg: "Status page retrieved successfully",
- data,
- });
- } catch (error) {
- next(error);
+ const teamId = requireTeamId(req?.user?.teamId);
+ const statusPageId = req.params.id as string;
+ if (!statusPageId) {
+ throw new AppError({ message: "Status page ID is required", status: 400 });
}
- };
-
- resolveStatusPageByDomain = async (req: Request, res: Response, next: NextFunction) => {
- try {
- resolveStatusPageQueryValidation.parse(req.query);
-
- const domain = resolveStatusPageDomainFromRequest(req.hostname, req.query.domain as string | undefined);
- if (!domain) {
- throw new AppError({ message: "Domain is required", status: 400 });
- }
-
- const statusPage = await this.statusPageService.getStatusPageByCustomDomain(domain);
- if (!statusPage.isPublished) {
- throw new AppError({ message: "Status page not found", status: 404 });
- }
-
- const data = await this.statusPageService.getPublicStatusPagePayload(statusPage, req.user?.teamId);
-
- return res.status(200).json({
- success: true,
- msg: "Status page retrieved successfully",
- data,
- });
- } catch (error) {
- next(error);
+ const statusPage = await this.statusPageService.updateStatusPage(statusPageId, teamId, req.file, req.body);
+ if (statusPage === null) {
+ throw new AppError({ message: "Status page not found", status: 404 });
}
- };
+ res.status(200).json({
+ success: true,
+ msg: "Status page updated successfully",
+ data: statusPage,
+ });
+ });
- getStatusPagesByTeamId = async (req: Request, res: Response, next: NextFunction) => {
- try {
- const teamId = requireTeamId(req.user?.teamId);
- const statusPages = await this.statusPageService.getStatusPagesByTeamId(teamId);
+ getStatusPageByUrl = catchAsync(async (req: Request, res: Response) => {
+ getStatusPageParamValidation.parse(req.params);
+ getStatusPageQueryValidation.parse(req.query);
- return res.status(200).json({
- success: true,
- msg: "Status pages retrieved successfully",
- data: statusPages,
- });
- } catch (error) {
- next(error);
+ if (!req.params.url) {
+ throw new AppError({ message: "Status page URL is required", status: 400 });
}
- };
- deleteStatusPage = async (req: Request, res: Response, next: NextFunction) => {
- try {
- const statusPageId = req.params.id as string;
- if (!statusPageId) {
- throw new AppError({ message: "Status page ID is required", status: 400 });
- }
- const teamId = requireTeamId(req.user?.teamId);
- await this.statusPageService.deleteStatusPage(statusPageId, teamId);
- return res.status(200).json({
- success: true,
- msg: "Status page deleted successfully",
- });
- } catch (error) {
- next(error);
+ const statusPage = await this.statusPageService.getStatusPageByUrl(req.params.url as string);
+ const data = await this.statusPageService.getPublicStatusPagePayload(statusPage, req.user?.teamId);
+
+ return res.status(200).json({
+ success: true,
+ msg: "Status page retrieved successfully",
+ data,
+ });
+ });
+
+ resolveStatusPageByDomain = catchAsync(async (req: Request, res: Response) => {
+ resolveStatusPageQueryValidation.parse(req.query);
+
+ const domain = resolveStatusPageDomainFromRequest(req.hostname, req.query.domain as string | undefined);
+ if (!domain) {
+ throw new AppError({ message: "Domain is required", status: 400 });
}
- };
+
+ const statusPage = await this.statusPageService.getStatusPageByCustomDomain(domain);
+ if (!statusPage.isPublished) {
+ throw new AppError({ message: "Status page not found", status: 404 });
+ }
+
+ const data = await this.statusPageService.getPublicStatusPagePayload(statusPage, req.user?.teamId);
+
+ return res.status(200).json({
+ success: true,
+ msg: "Status page retrieved successfully",
+ data,
+ });
+ });
+
+ getStatusPagesByTeamId = catchAsync(async (req: Request, res: Response) => {
+ const teamId = requireTeamId(req.user?.teamId);
+ const statusPages = await this.statusPageService.getStatusPagesByTeamId(teamId);
+
+ return res.status(200).json({
+ success: true,
+ msg: "Status pages retrieved successfully",
+ data: statusPages,
+ });
+ });
+
+ deleteStatusPage = catchAsync(async (req: Request, res: Response) => {
+ const statusPageId = req.params.id as string;
+ if (!statusPageId) {
+ throw new AppError({ message: "Status page ID is required", status: 400 });
+ }
+ const teamId = requireTeamId(req.user?.teamId);
+ await this.statusPageService.deleteStatusPage(statusPageId, teamId);
+ return res.status(200).json({
+ success: true,
+ msg: "Status page deleted successfully",
+ });
+ });
}
export default StatusPageController;
diff --git a/server/src/api/controllers/tagController.ts b/server/src/api/controllers/tagController.ts
index 95dd81296..abe042efa 100644
--- a/server/src/api/controllers/tagController.ts
+++ b/server/src/api/controllers/tagController.ts
@@ -1,4 +1,5 @@
-import { Request, Response, NextFunction } from "express";
+import { Request, Response, RequestHandler } from "express";
+import { catchAsync } from "@/utils/catchAsync.js";
import { ITagsService } from "@/domain/tags/tag.service.js";
import { requireTeamId } from "./controllerUtils.js";
import {
@@ -10,89 +11,69 @@ import {
} from "@/api/validation/index.js";
export interface ITagsController {
- createTag: (req: Request, res: Response, next: NextFunction) => Promise;
- getTagById: (req: Request, res: Response, next: NextFunction) => Promise;
- getTagsByTeamId: (req: Request, res: Response, next: NextFunction) => Promise;
- editTag: (req: Request, res: Response, next: NextFunction) => Promise;
- deleteTag: (req: Request, res: Response, next: NextFunction) => Promise;
+ createTag: RequestHandler;
+ getTagById: RequestHandler;
+ getTagsByTeamId: RequestHandler;
+ editTag: RequestHandler;
+ deleteTag: RequestHandler;
}
class TagsController implements ITagsController {
constructor(private tagsService: ITagsService) {}
- createTag = async (req: Request, res: Response, next: NextFunction) => {
- try {
- const validatedBody = createTagBodyValidation.parse(req.body);
- const teamId = requireTeamId(req.user?.teamId);
+ createTag = catchAsync(async (req: Request, res: Response) => {
+ const validatedBody = createTagBodyValidation.parse(req.body);
+ const teamId = requireTeamId(req.user?.teamId);
- const tag = await this.tagsService.createTag(validatedBody, teamId);
- return res.status(200).json({
- success: true,
- msg: "Tag created successfully",
- data: tag,
- });
- } catch (error) {
- next(error);
- }
- };
- getTagById = async (req: Request, res: Response, next: NextFunction) => {
- try {
- const teamId = requireTeamId(req.user?.teamId);
- const { id: tagId } = getTagByIdParamValidation.parse(req.params);
- const tag = await this.tagsService.getTag(tagId, teamId);
- return res.status(200).json({
- success: true,
- msg: "Tag retrieved successfully",
- data: tag,
- });
- } catch (error) {
- next(error);
- }
- };
+ const tag = await this.tagsService.createTag(validatedBody, teamId);
+ return res.status(200).json({
+ success: true,
+ msg: "Tag created successfully",
+ data: tag,
+ });
+ });
+ getTagById = catchAsync(async (req: Request, res: Response) => {
+ const teamId = requireTeamId(req.user?.teamId);
+ const { id: tagId } = getTagByIdParamValidation.parse(req.params);
+ const tag = await this.tagsService.getTag(tagId, teamId);
+ return res.status(200).json({
+ success: true,
+ msg: "Tag retrieved successfully",
+ data: tag,
+ });
+ });
- getTagsByTeamId = async (req: Request, res: Response, next: NextFunction) => {
- try {
- const teamId = requireTeamId(req.user?.teamId);
- const tags = await this.tagsService.getTagsByTeamId(teamId);
- return res.status(200).json({
- success: true,
- msg: "Tags retrieved successfully",
- data: tags,
- });
- } catch (error) {
- next(error);
- }
- };
+ getTagsByTeamId = catchAsync(async (req: Request, res: Response) => {
+ const teamId = requireTeamId(req.user?.teamId);
+ const tags = await this.tagsService.getTagsByTeamId(teamId);
+ return res.status(200).json({
+ success: true,
+ msg: "Tags retrieved successfully",
+ data: tags,
+ });
+ });
- editTag = async (req: Request, res: Response, next: NextFunction) => {
- try {
- const teamId = requireTeamId(req.user?.teamId);
- const { id: tagId } = editTagParamValidation.parse(req.params);
- const validatedBody = editTagBodyValidation.parse(req.body);
- const updatedTag = await this.tagsService.updateTag(tagId, teamId, validatedBody);
- return res.status(200).json({
- success: true,
- msg: "Tag updated successfully",
- data: updatedTag,
- });
- } catch (error) {
- next(error);
- }
- };
+ editTag = catchAsync(async (req: Request, res: Response) => {
+ const teamId = requireTeamId(req.user?.teamId);
+ const { id: tagId } = editTagParamValidation.parse(req.params);
+ const validatedBody = editTagBodyValidation.parse(req.body);
+ const updatedTag = await this.tagsService.updateTag(tagId, teamId, validatedBody);
+ return res.status(200).json({
+ success: true,
+ msg: "Tag updated successfully",
+ data: updatedTag,
+ });
+ });
- deleteTag = async (req: Request, res: Response, next: NextFunction) => {
- try {
- const teamId = requireTeamId(req.user?.teamId);
- const { id: tagId } = deleteTagParamValidation.parse(req.params);
- await this.tagsService.deleteTag(tagId, teamId);
- return res.status(200).json({
- success: true,
- msg: "Tag deleted successfully",
- });
- } catch (error) {
- next(error);
- }
- };
+ deleteTag = catchAsync(async (req: Request, res: Response) => {
+ const teamId = requireTeamId(req.user?.teamId);
+ const { id: tagId } = deleteTagParamValidation.parse(req.params);
+ await this.tagsService.deleteTag(tagId, teamId);
+ return res.status(200).json({
+ success: true,
+ msg: "Tag deleted successfully",
+ });
+ });
}
export default TagsController;
diff --git a/server/src/utils/catchAsync.ts b/server/src/utils/catchAsync.ts
new file mode 100644
index 000000000..3f207608b
--- /dev/null
+++ b/server/src/utils/catchAsync.ts
@@ -0,0 +1,14 @@
+import type { NextFunction, Request, RequestHandler, Response } from "express";
+type AsyncRequestHandler = (req: Request, res: Response) => Promise;
+
+/**
+ * Wraps an async route handler so that any error it throws or any promise it
+ * rejects is passed to Express's error middleware (`handleErrors`). Every async controller method must be wrapped.
+ */
+export const catchAsync = (fn: AsyncRequestHandler): RequestHandler => {
+ const wrappedHandler = (req: Request, res: Response, next: NextFunction) => {
+ fn(req, res).catch(next);
+ };
+
+ return wrappedHandler;
+};
From 825a81d3264378286b0899639079f02c7ff4a94b Mon Sep 17 00:00:00 2001
From: Alex Holliday
Date: Wed, 15 Jul 2026 10:20:16 -0700
Subject: [PATCH 33/84] extract shared mongo mapper utils
---
.../app-settings.repository.mongo.ts | 23 +++-------
.../domain/checks/check.repository.mongo.ts | 15 +------
server/src/domain/dlq/dlq.repository.mongo.ts | 28 +++---------
.../geo-checks/geo-check.repository.mongo.ts | 15 +------
.../incidents/incident.repository.mongo.ts | 43 +++++++------------
.../domain/invites/invite.repository.mongo.ts | 26 +++--------
.../maintenance-window.repository.mongo.ts | 30 ++++---------
.../monitor-stats.repository.mongo.ts | 12 +-----
.../monitors/monitor.repository.mongo.ts | 17 +-------
.../notification.repository.mongo.ts | 9 +---
.../recovery-token.repository.mongo.ts | 23 +++-------
.../status-page-repository.mongo.ts | 29 ++++---------
.../src/domain/tags/tag.repository.mongo.ts | 24 +++--------
.../src/domain/teams/team.repository.model.ts | 25 +++--------
.../src/domain/users/user.repository.mongo.ts | 23 +++-------
server/src/utils/mongoMappers.ts | 15 +++++++
16 files changed, 89 insertions(+), 268 deletions(-)
create mode 100644 server/src/utils/mongoMappers.ts
diff --git a/server/src/domain/app-settings/app-settings.repository.mongo.ts b/server/src/domain/app-settings/app-settings.repository.mongo.ts
index ab242e4b7..191e6cfcd 100644
--- a/server/src/domain/app-settings/app-settings.repository.mongo.ts
+++ b/server/src/domain/app-settings/app-settings.repository.mongo.ts
@@ -1,26 +1,13 @@
-import mongoose, { type UpdateQuery } from "mongoose";
+import { type UpdateQuery } from "mongoose";
import { ISettingsRepository } from "@/domain/app-settings/app-settings-repository.interface.js";
import type { Settings, SettingsUpdate } from "@/domain/app-settings/app-settings.type.js";
import { AppSettingsModel, type AppSettingsDocument } from "@/domain/app-settings/app-settings.model.js";
+import { toStringId, toDateString } from "@/utils/mongoMappers.js";
class MongoSettingsRepository implements ISettingsRepository {
- private toStringId = (value?: mongoose.Types.ObjectId | string | null): string => {
- if (!value) {
- return "";
- }
- return value instanceof mongoose.Types.ObjectId ? value.toString() : String(value);
- };
-
- private toDateString = (value?: Date | string | null): string => {
- if (!value) {
- return new Date(0).toISOString();
- }
- return value instanceof Date ? value.toISOString() : new Date(value).toISOString();
- };
-
protected toEntity = (doc: AppSettingsDocument): Settings => {
return {
- id: this.toStringId(doc._id),
+ id: toStringId(doc._id),
checkTTL: doc.checkTTL,
language: doc.language,
jwtSecret: doc.jwtSecret ?? undefined,
@@ -41,8 +28,8 @@ class MongoSettingsRepository implements ISettingsRepository {
singleton: doc.singleton,
version: doc.version ?? 1,
globalThresholds: doc.globalThresholds ?? undefined,
- createdAt: this.toDateString(doc.createdAt),
- updatedAt: this.toDateString(doc.updatedAt),
+ createdAt: toDateString(doc.createdAt),
+ updatedAt: toDateString(doc.updatedAt),
};
};
diff --git a/server/src/domain/checks/check.repository.mongo.ts b/server/src/domain/checks/check.repository.mongo.ts
index 3e8d1c52a..1433c950a 100644
--- a/server/src/domain/checks/check.repository.mongo.ts
+++ b/server/src/domain/checks/check.repository.mongo.ts
@@ -17,6 +17,7 @@ import { CheckModel, type CheckDocument } from "@/domain/checks/check.model.js";
import mongoose from "mongoose";
import { getDateForRange } from "@/utils/dataUtils.js";
import { ILogger } from "@/utils/logger.js";
+import { toStringId, toDateString } from "@/utils/mongoMappers.js";
import { getHardwareUpChecks, getHardwareStats, getHardwareTotalChecks } from "@/domain/checks/check.hardware.aggregations.js";
@@ -33,20 +34,6 @@ class MongoChecksRepository implements IChecksRepository {
}
private toEntity = (doc: CheckDocument): Check => {
- const toStringId = (value: mongoose.Types.ObjectId | string | undefined | null): string => {
- if (!value) {
- return "";
- }
- return value instanceof mongoose.Types.ObjectId ? value.toString() : String(value);
- };
-
- const toDateString = (value?: Date | string | null): string => {
- if (!value) {
- return new Date(0).toISOString();
- }
- return value instanceof Date ? value.toISOString() : new Date(value).toISOString();
- };
-
const mapTimings = (timings?: GotTimings): GotTimings => {
const phases = timings?.phases ?? {
wait: 0,
diff --git a/server/src/domain/dlq/dlq.repository.mongo.ts b/server/src/domain/dlq/dlq.repository.mongo.ts
index 4cf7bad85..bf02029b4 100644
--- a/server/src/domain/dlq/dlq.repository.mongo.ts
+++ b/server/src/domain/dlq/dlq.repository.mongo.ts
@@ -4,36 +4,22 @@ import type { DLQItem } from "@/domain/dlq/dlq.type.js";
import type { IDLQRepository, DLQQueryFilters, DLQStatusCount } from "@/domain/dlq/dlq.repository.interface.js";
import mongoose from "mongoose";
import { AppError } from "@/utils/AppError.js";
-
+import { toStringId, toDateString } from "@/utils/mongoMappers.js";
class MongoDLQRepository implements IDLQRepository {
- private toStringId = (value?: mongoose.Types.ObjectId | string | null): string => {
- if (!value) {
- return "";
- }
- return value instanceof mongoose.Types.ObjectId ? value.toString() : String(value);
- };
-
- private toDateString = (value?: Date | string | null): string => {
- if (!value) {
- return new Date(0).toISOString();
- }
- return value instanceof Date ? value.toISOString() : new Date(value).toISOString();
- };
-
protected toEntity = (doc: DLQItemDocument): DLQItem => {
return {
- id: this.toStringId(doc._id),
+ id: toStringId(doc._id),
type: doc.type,
status: doc.status,
payload: doc.payload,
- monitorId: this.toStringId(doc.monitorId),
- teamId: this.toStringId(doc.teamId),
+ monitorId: toStringId(doc.monitorId),
+ teamId: toStringId(doc.teamId),
retryCount: doc.retryCount,
maxRetries: doc.maxRetries,
lastError: doc.lastError,
- nextRetryAt: this.toDateString(doc.nextRetryAt),
- createdAt: this.toDateString(doc.createdAt),
- updatedAt: this.toDateString(doc.updatedAt),
+ nextRetryAt: toDateString(doc.nextRetryAt),
+ createdAt: toDateString(doc.createdAt),
+ updatedAt: toDateString(doc.updatedAt),
};
};
diff --git a/server/src/domain/geo-checks/geo-check.repository.mongo.ts b/server/src/domain/geo-checks/geo-check.repository.mongo.ts
index 426fb533d..e73056e95 100644
--- a/server/src/domain/geo-checks/geo-check.repository.mongo.ts
+++ b/server/src/domain/geo-checks/geo-check.repository.mongo.ts
@@ -5,6 +5,7 @@ import { GeoCheckMetadataDocument, GeoCheckModel, type GeoCheckDocument } from "
import mongoose, { PipelineStage } from "mongoose";
import { getDateForRange } from "@/utils/dataUtils.js";
import { ILogger } from "@/utils/logger.js";
+import { toStringId, toDateString } from "@/utils/mongoMappers.js";
const SERVICE_NAME = "GeoChecksRepository";
@@ -17,20 +18,6 @@ class MongoGeoChecksRepository implements IGeoChecksRepository {
}
private toEntity = (doc: GeoCheckDocument): GeoCheck => {
- const toStringId = (value: mongoose.Types.ObjectId | string | undefined | null): string => {
- if (!value) {
- return "";
- }
- return value instanceof mongoose.Types.ObjectId ? value.toString() : String(value);
- };
-
- const toDateString = (value?: Date | string | null): string => {
- if (!value) {
- return new Date(0).toISOString();
- }
- return value instanceof Date ? value.toISOString() : new Date(value).toISOString();
- };
-
const mapMetadata = (metadata: GeoCheckMetadataDocument): GeoCheckMetadata => ({
monitorId: toStringId(metadata.monitorId),
teamId: toStringId(metadata.teamId),
diff --git a/server/src/domain/incidents/incident.repository.mongo.ts b/server/src/domain/incidents/incident.repository.mongo.ts
index 4cf410e88..35cd5d56e 100644
--- a/server/src/domain/incidents/incident.repository.mongo.ts
+++ b/server/src/domain/incidents/incident.repository.mongo.ts
@@ -4,22 +4,9 @@ import type { Incident, IncidentSummary } from "@/domain/incidents/incident.type
import type { IIncidentsRepository } from "@/domain/incidents/incident.repository.interface.js";
import mongoose from "mongoose";
import { AppError } from "@/utils/AppError.js";
+import { toStringId, toDateString } from "@/utils/mongoMappers.js";
class MongoIncidentsRepository implements IIncidentsRepository {
- private toStringId = (value?: mongoose.Types.ObjectId | string | null): string => {
- if (!value) {
- return "";
- }
- return value instanceof mongoose.Types.ObjectId ? value.toString() : String(value);
- };
-
- private toDateString = (value?: Date | string | null): string => {
- if (!value) {
- return new Date(0).toISOString();
- }
- return value instanceof Date ? value.toISOString() : new Date(value).toISOString();
- };
-
private buildMatchStage({
teamId,
startDate,
@@ -48,20 +35,20 @@ class MongoIncidentsRepository implements IIncidentsRepository {
protected toEntity = (doc: IncidentDocument): Incident => {
return {
- id: this.toStringId(doc._id),
- monitorId: this.toStringId(doc.monitorId),
- teamId: this.toStringId(doc.teamId),
- startTime: this.toDateString(doc.startTime),
- endTime: doc.endTime ? this.toDateString(doc.endTime) : null,
+ id: toStringId(doc._id),
+ monitorId: toStringId(doc.monitorId),
+ teamId: toStringId(doc.teamId),
+ startTime: toDateString(doc.startTime),
+ endTime: doc.endTime ? toDateString(doc.endTime) : null,
status: doc.status,
message: doc.message ?? null,
statusCode: doc.statusCode ?? null,
resolutionType: doc.resolutionType ?? null,
- resolvedBy: doc.resolvedBy ? this.toStringId(doc.resolvedBy) : null,
+ resolvedBy: doc.resolvedBy ? toStringId(doc.resolvedBy) : null,
resolvedByEmail: doc.resolvedByEmail ?? null,
comment: doc.comment ?? null,
- createdAt: this.toDateString(doc.createdAt),
- updatedAt: this.toDateString(doc.updatedAt),
+ createdAt: toDateString(doc.createdAt),
+ updatedAt: toDateString(doc.updatedAt),
};
};
@@ -254,22 +241,22 @@ class MongoIncidentsRepository implements IIncidentsRepository {
avgResolutionTimeHours,
topMonitor: monitorResult[0]
? {
- monitorId: this.toStringId(monitorResult[0].monitorId),
+ monitorId: toStringId(monitorResult[0].monitorId),
monitorName: monitorResult[0].monitorName ?? null,
incidentCount: monitorResult[0].count,
}
: null,
latestIncidents: latestIncidents.map((incident) => ({
- id: this.toStringId(incident._id),
- monitorId: this.toStringId(incident.monitorId),
+ id: toStringId(incident._id),
+ monitorId: toStringId(incident.monitorId),
monitorName: incident.monitorName ?? null,
status: incident.status,
- startTime: this.toDateString(incident.startTime),
- endTime: incident.endTime ? this.toDateString(incident.endTime) : null,
+ startTime: toDateString(incident.startTime),
+ endTime: incident.endTime ? toDateString(incident.endTime) : null,
resolutionType: incident.resolutionType ?? null,
message: incident.message ?? null,
statusCode: incident.statusCode ?? null,
- createdAt: this.toDateString(incident.createdAt),
+ createdAt: toDateString(incident.createdAt),
})),
};
};
diff --git a/server/src/domain/invites/invite.repository.mongo.ts b/server/src/domain/invites/invite.repository.mongo.ts
index 8956a08b3..0c51147a1 100644
--- a/server/src/domain/invites/invite.repository.mongo.ts
+++ b/server/src/domain/invites/invite.repository.mongo.ts
@@ -2,34 +2,20 @@ import { IInvitesRepository } from "@/domain/invites/invite.repository.interface
import type { Invite } from "@/domain/invites/invite.type.js";
import { type InviteDocument, InviteModel } from "@/domain/invites/invite.model.js";
import { AppError } from "@/utils/AppError.js";
-import mongoose from "mongoose";
import crypto from "crypto";
+import { toStringId, toDateString } from "@/utils/mongoMappers.js";
class MongoInvitesRepository implements IInvitesRepository {
- private toStringId = (value?: mongoose.Types.ObjectId | string | null): string => {
- if (!value) {
- return "";
- }
- return value instanceof mongoose.Types.ObjectId ? value.toString() : String(value);
- };
-
- private toDateString = (value?: Date | string | null): string => {
- if (!value) {
- return new Date(0).toISOString();
- }
- return value instanceof Date ? value.toISOString() : new Date(value).toISOString();
- };
-
protected toEntity = (doc: InviteDocument): Invite => {
return {
- id: this.toStringId(doc._id),
+ id: toStringId(doc._id),
email: doc.email,
- teamId: this.toStringId(doc.teamId),
+ teamId: toStringId(doc.teamId),
role: doc.role ?? [],
token: doc.token,
- expiry: this.toDateString(doc.expiry),
- createdAt: this.toDateString(doc.createdAt),
- updatedAt: this.toDateString(doc.updatedAt),
+ expiry: toDateString(doc.expiry),
+ createdAt: toDateString(doc.createdAt),
+ updatedAt: toDateString(doc.updatedAt),
};
};
diff --git a/server/src/domain/maintenance-windows/maintenance-window.repository.mongo.ts b/server/src/domain/maintenance-windows/maintenance-window.repository.mongo.ts
index ce32a22d1..cc16dad54 100644
--- a/server/src/domain/maintenance-windows/maintenance-window.repository.mongo.ts
+++ b/server/src/domain/maintenance-windows/maintenance-window.repository.mongo.ts
@@ -3,22 +3,8 @@ import { type MaintenanceWindowDocument, MaintenanceWindowModel } from "@/domain
import { IMaintenanceWindowsRepository } from "./maintenance-window.repository.interface.js";
import mongoose, { SortOrder } from "mongoose";
import { AppError } from "@/utils/AppError.js";
-
+import { toStringId, toDateString } from "@/utils/mongoMappers.js";
class MongoMaintenanceWindowsRepository implements IMaintenanceWindowsRepository {
- private toStringId = (value?: mongoose.Types.ObjectId | string | null): string => {
- if (!value) {
- return "";
- }
- return value instanceof mongoose.Types.ObjectId ? value.toString() : String(value);
- };
-
- private toDateString = (value?: Date | string | null): string => {
- if (!value) {
- return new Date(0).toISOString();
- }
- return value instanceof Date ? value.toISOString() : new Date(value).toISOString();
- };
-
private mapDocuments = (documents: MaintenanceWindowDocument[]): MaintenanceWindow[] => {
if (!documents?.length) {
return [];
@@ -28,18 +14,18 @@ class MongoMaintenanceWindowsRepository implements IMaintenanceWindowsRepository
private toEntity = (doc: MaintenanceWindowDocument): MaintenanceWindow => {
return {
- id: this.toStringId(doc._id),
- monitorIds: doc.monitorIds.map(this.toStringId),
- teamId: this.toStringId(doc.teamId),
+ id: toStringId(doc._id),
+ monitorIds: doc.monitorIds.map(toStringId),
+ teamId: toStringId(doc.teamId),
active: doc.active,
name: doc.name,
duration: doc.duration,
durationUnit: doc.durationUnit,
repeat: doc.repeat,
- start: this.toDateString(doc.start),
- end: this.toDateString(doc.end),
- createdAt: this.toDateString(doc.createdAt),
- updatedAt: this.toDateString(doc.updatedAt),
+ start: toDateString(doc.start),
+ end: toDateString(doc.end),
+ createdAt: toDateString(doc.createdAt),
+ updatedAt: toDateString(doc.updatedAt),
};
};
diff --git a/server/src/domain/monitor-stats/monitor-stats.repository.mongo.ts b/server/src/domain/monitor-stats/monitor-stats.repository.mongo.ts
index 7027197da..1cd942995 100644
--- a/server/src/domain/monitor-stats/monitor-stats.repository.mongo.ts
+++ b/server/src/domain/monitor-stats/monitor-stats.repository.mongo.ts
@@ -3,19 +3,9 @@ import type { CheckResultInput, MonitorStats } from "@/domain/monitor-stats/moni
import { IMonitorStatsRepository } from "@/domain/monitor-stats/monitor-stats.repository.interface.js";
import mongoose from "mongoose";
import { AppError } from "@/utils/AppError.js";
+import { toStringId, toDateString } from "@/utils/mongoMappers.js";
class MongoMonitorStatsRepository implements IMonitorStatsRepository {
private toEntity = (doc: MonitorStatsDocument): MonitorStats => {
- const toStringId = (value: unknown): string => {
- if (value instanceof mongoose.Types.ObjectId) {
- return value.toString();
- }
- return value?.toString() ?? "";
- };
-
- const toDateString = (value: Date | string): string => {
- return value instanceof Date ? value.toISOString() : value;
- };
-
return {
id: toStringId(doc._id),
monitorId: toStringId(doc.monitorId),
diff --git a/server/src/domain/monitors/monitor.repository.mongo.ts b/server/src/domain/monitors/monitor.repository.mongo.ts
index e7ccf56e9..28e4513f2 100644
--- a/server/src/domain/monitors/monitor.repository.mongo.ts
+++ b/server/src/domain/monitors/monitor.repository.mongo.ts
@@ -6,6 +6,7 @@ import mongoose, { type FilterQuery, type PipelineStage } from "mongoose";
import { MongoBulkWriteError } from "mongodb";
import { AppError } from "@/utils/AppError.js";
import { IMonitorsRepository, TeamQueryConfig, SummaryConfig } from "@/domain/monitors/monitor.repository.interface.js";
+import { toStringId, toDateString } from "@/utils/mongoMappers.js";
class MongoMonitorsRepository implements IMonitorsRepository {
create = async (monitor: Monitor, teamId: string, userId: string) => {
const monitorModel = new MonitorModel({ ...monitor, teamId, userId });
@@ -440,18 +441,6 @@ class MongoMonitorsRepository implements IMonitorsRepository {
};
private toEntity = (doc: MonitorDocument): Monitor => {
- const toStringId = (value: unknown): string => {
- if (value instanceof mongoose.Types.ObjectId) {
- return value.toString();
- }
- return value?.toString() ?? "";
- };
-
- const toDateString = (value: Date | string): string => {
- if (!value) return "";
- return value instanceof Date ? value.toISOString() : value;
- };
-
const notificationIds = (doc.notifications ?? []).map((notification) => toStringId(notification));
const tagIds = (doc.tags ?? []).map((tag) => toStringId(tag));
return {
@@ -506,10 +495,6 @@ class MongoMonitorsRepository implements IMonitorsRepository {
};
private toCheckSnapshot = (doc: CheckSnapshotDocument): CheckSnapshot => {
- const toDateString = (value: Date | string): string => {
- return value instanceof Date ? value.toISOString() : value;
- };
-
return {
id: doc.id,
status: doc.status,
diff --git a/server/src/domain/notifications/notification.repository.mongo.ts b/server/src/domain/notifications/notification.repository.mongo.ts
index cc2729cf7..98c7501ed 100644
--- a/server/src/domain/notifications/notification.repository.mongo.ts
+++ b/server/src/domain/notifications/notification.repository.mongo.ts
@@ -3,6 +3,7 @@ import { NotificationModel, type NotificationDocument } from "@/domain/notificat
import { INotificationsRepository } from "@/domain/notifications/notification.repository.interface.js";
import type { Notification } from "@/domain/notifications/notification.type.js";
import { AppError } from "@/utils/AppError.js";
+import { toStringId, toDateString } from "@/utils/mongoMappers.js";
class MongoNotificationsRepository implements INotificationsRepository {
private mapDocuments = (documents: NotificationDocument[]): Notification[] => {
@@ -13,14 +14,6 @@ class MongoNotificationsRepository implements INotificationsRepository {
};
private toEntity = (doc: NotificationDocument): Notification => {
- const toStringId = (value: mongoose.Types.ObjectId | string): string => {
- return value instanceof mongoose.Types.ObjectId ? value.toString() : value;
- };
-
- const toDateString = (value: Date | string): string => {
- return value instanceof Date ? value.toISOString() : value;
- };
-
return {
id: toStringId(doc._id),
userId: toStringId(doc.userId),
diff --git a/server/src/domain/recovery-tokens/recovery-token.repository.mongo.ts b/server/src/domain/recovery-tokens/recovery-token.repository.mongo.ts
index 1e6140a88..5ee7db994 100644
--- a/server/src/domain/recovery-tokens/recovery-token.repository.mongo.ts
+++ b/server/src/domain/recovery-tokens/recovery-token.repository.mongo.ts
@@ -2,35 +2,22 @@ import type { RecoveryToken } from "@/domain/recovery-tokens/recovery-token.type
import type { IRecoveryTokensRepository } from "./recovery-token.repository.interface.js";
import type { RecoveryTokenDocument } from "@/domain/recovery-tokens/recovery-token.model.js";
import { RecoveryTokenModel } from "@/domain/recovery-tokens/recovery-token.model.js";
-import mongoose from "mongoose";
import crypto from "crypto";
import { AppError } from "@/utils/AppError.js";
+import { toStringId, toDateString } from "@/utils/mongoMappers.js";
const SERVICE_NAME = "MongoRecoveryTokensRepository";
class MongoRecoveryTokensRepository implements IRecoveryTokensRepository {
static SERVICE_NAME = SERVICE_NAME;
- private toStringId = (value?: mongoose.Types.ObjectId | string | null): string => {
- if (!value) {
- return "";
- }
- return value instanceof mongoose.Types.ObjectId ? value.toString() : String(value);
- };
-
- private toDateString = (value?: Date | string | null): string => {
- if (!value) {
- return new Date(0).toISOString();
- }
- return value instanceof Date ? value.toISOString() : new Date(value).toISOString();
- };
protected toEntity = (doc: RecoveryTokenDocument): RecoveryToken => {
return {
- id: this.toStringId(doc._id),
+ id: toStringId(doc._id),
email: doc.email,
token: doc.token,
- expiry: this.toDateString(doc.expiry),
- createdAt: this.toDateString(doc.createdAt),
- updatedAt: this.toDateString(doc.updatedAt),
+ expiry: toDateString(doc.expiry),
+ createdAt: toDateString(doc.createdAt),
+ updatedAt: toDateString(doc.updatedAt),
};
};
diff --git a/server/src/domain/status-pages/status-page-repository.mongo.ts b/server/src/domain/status-pages/status-page-repository.mongo.ts
index 2dedca4ef..467ce10f5 100644
--- a/server/src/domain/status-pages/status-page-repository.mongo.ts
+++ b/server/src/domain/status-pages/status-page-repository.mongo.ts
@@ -4,28 +4,15 @@ import type { StatusPage, StatusPageLogo, StatusPageLogoDocument } from "@/domai
import mongoose from "mongoose";
import { AppError } from "@/utils/AppError.js";
import { normalizeStatusPageDomain } from "@/utils/statusPageDomain.js";
-
+import { toStringId, toDateString } from "@/utils/mongoMappers.js";
// Type for update data that can include document-level fields (Buffer for logo)
type StatusPageUpdateData = Partial> & {
logo?: StatusPageLogoDocument | null;
};
+
class MongoStatusPagesRepository implements IStatusPagesRepository {
- private toStringId = (value?: mongoose.Types.ObjectId | string | null): string => {
- if (!value) {
- return "";
- }
- return value instanceof mongoose.Types.ObjectId ? value.toString() : String(value);
- };
-
- private toDateString = (value?: Date | string | null): string => {
- if (!value) {
- return new Date(0).toISOString();
- }
- return value instanceof Date ? value.toISOString() : new Date(value).toISOString();
- };
-
private mapIdArray = (values?: Array): string[] => {
- return values?.map((value) => this.toStringId(value)) ?? [];
+ return values?.map((value) => toStringId(value)) ?? [];
};
private mapLogo = (logo?: StatusPageLogoDocument | null): StatusPageLogo | undefined => {
@@ -42,9 +29,9 @@ class MongoStatusPagesRepository implements IStatusPagesRepository {
private toEntity = (doc: StatusPageDocument): StatusPage => {
return {
- id: this.toStringId(doc._id),
- userId: this.toStringId(doc.userId),
- teamId: this.toStringId(doc.teamId),
+ id: toStringId(doc._id),
+ userId: toStringId(doc.userId),
+ teamId: toStringId(doc.teamId),
type: doc.type,
companyName: doc.companyName,
url: doc.url,
@@ -63,8 +50,8 @@ class MongoStatusPagesRepository implements IStatusPagesRepository {
customCSS: doc.customCSS,
theme: doc.theme,
themeMode: doc.themeMode,
- createdAt: this.toDateString(doc.createdAt),
- updatedAt: this.toDateString(doc.updatedAt),
+ createdAt: toDateString(doc.createdAt),
+ updatedAt: toDateString(doc.updatedAt),
};
};
diff --git a/server/src/domain/tags/tag.repository.mongo.ts b/server/src/domain/tags/tag.repository.mongo.ts
index 5d7d8d0a2..51354b364 100644
--- a/server/src/domain/tags/tag.repository.mongo.ts
+++ b/server/src/domain/tags/tag.repository.mongo.ts
@@ -1,36 +1,22 @@
import { ITagsRepository } from "@/domain/tags/tag.repository.interface.js";
import { Tag } from "@/domain/tags/tag.type.js";
import { TagDocument, TagModel } from "@/domain/tags/tag.model.js";
-import mongoose from "mongoose";
import { AppError } from "@/utils/AppError.js";
+import { toStringId, toDateString } from "@/utils/mongoMappers.js";
const SERVICE_NAME = "TagsRepository";
class MongoTagsRepository implements ITagsRepository {
static SERVICE_NAME = SERVICE_NAME;
- private toStringId = (value?: mongoose.Types.ObjectId | string | null): string => {
- if (!value) {
- return "";
- }
- return value instanceof mongoose.Types.ObjectId ? value.toString() : String(value);
- };
-
- private toDateString = (value?: Date | string | null): string => {
- if (!value) {
- return new Date(0).toISOString();
- }
- return value instanceof Date ? value.toISOString() : new Date(value).toISOString();
- };
-
protected toEntity = (doc: TagDocument): Tag => {
return {
- id: this.toStringId(doc._id),
- teamId: this.toStringId(doc.teamId),
+ id: toStringId(doc._id),
+ teamId: toStringId(doc.teamId),
name: doc.name,
color: doc.color,
- createdAt: this.toDateString(doc.createdAt),
- updatedAt: this.toDateString(doc.updatedAt),
+ createdAt: toDateString(doc.createdAt),
+ updatedAt: toDateString(doc.updatedAt),
};
};
diff --git a/server/src/domain/teams/team.repository.model.ts b/server/src/domain/teams/team.repository.model.ts
index d050d6f59..bcbc43938 100644
--- a/server/src/domain/teams/team.repository.model.ts
+++ b/server/src/domain/teams/team.repository.model.ts
@@ -1,29 +1,14 @@
import { Team } from "@/domain/teams/team.type.js";
import { TeamDocument, TeamModel } from "@/domain/teams/team.model.js";
import { ITeamsRepository } from "@/domain/teams/team.repository.interface.js";
-import mongoose from "mongoose";
-
+import { toStringId, toDateString } from "@/utils/mongoMappers.js";
class MongoTeamsRepository implements ITeamsRepository {
- private toStringId = (value?: mongoose.Types.ObjectId | string | null): string => {
- if (!value) {
- return "";
- }
- return value instanceof mongoose.Types.ObjectId ? value.toString() : String(value);
- };
-
- private toDateString = (value?: Date | string | null): string => {
- if (!value) {
- return new Date(0).toISOString();
- }
- return value instanceof Date ? value.toISOString() : new Date(value).toISOString();
- };
-
private toEntity = (doc: TeamDocument): Team => {
return {
- id: this.toStringId(doc._id),
+ id: toStringId(doc._id),
email: doc.email,
- createdAt: this.toDateString(doc.createdAt),
- updatedAt: this.toDateString(doc.updatedAt),
+ createdAt: toDateString(doc.createdAt),
+ updatedAt: toDateString(doc.updatedAt),
};
};
@@ -34,7 +19,7 @@ class MongoTeamsRepository implements ITeamsRepository {
findAllTeamIds = async (): Promise => {
const teams = await TeamModel.find({}, { _id: 1 }).lean();
- return teams.map((team) => this.toStringId(team._id));
+ return teams.map((team) => toStringId(team._id));
};
}
diff --git a/server/src/domain/users/user.repository.mongo.ts b/server/src/domain/users/user.repository.mongo.ts
index 1dc729b81..22f171396 100644
--- a/server/src/domain/users/user.repository.mongo.ts
+++ b/server/src/domain/users/user.repository.mongo.ts
@@ -1,27 +1,14 @@
-import mongoose from "mongoose";
import { IUsersRepository } from "@/domain/users/user.repository.interface.js";
import { UserModel, type UserDocument } from "@/domain/users/user.model.js";
import type { User, UserProfileImage } from "@/domain/users/user.type.js";
import { GenerateAvatarImage } from "@/utils/imageProcessing.js";
import { ParseBoolean } from "@/utils/utils.js";
import { AppError } from "@/utils/AppError.js";
+import { toStringId, toDateString } from "@/utils/mongoMappers.js";
const SERVICE_NAME = "MongoUsersRepository";
class MongoUsersRepository implements IUsersRepository {
static SERVICE_NAME = SERVICE_NAME;
- private toStringId = (value?: mongoose.Types.ObjectId | string | null): string => {
- if (!value) {
- return "";
- }
- return value instanceof mongoose.Types.ObjectId ? value.toString() : String(value);
- };
-
- private toDateString = (value?: Date | string | null): string => {
- if (!value) {
- return new Date(0).toISOString();
- }
- return value instanceof Date ? value.toISOString() : new Date(value).toISOString();
- };
private mapProfileImage = (image?: (UserProfileImage & { data?: Buffer }) | null) => {
if (!image) {
@@ -35,7 +22,7 @@ class MongoUsersRepository implements IUsersRepository {
protected toEntity = (doc: UserDocument): User => {
return {
- id: this.toStringId(doc._id),
+ id: toStringId(doc._id),
firstName: doc.firstName,
lastName: doc.lastName,
email: doc.email,
@@ -45,10 +32,10 @@ class MongoUsersRepository implements IUsersRepository {
isActive: doc.isActive ?? false,
isVerified: doc.isVerified ?? false,
role: doc.role ?? [],
- teamId: this.toStringId(doc.teamId),
+ teamId: toStringId(doc.teamId),
checkTTL: doc.checkTTL ?? undefined,
- createdAt: this.toDateString(doc.createdAt),
- updatedAt: this.toDateString(doc.updatedAt),
+ createdAt: toDateString(doc.createdAt),
+ updatedAt: toDateString(doc.updatedAt),
};
};
diff --git a/server/src/utils/mongoMappers.ts b/server/src/utils/mongoMappers.ts
new file mode 100644
index 000000000..c817c1e6c
--- /dev/null
+++ b/server/src/utils/mongoMappers.ts
@@ -0,0 +1,15 @@
+import mongoose from "mongoose";
+
+export const toStringId = (value?: mongoose.Types.ObjectId | string | null): string => {
+ if (!value) {
+ return "";
+ }
+ return value instanceof mongoose.Types.ObjectId ? value.toString() : String(value);
+};
+
+export const toDateString = (value?: Date | string | null): string => {
+ if (!value) {
+ return new Date(0).toISOString();
+ }
+ return value instanceof Date ? value.toISOString() : new Date(value).toISOString();
+};
From 0e52cb6231ab57b28e6ca5432c26ded7cc5fb5f2 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jakub=20Pomyka=C5=82a?=
Date: Sat, 18 Jul 2026 20:22:00 +0200
Subject: [PATCH 34/84] Add polish
---
client/src/locales/pl.json | 1663 ++++++++++++++++++++++++++++++++++++
1 file changed, 1663 insertions(+)
create mode 100644 client/src/locales/pl.json
diff --git a/client/src/locales/pl.json b/client/src/locales/pl.json
new file mode 100644
index 000000000..40b5c7c4f
--- /dev/null
+++ b/client/src/locales/pl.json
@@ -0,0 +1,1663 @@
+{
+ "common": {
+ "alerts": {
+ "pageSpeedApiKey": {
+ "content": "Ostrzeżenie: Nie dodałeś jeszcze klucza Google PageSpeed API. Przejdź do Ustawienia , aby go dodać. Bez niego monitor PageSpeed nie będzie działał."
+ }
+ },
+ "appName": "Checkmate",
+ "auth": {
+ "roles": {
+ "admin": "Administrator",
+ "demo": "Demo",
+ "superadmin": "Super administrator",
+ "user": "Użytkownik"
+ }
+ },
+ "breadcrumbs": {
+ "details": "Szczegóły",
+ "home": "Strona główna"
+ },
+ "buttons": {
+ "addDemo": "Dodaj demo monitory",
+ "addMember": "Dodaj użytkownika",
+ "back": "Wstecz",
+ "cancel": "Anuluj",
+ "clear": "Wyczyść",
+ "clearFilters": "Wyczyść filtry",
+ "close": "Zamknij",
+ "configure": "Konfiguruj",
+ "confirm": "Potwierdź",
+ "create": "Utwórz",
+ "delete": "Usuń",
+ "exportToJSON": "Eksportuj do JSON",
+ "flushQueue": "Opróżnij kolejkę",
+ "generateToken": "Wygeneruj token",
+ "importFromJSON": "Importuj z JSON",
+ "incidents": "Incydenty",
+ "inviteMember": "Zaproś użytkownika",
+ "next": "Dalej",
+ "notFound": "Przejdź do głównego panelu",
+ "pause": "Wstrzymaj",
+ "removeMonitors": "Usuń monitory",
+ "removeUser": "Usuń użytkownika",
+ "reset": "Zresetuj",
+ "resetPassword": "Zresetuj hasło",
+ "resume": "Wznów",
+ "save": "Zapisz",
+ "sendInvite": "Wyślij zaproszenie",
+ "sendTestEmail": "Wyślij testowy e-mail",
+ "test": "Test",
+ "testNotifications": "Przetestuj powiadomienia",
+ "toggleTheme": "Przełącza jasny i ciemny"
+ },
+ "charts": {
+ "histogram": {
+ "avg": "Średnia: {{value}} ms",
+ "max": "Maks: {{value}} ms"
+ },
+ "labels": {
+ "averageResponseTime": "Średni czas odpowiedzi",
+ "downtime": "Czas przestoju",
+ "high": "Wysoki",
+ "low": "Niski",
+ "uptime": "Czas pracy"
+ }
+ },
+ "dialogs": {
+ "delete": {
+ "description": "Tej operacji nie można cofnąć.",
+ "title": "Czy na pewno chcesz to usunąć?"
+ }
+ },
+ "form": {
+ "email": {
+ "option": {
+ "email": {
+ "label": "Email",
+ "placeholder": "you@example.com"
+ }
+ }
+ },
+ "name": {
+ "option": {
+ "firstName": {
+ "label": "Imię",
+ "placeholder": "Wpisz swoje imię"
+ },
+ "lastName": {
+ "label": "Nazwisko",
+ "placeholder": "Wprowadź swoje nazwisko"
+ }
+ }
+ },
+ "role": {
+ "option": {
+ "role": {
+ "label": "Role"
+ }
+ }
+ }
+ },
+ "labels": {
+ "active": "Aktywny",
+ "idle": "Bezczynny",
+ "na": "Brak danych",
+ "paused": "Wstrzymano",
+ "resolved": "Rozwiązano",
+ "responseTime": "Czas reakcji",
+ "running": "Uruchomiony"
+ },
+ "statusCode": {
+ "down": "Niedostępny",
+ "networkError": {
+ "dns": "Nie można rozpoznać nazwy hosta (błąd wyszukiwania DNS).",
+ "refused": "Serwer odrzucił połączenie (port zamknięty lub usługa nie nasłuchuje).",
+ "reset": "Połączenie zostało zresetowane, zanim otrzymano odpowiedź.",
+ "timeout": "Serwer nie odpowiedział na czas (żądanie przekroczyło limit czasu).",
+ "tls": "Nie powiodło się uzgadnianie TLS lub SSL (problem z certyfikatem lub szyfrowaniem).",
+ "unknown": "Nie otrzymano odpowiedzi HTTP z docelowego serwera."
+ }
+ },
+ "table": {
+ "empty": "Brak danych do wyświetlenia",
+ "headers": {
+ "actions": "Akcje",
+ "active": "Aktywny",
+ "dateTime": "Data i godzina",
+ "interval": "Przedział",
+ "message": "Wiadomość",
+ "monitor": "Monitor",
+ "monitorId": "ID monitora",
+ "name": "Nazwa",
+ "responseTime": "Czas odpowiedzi",
+ "status": "Status",
+ "tags": "Tagi",
+ "type": "Typ",
+ "url": "Url"
+ }
+ }
+ },
+ "components": {
+ "headerStatusPageControls": {
+ "publicLink": "Publiczny link"
+ },
+ "headerTimeRange": {
+ "description": {
+ "day": "Wyświetlanie statystyk z ostatnich 24 godzin.",
+ "month": "Wyświetlanie statystyk z ostatnich 30 dni.",
+ "recent": "Wyświetlanie statystyk z ostatnich 2 godzin.",
+ "week": "Wyświetlanie statystyk z ostatnich 7 dni."
+ },
+ "labels": {
+ "day": "Dzień",
+ "month": "Miesiąc",
+ "recent": "Najnowsze",
+ "week": "Tydzień"
+ }
+ },
+ "imageUpload": {
+ "clickToUpload": "Kliknij, aby przesłać",
+ "dragAndDrop": "lub przeciągnij i upuść",
+ "errors": {
+ "invalidFileFormat": "Nieobsługiwany format pliku!",
+ "invalidFileSize": "Rozmiar pliku jest za duży!"
+ },
+ "maxSize": "Maksymalny rozmiar",
+ "orDragAndDrop": "lub przeciągnij i upuść",
+ "supportedFormats": "Obsługiwane formaty"
+ },
+ "offlineBanner": {
+ "reconnected": "Połączenie przywrócone",
+ "retry": "Ponów próbę",
+ "retrying": "Ponawianie próby...",
+ "serverUnreachable": "Nie można połączyć się z serwerem"
+ },
+ "pageHeader": {
+ "links": {
+ "changelog": "Dziennik zmian",
+ "discussions": "Dyskusje",
+ "docs": "Dokumentacja",
+ "support": "Pomoc"
+ }
+ },
+ "sidebar": {
+ "accountMenu": {
+ "password": "Hasło",
+ "profile": "Profil",
+ "team": "Zespół"
+ },
+ "authFooter": {
+ "logOut": "Wyloguj się",
+ "navControls": "Sterowanie",
+ "roles": {
+ "admin": "Administrator",
+ "demoUser": "Użytkownik demo",
+ "superAdmin": "Super administrator",
+ "user": "Użytkownik"
+ }
+ },
+ "bottomMenu": {
+ "changelog": "Dziennik zmian",
+ "discussions": "Dyskusje",
+ "docs": "Dokumentacja",
+ "support": "Wsparcie"
+ },
+ "menu": {
+ "checks": "Kontrole",
+ "incidents": "Incydenty",
+ "infrastructure": "Infrastruktura",
+ "logs": "Dzienniki",
+ "maintenance": "Konserwacja",
+ "notifications": "Powiadomienia",
+ "pagespeed": "Pagespeed",
+ "settings": "Ustawienia",
+ "statusPages": "Strony statusu",
+ "tags": "Tagi",
+ "uptime": "Czas działania"
+ },
+ "starPrompt": {
+ "description": "Zobacz najnowsze wydania i pomóż rozwijać społeczność na GitHub",
+ "title": "Zagwiazdkuj Checkmate"
+ }
+ },
+ "starPrompt": {
+ "description": "Zobacz najnowsze wydania i wspieraj rozwój społeczności na GitHub",
+ "title": "Zagwiazdkuj Checkmate"
+ }
+ },
+ "pages": {
+ "account": {
+ "form": {
+ "currentPassword": {
+ "description": "Wprowadź swoje obecne hasło, aby potwierdzić swoją tożsamość",
+ "option": {
+ "label": "Obecne hasło",
+ "placeholder": "Wprowadź obecne hasło"
+ },
+ "title": "Obecne hasło"
+ },
+ "deleteAccount": {
+ "description": "Ta akcja jest nieodwracalna i nie można jej cofnąć",
+ "title": "Usuń konto"
+ },
+ "name": {
+ "description": "Zaktualizuj swoje dane osobowe",
+ "title": "Imię"
+ },
+ "newPassword": {
+ "description": "Wybierz silne hasło składające się z co najmniej 8 znaków",
+ "option": {
+ "confirm": {
+ "label": "Potwierdź hasło",
+ "placeholder": "Potwierdź nowe hasło"
+ },
+ "newPassword": {
+ "label": "Nowe hasło",
+ "placeholder": "Wprowadź nowe hasło"
+ }
+ },
+ "title": "Nowe hasło"
+ },
+ "photo": {
+ "description": "Prześlij zdjęcie profilowe",
+ "title": "Zdjęcie profilowe"
+ }
+ },
+ "header": {
+ "description": "Zarządzaj swoim profilem, hasłem, zespołem i preferencjami przestrzeni roboczej.",
+ "title": "Konto"
+ },
+ "tabs": {
+ "password": "Hasło",
+ "profile": "Profil",
+ "team": "Zespół"
+ },
+ "team": {
+ "addMember": {
+ "description": "Utwórz nowe konto użytkownika. Po utworzeniu bezpiecznie udostępnij dane logowania.",
+ "title": "Zarejestruj nowego członka zespołu"
+ },
+ "fallback": {
+ "description": "Zaproś członków zespołu przez e-mail lub dodaj ich bezpośrednio. Po dołączeniu pojawią się tutaj wraz ze swoją rolą.",
+ "title": "Brak członków zespołu"
+ },
+ "filter": {
+ "admin": "Administrator",
+ "all": "Wszystkie role",
+ "member": "Członek",
+ "placeholder": "Filtruj według roli"
+ },
+ "invite": {
+ "description": "Wyślij zaproszenie do dołączenia do swojego zespołu",
+ "email": {
+ "label": "Adres e-mail",
+ "placeholder": "Wprowadź adres e-mail"
+ },
+ "linkLabel": "Link do zaproszenia",
+ "role": {
+ "label": "Rola",
+ "placeholder": "Wybierz rolę"
+ },
+ "title": "Zaproś członka zespołu"
+ },
+ "table": {
+ "headers": {
+ "created": "Utworzono dnia",
+ "email": "E-mail",
+ "role": "Rola"
+ }
+ }
+ }
+ },
+ "auth": {
+ "brandPanel": {
+ "description": "Monitorowanie działania, reakcji i incydentów w czasie rzeczywistym - do samodzielnego hostowania i na własność.",
+ "eyebrow": "Monitoring infrastruktury open source",
+ "tagline": "Obserwuj każdy ruch swoich serwerów."
+ },
+ "common": {
+ "form": {
+ "option": {
+ "confirmPassword": {
+ "label": "Potwierdź hasło"
+ },
+ "email": {
+ "label": "E-mail",
+ "placeholder": "me@example.com"
+ },
+ "password": {
+ "label": "Hasło",
+ "placeholder": "********"
+ }
+ }
+ },
+ "passwordRules": {
+ "length": {
+ "beginning": "Musi mieć co najmniej",
+ "highlighted": "8 znaków"
+ },
+ "lowercase": {
+ "beginning": "Musi zawierać co najmniej",
+ "highlighted": "jedna mała litera"
+ },
+ "match": {
+ "beginning": "Hasła",
+ "highlighted": "musi pasować"
+ },
+ "number": {
+ "beginning": "Musi zawierać co najmniej",
+ "highlighted": "jedna cyfra"
+ },
+ "special": {
+ "beginning": "Musi zawierać co najmniej",
+ "highlighted": "jeden znak specjalny (!?@#$%^&*()-_=+[]{}|;:'\",./\\~`)"
+ },
+ "uppercase": {
+ "beginning": "Musi zawierać co najmniej",
+ "highlighted": "jedna wielka litera"
+ }
+ }
+ },
+ "forgotPassword": {
+ "links": {
+ "login": {
+ "linkText": "zaloguj się",
+ "text": "Wróć do"
+ }
+ },
+ "submit": "Wyślij prośbę o odzyskanie",
+ "subtitle": "Nie martw się, wyślemy Ci instrukcje resetowania.",
+ "title": "Zapomniałeś hasła?"
+ },
+ "login": {
+ "links": {
+ "forgotPassword": {
+ "linkText": "Resetuj hasło",
+ "text": "Zapomniałeś hasła?"
+ },
+ "register": {
+ "linkText": "Zarejestruj się tutaj",
+ "text": "Nie masz konta?"
+ }
+ },
+ "submit": "Zaloguj się",
+ "subtitle": "Zaloguj się, aby kontynuować",
+ "title": "Witamy ponownie w Checkmate!"
+ },
+ "register": {
+ "setupNotice": "Ta strona jest dostępna tylko podczas początkowej konfiguracji. Po zarejestrowaniu superadministratora można zapraszać dodatkowych użytkowników z poziomu ustawień zespołu.",
+ "submit": "Zarejestruj się",
+ "subtitle": "Zarejestruj się, aby rozpocząć",
+ "title": "Witamy ponownie w Checkmate!"
+ },
+ "setNewPassword": {
+ "subtitle": "Twoje nowe hasło musi różnić się od wcześniej używanych haseł.",
+ "title": "Zresetuj hasło"
+ }
+ },
+ "checks": {
+ "fallback": {
+ "description": "Po utworzeniu monitora wszystkie sondy statusu i czasy odpowiedzi będą wyświetlane tutaj jako sprawdzenia.",
+ "title": "Brak jeszcze kontroli"
+ },
+ "header": {
+ "description": "Każde sprawdzenie wykonywane przez Checkmate na Twoich monitorach pojawia się tutaj wraz z kodem statusu i czasem odpowiedzi.",
+ "title": "Kontrole"
+ },
+ "selects": {
+ "monitor": {
+ "all": "Wszystkie monitory"
+ },
+ "status": {
+ "all": "Wszystkie",
+ "down": "Niedostępny",
+ "up": "Działa"
+ }
+ },
+ "table": {
+ "empty": "Brak awarii w tym przedziale czasowym",
+ "headers": {
+ "location": "Lokalizacja",
+ "statusCode": "Kod statusu"
+ }
+ }
+ },
+ "common": {
+ "monitors": {
+ "actions": {
+ "bulkSelected_one": "Wybrano {{count}} monitor",
+ "bulkSelected_other": "Wybrano {{count}} monitorów",
+ "configure": "Konfiguruj",
+ "delete": "Usuń",
+ "details": "Szczegóły",
+ "generateToken": "Wygeneruj token",
+ "incidents": "Incydenty",
+ "inviteMember": "Zaproś członka",
+ "openSite": "Otwórz stronę",
+ "pause": "Wstrzymaj",
+ "resume": "Wznów",
+ "selectAll": "Zaznacz wszystkie monitory",
+ "selectMonitor": "Wybierz {{name}}"
+ },
+ "bulkPause": {
+ "alreadyPaused_one": "Wybrany monitor jest już wstrzymany",
+ "alreadyPaused_other": "Wszystkie wybrane monitory są już wstrzymane",
+ "alreadyRunning_one": "Wybrany monitor jest już uruchomiony",
+ "alreadyRunning_other": "Wszystkie wybrane monitory są już uruchomione",
+ "partialFailure": "Zaktualizowano {{successCount}} monitorów, ale {{failedCount}} nie udało się zsynchronizować z kolejką zadań",
+ "paused_one": "{{count}} monitor wstrzymany",
+ "paused_other": "Wstrzymano {{count}} monitorów",
+ "resumed_one": "{{count}} monitor wznowiony",
+ "resumed_other": "Wznowiono {{count}} monitorów"
+ },
+ "monitorTypes": {
+ "optionDns": "DNS",
+ "optionDocker": "Docker",
+ "optionGame": "Gra",
+ "optionGrpc": "gRPC",
+ "optionHardware": "Infrastruktura",
+ "optionHttp": "HTTP(S)",
+ "optionPagespeed": "PageSpeed",
+ "optionPing": "Ping",
+ "optionPort": "Port",
+ "optionWebSocket": "WebSocket"
+ },
+ "statBoxes": {
+ "activeFor": "Aktywne przez",
+ "certificateExpiry": "Wygaśnięcie certyfikatu",
+ "lastCheck": "Ostatnia kontrola",
+ "lastResponseTime": "Ostatni czas odpowiedzi",
+ "serviceIsDown": "Usługa nie działa"
+ },
+ "status": {
+ "breached": "przekroczono próg",
+ "down": "niedostępny",
+ "initializing": "inicjowanie",
+ "maintenance": "konserwacja",
+ "paused": "wstrzymano",
+ "total": "łącznie",
+ "up": "działa"
+ }
+ }
+ },
+ "createMonitor": {
+ "form": {
+ "advanced": {
+ "description": "Opcjonalne ustawienia dla zaawansowanych przypadków użycia",
+ "option": {
+ "advancedMatching": {
+ "label": "Użyj zaawansowanego dopasowania"
+ },
+ "customUpCodes": {
+ "description": "Dodaj dowolne kody inne niż 2xx, które również powinny być uznane za pomyślne.",
+ "label": "Zaakceptowane kody statusu",
+ "removeAriaLabel": "Usuń kod statusu"
+ },
+ "expectedValue": {
+ "label": "Oczekiwana wartość"
+ },
+ "jsonPath": {
+ "description": "To wyrażenie zostanie ocenione względem danych JSON odpowiedzi, a wynik będzie użyty do porównania z oczekiwaną wartością. Zobacz jmespath.org po dokumentację języka zapytań.",
+ "label": "Wyrażenie JSONPath"
+ },
+ "matchMethod": {
+ "equal": "Równe",
+ "include": "Uwzględnij",
+ "label": "Metoda dopasowania",
+ "regex": "Wyrażenie regularne"
+ },
+ "method": {
+ "description": {
+ "GET": "GET pobiera pełną odpowiedź, łącznie z treścią. Zaawansowane dopasowywanie jest w pełni obsługiwane",
+ "HEAD": "HEAD pobiera tylko nagłówki odpowiedzi, aby zmniejszyć obciążenie docelowego serwera. Obsługuje dopasowywanie kodu statusu, ale nie zaawansowane dopasowywanie, ponieważ nie ma treści odpowiedzi."
+ },
+ "label": "Metoda żądania"
+ }
+ },
+ "title": "Ustawienia zaawansowane"
+ },
+ "frequency": {
+ "description": "Jak często chcesz sprawdzać status tego monitora?",
+ "option": {
+ "frequency": {
+ "label": "Sprawdź częstotliwość",
+ "value": {
+ "fifteenMinutes": "15 minut",
+ "fifteenSeconds": "15 sekund",
+ "fiveMinutes": "5 minut",
+ "fourMinutes": "4 minuty",
+ "oneMinute": "1 minuta",
+ "tenMinutes": "10 minut",
+ "thirtyMinutes": "30 minut",
+ "thirtySeconds": "30 sekund",
+ "threeMinutes": "3 minuty",
+ "twoMinutes": "2 minuty"
+ }
+ }
+ },
+ "title": "Częstotliwość sprawdzania"
+ },
+ "general": {
+ "description": {
+ "dns": "Monitoruj, czy określony serwer DNS odpowiada.",
+ "docker": "Wprowadź nazwę lub identyfikator kontenera Docker. Możesz użyć nazwy kontenera (np. my-app) lub identyfikatora kontenera (pełny 64-znakowy identyfikator lub skrócony identyfikator).",
+ "game": "Wprowadź adres IP lub nazwę hosta oraz numer portu do pingowania (np. 192.168.1.100 lub example.com) i wybierz typ gry. Niektóre gry wymagają włączenia zapytań serwera w pliku konfiguracyjnym — sprawdź dokumentację swojego serwera gry. Zobacz listę obsługiwanych gier , aby uzyskać szczegóły dotyczące protokołu.",
+ "grpc": "Wprowadź nazwę hosta i port serwera gRPC, opcjonalnie określ nazwę usługi do sprawdzania stanu oraz dodaj nazwę wyświetlaną.",
+ "hardware": "Monitoruj użycie procesora, pamięci, dysku oraz temperaturę w swojej infrastrukturze. Adres URL punktu końcowego Capture musi zawierać pełną ścieżkę /api/v1/metrics (np. http://192.168.1.10:5001/api/v1/metrics). Dla klucza autoryzacyjnego wklej tylko token — nie dodawaj 'Bearer'.",
+ "http": "Wprowadź adres URL lub IP do monitorowania (np. https://example.com/ lub 192.168.1.100) oraz dodaj czytelną nazwę wyświetlaną na pulpicie.",
+ "pagespeed": "Monitoruj wydajność ładowania strony, Core Web Vitals oraz wyniki optymalizacji swojej witryny.",
+ "ping": "Wprowadź adres IP lub nazwę hosta do pingowania (np. 192.168.1.100 lub example.com) oraz dodaj czytelną nazwę wyświetlaną na pulpicie.",
+ "port": "Wprowadź adres URL lub IP serwera, numer portu oraz czytelną nazwę wyświetlaną na pulpicie.",
+ "websocket": "Wprowadź adres URL WebSocket do monitorowania (np. wss://example.com/socket) i dodaj nazwę."
+ },
+ "option": {
+ "captureUrl": {
+ "label": "Punkt końcowy przechwytywania",
+ "placeholder": "http://192.168.1.10:5001/api/v1/metrics"
+ },
+ "container": {
+ "label": "Nazwa/ID kontenera",
+ "placeholder": "moja-aplikacja lub abcd1234"
+ },
+ "dnsRecordType": {
+ "label": "Typ rekordu DNS",
+ "value": {
+ "A": "A",
+ "AAAA": "AAAA",
+ "CNAME": "CNAME",
+ "MX": "MX",
+ "NS": "NS",
+ "TXT": "TXT"
+ }
+ },
+ "dnsServer": {
+ "label": "Serwer DNS",
+ "placeholder": "8.8.8.8"
+ },
+ "game": {
+ "label": "Wybierz grę",
+ "placeholder": "Wybierz grę"
+ },
+ "grpcServiceName": {
+ "label": "Nazwa usługi",
+ "placeholder": "np. my.service.v1 (pozostaw puste, aby monitorować ogólny stan zdrowia)"
+ },
+ "host": {
+ "label": "Host",
+ "placeholder": "192.168.1.100 lub example.com"
+ },
+ "name": {
+ "label": "Wyświetlana nazwa",
+ "placeholder": "np. Moja strona internetowa"
+ },
+ "port": {
+ "label": "Port do monitorowania",
+ "placeholder": "80"
+ },
+ "secret": {
+ "label": "Tajny klucz autoryzacji",
+ "placeholder": "Wprowadź swój klucz sekretu"
+ },
+ "strategy": {
+ "desktop": "Komputer stacjonarny",
+ "label": "Strategia",
+ "mobile": "Mobilny"
+ },
+ "url": {
+ "label": "URL",
+ "placeholder": "https://www.google.com"
+ },
+ "wsUrl": {
+ "label": "Adres URL WebSocket",
+ "placeholder": "wss://example.com/socket"
+ }
+ },
+ "title": "Ustawienia ogólne"
+ },
+ "geoChecks": {
+ "description": "Przeprowadzaj sprawdzenia z różnych lokalizacji geograficznych, aby monitorować globalną dostępność i wydajność.",
+ "option": {
+ "enabled": {
+ "label": "Włącz kontrole geograficznie rozproszone"
+ },
+ "interval": {
+ "label": "Interwał sprawdzania",
+ "value": {
+ "fifteenMinutes": "15 minut",
+ "fiveMinutes": "5 minut",
+ "tenMinutes": "10 minut",
+ "thirtyMinutes": "30 minut"
+ }
+ },
+ "locations": {
+ "label": "Lokalizacje",
+ "options": {
+ "AF": "Afryka",
+ "AS": "Azja",
+ "EU": "Europa",
+ "NA": "Ameryka Północna",
+ "OC": "Oceania",
+ "SA": "Ameryka Południowa"
+ },
+ "placeholder": "Wybierz lokalizacje"
+ }
+ },
+ "title": "Kontrole geograficznie rozproszone"
+ },
+ "ignoreTls": {
+ "description": "Skonfiguruj weryfikację certyfikatu TLS/SSL dla połączeń HTTPS.",
+ "option": {
+ "tls": {
+ "label": "Ignoruj błędy TLS/SSL"
+ }
+ },
+ "title": "Ustawienia TLS/SSL"
+ },
+ "incidents": {
+ "description": "Do określenia, kiedy monitor przestaje działać, używane jest okno przesuwne. Status monitora zmieni się tylko wtedy, gdy procent sprawdzeń w oknie przesuwnym osiągnie określoną wartość.",
+ "option": {
+ "checks": {
+ "label": "Liczba sprawdzeń w oknie przesuwnym"
+ },
+ "percentage": {
+ "label": "Jaki procent sprawdzeń w ruchomym oknie zakończy się niepowodzeniem/powodzeniem, zanim zmieni się status monitora?"
+ }
+ },
+ "title": "Incydenty"
+ },
+ "notifications": {
+ "description": "Wybierz kanały powiadomień, których chcesz używać",
+ "title": "Powiadomienia"
+ },
+ "stats": {
+ "buttonText": "Wyczyść wszystkie statystyki",
+ "description": "Wyczyść całą historię monitorowania i statystyki dla Twojego zespołu. Ta operacja jest nieodwracalna.",
+ "dialog": {
+ "confirm": "Tak, wyczyść wszystkie statystyki",
+ "description": "To spowoduje trwałe usunięcie całej historii monitorowania, statystyk i danych sprawdzających dla Twojego zespołu. Tej operacji nie można cofnąć.",
+ "title": "Wyczyścić całą historię monitorowania?"
+ },
+ "title": "Historia monitora"
+ },
+ "tags": {
+ "description": "Wybierz tagi, które chcesz przypisać do tego monitora",
+ "title": "Tagi"
+ },
+ "thresholds": {
+ "description": "Określ progi, przy których mają być wyzwalane alerty dla tego monitora sprzętu.",
+ "option": {
+ "cpuThreshold": {
+ "label": "Próg alertu CPU (%)"
+ },
+ "diskThreshold": {
+ "label": "Próg alertu dysku (%)"
+ },
+ "memoryThreshold": {
+ "label": "Próg alertu pamięci (%)"
+ },
+ "tempThreshold": {
+ "label": "Próg alarmowy temperatury (°C)"
+ }
+ },
+ "title": "Progi alertów"
+ },
+ "type": {
+ "description": "Wybierz typ sprawdzenia do wykonania",
+ "optionDnsDescription": "Monitoruj, czy określony serwer DNS odpowiada.",
+ "optionDockerDescription": "Użyj Dockera, aby monitorować, czy kontener jest uruchomiony.",
+ "optionGameDescription": "Monitoruj, czy konkretny serwer gry jest online.",
+ "optionGrpcDescription": "Monitoruj usługi gRPC za pomocą standardowego protokołu kontroli stanu.",
+ "optionHttpDescription": "Użyj protokołu HTTP(S), aby monitorować swoją stronę internetową lub punkt końcowy API.",
+ "optionPingDescription": "Użyj ICMP Ping, aby monitorować, czy serwer jest online.",
+ "optionPortDescription": "Monitoruj, czy określony port na serwerze jest otwarty.",
+ "optionWebSocketDescription": "Monitoruj punkty końcowe WebSocket pod kątem stanu połączenia i czasu odpowiedzi.",
+ "title": "Typ"
+ },
+ "url": {
+ "description": "Wyświetl adres IP lub URL monitora na publicznej stronie Status. Jeśli ta opcja jest wyłączona, zostanie wyświetlona tylko nazwa monitora, aby chronić poufne informacje.",
+ "option": {
+ "showURL": {
+ "disabled": "Wyłączone",
+ "enabled": "Włączone",
+ "label": "Wyświetl IP/URL na stronie statusu"
+ }
+ },
+ "title": "Monitoruj IP/URL na stronie statusu"
+ }
+ }
+ },
+ "editUser": {
+ "dialog": {
+ "removeUser": {
+ "content": "Czy na pewno chcesz usunąć {{name}} ze swojego zespołu? Tej operacji nie można cofnąć.",
+ "title": "Usuń użytkownika"
+ }
+ },
+ "form": {
+ "roles": {
+ "description": "Przypisz role użytkownikowi. Można wybrać wiele ról.",
+ "title": "Role"
+ }
+ }
+ },
+ "incidents": {
+ "dialog": {
+ "details": {
+ "analysis": "Analiza incydentu",
+ "comment": "Komentarz:",
+ "detailsLabel": "Szczegóły",
+ "downtime": "Czas niedostępności:",
+ "message": "Wiadomość:",
+ "monitor": "Monitor:",
+ "overview": "Przegląd",
+ "resolutionDetails": "Szczegóły rozwiązania",
+ "resolutionType": "Typ:",
+ "resolutionTypes": {
+ "automatic": "Automatyczny",
+ "manual": "Ręczne"
+ },
+ "resolve": "Rozwiąż incydent",
+ "resolvedAt": "Rozwiązano o:",
+ "resolvedBy": "Rozwiązane przez:",
+ "startedAt": "Rozpoczęto o:",
+ "status": "Status:",
+ "statusCode": "Kod statusu:",
+ "timeline": "Oś czasu",
+ "title": "Szczegóły incydentu",
+ "url": "URL:"
+ },
+ "resolveIncident": {
+ "option": {
+ "comment": {
+ "label": "Komentarz (opcjonalnie)",
+ "placeholder": "Dodaj komentarz dotyczący rozwiązania..."
+ }
+ },
+ "title": "Rozwiąż incydent"
+ }
+ },
+ "fallback": {
+ "description": "Gdy monitor przestanie działać lub zostanie przywrócony, incydent pojawi się tutaj wraz z czasem, szczegółami monitora i historią rozwiązań.",
+ "title": "Brak incydentów — wszystkie systemy działają poprawnie"
+ },
+ "filters": {
+ "allMonitors": "Wszystkie monitory",
+ "monitor": "Monitor",
+ "resolutionType": "Typ rozwiązania",
+ "resolutionTypes": {
+ "all": "Wszystkie",
+ "automatic": "Automatyczne",
+ "manual": "Ręczny"
+ }
+ },
+ "header": {
+ "description": "Gdy monitory przestają działać lub zostają przywrócone, incydenty pojawiają się tutaj wraz z informacjami o czasie, szczegółach monitora i historią rozwiązań.",
+ "title": "Incydenty"
+ },
+ "summaryCard": {
+ "activeIncidents": {
+ "active_one": "{{count}} aktywny incydent",
+ "active_other": "{{count}} aktywne incydenty",
+ "active_zero": "Brak aktywnych incydentów",
+ "title": "Aktywne incydenty"
+ },
+ "incidentStats": {
+ "avgResolutionTime": "Średni czas rozwiązania",
+ "mostAffectedMonitor": "Najbardziej dotknięty monitor",
+ "title": "Statystyki incydentów",
+ "totalIncidents": "Łączna liczba incydentów"
+ },
+ "latestIncidents": {
+ "title": "Najnowsze incydenty"
+ }
+ },
+ "table": {
+ "actions": {
+ "details": "Szczegóły",
+ "goToMonitor": "Przejdź do monitora",
+ "resolveManually": "Rozwiąż ręcznie"
+ },
+ "activeIncidents": "Aktywne incydenty",
+ "headers": {
+ "endTime": "Czas zakończenia",
+ "resolutionType": "Typ rozwiązania",
+ "startTime": "Czas rozpoczęcia",
+ "statusCode": "Kod statusu"
+ },
+ "resolvedIncidents": "Rozwiązane incydenty",
+ "status": {
+ "active": "Aktywne",
+ "resolved": "Rozwiązany"
+ }
+ }
+ },
+ "infrastructure": {
+ "charts": {
+ "labels": {
+ "cpu": "Użycie CPU",
+ "disk": "Zużycie dysku",
+ "loopbackOnlyNotice": "Przechwytywanie raportuje tylko interfejsy loopback lub container-bridge (lo, lo0, docker0). To normalne, gdy Capture działa wewnątrz kontenera, który widzi tylko własną przestrzeń nazw sieci — uruchom Capture bezpośrednio na hoście, aby zobaczyć rzeczywistą aktywność sieciową.",
+ "memory": "Zużycie pamięci",
+ "netBytesRecv": "{{name}} - Otrzymane bajty",
+ "netBytesSent": "{{name}} - Wysłane bajty",
+ "netNoTraffic": "Capture raportuje zerowy ruch na wszystkich interfejsach. Jest to normalne, gdy Capture działa w kontenerze z dostępem tylko do interfejsów loopback — aby zobaczyć rzeczywistą aktywność sieciową, uruchom Capture bezpośrednio na hoście.",
+ "temp": "Temperatura"
+ }
+ },
+ "fallback": {
+ "actionButton": "Utwórz monitor",
+ "description": "Kontroluj wydajność serwera, znajdź wąskie gardła i zapewnij niezawodność dzięki monitorowaniu infrastruktury w czasie rzeczywistym.",
+ "title": "Brak monitorów infrastruktury"
+ },
+ "gauges": {
+ "cpu": {
+ "lowerLabel": "Maksymalna częstotliwość",
+ "title": "Wykorzystanie procesora",
+ "upperLabel": "Aktualna częstotliwość"
+ },
+ "disk": {
+ "lowerLabel": "Wolne",
+ "title": "Użycie dysku {{idx}}",
+ "upperLabel": "Używane"
+ },
+ "memory": {
+ "lowerLabel": "Wolne",
+ "title": "Zużycie pamięci",
+ "upperLabel": "Użyte"
+ }
+ },
+ "header": {
+ "description": "Śledź wydajność serwera, identyfikuj wąskie gardła i zapewnij niezawodność dzięki monitorowaniu infrastruktury w czasie rzeczywistym.",
+ "title": "Monitory infrastruktury"
+ },
+ "statBoxes": {
+ "avgCpuTemperature": "Średnia temperatura CPU",
+ "cpuFrequency": "Częstotliwość CPU",
+ "cpuLogical": "CPU (Logiczne)",
+ "cpuPhysical": "CPU (fizyczny)",
+ "disk": "Dysk",
+ "memory": "Pamięć",
+ "os": "OS"
+ },
+ "table": {
+ "headers": {
+ "cpu": "CPU",
+ "disk": "Dysk",
+ "memory": "Pamięć"
+ }
+ },
+ "tabs": {
+ "labels": {
+ "network": "Sieć",
+ "overview": "Przegląd"
+ }
+ }
+ },
+ "logs": {
+ "diagnostics": {
+ "gauges": {
+ "allocatedPercentage": "% przydzielone",
+ "availableMemoryPercentage": "% dostępnej pamięci",
+ "heapAllocation": "Alokacja sterty",
+ "heapUsage": "Zużycie sterty",
+ "heapUtilization": "Wykorzystanie sterty",
+ "instantCpuUsage": "Bieżące użycie CPU",
+ "total": "Razem",
+ "used": "Używane",
+ "usedSPercentage": "% użycia 1s przez CPU"
+ },
+ "mongoDBCollectionStats": {
+ "bucketCount": "Liczba kubełków: {{count}}",
+ "dbUsageTooltip": "{{percentage}}% przestrzeni DB",
+ "documents": "Dokumenty: {{count}}",
+ "indexSize": "Rozmiar indeksu: {{size}}",
+ "storageSize": "Rozmiar magazynu: {{size}}",
+ "totalSize": "Całkowity rozmiar: {{size}}"
+ },
+ "mongoDBStats": {
+ "dbName": "Nazwa bazy danych",
+ "deletesPerSecond": "Usunięcia/s",
+ "host": "Host",
+ "insertsPerSecond": "Wstawienia/s",
+ "port": "Port",
+ "readsPerSecond": "Odczyty/s",
+ "readyState": "Stan gotowości",
+ "storageSize": "Rozmiar pamięci",
+ "updatesPerSecond": "Aktualizacje/s",
+ "writesPerSecond": "Łączna liczba zapisów/s"
+ },
+ "sections": {
+ "memoryCpu": {
+ "description": "Jak proces wykorzystuje przydzieloną mu pamięć i procesor hosta.",
+ "title": "Pamięć i CPU"
+ },
+ "mongoDBCollectionStats": {
+ "description": "Statystyki dla kolekcji MongoDB",
+ "title": "Statystyki kolekcji MongoDB"
+ },
+ "mongoDBStats": {
+ "description": "Migawka metryk MongoDB",
+ "title": "Statystyki MongoDB"
+ },
+ "runtimeStats": {
+ "description": "Migawka procesu Node.js i maszyny hosta. Aktualizuje się co 5 sekund.",
+ "title": "Statystyki środowiska uruchomieniowego"
+ }
+ },
+ "stats": {
+ "eventLoopDelay": "Opóźnienie pętli zdarzeń",
+ "osMemoryLimit": "Limit pamięci OS",
+ "totalHeapSize": "Całkowity rozmiar sterty",
+ "uptime": "Czas działania",
+ "usedHeapSize": "Użyty rozmiar sterty"
+ }
+ },
+ "failedJobs": "Nieudane zadania",
+ "failedJobsExplainer": "Ostatnie niepowodzenia monitora — czas ostatniego błędu, liczba niepowodzeń oraz powód.",
+ "fallback": {
+ "description": "Aktywność serwera, ostrzeżenia i błędy będą się pojawiać tutaj podczas działania Checkmate.",
+ "title": "Brak logów"
+ },
+ "header": {
+ "description": "Sprawdzaj logi serwera, monitoruj kolejkę zadań i diagnozę działania w jednym miejscu.",
+ "title": "Dzienniki i kolejka"
+ },
+ "jobQueue": "Kolejka zadań",
+ "jobQueueExplainer": "Wyróżnione wiersze to zadania aktualnie przetwarzane przez pracownika.",
+ "logLevelSelect": {
+ "label": "Poziom logowania"
+ },
+ "metrics": {
+ "activeJobs": "Aktywne zadania",
+ "failingJobs": "Nieudane zadania",
+ "jobs": "Zadania",
+ "totalFailures": "Łączna liczba błędów",
+ "totalRuns": "Łączna liczba uruchomień"
+ },
+ "noLogs": "Nie znaleziono logów",
+ "queueFallback": {
+ "description": "Sprawdzenia monitorujące, które są zaplanowane lub trwają, pojawią się tutaj. Pusta kolejka oznacza, że wszystko jest aktualne.",
+ "title": "Brak zadań w kolejce"
+ },
+ "table": {
+ "headers": {
+ "failCount": "Liczba",
+ "failReason": "Powód niepowodzenia",
+ "host": "Host",
+ "lastFailedAt": "Ostatnia nieudana próba",
+ "lastFinishedAt": "Ostatnie zakończenie o",
+ "lastSeenAt": "Ostatnio widziano",
+ "level": "Poziom",
+ "lockedBy": "Zablokowane przez",
+ "method": "Metoda",
+ "monitorId": "Identyfikator monitora",
+ "nextScheduledAt": "Następne uruchomienie o",
+ "processId": "Identyfikator procesu",
+ "runCount": "Liczba uruchomień",
+ "service": "Usługa",
+ "state": "Stan",
+ "timestamp": "Znacznik czasu",
+ "workerId": "ID pracownika",
+ "workerMode": "Tryb pracownika"
+ }
+ },
+ "tabs": {
+ "diagnostics": "Diagnostyka",
+ "logs": "Logi",
+ "queue": "Kolejka zadań"
+ },
+ "workers": "Pracownicy",
+ "workersExplainers": "Wyświetla procesy workerów. Identyfikatory workerów są skrócone, najedź, aby zobaczyć pełny identyfikator"
+ },
+ "maintenanceWindow": {
+ "fallback": {
+ "actionButton": "Utwórz okno konserwacji",
+ "description": "Zaplanuj planowaną przerwę, aby powiadomienia były wyciszone, a Twój zespół i użytkownicy wiedzieli, czego się spodziewać.",
+ "title": "Brak zaplanowanych okien konserwacyjnych"
+ },
+ "form": {
+ "general": {
+ "description": "Ustaw nazwę i opcję powtarzania dla okna konserwacji.",
+ "option": {
+ "name": {
+ "label": "Nazwa",
+ "placeholder": "np. Cotygodniowa konserwacja"
+ },
+ "repeat": {
+ "label": "Powtarzaj"
+ }
+ },
+ "title": "Ustawienia ogólne"
+ },
+ "startDate": {
+ "description": "Wybierz datę rozpoczęcia okna konserwacji.",
+ "option": {
+ "startDate": {
+ "label": "Data rozpoczęcia"
+ }
+ },
+ "title": "Data rozpoczęcia"
+ },
+ "startTime": {
+ "description": "Ustaw czas rozpoczęcia i czas trwania okna konserwacyjnego. Wszystkie wartości są w UTC",
+ "monitors": {
+ "description": "Monitory, do których należy zastosować okno konserwacji",
+ "option": {
+ "addMonitors": {
+ "label": "Dodaj monitry"
+ }
+ },
+ "title": "Monitory"
+ },
+ "option": {
+ "duration": {
+ "label": "Czas trwania"
+ },
+ "startTime": {
+ "label": "Czas rozpoczęcia"
+ }
+ },
+ "title": "Czas rozpoczęcia"
+ }
+ },
+ "header": {
+ "description": "Zaplanuj planowaną przerwę, aby powiadomienia były wyciszone, a Twój zespół i użytkownicy wiedzieli, czego się spodziewać.",
+ "title": "Okna konserwacji"
+ },
+ "table": {
+ "headers": {
+ "nextWindow": "Następne okno",
+ "repeat": "Powtórz"
+ }
+ }
+ },
+ "notFound": {
+ "subtitle": "Adres URL nie istnieje lub nie masz do niego dostępu.",
+ "title": "O nie! Upuściłeś swoje sushi!"
+ },
+ "notifications": {
+ "fallback": {
+ "actionButton": "Utwórz kanał",
+ "description": "Połącz Slacka, Discorda, e-mail, webhooki i inne, aby odpowiednie osoby dowiedziały się o incydentach natychmiast po ich wystąpieniu.",
+ "title": "Brak jeszcze kanałów powiadomień"
+ },
+ "form": {
+ "accessToken": {
+ "optionAccessToken": "Token dostępu",
+ "placeholder": "syt_YWxleF9ob2xsaWRheQ_VmtScmV0U2VjcmV0S2V5_abc123"
+ },
+ "address": {
+ "description": "Adres, na który będą wysyłane powiadomienia.",
+ "optionAddress": "Adres",
+ "placeholderEmail": "example@example.com",
+ "placeholderWebhook": "https://your-server.com/webhook",
+ "title": "Adres"
+ },
+ "homeServer": {
+ "optionHomeServer": "Serwer domowy",
+ "placeholder": "example.com"
+ },
+ "matrix": {
+ "description": "Skonfiguruj połączenie z serwerem Matrix dla powiadomień.",
+ "title": "Konfiguracja Matrix"
+ },
+ "notificationName": {
+ "description": "Opisowa nazwa kanału powiadomień",
+ "optionName": "Nazwa kanału",
+ "placeholder": "np. alerty produkcyjne",
+ "title": "Nazwa kanału"
+ },
+ "ntfy": {
+ "description": "Skonfiguruj serwer ntfy i temat do powiadomień push.",
+ "optionServerUrl": "Adres URL serwera",
+ "optionTopic": "Temat",
+ "placeholderServerUrl": "https://ntfy.sh",
+ "placeholderTopic": "checkmate-alerts",
+ "title": "konfiguracja ntfy"
+ },
+ "pagerDuty": {
+ "description": "Twój klucz integracyjny PagerDuty do otrzymywania powiadomień.",
+ "optionIntegrationKey": "Klucz integracyjny",
+ "placeholder": "a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6",
+ "title": "Klucz integracji"
+ },
+ "pushover": {
+ "description": "Skonfiguruj Pushover, aby otrzymywać powiadomienia push na swoich urządzeniach.",
+ "optionAppToken": "Token API aplikacji",
+ "optionUserKey": "Klucz użytkownika",
+ "placeholderAppToken": "azGDORePK8gMaC0QOYAMyEEuzJnyUi",
+ "placeholderUserKey": "uQiRzpo4DXghDmr9QzzfQu27cmVRsG",
+ "title": "Konfiguracja Pushover"
+ },
+ "roomId": {
+ "optionRoomId": "Identyfikator pokoju",
+ "placeholder": "!abcdefg:matrix.org"
+ },
+ "telegram": {
+ "description": "Skonfiguruj swojego bota Telegram, aby wysyłał powiadomienia.",
+ "optionBotToken": "Token bota",
+ "optionChatId": "Identyfikator czatu",
+ "placeholderBotToken": "123456789:ABCdefGhIJKlmNoPQRsTUVwxyZ",
+ "placeholderChatId": "-1001234567890",
+ "title": "Konfiguracja Telegram"
+ },
+ "twilio": {
+ "description": "Skonfiguruj Twilio, aby wysyłać powiadomienia SMS na numer telefonu.",
+ "optionAccountSid": "SID konta",
+ "optionAuthToken": "Token uwierzytelniający",
+ "optionFromNumber": "Numer nadawcy (Twilio)",
+ "optionToNumber": "Do numeru (odbiorca)",
+ "placeholderAccountSid": "ACxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
+ "placeholderAuthToken": "twój_token_uwierzytelniający",
+ "placeholderFromNumber": "+15551234567",
+ "placeholderToNumber": "+15559876543",
+ "title": "Konfiguracja SMS Twilio"
+ },
+ "type": {
+ "description": "Wybierz typ kanału powiadomień do utworzenia.",
+ "optionType": "Typ",
+ "title": "Typ kanału"
+ }
+ },
+ "header": {
+ "description": "Połącz Slacka, Discorda, e-mail, webhooki i inne, aby odpowiednie osoby otrzymywały informacje o incydentach natychmiast po ich wystąpieniu.",
+ "title": "Powiadomienia"
+ },
+ "table": {
+ "headers": {
+ "destination": "Miejsce docelowe"
+ }
+ }
+ },
+ "pageSpeed": {
+ "charts": {
+ "common": {
+ "cls": "Skumulowane przesunięcie układu (CLS)",
+ "fcp": "Pierwsze wyrenderowanie treści (FCP)",
+ "lcp": "Największe wyrenderowanie treści (LCP)",
+ "si": "Indeks szybkości (SI)",
+ "tbt": "Całkowity czas blokowania (TBT)"
+ },
+ "legend": {
+ "title": "Raport PageSpeed",
+ "weight": "Waga"
+ },
+ "pie": {
+ "title": "Raport wydajności"
+ }
+ },
+ "fallback": {
+ "actionButton": "Utwórz monitor",
+ "description": "Śledź wskaźniki Core Web Vitals i wyniki Lighthouse w czasie, aby wykryć regresje zanim zrobią to użytkownicy.",
+ "title": "Brak monitorów PageSpeed"
+ },
+ "header": {
+ "description": "Śledź Core Web Vitals i wyniki Lighthouse w czasie, aby wykryć regresje, zanim zauważą je użytkownicy.",
+ "title": "Monitory PageSpeed"
+ },
+ "strategy": {
+ "desktop": "Komputer stacjonarny",
+ "mobile": "Mobilne"
+ },
+ "table": {
+ "headers": {
+ "pageSpeedScore": "Wynik PageSpeed"
+ }
+ }
+ },
+ "settings": {
+ "form": {
+ "about": {
+ "developedBy": "Opracowane przez Bluewave Labs",
+ "title": "Informacje"
+ },
+ "demoMonitors": {
+ "description": "Dodaj przykładowe monitory do celów demonstracyjnych.",
+ "title": "Monitory demonstracyjne"
+ },
+ "email": {
+ "description": "Skonfiguruj ustawienia e-mail dla swojego systemu. Służą one do wysyłania powiadomień i alertów.",
+ "descriptionTransport": "To tworzy transport SMTP dla NodeMailera",
+ "descriptionTransportLink": "Zobacz specyfikacje tutaj",
+ "option": {
+ "address": {
+ "label": "Adres e-mail – używany do uwierzytelniania",
+ "placeholder": "you@example.com"
+ },
+ "connectionHost": {
+ "label": "Host połączenia e-mail – nazwa hosta używana w powitaniu HELO/EHLO",
+ "placeholder": "localhost"
+ },
+ "displayName": {
+ "label": "Nadawca (opcjonalnie) - Nazwa wyświetlana w polu 'Od'",
+ "placeholder": "Powiadomienia Checkmate"
+ },
+ "host": {
+ "label": "Host e-mail – nazwa hosta lub adres IP do połączenia",
+ "placeholder": "smtp.gmail.com"
+ },
+ "ignoreTLS": {
+ "label": "Wyłącz STARTTLS: Nie używaj TLS, nawet jeśli serwer go obsługuje"
+ },
+ "password": {
+ "label": "Hasło e-mail – Hasło do uwierzytelniania",
+ "labelSet": "Hasło jest ustawione. Kliknij Resetuj, aby je zmienić.",
+ "placeholder": "Wprowadź swoje hasło"
+ },
+ "pool": {
+ "label": "Włącz łączenie połączeń: Wykorzystaj istniejące połączenia, aby poprawić wydajność"
+ },
+ "port": {
+ "label": "Port e-mail — port do połączenia",
+ "placeholder": "587"
+ },
+ "rejectUnauthorized": {
+ "label": "Odrzucaj nieprawidłowe certyfikaty: Odrzucaj połączenia z certyfikatami samopodpisanymi lub niezaufanymi"
+ },
+ "requireTLS": {
+ "label": "Wymuś STARTTLS: Wymagaj aktualizacji do TLS, odmów w przypadku braku obsługi"
+ },
+ "secure": {
+ "label": "Użyj SSL (zalecane): Szyfruj połączenie za pomocą SSL/TLS"
+ },
+ "tlsServername": {
+ "label": "Nazwa serwera TLS - opcjonalna nazwa hosta do weryfikacji TLS, gdy hostem jest adres IP",
+ "placeholder": "example.com"
+ },
+ "user": {
+ "label": "Użytkownik e-mail — Nazwa użytkownika do uwierzytelniania, zastępuje adres e-mail, jeśli została podana",
+ "placeholder": "Pozostaw puste, jeśli nie jest wymagane"
+ }
+ },
+ "title": "Ustawienia e-mail"
+ },
+ "exportMonitors": {
+ "title": "Eksportuj monitory"
+ },
+ "importExportMonitors": {
+ "description": "Importuj lub eksportuj dane monitorów jako plik JSON w celu wykonania kopii zapasowej lub przeniesienia.",
+ "title": "Importuj / Eksportuj monitory"
+ },
+ "pagespeed": {
+ "description": "Wprowadź swój klucz API Google PageSpeed, aby włączyć monitorowanie Google PageSpeed. Kliknij Resetuj, aby zaktualizować klucz.",
+ "option": {
+ "apiKey": {
+ "label": "Klucz API PageSpeed",
+ "labelSet": "Klucz API jest ustawiony. Kliknij Resetuj, aby go zmienić.",
+ "placeholder": "Wprowadź swój klucz API Google PageSpeed"
+ }
+ },
+ "title": "Klucz API Google PageSpeed"
+ },
+ "removeMonitors": {
+ "description": "Usuń wszystkie monitory z systemu.",
+ "dialog": {
+ "description": "Tej operacji nie można cofnąć.",
+ "paragraph": "Cała historia sprawdzeń, incydenty, zaplanowane okna konserwacyjne oraz wszystkie strony statusowe, które odnosiły się do tych monitorów, również zostaną usunięte. Najpierw wyeksportuj swoje monitory, aby zachować kopię zapasową ich danych.",
+ "title": "Usunąć wszystkie monitory?"
+ },
+ "title": "Resetowanie systemu"
+ },
+ "retention": {
+ "description": "Ustaw, jak długo dane kontroli będą przechowywane przed ich automatycznym usunięciem.",
+ "option": {
+ "days": {
+ "label": "Okres przechowywania (dni)",
+ "unlimited": "Bez ograniczeń"
+ }
+ },
+ "title": "Sprawdź retencję"
+ },
+ "stats": {
+ "description": "Wyczyść całą historię monitorowania i statystyki dla Twojego zespołu. Ta operacja jest nieodwracalna.",
+ "dialog": {
+ "description": "Tej operacji nie można cofnąć",
+ "title": "Wyczyścić całą historię monitorowania?"
+ },
+ "option": {
+ "clear": {
+ "label": "Wyczyść wszystkie statystyki. Tej operacji nie można cofnąć."
+ }
+ },
+ "title": "Monitoruj historię"
+ },
+ "thresholds": {
+ "description": "Ustaw globalne progi CPU, pamięci, dysku i temperatury dla monitorowania infrastruktury. Mają one zastosowanie do wszystkich monitorów sprzętu, o ile nie zostaną zastąpione.",
+ "option": {
+ "cpu": {
+ "label": "Próg CPU (%)",
+ "placeholder": "1 - 100"
+ },
+ "disk": {
+ "label": "Próg dysku (%)",
+ "placeholder": "1 - 100"
+ },
+ "memory": {
+ "label": "Próg pamięci (%)",
+ "placeholder": "1 - 100"
+ },
+ "temperature": {
+ "label": "Próg temperatury (°C)",
+ "placeholder": "1 - 150"
+ }
+ },
+ "title": "Progi globalne"
+ },
+ "timezone": {
+ "description": "Wybierz strefę czasową używaną do wyświetlania dat i godzin w całej aplikacji.",
+ "option": {
+ "timezone": {
+ "label": "Wyświetlanie strefy czasowej"
+ }
+ },
+ "title": "Wyświetlana strefa czasowa"
+ },
+ "ui": {
+ "description": "Przełączaj między trybem jasnym i ciemnym, zmieniaj język lub dostosuj typ wyświetlania wykresu.",
+ "option": {
+ "chartType": {
+ "heatmap": "Mapa ciepła",
+ "histogram": "Histogram",
+ "label": "Typ wykresu"
+ },
+ "language": {
+ "label": "Język"
+ },
+ "theme": {
+ "dark": "Ciemny",
+ "label": "Tryb motywu",
+ "light": "Jasny"
+ }
+ },
+ "title": "Wygląd"
+ },
+ "url": {
+ "description": "Wyświetl adres IP lub URL monitora na publicznej stronie Status. Jeśli opcja jest wyłączona, wyświetlana będzie tylko nazwa monitora, aby chronić poufne informacje.",
+ "option": {
+ "showURL": {
+ "disabled": "Wyłączone",
+ "enabled": "Włączone",
+ "label": "Wyświetl IP/URL na stronie statusu"
+ }
+ },
+ "title": "Monitoruj IP/URL na stronie statusu"
+ },
+ "validation": {
+ "errorMessage": "Proszę poprawić następujące błędy walidacji:"
+ }
+ },
+ "header": {
+ "description": "Skonfiguruj SMTP, integracje, retencję oraz globalne zachowanie aplikacji.",
+ "title": "Ustawienia"
+ }
+ },
+ "statusPages": {
+ "deleteSuccess": "Strona statusu została pomyślnie usunięta",
+ "details": {
+ "empty": {
+ "addMonitor": "Dodaj monitor, aby rozpocząć",
+ "title": "Nie ma tu jeszcze nic"
+ }
+ },
+ "editorial": {
+ "allUp": "Wszystkie systemy działają prawidłowo.",
+ "reportTitle": "Raport serwisowy"
+ },
+ "fallback": {
+ "actionButton": "Utwórz stronę statusu",
+ "description": "Opublikuj publiczną stronę, która pokazuje w czasie rzeczywistym historię dostępności i incydentów dla Twoich klientów i interesariuszy.",
+ "title": "Brak stron statusu"
+ },
+ "footer": {
+ "poweredBy": "Zasilane przez"
+ },
+ "form": {
+ "access": {
+ "description": "Jeśli Twoja strona statusu jest gotowa, możesz oznaczyć ją jako opublikowaną.",
+ "option": {
+ "published": {
+ "name": "Opublikowane i widoczne publicznie"
+ }
+ },
+ "title": "Dostęp"
+ },
+ "appearance": {
+ "description": "Zdefiniuj domyślny wygląd i styl swojej publicznej strony statusu.",
+ "option": {
+ "color": {
+ "label": "Kolor marki"
+ },
+ "logo": {
+ "label": "Logo"
+ }
+ },
+ "title": "Wygląd"
+ },
+ "basicInfo": {
+ "description": "Zdefiniuj nazwę firmy oraz subdomenę, na którą wskazuje Twoja strona statusu.",
+ "option": {
+ "customDomain": {
+ "helper": "Opcjonalne. Skieruj rekord DNS na tę instancję Checkmate, aby wyświetlać stronę statusu pod własną domeną.",
+ "label": "Niestandardowa domena",
+ "placeholder": "status.przyklad.com"
+ },
+ "name": {
+ "label": "Nazwa firmy",
+ "placeholder": "Acme Inc."
+ },
+ "url": {
+ "label": "Adres Twojej strony statusowej",
+ "placeholder": "moja-strona-statusowa"
+ }
+ },
+ "title": "Podstawowe informacje"
+ },
+ "customCSS": {
+ "description": "Dodaj własny CSS, aby precyzyjnie dostosować wygląd swojej publicznej strony statusu.",
+ "option": {
+ "customCSS": {
+ "label": "Niestandardowy CSS",
+ "placeholder": "footer { display: none; }"
+ }
+ },
+ "title": "Niestandardowy CSS"
+ },
+ "features": {
+ "description": "Skonfiguruj, jakie informacje są wyświetlane na Twojej stronie statusu.",
+ "option": {
+ "showAdminLoginLink": {
+ "label": "Pokaż link do logowania administratora"
+ },
+ "showCharts": {
+ "label": "Pokaż wykresy czasu odpowiedzi"
+ },
+ "showInfrastructure": {
+ "label": "Pokaż metryki infrastruktury"
+ },
+ "showUptimePercentage": {
+ "label": "Pokaż procent czasu dostępności"
+ }
+ },
+ "title": "Funkcje"
+ },
+ "monitors": {
+ "description": "Wybierz monitry do wyświetlenia na swojej stronie statusu.",
+ "noMonitors": "Nie wybrano monitorów",
+ "option": {
+ "monitors": {
+ "label": "Wybierz monitory",
+ "placeholder": "Wyszukaj i wybierz monitory..."
+ }
+ },
+ "title": "Monitory"
+ },
+ "theme": {
+ "description": "Wybierz styl wizualny dla swojej publicznej strony statusu.",
+ "label": "Motyw",
+ "options": {
+ "bold": {
+ "description": "Duża czcionka i wyraźne odstępy. Idealne dla stron nastawionych na budowanie marki.",
+ "name": "Pogrubiony"
+ },
+ "editorial": {
+ "description": "Nagłówki szeryfowe i cienkie linie oddzielające. Wyrazisty i spokojny.",
+ "name": "Redakcyjny"
+ },
+ "minimal": {
+ "description": "Płaski, skoncentrowany i kompaktowy, zawierający tylko najważniejsze elementy.",
+ "name": "Minimalistyczny"
+ },
+ "modern": {
+ "description": "Klasa SaaS z pulsującą kropką statusu i delikatnie zanikającymi kartami.",
+ "name": "Nowoczesny"
+ },
+ "refined": {
+ "description": "Czysty, profesjonalny, zbliżony do klasycznego wyglądu Checkmate.",
+ "name": "Wyrafinowany"
+ }
+ },
+ "title": "Motyw strony"
+ },
+ "themeMode": {
+ "auto": "Automatycznie (zgodnie z urządzeniem odwiedzającego)",
+ "dark": "Tylko ciemny",
+ "label": "Tryb motywu",
+ "light": "Tylko jasny"
+ },
+ "timezone": {
+ "description": "Wybierz strefę czasową, w której będzie wyświetlana Twoja strona statusu.",
+ "option": {
+ "timezone": {
+ "label": "Strefa czasowa",
+ "placeholder": "Wybierz strefę czasową..."
+ }
+ },
+ "title": "Strefa czasowa"
+ }
+ },
+ "header": {
+ "description": "Publikuj publiczne strony pokazujące aktualny czas działania i historię incydentów swoim klientom i interesariuszom.",
+ "title": "Strony statusu"
+ },
+ "monitorsList": {
+ "chart": {
+ "avg": "śr. {{value}}ms",
+ "downTooltip": "Niedostępny",
+ "heatmapAria": "Mapa cieplna czasu odpowiedzi",
+ "max": "maks. {{value}}ms"
+ },
+ "chartTypeHeatmap": "Mapa ciepła",
+ "chartTypeHistogram": "Histogram",
+ "infrastructure": {
+ "cpu": "CPU",
+ "disk": "Dysk",
+ "memory": "RAM",
+ "memoryText": "Pamięć",
+ "title": "Infrastruktura",
+ "total": "Łącznie",
+ "usage": "Użycie",
+ "used": "Używane"
+ },
+ "noData": "Brak dostępnych danych",
+ "status": {
+ "breached": "Przekroczono próg",
+ "down": "Niedostępny",
+ "initializing": "Inicjowanie",
+ "maintenance": "Konserwacja",
+ "paused": "Wstrzymano",
+ "up": "Działa poprawnie"
+ },
+ "uptime": {
+ "responseTime": "Czas odpowiedzi",
+ "title": "Czas pracy"
+ }
+ },
+ "preview": {
+ "title": "Podgląd strony statusu"
+ },
+ "statusBar": {
+ "allBreached": "Wszystkie systemy przekroczyły progi",
+ "allDown": "Wszystkie systemy są niedostępne",
+ "allInitializing": "Wszystkie systemy są inicjalizowane",
+ "allMaintenance": "Wszystkie systemy są w trakcie konserwacji",
+ "allPaused": "Wszystkie systemy są wstrzymane",
+ "allUp": "Wszystkie systemy działają poprawnie",
+ "breached": "Niektóre systemy przekroczyły progi",
+ "breachedAndDown": "Niektóre systemy przekroczyły progi i są nieaktywne",
+ "degraded": "Niektóre systemy mają problemy",
+ "initializing": "Niektóre systemy są inicjalizowane",
+ "maintenance": "Niektóre systemy są w trakcie konserwacji",
+ "maintenanceAndDown": "Niektóre systemy są niedostępne podczas konserwacji",
+ "monitoringSummary": "Monitorowanie {{count}} usługi · Aktualizacje co 10 sekund",
+ "monitoringSummary_other": "Monitorowanie {{count}} usług · Aktualizacje co 10 sekund",
+ "noMonitors": "Nie skonfigurowano żadnych monitorów",
+ "partiallyPaused": "Niektóre systemy są wstrzymane",
+ "unknown": "Nie można określić stanu systemu"
+ },
+ "table": {
+ "headers": {
+ "name": "Nazwa strony statusu",
+ "url": "Publiczny adres URL"
+ },
+ "published": "Opublikowano",
+ "unpublished": "Nieopublikowane"
+ },
+ "title": "Strony statusu"
+ },
+ "tags": {
+ "fallback": {
+ "actionButton": "Utwórz tag",
+ "description": "Organizuj swoje monitory za pomocą tagów. Po utworzeniu tagu i przypisaniu go do monitora, będziesz mógł filtrować według tego tagu w całej aplikacji i zobaczyć szczegóły tagu tutaj.",
+ "title": "Brak jeszcze tagów"
+ },
+ "form": {
+ "color": {
+ "description": "Wybierz kolor dla swojego tagu",
+ "optionName": "Kolor tagu",
+ "placeholder": "np. #13715B",
+ "title": "Kolor etykiety"
+ },
+ "name": {
+ "description": "Wybierz nazwę dla swojego tagu",
+ "optionName": "Nazwa tagu",
+ "placeholder": "np. Grupa 1",
+ "title": "Nazwa tagu"
+ }
+ },
+ "header": {
+ "description": "Porządkuj swoje monitory za pomocą tagów",
+ "title": "Tagi"
+ },
+ "table": {
+ "headers": {
+ "appearance": "Wygląd",
+ "color": "Kolor"
+ }
+ }
+ },
+ "uptime": {
+ "fallback": {
+ "actionButton": "Utwórz monitor",
+ "description": "Monitoruj punkty końcowe HTTP, pingowania, kontenery i porty — i otrzymuj powiadomienia natychmiast, gdy coś przestanie działać.",
+ "title": "Brak monitorów dostępności"
+ },
+ "filters": {
+ "activeTags": "Aktywne tagi",
+ "search": {
+ "placeholder": "Wyszukaj monitory..."
+ }
+ },
+ "header": {
+ "description": "Monitoruj punkty końcowe HTTP, ping, kontenery i porty — i otrzymuj powiadomienia natychmiast, gdy coś przestanie działać.",
+ "title": "Monitory dostępności"
+ },
+ "table": {
+ "headers": {
+ "responseTime": "Czas odpowiedzi",
+ "uptime": "Czas działania %"
+ }
+ }
+ }
+ }
+}
From 6e2a64fc9fa03c43626b714ba7db35a9a20a0313 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jakub=20Pomyka=C5=82a?=
Date: Sat, 18 Jul 2026 20:57:50 +0200
Subject: [PATCH 35/84] Add Polish to language selector
---
client/src/Components/inputs/LanguageSelector.tsx | 1 +
1 file changed, 1 insertion(+)
diff --git a/client/src/Components/inputs/LanguageSelector.tsx b/client/src/Components/inputs/LanguageSelector.tsx
index 116f45069..2798e0539 100644
--- a/client/src/Components/inputs/LanguageSelector.tsx
+++ b/client/src/Components/inputs/LanguageSelector.tsx
@@ -15,6 +15,7 @@ export const languageNames: Record = {
fr: "Français",
it: "Italiano",
"pt-BR": "Português (BR)",
+ pl: "Polski",
cs: "Čeština",
fi: "Suomi",
tr: "Türkçe",
From cf451416b1385f74b61261920bb575a419f0ad1b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jakub=20Pomyka=C5=82a?=
Date: Sat, 18 Jul 2026 21:33:10 +0200
Subject: [PATCH 36/84] Update polish translations after review
---
client/src/locales/pl.json | 112 ++++++++++++++++++-------------------
1 file changed, 56 insertions(+), 56 deletions(-)
diff --git a/client/src/locales/pl.json b/client/src/locales/pl.json
index 40b5c7c4f..0fa50ea9f 100644
--- a/client/src/locales/pl.json
+++ b/client/src/locales/pl.json
@@ -275,7 +275,7 @@
},
"team": {
"addMember": {
- "description": "Utwórz nowe konto użytkownika. Po utworzeniu bezpiecznie udostępnij dane logowania.",
+ "description": "Utwórz nowe konto użytkownika. Następnie przekaż dane logowania w bezpieczny sposób.",
"title": "Zarejestruj nowego członka zespołu"
},
"fallback": {
@@ -285,7 +285,7 @@
"filter": {
"admin": "Administrator",
"all": "Wszystkie role",
- "member": "Członek",
+ "member": "Użytkownik",
"placeholder": "Filtruj według roli"
},
"invite": {
@@ -339,7 +339,7 @@
},
"lowercase": {
"beginning": "Musi zawierać co najmniej",
- "highlighted": "jedna mała litera"
+ "highlighted": "jedną małą literę"
},
"match": {
"beginning": "Hasła",
@@ -347,7 +347,7 @@
},
"number": {
"beginning": "Musi zawierać co najmniej",
- "highlighted": "jedna cyfra"
+ "highlighted": "jedną cyfrę"
},
"special": {
"beginning": "Musi zawierać co najmniej",
@@ -355,7 +355,7 @@
},
"uppercase": {
"beginning": "Musi zawierać co najmniej",
- "highlighted": "jedna wielka litera"
+ "highlighted": "jedną wielką literę"
}
}
},
@@ -366,7 +366,7 @@
"text": "Wróć do"
}
},
- "submit": "Wyślij prośbę o odzyskanie",
+ "submit": "Wyślij prośbę o odzyskanie hasła",
"subtitle": "Nie martw się, wyślemy Ci instrukcje resetowania.",
"title": "Zapomniałeś hasła?"
},
@@ -386,7 +386,7 @@
"title": "Witamy ponownie w Checkmate!"
},
"register": {
- "setupNotice": "Ta strona jest dostępna tylko podczas początkowej konfiguracji. Po zarejestrowaniu superadministratora można zapraszać dodatkowych użytkowników z poziomu ustawień zespołu.",
+ "setupNotice": "Ta strona jest dostępna tylko podczas początkowej konfiguracji. Po zarejestrowaniu super administratora można zapraszać dodatkowych użytkowników z poziomu ustawień zespołu.",
"submit": "Zarejestruj się",
"subtitle": "Zarejestruj się, aby rozpocząć",
"title": "Witamy ponownie w Checkmate!"
@@ -398,12 +398,12 @@
},
"checks": {
"fallback": {
- "description": "Po utworzeniu monitora wszystkie sondy statusu i czasy odpowiedzi będą wyświetlane tutaj jako sprawdzenia.",
- "title": "Brak jeszcze kontroli"
+ "description": "Po utworzeniu monitora wszystkie statusy i czasy odpowiedzi będą wyświetlane tutaj jako checki.",
+ "title": "Brak checków"
},
"header": {
"description": "Każde sprawdzenie wykonywane przez Checkmate na Twoich monitorach pojawia się tutaj wraz z kodem statusu i czasem odpowiedzi.",
- "title": "Kontrole"
+ "title": "Checki"
},
"selects": {
"monitor": {
@@ -412,7 +412,7 @@
"status": {
"all": "Wszystkie",
"down": "Niedostępny",
- "up": "Działa"
+ "up": "Dostępny"
}
},
"table": {
@@ -427,8 +427,8 @@
"monitors": {
"actions": {
"bulkSelected_one": "Wybrano {{count}} monitor",
- "bulkSelected_other": "Wybrano {{count}} monitorów",
- "configure": "Konfiguruj",
+ "bulkSelected_other": "Wybrano {{count}} monitor(ów)",
+ "configure": "Skonfiguruj",
"delete": "Usuń",
"details": "Szczegóły",
"generateToken": "Wygeneruj token",
@@ -445,11 +445,11 @@
"alreadyPaused_other": "Wszystkie wybrane monitory są już wstrzymane",
"alreadyRunning_one": "Wybrany monitor jest już uruchomiony",
"alreadyRunning_other": "Wszystkie wybrane monitory są już uruchomione",
- "partialFailure": "Zaktualizowano {{successCount}} monitorów, ale {{failedCount}} nie udało się zsynchronizować z kolejką zadań",
- "paused_one": "{{count}} monitor wstrzymany",
- "paused_other": "Wstrzymano {{count}} monitorów",
- "resumed_one": "{{count}} monitor wznowiony",
- "resumed_other": "Wznowiono {{count}} monitorów"
+ "partialFailure": "Zaktualizowano {{successCount}} monitor(ów), ale {{failedCount}} nie udało się zsynchronizować z kolejką zadań",
+ "paused_one": "{{count}} monitor(ów) wstrzymany",
+ "paused_other": "Wstrzymano {{count}} monitor(ów)",
+ "resumed_one": "{{count}} monitor(ów) wznowiony",
+ "resumed_other": "Wznowiono {{count}} monitor(ów)"
},
"monitorTypes": {
"optionDns": "DNS",
@@ -510,7 +510,7 @@
"method": {
"description": {
"GET": "GET pobiera pełną odpowiedź, łącznie z treścią. Zaawansowane dopasowywanie jest w pełni obsługiwane",
- "HEAD": "HEAD pobiera tylko nagłówki odpowiedzi, aby zmniejszyć obciążenie docelowego serwera. Obsługuje dopasowywanie kodu statusu, ale nie zaawansowane dopasowywanie, ponieważ nie ma treści odpowiedzi."
+ "HEAD": "HEAD pobiera tylko nagłówki odpowiedzi, aby zmniejszyć obciążenie testowanego serwera. Wspiera dopasowywanie status kodu. Nie obsługuje zaawansowanych dopasowywań, ponieważ nie ma treści odpowiedzi."
},
"label": "Metoda żądania"
}
@@ -543,12 +543,12 @@
"dns": "Monitoruj, czy określony serwer DNS odpowiada.",
"docker": "Wprowadź nazwę lub identyfikator kontenera Docker. Możesz użyć nazwy kontenera (np. my-app) lub identyfikatora kontenera (pełny 64-znakowy identyfikator lub skrócony identyfikator).",
"game": "Wprowadź adres IP lub nazwę hosta oraz numer portu do pingowania (np. 192.168.1.100 lub example.com) i wybierz typ gry. Niektóre gry wymagają włączenia zapytań serwera w pliku konfiguracyjnym — sprawdź dokumentację swojego serwera gry. Zobacz listę obsługiwanych gier , aby uzyskać szczegóły dotyczące protokołu.",
- "grpc": "Wprowadź nazwę hosta i port serwera gRPC, opcjonalnie określ nazwę usługi do sprawdzania stanu oraz dodaj nazwę wyświetlaną.",
+ "grpc": "Wprowadź nazwę hosta i port serwera gRPC, opcjonalnie określ nazwę usługi do sprawdzania stanu oraz dodaj nazwę.",
"hardware": "Monitoruj użycie procesora, pamięci, dysku oraz temperaturę w swojej infrastrukturze. Adres URL punktu końcowego Capture musi zawierać pełną ścieżkę /api/v1/metrics (np. http://192.168.1.10:5001/api/v1/metrics). Dla klucza autoryzacyjnego wklej tylko token — nie dodawaj 'Bearer'.",
- "http": "Wprowadź adres URL lub IP do monitorowania (np. https://example.com/ lub 192.168.1.100) oraz dodaj czytelną nazwę wyświetlaną na pulpicie.",
+ "http": "Wprowadź adres URL lub IP do monitorowania (np. https://example.com/ lub 192.168.1.100) oraz dodaj nazwę, która pojawi się na dashboardzie.",
"pagespeed": "Monitoruj wydajność ładowania strony, Core Web Vitals oraz wyniki optymalizacji swojej witryny.",
- "ping": "Wprowadź adres IP lub nazwę hosta do pingowania (np. 192.168.1.100 lub example.com) oraz dodaj czytelną nazwę wyświetlaną na pulpicie.",
- "port": "Wprowadź adres URL lub IP serwera, numer portu oraz czytelną nazwę wyświetlaną na pulpicie.",
+ "ping": "Wprowadź adres IP lub nazwę hosta do pingowania (np. 192.168.1.100 lub example.com) oraz dodaj nazwę na dashboardzie.",
+ "port": "Wprowadź adres URL lub IP serwera, numer portu oraz nazwę na dashboardzie.",
"websocket": "Wprowadź adres URL WebSocket do monitorowania (np. wss://example.com/socket) i dodaj nazwę."
},
"option": {
@@ -588,7 +588,7 @@
"placeholder": "192.168.1.100 lub example.com"
},
"name": {
- "label": "Wyświetlana nazwa",
+ "label": "Nazwa",
"placeholder": "np. Moja strona internetowa"
},
"port": {
@@ -596,20 +596,20 @@
"placeholder": "80"
},
"secret": {
- "label": "Tajny klucz autoryzacji",
- "placeholder": "Wprowadź swój klucz sekretu"
+ "label": "Klucz autoryzacyjny",
+ "placeholder": "Wprowadź swój klucz"
},
"strategy": {
- "desktop": "Komputer stacjonarny",
+ "desktop": "Desktop",
"label": "Strategia",
- "mobile": "Mobilny"
+ "mobile": "Mobile"
},
"url": {
"label": "URL",
"placeholder": "https://www.google.com"
},
"wsUrl": {
- "label": "Adres URL WebSocket",
+ "label": "WebSocket URL",
"placeholder": "wss://example.com/socket"
}
},
@@ -619,7 +619,7 @@
"description": "Przeprowadzaj sprawdzenia z różnych lokalizacji geograficznych, aby monitorować globalną dostępność i wydajność.",
"option": {
"enabled": {
- "label": "Włącz kontrole geograficznie rozproszone"
+ "label": "Włącz kontrole rozproszone geograficznie"
},
"interval": {
"label": "Interwał sprawdzania",
@@ -643,7 +643,7 @@
"placeholder": "Wybierz lokalizacje"
}
},
- "title": "Kontrole geograficznie rozproszone"
+ "title": "Kontrole rozproszone geograficznie"
},
"ignoreTls": {
"description": "Skonfiguruj weryfikację certyfikatu TLS/SSL dla połączeń HTTPS.",
@@ -655,13 +655,13 @@
"title": "Ustawienia TLS/SSL"
},
"incidents": {
- "description": "Do określenia, kiedy monitor przestaje działać, używane jest okno przesuwne. Status monitora zmieni się tylko wtedy, gdy procent sprawdzeń w oknie przesuwnym osiągnie określoną wartość.",
+ "description": "Do określenia, kiedy monitor przestaje działać, używane jest okno przesuwne. Status monitora zmieni się tylko wtedy, gdy procent sprawdzeń w oknie czasowym osiągnie określoną wartość.",
"option": {
"checks": {
- "label": "Liczba sprawdzeń w oknie przesuwnym"
+ "label": "Liczba sprawdzeń w oknie czasowym"
},
"percentage": {
- "label": "Jaki procent sprawdzeń w ruchomym oknie zakończy się niepowodzeniem/powodzeniem, zanim zmieni się status monitora?"
+ "label": "Jaki procent sprawdzeń w oknie czasowym zakończy się niepowodzeniem/powodzeniem, zanim zmieni się status monitora?"
}
},
"title": "Incydenty"
@@ -868,7 +868,7 @@
}
},
"header": {
- "description": "Śledź wydajność serwera, identyfikuj wąskie gardła i zapewnij niezawodność dzięki monitorowaniu infrastruktury w czasie rzeczywistym.",
+ "description": "Kontroluj wydajność serwera, identyfikuj wąskie gardła i zapewnij niezawodność dzięki monitorowaniu infrastruktury w czasie rzeczywistym.",
"title": "Monitory infrastruktury"
},
"statBoxes": {
@@ -1012,17 +1012,17 @@
},
"maintenanceWindow": {
"fallback": {
- "actionButton": "Utwórz okno konserwacji",
+ "actionButton": "Utwórz przerwę techniczną",
"description": "Zaplanuj planowaną przerwę, aby powiadomienia były wyciszone, a Twój zespół i użytkownicy wiedzieli, czego się spodziewać.",
- "title": "Brak zaplanowanych okien konserwacyjnych"
+ "title": "Brak zaplanowanych okien serwisowych"
},
"form": {
"general": {
- "description": "Ustaw nazwę i opcję powtarzania dla okna konserwacji.",
+ "description": "Ustaw nazwę i opcję powtarzania dla przerwy technicznej.",
"option": {
"name": {
"label": "Nazwa",
- "placeholder": "np. Cotygodniowa konserwacja"
+ "placeholder": "np. Cotygodniowa przerwa techniczna"
},
"repeat": {
"label": "Powtarzaj"
@@ -1031,7 +1031,7 @@
"title": "Ustawienia ogólne"
},
"startDate": {
- "description": "Wybierz datę rozpoczęcia okna konserwacji.",
+ "description": "Wybierz datę rozpoczęcia przerwy technicznej.",
"option": {
"startDate": {
"label": "Data rozpoczęcia"
@@ -1040,12 +1040,12 @@
"title": "Data rozpoczęcia"
},
"startTime": {
- "description": "Ustaw czas rozpoczęcia i czas trwania okna konserwacyjnego. Wszystkie wartości są w UTC",
+ "description": "Ustaw czas rozpoczęcia i czas trwania przerwy technicznej. Wszystkie wartości są w UTC",
"monitors": {
- "description": "Monitory, do których należy zastosować okno konserwacji",
+ "description": "Monitory, do których należy zastosować przerwę techniczną",
"option": {
"addMonitors": {
- "label": "Dodaj monitry"
+ "label": "Dodaj monitory"
}
},
"title": "Monitory"
@@ -1063,18 +1063,18 @@
},
"header": {
"description": "Zaplanuj planowaną przerwę, aby powiadomienia były wyciszone, a Twój zespół i użytkownicy wiedzieli, czego się spodziewać.",
- "title": "Okna konserwacji"
+ "title": "Przerwy techniczne"
},
"table": {
"headers": {
- "nextWindow": "Następne okno",
+ "nextWindow": "Następna przerwa",
"repeat": "Powtórz"
}
}
},
"notFound": {
"subtitle": "Adres URL nie istnieje lub nie masz do niego dostępu.",
- "title": "O nie! Upuściłeś swoje sushi!"
+ "title": "Coś się popsuło i nie było mnie słychać..."
},
"notifications": {
"fallback": {
@@ -1189,16 +1189,16 @@
},
"fallback": {
"actionButton": "Utwórz monitor",
- "description": "Śledź wskaźniki Core Web Vitals i wyniki Lighthouse w czasie, aby wykryć regresje zanim zrobią to użytkownicy.",
+ "description": "Monitoruj wskaźniki Core Web Vitals i wyniki Lighthouse w czasie, aby wykryć regresje zanim zrobią to użytkownicy.",
"title": "Brak monitorów PageSpeed"
},
"header": {
- "description": "Śledź Core Web Vitals i wyniki Lighthouse w czasie, aby wykryć regresje, zanim zauważą je użytkownicy.",
+ "description": "Monitoruj Core Web Vitals i wyniki Lighthouse w czasie, aby wykryć regresje, zanim zauważą je użytkownicy.",
"title": "Monitory PageSpeed"
},
"strategy": {
- "desktop": "Komputer stacjonarny",
- "mobile": "Mobilne"
+ "desktop": "Desktop",
+ "mobile": "Mobile"
},
"table": {
"headers": {
@@ -1410,7 +1410,7 @@
"title": "Brak stron statusu"
},
"footer": {
- "poweredBy": "Zasilane przez"
+ "poweredBy": "Powered by"
},
"form": {
"access": {
@@ -1482,7 +1482,7 @@
"title": "Funkcje"
},
"monitors": {
- "description": "Wybierz monitry do wyświetlenia na swojej stronie statusu.",
+ "description": "Wybierz monitory do wyświetlenia na swojej stronie statusu.",
"noMonitors": "Nie wybrano monitorów",
"option": {
"monitors": {
@@ -1505,11 +1505,11 @@
"name": "Redakcyjny"
},
"minimal": {
- "description": "Płaski, skoncentrowany i kompaktowy, zawierający tylko najważniejsze elementy.",
+ "description": "Prosty i kompaktowy, zawierający tylko najważniejsze elementy.",
"name": "Minimalistyczny"
},
"modern": {
- "description": "Klasa SaaS z pulsującą kropką statusu i delikatnie zanikającymi kartami.",
+ "description": "SaaS'owy z pulsującą kropką statusu i delikatnie zanikającymi kartami.",
"name": "Nowoczesny"
},
"refined": {
@@ -1544,10 +1544,10 @@
"chart": {
"avg": "śr. {{value}}ms",
"downTooltip": "Niedostępny",
- "heatmapAria": "Mapa cieplna czasu odpowiedzi",
+ "heatmapAria": "Heatmapa czasu odpowiedzi",
"max": "maks. {{value}}ms"
},
- "chartTypeHeatmap": "Mapa ciepła",
+ "chartTypeHeatmap": "Heatmapa",
"chartTypeHistogram": "Histogram",
"infrastructure": {
"cpu": "CPU",
From 924c862244c717b7e8bfceab9ab9e1cdebb681e2 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jakub=20Pomyka=C5=82a?=
Date: Sat, 18 Jul 2026 22:13:05 +0200
Subject: [PATCH 37/84] Format pl.json
---
client/src/locales/pl.json | 3322 ++++++++++++++++++------------------
1 file changed, 1661 insertions(+), 1661 deletions(-)
diff --git a/client/src/locales/pl.json b/client/src/locales/pl.json
index 0fa50ea9f..9a74b6cdb 100644
--- a/client/src/locales/pl.json
+++ b/client/src/locales/pl.json
@@ -1,1663 +1,1663 @@
{
- "common": {
- "alerts": {
- "pageSpeedApiKey": {
- "content": "Ostrzeżenie: Nie dodałeś jeszcze klucza Google PageSpeed API. Przejdź do Ustawienia , aby go dodać. Bez niego monitor PageSpeed nie będzie działał."
- }
- },
- "appName": "Checkmate",
- "auth": {
- "roles": {
- "admin": "Administrator",
- "demo": "Demo",
- "superadmin": "Super administrator",
- "user": "Użytkownik"
- }
- },
- "breadcrumbs": {
- "details": "Szczegóły",
- "home": "Strona główna"
- },
- "buttons": {
- "addDemo": "Dodaj demo monitory",
- "addMember": "Dodaj użytkownika",
- "back": "Wstecz",
- "cancel": "Anuluj",
- "clear": "Wyczyść",
- "clearFilters": "Wyczyść filtry",
- "close": "Zamknij",
- "configure": "Konfiguruj",
- "confirm": "Potwierdź",
- "create": "Utwórz",
- "delete": "Usuń",
- "exportToJSON": "Eksportuj do JSON",
- "flushQueue": "Opróżnij kolejkę",
- "generateToken": "Wygeneruj token",
- "importFromJSON": "Importuj z JSON",
- "incidents": "Incydenty",
- "inviteMember": "Zaproś użytkownika",
- "next": "Dalej",
- "notFound": "Przejdź do głównego panelu",
- "pause": "Wstrzymaj",
- "removeMonitors": "Usuń monitory",
- "removeUser": "Usuń użytkownika",
- "reset": "Zresetuj",
- "resetPassword": "Zresetuj hasło",
- "resume": "Wznów",
- "save": "Zapisz",
- "sendInvite": "Wyślij zaproszenie",
- "sendTestEmail": "Wyślij testowy e-mail",
- "test": "Test",
- "testNotifications": "Przetestuj powiadomienia",
- "toggleTheme": "Przełącza jasny i ciemny"
- },
- "charts": {
- "histogram": {
- "avg": "Średnia: {{value}} ms",
- "max": "Maks: {{value}} ms"
- },
- "labels": {
- "averageResponseTime": "Średni czas odpowiedzi",
- "downtime": "Czas przestoju",
- "high": "Wysoki",
- "low": "Niski",
- "uptime": "Czas pracy"
- }
- },
- "dialogs": {
- "delete": {
- "description": "Tej operacji nie można cofnąć.",
- "title": "Czy na pewno chcesz to usunąć?"
- }
- },
- "form": {
- "email": {
- "option": {
- "email": {
- "label": "Email",
- "placeholder": "you@example.com"
- }
- }
- },
- "name": {
- "option": {
- "firstName": {
- "label": "Imię",
- "placeholder": "Wpisz swoje imię"
- },
- "lastName": {
- "label": "Nazwisko",
- "placeholder": "Wprowadź swoje nazwisko"
- }
- }
- },
- "role": {
- "option": {
- "role": {
- "label": "Role"
- }
- }
- }
- },
- "labels": {
- "active": "Aktywny",
- "idle": "Bezczynny",
- "na": "Brak danych",
- "paused": "Wstrzymano",
- "resolved": "Rozwiązano",
- "responseTime": "Czas reakcji",
- "running": "Uruchomiony"
- },
- "statusCode": {
- "down": "Niedostępny",
- "networkError": {
- "dns": "Nie można rozpoznać nazwy hosta (błąd wyszukiwania DNS).",
- "refused": "Serwer odrzucił połączenie (port zamknięty lub usługa nie nasłuchuje).",
- "reset": "Połączenie zostało zresetowane, zanim otrzymano odpowiedź.",
- "timeout": "Serwer nie odpowiedział na czas (żądanie przekroczyło limit czasu).",
- "tls": "Nie powiodło się uzgadnianie TLS lub SSL (problem z certyfikatem lub szyfrowaniem).",
- "unknown": "Nie otrzymano odpowiedzi HTTP z docelowego serwera."
- }
- },
- "table": {
- "empty": "Brak danych do wyświetlenia",
- "headers": {
- "actions": "Akcje",
- "active": "Aktywny",
- "dateTime": "Data i godzina",
- "interval": "Przedział",
- "message": "Wiadomość",
- "monitor": "Monitor",
- "monitorId": "ID monitora",
- "name": "Nazwa",
- "responseTime": "Czas odpowiedzi",
- "status": "Status",
- "tags": "Tagi",
- "type": "Typ",
- "url": "Url"
- }
- }
- },
- "components": {
- "headerStatusPageControls": {
- "publicLink": "Publiczny link"
- },
- "headerTimeRange": {
- "description": {
- "day": "Wyświetlanie statystyk z ostatnich 24 godzin.",
- "month": "Wyświetlanie statystyk z ostatnich 30 dni.",
- "recent": "Wyświetlanie statystyk z ostatnich 2 godzin.",
- "week": "Wyświetlanie statystyk z ostatnich 7 dni."
- },
- "labels": {
- "day": "Dzień",
- "month": "Miesiąc",
- "recent": "Najnowsze",
- "week": "Tydzień"
- }
- },
- "imageUpload": {
- "clickToUpload": "Kliknij, aby przesłać",
- "dragAndDrop": "lub przeciągnij i upuść",
- "errors": {
- "invalidFileFormat": "Nieobsługiwany format pliku!",
- "invalidFileSize": "Rozmiar pliku jest za duży!"
- },
- "maxSize": "Maksymalny rozmiar",
- "orDragAndDrop": "lub przeciągnij i upuść",
- "supportedFormats": "Obsługiwane formaty"
- },
- "offlineBanner": {
- "reconnected": "Połączenie przywrócone",
- "retry": "Ponów próbę",
- "retrying": "Ponawianie próby...",
- "serverUnreachable": "Nie można połączyć się z serwerem"
- },
- "pageHeader": {
- "links": {
- "changelog": "Dziennik zmian",
- "discussions": "Dyskusje",
- "docs": "Dokumentacja",
- "support": "Pomoc"
- }
- },
- "sidebar": {
- "accountMenu": {
- "password": "Hasło",
- "profile": "Profil",
- "team": "Zespół"
- },
- "authFooter": {
- "logOut": "Wyloguj się",
- "navControls": "Sterowanie",
- "roles": {
- "admin": "Administrator",
- "demoUser": "Użytkownik demo",
- "superAdmin": "Super administrator",
- "user": "Użytkownik"
- }
- },
- "bottomMenu": {
- "changelog": "Dziennik zmian",
- "discussions": "Dyskusje",
- "docs": "Dokumentacja",
- "support": "Wsparcie"
- },
- "menu": {
- "checks": "Kontrole",
- "incidents": "Incydenty",
- "infrastructure": "Infrastruktura",
- "logs": "Dzienniki",
- "maintenance": "Konserwacja",
- "notifications": "Powiadomienia",
- "pagespeed": "Pagespeed",
- "settings": "Ustawienia",
- "statusPages": "Strony statusu",
- "tags": "Tagi",
- "uptime": "Czas działania"
- },
- "starPrompt": {
- "description": "Zobacz najnowsze wydania i pomóż rozwijać społeczność na GitHub",
- "title": "Zagwiazdkuj Checkmate"
- }
- },
- "starPrompt": {
- "description": "Zobacz najnowsze wydania i wspieraj rozwój społeczności na GitHub",
- "title": "Zagwiazdkuj Checkmate"
- }
- },
- "pages": {
- "account": {
- "form": {
- "currentPassword": {
- "description": "Wprowadź swoje obecne hasło, aby potwierdzić swoją tożsamość",
- "option": {
- "label": "Obecne hasło",
- "placeholder": "Wprowadź obecne hasło"
- },
- "title": "Obecne hasło"
- },
- "deleteAccount": {
- "description": "Ta akcja jest nieodwracalna i nie można jej cofnąć",
- "title": "Usuń konto"
- },
- "name": {
- "description": "Zaktualizuj swoje dane osobowe",
- "title": "Imię"
- },
- "newPassword": {
- "description": "Wybierz silne hasło składające się z co najmniej 8 znaków",
- "option": {
- "confirm": {
- "label": "Potwierdź hasło",
- "placeholder": "Potwierdź nowe hasło"
- },
- "newPassword": {
- "label": "Nowe hasło",
- "placeholder": "Wprowadź nowe hasło"
- }
- },
- "title": "Nowe hasło"
- },
- "photo": {
- "description": "Prześlij zdjęcie profilowe",
- "title": "Zdjęcie profilowe"
- }
- },
- "header": {
- "description": "Zarządzaj swoim profilem, hasłem, zespołem i preferencjami przestrzeni roboczej.",
- "title": "Konto"
- },
- "tabs": {
- "password": "Hasło",
- "profile": "Profil",
- "team": "Zespół"
- },
- "team": {
- "addMember": {
- "description": "Utwórz nowe konto użytkownika. Następnie przekaż dane logowania w bezpieczny sposób.",
- "title": "Zarejestruj nowego członka zespołu"
- },
- "fallback": {
- "description": "Zaproś członków zespołu przez e-mail lub dodaj ich bezpośrednio. Po dołączeniu pojawią się tutaj wraz ze swoją rolą.",
- "title": "Brak członków zespołu"
- },
- "filter": {
- "admin": "Administrator",
- "all": "Wszystkie role",
- "member": "Użytkownik",
- "placeholder": "Filtruj według roli"
- },
- "invite": {
- "description": "Wyślij zaproszenie do dołączenia do swojego zespołu",
- "email": {
- "label": "Adres e-mail",
- "placeholder": "Wprowadź adres e-mail"
- },
- "linkLabel": "Link do zaproszenia",
- "role": {
- "label": "Rola",
- "placeholder": "Wybierz rolę"
- },
- "title": "Zaproś członka zespołu"
- },
- "table": {
- "headers": {
- "created": "Utworzono dnia",
- "email": "E-mail",
- "role": "Rola"
- }
- }
- }
- },
- "auth": {
- "brandPanel": {
- "description": "Monitorowanie działania, reakcji i incydentów w czasie rzeczywistym - do samodzielnego hostowania i na własność.",
- "eyebrow": "Monitoring infrastruktury open source",
- "tagline": "Obserwuj każdy ruch swoich serwerów."
- },
- "common": {
- "form": {
- "option": {
- "confirmPassword": {
- "label": "Potwierdź hasło"
- },
- "email": {
- "label": "E-mail",
- "placeholder": "me@example.com"
- },
- "password": {
- "label": "Hasło",
- "placeholder": "********"
- }
- }
- },
- "passwordRules": {
- "length": {
- "beginning": "Musi mieć co najmniej",
- "highlighted": "8 znaków"
- },
- "lowercase": {
- "beginning": "Musi zawierać co najmniej",
- "highlighted": "jedną małą literę"
- },
- "match": {
- "beginning": "Hasła",
- "highlighted": "musi pasować"
- },
- "number": {
- "beginning": "Musi zawierać co najmniej",
- "highlighted": "jedną cyfrę"
- },
- "special": {
- "beginning": "Musi zawierać co najmniej",
- "highlighted": "jeden znak specjalny (!?@#$%^&*()-_=+[]{}|;:'\",./\\~`)"
- },
- "uppercase": {
- "beginning": "Musi zawierać co najmniej",
- "highlighted": "jedną wielką literę"
- }
- }
- },
- "forgotPassword": {
- "links": {
- "login": {
- "linkText": "zaloguj się",
- "text": "Wróć do"
- }
- },
- "submit": "Wyślij prośbę o odzyskanie hasła",
- "subtitle": "Nie martw się, wyślemy Ci instrukcje resetowania.",
- "title": "Zapomniałeś hasła?"
- },
- "login": {
- "links": {
- "forgotPassword": {
- "linkText": "Resetuj hasło",
- "text": "Zapomniałeś hasła?"
- },
- "register": {
- "linkText": "Zarejestruj się tutaj",
- "text": "Nie masz konta?"
- }
- },
- "submit": "Zaloguj się",
- "subtitle": "Zaloguj się, aby kontynuować",
- "title": "Witamy ponownie w Checkmate!"
- },
- "register": {
- "setupNotice": "Ta strona jest dostępna tylko podczas początkowej konfiguracji. Po zarejestrowaniu super administratora można zapraszać dodatkowych użytkowników z poziomu ustawień zespołu.",
- "submit": "Zarejestruj się",
- "subtitle": "Zarejestruj się, aby rozpocząć",
- "title": "Witamy ponownie w Checkmate!"
- },
- "setNewPassword": {
- "subtitle": "Twoje nowe hasło musi różnić się od wcześniej używanych haseł.",
- "title": "Zresetuj hasło"
- }
- },
- "checks": {
- "fallback": {
- "description": "Po utworzeniu monitora wszystkie statusy i czasy odpowiedzi będą wyświetlane tutaj jako checki.",
- "title": "Brak checków"
- },
- "header": {
- "description": "Każde sprawdzenie wykonywane przez Checkmate na Twoich monitorach pojawia się tutaj wraz z kodem statusu i czasem odpowiedzi.",
- "title": "Checki"
- },
- "selects": {
- "monitor": {
- "all": "Wszystkie monitory"
- },
- "status": {
- "all": "Wszystkie",
- "down": "Niedostępny",
- "up": "Dostępny"
- }
- },
- "table": {
- "empty": "Brak awarii w tym przedziale czasowym",
- "headers": {
- "location": "Lokalizacja",
- "statusCode": "Kod statusu"
- }
- }
- },
- "common": {
- "monitors": {
- "actions": {
- "bulkSelected_one": "Wybrano {{count}} monitor",
- "bulkSelected_other": "Wybrano {{count}} monitor(ów)",
- "configure": "Skonfiguruj",
- "delete": "Usuń",
- "details": "Szczegóły",
- "generateToken": "Wygeneruj token",
- "incidents": "Incydenty",
- "inviteMember": "Zaproś członka",
- "openSite": "Otwórz stronę",
- "pause": "Wstrzymaj",
- "resume": "Wznów",
- "selectAll": "Zaznacz wszystkie monitory",
- "selectMonitor": "Wybierz {{name}}"
- },
- "bulkPause": {
- "alreadyPaused_one": "Wybrany monitor jest już wstrzymany",
- "alreadyPaused_other": "Wszystkie wybrane monitory są już wstrzymane",
- "alreadyRunning_one": "Wybrany monitor jest już uruchomiony",
- "alreadyRunning_other": "Wszystkie wybrane monitory są już uruchomione",
- "partialFailure": "Zaktualizowano {{successCount}} monitor(ów), ale {{failedCount}} nie udało się zsynchronizować z kolejką zadań",
- "paused_one": "{{count}} monitor(ów) wstrzymany",
- "paused_other": "Wstrzymano {{count}} monitor(ów)",
- "resumed_one": "{{count}} monitor(ów) wznowiony",
- "resumed_other": "Wznowiono {{count}} monitor(ów)"
- },
- "monitorTypes": {
- "optionDns": "DNS",
- "optionDocker": "Docker",
- "optionGame": "Gra",
- "optionGrpc": "gRPC",
- "optionHardware": "Infrastruktura",
- "optionHttp": "HTTP(S)",
- "optionPagespeed": "PageSpeed",
- "optionPing": "Ping",
- "optionPort": "Port",
- "optionWebSocket": "WebSocket"
- },
- "statBoxes": {
- "activeFor": "Aktywne przez",
- "certificateExpiry": "Wygaśnięcie certyfikatu",
- "lastCheck": "Ostatnia kontrola",
- "lastResponseTime": "Ostatni czas odpowiedzi",
- "serviceIsDown": "Usługa nie działa"
- },
- "status": {
- "breached": "przekroczono próg",
- "down": "niedostępny",
- "initializing": "inicjowanie",
- "maintenance": "konserwacja",
- "paused": "wstrzymano",
- "total": "łącznie",
- "up": "działa"
- }
- }
- },
- "createMonitor": {
- "form": {
- "advanced": {
- "description": "Opcjonalne ustawienia dla zaawansowanych przypadków użycia",
- "option": {
- "advancedMatching": {
- "label": "Użyj zaawansowanego dopasowania"
- },
- "customUpCodes": {
- "description": "Dodaj dowolne kody inne niż 2xx, które również powinny być uznane za pomyślne.",
- "label": "Zaakceptowane kody statusu",
- "removeAriaLabel": "Usuń kod statusu"
- },
- "expectedValue": {
- "label": "Oczekiwana wartość"
- },
- "jsonPath": {
- "description": "To wyrażenie zostanie ocenione względem danych JSON odpowiedzi, a wynik będzie użyty do porównania z oczekiwaną wartością. Zobacz jmespath.org po dokumentację języka zapytań.",
- "label": "Wyrażenie JSONPath"
- },
- "matchMethod": {
- "equal": "Równe",
- "include": "Uwzględnij",
- "label": "Metoda dopasowania",
- "regex": "Wyrażenie regularne"
- },
- "method": {
- "description": {
- "GET": "GET pobiera pełną odpowiedź, łącznie z treścią. Zaawansowane dopasowywanie jest w pełni obsługiwane",
- "HEAD": "HEAD pobiera tylko nagłówki odpowiedzi, aby zmniejszyć obciążenie testowanego serwera. Wspiera dopasowywanie status kodu. Nie obsługuje zaawansowanych dopasowywań, ponieważ nie ma treści odpowiedzi."
- },
- "label": "Metoda żądania"
- }
- },
- "title": "Ustawienia zaawansowane"
- },
- "frequency": {
- "description": "Jak często chcesz sprawdzać status tego monitora?",
- "option": {
- "frequency": {
- "label": "Sprawdź częstotliwość",
- "value": {
- "fifteenMinutes": "15 minut",
- "fifteenSeconds": "15 sekund",
- "fiveMinutes": "5 minut",
- "fourMinutes": "4 minuty",
- "oneMinute": "1 minuta",
- "tenMinutes": "10 minut",
- "thirtyMinutes": "30 minut",
- "thirtySeconds": "30 sekund",
- "threeMinutes": "3 minuty",
- "twoMinutes": "2 minuty"
- }
- }
- },
- "title": "Częstotliwość sprawdzania"
- },
- "general": {
- "description": {
- "dns": "Monitoruj, czy określony serwer DNS odpowiada.",
- "docker": "Wprowadź nazwę lub identyfikator kontenera Docker. Możesz użyć nazwy kontenera (np. my-app) lub identyfikatora kontenera (pełny 64-znakowy identyfikator lub skrócony identyfikator).",
- "game": "Wprowadź adres IP lub nazwę hosta oraz numer portu do pingowania (np. 192.168.1.100 lub example.com) i wybierz typ gry. Niektóre gry wymagają włączenia zapytań serwera w pliku konfiguracyjnym — sprawdź dokumentację swojego serwera gry. Zobacz listę obsługiwanych gier , aby uzyskać szczegóły dotyczące protokołu.",
- "grpc": "Wprowadź nazwę hosta i port serwera gRPC, opcjonalnie określ nazwę usługi do sprawdzania stanu oraz dodaj nazwę.",
- "hardware": "Monitoruj użycie procesora, pamięci, dysku oraz temperaturę w swojej infrastrukturze. Adres URL punktu końcowego Capture musi zawierać pełną ścieżkę /api/v1/metrics (np. http://192.168.1.10:5001/api/v1/metrics). Dla klucza autoryzacyjnego wklej tylko token — nie dodawaj 'Bearer'.",
- "http": "Wprowadź adres URL lub IP do monitorowania (np. https://example.com/ lub 192.168.1.100) oraz dodaj nazwę, która pojawi się na dashboardzie.",
- "pagespeed": "Monitoruj wydajność ładowania strony, Core Web Vitals oraz wyniki optymalizacji swojej witryny.",
- "ping": "Wprowadź adres IP lub nazwę hosta do pingowania (np. 192.168.1.100 lub example.com) oraz dodaj nazwę na dashboardzie.",
- "port": "Wprowadź adres URL lub IP serwera, numer portu oraz nazwę na dashboardzie.",
- "websocket": "Wprowadź adres URL WebSocket do monitorowania (np. wss://example.com/socket) i dodaj nazwę."
- },
- "option": {
- "captureUrl": {
- "label": "Punkt końcowy przechwytywania",
- "placeholder": "http://192.168.1.10:5001/api/v1/metrics"
- },
- "container": {
- "label": "Nazwa/ID kontenera",
- "placeholder": "moja-aplikacja lub abcd1234"
- },
- "dnsRecordType": {
- "label": "Typ rekordu DNS",
- "value": {
- "A": "A",
- "AAAA": "AAAA",
- "CNAME": "CNAME",
- "MX": "MX",
- "NS": "NS",
- "TXT": "TXT"
- }
- },
- "dnsServer": {
- "label": "Serwer DNS",
- "placeholder": "8.8.8.8"
- },
- "game": {
- "label": "Wybierz grę",
- "placeholder": "Wybierz grę"
- },
- "grpcServiceName": {
- "label": "Nazwa usługi",
- "placeholder": "np. my.service.v1 (pozostaw puste, aby monitorować ogólny stan zdrowia)"
- },
- "host": {
- "label": "Host",
- "placeholder": "192.168.1.100 lub example.com"
- },
- "name": {
- "label": "Nazwa",
- "placeholder": "np. Moja strona internetowa"
- },
- "port": {
- "label": "Port do monitorowania",
- "placeholder": "80"
- },
- "secret": {
- "label": "Klucz autoryzacyjny",
- "placeholder": "Wprowadź swój klucz"
- },
- "strategy": {
- "desktop": "Desktop",
- "label": "Strategia",
- "mobile": "Mobile"
- },
- "url": {
- "label": "URL",
- "placeholder": "https://www.google.com"
- },
- "wsUrl": {
- "label": "WebSocket URL",
- "placeholder": "wss://example.com/socket"
- }
- },
- "title": "Ustawienia ogólne"
- },
- "geoChecks": {
- "description": "Przeprowadzaj sprawdzenia z różnych lokalizacji geograficznych, aby monitorować globalną dostępność i wydajność.",
- "option": {
- "enabled": {
- "label": "Włącz kontrole rozproszone geograficznie"
- },
- "interval": {
- "label": "Interwał sprawdzania",
- "value": {
- "fifteenMinutes": "15 minut",
- "fiveMinutes": "5 minut",
- "tenMinutes": "10 minut",
- "thirtyMinutes": "30 minut"
- }
- },
- "locations": {
- "label": "Lokalizacje",
- "options": {
- "AF": "Afryka",
- "AS": "Azja",
- "EU": "Europa",
- "NA": "Ameryka Północna",
- "OC": "Oceania",
- "SA": "Ameryka Południowa"
- },
- "placeholder": "Wybierz lokalizacje"
- }
- },
- "title": "Kontrole rozproszone geograficznie"
- },
- "ignoreTls": {
- "description": "Skonfiguruj weryfikację certyfikatu TLS/SSL dla połączeń HTTPS.",
- "option": {
- "tls": {
- "label": "Ignoruj błędy TLS/SSL"
- }
- },
- "title": "Ustawienia TLS/SSL"
- },
- "incidents": {
- "description": "Do określenia, kiedy monitor przestaje działać, używane jest okno przesuwne. Status monitora zmieni się tylko wtedy, gdy procent sprawdzeń w oknie czasowym osiągnie określoną wartość.",
- "option": {
- "checks": {
- "label": "Liczba sprawdzeń w oknie czasowym"
- },
- "percentage": {
- "label": "Jaki procent sprawdzeń w oknie czasowym zakończy się niepowodzeniem/powodzeniem, zanim zmieni się status monitora?"
- }
- },
- "title": "Incydenty"
- },
- "notifications": {
- "description": "Wybierz kanały powiadomień, których chcesz używać",
- "title": "Powiadomienia"
- },
- "stats": {
- "buttonText": "Wyczyść wszystkie statystyki",
- "description": "Wyczyść całą historię monitorowania i statystyki dla Twojego zespołu. Ta operacja jest nieodwracalna.",
- "dialog": {
- "confirm": "Tak, wyczyść wszystkie statystyki",
- "description": "To spowoduje trwałe usunięcie całej historii monitorowania, statystyk i danych sprawdzających dla Twojego zespołu. Tej operacji nie można cofnąć.",
- "title": "Wyczyścić całą historię monitorowania?"
- },
- "title": "Historia monitora"
- },
- "tags": {
- "description": "Wybierz tagi, które chcesz przypisać do tego monitora",
- "title": "Tagi"
- },
- "thresholds": {
- "description": "Określ progi, przy których mają być wyzwalane alerty dla tego monitora sprzętu.",
- "option": {
- "cpuThreshold": {
- "label": "Próg alertu CPU (%)"
- },
- "diskThreshold": {
- "label": "Próg alertu dysku (%)"
- },
- "memoryThreshold": {
- "label": "Próg alertu pamięci (%)"
- },
- "tempThreshold": {
- "label": "Próg alarmowy temperatury (°C)"
- }
- },
- "title": "Progi alertów"
- },
- "type": {
- "description": "Wybierz typ sprawdzenia do wykonania",
- "optionDnsDescription": "Monitoruj, czy określony serwer DNS odpowiada.",
- "optionDockerDescription": "Użyj Dockera, aby monitorować, czy kontener jest uruchomiony.",
- "optionGameDescription": "Monitoruj, czy konkretny serwer gry jest online.",
- "optionGrpcDescription": "Monitoruj usługi gRPC za pomocą standardowego protokołu kontroli stanu.",
- "optionHttpDescription": "Użyj protokołu HTTP(S), aby monitorować swoją stronę internetową lub punkt końcowy API.",
- "optionPingDescription": "Użyj ICMP Ping, aby monitorować, czy serwer jest online.",
- "optionPortDescription": "Monitoruj, czy określony port na serwerze jest otwarty.",
- "optionWebSocketDescription": "Monitoruj punkty końcowe WebSocket pod kątem stanu połączenia i czasu odpowiedzi.",
- "title": "Typ"
- },
- "url": {
- "description": "Wyświetl adres IP lub URL monitora na publicznej stronie Status. Jeśli ta opcja jest wyłączona, zostanie wyświetlona tylko nazwa monitora, aby chronić poufne informacje.",
- "option": {
- "showURL": {
- "disabled": "Wyłączone",
- "enabled": "Włączone",
- "label": "Wyświetl IP/URL na stronie statusu"
- }
- },
- "title": "Monitoruj IP/URL na stronie statusu"
- }
- }
- },
- "editUser": {
- "dialog": {
- "removeUser": {
- "content": "Czy na pewno chcesz usunąć {{name}} ze swojego zespołu? Tej operacji nie można cofnąć.",
- "title": "Usuń użytkownika"
- }
- },
- "form": {
- "roles": {
- "description": "Przypisz role użytkownikowi. Można wybrać wiele ról.",
- "title": "Role"
- }
- }
- },
- "incidents": {
- "dialog": {
- "details": {
- "analysis": "Analiza incydentu",
- "comment": "Komentarz:",
- "detailsLabel": "Szczegóły",
- "downtime": "Czas niedostępności:",
- "message": "Wiadomość:",
- "monitor": "Monitor:",
- "overview": "Przegląd",
- "resolutionDetails": "Szczegóły rozwiązania",
- "resolutionType": "Typ:",
- "resolutionTypes": {
- "automatic": "Automatyczny",
- "manual": "Ręczne"
- },
- "resolve": "Rozwiąż incydent",
- "resolvedAt": "Rozwiązano o:",
- "resolvedBy": "Rozwiązane przez:",
- "startedAt": "Rozpoczęto o:",
- "status": "Status:",
- "statusCode": "Kod statusu:",
- "timeline": "Oś czasu",
- "title": "Szczegóły incydentu",
- "url": "URL:"
- },
- "resolveIncident": {
- "option": {
- "comment": {
- "label": "Komentarz (opcjonalnie)",
- "placeholder": "Dodaj komentarz dotyczący rozwiązania..."
- }
- },
- "title": "Rozwiąż incydent"
- }
- },
- "fallback": {
- "description": "Gdy monitor przestanie działać lub zostanie przywrócony, incydent pojawi się tutaj wraz z czasem, szczegółami monitora i historią rozwiązań.",
- "title": "Brak incydentów — wszystkie systemy działają poprawnie"
- },
- "filters": {
- "allMonitors": "Wszystkie monitory",
- "monitor": "Monitor",
- "resolutionType": "Typ rozwiązania",
- "resolutionTypes": {
- "all": "Wszystkie",
- "automatic": "Automatyczne",
- "manual": "Ręczny"
- }
- },
- "header": {
- "description": "Gdy monitory przestają działać lub zostają przywrócone, incydenty pojawiają się tutaj wraz z informacjami o czasie, szczegółach monitora i historią rozwiązań.",
- "title": "Incydenty"
- },
- "summaryCard": {
- "activeIncidents": {
- "active_one": "{{count}} aktywny incydent",
- "active_other": "{{count}} aktywne incydenty",
- "active_zero": "Brak aktywnych incydentów",
- "title": "Aktywne incydenty"
- },
- "incidentStats": {
- "avgResolutionTime": "Średni czas rozwiązania",
- "mostAffectedMonitor": "Najbardziej dotknięty monitor",
- "title": "Statystyki incydentów",
- "totalIncidents": "Łączna liczba incydentów"
- },
- "latestIncidents": {
- "title": "Najnowsze incydenty"
- }
- },
- "table": {
- "actions": {
- "details": "Szczegóły",
- "goToMonitor": "Przejdź do monitora",
- "resolveManually": "Rozwiąż ręcznie"
- },
- "activeIncidents": "Aktywne incydenty",
- "headers": {
- "endTime": "Czas zakończenia",
- "resolutionType": "Typ rozwiązania",
- "startTime": "Czas rozpoczęcia",
- "statusCode": "Kod statusu"
- },
- "resolvedIncidents": "Rozwiązane incydenty",
- "status": {
- "active": "Aktywne",
- "resolved": "Rozwiązany"
- }
- }
- },
- "infrastructure": {
- "charts": {
- "labels": {
- "cpu": "Użycie CPU",
- "disk": "Zużycie dysku",
- "loopbackOnlyNotice": "Przechwytywanie raportuje tylko interfejsy loopback lub container-bridge (lo, lo0, docker0). To normalne, gdy Capture działa wewnątrz kontenera, który widzi tylko własną przestrzeń nazw sieci — uruchom Capture bezpośrednio na hoście, aby zobaczyć rzeczywistą aktywność sieciową.",
- "memory": "Zużycie pamięci",
- "netBytesRecv": "{{name}} - Otrzymane bajty",
- "netBytesSent": "{{name}} - Wysłane bajty",
- "netNoTraffic": "Capture raportuje zerowy ruch na wszystkich interfejsach. Jest to normalne, gdy Capture działa w kontenerze z dostępem tylko do interfejsów loopback — aby zobaczyć rzeczywistą aktywność sieciową, uruchom Capture bezpośrednio na hoście.",
- "temp": "Temperatura"
- }
- },
- "fallback": {
- "actionButton": "Utwórz monitor",
- "description": "Kontroluj wydajność serwera, znajdź wąskie gardła i zapewnij niezawodność dzięki monitorowaniu infrastruktury w czasie rzeczywistym.",
- "title": "Brak monitorów infrastruktury"
- },
- "gauges": {
- "cpu": {
- "lowerLabel": "Maksymalna częstotliwość",
- "title": "Wykorzystanie procesora",
- "upperLabel": "Aktualna częstotliwość"
- },
- "disk": {
- "lowerLabel": "Wolne",
- "title": "Użycie dysku {{idx}}",
- "upperLabel": "Używane"
- },
- "memory": {
- "lowerLabel": "Wolne",
- "title": "Zużycie pamięci",
- "upperLabel": "Użyte"
- }
- },
- "header": {
- "description": "Kontroluj wydajność serwera, identyfikuj wąskie gardła i zapewnij niezawodność dzięki monitorowaniu infrastruktury w czasie rzeczywistym.",
- "title": "Monitory infrastruktury"
- },
- "statBoxes": {
- "avgCpuTemperature": "Średnia temperatura CPU",
- "cpuFrequency": "Częstotliwość CPU",
- "cpuLogical": "CPU (Logiczne)",
- "cpuPhysical": "CPU (fizyczny)",
- "disk": "Dysk",
- "memory": "Pamięć",
- "os": "OS"
- },
- "table": {
- "headers": {
- "cpu": "CPU",
- "disk": "Dysk",
- "memory": "Pamięć"
- }
- },
- "tabs": {
- "labels": {
- "network": "Sieć",
- "overview": "Przegląd"
- }
- }
- },
- "logs": {
- "diagnostics": {
- "gauges": {
- "allocatedPercentage": "% przydzielone",
- "availableMemoryPercentage": "% dostępnej pamięci",
- "heapAllocation": "Alokacja sterty",
- "heapUsage": "Zużycie sterty",
- "heapUtilization": "Wykorzystanie sterty",
- "instantCpuUsage": "Bieżące użycie CPU",
- "total": "Razem",
- "used": "Używane",
- "usedSPercentage": "% użycia 1s przez CPU"
- },
- "mongoDBCollectionStats": {
- "bucketCount": "Liczba kubełków: {{count}}",
- "dbUsageTooltip": "{{percentage}}% przestrzeni DB",
- "documents": "Dokumenty: {{count}}",
- "indexSize": "Rozmiar indeksu: {{size}}",
- "storageSize": "Rozmiar magazynu: {{size}}",
- "totalSize": "Całkowity rozmiar: {{size}}"
- },
- "mongoDBStats": {
- "dbName": "Nazwa bazy danych",
- "deletesPerSecond": "Usunięcia/s",
- "host": "Host",
- "insertsPerSecond": "Wstawienia/s",
- "port": "Port",
- "readsPerSecond": "Odczyty/s",
- "readyState": "Stan gotowości",
- "storageSize": "Rozmiar pamięci",
- "updatesPerSecond": "Aktualizacje/s",
- "writesPerSecond": "Łączna liczba zapisów/s"
- },
- "sections": {
- "memoryCpu": {
- "description": "Jak proces wykorzystuje przydzieloną mu pamięć i procesor hosta.",
- "title": "Pamięć i CPU"
- },
- "mongoDBCollectionStats": {
- "description": "Statystyki dla kolekcji MongoDB",
- "title": "Statystyki kolekcji MongoDB"
- },
- "mongoDBStats": {
- "description": "Migawka metryk MongoDB",
- "title": "Statystyki MongoDB"
- },
- "runtimeStats": {
- "description": "Migawka procesu Node.js i maszyny hosta. Aktualizuje się co 5 sekund.",
- "title": "Statystyki środowiska uruchomieniowego"
- }
- },
- "stats": {
- "eventLoopDelay": "Opóźnienie pętli zdarzeń",
- "osMemoryLimit": "Limit pamięci OS",
- "totalHeapSize": "Całkowity rozmiar sterty",
- "uptime": "Czas działania",
- "usedHeapSize": "Użyty rozmiar sterty"
- }
- },
- "failedJobs": "Nieudane zadania",
- "failedJobsExplainer": "Ostatnie niepowodzenia monitora — czas ostatniego błędu, liczba niepowodzeń oraz powód.",
- "fallback": {
- "description": "Aktywność serwera, ostrzeżenia i błędy będą się pojawiać tutaj podczas działania Checkmate.",
- "title": "Brak logów"
- },
- "header": {
- "description": "Sprawdzaj logi serwera, monitoruj kolejkę zadań i diagnozę działania w jednym miejscu.",
- "title": "Dzienniki i kolejka"
- },
- "jobQueue": "Kolejka zadań",
- "jobQueueExplainer": "Wyróżnione wiersze to zadania aktualnie przetwarzane przez pracownika.",
- "logLevelSelect": {
- "label": "Poziom logowania"
- },
- "metrics": {
- "activeJobs": "Aktywne zadania",
- "failingJobs": "Nieudane zadania",
- "jobs": "Zadania",
- "totalFailures": "Łączna liczba błędów",
- "totalRuns": "Łączna liczba uruchomień"
- },
- "noLogs": "Nie znaleziono logów",
- "queueFallback": {
- "description": "Sprawdzenia monitorujące, które są zaplanowane lub trwają, pojawią się tutaj. Pusta kolejka oznacza, że wszystko jest aktualne.",
- "title": "Brak zadań w kolejce"
- },
- "table": {
- "headers": {
- "failCount": "Liczba",
- "failReason": "Powód niepowodzenia",
- "host": "Host",
- "lastFailedAt": "Ostatnia nieudana próba",
- "lastFinishedAt": "Ostatnie zakończenie o",
- "lastSeenAt": "Ostatnio widziano",
- "level": "Poziom",
- "lockedBy": "Zablokowane przez",
- "method": "Metoda",
- "monitorId": "Identyfikator monitora",
- "nextScheduledAt": "Następne uruchomienie o",
- "processId": "Identyfikator procesu",
- "runCount": "Liczba uruchomień",
- "service": "Usługa",
- "state": "Stan",
- "timestamp": "Znacznik czasu",
- "workerId": "ID pracownika",
- "workerMode": "Tryb pracownika"
- }
- },
- "tabs": {
- "diagnostics": "Diagnostyka",
- "logs": "Logi",
- "queue": "Kolejka zadań"
- },
- "workers": "Pracownicy",
- "workersExplainers": "Wyświetla procesy workerów. Identyfikatory workerów są skrócone, najedź, aby zobaczyć pełny identyfikator"
- },
- "maintenanceWindow": {
- "fallback": {
- "actionButton": "Utwórz przerwę techniczną",
- "description": "Zaplanuj planowaną przerwę, aby powiadomienia były wyciszone, a Twój zespół i użytkownicy wiedzieli, czego się spodziewać.",
- "title": "Brak zaplanowanych okien serwisowych"
- },
- "form": {
- "general": {
- "description": "Ustaw nazwę i opcję powtarzania dla przerwy technicznej.",
- "option": {
- "name": {
- "label": "Nazwa",
- "placeholder": "np. Cotygodniowa przerwa techniczna"
- },
- "repeat": {
- "label": "Powtarzaj"
- }
- },
- "title": "Ustawienia ogólne"
- },
- "startDate": {
- "description": "Wybierz datę rozpoczęcia przerwy technicznej.",
- "option": {
- "startDate": {
- "label": "Data rozpoczęcia"
- }
- },
- "title": "Data rozpoczęcia"
- },
- "startTime": {
- "description": "Ustaw czas rozpoczęcia i czas trwania przerwy technicznej. Wszystkie wartości są w UTC",
- "monitors": {
- "description": "Monitory, do których należy zastosować przerwę techniczną",
- "option": {
- "addMonitors": {
- "label": "Dodaj monitory"
- }
- },
- "title": "Monitory"
- },
- "option": {
- "duration": {
- "label": "Czas trwania"
- },
- "startTime": {
- "label": "Czas rozpoczęcia"
- }
- },
- "title": "Czas rozpoczęcia"
- }
- },
- "header": {
- "description": "Zaplanuj planowaną przerwę, aby powiadomienia były wyciszone, a Twój zespół i użytkownicy wiedzieli, czego się spodziewać.",
- "title": "Przerwy techniczne"
- },
- "table": {
- "headers": {
- "nextWindow": "Następna przerwa",
- "repeat": "Powtórz"
- }
- }
- },
- "notFound": {
- "subtitle": "Adres URL nie istnieje lub nie masz do niego dostępu.",
- "title": "Coś się popsuło i nie było mnie słychać..."
- },
- "notifications": {
- "fallback": {
- "actionButton": "Utwórz kanał",
- "description": "Połącz Slacka, Discorda, e-mail, webhooki i inne, aby odpowiednie osoby dowiedziały się o incydentach natychmiast po ich wystąpieniu.",
- "title": "Brak jeszcze kanałów powiadomień"
- },
- "form": {
- "accessToken": {
- "optionAccessToken": "Token dostępu",
- "placeholder": "syt_YWxleF9ob2xsaWRheQ_VmtScmV0U2VjcmV0S2V5_abc123"
- },
- "address": {
- "description": "Adres, na który będą wysyłane powiadomienia.",
- "optionAddress": "Adres",
- "placeholderEmail": "example@example.com",
- "placeholderWebhook": "https://your-server.com/webhook",
- "title": "Adres"
- },
- "homeServer": {
- "optionHomeServer": "Serwer domowy",
- "placeholder": "example.com"
- },
- "matrix": {
- "description": "Skonfiguruj połączenie z serwerem Matrix dla powiadomień.",
- "title": "Konfiguracja Matrix"
- },
- "notificationName": {
- "description": "Opisowa nazwa kanału powiadomień",
- "optionName": "Nazwa kanału",
- "placeholder": "np. alerty produkcyjne",
- "title": "Nazwa kanału"
- },
- "ntfy": {
- "description": "Skonfiguruj serwer ntfy i temat do powiadomień push.",
- "optionServerUrl": "Adres URL serwera",
- "optionTopic": "Temat",
- "placeholderServerUrl": "https://ntfy.sh",
- "placeholderTopic": "checkmate-alerts",
- "title": "konfiguracja ntfy"
- },
- "pagerDuty": {
- "description": "Twój klucz integracyjny PagerDuty do otrzymywania powiadomień.",
- "optionIntegrationKey": "Klucz integracyjny",
- "placeholder": "a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6",
- "title": "Klucz integracji"
- },
- "pushover": {
- "description": "Skonfiguruj Pushover, aby otrzymywać powiadomienia push na swoich urządzeniach.",
- "optionAppToken": "Token API aplikacji",
- "optionUserKey": "Klucz użytkownika",
- "placeholderAppToken": "azGDORePK8gMaC0QOYAMyEEuzJnyUi",
- "placeholderUserKey": "uQiRzpo4DXghDmr9QzzfQu27cmVRsG",
- "title": "Konfiguracja Pushover"
- },
- "roomId": {
- "optionRoomId": "Identyfikator pokoju",
- "placeholder": "!abcdefg:matrix.org"
- },
- "telegram": {
- "description": "Skonfiguruj swojego bota Telegram, aby wysyłał powiadomienia.",
- "optionBotToken": "Token bota",
- "optionChatId": "Identyfikator czatu",
- "placeholderBotToken": "123456789:ABCdefGhIJKlmNoPQRsTUVwxyZ",
- "placeholderChatId": "-1001234567890",
- "title": "Konfiguracja Telegram"
- },
- "twilio": {
- "description": "Skonfiguruj Twilio, aby wysyłać powiadomienia SMS na numer telefonu.",
- "optionAccountSid": "SID konta",
- "optionAuthToken": "Token uwierzytelniający",
- "optionFromNumber": "Numer nadawcy (Twilio)",
- "optionToNumber": "Do numeru (odbiorca)",
- "placeholderAccountSid": "ACxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
- "placeholderAuthToken": "twój_token_uwierzytelniający",
- "placeholderFromNumber": "+15551234567",
- "placeholderToNumber": "+15559876543",
- "title": "Konfiguracja SMS Twilio"
- },
- "type": {
- "description": "Wybierz typ kanału powiadomień do utworzenia.",
- "optionType": "Typ",
- "title": "Typ kanału"
- }
- },
- "header": {
- "description": "Połącz Slacka, Discorda, e-mail, webhooki i inne, aby odpowiednie osoby otrzymywały informacje o incydentach natychmiast po ich wystąpieniu.",
- "title": "Powiadomienia"
- },
- "table": {
- "headers": {
- "destination": "Miejsce docelowe"
- }
- }
- },
- "pageSpeed": {
- "charts": {
- "common": {
- "cls": "Skumulowane przesunięcie układu (CLS)",
- "fcp": "Pierwsze wyrenderowanie treści (FCP)",
- "lcp": "Największe wyrenderowanie treści (LCP)",
- "si": "Indeks szybkości (SI)",
- "tbt": "Całkowity czas blokowania (TBT)"
- },
- "legend": {
- "title": "Raport PageSpeed",
- "weight": "Waga"
- },
- "pie": {
- "title": "Raport wydajności"
- }
- },
- "fallback": {
- "actionButton": "Utwórz monitor",
- "description": "Monitoruj wskaźniki Core Web Vitals i wyniki Lighthouse w czasie, aby wykryć regresje zanim zrobią to użytkownicy.",
- "title": "Brak monitorów PageSpeed"
- },
- "header": {
- "description": "Monitoruj Core Web Vitals i wyniki Lighthouse w czasie, aby wykryć regresje, zanim zauważą je użytkownicy.",
- "title": "Monitory PageSpeed"
- },
- "strategy": {
- "desktop": "Desktop",
- "mobile": "Mobile"
- },
- "table": {
- "headers": {
- "pageSpeedScore": "Wynik PageSpeed"
- }
- }
- },
- "settings": {
- "form": {
- "about": {
- "developedBy": "Opracowane przez Bluewave Labs",
- "title": "Informacje"
- },
- "demoMonitors": {
- "description": "Dodaj przykładowe monitory do celów demonstracyjnych.",
- "title": "Monitory demonstracyjne"
- },
- "email": {
- "description": "Skonfiguruj ustawienia e-mail dla swojego systemu. Służą one do wysyłania powiadomień i alertów.",
- "descriptionTransport": "To tworzy transport SMTP dla NodeMailera",
- "descriptionTransportLink": "Zobacz specyfikacje tutaj",
- "option": {
- "address": {
- "label": "Adres e-mail – używany do uwierzytelniania",
- "placeholder": "you@example.com"
- },
- "connectionHost": {
- "label": "Host połączenia e-mail – nazwa hosta używana w powitaniu HELO/EHLO",
- "placeholder": "localhost"
- },
- "displayName": {
- "label": "Nadawca (opcjonalnie) - Nazwa wyświetlana w polu 'Od'",
- "placeholder": "Powiadomienia Checkmate"
- },
- "host": {
- "label": "Host e-mail – nazwa hosta lub adres IP do połączenia",
- "placeholder": "smtp.gmail.com"
- },
- "ignoreTLS": {
- "label": "Wyłącz STARTTLS: Nie używaj TLS, nawet jeśli serwer go obsługuje"
- },
- "password": {
- "label": "Hasło e-mail – Hasło do uwierzytelniania",
- "labelSet": "Hasło jest ustawione. Kliknij Resetuj, aby je zmienić.",
- "placeholder": "Wprowadź swoje hasło"
- },
- "pool": {
- "label": "Włącz łączenie połączeń: Wykorzystaj istniejące połączenia, aby poprawić wydajność"
- },
- "port": {
- "label": "Port e-mail — port do połączenia",
- "placeholder": "587"
- },
- "rejectUnauthorized": {
- "label": "Odrzucaj nieprawidłowe certyfikaty: Odrzucaj połączenia z certyfikatami samopodpisanymi lub niezaufanymi"
- },
- "requireTLS": {
- "label": "Wymuś STARTTLS: Wymagaj aktualizacji do TLS, odmów w przypadku braku obsługi"
- },
- "secure": {
- "label": "Użyj SSL (zalecane): Szyfruj połączenie za pomocą SSL/TLS"
- },
- "tlsServername": {
- "label": "Nazwa serwera TLS - opcjonalna nazwa hosta do weryfikacji TLS, gdy hostem jest adres IP",
- "placeholder": "example.com"
- },
- "user": {
- "label": "Użytkownik e-mail — Nazwa użytkownika do uwierzytelniania, zastępuje adres e-mail, jeśli została podana",
- "placeholder": "Pozostaw puste, jeśli nie jest wymagane"
- }
- },
- "title": "Ustawienia e-mail"
- },
- "exportMonitors": {
- "title": "Eksportuj monitory"
- },
- "importExportMonitors": {
- "description": "Importuj lub eksportuj dane monitorów jako plik JSON w celu wykonania kopii zapasowej lub przeniesienia.",
- "title": "Importuj / Eksportuj monitory"
- },
- "pagespeed": {
- "description": "Wprowadź swój klucz API Google PageSpeed, aby włączyć monitorowanie Google PageSpeed. Kliknij Resetuj, aby zaktualizować klucz.",
- "option": {
- "apiKey": {
- "label": "Klucz API PageSpeed",
- "labelSet": "Klucz API jest ustawiony. Kliknij Resetuj, aby go zmienić.",
- "placeholder": "Wprowadź swój klucz API Google PageSpeed"
- }
- },
- "title": "Klucz API Google PageSpeed"
- },
- "removeMonitors": {
- "description": "Usuń wszystkie monitory z systemu.",
- "dialog": {
- "description": "Tej operacji nie można cofnąć.",
- "paragraph": "Cała historia sprawdzeń, incydenty, zaplanowane okna konserwacyjne oraz wszystkie strony statusowe, które odnosiły się do tych monitorów, również zostaną usunięte. Najpierw wyeksportuj swoje monitory, aby zachować kopię zapasową ich danych.",
- "title": "Usunąć wszystkie monitory?"
- },
- "title": "Resetowanie systemu"
- },
- "retention": {
- "description": "Ustaw, jak długo dane kontroli będą przechowywane przed ich automatycznym usunięciem.",
- "option": {
- "days": {
- "label": "Okres przechowywania (dni)",
- "unlimited": "Bez ograniczeń"
- }
- },
- "title": "Sprawdź retencję"
- },
- "stats": {
- "description": "Wyczyść całą historię monitorowania i statystyki dla Twojego zespołu. Ta operacja jest nieodwracalna.",
- "dialog": {
- "description": "Tej operacji nie można cofnąć",
- "title": "Wyczyścić całą historię monitorowania?"
- },
- "option": {
- "clear": {
- "label": "Wyczyść wszystkie statystyki. Tej operacji nie można cofnąć."
- }
- },
- "title": "Monitoruj historię"
- },
- "thresholds": {
- "description": "Ustaw globalne progi CPU, pamięci, dysku i temperatury dla monitorowania infrastruktury. Mają one zastosowanie do wszystkich monitorów sprzętu, o ile nie zostaną zastąpione.",
- "option": {
- "cpu": {
- "label": "Próg CPU (%)",
- "placeholder": "1 - 100"
- },
- "disk": {
- "label": "Próg dysku (%)",
- "placeholder": "1 - 100"
- },
- "memory": {
- "label": "Próg pamięci (%)",
- "placeholder": "1 - 100"
- },
- "temperature": {
- "label": "Próg temperatury (°C)",
- "placeholder": "1 - 150"
- }
- },
- "title": "Progi globalne"
- },
- "timezone": {
- "description": "Wybierz strefę czasową używaną do wyświetlania dat i godzin w całej aplikacji.",
- "option": {
- "timezone": {
- "label": "Wyświetlanie strefy czasowej"
- }
- },
- "title": "Wyświetlana strefa czasowa"
- },
- "ui": {
- "description": "Przełączaj między trybem jasnym i ciemnym, zmieniaj język lub dostosuj typ wyświetlania wykresu.",
- "option": {
- "chartType": {
- "heatmap": "Mapa ciepła",
- "histogram": "Histogram",
- "label": "Typ wykresu"
- },
- "language": {
- "label": "Język"
- },
- "theme": {
- "dark": "Ciemny",
- "label": "Tryb motywu",
- "light": "Jasny"
- }
- },
- "title": "Wygląd"
- },
- "url": {
- "description": "Wyświetl adres IP lub URL monitora na publicznej stronie Status. Jeśli opcja jest wyłączona, wyświetlana będzie tylko nazwa monitora, aby chronić poufne informacje.",
- "option": {
- "showURL": {
- "disabled": "Wyłączone",
- "enabled": "Włączone",
- "label": "Wyświetl IP/URL na stronie statusu"
- }
- },
- "title": "Monitoruj IP/URL na stronie statusu"
- },
- "validation": {
- "errorMessage": "Proszę poprawić następujące błędy walidacji:"
- }
- },
- "header": {
- "description": "Skonfiguruj SMTP, integracje, retencję oraz globalne zachowanie aplikacji.",
- "title": "Ustawienia"
- }
- },
- "statusPages": {
- "deleteSuccess": "Strona statusu została pomyślnie usunięta",
- "details": {
- "empty": {
- "addMonitor": "Dodaj monitor, aby rozpocząć",
- "title": "Nie ma tu jeszcze nic"
- }
- },
- "editorial": {
- "allUp": "Wszystkie systemy działają prawidłowo.",
- "reportTitle": "Raport serwisowy"
- },
- "fallback": {
- "actionButton": "Utwórz stronę statusu",
- "description": "Opublikuj publiczną stronę, która pokazuje w czasie rzeczywistym historię dostępności i incydentów dla Twoich klientów i interesariuszy.",
- "title": "Brak stron statusu"
- },
- "footer": {
- "poweredBy": "Powered by"
- },
- "form": {
- "access": {
- "description": "Jeśli Twoja strona statusu jest gotowa, możesz oznaczyć ją jako opublikowaną.",
- "option": {
- "published": {
- "name": "Opublikowane i widoczne publicznie"
- }
- },
- "title": "Dostęp"
- },
- "appearance": {
- "description": "Zdefiniuj domyślny wygląd i styl swojej publicznej strony statusu.",
- "option": {
- "color": {
- "label": "Kolor marki"
- },
- "logo": {
- "label": "Logo"
- }
- },
- "title": "Wygląd"
- },
- "basicInfo": {
- "description": "Zdefiniuj nazwę firmy oraz subdomenę, na którą wskazuje Twoja strona statusu.",
- "option": {
- "customDomain": {
- "helper": "Opcjonalne. Skieruj rekord DNS na tę instancję Checkmate, aby wyświetlać stronę statusu pod własną domeną.",
- "label": "Niestandardowa domena",
- "placeholder": "status.przyklad.com"
- },
- "name": {
- "label": "Nazwa firmy",
- "placeholder": "Acme Inc."
- },
- "url": {
- "label": "Adres Twojej strony statusowej",
- "placeholder": "moja-strona-statusowa"
- }
- },
- "title": "Podstawowe informacje"
- },
- "customCSS": {
- "description": "Dodaj własny CSS, aby precyzyjnie dostosować wygląd swojej publicznej strony statusu.",
- "option": {
- "customCSS": {
- "label": "Niestandardowy CSS",
- "placeholder": "footer { display: none; }"
- }
- },
- "title": "Niestandardowy CSS"
- },
- "features": {
- "description": "Skonfiguruj, jakie informacje są wyświetlane na Twojej stronie statusu.",
- "option": {
- "showAdminLoginLink": {
- "label": "Pokaż link do logowania administratora"
- },
- "showCharts": {
- "label": "Pokaż wykresy czasu odpowiedzi"
- },
- "showInfrastructure": {
- "label": "Pokaż metryki infrastruktury"
- },
- "showUptimePercentage": {
- "label": "Pokaż procent czasu dostępności"
- }
- },
- "title": "Funkcje"
- },
- "monitors": {
- "description": "Wybierz monitory do wyświetlenia na swojej stronie statusu.",
- "noMonitors": "Nie wybrano monitorów",
- "option": {
- "monitors": {
- "label": "Wybierz monitory",
- "placeholder": "Wyszukaj i wybierz monitory..."
- }
- },
- "title": "Monitory"
- },
- "theme": {
- "description": "Wybierz styl wizualny dla swojej publicznej strony statusu.",
- "label": "Motyw",
- "options": {
- "bold": {
- "description": "Duża czcionka i wyraźne odstępy. Idealne dla stron nastawionych na budowanie marki.",
- "name": "Pogrubiony"
- },
- "editorial": {
- "description": "Nagłówki szeryfowe i cienkie linie oddzielające. Wyrazisty i spokojny.",
- "name": "Redakcyjny"
- },
- "minimal": {
- "description": "Prosty i kompaktowy, zawierający tylko najważniejsze elementy.",
- "name": "Minimalistyczny"
- },
- "modern": {
- "description": "SaaS'owy z pulsującą kropką statusu i delikatnie zanikającymi kartami.",
- "name": "Nowoczesny"
- },
- "refined": {
- "description": "Czysty, profesjonalny, zbliżony do klasycznego wyglądu Checkmate.",
- "name": "Wyrafinowany"
- }
- },
- "title": "Motyw strony"
- },
- "themeMode": {
- "auto": "Automatycznie (zgodnie z urządzeniem odwiedzającego)",
- "dark": "Tylko ciemny",
- "label": "Tryb motywu",
- "light": "Tylko jasny"
- },
- "timezone": {
- "description": "Wybierz strefę czasową, w której będzie wyświetlana Twoja strona statusu.",
- "option": {
- "timezone": {
- "label": "Strefa czasowa",
- "placeholder": "Wybierz strefę czasową..."
- }
- },
- "title": "Strefa czasowa"
- }
- },
- "header": {
- "description": "Publikuj publiczne strony pokazujące aktualny czas działania i historię incydentów swoim klientom i interesariuszom.",
- "title": "Strony statusu"
- },
- "monitorsList": {
- "chart": {
- "avg": "śr. {{value}}ms",
- "downTooltip": "Niedostępny",
- "heatmapAria": "Heatmapa czasu odpowiedzi",
- "max": "maks. {{value}}ms"
- },
- "chartTypeHeatmap": "Heatmapa",
- "chartTypeHistogram": "Histogram",
- "infrastructure": {
- "cpu": "CPU",
- "disk": "Dysk",
- "memory": "RAM",
- "memoryText": "Pamięć",
- "title": "Infrastruktura",
- "total": "Łącznie",
- "usage": "Użycie",
- "used": "Używane"
- },
- "noData": "Brak dostępnych danych",
- "status": {
- "breached": "Przekroczono próg",
- "down": "Niedostępny",
- "initializing": "Inicjowanie",
- "maintenance": "Konserwacja",
- "paused": "Wstrzymano",
- "up": "Działa poprawnie"
- },
- "uptime": {
- "responseTime": "Czas odpowiedzi",
- "title": "Czas pracy"
- }
- },
- "preview": {
- "title": "Podgląd strony statusu"
- },
- "statusBar": {
- "allBreached": "Wszystkie systemy przekroczyły progi",
- "allDown": "Wszystkie systemy są niedostępne",
- "allInitializing": "Wszystkie systemy są inicjalizowane",
- "allMaintenance": "Wszystkie systemy są w trakcie konserwacji",
- "allPaused": "Wszystkie systemy są wstrzymane",
- "allUp": "Wszystkie systemy działają poprawnie",
- "breached": "Niektóre systemy przekroczyły progi",
- "breachedAndDown": "Niektóre systemy przekroczyły progi i są nieaktywne",
- "degraded": "Niektóre systemy mają problemy",
- "initializing": "Niektóre systemy są inicjalizowane",
- "maintenance": "Niektóre systemy są w trakcie konserwacji",
- "maintenanceAndDown": "Niektóre systemy są niedostępne podczas konserwacji",
- "monitoringSummary": "Monitorowanie {{count}} usługi · Aktualizacje co 10 sekund",
- "monitoringSummary_other": "Monitorowanie {{count}} usług · Aktualizacje co 10 sekund",
- "noMonitors": "Nie skonfigurowano żadnych monitorów",
- "partiallyPaused": "Niektóre systemy są wstrzymane",
- "unknown": "Nie można określić stanu systemu"
- },
- "table": {
- "headers": {
- "name": "Nazwa strony statusu",
- "url": "Publiczny adres URL"
- },
- "published": "Opublikowano",
- "unpublished": "Nieopublikowane"
- },
- "title": "Strony statusu"
- },
- "tags": {
- "fallback": {
- "actionButton": "Utwórz tag",
- "description": "Organizuj swoje monitory za pomocą tagów. Po utworzeniu tagu i przypisaniu go do monitora, będziesz mógł filtrować według tego tagu w całej aplikacji i zobaczyć szczegóły tagu tutaj.",
- "title": "Brak jeszcze tagów"
- },
- "form": {
- "color": {
- "description": "Wybierz kolor dla swojego tagu",
- "optionName": "Kolor tagu",
- "placeholder": "np. #13715B",
- "title": "Kolor etykiety"
- },
- "name": {
- "description": "Wybierz nazwę dla swojego tagu",
- "optionName": "Nazwa tagu",
- "placeholder": "np. Grupa 1",
- "title": "Nazwa tagu"
- }
- },
- "header": {
- "description": "Porządkuj swoje monitory za pomocą tagów",
- "title": "Tagi"
- },
- "table": {
- "headers": {
- "appearance": "Wygląd",
- "color": "Kolor"
- }
- }
- },
- "uptime": {
- "fallback": {
- "actionButton": "Utwórz monitor",
- "description": "Monitoruj punkty końcowe HTTP, pingowania, kontenery i porty — i otrzymuj powiadomienia natychmiast, gdy coś przestanie działać.",
- "title": "Brak monitorów dostępności"
- },
- "filters": {
- "activeTags": "Aktywne tagi",
- "search": {
- "placeholder": "Wyszukaj monitory..."
- }
- },
- "header": {
- "description": "Monitoruj punkty końcowe HTTP, ping, kontenery i porty — i otrzymuj powiadomienia natychmiast, gdy coś przestanie działać.",
- "title": "Monitory dostępności"
- },
- "table": {
- "headers": {
- "responseTime": "Czas odpowiedzi",
- "uptime": "Czas działania %"
- }
- }
- }
- }
+ "common": {
+ "alerts": {
+ "pageSpeedApiKey": {
+ "content": "Ostrzeżenie: Nie dodałeś jeszcze klucza Google PageSpeed API. Przejdź do Ustawienia , aby go dodać. Bez niego monitor PageSpeed nie będzie działał."
+ }
+ },
+ "appName": "Checkmate",
+ "auth": {
+ "roles": {
+ "admin": "Administrator",
+ "demo": "Demo",
+ "superadmin": "Super administrator",
+ "user": "Użytkownik"
+ }
+ },
+ "breadcrumbs": {
+ "details": "Szczegóły",
+ "home": "Strona główna"
+ },
+ "buttons": {
+ "addDemo": "Dodaj demo monitory",
+ "addMember": "Dodaj użytkownika",
+ "back": "Wstecz",
+ "cancel": "Anuluj",
+ "clear": "Wyczyść",
+ "clearFilters": "Wyczyść filtry",
+ "close": "Zamknij",
+ "configure": "Konfiguruj",
+ "confirm": "Potwierdź",
+ "create": "Utwórz",
+ "delete": "Usuń",
+ "exportToJSON": "Eksportuj do JSON",
+ "flushQueue": "Opróżnij kolejkę",
+ "generateToken": "Wygeneruj token",
+ "importFromJSON": "Importuj z JSON",
+ "incidents": "Incydenty",
+ "inviteMember": "Zaproś użytkownika",
+ "next": "Dalej",
+ "notFound": "Przejdź do głównego panelu",
+ "pause": "Wstrzymaj",
+ "removeMonitors": "Usuń monitory",
+ "removeUser": "Usuń użytkownika",
+ "reset": "Zresetuj",
+ "resetPassword": "Zresetuj hasło",
+ "resume": "Wznów",
+ "save": "Zapisz",
+ "sendInvite": "Wyślij zaproszenie",
+ "sendTestEmail": "Wyślij testowy e-mail",
+ "test": "Test",
+ "testNotifications": "Przetestuj powiadomienia",
+ "toggleTheme": "Przełącza jasny i ciemny"
+ },
+ "charts": {
+ "histogram": {
+ "avg": "Średnia: {{value}} ms",
+ "max": "Maks: {{value}} ms"
+ },
+ "labels": {
+ "averageResponseTime": "Średni czas odpowiedzi",
+ "downtime": "Czas przestoju",
+ "high": "Wysoki",
+ "low": "Niski",
+ "uptime": "Czas pracy"
+ }
+ },
+ "dialogs": {
+ "delete": {
+ "description": "Tej operacji nie można cofnąć.",
+ "title": "Czy na pewno chcesz to usunąć?"
+ }
+ },
+ "form": {
+ "email": {
+ "option": {
+ "email": {
+ "label": "Email",
+ "placeholder": "you@example.com"
+ }
+ }
+ },
+ "name": {
+ "option": {
+ "firstName": {
+ "label": "Imię",
+ "placeholder": "Wpisz swoje imię"
+ },
+ "lastName": {
+ "label": "Nazwisko",
+ "placeholder": "Wprowadź swoje nazwisko"
+ }
+ }
+ },
+ "role": {
+ "option": {
+ "role": {
+ "label": "Role"
+ }
+ }
+ }
+ },
+ "labels": {
+ "active": "Aktywny",
+ "idle": "Bezczynny",
+ "na": "Brak danych",
+ "paused": "Wstrzymano",
+ "resolved": "Rozwiązano",
+ "responseTime": "Czas reakcji",
+ "running": "Uruchomiony"
+ },
+ "statusCode": {
+ "down": "Niedostępny",
+ "networkError": {
+ "dns": "Nie można rozpoznać nazwy hosta (błąd wyszukiwania DNS).",
+ "refused": "Serwer odrzucił połączenie (port zamknięty lub usługa nie nasłuchuje).",
+ "reset": "Połączenie zostało zresetowane, zanim otrzymano odpowiedź.",
+ "timeout": "Serwer nie odpowiedział na czas (żądanie przekroczyło limit czasu).",
+ "tls": "Nie powiodło się uzgadnianie TLS lub SSL (problem z certyfikatem lub szyfrowaniem).",
+ "unknown": "Nie otrzymano odpowiedzi HTTP z docelowego serwera."
+ }
+ },
+ "table": {
+ "empty": "Brak danych do wyświetlenia",
+ "headers": {
+ "actions": "Akcje",
+ "active": "Aktywny",
+ "dateTime": "Data i godzina",
+ "interval": "Przedział",
+ "message": "Wiadomość",
+ "monitor": "Monitor",
+ "monitorId": "ID monitora",
+ "name": "Nazwa",
+ "responseTime": "Czas odpowiedzi",
+ "status": "Status",
+ "tags": "Tagi",
+ "type": "Typ",
+ "url": "Url"
+ }
+ }
+ },
+ "components": {
+ "headerStatusPageControls": {
+ "publicLink": "Publiczny link"
+ },
+ "headerTimeRange": {
+ "description": {
+ "day": "Wyświetlanie statystyk z ostatnich 24 godzin.",
+ "month": "Wyświetlanie statystyk z ostatnich 30 dni.",
+ "recent": "Wyświetlanie statystyk z ostatnich 2 godzin.",
+ "week": "Wyświetlanie statystyk z ostatnich 7 dni."
+ },
+ "labels": {
+ "day": "Dzień",
+ "month": "Miesiąc",
+ "recent": "Najnowsze",
+ "week": "Tydzień"
+ }
+ },
+ "imageUpload": {
+ "clickToUpload": "Kliknij, aby przesłać",
+ "dragAndDrop": "lub przeciągnij i upuść",
+ "errors": {
+ "invalidFileFormat": "Nieobsługiwany format pliku!",
+ "invalidFileSize": "Rozmiar pliku jest za duży!"
+ },
+ "maxSize": "Maksymalny rozmiar",
+ "orDragAndDrop": "lub przeciągnij i upuść",
+ "supportedFormats": "Obsługiwane formaty"
+ },
+ "offlineBanner": {
+ "reconnected": "Połączenie przywrócone",
+ "retry": "Ponów próbę",
+ "retrying": "Ponawianie próby...",
+ "serverUnreachable": "Nie można połączyć się z serwerem"
+ },
+ "pageHeader": {
+ "links": {
+ "changelog": "Dziennik zmian",
+ "discussions": "Dyskusje",
+ "docs": "Dokumentacja",
+ "support": "Pomoc"
+ }
+ },
+ "sidebar": {
+ "accountMenu": {
+ "password": "Hasło",
+ "profile": "Profil",
+ "team": "Zespół"
+ },
+ "authFooter": {
+ "logOut": "Wyloguj się",
+ "navControls": "Sterowanie",
+ "roles": {
+ "admin": "Administrator",
+ "demoUser": "Użytkownik demo",
+ "superAdmin": "Super administrator",
+ "user": "Użytkownik"
+ }
+ },
+ "bottomMenu": {
+ "changelog": "Dziennik zmian",
+ "discussions": "Dyskusje",
+ "docs": "Dokumentacja",
+ "support": "Wsparcie"
+ },
+ "menu": {
+ "checks": "Kontrole",
+ "incidents": "Incydenty",
+ "infrastructure": "Infrastruktura",
+ "logs": "Dzienniki",
+ "maintenance": "Konserwacja",
+ "notifications": "Powiadomienia",
+ "pagespeed": "Pagespeed",
+ "settings": "Ustawienia",
+ "statusPages": "Strony statusu",
+ "tags": "Tagi",
+ "uptime": "Czas działania"
+ },
+ "starPrompt": {
+ "description": "Zobacz najnowsze wydania i pomóż rozwijać społeczność na GitHub",
+ "title": "Zagwiazdkuj Checkmate"
+ }
+ },
+ "starPrompt": {
+ "description": "Zobacz najnowsze wydania i wspieraj rozwój społeczności na GitHub",
+ "title": "Zagwiazdkuj Checkmate"
+ }
+ },
+ "pages": {
+ "account": {
+ "form": {
+ "currentPassword": {
+ "description": "Wprowadź swoje obecne hasło, aby potwierdzić swoją tożsamość",
+ "option": {
+ "label": "Obecne hasło",
+ "placeholder": "Wprowadź obecne hasło"
+ },
+ "title": "Obecne hasło"
+ },
+ "deleteAccount": {
+ "description": "Ta akcja jest nieodwracalna i nie można jej cofnąć",
+ "title": "Usuń konto"
+ },
+ "name": {
+ "description": "Zaktualizuj swoje dane osobowe",
+ "title": "Imię"
+ },
+ "newPassword": {
+ "description": "Wybierz silne hasło składające się z co najmniej 8 znaków",
+ "option": {
+ "confirm": {
+ "label": "Potwierdź hasło",
+ "placeholder": "Potwierdź nowe hasło"
+ },
+ "newPassword": {
+ "label": "Nowe hasło",
+ "placeholder": "Wprowadź nowe hasło"
+ }
+ },
+ "title": "Nowe hasło"
+ },
+ "photo": {
+ "description": "Prześlij zdjęcie profilowe",
+ "title": "Zdjęcie profilowe"
+ }
+ },
+ "header": {
+ "description": "Zarządzaj swoim profilem, hasłem, zespołem i preferencjami przestrzeni roboczej.",
+ "title": "Konto"
+ },
+ "tabs": {
+ "password": "Hasło",
+ "profile": "Profil",
+ "team": "Zespół"
+ },
+ "team": {
+ "addMember": {
+ "description": "Utwórz nowe konto użytkownika. Następnie przekaż dane logowania w bezpieczny sposób.",
+ "title": "Zarejestruj nowego członka zespołu"
+ },
+ "fallback": {
+ "description": "Zaproś członków zespołu przez e-mail lub dodaj ich bezpośrednio. Po dołączeniu pojawią się tutaj wraz ze swoją rolą.",
+ "title": "Brak członków zespołu"
+ },
+ "filter": {
+ "admin": "Administrator",
+ "all": "Wszystkie role",
+ "member": "Użytkownik",
+ "placeholder": "Filtruj według roli"
+ },
+ "invite": {
+ "description": "Wyślij zaproszenie do dołączenia do swojego zespołu",
+ "email": {
+ "label": "Adres e-mail",
+ "placeholder": "Wprowadź adres e-mail"
+ },
+ "linkLabel": "Link do zaproszenia",
+ "role": {
+ "label": "Rola",
+ "placeholder": "Wybierz rolę"
+ },
+ "title": "Zaproś członka zespołu"
+ },
+ "table": {
+ "headers": {
+ "created": "Utworzono dnia",
+ "email": "E-mail",
+ "role": "Rola"
+ }
+ }
+ }
+ },
+ "auth": {
+ "brandPanel": {
+ "description": "Monitorowanie działania, reakcji i incydentów w czasie rzeczywistym - do samodzielnego hostowania i na własność.",
+ "eyebrow": "Monitoring infrastruktury open source",
+ "tagline": "Obserwuj każdy ruch swoich serwerów."
+ },
+ "common": {
+ "form": {
+ "option": {
+ "confirmPassword": {
+ "label": "Potwierdź hasło"
+ },
+ "email": {
+ "label": "E-mail",
+ "placeholder": "me@example.com"
+ },
+ "password": {
+ "label": "Hasło",
+ "placeholder": "********"
+ }
+ }
+ },
+ "passwordRules": {
+ "length": {
+ "beginning": "Musi mieć co najmniej",
+ "highlighted": "8 znaków"
+ },
+ "lowercase": {
+ "beginning": "Musi zawierać co najmniej",
+ "highlighted": "jedną małą literę"
+ },
+ "match": {
+ "beginning": "Hasła",
+ "highlighted": "musi pasować"
+ },
+ "number": {
+ "beginning": "Musi zawierać co najmniej",
+ "highlighted": "jedną cyfrę"
+ },
+ "special": {
+ "beginning": "Musi zawierać co najmniej",
+ "highlighted": "jeden znak specjalny (!?@#$%^&*()-_=+[]{}|;:'\",./\\~`)"
+ },
+ "uppercase": {
+ "beginning": "Musi zawierać co najmniej",
+ "highlighted": "jedną wielką literę"
+ }
+ }
+ },
+ "forgotPassword": {
+ "links": {
+ "login": {
+ "linkText": "zaloguj się",
+ "text": "Wróć do"
+ }
+ },
+ "submit": "Wyślij prośbę o odzyskanie hasła",
+ "subtitle": "Nie martw się, wyślemy Ci instrukcje resetowania.",
+ "title": "Zapomniałeś hasła?"
+ },
+ "login": {
+ "links": {
+ "forgotPassword": {
+ "linkText": "Resetuj hasło",
+ "text": "Zapomniałeś hasła?"
+ },
+ "register": {
+ "linkText": "Zarejestruj się tutaj",
+ "text": "Nie masz konta?"
+ }
+ },
+ "submit": "Zaloguj się",
+ "subtitle": "Zaloguj się, aby kontynuować",
+ "title": "Witamy ponownie w Checkmate!"
+ },
+ "register": {
+ "setupNotice": "Ta strona jest dostępna tylko podczas początkowej konfiguracji. Po zarejestrowaniu super administratora można zapraszać dodatkowych użytkowników z poziomu ustawień zespołu.",
+ "submit": "Zarejestruj się",
+ "subtitle": "Zarejestruj się, aby rozpocząć",
+ "title": "Witamy ponownie w Checkmate!"
+ },
+ "setNewPassword": {
+ "subtitle": "Twoje nowe hasło musi różnić się od wcześniej używanych haseł.",
+ "title": "Zresetuj hasło"
+ }
+ },
+ "checks": {
+ "fallback": {
+ "description": "Po utworzeniu monitora wszystkie statusy i czasy odpowiedzi będą wyświetlane tutaj jako checki.",
+ "title": "Brak checków"
+ },
+ "header": {
+ "description": "Każde sprawdzenie wykonywane przez Checkmate na Twoich monitorach pojawia się tutaj wraz z kodem statusu i czasem odpowiedzi.",
+ "title": "Checki"
+ },
+ "selects": {
+ "monitor": {
+ "all": "Wszystkie monitory"
+ },
+ "status": {
+ "all": "Wszystkie",
+ "down": "Niedostępny",
+ "up": "Dostępny"
+ }
+ },
+ "table": {
+ "empty": "Brak awarii w tym przedziale czasowym",
+ "headers": {
+ "location": "Lokalizacja",
+ "statusCode": "Kod statusu"
+ }
+ }
+ },
+ "common": {
+ "monitors": {
+ "actions": {
+ "bulkSelected_one": "Wybrano {{count}} monitor",
+ "bulkSelected_other": "Wybrano {{count}} monitor(ów)",
+ "configure": "Skonfiguruj",
+ "delete": "Usuń",
+ "details": "Szczegóły",
+ "generateToken": "Wygeneruj token",
+ "incidents": "Incydenty",
+ "inviteMember": "Zaproś członka",
+ "openSite": "Otwórz stronę",
+ "pause": "Wstrzymaj",
+ "resume": "Wznów",
+ "selectAll": "Zaznacz wszystkie monitory",
+ "selectMonitor": "Wybierz {{name}}"
+ },
+ "bulkPause": {
+ "alreadyPaused_one": "Wybrany monitor jest już wstrzymany",
+ "alreadyPaused_other": "Wszystkie wybrane monitory są już wstrzymane",
+ "alreadyRunning_one": "Wybrany monitor jest już uruchomiony",
+ "alreadyRunning_other": "Wszystkie wybrane monitory są już uruchomione",
+ "partialFailure": "Zaktualizowano {{successCount}} monitor(ów), ale {{failedCount}} nie udało się zsynchronizować z kolejką zadań",
+ "paused_one": "{{count}} monitor(ów) wstrzymany",
+ "paused_other": "Wstrzymano {{count}} monitor(ów)",
+ "resumed_one": "{{count}} monitor(ów) wznowiony",
+ "resumed_other": "Wznowiono {{count}} monitor(ów)"
+ },
+ "monitorTypes": {
+ "optionDns": "DNS",
+ "optionDocker": "Docker",
+ "optionGame": "Gra",
+ "optionGrpc": "gRPC",
+ "optionHardware": "Infrastruktura",
+ "optionHttp": "HTTP(S)",
+ "optionPagespeed": "PageSpeed",
+ "optionPing": "Ping",
+ "optionPort": "Port",
+ "optionWebSocket": "WebSocket"
+ },
+ "statBoxes": {
+ "activeFor": "Aktywne przez",
+ "certificateExpiry": "Wygaśnięcie certyfikatu",
+ "lastCheck": "Ostatnia kontrola",
+ "lastResponseTime": "Ostatni czas odpowiedzi",
+ "serviceIsDown": "Usługa nie działa"
+ },
+ "status": {
+ "breached": "przekroczono próg",
+ "down": "niedostępny",
+ "initializing": "inicjowanie",
+ "maintenance": "konserwacja",
+ "paused": "wstrzymano",
+ "total": "łącznie",
+ "up": "działa"
+ }
+ }
+ },
+ "createMonitor": {
+ "form": {
+ "advanced": {
+ "description": "Opcjonalne ustawienia dla zaawansowanych przypadków użycia",
+ "option": {
+ "advancedMatching": {
+ "label": "Użyj zaawansowanego dopasowania"
+ },
+ "customUpCodes": {
+ "description": "Dodaj dowolne kody inne niż 2xx, które również powinny być uznane za pomyślne.",
+ "label": "Zaakceptowane kody statusu",
+ "removeAriaLabel": "Usuń kod statusu"
+ },
+ "expectedValue": {
+ "label": "Oczekiwana wartość"
+ },
+ "jsonPath": {
+ "description": "To wyrażenie zostanie ocenione względem danych JSON odpowiedzi, a wynik będzie użyty do porównania z oczekiwaną wartością. Zobacz jmespath.org po dokumentację języka zapytań.",
+ "label": "Wyrażenie JSONPath"
+ },
+ "matchMethod": {
+ "equal": "Równe",
+ "include": "Uwzględnij",
+ "label": "Metoda dopasowania",
+ "regex": "Wyrażenie regularne"
+ },
+ "method": {
+ "description": {
+ "GET": "GET pobiera pełną odpowiedź, łącznie z treścią. Zaawansowane dopasowywanie jest w pełni obsługiwane",
+ "HEAD": "HEAD pobiera tylko nagłówki odpowiedzi, aby zmniejszyć obciążenie testowanego serwera. Wspiera dopasowywanie status kodu. Nie obsługuje zaawansowanych dopasowywań, ponieważ nie ma treści odpowiedzi."
+ },
+ "label": "Metoda żądania"
+ }
+ },
+ "title": "Ustawienia zaawansowane"
+ },
+ "frequency": {
+ "description": "Jak często chcesz sprawdzać status tego monitora?",
+ "option": {
+ "frequency": {
+ "label": "Sprawdź częstotliwość",
+ "value": {
+ "fifteenMinutes": "15 minut",
+ "fifteenSeconds": "15 sekund",
+ "fiveMinutes": "5 minut",
+ "fourMinutes": "4 minuty",
+ "oneMinute": "1 minuta",
+ "tenMinutes": "10 minut",
+ "thirtyMinutes": "30 minut",
+ "thirtySeconds": "30 sekund",
+ "threeMinutes": "3 minuty",
+ "twoMinutes": "2 minuty"
+ }
+ }
+ },
+ "title": "Częstotliwość sprawdzania"
+ },
+ "general": {
+ "description": {
+ "dns": "Monitoruj, czy określony serwer DNS odpowiada.",
+ "docker": "Wprowadź nazwę lub identyfikator kontenera Docker. Możesz użyć nazwy kontenera (np. my-app) lub identyfikatora kontenera (pełny 64-znakowy identyfikator lub skrócony identyfikator).",
+ "game": "Wprowadź adres IP lub nazwę hosta oraz numer portu do pingowania (np. 192.168.1.100 lub example.com) i wybierz typ gry. Niektóre gry wymagają włączenia zapytań serwera w pliku konfiguracyjnym — sprawdź dokumentację swojego serwera gry. Zobacz listę obsługiwanych gier , aby uzyskać szczegóły dotyczące protokołu.",
+ "grpc": "Wprowadź nazwę hosta i port serwera gRPC, opcjonalnie określ nazwę usługi do sprawdzania stanu oraz dodaj nazwę.",
+ "hardware": "Monitoruj użycie procesora, pamięci, dysku oraz temperaturę w swojej infrastrukturze. Adres URL punktu końcowego Capture musi zawierać pełną ścieżkę /api/v1/metrics (np. http://192.168.1.10:5001/api/v1/metrics). Dla klucza autoryzacyjnego wklej tylko token — nie dodawaj 'Bearer'.",
+ "http": "Wprowadź adres URL lub IP do monitorowania (np. https://example.com/ lub 192.168.1.100) oraz dodaj nazwę, która pojawi się na dashboardzie.",
+ "pagespeed": "Monitoruj wydajność ładowania strony, Core Web Vitals oraz wyniki optymalizacji swojej witryny.",
+ "ping": "Wprowadź adres IP lub nazwę hosta do pingowania (np. 192.168.1.100 lub example.com) oraz dodaj nazwę na dashboardzie.",
+ "port": "Wprowadź adres URL lub IP serwera, numer portu oraz nazwę na dashboardzie.",
+ "websocket": "Wprowadź adres URL WebSocket do monitorowania (np. wss://example.com/socket) i dodaj nazwę."
+ },
+ "option": {
+ "captureUrl": {
+ "label": "Punkt końcowy przechwytywania",
+ "placeholder": "http://192.168.1.10:5001/api/v1/metrics"
+ },
+ "container": {
+ "label": "Nazwa/ID kontenera",
+ "placeholder": "moja-aplikacja lub abcd1234"
+ },
+ "dnsRecordType": {
+ "label": "Typ rekordu DNS",
+ "value": {
+ "A": "A",
+ "AAAA": "AAAA",
+ "CNAME": "CNAME",
+ "MX": "MX",
+ "NS": "NS",
+ "TXT": "TXT"
+ }
+ },
+ "dnsServer": {
+ "label": "Serwer DNS",
+ "placeholder": "8.8.8.8"
+ },
+ "game": {
+ "label": "Wybierz grę",
+ "placeholder": "Wybierz grę"
+ },
+ "grpcServiceName": {
+ "label": "Nazwa usługi",
+ "placeholder": "np. my.service.v1 (pozostaw puste, aby monitorować ogólny stan zdrowia)"
+ },
+ "host": {
+ "label": "Host",
+ "placeholder": "192.168.1.100 lub example.com"
+ },
+ "name": {
+ "label": "Nazwa",
+ "placeholder": "np. Moja strona internetowa"
+ },
+ "port": {
+ "label": "Port do monitorowania",
+ "placeholder": "80"
+ },
+ "secret": {
+ "label": "Klucz autoryzacyjny",
+ "placeholder": "Wprowadź swój klucz"
+ },
+ "strategy": {
+ "desktop": "Desktop",
+ "label": "Strategia",
+ "mobile": "Mobile"
+ },
+ "url": {
+ "label": "URL",
+ "placeholder": "https://www.google.com"
+ },
+ "wsUrl": {
+ "label": "WebSocket URL",
+ "placeholder": "wss://example.com/socket"
+ }
+ },
+ "title": "Ustawienia ogólne"
+ },
+ "geoChecks": {
+ "description": "Przeprowadzaj sprawdzenia z różnych lokalizacji geograficznych, aby monitorować globalną dostępność i wydajność.",
+ "option": {
+ "enabled": {
+ "label": "Włącz kontrole rozproszone geograficznie"
+ },
+ "interval": {
+ "label": "Interwał sprawdzania",
+ "value": {
+ "fifteenMinutes": "15 minut",
+ "fiveMinutes": "5 minut",
+ "tenMinutes": "10 minut",
+ "thirtyMinutes": "30 minut"
+ }
+ },
+ "locations": {
+ "label": "Lokalizacje",
+ "options": {
+ "AF": "Afryka",
+ "AS": "Azja",
+ "EU": "Europa",
+ "NA": "Ameryka Północna",
+ "OC": "Oceania",
+ "SA": "Ameryka Południowa"
+ },
+ "placeholder": "Wybierz lokalizacje"
+ }
+ },
+ "title": "Kontrole rozproszone geograficznie"
+ },
+ "ignoreTls": {
+ "description": "Skonfiguruj weryfikację certyfikatu TLS/SSL dla połączeń HTTPS.",
+ "option": {
+ "tls": {
+ "label": "Ignoruj błędy TLS/SSL"
+ }
+ },
+ "title": "Ustawienia TLS/SSL"
+ },
+ "incidents": {
+ "description": "Do określenia, kiedy monitor przestaje działać, używane jest okno przesuwne. Status monitora zmieni się tylko wtedy, gdy procent sprawdzeń w oknie czasowym osiągnie określoną wartość.",
+ "option": {
+ "checks": {
+ "label": "Liczba sprawdzeń w oknie czasowym"
+ },
+ "percentage": {
+ "label": "Jaki procent sprawdzeń w oknie czasowym zakończy się niepowodzeniem/powodzeniem, zanim zmieni się status monitora?"
+ }
+ },
+ "title": "Incydenty"
+ },
+ "notifications": {
+ "description": "Wybierz kanały powiadomień, których chcesz używać",
+ "title": "Powiadomienia"
+ },
+ "stats": {
+ "buttonText": "Wyczyść wszystkie statystyki",
+ "description": "Wyczyść całą historię monitorowania i statystyki dla Twojego zespołu. Ta operacja jest nieodwracalna.",
+ "dialog": {
+ "confirm": "Tak, wyczyść wszystkie statystyki",
+ "description": "To spowoduje trwałe usunięcie całej historii monitorowania, statystyk i danych sprawdzających dla Twojego zespołu. Tej operacji nie można cofnąć.",
+ "title": "Wyczyścić całą historię monitorowania?"
+ },
+ "title": "Historia monitora"
+ },
+ "tags": {
+ "description": "Wybierz tagi, które chcesz przypisać do tego monitora",
+ "title": "Tagi"
+ },
+ "thresholds": {
+ "description": "Określ progi, przy których mają być wyzwalane alerty dla tego monitora sprzętu.",
+ "option": {
+ "cpuThreshold": {
+ "label": "Próg alertu CPU (%)"
+ },
+ "diskThreshold": {
+ "label": "Próg alertu dysku (%)"
+ },
+ "memoryThreshold": {
+ "label": "Próg alertu pamięci (%)"
+ },
+ "tempThreshold": {
+ "label": "Próg alarmowy temperatury (°C)"
+ }
+ },
+ "title": "Progi alertów"
+ },
+ "type": {
+ "description": "Wybierz typ sprawdzenia do wykonania",
+ "optionDnsDescription": "Monitoruj, czy określony serwer DNS odpowiada.",
+ "optionDockerDescription": "Użyj Dockera, aby monitorować, czy kontener jest uruchomiony.",
+ "optionGameDescription": "Monitoruj, czy konkretny serwer gry jest online.",
+ "optionGrpcDescription": "Monitoruj usługi gRPC za pomocą standardowego protokołu kontroli stanu.",
+ "optionHttpDescription": "Użyj protokołu HTTP(S), aby monitorować swoją stronę internetową lub punkt końcowy API.",
+ "optionPingDescription": "Użyj ICMP Ping, aby monitorować, czy serwer jest online.",
+ "optionPortDescription": "Monitoruj, czy określony port na serwerze jest otwarty.",
+ "optionWebSocketDescription": "Monitoruj punkty końcowe WebSocket pod kątem stanu połączenia i czasu odpowiedzi.",
+ "title": "Typ"
+ },
+ "url": {
+ "description": "Wyświetl adres IP lub URL monitora na publicznej stronie Status. Jeśli ta opcja jest wyłączona, zostanie wyświetlona tylko nazwa monitora, aby chronić poufne informacje.",
+ "option": {
+ "showURL": {
+ "disabled": "Wyłączone",
+ "enabled": "Włączone",
+ "label": "Wyświetl IP/URL na stronie statusu"
+ }
+ },
+ "title": "Monitoruj IP/URL na stronie statusu"
+ }
+ }
+ },
+ "editUser": {
+ "dialog": {
+ "removeUser": {
+ "content": "Czy na pewno chcesz usunąć {{name}} ze swojego zespołu? Tej operacji nie można cofnąć.",
+ "title": "Usuń użytkownika"
+ }
+ },
+ "form": {
+ "roles": {
+ "description": "Przypisz role użytkownikowi. Można wybrać wiele ról.",
+ "title": "Role"
+ }
+ }
+ },
+ "incidents": {
+ "dialog": {
+ "details": {
+ "analysis": "Analiza incydentu",
+ "comment": "Komentarz:",
+ "detailsLabel": "Szczegóły",
+ "downtime": "Czas niedostępności:",
+ "message": "Wiadomość:",
+ "monitor": "Monitor:",
+ "overview": "Przegląd",
+ "resolutionDetails": "Szczegóły rozwiązania",
+ "resolutionType": "Typ:",
+ "resolutionTypes": {
+ "automatic": "Automatyczny",
+ "manual": "Ręczne"
+ },
+ "resolve": "Rozwiąż incydent",
+ "resolvedAt": "Rozwiązano o:",
+ "resolvedBy": "Rozwiązane przez:",
+ "startedAt": "Rozpoczęto o:",
+ "status": "Status:",
+ "statusCode": "Kod statusu:",
+ "timeline": "Oś czasu",
+ "title": "Szczegóły incydentu",
+ "url": "URL:"
+ },
+ "resolveIncident": {
+ "option": {
+ "comment": {
+ "label": "Komentarz (opcjonalnie)",
+ "placeholder": "Dodaj komentarz dotyczący rozwiązania..."
+ }
+ },
+ "title": "Rozwiąż incydent"
+ }
+ },
+ "fallback": {
+ "description": "Gdy monitor przestanie działać lub zostanie przywrócony, incydent pojawi się tutaj wraz z czasem, szczegółami monitora i historią rozwiązań.",
+ "title": "Brak incydentów — wszystkie systemy działają poprawnie"
+ },
+ "filters": {
+ "allMonitors": "Wszystkie monitory",
+ "monitor": "Monitor",
+ "resolutionType": "Typ rozwiązania",
+ "resolutionTypes": {
+ "all": "Wszystkie",
+ "automatic": "Automatyczne",
+ "manual": "Ręczny"
+ }
+ },
+ "header": {
+ "description": "Gdy monitory przestają działać lub zostają przywrócone, incydenty pojawiają się tutaj wraz z informacjami o czasie, szczegółach monitora i historią rozwiązań.",
+ "title": "Incydenty"
+ },
+ "summaryCard": {
+ "activeIncidents": {
+ "active_one": "{{count}} aktywny incydent",
+ "active_other": "{{count}} aktywne incydenty",
+ "active_zero": "Brak aktywnych incydentów",
+ "title": "Aktywne incydenty"
+ },
+ "incidentStats": {
+ "avgResolutionTime": "Średni czas rozwiązania",
+ "mostAffectedMonitor": "Najbardziej dotknięty monitor",
+ "title": "Statystyki incydentów",
+ "totalIncidents": "Łączna liczba incydentów"
+ },
+ "latestIncidents": {
+ "title": "Najnowsze incydenty"
+ }
+ },
+ "table": {
+ "actions": {
+ "details": "Szczegóły",
+ "goToMonitor": "Przejdź do monitora",
+ "resolveManually": "Rozwiąż ręcznie"
+ },
+ "activeIncidents": "Aktywne incydenty",
+ "headers": {
+ "endTime": "Czas zakończenia",
+ "resolutionType": "Typ rozwiązania",
+ "startTime": "Czas rozpoczęcia",
+ "statusCode": "Kod statusu"
+ },
+ "resolvedIncidents": "Rozwiązane incydenty",
+ "status": {
+ "active": "Aktywne",
+ "resolved": "Rozwiązany"
+ }
+ }
+ },
+ "infrastructure": {
+ "charts": {
+ "labels": {
+ "cpu": "Użycie CPU",
+ "disk": "Zużycie dysku",
+ "loopbackOnlyNotice": "Przechwytywanie raportuje tylko interfejsy loopback lub container-bridge (lo, lo0, docker0). To normalne, gdy Capture działa wewnątrz kontenera, który widzi tylko własną przestrzeń nazw sieci — uruchom Capture bezpośrednio na hoście, aby zobaczyć rzeczywistą aktywność sieciową.",
+ "memory": "Zużycie pamięci",
+ "netBytesRecv": "{{name}} - Otrzymane bajty",
+ "netBytesSent": "{{name}} - Wysłane bajty",
+ "netNoTraffic": "Capture raportuje zerowy ruch na wszystkich interfejsach. Jest to normalne, gdy Capture działa w kontenerze z dostępem tylko do interfejsów loopback — aby zobaczyć rzeczywistą aktywność sieciową, uruchom Capture bezpośrednio na hoście.",
+ "temp": "Temperatura"
+ }
+ },
+ "fallback": {
+ "actionButton": "Utwórz monitor",
+ "description": "Kontroluj wydajność serwera, znajdź wąskie gardła i zapewnij niezawodność dzięki monitorowaniu infrastruktury w czasie rzeczywistym.",
+ "title": "Brak monitorów infrastruktury"
+ },
+ "gauges": {
+ "cpu": {
+ "lowerLabel": "Maksymalna częstotliwość",
+ "title": "Wykorzystanie procesora",
+ "upperLabel": "Aktualna częstotliwość"
+ },
+ "disk": {
+ "lowerLabel": "Wolne",
+ "title": "Użycie dysku {{idx}}",
+ "upperLabel": "Używane"
+ },
+ "memory": {
+ "lowerLabel": "Wolne",
+ "title": "Zużycie pamięci",
+ "upperLabel": "Użyte"
+ }
+ },
+ "header": {
+ "description": "Kontroluj wydajność serwera, identyfikuj wąskie gardła i zapewnij niezawodność dzięki monitorowaniu infrastruktury w czasie rzeczywistym.",
+ "title": "Monitory infrastruktury"
+ },
+ "statBoxes": {
+ "avgCpuTemperature": "Średnia temperatura CPU",
+ "cpuFrequency": "Częstotliwość CPU",
+ "cpuLogical": "CPU (Logiczne)",
+ "cpuPhysical": "CPU (fizyczny)",
+ "disk": "Dysk",
+ "memory": "Pamięć",
+ "os": "OS"
+ },
+ "table": {
+ "headers": {
+ "cpu": "CPU",
+ "disk": "Dysk",
+ "memory": "Pamięć"
+ }
+ },
+ "tabs": {
+ "labels": {
+ "network": "Sieć",
+ "overview": "Przegląd"
+ }
+ }
+ },
+ "logs": {
+ "diagnostics": {
+ "gauges": {
+ "allocatedPercentage": "% przydzielone",
+ "availableMemoryPercentage": "% dostępnej pamięci",
+ "heapAllocation": "Alokacja sterty",
+ "heapUsage": "Zużycie sterty",
+ "heapUtilization": "Wykorzystanie sterty",
+ "instantCpuUsage": "Bieżące użycie CPU",
+ "total": "Razem",
+ "used": "Używane",
+ "usedSPercentage": "% użycia 1s przez CPU"
+ },
+ "mongoDBCollectionStats": {
+ "bucketCount": "Liczba kubełków: {{count}}",
+ "dbUsageTooltip": "{{percentage}}% przestrzeni DB",
+ "documents": "Dokumenty: {{count}}",
+ "indexSize": "Rozmiar indeksu: {{size}}",
+ "storageSize": "Rozmiar magazynu: {{size}}",
+ "totalSize": "Całkowity rozmiar: {{size}}"
+ },
+ "mongoDBStats": {
+ "dbName": "Nazwa bazy danych",
+ "deletesPerSecond": "Usunięcia/s",
+ "host": "Host",
+ "insertsPerSecond": "Wstawienia/s",
+ "port": "Port",
+ "readsPerSecond": "Odczyty/s",
+ "readyState": "Stan gotowości",
+ "storageSize": "Rozmiar pamięci",
+ "updatesPerSecond": "Aktualizacje/s",
+ "writesPerSecond": "Łączna liczba zapisów/s"
+ },
+ "sections": {
+ "memoryCpu": {
+ "description": "Jak proces wykorzystuje przydzieloną mu pamięć i procesor hosta.",
+ "title": "Pamięć i CPU"
+ },
+ "mongoDBCollectionStats": {
+ "description": "Statystyki dla kolekcji MongoDB",
+ "title": "Statystyki kolekcji MongoDB"
+ },
+ "mongoDBStats": {
+ "description": "Migawka metryk MongoDB",
+ "title": "Statystyki MongoDB"
+ },
+ "runtimeStats": {
+ "description": "Migawka procesu Node.js i maszyny hosta. Aktualizuje się co 5 sekund.",
+ "title": "Statystyki środowiska uruchomieniowego"
+ }
+ },
+ "stats": {
+ "eventLoopDelay": "Opóźnienie pętli zdarzeń",
+ "osMemoryLimit": "Limit pamięci OS",
+ "totalHeapSize": "Całkowity rozmiar sterty",
+ "uptime": "Czas działania",
+ "usedHeapSize": "Użyty rozmiar sterty"
+ }
+ },
+ "failedJobs": "Nieudane zadania",
+ "failedJobsExplainer": "Ostatnie niepowodzenia monitora — czas ostatniego błędu, liczba niepowodzeń oraz powód.",
+ "fallback": {
+ "description": "Aktywność serwera, ostrzeżenia i błędy będą się pojawiać tutaj podczas działania Checkmate.",
+ "title": "Brak logów"
+ },
+ "header": {
+ "description": "Sprawdzaj logi serwera, monitoruj kolejkę zadań i diagnozę działania w jednym miejscu.",
+ "title": "Dzienniki i kolejka"
+ },
+ "jobQueue": "Kolejka zadań",
+ "jobQueueExplainer": "Wyróżnione wiersze to zadania aktualnie przetwarzane przez pracownika.",
+ "logLevelSelect": {
+ "label": "Poziom logowania"
+ },
+ "metrics": {
+ "activeJobs": "Aktywne zadania",
+ "failingJobs": "Nieudane zadania",
+ "jobs": "Zadania",
+ "totalFailures": "Łączna liczba błędów",
+ "totalRuns": "Łączna liczba uruchomień"
+ },
+ "noLogs": "Nie znaleziono logów",
+ "queueFallback": {
+ "description": "Sprawdzenia monitorujące, które są zaplanowane lub trwają, pojawią się tutaj. Pusta kolejka oznacza, że wszystko jest aktualne.",
+ "title": "Brak zadań w kolejce"
+ },
+ "table": {
+ "headers": {
+ "failCount": "Liczba",
+ "failReason": "Powód niepowodzenia",
+ "host": "Host",
+ "lastFailedAt": "Ostatnia nieudana próba",
+ "lastFinishedAt": "Ostatnie zakończenie o",
+ "lastSeenAt": "Ostatnio widziano",
+ "level": "Poziom",
+ "lockedBy": "Zablokowane przez",
+ "method": "Metoda",
+ "monitorId": "Identyfikator monitora",
+ "nextScheduledAt": "Następne uruchomienie o",
+ "processId": "Identyfikator procesu",
+ "runCount": "Liczba uruchomień",
+ "service": "Usługa",
+ "state": "Stan",
+ "timestamp": "Znacznik czasu",
+ "workerId": "ID pracownika",
+ "workerMode": "Tryb pracownika"
+ }
+ },
+ "tabs": {
+ "diagnostics": "Diagnostyka",
+ "logs": "Logi",
+ "queue": "Kolejka zadań"
+ },
+ "workers": "Pracownicy",
+ "workersExplainers": "Wyświetla procesy workerów. Identyfikatory workerów są skrócone, najedź, aby zobaczyć pełny identyfikator"
+ },
+ "maintenanceWindow": {
+ "fallback": {
+ "actionButton": "Utwórz przerwę techniczną",
+ "description": "Zaplanuj planowaną przerwę, aby powiadomienia były wyciszone, a Twój zespół i użytkownicy wiedzieli, czego się spodziewać.",
+ "title": "Brak zaplanowanych okien serwisowych"
+ },
+ "form": {
+ "general": {
+ "description": "Ustaw nazwę i opcję powtarzania dla przerwy technicznej.",
+ "option": {
+ "name": {
+ "label": "Nazwa",
+ "placeholder": "np. Cotygodniowa przerwa techniczna"
+ },
+ "repeat": {
+ "label": "Powtarzaj"
+ }
+ },
+ "title": "Ustawienia ogólne"
+ },
+ "startDate": {
+ "description": "Wybierz datę rozpoczęcia przerwy technicznej.",
+ "option": {
+ "startDate": {
+ "label": "Data rozpoczęcia"
+ }
+ },
+ "title": "Data rozpoczęcia"
+ },
+ "startTime": {
+ "description": "Ustaw czas rozpoczęcia i czas trwania przerwy technicznej. Wszystkie wartości są w UTC",
+ "monitors": {
+ "description": "Monitory, do których należy zastosować przerwę techniczną",
+ "option": {
+ "addMonitors": {
+ "label": "Dodaj monitory"
+ }
+ },
+ "title": "Monitory"
+ },
+ "option": {
+ "duration": {
+ "label": "Czas trwania"
+ },
+ "startTime": {
+ "label": "Czas rozpoczęcia"
+ }
+ },
+ "title": "Czas rozpoczęcia"
+ }
+ },
+ "header": {
+ "description": "Zaplanuj planowaną przerwę, aby powiadomienia były wyciszone, a Twój zespół i użytkownicy wiedzieli, czego się spodziewać.",
+ "title": "Przerwy techniczne"
+ },
+ "table": {
+ "headers": {
+ "nextWindow": "Następna przerwa",
+ "repeat": "Powtórz"
+ }
+ }
+ },
+ "notFound": {
+ "subtitle": "Adres URL nie istnieje lub nie masz do niego dostępu.",
+ "title": "Coś się popsuło i nie było mnie słychać..."
+ },
+ "notifications": {
+ "fallback": {
+ "actionButton": "Utwórz kanał",
+ "description": "Połącz Slacka, Discorda, e-mail, webhooki i inne, aby odpowiednie osoby dowiedziały się o incydentach natychmiast po ich wystąpieniu.",
+ "title": "Brak jeszcze kanałów powiadomień"
+ },
+ "form": {
+ "accessToken": {
+ "optionAccessToken": "Token dostępu",
+ "placeholder": "syt_YWxleF9ob2xsaWRheQ_VmtScmV0U2VjcmV0S2V5_abc123"
+ },
+ "address": {
+ "description": "Adres, na który będą wysyłane powiadomienia.",
+ "optionAddress": "Adres",
+ "placeholderEmail": "example@example.com",
+ "placeholderWebhook": "https://your-server.com/webhook",
+ "title": "Adres"
+ },
+ "homeServer": {
+ "optionHomeServer": "Serwer domowy",
+ "placeholder": "example.com"
+ },
+ "matrix": {
+ "description": "Skonfiguruj połączenie z serwerem Matrix dla powiadomień.",
+ "title": "Konfiguracja Matrix"
+ },
+ "notificationName": {
+ "description": "Opisowa nazwa kanału powiadomień",
+ "optionName": "Nazwa kanału",
+ "placeholder": "np. alerty produkcyjne",
+ "title": "Nazwa kanału"
+ },
+ "ntfy": {
+ "description": "Skonfiguruj serwer ntfy i temat do powiadomień push.",
+ "optionServerUrl": "Adres URL serwera",
+ "optionTopic": "Temat",
+ "placeholderServerUrl": "https://ntfy.sh",
+ "placeholderTopic": "checkmate-alerts",
+ "title": "konfiguracja ntfy"
+ },
+ "pagerDuty": {
+ "description": "Twój klucz integracyjny PagerDuty do otrzymywania powiadomień.",
+ "optionIntegrationKey": "Klucz integracyjny",
+ "placeholder": "a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6",
+ "title": "Klucz integracji"
+ },
+ "pushover": {
+ "description": "Skonfiguruj Pushover, aby otrzymywać powiadomienia push na swoich urządzeniach.",
+ "optionAppToken": "Token API aplikacji",
+ "optionUserKey": "Klucz użytkownika",
+ "placeholderAppToken": "azGDORePK8gMaC0QOYAMyEEuzJnyUi",
+ "placeholderUserKey": "uQiRzpo4DXghDmr9QzzfQu27cmVRsG",
+ "title": "Konfiguracja Pushover"
+ },
+ "roomId": {
+ "optionRoomId": "Identyfikator pokoju",
+ "placeholder": "!abcdefg:matrix.org"
+ },
+ "telegram": {
+ "description": "Skonfiguruj swojego bota Telegram, aby wysyłał powiadomienia.",
+ "optionBotToken": "Token bota",
+ "optionChatId": "Identyfikator czatu",
+ "placeholderBotToken": "123456789:ABCdefGhIJKlmNoPQRsTUVwxyZ",
+ "placeholderChatId": "-1001234567890",
+ "title": "Konfiguracja Telegram"
+ },
+ "twilio": {
+ "description": "Skonfiguruj Twilio, aby wysyłać powiadomienia SMS na numer telefonu.",
+ "optionAccountSid": "SID konta",
+ "optionAuthToken": "Token uwierzytelniający",
+ "optionFromNumber": "Numer nadawcy (Twilio)",
+ "optionToNumber": "Do numeru (odbiorca)",
+ "placeholderAccountSid": "ACxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
+ "placeholderAuthToken": "twój_token_uwierzytelniający",
+ "placeholderFromNumber": "+15551234567",
+ "placeholderToNumber": "+15559876543",
+ "title": "Konfiguracja SMS Twilio"
+ },
+ "type": {
+ "description": "Wybierz typ kanału powiadomień do utworzenia.",
+ "optionType": "Typ",
+ "title": "Typ kanału"
+ }
+ },
+ "header": {
+ "description": "Połącz Slacka, Discorda, e-mail, webhooki i inne, aby odpowiednie osoby otrzymywały informacje o incydentach natychmiast po ich wystąpieniu.",
+ "title": "Powiadomienia"
+ },
+ "table": {
+ "headers": {
+ "destination": "Miejsce docelowe"
+ }
+ }
+ },
+ "pageSpeed": {
+ "charts": {
+ "common": {
+ "cls": "Skumulowane przesunięcie układu (CLS)",
+ "fcp": "Pierwsze wyrenderowanie treści (FCP)",
+ "lcp": "Największe wyrenderowanie treści (LCP)",
+ "si": "Indeks szybkości (SI)",
+ "tbt": "Całkowity czas blokowania (TBT)"
+ },
+ "legend": {
+ "title": "Raport PageSpeed",
+ "weight": "Waga"
+ },
+ "pie": {
+ "title": "Raport wydajności"
+ }
+ },
+ "fallback": {
+ "actionButton": "Utwórz monitor",
+ "description": "Monitoruj wskaźniki Core Web Vitals i wyniki Lighthouse w czasie, aby wykryć regresje zanim zrobią to użytkownicy.",
+ "title": "Brak monitorów PageSpeed"
+ },
+ "header": {
+ "description": "Monitoruj Core Web Vitals i wyniki Lighthouse w czasie, aby wykryć regresje, zanim zauważą je użytkownicy.",
+ "title": "Monitory PageSpeed"
+ },
+ "strategy": {
+ "desktop": "Desktop",
+ "mobile": "Mobile"
+ },
+ "table": {
+ "headers": {
+ "pageSpeedScore": "Wynik PageSpeed"
+ }
+ }
+ },
+ "settings": {
+ "form": {
+ "about": {
+ "developedBy": "Opracowane przez Bluewave Labs",
+ "title": "Informacje"
+ },
+ "demoMonitors": {
+ "description": "Dodaj przykładowe monitory do celów demonstracyjnych.",
+ "title": "Monitory demonstracyjne"
+ },
+ "email": {
+ "description": "Skonfiguruj ustawienia e-mail dla swojego systemu. Służą one do wysyłania powiadomień i alertów.",
+ "descriptionTransport": "To tworzy transport SMTP dla NodeMailera",
+ "descriptionTransportLink": "Zobacz specyfikacje tutaj",
+ "option": {
+ "address": {
+ "label": "Adres e-mail – używany do uwierzytelniania",
+ "placeholder": "you@example.com"
+ },
+ "connectionHost": {
+ "label": "Host połączenia e-mail – nazwa hosta używana w powitaniu HELO/EHLO",
+ "placeholder": "localhost"
+ },
+ "displayName": {
+ "label": "Nadawca (opcjonalnie) - Nazwa wyświetlana w polu 'Od'",
+ "placeholder": "Powiadomienia Checkmate"
+ },
+ "host": {
+ "label": "Host e-mail – nazwa hosta lub adres IP do połączenia",
+ "placeholder": "smtp.gmail.com"
+ },
+ "ignoreTLS": {
+ "label": "Wyłącz STARTTLS: Nie używaj TLS, nawet jeśli serwer go obsługuje"
+ },
+ "password": {
+ "label": "Hasło e-mail – Hasło do uwierzytelniania",
+ "labelSet": "Hasło jest ustawione. Kliknij Resetuj, aby je zmienić.",
+ "placeholder": "Wprowadź swoje hasło"
+ },
+ "pool": {
+ "label": "Włącz łączenie połączeń: Wykorzystaj istniejące połączenia, aby poprawić wydajność"
+ },
+ "port": {
+ "label": "Port e-mail — port do połączenia",
+ "placeholder": "587"
+ },
+ "rejectUnauthorized": {
+ "label": "Odrzucaj nieprawidłowe certyfikaty: Odrzucaj połączenia z certyfikatami samopodpisanymi lub niezaufanymi"
+ },
+ "requireTLS": {
+ "label": "Wymuś STARTTLS: Wymagaj aktualizacji do TLS, odmów w przypadku braku obsługi"
+ },
+ "secure": {
+ "label": "Użyj SSL (zalecane): Szyfruj połączenie za pomocą SSL/TLS"
+ },
+ "tlsServername": {
+ "label": "Nazwa serwera TLS - opcjonalna nazwa hosta do weryfikacji TLS, gdy hostem jest adres IP",
+ "placeholder": "example.com"
+ },
+ "user": {
+ "label": "Użytkownik e-mail — Nazwa użytkownika do uwierzytelniania, zastępuje adres e-mail, jeśli została podana",
+ "placeholder": "Pozostaw puste, jeśli nie jest wymagane"
+ }
+ },
+ "title": "Ustawienia e-mail"
+ },
+ "exportMonitors": {
+ "title": "Eksportuj monitory"
+ },
+ "importExportMonitors": {
+ "description": "Importuj lub eksportuj dane monitorów jako plik JSON w celu wykonania kopii zapasowej lub przeniesienia.",
+ "title": "Importuj / Eksportuj monitory"
+ },
+ "pagespeed": {
+ "description": "Wprowadź swój klucz API Google PageSpeed, aby włączyć monitorowanie Google PageSpeed. Kliknij Resetuj, aby zaktualizować klucz.",
+ "option": {
+ "apiKey": {
+ "label": "Klucz API PageSpeed",
+ "labelSet": "Klucz API jest ustawiony. Kliknij Resetuj, aby go zmienić.",
+ "placeholder": "Wprowadź swój klucz API Google PageSpeed"
+ }
+ },
+ "title": "Klucz API Google PageSpeed"
+ },
+ "removeMonitors": {
+ "description": "Usuń wszystkie monitory z systemu.",
+ "dialog": {
+ "description": "Tej operacji nie można cofnąć.",
+ "paragraph": "Cała historia sprawdzeń, incydenty, zaplanowane okna konserwacyjne oraz wszystkie strony statusowe, które odnosiły się do tych monitorów, również zostaną usunięte. Najpierw wyeksportuj swoje monitory, aby zachować kopię zapasową ich danych.",
+ "title": "Usunąć wszystkie monitory?"
+ },
+ "title": "Resetowanie systemu"
+ },
+ "retention": {
+ "description": "Ustaw, jak długo dane kontroli będą przechowywane przed ich automatycznym usunięciem.",
+ "option": {
+ "days": {
+ "label": "Okres przechowywania (dni)",
+ "unlimited": "Bez ograniczeń"
+ }
+ },
+ "title": "Sprawdź retencję"
+ },
+ "stats": {
+ "description": "Wyczyść całą historię monitorowania i statystyki dla Twojego zespołu. Ta operacja jest nieodwracalna.",
+ "dialog": {
+ "description": "Tej operacji nie można cofnąć",
+ "title": "Wyczyścić całą historię monitorowania?"
+ },
+ "option": {
+ "clear": {
+ "label": "Wyczyść wszystkie statystyki. Tej operacji nie można cofnąć."
+ }
+ },
+ "title": "Monitoruj historię"
+ },
+ "thresholds": {
+ "description": "Ustaw globalne progi CPU, pamięci, dysku i temperatury dla monitorowania infrastruktury. Mają one zastosowanie do wszystkich monitorów sprzętu, o ile nie zostaną zastąpione.",
+ "option": {
+ "cpu": {
+ "label": "Próg CPU (%)",
+ "placeholder": "1 - 100"
+ },
+ "disk": {
+ "label": "Próg dysku (%)",
+ "placeholder": "1 - 100"
+ },
+ "memory": {
+ "label": "Próg pamięci (%)",
+ "placeholder": "1 - 100"
+ },
+ "temperature": {
+ "label": "Próg temperatury (°C)",
+ "placeholder": "1 - 150"
+ }
+ },
+ "title": "Progi globalne"
+ },
+ "timezone": {
+ "description": "Wybierz strefę czasową używaną do wyświetlania dat i godzin w całej aplikacji.",
+ "option": {
+ "timezone": {
+ "label": "Wyświetlanie strefy czasowej"
+ }
+ },
+ "title": "Wyświetlana strefa czasowa"
+ },
+ "ui": {
+ "description": "Przełączaj między trybem jasnym i ciemnym, zmieniaj język lub dostosuj typ wyświetlania wykresu.",
+ "option": {
+ "chartType": {
+ "heatmap": "Mapa ciepła",
+ "histogram": "Histogram",
+ "label": "Typ wykresu"
+ },
+ "language": {
+ "label": "Język"
+ },
+ "theme": {
+ "dark": "Ciemny",
+ "label": "Tryb motywu",
+ "light": "Jasny"
+ }
+ },
+ "title": "Wygląd"
+ },
+ "url": {
+ "description": "Wyświetl adres IP lub URL monitora na publicznej stronie Status. Jeśli opcja jest wyłączona, wyświetlana będzie tylko nazwa monitora, aby chronić poufne informacje.",
+ "option": {
+ "showURL": {
+ "disabled": "Wyłączone",
+ "enabled": "Włączone",
+ "label": "Wyświetl IP/URL na stronie statusu"
+ }
+ },
+ "title": "Monitoruj IP/URL na stronie statusu"
+ },
+ "validation": {
+ "errorMessage": "Proszę poprawić następujące błędy walidacji:"
+ }
+ },
+ "header": {
+ "description": "Skonfiguruj SMTP, integracje, retencję oraz globalne zachowanie aplikacji.",
+ "title": "Ustawienia"
+ }
+ },
+ "statusPages": {
+ "deleteSuccess": "Strona statusu została pomyślnie usunięta",
+ "details": {
+ "empty": {
+ "addMonitor": "Dodaj monitor, aby rozpocząć",
+ "title": "Nie ma tu jeszcze nic"
+ }
+ },
+ "editorial": {
+ "allUp": "Wszystkie systemy działają prawidłowo.",
+ "reportTitle": "Raport serwisowy"
+ },
+ "fallback": {
+ "actionButton": "Utwórz stronę statusu",
+ "description": "Opublikuj publiczną stronę, która pokazuje w czasie rzeczywistym historię dostępności i incydentów dla Twoich klientów i interesariuszy.",
+ "title": "Brak stron statusu"
+ },
+ "footer": {
+ "poweredBy": "Powered by"
+ },
+ "form": {
+ "access": {
+ "description": "Jeśli Twoja strona statusu jest gotowa, możesz oznaczyć ją jako opublikowaną.",
+ "option": {
+ "published": {
+ "name": "Opublikowane i widoczne publicznie"
+ }
+ },
+ "title": "Dostęp"
+ },
+ "appearance": {
+ "description": "Zdefiniuj domyślny wygląd i styl swojej publicznej strony statusu.",
+ "option": {
+ "color": {
+ "label": "Kolor marki"
+ },
+ "logo": {
+ "label": "Logo"
+ }
+ },
+ "title": "Wygląd"
+ },
+ "basicInfo": {
+ "description": "Zdefiniuj nazwę firmy oraz subdomenę, na którą wskazuje Twoja strona statusu.",
+ "option": {
+ "customDomain": {
+ "helper": "Opcjonalne. Skieruj rekord DNS na tę instancję Checkmate, aby wyświetlać stronę statusu pod własną domeną.",
+ "label": "Niestandardowa domena",
+ "placeholder": "status.przyklad.com"
+ },
+ "name": {
+ "label": "Nazwa firmy",
+ "placeholder": "Acme Inc."
+ },
+ "url": {
+ "label": "Adres Twojej strony statusowej",
+ "placeholder": "moja-strona-statusowa"
+ }
+ },
+ "title": "Podstawowe informacje"
+ },
+ "customCSS": {
+ "description": "Dodaj własny CSS, aby precyzyjnie dostosować wygląd swojej publicznej strony statusu.",
+ "option": {
+ "customCSS": {
+ "label": "Niestandardowy CSS",
+ "placeholder": "footer { display: none; }"
+ }
+ },
+ "title": "Niestandardowy CSS"
+ },
+ "features": {
+ "description": "Skonfiguruj, jakie informacje są wyświetlane na Twojej stronie statusu.",
+ "option": {
+ "showAdminLoginLink": {
+ "label": "Pokaż link do logowania administratora"
+ },
+ "showCharts": {
+ "label": "Pokaż wykresy czasu odpowiedzi"
+ },
+ "showInfrastructure": {
+ "label": "Pokaż metryki infrastruktury"
+ },
+ "showUptimePercentage": {
+ "label": "Pokaż procent czasu dostępności"
+ }
+ },
+ "title": "Funkcje"
+ },
+ "monitors": {
+ "description": "Wybierz monitory do wyświetlenia na swojej stronie statusu.",
+ "noMonitors": "Nie wybrano monitorów",
+ "option": {
+ "monitors": {
+ "label": "Wybierz monitory",
+ "placeholder": "Wyszukaj i wybierz monitory..."
+ }
+ },
+ "title": "Monitory"
+ },
+ "theme": {
+ "description": "Wybierz styl wizualny dla swojej publicznej strony statusu.",
+ "label": "Motyw",
+ "options": {
+ "bold": {
+ "description": "Duża czcionka i wyraźne odstępy. Idealne dla stron nastawionych na budowanie marki.",
+ "name": "Pogrubiony"
+ },
+ "editorial": {
+ "description": "Nagłówki szeryfowe i cienkie linie oddzielające. Wyrazisty i spokojny.",
+ "name": "Redakcyjny"
+ },
+ "minimal": {
+ "description": "Prosty i kompaktowy, zawierający tylko najważniejsze elementy.",
+ "name": "Minimalistyczny"
+ },
+ "modern": {
+ "description": "SaaS'owy z pulsującą kropką statusu i delikatnie zanikającymi kartami.",
+ "name": "Nowoczesny"
+ },
+ "refined": {
+ "description": "Czysty, profesjonalny, zbliżony do klasycznego wyglądu Checkmate.",
+ "name": "Wyrafinowany"
+ }
+ },
+ "title": "Motyw strony"
+ },
+ "themeMode": {
+ "auto": "Automatycznie (zgodnie z urządzeniem odwiedzającego)",
+ "dark": "Tylko ciemny",
+ "label": "Tryb motywu",
+ "light": "Tylko jasny"
+ },
+ "timezone": {
+ "description": "Wybierz strefę czasową, w której będzie wyświetlana Twoja strona statusu.",
+ "option": {
+ "timezone": {
+ "label": "Strefa czasowa",
+ "placeholder": "Wybierz strefę czasową..."
+ }
+ },
+ "title": "Strefa czasowa"
+ }
+ },
+ "header": {
+ "description": "Publikuj publiczne strony pokazujące aktualny czas działania i historię incydentów swoim klientom i interesariuszom.",
+ "title": "Strony statusu"
+ },
+ "monitorsList": {
+ "chart": {
+ "avg": "śr. {{value}}ms",
+ "downTooltip": "Niedostępny",
+ "heatmapAria": "Heatmapa czasu odpowiedzi",
+ "max": "maks. {{value}}ms"
+ },
+ "chartTypeHeatmap": "Heatmapa",
+ "chartTypeHistogram": "Histogram",
+ "infrastructure": {
+ "cpu": "CPU",
+ "disk": "Dysk",
+ "memory": "RAM",
+ "memoryText": "Pamięć",
+ "title": "Infrastruktura",
+ "total": "Łącznie",
+ "usage": "Użycie",
+ "used": "Używane"
+ },
+ "noData": "Brak dostępnych danych",
+ "status": {
+ "breached": "Przekroczono próg",
+ "down": "Niedostępny",
+ "initializing": "Inicjowanie",
+ "maintenance": "Konserwacja",
+ "paused": "Wstrzymano",
+ "up": "Działa poprawnie"
+ },
+ "uptime": {
+ "responseTime": "Czas odpowiedzi",
+ "title": "Czas pracy"
+ }
+ },
+ "preview": {
+ "title": "Podgląd strony statusu"
+ },
+ "statusBar": {
+ "allBreached": "Wszystkie systemy przekroczyły progi",
+ "allDown": "Wszystkie systemy są niedostępne",
+ "allInitializing": "Wszystkie systemy są inicjalizowane",
+ "allMaintenance": "Wszystkie systemy są w trakcie konserwacji",
+ "allPaused": "Wszystkie systemy są wstrzymane",
+ "allUp": "Wszystkie systemy działają poprawnie",
+ "breached": "Niektóre systemy przekroczyły progi",
+ "breachedAndDown": "Niektóre systemy przekroczyły progi i są nieaktywne",
+ "degraded": "Niektóre systemy mają problemy",
+ "initializing": "Niektóre systemy są inicjalizowane",
+ "maintenance": "Niektóre systemy są w trakcie konserwacji",
+ "maintenanceAndDown": "Niektóre systemy są niedostępne podczas konserwacji",
+ "monitoringSummary": "Monitorowanie {{count}} usługi · Aktualizacje co 10 sekund",
+ "monitoringSummary_other": "Monitorowanie {{count}} usług · Aktualizacje co 10 sekund",
+ "noMonitors": "Nie skonfigurowano żadnych monitorów",
+ "partiallyPaused": "Niektóre systemy są wstrzymane",
+ "unknown": "Nie można określić stanu systemu"
+ },
+ "table": {
+ "headers": {
+ "name": "Nazwa strony statusu",
+ "url": "Publiczny adres URL"
+ },
+ "published": "Opublikowano",
+ "unpublished": "Nieopublikowane"
+ },
+ "title": "Strony statusu"
+ },
+ "tags": {
+ "fallback": {
+ "actionButton": "Utwórz tag",
+ "description": "Organizuj swoje monitory za pomocą tagów. Po utworzeniu tagu i przypisaniu go do monitora, będziesz mógł filtrować według tego tagu w całej aplikacji i zobaczyć szczegóły tagu tutaj.",
+ "title": "Brak jeszcze tagów"
+ },
+ "form": {
+ "color": {
+ "description": "Wybierz kolor dla swojego tagu",
+ "optionName": "Kolor tagu",
+ "placeholder": "np. #13715B",
+ "title": "Kolor etykiety"
+ },
+ "name": {
+ "description": "Wybierz nazwę dla swojego tagu",
+ "optionName": "Nazwa tagu",
+ "placeholder": "np. Grupa 1",
+ "title": "Nazwa tagu"
+ }
+ },
+ "header": {
+ "description": "Porządkuj swoje monitory za pomocą tagów",
+ "title": "Tagi"
+ },
+ "table": {
+ "headers": {
+ "appearance": "Wygląd",
+ "color": "Kolor"
+ }
+ }
+ },
+ "uptime": {
+ "fallback": {
+ "actionButton": "Utwórz monitor",
+ "description": "Monitoruj punkty końcowe HTTP, pingowania, kontenery i porty — i otrzymuj powiadomienia natychmiast, gdy coś przestanie działać.",
+ "title": "Brak monitorów dostępności"
+ },
+ "filters": {
+ "activeTags": "Aktywne tagi",
+ "search": {
+ "placeholder": "Wyszukaj monitory..."
+ }
+ },
+ "header": {
+ "description": "Monitoruj punkty końcowe HTTP, ping, kontenery i porty — i otrzymuj powiadomienia natychmiast, gdy coś przestanie działać.",
+ "title": "Monitory dostępności"
+ },
+ "table": {
+ "headers": {
+ "responseTime": "Czas odpowiedzi",
+ "uptime": "Czas działania %"
+ }
+ }
+ }
+ }
}
From 368595178d73868470121c8ca8cf375679e8a553 Mon Sep 17 00:00:00 2001
From: Osamaali313
Date: Sat, 18 Jul 2026 23:36:43 +0300
Subject: [PATCH 38/84] Fix missing "Poor" label on the average response-time
gauge
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
`RadialAvgResponse` maps the response-time palette to a qualitative word:
const palette = getResponseTimeColor(avg);
const msg: Record = {
success: "Excellent",
warning: "Average",
danger: "Poor",
};
...
{msg[palette]}
But `getResponseTimeColor` only ever returns `"success" | "warning" | "error"`
(MonitorUtils.ts) — never `"danger"`. So for the slow case `palette` is
`"error"`, `msg["error"]` is `undefined`, and no label renders. The same
component uses `theme.palette[palette].main`, which works precisely because
`palette` is the real MUI key `"error"`; every other palette helper in
MonitorUtils.ts also uses `"error"`, and `"danger"` appears nowhere else in
the client. Any monitor whose average response time is >= 300 ms shows an
empty label exactly where the "Poor" signal matters most. Use the `error`
key.
---
client/src/Components/monitors/charts/RadialAvgResponse.tsx | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/client/src/Components/monitors/charts/RadialAvgResponse.tsx b/client/src/Components/monitors/charts/RadialAvgResponse.tsx
index 9a88fb42c..dccb18984 100644
--- a/client/src/Components/monitors/charts/RadialAvgResponse.tsx
+++ b/client/src/Components/monitors/charts/RadialAvgResponse.tsx
@@ -20,7 +20,7 @@ export const RadialAvgResponse = ({ avg, max }: { avg: number; max: number }) =>
const msg: Record = {
success: "Excellent",
warning: "Average",
- danger: "Poor",
+ error: "Poor",
};
return (
From 3eadb99c5e4c68213358c6b499836b41c32d3b47 Mon Sep 17 00:00:00 2001
From: Alex Holliday
Date: Mon, 20 Jul 2026 10:01:48 -0700
Subject: [PATCH 39/84] protected -> private as no class inherits
---
.../src/domain/app-settings/app-settings.repository.mongo.ts | 2 +-
server/src/domain/dlq/dlq.repository.mongo.ts | 4 ++--
server/src/domain/incidents/incident.repository.mongo.ts | 4 ++--
server/src/domain/invites/invite.repository.mongo.ts | 2 +-
.../src/domain/queue-workers/queue-worker.repository.mongo.ts | 2 +-
.../domain/recovery-tokens/recovery-token.repository.mongo.ts | 2 +-
server/src/domain/tags/tag.repository.mongo.ts | 2 +-
server/src/domain/users/user.repository.mongo.ts | 2 +-
8 files changed, 10 insertions(+), 10 deletions(-)
diff --git a/server/src/domain/app-settings/app-settings.repository.mongo.ts b/server/src/domain/app-settings/app-settings.repository.mongo.ts
index 191e6cfcd..d88141ce5 100644
--- a/server/src/domain/app-settings/app-settings.repository.mongo.ts
+++ b/server/src/domain/app-settings/app-settings.repository.mongo.ts
@@ -5,7 +5,7 @@ import { AppSettingsModel, type AppSettingsDocument } from "@/domain/app-setting
import { toStringId, toDateString } from "@/utils/mongoMappers.js";
class MongoSettingsRepository implements ISettingsRepository {
- protected toEntity = (doc: AppSettingsDocument): Settings => {
+ private toEntity = (doc: AppSettingsDocument): Settings => {
return {
id: toStringId(doc._id),
checkTTL: doc.checkTTL,
diff --git a/server/src/domain/dlq/dlq.repository.mongo.ts b/server/src/domain/dlq/dlq.repository.mongo.ts
index bf02029b4..c30ccb261 100644
--- a/server/src/domain/dlq/dlq.repository.mongo.ts
+++ b/server/src/domain/dlq/dlq.repository.mongo.ts
@@ -6,7 +6,7 @@ import mongoose from "mongoose";
import { AppError } from "@/utils/AppError.js";
import { toStringId, toDateString } from "@/utils/mongoMappers.js";
class MongoDLQRepository implements IDLQRepository {
- protected toEntity = (doc: DLQItemDocument): DLQItem => {
+ private toEntity = (doc: DLQItemDocument): DLQItem => {
return {
id: toStringId(doc._id),
type: doc.type,
@@ -23,7 +23,7 @@ class MongoDLQRepository implements IDLQRepository {
};
};
- protected mapDocuments = (documents: DLQItemDocument[] | DLQItemDocument | null): DLQItem[] => {
+ private mapDocuments = (documents: DLQItemDocument[] | DLQItemDocument | null): DLQItem[] => {
if (!documents) {
return [];
}
diff --git a/server/src/domain/incidents/incident.repository.mongo.ts b/server/src/domain/incidents/incident.repository.mongo.ts
index 35cd5d56e..0123ef7c8 100644
--- a/server/src/domain/incidents/incident.repository.mongo.ts
+++ b/server/src/domain/incidents/incident.repository.mongo.ts
@@ -33,7 +33,7 @@ class MongoIncidentsRepository implements IIncidentsRepository {
return matchStage;
}
- protected toEntity = (doc: IncidentDocument): Incident => {
+ private toEntity = (doc: IncidentDocument): Incident => {
return {
id: toStringId(doc._id),
monitorId: toStringId(doc.monitorId),
@@ -52,7 +52,7 @@ class MongoIncidentsRepository implements IIncidentsRepository {
};
};
- protected mapDocuments = (documents: IncidentDocument[] | IncidentDocument | null): Incident[] => {
+ private mapDocuments = (documents: IncidentDocument[] | IncidentDocument | null): Incident[] => {
if (!documents) {
return [];
}
diff --git a/server/src/domain/invites/invite.repository.mongo.ts b/server/src/domain/invites/invite.repository.mongo.ts
index 0c51147a1..78c666d52 100644
--- a/server/src/domain/invites/invite.repository.mongo.ts
+++ b/server/src/domain/invites/invite.repository.mongo.ts
@@ -6,7 +6,7 @@ import crypto from "crypto";
import { toStringId, toDateString } from "@/utils/mongoMappers.js";
class MongoInvitesRepository implements IInvitesRepository {
- protected toEntity = (doc: InviteDocument): Invite => {
+ private toEntity = (doc: InviteDocument): Invite => {
return {
id: toStringId(doc._id),
email: doc.email,
diff --git a/server/src/domain/queue-workers/queue-worker.repository.mongo.ts b/server/src/domain/queue-workers/queue-worker.repository.mongo.ts
index da62d9850..3b125cbbd 100644
--- a/server/src/domain/queue-workers/queue-worker.repository.mongo.ts
+++ b/server/src/domain/queue-workers/queue-worker.repository.mongo.ts
@@ -7,7 +7,7 @@ const SERVICE_NAME = "MongoQueueWorkersRepository";
class MongoQueueWorkersRepository implements IQueueWorkersRepository {
static SERVICE_NAME = SERVICE_NAME;
- protected toEntity = (doc: QueueWorkerDocument): QueueWorker => {
+ private toEntity = (doc: QueueWorkerDocument): QueueWorker => {
return {
workerId: doc._id,
mode: doc.mode,
diff --git a/server/src/domain/recovery-tokens/recovery-token.repository.mongo.ts b/server/src/domain/recovery-tokens/recovery-token.repository.mongo.ts
index 5ee7db994..02ba3089b 100644
--- a/server/src/domain/recovery-tokens/recovery-token.repository.mongo.ts
+++ b/server/src/domain/recovery-tokens/recovery-token.repository.mongo.ts
@@ -10,7 +10,7 @@ const SERVICE_NAME = "MongoRecoveryTokensRepository";
class MongoRecoveryTokensRepository implements IRecoveryTokensRepository {
static SERVICE_NAME = SERVICE_NAME;
- protected toEntity = (doc: RecoveryTokenDocument): RecoveryToken => {
+ private toEntity = (doc: RecoveryTokenDocument): RecoveryToken => {
return {
id: toStringId(doc._id),
email: doc.email,
diff --git a/server/src/domain/tags/tag.repository.mongo.ts b/server/src/domain/tags/tag.repository.mongo.ts
index 51354b364..6345f23da 100644
--- a/server/src/domain/tags/tag.repository.mongo.ts
+++ b/server/src/domain/tags/tag.repository.mongo.ts
@@ -9,7 +9,7 @@ const SERVICE_NAME = "TagsRepository";
class MongoTagsRepository implements ITagsRepository {
static SERVICE_NAME = SERVICE_NAME;
- protected toEntity = (doc: TagDocument): Tag => {
+ private toEntity = (doc: TagDocument): Tag => {
return {
id: toStringId(doc._id),
teamId: toStringId(doc.teamId),
diff --git a/server/src/domain/users/user.repository.mongo.ts b/server/src/domain/users/user.repository.mongo.ts
index 22f171396..b137c5f4c 100644
--- a/server/src/domain/users/user.repository.mongo.ts
+++ b/server/src/domain/users/user.repository.mongo.ts
@@ -20,7 +20,7 @@ class MongoUsersRepository implements IUsersRepository {
};
};
- protected toEntity = (doc: UserDocument): User => {
+ private toEntity = (doc: UserDocument): User => {
return {
id: toStringId(doc._id),
firstName: doc.firstName,
From d485adbb5479ce3e13d985b38828015b0fa0cf2e Mon Sep 17 00:00:00 2001
From: Alex Holliday
Date: Mon, 20 Jul 2026 10:04:55 -0700
Subject: [PATCH 40/84] narrow mapDocument types
---
server/src/domain/dlq/dlq.repository.mongo.ts | 2 +-
server/src/domain/incidents/incident.repository.mongo.ts | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/server/src/domain/dlq/dlq.repository.mongo.ts b/server/src/domain/dlq/dlq.repository.mongo.ts
index c30ccb261..63fec34c5 100644
--- a/server/src/domain/dlq/dlq.repository.mongo.ts
+++ b/server/src/domain/dlq/dlq.repository.mongo.ts
@@ -23,7 +23,7 @@ class MongoDLQRepository implements IDLQRepository {
};
};
- private mapDocuments = (documents: DLQItemDocument[] | DLQItemDocument | null): DLQItem[] => {
+ private mapDocuments = (documents: DLQItemDocument[]) => {
if (!documents) {
return [];
}
diff --git a/server/src/domain/incidents/incident.repository.mongo.ts b/server/src/domain/incidents/incident.repository.mongo.ts
index 0123ef7c8..61cea96b8 100644
--- a/server/src/domain/incidents/incident.repository.mongo.ts
+++ b/server/src/domain/incidents/incident.repository.mongo.ts
@@ -52,7 +52,7 @@ class MongoIncidentsRepository implements IIncidentsRepository {
};
};
- private mapDocuments = (documents: IncidentDocument[] | IncidentDocument | null): Incident[] => {
+ private mapDocuments = (documents: IncidentDocument[]): Incident[] => {
if (!documents) {
return [];
}
From 4d9e5821a8b3fb28bdb0664dbd93ed748f8e72a2 Mon Sep 17 00:00:00 2001
From: Alex Holliday
Date: Mon, 20 Jul 2026 10:21:43 -0700
Subject: [PATCH 41/84] purge unused groups route, update tests
---
server/src/api/controllers/monitorController.ts | 11 -----------
server/src/api/routes/monitorRoute.ts | 1 -
.../domain/monitors/monitor.repository.interface.ts | 1 -
.../src/domain/monitors/monitor.repository.mongo.ts | 8 --------
server/src/domain/monitors/monitor.service.ts | 5 -----
server/test/helpers/InMemoryMonitorsRepository.ts | 4 ----
server/test/unit/services/monitorService.test.ts | 13 -------------
7 files changed, 43 deletions(-)
diff --git a/server/src/api/controllers/monitorController.ts b/server/src/api/controllers/monitorController.ts
index e0f820a62..2ffb18802 100644
--- a/server/src/api/controllers/monitorController.ts
+++ b/server/src/api/controllers/monitorController.ts
@@ -44,7 +44,6 @@ export interface IMonitorController {
getMonitorsWithChecksByTeamId: RequestHandler;
exportMonitorsToJSON: RequestHandler;
getAllGames: RequestHandler;
- getGroupsByTeamId: RequestHandler;
updateNotifications: RequestHandler;
}
class MonitorController implements IMonitorController {
@@ -365,16 +364,6 @@ class MonitorController implements IMonitorController {
});
});
- getGroupsByTeamId = catchAsync(async (req: Request, res: Response) => {
- const teamId = requireTeamId(req.user?.teamId);
- const groups = await this.monitorService.getGroupsByTeamId({ teamId });
-
- return res.status(200).json({
- msg: "Groups retrieved successfully",
- data: groups,
- });
- });
-
updateNotifications = catchAsync(async (req: Request, res: Response) => {
updateNotificationsValidation.parse(req.body);
diff --git a/server/src/api/routes/monitorRoute.ts b/server/src/api/routes/monitorRoute.ts
index 29834ce70..74a00637d 100644
--- a/server/src/api/routes/monitorRoute.ts
+++ b/server/src/api/routes/monitorRoute.ts
@@ -15,7 +15,6 @@ class MonitorRoutes {
// Team routes
this.router.get("/team", this.monitorController.getMonitorsByTeamId);
this.router.get("/team/with-checks", this.monitorController.getMonitorsWithChecksByTeamId);
- this.router.get("/team/groups", this.monitorController.getGroupsByTeamId);
// Uptime routes
this.router.get("/uptime/details/:monitorId", this.monitorController.getUptimeDetailsById);
diff --git a/server/src/domain/monitors/monitor.repository.interface.ts b/server/src/domain/monitors/monitor.repository.interface.ts
index 46f3df396..a710e366f 100644
--- a/server/src/domain/monitors/monitor.repository.interface.ts
+++ b/server/src/domain/monitors/monitor.repository.interface.ts
@@ -55,7 +55,6 @@ export interface IMonitorsRepository {
// other
findMonitorsSummaryByTeamId(teamId: string, config?: SummaryConfig): Promise;
- findGroupsByTeamId(teamId: string): Promise;
removeNotificationFromMonitors(notificationId: string): Promise;
removeTagFromMonitors(tagId: string): Promise;
updateNotifications(teamId: string, monitorIds: string[], notificationIds: string[], action: "add" | "remove" | "set"): Promise;
diff --git a/server/src/domain/monitors/monitor.repository.mongo.ts b/server/src/domain/monitors/monitor.repository.mongo.ts
index 28e4513f2..96db34c15 100644
--- a/server/src/domain/monitors/monitor.repository.mongo.ts
+++ b/server/src/domain/monitors/monitor.repository.mongo.ts
@@ -382,14 +382,6 @@ class MongoMonitorsRepository implements IMonitorsRepository {
);
};
- findGroupsByTeamId = async (teamId: string): Promise => {
- const groups = await MonitorModel.distinct("group", {
- teamId: new mongoose.Types.ObjectId(teamId),
- group: { $nin: [null, ""] },
- });
- return groups.sort();
- };
-
removeNotificationFromMonitors = async (notificationId: string): Promise => {
await MonitorModel.updateMany({ notifications: notificationId }, { $pull: { notifications: notificationId } });
};
diff --git a/server/src/domain/monitors/monitor.service.ts b/server/src/domain/monitors/monitor.service.ts
index 1402cc453..66684c2ac 100644
--- a/server/src/domain/monitors/monitor.service.ts
+++ b/server/src/domain/monitors/monitor.service.ts
@@ -72,7 +72,6 @@ export interface IMonitorService {
order?: "asc" | "desc";
}): Promise;
getAllGames(): GamesMap;
- getGroupsByTeamId(args: { teamId: string }): Promise;
// update
editMonitor(args: { teamId: string; monitorId: string; body: Partial }): Promise;
@@ -422,10 +421,6 @@ export class MonitorService implements IMonitorService {
return this.games;
};
- getGroupsByTeamId = async ({ teamId }: { teamId: string }): Promise => {
- return await this.monitorsRepository.findGroupsByTeamId(teamId);
- };
-
editMonitor = async ({ teamId, monitorId, body }: { teamId: string; monitorId: string; body: Partial }) => {
const editedMonitor = await this.monitorsRepository.updateById(monitorId, teamId, body);
await this.scheduler.updateJob(editedMonitor);
diff --git a/server/test/helpers/InMemoryMonitorsRepository.ts b/server/test/helpers/InMemoryMonitorsRepository.ts
index 45db5b3f7..09162cfd2 100644
--- a/server/test/helpers/InMemoryMonitorsRepository.ts
+++ b/server/test/helpers/InMemoryMonitorsRepository.ts
@@ -119,10 +119,6 @@ export class InMemoryMonitorsRepository implements IMonitorsRepository {
throw new Error("Not implemented");
}
- async findGroupsByTeamId(_teamId: string): Promise {
- throw new Error("Not implemented");
- }
-
async removeNotificationFromMonitors(_notificationId: string): Promise {
throw new Error("Not implemented");
}
diff --git a/server/test/unit/services/monitorService.test.ts b/server/test/unit/services/monitorService.test.ts
index 65d6923a6..6a1cf8343 100644
--- a/server/test/unit/services/monitorService.test.ts
+++ b/server/test/unit/services/monitorService.test.ts
@@ -18,7 +18,6 @@ const createMonitorsRepositoryMock = () =>
findByIds: jest.fn(),
findMonitorCountByTeamIdAndType: jest.fn(),
findMonitorsSummaryByTeamId: jest.fn(),
- findGroupsByTeamId: jest.fn(),
updateById: jest.fn(),
updateNotifications: jest.fn(),
togglePauseById: jest.fn(),
@@ -746,18 +745,6 @@ describe("MonitorService", () => {
});
});
- describe("getGroupsByTeamId", () => {
- it("delegates to repository", async () => {
- const monitorsRepository = createMonitorsRepositoryMock();
- (monitorsRepository.findGroupsByTeamId as jest.Mock).mockResolvedValue(["group1", "group2"]);
- const { service } = createService({ monitorsRepository });
-
- const result = await service.getGroupsByTeamId({ teamId: TEAM_ID });
- expect(result).toEqual(["group1", "group2"]);
- expect(monitorsRepository.findGroupsByTeamId).toHaveBeenCalledWith(TEAM_ID);
- });
- });
-
describe("editMonitor", () => {
it("updates monitor and refreshes job", async () => {
const monitorsRepository = createMonitorsRepositoryMock();
From 36434c11601a115c32b8b1dd0c78ff8bc6336c22 Mon Sep 17 00:00:00 2001
From: Alex Holliday
Date: Mon, 20 Jul 2026 11:25:39 -0700
Subject: [PATCH 42/84] convert router classes to exported functions
---
server/src/api/routes/authRoute.ts | 48 -----------
server/src/api/routes/authRoutes.ts | 27 ++++++
server/src/api/routes/checkRoute.ts | 29 -------
server/src/api/routes/checkRoutes.ts | 14 +++
server/src/api/routes/diagnosticRoute.ts | 24 ------
server/src/api/routes/diagnosticRoutes.ts | 9 ++
server/src/api/routes/geoCheckRoutes.ts | 24 ++----
server/src/api/routes/incidentRoute.ts | 30 -------
server/src/api/routes/incidentRoutes.ts | 12 +++
server/src/api/routes/inviteRoute.ts | 26 ------
server/src/api/routes/inviteRoutes.ts | 11 +++
server/src/api/routes/logRoutes.ts | 24 ++----
.../src/api/routes/maintenanceWindowRoute.ts | 29 -------
.../src/api/routes/maintenanceWindowRoutes.ts | 15 ++++
server/src/api/routes/monitorRoute.ts | 63 --------------
server/src/api/routes/monitorRoutes.ts | 50 +++++++++++
server/src/api/routes/notificationRoute.ts | 31 -------
server/src/api/routes/notificationRoutes.ts | 14 +++
server/src/api/routes/queueRoute.ts | 26 ------
server/src/api/routes/queueRoutes.ts | 12 +++
server/src/api/routes/settingsRoute.ts | 26 ------
server/src/api/routes/settingsRoutes.ts | 11 +++
server/src/api/routes/statusPageRoute.ts | 32 -------
server/src/api/routes/statusPageRoutes.ts | 19 +++++
server/src/api/routes/tagRoutes.ts | 34 ++------
server/src/config/routes.ts | 85 +++++++++----------
26 files changed, 256 insertions(+), 469 deletions(-)
delete mode 100644 server/src/api/routes/authRoute.ts
create mode 100644 server/src/api/routes/authRoutes.ts
delete mode 100644 server/src/api/routes/checkRoute.ts
create mode 100644 server/src/api/routes/checkRoutes.ts
delete mode 100644 server/src/api/routes/diagnosticRoute.ts
create mode 100644 server/src/api/routes/diagnosticRoutes.ts
delete mode 100644 server/src/api/routes/incidentRoute.ts
create mode 100644 server/src/api/routes/incidentRoutes.ts
delete mode 100644 server/src/api/routes/inviteRoute.ts
create mode 100644 server/src/api/routes/inviteRoutes.ts
delete mode 100644 server/src/api/routes/maintenanceWindowRoute.ts
create mode 100644 server/src/api/routes/maintenanceWindowRoutes.ts
delete mode 100644 server/src/api/routes/monitorRoute.ts
create mode 100644 server/src/api/routes/monitorRoutes.ts
delete mode 100644 server/src/api/routes/notificationRoute.ts
create mode 100644 server/src/api/routes/notificationRoutes.ts
delete mode 100644 server/src/api/routes/queueRoute.ts
create mode 100644 server/src/api/routes/queueRoutes.ts
delete mode 100644 server/src/api/routes/settingsRoute.ts
create mode 100644 server/src/api/routes/settingsRoutes.ts
delete mode 100644 server/src/api/routes/statusPageRoute.ts
create mode 100644 server/src/api/routes/statusPageRoutes.ts
diff --git a/server/src/api/routes/authRoute.ts b/server/src/api/routes/authRoute.ts
deleted file mode 100644
index 3b202a502..000000000
--- a/server/src/api/routes/authRoute.ts
+++ /dev/null
@@ -1,48 +0,0 @@
-import { Router, RequestHandler } from "express";
-import { isAllowed } from "@/api/middleware/isAllowed.js";
-import { imageUpload } from "@/api/middleware/upload.js";
-import { IAuthController } from "@/api/controllers/authController.js";
-
-class AuthRoutes {
- private router: Router;
- private authController: IAuthController;
-
- constructor(authController: IAuthController, verifyJWT: RequestHandler) {
- this.router = Router();
- this.authController = authController;
- this.initRoutes(verifyJWT);
- }
-
- initRoutes(verifyJWT: RequestHandler) {
- this.router.post("/register", imageUpload.single("profileImage"), this.authController.registerUser);
- this.router.post("/login", this.authController.loginUser);
-
- this.router.post("/recovery/request", this.authController.requestRecovery);
- this.router.post("/recovery/validate", this.authController.validateRecovery);
- this.router.post("/recovery/reset/", this.authController.resetPassword);
-
- this.router.get("/users/superadmin", this.authController.checkSuperadminExists);
-
- this.router.get("/users", verifyJWT, isAllowed(["admin", "superadmin"]), this.authController.getAllUsers);
- this.router.post("/users", verifyJWT, isAllowed(["superadmin"]), imageUpload.single("profileImage"), this.authController.createUser);
- this.router.get("/users/:userId", verifyJWT, isAllowed(["admin", "superadmin"]), this.authController.getUserById);
- this.router.patch("/users/:userId", verifyJWT, isAllowed(["superadmin"]), this.authController.editUserById);
- this.router.patch("/users/:userId/password", verifyJWT, isAllowed(["superadmin"]), this.authController.editUserPasswordById);
- this.router.delete("/users/:userId", verifyJWT, isAllowed(["admin", "superadmin"]), this.authController.deleteUserById);
-
- this.router.patch(
- "/user",
- verifyJWT,
- imageUpload.single("profileImage"),
- isAllowed(["admin", "superadmin", "user"]),
- this.authController.editUser
- );
- this.router.delete("/user", verifyJWT, isAllowed(["admin", "superadmin", "user"]), this.authController.deleteUser);
- }
-
- getRouter() {
- return this.router;
- }
-}
-
-export default AuthRoutes;
diff --git a/server/src/api/routes/authRoutes.ts b/server/src/api/routes/authRoutes.ts
new file mode 100644
index 000000000..ba92091bd
--- /dev/null
+++ b/server/src/api/routes/authRoutes.ts
@@ -0,0 +1,27 @@
+import { Router, RequestHandler } from "express";
+import { isAllowed } from "@/api/middleware/isAllowed.js";
+import { imageUpload } from "@/api/middleware/upload.js";
+import { IAuthController } from "@/api/controllers/authController.js";
+
+export const createAuthRoutes = (authController: IAuthController, verifyJWT: RequestHandler): Router => {
+ const router = Router();
+ router.post("/register", imageUpload.single("profileImage"), authController.registerUser);
+ router.post("/login", authController.loginUser);
+
+ router.post("/recovery/request", authController.requestRecovery);
+ router.post("/recovery/validate", authController.validateRecovery);
+ router.post("/recovery/reset/", authController.resetPassword);
+
+ router.get("/users/superadmin", authController.checkSuperadminExists);
+
+ router.get("/users", verifyJWT, isAllowed(["admin", "superadmin"]), authController.getAllUsers);
+ router.post("/users", verifyJWT, isAllowed(["superadmin"]), imageUpload.single("profileImage"), authController.createUser);
+ router.get("/users/:userId", verifyJWT, isAllowed(["admin", "superadmin"]), authController.getUserById);
+ router.patch("/users/:userId", verifyJWT, isAllowed(["superadmin"]), authController.editUserById);
+ router.patch("/users/:userId/password", verifyJWT, isAllowed(["superadmin"]), authController.editUserPasswordById);
+ router.delete("/users/:userId", verifyJWT, isAllowed(["admin", "superadmin"]), authController.deleteUserById);
+
+ router.patch("/user", verifyJWT, imageUpload.single("profileImage"), isAllowed(["admin", "superadmin", "user"]), authController.editUser);
+ router.delete("/user", verifyJWT, isAllowed(["admin", "superadmin", "user"]), authController.deleteUser);
+ return router;
+};
diff --git a/server/src/api/routes/checkRoute.ts b/server/src/api/routes/checkRoute.ts
deleted file mode 100644
index 171d79ab4..000000000
--- a/server/src/api/routes/checkRoute.ts
+++ /dev/null
@@ -1,29 +0,0 @@
-import { Router } from "express";
-
-import { isAllowed } from "../middleware/isAllowed.js";
-import { ICheckController } from "@/api/controllers/checkController.js";
-
-class CheckRoutes {
- private router: Router;
- private checkController: ICheckController;
-
- constructor(checkController: ICheckController) {
- this.router = Router();
- this.checkController = checkController;
- this.initRoutes();
- }
-
- initRoutes() {
- this.router.get("/team/summary", this.checkController.getChecksSummaryByTeamId);
- this.router.get("/team", this.checkController.getChecksByTeam);
- this.router.delete("/team", isAllowed(["admin", "superadmin"]), this.checkController.deleteChecksByTeamId);
- this.router.get("/:monitorId", this.checkController.getChecksByMonitor);
- this.router.delete("/:monitorId", this.checkController.deleteChecks);
- }
-
- getRouter() {
- return this.router;
- }
-}
-
-export default CheckRoutes;
diff --git a/server/src/api/routes/checkRoutes.ts b/server/src/api/routes/checkRoutes.ts
new file mode 100644
index 000000000..97974f9df
--- /dev/null
+++ b/server/src/api/routes/checkRoutes.ts
@@ -0,0 +1,14 @@
+import { Router } from "express";
+
+import { isAllowed } from "../middleware/isAllowed.js";
+import { ICheckController } from "@/api/controllers/checkController.js";
+
+export const createCheckRoutes = (checkController: ICheckController): Router => {
+ const router = Router();
+ router.get("/team/summary", checkController.getChecksSummaryByTeamId);
+ router.get("/team", checkController.getChecksByTeam);
+ router.delete("/team", isAllowed(["admin", "superadmin"]), checkController.deleteChecksByTeamId);
+ router.get("/:monitorId", checkController.getChecksByMonitor);
+ router.delete("/:monitorId", checkController.deleteChecks);
+ return router;
+};
diff --git a/server/src/api/routes/diagnosticRoute.ts b/server/src/api/routes/diagnosticRoute.ts
deleted file mode 100644
index 3fe29bc1e..000000000
--- a/server/src/api/routes/diagnosticRoute.ts
+++ /dev/null
@@ -1,24 +0,0 @@
-import { RequestHandler, Router } from "express";
-import { isAllowed } from "../middleware/isAllowed.js";
-import { IDiagnosticController } from "@/api/controllers/diagnosticController.js";
-
-class DiagnosticRoutes {
- private router: Router;
- private diagnosticController: IDiagnosticController;
-
- constructor(diagnosticController: IDiagnosticController, verifyJWT: RequestHandler) {
- this.router = Router();
- this.diagnosticController = diagnosticController;
- this.initRoutes(verifyJWT);
- }
-
- initRoutes(verifyJWT: RequestHandler) {
- this.router.get("/system", verifyJWT, isAllowed(["admin", "superadmin"]), this.diagnosticController.getSystemStats);
- }
-
- getRouter() {
- return this.router;
- }
-}
-
-export default DiagnosticRoutes;
diff --git a/server/src/api/routes/diagnosticRoutes.ts b/server/src/api/routes/diagnosticRoutes.ts
new file mode 100644
index 000000000..680c10015
--- /dev/null
+++ b/server/src/api/routes/diagnosticRoutes.ts
@@ -0,0 +1,9 @@
+import { RequestHandler, Router } from "express";
+import { isAllowed } from "../middleware/isAllowed.js";
+import { IDiagnosticController } from "@/api/controllers/diagnosticController.js";
+
+export const createDiagnosticRoutes = (diagnosticController: IDiagnosticController, verifyJWT: RequestHandler): Router => {
+ const router = Router();
+ router.get("/system", verifyJWT, isAllowed(["admin", "superadmin"]), diagnosticController.getSystemStats);
+ return router;
+};
diff --git a/server/src/api/routes/geoCheckRoutes.ts b/server/src/api/routes/geoCheckRoutes.ts
index bf9178afe..08c577c0e 100644
--- a/server/src/api/routes/geoCheckRoutes.ts
+++ b/server/src/api/routes/geoCheckRoutes.ts
@@ -1,23 +1,9 @@
import { IGeoCheckController } from "@/api/controllers/geoCheckController.js";
import { Router } from "express";
-class GeoCheckRoutes {
- private router: Router;
- private geoCheckController: IGeoCheckController;
+export const createGeoCheckRoutes = (geoCheckController: IGeoCheckController): Router => {
+ const router = Router();
+ router.get("/:monitorId", geoCheckController.getGeoChecksByMonitor);
- constructor(geoCheckController: IGeoCheckController) {
- this.router = Router();
- this.geoCheckController = geoCheckController;
- this.initRoutes();
- }
-
- initRoutes() {
- this.router.get("/:monitorId", this.geoCheckController.getGeoChecksByMonitor);
- }
-
- getRouter() {
- return this.router;
- }
-}
-
-export default GeoCheckRoutes;
+ return router;
+};
diff --git a/server/src/api/routes/incidentRoute.ts b/server/src/api/routes/incidentRoute.ts
deleted file mode 100644
index 0f2f4485c..000000000
--- a/server/src/api/routes/incidentRoute.ts
+++ /dev/null
@@ -1,30 +0,0 @@
-import { Router } from "express";
-import { isAllowed } from "../middleware/isAllowed.js";
-import { IIncidentController } from "@/api/controllers/incidentController.js";
-
-class IncidentRoutes {
- private router: Router;
- private incidentController: IIncidentController;
-
- constructor(incidentController: IIncidentController) {
- this.router = Router();
- this.incidentController = incidentController;
- this.initRoutes();
- }
-
- initRoutes() {
- // Team routes
- this.router.get("/team", this.incidentController.getIncidentsByTeam);
- this.router.get("/team/summary", this.incidentController.getIncidentSummary);
-
- // Individual incident routes
- this.router.get("/:incidentId", this.incidentController.getIncidentById);
- this.router.put("/:incidentId/resolve", isAllowed(["admin", "superadmin"]), this.incidentController.resolveIncidentManually);
- }
-
- getRouter() {
- return this.router;
- }
-}
-
-export default IncidentRoutes;
diff --git a/server/src/api/routes/incidentRoutes.ts b/server/src/api/routes/incidentRoutes.ts
new file mode 100644
index 000000000..f9c42d0f2
--- /dev/null
+++ b/server/src/api/routes/incidentRoutes.ts
@@ -0,0 +1,12 @@
+import { Router } from "express";
+import { isAllowed } from "../middleware/isAllowed.js";
+import { IIncidentController } from "@/api/controllers/incidentController.js";
+
+export const createIncidentRoutes = (incidentController: IIncidentController): Router => {
+ const router = Router();
+ router.get("/team", incidentController.getIncidentsByTeam);
+ router.get("/team/summary", incidentController.getIncidentSummary);
+ router.get("/:incidentId", incidentController.getIncidentById);
+ router.put("/:incidentId/resolve", isAllowed(["admin", "superadmin"]), incidentController.resolveIncidentManually);
+ return router;
+};
diff --git a/server/src/api/routes/inviteRoute.ts b/server/src/api/routes/inviteRoute.ts
deleted file mode 100644
index 2e25694d2..000000000
--- a/server/src/api/routes/inviteRoute.ts
+++ /dev/null
@@ -1,26 +0,0 @@
-import { RequestHandler, Router } from "express";
-import { isAllowed } from "../middleware/isAllowed.js";
-import { IInviteController } from "@/api/controllers/inviteController.js";
-
-class InviteRoutes {
- private router: Router;
- private inviteController: IInviteController;
-
- constructor(inviteController: IInviteController, verifyJWT: RequestHandler) {
- this.router = Router();
- this.inviteController = inviteController;
- this.initRoutes(verifyJWT);
- }
-
- initRoutes(verifyJWT: RequestHandler) {
- this.router.post("/send", verifyJWT, isAllowed(["admin", "superadmin"]), this.inviteController.sendInviteEmail);
- this.router.post("/verify", this.inviteController.verifyInviteToken);
- this.router.post("/", verifyJWT, isAllowed(["admin", "superadmin"]), this.inviteController.getInviteToken);
- }
-
- getRouter() {
- return this.router;
- }
-}
-
-export default InviteRoutes;
diff --git a/server/src/api/routes/inviteRoutes.ts b/server/src/api/routes/inviteRoutes.ts
new file mode 100644
index 000000000..fe6ba9664
--- /dev/null
+++ b/server/src/api/routes/inviteRoutes.ts
@@ -0,0 +1,11 @@
+import { RequestHandler, Router } from "express";
+import { isAllowed } from "../middleware/isAllowed.js";
+import { IInviteController } from "@/api/controllers/inviteController.js";
+
+export const createInviteRoutes = (inviteController: IInviteController, verifyJWT: RequestHandler): Router => {
+ const router = Router();
+ router.post("/send", verifyJWT, isAllowed(["admin", "superadmin"]), inviteController.sendInviteEmail);
+ router.post("/verify", inviteController.verifyInviteToken);
+ router.post("/", verifyJWT, isAllowed(["admin", "superadmin"]), inviteController.getInviteToken);
+ return router;
+};
diff --git a/server/src/api/routes/logRoutes.ts b/server/src/api/routes/logRoutes.ts
index ef98b5189..e0de3b93b 100644
--- a/server/src/api/routes/logRoutes.ts
+++ b/server/src/api/routes/logRoutes.ts
@@ -1,22 +1,10 @@
import { Router } from "express";
import { isAllowed } from "@/api/middleware/isAllowed.js";
+
import { ILogController } from "@/api/controllers/logController.js";
-class LogRoutes {
- private router: Router;
- private logController: ILogController;
- constructor(logController: ILogController) {
- this.router = Router();
- this.logController = logController;
- this.initRoutes();
- }
- initRoutes() {
- this.router.get("/", isAllowed(["admin", "superadmin"]), this.logController.getLogs);
- }
-
- getRouter() {
- return this.router;
- }
-}
-
-export default LogRoutes;
+export const createLogRoutes = (logController: ILogController): Router => {
+ const router = Router();
+ router.get("/", isAllowed(["admin", "superadmin"]), logController.getLogs);
+ return router;
+};
diff --git a/server/src/api/routes/maintenanceWindowRoute.ts b/server/src/api/routes/maintenanceWindowRoute.ts
deleted file mode 100644
index 43d03ff34..000000000
--- a/server/src/api/routes/maintenanceWindowRoute.ts
+++ /dev/null
@@ -1,29 +0,0 @@
-import { IMaintenanceWindowController } from "@/api/controllers/maintenanceWindowController.js";
-import { Router } from "express";
-
-class MaintenanceWindowRoutes {
- private router: Router;
- private mwController: IMaintenanceWindowController;
-
- constructor(maintenanceWindowController: IMaintenanceWindowController) {
- this.router = Router();
- this.mwController = maintenanceWindowController;
- this.initRoutes();
- }
- initRoutes() {
- this.router.post("/", this.mwController.createMaintenanceWindows);
- this.router.get("/team/", this.mwController.getMaintenanceWindowsByTeamId);
-
- this.router.get("/monitor/:monitorId", this.mwController.getMaintenanceWindowsByMonitorId);
-
- this.router.get("/:id", this.mwController.getMaintenanceWindowById);
- this.router.patch("/:id", this.mwController.editMaintenanceWindow);
- this.router.delete("/:id", this.mwController.deleteMaintenanceWindow);
- }
-
- getRouter() {
- return this.router;
- }
-}
-
-export default MaintenanceWindowRoutes;
diff --git a/server/src/api/routes/maintenanceWindowRoutes.ts b/server/src/api/routes/maintenanceWindowRoutes.ts
new file mode 100644
index 000000000..22c3fd94a
--- /dev/null
+++ b/server/src/api/routes/maintenanceWindowRoutes.ts
@@ -0,0 +1,15 @@
+import { IMaintenanceWindowController } from "@/api/controllers/maintenanceWindowController.js";
+import { Router } from "express";
+
+export const createMaintenanceWindowRoutes = (maintenanceWindowController: IMaintenanceWindowController): Router => {
+ const router = Router();
+ router.post("/", maintenanceWindowController.createMaintenanceWindows);
+ router.get("/team/", maintenanceWindowController.getMaintenanceWindowsByTeamId);
+
+ router.get("/monitor/:monitorId", maintenanceWindowController.getMaintenanceWindowsByMonitorId);
+
+ router.get("/:id", maintenanceWindowController.getMaintenanceWindowById);
+ router.patch("/:id", maintenanceWindowController.editMaintenanceWindow);
+ router.delete("/:id", maintenanceWindowController.deleteMaintenanceWindow);
+ return router;
+};
diff --git a/server/src/api/routes/monitorRoute.ts b/server/src/api/routes/monitorRoute.ts
deleted file mode 100644
index 74a00637d..000000000
--- a/server/src/api/routes/monitorRoute.ts
+++ /dev/null
@@ -1,63 +0,0 @@
-import { Router } from "express";
-import { isAllowed } from "@/api/middleware/isAllowed.js";
-import { IMonitorController } from "@/api/controllers/monitorController.js";
-
-class MonitorRoutes {
- private router: Router;
- private monitorController: IMonitorController;
- constructor(monitorController: IMonitorController) {
- this.router = Router();
- this.monitorController = monitorController;
- this.initRoutes();
- }
-
- initRoutes() {
- // Team routes
- this.router.get("/team", this.monitorController.getMonitorsByTeamId);
- this.router.get("/team/with-checks", this.monitorController.getMonitorsWithChecksByTeamId);
-
- // Uptime routes
- this.router.get("/uptime/details/:monitorId", this.monitorController.getUptimeDetailsById);
-
- // Hardware routes
- this.router.get("/hardware/details/:monitorId", this.monitorController.getHardwareDetailsById);
-
- // PageSpeed routes
- this.router.get("/pagespeed/details/:monitorId", this.monitorController.getPageSpeedDetailsById);
-
- // Geo checks routes
- this.router.get("/:monitorId/geo-checks", this.monitorController.getGeoChecksByMonitorId);
-
- // General monitor routes
- this.router.post("/pause/:monitorId", isAllowed(["admin", "superadmin"]), this.monitorController.pauseMonitor);
- this.router.post("/bulk/pause", isAllowed(["admin", "superadmin"]), this.monitorController.bulkPauseMonitors);
-
- // Util routes
- this.router.get("/certificate/:monitorId", (req, res, next) => {
- this.monitorController.getMonitorCertificate(req, res, next);
- });
-
- // General monitor CRUD routes
- this.router.patch("/notifications", isAllowed(["admin", "superadmin"]), this.monitorController.updateNotifications);
- this.router.post("/", isAllowed(["admin", "superadmin"]), this.monitorController.createMonitor);
- this.router.delete("/", isAllowed(["superadmin"]), this.monitorController.deleteAllMonitors);
-
- // Other static routes
- this.router.post("/demo", isAllowed(["admin", "superadmin"]), this.monitorController.addDemoMonitors);
- this.router.get("/export/json", isAllowed(["admin", "superadmin"]), this.monitorController.exportMonitorsToJSON);
- this.router.post("/import/json", isAllowed(["admin", "superadmin"]), this.monitorController.importMonitorsFromJSON);
-
- this.router.get("/games", this.monitorController.getAllGames);
-
- // Individual monitor CRUD routes
- this.router.get("/:monitorId", this.monitorController.getMonitorById);
- this.router.patch("/:monitorId", isAllowed(["admin", "superadmin"]), this.monitorController.editMonitor);
- this.router.delete("/:monitorId", isAllowed(["admin", "superadmin"]), this.monitorController.deleteMonitor);
- }
-
- getRouter() {
- return this.router;
- }
-}
-
-export default MonitorRoutes;
diff --git a/server/src/api/routes/monitorRoutes.ts b/server/src/api/routes/monitorRoutes.ts
new file mode 100644
index 000000000..43a94b5c4
--- /dev/null
+++ b/server/src/api/routes/monitorRoutes.ts
@@ -0,0 +1,50 @@
+import { Router } from "express";
+import { isAllowed } from "@/api/middleware/isAllowed.js";
+import { IMonitorController } from "@/api/controllers/monitorController.js";
+
+export const createMonitorRoutes = (monitorController: IMonitorController): Router => {
+ const router = Router();
+
+ // Team routes
+ router.get("/team", monitorController.getMonitorsByTeamId);
+ router.get("/team/with-checks", monitorController.getMonitorsWithChecksByTeamId);
+
+ // Uptime routes
+ router.get("/uptime/details/:monitorId", monitorController.getUptimeDetailsById);
+
+ // Hardware routes
+ router.get("/hardware/details/:monitorId", monitorController.getHardwareDetailsById);
+
+ // PageSpeed routes
+ router.get("/pagespeed/details/:monitorId", monitorController.getPageSpeedDetailsById);
+
+ // Geo checks routes
+ router.get("/:monitorId/geo-checks", monitorController.getGeoChecksByMonitorId);
+
+ // General monitor routes
+ router.post("/pause/:monitorId", isAllowed(["admin", "superadmin"]), monitorController.pauseMonitor);
+ router.post("/bulk/pause", isAllowed(["admin", "superadmin"]), monitorController.bulkPauseMonitors);
+
+ // Util routes
+ router.get("/certificate/:monitorId", (req, res, next) => {
+ monitorController.getMonitorCertificate(req, res, next);
+ });
+
+ // General monitor CRUD routes
+ router.patch("/notifications", isAllowed(["admin", "superadmin"]), monitorController.updateNotifications);
+ router.post("/", isAllowed(["admin", "superadmin"]), monitorController.createMonitor);
+ router.delete("/", isAllowed(["superadmin"]), monitorController.deleteAllMonitors);
+
+ // Other static routes
+ router.post("/demo", isAllowed(["admin", "superadmin"]), monitorController.addDemoMonitors);
+ router.get("/export/json", isAllowed(["admin", "superadmin"]), monitorController.exportMonitorsToJSON);
+ router.post("/import/json", isAllowed(["admin", "superadmin"]), monitorController.importMonitorsFromJSON);
+
+ router.get("/games", monitorController.getAllGames);
+
+ // Individual monitor CRUD routes
+ router.get("/:monitorId", monitorController.getMonitorById);
+ router.patch("/:monitorId", isAllowed(["admin", "superadmin"]), monitorController.editMonitor);
+ router.delete("/:monitorId", isAllowed(["admin", "superadmin"]), monitorController.deleteMonitor);
+ return router;
+};
diff --git a/server/src/api/routes/notificationRoute.ts b/server/src/api/routes/notificationRoute.ts
deleted file mode 100644
index 16d9a7086..000000000
--- a/server/src/api/routes/notificationRoute.ts
+++ /dev/null
@@ -1,31 +0,0 @@
-import { INotificationController } from "@/api/controllers/notificationController.js";
-import { Router } from "express";
-class NotificationRoutes {
- private router: Router;
- private notificationController: INotificationController;
-
- constructor(notificationController: INotificationController) {
- this.router = Router();
- this.notificationController = notificationController;
- this.initializeRoutes();
- }
-
- initializeRoutes() {
- this.router.post("/", this.notificationController.createNotification);
-
- this.router.post("/test/all", this.notificationController.testAllNotifications);
- this.router.post("/test", this.notificationController.testNotification);
-
- this.router.get("/team", this.notificationController.getNotificationsByTeamId);
-
- this.router.get("/:id", this.notificationController.getNotificationById);
- this.router.delete("/:id", this.notificationController.deleteNotification);
- this.router.patch("/:id", this.notificationController.editNotification);
- }
-
- getRouter() {
- return this.router;
- }
-}
-
-export default NotificationRoutes;
diff --git a/server/src/api/routes/notificationRoutes.ts b/server/src/api/routes/notificationRoutes.ts
new file mode 100644
index 000000000..32ea1923c
--- /dev/null
+++ b/server/src/api/routes/notificationRoutes.ts
@@ -0,0 +1,14 @@
+import { INotificationController } from "@/api/controllers/notificationController.js";
+import { Router } from "express";
+
+export const createNotificationRoutes = (notificationController: INotificationController): Router => {
+ const router = Router();
+ router.post("/", notificationController.createNotification);
+ router.post("/test/all", notificationController.testAllNotifications);
+ router.post("/test", notificationController.testNotification);
+ router.get("/team", notificationController.getNotificationsByTeamId);
+ router.get("/:id", notificationController.getNotificationById);
+ router.delete("/:id", notificationController.deleteNotification);
+ router.patch("/:id", notificationController.editNotification);
+ return router;
+};
diff --git a/server/src/api/routes/queueRoute.ts b/server/src/api/routes/queueRoute.ts
deleted file mode 100644
index 0a9d41bb1..000000000
--- a/server/src/api/routes/queueRoute.ts
+++ /dev/null
@@ -1,26 +0,0 @@
-import { Router } from "express";
-import { isAllowed } from "@/api/middleware/isAllowed.js";
-import { IJobQueueController } from "@/api/controllers/queueController.js";
-class QueueRoutes {
- private router: Router;
- private queueController: IJobQueueController;
-
- constructor(queueController: IJobQueueController) {
- this.router = Router();
- this.queueController = queueController;
- this.initRoutes();
- }
- initRoutes() {
- this.router.get("/jobs", isAllowed(["admin", "superadmin"]), this.queueController.getJobs);
-
- this.router.get("/metrics", isAllowed(["admin", "superadmin"]), this.queueController.getMetrics);
- this.router.get("/all-metrics", isAllowed(["admin", "superadmin"]), this.queueController.getAllMetrics);
- this.router.post("/flush", isAllowed(["admin", "superadmin"]), this.queueController.flushQueue);
- }
-
- getRouter() {
- return this.router;
- }
-}
-
-export default QueueRoutes;
diff --git a/server/src/api/routes/queueRoutes.ts b/server/src/api/routes/queueRoutes.ts
new file mode 100644
index 000000000..9a0d2f2e5
--- /dev/null
+++ b/server/src/api/routes/queueRoutes.ts
@@ -0,0 +1,12 @@
+import { Router } from "express";
+import { isAllowed } from "@/api/middleware/isAllowed.js";
+import { IJobQueueController } from "@/api/controllers/queueController.js";
+
+export const createQueueRoutes = (queueController: IJobQueueController): Router => {
+ const router = Router();
+ router.get("/jobs", isAllowed(["admin", "superadmin"]), queueController.getJobs);
+ router.get("/metrics", isAllowed(["admin", "superadmin"]), queueController.getMetrics);
+ router.get("/all-metrics", isAllowed(["admin", "superadmin"]), queueController.getAllMetrics);
+ router.post("/flush", isAllowed(["admin", "superadmin"]), queueController.flushQueue);
+ return router;
+};
diff --git a/server/src/api/routes/settingsRoute.ts b/server/src/api/routes/settingsRoute.ts
deleted file mode 100644
index 3bb0e75fd..000000000
--- a/server/src/api/routes/settingsRoute.ts
+++ /dev/null
@@ -1,26 +0,0 @@
-import { Router } from "express";
-import { isAllowed } from "../middleware/isAllowed.js";
-import { ISettingsController } from "@/api/controllers/settingsController.js";
-
-class SettingsRoutes {
- private router: Router;
- private settingsController: ISettingsController;
-
- constructor(settingsController: ISettingsController) {
- this.router = Router();
- this.settingsController = settingsController;
- this.initRoutes();
- }
-
- initRoutes() {
- this.router.get("/", this.settingsController.getAppSettings);
- this.router.patch("/", isAllowed(["admin", "superadmin"]), this.settingsController.updateAppSettings);
- this.router.post("/test-email", isAllowed(["admin", "superadmin"]), this.settingsController.sendTestEmail);
- }
-
- getRouter() {
- return this.router;
- }
-}
-
-export default SettingsRoutes;
diff --git a/server/src/api/routes/settingsRoutes.ts b/server/src/api/routes/settingsRoutes.ts
new file mode 100644
index 000000000..5b6e6c9a5
--- /dev/null
+++ b/server/src/api/routes/settingsRoutes.ts
@@ -0,0 +1,11 @@
+import { Router } from "express";
+import { isAllowed } from "../middleware/isAllowed.js";
+import { ISettingsController } from "@/api/controllers/settingsController.js";
+
+export const createSettingsRoutes = (settingsController: ISettingsController): Router => {
+ const router = Router();
+ router.get("/", settingsController.getAppSettings);
+ router.patch("/", isAllowed(["admin", "superadmin"]), settingsController.updateAppSettings);
+ router.post("/test-email", isAllowed(["admin", "superadmin"]), settingsController.sendTestEmail);
+ return router;
+};
diff --git a/server/src/api/routes/statusPageRoute.ts b/server/src/api/routes/statusPageRoute.ts
deleted file mode 100644
index 816c7212a..000000000
--- a/server/src/api/routes/statusPageRoute.ts
+++ /dev/null
@@ -1,32 +0,0 @@
-import { IStatusPageController } from "@/api/controllers/statusPageController.js";
-import { RequestHandler, Router } from "express";
-import { isAllowed } from "@/api/middleware/isAllowed.js";
-import { imageUpload } from "@/api/middleware/upload.js";
-
-class StatusPageRoutes {
- private router: Router;
- private statusPageController: IStatusPageController;
-
- constructor(statusPageController: IStatusPageController, verifyJWT: RequestHandler, verifyStatusPageAccess: RequestHandler) {
- this.router = Router();
- this.statusPageController = statusPageController;
- this.initRoutes(verifyJWT, verifyStatusPageAccess);
- }
-
- initRoutes(verifyJWT: RequestHandler, verifyStatusPageAccess: RequestHandler) {
- this.router.get("/team", verifyJWT, this.statusPageController.getStatusPagesByTeamId);
-
- this.router.post("/", imageUpload.single("logo"), verifyJWT, isAllowed(["admin", "superadmin"]), this.statusPageController.createStatusPage);
- this.router.put("/:id", imageUpload.single("logo"), verifyJWT, isAllowed(["admin", "superadmin"]), this.statusPageController.updateStatusPage);
-
- this.router.get("/resolve", this.statusPageController.resolveStatusPageByDomain);
- this.router.get("/:url", verifyStatusPageAccess, this.statusPageController.getStatusPageByUrl);
- this.router.delete("/:id", verifyJWT, isAllowed(["admin", "superadmin"]), this.statusPageController.deleteStatusPage);
- }
-
- getRouter() {
- return this.router;
- }
-}
-
-export default StatusPageRoutes;
diff --git a/server/src/api/routes/statusPageRoutes.ts b/server/src/api/routes/statusPageRoutes.ts
new file mode 100644
index 000000000..b37ae5499
--- /dev/null
+++ b/server/src/api/routes/statusPageRoutes.ts
@@ -0,0 +1,19 @@
+import { IStatusPageController } from "@/api/controllers/statusPageController.js";
+import { RequestHandler, Router } from "express";
+import { isAllowed } from "@/api/middleware/isAllowed.js";
+import { imageUpload } from "@/api/middleware/upload.js";
+
+export const createStatusPageRoutes = (
+ statusPageController: IStatusPageController,
+ verifyJWT: RequestHandler,
+ verifyStatusPageAccess: RequestHandler
+): Router => {
+ const router = Router();
+ router.get("/team", verifyJWT, statusPageController.getStatusPagesByTeamId);
+ router.post("/", imageUpload.single("logo"), verifyJWT, isAllowed(["admin", "superadmin"]), statusPageController.createStatusPage);
+ router.put("/:id", imageUpload.single("logo"), verifyJWT, isAllowed(["admin", "superadmin"]), statusPageController.updateStatusPage);
+ router.get("/resolve", statusPageController.resolveStatusPageByDomain);
+ router.get("/:url", verifyStatusPageAccess, statusPageController.getStatusPageByUrl);
+ router.delete("/:id", verifyJWT, isAllowed(["admin", "superadmin"]), statusPageController.deleteStatusPage);
+ return router;
+};
diff --git a/server/src/api/routes/tagRoutes.ts b/server/src/api/routes/tagRoutes.ts
index c1a3f7972..d9632ce9c 100644
--- a/server/src/api/routes/tagRoutes.ts
+++ b/server/src/api/routes/tagRoutes.ts
@@ -1,28 +1,12 @@
import { ITagsController } from "@/api/controllers/tagController.js";
import { Router } from "express";
-class TagRoutes {
- private router: Router;
- private tagController: ITagsController;
- constructor(tagController: ITagsController) {
- this.router = Router();
- this.tagController = tagController;
- this.initializeRoutes();
- }
-
- initializeRoutes() {
- this.router.post("/", this.tagController.createTag);
-
- this.router.get("/team", this.tagController.getTagsByTeamId);
-
- this.router.get("/:id", this.tagController.getTagById);
- this.router.delete("/:id", this.tagController.deleteTag);
- this.router.patch("/:id", this.tagController.editTag);
- }
-
- getRouter() {
- return this.router;
- }
-}
-
-export default TagRoutes;
+export const createTagRoutes = (tagController: ITagsController): Router => {
+ const router = Router();
+ router.post("/", tagController.createTag);
+ router.get("/team", tagController.getTagsByTeamId);
+ router.get("/:id", tagController.getTagById);
+ router.delete("/:id", tagController.deleteTag);
+ router.patch("/:id", tagController.editTag);
+ return router;
+};
diff --git a/server/src/config/routes.ts b/server/src/config/routes.ts
index 855c18abb..810594354 100644
--- a/server/src/config/routes.ts
+++ b/server/src/config/routes.ts
@@ -5,52 +5,51 @@ import { authApiLimiter } from "../api/middleware/rateLimiter.js";
import type { InitializedControllers } from "./controllers.js";
import type { ApiServices } from "@/config/services.api.js";
-import AuthRoutes from "../api/routes/authRoute.js";
-import InviteRoutes from "../api/routes/inviteRoute.js";
-import MonitorRoutes from "../api/routes/monitorRoute.js";
-import CheckRoutes from "../api/routes/checkRoute.js";
-import GeoCheckRoutes from "../api/routes/geoCheckRoutes.js";
-import SettingsRoutes from "../api/routes/settingsRoute.js";
-import MaintenanceWindowRoutes from "../api/routes/maintenanceWindowRoute.js";
-import StatusPageRoutes from "../api/routes/statusPageRoute.js";
-import QueueRoutes from "../api/routes/queueRoute.js";
-import LogRoutes from "../api/routes/logRoutes.js";
-import DiagnosticRoutes from "../api/routes/diagnosticRoute.js";
-import NotificationRoutes from "../api/routes/notificationRoute.js";
-import TagRoutes from "../api/routes/tagRoutes.js";
-
-import IncidentRoutes from "../api/routes/incidentRoute.js";
+import { createAuthRoutes } from "@/api/routes/authRoutes.js";
+import { createInviteRoutes } from "@/api/routes/inviteRoutes.js";
+import { createMonitorRoutes } from "@/api/routes/monitorRoutes.js";
+import { createCheckRoutes } from "@/api/routes/checkRoutes.js";
+import { createGeoCheckRoutes } from "@/api/routes/geoCheckRoutes.js";
+import { createSettingsRoutes } from "@/api/routes/settingsRoutes.js";
+import { createMaintenanceWindowRoutes } from "@/api/routes/maintenanceWindowRoutes.js";
+import { createQueueRoutes } from "@/api/routes/queueRoutes.js";
+import { createLogRoutes } from "@/api/routes/logRoutes.js";
+import { createStatusPageRoutes } from "@/api/routes/statusPageRoutes.js";
+import { createDiagnosticRoutes } from "@/api/routes/diagnosticRoutes.js";
+import { createNotificationRoutes } from "@/api/routes/notificationRoutes.js";
+import { createTagRoutes } from "@/api/routes/tagRoutes.js";
+import { createIncidentRoutes } from "@/api/routes/incidentRoutes.js";
export const setupRoutes = (app: Application, controllers: InitializedControllers, apiServices: ApiServices) => {
const verifyJWT = createVerifyJWT(apiServices.settingsService);
- const authRoutes = new AuthRoutes(controllers.authController, verifyJWT);
- const monitorRoutes = new MonitorRoutes(controllers.monitorController);
- const settingsRoutes = new SettingsRoutes(controllers.settingsController);
- const checkRoutes = new CheckRoutes(controllers.checkController);
- const geoCheckRoutes = new GeoCheckRoutes(controllers.geoCheckController);
- const inviteRoutes = new InviteRoutes(controllers.inviteController, verifyJWT);
- const maintenanceWindowRoutes = new MaintenanceWindowRoutes(controllers.maintenanceWindowController);
- const queueRoutes = new QueueRoutes(controllers.queueController);
- const logRoutes = new LogRoutes(controllers.logController);
+ const authRoutes = createAuthRoutes(controllers.authController, verifyJWT);
+ const monitorRoutes = createMonitorRoutes(controllers.monitorController);
+ const settingsRoutes = createSettingsRoutes(controllers.settingsController);
+ const checkRoutes = createCheckRoutes(controllers.checkController);
+ const geoCheckRoutes = createGeoCheckRoutes(controllers.geoCheckController);
+ const inviteRoutes = createInviteRoutes(controllers.inviteController, verifyJWT);
+ const maintenanceWindowRoutes = createMaintenanceWindowRoutes(controllers.maintenanceWindowController);
+ const queueRoutes = createQueueRoutes(controllers.queueController);
+ const logRoutes = createLogRoutes(controllers.logController);
const verifyStatusPageAccess = createVerifyStatusPageAccess(apiServices.statusPagesRepository, verifyJWT);
- const statusPageRoutes = new StatusPageRoutes(controllers.statusPageController, verifyJWT, verifyStatusPageAccess);
- const notificationRoutes = new NotificationRoutes(controllers.notificationController);
- const tagRoutes = new TagRoutes(controllers.tagController);
- const diagnosticRoutes = new DiagnosticRoutes(controllers.diagnosticController, verifyJWT);
- const incidentRoutes = new IncidentRoutes(controllers.incidentController);
+ const statusPageRoutes = createStatusPageRoutes(controllers.statusPageController, verifyJWT, verifyStatusPageAccess);
+ const notificationRoutes = createNotificationRoutes(controllers.notificationController);
+ const tagRoutes = createTagRoutes(controllers.tagController);
+ const diagnosticRoutes = createDiagnosticRoutes(controllers.diagnosticController, verifyJWT);
+ const incidentRoutes = createIncidentRoutes(controllers.incidentController);
- app.use("/api/v1/auth", authApiLimiter, authRoutes.getRouter());
- app.use("/api/v1/monitors", verifyJWT, monitorRoutes.getRouter());
- app.use("/api/v1/settings", verifyJWT, settingsRoutes.getRouter());
- app.use("/api/v1/checks", verifyJWT, checkRoutes.getRouter());
- app.use("/api/v1/geo-checks", verifyJWT, geoCheckRoutes.getRouter());
- app.use("/api/v1/invite", inviteRoutes.getRouter());
- app.use("/api/v1/maintenance-window", verifyJWT, maintenanceWindowRoutes.getRouter());
- app.use("/api/v1/queue", verifyJWT, queueRoutes.getRouter());
- app.use("/api/v1/logs", verifyJWT, logRoutes.getRouter());
- app.use("/api/v1/status-page", statusPageRoutes.getRouter());
- app.use("/api/v1/notifications", verifyJWT, notificationRoutes.getRouter());
- app.use("/api/v1/tags", verifyJWT, tagRoutes.getRouter());
- app.use("/api/v1/diagnostic", verifyJWT, diagnosticRoutes.getRouter());
- app.use("/api/v1/incidents", verifyJWT, incidentRoutes.getRouter());
+ app.use("/api/v1/auth", authApiLimiter, authRoutes);
+ app.use("/api/v1/monitors", verifyJWT, monitorRoutes);
+ app.use("/api/v1/settings", verifyJWT, settingsRoutes);
+ app.use("/api/v1/checks", verifyJWT, checkRoutes);
+ app.use("/api/v1/geo-checks", verifyJWT, geoCheckRoutes);
+ app.use("/api/v1/invite", inviteRoutes);
+ app.use("/api/v1/maintenance-window", verifyJWT, maintenanceWindowRoutes);
+ app.use("/api/v1/queue", verifyJWT, queueRoutes);
+ app.use("/api/v1/logs", verifyJWT, logRoutes);
+ app.use("/api/v1/status-page", statusPageRoutes);
+ app.use("/api/v1/notifications", verifyJWT, notificationRoutes);
+ app.use("/api/v1/tags", verifyJWT, tagRoutes);
+ app.use("/api/v1/diagnostic", verifyJWT, diagnosticRoutes);
+ app.use("/api/v1/incidents", verifyJWT, incidentRoutes);
};
From 5d3dc03c89005054ae0d1f1e70ffcb12f2d9954b Mon Sep 17 00:00:00 2001
From: Alex Holliday
Date: Tue, 21 Jul 2026 10:02:20 -0700
Subject: [PATCH 43/84] add a provider registry to notificaiton service,
replace positional args and switch statements
---
.../notifications/notification.service.ts | 146 ++++++------------
1 file changed, 50 insertions(+), 96 deletions(-)
diff --git a/server/src/domain/notifications/notification.service.ts b/server/src/domain/notifications/notification.service.ts
index 7fc6e3543..59d36d928 100644
--- a/server/src/domain/notifications/notification.service.ts
+++ b/server/src/domain/notifications/notification.service.ts
@@ -9,6 +9,10 @@ import type { MonitorActionDecision } from "@/worker/worker.helper.js";
import type { ISettingsService } from "@/domain/app-settings/app-settings.service.js";
import { ILogger } from "@/utils/logger.js";
import type { INotificationMessageBuilder } from "@/domain/notifications/notification.message-builder.js";
+import type { NotificationChannel } from "@/domain/notifications/notification.type.js";
+import { no } from "zod/v4/locales";
+
+export type NotificationProviderRegistry = Record;
export interface INotificationsService {
createNotification: (notificationData: Partial, userId: string, teamId: string) => Promise;
@@ -29,52 +33,29 @@ export class NotificationsService implements INotificationsService {
private notificationsRepository: INotificationsRepository;
private monitorsRepository: IMonitorsRepository;
- private webhookProvider: INotificationProvider;
- private emailProvider: INotificationProvider;
- private slackProvider: INotificationProvider;
- private discordProvider: INotificationProvider;
- private pagerDutyProvider: INotificationProvider;
- private matrixProvider: INotificationProvider;
- private teamsProvider: INotificationProvider;
- private telegramProvider: INotificationProvider;
- private pushoverProvider: INotificationProvider;
- private twilioProvider: INotificationProvider;
- private ntfyProvider: INotificationProvider;
- private logger: ILogger;
+ private providers: NotificationProviderRegistry;
private settingsService: ISettingsService;
+ private logger: ILogger;
private notificationMessageBuilder: INotificationMessageBuilder;
- constructor(
- notificationsRepository: INotificationsRepository,
- monitorsRepository: IMonitorsRepository,
- webhookProvider: INotificationProvider,
- emailProvider: INotificationProvider,
- slackProvider: INotificationProvider,
- discordProvider: INotificationProvider,
- pagerDutyProvider: INotificationProvider,
- matrixProvider: INotificationProvider,
- teamsProvider: INotificationProvider,
- telegramProvider: INotificationProvider,
- pushoverProvider: INotificationProvider,
- twilioProvider: INotificationProvider,
- ntfyProvider: INotificationProvider,
- settingsService: ISettingsService,
- logger: ILogger,
- notificationMessageBuilder: INotificationMessageBuilder
- ) {
+ constructor({
+ notificationsRepository,
+ monitorsRepository,
+ providers,
+ settingsService,
+ logger,
+ notificationMessageBuilder,
+ }: {
+ notificationsRepository: INotificationsRepository;
+ monitorsRepository: IMonitorsRepository;
+ providers: NotificationProviderRegistry;
+ settingsService: ISettingsService;
+ logger: ILogger;
+ notificationMessageBuilder: INotificationMessageBuilder;
+ }) {
this.notificationsRepository = notificationsRepository;
this.monitorsRepository = monitorsRepository;
- this.webhookProvider = webhookProvider;
- this.emailProvider = emailProvider;
- this.slackProvider = slackProvider;
- this.discordProvider = discordProvider;
- this.pagerDutyProvider = pagerDutyProvider;
- this.matrixProvider = matrixProvider;
- this.teamsProvider = teamsProvider;
- this.telegramProvider = telegramProvider;
- this.pushoverProvider = pushoverProvider;
- this.twilioProvider = twilioProvider;
- this.ntfyProvider = ntfyProvider;
+ this.providers = providers;
this.settingsService = settingsService;
this.logger = logger;
this.notificationMessageBuilder = notificationMessageBuilder;
@@ -97,37 +78,16 @@ export class NotificationsService implements INotificationsService {
}
// Route to provider based on notification type
- switch (notification.type) {
- case "webhook":
- return await this.webhookProvider.sendMessage!(notification, notificationMessage);
- case "slack":
- return await this.slackProvider.sendMessage!(notification, notificationMessage);
- case "matrix":
- return await this.matrixProvider.sendMessage!(notification, notificationMessage);
- case "pager_duty":
- return await this.pagerDutyProvider.sendMessage!(notification, notificationMessage);
- case "discord":
- return await this.discordProvider.sendMessage!(notification, notificationMessage);
- case "email":
- return await this.emailProvider.sendMessage!(notification, notificationMessage);
- case "teams":
- return await this.teamsProvider.sendMessage!(notification, notificationMessage);
- case "telegram":
- return await this.telegramProvider.sendMessage!(notification, notificationMessage);
- case "pushover":
- return await this.pushoverProvider.sendMessage!(notification, notificationMessage);
- case "twilio":
- return await this.twilioProvider.sendMessage!(notification, notificationMessage);
- case "ntfy":
- return await this.ntfyProvider.sendMessage!(notification, notificationMessage);
- default:
- this.logger.warn({
- message: `Unknown notification type: ${notification.type}`,
- service: SERVICE_NAME,
- method: "send",
- });
- return false;
+ const provider = this.providers[notification.type];
+ if (!provider) {
+ this.logger.warn({
+ message: `Unknown notification type: ${notification.type}`,
+ service: SERVICE_NAME,
+ method: "send",
+ });
+ return false;
}
+ return await provider.sendMessage(notification, notificationMessage);
};
private sendNotifications = async (monitor: Monitor, monitorStatusResponse: MonitorStatusResponse, decision: MonitorActionDecision) => {
@@ -165,32 +125,26 @@ export class NotificationsService implements INotificationsService {
};
sendTestNotification = async (notification: Partial) => {
- switch (notification.type) {
- case "email":
- return await this.emailProvider.sendTestAlert(notification);
- case "slack":
- return await this.slackProvider.sendTestAlert(notification);
- case "discord":
- return await this.discordProvider.sendTestAlert(notification);
- case "pager_duty":
- return await this.pagerDutyProvider.sendTestAlert(notification);
- case "matrix":
- return await this.matrixProvider.sendTestAlert(notification);
- case "webhook":
- return await this.webhookProvider.sendTestAlert(notification);
- case "teams":
- return await this.teamsProvider.sendTestAlert(notification);
- case "telegram":
- return await this.telegramProvider.sendTestAlert(notification);
- case "pushover":
- return await this.pushoverProvider.sendTestAlert(notification);
- case "twilio":
- return await this.twilioProvider.sendTestAlert(notification);
- case "ntfy":
- return await this.ntfyProvider.sendTestAlert(notification);
- default:
- return false;
+ const type = notification.type;
+ if (!type) {
+ this.logger.warn({
+ message: "Notification type not provided",
+ service: SERVICE_NAME,
+ method: "sendTestNotification",
+ });
+ return false;
}
+
+ const provider = this.providers[type];
+ if (!provider) {
+ this.logger.warn({
+ message: `Unknown notification type: ${notification.type}`,
+ service: SERVICE_NAME,
+ method: "sendTestNotification",
+ });
+ return false;
+ }
+ return await provider.sendTestAlert(notification);
};
testAllNotifications = async (notificationIds: string[]) => {
From 57e008c7ba4d75c836cf1a647cb473ff38d6221f Mon Sep 17 00:00:00 2001
From: Alex Holliday
Date: Tue, 21 Jul 2026 10:05:17 -0700
Subject: [PATCH 44/84] update services config
---
server/src/config/services.shared.ts | 33 ++++++++++++++++------------
1 file changed, 19 insertions(+), 14 deletions(-)
diff --git a/server/src/config/services.shared.ts b/server/src/config/services.shared.ts
index 49b73f52e..72fe3fcf4 100644
--- a/server/src/config/services.shared.ts
+++ b/server/src/config/services.shared.ts
@@ -21,6 +21,7 @@ import { GlobalPingService } from "@/service/globalPingService.js";
import { ILogger } from "@/utils/logger.js";
// Notification providers
+import type { NotificationProviderRegistry } from "@/domain/notifications/notification.service.js";
import { WebhookProvider } from "@/domain/notifications/providers/webhook.js";
import { SlackProvider } from "@/domain/notifications/providers/slack.js";
import { EmailProvider } from "@/domain/notifications/providers/email.js";
@@ -167,24 +168,28 @@ export const buildShared = async ({
const twilioProvider = new TwilioProvider(logger);
const ntfyProvider = new NtfyProvider(logger);
- const notificationsService = new NotificationsService(
+ const notificationProviders: NotificationProviderRegistry = {
+ webhook: webhookProvider,
+ email: emailProvider,
+ slack: slackProvider,
+ discord: discordProvider,
+ pager_duty: pagerDutyProvider,
+ matrix: matrixProvider,
+ teams: teamsProvider,
+ telegram: telegramProvider,
+ pushover: pushoverProvider,
+ twilio: twilioProvider,
+ ntfy: ntfyProvider,
+ };
+
+ const notificationsService = new NotificationsService({
notificationsRepository,
monitorsRepository,
- webhookProvider,
- emailProvider,
- slackProvider,
- discordProvider,
- pagerDutyProvider,
- matrixProvider,
- teamsProvider,
- telegramProvider,
- pushoverProvider,
- twilioProvider,
- ntfyProvider,
+ providers: notificationProviders,
settingsService,
logger,
- notificationMessageBuilder
- );
+ notificationMessageBuilder,
+ });
const sharedServices: SharedServices = {
logger,
From bb374db463a158f2f6c60f903dea7bd5cc4bd8d7 Mon Sep 17 00:00:00 2001
From: Alex Holliday
Date: Tue, 21 Jul 2026 10:07:09 -0700
Subject: [PATCH 45/84] remove stray import
---
server/src/domain/notifications/notification.service.ts | 1 -
1 file changed, 1 deletion(-)
diff --git a/server/src/domain/notifications/notification.service.ts b/server/src/domain/notifications/notification.service.ts
index 59d36d928..30141c3ea 100644
--- a/server/src/domain/notifications/notification.service.ts
+++ b/server/src/domain/notifications/notification.service.ts
@@ -10,7 +10,6 @@ import type { ISettingsService } from "@/domain/app-settings/app-settings.servic
import { ILogger } from "@/utils/logger.js";
import type { INotificationMessageBuilder } from "@/domain/notifications/notification.message-builder.js";
import type { NotificationChannel } from "@/domain/notifications/notification.type.js";
-import { no } from "zod/v4/locales";
export type NotificationProviderRegistry = Record;
From aad03b787ff927d56e02855483e6ed11dad9f2f2 Mon Sep 17 00:00:00 2001
From: Alex Holliday
Date: Tue, 21 Jul 2026 10:07:18 -0700
Subject: [PATCH 46/84] update tests
---
.../services/notificationsService.test.ts | 38 ++++++++++---------
1 file changed, 20 insertions(+), 18 deletions(-)
diff --git a/server/test/unit/services/notificationsService.test.ts b/server/test/unit/services/notificationsService.test.ts
index 8ba8b34aa..6fbbfa4a1 100644
--- a/server/test/unit/services/notificationsService.test.ts
+++ b/server/test/unit/services/notificationsService.test.ts
@@ -73,24 +73,26 @@ const createService = (overrides?: Record) => {
...overrides,
};
- const service = new NotificationsService(
- defaults.notificationsRepository as any,
- defaults.monitorsRepository as any,
- defaults.webhookProvider as any,
- defaults.emailProvider as any,
- defaults.slackProvider as any,
- defaults.discordProvider as any,
- defaults.pagerDutyProvider as any,
- defaults.matrixProvider as any,
- defaults.teamsProvider as any,
- defaults.telegramProvider as any,
- defaults.pushoverProvider as any,
- defaults.twilioProvider as any,
- defaults.ntfyProvider as any,
- defaults.settingsService as any,
- defaults.logger as any,
- defaults.notificationMessageBuilder as any
- );
+ const service = new NotificationsService({
+ notificationsRepository: defaults.notificationsRepository,
+ monitorsRepository: defaults.monitorsRepository,
+ providers: {
+ webhook: defaults.webhookProvider,
+ email: defaults.emailProvider,
+ slack: defaults.slackProvider,
+ discord: defaults.discordProvider,
+ pager_duty: defaults.pagerDutyProvider,
+ matrix: defaults.matrixProvider,
+ teams: defaults.teamsProvider,
+ telegram: defaults.telegramProvider,
+ pushover: defaults.pushoverProvider,
+ twilio: defaults.twilioProvider,
+ ntfy: defaults.ntfyProvider,
+ },
+ settingsService: defaults.settingsService,
+ logger: defaults.logger,
+ notificationMessageBuilder: defaults.notificationMessageBuilder,
+ } as any);
return { service, ...defaults };
};
From 6e047d2cdb20d5e96a95524dcd1ec18f4f05ce41 Mon Sep 17 00:00:00 2001
From: Alex Holliday
Date: Tue, 21 Jul 2026 10:25:50 -0700
Subject: [PATCH 47/84] convert positional args to config obj
---
server/src/worker/worker.db-queue.ts | 106 +++++++++++++--------------
1 file changed, 50 insertions(+), 56 deletions(-)
diff --git a/server/src/worker/worker.db-queue.ts b/server/src/worker/worker.db-queue.ts
index 3ea2fddb8..50e41a4f1 100644
--- a/server/src/worker/worker.db-queue.ts
+++ b/server/src/worker/worker.db-queue.ts
@@ -31,6 +31,25 @@ const CONCURRENCY: Record = {
"cleanup-retention": 1,
};
+export interface DBQueueWorkerDependencies {
+ logger: ILogger; // forwarded to super
+ isDbConnected: () => boolean;
+ jobsRepository: IJobsRepository; // forwarded
+ monitorsRepository: IMonitorsRepository; // forwarded
+ checksRepository: IChecksRepository;
+ checkService: ICheckService;
+ bufferService: IBufferService;
+ checkProducer: ICheckProducer;
+ checkEvaluator: ICheckEvaluator;
+ geoCheckPipeline: ICheckPipeline;
+ dispatcher: IReactorDispatcher;
+ helper: IWorkerHelper;
+ queueWorkersRepository: IQueueWorkersRepository; // forwarded
+ queueMode: QueueMode; // forwarded
+ queuePrimaryProcesses: boolean; // consumed immediately
+ workerId: string;
+}
+
export class DBQueueWorker extends JobScheduler implements IQueueWorker {
static SERVICE_NAME = SERVICE_NAME;
@@ -45,68 +64,43 @@ export class DBQueueWorker extends JobScheduler implements IQueueWorker {
"cleanup-retention": 0,
};
- constructor(
- logger: ILogger, // forwarded to super
- private isDbConnected: () => boolean,
- jobsRepository: IJobsRepository, // forwarded
- monitorsRepository: IMonitorsRepository, // forwarded
- private checksRepository: IChecksRepository,
- private checkService: ICheckService,
- private bufferService: IBufferService,
- private checkProducer: ICheckProducer,
- private checkEvaluator: ICheckEvaluator,
- private geoCheckPipeline: ICheckPipeline,
- private dispatcher: IReactorDispatcher,
- private helper: IWorkerHelper,
- queueWorkersRepository: IQueueWorkersRepository, // forwarded
- queueMode: QueueMode, // forwarded
- queuePrimaryProcesses: boolean, // consumed immediately
- workerId: string
- ) {
- super(jobsRepository, queueWorkersRepository, monitorsRepository, queueMode, logger, workerId);
- this.processesJobs = queuePrimaryProcesses === true || queueMode === "worker";
+ private checksRepository: IChecksRepository;
+ private checkProducer: ICheckProducer;
+ private checkService: ICheckService;
+ private checkEvaluator: ICheckEvaluator;
+ private dispatcher: IReactorDispatcher;
+ private geoCheckPipeline: ICheckPipeline;
+ private helper: IWorkerHelper;
+ private bufferService: IBufferService;
+ private isDbConnected: () => boolean;
+
+ constructor(dependencies: DBQueueWorkerDependencies) {
+ super(
+ dependencies.jobsRepository,
+ dependencies.queueWorkersRepository,
+ dependencies.monitorsRepository,
+ dependencies.queueMode,
+ dependencies.logger,
+ dependencies.workerId
+ );
+ this.processesJobs = dependencies.queuePrimaryProcesses === true || dependencies.queueMode === "worker";
+ this.checksRepository = dependencies.checksRepository;
+ this.checkProducer = dependencies.checkProducer;
+ this.checkService = dependencies.checkService;
+ this.checkEvaluator = dependencies.checkEvaluator;
+ this.dispatcher = dependencies.dispatcher;
+ this.geoCheckPipeline = dependencies.geoCheckPipeline;
+ this.helper = dependencies.helper;
+ this.bufferService = dependencies.bufferService;
+ this.isDbConnected = dependencies.isDbConnected;
}
get serviceName() {
return DBQueueWorker.SERVICE_NAME;
}
- static async create(
- logger: ILogger,
- isDbConnected: () => boolean,
- jobsRepository: IJobsRepository,
- monitorsRepository: IMonitorsRepository,
- checksRepository: IChecksRepository,
- checkService: ICheckService,
- bufferService: IBufferService,
- checkProducer: ICheckProducer,
- checkEvaluator: ICheckEvaluator,
- geoCheckPipeline: ICheckPipeline,
- dispatcher: IReactorDispatcher,
- helper: IWorkerHelper,
- queueWorkersRepository: IQueueWorkersRepository,
- queueMode: QueueMode,
- queuePrimaryProcesses: boolean,
- workerId: string
- ): Promise {
- const instance = new DBQueueWorker(
- logger,
- isDbConnected,
- jobsRepository,
- monitorsRepository,
- checksRepository,
- checkService,
- bufferService,
- checkProducer,
- checkEvaluator,
- geoCheckPipeline,
- dispatcher,
- helper,
- queueWorkersRepository,
- queueMode,
- queuePrimaryProcesses,
- workerId
- );
+ static async create(dependencies: DBQueueWorkerDependencies): Promise {
+ const instance = new DBQueueWorker(dependencies);
await instance.init();
return instance;
}
From 936d9cd9a0144864250349a13b523179d23847e3 Mon Sep 17 00:00:00 2001
From: Alex Holliday
Date: Tue, 21 Jul 2026 10:26:02 -0700
Subject: [PATCH 48/84] update call site
---
server/src/config/services.worker.ts | 16 ++++++++--------
1 file changed, 8 insertions(+), 8 deletions(-)
diff --git a/server/src/config/services.worker.ts b/server/src/config/services.worker.ts
index 44997af5f..b0bdff2d6 100644
--- a/server/src/config/services.worker.ts
+++ b/server/src/config/services.worker.ts
@@ -131,9 +131,9 @@ export const buildWorker = async (shared: SharedServices, envSettings: EnvConfig
geoChecksRepository
);
- const worker = await DBQueueWorker.create(
+ const worker = await DBQueueWorker.create({
logger,
- () => mongoose.connection.readyState === 1,
+ isDbConnected: () => mongoose.connection.readyState === 1,
jobsRepository,
monitorsRepository,
checksRepository,
@@ -142,13 +142,13 @@ export const buildWorker = async (shared: SharedServices, envSettings: EnvConfig
checkProducer,
checkEvaluator,
geoCheckPipeline,
- reactorDispatcher,
- workerHelper,
+ dispatcher: reactorDispatcher,
+ helper: workerHelper,
queueWorkersRepository,
- envSettings.queueMode,
- envSettings.queuePrimaryProcesses,
- workerId
- );
+ queueMode: envSettings.queueMode,
+ queuePrimaryProcesses: envSettings.queuePrimaryProcesses,
+ workerId,
+ });
return { worker, networkService, bufferService, statusService };
};
From c567e4be5e59dd5deca9d32083c347f136c52ae3 Mon Sep 17 00:00:00 2001
From: Alex Holliday
Date: Tue, 21 Jul 2026 10:26:10 -0700
Subject: [PATCH 49/84] Update tests
---
.../test/unit/services/dbQueueWorker.test.ts | 48 +++++--------------
1 file changed, 12 insertions(+), 36 deletions(-)
diff --git a/server/test/unit/services/dbQueueWorker.test.ts b/server/test/unit/services/dbQueueWorker.test.ts
index 68df5f02a..4956e474c 100644
--- a/server/test/unit/services/dbQueueWorker.test.ts
+++ b/server/test/unit/services/dbQueueWorker.test.ts
@@ -139,24 +139,12 @@ const createWorker = (overrides?: { queueMode?: QueueMode; queuePrimaryProcesses
...overrides?.mocks,
};
- const worker = new DBQueueWorker(
- mocks.logger as any,
- mocks.isDbConnected as any,
- mocks.jobsRepository as any,
- mocks.monitorsRepository as any,
- mocks.checksRepository as any,
- mocks.checkService as any,
- mocks.bufferService as any,
- mocks.checkProducer as any,
- mocks.checkEvaluator as any,
- mocks.geoCheckPipeline as any,
- mocks.dispatcher as any,
- mocks.helper as any,
- mocks.queueWorkersRepository as any,
- overrides?.queueMode ?? "worker",
- overrides?.queuePrimaryProcesses ?? true,
- "worker-1"
- );
+ const worker = new DBQueueWorker({
+ ...mocks,
+ queueMode: overrides?.queueMode ?? "worker",
+ queuePrimaryProcesses: overrides?.queuePrimaryProcesses ?? true,
+ workerId: "worker-1",
+ } as any);
return { worker, mocks };
};
@@ -243,24 +231,12 @@ describe("DBQueueWorker", () => {
it("create() constructs the worker and runs init", async () => {
const { mocks } = createWorker(); // reuse the mock set; the un-init'd instance is discarded
- const worker = await DBQueueWorker.create(
- mocks.logger as any,
- mocks.isDbConnected as any,
- mocks.jobsRepository as any,
- mocks.monitorsRepository as any,
- mocks.checksRepository as any,
- mocks.checkService as any,
- mocks.bufferService as any,
- mocks.checkProducer as any,
- mocks.checkEvaluator as any,
- mocks.geoCheckPipeline as any,
- mocks.dispatcher as any,
- mocks.helper as any,
- mocks.queueWorkersRepository as any,
- "worker",
- true,
- "worker-1"
- );
+ const worker = await DBQueueWorker.create({
+ ...mocks,
+ queueMode: "worker",
+ queuePrimaryProcesses: true,
+ workerId: "worker-1",
+ } as any);
active = worker;
await jest.advanceTimersByTimeAsync(1);
From 8e600e9a87e783d2a584e69c5e5116b9c5a6907f Mon Sep 17 00:00:00 2001
From: Alex Holliday
Date: Tue, 21 Jul 2026 10:48:18 -0700
Subject: [PATCH 50/84] remove inline enum definitions
---
server/src/api/validation/checkValidation.ts | 15 ++++++++-------
server/src/api/validation/incidentValidation.ts | 8 +++++---
.../api/validation/maintenanceWindowValidation.ts | 3 ++-
server/src/api/validation/monitorValidation.ts | 13 +++++++------
server/src/api/validation/statusPageValidation.ts | 3 ++-
server/src/domain/incidents/incident.type.ts | 4 ++--
server/src/types/query.ts | 8 ++++++++
7 files changed, 34 insertions(+), 20 deletions(-)
create mode 100644 server/src/types/query.ts
diff --git a/server/src/api/validation/checkValidation.ts b/server/src/api/validation/checkValidation.ts
index db0978357..d041c9e8f 100644
--- a/server/src/api/validation/checkValidation.ts
+++ b/server/src/api/validation/checkValidation.ts
@@ -2,6 +2,7 @@ import { z } from "zod";
import { booleanCoercion } from "./shared.js";
import { GeoContinents } from "@/domain/geo-checks/geo-check.type.js";
import { MonitorTypes } from "@/domain/monitors/monitor.types.js";
+import { CheckFilters, DateRanges, SortOrders } from "@/types/query.js";
//****************************************
// Check Validations
@@ -13,10 +14,10 @@ export const getChecksParamValidation = z.object({
export const getChecksQueryValidation = z.object({
type: z.enum(MonitorTypes).optional(),
- sortOrder: z.enum(["asc", "desc"]),
+ sortOrder: z.enum(SortOrders),
limit: z.coerce.number().optional(),
- dateRange: z.enum(["recent", "hour", "day", "week", "month", "all"]),
- filter: z.enum(["all", "up", "down", "resolve"]).optional(),
+ dateRange: z.enum(DateRanges),
+ filter: z.enum(CheckFilters).optional(),
ack: booleanCoercion.optional(),
page: z.coerce.number(),
rowsPerPage: z.coerce.number(),
@@ -25,10 +26,10 @@ export const getChecksQueryValidation = z.object({
});
export const getTeamChecksQueryValidation = z.object({
- sortOrder: z.enum(["asc", "desc"]),
+ sortOrder: z.enum(SortOrders),
limit: z.coerce.number().optional(),
- dateRange: z.enum(["recent", "hour", "day", "week", "month", "all"]),
- filter: z.enum(["all", "up", "down", "resolve"]).optional(),
+ dateRange: z.enum(DateRanges),
+ filter: z.enum(CheckFilters).optional(),
ack: booleanCoercion.optional(),
page: z.coerce.number(),
rowsPerPage: z.coerce.number(),
@@ -39,5 +40,5 @@ export const deleteChecksParamValidation = z.object({
});
export const getChecksSummaryByTeamIdQueryValidation = z.object({
- dateRange: z.enum(["recent", "hour", "day", "week", "month", "all"]).optional(),
+ dateRange: z.enum(DateRanges).optional(),
});
diff --git a/server/src/api/validation/incidentValidation.ts b/server/src/api/validation/incidentValidation.ts
index 2047eefa5..322eaa2d3 100644
--- a/server/src/api/validation/incidentValidation.ts
+++ b/server/src/api/validation/incidentValidation.ts
@@ -1,18 +1,20 @@
import { z } from "zod";
import { booleanCoercion } from "./shared.js";
+import { DateRanges, SortOrders } from "@/types/query.js";
+import { IncidentResolutionTypes } from "@/domain/incidents/incident.type.js";
//****************************************
// Incident Validations
//****************************************
export const getIncidentsByTeamQueryValidation = z.object({
- sortOrder: z.enum(["asc", "desc"]),
- dateRange: z.enum(["recent", "hour", "day", "week", "month", "all"]).default("all"),
+ sortOrder: z.enum(SortOrders),
+ dateRange: z.enum(DateRanges).default("all"),
page: z.coerce.number().int().min(0),
rowsPerPage: z.coerce.number().int().min(1),
status: booleanCoercion.optional(),
monitorId: z.string().optional(),
- resolutionType: z.enum(["manual", "automatic"]).optional(),
+ resolutionType: z.enum(IncidentResolutionTypes).optional(),
});
export const getIncidentSummaryQueryValidation = z.object({
diff --git a/server/src/api/validation/maintenanceWindowValidation.ts b/server/src/api/validation/maintenanceWindowValidation.ts
index 2ee22bc19..c054d5b5d 100644
--- a/server/src/api/validation/maintenanceWindowValidation.ts
+++ b/server/src/api/validation/maintenanceWindowValidation.ts
@@ -1,6 +1,7 @@
import { z } from "zod";
import { booleanCoercion } from "./shared.js";
import { DurationUnits } from "@/domain/maintenance-windows/maintenance-window.type.js";
+import { SortOrders } from "@/types/query.js";
const dateToString = z.coerce.date().transform((d) => d.toISOString());
@@ -49,7 +50,7 @@ export const getMaintenanceWindowsByTeamIdQueryValidation = z.object({
page: z.coerce.number().optional(),
rowsPerPage: z.coerce.number().optional(),
field: z.string().optional(),
- order: z.enum(["asc", "desc"]).optional(),
+ order: z.enum(SortOrders).optional(),
});
export const getMaintenanceWindowsByMonitorIdParamValidation = z.object({
diff --git a/server/src/api/validation/monitorValidation.ts b/server/src/api/validation/monitorValidation.ts
index e8bae9261..1ed9dca84 100644
--- a/server/src/api/validation/monitorValidation.ts
+++ b/server/src/api/validation/monitorValidation.ts
@@ -11,6 +11,7 @@ import {
MonitorTypes,
PageSpeedStrategies,
} from "@/domain/monitors/monitor.types.js";
+import { DateRanges, SortOrders } from "@/types/query.js";
const httpStatusCode = z.number().refine((code) => HttpStatusCodeSet.has(code), { message: "Must be a valid HTTP status code" });
@@ -20,9 +21,9 @@ export const getMonitorByIdParamValidation = z.object({
export const getMonitorByIdQueryValidation = z.object({
status: booleanCoercion.optional(),
- sortOrder: z.enum(["asc", "desc"]).optional(),
+ sortOrder: z.enum(SortOrders).optional(),
limit: z.coerce.number().optional(),
- dateRange: z.enum(["recent", "hour", "day", "week", "month", "all"]).optional(),
+ dateRange: z.enum(DateRanges).optional(),
numToDisplay: z.coerce.number().optional(),
normalize: booleanCoercion.optional(),
continent: z.union([z.enum(GeoContinents), z.array(z.enum(GeoContinents))]).optional(),
@@ -42,7 +43,7 @@ export const getMonitorsWithChecksQueryValidation = z.object({
rowsPerPage: z.coerce.number().int().min(1).max(100).optional(),
filter: z.union([z.string(), z.literal("")]).optional(),
field: z.string().optional(),
- order: z.enum(["asc", "desc"]).optional(),
+ order: z.enum(SortOrders).optional(),
type: z.union([z.enum(MonitorTypes), z.array(z.enum(MonitorTypes))]).optional(),
tags: z.union([z.string(), z.array(z.string())]).optional(),
explain: booleanCoercion.optional(),
@@ -195,7 +196,7 @@ export const getUptimeDetailsByIdParamValidation = z.object({
});
export const getUptimeDetailsByIdQueryValidation = z.object({
- dateRange: z.enum(["recent", "hour", "day", "week", "month", "all"]),
+ dateRange: z.enum(DateRanges),
normalize: booleanCoercion.optional(),
});
@@ -206,7 +207,7 @@ const importedMonitorSchema = z
teamId: z.string().optional(),
name: z.string().min(1, "Name is required"),
description: z.union([z.string(), z.literal("")]).optional(),
- status: z.enum(["up", "down", "paused", "initializing", "maintenance", "breached"]).default("initializing"),
+ status: z.enum(MonitorStatuses).default("initializing"),
statusWindow: z.array(z.boolean()).default([]),
statusWindowSize: z.number().min(1).max(20).default(5),
statusWindowThreshold: z.number().min(1).max(100).default(60),
@@ -263,7 +264,7 @@ export const getHardwareDetailsByIdParamValidation = z.object({
});
export const getHardwareDetailsByIdQueryValidation = z.object({
- dateRange: z.enum(["recent", "hour", "day", "week", "month", "all"]).optional(),
+ dateRange: z.enum(DateRanges).optional(),
});
// Canonical monitor shape returned by /monitors endpoints. Keep aligned with
diff --git a/server/src/api/validation/statusPageValidation.ts b/server/src/api/validation/statusPageValidation.ts
index a650129cd..ee2b7376c 100644
--- a/server/src/api/validation/statusPageValidation.ts
+++ b/server/src/api/validation/statusPageValidation.ts
@@ -3,6 +3,7 @@ import { booleanCoercion, dnsHostnameRegex } from "./shared.js";
import { StatusPageTypes, StatusPageThemes, StatusPageThemeModes } from "@/domain/status-pages/status-page.type.js";
import { normalizeStatusPageDomain } from "@/utils/statusPageDomain.js";
import { cssReferencesExternalResource } from "@/utils/customCss.js";
+import { ImageMimeTypes } from "@/types/upload.js";
//****************************************
// Status Page Validations
@@ -67,7 +68,7 @@ export const imageValidation = z
fieldname: z.string().min(1, "Field name is required"),
originalname: z.string().min(1, "Original name is required"),
encoding: z.string().min(1, "Encoding is required"),
- mimetype: z.enum(["image/jpeg", "image/png", "image/jpg"], {
+ mimetype: z.enum(ImageMimeTypes, {
message: "File must be a valid image (jpeg, jpg, or png)",
}),
size: z.number().max(3145728, "File size must be less than 3MB"),
diff --git a/server/src/domain/incidents/incident.type.ts b/server/src/domain/incidents/incident.type.ts
index 6b076ff83..2b45e72c4 100644
--- a/server/src/domain/incidents/incident.type.ts
+++ b/server/src/domain/incidents/incident.type.ts
@@ -1,7 +1,7 @@
// export type IncidentResolutionType = "automatic" | "manual" | null;
-export const IncidentResolutionTypes = ["automatic", "manual", null] as const;
-export type IncidentResolutionType = (typeof IncidentResolutionTypes)[number];
+export const IncidentResolutionTypes = ["automatic", "manual"] as const;
+export type IncidentResolutionType = (typeof IncidentResolutionTypes)[number] | null;
export interface Incident {
id: string;
diff --git a/server/src/types/query.ts b/server/src/types/query.ts
new file mode 100644
index 000000000..9809517ee
--- /dev/null
+++ b/server/src/types/query.ts
@@ -0,0 +1,8 @@
+export const DateRanges = ["recent", "hour", "day", "week", "month", "all"] as const;
+export type DateRange = (typeof DateRanges)[number];
+
+export const SortOrders = ["asc", "desc"] as const;
+export type SortOrder = (typeof SortOrders)[number];
+
+export const CheckFilters = ["all", "up", "down", "resolve"] as const;
+export type CheckFilter = (typeof CheckFilters)[number];
From ed1ef56184b0bef2264196dbe938ccdb25227c3f Mon Sep 17 00:00:00 2001
From: Iven Schlenther
Date: Wed, 22 Jul 2026 11:27:38 +0200
Subject: [PATCH 51/84] - Add `sx={{ overflowWrap: "anywhere" }}` to Typography
in BaseChart for improved text wrapping. - Adjust `alignItems` to `stretch`
and add `maxWidth` (260) to Gauge components in Infrastructure Details page.
- Update German translations for memory titles, temperatures, and labels in
locales file.
---
client/src/Components/design-elements/BaseChart.tsx | 1 +
.../Pages/Infrastructure/Details/Components/Gauges.tsx | 5 ++++-
client/src/locales/de.json | 8 ++++----
3 files changed, 9 insertions(+), 5 deletions(-)
diff --git a/client/src/Components/design-elements/BaseChart.tsx b/client/src/Components/design-elements/BaseChart.tsx
index c6d8298a7..ddfd82a3e 100644
--- a/client/src/Components/design-elements/BaseChart.tsx
+++ b/client/src/Components/design-elements/BaseChart.tsx
@@ -40,6 +40,7 @@ export const BaseChart = ({
{title}
diff --git a/client/src/Pages/Infrastructure/Details/Components/Gauges.tsx b/client/src/Pages/Infrastructure/Details/Components/Gauges.tsx
index 04c87ca95..e2b053aef 100644
--- a/client/src/Pages/Infrastructure/Details/Components/Gauges.tsx
+++ b/client/src/Pages/Infrastructure/Details/Components/Gauges.tsx
@@ -25,10 +25,11 @@ export const InfraDetailsGauges = ({
Date: Wed, 22 Jul 2026 11:33:06 +0200
Subject: [PATCH 52/84] Update English and German translations for "temp" label
to "CPU temperature" in locales files.
---
client/src/locales/de.json | 2 +-
client/src/locales/en.json | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/client/src/locales/de.json b/client/src/locales/de.json
index 29930bdfc..7b2e64945 100644
--- a/client/src/locales/de.json
+++ b/client/src/locales/de.json
@@ -825,7 +825,7 @@
"netBytesRecv": "{{name}} - Empfangene Bytes",
"netBytesSent": "{{name}} - Gesendete Bytes",
"netNoTraffic": "Capture meldet null Datenverkehr auf allen Schnittstellen. Das ist normal, wenn Capture in einem Container läuft, der nur Loopback-Schnittstellen sieht — führe Capture direkt auf dem Host aus, um echten Netzwerkverkehr zu sehen.",
- "temp": "Temperatur"
+ "temp": "CPU-Temperatur"
}
},
"gauges": {
diff --git a/client/src/locales/en.json b/client/src/locales/en.json
index 2adde28ec..d940f95fc 100644
--- a/client/src/locales/en.json
+++ b/client/src/locales/en.json
@@ -845,7 +845,7 @@
"netBytesRecv": "{{name}} - Bytes Received",
"netBytesSent": "{{name}} - Bytes Sent",
"loopbackOnlyNotice": "Capture is only reporting loopback or container-bridge interfaces (lo, lo0, docker0). This is normal when Capture runs inside a container that can only see its own network namespace — run Capture directly on the host to see real network activity.",
- "temp": "Temp"
+ "temp": "CPU temperature"
}
},
"gauges": {
From d53785b768a69cb97dfd71e0d0f3b51ec36f83d0 Mon Sep 17 00:00:00 2001
From: Alex Holliday
Date: Wed, 22 Jul 2026 10:03:48 -0700
Subject: [PATCH 53/84] remove unused utilities, fix delete profile image issue
---
server/src/api/controllers/controllerUtils.ts | 18 ------------------
server/src/api/validation/userValidation.ts | 3 ++-
.../src/domain/users/user.repository.mongo.ts | 4 +---
server/src/utils/utils.ts | 17 -----------------
4 files changed, 3 insertions(+), 39 deletions(-)
delete mode 100755 server/src/utils/utils.ts
diff --git a/server/src/api/controllers/controllerUtils.ts b/server/src/api/controllers/controllerUtils.ts
index 45bf3fa9f..be3f46533 100755
--- a/server/src/api/controllers/controllerUtils.ts
+++ b/server/src/api/controllers/controllerUtils.ts
@@ -54,24 +54,6 @@ export const optionalNumber = (value: unknown, fieldName: string): number | unde
throw new AppError({ message: `${fieldName} must be a number`, status: 400 });
};
-export const optionalBoolean = (value: unknown, fieldName: string): boolean | undefined => {
- if (value === undefined) {
- return undefined;
- }
- if (typeof value === "boolean") {
- return value;
- }
- if (typeof value === "string") {
- if (value === "true") {
- return true;
- }
- if (value === "false") {
- return false;
- }
- }
- throw new AppError({ message: `${fieldName} must be a boolean`, status: 400 });
-};
-
export const parseMonitorTypeFilter = (value: unknown): MonitorType | MonitorType[] | undefined => {
const parseSingle = (input: unknown): MonitorType => {
if (typeof input !== "string") {
diff --git a/server/src/api/validation/userValidation.ts b/server/src/api/validation/userValidation.ts
index 21fb55317..882c2d898 100644
--- a/server/src/api/validation/userValidation.ts
+++ b/server/src/api/validation/userValidation.ts
@@ -1,6 +1,6 @@
import { z } from "zod";
import { UserRoles } from "@/domain/users/user.type.js";
-import { nameValidation, lowercaseEmailValidation, passwordValidation } from "./shared.js";
+import { nameValidation, lowercaseEmailValidation, passwordValidation, booleanCoercion } from "@/api/validation/shared.js";
//****************************************
// User Validations
@@ -46,6 +46,7 @@ export const editUserBodyValidation = z
profilePicture: z.string().optional(),
password: passwordValidation.optional(),
newPassword: passwordValidation.optional(),
+ deleteProfileImage: booleanCoercion.optional(),
})
// Changing a password requires both the current password and the new one.
// The service only runs the change-password flow when both are present, so
diff --git a/server/src/domain/users/user.repository.mongo.ts b/server/src/domain/users/user.repository.mongo.ts
index b137c5f4c..af4b78b26 100644
--- a/server/src/domain/users/user.repository.mongo.ts
+++ b/server/src/domain/users/user.repository.mongo.ts
@@ -2,7 +2,6 @@ import { IUsersRepository } from "@/domain/users/user.repository.interface.js";
import { UserModel, type UserDocument } from "@/domain/users/user.model.js";
import type { User, UserProfileImage } from "@/domain/users/user.type.js";
import { GenerateAvatarImage } from "@/utils/imageProcessing.js";
-import { ParseBoolean } from "@/utils/utils.js";
import { AppError } from "@/utils/AppError.js";
import { toStringId, toDateString } from "@/utils/mongoMappers.js";
const SERVICE_NAME = "MongoUsersRepository";
@@ -87,9 +86,8 @@ class MongoUsersRepository implements IUsersRepository {
const candidateUser = { ...patch };
let unsetFields: Record | undefined;
- if (ParseBoolean(candidateUser.deleteProfileImage) === true) {
+ if (candidateUser.deleteProfileImage === true) {
unsetFields = { profileImage: 1, avatarImage: 1 };
- delete candidateUser.deleteProfileImage;
} else if (file) {
// 1. Save the full size image
candidateUser.profileImage = {
diff --git a/server/src/utils/utils.ts b/server/src/utils/utils.ts
deleted file mode 100755
index a82d8734f..000000000
--- a/server/src/utils/utils.ts
+++ /dev/null
@@ -1,17 +0,0 @@
-export const ParseBoolean = (value: boolean | string | null | undefined) => {
- if (value === true || value === "true") {
- return true;
- } else if (value === false || value === "false" || value === null || value === undefined) {
- return false;
- }
-};
-
-export const getTokenFromHeaders = (headers: Record) => {
- const authorizationHeader = headers.authorization;
- if (!authorizationHeader) throw new Error("No auth headers");
-
- const parts = authorizationHeader.split(" ");
- if (parts.length !== 2 || parts[0] !== "Bearer") throw new Error("Invalid auth headers");
-
- return parts[1];
-};
From c961f5292dbf3bf1173ee535ce6e7c1129fdbbbb Mon Sep 17 00:00:00 2001
From: Alex Holliday
Date: Wed, 22 Jul 2026 10:05:36 -0700
Subject: [PATCH 54/84] fix signature, add tests
---
server/src/domain/users/user.service.ts | 4 ++--
.../unit/validation/userValidation.test.ts | 21 +++++++++++++++++++
2 files changed, 23 insertions(+), 2 deletions(-)
diff --git a/server/src/domain/users/user.service.ts b/server/src/domain/users/user.service.ts
index 7bdb9677f..78a884a51 100644
--- a/server/src/domain/users/user.service.ts
+++ b/server/src/domain/users/user.service.ts
@@ -24,7 +24,7 @@ export interface IUserService {
createUser(userData: Partial, teamId: string, actorRoles: UserRole[], file: Express.Multer.File | null): Promise;
loginUser(email: string, password: string): Promise<{ user: User; token: string }>;
editUser(
- updates: Partial,
+ updates: Partial,
file: Express.Multer.File | null,
currentUserId: string,
currentUserEmail: string
@@ -242,7 +242,7 @@ export class UserService implements IUserService {
};
editUser = async (
- updates: Partial,
+ updates: Partial,
file: Express.Multer.File | null,
currentUserId: string,
currentUserEmail: string
diff --git a/server/test/unit/validation/userValidation.test.ts b/server/test/unit/validation/userValidation.test.ts
index c50a70caa..1c2ffb83a 100644
--- a/server/test/unit/validation/userValidation.test.ts
+++ b/server/test/unit/validation/userValidation.test.ts
@@ -43,4 +43,25 @@ describe("editUserBodyValidation", () => {
});
expect(result.success).toBe(false);
});
+
+ // Regression: `deleteProfileImage` was missing from this schema, so z.object()
+ // stripped it before it reached the repository and the remove-profile-image
+ // flow was silently a no-op. The flag arrives as the multipart form-data
+ // string "true", so it must both survive parsing and coerce to a boolean.
+ it("retains deleteProfileImage and coerces the multipart string 'true' to boolean true", () => {
+ const result = editUserBodyValidation.safeParse({ deleteProfileImage: "true" });
+ expect(result.success).toBe(true);
+ expect(result.data).toEqual({ deleteProfileImage: true });
+ });
+
+ it("coerces the multipart string 'false' to boolean false", () => {
+ const result = editUserBodyValidation.safeParse({ deleteProfileImage: "false" });
+ expect(result.success).toBe(true);
+ expect(result.data).toEqual({ deleteProfileImage: false });
+ });
+
+ it("rejects a non-boolean deleteProfileImage value", () => {
+ const result = editUserBodyValidation.safeParse({ deleteProfileImage: "banana" });
+ expect(result.success).toBe(false);
+ });
});
From 586e79efb284ac7a88574a11addbfb99b5dab520 Mon Sep 17 00:00:00 2001
From: Alex Holliday
Date: Wed, 22 Jul 2026 10:13:37 -0700
Subject: [PATCH 55/84] remove unused getters
---
server/src/api/controllers/authController.ts | 4 ----
server/src/api/controllers/checkController.ts | 5 -----
server/src/api/controllers/diagnosticController.ts | 4 ----
server/src/api/controllers/geoCheckController.ts | 4 ----
server/src/api/controllers/inviteController.ts | 4 ----
server/src/api/controllers/logController.ts | 3 ---
server/src/api/controllers/monitorController.ts | 4 ----
server/src/api/controllers/queueController.ts | 4 ----
server/src/api/controllers/settingsController.ts | 5 -----
server/src/api/controllers/statusPageController.ts | 5 -----
server/src/db/db.mongo.ts | 4 ----
.../src/domain/app-settings/app-settings.service.ts | 5 -----
server/src/domain/checks/check.service.ts | 4 ----
server/src/domain/diagnostics/diagnostic.service.ts | 4 ----
server/src/domain/geo-checks/geo-check.service.ts | 5 -----
server/src/domain/incidents/incident.service.ts | 4 ----
server/src/domain/invites/invite.service.ts | 4 ----
.../maintenance-windows/maintenance-window.service.ts | 4 ----
server/src/domain/monitors/monitor.service.ts | 6 ------
server/src/domain/users/user.service.ts | 4 ----
server/src/service/bufferService.ts | 4 ----
server/src/service/emailService.ts | 4 ----
server/src/service/globalPingService.ts | 5 -----
server/src/service/networkService.ts | 5 -----
server/src/service/statusService.ts | 4 ----
server/src/utils/logger.ts | 5 -----
server/src/worker/worker.db-queue.ts | 4 ----
server/src/worker/worker.helper.ts | 5 -----
server/src/worker/worker.interface.ts | 2 --
server/src/worker/worker.job-scheduler.ts | 3 ---
server/test/unit/services/bufferService.test.ts | 11 -----------
server/test/unit/services/checkService.test.ts | 7 -------
server/test/unit/services/diagnosticService.test.ts | 11 -----------
server/test/unit/services/emailService.test.ts | 5 -----
server/test/unit/services/geoChecksService.test.ts | 11 -----------
server/test/unit/services/globalPingService.test.ts | 11 -----------
server/test/unit/services/incidentService.test.ts | 7 -------
server/test/unit/services/inviteService.test.ts | 11 -----------
.../unit/services/maintenanceWindowService.test.ts | 11 -----------
server/test/unit/services/monitorService.test.ts | 7 -------
server/test/unit/services/networkService.test.ts | 11 -----------
server/test/unit/services/settingsService.test.ts | 11 -----------
server/test/unit/services/statusService.test.ts | 7 -------
server/test/unit/services/userService.test.ts | 7 -------
server/test/unit/services/workerHelper.test.ts | 7 -------
45 files changed, 262 deletions(-)
diff --git a/server/src/api/controllers/authController.ts b/server/src/api/controllers/authController.ts
index 1eb992529..59afd4cc8 100755
--- a/server/src/api/controllers/authController.ts
+++ b/server/src/api/controllers/authController.ts
@@ -50,10 +50,6 @@ class AuthController implements IAuthController {
this.userService = userService;
}
- get serviceName() {
- return AuthController.SERVICE_NAME;
- }
-
registerUser = catchAsync(async (req: Request, res: Response) => {
const newUser = req.body.user;
const newUserToken = req.body.token;
diff --git a/server/src/api/controllers/checkController.ts b/server/src/api/controllers/checkController.ts
index ccc91921d..525a00080 100755
--- a/server/src/api/controllers/checkController.ts
+++ b/server/src/api/controllers/checkController.ts
@@ -14,7 +14,6 @@ import { requireTeamId } from "@/api/controllers/controllerUtils.js";
const SERVICE_NAME = "checkController";
export interface ICheckController {
- serviceName: string;
getChecksByMonitor: RequestHandler;
getChecksByTeam: RequestHandler;
getChecksSummaryByTeamId: RequestHandler;
@@ -30,10 +29,6 @@ class CheckController implements ICheckController {
this.checkService = checkService;
}
- get serviceName() {
- return CheckController.SERVICE_NAME;
- }
-
getChecksByMonitor = catchAsync(async (req: Request, res: Response) => {
const validatedParams = getChecksParamValidation.parse(req.params);
const validatedQuery = getChecksQueryValidation.parse(req.query);
diff --git a/server/src/api/controllers/diagnosticController.ts b/server/src/api/controllers/diagnosticController.ts
index 0864f7237..382b4349a 100755
--- a/server/src/api/controllers/diagnosticController.ts
+++ b/server/src/api/controllers/diagnosticController.ts
@@ -17,10 +17,6 @@ class DiagnosticController implements IDiagnosticController {
this.diagnosticService = diagnosticService;
}
- get serviceName() {
- return DiagnosticController.SERVICE_NAME;
- }
-
getSystemStats = catchAsync(async (req: Request, res: Response) => {
const diagnostics = await this.diagnosticService.getSystemStats();
return res.status(200).json({
diff --git a/server/src/api/controllers/geoCheckController.ts b/server/src/api/controllers/geoCheckController.ts
index 81082b6ed..0c7f62260 100644
--- a/server/src/api/controllers/geoCheckController.ts
+++ b/server/src/api/controllers/geoCheckController.ts
@@ -17,10 +17,6 @@ class GeoCheckController implements IGeoCheckController {
this.geoChecksService = geoChecksService;
}
- get serviceName() {
- return GeoCheckController.SERVICE_NAME;
- }
-
getGeoChecksByMonitor = catchAsync(async (req: Request, res: Response) => {
const validatedParams = getChecksParamValidation.parse(req.params);
const validatedQuery = getChecksQueryValidation.parse(req.query);
diff --git a/server/src/api/controllers/inviteController.ts b/server/src/api/controllers/inviteController.ts
index 0efc1e0a7..5af172dfb 100755
--- a/server/src/api/controllers/inviteController.ts
+++ b/server/src/api/controllers/inviteController.ts
@@ -18,10 +18,6 @@ class InviteController implements IInviteController {
this.inviteService = inviteService;
}
- get serviceName() {
- return InviteController.SERVICE_NAME;
- }
-
getInviteToken = catchAsync(async (req: Request, res: Response) => {
const teamId = requireTeamId(req.user?.teamId);
const userRoles = requireUserRoles(req.user?.role);
diff --git a/server/src/api/controllers/logController.ts b/server/src/api/controllers/logController.ts
index f0449bbf2..bbbeef2a9 100644
--- a/server/src/api/controllers/logController.ts
+++ b/server/src/api/controllers/logController.ts
@@ -14,9 +14,6 @@ class LogController {
constructor(logger: ILogger) {
this.logger = logger;
}
- get serviceName() {
- return LogController.SERVICE_NAME;
- }
getLogs = catchAsync(async (req: Request, res: Response) => {
const logs = this.logger.getLogs();
diff --git a/server/src/api/controllers/monitorController.ts b/server/src/api/controllers/monitorController.ts
index 2ffb18802..f81e64f4a 100644
--- a/server/src/api/controllers/monitorController.ts
+++ b/server/src/api/controllers/monitorController.ts
@@ -57,10 +57,6 @@ class MonitorController implements IMonitorController {
this.notificationsService = notificationsService;
}
- get serviceName() {
- return MonitorController.SERVICE_NAME;
- }
-
getMonitorCertificate = catchAsync(async (req: Request, res: Response) => {
const validatedParams = getCertificateParamValidation.parse(req.params);
const teamId = requireTeamId(req.user?.teamId);
diff --git a/server/src/api/controllers/queueController.ts b/server/src/api/controllers/queueController.ts
index 72c1463f9..09e0474e5 100755
--- a/server/src/api/controllers/queueController.ts
+++ b/server/src/api/controllers/queueController.ts
@@ -16,10 +16,6 @@ class JobQueueController implements IJobQueueController {
static SERVICE_NAME = SERVICE_NAME;
constructor(private scheduler: IJobScheduler) {}
- get serviceName() {
- return JobQueueController.SERVICE_NAME;
- }
-
getMetrics = catchAsync(async (req: Request, res: Response) => {
const metrics = await this.scheduler.getMetrics();
res.status(200).json({
diff --git a/server/src/api/controllers/settingsController.ts b/server/src/api/controllers/settingsController.ts
index 06085c3f8..a3237d92b 100644
--- a/server/src/api/controllers/settingsController.ts
+++ b/server/src/api/controllers/settingsController.ts
@@ -10,7 +10,6 @@ import { Settings } from "@/domain/app-settings/app-settings.type.js";
const SERVICE_NAME = "SettingsController";
export interface ISettingsController {
- serviceName: string;
getAppSettings: RequestHandler;
updateAppSettings: RequestHandler;
sendTestEmail: RequestHandler;
@@ -25,10 +24,6 @@ class SettingsController implements ISettingsController {
this.emailService = emailService;
}
- get serviceName() {
- return SettingsController.SERVICE_NAME;
- }
-
buildAppSettings = (dbSettings: Settings) => {
const sanitizedSettings: Record = { ...dbSettings };
delete sanitizedSettings.version;
diff --git a/server/src/api/controllers/statusPageController.ts b/server/src/api/controllers/statusPageController.ts
index f75c5e7fb..5ba1cf527 100644
--- a/server/src/api/controllers/statusPageController.ts
+++ b/server/src/api/controllers/statusPageController.ts
@@ -16,7 +16,6 @@ import { resolveStatusPageDomainFromRequest } from "@/utils/statusPageDomain.js"
const SERVICE_NAME = "statusPageController";
export interface IStatusPageController {
- readonly serviceName: string;
createStatusPage: RequestHandler;
updateStatusPage: RequestHandler;
getStatusPageByUrl: RequestHandler;
@@ -32,10 +31,6 @@ class StatusPageController implements IStatusPageController {
this.statusPageService = statusPageService;
}
- get serviceName() {
- return StatusPageController.SERVICE_NAME;
- }
-
createStatusPage = catchAsync(async (req: Request, res: Response) => {
createStatusPageBodyValidation.parse(req.body);
if (req.file) {
diff --git a/server/src/db/db.mongo.ts b/server/src/db/db.mongo.ts
index 5aca5550a..e0b39c188 100755
--- a/server/src/db/db.mongo.ts
+++ b/server/src/db/db.mongo.ts
@@ -21,10 +21,6 @@ class MongoDB implements IDb {
this.db = null;
}
- get serviceName() {
- return MongoDB.SERVICE_NAME;
- }
-
connect = async () => {
try {
const connectionString = this.envSettings.dbConnectionString || "mongodb://localhost:27017/uptime_db";
diff --git a/server/src/domain/app-settings/app-settings.service.ts b/server/src/domain/app-settings/app-settings.service.ts
index 92a54702e..12f75d1c8 100755
--- a/server/src/domain/app-settings/app-settings.service.ts
+++ b/server/src/domain/app-settings/app-settings.service.ts
@@ -20,7 +20,6 @@ export type EnvConfig = {
};
export interface ISettingsService {
- readonly serviceName: string;
loadSettings(): EnvConfig;
getSettings(): EnvConfig;
areStatusPageThemesEnabled(): boolean;
@@ -57,10 +56,6 @@ export class SettingsService implements ISettingsService {
this.settingsRepository = settingsRepository;
}
- get serviceName() {
- return SettingsService.SERVICE_NAME;
- }
-
loadSettings() {
return this.settings;
}
diff --git a/server/src/domain/checks/check.service.ts b/server/src/domain/checks/check.service.ts
index ca7fca457..f3c18ec91 100644
--- a/server/src/domain/checks/check.service.ts
+++ b/server/src/domain/checks/check.service.ts
@@ -50,10 +50,6 @@ export class CheckService implements ICheckService {
this.checksRepository = checksRepository;
}
- get serviceName() {
- return CheckService.SERVICE_NAME;
- }
-
createChecks = async (checks: Check[]) => {
return this.checksRepository.createChecks(checks);
};
diff --git a/server/src/domain/diagnostics/diagnostic.service.ts b/server/src/domain/diagnostics/diagnostic.service.ts
index f26fa02e3..1f7a0314a 100644
--- a/server/src/domain/diagnostics/diagnostic.service.ts
+++ b/server/src/domain/diagnostics/diagnostic.service.ts
@@ -79,10 +79,6 @@ export class DiagnosticService implements IDiagnosticService {
obs.observe({ entryTypes: ["measure"] });
}
- get serviceName() {
- return DiagnosticService.SERVICE_NAME;
- }
-
private getCPUUsagePercentage = async (): Promise => {
const startUsage = process.cpuUsage();
const timingPeriod = 1000; // measured in ms
diff --git a/server/src/domain/geo-checks/geo-check.service.ts b/server/src/domain/geo-checks/geo-check.service.ts
index 0ace565fc..040383811 100644
--- a/server/src/domain/geo-checks/geo-check.service.ts
+++ b/server/src/domain/geo-checks/geo-check.service.ts
@@ -11,7 +11,6 @@ import { AppError } from "@/utils/AppError.js";
const SERVICE_NAME = "GeoChecksService";
export interface IGeoChecksService {
- readonly serviceName: string;
buildGeoCheck(monitor: Monitor): Promise;
createGeoChecks(geoChecks: GeoCheck[]): Promise;
getGeoChecksByMonitor(args: {
@@ -50,10 +49,6 @@ export class GeoChecksService implements IGeoChecksService {
this.monitorsRepository = monitorsRepository;
}
- get serviceName() {
- return GeoChecksService.SERVICE_NAME;
- }
-
async buildGeoCheck(monitor: Monitor): Promise {
try {
if (!monitor.url) {
diff --git a/server/src/domain/incidents/incident.service.ts b/server/src/domain/incidents/incident.service.ts
index 311fad567..98f092088 100644
--- a/server/src/domain/incidents/incident.service.ts
+++ b/server/src/domain/incidents/incident.service.ts
@@ -57,10 +57,6 @@ export class IncidentService implements IIncidentService {
this.notificationMessageBuilder = notificationMessageBuilder;
}
- get serviceName() {
- return IncidentService.SERVICE_NAME;
- }
-
handleIncident = async (
monitor: Monitor,
code: number,
diff --git a/server/src/domain/invites/invite.service.ts b/server/src/domain/invites/invite.service.ts
index 76c5c6289..3dcffd9e0 100644
--- a/server/src/domain/invites/invite.service.ts
+++ b/server/src/domain/invites/invite.service.ts
@@ -35,10 +35,6 @@ export class InviteService implements IInviteService {
this.emailService = emailService;
}
- get serviceName() {
- return InviteService.SERVICE_NAME;
- }
-
getInviteToken = async ({ invite, teamId, userRoles }: { invite: Partial; teamId: string; userRoles: UserRole[] }) => {
invite.teamId = teamId;
diff --git a/server/src/domain/maintenance-windows/maintenance-window.service.ts b/server/src/domain/maintenance-windows/maintenance-window.service.ts
index 5551aef62..350e755aa 100644
--- a/server/src/domain/maintenance-windows/maintenance-window.service.ts
+++ b/server/src/domain/maintenance-windows/maintenance-window.service.ts
@@ -62,10 +62,6 @@ export class MaintenanceWindowService implements IMaintenanceWindowService {
this.scheduler = scheduler;
}
- get serviceName() {
- return MaintenanceWindowService.SERVICE_NAME;
- }
-
private flipMonitorsLeavingMaintenance = async (monitorIds: string[], teamId: string, excludeWindowId: string, now: Date): Promise => {
if (monitorIds.length === 0) return;
diff --git a/server/src/domain/monitors/monitor.service.ts b/server/src/domain/monitors/monitor.service.ts
index 66684c2ac..e7a79a161 100644
--- a/server/src/domain/monitors/monitor.service.ts
+++ b/server/src/domain/monitors/monitor.service.ts
@@ -31,8 +31,6 @@ const isUptimeChecksResult = (result: UptimeChecksResult | HardwareChecksResult
supportsUptimeDetails(result.monitorType);
export interface IMonitorService {
- readonly serviceName: string;
-
// create
createMonitor(teamId: string, userId: string, body: Partial): Promise;
createMonitors(monitors: Array): Promise;
@@ -135,10 +133,6 @@ export class MonitorService implements IMonitorService {
this.incidentsRepository = incidentsRepository;
}
- get serviceName(): string {
- return MonitorService.SERVICE_NAME;
- }
-
private getDateRange = (dateRange: DateRangeKey) => {
const startDates = {
recent: new Date(new Date().setHours(new Date().getHours() - 2)),
diff --git a/server/src/domain/users/user.service.ts b/server/src/domain/users/user.service.ts
index 78a884a51..f3db2f3b5 100644
--- a/server/src/domain/users/user.service.ts
+++ b/server/src/domain/users/user.service.ts
@@ -103,10 +103,6 @@ export class UserService implements IUserService {
this.teamsRepository = teamsRepository;
}
- get serviceName() {
- return UserService.SERVICE_NAME;
- }
-
issueToken = (payload: Partial, appSettings: EnvConfig) => {
return this.jwt.sign(payload, appSettings.jwtSecret, { expiresIn: appSettings.jwtTTL });
};
diff --git a/server/src/service/bufferService.ts b/server/src/service/bufferService.ts
index d47a6472e..2d10aebbb 100755
--- a/server/src/service/bufferService.ts
+++ b/server/src/service/bufferService.ts
@@ -51,10 +51,6 @@ export class BufferService implements IBufferService {
});
}
- get serviceName() {
- return BufferService.SERVICE_NAME;
- }
-
addToBuffer(check: Check) {
try {
this.buffer.push(check);
diff --git a/server/src/service/emailService.ts b/server/src/service/emailService.ts
index c156cc18b..f057e955a 100755
--- a/server/src/service/emailService.ts
+++ b/server/src/service/emailService.ts
@@ -56,10 +56,6 @@ export class EmailService implements IEmailService {
this.init();
}
- get serviceName() {
- return EmailService.SERVICE_NAME;
- }
-
init = () => {
const loadTemplate = (templateName: string) => {
try {
diff --git a/server/src/service/globalPingService.ts b/server/src/service/globalPingService.ts
index cebd95408..4b0257df4 100644
--- a/server/src/service/globalPingService.ts
+++ b/server/src/service/globalPingService.ts
@@ -61,7 +61,6 @@ interface GlobalPingProbeResult {
}
export interface IGlobalPingService {
- readonly serviceName: string;
createMeasurement(monitorType: MonitorType, url: string, locations: GeoContinent[]): Promise;
pollForResults(measurementId: string, timeoutMs?: number, customUpCodes?: HttpStatusCode[]): Promise;
}
@@ -75,10 +74,6 @@ export class GlobalPingService implements IGlobalPingService {
this.logger = logger;
}
- get serviceName() {
- return GlobalPingService.SERVICE_NAME;
- }
-
async createMeasurement(monitorType: MonitorType, url: string, locations: GeoContinent[]): Promise {
try {
if (!supportsGeoCheck(monitorType)) {
diff --git a/server/src/service/networkService.ts b/server/src/service/networkService.ts
index 3470c6086..632a03141 100644
--- a/server/src/service/networkService.ts
+++ b/server/src/service/networkService.ts
@@ -8,7 +8,6 @@ import { IStatusProvider } from "./network/IStatusProvider.js";
const SERVICE_NAME = "NetworkService";
export interface INetworkService {
- readonly serviceName: string;
requestStatus(monitor: Monitor & { type: T }): Promise>;
requestWebhook(
type: string,
@@ -39,10 +38,6 @@ export class NetworkService implements INetworkService {
this.logger = logger;
}
- get serviceName(): string {
- return NetworkService.SERVICE_NAME;
- }
-
// Main entry point
async requestStatus(monitor: Monitor & { type: T }): Promise> {
const provider = this.providers.find((p) => p.supports(monitor.type));
diff --git a/server/src/service/statusService.ts b/server/src/service/statusService.ts
index ccda0c5d7..6435958cb 100755
--- a/server/src/service/statusService.ts
+++ b/server/src/service/statusService.ts
@@ -57,10 +57,6 @@ export class StatusService implements IStatusService {
this.monitorStatsRepository = monitorStatsRepository;
}
- get serviceName() {
- return StatusService.SERVICE_NAME;
- }
-
async updateRunningStats(monitor: Monitor, networkResponse: MonitorStatusResponse) {
try {
await this.monitorStatsRepository.updateByMonitorId(monitor.id, {
diff --git a/server/src/utils/logger.ts b/server/src/utils/logger.ts
index 41142fd92..3cd8570ca 100755
--- a/server/src/utils/logger.ts
+++ b/server/src/utils/logger.ts
@@ -19,7 +19,6 @@ interface LogEntry extends LogConfig {
}
export interface ILogger {
- readonly serviceName: string;
info(config: LogConfig): void;
warn(config: LogConfig): void;
error(config: LogConfig): void;
@@ -97,10 +96,6 @@ class Logger implements ILogger {
});
}
- get serviceName() {
- return Logger.SERVICE_NAME;
- }
-
info(config: LogConfig) {
const logEntry = this.buildLogEntry("info", config);
this.cacheLog(logEntry);
diff --git a/server/src/worker/worker.db-queue.ts b/server/src/worker/worker.db-queue.ts
index 50e41a4f1..9420660c7 100644
--- a/server/src/worker/worker.db-queue.ts
+++ b/server/src/worker/worker.db-queue.ts
@@ -95,10 +95,6 @@ export class DBQueueWorker extends JobScheduler implements IQueueWorker {
this.isDbConnected = dependencies.isDbConnected;
}
- get serviceName() {
- return DBQueueWorker.SERVICE_NAME;
- }
-
static async create(dependencies: DBQueueWorkerDependencies): Promise {
const instance = new DBQueueWorker(dependencies);
await instance.init();
diff --git a/server/src/worker/worker.helper.ts b/server/src/worker/worker.helper.ts
index 3d2d2e183..9f044763d 100644
--- a/server/src/worker/worker.helper.ts
+++ b/server/src/worker/worker.helper.ts
@@ -12,7 +12,6 @@ import { ITeamsRepository } from "@/domain/teams/team.repository.interface.js";
import { ILogger } from "@/utils/logger.js";
export interface IWorkerHelper {
- readonly serviceName: string;
getCleanupOrphanedJob(): () => Promise;
getCleanupRetentionJob(): () => Promise;
}
@@ -69,10 +68,6 @@ export class WorkerHelper implements IWorkerHelper {
this.geoChecksRepository = geoChecksRepository;
}
- get serviceName() {
- return WorkerHelper.SERVICE_NAME;
- }
-
getCleanupOrphanedJob = () => {
return async () => {
try {
diff --git a/server/src/worker/worker.interface.ts b/server/src/worker/worker.interface.ts
index 98120fa47..e70af5f35 100644
--- a/server/src/worker/worker.interface.ts
+++ b/server/src/worker/worker.interface.ts
@@ -67,7 +67,6 @@ export type WorkerHealth = {
inFlight: number; // total in-flight jobs across types
};
export interface IJobScheduler {
- readonly serviceName: string;
addJob(monitorId: string, monitor: Monitor): Promise;
deleteJob(monitor: Monitor): Promise;
pauseJob(monitor: Monitor): Promise;
@@ -83,7 +82,6 @@ export interface IJobScheduler {
}
export interface IQueueWorker extends IJobScheduler {
- readonly serviceName: string;
getHealth(): WorkerHealth;
countDueBacklog(): Promise;
countAliveWorkers(): Promise;
diff --git a/server/src/worker/worker.job-scheduler.ts b/server/src/worker/worker.job-scheduler.ts
index 458db1d08..0f04a39b7 100644
--- a/server/src/worker/worker.job-scheduler.ts
+++ b/server/src/worker/worker.job-scheduler.ts
@@ -26,9 +26,6 @@ export class JobScheduler implements IJobScheduler {
protected readonly workerId: string // same id MongoJobsRepository stamps into lockedBy
) {}
- get serviceName() {
- return JobScheduler.SERVICE_NAME;
- }
private heartbeatInFlight: Promise = Promise.resolve();
protected processesJobs = false; // scheduler-only by default; DBQueueWorker flips this on when it runs job loops
diff --git a/server/test/unit/services/bufferService.test.ts b/server/test/unit/services/bufferService.test.ts
index d994e5f24..90b5489bf 100644
--- a/server/test/unit/services/bufferService.test.ts
+++ b/server/test/unit/services/bufferService.test.ts
@@ -71,17 +71,6 @@ describe("BufferService", () => {
// ── Static / instance properties ─────────────────────────────────────
- describe("serviceName", () => {
- it("returns BufferService from static property", () => {
- expect(BufferService.SERVICE_NAME).toBe("BufferService");
- });
-
- it("returns BufferService from instance getter", () => {
- const { service } = createService();
- expect(service.serviceName).toBe("BufferService");
- });
- });
-
// ── constructor ──────────────────────────────────────────────────────
describe("constructor", () => {
diff --git a/server/test/unit/services/checkService.test.ts b/server/test/unit/services/checkService.test.ts
index 104ccbaa2..587731951 100644
--- a/server/test/unit/services/checkService.test.ts
+++ b/server/test/unit/services/checkService.test.ts
@@ -48,13 +48,6 @@ const makeStatusResponse = (overrides?: Partial): Monitor
// ── Tests ────────────────────────────────────────────────────────────────────
describe("CheckService", () => {
- describe("serviceName", () => {
- it("returns checkService", () => {
- const { service } = createService();
- expect(service.serviceName).toBe("checkService");
- });
- });
-
// ── toCheck ──────────────────────────────────────────────────────────────
describe("toCheck", () => {
diff --git a/server/test/unit/services/diagnosticService.test.ts b/server/test/unit/services/diagnosticService.test.ts
index 07962a68e..d2512717f 100644
--- a/server/test/unit/services/diagnosticService.test.ts
+++ b/server/test/unit/services/diagnosticService.test.ts
@@ -56,17 +56,6 @@ describe("DiagnosticService", () => {
jest.useRealTimers();
});
- describe("serviceName", () => {
- it("returns diagnosticService from static property", () => {
- expect(DiagnosticService.SERVICE_NAME).toBe("diagnosticService");
- });
-
- it("returns diagnosticService from instance getter", () => {
- const service = new DiagnosticService(makeDb());
- expect(service.serviceName).toBe("diagnosticService");
- });
- });
-
// ── constructor ─────────────────────────────────────────────────────────
describe("constructor", () => {
diff --git a/server/test/unit/services/emailService.test.ts b/server/test/unit/services/emailService.test.ts
index fbfdf15fd..c49d525b1 100644
--- a/server/test/unit/services/emailService.test.ts
+++ b/server/test/unit/services/emailService.test.ts
@@ -82,11 +82,6 @@ describe("EmailService", () => {
it("returns EmailService from static property", () => {
expect(EmailService.SERVICE_NAME).toBe("EmailService");
});
-
- it("returns EmailService from instance getter", () => {
- const { service } = createService();
- expect(service.serviceName).toBe("EmailService");
- });
});
// ── init / loadTemplate ──────────────────────────────────────────────
diff --git a/server/test/unit/services/geoChecksService.test.ts b/server/test/unit/services/geoChecksService.test.ts
index 8e50fa3e8..69bfff730 100644
--- a/server/test/unit/services/geoChecksService.test.ts
+++ b/server/test/unit/services/geoChecksService.test.ts
@@ -48,17 +48,6 @@ const createService = (overrides?: Record) => {
// ── Tests ────────────────────────────────────────────────────────────────────
describe("GeoChecksService", () => {
- describe("serviceName", () => {
- it("returns GeoChecksService from static property", () => {
- expect(GeoChecksService.SERVICE_NAME).toBe("GeoChecksService");
- });
-
- it("returns GeoChecksService from instance getter", () => {
- const { service } = createService();
- expect(service.serviceName).toBe("GeoChecksService");
- });
- });
-
// ── buildGeoCheck ───────────────────────────────────────────────────────
describe("buildGeoCheck", () => {
diff --git a/server/test/unit/services/globalPingService.test.ts b/server/test/unit/services/globalPingService.test.ts
index 376653110..5169f205b 100644
--- a/server/test/unit/services/globalPingService.test.ts
+++ b/server/test/unit/services/globalPingService.test.ts
@@ -66,17 +66,6 @@ describe("GlobalPingService", () => {
// ── Static / instance properties ─────────────────────────────────────
- describe("serviceName", () => {
- it("returns GlobalPingService from static property", () => {
- expect(GlobalPingService.SERVICE_NAME).toBe("GlobalPingService");
- });
-
- it("returns GlobalPingService from instance getter", () => {
- const { service } = createService();
- expect(service.serviceName).toBe("GlobalPingService");
- });
- });
-
// ── createMeasurement ────────────────────────────────────────────────
describe("createMeasurement", () => {
diff --git a/server/test/unit/services/incidentService.test.ts b/server/test/unit/services/incidentService.test.ts
index aa4fffce8..27b2736df 100644
--- a/server/test/unit/services/incidentService.test.ts
+++ b/server/test/unit/services/incidentService.test.ts
@@ -91,13 +91,6 @@ const makeDecision = (overrides?: Partial): MonitorAction
// ── Tests ────────────────────────────────────────────────────────────────────
describe("IncidentService", () => {
- describe("serviceName", () => {
- it("returns incidentService", () => {
- const { service } = createService();
- expect(service.serviceName).toBe("incidentService");
- });
- });
-
// ── handleIncident ───────────────────────────────────────────────────────
describe("handleIncident", () => {
diff --git a/server/test/unit/services/inviteService.test.ts b/server/test/unit/services/inviteService.test.ts
index c050d91e6..59d472539 100644
--- a/server/test/unit/services/inviteService.test.ts
+++ b/server/test/unit/services/inviteService.test.ts
@@ -39,17 +39,6 @@ const createService = (overrides?: Record) => {
// ── Tests ────────────────────────────────────────────────────────────────────
describe("InviteService", () => {
- describe("serviceName", () => {
- it("returns inviteService from static property", () => {
- expect(InviteService.SERVICE_NAME).toBe("inviteService");
- });
-
- it("returns inviteService from instance getter", () => {
- const { service } = createService();
- expect(service.serviceName).toBe("inviteService");
- });
- });
-
// ── getInviteToken ──────────────────────────────────────────────────────
describe("getInviteToken", () => {
diff --git a/server/test/unit/services/maintenanceWindowService.test.ts b/server/test/unit/services/maintenanceWindowService.test.ts
index 9547eaac8..e15f20d46 100644
--- a/server/test/unit/services/maintenanceWindowService.test.ts
+++ b/server/test/unit/services/maintenanceWindowService.test.ts
@@ -94,17 +94,6 @@ const defaultCreateParams = {
// ── Tests ────────────────────────────────────────────────────────────────────
describe("MaintenanceWindowService", () => {
- describe("serviceName", () => {
- it("returns maintenanceWindowService from static property", () => {
- expect(MaintenanceWindowService.SERVICE_NAME).toBe("maintenanceWindowService");
- });
-
- it("returns maintenanceWindowService from instance getter", () => {
- const { service } = createService();
- expect(service.serviceName).toBe("maintenanceWindowService");
- });
- });
-
// ── createMaintenanceWindow ──────────────────────────────────────────────
describe("createMaintenanceWindow", () => {
diff --git a/server/test/unit/services/monitorService.test.ts b/server/test/unit/services/monitorService.test.ts
index 6a1cf8343..2b6f72d1d 100644
--- a/server/test/unit/services/monitorService.test.ts
+++ b/server/test/unit/services/monitorService.test.ts
@@ -111,13 +111,6 @@ const createService = (
};
describe("MonitorService", () => {
- describe("serviceName", () => {
- it("returns 'MonitorService'", () => {
- const { service } = createService();
- expect(service.serviceName).toBe("MonitorService");
- });
- });
-
describe("createMonitor", () => {
it("creates a monitor and adds a job", async () => {
const monitorsRepository = createMonitorsRepositoryMock();
diff --git a/server/test/unit/services/networkService.test.ts b/server/test/unit/services/networkService.test.ts
index 268b52e3a..0943b1f22 100644
--- a/server/test/unit/services/networkService.test.ts
+++ b/server/test/unit/services/networkService.test.ts
@@ -43,17 +43,6 @@ const createService = (providers: IStatusProvider[] = []) => {
describe("NetworkService", () => {
// ── Static / instance properties ─────────────────────────────────────────
- describe("serviceName", () => {
- it("returns NetworkService from static property", () => {
- expect(NetworkService.SERVICE_NAME).toBe("NetworkService");
- });
-
- it("returns NetworkService from instance getter", () => {
- const { service } = createService();
- expect(service.serviceName).toBe("NetworkService");
- });
- });
-
// ── requestStatus ────────────────────────────────────────────────────────
describe("requestStatus", () => {
diff --git a/server/test/unit/services/settingsService.test.ts b/server/test/unit/services/settingsService.test.ts
index 43ea7fe18..e46472fac 100644
--- a/server/test/unit/services/settingsService.test.ts
+++ b/server/test/unit/services/settingsService.test.ts
@@ -56,17 +56,6 @@ const createService = (envOverrides?: Partial) => {
// ── Tests ────────────────────────────────────────────────────────────────────
describe("SettingsService", () => {
- describe("serviceName", () => {
- it("returns SettingsService from static property", () => {
- expect(SettingsService.SERVICE_NAME).toBe("SettingsService");
- });
-
- it("returns SettingsService from instance getter", () => {
- const { service } = createService();
- expect(service.serviceName).toBe("SettingsService");
- });
- });
-
// ── loadSettings ────────────────────────────────────────────────────────
describe("loadSettings", () => {
diff --git a/server/test/unit/services/statusService.test.ts b/server/test/unit/services/statusService.test.ts
index 5712de301..4f4c0264a 100644
--- a/server/test/unit/services/statusService.test.ts
+++ b/server/test/unit/services/statusService.test.ts
@@ -143,13 +143,6 @@ const makeExistingStats = (overrides?: Record) => ({
// ── Tests ────────────────────────────────────────────────────────────────────
describe("StatusService", () => {
- describe("serviceName", () => {
- it("returns StatusService", () => {
- const { service } = createService();
- expect(service.serviceName).toBe("StatusService");
- });
- });
-
// ── updateRunningStats ───────────────────────────────────────────────────
describe("updateRunningStats", () => {
diff --git a/server/test/unit/services/userService.test.ts b/server/test/unit/services/userService.test.ts
index 061003e7f..bce8769cf 100644
--- a/server/test/unit/services/userService.test.ts
+++ b/server/test/unit/services/userService.test.ts
@@ -98,13 +98,6 @@ const createService = (overrides?: Record) => {
// ── Tests ────────────────────────────────────────────────────────────────────
describe("UserService", () => {
- describe("serviceName", () => {
- it("returns userService", () => {
- const { service } = createService();
- expect(service.serviceName).toBe("userService");
- });
- });
-
// ── issueToken ──────────────────────────────────────────────────────────
describe("issueToken", () => {
diff --git a/server/test/unit/services/workerHelper.test.ts b/server/test/unit/services/workerHelper.test.ts
index 96b45d3b3..fe7dca42d 100644
--- a/server/test/unit/services/workerHelper.test.ts
+++ b/server/test/unit/services/workerHelper.test.ts
@@ -71,13 +71,6 @@ const createHelper = (overrides?: Record) => {
// ── Tests ────────────────────────────────────────────────────────────────────
describe("WorkerHelper", () => {
- describe("serviceName", () => {
- it("returns JobQueueHelper", () => {
- const { helper } = createHelper();
- expect(helper.serviceName).toBe("JobQueueHelper");
- });
- });
-
// ── getCleanupOrphanedJob ────────────────────────────────────────────────
describe("getCleanupOrphanedJob", () => {
From 4949acac1cace44fe453d9d6630abbe74533f5ad Mon Sep 17 00:00:00 2001
From: Alex Holliday
Date: Wed, 22 Jul 2026 10:26:00 -0700
Subject: [PATCH 56/84] remove unused email templates, remove duplicate
constants
---
.../app-settings/app-settings.service.ts | 5 --
.../queue-workers/queue-worker.model.ts | 3 +-
server/src/index.ts | 2 +-
server/src/service/emailService.ts | 1 -
server/src/templates/addReview.mjml | 58 ----------------
server/src/templates/noIncidentsThisWeek.mjml | 66 -------------------
server/src/worker/worker.db-queue.ts | 5 +-
server/src/worker/worker.job-scheduler.ts | 5 +-
8 files changed, 7 insertions(+), 138 deletions(-)
delete mode 100755 server/src/templates/addReview.mjml
delete mode 100755 server/src/templates/noIncidentsThisWeek.mjml
diff --git a/server/src/domain/app-settings/app-settings.service.ts b/server/src/domain/app-settings/app-settings.service.ts
index 12f75d1c8..2df2b7512 100755
--- a/server/src/domain/app-settings/app-settings.service.ts
+++ b/server/src/domain/app-settings/app-settings.service.ts
@@ -20,7 +20,6 @@ export type EnvConfig = {
};
export interface ISettingsService {
- loadSettings(): EnvConfig;
getSettings(): EnvConfig;
areStatusPageThemesEnabled(): boolean;
getDBSettings(): Promise;
@@ -56,10 +55,6 @@ export class SettingsService implements ISettingsService {
this.settingsRepository = settingsRepository;
}
- loadSettings() {
- return this.settings;
- }
-
getSettings() {
return this.settings;
}
diff --git a/server/src/domain/queue-workers/queue-worker.model.ts b/server/src/domain/queue-workers/queue-worker.model.ts
index 0dd90b5c5..c89ecca5d 100644
--- a/server/src/domain/queue-workers/queue-worker.model.ts
+++ b/server/src/domain/queue-workers/queue-worker.model.ts
@@ -4,7 +4,8 @@ import { QueueWorkerDocument } from "@/domain/queue-workers/queue-worker.type.js
// Single source of truth for worker liveness: the TTL that GCs stale records and
// the window readers use to count a worker as alive. Heartbeat cadence must stay well below it.
-export const WORKER_TTL_SECONDS = 180;
+const WORKER_TTL_SECONDS = 180;
+export const WORKER_STALE_MS = WORKER_TTL_SECONDS * 1000;
const QueueWorkerSchema = new Schema(
{
diff --git a/server/src/index.ts b/server/src/index.ts
index b5e870ded..68ef00a1f 100755
--- a/server/src/index.ts
+++ b/server/src/index.ts
@@ -27,7 +27,7 @@ const startApp = async () => {
const env = validateEnv(logger);
logger.setLogLevel(env.LOG_LEVEL);
const settingsService = new SettingsService(env);
- const envSettings = settingsService.loadSettings();
+ const envSettings = settingsService.getSettings();
const { queueMode, queuePrimaryProcesses } = envSettings;
logger.info({
diff --git a/server/src/service/emailService.ts b/server/src/service/emailService.ts
index f057e955a..26b89f84b 100755
--- a/server/src/service/emailService.ts
+++ b/server/src/service/emailService.ts
@@ -75,7 +75,6 @@ export class EmailService implements IEmailService {
this.templateLookup = {
welcomeEmailTemplate: loadTemplate("welcomeEmail"),
employeeActivationTemplate: loadTemplate("employeeActivation"),
- noIncidentsThisWeekTemplate: loadTemplate("noIncidentsThisWeek"),
passwordResetTemplate: loadTemplate("passwordReset"),
testEmailTemplate: loadTemplate("testEmailTemplate"),
unifiedNotificationTemplate: loadTemplate("unifiedNotification"),
diff --git a/server/src/templates/addReview.mjml b/server/src/templates/addReview.mjml
deleted file mode 100755
index e193ac1ba..000000000
--- a/server/src/templates/addReview.mjml
+++ /dev/null
@@ -1,58 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Message from Checkmate Service
-
-
-
-
-
-
- Hello {{ name }}!
-
- We hope you’re finding Checkmate helpful in monitoring your infrastructure. Your support means a lot to us, and we
- truly appreciate having you as part of our community.
-
-
- If you’re happy with Checkmate, we’d love to hear about your experience! Leaving a review on G2 helps others discover Checkmate and
- supports our ongoing improvements.
-
- G2 Link: TBD
- Thank you for taking the time to share your thoughts - we greatly appreciate it!
- Checkmate Team
-
-
-
-
-
- This email was sent by Checkmate.
-
-
-
-
-
diff --git a/server/src/templates/noIncidentsThisWeek.mjml b/server/src/templates/noIncidentsThisWeek.mjml
deleted file mode 100755
index d160e44de..000000000
--- a/server/src/templates/noIncidentsThisWeek.mjml
+++ /dev/null
@@ -1,66 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
- Message from Checkmate Service
-
-
- No incidents this week!
-
-
-
-
-
-
- Hello {{ name }}!
- There were no incidents this week. Good job!
- Current monitors:
- Google: 100% availability
- Canada.ca:100% availability
-
-
-
-
-
- This email was sent by Checkmate.
-
-
-
-
-
diff --git a/server/src/worker/worker.db-queue.ts b/server/src/worker/worker.db-queue.ts
index 9420660c7..6f4f31353 100644
--- a/server/src/worker/worker.db-queue.ts
+++ b/server/src/worker/worker.db-queue.ts
@@ -11,14 +11,13 @@ import { ICheckPipeline } from "@/worker/worker.check-pipeline.js";
import { IReactorDispatcher } from "@/worker/reactors/reactor.dispatcher.js";
import { IWorkerHelper } from "@/worker/worker.helper.js";
import { IQueueWorkersRepository } from "@/domain/queue-workers/queue-worker.repository.interface.js";
-import { WORKER_TTL_SECONDS } from "@/domain/queue-workers/queue-worker.model.js";
+import { WORKER_STALE_MS } from "@/domain/queue-workers/queue-worker.model.js";
import { QueueMode } from "@/domain/app-settings/app-settings.type.js";
import { JobScheduler } from "@/worker/worker.job-scheduler.js";
import { IBufferService } from "@/service/bufferService.js";
+import { POLL_MS } from "@/worker/worker.job-scheduler.js";
const SERVICE_NAME = "JobQueue";
-const POLL_MS = 250; // base poll interval while a loop is actively claiming work
const POLL_MAX_MS = 5000; // back off poll interval to 5s if no jobs
-const WORKER_STALE_MS = WORKER_TTL_SECONDS * 1000; // a worker counts as alive if seen within this window
const LOCK_RENEW_MS = LOCK_MS / 3; // renew an in-flight job's lock at 1/3 the lease, so it survives two missed renewals
const DRAIN_TIMEOUT_MS = 25_000;
const DRAIN_POLL_MS = 200;
diff --git a/server/src/worker/worker.job-scheduler.ts b/server/src/worker/worker.job-scheduler.ts
index 0f04a39b7..433e69955 100644
--- a/server/src/worker/worker.job-scheduler.ts
+++ b/server/src/worker/worker.job-scheduler.ts
@@ -5,13 +5,12 @@ import { Monitor, supportsGeoCheck } from "@/domain/monitors/monitor.types.js";
import { type Job, type JobSeed, type JobType, jobId } from "@/domain/jobs/job.type.js";
import { IQueueWorkersRepository } from "@/domain/queue-workers/queue-worker.repository.interface.js";
import { WorkerJobsPagination, WorkerJobSummary, WorkerMetrics } from "@/worker/worker.interface.js";
-import { WORKER_TTL_SECONDS } from "@/domain/queue-workers/queue-worker.model.js";
+import { WORKER_STALE_MS } from "@/domain/queue-workers/queue-worker.model.js";
import { QueueMode } from "@/domain/app-settings/app-settings.type.js";
import { ILogger } from "@/utils/logger.js";
-const POLL_MS = 250; // base poll interval while a loop is actively claiming work
+export const POLL_MS = 250; // base poll interval while a loop is actively claiming work
const SERVICE_NAME = "JobScheduler";
-const WORKER_STALE_MS = WORKER_TTL_SECONDS * 1000; // a worker counts as alive if seen within this window
const HEARTBEAT_MS = WORKER_STALE_MS / 3; // Worker can miss two beats without being considered stale
const CLEANUP_INTERVAL_MS = 24 * 60 * 60 * 1000;
From 80d4e818f1beb8e35d840194bcd473a8ea4d6fea Mon Sep 17 00:00:00 2001
From: Alex Holliday
Date: Wed, 22 Jul 2026 10:36:16 -0700
Subject: [PATCH 57/84] remove unused static vars
---
server/src/api/controllers/authController.ts | 4 ----
server/src/api/controllers/checkController.ts | 4 ----
.../api/controllers/diagnosticController.ts | 4 ----
.../src/api/controllers/geoCheckController.ts | 4 ----
.../src/api/controllers/inviteController.ts | 2 --
server/src/api/controllers/logController.ts | 3 ---
.../src/api/controllers/monitorController.ts | 4 ----
server/src/api/controllers/queueController.ts | 3 ---
.../src/api/controllers/settingsController.ts | 3 ---
.../api/controllers/statusPageController.ts | 3 ---
.../test/unit/services/emailService.test.ts | 8 ++++----
.../unit/services/settingsService.test.ts | 20 +++++--------------
12 files changed, 9 insertions(+), 53 deletions(-)
diff --git a/server/src/api/controllers/authController.ts b/server/src/api/controllers/authController.ts
index 59afd4cc8..ab8d1fc61 100755
--- a/server/src/api/controllers/authController.ts
+++ b/server/src/api/controllers/authController.ts
@@ -22,8 +22,6 @@ import {
} from "@/api/validation/userValidation.js";
import { IUserService } from "@/domain/users/user.service.js";
-const SERVICE_NAME = "authController";
-
export interface IAuthController {
registerUser: RequestHandler;
createUser: RequestHandler;
@@ -42,8 +40,6 @@ export interface IAuthController {
}
class AuthController implements IAuthController {
- static SERVICE_NAME = SERVICE_NAME;
-
private userService: IUserService;
constructor(userService: IUserService) {
diff --git a/server/src/api/controllers/checkController.ts b/server/src/api/controllers/checkController.ts
index 525a00080..7b69c7047 100755
--- a/server/src/api/controllers/checkController.ts
+++ b/server/src/api/controllers/checkController.ts
@@ -11,8 +11,6 @@ import {
import { ICheckService } from "@/domain/checks/check.service.js";
import { requireTeamId } from "@/api/controllers/controllerUtils.js";
-const SERVICE_NAME = "checkController";
-
export interface ICheckController {
getChecksByMonitor: RequestHandler;
getChecksByTeam: RequestHandler;
@@ -22,8 +20,6 @@ export interface ICheckController {
}
class CheckController implements ICheckController {
- static SERVICE_NAME = SERVICE_NAME;
-
private checkService: ICheckService;
constructor(checkService: ICheckService) {
this.checkService = checkService;
diff --git a/server/src/api/controllers/diagnosticController.ts b/server/src/api/controllers/diagnosticController.ts
index 382b4349a..9d70304d4 100755
--- a/server/src/api/controllers/diagnosticController.ts
+++ b/server/src/api/controllers/diagnosticController.ts
@@ -2,15 +2,11 @@ import { IDiagnosticService } from "@/domain/diagnostics/diagnostic.service.js";
import { Request, Response, RequestHandler } from "express";
import { catchAsync } from "@/utils/catchAsync.js";
-const SERVICE_NAME = "diagnosticController";
-
export interface IDiagnosticController {
getSystemStats: RequestHandler;
}
class DiagnosticController implements IDiagnosticController {
- static SERVICE_NAME = SERVICE_NAME;
-
private diagnosticService: IDiagnosticService;
constructor(diagnosticService: IDiagnosticService) {
diff --git a/server/src/api/controllers/geoCheckController.ts b/server/src/api/controllers/geoCheckController.ts
index 0c7f62260..24caef382 100644
--- a/server/src/api/controllers/geoCheckController.ts
+++ b/server/src/api/controllers/geoCheckController.ts
@@ -4,14 +4,10 @@ import { getChecksParamValidation, getChecksQueryValidation } from "@/api/valida
import type { IGeoChecksService } from "@/domain/geo-checks/geo-check.service.js";
import { requireTeamId } from "./controllerUtils.js";
-const SERVICE_NAME = "geoCheckController";
-
export interface IGeoCheckController {
getGeoChecksByMonitor: RequestHandler;
}
class GeoCheckController implements IGeoCheckController {
- static SERVICE_NAME = SERVICE_NAME;
-
private geoChecksService: IGeoChecksService;
constructor(geoChecksService: IGeoChecksService) {
this.geoChecksService = geoChecksService;
diff --git a/server/src/api/controllers/inviteController.ts b/server/src/api/controllers/inviteController.ts
index 5af172dfb..511145b7b 100755
--- a/server/src/api/controllers/inviteController.ts
+++ b/server/src/api/controllers/inviteController.ts
@@ -3,7 +3,6 @@ import { catchAsync } from "@/utils/catchAsync.js";
import { inviteBodyValidation, inviteVerificationBodyValidation } from "@/api/validation/authValidation.js";
import { requireFirstName, requireTeamId, requireUserRoles } from "@/api/controllers/controllerUtils.js";
import { IInviteService } from "@/domain/invites/invite.service.js";
-const SERVICE_NAME = "inviteController";
export interface IInviteController {
getInviteToken: RequestHandler;
@@ -12,7 +11,6 @@ export interface IInviteController {
}
class InviteController implements IInviteController {
- static SERVICE_NAME = SERVICE_NAME;
private inviteService: IInviteService;
constructor(inviteService: IInviteService) {
this.inviteService = inviteService;
diff --git a/server/src/api/controllers/logController.ts b/server/src/api/controllers/logController.ts
index bbbeef2a9..316d03788 100644
--- a/server/src/api/controllers/logController.ts
+++ b/server/src/api/controllers/logController.ts
@@ -2,14 +2,11 @@ import { ILogger } from "@/utils/logger.js";
import { Request, Response, RequestHandler } from "express";
import { catchAsync } from "@/utils/catchAsync.js";
-const SERVICE_NAME = "LogController";
-
export interface ILogController {
getLogs: RequestHandler;
}
class LogController {
- static SERVICE_NAME = SERVICE_NAME;
private logger: ILogger;
constructor(logger: ILogger) {
this.logger = logger;
diff --git a/server/src/api/controllers/monitorController.ts b/server/src/api/controllers/monitorController.ts
index f81e64f4a..664a78f45 100644
--- a/server/src/api/controllers/monitorController.ts
+++ b/server/src/api/controllers/monitorController.ts
@@ -23,8 +23,6 @@ import { AppError } from "@/utils/AppError.js";
import { IMonitorService } from "@/domain/monitors/monitor.service.js";
import { INotificationsService } from "@/domain/notifications/notification.service.js";
-const SERVICE_NAME = "monitorController";
-
export interface IMonitorController {
getMonitorCertificate: RequestHandler;
getUptimeDetailsById: RequestHandler;
@@ -47,8 +45,6 @@ export interface IMonitorController {
updateNotifications: RequestHandler;
}
class MonitorController implements IMonitorController {
- static SERVICE_NAME = SERVICE_NAME;
-
private monitorService: IMonitorService;
private notificationsService: INotificationsService;
diff --git a/server/src/api/controllers/queueController.ts b/server/src/api/controllers/queueController.ts
index 09e0474e5..03204167c 100755
--- a/server/src/api/controllers/queueController.ts
+++ b/server/src/api/controllers/queueController.ts
@@ -3,8 +3,6 @@ import { getQueueJobsQueryValidation } from "@/api/validation/queueValidation.js
import { Request, Response, RequestHandler } from "express";
import { catchAsync } from "@/utils/catchAsync.js";
-const SERVICE_NAME = "JobQueueController";
-
export interface IJobQueueController {
getMetrics: RequestHandler;
getJobs: RequestHandler;
@@ -13,7 +11,6 @@ export interface IJobQueueController {
}
class JobQueueController implements IJobQueueController {
- static SERVICE_NAME = SERVICE_NAME;
constructor(private scheduler: IJobScheduler) {}
getMetrics = catchAsync(async (req: Request, res: Response) => {
diff --git a/server/src/api/controllers/settingsController.ts b/server/src/api/controllers/settingsController.ts
index a3237d92b..3cb032e93 100644
--- a/server/src/api/controllers/settingsController.ts
+++ b/server/src/api/controllers/settingsController.ts
@@ -7,8 +7,6 @@ import { ISettingsService } from "@/domain/app-settings/app-settings.service.js"
import { IEmailService } from "@/service/emailService.js";
import { Settings } from "@/domain/app-settings/app-settings.type.js";
-const SERVICE_NAME = "SettingsController";
-
export interface ISettingsController {
getAppSettings: RequestHandler;
updateAppSettings: RequestHandler;
@@ -16,7 +14,6 @@ export interface ISettingsController {
}
class SettingsController implements ISettingsController {
- static SERVICE_NAME = SERVICE_NAME;
private settingsService: ISettingsService;
private emailService: IEmailService;
constructor(settingsService: ISettingsService, emailService: IEmailService) {
diff --git a/server/src/api/controllers/statusPageController.ts b/server/src/api/controllers/statusPageController.ts
index 5ba1cf527..038ddb423 100644
--- a/server/src/api/controllers/statusPageController.ts
+++ b/server/src/api/controllers/statusPageController.ts
@@ -13,8 +13,6 @@ import { requireTeamId, requireUserId } from "@/api/controllers/controllerUtils.
import { IStatusPageService } from "@/domain/status-pages/status-page.service.js";
import { resolveStatusPageDomainFromRequest } from "@/utils/statusPageDomain.js";
-const SERVICE_NAME = "statusPageController";
-
export interface IStatusPageController {
createStatusPage: RequestHandler;
updateStatusPage: RequestHandler;
@@ -25,7 +23,6 @@ export interface IStatusPageController {
}
class StatusPageController implements IStatusPageController {
- static SERVICE_NAME = SERVICE_NAME;
private statusPageService: IStatusPageService;
constructor(statusPageService: IStatusPageService) {
this.statusPageService = statusPageService;
diff --git a/server/test/unit/services/emailService.test.ts b/server/test/unit/services/emailService.test.ts
index c49d525b1..971c712da 100644
--- a/server/test/unit/services/emailService.test.ts
+++ b/server/test/unit/services/emailService.test.ts
@@ -90,14 +90,14 @@ describe("EmailService", () => {
it("loads all expected templates on construction", () => {
const { mockFs } = createService();
- // 6 templates loaded
- expect(mockFs.readFileSync).toHaveBeenCalledTimes(6);
+ // 5 templates loaded
+ expect(mockFs.readFileSync).toHaveBeenCalledTimes(5);
});
it("compiles each loaded template", () => {
const { compile } = createService();
- expect(compile).toHaveBeenCalledTimes(6);
+ expect(compile).toHaveBeenCalledTimes(5);
});
it("logs error when a template file is not found", () => {
@@ -141,7 +141,7 @@ describe("EmailService", () => {
service.init();
- expect(mockFs.readFileSync).toHaveBeenCalledTimes(initialCalls + 6);
+ expect(mockFs.readFileSync).toHaveBeenCalledTimes(initialCalls + 5);
});
});
diff --git a/server/test/unit/services/settingsService.test.ts b/server/test/unit/services/settingsService.test.ts
index e46472fac..063a64f6d 100644
--- a/server/test/unit/services/settingsService.test.ts
+++ b/server/test/unit/services/settingsService.test.ts
@@ -56,12 +56,12 @@ const createService = (envOverrides?: Partial) => {
// ── Tests ────────────────────────────────────────────────────────────────────
describe("SettingsService", () => {
- // ── loadSettings ────────────────────────────────────────────────────────
+ // ── getSettings ─────────────────────────────────────────────────────────
- describe("loadSettings", () => {
+ describe("getSettings", () => {
it("returns env config mapped from constructor", () => {
const { service } = createService();
- const settings = service.loadSettings();
+ const settings = service.getSettings();
expect(settings).toEqual({
jwtSecret: "test-secret",
@@ -82,7 +82,7 @@ describe("SettingsService", () => {
CLIENT_CONFIG_LOG_LEVEL: "warn",
});
- expect(service.loadSettings().clientConfig).toEqual({
+ expect(service.getSettings().clientConfig).toEqual({
apiBaseUrl: "https://api.example.com/api/v1",
logLevel: "warn",
});
@@ -90,23 +90,13 @@ describe("SettingsService", () => {
it("reflects custom env values", () => {
const { service } = createService({ NODE_ENV: "production", LOG_LEVEL: "error" });
- const settings = service.loadSettings();
+ const settings = service.getSettings();
expect(settings.nodeEnv).toBe("production");
expect(settings.logLevel).toBe("error");
});
});
- // ── getSettings ─────────────────────────────────────────────────────────
-
- describe("getSettings", () => {
- it("returns the same config as loadSettings", () => {
- const { service } = createService();
-
- expect(service.getSettings()).toEqual(service.loadSettings());
- });
- });
-
// ── areStatusPageThemesEnabled ──────────────────────────────────────────
describe("areStatusPageThemesEnabled", () => {
From e1c89cd0939287d685468ee2b0a6ca8082061c79 Mon Sep 17 00:00:00 2001
From: Alex Holliday
Date: Wed, 22 Jul 2026 11:00:59 -0700
Subject: [PATCH 58/84] dedupe normalization
---
server/src/utils/dataUtils.ts | 81 ++++--------
server/test/unit/utils/dataUtils.test.ts | 159 +++++++++++++++++++++++
2 files changed, 186 insertions(+), 54 deletions(-)
create mode 100644 server/test/unit/utils/dataUtils.test.ts
diff --git a/server/src/utils/dataUtils.ts b/server/src/utils/dataUtils.ts
index 7b46619e8..b264f85b1 100755
--- a/server/src/utils/dataUtils.ts
+++ b/server/src/utils/dataUtils.ts
@@ -20,72 +20,45 @@ export const getDateForRange = (dateRange: string): Date | undefined => {
}
};
-const calculatePercentile = (arr: T[], percentile: number): number => {
- const sorted = arr.slice().sort((a, b) => a.responseTime - b.responseTime);
+const percentileBy = (arr: T[], percentile: number, value: (item: T) => number): number => {
+ const sorted = arr.slice().sort((a, b) => value(a) - value(b));
const index = (percentile / 100) * (sorted.length - 1);
const lower = Math.floor(index);
const upper = lower + 1;
const weight = index % 1;
- if (upper >= sorted.length) return sorted[lower]!.responseTime;
- return sorted[lower]!.responseTime * (1 - weight) + sorted[upper]!.responseTime * weight;
+ if (upper >= sorted.length) return value(sorted[lower]!);
+ return value(sorted[lower]!) * (1 - weight) + value(sorted[upper]!) * weight;
};
-const calculatePercentileUptimeDetails = (arr: GroupedCheck[], percentile: number): number => {
- const sorted = arr.slice().sort((a, b) => a.avgResponseTime - b.avgResponseTime);
- const index = (percentile / 100) * (sorted.length - 1);
- const lower = Math.floor(index);
- const upper = lower + 1;
- const weight = index % 1;
- if (upper >= sorted.length) return sorted[lower]!.avgResponseTime;
- return sorted[lower]!.avgResponseTime * (1 - weight) + sorted[upper]!.avgResponseTime * weight;
+const rescale = (raw: number, min: number, max: number, rangeMin: number, rangeMax: number): number => {
+ const scaled = rangeMin + ((raw - min) * (rangeMax - rangeMin)) / (max - min);
+ return Math.max(rangeMin, Math.min(rangeMax, scaled));
};
export const NormalizeData = (checks: T[], rangeMin: number, rangeMax: number): NormalizedCheck[] => {
- if (checks.length > 1) {
- const min = calculatePercentile(checks, 0);
- const max = calculatePercentile(checks, 95);
- const normalizedChecks = checks.map((check) => {
- const originalResponseTime = typeof check.responseTime === "number" ? check.responseTime : 0;
- let normalizedResponseTime = rangeMin + ((check.responseTime - min) * (rangeMax - rangeMin)) / (max - min);
-
- normalizedResponseTime = Math.max(rangeMin, Math.min(rangeMax, normalizedResponseTime));
- return {
- ...check,
- responseTime: normalizedResponseTime,
- originalResponseTime: originalResponseTime,
- };
- });
-
- return normalizedChecks;
- } else {
- return checks.map((check) => {
- const originalResponseTime = typeof check.responseTime === "number" ? check.responseTime : 0;
- return { ...check, originalResponseTime: originalResponseTime };
- });
+ const original = (check: T) => (typeof check.responseTime === "number" ? check.responseTime : 0);
+ if (checks.length <= 1) {
+ return checks.map((check) => ({ ...check, originalResponseTime: original(check) }));
}
+ const min = percentileBy(checks, 0, (c) => c.responseTime);
+ const max = percentileBy(checks, 95, (c) => c.responseTime);
+ return checks.map((check) => ({
+ ...check,
+ responseTime: rescale(check.responseTime, min, max, rangeMin, rangeMax),
+ originalResponseTime: original(check),
+ }));
};
export const NormalizeDataUptimeDetails = (checks: T[], rangeMin: number, rangeMax: number): NormalizedUptimeCheck[] => {
- if (checks.length > 1) {
- const min = calculatePercentileUptimeDetails(checks, 0);
- const max = calculatePercentileUptimeDetails(checks, 95);
-
- const normalizedChecks = checks.map((check) => {
- const originalResponseTime = check.avgResponseTime;
- let normalizedResponseTime = rangeMin + ((check.avgResponseTime - min) * (rangeMax - rangeMin)) / (max - min);
-
- normalizedResponseTime = Math.max(rangeMin, Math.min(rangeMax, normalizedResponseTime));
- return {
- ...check,
- avgResponseTime: normalizedResponseTime,
- originalAvgResponseTime: originalResponseTime,
- };
- });
-
- return normalizedChecks;
- } else {
- return checks.map((check) => {
- return { ...check, originalAvgResponseTime: check.avgResponseTime };
- });
+ const original = (check: T) => (typeof check.avgResponseTime === "number" ? check.avgResponseTime : 0);
+ if (checks.length <= 1) {
+ return checks.map((check) => ({ ...check, originalAvgResponseTime: original(check) }));
}
+ const min = percentileBy(checks, 0, (c) => c.avgResponseTime);
+ const max = percentileBy(checks, 95, (c) => c.avgResponseTime);
+ return checks.map((check) => ({
+ ...check,
+ avgResponseTime: rescale(check.avgResponseTime, min, max, rangeMin, rangeMax),
+ originalAvgResponseTime: original(check),
+ }));
};
diff --git a/server/test/unit/utils/dataUtils.test.ts b/server/test/unit/utils/dataUtils.test.ts
new file mode 100644
index 000000000..f4893fe03
--- /dev/null
+++ b/server/test/unit/utils/dataUtils.test.ts
@@ -0,0 +1,159 @@
+import { describe, expect, it } from "@jest/globals";
+import { getDateForRange, NormalizeData, NormalizeDataUptimeDetails } from "../../../src/utils/dataUtils.ts";
+import type { GroupedCheck } from "../../../src/domain/checks/check.type.ts";
+
+// ── Helpers ──────────────────────────────────────────────────────────────────
+
+const makeCheck = (responseTime: number, overrides?: Record) => ({
+ id: `check-${responseTime}`,
+ status: true,
+ responseTime,
+ ...overrides,
+});
+
+const makeGrouped = (avgResponseTime: number, overrides?: Partial): GroupedCheck => ({
+ bucketDate: `bucket-${avgResponseTime}`,
+ avgResponseTime,
+ totalChecks: 5,
+ ...overrides,
+});
+
+// ── Tests ────────────────────────────────────────────────────────────────────
+
+describe("getDateForRange", () => {
+ it.each([
+ ["recent", 2 * 60 * 60 * 1000],
+ ["hour", 60 * 60 * 1000],
+ ["day", 24 * 60 * 60 * 1000],
+ ["week", 7 * 24 * 60 * 60 * 1000],
+ ["month", 30 * 24 * 60 * 60 * 1000],
+ ])("returns now minus the %s window", (range, offsetMs) => {
+ const before = Date.now();
+ const result = getDateForRange(range as string);
+ const after = Date.now();
+
+ expect(result).toBeInstanceOf(Date);
+ expect(result!.getTime()).toBeGreaterThanOrEqual(before - offsetMs);
+ expect(result!.getTime()).toBeLessThanOrEqual(after - offsetMs);
+ });
+
+ it("returns undefined for 'all'", () => {
+ expect(getDateForRange("all")).toBeUndefined();
+ });
+
+ it("returns undefined for unknown ranges", () => {
+ expect(getDateForRange("fortnight")).toBeUndefined();
+ });
+});
+
+describe("NormalizeData", () => {
+ // Fixture math: p0 of [100..500] is 100; p95 interpolates between the 4th and 5th
+ // sorted values at weight 0.8 → 400*0.2 + 500*0.8 = 480. Values rescale into
+ // [10, 100] via 10 + (rt - 100) * 90 / 380, clamped.
+ it("rescales responseTime into [rangeMin, rangeMax] anchored to the p0–p95 window", () => {
+ const checks = [100, 200, 300, 400, 500].map((rt) => makeCheck(rt));
+
+ const result = NormalizeData(checks, 10, 100);
+
+ expect(result[0]!.responseTime).toBe(10);
+ expect(result[1]!.responseTime).toBeCloseTo(33.6842, 4);
+ expect(result[2]!.responseTime).toBeCloseTo(57.3684, 4);
+ expect(result[3]!.responseTime).toBeCloseTo(81.0526, 4);
+ });
+
+ it("clamps values above the p95 anchor to rangeMax", () => {
+ const checks = [100, 200, 300, 400, 500].map((rt) => makeCheck(rt));
+
+ const result = NormalizeData(checks, 10, 100);
+
+ expect(result[4]!.responseTime).toBe(100);
+ });
+
+ it("preserves the raw value in originalResponseTime and spreads other fields", () => {
+ const checks = [makeCheck(100, { message: "OK" }), makeCheck(500)];
+
+ const result = NormalizeData(checks, 10, 100);
+
+ expect(result[0]!.originalResponseTime).toBe(100);
+ expect(result[1]!.originalResponseTime).toBe(500);
+ expect(result[0]).toMatchObject({ id: "check-100", status: true, message: "OK" });
+ });
+
+ it("passes a single check through, only adding originalResponseTime", () => {
+ const result = NormalizeData([makeCheck(250)], 10, 100);
+
+ expect(result).toEqual([{ id: "check-250", status: true, responseTime: 250, originalResponseTime: 250 }]);
+ });
+
+ it("returns an empty array for empty input", () => {
+ expect(NormalizeData([], 10, 100)).toEqual([]);
+ });
+
+ it("records originalResponseTime as 0 for a non-numeric responseTime (failed checks)", () => {
+ const checks = [makeCheck(undefined as unknown as number), makeCheck(100), makeCheck(200)];
+
+ const result = NormalizeData(checks, 10, 100);
+
+ expect(result[0]!.originalResponseTime).toBe(0);
+ });
+
+ // Characterization, not endorsement: when every value is equal the p0–p95 window
+ // has zero width and the rescale divides by zero. Fixing this is a planned
+ // follow-up (docs/planning/dataUtils-generic-normalization-refactor.md); this
+ // test pins the current behavior so the fix is a deliberate, visible change.
+ it("produces NaN when all values are equal (division by zero — known edge case)", () => {
+ const result = NormalizeData([makeCheck(50), makeCheck(50), makeCheck(50)], 10, 100);
+
+ result.forEach((check) => {
+ expect(check.responseTime).toBeNaN();
+ expect(check.originalResponseTime).toBe(50);
+ });
+ });
+});
+
+describe("NormalizeDataUptimeDetails", () => {
+ it("rescales avgResponseTime into [rangeMin, rangeMax] anchored to the p0–p95 window", () => {
+ const checks = [100, 200, 300, 400, 500].map((avg) => makeGrouped(avg));
+
+ const result = NormalizeDataUptimeDetails(checks, 10, 100);
+
+ expect(result[0]!.avgResponseTime).toBe(10);
+ expect(result[1]!.avgResponseTime).toBeCloseTo(33.6842, 4);
+ expect(result[4]!.avgResponseTime).toBe(100);
+ });
+
+ it("preserves the raw value in originalAvgResponseTime and spreads other fields", () => {
+ const checks = [makeGrouped(100), makeGrouped(500)];
+
+ const result = NormalizeDataUptimeDetails(checks, 10, 100);
+
+ expect(result[0]!.originalAvgResponseTime).toBe(100);
+ expect(result[1]!.originalAvgResponseTime).toBe(500);
+ expect(result[0]).toMatchObject({ bucketDate: "bucket-100", totalChecks: 5 });
+ });
+
+ it("passes a single bucket through, only adding originalAvgResponseTime", () => {
+ const result = NormalizeDataUptimeDetails([makeGrouped(250)], 10, 100);
+
+ expect(result).toEqual([{ bucketDate: "bucket-250", avgResponseTime: 250, totalChecks: 5, originalAvgResponseTime: 250 }]);
+ });
+
+ it("returns an empty array for empty input", () => {
+ expect(NormalizeDataUptimeDetails([], 10, 100)).toEqual([]);
+ });
+
+ it("records originalAvgResponseTime as 0 for a null avgResponseTime ($avg over an empty bucket)", () => {
+ const result = NormalizeDataUptimeDetails([makeGrouped(null as unknown as number)], 10, 100);
+
+ expect(result[0]!.originalAvgResponseTime).toBe(0);
+ });
+
+ it("produces NaN when all values are equal (division by zero — known edge case)", () => {
+ const result = NormalizeDataUptimeDetails([makeGrouped(50), makeGrouped(50)], 10, 100);
+
+ result.forEach((check) => {
+ expect(check.avgResponseTime).toBeNaN();
+ expect(check.originalAvgResponseTime).toBe(50);
+ });
+ });
+});
From 4a90ab8e781f010c5088490a07673e2ab53bcb40 Mon Sep 17 00:00:00 2001
From: Alex Holliday
Date: Thu, 23 Jul 2026 09:36:06 -0700
Subject: [PATCH 59/84] extract pagination
---
.../monitors/monitor.repository.mongo.ts | 19 +++++++++----------
1 file changed, 9 insertions(+), 10 deletions(-)
diff --git a/server/src/domain/monitors/monitor.repository.mongo.ts b/server/src/domain/monitors/monitor.repository.mongo.ts
index 96db34c15..d9e35855c 100644
--- a/server/src/domain/monitors/monitor.repository.mongo.ts
+++ b/server/src/domain/monitors/monitor.repository.mongo.ts
@@ -91,27 +91,26 @@ class MongoMonitorsRepository implements IMonitorsRepository {
{ $project: { stats: 0 } },
];
- findByTeamId = async (teamId: string, config: TeamQueryConfig): Promise => {
+ private pageOptions = (config: TeamQueryConfig) => {
const { page = 0, rowsPerPage = 0, field = "createdAt", order = "desc" } = config ?? {};
+ return { sort: { [field]: order === "asc" ? 1 : -1 } as const, skip: Math.max(page, 0) * rowsPerPage, limit: rowsPerPage };
+ };
+
+ findByTeamId = async (teamId: string, config: TeamQueryConfig): Promise => {
const query = this.queryBuilder(config, teamId);
- const sort = { [field]: order === "asc" ? 1 : -1 } as const;
- const skip = Math.max(page, 0) * rowsPerPage;
- const documents = await MonitorModel.find(query).sort(sort).skip(skip).limit(rowsPerPage);
+ const { sort, skip, limit } = this.pageOptions(config);
+ const documents = await MonitorModel.find(query).sort(sort).skip(skip).limit(limit);
return this.mapDocuments(documents);
};
findByTeamIdWithStats = async (teamId: string, config: TeamQueryConfig): Promise => {
- const { page = 0, rowsPerPage = 0, field = "createdAt", order = "desc" } = config ?? {};
-
+ const { sort, skip, limit } = this.pageOptions(config);
const query = this.queryBuilder(config, teamId);
- const sort = { [field]: order === "asc" ? 1 : -1 } as const;
- const skip = Math.max(page, 0) * rowsPerPage;
-
const pipeline: PipelineStage[] = [
{ $match: query },
{ $sort: sort },
- ...(rowsPerPage ? [{ $skip: skip }, { $limit: rowsPerPage }] : []),
+ ...(limit ? [{ $skip: skip }, { $limit: limit }] : []),
...this.uptimeStatsLookupStages,
];
From 3af00281c48a26a5716c751a350106bbcc9e3d60 Mon Sep 17 00:00:00 2001
From: Alex Holliday
Date: Thu, 23 Jul 2026 10:25:24 -0700
Subject: [PATCH 60/84] extract date range calculation for conisistency
---
.../checks/check.repository.interface.ts | 11 ++--
.../domain/checks/check.repository.mongo.ts | 39 +++++------
server/src/domain/checks/check.service.ts | 13 ++--
.../geo-check.repository.interface.ts | 11 +---
.../geo-checks/geo-check.repository.mongo.ts | 29 ++++-----
.../domain/geo-checks/geo-check.service.ts | 5 +-
.../src/domain/incidents/incident.service.ts | 5 +-
server/src/domain/monitors/monitor.service.ts | 65 ++++---------------
server/src/utils/dataUtils.ts | 19 +++++-
9 files changed, 78 insertions(+), 119 deletions(-)
diff --git a/server/src/domain/checks/check.repository.interface.ts b/server/src/domain/checks/check.repository.interface.ts
index 95c0ade80..8c921c843 100644
--- a/server/src/domain/checks/check.repository.interface.ts
+++ b/server/src/domain/checks/check.repository.interface.ts
@@ -8,6 +8,7 @@ import type {
} from "@/domain/checks/check.type.js";
import type { MonitorType } from "@/domain/monitors/monitor.types.js";
import type { LatestChecksMap } from "@/domain/checks/check.repository.mongo.js";
+import { DateRange } from "@/types/query.js";
export interface IChecksRepository {
// create
@@ -19,7 +20,7 @@ export interface IChecksRepository {
findByMonitorId(
monitorId: string,
sortOrder: string,
- dateRange: string,
+ dateRange: DateRange,
filter: string | undefined,
page: number,
rowsPerPage: number,
@@ -27,7 +28,7 @@ export interface IChecksRepository {
): Promise;
findByTeamId(
sortOrder: string,
- dateRange: string,
+ dateRange: DateRange,
filter: string | undefined,
page: number,
rowsPerPage: number,
@@ -36,12 +37,10 @@ export interface IChecksRepository {
findLatestByMonitorIds(monitorIds: string[], options?: { limitPerMonitor?: number }): Promise;
findByDateRangeAndMonitorId(
monitorId: string,
- startDate: Date,
- endDate: Date,
- dateString: string,
+ dateRange: DateRange,
options?: { type?: MonitorType }
): Promise;
- findSummaryByTeamId(teamId: string, dateRange: string): Promise;
+ findSummaryByTeamId(teamId: string, dateRange: DateRange): Promise;
findUnevaluatedByMonitorId(monitorId: string, since: number): Promise;
// update
//delete
diff --git a/server/src/domain/checks/check.repository.mongo.ts b/server/src/domain/checks/check.repository.mongo.ts
index 1433c950a..4bfb65dd4 100644
--- a/server/src/domain/checks/check.repository.mongo.ts
+++ b/server/src/domain/checks/check.repository.mongo.ts
@@ -15,11 +15,12 @@ import type {
import type { MonitorType } from "@/domain/monitors/monitor.types.js";
import { CheckModel, type CheckDocument } from "@/domain/checks/check.model.js";
import mongoose from "mongoose";
-import { getDateForRange } from "@/utils/dataUtils.js";
+import { getDateFormat, getDateForRange } from "@/utils/dataUtils.js";
import { ILogger } from "@/utils/logger.js";
import { toStringId, toDateString } from "@/utils/mongoMappers.js";
import { getHardwareUpChecks, getHardwareStats, getHardwareTotalChecks } from "@/domain/checks/check.hardware.aggregations.js";
+import { DateRange } from "@/types/query.js";
const SERVICE_NAME = "StatusService";
@@ -207,7 +208,7 @@ class MongoChecksRepository implements IChecksRepository {
findByMonitorId = async (
monitorId: string,
sortOrder: string,
- dateRange: string,
+ dateRange: DateRange,
filter: string | undefined,
page: number,
rowsPerPage: number,
@@ -217,11 +218,7 @@ class MongoChecksRepository implements IChecksRepository {
const matchStage: Record = {
"metadata.monitorId": new mongoose.Types.ObjectId(monitorId),
...(typeof status !== "undefined" && { status }),
- ...(getDateForRange(dateRange) && {
- createdAt: {
- $gte: getDateForRange(dateRange),
- },
- }),
+ createdAt: { $gte: getDateForRange(dateRange) },
};
if (filter !== undefined) {
@@ -265,14 +262,10 @@ class MongoChecksRepository implements IChecksRepository {
return { checksCount, checks: this.mapDocuments(checks) };
};
- findByTeamId = async (sortOrder: string, dateRange: string, filter: string, page: number, rowsPerPage: number, teamId: string) => {
+ findByTeamId = async (sortOrder: string, dateRange: DateRange, filter: string, page: number, rowsPerPage: number, teamId: string) => {
const matchStage: Record = {
"metadata.teamId": new mongoose.Types.ObjectId(teamId),
- ...(getDateForRange(dateRange) && {
- createdAt: {
- $gte: getDateForRange(dateRange),
- },
- }),
+ createdAt: { $gte: getDateForRange(dateRange) },
};
// Add filter to match stage
if (filter !== undefined) {
@@ -341,25 +334,25 @@ class MongoChecksRepository implements IChecksRepository {
return mapped;
};
- findByDateRangeAndMonitorId = async (monitorId: string, startDate: Date, endDate: Date, dateString: string, options?: { type?: MonitorType }) => {
+ findByDateRangeAndMonitorId = async (monitorId: string, dateRange: DateRange, options?: { type?: MonitorType }) => {
const monitorObjectId = new mongoose.Types.ObjectId(monitorId);
+ const start = getDateForRange(dateRange);
+ const dateString = getDateFormat(dateRange);
+
+ const end = new Date();
if (options?.type === "hardware") {
- return this.findHardwareDateRangeChecks(monitorObjectId, startDate, endDate, dateString);
+ return this.findHardwareDateRangeChecks(monitorObjectId, start, end, dateString);
}
if (options?.type === "pagespeed") {
- return this.findPageSpeedDateRangeChecks(monitorObjectId, startDate, endDate, dateString);
+ return this.findPageSpeedDateRangeChecks(monitorObjectId, start, end, dateString);
}
- return this.findUptimeDateRangeChecks(options?.type ?? "http", monitorObjectId, startDate, endDate, dateString);
+ return this.findUptimeDateRangeChecks(options?.type ?? "http", monitorObjectId, start, end, dateString);
};
- findSummaryByTeamId = async (teamId: string, dateRange: string) => {
+ findSummaryByTeamId = async (teamId: string, dateRange: DateRange) => {
const baseMatch = {
"metadata.teamId": new mongoose.Types.ObjectId(teamId),
- ...(getDateForRange(dateRange) && {
- createdAt: {
- $gte: getDateForRange(dateRange),
- },
- }),
+ createdAt: { $gte: getDateForRange(dateRange) },
};
const [totalResult, downResult] = await Promise.all([
diff --git a/server/src/domain/checks/check.service.ts b/server/src/domain/checks/check.service.ts
index f3c18ec91..b577658bd 100644
--- a/server/src/domain/checks/check.service.ts
+++ b/server/src/domain/checks/check.service.ts
@@ -6,6 +6,7 @@ import type { MonitorPayloadMap, MonitorStatusResponse } from "@/types/network.j
import type { HardwareStatusPayload, PageSpeedStatusPayload } from "@/types/network.js";
import { AppError } from "@/utils/AppError.js";
import { ILogger } from "@/utils/logger.js";
+import { DateRange } from "@/types/query.js";
const SERVICE_NAME = "checkService";
@@ -18,7 +19,7 @@ export interface ICheckService {
monitorId: string;
teamId: string;
sortOrder: string;
- dateRange: string;
+ dateRange: DateRange;
page: number;
rowsPerPage: number;
filter?: string;
@@ -27,12 +28,12 @@ export interface ICheckService {
getChecksByTeam(params: {
teamId: string;
sortOrder: string;
- dateRange: string;
+ dateRange: DateRange;
page: number;
rowsPerPage: number;
filter?: string;
}): Promise;
- getChecksSummaryByTeamId(params: { teamId: string; dateRange: string }): Promise;
+ getChecksSummaryByTeamId(params: { teamId: string; dateRange: DateRange }): Promise;
deleteChecks(params: { monitorId: string; teamId: string }): Promise;
deleteChecksByTeamId(params: { teamId: string }): Promise;
deleteOlderThan(date: Date): Promise;
@@ -164,7 +165,7 @@ export class CheckService implements ICheckService {
monitorId: string;
teamId: string;
sortOrder: string;
- dateRange: string;
+ dateRange: DateRange;
page: number;
rowsPerPage: number;
status?: boolean;
@@ -198,7 +199,7 @@ export class CheckService implements ICheckService {
}: {
teamId: string;
sortOrder: string;
- dateRange: string;
+ dateRange: DateRange;
page: number;
rowsPerPage: number;
filter?: string;
@@ -210,7 +211,7 @@ export class CheckService implements ICheckService {
return checkData;
};
- getChecksSummaryByTeamId = async ({ teamId, dateRange }: { teamId: string; dateRange: string }) => {
+ getChecksSummaryByTeamId = async ({ teamId, dateRange }: { teamId: string; dateRange: DateRange }) => {
const summary = await this.checksRepository.findSummaryByTeamId(teamId, dateRange);
return summary;
};
diff --git a/server/src/domain/geo-checks/geo-check.repository.interface.ts b/server/src/domain/geo-checks/geo-check.repository.interface.ts
index 2e6f33e33..a2cb7f84e 100644
--- a/server/src/domain/geo-checks/geo-check.repository.interface.ts
+++ b/server/src/domain/geo-checks/geo-check.repository.interface.ts
@@ -1,5 +1,6 @@
import type { GeoCheck, GroupedGeoCheck } from "@/domain/geo-checks/geo-check.type.js";
import type { GeoContinent, FlatGeoCheck } from "@/domain/geo-checks/geo-check.type.js";
+import { DateRange } from "@/types/query.js";
export interface GeoChecksQueryResult {
geoChecksCount: number;
@@ -16,19 +17,13 @@ export interface IGeoChecksRepository {
findByMonitorId(
monitorId: string,
sortOrder: string,
- dateRange: string,
+ dateRange: DateRange,
page: number,
rowsPerPage: number,
continents?: GeoContinent[]
): Promise