├── .gitignore ├── CONTRIBUTING.md ├── LICENSE.md ├── README.md ├── SECURITY.md ├── application ├── .gitignore ├── bun.lockb ├── components.json ├── eslint.config.js ├── index.html ├── package-lock.json ├── package.json ├── postcss.config.js ├── public │ ├── favicon.ico │ ├── placeholder.svg │ ├── robots.txt │ └── upload │ │ └── profile │ │ ├── profile1.svg │ │ ├── profile2.svg │ │ ├── profile3.svg │ │ ├── profile4.svg │ │ ├── profile5.svg │ │ ├── profile6.svg │ │ ├── profile7.svg │ │ └── profile8.svg ├── src │ ├── App.css │ ├── App.tsx │ ├── api │ │ ├── index.ts │ │ ├── realtime │ │ │ └── index.ts │ │ └── settings │ │ │ └── index.ts │ ├── components │ │ ├── dashboard │ │ │ ├── DashboardContent.tsx │ │ │ ├── Header.tsx │ │ │ ├── QuickActionsDialog.tsx │ │ │ ├── ServiceFilters.tsx │ │ │ ├── ServicesTable.tsx │ │ │ ├── Sidebar.tsx │ │ │ └── StatusCards.tsx │ │ ├── profile │ │ │ ├── ChangePasswordForm.tsx │ │ │ ├── ProfileContent.tsx │ │ │ ├── UpdateProfileForm.tsx │ │ │ └── UserProfileDetails.tsx │ │ ├── schedule-incident │ │ │ ├── IncidentManagementTab.tsx │ │ │ ├── ScheduleIncidentContent.tsx │ │ │ ├── ScheduledMaintenanceTab.tsx │ │ │ ├── common │ │ │ │ └── OverviewCard.tsx │ │ │ ├── hooks │ │ │ │ ├── index.ts │ │ │ │ ├── useIncidentData.ts │ │ │ │ └── useMaintenanceData.ts │ │ │ ├── incident-management │ │ │ │ ├── ErrorState.tsx │ │ │ │ ├── HeaderActions.tsx │ │ │ │ ├── IncidentManagementContainer.tsx │ │ │ │ ├── OverviewCards.tsx │ │ │ │ ├── TabContent.tsx │ │ │ │ └── index.ts │ │ │ ├── incident │ │ │ │ ├── CreateIncidentDialog.tsx │ │ │ │ ├── EditIncidentDialog.tsx │ │ │ │ ├── EmptyIncidentState.tsx │ │ │ │ ├── IncidentActionsMenu.tsx │ │ │ │ ├── IncidentStatusBadge.tsx │ │ │ │ ├── IncidentStatusDropdown.tsx │ │ │ │ ├── IncidentTable.tsx │ │ │ │ ├── detail-dialog │ │ │ │ │ ├── IncidentDetailContent.tsx │ │ │ │ │ ├── IncidentDetailDialog.tsx │ │ │ │ │ ├── IncidentDetailFooter.tsx │ │ │ │ │ ├── IncidentDetailHeader.tsx │ │ │ │ │ ├── IncidentDetailSections.tsx │ │ │ │ │ ├── components │ │ │ │ │ │ ├── CloseButton.tsx │ │ │ │ │ │ ├── DownloadPdfButton.tsx │ │ │ │ │ │ ├── PrintButton.tsx │ │ │ │ │ │ └── index.ts │ │ │ │ │ ├── hooks │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── useDownloadIncidentPdf.ts │ │ │ │ │ │ └── usePrintIncident.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ └── sections │ │ │ │ │ │ ├── AffectedSystemsSection.tsx │ │ │ │ │ │ ├── AssignmentSection.tsx │ │ │ │ │ │ ├── BasicInfoSection.tsx │ │ │ │ │ │ ├── ImpactAnalysisSection.tsx │ │ │ │ │ │ ├── ResolutionSection.tsx │ │ │ │ │ │ ├── TimelineSection.tsx │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ └── utils.ts │ │ │ │ ├── form │ │ │ │ │ ├── IncidentAffectedFields.tsx │ │ │ │ │ ├── IncidentBasicFields.tsx │ │ │ │ │ ├── IncidentConfigFields.tsx │ │ │ │ │ ├── IncidentDetailsFields.tsx │ │ │ │ │ └── index.ts │ │ │ │ ├── hooks │ │ │ │ │ ├── useIncidentEditForm.ts │ │ │ │ │ └── useIncidentForm.ts │ │ │ │ ├── index.ts │ │ │ │ └── table │ │ │ │ │ ├── IncidentTable.tsx │ │ │ │ │ ├── IncidentTableRow.tsx │ │ │ │ │ ├── IncidentTableSkeleton.tsx │ │ │ │ │ ├── IncidentTableUtils.tsx │ │ │ │ │ └── index.ts │ │ │ ├── index.ts │ │ │ └── maintenance │ │ │ │ ├── CreateMaintenanceDialog.tsx │ │ │ │ ├── EmptyMaintenanceState.tsx │ │ │ │ ├── MaintenanceActionsMenu.tsx │ │ │ │ ├── MaintenanceStatusBadge.tsx │ │ │ │ ├── MaintenanceStatusChecker.tsx │ │ │ │ ├── MaintenanceStatusDropdown.tsx │ │ │ │ ├── MaintenanceTable.tsx │ │ │ │ ├── detail-dialog │ │ │ │ ├── MaintenanceDetailContent.tsx │ │ │ │ ├── MaintenanceDetailDialog.tsx │ │ │ │ ├── MaintenanceDetailFooter.tsx │ │ │ │ ├── MaintenanceDetailHeader.tsx │ │ │ │ ├── MaintenanceDetailSections.tsx │ │ │ │ ├── components │ │ │ │ │ ├── CloseButton.tsx │ │ │ │ │ ├── DownloadPdfButton.tsx │ │ │ │ │ ├── PrintButton.tsx │ │ │ │ │ └── index.ts │ │ │ │ ├── hooks │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── useDownloadMaintenancePdf.ts │ │ │ │ │ └── usePrintMaintenance.ts │ │ │ │ └── index.ts │ │ │ │ ├── edit-dialog │ │ │ │ ├── EditMaintenanceDialog.tsx │ │ │ │ └── index.ts │ │ │ │ ├── form │ │ │ │ ├── MaintenanceAffectedFields.tsx │ │ │ │ ├── MaintenanceBasicFields.tsx │ │ │ │ ├── MaintenanceConfigFields.tsx │ │ │ │ ├── MaintenanceNotificationSettingsField.tsx │ │ │ │ ├── MaintenanceTimeFields.tsx │ │ │ │ ├── config │ │ │ │ │ ├── AssignedUsersField.tsx │ │ │ │ │ ├── ImpactLevelField.tsx │ │ │ │ │ ├── PriorityField.tsx │ │ │ │ │ ├── StatusField.tsx │ │ │ │ │ └── index.ts │ │ │ │ └── index.ts │ │ │ │ ├── hooks │ │ │ │ ├── useMaintenanceEditForm.ts │ │ │ │ └── useMaintenanceForm.ts │ │ │ │ └── index.ts │ │ ├── services │ │ │ ├── AddServiceDialog.tsx │ │ │ ├── DateRangeFilter.tsx │ │ │ ├── LastCheckedTime.tsx │ │ │ ├── LoadingState.tsx │ │ │ ├── ResponseTimeChart.tsx │ │ │ ├── ServiceDeleteDialog.tsx │ │ │ ├── ServiceDetailContainer.tsx │ │ │ ├── ServiceDetailContainer │ │ │ │ ├── ServiceDetailWrapper.tsx │ │ │ │ ├── hooks │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── useRealTimeUpdates.tsx │ │ │ │ │ └── useServiceData.tsx │ │ │ │ └── index.tsx │ │ │ ├── ServiceDetailContent.tsx │ │ │ ├── ServiceEditDialog.tsx │ │ │ ├── ServiceForm.tsx │ │ │ ├── ServiceHeader.tsx │ │ │ ├── ServiceHistoryDialog.tsx │ │ │ ├── ServiceMonitoringButton.tsx │ │ │ ├── ServiceNotFound.tsx │ │ │ ├── ServiceRow.tsx │ │ │ ├── ServiceStatsCards.tsx │ │ │ ├── ServiceUptimeHistory.tsx │ │ │ ├── ServicesTableContainer.tsx │ │ │ ├── ServicesTableView.tsx │ │ │ ├── StatusBadge.tsx │ │ │ ├── UptimeBar.tsx │ │ │ ├── add-service │ │ │ │ ├── ServiceBasicFields.tsx │ │ │ │ ├── ServiceConfigFields.tsx │ │ │ │ ├── ServiceForm.tsx │ │ │ │ ├── ServiceFormActions.tsx │ │ │ │ ├── ServiceNotificationFields.tsx │ │ │ │ ├── ServiceTypeField.tsx │ │ │ │ ├── index.ts │ │ │ │ └── types.ts │ │ │ ├── hooks │ │ │ │ ├── index.ts │ │ │ │ ├── useDialogState.ts │ │ │ │ └── useServiceActions.ts │ │ │ ├── incident-history │ │ │ │ ├── EmptyState.tsx │ │ │ │ ├── IncidentTable.tsx │ │ │ │ ├── LatestChecksTable.tsx │ │ │ │ ├── StatusFilterTabs.tsx │ │ │ │ ├── TablePagination.tsx │ │ │ │ ├── index.ts │ │ │ │ ├── types.ts │ │ │ │ └── utils.tsx │ │ │ ├── index.ts │ │ │ └── service-row │ │ │ │ ├── ServiceRowActions.tsx │ │ │ │ ├── ServiceRowHeader.tsx │ │ │ │ ├── ServiceRowResponseTime.tsx │ │ │ │ └── index.ts │ │ ├── settings │ │ │ ├── GeneralSettings.tsx │ │ │ ├── about-system │ │ │ │ ├── AboutSystem.tsx │ │ │ │ └── index.ts │ │ │ ├── alerts-templates │ │ │ │ ├── AlertsTemplates.tsx │ │ │ │ ├── TemplateDialog.tsx │ │ │ │ ├── TemplateList.tsx │ │ │ │ ├── form │ │ │ │ │ ├── BasicTemplateFields.tsx │ │ │ │ │ ├── MessagesTabContent.tsx │ │ │ │ │ ├── PlaceholdersTabContent.tsx │ │ │ │ │ └── index.ts │ │ │ │ ├── hooks │ │ │ │ │ ├── index.ts │ │ │ │ │ └── useTemplateForm.ts │ │ │ │ └── index.ts │ │ │ ├── data-retention │ │ │ │ ├── DataRetentionSettings.tsx │ │ │ │ └── index.ts │ │ │ ├── general │ │ │ │ ├── GeneralSettingsPanel.tsx │ │ │ │ ├── MailSettingsTab.tsx │ │ │ │ ├── SystemSettingsTab.tsx │ │ │ │ └── types.ts │ │ │ ├── notification-settings │ │ │ │ ├── NotificationChannelDialog.tsx │ │ │ │ ├── NotificationChannelList.tsx │ │ │ │ ├── NotificationSettings.tsx │ │ │ │ └── index.ts │ │ │ └── user-management │ │ │ │ ├── AddUserDialog.tsx │ │ │ │ ├── DeleteUserDialog.tsx │ │ │ │ ├── EditUserDialog.tsx │ │ │ │ ├── UserManagement.tsx │ │ │ │ ├── UserTable.tsx │ │ │ │ ├── avatarOptions.ts │ │ │ │ ├── form-fields │ │ │ │ ├── AddUserForm.tsx │ │ │ │ ├── UserProfilePictureField.tsx │ │ │ │ ├── UserRoleField.tsx │ │ │ │ ├── UserTextField.tsx │ │ │ │ ├── UserToggleField.tsx │ │ │ │ └── index.ts │ │ │ │ ├── hooks │ │ │ │ ├── index.ts │ │ │ │ ├── useUserDialogs.ts │ │ │ │ ├── useUserForm.ts │ │ │ │ ├── useUserManagement.ts │ │ │ │ ├── useUserOperations.ts │ │ │ │ └── useUsersList.ts │ │ │ │ ├── index.ts │ │ │ │ └── userForms.ts │ │ ├── ssl-domain │ │ │ ├── AddSSLCertificateForm.tsx │ │ │ ├── EditSSLCertificateForm.tsx │ │ │ ├── SSLCertificateStatusCards.tsx │ │ │ ├── SSLCertificatesTable.tsx │ │ │ ├── SSLDomainContent.tsx │ │ │ └── SSLStatusBadge.tsx │ │ └── ui │ │ │ ├── accordion.tsx │ │ │ ├── alert-dialog.tsx │ │ │ ├── alert.tsx │ │ │ ├── aspect-ratio.tsx │ │ │ ├── avatar.tsx │ │ │ ├── badge.tsx │ │ │ ├── breadcrumb.tsx │ │ │ ├── button.tsx │ │ │ ├── calendar.tsx │ │ │ ├── card.tsx │ │ │ ├── carousel.tsx │ │ │ ├── chart.tsx │ │ │ ├── checkbox.tsx │ │ │ ├── collapsible.tsx │ │ │ ├── command.tsx │ │ │ ├── context-menu.tsx │ │ │ ├── dialog.tsx │ │ │ ├── drawer.tsx │ │ │ ├── dropdown-menu.tsx │ │ │ ├── form.tsx │ │ │ ├── hover-card.tsx │ │ │ ├── input-otp.tsx │ │ │ ├── input.tsx │ │ │ ├── label.tsx │ │ │ ├── menubar.tsx │ │ │ ├── navigation-menu.tsx │ │ │ ├── pagination.tsx │ │ │ ├── popover.tsx │ │ │ ├── progress.tsx │ │ │ ├── radio-group.tsx │ │ │ ├── resizable.tsx │ │ │ ├── scroll-area.tsx │ │ │ ├── select.tsx │ │ │ ├── separator.tsx │ │ │ ├── sheet.tsx │ │ │ ├── sidebar.tsx │ │ │ ├── skeleton.tsx │ │ │ ├── slider.tsx │ │ │ ├── sonner.tsx │ │ │ ├── switch.tsx │ │ │ ├── table.tsx │ │ │ ├── tabs.tsx │ │ │ ├── textarea.tsx │ │ │ ├── toast.tsx │ │ │ ├── toaster.tsx │ │ │ ├── toggle-group.tsx │ │ │ ├── toggle.tsx │ │ │ ├── tooltip.tsx │ │ │ └── use-toast.ts │ ├── contexts │ │ ├── LanguageContext.tsx │ │ └── ThemeContext.tsx │ ├── hooks │ │ ├── use-mobile.tsx │ │ ├── use-toast.ts │ │ └── useSystemSettings.tsx │ ├── index.css │ ├── lib │ │ ├── pocketbase.ts │ │ └── utils.ts │ ├── main.tsx │ ├── pages │ │ ├── Dashboard.tsx │ │ ├── Index.tsx │ │ ├── Login.tsx │ │ ├── NotFound.tsx │ │ ├── Profile.tsx │ │ ├── ScheduleIncident.tsx │ │ ├── ServiceDetail.tsx │ │ ├── Settings.tsx │ │ └── SslDomain.tsx │ ├── services │ │ ├── alertConfigService.ts │ │ ├── authService.ts │ │ ├── dataRetentionService.ts │ │ ├── incident │ │ │ ├── incidentCache.ts │ │ │ ├── incidentFetch.ts │ │ │ ├── incidentOperations.ts │ │ │ ├── incidentPdfService.ts │ │ │ ├── incidentService.ts │ │ │ ├── incidentUtils.ts │ │ │ ├── index.ts │ │ │ ├── pdf │ │ │ │ ├── generator.ts │ │ │ │ ├── headerFooter.ts │ │ │ │ ├── index.ts │ │ │ │ ├── sections.ts │ │ │ │ └── utils.ts │ │ │ └── types.ts │ │ ├── incidentService.ts │ │ ├── maintenance │ │ │ ├── api │ │ │ │ ├── index.ts │ │ │ │ ├── maintenanceCache.ts │ │ │ │ ├── maintenanceFetch.ts │ │ │ │ ├── maintenanceOperations.ts │ │ │ │ └── maintenanceValidation.ts │ │ │ ├── index.ts │ │ │ ├── maintenanceNotificationService.ts │ │ │ ├── maintenanceService.ts │ │ │ ├── maintenanceUtils.ts │ │ │ └── pdf │ │ │ │ ├── generator.ts │ │ │ │ ├── headerFooter.ts │ │ │ │ ├── index.ts │ │ │ │ ├── sections.ts │ │ │ │ └── utils.ts │ │ ├── maintenanceService.ts │ │ ├── monitoring │ │ │ ├── handlers │ │ │ │ └── serviceStatusHandlers.ts │ │ │ ├── httpChecker.ts │ │ │ ├── index.ts │ │ │ ├── monitoringIntervals.ts │ │ │ ├── service-status │ │ │ │ ├── index.ts │ │ │ │ ├── pauseMonitoring.ts │ │ │ │ ├── resumeMonitoring.ts │ │ │ │ ├── startAllServices.ts │ │ │ │ └── startMonitoring.ts │ │ │ └── utils │ │ │ │ ├── httpUtils.ts │ │ │ │ └── notificationUtils.ts │ │ ├── notification │ │ │ ├── index.ts │ │ │ ├── signalService.ts │ │ │ ├── telegramService.ts │ │ │ ├── templateProcessor.ts │ │ │ └── types.ts │ │ ├── notificationService.ts │ │ ├── serviceService.ts │ │ ├── settingsService.ts │ │ ├── ssl │ │ │ ├── index.ts │ │ │ ├── notification │ │ │ │ ├── index.ts │ │ │ │ ├── sslCheckNotifier.ts │ │ │ │ ├── sslNotificationSender.ts │ │ │ │ └── sslScheduleUtils.ts │ │ │ ├── sslCertificateOperations.ts │ │ │ ├── sslCheckerService.ts │ │ │ ├── sslCheckerUtils.ts │ │ │ ├── sslFetchService.ts │ │ │ ├── sslPrimaryChecker.ts │ │ │ ├── sslStatusUtils.ts │ │ │ ├── types.ts │ │ │ └── utils.ts │ │ ├── sslCertificateService.ts │ │ ├── sslCheckerService.ts │ │ ├── templateService.ts │ │ ├── types │ │ │ └── maintenance.types.ts │ │ ├── uptimeService.ts │ │ └── userService.ts │ ├── translations │ │ ├── de │ │ │ ├── about.ts │ │ │ ├── common.ts │ │ │ ├── incident.ts │ │ │ ├── index.ts │ │ │ ├── login.ts │ │ │ ├── maintenance.ts │ │ │ ├── menu.ts │ │ │ ├── services.ts │ │ │ ├── settings.ts │ │ │ └── ssl.ts │ │ ├── en │ │ │ ├── about.ts │ │ │ ├── common.ts │ │ │ ├── incident.ts │ │ │ ├── index.ts │ │ │ ├── login.ts │ │ │ ├── maintenance.ts │ │ │ ├── menu.ts │ │ │ ├── services.ts │ │ │ ├── settings.ts │ │ │ └── ssl.ts │ │ ├── index.ts │ │ ├── km │ │ │ ├── about.ts │ │ │ ├── common.ts │ │ │ ├── incident.ts │ │ │ ├── index.ts │ │ │ ├── login.ts │ │ │ ├── maintenance.ts │ │ │ ├── menu.ts │ │ │ ├── services.ts │ │ │ ├── settings.ts │ │ │ └── ssl.ts │ │ └── types │ │ │ ├── about.ts │ │ │ ├── common.ts │ │ │ ├── incident.ts │ │ │ ├── index.ts │ │ │ ├── login.ts │ │ │ ├── maintenance.ts │ │ │ ├── menu.ts │ │ │ ├── services.ts │ │ │ ├── settings.ts │ │ │ └── ssl.ts │ ├── types │ │ ├── jspdf-autotable.d.ts │ │ ├── service.types.ts │ │ └── ssl.types.ts │ └── vite-env.d.ts ├── tailwind.config.ts ├── tsconfig.app.json ├── tsconfig.json ├── tsconfig.node.json └── vite.config.ts ├── docker-compose.yml ├── docker ├── docker-compose-dev.yml └── docker-compose.prod.yml ├── package-lock.json ├── scripts ├── entrypoint.sh └── setup.sh └── server ├── pb_data ├── auxiliary.db ├── auxiliary.db-shm ├── auxiliary.db-wal ├── data.db └── types.d.ts ├── pb_migrations ├── 1746786764_updated_services.js ├── 1746786820_updated_services.js ├── 1746786864_updated_services.js ├── 1746787173_updated_services.js ├── 1746787500_updated_services.js ├── 1746787517_updated_services.js ├── 1746787595_updated_uptime_data.js ├── 1746787681_updated_alert_configurations.js ├── 1748029497_updated__superusers.js └── pb_schema.json └── pocketbase /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | application/src/lib/pocketbase.ts 26 | Dockerfile 27 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | Copyright (c) 2025, Tola Leng 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software 5 | and associated documentation files (the "Software"), to deal in the Software without restriction, 6 | including without limitation the rights to use, copy, modify, merge, publish, distribute, 7 | sublicense, and/or sell copies of the Software, and to permit persons to whom the Software 8 | is furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all copies or 11 | substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING 14 | BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 15 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 16 | DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 17 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 18 | -------------------------------------------------------------------------------- /application/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /application/bun.lockb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/operacle/checkcle/4dc005c7e4afe7aa2733c00d8434b5e97a804fa9/application/bun.lockb -------------------------------------------------------------------------------- /application/components.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://ui.shadcn.com/schema.json", 3 | "style": "default", 4 | "rsc": false, 5 | "tsx": true, 6 | "tailwind": { 7 | "config": "tailwind.config.ts", 8 | "css": "src/index.css", 9 | "baseColor": "slate", 10 | "cssVariables": true, 11 | "prefix": "" 12 | }, 13 | "aliases": { 14 | "components": "@/components", 15 | "utils": "@/lib/utils", 16 | "ui": "@/components/ui", 17 | "lib": "@/lib", 18 | "hooks": "@/hooks" 19 | } 20 | } -------------------------------------------------------------------------------- /application/eslint.config.js: -------------------------------------------------------------------------------- 1 | import js from "@eslint/js"; 2 | import globals from "globals"; 3 | import reactHooks from "eslint-plugin-react-hooks"; 4 | import reactRefresh from "eslint-plugin-react-refresh"; 5 | import tseslint from "typescript-eslint"; 6 | 7 | export default tseslint.config( 8 | { ignores: ["dist"] }, 9 | { 10 | extends: [js.configs.recommended, ...tseslint.configs.recommended], 11 | files: ["**/*.{ts,tsx}"], 12 | languageOptions: { 13 | ecmaVersion: 2020, 14 | globals: globals.browser, 15 | }, 16 | plugins: { 17 | "react-hooks": reactHooks, 18 | "react-refresh": reactRefresh, 19 | }, 20 | rules: { 21 | ...reactHooks.configs.recommended.rules, 22 | "react-refresh/only-export-components": [ 23 | "warn", 24 | { allowConstantExport: true }, 25 | ], 26 | "@typescript-eslint/no-unused-vars": "off", 27 | }, 28 | } 29 | ); 30 | -------------------------------------------------------------------------------- /application/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | CheckCle is an open-source monitoring stack 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /application/postcss.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /application/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/operacle/checkcle/4dc005c7e4afe7aa2733c00d8434b5e97a804fa9/application/public/favicon.ico -------------------------------------------------------------------------------- /application/public/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: Googlebot 2 | Allow: / 3 | 4 | User-agent: Bingbot 5 | Allow: / 6 | 7 | User-agent: Twitterbot 8 | Allow: / 9 | 10 | User-agent: facebookexternalhit 11 | Allow: / 12 | 13 | User-agent: * 14 | Allow: / 15 | -------------------------------------------------------------------------------- /application/public/upload/profile/profile1.svg: -------------------------------------------------------------------------------- 1 | Fun Emoji SetDavis Uchehttps://www.figma.com/community/file/968125295144990435https://creativecommons.org/licenses/by/4.0/Remix of „Fun Emoji Set” (https://www.figma.com/community/file/968125295144990435) by „Davis Uche”, licensed under „CC BY 4.0” (https://creativecommons.org/licenses/by/4.0/) -------------------------------------------------------------------------------- /application/src/App.css: -------------------------------------------------------------------------------- 1 | #root { 2 | max-width: 1280px; 3 | margin: 0 auto; 4 | padding: 2rem; 5 | text-align: center; 6 | } 7 | 8 | .logo { 9 | height: 6em; 10 | padding: 1.5em; 11 | will-change: filter; 12 | transition: filter 300ms; 13 | } 14 | .logo:hover { 15 | filter: drop-shadow(0 0 2em #646cffaa); 16 | } 17 | .logo.react:hover { 18 | filter: drop-shadow(0 0 2em #61dafbaa); 19 | } 20 | 21 | @keyframes logo-spin { 22 | from { 23 | transform: rotate(0deg); 24 | } 25 | to { 26 | transform: rotate(360deg); 27 | } 28 | } 29 | 30 | @media (prefers-reduced-motion: no-preference) { 31 | a:nth-of-type(2) .logo { 32 | animation: logo-spin infinite 20s linear; 33 | } 34 | } 35 | 36 | .card { 37 | padding: 2em; 38 | } 39 | 40 | .read-the-docs { 41 | color: #888; 42 | } 43 | -------------------------------------------------------------------------------- /application/src/components/dashboard/ServiceFilters.tsx: -------------------------------------------------------------------------------- 1 | 2 | import { Button } from "@/components/ui/button"; 3 | import { Input } from "@/components/ui/input"; 4 | import { Select, SelectTrigger, SelectValue, SelectContent, SelectItem } from "@/components/ui/select"; 5 | import { Plus } from "lucide-react"; 6 | 7 | interface ServiceFiltersProps { 8 | filter: string; 9 | setFilter: (value: string) => void; 10 | searchTerm: string; 11 | setSearchTerm: (value: string) => void; 12 | servicesCount: number; 13 | } 14 | 15 | export const ServiceFilters = ({ 16 | filter, 17 | setFilter, 18 | searchTerm, 19 | setSearchTerm, 20 | servicesCount 21 | }: ServiceFiltersProps) => { 22 | return ( 23 |
24 |
25 |

