Merge pull request #137 from sqkkyzx/develop

improve i18n and add new translations
This commit is contained in:
Tola Leng
2025-08-18 21:06:03 +07:00
committed by GitHub
50 changed files with 556 additions and 165 deletions
@@ -7,6 +7,7 @@ import { StatusCards } from "./StatusCards";
import { ServiceFilters } from "./ServiceFilters";
import { ServicesTable } from "./ServicesTable";
import { AddServiceDialog } from "@/components/services/AddServiceDialog";
import { useLanguage } from "@/contexts/LanguageContext";
interface DashboardContentProps {
services: Service[];
@@ -15,6 +16,7 @@ interface DashboardContentProps {
}
export const DashboardContent = ({ services, isLoading, error }: DashboardContentProps) => {
const { t } = useLanguage();
const [filter, setFilter] = useState<string>("all");
const [searchTerm, setSearchTerm] = useState<string>("");
const [isAddDialogOpen, setIsAddDialogOpen] = useState<boolean>(false);
@@ -31,7 +33,7 @@ export const DashboardContent = ({ services, isLoading, error }: DashboardConten
return (
<div className="flex flex-col items-center justify-center h-full gap-4 text-foreground">
<p>Error loading service data.</p>
<Button onClick={() => window.location.reload()}>Retry</Button>
<Button onClick={() => window.location.reload()}>{t('retry')}</Button>
</div>
);
}
@@ -40,12 +42,12 @@ export const DashboardContent = ({ services, isLoading, error }: DashboardConten
<main className="flex-1 flex flex-col overflow-auto bg-background p-6 pb-0">
<div className="flex flex-col flex-1">
<div className="flex justify-between items-center mb-6">
<h2 className="text-2xl font-bold text-foreground">Overview</h2>
<h2 className="text-2xl font-bold text-foreground">{t('overview')}</h2>
<Button
className="text-primary-foreground"
onClick={() => setIsAddDialogOpen(true)}
>
<Plus className="w-4 h-4 mr-2" /> New Service
<Plus className="w-4 h-4 mr-2" /> {t('newService')}
</Button>
</div>
@@ -3,6 +3,7 @@ import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Select, SelectTrigger, SelectValue, SelectContent, SelectItem } from "@/components/ui/select";
import { Plus } from "lucide-react";
import { useLanguage } from "@/contexts/LanguageContext";
interface ServiceFiltersProps {
filter: string;
@@ -19,10 +20,11 @@ export const ServiceFilters = ({
setSearchTerm,
servicesCount
}: ServiceFiltersProps) => {
const { t } = useLanguage();
return (
<div className="mb-6 flex justify-between items-center">
<div className="flex items-center">
<h3 className="text-xl font-semibold mr-2 text-foreground">Currently Monitoring</h3>
<h3 className="text-xl font-semibold mr-2 text-foreground">{t('currentlyMonitoring')}</h3>
<span className="bg-secondary text-secondary-foreground px-2 py-0.5 rounded text-sm">
{servicesCount}
</span>
@@ -30,10 +32,10 @@ export const ServiceFilters = ({
<div className="flex space-x-4">
<Select value={filter} onValueChange={setFilter}>
<SelectTrigger className="w-40 bg-card border-border">
<SelectValue placeholder="All Types" />
<SelectValue placeholder={t('allTypes')} />
</SelectTrigger>
<SelectContent>
<SelectItem value="all">All Types</SelectItem>
<SelectItem value="all">{t('allTypes')}</SelectItem>
<SelectItem value="HTTP">HTTP</SelectItem>
<SelectItem value="PING">PING</SelectItem>
<SelectItem value="TCP">TCP</SelectItem>
@@ -43,7 +45,7 @@ export const ServiceFilters = ({
<div className="relative">
<Input
className="w-72 bg-card border-border"
placeholder="Search"
placeholder={t('search')}
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
/>
@@ -3,12 +3,15 @@ import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { ArrowUp, ArrowDown, Pause, AlertTriangle } from "lucide-react";
import { Service } from "@/services/serviceService";
import { useTheme } from "@/contexts/ThemeContext";
import {useLanguage} from "@/contexts/LanguageContext.tsx";
interface StatusCardsProps {
services: Service[];
}
export const StatusCards = ({ services }: StatusCardsProps) => {
const { t } = useLanguage();
// Count services by status
const upServices = services.filter(s => s.status === "up").length;
const downServices = services.filter(s => s.status === "down").length;
@@ -42,7 +45,7 @@ export const StatusCards = ({ services }: StatusCardsProps) => {
></div>
</div>
<CardHeader className="pb-2 relative z-10">
<CardTitle className="text-sm font-medium text-white">UP SERVICES</CardTitle>
<CardTitle className="text-sm font-medium text-white">{t("upServices")}</CardTitle>
</CardHeader>
<CardContent className="flex items-center justify-between relative z-10">
<span className="text-5xl font-bold text-white">{upServices}</span>
@@ -74,7 +77,7 @@ export const StatusCards = ({ services }: StatusCardsProps) => {
></div>
</div>
<CardHeader className="pb-2 relative z-10">
<CardTitle className="text-sm font-medium text-white">DOWN SERVICES</CardTitle>
<CardTitle className="text-sm font-medium text-white">{t("downServices")}</CardTitle>
</CardHeader>
<CardContent className="flex items-center justify-between relative z-10">
<span className="text-5xl font-bold text-white">{downServices}</span>
@@ -106,7 +109,7 @@ export const StatusCards = ({ services }: StatusCardsProps) => {
></div>
</div>
<CardHeader className="pb-2 relative z-10">
<CardTitle className="text-sm font-medium text-white">PAUSED SERVICES</CardTitle>
<CardTitle className="text-sm font-medium text-white">{t("pausedServices")}</CardTitle>
</CardHeader>
<CardContent className="flex items-center justify-between relative z-10">
<span className="text-5xl font-bold text-white">{pausedServices}</span>
@@ -138,7 +141,7 @@ export const StatusCards = ({ services }: StatusCardsProps) => {
></div>
</div>
<CardHeader className="pb-2 relative z-10">
<CardTitle className="text-sm font-medium text-white">WARNING SERVICES</CardTitle>
<CardTitle className="text-sm font-medium text-white">{t("warningServices")}</CardTitle>
</CardHeader>
<CardContent className="flex items-center justify-between relative z-10">
<span className="text-5xl font-bold text-white">{warningServices}</span>
@@ -9,6 +9,7 @@ import {
import { ServiceForm } from "./ServiceForm";
import { ScrollArea } from "@/components/ui/scroll-area";
import { useQueryClient } from "@tanstack/react-query";
import { useLanguage } from "@/contexts/LanguageContext";
interface AddServiceDialogProps {
open: boolean;
@@ -16,6 +17,7 @@ interface AddServiceDialogProps {
}
export function AddServiceDialog({ open, onOpenChange }: AddServiceDialogProps) {
const { t } = useLanguage();
const queryClient = useQueryClient();
const handleSuccess = async () => {
// Immediately invalidate and refetch services data
@@ -32,9 +34,9 @@ export function AddServiceDialog({ open, onOpenChange }: AddServiceDialogProps)
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent className="sm:max-w-[700px] max-h-[90vh] flex flex-col">
<DialogHeader>
<DialogTitle className="text-xl">Create New Service</DialogTitle>
<DialogTitle className="text-xl">{t("createNewService")}</DialogTitle>
<DialogDescription>
Fill in the details to create a new service to monitor.
{t("createNewServiceDesc")}
</DialogDescription>
</DialogHeader>
<ScrollArea className="flex-1 pr-4 overflow-auto" style={{ height: "calc(80vh - 180px)" }}>
@@ -3,22 +3,24 @@ import { FormControl, FormField, FormItem, FormLabel, FormMessage } from "@/comp
import { Input } from "@/components/ui/input";
import { UseFormReturn } from "react-hook-form";
import { ServiceFormData } from "./types";
import {useLanguage} from "@/contexts/LanguageContext.tsx";
interface ServiceBasicFieldsProps {
form: UseFormReturn<ServiceFormData>;
}
export function ServiceBasicFields({ form }: ServiceBasicFieldsProps) {
const { t } = useLanguage();
return (
<FormField
control={form.control}
name="name"
render={({ field }) => (
<FormItem>
<FormLabel>Service Name</FormLabel>
<FormLabel>{t('serviceName')}</FormLabel>
<FormControl>
<Input
placeholder="Enter a descriptive name for your service"
placeholder={t('serviceNameDesc')}
{...field}
/>
</FormControl>
@@ -6,12 +6,14 @@ import { UseFormReturn } from "react-hook-form";
import { ServiceFormData } from "./types";
import { ServiceUrlField } from "./ServiceUrlField";
import { useState } from "react";
import {useLanguage} from "@/contexts/LanguageContext.tsx";
interface ServiceConfigFieldsProps {
form: UseFormReturn<ServiceFormData>;
}
export function ServiceConfigFields({ form }: ServiceConfigFieldsProps) {
const { t } = useLanguage();
const [isCustomInterval, setIsCustomInterval] = useState(false);
const intervalValue = form.watch("interval");
@@ -35,7 +37,7 @@ export function ServiceConfigFields({ form }: ServiceConfigFieldsProps) {
name="interval"
render={({ field }) => (
<FormItem>
<FormLabel>Check Interval</FormLabel>
<FormLabel>{t("checkInterval")}</FormLabel>
{!isCustomInterval ? (
<FormControl>
<Select onValueChange={handleIntervalChange} value={isCustomInterval ? "custom" : field.value}>
@@ -43,13 +45,13 @@ export function ServiceConfigFields({ form }: ServiceConfigFieldsProps) {
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="30">30 seconds</SelectItem>
<SelectItem value="60">1 minute</SelectItem>
<SelectItem value="300">5 minutes</SelectItem>
<SelectItem value="900">15 minutes</SelectItem>
<SelectItem value="1800">30 minutes</SelectItem>
<SelectItem value="3600">1 hour</SelectItem>
<SelectItem value="custom">Custom</SelectItem>
<SelectItem value="30">30 {t("seconds")}</SelectItem>
<SelectItem value="60">1 {t("minute")}</SelectItem>
<SelectItem value="300">5 {t("minutes")}</SelectItem>
<SelectItem value="900">15 {t("minutes")}</SelectItem>
<SelectItem value="1800">30 {t("minutes")}</SelectItem>
<SelectItem value="3600">1 {t("hour")}</SelectItem>
<SelectItem value="custom">{t("custom")}</SelectItem>
</SelectContent>
</Select>
</FormControl>
@@ -58,7 +60,7 @@ export function ServiceConfigFields({ form }: ServiceConfigFieldsProps) {
<FormControl>
<Input
type="number"
placeholder="Enter interval in seconds"
placeholder={t("checkIntervalPlaceholder")}
value={field.value}
onChange={field.onChange}
min="10"
@@ -72,14 +74,14 @@ export function ServiceConfigFields({ form }: ServiceConfigFieldsProps) {
}}
className="text-sm text-blue-600 hover:text-blue-800"
>
Back to presets
{t("backToPresets")}
</button>
</div>
)}
<FormDescription className="text-xs">
{isCustomInterval
? "Enter custom interval in seconds (minimum 10 seconds)"
: "How often to check the service status"
? t("checkIntervalDescCustom")
: t("checkIntervalDesc")
}
</FormDescription>
<FormMessage />
@@ -92,22 +94,22 @@ export function ServiceConfigFields({ form }: ServiceConfigFieldsProps) {
name="retries"
render={({ field }) => (
<FormItem>
<FormLabel>Retry Attempts</FormLabel>
<FormLabel>{t("retryAttempts")}</FormLabel>
<FormControl>
<Select onValueChange={field.onChange} defaultValue={field.value}>
<SelectTrigger>
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="1">1 attempt</SelectItem>
<SelectItem value="2">2 attempts</SelectItem>
<SelectItem value="3">3 attempts</SelectItem>
<SelectItem value="5">5 attempts</SelectItem>
<SelectItem value="1">1 {t("attempt")}</SelectItem>
<SelectItem value="2">2 {t("attempts")}</SelectItem>
<SelectItem value="3">3 {t("attempts")}</SelectItem>
<SelectItem value="5">5 {t("attempts")}</SelectItem>
</SelectContent>
</Select>
</FormControl>
<FormDescription className="text-xs">
Number of retry attempts before marking as down
{t("retryAttemptsDesc")}
</FormDescription>
<FormMessage />
</FormItem>
@@ -3,7 +3,7 @@ import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { useState, useEffect } from "react";
import { useToast } from "@/hooks/use-toast";
import { serviceSchema, ServiceFormData } from "./types";
import { useServiceSchema, ServiceFormData } from "./types";
import { ServiceBasicFields } from "./ServiceBasicFields";
import { ServiceTypeField } from "./ServiceTypeField";
import { ServiceConfigFields } from "./ServiceConfigFields";
@@ -14,6 +14,7 @@ import { Service } from "@/types/service.types";
import { ServiceRegionalFields } from "./ServiceRegionalFields";
import { getServiceFormDefaults, mapServiceToFormData, mapFormDataToServiceData } from "./serviceFormUtils";
import { useQueryClient } from "@tanstack/react-query";
import {useLanguage} from "@/contexts/LanguageContext.tsx";
interface ServiceFormProps {
onSuccess: () => void;
@@ -30,10 +31,13 @@ export function ServiceForm({
isEdit = false,
onSubmitStart
}: ServiceFormProps) {
const { t } = useLanguage();
const { toast } = useToast();
const [isSubmitting, setIsSubmitting] = useState(false);
const queryClient = useQueryClient();
const serviceSchema = useServiceSchema();
// Initialize form with default values
const form = useForm<ServiceFormData>({
resolver: zodResolver(serviceSchema),
@@ -124,23 +128,23 @@ export function ServiceForm({
<form onSubmit={form.handleSubmit(handleSubmit)} className="space-y-6 pb-6">
<div className="space-y-6">
<div className="space-y-4">
<h3 className="text-sm font-medium text-muted-foreground border-b pb-2">Basic Information</h3>
<h3 className="text-sm font-medium text-muted-foreground border-b pb-2">{t('basicInformation')}</h3>
<ServiceBasicFields form={form} />
<ServiceTypeField form={form} />
</div>
<div className="space-y-4">
<h3 className="text-sm font-medium text-muted-foreground border-b pb-2">Configuration</h3>
<h3 className="text-sm font-medium text-muted-foreground border-b pb-2">{t('configuration')}</h3>
<ServiceConfigFields form={form} />
</div>
<div className="space-y-4">
<h3 className="text-sm font-medium text-muted-foreground border-b pb-2">Regional Monitoring</h3>
<h3 className="text-sm font-medium text-muted-foreground border-b pb-2">{t('regionalMonitoring')}</h3>
<ServiceRegionalFields form={form} />
</div>
<div className="space-y-4">
<h3 className="text-sm font-medium text-muted-foreground border-b pb-2">Notifications</h3>
<h3 className="text-sm font-medium text-muted-foreground border-b pb-2">{t('notifications')}</h3>
<ServiceNotificationFields form={form} />
</div>
</div>
@@ -148,7 +152,7 @@ export function ServiceForm({
<ServiceFormActions
isSubmitting={isSubmitting}
onCancel={onCancel}
submitLabel={isEdit ? "Update Service" : "Create Service"}
submitLabel={isEdit ? t("updateService") : t("createService")}
/>
</form>
</Form>
@@ -9,12 +9,14 @@ import { useQuery } from "@tanstack/react-query";
import { alertConfigService, AlertConfiguration } from "@/services/alertConfigService";
import { serviceNotificationTemplateService, ServiceNotificationTemplate } from "@/services/serviceNotificationTemplateService";
import { useState, useEffect } from "react";
import { useLanguage } from "@/contexts/LanguageContext.tsx";
interface ServiceNotificationFieldsProps {
form: UseFormReturn<ServiceFormData>;
}
export function ServiceNotificationFields({ form }: ServiceNotificationFieldsProps) {
const { t } = useLanguage();
const [alertConfigs, setAlertConfigs] = useState<AlertConfiguration[]>([]);
// Get the current form values for debugging
@@ -88,10 +90,10 @@ export function ServiceNotificationFields({ form }: ServiceNotificationFieldsPro
<FormItem className="flex flex-row items-center justify-between rounded-lg border p-4">
<div className="space-y-0.5">
<FormLabel className="text-base">
Enable Notifications
{t("enableNotifications")}
</FormLabel>
<FormDescription>
Enable or disable notifications for this service
{t("enableNotificationsDesc")}
</FormDescription>
</div>
<FormControl>
@@ -115,11 +117,11 @@ export function ServiceNotificationFields({ form }: ServiceNotificationFieldsPro
name="notificationChannels"
render={({ field }) => (
<FormItem>
<FormLabel>Notification Channels</FormLabel>
<FormLabel>{t("notificationChannels")}</FormLabel>
<FormDescription>
{notificationStatus === "enabled"
? "Select notification channels for this service"
: "Enable notifications first to select channels"}
? t("notificationChannelsEnabledDesc")
: t("notificationChannelsDesc")}
</FormDescription>
{/* Display selected channels as badges */}
@@ -144,7 +146,7 @@ export function ServiceNotificationFields({ form }: ServiceNotificationFieldsPro
value="" // Always reset to empty after selection
>
<SelectTrigger className={notificationStatus !== "enabled" ? 'opacity-50' : ''}>
<SelectValue placeholder="Add a notification channel" />
<SelectValue placeholder={t("notificationChannelsPlaceholder")} />
</SelectTrigger>
<SelectContent>
{alertConfigs
@@ -169,7 +171,7 @@ export function ServiceNotificationFields({ form }: ServiceNotificationFieldsPro
return (
<FormItem>
<FormLabel>Alert Template</FormLabel>
<FormLabel>{t("alertTemplate")}</FormLabel>
<FormControl>
<Select
onValueChange={(value) => {
@@ -179,7 +181,7 @@ export function ServiceNotificationFields({ form }: ServiceNotificationFieldsPro
disabled={notificationStatus !== "enabled" || isLoadingTemplates}
>
<SelectTrigger className={notificationStatus !== "enabled" ? 'opacity-50' : ''}>
<SelectValue placeholder={isLoadingTemplates ? "Loading templates..." : "Select an alert template"} />
<SelectValue placeholder={isLoadingTemplates ? t("alertTemplateLoading") : t("alertTemplatePlaceholder")} />
</SelectTrigger>
<SelectContent>
{serviceTemplates?.map((template) => (
@@ -192,8 +194,8 @@ export function ServiceNotificationFields({ form }: ServiceNotificationFieldsPro
</FormControl>
<FormDescription>
{notificationStatus === "enabled"
? "Choose a template for alert messages"
: "Enable notifications first to select template"}
? t("alertTemplateEnabledDesc")
: t("alertTemplateDesc")}
</FormDescription>
</FormItem>
);
@@ -9,12 +9,14 @@ import { regionalService } from "@/services/regionalService";
import { MapPin, Loader2, X, Plus } from "lucide-react";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import { useLanguage } from "@/contexts/LanguageContext.tsx";
interface ServiceRegionalFieldsProps {
form: UseFormReturn<ServiceFormData>;
}
export function ServiceRegionalFields({ form }: ServiceRegionalFieldsProps) {
const { t } = useLanguage();
const regionalMonitoringEnabled = form.watch("regionalMonitoringEnabled");
const currentRegionalAgents = form.watch("regionalAgents") || [];
@@ -79,10 +81,10 @@ export function ServiceRegionalFields({ form }: ServiceRegionalFieldsProps) {
<div className="space-y-0.5">
<FormLabel className="text-base font-medium flex items-center gap-2">
<MapPin className="h-4 w-4" />
Regional Monitoring
{t("regionalMonitoring")}
</FormLabel>
<div className="text-sm text-muted-foreground">
Assign this service to regional monitoring agents for distributed monitoring
{t("regionalMonitoringDesc")}
</div>
</div>
<FormControl>
@@ -107,7 +109,7 @@ export function ServiceRegionalFields({ form }: ServiceRegionalFieldsProps) {
name="regionalAgents"
render={({ field }) => (
<FormItem>
<FormLabel>Regional Agents</FormLabel>
<FormLabel>{t("regionalAgents")}</FormLabel>
{/* Display selected agents */}
{currentRegionalAgents.length > 0 && (
@@ -140,12 +142,12 @@ export function ServiceRegionalFields({ form }: ServiceRegionalFieldsProps) {
<SelectTrigger>
<SelectValue placeholder={
isLoading
? "Loading agents..."
? t("regionalAgentsLoading")
: availableAgents.length > 0
? "Select additional regional agents..."
? t("regionalAgentsAvailablePlaceholder")
: currentRegionalAgents.length > 0
? "All available agents selected"
: "No regional agents available"
? t("regionalAgentsAllSelected")
: t("regionalAgentsNoAvailable")
} />
</SelectTrigger>
</FormControl>
@@ -154,14 +156,14 @@ export function ServiceRegionalFields({ form }: ServiceRegionalFieldsProps) {
<SelectItem value="loading" disabled>
<div className="flex items-center gap-2">
<Loader2 className="h-4 w-4 animate-spin" />
Loading agents...
t("regionalAgentsLoading")
</div>
</SelectItem>
) : availableAgents.length === 0 ? (
<SelectItem value="no-agents" disabled>
{currentRegionalAgents.length > 0
? "All available agents selected"
: "No online regional agents available"
? t("regionalAgentsAllSelected")
: t("regionalAgentsNoOnlineAvailable")
}
</SelectItem>
) : (
@@ -190,13 +192,13 @@ export function ServiceRegionalFields({ form }: ServiceRegionalFieldsProps) {
{regionalMonitoringEnabled && onlineAgents.length === 0 && !isLoading && (
<p className="text-sm text-amber-600">
No online regional agents found. Services will use default monitoring.
{t("regionalAgentsNotFoundMessage")}
</p>
)}
{currentRegionalAgents.length === 0 && regionalMonitoringEnabled && (
<p className="text-sm text-orange-600">
No regional agents selected. Service will use default monitoring.
{t("regionalAgentsNotSelectedMessage")}
</p>
)}
@@ -4,12 +4,14 @@ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@
import { Globe, Wifi, Server, Globe2 } from "lucide-react";
import { UseFormReturn } from "react-hook-form";
import { ServiceFormData } from "./types";
import { useLanguage } from "@/contexts/LanguageContext.tsx";
interface ServiceTypeFieldProps {
form: UseFormReturn<ServiceFormData>;
}
export function ServiceTypeField({ form }: ServiceTypeFieldProps) {
const { t } = useLanguage();
const getServiceIcon = (type: string) => {
switch (type) {
case "http":
@@ -31,7 +33,7 @@ export function ServiceTypeField({ form }: ServiceTypeFieldProps) {
name="type"
render={({ field }) => (
<FormItem>
<FormLabel>Service Type</FormLabel>
<FormLabel>{t("serviceType")}</FormLabel>
<FormControl>
<Select
onValueChange={field.onChange}
@@ -56,7 +58,7 @@ export function ServiceTypeField({ form }: ServiceTypeFieldProps) {
<span>HTTP/S</span>
</div>
<p className="text-xs text-muted-foreground mt-1">
Monitor websites and REST APIs with HTTP/HTTPS Protocol
{t("serviceTypeHTTPDesc")}
</p>
</div>
</SelectItem>
@@ -67,7 +69,7 @@ export function ServiceTypeField({ form }: ServiceTypeFieldProps) {
<span>PING</span>
</div>
<p className="text-xs text-muted-foreground mt-1">
Monitor host availability with PING Protocol
{t("serviceTypePINGDesc")}
</p>
</div>
</SelectItem>
@@ -78,7 +80,7 @@ export function ServiceTypeField({ form }: ServiceTypeFieldProps) {
<span>TCP</span>
</div>
<p className="text-xs text-muted-foreground mt-1">
Monitor TCP port connectivity with TCP Protocol
{t("serviceTypeTCPDesc")}
</p>
</div>
</SelectItem>
@@ -89,7 +91,7 @@ export function ServiceTypeField({ form }: ServiceTypeFieldProps) {
<span>DNS</span>
</div>
<p className="text-xs text-muted-foreground mt-1">
Monitor DNS resolution
{t("serviceTypeDNSDesc")}
</p>
</div>
</SelectItem>
@@ -3,12 +3,14 @@ import { FormControl, FormField, FormItem, FormLabel, FormMessage, FormDescripti
import { Input } from "@/components/ui/input";
import { UseFormReturn } from "react-hook-form";
import { ServiceFormData } from "./types";
import { useLanguage } from "@/contexts/LanguageContext.tsx";
interface ServiceUrlFieldProps {
form: UseFormReturn<ServiceFormData>;
}
export function ServiceUrlField({ form }: ServiceUrlFieldProps) {
const { t } = useLanguage();
const serviceType = form.watch("type");
const getPlaceholder = () => {
@@ -22,35 +24,35 @@ export function ServiceUrlField({ form }: ServiceUrlFieldProps) {
case "dns":
return "example.com";
default:
return "Enter URL or hostname";
return t("targetDefaultPlaceholder");
}
};
const getDescription = () => {
switch (serviceType) {
case "http":
return "Enter the full URL including protocol (http:// or https://)";
return t("targetHTTPDesc");
case "ping":
return "Enter hostname or IP address to ping";
return t("targetPINGDesc");
case "tcp":
return "Enter hostname or IP address for TCP connection test";
return t("targetTCPDesc");
case "dns":
return "Enter domain name for DNS record monitoring (A, AAAA, MX, etc.)";
return t("targetDNSDesc");
default:
return "Enter the target URL or hostname for monitoring";
return t("targetDefaultDesc");
}
};
const getFieldLabel = () => {
switch (serviceType) {
case "dns":
return "Domain Name";
return t("targetDNS");
case "ping":
return "Hostname/IP";
case "tcp":
return "Hostname/IP";
default:
return "Target URL/Host";
return t("targetDefault");
}
};
@@ -99,7 +101,7 @@ export function ServiceUrlField({ form }: ServiceUrlFieldProps) {
/>
</FormControl>
<FormDescription className="text-xs">
Enter the port number for TCP connection test
{t("targetTCPPortDesc")}
</FormDescription>
<FormMessage />
</FormItem>
@@ -1,22 +1,42 @@
import { z } from "zod";
import type { ZodSchema } from "zod";
import { useLanguage } from "@/contexts/LanguageContext";
export const serviceSchema = z.object({
name: z.string().min(1, "Service name is required"),
type: z.enum(["http", "ping", "tcp", "dns"]),
url: z.string().min(1, "URL/Domain/Host is required").refine((value) => {
// Basic validation - more specific validation can be added per type
return value.trim().length > 0;
}, "Please enter a valid URL, hostname, or domain"),
port: z.string().optional(),
interval: z.string(),
retries: z.string(),
notificationStatus: z.enum(["enabled", "disabled"]).optional(),
notificationChannels: z.array(z.string()).optional(),
alertTemplate: z.string().optional(),
// Regional monitoring fields - now supports multiple agents
regionalMonitoringEnabled: z.boolean().optional(),
regionalAgents: z.array(z.string()).optional(),
});
export type ServiceFormData = z.infer<typeof serviceSchema>;
export type ServiceFormData = {
name: string;
type: "http" | "ping" | "tcp" | "dns";
url: string;
port?: string;
interval: string;
retries: string;
notificationStatus?: "enabled" | "disabled";
notificationChannels?: string[];
alertTemplate?: string;
regionalMonitoringEnabled?: boolean;
regionalAgents?: string[];
};
// Hook to use the schema with translations
export const useServiceSchema = () => {
const { t } = useLanguage();
return z.object({
name: z.string().min(1, t("serviceNameRequired")),
type: z.enum(["http", "ping", "tcp", "dns"]),
url: z.string()
.min(1, t("urlDomainHostRequired"))
.refine(
(value) => value.trim().length > 0,
t("enterValidUrlHostnameDomain")
),
port: z.string().optional(),
interval: z.string(),
retries: z.string(),
notificationStatus: z.enum(["enabled", "disabled"]).optional(),
notificationChannels: z.array(z.string()).optional(),
alertTemplate: z.string().optional(),
// Regional monitoring fields - now supports multiple agents
regionalMonitoringEnabled: z.boolean().optional(),
regionalAgents: z.array(z.string()).optional(),
});
};
@@ -170,7 +170,7 @@ const DataRetentionSettings = () => {
<Alert className="border-blue-200 bg-blue-50 dark:bg-blue-950 dark:border-blue-800">
<AlertTriangle className="h-5 w-5 text-blue-600 dark:text-blue-400" />
<AlertDescription className="text-blue-700 dark:text-blue-300">
<span className="font-medium">Permission Notice:</span> As an admin user, you do not have access to data retention settings. These settings can only be accessed and modified by Super Admins.
<span className="font-medium">{t("permissionNotice")}</span> As an admin user, you do not have access to data retention settings. These settings can only be accessed and modified by Super Admins.
</AlertDescription>
</Alert>
</CardContent>
@@ -143,7 +143,7 @@ const GeneralSettingsPanel: React.FC<GeneralSettingsPanelProps> = () => {
<Alert className="border-blue-200 bg-blue-50 dark:bg-blue-950 dark:border-blue-800">
<ShieldAlert className="h-5 w-5 text-blue-600 dark:text-blue-400" />
<AlertDescription className="text-blue-700 dark:text-blue-300">
<span className="font-medium">Permission Notice:</span> As an admin user, you do not have access to view or modify system and mail settings. These settings can only be accessed and modified by Super Admins. Contact your Super Admin if you need to make changes to system configuration or mail settings.
<span className="font-medium">{t("permissionNotice")}</span> {t("permissionNoticeAddUser")}
</AlertDescription>
</Alert>
</CardContent>
@@ -153,11 +153,11 @@ const GeneralSettingsPanel: React.FC<GeneralSettingsPanelProps> = () => {
}
if (isLoading) {
return <div className="p-4">Loading settings...</div>;
return <div className="p-4">{t("loadingSettings")}</div>;
}
if (error) {
return <div className="p-4 text-red-500">Error loading settings</div>;
return <div className="p-4 text-red-500">{t("loadingSettingsError")}</div>;
}
return (
@@ -10,7 +10,10 @@ import { UserCog, Loader2, AlertCircle, Info, Users, ShieldAlert } from "lucide-
import { Button } from "@/components/ui/button";
import { Alert, AlertDescription } from "@/components/ui/alert";
import { authService } from "@/services/authService";
import { useLanguage } from "@/contexts/LanguageContext.tsx";
const UserManagement = () => {
const { t } = useLanguage();
const {
users,
loading,
@@ -44,21 +47,21 @@ const UserManagement = () => {
<AccordionTrigger className="py-4 px-5 bg-card hover:bg-card/90 hover:no-underline rounded-lg text-lg font-medium flex items-center w-full">
<div className="flex items-center">
<UserCog className="h-5 w-5 mr-2 text-green-500" />
<span>User Management</span>
<span>{t("userManagement")}</span>
</div>
</AccordionTrigger>
<AccordionContent className="p-4 pt-6 bg-background rounded-b-lg">
<div className="flex justify-between items-center mb-6">
<h2 className="text-2xl font-bold">User Management</h2>
<h2 className="text-2xl font-bold">{t("userManagement")}</h2>
{isSuperAdmin && <button onClick={() => setIsAddUserDialogOpen(true)} className="bg-green-500 hover:bg-green-600 text-white py-2 px-4 rounded-md flex items-center">
<span className="mr-1">+</span> Add User
<span className="mr-1">+</span> {t("addUser")}
</button>}
</div>
{!isSuperAdmin && <Alert className="mb-6 border-blue-200 bg-blue-50 dark:bg-blue-950 dark:border-blue-800">
<ShieldAlert className="h-5 w-5 text-blue-600 dark:text-blue-400" />
<AlertDescription className="text-blue-700 dark:text-blue-300">
<span className="font-medium">Permission Notice:</span> As an admin user, you have access to view and modify existing user details. However, only Super Admins have permission to create new user accounts. Contact your Super Admin if you need to add a new user.
<span className="font-medium">{t("permissionNotice")}</span> As an admin user, you have access to view and modify existing user details. However, only Super Admins have permission to create new user accounts. Contact your Super Admin if you need to add a new user.
</AlertDescription>
</Alert>}
+24 -12
View File
@@ -1,4 +1,4 @@
import React, { createContext, useContext, useState, ReactNode } from "react";
import React, { createContext, useContext, useState, ReactNode, useCallback } from "react";
import { translations, Language, TranslationModule, TranslationKey } from "@/translations";
type LanguageContextType = {
@@ -21,27 +21,39 @@ export const useLanguage = () => {
export const LanguageProvider = ({ children }: { children: ReactNode }) => {
const [language, setLanguage] = useState<Language>("en");
const fallbackLanguage: Language = "en";
const t = <M extends TranslationModule>(key: string, module?: M): string => {
const t = useCallback(<M extends TranslationModule>(key: string, module?: M): string => {
const langPack = translations[language];
const fallbackPack = translations[fallbackLanguage];
if (module) {
const translatedValue = translations[language][module][key as TranslationKey<M>];
return typeof translatedValue === "string" ? translatedValue : key;
const valCur = langPack?.[module]?.[key as TranslationKey<M>];
if (typeof valCur === "string") return valCur;
const valEn = fallbackPack?.[module]?.[key as TranslationKey<M>];
if (typeof valEn === "string") return valEn;
return key;
}
for (const mod in translations[language]) {
const moduleKey = mod as TranslationModule;
const translatedValue = translations[language][moduleKey][key as any];
if (translatedValue && typeof translatedValue === "string") {
return translatedValue;
}
for (const mod in langPack) {
const m = mod as TranslationModule;
const val = langPack[m]?.[key as any];
if (typeof val === "string") return val;
}
for (const mod in fallbackPack) {
const m = mod as TranslationModule;
const val = fallbackPack[m]?.[key as any];
if (typeof val === "string") return val;
}
return key;
};
}, [language]);
return (
<LanguageContext.Provider value={{ language, setLanguage, t }}>
{children}
</LanguageContext.Provider>
);
};
};
+2 -1
View File
@@ -2,7 +2,7 @@
import { AboutTranslations } from '../types/about';
export const aboutTranslations: AboutTranslations = {
aboutCheckcle: "Über Checkcle",
aboutCheckCle: "Über Checkcle",
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.",
systemVersion: "Systemversion",
license: "Lizenz",
@@ -15,6 +15,7 @@ export const aboutTranslations: AboutTranslations = {
quickActions: "Schnelle Aktionen",
quickActionsDescription: "Greifen Sie schnell auf gängige Überwachungsvorgänge und -funktionen zu. Wählen Sie unten eine Aktion aus, um loszulegen.",
quickTips: "Schnelle Tipps",
releasedOn: "Veröffentlicht",
};
+3 -2
View File
@@ -6,9 +6,10 @@ export const commonTranslations: CommonTranslations = {
logout: "Abmelden",
language: "Sprache",
english: "Englisch",
khmer: "Khmer",
khmer: "ភាសាខ្មែរ",
german: "Deutsch",
simplifiedChinese: "Simplified Chinese",
japanese: "日本語",
simplifiedChinese: "简体中文",
goodMorning: "Guten Morgen",
goodAfternoon: "Guten Nachmittag",
goodEvening: "Guten Abend",
@@ -51,4 +51,5 @@ export const incidentTranslations: IncidentTranslations = {
configuration: 'Konfiguration',
failedToUpdateStatus: 'Fehler beim Aktualisieren des Status',
inProgress: 'In Bearbeitung',
enterRootCause: 'Grundursache eingeben',
};
@@ -4,10 +4,20 @@ import { ServicesTranslations } from '../types/services';
export const servicesTranslations: ServicesTranslations = {
// Dienst-Tabelle
serviceName: "Dienstname",
serviceNameDesc: "Geben Sie einen aussagekräftigen Namen für Ihren Service ein",
serviceType: "Diensttyp",
serviceStatus: "Dienststatus",
responseTime: "Antwortzeit",
uptime: "Betriebszeit",
lastChecked: "Zuletzt überprüft",
noServices: "Keine Dienste entsprechen Ihren Filterkriterien.",
currentlyMonitoring: "Derzeit überwacht",
retry: "Wiederholen",
overview: "Überblick",
newService: "Neuer Dienst",
rowsPerPage: "Zeilen pro Seite",
search: "Suchen",
allTypes: "Alle Arten",
createNewService: "Neuen Service hinzufügen",
createNewServiceDesc: "Geben Sie detaillierte Informationen ein, um einen neuen zu erstellen, den Sie überwachen möchten.",
};
+2 -1
View File
@@ -112,6 +112,7 @@ export const sslTranslations: SSLTranslations = {
created: "Erstellt",
lastUpdated: "Zuletzt aktualisiert",
lastNotification: "Letzte Benachrichtigung",
collectionId: "Sammlungs-ID"
collectionId: "Sammlungs-ID",
noCertificatesFound: "Keine Zertifikate gefunden",
}
+2 -1
View File
@@ -2,7 +2,7 @@
import { AboutTranslations } from '../types/about';
export const aboutTranslations: AboutTranslations = {
aboutCheckcle: "About Checkcle",
aboutCheckCle: "About Checkcle",
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.",
systemVersion: "System Version",
license: "License",
@@ -15,4 +15,5 @@ export const aboutTranslations: AboutTranslations = {
quickActions: "Quick Actions",
quickActionsDescription: "Access common monitoring operations and features quickly. Select an action below to get started.",
quickTips: "Quick Tips",
releasedOn: "Released On",
};
+4 -3
View File
@@ -5,10 +5,11 @@ export const commonTranslations: CommonTranslations = {
welcome: "Welcome",
logout: "Logout",
language: "Language",
english: "English",
khmer: "Khmer",
english: "Englisch",
khmer: "ភាសាខ្មែរ",
german: "Deutsch",
simplifiedChinese: "Simplified Chinese",
japanese: "日本語",
simplifiedChinese: "简体中文",
goodMorning: "Good morning",
goodAfternoon: "Good afternoon",
goodEvening: "Good evening",
@@ -52,4 +52,5 @@ export const incidentTranslations: IncidentTranslations = {
configuration: 'Configuration',
failedToUpdateStatus: 'Failed to update status',
inProgress: 'In Progress',
enterRootCause: 'Enter Root Cause',
};
+89 -3
View File
@@ -2,11 +2,97 @@
import { ServicesTranslations } from '../types/services';
export const servicesTranslations: ServicesTranslations = {
serviceName: "Service Name",
serviceType: "Service Type",
serviceStatus: "Service Status",
responseTime: "Response Time",
uptime: "Uptime",
lastChecked: "Last Checked",
noServices: "No services match your filter criteria.",
};
currentlyMonitoring: "Currently Monitoring",
retry: "Retry",
overview: "Overview",
newService: "NewService",
rowsPerPage: "Rows Per Page",
search: "Search",
allTypes: "All Types",
createNewService: "Create New Service",
createNewServiceDesc: "Fill in the details to create a new service to monitor.",
// ServiceBasicFields.tsx
serviceName: "Service Name",
serviceNameDesc: "Enter a descriptive name for your service",
// ServiceConfigFields.tsx
checkInterval: "Check Interval",
seconds: "seconds",
minute: "minute",
minutes: "minutes",
hour: "hour",
hours: "hours",
custom: "Custom",
checkIntervalPlaceholder: "Enter interval in seconds",
backToPresets: "Back to presets",
checkIntervalDesc: "How often to check the service status",
checkIntervalDescCustom: "Enter custom interval in seconds (minimum 10 seconds)",
retryAttempts: "Retry Attempts",
attempt: "attempt",
attempts: "attempts",
retryAttemptsDesc: "Number of retry attempts before marking as down",
// ServiceForm.tsx
updateService: "Update Service",
createService: "Create Service",
// ServiceNotificationFields.tsx
enableNotifications: "Enable Notifications",
enableNotificationsDesc: "Enable or disable notifications for this service",
notificationChannels: "Notification Channels",
notificationChannelsEnabledDesc: "Select notification channels for this service",
notificationChannelsDesc: "Enable notifications first to select channels",
notificationChannelsPlaceholder: "Add a notification channel",
alertTemplate: "Alert Template",
alertTemplateLoading: "Loading templates...",
alertTemplatePlaceholder: "Select an alert template",
alertTemplateEnabledDesc: "Choose a template for alert messages",
alertTemplateDesc: "Enable notifications first to select template",
// ServiceTypeField.tsx
serviceType: "Service Type",
serviceTypeHTTPDesc: "Monitor websites and REST APIs with HTTP/HTTPS Protocol",
serviceTypePINGDesc: "Monitor host availability with PING Protocol",
serviceTypeTCPDesc: "Monitor TCP port connectivity with TCP Protocol",
serviceTypeDNSDesc: "Monitor DNS resolution",
// ServiceRegionalFields.tsx
regionalMonitoring: "Regional Monitoring",
regionalMonitoringDesc: "Assign this service to regional monitoring agents for distributed monitoring",
regionalAgents: "Regional Agents",
regionalAgentsLoading: "Loading agents...",
regionalAgentsAvailablePlaceholder: "Select additional regional agents...",
regionalAgentsAllSelected: "All available agents selected",
regionalAgentsNoAvailable: "No regional agents available",
regionalAgentsNoOnlineAvailable: "No online regional agents available",
regionalAgentsNotFoundMessage: "No online regional agents found. Services will use default monitoring.",
regionalAgentsNotSelectedMessage: "No regional agents selected. Service will use default monitoring.",
// ServiceUrlField.tsx
targetDefault: "Target URL/Host",
targetDNS: "Domain Name",
targetHTTPDesc: "Enter the full URL including protocol (http:// or https://)",
targetPINGDesc: "Enter hostname or IP address to ping",
targetTCPDesc: "Enter hostname or IP address for TCP connection test",
targetTCPPortDesc: "Enter the port number for TCP connection test",
targetDNSDesc: "Enter domain name for DNS record monitoring (A, AAAA, MX, etc.)",
targetDefaultDesc: "Enter the target URL or hostname for monitoring",
targetDefaultPlaceholder: "Enter URL or hostname",
// types.ts
serviceNameRequired: "Service name is required",
urlDomainHostRequired: "URL/Domain/Host is required",
enterValidUrlHostnameDomain: "Please enter a valid URL, hostname, or domain",
// Dashboard
upServices: "UP SERVICES",
downServices: "DOWN SERVICES",
pausedServices: "PAUSED SERVICES",
warningServices: "WARNING SERVICES",
};
+13 -6
View File
@@ -2,18 +2,18 @@
import { SettingsTranslations } from '../types/settings';
export const settingsTranslations: SettingsTranslations = {
// Tabs
// General Settings - Tabs
systemSettings: "System Settings",
mailSettings: "Mail Settings",
// System Settings
// General Settings - System Settings
appName: "Application Name",
appURL: "Application URL",
senderName: "Sender Name",
senderEmail: "Sender Email Address",
hideControls: "Hide Controls",
// Mail Settings
// General Settings - Mail Settings
smtpSettings: "SMTP Configuration",
smtpEnabled: "Enable SMTP",
smtpHost: "SMTP Host",
@@ -24,7 +24,7 @@ export const settingsTranslations: SettingsTranslations = {
enableTLS: "Enable TLS",
localName: "Local Name",
// Test Email
// General Settings - Test Email
testEmail: "Test Email",
sendTestEmail: "Send test email",
emailTemplate: "Email template",
@@ -39,7 +39,7 @@ export const settingsTranslations: SettingsTranslations = {
enterEmailAddress: "Enter email address",
sending: "Sending...",
// Actions and status
// General Settings - Actions and status
save: "Save Changes",
saving: "Saving...",
settingsUpdated: "Settings updated successfully",
@@ -48,5 +48,12 @@ export const settingsTranslations: SettingsTranslations = {
testConnection: "Test Connection",
testingConnection: "Testing Connection...",
connectionSuccess: "Connection successful",
connectionFailed: "Connection failed"
connectionFailed: "Connection failed",
// User Management
addUser: "Add User",
permissionNotice: "Permission Notice:",
permissionNoticeAddUser: "As an admin user, you do not have access to view or modify system and mail settings. These settings can only be accessed and modified by Super Admins. Contact your Super Admin if you need to make changes to system configuration or mail settings.",
loadingSettings: "Loading settings...",
loadingSettingsError: "Error loading settings",
};
+2 -1
View File
@@ -112,5 +112,6 @@ export const sslTranslations: SSLTranslations = {
created: "Created",
lastUpdated: "Last Updated",
lastNotification: "Last Notification",
collectionId: "Collection ID"
collectionId: "Collection ID",
noCertificatesFound: "No Certificates Found",
};
+2 -1
View File
@@ -1,7 +1,7 @@
import { AboutTranslations } from '../types/about';
export const aboutTranslations: AboutTranslations = {
aboutCheckcle: "Checkcleについて",
aboutCheckCle: "Checkcleについて",
systemDescription: "Checkcleは、サーバーとサービスの健全性に関するリアルタイム監視、インシデント管理、運用の透明性を提供するオープンソースの監視スタックです。MIT ライセンスの下で公開されています。",
systemVersion: "システムバージョン",
license: "ライセンス",
@@ -14,4 +14,5 @@ export const aboutTranslations: AboutTranslations = {
quickActions: "クイックアクション",
quickActionsDescription: "一般的な監視操作と機能に素早くアクセスできます。開始するには、以下のアクションを選択してください。",
quickTips: "クイックヒント",
releasedOn: "公開日",
};
+3 -3
View File
@@ -4,11 +4,11 @@ export const commonTranslations: CommonTranslations = {
welcome: "ようこそ",
logout: "ログアウト",
language: "言語",
english: "English",
khmer: "Khmer",
english: "Englisch",
khmer: "ភាសាខ្មែរ",
german: "Deutsch",
simplifiedChinese: "简体中文",
japanese: "日本語",
simplifiedChinese: "简体中文",
goodMorning: "おはようございます",
goodAfternoon: "こんにちは",
goodEvening: "こんばんは",
@@ -51,4 +51,5 @@ export const incidentTranslations: IncidentTranslations = {
configuration: '設定',
failedToUpdateStatus: 'ステータスの更新に失敗しました',
inProgress: '進行中',
enterRootCause: '原因を入力してください',
};
@@ -2,10 +2,20 @@ import { ServicesTranslations } from '../types/services';
export const servicesTranslations: ServicesTranslations = {
serviceName: "サービス名",
serviceNameDesc: "サービスのわかりやすい名前を入力してください",
serviceType: "サービスタイプ",
serviceStatus: "サービスステータス",
responseTime: "応答時間",
uptime: "稼働時間",
lastChecked: "最終チェック",
noServices: "フィルタ条件に一致するサービスがありません。",
currentlyMonitoring: "現在監視中",
retry: "再試行",
overview: "概要",
newService: "新しいサービス",
rowsPerPage: "1ページあたりの行数",
search: "検索",
allTypes: "すべてのタイプ",
createNewService: "新しいサービスを作成",
createNewServiceDesc: "監視する新しいサービスを作成するには、詳細を入力してください。",
};
+2 -1
View File
@@ -111,5 +111,6 @@ export const sslTranslations: SSLTranslations = {
created: "作成日",
lastUpdated: "最終更新",
lastNotification: "最終通知",
collectionId: "コレクションID"
collectionId: "コレクションID",
noCertificatesFound: "証明書が見つかりません",
};
+2 -1
View File
@@ -2,7 +2,7 @@
import { AboutTranslations } from '../types/about';
export const aboutTranslations: AboutTranslations = {
aboutCheckcle: "អំពី Checkcle",
aboutCheckCle: "អំពី Checkcle",
systemDescription: "Checkcle គឺជាស្តាកការត្រួតពិនិត្យប្រភពបើកទូលាយដែលផ្តល់នូវការយល់ដឹងជាក់ស្តែងអំពីសុខភាពម៉ាស៊ីនមេ និងសេវាកម្ម, ការគ្រប់គ្រងឧបទ្ទវហេតុ, និងតម្លាភាពនៃប្រតិបត្តិការ។ ចេញផ្សាយជាអាជ្ញាបណ្ណ MIT។",
systemVersion: "កំណែប្រព័ន្ធ",
license: "អាជ្ញាបណ្ណ",
@@ -15,4 +15,5 @@ export const aboutTranslations: AboutTranslations = {
quickActions: "សកម្មភាពរហ័ស",
quickActionsDescription: "ចូលប្រើប្រតិបត្តិការត្រួតពិនិត្យ និងមុខងារទូទៅយ៉ាងរហ័ស។ ជ្រើសរើសសកម្មភាពខាងក្រោមដើម្បីចាប់ផ្តើម។",
quickTips: "គន្លឹះរហ័ស",
releasedOn: "បានចេញផ្សាយនៅថ្ងៃទី",
};
+3 -2
View File
@@ -5,9 +5,10 @@ export const commonTranslations: CommonTranslations = {
welcome: "សូមស្វាគមន៍",
logout: "ចាកចេញ",
language: "ភាសា",
english: "អង់គ្លេស",
khmer: "ខ្មែរ",
english: "Englisch",
khmer: "ភាសាខ្មែរ",
german: "Deutsch",
japanese: "日本語",
simplifiedChinese: "简体中文",
goodMorning: "អរុណសួស្តី",
goodAfternoon: "ទិវាសួស្តី",
@@ -52,4 +52,5 @@ export const incidentTranslations: IncidentTranslations = {
configuration: "ការកំណត់រចនាសម្ព័ន្ធ",
failedToUpdateStatus: "បរាជ័យក្នុងការធ្វើបច្ចុប្បន្នភាពស្ថានភាព",
inProgress: "កំពុងដំណើរការ",
enterRootCause: 'បញ្ចូលហេតុផលដើម',
};
@@ -3,10 +3,20 @@ import { ServicesTranslations } from '../types/services';
export const servicesTranslations: ServicesTranslations = {
serviceName: "ឈ្មោះសេវាកម្ម",
serviceNameDesc: "បញ្ចូលឈ្មោះពិពណ៌នាសម្រាប់សេវាកម្មរបស់អ្នក",
serviceType: "ប្រភេទសេវាកម្ម",
serviceStatus: "ស្ថានភាពសេវាកម្ម",
responseTime: "ពេលវេលាឆ្លើយតប",
uptime: "ពេលវេលាដំណើរការ",
lastChecked: "ពិនិត្យចុងក្រោយ",
noServices: "មិនមានសេវាកម្មដែលត្រូវនឹងលក្ខណៈវិនិច្ឆ័យរបស់អ្នក។",
currentlyMonitoring: "កំពុងតាមដានពេលនេះ",
retry: "សាកល្បងម្ដងទៀត",
overview: "ទិដ្ឋភាពទូទៅ",
newService: "សេវាកម្មថ្មី",
rowsPerPage: "ចំនួនជួរដេកក្នុងមួយទំព័រ",
search: "ស្វែងរក",
allTypes: "គ្រប់ប្រភេទ",
createNewService: "បង្កើតសេវាកម្មថ្មី",
createNewServiceDesc: "បញ្ចូលព័ត៌មានលម្អិតដើម្បីបង្កើតសេវាកម្មថ្មីដែលត្រូវតាមដាន។",
};
+2 -1
View File
@@ -112,5 +112,6 @@ export const sslTranslations: SSLTranslations = {
created: "បានបង្កើត",
lastUpdated: "បានធ្វើបច្ចុប្បន្នភាពចុងក្រោយ",
lastNotification: "ការជូនដំណឹងចុងក្រោយ",
collectionId: "លេខសម្គាល់ប្រមូលផ្តុំ"
collectionId: "លេខសម្គាល់ប្រមូលផ្តុំ",
noCertificatesFound: "រកមិនឃើញវិញ្ញាបនប័ត្រ",
};
@@ -13,4 +13,5 @@ export interface AboutTranslations {
quickActions: string;
quickActionsDescription: string;
quickTips: string;
releasedOn: string;
}
@@ -50,4 +50,5 @@ export interface IncidentTranslations {
configuration: string;
failedToUpdateStatus: string;
inProgress: string;
enterRootCause: string;
}
+88 -2
View File
@@ -1,10 +1,96 @@
export interface ServicesTranslations {
serviceName: string;
serviceType: string;
serviceStatus: string;
responseTime: string;
uptime: string;
lastChecked: string;
noServices: string;
currentlyMonitoring: string;
retry: string;
overview: string;
newService: string;
rowsPerPage: string;
search: string;
allTypes: string;
createNewService: string;
createNewServiceDesc: string;
// ServiceBasicFields.tsx
serviceName: string;
serviceNameDesc: string;
// ServiceConfigFields.tsx
checkInterval: string;
seconds: string;
minute: string;
minutes: string;
hour: string;
hours: string;
custom: string;
checkIntervalPlaceholder: string;
backToPresets: string;
checkIntervalDesc: string;
checkIntervalDescCustom: string;
retryAttempts: string;
attempt: string;
attempts: string;
retryAttemptsDesc: string;
// ServiceForm.tsx
updateService: string;
createService: string;
// ServiceNotificationFields.tsx
enableNotifications: string;
enableNotificationsDesc: string;
notificationChannels: string;
notificationChannelsEnabledDesc: string;
notificationChannelsDesc: string;
notificationChannelsPlaceholder: string;
alertTemplate: string;
alertTemplateLoading: string;
alertTemplatePlaceholder: string;
alertTemplateEnabledDesc: string;
alertTemplateDesc: string;
// ServiceTypeField.tsx
serviceType: string;
serviceTypeHTTPDesc: string;
serviceTypePINGDesc: string;
serviceTypeTCPDesc: string;
serviceTypeDNSDesc: string;
// ServiceRegionalFields.tsx
regionalMonitoring: string;
regionalMonitoringDesc: string;
regionalAgents: string;
regionalAgentsLoading: string;
regionalAgentsAvailablePlaceholder: string;
regionalAgentsAllSelected: string;
regionalAgentsNoAvailable: string;
regionalAgentsNotFoundMessage: string;
regionalAgentsNotSelectedMessage: string;
// ServiceUrlField.tsx
targetDefault: string;
targetDNS: string;
targetHTTPDesc: string;
targetPINGDesc: string;
targetTCPDesc: string;
targetTCPPortDesc: string;
targetDNSDesc: string;
targetDefaultDesc: string;
targetDefaultPlaceholder: string;
// types.ts
serviceNameRequired: string;
urlDomainHostRequired: string;
enterValidUrlHostnameDomain: string;
// Dashboard
upServices: string;
downServices: string;
pausedServices: string;
warningServices: string;
}
+12 -5
View File
@@ -1,17 +1,17 @@
export interface SettingsTranslations {
// Tabs
// General Settings - Tabs
systemSettings: string;
mailSettings: string;
// System Settings
// General Settings - System Settings
appName: string;
appURL: string;
senderName: string;
senderEmail: string;
hideControls: string;
// Mail Settings
// General Settings - Mail Settings
smtpSettings?: string;
smtpEnabled: string;
smtpHost: string;
@@ -22,7 +22,7 @@ export interface SettingsTranslations {
enableTLS: string;
localName: string;
// Test Email
// General Settings - Test Email
testEmail: string;
sendTestEmail: string;
emailTemplate: string;
@@ -37,7 +37,7 @@ export interface SettingsTranslations {
enterEmailAddress: string;
sending: string;
// Actions and status
// General Settings - Actions and status
save: string;
saving: string;
settingsUpdated: string;
@@ -47,4 +47,11 @@ export interface SettingsTranslations {
testingConnection: string;
connectionSuccess: string;
connectionFailed: string;
// User Management
addUser: string;
permissionNotice: string;
permissionNoticeAddUser: string;
loadingSettings: string;
loadingSettingsError: string;
}
@@ -111,4 +111,5 @@ export interface SSLTranslations {
lastUpdated: string;
lastNotification: string;
collectionId: string;
noCertificatesFound: string;
}
+2 -1
View File
@@ -2,7 +2,7 @@
import { AboutTranslations } from '../types/about';
export const aboutTranslations: AboutTranslations = {
aboutCheckcle: "关于 Checkcle",
aboutCheckCle: "关于 Checkcle",
systemDescription: "Checkcle 是一个开源监控平台,可提供有关服务器和服务健康状况的实时洞察、事件管理以及透明化运营。以 MIT 许可证发布。",
systemVersion: "系统版本",
license: "许可证",
@@ -15,4 +15,5 @@ export const aboutTranslations: AboutTranslations = {
quickActions: "快速操作",
quickActionsDescription: "快速访问常用的监控操作和功能。选择下面的操作开始。",
quickTips: "快速提示",
releasedOn: "发布于",
};
+3 -2
View File
@@ -5,9 +5,10 @@ export const commonTranslations: CommonTranslations = {
welcome: "欢迎",
logout: "注销",
language: "语言",
english: "English",
khmer: "Khmer",
english: "Englisch",
khmer: "ភាសាខ្មែរ",
german: "Deutsch",
japanese: "日本語",
simplifiedChinese: "简体中文",
goodMorning: "早上好",
goodAfternoon: "下午好",
@@ -52,4 +52,5 @@ export const incidentTranslations: IncidentTranslations = {
configuration: '配置',
failedToUpdateStatus: '更新状态失败',
inProgress: '进行中',
enterRootCause: '输入故障根源',
};
@@ -4,7 +4,7 @@ import { MaintenanceTranslations } from '../types/maintenance';
export const maintenanceTranslations: MaintenanceTranslations = {
scheduledMaintenance: '计划维护',
scheduledMaintenanceDesc: '查看和管理您系统和服务的计划维护窗口',
upcomingMaintenance: '即将',
upcomingMaintenance: '计划中',
ongoingMaintenance: '进行中',
completedMaintenance: '已完成',
createMaintenanceWindow: '创建维护',
+1 -1
View File
@@ -2,7 +2,7 @@
import { MenuTranslations } from '../types/menu';
export const menuTranslations: MenuTranslations = {
uptimeMonitoring: "Uptime 监控",
uptimeMonitoring: "在线监控",
instanceMonitoring: "实例监控",
sslDomain: "SSL & 域名",
scheduleIncident: "计划与事件",
+90 -4
View File
@@ -2,11 +2,97 @@
import { ServicesTranslations } from '../types/services';
export const servicesTranslations: ServicesTranslations = {
serviceName: "服务名称",
serviceType: "服务类型",
serviceStatus: "服务状态",
responseTime: "响应时间",
uptime: "Uptime",
uptime: "在线时间",
lastChecked: "最后检查时间",
noServices: "没有符合您筛选条件的服务。",
};
currentlyMonitoring: "当前监控",
retry: "重试",
overview: "概览",
newService: "新增服务",
rowsPerPage: "每页行数",
search: "搜索",
allTypes: "所有类型",
createNewService: "添加新服务",
createNewServiceDesc: "填写详细信息以创建要监控的新服务。",
// ServiceBasicFields.tsx
serviceName: "服务名称",
serviceNameDesc: "为你的服务输入一个描述性名称",
// ServiceConfigFields.tsx
checkInterval: "检查间隔",
seconds: "秒",
minute: "分钟",
minutes: "分钟",
hour: "小时",
hours: "小时",
custom: "自定义",
checkIntervalPlaceholder: "输入间隔时间(秒)",
backToPresets: "回到预设",
checkIntervalDesc: "多久检查一次服务状态",
checkIntervalDescCustom: "输入自定义间隔时间(以秒为单位,最小10秒)",
retryAttempts: "重试次数",
attempt: "次",
attempts: "次",
retryAttemptsDesc: "在标记为下线之前重试的次数",
// ServiceForm.tsx
updateService: "更新服务",
createService: "创建服务",
// ServiceNotificationFields.tsx
enableNotifications: "启用通知",
enableNotificationsDesc: "为本服务启用或禁用通知",
notificationChannels: "通知渠道",
notificationChannelsEnabledDesc: "为本服务选择通知渠道",
notificationChannelsDesc: "先启用通知以选择渠道",
notificationChannelsPlaceholder: "添加一个通知渠道",
alertTemplate: "警报模板",
alertTemplateLoading: "正在加载模板...",
alertTemplatePlaceholder: "选择一个警报模板",
alertTemplateEnabledDesc: "选择一个警报消息的模板",
alertTemplateDesc: "先启用通知才能选择模板",
// ServiceTypeField.tsx
serviceType: "服务类型",
serviceTypeHTTPDesc: "使用 HTTP/HTTPS 协议监控网站和 RESTAPI",
serviceTypePINGDesc: "使用 PING 协议监控主机可用性",
serviceTypeTCPDesc: "使用 TCP 协议监控 TCP 端口连接性",
serviceTypeDNSDesc: "监控 DNS 解析",
// ServiceRegionalFields.tsx
regionalMonitoring: "区域监控",
regionalMonitoringDesc: "将此服务分配给区域监控代理以进行分布式监控",
regionalAgents: "区域代理",
regionalAgentsLoading: "加载代理中...",
regionalAgentsAvailablePlaceholder: "选择额外的区域代理...",
regionalAgentsAllSelected: "已选择所有可用代理",
regionalAgentsNoAvailable: "无可用区域代理",
regionalAgentsNoOnlineAvailable: "无可用在线区域代理",
regionalAgentsNotFoundMessage: "未找到在线区域代理。服务将使用默认监控。",
regionalAgentsNotSelectedMessage: "未选择区域代理。服务将使用默认监控。",
// ServiceUrlField.tsx
targetDefault: "目标 URL/Host",
targetDNS: "域名",
targetHTTPDesc: "输入完整的 URL,包括协议(http:// 或 https://",
targetPINGDesc: "输入要 ping 的主机名或 IP 地址",
targetTCPDesc: "输入主机名或 IP 地址以进行 TCP 连接测试",
targetTCPPortDesc: "输入用于 TCP 连接测试的端口号",
targetDNSDesc: "输入要监控的 DNS 记录域名(A、AAAA、MX等)",
targetDefaultDesc: "输入要监控的目标 URL 或主机名",
targetDefaultPlaceholder: "输入 URL 或主机名",
// types.ts
serviceNameRequired: "服务名称是必填项",
urlDomainHostRequired: "URL/域名/主机名是必填项",
enterValidUrlHostnameDomain: "请输入有效的URL、主机名或域名",
// Dashboard
upServices: "在线服务",
downServices: "下线服务",
pausedServices: "暂停服务",
warningServices: "告警服务",
};
+14 -7
View File
@@ -2,18 +2,18 @@
import { SettingsTranslations } from '../types/settings';
export const settingsTranslations: SettingsTranslations = {
// Tabs
// General Settings - Tabs
systemSettings: "系统设置",
mailSettings: "邮件设置",
// System Settings
// General Settings - System Settings
appName: "应用名称",
appURL: "应用 URL",
senderName: "发送者名称",
senderEmail: "发送者邮箱地址",
hideControls: "隐藏控件",
// Mail Settings
// General Settings - Mail Settings
smtpSettings: "SMTP 配置",
smtpEnabled: "启用 SMTP",
smtpHost: "SMTP 主机",
@@ -24,7 +24,7 @@ export const settingsTranslations: SettingsTranslations = {
enableTLS: "启用 TLS",
localName: "本地名称",
// Test Email
// General Settings - Test Email
testEmail: "测试邮箱",
sendTestEmail: "发送测试邮箱",
emailTemplate: "邮箱模板",
@@ -39,7 +39,7 @@ export const settingsTranslations: SettingsTranslations = {
enterEmailAddress: "输入收件人邮箱地址",
sending: "发送中...",
// Actions and status
// General Settings - Actions and status
save: "保存变更",
saving: "保存中...",
settingsUpdated: "设置已成功更新",
@@ -48,5 +48,12 @@ export const settingsTranslations: SettingsTranslations = {
testConnection: "测试连接",
testingConnection: "测试连接中...",
connectionSuccess: "连接成功",
connectionFailed: "连接失败"
};
connectionFailed: "连接失败",
// User Management
addUser: "添加用户",
permissionNotice: "权限问题:",
permissionNoticeAddUser: "作为管理员用户,您无权查看或修改系统及邮件设置。这些设置仅超级管理员可访问和修改。如需更改系统配置或邮件设置,请联系您的超级管理员。",
loadingSettings: "加载设置中...",
loadingSettingsError: "加载设置时出错",
};
+2 -1
View File
@@ -112,5 +112,6 @@ export const sslTranslations: SSLTranslations = {
created: "创建时间",
lastUpdated: "最后更新时间",
lastNotification: "最后通知时间",
collectionId: "集合 ID"
collectionId: "集合 ID",
noCertificatesFound: "未找到证书",
};