Merge pull request #25 from operacle/develop

feat: Auto logout after password change
This commit is contained in:
Tola Leng
2025-05-28 15:37:53 +07:00
committed by GitHub
14 changed files with 309 additions and 358 deletions
+6 -1
View File
@@ -85,7 +85,8 @@ services:
- [ ] Infrastructure Server Monitoring - [ ] Infrastructure Server Monitoring
- [ ] Operational Status / Public Status Pages - [ ] Operational Status / Public Status Pages
- [ ] Uptime monitoring (TCP, PING, DNS) - [ ] Uptime monitoring (TCP, PING, DNS)
- [ ] User Permission Roles & Service Group - [x] System Setting Panel and Mail Settings
- [x] User Permission Roles
- [ ] Notifications (Email/Slack/Discord/Signal) - [ ] Notifications (Email/Slack/Discord/Signal)
- [x] Open-source release with full documentation - [x] Open-source release with full documentation
@@ -122,4 +123,8 @@ CheckCle is released under the MIT License.
--- ---
## Star History
[![Star History Chart](https://api.star-history.com/svg?repos=operacle/checkcle&type=Date)](https://www.star-history.com/#operacle/checkcle&Date)
Stay informed, stay online, with CheckCle! 🌐 Stay informed, stay online, with CheckCle! 🌐
@@ -1,4 +1,3 @@
import { useState } from "react"; import { useState } from "react";
import { z } from "zod"; import { z } from "zod";
import { useForm } from "react-hook-form"; import { useForm } from "react-hook-form";
@@ -10,6 +9,7 @@ import { useToast } from "@/hooks/use-toast";
import { Eye, EyeOff } from "lucide-react"; import { Eye, EyeOff } from "lucide-react";
import { pb } from "@/lib/pocketbase"; import { pb } from "@/lib/pocketbase";
import { authService } from "@/services/authService"; import { authService } from "@/services/authService";
import { useNavigate } from "react-router-dom";
// Password change form schema // Password change form schema
const passwordFormSchema = z.object({ const passwordFormSchema = z.object({
@@ -34,6 +34,7 @@ export function ChangePasswordForm({ userId }: ChangePasswordFormProps) {
const [showConfirmPassword, setShowConfirmPassword] = useState(false); const [showConfirmPassword, setShowConfirmPassword] = useState(false);
const { toast } = useToast(); const { toast } = useToast();
const navigate = useNavigate();
const form = useForm<PasswordFormValues>({ const form = useForm<PasswordFormValues>({
resolver: zodResolver(passwordFormSchema), resolver: zodResolver(passwordFormSchema),
@@ -44,12 +45,35 @@ export function ChangePasswordForm({ userId }: ChangePasswordFormProps) {
}, },
}); });
// Function to determine which collection the user belongs to
const getUserCollection = async (userId: string): Promise<string> => {
try {
// First try to find the user in the regular users collection
await pb.collection('users').getOne(userId);
return 'users';
} catch (error) {
try {
// If not found, try the superadmin collection
await pb.collection('_superusers').getOne(userId);
return '_superusers';
} catch (error) {
throw new Error('User not found in any collection');
}
}
};
async function onSubmit(data: PasswordFormValues) { async function onSubmit(data: PasswordFormValues) {
setIsSubmitting(true); setIsSubmitting(true);
try { try {
console.log("Starting password change for user:", userId);
// Determine which collection the user belongs to
const collection = await getUserCollection(userId);
console.log("User found in collection:", collection);
// PocketBase requires the old password along with the new one // PocketBase requires the old password along with the new one
await pb.collection('users').update(userId, { await pb.collection(collection).update(userId, {
oldPassword: data.currentPassword, oldPassword: data.currentPassword,
password: data.newPassword, password: data.newPassword,
passwordConfirm: data.confirmPassword, passwordConfirm: data.confirmPassword,
@@ -60,17 +84,31 @@ export function ChangePasswordForm({ userId }: ChangePasswordFormProps) {
toast({ toast({
title: "Password updated", title: "Password updated",
description: "Your password has been changed successfully.", description: "Your password has been changed successfully. You will be logged out in 3 seconds for security.",
}); });
// Reset the form // Reset the form
form.reset(); form.reset();
// Auto logout after successful password change
setTimeout(() => {
console.log("Auto logout after password change");
authService.logout();
navigate("/login");
}, 3000);
} catch (error) { } catch (error) {
console.error("Password change error:", error); console.error("Password change error:", error);
let errorMessage = "Failed to update password. Please try again."; let errorMessage = "Failed to update password. Please try again.";
if (error instanceof Error) { if (error instanceof Error) {
errorMessage = error.message; if (error.message.includes("Failed to authenticate")) {
errorMessage = "Current password is incorrect. Please try again.";
} else if (error.message.includes("User not found")) {
errorMessage = "User account not found. Please contact your administrator.";
} else {
errorMessage = error.message;
}
} }
toast({ toast({
@@ -188,4 +226,4 @@ export function ChangePasswordForm({ userId }: ChangePasswordFormProps) {
</form> </form>
</Form> </Form>
); );
} }
@@ -1,7 +1,6 @@
import React from 'react'; import React from 'react';
import { Input } from "@/components/ui/input"; import { Input } from "@/components/ui/input";
import { Switch } from "@/components/ui/switch";
import { FormControl, FormField, FormItem, FormLabel } from "@/components/ui/form"; import { FormControl, FormField, FormItem, FormLabel } from "@/components/ui/form";
import { useLanguage } from "@/contexts/LanguageContext"; import { useLanguage } from "@/contexts/LanguageContext";
import { SettingsTabProps } from "./types"; import { SettingsTabProps } from "./types";
@@ -48,7 +47,7 @@ const SystemSettingsTab: React.FC<SettingsTabProps> = ({ form, isEditing, settin
<Input <Input
{...field} {...field}
disabled={!isEditing} disabled={!isEditing}
placeholder="https://localhost:8090" placeholder="https://pb-api.k8sops.asia"
className="bg-background border-input text-foreground placeholder:text-muted-foreground disabled:bg-muted disabled:text-muted-foreground" className="bg-background border-input text-foreground placeholder:text-muted-foreground disabled:bg-muted disabled:text-muted-foreground"
/> />
</FormControl> </FormControl>
@@ -57,72 +56,6 @@ const SystemSettingsTab: React.FC<SettingsTabProps> = ({ form, isEditing, settin
/> />
</div> </div>
</div> </div>
<div>
<FormField
control={form.control}
name="meta.senderName"
render={({ field }) => (
<FormItem>
<FormLabel className="text-sm font-medium text-foreground">
{t("senderName", "settings")}
</FormLabel>
<FormControl>
<Input
{...field}
disabled={!isEditing}
placeholder="System Administrator"
className="bg-background border-input text-foreground placeholder:text-muted-foreground disabled:bg-muted disabled:text-muted-foreground"
/>
</FormControl>
</FormItem>
)}
/>
</div>
<div>
<FormField
control={form.control}
name="meta.senderAddress"
render={({ field }) => (
<FormItem>
<FormLabel className="text-sm font-medium text-foreground">
{t("senderEmail", "settings")}
</FormLabel>
<FormControl>
<Input
{...field}
disabled={!isEditing}
placeholder="admin@example.com"
type="email"
className="bg-background border-input text-foreground placeholder:text-muted-foreground disabled:bg-muted disabled:text-muted-foreground"
/>
</FormControl>
</FormItem>
)}
/>
</div>
<div className="flex items-center space-x-3 pt-4">
<FormField
control={form.control}
name="meta.hideControls"
render={({ field }) => (
<FormItem className="flex flex-row items-center space-x-3 space-y-0">
<FormControl>
<Switch
checked={field.value}
onCheckedChange={field.onChange}
disabled={!isEditing}
/>
</FormControl>
<FormLabel className="text-sm font-medium text-foreground cursor-pointer">
{t("hideControls", "settings")}
</FormLabel>
</FormItem>
)}
/>
</div>
</div> </div>
); );
}; };
+3 -3
View File
@@ -12,9 +12,9 @@ export const aboutTranslations: AboutTranslations = {
viewDocumentation: "Dokumentation ansehen", viewDocumentation: "Dokumentation ansehen",
followOnX: "Auf X folgen", followOnX: "Auf X folgen",
joinDiscord: "Discord beitreten", joinDiscord: "Discord beitreten",
quickActions: "Quick Actions", quickActions: "Schnelle Aktionen",
quickActionsDescription: "Access common monitoring operations and features quickly. Select an action below to get started.", quickActionsDescription: "Greifen Sie schnell auf gängige Überwachungsvorgänge und -funktionen zu. Wählen Sie unten eine Aktion aus, um loszulegen.",
quickTips: "Quick Tips", quickTips: "Schnelle Tipps",
}; };
+14 -14
View File
@@ -11,19 +11,19 @@ export const commonTranslations: CommonTranslations = {
goodMorning: "Guten Morgen", goodMorning: "Guten Morgen",
goodAfternoon: "Guten Nachmittag", goodAfternoon: "Guten Nachmittag",
goodEvening: "Guten Abend", goodEvening: "Guten Abend",
profile: "Profile", profile: "Profil",
settings: "Settings", settings: "Einstellungen",
documentation: "Documentation", documentation: "Dokumentation",
notifications: "Notifications", notifications: "Benachrichtigungen",
close: "Close", close: "Schließen",
cancel: "Cancel", cancel: "Abbrechen",
view: "View", view: "Anzeigen",
edit: "Edit", edit: "Bearbeiten",
delete: "Delete", delete: "Löschen",
status: "Status", status: "Status",
time: "Time", time: "Zeit",
title: "Title", title: "Titel",
description: "Description", description: "Beschreibung",
success: "Success", success: "Erfolg",
error: "Error", error: "Fehler",
}; };
+50 -51
View File
@@ -1,55 +1,54 @@
import { IncidentTranslations } from '../types/incident'; import { IncidentTranslations } from '../types/incident';
export const incidentTranslations: IncidentTranslations = { export const incidentTranslations: IncidentTranslations = {
incidentManagement: 'Incident Management', incidentManagement: 'Vorfallmanagement',
incidentsManagementDesc: 'Track and manage service incidents and their resolutions', incidentsManagementDesc: 'Verfolgen und Verwalten von Servicevorfällen und deren Lösungen',
unresolvedIncidents: 'Unresolved', unresolvedIncidents: 'Ungelöst',
resolvedIncidents: 'Resolved', resolvedIncidents: 'Gelöst',
activeIncidents: 'Active Incidents', activeIncidents: 'Aktive Vorfälle',
criticalIssues: 'Critical Issues', criticalIssues: 'Kritische Probleme',
avgResolutionTime: 'Avg. Resolution Time', avgResolutionTime: 'Durchschn. Lösungszeit',
noIncidents: 'No Active Incidents', noIncidents: 'Keine aktiven Vorfälle',
createIncident: 'Create Incident', createIncident: 'Vorfall erstellen',
investigating: 'Investigating', investigating: 'Untersuchung läuft',
identified: 'Identified', identified: 'Identifiziert',
monitoring: 'Monitoring', monitoring: 'Überwachung',
resolved: 'Resolved', resolved: 'Gelöst',
scheduleIncidentManagement: 'Schedule & Incident Management', scheduleIncidentManagement: 'Zeitplanung & Vorfallmanagement',
incidentName: 'Incident Name', incidentName: 'Vorfallname',
incidentStatus: 'Incident Status', incidentStatus: 'Vorfallstatus',
highPriority: 'High Priority', highPriority: 'Hohe Priorität',
configurationSettings: 'Configuration Settings', configurationSettings: 'Konfigurationseinstellungen',
incidentCreatedSuccess: 'Incident created successfully', incidentCreatedSuccess: 'Vorfall erfolgreich erstellt',
basicInfo: 'Basic Information', basicInfo: 'Grundinformationen',
serviceId: 'Service ID', serviceId: 'Service-ID',
assignedTo: 'Assigned To', assignedTo: 'Zugewiesen an',
unassigned: 'Unassigned', unassigned: 'Nicht zugewiesen',
timeline: 'Timeline', timeline: 'Zeitverlauf',
incidentTime: 'Incident Time', incidentTime: 'Vorfallszeit',
resolutionTime: 'Resolution Time', resolutionTime: 'Lösungszeit',
systems: 'Systems', systems: 'Systeme',
noSystems: 'No systems affected', noSystems: 'Keine betroffenen Systeme',
impactAnalysis: 'Impact Analysis', impactAnalysis: 'Auswirkungsanalyse',
rootCause: 'Root Cause', rootCause: 'Ursache',
resolutionSteps: 'Resolution Steps', resolutionSteps: 'Lösungsschritte',
lessonsLearned: 'Lessons Learned', lessonsLearned: 'Erkenntnisse',
resolutionDetails: 'Resolution Details', resolutionDetails: 'Details zur Lösung',
assignment: 'Assignment', assignment: 'Zuweisung',
download: 'Download', download: 'Herunterladen',
downloadPdf: 'Download PDF', downloadPdf: 'PDF herunterladen',
print: 'Print', print: 'Drucken',
confidentialNote: 'This document is confidential and intended for internal use only.', confidentialNote: 'Dieses Dokument ist vertraulich und nur für den internen Gebrauch bestimmt.',
generatedOn: 'Generated on', generatedOn: 'Erstellt am',
enterResolutionSteps: 'Enter steps taken to resolve the incident', enterResolutionSteps: 'Geben Sie die zur Lösung ergriffenen Schritte ein',
enterLessonsLearned: 'Enter lessons learned from this incident', enterLessonsLearned: 'Geben Sie die aus diesem Vorfall gewonnenen Erkenntnisse ein',
editIncident: 'Edit Incident', editIncident: 'Vorfall bearbeiten',
editIncidentDesc: 'Update details for this incident', editIncidentDesc: 'Details zu diesem Vorfall aktualisieren',
updating: 'Updating...', updating: 'Aktualisiere...',
update: 'Update', update: 'Aktualisieren',
create: 'Create', create: 'Erstellen',
creating: 'Creating...', creating: 'Erstelle...',
configuration: 'Configuration', configuration: 'Konfiguration',
failedToUpdateStatus: 'Failed to update status', failedToUpdateStatus: 'Fehler beim Aktualisieren des Status',
inProgress: 'In Progress', inProgress: 'In Bearbeitung',
}; };
+62 -63
View File
@@ -1,68 +1,67 @@
import { MaintenanceTranslations } from '../types/maintenance'; import { MaintenanceTranslations } from '../types/maintenance';
export const maintenanceTranslations: MaintenanceTranslations = { export const maintenanceTranslations: MaintenanceTranslations = {
scheduledMaintenance: 'Scheduled Maintenance', scheduledMaintenance: 'Geplante Wartung',
scheduledMaintenanceDesc: 'View and manage planned maintenance windows for your systems and services', scheduledMaintenanceDesc: 'Anzeigen und Verwalten geplanter Wartungsfenster für Ihre Systeme und Dienste',
upcomingMaintenance: 'Upcoming', upcomingMaintenance: 'Bevorstehend',
ongoingMaintenance: 'Ongoing', ongoingMaintenance: 'Laufend',
completedMaintenance: 'Completed', completedMaintenance: 'Abgeschlossen',
createMaintenanceWindow: 'Create Maintenance', createMaintenanceWindow: 'Wartung erstellen',
totalScheduledHours: 'Total Scheduled Hours', totalScheduledHours: 'Geplante Gesamtstunden',
maintenanceName: 'Maintenance Name', maintenanceName: 'Wartungsname',
maintenanceStatus: 'Status', maintenanceStatus: 'Status',
scheduledStart: 'Scheduled Start', scheduledStart: 'Geplanter Start',
scheduledEnd: 'Scheduled End', scheduledEnd: 'Geplantes Ende',
affectedServices: 'Affected Services', affectedServices: 'Betroffene Dienste',
impact: 'Impact', impact: 'Auswirkung',
minor: 'Minor', minor: 'Geringfügig',
major: 'Major', major: 'Gravierend',
critical: 'Critical', critical: 'Kritisch',
none: 'None', none: 'Keine',
actions: 'Actions', actions: 'Aktionen',
scheduled: 'Scheduled', scheduled: 'Geplant',
inprogress: 'In Progress', inprogress: 'In Bearbeitung',
completed: 'Completed', completed: 'Abgeschlossen',
cancelled: 'Cancelled', cancelled: 'Abgebrochen',
markAsInProgress: 'Mark as In Progress', markAsInProgress: 'Als in Bearbeitung markieren',
markAsCompleted: 'Mark as Completed', markAsCompleted: 'Als abgeschlossen markieren',
markAsCancelled: 'Mark as Cancelled', markAsCancelled: 'Als abgebrochen markieren',
confirmDelete: 'Confirm Delete', confirmDelete: 'Löschen bestätigen',
deleteMaintenanceConfirmation: 'Are you sure you want to delete this maintenance window?', deleteMaintenanceConfirmation: 'Möchten Sie dieses Wartungsfenster wirklich löschen?',
thisActionCannotBeUndone: 'This action cannot be undone.', thisActionCannotBeUndone: 'Diese Aktion kann nicht rückgängig gemacht werden.',
maintenanceDeleted: 'Maintenance Deleted', maintenanceDeleted: 'Wartung gelöscht',
maintenanceDeletedDesc: 'The maintenance window has been deleted successfully.', maintenanceDeletedDesc: 'Das Wartungsfenster wurde erfolgreich gelöscht.',
errorDeletingMaintenance: 'There was an error deleting the maintenance window.', errorDeletingMaintenance: 'Fehler beim Löschen des Wartungsfensters.',
statusUpdated: 'Status Updated', statusUpdated: 'Status aktualisiert',
maintenanceStatusUpdated: 'Maintenance status has been updated successfully.', maintenanceStatusUpdated: 'Wartungsstatus wurde erfolgreich aktualisiert.',
errorUpdatingMaintenanceStatus: 'There was an error updating the maintenance status.', errorUpdatingMaintenanceStatus: 'Fehler beim Aktualisieren des Wartungsstatus.',
createMaintenance: 'Create Maintenance', createMaintenance: 'Wartung erstellen',
createMaintenanceDesc: 'Schedule a new maintenance window for your services', createMaintenanceDesc: 'Planen Sie ein neues Wartungsfenster für Ihre Dienste',
enterTitle: 'Enter maintenance title', enterTitle: 'Wartungstitel eingeben',
enterDescription: 'Enter detailed description of the maintenance', enterDescription: 'Geben Sie eine detaillierte Beschreibung der Wartung ein',
startTime: 'Start Time', startTime: 'Startzeit',
endTime: 'End Time', endTime: 'Endzeit',
selectDate: 'Select a date', selectDate: 'Datum auswählen',
enterAffectedServices: 'Enter affected services', enterAffectedServices: 'Betroffene Dienste eingeben',
separateServicesWithComma: 'Separate multiple services with commas', separateServicesWithComma: 'Mehrere Dienste durch Kommas trennen',
priority: 'Priority', priority: 'Priorität',
selectPriority: 'Select priority', selectPriority: 'Priorität auswählen',
selectStatus: 'Select status', selectStatus: 'Status auswählen',
selectImpact: 'Select impact', selectImpact: 'Auswirkung auswählen',
notifySubscribers: 'Notify Subscribers', notifySubscribers: 'Abonnenten benachrichtigen',
notifySubscribersDesc: 'Send notifications to all subscribers when this maintenance starts', notifySubscribersDesc: 'Beim Start dieser Wartung eine Benachrichtigung an alle Abonnenten senden',
maintenanceCreated: 'Maintenance Created', maintenanceCreated: 'Wartung erstellt',
maintenanceCreatedDesc: 'The maintenance window has been scheduled successfully.', maintenanceCreatedDesc: 'Das Wartungsfenster wurde erfolgreich geplant.',
errorCreatingMaintenance: 'There was an error creating the maintenance window.', errorCreatingMaintenance: 'Fehler beim Erstellen des Wartungsfensters.',
errorFetchingMaintenanceData: 'There was an error fetching maintenance data.', errorFetchingMaintenanceData: 'Fehler beim Abrufen der Wartungsdaten.',
low: 'Low', low: 'Niedrig',
medium: 'Medium', medium: 'Mittel',
high: 'High', high: 'Hoch',
created: 'Created', created: 'Erstellt',
lastUpdated: 'Last Updated', lastUpdated: 'Zuletzt aktualisiert',
subscribersWillBeNotified: 'Subscribers will be notified when maintenance begins', subscribersWillBeNotified: 'Abonnenten werden benachrichtigt, wenn die Wartung beginnt',
noNotifications: 'No notifications will be sent', noNotifications: 'Es werden keine Benachrichtigungen gesendet',
noScheduledMaintenance: 'No Scheduled Maintenance', noScheduledMaintenance: 'Keine geplanten Wartungen',
noMaintenanceWindows: 'There are no maintenance windows for this period. Create one by clicking the "Create Maintenance" button.', noMaintenanceWindows: 'Für diesen Zeitraum sind keine Wartungsfenster vorhanden. Erstellen Sie eines über die Schaltfläche „Wartung erstellen“.',
maintenanceCreatedSuccess: 'Maintenance window created successfully', maintenanceCreatedSuccess: 'Wartungsfenster erfolgreich erstellt',
}; };
+1 -1
View File
@@ -9,5 +9,5 @@ export const servicesTranslations: ServicesTranslations = {
responseTime: "Antwortzeit", responseTime: "Antwortzeit",
uptime: "Betriebszeit", uptime: "Betriebszeit",
lastChecked: "Zuletzt überprüft", lastChecked: "Zuletzt überprüft",
noServices: "No services match your filter criteria.", noServices: "Keine Dienste entsprechen Ihren Filterkriterien.",
}; };
+27 -28
View File
@@ -1,35 +1,34 @@
import { SettingsTranslations } from '../types/settings'; import { SettingsTranslations } from '../types/settings';
export const settingsTranslations: SettingsTranslations = { export const settingsTranslations: SettingsTranslations = {
// Tabs // Tabs
systemSettings: "System Settings", systemSettings: "Systemeinstellungen",
mailSettings: "Mail Settings", mailSettings: "E-Mail-Einstellungen",
// System Settings // System Settings
appName: "Application Name", appName: "Anwendungsname",
appURL: "Application URL", appURL: "Anwendungs-URL",
senderName: "Sender Name", senderName: "Absendername",
senderEmail: "Sender Email Address", senderEmail: "Absender-E-Mail-Adresse",
hideControls: "Hide Controls", hideControls: "Steuerelemente ausblenden",
// Mail Settings // Mail Settings
smtpSettings: "SMTP Configuration", smtpSettings: "SMTP-Konfiguration",
smtpEnabled: "Enable SMTP", smtpEnabled: "SMTP aktivieren",
smtpHost: "SMTP Host", smtpHost: "SMTP-Host",
smtpPort: "SMTP Port", smtpPort: "SMTP-Port",
smtpUsername: "SMTP Username", smtpUsername: "SMTP-Benutzername",
smtpAuthMethod: "Authentication Method", smtpAuthMethod: "Authentifizierungsmethode",
enableTLS: "Enable TLS", enableTLS: "TLS aktivieren",
localName: "Local Name", localName: "Lokaler Name",
// Actions and status // Actions and status
save: "Save Changes", save: "Änderungen speichern",
saving: "Saving...", saving: "Speichere...",
settingsUpdated: "Settings updated successfully", settingsUpdated: "Einstellungen erfolgreich aktualisiert",
errorSavingSettings: "Error saving settings", errorSavingSettings: "Fehler beim Speichern der Einstellungen",
testConnection: "Test Connection", testConnection: "Verbindung testen",
testingConnection: "Testing Connection...", testingConnection: "Verbindung wird getestet...",
connectionSuccess: "Connection successful", connectionSuccess: "Verbindung erfolgreich",
connectionFailed: "Connection failed" connectionFailed: "Verbindung fehlgeschlagen"
}; };
+58 -57
View File
@@ -43,65 +43,66 @@ export const sslTranslations: SSLTranslations = {
expirationDate: "Ablaufdatum", expirationDate: "Ablaufdatum",
daysLeft: "Verbleibende Tage", daysLeft: "Verbleibende Tage",
status: "Status", status: "Status",
lastNotified: "Last Notified", lastNotified: "Zuletzt benachrichtigt",
actions: "Actions", actions: "Aktionen",
validFrom: "Valid From", validFrom: "Gültig von",
validUntil: "Valid Until", validUntil: "Gültig bis",
validityDays: "Validity Days", validityDays: "Gültigkeitsdauer Tage",
organization: "Organization", organization: "Organisation",
commonName: "Common Name", commonName: "Allgemeiner Name",
serialNumber: "Serial Number", serialNumber: "Seriennummer",
algorithm: "Algorithm", algorithm: "Algorithmus",
subjectAltNames: "Subject Alternative Names", subjectAltNames: "Thema Alternative Bezeichnungen",
// Buttons and actions // Buttons and actions
addDomain: "Add Domain", addDomain: "Domain hinzufügen",
refreshAll: "Refresh All", refreshAll: "Alle aktualisieren",
cancel: "Cancel", cancel: "Abbrechen",
addCertificate: "Add Certificate", addCertificate: "Zertifikat hinzufügen",
check: "Check", check: "Prüfen",
view: "View", view: "Ansehen",
edit: "Edit", edit: "Bearbeiten",
delete: "Delete", delete: "Löschen",
close: "Close", close: "Schließen",
saveChanges: "Save Changes", saveChanges: "Änderungen speichern",
updating: "Updating", updating: "Aktualisiere",
// Sections in detail view // Sections in detail view
basicInformation: "Basic Information", basicInformation: "Basisinformationen",
validity: "Validity", validity: "Gültigkeit",
issuerInfo: "Issuer Information", issuerInfo: "Informationen zum Aussteller",
technicalDetails: "Technical Details", technicalDetails: "Technische Details",
monitoringConfig: "Monitoring Configuration", monitoringConfig: "Monitoring-Konfiguration",
recordInfo: "Record Information", recordInfo: "Aufzeichnungsinformationen",
// Notifications and messages // Notifications and messages
sslCertificateAdded: "SSL Certificate added successfully", sslCertificateAdded: "SSL-Zertifikat erfolgreich hinzugefügt",
sslCertificateUpdated: "SSL Certificate updated successfully", sslCertificateUpdated: "SSL-Zertifikat erfolgreich aktualisiert",
sslCertificateDeleted: "SSL Certificate deleted successfully", sslCertificateDeleted: "SSL-Zertifikat erfolgreich gelöscht",
sslCertificateRefreshed: "SSL Certificate for {domain} refreshed successfully", sslCertificateRefreshed: "SSL-Zertifikat für {domain} erfolgreich aktualisiert",
allCertificatesRefreshed: "All {count} certificates refreshed successfully", allCertificatesRefreshed: "Alle {count} Zertifikate wurden erfolgreich aktualisiert",
someCertificatesFailed: "{success} certificates refreshed, {failed} failed", someCertificatesFailed: "{success} Zertifikate aktualisiert, {failed} fehlgeschlagen",
failedToAddCertificate: "Failed to add SSL certificate", failedToAddCertificate: "Fehler beim Hinzufügen des SSL-Zertifikats",
failedToLoadCertificates: "Failed to load SSL certificates", failedToLoadCertificates: "Fehler beim Laden der SSL-Zertifikate",
failedToUpdateCertificate: "Failed to update SSL certificate", failedToUpdateCertificate: "Fehler beim Aktualisieren des SSL-Zertifikats",
failedToDeleteCertificate: "Failed to delete SSL certificate", failedToDeleteCertificate: "Fehler beim Löschen des SSL-Zertifikats",
failedToCheckCertificate: "Failed to check SSL certificate", failedToCheckCertificate: "Fehler beim Prüfen des SSL-Zertifikats",
noCertificatesToRefresh: "No certificates to refresh", noCertificatesToRefresh: "Keine Zertifikate zum Aktualisieren vorhanden",
startingRefreshAll: "Starting refresh of {count} certificates", startingRefreshAll: "Starte Aktualisierung von {count} Zertifikaten",
checkingSSLCertificate: "Checking SSL certificate...", checkingSSLCertificate: "SSL-Zertifikat wird überprüft...",
deleteConfirmation: "Are you sure you want to delete the certificate for", deleteConfirmation: "Möchten Sie das Zertifikat für wirklich löschen?",
deleteWarning: "This action cannot be undone. This will permanently delete the certificate.", deleteWarning: "Diese Aktion kann nicht rückgängig gemacht werden. Das Zertifikat wird dauerhaft gelöscht.",
// Misc // Misc
unknown: "Unknown", unknown: "Unbekannt",
never: "Never", never: "Nie",
none: "None", none: "Keine",
loadingChannels: "Loading channels...", loadingChannels: "Lade Kanäle...",
noChannelsFound: "No notification channels found", noChannelsFound: "Keine Benachrichtigungskanäle gefunden",
noSSLCertificates: "No SSL certificates found", noSSLCertificates: "Keine SSL-Zertifikate gefunden",
created: "Created", created: "Erstellt",
lastUpdated: "Last Updated", lastUpdated: "Zuletzt aktualisiert",
lastNotification: "Last Notification", lastNotification: "Letzte Benachrichtigung",
collectionId: "Collection ID" collectionId: "Sammlungs-ID"
}; }
+44 -67
View File
@@ -2,82 +2,59 @@
set -e set -e
echo "🚀 Starting Checkcle Installation..." REPO_URL="https://github.com/operacle/checkcle.git"
CLONE_DIR="/opt/checkcle"
DATA_DIR="/opt/pb_data"
PORT=8090
# Check if npm is installed echo "🚀 Installing Checkcle from $REPO_URL"
if ! command -v npm &> /dev/null; then
echo "📦 npm is not installed. Installing Node.js and npm..." # Step 1: Check if port 8090 is already in use
if lsof -i :"$PORT" &>/dev/null; then
# Install Node.js and npm (Ubuntu/Debian-based systems) echo "❗ ERROR: Port $PORT is already in use. Please free the port or change the Docker Compose configuration."
curl -fsSL https://deb.nodesource.com/setup_18.x | sudo -E bash - exit 1
sudo apt-get install -y nodejs
fi fi
# Clone the repo # Step 2: Ensure Docker is installed
if [ ! -d "checkcle" ]; then if ! command -v docker &> /dev/null; then
echo "📁 Cloning repository..." echo "🔧 Docker not found. Installing Docker..."
git clone https://github.com/operacle/checkcle.git curl -fsSL https://get.docker.com | sh
fi fi
cd checkcle # Step 3: Ensure Docker Compose v2 is available
if ! docker compose version &> /dev/null; then
echo "❗ Docker Compose v2 not found. Please install Docker Compose v2 and rerun this script."
exit 1
fi
# Set up paths # Step 4: Clone the repository
PROJECT_DIR=$(pwd) if [ -d "$CLONE_DIR" ]; then
APP_DIR="$PROJECT_DIR/application" echo "📁 Directory $CLONE_DIR already exists. Pulling latest changes..."
PB_DIR="$PROJECT_DIR/server" git -C "$CLONE_DIR" pull
PB_BINARY="$PB_DIR/pocketbase" else
echo "📥 Cloning repo to $CLONE_DIR"
git clone "$REPO_URL" "$CLONE_DIR"
fi
# Install web dependencies # Step 5: Create data volume directory if it doesnt exist
echo "📦 Installing Web Application dependencies..." if [ ! -d "$DATA_DIR" ]; then
cd "$APP_DIR" echo "📁 Creating data volume directory at $DATA_DIR"
npm install sudo mkdir -p "$DATA_DIR"
sudo chown "$(whoami)":"$(whoami)" "$DATA_DIR"
fi
# Create systemd service for frontend # Step 6: Start the service
echo "🛠️ Setting up systemd service for Web Application..." cd "$CLONE_DIR"
sudo tee /etc/systemd/system/checkcle-web.service > /dev/null <<EOF echo "📦 Starting Checkcle service with Docker Compose..."
[Unit] docker compose up -d
Description=Checkcle Web Application
After=network.target
[Service]
Type=simple
WorkingDirectory=$APP_DIR
ExecStart=/usr/bin/npm run dev
Restart=on-failure
User=$USER
Environment=NODE_ENV=development
[Install]
WantedBy=multi-user.target
EOF
# Create systemd service for PocketBase
echo "🛠️ Setting up systemd service for PocketBase..."
sudo tee /etc/systemd/system/checkcle-pb.service > /dev/null <<EOF
[Unit]
Description=Checkcle PocketBase Server
After=network.target
[Service]
Type=simple
WorkingDirectory=$PB_DIR
ExecStart=$PB_BINARY serve --dir pb_data
Restart=on-failure
User=$USER
[Install]
WantedBy=multi-user.target
EOF
# Reload systemd and start services
echo "🔄 Enabling and starting services..."
sudo systemctl daemon-reexec
sudo systemctl daemon-reload
sudo systemctl enable --now checkcle-web
sudo systemctl enable --now checkcle-pb
# Step 7: Show success output
echo "" echo ""
echo "✅ All done!" echo "✅ Checkcle has been successfully installed and started."
echo "🌐 Admin Panel: http://0.0.0.0:8090" echo ""
echo "🛠️ Admin Web Management"
echo "🔗 Default URL: http://0.0.0.0:$PORT"
echo "👤 User: admin@example.com" echo "👤 User: admin@example.com"
echo "🔑 Passwd: Admin123456" echo "🔑 Passwd: Admin123456"
echo ""
echo "📌 Make sure port $PORT is accessible from your host system or cloud firewall."
Binary file not shown.
Binary file not shown.
Binary file not shown.