Currently Monitoring

26 | 27 | {servicesCount} 28 | 29 |
30 |
31 | 43 |
44 | setSearchTerm(e.target.value)} 49 | /> 50 |
51 |
52 |
53 | ); 54 | }; 55 | -------------------------------------------------------------------------------- /application/src/components/dashboard/ServicesTable.tsx: -------------------------------------------------------------------------------- 1 | 2 | import { Service } from "@/types/service.types"; 3 | import { ServicesTableContainer } from "@/components/services/ServicesTableContainer"; 4 | 5 | interface ServicesTableProps { 6 | services: Service[]; 7 | } 8 | 9 | export const ServicesTable = ({ services }: ServicesTableProps) => { 10 | return ( 11 |
12 | 13 |
14 | ); 15 | }; 16 | -------------------------------------------------------------------------------- /application/src/components/schedule-incident/IncidentManagementTab.tsx: -------------------------------------------------------------------------------- 1 | 2 | import React from 'react'; 3 | import { IncidentManagementContainer } from './incident-management'; 4 | 5 | interface IncidentManagementTabProps { 6 | refreshTrigger?: number; 7 | } 8 | 9 | export const IncidentManagementTab = React.memo(({ refreshTrigger = 0 }: IncidentManagementTabProps) => { 10 | return ; 11 | }); 12 | 13 | IncidentManagementTab.displayName = 'IncidentManagementTab'; 14 | -------------------------------------------------------------------------------- /application/src/components/schedule-incident/hooks/index.ts: -------------------------------------------------------------------------------- 1 | 2 | export * from './useMaintenanceData'; 3 | export * from './useIncidentData'; 4 | -------------------------------------------------------------------------------- /application/src/components/schedule-incident/incident-management/ErrorState.tsx: -------------------------------------------------------------------------------- 1 | 2 | import React from 'react'; 3 | import { useLanguage } from '@/contexts/LanguageContext'; 4 | import { Button } from '@/components/ui/button'; 5 | import { RefreshCw } from 'lucide-react'; 6 | 7 | interface ErrorStateProps { 8 | error: string | null; 9 | onRefresh: () => void; 10 | isRefreshing: boolean; 11 | } 12 | 13 | export const ErrorState: React.FC = ({ 14 | error, 15 | onRefresh, 16 | isRefreshing 17 | }) => { 18 | const { t } = useLanguage(); 19 | 20 | if (!error) return null; 21 | 22 | return ( 23 |
24 |

{error}

25 | 35 |
36 | ); 37 | }; 38 | -------------------------------------------------------------------------------- /application/src/components/schedule-incident/incident-management/HeaderActions.tsx: -------------------------------------------------------------------------------- 1 | 2 | import React from 'react'; 3 | import { CardDescription, CardTitle } from '@/components/ui/card'; 4 | import { Button } from '@/components/ui/button'; 5 | import { RefreshCw } from 'lucide-react'; 6 | import { useLanguage } from '@/contexts/LanguageContext'; 7 | 8 | interface HeaderActionsProps { 9 | onRefresh: () => void; 10 | isRefreshing: boolean; 11 | } 12 | 13 | export const HeaderActions: React.FC = ({ onRefresh, isRefreshing }) => { 14 | const { t } = useLanguage(); 15 | 16 | return ( 17 |
18 |
19 | {t('incidentManagement')} 20 | 21 | {t('incidentsManagementDesc')} 22 | 23 |
24 | 35 |
36 | ); 37 | }; 38 | -------------------------------------------------------------------------------- /application/src/components/schedule-incident/incident-management/TabContent.tsx: -------------------------------------------------------------------------------- 1 | 2 | import React from 'react'; 3 | import { IncidentTable } from '../incident/table/IncidentTable'; 4 | import { EmptyIncidentState } from '../incident/EmptyIncidentState'; 5 | import { ErrorState } from './ErrorState'; 6 | import { IncidentItem } from '@/services/incident'; 7 | 8 | interface TabContentProps { 9 | error: string | null; 10 | isEmpty: boolean; 11 | data: IncidentItem[]; 12 | loading: boolean; 13 | initialized: boolean; 14 | isRefreshing: boolean; 15 | onIncidentUpdated: () => void; 16 | onViewDetails: (incident: IncidentItem) => void; 17 | onRefresh: () => void; 18 | } 19 | 20 | export const TabContent: React.FC = ({ 21 | error, 22 | isEmpty, 23 | data, 24 | loading, 25 | initialized, 26 | isRefreshing, 27 | onIncidentUpdated, 28 | onViewDetails, 29 | onRefresh 30 | }) => { 31 | if (error) { 32 | return ; 33 | } 34 | 35 | if (isEmpty) { 36 | return ; 37 | } 38 | 39 | return ( 40 | 46 | ); 47 | }; 48 | -------------------------------------------------------------------------------- /application/src/components/schedule-incident/incident-management/index.ts: -------------------------------------------------------------------------------- 1 | 2 | export * from './IncidentManagementContainer'; 3 | export * from './OverviewCards'; 4 | export * from './TabContent'; 5 | export * from './ErrorState'; 6 | export * from './HeaderActions'; 7 | -------------------------------------------------------------------------------- /application/src/components/schedule-incident/incident/EmptyIncidentState.tsx: -------------------------------------------------------------------------------- 1 | 2 | import React from 'react'; 3 | import { useLanguage } from '@/contexts/LanguageContext'; 4 | import { AlertCircle } from 'lucide-react'; 5 | 6 | export const EmptyIncidentState = () => { 7 | const { t } = useLanguage(); 8 | 9 | return ( 10 |
11 | 12 |

{t('noIncidents')}

13 |

14 | {t('noServices')} 15 |

16 |
17 | ); 18 | }; 19 | -------------------------------------------------------------------------------- /application/src/components/schedule-incident/incident/IncidentTable.tsx: -------------------------------------------------------------------------------- 1 | 2 | // Re-export the refactored IncidentTable component 3 | export { IncidentTable } from './table/IncidentTable'; 4 | -------------------------------------------------------------------------------- /application/src/components/schedule-incident/incident/detail-dialog/IncidentDetailDialog.tsx: -------------------------------------------------------------------------------- 1 | 2 | import React from 'react'; 3 | import { Dialog, DialogContent } from '@/components/ui/dialog'; 4 | import { IncidentItem } from '@/services/incident/types'; 5 | import { useQuery } from '@tanstack/react-query'; 6 | import { userService } from '@/services/userService'; 7 | import { IncidentDetailContent } from './IncidentDetailContent'; 8 | 9 | interface IncidentDetailDialogProps { 10 | open: boolean; 11 | onOpenChange: (open: boolean) => void; 12 | incident: IncidentItem | null; 13 | } 14 | 15 | export const IncidentDetailDialog = ({ 16 | open, 17 | onOpenChange, 18 | incident 19 | }: IncidentDetailDialogProps) => { 20 | // Fetch user data for assigned_to field 21 | const { data: users = [] } = useQuery({ 22 | queryKey: ['users'], 23 | queryFn: async () => { 24 | const usersList = await userService.getUsers(); 25 | return Array.isArray(usersList) ? usersList : []; 26 | }, 27 | staleTime: 300000, // Cache for 5 minutes 28 | enabled: !!incident?.assigned_to && open // Only run query if there's an assigned_to value and dialog is open 29 | }); 30 | 31 | // Find the assigned user 32 | const assignedUser = incident?.assigned_to 33 | ? users.find(user => user.id === incident.assigned_to) 34 | : null; 35 | 36 | if (!incident) return null; 37 | 38 | return ( 39 | 40 | 46 | onOpenChange(false)} 49 | assignedUser={assignedUser} 50 | /> 51 | 52 | 53 | ); 54 | }; 55 | -------------------------------------------------------------------------------- /application/src/components/schedule-incident/incident/detail-dialog/IncidentDetailFooter.tsx: -------------------------------------------------------------------------------- 1 | 2 | import React from 'react'; 3 | import { IncidentItem } from '@/services/incident'; 4 | import { Separator } from '@/components/ui/separator'; 5 | import { CloseButton, DownloadPdfButton, PrintButton } from './components'; 6 | import { useLanguage } from '@/contexts/LanguageContext'; 7 | 8 | interface IncidentDetailFooterProps { 9 | onClose: () => void; 10 | incident: IncidentItem; 11 | } 12 | 13 | export const IncidentDetailFooter: React.FC = ({ 14 | onClose, 15 | incident, 16 | }) => { 17 | const { t } = useLanguage(); 18 | 19 | return ( 20 |
21 | 22 |
23 | 24 |
25 | 26 | 27 |
28 |
29 |
30 | ); 31 | }; 32 | -------------------------------------------------------------------------------- /application/src/components/schedule-incident/incident/detail-dialog/IncidentDetailHeader.tsx: -------------------------------------------------------------------------------- 1 | 2 | import React from 'react'; 3 | import { DialogHeader, DialogTitle } from '@/components/ui/dialog'; 4 | import { IncidentItem } from '@/services/incident/types'; 5 | 6 | interface IncidentDetailHeaderProps { 7 | incident: IncidentItem; 8 | } 9 | 10 | export const IncidentDetailHeader = ({ incident }: IncidentDetailHeaderProps) => { 11 | return ( 12 | 13 |
14 | {incident.title || 'Incident Details'} 15 | #{incident.id} 16 |
17 |
18 | ); 19 | }; 20 | -------------------------------------------------------------------------------- /application/src/components/schedule-incident/incident/detail-dialog/IncidentDetailSections.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { useLanguage } from '@/contexts/LanguageContext'; 3 | import { IncidentItem } from '@/services/incident'; 4 | import { Separator } from '@/components/ui/separator'; 5 | import { useQuery } from '@tanstack/react-query'; 6 | import { userService } from '@/services/userService'; 7 | 8 | // Import all section components from the new location 9 | import { 10 | BasicInfoSection, 11 | TimelineSection, 12 | AffectedSystemsSection, 13 | ResolutionSection 14 | } from './sections'; 15 | 16 | // Re-export all section components for compatibility with existing imports 17 | export { 18 | BasicInfoSection, 19 | TimelineSection, 20 | AffectedSystemsSection, 21 | ResolutionSection 22 | }; 23 | 24 | // Legacy component - keeping this for backward compatibility with other imports 25 | export const IncidentDetailSections = ({ incident }: { incident: IncidentItem | null }) => { 26 | // Fetch assigned user details if there's an assigned_to field 27 | const { data: assignedUser } = useQuery({ 28 | queryKey: ['user', incident?.assigned_to], 29 | queryFn: async () => { 30 | if (!incident?.assigned_to) return null; 31 | try { 32 | return await userService.getUser(incident.assigned_to); 33 | } catch (error) { 34 | console.error("Failed to fetch assigned user:", error); 35 | return null; 36 | } 37 | }, 38 | enabled: !!incident?.assigned_to, 39 | staleTime: 300000 // Cache for 5 minutes 40 | }); 41 | 42 | if (!incident) return null; 43 | 44 | return ( 45 |
46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 |
54 | ); 55 | }; 56 | -------------------------------------------------------------------------------- /application/src/components/schedule-incident/incident/detail-dialog/components/CloseButton.tsx: -------------------------------------------------------------------------------- 1 | 2 | import React from 'react'; 3 | import { Button } from '@/components/ui/button'; 4 | import { useLanguage } from '@/contexts/LanguageContext'; 5 | 6 | interface CloseButtonProps { 7 | onClose: () => void; 8 | className?: string; 9 | } 10 | 11 | export const CloseButton: React.FC = ({ 12 | onClose, 13 | className 14 | }) => { 15 | const { t } = useLanguage(); 16 | 17 | return ( 18 | 25 | ); 26 | }; 27 | -------------------------------------------------------------------------------- /application/src/components/schedule-incident/incident/detail-dialog/components/DownloadPdfButton.tsx: -------------------------------------------------------------------------------- 1 | 2 | import React from 'react'; 3 | import { Button } from '@/components/ui/button'; 4 | import { Download } from 'lucide-react'; 5 | import { useLanguage } from '@/contexts/LanguageContext'; 6 | import { useDownloadIncidentPdf } from '../hooks'; 7 | import { IncidentItem } from '@/services/incident'; 8 | 9 | interface DownloadPdfButtonProps { 10 | incident: IncidentItem; 11 | className?: string; 12 | } 13 | 14 | export const DownloadPdfButton: React.FC = ({ 15 | incident, 16 | className 17 | }) => { 18 | const { t } = useLanguage(); 19 | const { handleDownloadPDF } = useDownloadIncidentPdf(); 20 | 21 | return ( 22 | 30 | ); 31 | }; 32 | -------------------------------------------------------------------------------- /application/src/components/schedule-incident/incident/detail-dialog/components/PrintButton.tsx: -------------------------------------------------------------------------------- 1 | 2 | import React from 'react'; 3 | import { Button } from '@/components/ui/button'; 4 | import { Printer } from 'lucide-react'; 5 | import { useLanguage } from '@/contexts/LanguageContext'; 6 | import { usePrintIncident } from '../hooks'; 7 | 8 | interface PrintButtonProps { 9 | className?: string; 10 | } 11 | 12 | export const PrintButton: React.FC = ({ className }) => { 13 | const { t } = useLanguage(); 14 | const { handlePrint } = usePrintIncident(); 15 | 16 | return ( 17 | 25 | ); 26 | }; 27 | -------------------------------------------------------------------------------- /application/src/components/schedule-incident/incident/detail-dialog/components/index.ts: -------------------------------------------------------------------------------- 1 | 2 | export * from './PrintButton'; 3 | export * from './DownloadPdfButton'; 4 | export * from './CloseButton'; 5 | -------------------------------------------------------------------------------- /application/src/components/schedule-incident/incident/detail-dialog/hooks/index.ts: -------------------------------------------------------------------------------- 1 | 2 | export * from './usePrintIncident'; 3 | export * from './useDownloadIncidentPdf'; 4 | -------------------------------------------------------------------------------- /application/src/components/schedule-incident/incident/detail-dialog/hooks/useDownloadIncidentPdf.ts: -------------------------------------------------------------------------------- 1 | 2 | import { useToast } from '@/hooks/use-toast'; 3 | import { useLanguage } from '@/contexts/LanguageContext'; 4 | import { IncidentItem, incidentService } from '@/services/incident'; 5 | 6 | export const useDownloadIncidentPdf = () => { 7 | const { toast } = useToast(); 8 | const { t } = useLanguage(); 9 | 10 | const handleDownloadPDF = async (incident: IncidentItem) => { 11 | try { 12 | await incidentService.generateIncidentPDF(incident); 13 | 14 | toast({ 15 | title: t('success'), 16 | description: t('pdfDownloaded'), 17 | }); 18 | } catch (error) { 19 | console.error("Error generating PDF:", error); 20 | toast({ 21 | title: t('error'), 22 | description: t('pdfGenerationFailed'), 23 | variant: 'destructive', 24 | }); 25 | } 26 | }; 27 | 28 | return { handleDownloadPDF }; 29 | }; 30 | -------------------------------------------------------------------------------- /application/src/components/schedule-incident/incident/detail-dialog/index.ts: -------------------------------------------------------------------------------- 1 | 2 | export * from './IncidentDetailDialog'; 3 | export * from './IncidentDetailHeader'; 4 | export * from './IncidentDetailContent'; 5 | export * from './IncidentDetailSections'; 6 | export * from './IncidentDetailFooter'; 7 | -------------------------------------------------------------------------------- /application/src/components/schedule-incident/incident/detail-dialog/sections/AssignmentSection.tsx: -------------------------------------------------------------------------------- 1 | 2 | import React from 'react'; 3 | import { useLanguage } from '@/contexts/LanguageContext'; 4 | import { IncidentItem } from '@/services/incident'; 5 | import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'; 6 | import { User } from '@/services/userService'; 7 | import { getUserInitials } from './utils'; 8 | 9 | interface AssignmentSectionProps { 10 | incident: IncidentItem | null; 11 | assignedUser?: User | null; 12 | } 13 | 14 | export const AssignmentSection: React.FC = ({ incident, assignedUser }) => { 15 | const { t } = useLanguage(); 16 | 17 | if (!incident) return null; 18 | 19 | return ( 20 |
21 |

