├── .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 |
--------------------------------------------------------------------------------
/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 |
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 |
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 |
55 | );
56 | };
57 |
--------------------------------------------------------------------------------
/application/src/components/schedule-incident/incident/form/IncidentDetailsFields.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 { Textarea } from '@/components/ui/textarea';
14 |
15 | export const IncidentDetailsFields: React.FC = () => {
16 | const { t } = useLanguage();
17 | const { control } = useFormContext();
18 |
19 | return (
20 |
21 | (
25 |
26 | {t('resolutionSteps')}
27 |
28 |
33 |
34 |
35 |
36 | )}
37 | />
38 |
39 | (
43 |
44 | {t('lessonsLearned')}
45 |
46 |
51 |
52 |
53 |
54 | )}
55 | />
56 |
57 | );
58 | };
59 |
60 |
--------------------------------------------------------------------------------
/application/src/components/schedule-incident/incident/form/index.ts:
--------------------------------------------------------------------------------
1 |
2 | export * from './IncidentBasicFields';
3 | export * from './IncidentAffectedFields';
4 | export * from './IncidentConfigFields';
5 | export * from './IncidentDetailsFields';
6 |
--------------------------------------------------------------------------------
/application/src/components/schedule-incident/incident/index.ts:
--------------------------------------------------------------------------------
1 |
2 | export * from './IncidentTable';
3 | export * from './IncidentActionsMenu';
4 | export * from './IncidentStatusBadge';
5 | export * from './IncidentStatusDropdown';
6 | export * from './CreateIncidentDialog';
7 | export * from './EditIncidentDialog';
8 | export * from './EmptyIncidentState';
9 | export * from './detail-dialog';
10 |
--------------------------------------------------------------------------------
/application/src/components/schedule-incident/incident/table/IncidentTableSkeleton.tsx:
--------------------------------------------------------------------------------
1 |
2 | import React from 'react';
3 | import { TableRow, TableCell } from '@/components/ui/table';
4 | import { Skeleton } from '@/components/ui/skeleton';
5 |
6 | export const IncidentTableRowSkeleton = () => (
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 | );
18 |
19 | export const IncidentTableSkeleton = () => (
20 |
21 |
22 | {Array(3).fill(0).map((_, index) => (
23 |
24 | ))}
25 |
26 |
27 | );
28 |
--------------------------------------------------------------------------------
/application/src/components/schedule-incident/incident/table/IncidentTableUtils.tsx:
--------------------------------------------------------------------------------
1 |
2 | import React from 'react';
3 | import { useQuery } from '@tanstack/react-query';
4 | import { userService, User } from '@/services/userService';
5 | import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';
6 | import { Skeleton } from '@/components/ui/skeleton';
7 |
8 | // Helper function to get user initials
9 | export const getUserInitials = (user: User): string => {
10 | if (user.full_name) {
11 | const nameParts = user.full_name.split(' ');
12 | if (nameParts.length > 1) {
13 | return `${nameParts[0][0]}${nameParts[1][0]}`.toUpperCase();
14 | }
15 | return user.full_name.substring(0, 2).toUpperCase();
16 | }
17 | return user.username.substring(0, 2).toUpperCase();
18 | };
19 |
20 | interface AssignedUserCellProps {
21 | userId?: string;
22 | }
23 |
24 | export const AssignedUserCell: React.FC = ({ userId }) => {
25 | const { data: user, isLoading } = useQuery({
26 | queryKey: ['user', userId],
27 | queryFn: async () => {
28 | if (!userId) return null;
29 | try {
30 | return await userService.getUser(userId);
31 | } catch (error) {
32 | console.error("Failed to fetch assigned user:", error);
33 | return null;
34 | }
35 | },
36 | enabled: !!userId,
37 | staleTime: 300000 // Cache for 5 minutes
38 | });
39 |
40 | if (isLoading) {
41 | return ;
42 | }
43 |
44 | if (!user || !userId) {
45 | return -;
46 | }
47 |
48 | return (
49 |
50 |
51 |
52 | {getUserInitials(user)}
53 |
54 |
{user.full_name || user.username}
55 |
56 | );
57 | };
58 |
--------------------------------------------------------------------------------
/application/src/components/schedule-incident/incident/table/index.ts:
--------------------------------------------------------------------------------
1 |
2 | export * from './IncidentTable';
3 | export * from './IncidentTableRow';
4 | export * from './IncidentTableSkeleton';
5 | export * from './IncidentTableUtils';
6 |
--------------------------------------------------------------------------------
/application/src/components/schedule-incident/index.ts:
--------------------------------------------------------------------------------
1 |
2 | // Export all components for easier imports
3 | export * from './ScheduleIncidentContent';
4 | export * from './ScheduledMaintenanceTab';
5 | export * from './IncidentManagementTab';
6 | export * from './maintenance/MaintenanceTable';
7 | export * from './maintenance/MaintenanceStatusBadge';
8 | export * from './maintenance/MaintenanceActionsMenu';
9 | export * from './maintenance/EmptyMaintenanceState';
10 | export * from './maintenance/CreateMaintenanceDialog';
11 | export * from './incident/IncidentTable';
12 | export * from './incident/IncidentStatusBadge';
13 | export * from './incident/IncidentActionsMenu';
14 | export * from './incident/EmptyIncidentState';
15 | export * from './incident/CreateIncidentDialog';
16 | export * from './incident-management';
17 | export * from './hooks';
18 |
19 |
--------------------------------------------------------------------------------
/application/src/components/schedule-incident/maintenance/EmptyMaintenanceState.tsx:
--------------------------------------------------------------------------------
1 |
2 | import React from 'react';
3 | import { useLanguage } from '@/contexts/LanguageContext';
4 | import { CalendarClock } from 'lucide-react';
5 |
6 | export const EmptyMaintenanceState = () => {
7 | const { t } = useLanguage();
8 |
9 | return (
10 |
11 |
12 |
{t('noScheduledMaintenance')}
13 |
14 | {t('noMaintenanceWindows')}
15 |
16 |
17 | );
18 | };
19 |
--------------------------------------------------------------------------------
/application/src/components/schedule-incident/maintenance/detail-dialog/MaintenanceDetailDialog.tsx:
--------------------------------------------------------------------------------
1 |
2 | import React from 'react';
3 | import {
4 | Dialog,
5 | DialogContent
6 | } from '@/components/ui/dialog';
7 | import { MaintenanceItem } from '@/services/types/maintenance.types';
8 | import { MaintenanceDetailContent } from './MaintenanceDetailContent';
9 |
10 | interface MaintenanceDetailDialogProps {
11 | open: boolean;
12 | onOpenChange: (open: boolean) => void;
13 | maintenance: MaintenanceItem | null;
14 | }
15 |
16 | export const MaintenanceDetailDialog = ({
17 | open,
18 | onOpenChange,
19 | maintenance
20 | }: MaintenanceDetailDialogProps) => {
21 | if (!maintenance) {
22 | return null;
23 | }
24 |
25 | return (
26 |
36 | );
37 | };
38 |
--------------------------------------------------------------------------------
/application/src/components/schedule-incident/maintenance/detail-dialog/MaintenanceDetailFooter.tsx:
--------------------------------------------------------------------------------
1 |
2 | import React from 'react';
3 | import { DialogFooter } from '@/components/ui/dialog';
4 | import { MaintenanceItem } from '@/services/types/maintenance.types';
5 | import {
6 | PrintButton,
7 | DownloadPdfButton,
8 | CloseButton
9 | } from './components';
10 |
11 | interface MaintenanceDetailFooterProps {
12 | maintenance: MaintenanceItem;
13 | onClose: () => void;
14 | }
15 |
16 | export const MaintenanceDetailFooter = ({
17 | maintenance,
18 | onClose
19 | }: MaintenanceDetailFooterProps) => {
20 | return (
21 |
22 |
23 |
24 |
25 |
26 | );
27 | };
28 |
--------------------------------------------------------------------------------
/application/src/components/schedule-incident/maintenance/detail-dialog/components/CloseButton.tsx:
--------------------------------------------------------------------------------
1 |
2 | import React from 'react';
3 | import { useLanguage } from '@/contexts/LanguageContext';
4 | import { Button } from '@/components/ui/button';
5 |
6 | interface CloseButtonProps {
7 | onClose: () => void;
8 | }
9 |
10 | export const CloseButton: React.FC = ({ onClose }) => {
11 | const { t } = useLanguage();
12 |
13 | return (
14 |
17 | );
18 | };
19 |
--------------------------------------------------------------------------------
/application/src/components/schedule-incident/maintenance/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 { useDownloadMaintenancePdf } from '../hooks';
7 | import { MaintenanceItem } from '@/services/maintenance';
8 |
9 | interface DownloadPdfButtonProps {
10 | maintenance: MaintenanceItem;
11 | className?: string;
12 | }
13 |
14 | export const DownloadPdfButton: React.FC = ({
15 | maintenance,
16 | className
17 | }) => {
18 | const { t } = useLanguage();
19 | const { handleDownloadPDF } = useDownloadMaintenancePdf();
20 |
21 | return (
22 |
30 | );
31 | };
32 |
--------------------------------------------------------------------------------
/application/src/components/schedule-incident/maintenance/detail-dialog/components/PrintButton.tsx:
--------------------------------------------------------------------------------
1 |
2 | import React from 'react';
3 | import { useLanguage } from '@/contexts/LanguageContext';
4 | import { Printer } from 'lucide-react';
5 | import { Button } from '@/components/ui/button';
6 | import { usePrintMaintenance } from '../hooks/usePrintMaintenance';
7 | import { MaintenanceItem } from '@/services/types/maintenance.types';
8 |
9 | interface PrintButtonProps {
10 | maintenance: MaintenanceItem;
11 | }
12 |
13 | export const PrintButton: React.FC = ({ maintenance }) => {
14 | const { t } = useLanguage();
15 | const { handlePrint } = usePrintMaintenance();
16 |
17 | return (
18 |
26 | );
27 | };
28 |
--------------------------------------------------------------------------------
/application/src/components/schedule-incident/maintenance/detail-dialog/components/index.ts:
--------------------------------------------------------------------------------
1 |
2 | export * from './DownloadPdfButton';
3 | export * from './PrintButton';
4 | export * from './CloseButton';
5 |
--------------------------------------------------------------------------------
/application/src/components/schedule-incident/maintenance/detail-dialog/hooks/index.ts:
--------------------------------------------------------------------------------
1 |
2 | export * from './useDownloadMaintenancePdf';
3 | export * from './usePrintMaintenance';
4 |
--------------------------------------------------------------------------------
/application/src/components/schedule-incident/maintenance/detail-dialog/hooks/useDownloadMaintenancePdf.ts:
--------------------------------------------------------------------------------
1 |
2 | import { useToast } from '@/hooks/use-toast';
3 | import { useLanguage } from '@/contexts/LanguageContext';
4 | import { MaintenanceItem, generateMaintenancePDF } from '@/services/maintenance';
5 |
6 | export const useDownloadMaintenancePdf = () => {
7 | const { toast } = useToast();
8 | const { t } = useLanguage();
9 |
10 | const handleDownloadPDF = async (maintenance: MaintenanceItem) => {
11 | try {
12 | // Show loading toast
13 | toast({
14 | title: t('generating'),
15 | description: t('preparingPdf'),
16 | });
17 |
18 | // Generate and download the PDF
19 | const filename = await generateMaintenancePDF(maintenance);
20 |
21 | // Show success toast after successful generation
22 | toast({
23 | title: t('success'),
24 | description: t('pdfDownloaded'),
25 | });
26 |
27 | return filename;
28 | } catch (error) {
29 | console.error("Error generating PDF:", error);
30 |
31 | // Provide more detailed error message if available
32 | const errorMessage = error instanceof Error
33 | ? error.message
34 | : t('pdfGenerationFailed');
35 |
36 | toast({
37 | title: t('error'),
38 | description: errorMessage,
39 | variant: 'destructive',
40 | });
41 |
42 | throw error;
43 | }
44 | };
45 |
46 | return { handleDownloadPDF };
47 | };
48 |
--------------------------------------------------------------------------------
/application/src/components/schedule-incident/maintenance/detail-dialog/index.ts:
--------------------------------------------------------------------------------
1 |
2 | export * from './MaintenanceDetailDialog';
3 | export * from './MaintenanceDetailContent';
4 | export * from './MaintenanceDetailHeader';
5 | export * from './MaintenanceDetailSections';
6 | export * from './MaintenanceDetailFooter';
7 | export * from './components';
8 | export * from './hooks';
9 |
--------------------------------------------------------------------------------
/application/src/components/schedule-incident/maintenance/edit-dialog/index.ts:
--------------------------------------------------------------------------------
1 |
2 | export * from './EditMaintenanceDialog';
3 |
--------------------------------------------------------------------------------
/application/src/components/schedule-incident/maintenance/form/MaintenanceAffectedFields.tsx:
--------------------------------------------------------------------------------
1 |
2 | import React from 'react';
3 | import { useFormContext } from 'react-hook-form';
4 | import { useLanguage } from '@/contexts/LanguageContext';
5 | import { MaintenanceFormValues } from '../hooks/useMaintenanceForm';
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 |
15 | export const MaintenanceAffectedFields: React.FC = () => {
16 | const { t } = useLanguage();
17 | const { control } = useFormContext();
18 |
19 | return (
20 | (
24 |
25 | {t('affectedServices')}
26 |
27 |
28 |
29 |
30 | {t('separateServicesWithComma')}
31 |
32 | )}
33 | />
34 | );
35 | };
36 |
--------------------------------------------------------------------------------
/application/src/components/schedule-incident/maintenance/form/MaintenanceBasicFields.tsx:
--------------------------------------------------------------------------------
1 |
2 | import React from 'react';
3 | import { useFormContext } from 'react-hook-form';
4 | import { useLanguage } from '@/contexts/LanguageContext';
5 | import { MaintenanceFormValues } from '../hooks/useMaintenanceForm';
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 MaintenanceBasicFields: React.FC = () => {
17 | const { t } = useLanguage();
18 | const { control } = useFormContext();
19 |
20 | return (
21 | <>
22 | (
26 |
27 | {t('title')}
28 |
29 |
30 |
31 |
32 |
33 | )}
34 | />
35 |
36 | (
40 |
41 | {t('description')}
42 |
43 |
48 |
49 |
50 |
51 | )}
52 | />
53 | >
54 | );
55 | };
56 |
--------------------------------------------------------------------------------
/application/src/components/schedule-incident/maintenance/form/MaintenanceConfigFields.tsx:
--------------------------------------------------------------------------------
1 |
2 | import React from 'react';
3 | import { useLanguage } from '@/contexts/LanguageContext';
4 | import {
5 | PriorityField,
6 | StatusField,
7 | ImpactLevelField,
8 | AssignedUsersField
9 | } from './config';
10 |
11 | export const MaintenanceConfigFields = () => {
12 | const { t } = useLanguage();
13 |
14 | return (
15 |
16 |
{t('configurationSettings')}
17 |
18 |
22 |
23 |
24 |
25 |
26 |
27 |
30 |
31 | );
32 | };
33 |
--------------------------------------------------------------------------------
/application/src/components/schedule-incident/maintenance/form/config/ImpactLevelField.tsx:
--------------------------------------------------------------------------------
1 |
2 | import React from 'react';
3 | import { useLanguage } from '@/contexts/LanguageContext';
4 | import { useFormContext } from 'react-hook-form';
5 | import {
6 | FormField,
7 | FormItem,
8 | FormLabel,
9 | FormControl,
10 | FormMessage,
11 | FormDescription
12 | } from '@/components/ui/form';
13 | import {
14 | Select,
15 | SelectContent,
16 | SelectItem,
17 | SelectTrigger,
18 | SelectValue
19 | } from '@/components/ui/select';
20 |
21 | export const ImpactLevelField = () => {
22 | const { t } = useLanguage();
23 | const form = useFormContext();
24 |
25 | return (
26 | (
30 |
31 | {t('impactLevel')}
32 |
48 |
49 | {t('impactLevelDescription')}
50 |
51 |
52 |
53 | )}
54 | />
55 | );
56 | };
57 |
--------------------------------------------------------------------------------
/application/src/components/schedule-incident/maintenance/form/config/PriorityField.tsx:
--------------------------------------------------------------------------------
1 |
2 | import React from 'react';
3 | import { useLanguage } from '@/contexts/LanguageContext';
4 | import { useFormContext } from 'react-hook-form';
5 | import {
6 | FormField,
7 | FormItem,
8 | FormLabel,
9 | FormControl,
10 | FormMessage,
11 | FormDescription
12 | } from '@/components/ui/form';
13 | import {
14 | Select,
15 | SelectContent,
16 | SelectItem,
17 | SelectTrigger,
18 | SelectValue
19 | } from '@/components/ui/select';
20 |
21 | export const PriorityField = () => {
22 | const { t } = useLanguage();
23 | const form = useFormContext();
24 |
25 | return (
26 | (
30 |
31 | {t('priority')}
32 |
48 |
49 | {t('priorityDescription')}
50 |
51 |
52 |
53 | )}
54 | />
55 | );
56 | };
57 |
--------------------------------------------------------------------------------
/application/src/components/schedule-incident/maintenance/form/config/StatusField.tsx:
--------------------------------------------------------------------------------
1 |
2 | import React from 'react';
3 | import { useLanguage } from '@/contexts/LanguageContext';
4 | import { useFormContext } from 'react-hook-form';
5 | import {
6 | FormField,
7 | FormItem,
8 | FormLabel,
9 | FormControl,
10 | FormMessage,
11 | FormDescription
12 | } from '@/components/ui/form';
13 | import {
14 | Select,
15 | SelectContent,
16 | SelectItem,
17 | SelectTrigger,
18 | SelectValue
19 | } from '@/components/ui/select';
20 |
21 | export const StatusField = () => {
22 | const { t } = useLanguage();
23 | const form = useFormContext();
24 |
25 | return (
26 | (
30 |
31 | {t('status')}
32 |
48 |
49 | {t('statusDescription')}
50 |
51 |
52 |
53 | )}
54 | />
55 | );
56 | };
57 |
--------------------------------------------------------------------------------
/application/src/components/schedule-incident/maintenance/form/config/index.ts:
--------------------------------------------------------------------------------
1 |
2 | export * from './PriorityField';
3 | export * from './StatusField';
4 | export * from './ImpactLevelField';
5 | export * from './AssignedUsersField';
6 |
--------------------------------------------------------------------------------
/application/src/components/schedule-incident/maintenance/form/index.ts:
--------------------------------------------------------------------------------
1 |
2 | export * from './MaintenanceBasicFields';
3 | export * from './MaintenanceTimeFields';
4 | export * from './MaintenanceAffectedFields';
5 | export * from './MaintenanceConfigFields';
6 | export * from './MaintenanceNotificationSettingsField';
7 | export * from './config';
8 |
--------------------------------------------------------------------------------
/application/src/components/schedule-incident/maintenance/index.ts:
--------------------------------------------------------------------------------
1 |
2 | // Export all maintenance components
3 | export * from './MaintenanceTable';
4 | export * from './MaintenanceStatusBadge';
5 | export * from './MaintenanceActionsMenu';
6 | export * from './EmptyMaintenanceState';
7 | export * from './CreateMaintenanceDialog';
8 | export * from './MaintenanceStatusDropdown';
9 | export * from './MaintenanceStatusChecker';
10 | export * from './detail-dialog';
11 |
12 |
--------------------------------------------------------------------------------
/application/src/components/services/AddServiceDialog.tsx:
--------------------------------------------------------------------------------
1 |
2 | import {
3 | Dialog,
4 | DialogContent,
5 | DialogDescription,
6 | DialogHeader,
7 | DialogTitle,
8 | } from "@/components/ui/dialog";
9 | import { ServiceForm } from "./ServiceForm";
10 | import { ScrollArea } from "@/components/ui/scroll-area";
11 |
12 | interface AddServiceDialogProps {
13 | open: boolean;
14 | onOpenChange: (open: boolean) => void;
15 | }
16 |
17 | export function AddServiceDialog({ open, onOpenChange }: AddServiceDialogProps) {
18 | const handleSuccess = () => {
19 | onOpenChange(false);
20 | };
21 |
22 | const handleCancel = () => {
23 | onOpenChange(false);
24 | };
25 |
26 | return (
27 |
42 | );
43 | }
--------------------------------------------------------------------------------
/application/src/components/services/LoadingState.tsx:
--------------------------------------------------------------------------------
1 |
2 | export function LoadingState() {
3 | return (
4 |
5 |
6 |
10 |
Loading server data
11 |
Please wait while we retrieve your information...
12 |
13 |
14 | );
15 | }
16 |
--------------------------------------------------------------------------------
/application/src/components/services/ServiceDetailContainer.tsx:
--------------------------------------------------------------------------------
1 |
2 | import { ServiceDetailContainer as NewServiceDetailContainer } from "./ServiceDetailContainer/index";
3 |
4 | export const ServiceDetailContainer = () => {
5 | return ;
6 | };
7 |
--------------------------------------------------------------------------------
/application/src/components/services/ServiceDetailContainer/ServiceDetailWrapper.tsx:
--------------------------------------------------------------------------------
1 |
2 | import React from "react";
3 | import { Sidebar } from "@/components/dashboard/Sidebar";
4 | import { Header } from "@/components/dashboard/Header";
5 | import { LoadingState } from "@/components/services/LoadingState";
6 | import { ServiceNotFound } from "@/components/services/ServiceNotFound";
7 | import { Service } from "@/types/service.types";
8 |
9 | interface ServiceDetailWrapperProps {
10 | children: React.ReactNode;
11 | isLoading: boolean;
12 | service: Service | null;
13 | sidebarCollapsed: boolean;
14 | toggleSidebar: () => void;
15 | currentUser: any;
16 | handleLogout: () => void;
17 | }
18 |
19 | export const ServiceDetailWrapper = ({
20 | children,
21 | isLoading,
22 | service,
23 | sidebarCollapsed,
24 | toggleSidebar,
25 | currentUser,
26 | handleLogout
27 | }: ServiceDetailWrapperProps) => {
28 | return (
29 |
30 |
31 |
32 |
38 |
39 | {isLoading ? (
40 |
41 | ) : !service ? (
42 |
43 | ) : (
44 |
45 | {children}
46 |
47 | )}
48 |
49 |
50 | );
51 | };
52 |
--------------------------------------------------------------------------------
/application/src/components/services/ServiceDetailContainer/hooks/index.ts:
--------------------------------------------------------------------------------
1 |
2 | export * from './useServiceData';
3 | export * from './useRealTimeUpdates';
4 |
--------------------------------------------------------------------------------
/application/src/components/services/ServiceNotFound.tsx:
--------------------------------------------------------------------------------
1 |
2 | import { AlertTriangle } from "lucide-react";
3 | import { Button } from "@/components/ui/button";
4 | import { useNavigate } from "react-router-dom";
5 |
6 | export function ServiceNotFound() {
7 | const navigate = useNavigate();
8 |
9 | return (
10 |
11 |
12 |
13 |
Service Not Found
14 |
The service you're looking for doesn't exist or has been deleted.
15 |
22 |
23 |
24 | );
25 | }
26 |
--------------------------------------------------------------------------------
/application/src/components/services/add-service/ServiceBasicFields.tsx:
--------------------------------------------------------------------------------
1 |
2 | import { FormControl, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form";
3 | import { Input } from "@/components/ui/input";
4 | import { UseFormReturn } from "react-hook-form";
5 | import { ServiceFormData } from "./types";
6 |
7 | interface ServiceBasicFieldsProps {
8 | form: UseFormReturn;
9 | }
10 |
11 | export function ServiceBasicFields({ form }: ServiceBasicFieldsProps) {
12 | return (
13 | <>
14 | (
18 |
19 | Service Name
20 |
21 |
25 |
26 |
27 |
28 | )}
29 | />
30 |
31 | (
35 |
36 | Service URL
37 |
38 | {
42 | console.log("URL field changed:", e.target.value);
43 | field.onChange(e);
44 | }}
45 | />
46 |
47 |
48 |
49 | )}
50 | />
51 | >
52 | );
53 | }
--------------------------------------------------------------------------------
/application/src/components/services/add-service/ServiceConfigFields.tsx:
--------------------------------------------------------------------------------
1 |
2 | import { FormControl, FormField, FormItem, FormLabel } from "@/components/ui/form";
3 | import { Input } from "@/components/ui/input";
4 | import { UseFormReturn } from "react-hook-form";
5 | import { ServiceFormData } from "./types";
6 |
7 | interface ServiceConfigFieldsProps {
8 | form: UseFormReturn;
9 | }
10 |
11 | export function ServiceConfigFields({ form }: ServiceConfigFieldsProps) {
12 | return (
13 | <>
14 | (
18 |
19 | Heartbeat Interval
20 |
21 |
26 |
27 |
28 | )}
29 | />
30 |
31 | (
35 |
36 | Maximum Retries
37 |
38 |
43 |
44 |
45 | )}
46 | />
47 | >
48 | );
49 | }
--------------------------------------------------------------------------------
/application/src/components/services/add-service/ServiceFormActions.tsx:
--------------------------------------------------------------------------------
1 |
2 | import { Button } from "@/components/ui/button";
3 | import { Loader2 } from "lucide-react";
4 | import { MouseEvent } from "react";
5 |
6 | interface ServiceFormActionsProps {
7 | isSubmitting: boolean;
8 | onCancel: () => void;
9 | submitLabel?: string;
10 | }
11 |
12 | export function ServiceFormActions({
13 | isSubmitting,
14 | onCancel,
15 | submitLabel = "Create Service"
16 | }: ServiceFormActionsProps) {
17 | const handleCancel = (e: MouseEvent) => {
18 | e.preventDefault();
19 | if (!isSubmitting) {
20 | onCancel();
21 | }
22 | };
23 |
24 | return (
25 |
26 |
34 |
47 |
48 | );
49 | }
--------------------------------------------------------------------------------
/application/src/components/services/add-service/index.ts:
--------------------------------------------------------------------------------
1 |
2 | export * from "./ServiceForm";
3 | export * from "./types";
4 |
--------------------------------------------------------------------------------
/application/src/components/services/add-service/types.ts:
--------------------------------------------------------------------------------
1 |
2 | import { z } from "zod";
3 |
4 | export const serviceSchema = z.object({
5 | name: z.string().min(1, "Service name is required"),
6 | type: z.string().min(1, "Service type is required"),
7 | url: z.string().min(1, "Service URL is required"),
8 | interval: z.string().min(1, "Heartbeat interval is required"),
9 | retries: z.string().min(1, "Maximum retries is required"),
10 | notificationChannel: z.string().optional(),
11 | alertTemplate: z.string().optional(),
12 | });
13 |
14 | export type ServiceFormData = z.infer;
15 |
--------------------------------------------------------------------------------
/application/src/components/services/hooks/index.ts:
--------------------------------------------------------------------------------
1 |
2 | export * from './useServiceActions';
3 | export * from './useDialogState';
4 |
--------------------------------------------------------------------------------
/application/src/components/services/hooks/useDialogState.ts:
--------------------------------------------------------------------------------
1 |
2 | import { useState } from "react";
3 |
4 | export function useDialogState() {
5 | const [isHistoryDialogOpen, setIsHistoryDialogOpen] = useState(false);
6 | const [isDeleteDialogOpen, setIsDeleteDialogOpen] = useState(false);
7 | const [isEditDialogOpen, setIsEditDialogOpen] = useState(false);
8 |
9 | const handleEditDialogChange = (open: boolean) => {
10 | setIsEditDialogOpen(open);
11 | };
12 |
13 | const handleDeleteDialogChange = (open: boolean, isDeleting: boolean = false) => {
14 | // Only allow closing if not currently deleting
15 | if (!isDeleting || !open) {
16 | setIsDeleteDialogOpen(open);
17 | }
18 | };
19 |
20 | return {
21 | isHistoryDialogOpen,
22 | isDeleteDialogOpen,
23 | isEditDialogOpen,
24 | setIsHistoryDialogOpen,
25 | setIsDeleteDialogOpen,
26 | setIsEditDialogOpen,
27 | handleEditDialogChange,
28 | handleDeleteDialogChange
29 | };
30 | }
31 |
--------------------------------------------------------------------------------
/application/src/components/services/incident-history/EmptyState.tsx:
--------------------------------------------------------------------------------
1 |
2 | interface EmptyStateProps {
3 | statusFilter: string;
4 | }
5 |
6 | export function EmptyState({ statusFilter }: EmptyStateProps) {
7 | return (
8 |
9 | {statusFilter === "all"
10 | ? "No incidents recorded in selected time period"
11 | : `No ${
12 | statusFilter === "up" ? "uptime" :
13 | statusFilter === "down" ? "downtime" :
14 | statusFilter === "warning" ? "warning" :
15 | "paused"
16 | } incidents recorded in selected time period`}
17 |
18 | );
19 | }
20 |
--------------------------------------------------------------------------------
/application/src/components/services/incident-history/index.ts:
--------------------------------------------------------------------------------
1 |
2 | // Export components
3 | export { LatestChecksTable } from './LatestChecksTable';
4 | export { StatusFilterTabs } from './StatusFilterTabs';
5 | export { TablePagination } from './TablePagination';
6 | export { IncidentTable } from './IncidentTable';
7 | export { EmptyState } from './EmptyState';
8 |
9 | // Export types and utils
10 | export * from './types';
11 | export * from './utils';
12 |
--------------------------------------------------------------------------------
/application/src/components/services/incident-history/types.ts:
--------------------------------------------------------------------------------
1 |
2 | import { UptimeData } from "@/types/service.types";
3 |
4 | export type StatusFilter = "all" | "up" | "down" | "paused" | "warning";
5 | export type PageSize = "10" | "25" | "50" | "100" | "250" | "all";
6 |
7 | export interface LatestChecksTableProps {
8 | uptimeData: UptimeData[];
9 | }
10 |
11 | export interface StatusInfo {
12 | icon: JSX.Element;
13 | text: string;
14 | textColor: string;
15 | badge: JSX.Element;
16 | }
17 |
--------------------------------------------------------------------------------
/application/src/components/services/index.ts:
--------------------------------------------------------------------------------
1 |
2 | // Export all service components for easier imports
3 | export * from './ServiceHeader';
4 | export * from './ServiceStatsCards';
5 | export * from './ResponseTimeChart';
6 | export * from './incident-history/LatestChecksTable';
7 | export * from './LoadingState';
8 | export * from './ServiceNotFound';
9 | export * from './ServiceMonitoringButton';
10 | export * from './AddServiceDialog';
11 | export * from './ServicesTableContainer';
12 | export * from './ServicesTableView';
13 | export * from './ServiceDeleteDialog';
14 | export * from './ServiceHistoryDialog';
15 | export * from './ServiceEditDialog';
16 |
--------------------------------------------------------------------------------
/application/src/components/services/service-row/ServiceRowResponseTime.tsx:
--------------------------------------------------------------------------------
1 |
2 | import React from "react";
3 | import { AlertTriangle } from "lucide-react";
4 |
5 | interface ServiceRowResponseTimeProps {
6 | responseTime: number;
7 | }
8 |
9 | export const ServiceRowResponseTime = ({ responseTime }: ServiceRowResponseTimeProps) => {
10 | // Determine if response time is high (≥ 1000ms)
11 | const isResponseTimeHigh = responseTime >= 1000;
12 |
13 | return (
14 |
15 | {responseTime > 0 ? (
16 | <>
17 |
18 | {responseTime}ms
19 |
20 | {isResponseTimeHigh && (
21 |
22 | )}
23 | >
24 | ) : 'N/A'}
25 |
26 | );
27 | };
28 |
--------------------------------------------------------------------------------
/application/src/components/services/service-row/index.ts:
--------------------------------------------------------------------------------
1 |
2 | export * from './ServiceRowActions';
3 | export * from './ServiceRowHeader';
4 | export * from './ServiceRowResponseTime';
5 |
--------------------------------------------------------------------------------
/application/src/components/settings/GeneralSettings.tsx:
--------------------------------------------------------------------------------
1 |
2 | import GeneralSettingsPanel from './general/GeneralSettingsPanel';
3 |
4 | export default GeneralSettingsPanel;
--------------------------------------------------------------------------------
/application/src/components/settings/about-system/index.ts:
--------------------------------------------------------------------------------
1 |
2 | export { default as AboutSystem } from './AboutSystem';
3 |
--------------------------------------------------------------------------------
/application/src/components/settings/alerts-templates/form/index.ts:
--------------------------------------------------------------------------------
1 |
2 | export * from './BasicTemplateFields';
3 | export * from './MessagesTabContent';
4 | export * from './PlaceholdersTabContent';
5 |
--------------------------------------------------------------------------------
/application/src/components/settings/alerts-templates/hooks/index.ts:
--------------------------------------------------------------------------------
1 |
2 | export * from './useTemplateForm';
3 |
--------------------------------------------------------------------------------
/application/src/components/settings/alerts-templates/index.ts:
--------------------------------------------------------------------------------
1 |
2 | export { AlertsTemplates } from "./AlertsTemplates";
3 |
--------------------------------------------------------------------------------
/application/src/components/settings/data-retention/index.ts:
--------------------------------------------------------------------------------
1 |
2 | import DataRetentionSettings from './DataRetentionSettings';
3 |
4 | export default DataRetentionSettings;
5 | export { DataRetentionSettings };
--------------------------------------------------------------------------------
/application/src/components/settings/general/types.ts:
--------------------------------------------------------------------------------
1 |
2 | import { GeneralSettings } from "@/services/settingsService";
3 |
4 | export interface SettingsTabProps {
5 | form: any;
6 | isEditing: boolean;
7 | settings?: GeneralSettings;
8 | }
9 |
10 | export interface GeneralSettingsPanelProps {
11 | // No props needed for now, but we can add them in the future
12 | }
--------------------------------------------------------------------------------
/application/src/components/settings/notification-settings/index.ts:
--------------------------------------------------------------------------------
1 |
2 | import NotificationSettings from "./NotificationSettings";
3 |
4 | export { NotificationSettings };
5 |
--------------------------------------------------------------------------------
/application/src/components/settings/user-management/AddUserDialog.tsx:
--------------------------------------------------------------------------------
1 |
2 | import React from "react";
3 | import {
4 | Dialog,
5 | DialogContent,
6 | DialogDescription,
7 | DialogHeader,
8 | DialogTitle,
9 | } from "@/components/ui/dialog";
10 | import { ScrollArea } from "@/components/ui/scroll-area";
11 | import { UseFormReturn } from "react-hook-form";
12 | import AddUserForm from "./form-fields/AddUserForm";
13 |
14 | interface AddUserDialogProps {
15 | isOpen: boolean;
16 | setIsOpen: (open: boolean) => void;
17 | form: UseFormReturn;
18 | onSubmit: (data: any) => void;
19 | isSubmitting: boolean;
20 | }
21 |
22 | const AddUserDialog = ({ isOpen, setIsOpen, form, onSubmit, isSubmitting }: AddUserDialogProps) => {
23 | return (
24 |
43 | );
44 | };
45 |
46 | export default AddUserDialog;
--------------------------------------------------------------------------------
/application/src/components/settings/user-management/avatarOptions.ts:
--------------------------------------------------------------------------------
1 |
2 | export const avatarOptions = [
3 | { url: "https://api.dicebear.com/7.x/bottts/svg?seed=Felix", label: "Avatar 1" },
4 | { url: "https://api.dicebear.com/7.x/bottts/svg?seed=Aneka", label: "Avatar 2" },
5 | { url: "https://api.dicebear.com/7.x/bottts/svg?seed=Milo", label: "Avatar 3" },
6 | { url: "https://api.dicebear.com/7.x/fun-emoji/svg?seed=Cuddles", label: "Avatar 4" },
7 | { url: "https://api.dicebear.com/7.x/fun-emoji/svg?seed=Bella", label: "Avatar 5" },
8 | { url: "https://api.dicebear.com/7.x/fun-emoji/svg?seed=Shadow", label: "Avatar 6" },
9 | { url: "https://api.dicebear.com/7.x/lorelei/svg?seed=Jasper", label: "Avatar 7" },
10 | { url: "https://api.dicebear.com/7.x/lorelei/svg?seed=Willow", label: "Avatar 8" },
11 | { url: "https://api.dicebear.com/7.x/notionists/svg?seed=Mittens", label: "Avatar 9" },
12 | { url: "https://api.dicebear.com/7.x/notionists/svg?seed=Oliver", label: "Avatar 10" },
13 | ];
14 |
--------------------------------------------------------------------------------
/application/src/components/settings/user-management/form-fields/UserRoleField.tsx:
--------------------------------------------------------------------------------
1 |
2 | import React from "react";
3 | import { Control } from "react-hook-form";
4 | import { FormControl, FormField, FormItem, FormLabel } from "@/components/ui/form";
5 | import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
6 | import { userRoles } from "../userForms";
7 |
8 | interface UserRoleFieldProps {
9 | control: Control;
10 | name: string;
11 | label: string;
12 | disabled?: boolean;
13 | }
14 |
15 | const UserRoleField = ({ control, name, label, disabled = false }: UserRoleFieldProps) => {
16 | return (
17 | (
21 |
22 | {label}
23 |
24 |
40 |
41 |
42 | )}
43 | />
44 | );
45 | };
46 |
47 | export default UserRoleField;
--------------------------------------------------------------------------------
/application/src/components/settings/user-management/form-fields/UserTextField.tsx:
--------------------------------------------------------------------------------
1 |
2 | import React from "react";
3 | import { FormField, FormItem, FormLabel, FormControl, FormMessage } from "@/components/ui/form";
4 | import { Input } from "@/components/ui/input";
5 | import { Control } from "react-hook-form";
6 |
7 | interface UserTextFieldProps {
8 | control: Control;
9 | name: string;
10 | label: string;
11 | placeholder: string;
12 | type?: string;
13 | required?: boolean;
14 | }
15 |
16 | const UserTextField = ({
17 | control,
18 | name,
19 | label,
20 | placeholder,
21 | type = "text",
22 | required = false
23 | }: UserTextFieldProps) => {
24 | return (
25 | (
29 |
30 |
31 | {label}
32 | {required && *}
33 |
34 |
35 | {
41 | // Handle the value change properly
42 | const value = e.target.value;
43 | field.onChange(value);
44 | }}
45 | />
46 |
47 |
48 |
49 | )}
50 | />
51 | );
52 | };
53 |
54 | export default UserTextField;
55 |
--------------------------------------------------------------------------------
/application/src/components/settings/user-management/form-fields/UserToggleField.tsx:
--------------------------------------------------------------------------------
1 |
2 | import React from "react";
3 | import { FormField, FormItem, FormLabel, FormControl, FormMessage, FormDescription } from "@/components/ui/form";
4 | import { Switch } from "@/components/ui/switch";
5 | import { Control } from "react-hook-form";
6 |
7 | interface UserToggleFieldProps {
8 | control: Control;
9 | name: string;
10 | label: string;
11 | description: string;
12 | }
13 |
14 | const UserToggleField = ({ control, name, label, description }: UserToggleFieldProps) => {
15 | return (
16 | (
20 |
21 |
22 | {label}
23 |
24 | {description}
25 |
26 |
27 |
28 |
32 |
33 |
34 | )}
35 | />
36 | );
37 | };
38 |
39 | export default UserToggleField;
40 |
--------------------------------------------------------------------------------
/application/src/components/settings/user-management/form-fields/index.ts:
--------------------------------------------------------------------------------
1 |
2 | export { default as UserTextField } from "./UserTextField";
3 | export { default as UserToggleField } from "./UserToggleField";
4 | export { default as UserProfilePictureField } from "./UserProfilePictureField";
5 | export { default as UserRoleField } from "./UserRoleField";
--------------------------------------------------------------------------------
/application/src/components/settings/user-management/hooks/index.ts:
--------------------------------------------------------------------------------
1 |
2 | export * from './useUserManagement';
3 | export * from './useUsersList';
4 | export * from './useUserForm';
5 | export * from './useUserDialogs';
6 | export * from './useUserOperations';
7 |
--------------------------------------------------------------------------------
/application/src/components/settings/user-management/hooks/useUserDialogs.ts:
--------------------------------------------------------------------------------
1 |
2 | import { useState } from "react";
3 | import { User } from "@/services/userService";
4 |
5 | export const useUserDialogs = () => {
6 | const [isDialogOpen, setIsDialogOpen] = useState(false);
7 | const [isAddUserDialogOpen, setIsAddUserDialogOpen] = useState(false);
8 | const [isDeleting, setIsDeleting] = useState(false);
9 | const [currentUser, setCurrentUser] = useState(null);
10 | const [userToDelete, setUserToDelete] = useState(null);
11 | const [isSubmitting, setIsSubmitting] = useState(false);
12 | const [updateError, setUpdateError] = useState(null);
13 |
14 | const handleEditUser = (user: User, form: any) => {
15 | setUpdateError(null);
16 | setCurrentUser(user);
17 | form.reset({
18 | full_name: user.full_name || "",
19 | email: user.email,
20 | username: user.username,
21 | isActive: user.isActive !== undefined ? user.isActive : true,
22 | role: user.role || "user",
23 | avatar: user.avatar || "",
24 | });
25 | setIsDialogOpen(true);
26 | };
27 |
28 | const handleDeletePrompt = (user: User) => {
29 | setUserToDelete(user);
30 | setIsDeleting(true);
31 | };
32 |
33 | return {
34 | isDialogOpen,
35 | setIsDialogOpen,
36 | isAddUserDialogOpen,
37 | setIsAddUserDialogOpen,
38 | isDeleting,
39 | setIsDeleting,
40 | isSubmitting,
41 | setIsSubmitting,
42 | updateError,
43 | setUpdateError,
44 | currentUser,
45 | setCurrentUser,
46 | userToDelete,
47 | setUserToDelete,
48 | handleEditUser,
49 | handleDeletePrompt,
50 | };
51 | };
52 |
--------------------------------------------------------------------------------
/application/src/components/settings/user-management/hooks/useUserForm.ts:
--------------------------------------------------------------------------------
1 |
2 | import { useForm } from "react-hook-form";
3 | import { zodResolver } from "@hookform/resolvers/zod";
4 | import { userFormSchema, newUserFormSchema, UserFormValues, NewUserFormValues } from "../userForms";
5 | import { avatarOptions } from "../avatarOptions";
6 |
7 | export const useUserForm = () => {
8 | const form = useForm({
9 | resolver: zodResolver(userFormSchema),
10 | defaultValues: {
11 | full_name: "",
12 | email: "",
13 | username: "",
14 | isActive: true,
15 | role: "user",
16 | avatar: "",
17 | },
18 | });
19 |
20 | const newUserForm = useForm({
21 | resolver: zodResolver(newUserFormSchema),
22 | defaultValues: {
23 | full_name: "",
24 | email: "",
25 | username: "",
26 | password: "",
27 | passwordConfirm: "",
28 | isActive: true,
29 | role: "user",
30 | avatar: avatarOptions[0].url,
31 | },
32 | });
33 |
34 | return { form, newUserForm };
35 | };
36 |
--------------------------------------------------------------------------------
/application/src/components/settings/user-management/hooks/useUsersList.ts:
--------------------------------------------------------------------------------
1 |
2 | import { useState, useEffect } from "react";
3 | import { useToast } from "@/hooks/use-toast";
4 | import { userService, User } from "@/services/userService";
5 |
6 | export const useUsersList = () => {
7 | const { toast } = useToast();
8 | const [users, setUsers] = useState([]);
9 | const [loading, setLoading] = useState(true);
10 | const [error, setError] = useState(null);
11 |
12 | const fetchUsers = async () => {
13 | setLoading(true);
14 | setError(null);
15 | try {
16 | console.log("Fetching users list");
17 | const data = await userService.getUsers();
18 | console.log("Received users data:", data);
19 |
20 | if (Array.isArray(data) && data.length >= 0) {
21 | setUsers(data);
22 | // Clear any previous errors
23 | setError(null);
24 | } else {
25 | console.error("Invalid users data format:", data);
26 | setUsers([]);
27 | setError("Invalid data format received from server");
28 | toast({
29 | title: "Warning",
30 | description: "No users found or could not load users.",
31 | variant: "destructive",
32 | });
33 | }
34 | } catch (error) {
35 | console.error("Error fetching users:", error);
36 | setError(error instanceof Error ? error.message : "Unknown error");
37 | toast({
38 | title: "Error fetching users",
39 | description: "Could not load users. Please try again later.",
40 | variant: "destructive",
41 | });
42 | setUsers([]);
43 | } finally {
44 | setLoading(false);
45 | }
46 | };
47 |
48 | useEffect(() => {
49 | fetchUsers();
50 | }, []);
51 |
52 | return { users, loading, error, fetchUsers };
53 | };
54 |
--------------------------------------------------------------------------------
/application/src/components/settings/user-management/index.ts:
--------------------------------------------------------------------------------
1 |
2 | import UserManagement from "./UserManagement";
3 | export default UserManagement;
4 |
--------------------------------------------------------------------------------
/application/src/components/settings/user-management/userForms.ts:
--------------------------------------------------------------------------------
1 |
2 | import * as z from "zod";
3 |
4 | // Define the available roles
5 | export const userRoles = [
6 | { label: "Admin", value: "admin" },
7 | { label: "Super Admin", value: "superadmin" }
8 | ];
9 |
10 | export const userFormSchema = z.object({
11 | full_name: z.string().min(2, {
12 | message: "Name must be at least 2 characters.",
13 | }),
14 | email: z.string().email({
15 | message: "Please enter a valid email address.",
16 | }),
17 | username: z.string().min(3, {
18 | message: "Username must be at least 3 characters.",
19 | }),
20 | isActive: z.boolean().optional(),
21 | role: z.string().min(1, {
22 | message: "Please select a role",
23 | }),
24 | avatar: z.string().optional(),
25 | });
26 |
27 | export const newUserFormSchema = userFormSchema.extend({
28 | password: z.string().min(8, {
29 | message: "Password must be at least 8 characters.",
30 | }),
31 | passwordConfirm: z.string().min(8, {
32 | message: "Password confirmation must be at least 8 characters.",
33 | }),
34 | }).refine((data) => data.password === data.passwordConfirm, {
35 | message: "Passwords don't match",
36 | path: ["passwordConfirm"],
37 | });
38 |
39 | export type UserFormValues = z.infer;
40 | export type NewUserFormValues = z.infer;
--------------------------------------------------------------------------------
/application/src/components/ssl-domain/SSLCertificateStatusCards.tsx:
--------------------------------------------------------------------------------
1 |
2 | import React from "react";
3 | import { SSLCertificate } from "@/types/ssl.types";
4 | import { useLanguage } from "@/contexts/LanguageContext";
5 | import { OverviewCard } from "@/components/schedule-incident/common/OverviewCard";
6 | import { Shield, ShieldAlert, ShieldX } from "lucide-react";
7 |
8 | interface SSLCertificateStatusCardsProps {
9 | certificates: SSLCertificate[];
10 | }
11 |
12 | export const SSLCertificateStatusCards = ({ certificates }: SSLCertificateStatusCardsProps) => {
13 | const { t } = useLanguage();
14 |
15 | // Count certificates by status
16 | const validCount = certificates.filter(cert => cert.status === 'valid').length;
17 | const expiringCount = certificates.filter(cert => cert.status === 'expiring_soon').length;
18 | const expiredCount = certificates.filter(cert => cert.status === 'expired').length;
19 |
20 | return (
21 |
22 | }
26 | color="green"
27 | className="hover:scale-105 transition-transform duration-200"
28 | />
29 |
30 | }
34 | color="amber"
35 | className="hover:scale-105 transition-transform duration-200"
36 | />
37 |
38 | }
42 | color="red"
43 | className="hover:scale-105 transition-transform duration-200"
44 | />
45 |
46 | );
47 | };
--------------------------------------------------------------------------------
/application/src/components/ssl-domain/SSLStatusBadge.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Badge } from "@/components/ui/badge";
3 | import { useLanguage } from "@/contexts/LanguageContext";
4 |
5 | interface SSLStatusBadgeProps {
6 | status: string;
7 | }
8 |
9 | export const SSLStatusBadge: React.FC = ({ status }) => {
10 | const { t } = useLanguage();
11 | let variant = "";
12 | let label = "";
13 |
14 | switch (status) {
15 | case "valid":
16 | variant = "bg-green-500 hover:bg-green-600";
17 | label = t('valid');
18 | break;
19 | case "expiring_soon":
20 | variant = "bg-yellow-500 hover:bg-yellow-600";
21 | label = t('expiringSoon');
22 | break;
23 | case "expired":
24 | variant = "bg-red-500 hover:bg-red-600";
25 | label = t('expired');
26 | break;
27 | case "pending":
28 | variant = "bg-blue-500 hover:bg-blue-600";
29 | label = t('pending');
30 | break;
31 | default:
32 | variant = "bg-gray-500 hover:bg-gray-600";
33 | label = status.charAt(0).toUpperCase() + status.slice(1);
34 | }
35 |
36 | return (
37 |
38 | {label}
39 |
40 | );
41 | };
--------------------------------------------------------------------------------
/application/src/components/ui/alert.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import { cva, type VariantProps } from "class-variance-authority"
3 |
4 | import { cn } from "@/lib/utils"
5 |
6 | const alertVariants = cva(
7 | "relative w-full rounded-lg border p-4 [&>svg~*]:pl-7 [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground",
8 | {
9 | variants: {
10 | variant: {
11 | default: "bg-background text-foreground",
12 | destructive:
13 | "border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive",
14 | },
15 | },
16 | defaultVariants: {
17 | variant: "default",
18 | },
19 | }
20 | )
21 |
22 | const Alert = React.forwardRef<
23 | HTMLDivElement,
24 | React.HTMLAttributes & VariantProps
25 | >(({ className, variant, ...props }, ref) => (
26 |
32 | ))
33 | Alert.displayName = "Alert"
34 |
35 | const AlertTitle = React.forwardRef<
36 | HTMLParagraphElement,
37 | React.HTMLAttributes
38 | >(({ className, ...props }, ref) => (
39 |
44 | ))
45 | AlertTitle.displayName = "AlertTitle"
46 |
47 | const AlertDescription = React.forwardRef<
48 | HTMLParagraphElement,
49 | React.HTMLAttributes
50 | >(({ className, ...props }, ref) => (
51 |
56 | ))
57 | AlertDescription.displayName = "AlertDescription"
58 |
59 | export { Alert, AlertTitle, AlertDescription }
60 |
--------------------------------------------------------------------------------
/application/src/components/ui/aspect-ratio.tsx:
--------------------------------------------------------------------------------
1 | import * as AspectRatioPrimitive from "@radix-ui/react-aspect-ratio"
2 |
3 | const AspectRatio = AspectRatioPrimitive.Root
4 |
5 | export { AspectRatio }
6 |
--------------------------------------------------------------------------------
/application/src/components/ui/avatar.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import * as AvatarPrimitive from "@radix-ui/react-avatar"
3 |
4 | import { cn } from "@/lib/utils"
5 |
6 | const Avatar = React.forwardRef<
7 | React.ElementRef,
8 | React.ComponentPropsWithoutRef
9 | >(({ className, ...props }, ref) => (
10 |
18 | ))
19 | Avatar.displayName = AvatarPrimitive.Root.displayName
20 |
21 | const AvatarImage = React.forwardRef<
22 | React.ElementRef,
23 | React.ComponentPropsWithoutRef
24 | >(({ className, ...props }, ref) => (
25 |
30 | ))
31 | AvatarImage.displayName = AvatarPrimitive.Image.displayName
32 |
33 | const AvatarFallback = React.forwardRef<
34 | React.ElementRef,
35 | React.ComponentPropsWithoutRef
36 | >(({ className, ...props }, ref) => (
37 |
45 | ))
46 | AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName
47 |
48 | export { Avatar, AvatarImage, AvatarFallback }
49 |
--------------------------------------------------------------------------------
/application/src/components/ui/badge.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import { cva, type VariantProps } from "class-variance-authority"
3 |
4 | import { cn } from "@/lib/utils"
5 |
6 | const badgeVariants = cva(
7 | "inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
8 | {
9 | variants: {
10 | variant: {
11 | default:
12 | "border-transparent bg-primary text-primary-foreground hover:bg-primary/80",
13 | secondary:
14 | "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80",
15 | destructive:
16 | "border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80",
17 | outline: "text-foreground",
18 | },
19 | },
20 | defaultVariants: {
21 | variant: "default",
22 | },
23 | }
24 | )
25 |
26 | export interface BadgeProps
27 | extends React.HTMLAttributes,
28 | VariantProps {}
29 |
30 | function Badge({ className, variant, ...props }: BadgeProps) {
31 | return (
32 |
33 | )
34 | }
35 |
36 | export { Badge, badgeVariants }
37 |
--------------------------------------------------------------------------------
/application/src/components/ui/checkbox.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import * as CheckboxPrimitive from "@radix-ui/react-checkbox"
3 | import { Check } from "lucide-react"
4 |
5 | import { cn } from "@/lib/utils"
6 |
7 | const Checkbox = React.forwardRef<
8 | React.ElementRef,
9 | React.ComponentPropsWithoutRef
10 | >(({ className, ...props }, ref) => (
11 |
19 |
22 |
23 |
24 |
25 | ))
26 | Checkbox.displayName = CheckboxPrimitive.Root.displayName
27 |
28 | export { Checkbox }
29 |
--------------------------------------------------------------------------------
/application/src/components/ui/collapsible.tsx:
--------------------------------------------------------------------------------
1 | import * as CollapsiblePrimitive from "@radix-ui/react-collapsible"
2 |
3 | const Collapsible = CollapsiblePrimitive.Root
4 |
5 | const CollapsibleTrigger = CollapsiblePrimitive.CollapsibleTrigger
6 |
7 | const CollapsibleContent = CollapsiblePrimitive.CollapsibleContent
8 |
9 | export { Collapsible, CollapsibleTrigger, CollapsibleContent }
10 |
--------------------------------------------------------------------------------
/application/src/components/ui/hover-card.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import * as HoverCardPrimitive from "@radix-ui/react-hover-card"
3 |
4 | import { cn } from "@/lib/utils"
5 |
6 | const HoverCard = HoverCardPrimitive.Root
7 |
8 | const HoverCardTrigger = HoverCardPrimitive.Trigger
9 |
10 | const HoverCardContent = React.forwardRef<
11 | React.ElementRef,
12 | React.ComponentPropsWithoutRef
13 | >(({ className, align = "center", sideOffset = 4, ...props }, ref) => (
14 |
24 | ))
25 | HoverCardContent.displayName = HoverCardPrimitive.Content.displayName
26 |
27 | export { HoverCard, HoverCardTrigger, HoverCardContent }
28 |
--------------------------------------------------------------------------------
/application/src/components/ui/input.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 |
3 | import { cn } from "@/lib/utils"
4 |
5 | const Input = React.forwardRef>(
6 | ({ className, type, ...props }, ref) => {
7 | return (
8 |
17 | )
18 | }
19 | )
20 | Input.displayName = "Input"
21 |
22 | export { Input }
23 |
--------------------------------------------------------------------------------
/application/src/components/ui/label.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import * as LabelPrimitive from "@radix-ui/react-label"
3 | import { cva, type VariantProps } from "class-variance-authority"
4 |
5 | import { cn } from "@/lib/utils"
6 |
7 | const labelVariants = cva(
8 | "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
9 | )
10 |
11 | const Label = React.forwardRef<
12 | React.ElementRef,
13 | React.ComponentPropsWithoutRef &
14 | VariantProps
15 | >(({ className, ...props }, ref) => (
16 |
21 | ))
22 | Label.displayName = LabelPrimitive.Root.displayName
23 |
24 | export { Label }
25 |
--------------------------------------------------------------------------------
/application/src/components/ui/popover.tsx:
--------------------------------------------------------------------------------
1 |
2 | import * as React from "react"
3 | import * as PopoverPrimitive from "@radix-ui/react-popover"
4 |
5 | import { cn } from "@/lib/utils"
6 |
7 | const Popover = PopoverPrimitive.Root
8 |
9 | const PopoverTrigger = PopoverPrimitive.Trigger
10 |
11 | const PopoverContent = React.forwardRef<
12 | React.ElementRef,
13 | React.ComponentPropsWithoutRef
14 | >(({ className, align = "center", sideOffset = 4, ...props }, ref) => (
15 |
16 |
26 |
27 | ))
28 | PopoverContent.displayName = PopoverPrimitive.Content.displayName
29 |
30 | export { Popover, PopoverTrigger, PopoverContent }
--------------------------------------------------------------------------------
/application/src/components/ui/progress.tsx:
--------------------------------------------------------------------------------
1 |
2 | import * as React from "react"
3 | import * as ProgressPrimitive from "@radix-ui/react-progress"
4 |
5 | import { cn } from "@/lib/utils"
6 |
7 | interface ProgressProps extends React.ComponentPropsWithoutRef {
8 | indicatorClassName?: string;
9 | }
10 |
11 | const Progress = React.forwardRef<
12 | React.ElementRef,
13 | ProgressProps
14 | >(({ className, indicatorClassName, value, ...props }, ref) => (
15 |
23 |
27 |
28 | ))
29 | Progress.displayName = ProgressPrimitive.Root.displayName
30 |
31 | export { Progress }
32 |
--------------------------------------------------------------------------------
/application/src/components/ui/radio-group.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import * as RadioGroupPrimitive from "@radix-ui/react-radio-group"
3 | import { Circle } from "lucide-react"
4 |
5 | import { cn } from "@/lib/utils"
6 |
7 | const RadioGroup = React.forwardRef<
8 | React.ElementRef,
9 | React.ComponentPropsWithoutRef
10 | >(({ className, ...props }, ref) => {
11 | return (
12 |
17 | )
18 | })
19 | RadioGroup.displayName = RadioGroupPrimitive.Root.displayName
20 |
21 | const RadioGroupItem = React.forwardRef<
22 | React.ElementRef,
23 | React.ComponentPropsWithoutRef
24 | >(({ className, ...props }, ref) => {
25 | return (
26 |
34 |
35 |
36 |
37 |
38 | )
39 | })
40 | RadioGroupItem.displayName = RadioGroupPrimitive.Item.displayName
41 |
42 | export { RadioGroup, RadioGroupItem }
43 |
--------------------------------------------------------------------------------
/application/src/components/ui/resizable.tsx:
--------------------------------------------------------------------------------
1 | import { GripVertical } from "lucide-react"
2 | import * as ResizablePrimitive from "react-resizable-panels"
3 |
4 | import { cn } from "@/lib/utils"
5 |
6 | const ResizablePanelGroup = ({
7 | className,
8 | ...props
9 | }: React.ComponentProps) => (
10 |
17 | )
18 |
19 | const ResizablePanel = ResizablePrimitive.Panel
20 |
21 | const ResizableHandle = ({
22 | withHandle,
23 | className,
24 | ...props
25 | }: React.ComponentProps & {
26 | withHandle?: boolean
27 | }) => (
28 | div]:rotate-90",
31 | className
32 | )}
33 | {...props}
34 | >
35 | {withHandle && (
36 |
37 |
38 |
39 | )}
40 |
41 | )
42 |
43 | export { ResizablePanelGroup, ResizablePanel, ResizableHandle }
44 |
--------------------------------------------------------------------------------
/application/src/components/ui/scroll-area.tsx:
--------------------------------------------------------------------------------
1 |
2 | import * as React from "react"
3 | import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area"
4 |
5 | import { cn } from "@/lib/utils"
6 |
7 | const ScrollArea = React.forwardRef<
8 | React.ElementRef,
9 | React.ComponentPropsWithoutRef
10 | >(({ className, children, ...props }, ref) => (
11 |
16 |
17 | {children}
18 |
19 |
20 |
21 |
22 | ))
23 | ScrollArea.displayName = ScrollAreaPrimitive.Root.displayName
24 |
25 | const ScrollBar = React.forwardRef<
26 | React.ElementRef,
27 | React.ComponentPropsWithoutRef
28 | >(({ className, orientation = "vertical", ...props }, ref) => (
29 |
42 |
45 |
46 | ))
47 | ScrollBar.displayName = ScrollAreaPrimitive.ScrollAreaScrollbar.displayName
48 |
49 | export { ScrollArea, ScrollBar }
--------------------------------------------------------------------------------
/application/src/components/ui/separator.tsx:
--------------------------------------------------------------------------------
1 |
2 | import * as React from "react"
3 | import * as SeparatorPrimitive from "@radix-ui/react-separator"
4 |
5 | import { cn } from "@/lib/utils"
6 |
7 | const Separator = React.forwardRef<
8 | React.ElementRef,
9 | React.ComponentPropsWithoutRef
10 | >(
11 | (
12 | { className, orientation = "horizontal", decorative = true, ...props },
13 | ref
14 | ) => (
15 |
26 | )
27 | )
28 | Separator.displayName = SeparatorPrimitive.Root.displayName
29 |
30 | export { Separator }
--------------------------------------------------------------------------------
/application/src/components/ui/skeleton.tsx:
--------------------------------------------------------------------------------
1 | import { cn } from "@/lib/utils"
2 |
3 | function Skeleton({
4 | className,
5 | ...props
6 | }: React.HTMLAttributes) {
7 | return (
8 |
12 | )
13 | }
14 |
15 | export { Skeleton }
16 |
--------------------------------------------------------------------------------
/application/src/components/ui/slider.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import * as SliderPrimitive from "@radix-ui/react-slider"
3 |
4 | import { cn } from "@/lib/utils"
5 |
6 | const Slider = React.forwardRef<
7 | React.ElementRef,
8 | React.ComponentPropsWithoutRef
9 | >(({ className, ...props }, ref) => (
10 |
18 |
19 |
20 |
21 |
22 |
23 | ))
24 | Slider.displayName = SliderPrimitive.Root.displayName
25 |
26 | export { Slider }
27 |
--------------------------------------------------------------------------------
/application/src/components/ui/sonner.tsx:
--------------------------------------------------------------------------------
1 |
2 | import { useTheme } from "@/contexts/ThemeContext";
3 | import { Toaster as Sonner, toast } from "sonner";
4 |
5 | type ToasterProps = React.ComponentProps;
6 |
7 | const Toaster = ({ ...props }: ToasterProps) => {
8 | const { theme } = useTheme();
9 |
10 | return (
11 |
29 | );
30 | };
31 |
32 | export { Toaster, toast };
33 |
--------------------------------------------------------------------------------
/application/src/components/ui/switch.tsx:
--------------------------------------------------------------------------------
1 |
2 | import * as React from "react"
3 | import * as SwitchPrimitives from "@radix-ui/react-switch"
4 |
5 | import { cn } from "@/lib/utils"
6 |
7 | const Switch = React.forwardRef<
8 | React.ElementRef,
9 | React.ComponentPropsWithoutRef
10 | >(({ className, ...props }, ref) => (
11 |
19 |
24 |
25 | ))
26 | Switch.displayName = SwitchPrimitives.Root.displayName
27 |
28 | export { Switch }
29 |
--------------------------------------------------------------------------------
/application/src/components/ui/textarea.tsx:
--------------------------------------------------------------------------------
1 |
2 | import * as React from "react"
3 |
4 | import { cn } from "@/lib/utils"
5 |
6 | export interface TextareaProps
7 | extends React.TextareaHTMLAttributes {}
8 |
9 | const Textarea = React.forwardRef(
10 | ({ className, ...props }, ref) => {
11 | return (
12 |
20 | )
21 | }
22 | )
23 | Textarea.displayName = "Textarea"
24 |
25 | export { Textarea }
--------------------------------------------------------------------------------
/application/src/components/ui/toaster.tsx:
--------------------------------------------------------------------------------
1 | import { useToast } from "@/hooks/use-toast"
2 | import {
3 | Toast,
4 | ToastClose,
5 | ToastDescription,
6 | ToastProvider,
7 | ToastTitle,
8 | ToastViewport,
9 | } from "@/components/ui/toast"
10 |
11 | export function Toaster() {
12 | const { toasts } = useToast()
13 |
14 | return (
15 |
16 | {toasts.map(function ({ id, title, description, action, ...props }) {
17 | return (
18 |
19 |
20 | {title && {title}}
21 | {description && (
22 | {description}
23 | )}
24 |
25 | {action}
26 |
27 |
28 | )
29 | })}
30 |
31 |
32 | )
33 | }
34 |
--------------------------------------------------------------------------------
/application/src/components/ui/toggle-group.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import * as ToggleGroupPrimitive from "@radix-ui/react-toggle-group"
3 | import { type VariantProps } from "class-variance-authority"
4 |
5 | import { cn } from "@/lib/utils"
6 | import { toggleVariants } from "@/components/ui/toggle"
7 |
8 | const ToggleGroupContext = React.createContext<
9 | VariantProps
10 | >({
11 | size: "default",
12 | variant: "default",
13 | })
14 |
15 | const ToggleGroup = React.forwardRef<
16 | React.ElementRef,
17 | React.ComponentPropsWithoutRef &
18 | VariantProps
19 | >(({ className, variant, size, children, ...props }, ref) => (
20 |
25 |
26 | {children}
27 |
28 |
29 | ))
30 |
31 | ToggleGroup.displayName = ToggleGroupPrimitive.Root.displayName
32 |
33 | const ToggleGroupItem = React.forwardRef<
34 | React.ElementRef,
35 | React.ComponentPropsWithoutRef &
36 | VariantProps
37 | >(({ className, children, variant, size, ...props }, ref) => {
38 | const context = React.useContext(ToggleGroupContext)
39 |
40 | return (
41 |
52 | {children}
53 |
54 | )
55 | })
56 |
57 | ToggleGroupItem.displayName = ToggleGroupPrimitive.Item.displayName
58 |
59 | export { ToggleGroup, ToggleGroupItem }
60 |
--------------------------------------------------------------------------------
/application/src/components/ui/toggle.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import * as TogglePrimitive from "@radix-ui/react-toggle"
3 | import { cva, type VariantProps } from "class-variance-authority"
4 |
5 | import { cn } from "@/lib/utils"
6 |
7 | const toggleVariants = cva(
8 | "inline-flex items-center justify-center rounded-md text-sm font-medium ring-offset-background transition-colors hover:bg-muted hover:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=on]:bg-accent data-[state=on]:text-accent-foreground",
9 | {
10 | variants: {
11 | variant: {
12 | default: "bg-transparent",
13 | outline:
14 | "border border-input bg-transparent hover:bg-accent hover:text-accent-foreground",
15 | },
16 | size: {
17 | default: "h-10 px-3",
18 | sm: "h-9 px-2.5",
19 | lg: "h-11 px-5",
20 | },
21 | },
22 | defaultVariants: {
23 | variant: "default",
24 | size: "default",
25 | },
26 | }
27 | )
28 |
29 | const Toggle = React.forwardRef<
30 | React.ElementRef,
31 | React.ComponentPropsWithoutRef &
32 | VariantProps
33 | >(({ className, variant, size, ...props }, ref) => (
34 |
39 | ))
40 |
41 | Toggle.displayName = TogglePrimitive.Root.displayName
42 |
43 | export { Toggle, toggleVariants }
44 |
--------------------------------------------------------------------------------
/application/src/components/ui/tooltip.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import * as TooltipPrimitive from "@radix-ui/react-tooltip"
3 |
4 | import { cn } from "@/lib/utils"
5 |
6 | const TooltipProvider = TooltipPrimitive.Provider
7 |
8 | const Tooltip = TooltipPrimitive.Root
9 |
10 | const TooltipTrigger = TooltipPrimitive.Trigger
11 |
12 | const TooltipContent = React.forwardRef<
13 | React.ElementRef,
14 | React.ComponentPropsWithoutRef
15 | >(({ className, sideOffset = 4, ...props }, ref) => (
16 |
25 | ))
26 | TooltipContent.displayName = TooltipPrimitive.Content.displayName
27 |
28 | export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider }
29 |
--------------------------------------------------------------------------------
/application/src/components/ui/use-toast.ts:
--------------------------------------------------------------------------------
1 | import { useToast, toast } from "@/hooks/use-toast";
2 |
3 | export { useToast, toast };
4 |
--------------------------------------------------------------------------------
/application/src/contexts/LanguageContext.tsx:
--------------------------------------------------------------------------------
1 | import React, { createContext, useContext, useState, ReactNode } from "react";
2 | import { translations, Language, TranslationModule, TranslationKey } from "@/translations";
3 |
4 | type LanguageContextType = {
5 | language: Language;
6 | setLanguage: (language: Language) => void;
7 | t: (key: string, module?: M) => string;
8 | };
9 |
10 | // ❗ Create the context with `undefined` to enforce provider usage
11 | const LanguageContext = createContext(undefined);
12 |
13 | // ✅ Stable custom hook
14 | export const useLanguage = () => {
15 | const context = useContext(LanguageContext);
16 | if (!context) {
17 | throw new Error("useLanguage must be used within a LanguageProvider");
18 | }
19 | return context;
20 | };
21 |
22 | export const LanguageProvider = ({ children }: { children: ReactNode }) => {
23 | const [language, setLanguage] = useState("en");
24 |
25 | const t = (key: string, module?: M): string => {
26 | if (module) {
27 | const translatedValue = translations[language][module][key as TranslationKey];
28 | return typeof translatedValue === "string" ? translatedValue : key;
29 | }
30 |
31 | for (const mod in translations[language]) {
32 | const moduleKey = mod as TranslationModule;
33 | const translatedValue = translations[language][moduleKey][key as any];
34 | if (translatedValue && typeof translatedValue === "string") {
35 | return translatedValue;
36 | }
37 | }
38 |
39 | return key;
40 | };
41 |
42 | return (
43 |
44 | {children}
45 |
46 | );
47 | };
48 |
--------------------------------------------------------------------------------
/application/src/hooks/use-mobile.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 |
3 | const MOBILE_BREAKPOINT = 768
4 |
5 | export function useIsMobile() {
6 | const [isMobile, setIsMobile] = React.useState(undefined)
7 |
8 | React.useEffect(() => {
9 | const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`)
10 | const onChange = () => {
11 | setIsMobile(window.innerWidth < MOBILE_BREAKPOINT)
12 | }
13 | mql.addEventListener("change", onChange)
14 | setIsMobile(window.innerWidth < MOBILE_BREAKPOINT)
15 | return () => mql.removeEventListener("change", onChange)
16 | }, [])
17 |
18 | return !!isMobile
19 | }
20 |
--------------------------------------------------------------------------------
/application/src/lib/utils.ts:
--------------------------------------------------------------------------------
1 | import { clsx, type ClassValue } from "clsx"
2 | import { twMerge } from "tailwind-merge"
3 |
4 | export function cn(...inputs: ClassValue[]) {
5 | return twMerge(clsx(inputs))
6 | }
7 |
--------------------------------------------------------------------------------
/application/src/main.tsx:
--------------------------------------------------------------------------------
1 | import { createRoot } from 'react-dom/client'
2 | import App from './App.tsx'
3 | import './index.css'
4 |
5 | createRoot(document.getElementById("root")!).render();
6 |
--------------------------------------------------------------------------------
/application/src/pages/Index.tsx:
--------------------------------------------------------------------------------
1 | // Update this page (the content is just a fallback if you fail to update the page)
2 |
3 | const Index = () => {
4 | return (
5 |
6 |
7 |
Welcome to Your Blank App
8 |
Start building your amazing project here!
9 |
10 |
11 | );
12 | };
13 |
14 | export default Index;
15 |
--------------------------------------------------------------------------------
/application/src/pages/NotFound.tsx:
--------------------------------------------------------------------------------
1 | import { useLocation } from "react-router-dom";
2 | import { useEffect } from "react";
3 |
4 | const NotFound = () => {
5 | const location = useLocation();
6 |
7 | useEffect(() => {
8 | console.error(
9 | "404 Error: User attempted to access non-existent route:",
10 | location.pathname
11 | );
12 | }, [location.pathname]);
13 |
14 | return (
15 |
24 | );
25 | };
26 |
27 | export default NotFound;
28 |
--------------------------------------------------------------------------------
/application/src/pages/ServiceDetail.tsx:
--------------------------------------------------------------------------------
1 |
2 | import { ServiceDetailContainer } from "@/components/services/ServiceDetailContainer";
3 |
4 | const ServiceDetail = () => {
5 | return ;
6 | };
7 |
8 | export default ServiceDetail;
9 |
--------------------------------------------------------------------------------
/application/src/services/incident/incidentCache.ts:
--------------------------------------------------------------------------------
1 |
2 | import { IncidentItem } from './types';
3 |
4 | // Export functions to update and invalidate the cache
5 | export const updateCache = (data: IncidentItem[]) => {
6 | console.log(`Updating cache with ${data.length} incidents`);
7 | // The actual cache is now maintained in incidentFetch.ts
8 | };
9 |
10 | // Reset cache and request state
11 | export const invalidateCache = () => {
12 | console.log('Invalidating incidents cache');
13 | // The invalidation is handled in incidentFetch.ts implementation
14 | };
15 |
--------------------------------------------------------------------------------
/application/src/services/incident/incidentPdfService.ts:
--------------------------------------------------------------------------------
1 |
2 | import { generateIncidentPDF } from './pdf';
3 |
4 | export { generateIncidentPDF };
5 |
6 |
--------------------------------------------------------------------------------
/application/src/services/incident/incidentService.ts:
--------------------------------------------------------------------------------
1 |
2 | import { CreateIncidentInput, IncidentItem, UpdateIncidentInput } from './types';
3 | import { createIncident, updateIncident, updateIncidentStatus, deleteIncident } from './incidentOperations';
4 | import { getAllIncidents, getIncidentById } from './incidentFetch';
5 | import { generateIncidentPDF } from './incidentPdfService';
6 |
7 | export const incidentService = {
8 | // Fetch operations
9 | getAllIncidents,
10 | getIncidentById,
11 |
12 | // CRUD operations
13 | createIncident,
14 | updateIncident,
15 | updateIncidentStatus,
16 | deleteIncident,
17 |
18 | // PDF operations
19 | generateIncidentPDF,
20 | };
21 |
22 | export default incidentService;
23 |
--------------------------------------------------------------------------------
/application/src/services/incident/incidentUtils.ts:
--------------------------------------------------------------------------------
1 |
2 | import { pb } from '@/lib/pocketbase';
3 | import { IncidentItem } from './types';
4 |
5 | // Helper function to get the API URL
6 | export const getApiUrl = (): string => {
7 | try {
8 | return pb.baseUrl;
9 | } catch (error) {
10 | console.error('Error getting API URL:', error);
11 | return '';
12 | }
13 | };
14 |
15 | // Normalize fetched items to ensure consistent data structure
16 | export const normalizeFetchedItem = (item: any): IncidentItem => {
17 | return {
18 | ...item,
19 | affected_systems: item.affected_systems || '',
20 | status: item.impact_status || item.status || 'Investigating',
21 | impact: item.impact || 'Low',
22 | priority: item.priority || 'Low',
23 | } as IncidentItem;
24 | };
25 |
26 | // Format status with first letter capitalized
27 | export const formatStatus = (status: string): string => {
28 | return status.charAt(0).toUpperCase() + status.slice(1);
29 | };
30 |
--------------------------------------------------------------------------------
/application/src/services/incident/index.ts:
--------------------------------------------------------------------------------
1 |
2 | export * from './types';
3 | export * from './incidentFetch';
4 | export * from './incidentOperations';
5 | export * from './incidentCache';
6 | export * from './incidentUtils';
7 | export * from './incidentPdfService';
8 | export * from './incidentService';
9 |
10 | // Export the incidentService as the default export
11 | export { incidentService } from './incidentService';
12 |
13 |
--------------------------------------------------------------------------------
/application/src/services/incident/pdf/headerFooter.ts:
--------------------------------------------------------------------------------
1 |
2 | import jsPDF from 'jspdf';
3 | import { IncidentItem } from '../types';
4 | import { fonts } from './utils';
5 |
6 | /**
7 | * Add the PDF header with title and metadata
8 | */
9 | export const addHeader = (doc: jsPDF, incident: IncidentItem): number => {
10 | // Set initial y position
11 | let yPos = 15;
12 |
13 | // Add header with title
14 | doc.setFont(fonts.bold);
15 | doc.setFontSize(18);
16 | doc.setTextColor(0, 0, 0);
17 | doc.text('INCIDENT REPORT', 105, yPos, { align: 'center' });
18 |
19 | // Add incident title
20 | yPos += 8;
21 | doc.setFontSize(14);
22 | const title = incident.title || `Incident Report #${incident.id}`;
23 | doc.text(title, 105, yPos, { align: 'center' });
24 |
25 | // Add current date
26 | yPos += 8;
27 | doc.setFont(fonts.normal);
28 | doc.setFontSize(10);
29 | doc.text(`Generated on: ${new Date().toLocaleDateString()}`, 105, yPos, { align: 'center' });
30 |
31 | // Add ReamStack logo or text
32 | yPos += 8;
33 | doc.setFontSize(12);
34 | doc.setFont(fonts.italic);
35 | doc.text('ReamStack Incident Management', 105, yPos, { align: 'center' });
36 |
37 | // Add horizontal line
38 | yPos += 5;
39 | doc.setLineWidth(0.5);
40 | doc.line(15, yPos, 195, yPos);
41 |
42 | // Return next Y position with some padding
43 | return yPos + 10;
44 | };
45 |
46 | /**
47 | * Add footer to all pages
48 | */
49 | export const addFooter = (doc: jsPDF): void => {
50 | const pageCount = doc.getNumberOfPages();
51 | for (let i = 1; i <= pageCount; i++) {
52 | doc.setPage(i);
53 | doc.setFontSize(8);
54 | doc.setTextColor(100, 100, 100);
55 | doc.text(
56 | `Page ${i} of ${pageCount} | Generated by ReamStack Incident Management System`,
57 | 105,
58 | 285,
59 | { align: 'center' }
60 | );
61 | }
62 | };
63 |
64 |
--------------------------------------------------------------------------------
/application/src/services/incident/pdf/index.ts:
--------------------------------------------------------------------------------
1 |
2 | import { generatePdf } from './generator';
3 | import { IncidentItem } from '../types';
4 |
5 | /**
6 | * Generate and download PDF for incident report
7 | */
8 | export const generateIncidentPDF = async (incident: IncidentItem): Promise => {
9 | await generatePdf(incident);
10 | return Promise.resolve();
11 | };
12 |
13 |
--------------------------------------------------------------------------------
/application/src/services/incident/pdf/utils.ts:
--------------------------------------------------------------------------------
1 |
2 | import { format } from 'date-fns';
3 |
4 | // Font configuration for the PDF
5 | export const fonts = {
6 | normal: 'Helvetica',
7 | bold: 'Helvetica-Bold',
8 | italic: 'Helvetica-Oblique',
9 | };
10 |
11 | // Helper function to format dates
12 | export const formatDate = (dateString: string | undefined): string => {
13 | if (!dateString) return 'N/A';
14 | try {
15 | return format(new Date(dateString), 'PPp');
16 | } catch (e) {
17 | return dateString || 'N/A';
18 | }
19 | };
20 |
21 | // Helper function to capitalize first letter
22 | export const capitalize = (str: string): string => {
23 | if (!str) return '';
24 | return str.charAt(0).toUpperCase() + str.slice(1);
25 | };
26 |
27 |
--------------------------------------------------------------------------------
/application/src/services/incident/types.ts:
--------------------------------------------------------------------------------
1 |
2 | // Define the type for incident item
3 | export type IncidentItem = {
4 | id: string;
5 | service_id?: string;
6 | timestamp?: string;
7 | description: string;
8 | assigned_to?: string;
9 | resolution_time?: string;
10 | impact: string;
11 | affected_systems: string;
12 | root_cause?: string;
13 | resolution_steps?: string;
14 | lessons_learned?: string;
15 | operational_status_id?: string;
16 | server_id?: string;
17 | priority: string;
18 | status: string;
19 | impact_status?: string;
20 | created: string;
21 | updated: string;
22 | category?: string;
23 | title: string;
24 | };
25 |
26 | // Define the input type for creating an incident
27 | export type CreateIncidentInput = {
28 | title: string;
29 | description: string;
30 | status: string;
31 | impact: string;
32 | affected_systems: string;
33 | priority: string;
34 | service_id?: string;
35 | assigned_to?: string;
36 | root_cause?: string;
37 | resolution_steps?: string;
38 | lessons_learned?: string;
39 | timestamp?: string;
40 | created_by: string;
41 | };
42 |
43 | // Define the input type for updating an incident
44 | export type UpdateIncidentInput = {
45 | id: string;
46 | title?: string;
47 | description?: string;
48 | status?: string;
49 | impact?: string;
50 | affected_systems?: string;
51 | priority?: string;
52 | service_id?: string;
53 | assigned_to?: string;
54 | root_cause?: string;
55 | resolution_steps?: string;
56 | lessons_learned?: string;
57 | };
58 |
--------------------------------------------------------------------------------
/application/src/services/incidentService.ts:
--------------------------------------------------------------------------------
1 |
2 | // Re-export incident service from the new modular structure
3 | export * from './incident';
4 | export { incidentService as default } from './incident';
5 |
--------------------------------------------------------------------------------
/application/src/services/maintenance/api/index.ts:
--------------------------------------------------------------------------------
1 |
2 | // Re-export all maintenance API functions from their respective modules
3 | export { clearCache as clearMaintenanceCache } from './maintenanceCache';
4 | export { fetchAllMaintenanceRecords } from './maintenanceFetch';
5 | export {
6 | updateMaintenanceStatus,
7 | updateMaintenance,
8 | deleteMaintenance,
9 | createMaintenance
10 | } from './maintenanceOperations';
11 |
--------------------------------------------------------------------------------
/application/src/services/maintenance/api/maintenanceCache.ts:
--------------------------------------------------------------------------------
1 |
2 | import { MaintenanceItem } from '../../types/maintenance.types';
3 |
4 | // Cache management for maintenance records
5 |
6 | // Cache for maintenance records
7 | let cachedMaintenanceRecords: MaintenanceItem[] | null = null;
8 | let lastFetchTimestamp = 0;
9 | const CACHE_EXPIRY_MS = 600000; // 10 minutes cache expiry (increased significantly)
10 |
11 | /**
12 | * Check if cache is valid and can be used
13 | */
14 | export const isCacheValid = (): boolean => {
15 | const now = Date.now();
16 | return !!(cachedMaintenanceRecords && now - lastFetchTimestamp < CACHE_EXPIRY_MS);
17 | };
18 |
19 | /**
20 | * Get cached maintenance records if available
21 | */
22 | export const getCachedRecords = () => {
23 | if (cachedMaintenanceRecords) {
24 | return [...cachedMaintenanceRecords]; // Return a copy to prevent mutation
25 | }
26 | return null;
27 | };
28 |
29 | /**
30 | * Update cache with new data
31 | */
32 | export const updateCache = (data: MaintenanceItem[], timestamp?: number): void => {
33 | cachedMaintenanceRecords = data;
34 | lastFetchTimestamp = timestamp || Date.now();
35 | };
36 |
37 | /**
38 | * Clear cache to force refresh
39 | */
40 | export const clearCache = (): void => {
41 | cachedMaintenanceRecords = null;
42 | lastFetchTimestamp = 0;
43 | };
44 |
45 | /**
46 | * Get cache statistics for debugging
47 | */
48 | export const getCacheStats = () => {
49 | const now = Date.now();
50 | const cacheAge = lastFetchTimestamp ? now - lastFetchTimestamp : 0;
51 | const timeUntilExpiry = lastFetchTimestamp ? CACHE_EXPIRY_MS - cacheAge : 0;
52 |
53 | return {
54 | hasCache: !!cachedMaintenanceRecords,
55 | cacheSize: cachedMaintenanceRecords?.length || 0,
56 | cacheAge: Math.round(cacheAge / 1000), // in seconds
57 | timeUntilExpiry: Math.round(timeUntilExpiry / 1000), // in seconds
58 | isValid: isCacheValid()
59 | };
60 | };
--------------------------------------------------------------------------------
/application/src/services/maintenance/index.ts:
--------------------------------------------------------------------------------
1 |
2 | export * from './maintenanceService';
3 | // Export maintenance API functions from the new structure
4 | export {
5 | fetchAllMaintenanceRecords,
6 | updateMaintenanceStatus,
7 | updateMaintenance,
8 | deleteMaintenance,
9 | createMaintenance,
10 | clearMaintenanceCache
11 | } from './api';
12 | // Export everything from maintenanceUtils
13 | export * from './maintenanceUtils';
14 | export * from './pdf';
15 | export * from '../types/maintenance.types';
16 |
--------------------------------------------------------------------------------
/application/src/services/maintenance/pdf/index.ts:
--------------------------------------------------------------------------------
1 |
2 | import { generatePdf } from './generator';
3 | import { MaintenanceItem } from '../../types/maintenance.types';
4 |
5 | /**
6 | * Generate and download PDF for maintenance report
7 | */
8 | export const generateMaintenancePDF = async (maintenance: MaintenanceItem): Promise => {
9 | try {
10 | const filename = await generatePdf(maintenance);
11 | return filename;
12 | } catch (error) {
13 | console.error("Error in generateMaintenancePDF:", error);
14 | throw error;
15 | }
16 | };
17 |
--------------------------------------------------------------------------------
/application/src/services/maintenance/pdf/utils.ts:
--------------------------------------------------------------------------------
1 |
2 | import { format } from 'date-fns';
3 |
4 | // Font configuration for the PDF
5 | export const fonts = {
6 | normal: 'Helvetica',
7 | bold: 'Helvetica-Bold',
8 | italic: 'Helvetica-Oblique',
9 | };
10 |
11 | // Helper function to format dates
12 | export const formatDate = (dateString: string | undefined): string => {
13 | if (!dateString) return 'N/A';
14 | try {
15 | return format(new Date(dateString), 'PPp');
16 | } catch (e) {
17 | return dateString || 'N/A';
18 | }
19 | };
20 |
21 | // Helper function to calculate duration between two dates
22 | export const calculateDuration = (start: string, end: string): string => {
23 | try {
24 | if (!start || !end) return 'N/A';
25 |
26 | const startTime = new Date(start);
27 | const endTime = new Date(end);
28 |
29 | // Validate dates
30 | if (isNaN(startTime.getTime()) || isNaN(endTime.getTime())) {
31 | return 'N/A';
32 | }
33 |
34 | const durationMs = endTime.getTime() - startTime.getTime();
35 |
36 | // Check for negative duration
37 | if (durationMs < 0) {
38 | return 'Invalid date range';
39 | }
40 |
41 | // Convert to hours and minutes
42 | const hours = Math.floor(durationMs / (1000 * 60 * 60));
43 | const minutes = Math.floor((durationMs % (1000 * 60 * 60)) / (1000 * 60));
44 |
45 | return `${hours}h ${minutes}m`;
46 | } catch (error) {
47 | console.error('Error calculating duration:', error);
48 | return "N/A";
49 | }
50 | };
51 |
--------------------------------------------------------------------------------
/application/src/services/monitoring/index.ts:
--------------------------------------------------------------------------------
1 |
2 | import { monitoringIntervals } from './monitoringIntervals';
3 | import { checkHttpService } from './httpChecker';
4 | import {
5 | startMonitoringService,
6 | pauseMonitoring,
7 | resumeMonitoring,
8 | startAllActiveServices
9 | } from './service-status';
10 |
11 | export const monitoringService = {
12 | startMonitoringService,
13 | pauseMonitoring,
14 | resumeMonitoring,
15 | checkHttpService,
16 | startAllActiveServices
17 | };
18 |
--------------------------------------------------------------------------------
/application/src/services/monitoring/monitoringIntervals.ts:
--------------------------------------------------------------------------------
1 | // Map to keep track of monitoring intervals by service ID
2 | const monitoringIntervals = new Map();
3 |
4 | export { monitoringIntervals };
5 |
--------------------------------------------------------------------------------
/application/src/services/monitoring/service-status/index.ts:
--------------------------------------------------------------------------------
1 |
2 | import { startMonitoringService } from './startMonitoring';
3 | import { resumeMonitoring } from './resumeMonitoring';
4 | import { pauseMonitoring } from './pauseMonitoring';
5 | import { startAllActiveServices } from './startAllServices';
6 |
7 | // Export all service monitoring control functions
8 | export {
9 | startMonitoringService,
10 | resumeMonitoring,
11 | pauseMonitoring,
12 | startAllActiveServices
13 | };
14 |
--------------------------------------------------------------------------------
/application/src/services/monitoring/service-status/pauseMonitoring.ts:
--------------------------------------------------------------------------------
1 |
2 | import { pb } from '@/lib/pocketbase';
3 | import { monitoringIntervals } from '../monitoringIntervals';
4 | import { Service } from '@/types/service.types';
5 |
6 | /**
7 | * Pause monitoring for a specific service
8 | */
9 | export async function pauseMonitoring(serviceId: string): Promise {
10 | try {
11 | // Clear the monitoring interval if it exists
12 | const intervalId = monitoringIntervals.get(serviceId);
13 | if (intervalId) {
14 | clearInterval(intervalId);
15 | monitoringIntervals.delete(serviceId);
16 | console.log(`Monitoring paused for service ${serviceId}`);
17 | }
18 |
19 | // Get current timestamp formatted as a string
20 | const now = new Date().toISOString();
21 |
22 | // Fetch the current service to get its name for better logging
23 | const service = await pb.collection('services').getOne(serviceId);
24 |
25 | // Update the service status to paused and store the exact pause time
26 | await pb.collection('services').update(serviceId, {
27 | status: "paused",
28 | lastChecked: now,
29 | last_checked: now // Ensure both field names are updated
30 | });
31 |
32 | // We'll skip the notification here since it will be handled by the UI component
33 | // This prevents duplicate notifications for the paused status
34 | console.log(`Service ${service.name} paused at ${now}, skipping notification to prevent duplication`);
35 | } catch (error) {
36 | console.error("Error pausing monitoring:", error);
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/application/src/services/monitoring/service-status/startAllServices.ts:
--------------------------------------------------------------------------------
1 |
2 | import { pb } from '@/lib/pocketbase';
3 | import { startMonitoringService } from './startMonitoring';
4 |
5 | /**
6 | * Start monitoring for all active services
7 | */
8 | export async function startAllActiveServices(): Promise {
9 | try {
10 | // Get all services that are not paused
11 | const result = await pb.collection('services').getList(1, 100, {
12 | filter: 'status != "paused"'
13 | });
14 |
15 | console.log(`Starting monitoring for ${result.items.length} active services`);
16 |
17 | // Start monitoring each active service
18 | for (const service of result.items) {
19 | await startMonitoringService(service.id);
20 | }
21 | } catch (error) {
22 | console.error("Error starting all active services:", error);
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/application/src/services/monitoring/utils/notificationUtils.ts:
--------------------------------------------------------------------------------
1 |
2 | import { pb } from '@/lib/pocketbase';
3 | import { formatCurrentTime } from './httpUtils';
4 |
5 | /**
6 | * Records a mute status change for a service in the database
7 | * @param serviceId The ID of the service
8 | * @param serviceName The name of the service
9 | * @param muteStatus The new mute status (true = muted, false = unmuted)
10 | * @param userId Optional user ID who performed the action
11 | */
12 | export async function recordMuteStatusChange(
13 | serviceId: string,
14 | serviceName: string,
15 | muteStatus: boolean,
16 | userId?: string
17 | ): Promise {
18 | try {
19 | const timestamp = new Date().toISOString();
20 | const formattedTime = formatCurrentTime();
21 |
22 | console.log(`Recording ${muteStatus ? "mute" : "unmute"} status change for service ${serviceName} at ${formattedTime}`);
23 |
24 | // Update the service record with the correct alerts field value
25 | // Use both mute_alerts and alerts fields for backward compatibility
26 | const updateData = {
27 | mute_changed_at: timestamp,
28 | mute_alerts: muteStatus,
29 | alerts: muteStatus ? "muted" : "unmuted" // Use the correct field name as per DB schema
30 | };
31 |
32 | console.log(`Updating service with data: ${JSON.stringify(updateData)}`);
33 |
34 | await pb.collection('services').update(serviceId, updateData);
35 |
36 | console.log(`Mute status change recorded for ${serviceName}: ${muteStatus ? "Muted" : "Unmuted"}`);
37 | } catch (error) {
38 | console.error("Error recording mute status change:", error);
39 | throw error; // Rethrow the error to be handled by the caller
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/application/src/services/notification/types.ts:
--------------------------------------------------------------------------------
1 |
2 | import { Service } from "@/types/service.types";
3 |
4 | export interface NotificationPayload {
5 | service: Service;
6 | status: string;
7 | responseTime?: number;
8 | timestamp: string;
9 | message?: string;
10 | _notificationSource?: string; // Internal flag to prevent duplicate notifications
11 | }
12 |
--------------------------------------------------------------------------------
/application/src/services/ssl/index.ts:
--------------------------------------------------------------------------------
1 |
2 | // Re-export all SSL-related functionality for domain SSL checking
3 | // Use explicit re-exports to avoid naming conflicts
4 |
5 | // SSL Checker service
6 | export { checkSSLCertificate, checkSSLApi } from './sslCheckerService';
7 |
8 | // SSL Status utilities
9 | export { determineSSLStatus } from './sslStatusUtils';
10 |
11 | // Primary export for fetchSSLCertificates
12 | export { fetchSSLCertificates } from './sslFetchService';
13 |
14 | // Certificate operations
15 | export {
16 | addSSLCertificate,
17 | checkAndUpdateCertificate,
18 | deleteSSLCertificate,
19 | refreshAllCertificates
20 | } from './sslCertificateOperations';
21 |
22 | // SSL-specific notification service
23 | export {
24 | checkAllCertificatesAndNotify,
25 | checkCertificateAndNotify,
26 | shouldRunDailyCheck,
27 | sendSSLNotification
28 | } from './notification';
29 |
30 | // Export types
31 | export type { SSLCheckerResponse, SSLCertificate, AddSSLCertificateDto, SSLNotification } from './types';
32 |
33 | // Export utility functions
34 | export { normalizeDomain, createErrorResponse } from './sslCheckerUtils';
35 |
36 | // Export checking mechanisms
37 | export { checkWithFetch } from './sslPrimaryChecker';
--------------------------------------------------------------------------------
/application/src/services/ssl/notification/index.ts:
--------------------------------------------------------------------------------
1 |
2 | // Export all SSL notification related functionality
3 | export {
4 | checkAllCertificatesAndNotify,
5 | checkCertificateAndNotify
6 | } from './sslCheckNotifier';
7 |
8 | export { sendSSLNotification } from './sslNotificationSender';
9 | export { shouldRunDailyCheck } from './sslScheduleUtils';
--------------------------------------------------------------------------------
/application/src/services/ssl/notification/sslScheduleUtils.ts:
--------------------------------------------------------------------------------
1 |
2 | /**
3 | * Check if SSL certificate daily check should run
4 | * This helps implement the once-per-day check logic
5 | */
6 | export function shouldRunDailyCheck(): boolean {
7 | const lastRunKey = 'ssl_daily_check_last_run';
8 | const lastRun = localStorage.getItem(lastRunKey);
9 |
10 | if (!lastRun) {
11 | // First time running, save timestamp and run
12 | localStorage.setItem(lastRunKey, new Date().toISOString());
13 | return true;
14 | }
15 |
16 | const lastRunDate = new Date(lastRun).setHours(0, 0, 0, 0);
17 | const today = new Date().setHours(0, 0, 0, 0);
18 |
19 | // If last run was not today, run again
20 | if (lastRunDate < today) {
21 | localStorage.setItem(lastRunKey, new Date().toISOString());
22 | return true;
23 | }
24 |
25 | // Already ran today
26 | return false;
27 | }
--------------------------------------------------------------------------------
/application/src/services/ssl/sslCheckerService.ts:
--------------------------------------------------------------------------------
1 |
2 | import type { SSLCheckerResponse } from "./types";
3 | import { toast } from "sonner";
4 | import { checkWithFetch } from "./sslPrimaryChecker";
5 | import { normalizeDomain, createErrorResponse } from "./sslCheckerUtils";
6 |
7 | /**
8 | * Check SSL certificate for a domain
9 | * Uses the reliable CORS proxy approach
10 | */
11 | export const checkSSLCertificate = async (domain: string): Promise => {
12 | try {
13 | console.log(`Checking SSL certificate for domain: ${domain}`);
14 |
15 | // Normalize domain (remove protocol if present)
16 | const normalizedDomain = normalizeDomain(domain);
17 |
18 | if (!normalizedDomain) {
19 | throw new Error("Invalid domain provided");
20 | }
21 |
22 | // Use the working CORS proxy approach
23 | const result = await checkWithFetch(normalizedDomain);
24 | console.log("SSL check result:", result);
25 | return result;
26 | } catch (error) {
27 | console.error("SSL check failed completely:", error);
28 | toast.error(`SSL check failed: ${error instanceof Error ? error.message : 'Unknown error'}`);
29 | return createErrorResponse(domain, error);
30 | }
31 | };
32 |
33 | /**
34 | * Check SSL certificate using the CORS proxy
35 | * This is kept for backward compatibility
36 | */
37 | export const checkSSLApi = async (domain: string): Promise => {
38 | return checkSSLCertificate(domain);
39 | };
--------------------------------------------------------------------------------
/application/src/services/ssl/sslCheckerUtils.ts:
--------------------------------------------------------------------------------
1 |
2 | /**
3 | * Normalize domain by removing protocol if present
4 | */
5 | export const normalizeDomain = (domain: string): string => {
6 | if (!domain) return '';
7 |
8 | // Remove any protocol (http://, https://)
9 | return domain.replace(/^(https?:\/\/)/, '').trim();
10 | };
11 |
12 | /**
13 | * Create error response for SSL check failures
14 | */
15 | export const createErrorResponse = (domain: string, error: unknown): any => {
16 | const errorMessage = error instanceof Error ? error.message : 'Unknown SSL check error';
17 |
18 | const errorResponse = {
19 | version: "1.0",
20 | app: "ssl-checker",
21 | host: domain,
22 | response_time_sec: "0",
23 | status: "error",
24 | message: errorMessage,
25 | error: errorMessage,
26 | result: {
27 | host: domain,
28 | resolved_ip: domain,
29 | issued_to: domain,
30 | issued_o: null,
31 | issuer_c: "Unknown",
32 | issuer_o: "Unknown",
33 | issuer_ou: null,
34 | issuer_cn: "Unknown",
35 | cert_sn: "Unknown",
36 | cert_sha1: "",
37 | cert_alg: "Unknown",
38 | cert_ver: 0,
39 | cert_sans: domain,
40 | cert_exp: true,
41 | cert_valid: false,
42 | valid_from: "Unknown",
43 | valid_till: "Unknown",
44 | validity_days: 0,
45 | days_left: 0,
46 | valid_days_to_expire: 0,
47 | hsts_header_enabled: false
48 | }
49 | };
50 |
51 | return errorResponse;
52 | };
--------------------------------------------------------------------------------
/application/src/services/ssl/sslPrimaryChecker.ts:
--------------------------------------------------------------------------------
1 |
2 | import { createErrorResponse } from "./sslCheckerUtils";
3 | import type { SSLCheckerResponse } from "./types";
4 |
5 | /**
6 | * Check SSL certificate using fetch with CORS proxy
7 | * This is our primary method that's proven to work
8 | */
9 | export const checkWithFetch = async (domain: string): Promise => {
10 | console.log(`Checking SSL via CORS proxy for: ${domain}`);
11 |
12 | try {
13 | // Use the working CORS proxy
14 | const corsProxyUrl = `https://api.allorigins.win/raw?url=${encodeURIComponent(`https://ssl-checker.io/api/v1/check/${domain}`)}`;
15 | console.log("Using CORS proxy for SSL check:", corsProxyUrl);
16 |
17 | const proxyResponse = await fetch(corsProxyUrl);
18 | if (!proxyResponse.ok) {
19 | throw new Error(`CORS proxy request failed with status: ${proxyResponse.status}`);
20 | }
21 |
22 | const proxyData = await proxyResponse.json();
23 | console.log("CORS proxy returned SSL data:", proxyData);
24 | return proxyData;
25 | } catch (error) {
26 | console.error("SSL check failed:", error);
27 | return createErrorResponse(domain, error);
28 | }
29 | };
--------------------------------------------------------------------------------
/application/src/services/ssl/sslStatusUtils.ts:
--------------------------------------------------------------------------------
1 |
2 | /**
3 | * Determine SSL certificate status based on days left
4 | */
5 | export const determineSSLStatus = (daysLeft: number, warningThreshold: number, expiryThreshold: number): string => {
6 | if (daysLeft <= 0) {
7 | return "expired";
8 | } else if (daysLeft <= expiryThreshold) {
9 | return "expiring_soon";
10 | } else if (daysLeft > expiryThreshold) {
11 | return "valid";
12 | } else {
13 | return "unknown";
14 | }
15 | };
--------------------------------------------------------------------------------
/application/src/services/ssl/utils.ts:
--------------------------------------------------------------------------------
1 |
2 | import { SSLCheckerResponse } from "./types";
3 |
4 | // Calculate days remaining from expiration date
5 | export function calculateDaysRemaining(validTo: string): number {
6 | try {
7 | const expirationDate = new Date(validTo);
8 | const currentDate = new Date();
9 | const diffTime = expirationDate.getTime() - currentDate.getTime();
10 | return Math.ceil(diffTime / (1000 * 3600 * 24)); // Convert ms to days
11 | } catch (error) {
12 | console.error("Error calculating days remaining:", error);
13 | return 0;
14 | }
15 | }
16 |
17 | // Check if the certificate is valid
18 | export function isValid(validTo: string): boolean {
19 | try {
20 | return new Date(validTo).getTime() > Date.now();
21 | } catch (error) {
22 | console.error("Error checking certificate validity:", error);
23 | return false;
24 | }
25 | }
26 |
27 | // Convert results to our expected response format
28 | export function convertResultToResponse(result: any): SSLCheckerResponse {
29 | return {
30 | version: "1.0",
31 | app: "ssl-checker",
32 | host: result.host || "",
33 | response_time_sec: result.response_time_sec || "0.5",
34 | status: result.status || "ok",
35 | result: {
36 | host: result.host || "",
37 | issued_to: result.subject || result.host || "",
38 | issuer_o: result.issuer || "Unknown",
39 | cert_sn: result.serial_number || "0",
40 | cert_alg: result.algorithm || "Unknown",
41 | cert_sans: result.sans || "",
42 | cert_exp: !result.is_valid,
43 | cert_valid: result.is_valid || false,
44 | valid_from: result.valid_from || new Date().toISOString(),
45 | valid_till: result.valid_to || new Date().toISOString(),
46 | validity_days: result.validity_days || 365,
47 | days_left: result.days_remaining || 0,
48 | valid_days_to_expire: result.days_remaining || 0
49 | }
50 | };
51 | }
--------------------------------------------------------------------------------
/application/src/services/sslCertificateService.ts:
--------------------------------------------------------------------------------
1 |
2 | // This file re-exports all SSL certificate related services for backward compatibility
3 | import {
4 | checkSSLCertificate,
5 | fetchSSLCertificates,
6 | addSSLCertificate,
7 | checkAndUpdateCertificate
8 | } from './ssl';
9 |
10 | import { determineSSLStatus } from './ssl/sslStatusUtils';
11 |
12 | // Import from the new refactored location
13 | import {
14 | checkAllCertificatesAndNotify,
15 | checkCertificateAndNotify,
16 | shouldRunDailyCheck
17 | } from './ssl/notification';
18 |
19 | export {
20 | checkSSLCertificate,
21 | determineSSLStatus,
22 | fetchSSLCertificates,
23 | addSSLCertificate,
24 | checkAndUpdateCertificate,
25 | checkAllCertificatesAndNotify,
26 | checkCertificateAndNotify,
27 | shouldRunDailyCheck
28 | };
--------------------------------------------------------------------------------
/application/src/services/sslCheckerService.ts:
--------------------------------------------------------------------------------
1 |
2 | // This file re-exports the SSL checker functionality from different implementations
3 | // Primary implementation is in sslCheckerService.ts in the ssl folder
4 |
5 | export { checkSSLCertificate, checkSSLApi } from './ssl/sslCheckerService';
6 |
--------------------------------------------------------------------------------
/application/src/services/types/maintenance.types.ts:
--------------------------------------------------------------------------------
1 |
2 | export interface MaintenanceItem {
3 | id: string;
4 | title: string;
5 | description: string;
6 | start_time: string;
7 | end_time: string;
8 | affected: string;
9 | priority: string;
10 | status: string;
11 | field: string;
12 | created_by: string;
13 | assigned_users: string[] | string; // Can be array of user IDs or string (JSON or comma-separated)
14 | notify_subscribers: string;
15 | notification_channel_id?: string;
16 | notification_id?: string;
17 | operational_status_id?: string;
18 | created: string;
19 | updated: string;
20 | notification_channel_name?: string; // Add this optional property for UI display purposes
21 | expand?: {
22 | assigned_users?: any[]; // For expanded user data
23 | };
24 | }
25 |
26 | export interface CreateMaintenanceInput {
27 | title: string;
28 | description: string;
29 | start_time: string;
30 | end_time: string;
31 | affected: string;
32 | priority: string;
33 | status: string;
34 | field: string;
35 | created_by: string;
36 | assigned_users: string[] | string; // Can be array of user IDs or string representation
37 | notify_subscribers: string;
38 | notification_channel_id?: string;
39 | notification_id?: string; // Added to ensure it's included in create operations
40 | }
41 |
42 | export interface MaintenanceFilter {
43 | status?: string;
44 | priority?: string;
45 | field?: string;
46 | }
--------------------------------------------------------------------------------
/application/src/translations/de/about.ts:
--------------------------------------------------------------------------------
1 | // Über das System
2 | import { AboutTranslations } from '../types/about';
3 |
4 | export const aboutTranslations: AboutTranslations = {
5 | aboutCheckcle: "Über Checkcle",
6 | systemDescription: "Checkcle ist ein Open-Source-Überwachungs-Stack, der Echtzeit-Einblicke in Server- und Dienstzustände, Vorfallmanagement und operative Transparenz bietet. Veröffentlicht unter der MIT-Lizenz.",
7 | systemVersion: "Systemversion",
8 | license: "Lizenz",
9 | mitLicense: "MIT-Lizenz",
10 | links: "Links",
11 | viewOnGithub: "Auf GitHub ansehen",
12 | viewDocumentation: "Dokumentation ansehen",
13 | followOnX: "Auf X folgen",
14 | joinDiscord: "Discord beitreten",
15 | quickActions: "Schnelle Aktionen",
16 | quickActionsDescription: "Greifen Sie schnell auf gängige Überwachungsvorgänge und -funktionen zu. Wählen Sie unten eine Aktion aus, um loszulegen.",
17 | quickTips: "Schnelle Tipps",
18 | };
19 |
20 |
21 |
--------------------------------------------------------------------------------
/application/src/translations/de/common.ts:
--------------------------------------------------------------------------------
1 |
2 | import { CommonTranslations } from '../types/common';
3 |
4 | export const commonTranslations: CommonTranslations = {
5 | welcome: "Willkommen",
6 | logout: "Abmelden",
7 | language: "Sprache",
8 | english: "Englisch",
9 | khmer: "Khmer",
10 | german: "Deutsch",
11 | goodMorning: "Guten Morgen",
12 | goodAfternoon: "Guten Nachmittag",
13 | goodEvening: "Guten Abend",
14 | profile: "Profil",
15 | settings: "Einstellungen",
16 | documentation: "Dokumentation",
17 | notifications: "Benachrichtigungen",
18 | close: "Schließen",
19 | cancel: "Abbrechen",
20 | view: "Anzeigen",
21 | edit: "Bearbeiten",
22 | delete: "Löschen",
23 | status: "Status",
24 | time: "Zeit",
25 | title: "Titel",
26 | description: "Beschreibung",
27 | success: "Erfolg",
28 | error: "Fehler",
29 | };
30 |
--------------------------------------------------------------------------------
/application/src/translations/de/index.ts:
--------------------------------------------------------------------------------
1 |
2 | import { Translations } from '../types';
3 | import { commonTranslations } from './common';
4 | import { menuTranslations } from './menu';
5 | import { loginTranslations } from './login';
6 | import { aboutTranslations } from './about';
7 | import { servicesTranslations } from './services';
8 | import { maintenanceTranslations } from './maintenance';
9 | import { incidentTranslations } from './incident';
10 | import { sslTranslations } from './ssl';
11 | import { settingsTranslations } from './settings';
12 |
13 | const enTranslations: Translations = {
14 | common: commonTranslations,
15 | menu: menuTranslations,
16 | login: loginTranslations,
17 | about: aboutTranslations,
18 | services: servicesTranslations,
19 | maintenance: maintenanceTranslations,
20 | incident: incidentTranslations,
21 | ssl: sslTranslations,
22 | settings: settingsTranslations
23 | };
24 |
25 | export default enTranslations;
--------------------------------------------------------------------------------
/application/src/translations/de/login.ts:
--------------------------------------------------------------------------------
1 |
2 | import { LoginTranslations } from '../types/login';
3 |
4 | export const loginTranslations: LoginTranslations = {
5 | // Login-Seite
6 | signInToYourAccount: "Melden Sie sich bei Ihrem Konto an",
7 | dontHaveAccount: "Sie haben kein Konto?",
8 | createOne: "Erstellen Sie eines",
9 | signInWithGoogle: "Mit Google anmelden",
10 | orContinueWith: "ODER",
11 | email: "E-Mail",
12 | password: "Passwort",
13 | forgot: "Vergessen?",
14 | signIn: "Anmelden",
15 | signingIn: "Anmeldung läuft...",
16 | loginSuccessful: "Anmeldung erfolgreich",
17 | loginSuccessMessage: "Sie wurden erfolgreich angemeldet.",
18 | loginFailed: "Anmeldung fehlgeschlagen",
19 | authenticationFailed: "Authentifizierung fehlgeschlagen",
20 | bySigningIn: "Durch die Anmeldung stimmen Sie unseren",
21 | termsAndConditions: "Allgemeinen Geschäftsbedingungen",
22 | and: "und",
23 | privacyPolicy: "Datenschutzrichtlinie",
24 | };
25 |
--------------------------------------------------------------------------------
/application/src/translations/de/menu.ts:
--------------------------------------------------------------------------------
1 |
2 | import { MenuTranslations } from '../types/menu';
3 |
4 | export const menuTranslations: MenuTranslations = {
5 | // Hauptmenü-Übersetzungen
6 | uptimeMonitoring: "Betriebszeitüberwachung",
7 | instanceMonitoring: "Instanzüberwachung",
8 | sslDomain: "SSL & Domain",
9 | scheduleIncident: "Zeitplan & Vorfall",
10 | operationalPage: "Betriebsstatus-Seite",
11 | reports: "Berichte",
12 | apiDocumentation: "API-Dokumentation",
13 | // Einstellungen-Panel
14 | settingPanel: "Einstellungsbereich",
15 | generalSettings: "Allgemeine Einstellungen",
16 | userManagement: "Benutzerverwaltung",
17 | notificationSettings: "Benachrichtigungseinstellungen",
18 | alertsTemplates: "Alarmvorlagen",
19 | rolesManagement: "Rollenverwaltung",
20 | dataRetention: "Datenspeicherung",
21 | backupSettings: "Backup-Einstellungen",
22 | aboutSystem: "Über das System",
23 | };
24 |
--------------------------------------------------------------------------------
/application/src/translations/de/services.ts:
--------------------------------------------------------------------------------
1 |
2 | import { ServicesTranslations } from '../types/services';
3 |
4 | export const servicesTranslations: ServicesTranslations = {
5 | // Dienst-Tabelle
6 | serviceName: "Dienstname",
7 | serviceType: "Diensttyp",
8 | serviceStatus: "Dienststatus",
9 | responseTime: "Antwortzeit",
10 | uptime: "Betriebszeit",
11 | lastChecked: "Zuletzt überprüft",
12 | noServices: "Keine Dienste entsprechen Ihren Filterkriterien.",
13 | };
14 |
--------------------------------------------------------------------------------
/application/src/translations/de/settings.ts:
--------------------------------------------------------------------------------
1 | import { SettingsTranslations } from '../types/settings';
2 |
3 | export const settingsTranslations: SettingsTranslations = {
4 | // Tabs
5 | systemSettings: "Systemeinstellungen",
6 | mailSettings: "E-Mail-Einstellungen",
7 |
8 | // System Settings
9 | appName: "Anwendungsname",
10 | appURL: "Anwendungs-URL",
11 | senderName: "Absendername",
12 | senderEmail: "Absender-E-Mail-Adresse",
13 | hideControls: "Steuerelemente ausblenden",
14 |
15 | // Mail Settings
16 | smtpSettings: "SMTP-Konfiguration",
17 | smtpEnabled: "SMTP aktivieren",
18 | smtpHost: "SMTP-Host",
19 | smtpPort: "SMTP-Port",
20 | smtpUsername: "SMTP-Benutzername",
21 | smtpPassword: "SMTP Password",
22 | smtpAuthMethod: "Authentifizierungsmethode",
23 | enableTLS: "TLS aktivieren",
24 | localName: "Lokaler Name",
25 |
26 | // Actions and status
27 | save: "Änderungen speichern",
28 | saving: "Speichere...",
29 | settingsUpdated: "Einstellungen erfolgreich aktualisiert",
30 | errorSavingSettings: "Fehler beim Speichern der Einstellungen",
31 | testConnection: "Verbindung testen",
32 | testingConnection: "Verbindung wird getestet...",
33 | connectionSuccess: "Verbindung erfolgreich",
34 | connectionFailed: "Verbindung fehlgeschlagen"
35 | };
36 |
--------------------------------------------------------------------------------
/application/src/translations/en/about.ts:
--------------------------------------------------------------------------------
1 |
2 | import { AboutTranslations } from '../types/about';
3 |
4 | export const aboutTranslations: AboutTranslations = {
5 | aboutCheckcle: "About Checkcle",
6 | systemDescription: "Checkcle is an open-source monitoring stack offering real-time insights into server and service health, incident management, and operational transparency. Released as MIT License.",
7 | systemVersion: "System Version",
8 | license: "License",
9 | mitLicense: "MIT License",
10 | links: "Links",
11 | viewOnGithub: "View on GitHub",
12 | viewDocumentation: "View Documentation",
13 | followOnX: "Follow on X",
14 | joinDiscord: "Join Discord",
15 | quickActions: "Quick Actions",
16 | quickActionsDescription: "Access common monitoring operations and features quickly. Select an action below to get started.",
17 | quickTips: "Quick Tips",
18 | };
19 |
--------------------------------------------------------------------------------
/application/src/translations/en/common.ts:
--------------------------------------------------------------------------------
1 |
2 | import { CommonTranslations } from '../types/common';
3 |
4 | export const commonTranslations: CommonTranslations = {
5 | welcome: "Welcome",
6 | logout: "Logout",
7 | language: "Language",
8 | english: "English",
9 | khmer: "Khmer",
10 | german: "Deutsch",
11 | goodMorning: "Good morning",
12 | goodAfternoon: "Good afternoon",
13 | goodEvening: "Good evening",
14 | profile: "Profile",
15 | settings: "Settings",
16 | documentation: "Documentation",
17 | notifications: "Notifications",
18 | close: "Close",
19 | cancel: "Cancel",
20 | view: "View",
21 | edit: "Edit",
22 | delete: "Delete",
23 | status: "Status",
24 | time: "Time",
25 | title: "Title",
26 | description: "Description",
27 | success: "Success",
28 | error: "Error",
29 | };
30 |
--------------------------------------------------------------------------------
/application/src/translations/en/index.ts:
--------------------------------------------------------------------------------
1 |
2 | import { Translations } from '../types';
3 | import { commonTranslations } from './common';
4 | import { menuTranslations } from './menu';
5 | import { loginTranslations } from './login';
6 | import { aboutTranslations } from './about';
7 | import { servicesTranslations } from './services';
8 | import { maintenanceTranslations } from './maintenance';
9 | import { incidentTranslations } from './incident';
10 | import { sslTranslations } from './ssl';
11 | import { settingsTranslations } from './settings';
12 |
13 | const enTranslations: Translations = {
14 | common: commonTranslations,
15 | menu: menuTranslations,
16 | login: loginTranslations,
17 | about: aboutTranslations,
18 | services: servicesTranslations,
19 | maintenance: maintenanceTranslations,
20 | incident: incidentTranslations,
21 | ssl: sslTranslations,
22 | settings: settingsTranslations
23 | };
24 |
25 | export default enTranslations;
--------------------------------------------------------------------------------
/application/src/translations/en/login.ts:
--------------------------------------------------------------------------------
1 |
2 | import { LoginTranslations } from '../types/login';
3 |
4 | export const loginTranslations: LoginTranslations = {
5 | signInToYourAccount: "Sign in to your account",
6 | dontHaveAccount: "Don't have an account?",
7 | createOne: "Create one",
8 | signInWithGoogle: "Sign in with Google",
9 | orContinueWith: "OR",
10 | email: "Email",
11 | password: "Password",
12 | forgot: "Forgot?",
13 | signIn: "Sign In",
14 | signingIn: "Signing in...",
15 | loginSuccessful: "Login successful",
16 | loginSuccessMessage: "You have been logged in successfully.",
17 | loginFailed: "Login failed",
18 | authenticationFailed: "Authentication failed",
19 | bySigningIn: "By signing in, you agree to our",
20 | termsAndConditions: "Terms & Conditions",
21 | and: "and",
22 | privacyPolicy: "Privacy Policy",
23 | };
24 |
--------------------------------------------------------------------------------
/application/src/translations/en/menu.ts:
--------------------------------------------------------------------------------
1 |
2 | import { MenuTranslations } from '../types/menu';
3 |
4 | export const menuTranslations: MenuTranslations = {
5 | uptimeMonitoring: "Uptime Monitoring",
6 | instanceMonitoring: "Instance Monitoring",
7 | sslDomain: "SSL & Domain",
8 | scheduleIncident: "Schedule & Incident",
9 | operationalPage: "Operational Page",
10 | reports: "Reports",
11 | apiDocumentation: "API Documentation",
12 | settingPanel: "Setting Panel",
13 | generalSettings: "General Settings",
14 | userManagement: "User Management",
15 | notificationSettings: "Notification Settings",
16 | alertsTemplates: "Alerts Templates",
17 | rolesManagement: "Roles Management",
18 | dataRetention: "Data Retention",
19 | backupSettings: "Backup Settings",
20 | aboutSystem: "About System",
21 | };
22 |
--------------------------------------------------------------------------------
/application/src/translations/en/services.ts:
--------------------------------------------------------------------------------
1 |
2 | import { ServicesTranslations } from '../types/services';
3 |
4 | export const servicesTranslations: ServicesTranslations = {
5 | serviceName: "Service Name",
6 | serviceType: "Service Type",
7 | serviceStatus: "Service Status",
8 | responseTime: "Response Time",
9 | uptime: "Uptime",
10 | lastChecked: "Last Checked",
11 | noServices: "No services match your filter criteria.",
12 | };
13 |
--------------------------------------------------------------------------------
/application/src/translations/en/settings.ts:
--------------------------------------------------------------------------------
1 |
2 | import { SettingsTranslations } from '../types/settings';
3 |
4 | export const settingsTranslations: SettingsTranslations = {
5 | // Tabs
6 | systemSettings: "System Settings",
7 | mailSettings: "Mail Settings",
8 |
9 | // System Settings
10 | appName: "Application Name",
11 | appURL: "Application URL",
12 | senderName: "Sender Name",
13 | senderEmail: "Sender Email Address",
14 | hideControls: "Hide Controls",
15 |
16 | // Mail Settings
17 | smtpSettings: "SMTP Configuration",
18 | smtpEnabled: "Enable SMTP",
19 | smtpHost: "SMTP Host",
20 | smtpPort: "SMTP Port",
21 | smtpUsername: "SMTP Username",
22 | smtpPassword: "SMTP Password",
23 | smtpAuthMethod: "Authentication Method",
24 | enableTLS: "Enable TLS",
25 | localName: "Local Name",
26 |
27 | // Actions and status
28 | save: "Save Changes",
29 | saving: "Saving...",
30 | settingsUpdated: "Settings updated successfully",
31 | errorSavingSettings: "Error saving settings",
32 | errorFetchingSettings: "Error loading settings",
33 | testConnection: "Test Connection",
34 | testingConnection: "Testing Connection...",
35 | connectionSuccess: "Connection successful",
36 | connectionFailed: "Connection failed"
37 | };
--------------------------------------------------------------------------------
/application/src/translations/index.ts:
--------------------------------------------------------------------------------
1 | import enTranslations from './en';
2 | import kmTranslations from './km';
3 | import deTranslations from './de';
4 |
5 | export type Language = "en" | "km" | "de";
6 |
7 | export const translations = {
8 | en: enTranslations,
9 | km: kmTranslations,
10 | de: deTranslations,
11 | };
12 |
13 | // Type for accessing translations by module and key
14 | export type TranslationModule = keyof typeof enTranslations;
15 | export type TranslationKey = keyof typeof enTranslations[M];
16 |
--------------------------------------------------------------------------------
/application/src/translations/km/about.ts:
--------------------------------------------------------------------------------
1 |
2 | import { AboutTranslations } from '../types/about';
3 |
4 | export const aboutTranslations: AboutTranslations = {
5 | aboutCheckcle: "អំពី Checkcle",
6 | systemDescription: "Checkcle គឺជាស្តាកការត្រួតពិនិត្យប្រភពបើកទូលាយដែលផ្តល់នូវការយល់ដឹងជាក់ស្តែងអំពីសុខភាពម៉ាស៊ីនមេ និងសេវាកម្ម, ការគ្រប់គ្រងឧបទ្ទវហេតុ, និងតម្លាភាពនៃប្រតិបត្តិការ។ ចេញផ្សាយជាអាជ្ញាបណ្ណ MIT។",
7 | systemVersion: "កំណែប្រព័ន្ធ",
8 | license: "អាជ្ញាបណ្ណ",
9 | mitLicense: "អាជ្ញាបណ្ណ MIT",
10 | links: "តំណភ្ជាប់",
11 | viewOnGithub: "មើលលើ GitHub",
12 | viewDocumentation: "មើលឯកសារ",
13 | followOnX: "តាមដានលើ X",
14 | joinDiscord: "ចូលរួមក្នុង Discord",
15 | quickActions: "សកម្មភាពរហ័ស",
16 | quickActionsDescription: "ចូលប្រើប្រតិបត្តិការត្រួតពិនិត្យ និងមុខងារទូទៅយ៉ាងរហ័ស។ ជ្រើសរើសសកម្មភាពខាងក្រោមដើម្បីចាប់ផ្តើម។",
17 | quickTips: "គន្លឹះរហ័ស",
18 | };
--------------------------------------------------------------------------------
/application/src/translations/km/common.ts:
--------------------------------------------------------------------------------
1 |
2 | import { CommonTranslations } from '../types/common';
3 |
4 | export const commonTranslations: CommonTranslations = {
5 | welcome: "សូមស្វាគមន៍",
6 | logout: "ចាកចេញ",
7 | language: "ភាសា",
8 | english: "អង់គ្លេស",
9 | khmer: "ខ្មែរ",
10 | german: "Deutsch",
11 | goodMorning: "អរុណសួស្តី",
12 | goodAfternoon: "ទិវាសួស្តី",
13 | goodEvening: "សាយណ្ហសួស្តី",
14 | profile: "ប្រវត្តិរូប",
15 | settings: "ការកំណត់",
16 | documentation: "ឯកសារ",
17 | notifications: "ការជូនដំណឹង",
18 | close: "បិទ",
19 | cancel: "បោះបង់",
20 | view: "មើល",
21 | edit: "កែសម្រួល",
22 | delete: "លុប",
23 | status: "ស្ថានភាព",
24 | time: "ពេលវេលា",
25 | title: "ចំណងជើង",
26 | description: "ការពិពណ៌នា",
27 | success: "ជោគជ័យ",
28 | error: "កំហុស",
29 | };
30 |
--------------------------------------------------------------------------------
/application/src/translations/km/index.ts:
--------------------------------------------------------------------------------
1 |
2 | import { Translations } from '../types';
3 | import { commonTranslations } from './common';
4 | import { menuTranslations } from './menu';
5 | import { loginTranslations } from './login';
6 | import { aboutTranslations } from './about';
7 | import { servicesTranslations } from './services';
8 | import { maintenanceTranslations } from './maintenance';
9 | import { incidentTranslations } from './incident';
10 | import { sslTranslations } from './ssl';
11 | import { settingsTranslations } from './settings';
12 |
13 | const enTranslations: Translations = {
14 | common: commonTranslations,
15 | menu: menuTranslations,
16 | login: loginTranslations,
17 | about: aboutTranslations,
18 | services: servicesTranslations,
19 | maintenance: maintenanceTranslations,
20 | incident: incidentTranslations,
21 | ssl: sslTranslations,
22 | settings: settingsTranslations
23 | };
24 |
25 | export default enTranslations;
--------------------------------------------------------------------------------
/application/src/translations/km/login.ts:
--------------------------------------------------------------------------------
1 |
2 | import { LoginTranslations } from '../types/login';
3 |
4 | export const loginTranslations: LoginTranslations = {
5 | signInToYourAccount: "ចូលគណនីរបស់អ្នក",
6 | dontHaveAccount: "មិនមានគណនីមែនទេ?",
7 | createOne: "បង្កើតមួយ",
8 | signInWithGoogle: "ចូលជាមួយ Google",
9 | orContinueWith: "ឬ",
10 | email: "អ៊ីមែល",
11 | password: "ពាក្យសម្ងាត់",
12 | forgot: "ភ្លេច?",
13 | signIn: "ចូល",
14 | signingIn: "កំពុងចូល...",
15 | loginSuccessful: "ការចូលបានជោគជ័យ",
16 | loginSuccessMessage: "អ្នកបានចូលប្រើដោយជោគជ័យ។",
17 | loginFailed: "ការចូលបានបរាជ័យ",
18 | authenticationFailed: "ការផ្ទៀងផ្ទាត់បានបរាជ័យ",
19 | bySigningIn: "ដោយការចូល អ្នកយល់ព្រមនឹង",
20 | termsAndConditions: "លក្ខខណ្ឌ",
21 | and: "និង",
22 | privacyPolicy: "គោលនយោបាយឯកជនភាព",
23 | };
24 |
--------------------------------------------------------------------------------
/application/src/translations/km/menu.ts:
--------------------------------------------------------------------------------
1 |
2 | import { MenuTranslations } from '../types/menu';
3 |
4 | export const menuTranslations: MenuTranslations = {
5 | uptimeMonitoring: "ការត្រួតពិនិត្យ Uptime",
6 | instanceMonitoring: "ការត្រួតពិនិត្យឧបករណ៍",
7 | sslDomain: "SSL និងដមែន",
8 | scheduleIncident: "កាលវិភាគនិងឧបទ្ទវហេតុ",
9 | operationalPage: "ទំព័រប្រតិបត្តិការ",
10 | reports: "របាយការណ៍",
11 | apiDocumentation: "ឯកសារ API",
12 | settingPanel: "ផ្ទាំងការកំណត់",
13 | generalSettings: "ការកំណត់ទូទៅ",
14 | userManagement: "ការគ្រប់គ្រងអ្នកប្រើប្រាស់",
15 | notificationSettings: "ការកំណត់ការជូនដំណឹង",
16 | alertsTemplates: "គំរូការជូនដំណឹង",
17 | rolesManagement: "ការគ្រប់គ្រងតួនាទី",
18 | dataRetention: "ការរក្សាទុកទិន្នន័យ",
19 | backupSettings: "ការកំណត់បម្រុងទុក",
20 | aboutSystem: "អំពីប្រព័ន្ធ",
21 | };
22 |
--------------------------------------------------------------------------------
/application/src/translations/km/services.ts:
--------------------------------------------------------------------------------
1 |
2 | import { ServicesTranslations } from '../types/services';
3 |
4 | export const servicesTranslations: ServicesTranslations = {
5 | serviceName: "ឈ្មោះសេវាកម្ម",
6 | serviceType: "ប្រភេទសេវាកម្ម",
7 | serviceStatus: "ស្ថានភាពសេវាកម្ម",
8 | responseTime: "ពេលវេលាឆ្លើយតប",
9 | uptime: "ពេលវេលាដំណើរការ",
10 | lastChecked: "ពិនិត្យចុងក្រោយ",
11 | noServices: "មិនមានសេវាកម្មដែលត្រូវនឹងលក្ខណៈវិនិច្ឆ័យរបស់អ្នក។",
12 | };
13 |
--------------------------------------------------------------------------------
/application/src/translations/km/settings.ts:
--------------------------------------------------------------------------------
1 |
2 | import { SettingsTranslations } from '../types/settings';
3 |
4 | export const settingsTranslations: SettingsTranslations = {
5 | // Tabs
6 | systemSettings: "ការកំណត់ប្រព័ន្ធ",
7 | mailSettings: "ការកំណត់សំបុត្រ",
8 |
9 | // System Settings
10 | appName: "ឈ្មោះកម្មវិធី",
11 | appURL: "URL កម្មវិធី",
12 | senderName: "ឈ្មោះអ្នកផ្ញើ",
13 | senderEmail: "អាសយដ្ឋានអ៊ីមែលអ្នកផ្ញើ",
14 | hideControls: "លាក់ការគ្រប់គ្រង",
15 |
16 | // Mail Settings
17 | smtpSettings: "ការកំណត់រចនាសម្ព័ន្ធ SMTP",
18 | smtpEnabled: "បើក SMTP",
19 | smtpHost: "ម៉ាស៊ីន SMTP",
20 | smtpPort: "ច្រក SMTP",
21 | smtpUsername: "ឈ្មោះអ្នកប្រើ SMTP",
22 | smtpPassword: "ពាក្យសម្ងាត់ SMTP",
23 | smtpAuthMethod: "វិធីសាស្ត្រផ្ទៀងផ្ទាត់",
24 | enableTLS: "បើក TLS",
25 | localName: "ឈ្មោះមូលដ្ឋាន",
26 |
27 | // Actions and status
28 | save: "រក្សាទុកការផ្លាស់ប្ដូរ",
29 | saving: "កំពុងរក្សាទុក...",
30 | settingsUpdated: "បានធ្វើបច្ចុប្បន្នភាពការកំណត់ដោយជោគជ័យ",
31 | errorSavingSettings: "មានបញ្ហាក្នុងការរក្សាទុកការកំណត់",
32 | errorFetchingSettings: "មានបញ្ហាក្នុងការទាញយកការកំណត់",
33 | testConnection: "សាកល្បងការតភ្ជាប់",
34 | testingConnection: "កំពុងសាកល្បងការតភ្ជាប់...",
35 | connectionSuccess: "ការតភ្ជាប់ជោគជ័យ",
36 | connectionFailed: "ការតភ្ជាប់បរាជ័យ"
37 | };
--------------------------------------------------------------------------------
/application/src/translations/types/about.ts:
--------------------------------------------------------------------------------
1 |
2 | export interface AboutTranslations {
3 | aboutReamStack: string;
4 | systemDescription: string;
5 | systemVersion: string;
6 | license: string;
7 | mitLicense: string;
8 | links: string;
9 | viewOnGithub: string;
10 | viewDocumentation: string;
11 | followOnX: string;
12 | joinDiscord: string;
13 | quickActions: string;
14 | quickActionsDescription: string;
15 | quickTips: string;
16 | }
17 |
--------------------------------------------------------------------------------
/application/src/translations/types/common.ts:
--------------------------------------------------------------------------------
1 |
2 | export interface CommonTranslations {
3 | welcome: string;
4 | logout: string;
5 | language: string;
6 | english: string;
7 | khmer: string;
8 | goodMorning: string;
9 | goodAfternoon: string;
10 | goodEvening: string;
11 | profile: string;
12 | settings: string;
13 | documentation: string;
14 | notifications: string;
15 | close: string;
16 | cancel: string;
17 | view: string;
18 | edit: string;
19 | delete: string;
20 | status: string;
21 | time: string;
22 | title: string;
23 | description: string;
24 | success: string;
25 | error: string;
26 | }
27 |
--------------------------------------------------------------------------------
/application/src/translations/types/incident.ts:
--------------------------------------------------------------------------------
1 |
2 | export interface IncidentTranslations {
3 | incidentManagement: string;
4 | incidentsManagementDesc: string;
5 | unresolvedIncidents: string;
6 | resolvedIncidents: string;
7 | activeIncidents: string;
8 | criticalIssues: string;
9 | avgResolutionTime: string;
10 | noIncidents: string;
11 | createIncident: string;
12 | investigating: string;
13 | identified: string;
14 | monitoring: string;
15 | resolved: string;
16 | scheduleIncidentManagement: string;
17 | incidentName: string;
18 | incidentStatus: string;
19 | highPriority: string;
20 | configurationSettings: string;
21 | incidentCreatedSuccess: string;
22 | basicInfo: string;
23 | serviceId: string;
24 | assignedTo: string;
25 | unassigned: string;
26 | timeline: string;
27 | incidentTime: string;
28 | resolutionTime: string;
29 | systems: string;
30 | noSystems: string;
31 | impactAnalysis: string;
32 | rootCause: string;
33 | resolutionSteps: string;
34 | lessonsLearned: string;
35 | resolutionDetails: string;
36 | assignment: string;
37 | download: string;
38 | downloadPdf: string;
39 | print: string;
40 | confidentialNote: string;
41 | generatedOn: string;
42 | enterResolutionSteps: string;
43 | enterLessonsLearned: string;
44 | editIncident: string;
45 | editIncidentDesc: string;
46 | updating: string;
47 | update: string;
48 | create: string;
49 | creating: string;
50 | configuration: string;
51 | failedToUpdateStatus: string;
52 | inProgress: string;
53 | }
54 |
--------------------------------------------------------------------------------
/application/src/translations/types/index.ts:
--------------------------------------------------------------------------------
1 |
2 | import { CommonTranslations } from './common';
3 | import { MenuTranslations } from './menu';
4 | import { LoginTranslations } from './login';
5 | import { AboutTranslations } from './about';
6 | import { ServicesTranslations } from './services';
7 | import { MaintenanceTranslations } from './maintenance';
8 | import { IncidentTranslations } from './incident';
9 | import { SSLTranslations } from './ssl';
10 | import { SettingsTranslations } from './settings';
11 |
12 | export interface Translations {
13 | common: CommonTranslations;
14 | menu: MenuTranslations;
15 | login: LoginTranslations;
16 | about: AboutTranslations;
17 | services: ServicesTranslations;
18 | maintenance: MaintenanceTranslations;
19 | incident: IncidentTranslations;
20 | ssl: SSLTranslations;
21 | settings: SettingsTranslations;
22 | }
--------------------------------------------------------------------------------
/application/src/translations/types/login.ts:
--------------------------------------------------------------------------------
1 |
2 | export interface LoginTranslations {
3 | signInToYourAccount: string;
4 | dontHaveAccount: string;
5 | createOne: string;
6 | signInWithGoogle: string;
7 | orContinueWith: string;
8 | email: string;
9 | password: string;
10 | forgot: string;
11 | signIn: string;
12 | signingIn: string;
13 | loginSuccessful: string;
14 | loginSuccessMessage: string;
15 | loginFailed: string;
16 | authenticationFailed: string;
17 | bySigningIn: string;
18 | termsAndConditions: string;
19 | and: string;
20 | privacyPolicy: string;
21 | }
22 |
--------------------------------------------------------------------------------
/application/src/translations/types/menu.ts:
--------------------------------------------------------------------------------
1 |
2 | export interface MenuTranslations {
3 | uptimeMonitoring: string;
4 | instanceMonitoring: string;
5 | sslDomain: string;
6 | scheduleIncident: string;
7 | operationalPage: string;
8 | reports: string;
9 | apiDocumentation: string;
10 | settingPanel: string;
11 | generalSettings: string;
12 | userManagement: string;
13 | notificationSettings: string;
14 | alertsTemplates: string;
15 | rolesManagement: string;
16 | dataRetention: string;
17 | backupSettings: string;
18 | aboutSystem: string;
19 | }
20 |
--------------------------------------------------------------------------------
/application/src/translations/types/services.ts:
--------------------------------------------------------------------------------
1 |
2 | export interface ServicesTranslations {
3 | serviceName: string;
4 | serviceType: string;
5 | serviceStatus: string;
6 | responseTime: string;
7 | uptime: string;
8 | lastChecked: string;
9 | noServices: string;
10 | }
11 |
--------------------------------------------------------------------------------
/application/src/translations/types/settings.ts:
--------------------------------------------------------------------------------
1 |
2 | export interface SettingsTranslations {
3 | // Tabs
4 | systemSettings: string;
5 | mailSettings: string;
6 |
7 | // System Settings
8 | appName: string;
9 | appURL: string;
10 | senderName: string;
11 | senderEmail: string;
12 | hideControls: string;
13 |
14 | // Mail Settings
15 | smtpSettings?: string;
16 | smtpEnabled: string;
17 | smtpHost: string;
18 | smtpPort: string;
19 | smtpUsername: string;
20 | smtpPassword: string;
21 | smtpAuthMethod: string;
22 | enableTLS: string;
23 | localName: string;
24 |
25 | // Actions and status
26 | save: string;
27 | saving: string;
28 | settingsUpdated: string;
29 | errorSavingSettings: string;
30 | errorFetchingSettings: string;
31 | testConnection: string;
32 | testingConnection: string;
33 | connectionSuccess: string;
34 | connectionFailed: string;
35 | }
--------------------------------------------------------------------------------
/application/src/types/jspdf-autotable.d.ts:
--------------------------------------------------------------------------------
1 | import 'jspdf';
2 |
3 | declare module 'jspdf' {
4 | interface jsPDF {
5 | lastAutoTable: {
6 | finalY: number;
7 | };
8 | }
9 | }
--------------------------------------------------------------------------------
/application/src/types/service.types.ts:
--------------------------------------------------------------------------------
1 |
2 | export interface Service {
3 | id: string;
4 | name: string;
5 | url: string;
6 | type: "HTTP" | "HTTPS" | "TCP" | "DNS" | "PING" | "HTTP" | "http" | "https" | "tcp" | "dns" | "ping" | "smtp" | "icmp";
7 | status: "up" | "down" | "paused" | "pending" | "warning";
8 | responseTime: number;
9 | uptime: number;
10 | lastChecked: string;
11 | interval: number;
12 | retries: number;
13 | notificationChannel?: string;
14 | alertTemplate?: string;
15 | muteAlerts?: boolean; // Keep this to avoid breaking existing code
16 | alerts?: "muted" | "unmuted"; // Make sure alerts is properly typed as union
17 | muteChangedAt?: string;
18 | }
19 |
20 | export interface CreateServiceParams {
21 | name: string;
22 | url: string;
23 | type: string;
24 | interval: number;
25 | retries: number;
26 | notificationChannel?: string;
27 | alertTemplate?: string;
28 | }
29 |
30 | export interface UptimeData {
31 | date?: string;
32 | uptime?: number;
33 | id?: string;
34 | serviceId?: string;
35 | timestamp: string;
36 | status: "up" | "down" | "paused" | "pending" | "warning";
37 | responseTime: number;
38 | }
39 |
--------------------------------------------------------------------------------
/application/src/types/ssl.types.ts:
--------------------------------------------------------------------------------
1 |
2 | export interface SSLCertificate {
3 | id: string;
4 | domain: string;
5 | issued_to: string;
6 | issuer_o: string;
7 | status: string;
8 | cert_sans?: string;
9 | cert_alg?: string;
10 | serial_number?: number | string;
11 | valid_from: string;
12 | valid_till: string;
13 | validity_days: number;
14 | days_left: number;
15 | valid_days_to_expire?: number;
16 | warning_threshold: number;
17 | expiry_threshold: number;
18 | notification_channel: string;
19 | last_notified?: string;
20 | created?: string;
21 | updated?: string;
22 | // New fields based on the provided structure
23 | collectionId?: string;
24 | collectionName?: string;
25 | resolved_ip?: string;
26 | issuer_cn?: string;
27 | }
28 |
29 | export interface AddSSLCertificateDto {
30 | domain: string;
31 | warning_threshold: number;
32 | expiry_threshold: number;
33 | notification_channel: string;
34 | }
--------------------------------------------------------------------------------
/application/src/vite-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/application/tsconfig.app.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ES2020",
4 | "useDefineForClassFields": true,
5 | "lib": ["ES2020", "DOM", "DOM.Iterable"],
6 | "module": "ESNext",
7 | "skipLibCheck": true,
8 |
9 | /* Bundler mode */
10 | "moduleResolution": "bundler",
11 | "allowImportingTsExtensions": true,
12 | "isolatedModules": true,
13 | "moduleDetection": "force",
14 | "noEmit": true,
15 | "jsx": "react-jsx",
16 |
17 | /* Linting */
18 | "strict": false,
19 | "noUnusedLocals": false,
20 | "noUnusedParameters": false,
21 | "noImplicitAny": false,
22 | "noFallthroughCasesInSwitch": false,
23 |
24 | "baseUrl": ".",
25 | "paths": {
26 | "@/*": ["./src/*"]
27 | }
28 | },
29 | "include": ["src"]
30 | }
31 |
--------------------------------------------------------------------------------
/application/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "files": [],
3 | "references": [
4 | { "path": "./tsconfig.app.json" },
5 | { "path": "./tsconfig.node.json" }
6 | ],
7 | "compilerOptions": {
8 | "baseUrl": ".",
9 | "paths": {
10 | "@/*": ["./src/*"]
11 | },
12 | "noImplicitAny": false,
13 | "noUnusedParameters": false,
14 | "skipLibCheck": true,
15 | "allowJs": true,
16 | "noUnusedLocals": false,
17 | "strictNullChecks": false
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/application/tsconfig.node.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ES2022",
4 | "lib": ["ES2023"],
5 | "module": "ESNext",
6 | "skipLibCheck": true,
7 |
8 | /* Bundler mode */
9 | "moduleResolution": "bundler",
10 | "allowImportingTsExtensions": true,
11 | "isolatedModules": true,
12 | "moduleDetection": "force",
13 | "noEmit": true,
14 |
15 | /* Linting */
16 | "strict": true,
17 | "noUnusedLocals": false,
18 | "noUnusedParameters": false,
19 | "noFallthroughCasesInSwitch": true
20 | },
21 | "include": ["vite.config.ts"]
22 | }
23 |
--------------------------------------------------------------------------------
/application/vite.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from "vite";
2 | import react from "@vitejs/plugin-react-swc";
3 | import path from "path";
4 |
5 | // https://vitejs.dev/config/
6 | export default defineConfig(({ mode }) => ({
7 | server: {
8 | host: "::",
9 | port: 8990,
10 | },
11 | plugins: [
12 | react(),
13 | ],
14 | resolve: {
15 | alias: {
16 | "@": path.resolve(__dirname, "./src"),
17 | },
18 | },
19 | }));
20 |
--------------------------------------------------------------------------------
/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: '3.9'
2 | services:
3 | checkcle:
4 | image: operacle/checkcle:latest
5 | container_name: checkcle
6 | restart: unless-stopped
7 | ports:
8 | - "8090:8090" # Web Application
9 | volumes:
10 | - /opt/pb_data:/mnt/pb_data # Host directory mapped to container path
11 | ulimits:
12 | nofile:
13 | soft: 4096
14 | hard: 8192
--------------------------------------------------------------------------------
/docker/docker-compose-dev.yml:
--------------------------------------------------------------------------------
1 | version: '3.9'
2 |
3 | services:
4 | checkcle:
5 | build:
6 | context: .
7 | dockerfile: Dockerfile
8 | container_name: checkcle
9 | restart: unless-stopped
10 | ports:
11 | - "8090:8090"
12 | volumes:
13 | - /var/pb_data:/mnt/pb_data # Updated mount target to match CMD in Dockerfile
14 | ulimits:
15 | nofile:
16 | soft: 4096
17 | hard: 8192
18 |
--------------------------------------------------------------------------------
/docker/docker-compose.prod.yml:
--------------------------------------------------------------------------------
1 | version: '3.9'
2 |
3 | services:
4 | checkcle:
5 | image: operacle/checkcle:latest
6 | container_name: checkcle
7 | restart: unless-stopped
8 | ports:
9 | - "8090:8090" # Web Application
10 | volumes:
11 | - /opt/pb_data:/mnt/pb_data # Host directory mapped to container path
12 | ulimits:
13 | nofile:
14 | soft: 4096
15 | hard: 8192
--------------------------------------------------------------------------------
/package-lock.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "checkcle-dev",
3 | "lockfileVersion": 3,
4 | "requires": true,
5 | "packages": {}
6 | }
7 |
--------------------------------------------------------------------------------
/scripts/entrypoint.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | if [ -z "$(ls -A /app/pb_data)" ]; then
3 | echo "Initializing pb_data from image..."
4 | cp -r /app/pb_data_seed/* /app/pb_data/
5 | fi
6 |
7 | exec /app/pocketbase serve --dir /app/pb_data
8 |
--------------------------------------------------------------------------------
/scripts/setup.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | set -e
4 |
5 | REPO_URL="https://github.com/operacle/checkcle.git"
6 | CLONE_DIR="/opt/checkcle"
7 | DATA_DIR="/opt/pb_data"
8 | PORT=8090
9 |
10 | echo "🚀 Installing Checkcle from $REPO_URL"
11 |
12 | # Step 1: Check if port 8090 is already in use
13 | if lsof -i :"$PORT" &>/dev/null; then
14 | echo "❗ ERROR: Port $PORT is already in use. Please free the port or change the Docker Compose configuration."
15 | exit 1
16 | fi
17 |
18 | # Step 2: Ensure Docker is installed
19 | if ! command -v docker &> /dev/null; then
20 | echo "🔧 Docker not found. Installing Docker..."
21 | curl -fsSL https://get.docker.com | sh
22 | fi
23 |
24 | # Step 3: Ensure Docker Compose v2 is available
25 | if ! docker compose version &> /dev/null; then
26 | echo "❗ Docker Compose v2 not found. Please install Docker Compose v2 and rerun this script."
27 | exit 1
28 | fi
29 |
30 | # Step 4: Clone the repository
31 | if [ -d "$CLONE_DIR" ]; then
32 | echo "📁 Directory $CLONE_DIR already exists. Pulling latest changes..."
33 | git -C "$CLONE_DIR" pull
34 | else
35 | echo "📥 Cloning repo to $CLONE_DIR"
36 | git clone "$REPO_URL" "$CLONE_DIR"
37 | fi
38 |
39 | # Step 5: Create data volume directory if it doesn’t exist
40 | if [ ! -d "$DATA_DIR" ]; then
41 | echo "📁 Creating data volume directory at $DATA_DIR"
42 | sudo mkdir -p "$DATA_DIR"
43 | sudo chown "$(whoami)":"$(whoami)" "$DATA_DIR"
44 | fi
45 |
46 | # Step 6: Start the service
47 | cd "$CLONE_DIR"
48 | echo "📦 Starting Checkcle service with Docker Compose..."
49 | docker compose up -d
50 |
51 | # Step 7: Show success output
52 | echo ""
53 | echo "✅ Checkcle has been successfully installed and started."
54 | echo ""
55 | echo "🛠️ Admin Web Management"
56 | echo "🔗 Default URL: http://0.0.0.0:$PORT"
57 | echo "👤 User: admin@example.com"
58 | echo "🔑 Passwd: Admin123456"
59 | echo ""
60 | echo "📌 Make sure port $PORT is accessible from your host system or cloud firewall."
61 |
--------------------------------------------------------------------------------
/server/pb_data/auxiliary.db:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/operacle/checkcle/4dc005c7e4afe7aa2733c00d8434b5e97a804fa9/server/pb_data/auxiliary.db
--------------------------------------------------------------------------------
/server/pb_data/auxiliary.db-shm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/operacle/checkcle/4dc005c7e4afe7aa2733c00d8434b5e97a804fa9/server/pb_data/auxiliary.db-shm
--------------------------------------------------------------------------------
/server/pb_data/auxiliary.db-wal:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/operacle/checkcle/4dc005c7e4afe7aa2733c00d8434b5e97a804fa9/server/pb_data/auxiliary.db-wal
--------------------------------------------------------------------------------
/server/pb_data/data.db:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/operacle/checkcle/4dc005c7e4afe7aa2733c00d8434b5e97a804fa9/server/pb_data/data.db
--------------------------------------------------------------------------------
/server/pb_migrations/1746786764_updated_services.js:
--------------------------------------------------------------------------------
1 | ///
2 | migrate((app) => {
3 | const collection = app.findCollectionByNameOrId("pbc_863811952")
4 |
5 | // update collection data
6 | unmarshal({
7 | "createRule": null,
8 | "deleteRule": null,
9 | "listRule": null,
10 | "updateRule": null,
11 | "viewRule": null
12 | }, collection)
13 |
14 | return app.save(collection)
15 | }, (app) => {
16 | const collection = app.findCollectionByNameOrId("pbc_863811952")
17 |
18 | // update collection data
19 | unmarshal({
20 | "createRule": "",
21 | "deleteRule": "",
22 | "listRule": "",
23 | "updateRule": "",
24 | "viewRule": ""
25 | }, collection)
26 |
27 | return app.save(collection)
28 | })
29 |
--------------------------------------------------------------------------------
/server/pb_migrations/1746786820_updated_services.js:
--------------------------------------------------------------------------------
1 | ///
2 | migrate((app) => {
3 | const collection = app.findCollectionByNameOrId("pbc_863811952")
4 |
5 | // update collection data
6 | unmarshal({
7 | "createRule": "id = @request.auth.id",
8 | "deleteRule": "id = @request.auth.id",
9 | "listRule": "id = @request.auth.id",
10 | "updateRule": "id = @request.auth.id",
11 | "viewRule": "id = @request.auth.id"
12 | }, collection)
13 |
14 | return app.save(collection)
15 | }, (app) => {
16 | const collection = app.findCollectionByNameOrId("pbc_863811952")
17 |
18 | // update collection data
19 | unmarshal({
20 | "createRule": null,
21 | "deleteRule": null,
22 | "listRule": null,
23 | "updateRule": null,
24 | "viewRule": null
25 | }, collection)
26 |
27 | return app.save(collection)
28 | })
29 |
--------------------------------------------------------------------------------
/server/pb_migrations/1746786864_updated_services.js:
--------------------------------------------------------------------------------
1 | ///
2 | migrate((app) => {
3 | const collection = app.findCollectionByNameOrId("pbc_863811952")
4 |
5 | // update collection data
6 | unmarshal({
7 | "listRule": ""
8 | }, collection)
9 |
10 | return app.save(collection)
11 | }, (app) => {
12 | const collection = app.findCollectionByNameOrId("pbc_863811952")
13 |
14 | // update collection data
15 | unmarshal({
16 | "listRule": "id = @request.auth.id"
17 | }, collection)
18 |
19 | return app.save(collection)
20 | })
21 |
--------------------------------------------------------------------------------
/server/pb_migrations/1746787173_updated_services.js:
--------------------------------------------------------------------------------
1 | ///
2 | migrate((app) => {
3 | const collection = app.findCollectionByNameOrId("pbc_863811952")
4 |
5 | // update collection data
6 | unmarshal({
7 | "createRule": "",
8 | "deleteRule": "",
9 | "updateRule": "",
10 | "viewRule": ""
11 | }, collection)
12 |
13 | return app.save(collection)
14 | }, (app) => {
15 | const collection = app.findCollectionByNameOrId("pbc_863811952")
16 |
17 | // update collection data
18 | unmarshal({
19 | "createRule": "id = @request.auth.id",
20 | "deleteRule": "id = @request.auth.id",
21 | "updateRule": "id = @request.auth.id",
22 | "viewRule": "id = @request.auth.id"
23 | }, collection)
24 |
25 | return app.save(collection)
26 | })
27 |
--------------------------------------------------------------------------------
/server/pb_migrations/1746787500_updated_services.js:
--------------------------------------------------------------------------------
1 | ///
2 | migrate((app) => {
3 | const collection = app.findCollectionByNameOrId("pbc_863811952")
4 |
5 | // update collection data
6 | unmarshal({
7 | "listRule": "@request.auth.id != \"\""
8 | }, collection)
9 |
10 | return app.save(collection)
11 | }, (app) => {
12 | const collection = app.findCollectionByNameOrId("pbc_863811952")
13 |
14 | // update collection data
15 | unmarshal({
16 | "listRule": ""
17 | }, collection)
18 |
19 | return app.save(collection)
20 | })
21 |
--------------------------------------------------------------------------------
/server/pb_migrations/1746787517_updated_services.js:
--------------------------------------------------------------------------------
1 | ///
2 | migrate((app) => {
3 | const collection = app.findCollectionByNameOrId("pbc_863811952")
4 |
5 | // update collection data
6 | unmarshal({
7 | "createRule": "@request.auth.id != \"\"",
8 | "deleteRule": "@request.auth.id != \"\"",
9 | "updateRule": "@request.auth.id != \"\"",
10 | "viewRule": "@request.auth.id != \"\""
11 | }, collection)
12 |
13 | return app.save(collection)
14 | }, (app) => {
15 | const collection = app.findCollectionByNameOrId("pbc_863811952")
16 |
17 | // update collection data
18 | unmarshal({
19 | "createRule": "",
20 | "deleteRule": "",
21 | "updateRule": "",
22 | "viewRule": ""
23 | }, collection)
24 |
25 | return app.save(collection)
26 | })
27 |
--------------------------------------------------------------------------------
/server/pb_migrations/1746787595_updated_uptime_data.js:
--------------------------------------------------------------------------------
1 | ///
2 | migrate((app) => {
3 | const collection = app.findCollectionByNameOrId("pbc_67962705")
4 |
5 | // update collection data
6 | unmarshal({
7 | "createRule": "@request.auth.id != \"\"",
8 | "deleteRule": "@request.auth.id != \"\"",
9 | "listRule": "@request.auth.id != \"\"",
10 | "updateRule": "@request.auth.id != \"\"",
11 | "viewRule": "@request.auth.id != \"\""
12 | }, collection)
13 |
14 | return app.save(collection)
15 | }, (app) => {
16 | const collection = app.findCollectionByNameOrId("pbc_67962705")
17 |
18 | // update collection data
19 | unmarshal({
20 | "createRule": "",
21 | "deleteRule": "",
22 | "listRule": "",
23 | "updateRule": "",
24 | "viewRule": ""
25 | }, collection)
26 |
27 | return app.save(collection)
28 | })
29 |
--------------------------------------------------------------------------------
/server/pb_migrations/1746787681_updated_alert_configurations.js:
--------------------------------------------------------------------------------
1 | ///
2 | migrate((app) => {
3 | const collection = app.findCollectionByNameOrId("pbc_1938176441")
4 |
5 | // update collection data
6 | unmarshal({
7 | "createRule": "@request.auth.id != \"\"",
8 | "deleteRule": "@request.auth.id != \"\"",
9 | "listRule": "@request.auth.id != \"\"",
10 | "updateRule": "@request.auth.id != \"\"",
11 | "viewRule": "@request.auth.id != \"\""
12 | }, collection)
13 |
14 | return app.save(collection)
15 | }, (app) => {
16 | const collection = app.findCollectionByNameOrId("pbc_1938176441")
17 |
18 | // update collection data
19 | unmarshal({
20 | "createRule": "",
21 | "deleteRule": "",
22 | "listRule": "",
23 | "updateRule": "",
24 | "viewRule": ""
25 | }, collection)
26 |
27 | return app.save(collection)
28 | })
29 |
--------------------------------------------------------------------------------
/server/pb_migrations/1748029497_updated__superusers.js:
--------------------------------------------------------------------------------
1 | ///
2 | migrate((app) => {
3 | const collection = app.findCollectionByNameOrId("pbc_3142635823")
4 |
5 | // add field
6 | collection.fields.addAt(6, new Field({
7 | "autogeneratePattern": "",
8 | "hidden": false,
9 | "id": "text3687080900",
10 | "max": 0,
11 | "min": 0,
12 | "name": "full_name",
13 | "pattern": "",
14 | "presentable": false,
15 | "primaryKey": false,
16 | "required": false,
17 | "system": false,
18 | "type": "text"
19 | }))
20 |
21 | // add field
22 | collection.fields.addAt(7, new Field({
23 | "autogeneratePattern": "",
24 | "hidden": false,
25 | "id": "text4166911607",
26 | "max": 0,
27 | "min": 0,
28 | "name": "username",
29 | "pattern": "",
30 | "presentable": false,
31 | "primaryKey": false,
32 | "required": false,
33 | "system": false,
34 | "type": "text"
35 | }))
36 |
37 | // add field
38 | collection.fields.addAt(8, new Field({
39 | "hidden": false,
40 | "id": "select2063623452",
41 | "maxSelect": 1,
42 | "name": "status",
43 | "presentable": false,
44 | "required": false,
45 | "system": false,
46 | "type": "select",
47 | "values": [
48 | "active",
49 | "inactive"
50 | ]
51 | }))
52 |
53 | return app.save(collection)
54 | }, (app) => {
55 | const collection = app.findCollectionByNameOrId("pbc_3142635823")
56 |
57 | // remove field
58 | collection.fields.removeById("text3687080900")
59 |
60 | // remove field
61 | collection.fields.removeById("text4166911607")
62 |
63 | // remove field
64 | collection.fields.removeById("select2063623452")
65 |
66 | return app.save(collection)
67 | })
68 |
--------------------------------------------------------------------------------
/server/pocketbase:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/operacle/checkcle/4dc005c7e4afe7aa2733c00d8434b5e97a804fa9/server/pocketbase
--------------------------------------------------------------------------------