{t('assignment')}

22 | 23 |
24 |

{t('assignedTo')}

25 |
26 | {assignedUser ? ( 27 |
28 | 29 | 30 | {getUserInitials(assignedUser)} 31 | 32 | {assignedUser.full_name || assignedUser.username} 33 |
34 | ) : incident.assigned_to ? ( 35 | {incident.assigned_to} 36 | ) : ( 37 | {t('unassigned')} 38 | )} 39 |
40 |
41 |
42 | ); 43 | }; 44 | 45 | -------------------------------------------------------------------------------- /application/src/components/schedule-incident/incident/detail-dialog/sections/ImpactAnalysisSection.tsx: -------------------------------------------------------------------------------- 1 | 2 | import React from 'react'; 3 | import { useLanguage } from '@/contexts/LanguageContext'; 4 | import { IncidentItem } from '@/services/incident'; 5 | import { Badge } from '@/components/ui/badge'; 6 | 7 | interface ImpactAnalysisSectionProps { 8 | incident: IncidentItem | null; 9 | assignedUser?: any | null; 10 | } 11 | 12 | export const ImpactAnalysisSection: React.FC = ({ incident }) => { 13 | const { t } = useLanguage(); 14 | 15 | if (!incident) return null; 16 | 17 | return ( 18 |
19 |

{t('impactAnalysis')}

20 | 21 |
22 |
23 |

{t('impact')}

24 |
25 | 30 | {t(incident.impact?.toLowerCase() || 'low')} 31 | 32 |
33 |
34 | 35 |
36 |

{t('priority')}

37 |
38 | 43 | {t(incident.priority?.toLowerCase() || 'low')} 44 | 45 |
46 |
47 |
48 |
49 | ); 50 | }; 51 | 52 | -------------------------------------------------------------------------------- /application/src/components/schedule-incident/incident/detail-dialog/sections/ResolutionSection.tsx: -------------------------------------------------------------------------------- 1 | 2 | import React from 'react'; 3 | import { useLanguage } from '@/contexts/LanguageContext'; 4 | import { IncidentItem } from '@/services/incident'; 5 | 6 | interface ResolutionSectionProps { 7 | incident: IncidentItem | null; 8 | assignedUser?: any | null; 9 | } 10 | 11 | export const ResolutionSection: React.FC = ({ incident }) => { 12 | const { t } = useLanguage(); 13 | 14 | if (!incident) return null; 15 | 16 | return ( 17 |
18 |

{t('resolutionDetails')}

19 | 20 |
21 |
22 |

{t('rootCause')}

23 |

{incident.root_cause || '-'}

24 |
25 | 26 |
27 |

{t('resolutionSteps')}

28 |

{incident.resolution_steps || '-'}

29 |
30 | 31 |
32 |

{t('lessonsLearned')}

33 |

{incident.lessons_learned || '-'}

34 |
35 |
36 |
37 | ); 38 | }; 39 | 40 | -------------------------------------------------------------------------------- /application/src/components/schedule-incident/incident/detail-dialog/sections/TimelineSection.tsx: -------------------------------------------------------------------------------- 1 | 2 | import React from 'react'; 3 | import { useLanguage } from '@/contexts/LanguageContext'; 4 | import { IncidentItem } from '@/services/incident'; 5 | import { formatDate } from './utils'; 6 | 7 | interface TimelineSectionProps { 8 | incident: IncidentItem | null; 9 | assignedUser?: any | null; 10 | } 11 | 12 | export const TimelineSection: React.FC = ({ incident }) => { 13 | const { t } = useLanguage(); 14 | 15 | if (!incident) return null; 16 | 17 | return ( 18 |
19 |

{t('timeline')}

20 | 21 |
22 |
23 |

{t('created')}

24 |

{formatDate(incident.created)}

25 |
26 | 27 |
28 |

{t('lastUpdated')}

29 |

{formatDate(incident.updated)}

30 |
31 | 32 |
33 |

{t('incidentTime')}

34 |

{formatDate(incident.timestamp)}

35 |
36 | 37 |
38 |

{t('resolutionTime')}

39 |

{formatDate(incident.resolution_time)}

40 |
41 |
42 |
43 | ); 44 | }; 45 | 46 | -------------------------------------------------------------------------------- /application/src/components/schedule-incident/incident/detail-dialog/sections/index.ts: -------------------------------------------------------------------------------- 1 | 2 | // Export all section components 3 | export * from './BasicInfoSection'; 4 | export * from './TimelineSection'; 5 | export * from './AffectedSystemsSection'; 6 | export * from './ResolutionSection'; 7 | export * from './utils'; 8 | -------------------------------------------------------------------------------- /application/src/components/schedule-incident/incident/detail-dialog/sections/utils.ts: -------------------------------------------------------------------------------- 1 | 2 | import { User } from '@/services/userService'; 3 | import { format } from 'date-fns'; 4 | 5 | // Helper function to get user initials 6 | export const getUserInitials = (user: User): string => { 7 | if (user.full_name) { 8 | const nameParts = user.full_name.split(' '); 9 | if (nameParts.length > 1) { 10 | return `${nameParts[0][0]}${nameParts[1][0]}`.toUpperCase(); 11 | } 12 | return user.full_name.substring(0, 2).toUpperCase(); 13 | } 14 | return user.username.substring(0, 2).toUpperCase(); 15 | }; 16 | 17 | // Format date helper 18 | export const formatDate = (dateStr?: string) => { 19 | if (!dateStr) return '-'; 20 | try { 21 | return format(new Date(dateStr), 'PPp'); 22 | } catch (error) { 23 | console.error('Error formatting date:', dateStr, error); 24 | return dateStr; 25 | } 26 | }; 27 | 28 | // Get affected systems helper 29 | export const getAffectedSystemsArray = (systems?: string) => { 30 | if (!systems) return []; 31 | return systems.split(',').map(system => system.trim()).filter(Boolean); 32 | }; 33 | -------------------------------------------------------------------------------- /application/src/components/schedule-incident/incident/form/IncidentAffectedFields.tsx: -------------------------------------------------------------------------------- 1 | 2 | import React from 'react'; 3 | import { useFormContext } from 'react-hook-form'; 4 | import { useLanguage } from '@/contexts/LanguageContext'; 5 | import { IncidentFormValues } from '../hooks/useIncidentForm'; 6 | import { 7 | FormField, 8 | FormItem, 9 | FormLabel, 10 | FormControl, 11 | FormMessage, 12 | } from '@/components/ui/form'; 13 | import { Input } from '@/components/ui/input'; 14 | import { Textarea } from '@/components/ui/textarea'; 15 | 16 | export const IncidentAffectedFields: React.FC = () => { 17 | const { t } = useLanguage(); 18 | const { control } = useFormContext(); 19 | 20 | return ( 21 |
22 | ( 26 | 27 | {t('affectedSystems')} 28 | 29 | 30 | 31 | 32 |

{t('separateSystemsWithComma')}

33 |
34 | )} 35 | /> 36 | 37 | ( 41 | 42 | {t('rootCause')} 43 | 44 |