Merge pull request #68 from operacle/develop

- feat: Implement notification channels based on status (Service dialog form) and refactor Service form into smaller components
- feat: Allow multiple selections for regional agents and support multiple notifications in the Service form
- feat: Add Japanese language support. Thank you @gnsworks for the contribution!
This commit is contained in:
Tola Leng
2025-07-13 18:44:44 +07:00
committed by GitHub
28 changed files with 1431 additions and 130 deletions
+1
View File
@@ -24,3 +24,4 @@ dist-ssr
*.sw?
application/src/lib/pocketbase.ts
Dockerfile
application/src/lib/pocketbase-prod.ts
+64 -2
View File
@@ -1,3 +1,36 @@
## 🌐 Select Language
<table align="center">
<tr>
<td align="center">
<a href="CONTRIBUTING.md">
<img src="https://flagcdn.com/24x18/gb.png" alt="English" />
<br/><strong>English</strong>
</a>
</td>
<td align="center">
<a href="docs/CONTRIBUTING_km.md">
<img src="https://flagcdn.com/24x18/kh.png" alt="Khmer" />
<br/><strong>ខ្មែរ</strong>
</a>
</td>
<td align="center">
<a href="docs/CONTRIBUTING_ja.md">
<img src="https://flagcdn.com/24x18/jp.png" alt="Japanese" />
<br/><strong>Japanese</strong>
</a>
</td>
</tr>
</table>
<p align="center">
Thank you to all our contributors, users, and supporters for making this project thrive.
</p>
<p align="center">
🚀 <strong>Stay tuned for more updates, features, and improvements.</strong>
</p>
# 🛠️ Contributing to CheckCle
@@ -61,16 +94,45 @@ npm install && npm run dev
#Server Backend
cd server
./pocketbase serve --dir pb_data
./pocketbase serve --dir pb_data
If you're not using localhost, please run with this (./pocketbase serve --http=0.0.0.0:8090 --dir pb_data)
```
### 4. Start Service Check Operation
```bash
#Server Backend
Start Service Operation (Check Service for PING, HTTP, TCP, DNS)
cd server/service-operation
go run main.go (you do not need to change the .env while it's the localhost connection)
```
### 5. Start Distributed Regional Agent
```bash
#### 1. Fork the Repository
Click "Fork" on [GitHub](https://github.com/operacle/Distributed-Regional-Monitoring) to create your own copy.
#### 2. Clone Your Fork
git clone --branch main https://github.com/operacle/Distributed-Regional-Monitoring.git
cd Distributed-Regional-Monitoring
#### 3. Install Go Service (make sure you have installed the Go Service)
Copy .env.example -> .env
Change regional agent configuration in .env file
and Run: go run main.go
```
---
## ✅ Pull Request Process
1. Ensure your code follows the existing style and naming conventions.
2. Write clear, concise commit messages.
3. Push your branch and open a Pull Request (PR) on the `main` branch.
3. Push your branch and open a Pull Request (PR) on the `develop` branch.
4. Provide a meaningful PR description (what/why/how).
5. Link related issues if applicable (e.g. `Closes #12`).
6. Make sure all checks pass (e.g., linting, tests).
+33
View File
@@ -1,3 +1,36 @@
## 🌐 Select Language
<table align="center">
<tr>
<td align="center">
<a href="README.md">
<img src="https://flagcdn.com/24x18/gb.png" alt="English" />
<br/><strong>English</strong>
</a>
</td>
<td align="center">
<a href="docs/README_km.md">
<img src="https://flagcdn.com/24x18/kh.png" alt="Khmer" />
<br/><strong>ខ្មែរ</strong>
</a>
</td>
<td align="center">
<a href="docs/README_ja.md">
<img src="https://flagcdn.com/24x18/jp.png" alt="Japanese" />
<br/><strong>Japanese</strong>
</a>
</td>
</tr>
</table>
<p align="center">
Thank you to all our contributors, users, and supporters for making this project thrive.
</p>
<p align="center">
🚀 <strong>Stay tuned for more updates, features, and improvements.</strong>
</p>
![CheckCle Platform](https://pub-4a4062303020445f8f289a2fee84f9e8.r2.dev/images/server-detail-page.png)
# 🚀 What is CheckCle?
+9 -10
View File
@@ -2,7 +2,7 @@
## 📦 Project: [checkcle](https://github.com/operacle/checkcle)
**checkcle** is a lightweight, TypeScript-based built for uptime and server monitoring tools.
**checkcle** is a self-hosted uptime and server monitoring tool built with TypeScript and Go.
We care about the security and privacy of users running this project in production environments.
@@ -47,22 +47,21 @@ We support the latest stable release of `checkcle`. Security patches may also be
## 🔍 Security Practices
This project adheres to the following practices to enhance security:
CheckCle follows these practices to improve overall security:
- 🔎 Regular vulnerability checks using `npm audit`
- ⛓️ Dependency pinning via `package-lock.json`
- ✅ Type-safe code using `TypeScript`
- 🧪 Continuous testing and CI
- 🔎 Regular vulnerability scanning (npm audit for JavaScript dependencies, govulncheck for Go modules)
- ⛓️ Dependency pinning (package-lock.json and Go modules)
- ✅ Type-safe code in TypeScript and memory-safe design in Go
- 🧪 Continuous testing and CI pipelines
- 🔐 No data is stored or transmitted unless explicitly configured by the user
- 🧑‍💻 All contributions are reviewed before being merged
- 🧑‍💻 All code contributions are reviewed before merging
---
## ⚠️ Known Security Limitations
- `checkcle` makes outbound HTTPS requests to check certificate details — avoid running in untrusted or high-risk environments without proper network policies.
- Output may contain certificate metadata (e.g., CN, SANs, expiry dates) — avoid exposing this to public logs unless sanitized.
- The data may be lost upon system restarts or crashes. Always ensure that backup and recovery mechanisms are in place in production environments.
- Outbound HTTPS requests: CheckCle agents perform outbound HTTPS connections to send metric data to the backend server. Avoid deploying in untrusted or high-risk environments without appropriate network policies and monitoring.
- The data may be lost upon system restarts or crashes. Always ensure that backup (pb_data) and recovery mechanisms are in place in production environments.
---
@@ -129,6 +129,9 @@ export const Header = ({
<DropdownMenuItem onClick={() => setLanguage("de")} className={language === "de" ? "bg-accent" : ""}>
{t("Deutsch")}
</DropdownMenuItem>
<DropdownMenuItem onClick={() => setLanguage("ja")} className={language === "ja" ? "bg-accent" : ""}>
{t("japanese")}
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
@@ -1,4 +1,3 @@
import { Form } from "@/components/ui/form";
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
@@ -47,18 +46,18 @@ export function ServiceForm({
form.reset(formData);
// Log for debugging
console.log("Populating form with data:", {
type: formData.type,
url: formData.url,
port: formData.port,
regionalAgent: formData.regionalAgent,
regionalMonitoringEnabled: formData.regionalMonitoringEnabled,
regional_status: initialData.regional_status,
region_name: initialData.region_name,
agent_id: initialData.agent_id,
notification_status: initialData.notification_status,
notificationChannels: formData.notificationChannels
});
// console.log("Populating form with data:", {
// type: formData.type,
// url: formData.url,
// port: formData.port,
// regionalAgents: formData.regionalAgents,
// regionalMonitoringEnabled: formData.regionalMonitoringEnabled,
// regional_status: initialData.regional_status,
// region_name: initialData.region_name,
// agent_id: initialData.agent_id,
// notification_status: initialData.notification_status,
// notificationChannels: formData.notificationChannels
// });
}
}, [initialData, isEdit, form]);
@@ -97,7 +96,7 @@ export function ServiceForm({
form.reset();
}
} catch (error) {
console.error(`Error ${isEdit ? 'updating' : 'creating'} service:`, error);
// console.error(`Error ${isEdit ? 'updating' : 'creating'} service:`, error);
toast({
title: `Failed to ${isEdit ? 'update' : 'create'} service`,
description: `An error occurred while ${isEdit ? 'updating' : 'creating'} the service.`,
@@ -6,7 +6,9 @@ import { UseFormReturn } from "react-hook-form";
import { ServiceFormData } from "./types";
import { useQuery } from "@tanstack/react-query";
import { regionalService } from "@/services/regionalService";
import { MapPin, Loader2, X } from "lucide-react";
import { MapPin, Loader2, X, Plus } from "lucide-react";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
interface ServiceRegionalFieldsProps {
form: UseFormReturn<ServiceFormData>;
@@ -14,7 +16,7 @@ interface ServiceRegionalFieldsProps {
export function ServiceRegionalFields({ form }: ServiceRegionalFieldsProps) {
const regionalMonitoringEnabled = form.watch("regionalMonitoringEnabled");
const currentRegionalAgent = form.watch("regionalAgent");
const currentRegionalAgents = form.watch("regionalAgents") || [];
const { data: regionalAgents = [], isLoading } = useQuery({
queryKey: ['regional-services'],
@@ -27,15 +29,16 @@ export function ServiceRegionalFields({ form }: ServiceRegionalFieldsProps) {
agent.connection === 'online' && agent.agent_id !== "1"
);
// Find the current agent name for display
const getCurrentAgentDisplay = () => {
if (!currentRegionalAgent || currentRegionalAgent === "unassign") {
return "Select a regional agent or unassign";
}
const [regionName, agentId] = currentRegionalAgent.split("|");
// Get available agents (not already selected)
const availableAgents = onlineAgents.filter(agent =>
!currentRegionalAgents.includes(`${agent.region_name}|${agent.agent_id}`)
);
// Get agent display name
const getAgentDisplayName = (agentValue: string) => {
const [regionName, agentId] = agentValue.split("|");
const agent = onlineAgents.find(agent =>
`${agent.region_name}|${agent.agent_id}` === currentRegionalAgent
`${agent.region_name}|${agent.agent_id}` === agentValue
);
if (agent) {
@@ -43,25 +46,27 @@ export function ServiceRegionalFields({ form }: ServiceRegionalFieldsProps) {
}
// If agent is not found in online agents, it might be offline but still assigned
// Show the region name from the stored value
if (regionName && agentId) {
return `${regionName} (Agent ${agentId}) - Offline`;
}
return "Select a regional agent or unassign";
return agentValue;
};
// Get the proper select value - handle both assigned and unassigned cases
const getSelectValue = () => {
if (!regionalMonitoringEnabled) {
return "unassign";
// Add regional agent
const addRegionalAgent = (agentValue: string) => {
if (agentValue && agentValue !== "select") {
const currentAgents = form.getValues("regionalAgents") || [];
if (!currentAgents.includes(agentValue)) {
form.setValue("regionalAgents", [...currentAgents, agentValue]);
}
}
if (!currentRegionalAgent || currentRegionalAgent === "") {
return "unassign";
}
return currentRegionalAgent;
};
// Remove regional agent
const removeRegionalAgent = (agentValue: string) => {
const currentAgents = form.getValues("regionalAgents") || [];
form.setValue("regionalAgents", currentAgents.filter(agent => agent !== agentValue));
};
return (
@@ -77,13 +82,19 @@ export function ServiceRegionalFields({ form }: ServiceRegionalFieldsProps) {
Regional Monitoring
</FormLabel>
<div className="text-sm text-muted-foreground">
Assign this service to a regional monitoring agent for distributed monitoring
Assign this service to regional monitoring agents for distributed monitoring
</div>
</div>
<FormControl>
<Switch
checked={field.value || false}
onCheckedChange={field.onChange}
onCheckedChange={(checked) => {
field.onChange(checked);
// Clear agents when disabling regional monitoring
if (!checked) {
form.setValue("regionalAgents", []);
}
}}
/>
</FormControl>
</FormItem>
@@ -93,16 +104,36 @@ export function ServiceRegionalFields({ form }: ServiceRegionalFieldsProps) {
{regionalMonitoringEnabled && (
<FormField
control={form.control}
name="regionalAgent"
name="regionalAgents"
render={({ field }) => (
<FormItem>
<FormLabel>Regional Agent</FormLabel>
<FormLabel>Regional Agents</FormLabel>
{/* Display selected agents */}
{currentRegionalAgents.length > 0 && (
<div className="flex flex-wrap gap-2 mb-3">
{currentRegionalAgents.map((agentValue) => (
<Badge key={agentValue} variant="secondary" className="flex items-center gap-2">
<div className="w-2 h-2 bg-green-500 rounded-full"></div>
<span className="text-sm">{getAgentDisplayName(agentValue)}</span>
<Button
type="button"
variant="ghost"
size="sm"
className="h-4 w-4 p-0 ml-1"
onClick={() => removeRegionalAgent(agentValue)}
>
<X className="h-3 w-3" />
</Button>
</Badge>
))}
</div>
)}
{/* Add new agent selector */}
<Select
onValueChange={(value) => {
// Handle the unassign case by setting to empty string
field.onChange(value === "unassign" ? "" : value);
}}
value={getSelectValue()}
onValueChange={addRegionalAgent}
value="select"
disabled={isLoading}
>
<FormControl>
@@ -110,7 +141,11 @@ export function ServiceRegionalFields({ form }: ServiceRegionalFieldsProps) {
<SelectValue placeholder={
isLoading
? "Loading agents..."
: getCurrentAgentDisplay()
: availableAgents.length > 0
? "Select additional regional agents..."
: currentRegionalAgents.length > 0
? "All available agents selected"
: "No regional agents available"
} />
</SelectTrigger>
</FormControl>
@@ -122,47 +157,52 @@ export function ServiceRegionalFields({ form }: ServiceRegionalFieldsProps) {
Loading agents...
</div>
</SelectItem>
) : availableAgents.length === 0 ? (
<SelectItem value="no-agents" disabled>
{currentRegionalAgents.length > 0
? "All available agents selected"
: "No online regional agents available"
}
</SelectItem>
) : (
<>
<SelectItem value="unassign">
<SelectItem value="select" disabled>
<div className="flex items-center gap-2">
<X className="h-4 w-4 text-red-500" />
<span className="text-red-600">Unassign (No Regional Agent)</span>
<Plus className="h-4 w-4 text-muted-foreground" />
<span className="text-muted-foreground">Select an agent to add...</span>
</div>
</SelectItem>
{onlineAgents.length === 0 ? (
<SelectItem value="no-agents" disabled>
No online regional agents available
{availableAgents.map((agent) => (
<SelectItem key={agent.id} value={`${agent.region_name}|${agent.agent_id}`}>
<div className="flex items-center gap-2">
<div className="w-2 h-2 bg-green-500 rounded-full"></div>
<span className="font-medium">{agent.region_name}</span>
<span className="text-muted-foreground">({agent.agent_ip_address})</span>
</div>
</SelectItem>
) : (
onlineAgents.map((agent) => (
<SelectItem key={agent.id} value={`${agent.region_name}|${agent.agent_id}`}>
<div className="flex items-center gap-2">
<div className="w-2 h-2 bg-green-500 rounded-full"></div>
<span className="font-medium">{agent.region_name}</span>
<span className="text-muted-foreground">({agent.agent_ip_address})</span>
</div>
</SelectItem>
))
)}
))}
</>
)}
</SelectContent>
</Select>
<FormMessage />
{regionalMonitoringEnabled && onlineAgents.length === 0 && !isLoading && (
<p className="text-sm text-amber-600">
No online regional agents found. Services will use default monitoring.
</p>
)}
{currentRegionalAgent && currentRegionalAgent !== "" && (
<p className="text-sm text-green-600">
Currently assigned to: {getCurrentAgentDisplay()}
{currentRegionalAgents.length === 0 && regionalMonitoringEnabled && (
<p className="text-sm text-orange-600">
No regional agents selected. Service will use default monitoring.
</p>
)}
{(!currentRegionalAgent || currentRegionalAgent === "") && regionalMonitoringEnabled && (
<p className="text-sm text-orange-600">
Service is unassigned and will use default monitoring.
{currentRegionalAgents.length > 0 && (
<p className="text-sm text-green-600">
Service assigned to {currentRegionalAgents.length} regional agent{currentRegionalAgents.length > 1 ? 's' : ''}.
</p>
)}
</FormItem>
@@ -13,7 +13,7 @@ export const getServiceFormDefaults = (): ServiceFormData => ({
notificationChannels: [],
alertTemplate: "",
regionalMonitoringEnabled: false,
regionalAgent: "",
regionalAgents: [],
});
export const mapServiceToFormData = (service: Service): ServiceFormData => {
@@ -40,30 +40,67 @@ export const mapServiceToFormData = (service: Service): ServiceFormData => {
// Handle regional monitoring data - check regional_status field
const isRegionalEnabled = service.regional_status === "enabled";
const regionalAgent = isRegionalEnabled && service.region_name && service.agent_id
? `${service.region_name}|${service.agent_id}`
: "";
const regionalAgents: string[] = [];
// Parse multiple regional agents from comma-separated region_name and agent_id fields
if (isRegionalEnabled && service.region_name && service.agent_id) {
const regionNames = service.region_name.includes(',')
? service.region_name.split(',').map(name => name.trim()).filter(name => name)
: [service.region_name];
const agentIds = service.agent_id.includes(',')
? service.agent_id.split(',').map(id => id.trim()).filter(id => id)
: [service.agent_id];
// Combine region names and agent IDs (they should have the same length)
const maxLength = Math.max(regionNames.length, agentIds.length);
for (let i = 0; i < maxLength; i++) {
const regionName = regionNames[i] || regionNames[0] || "";
const agentId = agentIds[i] || agentIds[0] || "";
if (regionName && agentId) {
regionalAgents.push(`${regionName}|${agentId}`);
}
}
}
// Handle notification channels - convert notification_channel and notificationChannel to array
// Handle notification channels - prioritize notification_channel field which contains JSON array
const notificationChannels: string[] = [];
// Check for notification_channel field (from database)
// First check for notification_channel field (JSON string of array)
if (service.notification_channel) {
notificationChannels.push(service.notification_channel);
try {
const parsedChannels = JSON.parse(service.notification_channel);
if (Array.isArray(parsedChannels)) {
notificationChannels.push(...parsedChannels);
}
} catch (error) {
// console.warn("Failed to parse notification_channel JSON:", error);
// If parsing fails, treat as single channel ID
notificationChannels.push(service.notification_channel);
}
}
// Also check for notificationChannel field (backward compatibility)
if (service.notificationChannel && !notificationChannels.includes(service.notificationChannel)) {
notificationChannels.push(service.notificationChannel);
// Fallback to comma-separated notification_id field
if (notificationChannels.length === 0 && service.notificationChannel) {
// Check if it's comma-separated
if (service.notificationChannel.includes(',')) {
const channels = service.notificationChannel.split(',').map(id => id.trim()).filter(id => id);
notificationChannels.push(...channels);
} else {
notificationChannels.push(service.notificationChannel);
}
}
console.log("Mapping service to form data:", {
serviceName: service.name,
notification_status: service.notification_status,
notification_channel: service.notification_channel,
notificationChannel: service.notificationChannel,
mappedChannels: notificationChannels
});
// console.log("Mapping service to form data:", {
// serviceName: service.name,
// notification_status: service.notification_status,
// notification_channel: service.notification_channel,
// notificationChannel: service.notificationChannel,
// mappedChannels: notificationChannels,
// regionalAgents: regionalAgents,
// region_name: service.region_name,
/// agent_id: service.agent_id
// });
return {
name: service.name || "",
@@ -76,24 +113,37 @@ export const mapServiceToFormData = (service: Service): ServiceFormData => {
notificationChannels: notificationChannels,
alertTemplate: service.alertTemplate === "default" ? "" : service.alertTemplate || "",
regionalMonitoringEnabled: isRegionalEnabled,
regionalAgent: regionalAgent,
regionalAgents: regionalAgents,
};
};
export const mapFormDataToServiceData = (data: ServiceFormData) => {
// Parse regional agent selection
let regionName = "";
let agentId = "";
// Parse regional agent selections - store multiple agents as comma-separated values
let regionNames = "";
let agentIds = "";
let regionalStatus: "enabled" | "disabled" = "disabled";
// Set regional status and agent data based on form values
if (data.regionalMonitoringEnabled) {
if (data.regionalMonitoringEnabled && data.regionalAgents && data.regionalAgents.length > 0) {
regionalStatus = "enabled";
if (data.regionalAgent && data.regionalAgent !== "") {
const [parsedRegionName, parsedAgentId] = data.regionalAgent.split("|");
regionName = parsedRegionName || "";
agentId = parsedAgentId || "";
}
// Extract region names and agent IDs from the selected agents
const parsedRegions: string[] = [];
const parsedAgentIds: string[] = [];
data.regionalAgents.forEach(agentValue => {
if (agentValue && agentValue !== "") {
const [regionName, agentId] = agentValue.split("|");
if (regionName && agentId) {
parsedRegions.push(regionName);
parsedAgentIds.push(agentId);
}
}
});
// Store as comma-separated strings
regionNames = parsedRegions.join(',');
agentIds = parsedAgentIds.join(',');
}
// Prepare service data with proper field mapping
@@ -105,10 +155,10 @@ export const mapFormDataToServiceData = (data: ServiceFormData) => {
notificationStatus: data.notificationStatus || "disabled",
notificationChannels: data.notificationChannels || [],
alertTemplate: data.alertTemplate === "default" ? "" : data.alertTemplate,
// Use regional_status field instead of regionalMonitoringEnabled
// Use regional_status field and store multiple agents as comma-separated values
regionalStatus: regionalStatus,
regionName: regionName,
agentId: agentId,
regionName: regionNames,
agentId: agentIds,
// Map the URL field to appropriate database field based on service type
...(data.type === "dns"
? { domain: data.url, url: "", host: "", port: undefined } // DNS: store in domain field
@@ -14,9 +14,9 @@ export const serviceSchema = z.object({
notificationStatus: z.enum(["enabled", "disabled"]).optional(),
notificationChannels: z.array(z.string()).optional(),
alertTemplate: z.string().optional(),
// Regional monitoring fields
// Regional monitoring fields - now supports multiple agents
regionalMonitoringEnabled: z.boolean().optional(),
regionalAgent: z.string().optional(),
regionalAgents: z.array(z.string()).optional(),
});
export type ServiceFormData = z.infer<typeof serviceSchema>;
export type ServiceFormData = z.infer<typeof serviceSchema>;
@@ -107,7 +107,7 @@ export const useConsolidatedUptimeData = ({ serviceId, serviceType, status, inte
// console.log(`Found default monitoring: ${sourceName} for normalized timestamp ${normalizedTimestamp}`);
} else {
// Default monitoring fallback
sourceName = 'Default System Check (Agent 1)';
sourceName = 'Default (Agent 1)';
isDefault = true;
// console.log(`Using fallback default monitoring for normalized timestamp ${normalizedTimestamp}`);
}
+1 -1
View File
@@ -34,4 +34,4 @@ function Badge({ className, variant, ...props }: BadgeProps) {
)
}
export { Badge, badgeVariants }
export { Badge, badgeVariants }
+3 -2
View File
@@ -1,3 +1,4 @@
import * as React from "react"
import { Slot } from "@radix-ui/react-slot"
import { cva, type VariantProps } from "class-variance-authority"
@@ -5,7 +6,7 @@ import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "@/lib/utils"
const buttonVariants = cva(
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
"inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",
{
variants: {
variant: {
@@ -53,4 +54,4 @@ const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
)
Button.displayName = "Button"
export { Button, buttonVariants }
export { Button, buttonVariants }
+25 -11
View File
@@ -1,3 +1,4 @@
import { pb } from '@/lib/pocketbase';
import { Service, CreateServiceParams, UptimeData } from '@/types/service.types';
import { monitoringService } from './monitoring';
@@ -27,6 +28,7 @@ export const serviceService = {
interval: item.heartbeat_interval || item.interval || 60,
retries: item.max_retries || item.retries || 3,
notificationChannel: item.notification_id,
notification_channel: item.notification_channel, // Add this field for multiple channels support
notification_status: item.notification_status || "disabled",
alertTemplate: item.template_id,
muteAlerts: item.alerts === "muted", // Convert string to boolean for compatibility
@@ -39,7 +41,7 @@ export const serviceService = {
regional_monitoring_enabled: item.regional_status === "enabled", // Backward compatibility
}));
} catch (error) {
// console.error("Error fetching services:", error);
// console.error("Error fetching services:", error);
throw new Error('Failed to load services data.');
}
},
@@ -50,7 +52,7 @@ export const serviceService = {
const serviceType = params.type.toLowerCase();
// Debug log to check what we're sending
console.log("Creating service with params:", params);
// console.log("Creating service with params:", params);
const data = {
name: params.name,
@@ -62,8 +64,13 @@ export const serviceService = {
heartbeat_interval: params.interval,
max_retries: params.retries,
notification_status: params.notificationStatus || "disabled",
// Store multiple notification channels as JSON string
notification_channel: params.notificationChannels && params.notificationChannels.length > 0
? JSON.stringify(params.notificationChannels)
: null,
// Store multiple notification IDs as comma-separated string in notification_id field
notification_id: params.notificationChannels && params.notificationChannels.length > 0
? params.notificationChannels[0] // Store first channel for backward compatibility
? params.notificationChannels.join(',')
: null,
template_id: params.alertTemplate,
// Regional monitoring fields - use regional_status
@@ -81,9 +88,9 @@ export const serviceService = {
)
};
console.log("Creating service with data:", data);
// console.log("Creating service with data:", data);
const record = await pb.collection('services').create(data);
console.log("Service created, returned record:", record);
// console.log("Service created, returned record:", record);
// Return the newly created service
const newService = {
@@ -101,6 +108,7 @@ export const serviceService = {
interval: record.heartbeat_interval || 60,
retries: record.max_retries || 3,
notificationChannel: record.notification_id,
notification_channel: record.notification_channel,
notification_status: record.notification_status || "disabled",
alertTemplate: record.template_id,
regional_status: record.regional_status || "disabled",
@@ -114,7 +122,7 @@ export const serviceService = {
return newService;
} catch (error) {
console.error("Error creating service:", error);
// console.error("Error creating service:", error);
throw new Error('Failed to create service.');
}
},
@@ -125,7 +133,7 @@ export const serviceService = {
const serviceType = params.type.toLowerCase();
// Debug log to check what we're updating
console.log("Updating service with params:", params);
// console.log("Updating service with params:", params);
const data = {
name: params.name,
@@ -133,8 +141,13 @@ export const serviceService = {
heartbeat_interval: params.interval,
max_retries: params.retries,
notification_status: params.notificationStatus || "disabled",
// Store multiple notification channels as JSON string
notification_channel: params.notificationChannels && params.notificationChannels.length > 0
? JSON.stringify(params.notificationChannels)
: null,
// Store multiple notification IDs as comma-separated string in notification_id field
notification_id: params.notificationChannels && params.notificationChannels.length > 0
? params.notificationChannels[0] // Store first channel for backward compatibility
? params.notificationChannels.join(',')
: null,
template_id: params.alertTemplate || null,
// Regional monitoring fields - use regional_status
@@ -152,7 +165,7 @@ export const serviceService = {
)
};
console.log("Updating service with data:", data);
// console.log("Updating service with data:", data);
// Use timeout to ensure the request doesn't hang
const timeoutPromise = new Promise<never>((_, reject) => {
@@ -161,7 +174,7 @@ export const serviceService = {
const updatePromise = pb.collection('services').update(id, data);
const record = await Promise.race([updatePromise, timeoutPromise]) as any;
console.log("Service updated, returned record:", record);
// console.log("Service updated, returned record:", record);
// Return the updated service
const updatedService = {
@@ -179,6 +192,7 @@ export const serviceService = {
interval: record.heartbeat_interval || 60,
retries: record.max_retries || 3,
notificationChannel: record.notification_id,
notification_channel: record.notification_channel,
notification_status: record.notification_status || "disabled",
alertTemplate: record.template_id,
regional_status: record.regional_status || "disabled",
@@ -189,7 +203,7 @@ export const serviceService = {
return updatedService;
} catch (error) {
console.error("Error updating service:", error);
//console.error("Error updating service:", error);
throw new Error(`Failed to update service: ${error instanceof Error ? error.message : 'Unknown error'}`);
}
},
+3 -1
View File
@@ -1,13 +1,15 @@
import enTranslations from './en';
import kmTranslations from './km';
import deTranslations from './de';
import jaTranslations from './ja';
export type Language = "en" | "km" | "de";
export type Language = "en" | "km" | "de" | "ja";
export const translations = {
en: enTranslations,
km: kmTranslations,
de: deTranslations,
ja: jaTranslations,
};
// Type for accessing translations by module and key
+17
View File
@@ -0,0 +1,17 @@
import { AboutTranslations } from '../types/about';
export const aboutTranslations: AboutTranslations = {
aboutCheckcle: "Checkcleについて",
systemDescription: "Checkcleは、サーバーとサービスの健全性に関するリアルタイム監視、インシデント管理、運用の透明性を提供するオープンソースの監視スタックです。MIT ライセンスの下で公開されています。",
systemVersion: "システムバージョン",
license: "ライセンス",
mitLicense: "MIT ライセンス",
links: "リンク",
viewOnGithub: "GitHubで表示",
viewDocumentation: "ドキュメントを表示",
followOnX: "Xでフォロー",
joinDiscord: "Discordに参加",
quickActions: "クイックアクション",
quickActionsDescription: "一般的な監視操作と機能に素早くアクセスできます。開始するには、以下のアクションを選択してください。",
quickTips: "クイックヒント",
};
+29
View File
@@ -0,0 +1,29 @@
import { CommonTranslations } from '../types/common';
export const commonTranslations: CommonTranslations = {
welcome: "ようこそ",
logout: "ログアウト",
language: "言語",
english: "English",
khmer: "Khmer",
german: "Deutsch",
japanese: "日本語",
goodMorning: "おはようございます",
goodAfternoon: "こんにちは",
goodEvening: "こんばんは",
profile: "プロファイル",
settings: "設定",
documentation: "ドキュメント",
notifications: "通知",
close: "閉じる",
cancel: "キャンセル",
view: "表示",
edit: "編集",
delete: "削除",
status: "ステータス",
time: "時間",
title: "タイトル",
description: "説明",
success: "成功",
error: "エラー",
};
@@ -0,0 +1,54 @@
import { IncidentTranslations } from '../types/incident';
export const incidentTranslations: IncidentTranslations = {
incidentManagement: 'インシデント管理',
incidentsManagementDesc: 'サービスのインシデントとその解決を追跡・管理する',
unresolvedIncidents: '未解決',
resolvedIncidents: '解決済み',
activeIncidents: 'アクティブなインシデント',
criticalIssues: '重要な問題',
avgResolutionTime: '平均解決時間',
noIncidents: 'アクティブなインシデントはありません',
createIncident: 'インシデントを作成',
investigating: '調査中',
identified: '特定済み',
monitoring: '監視中',
resolved: '解決済み',
scheduleIncidentManagement: 'スケジュール・インシデント管理',
incidentName: 'インシデント名',
incidentStatus: 'インシデントステータス',
highPriority: '高優先度',
configurationSettings: '設定',
incidentCreatedSuccess: 'インシデントが正常に作成されました',
basicInfo: '基本情報',
serviceId: 'サービスID',
assignedTo: '担当者',
unassigned: '未割り当て',
timeline: 'タイムライン',
incidentTime: 'インシデント発生時刻',
resolutionTime: '解決時刻',
systems: 'システム',
noSystems: '影響を受けるシステムはありません',
impactAnalysis: '影響分析',
rootCause: '根本原因',
resolutionSteps: '解決手順',
lessonsLearned: '学んだ教訓',
resolutionDetails: '解決詳細',
assignment: '割り当て',
download: 'ダウンロード',
downloadPdf: 'PDFダウンロード',
print: '印刷',
confidentialNote: 'この文書は機密情報であり、内部使用のみを目的としています。',
generatedOn: '生成日時',
enterResolutionSteps: 'インシデント解決のために取った手順を入力してください',
enterLessonsLearned: 'このインシデントから学んだ教訓を入力してください',
editIncident: 'インシデントを編集',
editIncidentDesc: 'このインシデントの詳細を更新',
updating: '更新中...',
update: '更新',
create: '作成',
creating: '作成中...',
configuration: '設定',
failedToUpdateStatus: 'ステータスの更新に失敗しました',
inProgress: '進行中',
};
+24
View File
@@ -0,0 +1,24 @@
import { Translations } from '../types';
import { commonTranslations } from './common';
import { menuTranslations } from './menu';
import { loginTranslations } from './login';
import { aboutTranslations } from './about';
import { servicesTranslations } from './services';
import { maintenanceTranslations } from './maintenance';
import { incidentTranslations } from './incident';
import { sslTranslations } from './ssl';
import { settingsTranslations } from './settings';
const jaTranslations: Translations = {
common: commonTranslations,
menu: menuTranslations,
login: loginTranslations,
about: aboutTranslations,
services: servicesTranslations,
maintenance: maintenanceTranslations,
incident: incidentTranslations,
ssl: sslTranslations,
settings: settingsTranslations
};
export default jaTranslations;
+22
View File
@@ -0,0 +1,22 @@
import { LoginTranslations } from '../types/login';
export const loginTranslations: LoginTranslations = {
signInToYourAccount: "アカウントにサインイン",
dontHaveAccount: "アカウントをお持ちでない場合",
createOne: "作成する",
signInWithGoogle: "Googleでサインイン",
orContinueWith: "または",
email: "メール",
password: "パスワード",
forgot: "忘れた場合",
signIn: "サインイン",
signingIn: "サインイン中...",
loginSuccessful: "ログイン成功",
loginSuccessMessage: "正常にログインしました。",
loginFailed: "ログイン失敗",
authenticationFailed: "認証に失敗しました",
bySigningIn: "サインインすることで、",
termsAndConditions: "利用規約",
and: "と",
privacyPolicy: "プライバシーポリシー",
};
@@ -0,0 +1,67 @@
import { MaintenanceTranslations } from '../types/maintenance';
export const maintenanceTranslations: MaintenanceTranslations = {
scheduledMaintenance: 'スケジュール済みメンテナンス',
scheduledMaintenanceDesc: 'システムとサービスの計画されたメンテナンスウィンドウを表示・管理',
upcomingMaintenance: '予定',
ongoingMaintenance: '実行中',
completedMaintenance: '完了',
createMaintenanceWindow: 'メンテナンス作成',
totalScheduledHours: '総スケジュール時間',
maintenanceName: 'メンテナンス名',
maintenanceStatus: 'ステータス',
scheduledStart: 'スケジュール開始',
scheduledEnd: 'スケジュール終了',
affectedServices: '影響を受けるサービス',
impact: '影響',
minor: '軽微',
major: '重要',
critical: '重大',
none: 'なし',
actions: 'アクション',
scheduled: 'スケジュール済み',
inprogress: '進行中',
completed: '完了',
cancelled: 'キャンセル',
markAsInProgress: '進行中としてマーク',
markAsCompleted: '完了としてマーク',
markAsCancelled: 'キャンセルとしてマーク',
confirmDelete: '削除確認',
deleteMaintenanceConfirmation: 'このメンテナンスウィンドウを削除してもよろしいですか?',
thisActionCannotBeUndone: 'この操作は元に戻せません。',
maintenanceDeleted: 'メンテナンス削除完了',
maintenanceDeletedDesc: 'メンテナンスウィンドウが正常に削除されました。',
errorDeletingMaintenance: 'メンテナンスウィンドウの削除中にエラーが発生しました。',
statusUpdated: 'ステータス更新完了',
maintenanceStatusUpdated: 'メンテナンスステータスが正常に更新されました。',
errorUpdatingMaintenanceStatus: 'メンテナンスステータスの更新中にエラーが発生しました。',
createMaintenance: 'メンテナンス作成',
createMaintenanceDesc: 'サービスの新しいメンテナンスウィンドウをスケジュール',
enterTitle: 'メンテナンスタイトルを入力',
enterDescription: 'メンテナンスの詳細説明を入力',
startTime: '開始時刻',
endTime: '終了時刻',
selectDate: '日付を選択',
enterAffectedServices: '影響を受けるサービスを入力',
separateServicesWithComma: '複数のサービスはカンマで区切ってください',
priority: '優先度',
selectPriority: '優先度を選択',
selectStatus: 'ステータスを選択',
selectImpact: '影響を選択',
notifySubscribers: '購読者に通知',
notifySubscribersDesc: 'このメンテナンスが開始されたときに全購読者に通知を送信',
maintenanceCreated: 'メンテナンス作成完了',
maintenanceCreatedDesc: 'メンテナンスウィンドウが正常にスケジュールされました。',
errorCreatingMaintenance: 'メンテナンスウィンドウの作成中にエラーが発生しました。',
errorFetchingMaintenanceData: 'メンテナンスデータの取得中にエラーが発生しました。',
low: '低',
medium: '中',
high: '高',
created: '作成済み',
lastUpdated: '最終更新',
subscribersWillBeNotified: 'メンテナンス開始時に購読者に通知されます',
noNotifications: '通知は送信されません',
noScheduledMaintenance: 'スケジュール済みメンテナンスなし',
noMaintenanceWindows: 'この期間にメンテナンスウィンドウはありません。「メンテナンス作成」ボタンをクリックして作成してください。',
maintenanceCreatedSuccess: 'メンテナンスウィンドウが正常に作成されました',
};
+20
View File
@@ -0,0 +1,20 @@
import { MenuTranslations } from '../types/menu';
export const menuTranslations: MenuTranslations = {
uptimeMonitoring: "稼働監視",
instanceMonitoring: "インスタンス監視",
sslDomain: "SSL・ドメイン",
scheduleIncident: "スケジュール・インシデント",
operationalPage: "運用ページ",
reports: "レポート",
regionalMonitoring: "地域監視",
settingPanel: "設定パネル",
generalSettings: "一般設定",
userManagement: "ユーザー管理",
notificationSettings: "通知設定",
alertsTemplates: "アラートテンプレート",
rolesManagement: "ロール管理",
dataRetention: "データ保持",
backupSettings: "バックアップ設定",
aboutSystem: "システム情報",
};
@@ -0,0 +1,11 @@
import { ServicesTranslations } from '../types/services';
export const servicesTranslations: ServicesTranslations = {
serviceName: "サービス名",
serviceType: "サービスタイプ",
serviceStatus: "サービスステータス",
responseTime: "応答時間",
uptime: "稼働時間",
lastChecked: "最終チェック",
noServices: "フィルタ条件に一致するサービスがありません。",
};
@@ -0,0 +1,51 @@
import { SettingsTranslations } from '../types/settings';
export const settingsTranslations: SettingsTranslations = {
// タブ
systemSettings: "システム設定",
mailSettings: "メール設定",
// システム設定
appName: "アプリケーション名",
appURL: "アプリケーションURL",
senderName: "送信者名",
senderEmail: "送信者メールアドレス",
hideControls: "コントロールを非表示",
// メール設定
smtpSettings: "SMTP設定",
smtpEnabled: "SMTPを有効にする",
smtpHost: "SMTPホスト",
smtpPort: "SMTPポート",
smtpUsername: "SMTPユーザー名",
smtpPassword: "SMTPパスワード",
smtpAuthMethod: "認証方式",
enableTLS: "TLSを有効にする",
localName: "ローカル名",
// テストメール
testEmail: "テストメール",
sendTestEmail: "テストメールを送信",
emailTemplate: "メールテンプレート",
verification: "認証",
passwordReset: "パスワードリセット",
confirmEmailChange: "メールアドレス変更確認",
otp: "OTP",
loginAlert: "ログインアラート",
authCollection: "認証コレクション",
selectCollection: "コレクションを選択",
toEmailAddress: "宛先メールアドレス",
enterEmailAddress: "メールアドレスを入力",
sending: "送信中...",
// アクションとステータス
save: "変更を保存",
saving: "保存中...",
settingsUpdated: "設定が正常に更新されました",
errorSavingSettings: "設定の保存中にエラーが発生しました",
errorFetchingSettings: "設定の読み込み中にエラーが発生しました",
testConnection: "接続テスト",
testingConnection: "接続テスト中...",
connectionSuccess: "接続成功",
connectionFailed: "接続失敗"
};
+106
View File
@@ -0,0 +1,106 @@
import { SSLTranslations } from '../types/ssl';
export const sslTranslations: SSLTranslations = {
// ページとセクションタイトル
sslDomainManagement: "SSL・ドメイン管理",
monitorSSLCertificates: "ドメインのSSL証明書を監視・管理",
addSSLCertificate: "SSL証明書を追加",
editSSLCertificate: "SSL証明書を編集",
deleteSSLCertificate: "SSL証明書を削除",
sslCertificateDetails: "SSL証明書詳細",
detailedInfo: "詳細情報:",
// ステータス関連
valid: "有効",
expiringSoon: "期限切れ間近",
expired: "期限切れ",
pending: "保留中",
// 統計とカード
validCertificates: "有効な証明書",
expiringSoonCertificates: "期限切れ間近",
expiredCertificates: "期限切れ証明書",
// フォームフィールド
domain: "ドメイン",
domainName: "ドメイン名",
domainCannotChange: "ドメインは作成後変更できません",
warningThreshold: "警告閾値",
warningThresholdDays: "警告閾値(日)",
expiryThreshold: "期限切れ閾値",
expiryThresholdDays: "期限切れ閾値(日)",
notificationChannel: "通知チャンネル",
chooseChannel: "通知チャンネルを選択",
whereToSend: "通知の送信先",
daysBeforeExpiration: "期限切れ前の警告を受信する日数",
daysBeforeCritical: "期限切れ前の重要アラートを受信する日数",
getNotifiedExpiration: "証明書の期限切れが近い時に通知を受信",
getNotifiedCritical: "証明書の期限切れが非常に近い時に通知を受信",
// テーブルヘッダーとフィールド
issuer: "発行者",
expirationDate: "有効期限",
daysLeft: "残り日数",
status: "ステータス",
lastNotified: "最終通知",
actions: "アクション",
validFrom: "有効開始日",
validUntil: "有効終了日",
validityDays: "有効期間",
organization: "組織",
commonName: "コモンネーム",
serialNumber: "シリアル番号",
algorithm: "アルゴリズム",
subjectAltNames: "サブジェクト代替名",
// ボタンとアクション
addDomain: "ドメインを追加",
refreshAll: "全て更新",
cancel: "キャンセル",
addCertificate: "証明書を追加",
check: "チェック",
view: "表示",
edit: "編集",
delete: "削除",
close: "閉じる",
saveChanges: "変更を保存",
updating: "更新中",
// 詳細ビューのセクション
basicInformation: "基本情報",
validity: "有効性",
issuerInfo: "発行者情報",
technicalDetails: "技術的詳細",
monitoringConfig: "監視設定",
recordInfo: "レコード情報",
// 通知とメッセージ
sslCertificateAdded: "SSL証明書が正常に追加されました",
sslCertificateUpdated: "SSL証明書が正常に更新されました",
sslCertificateDeleted: "SSL証明書が正常に削除されました",
sslCertificateRefreshed: "{domain}のSSL証明書が正常に更新されました",
allCertificatesRefreshed: "全{count}件の証明書が正常に更新されました",
someCertificatesFailed: "{success}件の証明書が更新され、{failed}件が失敗しました",
failedToAddCertificate: "SSL証明書の追加に失敗しました",
failedToLoadCertificates: "SSL証明書の読み込みに失敗しました",
failedToUpdateCertificate: "SSL証明書の更新に失敗しました",
failedToDeleteCertificate: "SSL証明書の削除に失敗しました",
failedToCheckCertificate: "SSL証明書のチェックに失敗しました",
noCertificatesToRefresh: "更新する証明書がありません",
startingRefreshAll: "{count}件の証明書の更新を開始しています",
checkingSSLCertificate: "SSL証明書をチェック中...",
deleteConfirmation: "以下の証明書を削除してもよろしいですか?",
deleteWarning: "この操作は元に戻せません。証明書が完全に削除されます。",
// その他
unknown: "不明",
never: "なし",
none: "なし",
loadingChannels: "チャンネル読み込み中...",
noChannelsFound: "通知チャンネルが見つかりません",
noSSLCertificates: "SSL証明書が見つかりません",
created: "作成日",
lastUpdated: "最終更新",
lastNotification: "最終通知",
collectionId: "コレクションID"
};
+152
View File
@@ -0,0 +1,152 @@
## 🌐 Select Language
<table align="center">
<tr>
<td align="center">
<a href="../CONTRIBUTING.md">
<img src="https://flagcdn.com/24x18/gb.png" alt="English" />
<br/><strong>English</strong>
</a>
</td>
<td align="center">
<a href="CONTRIBUTING_km.md">
<img src="https://flagcdn.com/24x18/kh.png" alt="Khmer" />
<br/><strong>ខ្មែរ</strong>
</a>
</td>
<td align="center">
<a href="CONTRIBUTING_ja.md">
<img src="https://flagcdn.com/24x18/jp.png" alt="Japanese" />
<br/><strong>Japanese</strong>
</a>
</td>
</tr>
</table>
<p align="center">
Thank you to all our contributors, users, and supporters for making this project thrive.
</p>
<p align="center">
🚀 <strong>Stay tuned for more updates, features, and improvements.</strong>
</p>
# 🛠️ CheckCleへの貢献
**CheckCle**への貢献にご興味をお持ちいただき、ありがとうございます — リアルタイムフルスタック監視のための究極のオープンソースプラットフォームです。バグの報告、機能の提案、ドキュメントの改善、またはコードの提出など、どのような形でのご貢献でも歓迎いたします!
大小を問わず、あらゆる種類の貢献を歓迎します。このガイドが始めるのに役立ちます。
---
## 📌 目次
- [行動規範](#-行動規範)
- [貢献の方法](#-貢献の方法)
- [開発環境のセットアップ](#-開発環境のセットアップ)
- [プルリクエストのプロセス](#-プルリクエストのプロセス)
- [バグと問題の報告](#-バグと問題の報告)
- [機能リクエスト](#-機能リクエスト)
- [コミュニティとサポート](#-コミュニティとサポート)
- [ライセンス](#-ライセンス)
---
## 📜 行動規範
私たちは[行動規範](https://opensource.guide/code-of-conduct/)に従い、オープンで歓迎するコミュニティを促進しています。参加することで、これらの基準を守ることに同意したものとします。
---
## 🤝 貢献の方法
CheckCleの改善に役立つ方法をいくつか紹介します:
- 🐞 **バグの報告** 不具合を見つけましたか?[GitHub Issue](https://github.com/operacle/checkcle/issues)を開いてお知らせください。
- 🌟 **機能の提案** アイデアはありますか?[Discussion](https://github.com/operacle/checkcle/discussions)を始めるか、機能リクエストのissueを開いてください。
- 🛠 **プルリクエストの提出** – コードの改善、バグの修正、機能の追加、ドキュメントの改善を行ってください。
- 📝 **ドキュメントの改善** – タイポの修正でも大変助かります!
- 🌍 **宣伝の協力** – リポジトリに⭐をつけて、SNSでシェアし、他の方にも貢献を呼びかけてください!
---
## 🧰 開発環境のセットアップ
コードに貢献する前に、プロジェクトをローカルで設定してください:
### 1. リポジトリをフォーク
[GitHub](https://github.com/operacle/checkcle)で「Fork」をクリックして、自分のコピーを作成してください。
### 2. フォークをクローン
```bash
git clone --branch develop https://github.com/operacle/checkcle.git
cd checkcle
```
### 3. 依存関係のインストール
READMEまたはプロジェクトドキュメントの指示に従って、必要なパッケージをインストールし、ローカル開発サーバーを実行してください。
### 4. ローカル開発の開始
```bash
#Webアプリケーション
cd application/
npm install && npm run dev
#サーバーバックエンド
cd server
./pocketbase serve --dir pb_data
```
---
## ✅ プルリクエストのプロセス
1. コードが既存のスタイルと命名規則に従っていることを確認してください。
2. 明確で簡潔なコミットメッセージを書いてください。
3. ブランチをプッシュし、`main`ブランチにプルリクエスト(PR)を開いてください。
4. 意味のあるPRの説明を提供してください(何を/なぜ/どのように)。
5. 関連するissueがある場合はリンクしてください(例:`Closes #12`)。
6. すべてのチェックが通ることを確認してください(例:リンティング、テスト)。
PRをレビューし、必要に応じて変更をリクエストし、準備ができ次第マージします!
---
## 🐛 バグと問題の報告
可能な限り多くの情報を含めてください:
- 明確で説明的なタイトル
- 再現手順
- 期待される動作と実際の動作
- 環境情報(OS、ブラウザ、デバイスなど)
- 該当する場合はスクリーンショットやログ
報告には[Issue Tracker](https://github.com/operacle/checkcle/issues)をご利用ください。
---
## 💡 機能リクエスト
アイデアをお聞かせください![Discussion](https://github.com/operacle/checkcle/discussions)または機能リクエストのissueを開いてください。[ロードマップ](https://github.com/operacle/checkcle#development-roadmap)に既に記載されていないことを確認してください。
---
## 🌍 コミュニティとサポート
ヘルプが必要ですか?つながりたいですか?
- 💬 [Discordに参加](https://discord.gg/xs9gbubGwX)
- 🗣 [GitHub Discussion](https://github.com/operacle/checkcle/discussions)を開始または参加
- 🐦 [XTwitter](https://x.com/tl)でフォロー
---
## 📜 ライセンス
貢献することで、あなたの貢献が[MITライセンス](LICENSE)の下でライセンスされることに同意したものとします。
---
## 🙏 ありがとうございます
CheckCleを一緒に構築できることを嬉しく思います — **コミュニティによる、コミュニティのための**強力な監視プラットフォーム。あなたのサポートは私たちにとってとても大切です!💙
+181
View File
@@ -0,0 +1,181 @@
## 🌐 Select Language
<table align="center">
<tr>
<td align="center">
<a href="../CONTRIBUTING.md">
<img src="https://flagcdn.com/24x18/gb.png" alt="English" />
<br/><strong>English</strong>
</a>
</td>
<td align="center">
<a href="CONTRIBUTING_km.md">
<img src="https://flagcdn.com/24x18/kh.png" alt="Khmer" />
<br/><strong>ខ្មែរ</strong>
</a>
</td>
<td align="center">
<a href="CONTRIBUTING_ja.md">
<img src="https://flagcdn.com/24x18/jp.png" alt="Japanese" />
<br/><strong>Japanese</strong>
</a>
</td>
</tr>
</table>
<p align="center">
Thank you to all our contributors, users, and supporters for making this project thrive.
</p>
<p align="center">
🚀 <strong>Stay tuned for more updates, features, and improvements.</strong>
</p>
# 🛠️ Contributing to CheckCle
Thank you for your interest in contributing to **CheckCle** — the ultimate open-source platform for real-time full-stack monitoring. Whether you're here to report bugs, suggest features, improve documentation, or submit code, your contribution matters!
We welcome all kinds of contributions, big or small. This guide will help you get started.
---
## 📌 Table of Contents
- [Code of Conduct](#-code-of-conduct)
- [Ways to Contribute](#-ways-to-contribute)
- [Development Setup](#-development-setup)
- [Pull Request Process](#-pull-request-process)
- [Reporting Bugs & Issues](#-reporting-bugs--issues)
- [Feature Requests](#-feature-requests)
- [Community & Support](#-community--support)
- [License](#-license)
---
## 📜 Code of Conduct
We follow a [Code of Conduct](https://opensource.guide/code-of-conduct/) to foster an open and welcoming community. By participating, you agree to uphold these standards.
---
## 🤝 Ways to Contribute
Here are some ways you can help improve CheckCle:
- 🐞 **Report Bugs** Found a glitch? Let us know by opening a [GitHub Issue](https://github.com/operacle/checkcle/issues).
- 🌟 **Suggest Features** Have an idea? Start a [Discussion](https://github.com/operacle/checkcle/discussions) or open a Feature Request issue.
- 🛠 **Submit Pull Requests** Improve the code, fix bugs, add features, or enhance the docs.
- 📝 **Improve Documentation** Even a typo fix helps!
- 🌍 **Spread the Word** Star ⭐ the repo, share it on socials, and invite others to contribute!
---
## 🧰 Development Setup
Before contributing code, set up the project locally:
### 1. Fork the Repository
Click "Fork" on [GitHub](https://github.com/operacle/checkcle) to create your own copy.
### 2. Clone Your Fork
```bash
git clone --branch develop https://github.com/operacle/checkcle.git
cd checkcle
```
### 3. Install Dependencies
Follow the instructions in the README or project docs to install required packages and run the local development server.
### 4. Start Local Development
```bash
#Web Application
cd application/
npm install && npm run dev
#Server Backend
cd server
./pocketbase serve --dir pb_data
If you're not using localhost, please run with this (./pocketbase serve --http=0.0.0.0:8090 --dir pb_data)
```
### 4. Start Service Check Operation
```bash
#Server Backend
Start Service Operation (Check Service for PING, HTTP, TCP, DNS)
cd server/service-operation
go run main.go (you do not need to change the .env while it's the localhost connection)
```
### 5. Start Distributed Regional Agent
```bash
#### 1. Fork the Repository
Click "Fork" on [GitHub](https://github.com/operacle/Distributed-Regional-Monitoring) to create your own copy.
#### 2. Clone Your Fork
git clone --branch main https://github.com/operacle/Distributed-Regional-Monitoring.git
cd Distributed-Regional-Monitoring
#### 3. Install Go Service (make sure you have installed the Go Service)
Copy .env.example -> .env
Change regional agent configuration in .env file
and Run: go run main.go
```
---
## ✅ Pull Request Process
1. Ensure your code follows the existing style and naming conventions.
2. Write clear, concise commit messages.
3. Push your branch and open a Pull Request (PR) on the `develop` branch.
4. Provide a meaningful PR description (what/why/how).
5. Link related issues if applicable (e.g. `Closes #12`).
6. Make sure all checks pass (e.g., linting, tests).
Well review your PR, request changes if needed, and merge it once ready!
---
## 🐛 Reporting Bugs & Issues
Please include as much information as possible:
- A clear, descriptive title
- Steps to reproduce
- Expected vs actual behavior
- Environment info (OS, browser, device, etc.)
- Screenshots or logs if applicable
Use the [Issue Tracker](https://github.com/operacle/checkcle/issues) to report.
---
## 💡 Feature Requests
Wed love to hear your ideas! Open a [Discussion](https://github.com/operacle/checkcle/discussions) or Feature Request issue. Make sure its not already listed in the [Roadmap](https://github.com/operacle/checkcle#development-roadmap).
---
## 🌍 Community & Support
Need help? Want to connect?
- 💬 [Join our Discord](https://discord.gg/xs9gbubGwX)
- 🗣 Start or join a [GitHub Discussion](https://github.com/operacle/checkcle/discussions)
- 🐦 Follow us on [X (Twitter)](https://x.com/tl)
---
## 📜 License
By contributing, you agree that your contributions will be licensed under the [MIT License](LICENSE).
---
## 🙏 Thank You
Were excited to build CheckCle together — a powerful monitoring platform **for the community, by the community**. Your support means the world! 💙
+181
View File
@@ -0,0 +1,181 @@
## 🌐 Select Language
<table align="center">
<tr>
<td align="center">
<a href="../README.md">
<img src="https://flagcdn.com/24x18/gb.png" alt="English" />
<br/><strong>English</strong>
</a>
</td>
<td align="center">
<a href="README_km.md">
<img src="https://flagcdn.com/24x18/kh.png" alt="Khmer" />
<br/><strong>ខ្មែរ</strong>
</a>
</td>
<td align="center">
<a href="README_ja.md">
<img src="https://flagcdn.com/24x18/jp.png" alt="Japanese" />
<br/><strong>Japanese</strong>
</a>
</td>
</tr>
</table>
<p align="center">
Thank you to all our contributors, users, and supporters for making this project thrive.
</p>
<p align="center">
🚀 <strong>Stay tuned for more updates, features, and improvements.</strong>
</p>
![CheckCle Platform](https://pub-4a4062303020445f8f289a2fee84f9e8.r2.dev/images/server-detail-page.png)
# 🚀 CheckCleとは?
CheckCleは、フルスタックシステム、アプリケーション、インフラストラクチャのシームレスでリアルタイムな監視を実現するオープンソースソリューションです。開発者、システム管理者、DevOpsチームに対して、環境の全レイヤー(サーバー、アプリケーション、サービス)にわたる深い洞察と実用的なデータを提供します。CheckCleにより、テクノロジースタック全体の可視性、制御、最適なパフォーマンスの確保が可能になります。
## 🎯 ライブデモ
👉 **今すぐ試す:** [CheckCle ライブデモ](https://demo.checkcle.io)
## 🌟 主要機能
### アップタイムサービス & インフラストラクチャサーバー監視
- HTTP、DNS、Pingプロトコルの監視
- TCPベースのAPIサービス(FTP、SMTP、HTTPなど)の監視
- 詳細なアップタイム、レスポンス時間、パフォーマンス問題の追跡
- 分散地域監視
- インシデント履歴(UP/DOWN/WARNING/PAUSE
- SSL & ドメイン監視(ドメイン、発行者、有効期限、残り日数、ステータス、最終通知日)
- インフラストラクチャサーバー監視:Linux(🐧 Debian、Ubuntu、CentOS、Red Hatなど)およびWindows(ベータ版)をサポート。ワンライン・インストール・エージェント・スクリプトによるサーバーメトリクス(CPU、RAM、ディスク使用量、ネットワーク活動)の監視
- メンテナンススケジュール & インシデント管理
- 運用ステータス / パブリックステータスページ
- メール、Telegram、Discord、Slack経由の通知
- レポート & 分析
- 設定パネル(ユーザー管理、データ保持、多言語、テーマ(ダーク & ライトモード)、通知とチャネル、アラートテンプレート)
## #️⃣ はじめに
### 現在サポートされているアーキテクチャ
* ✅ x86_64 PC、ラップトップ、サーバー(amd64)
* ✅ 最新のRaspberry Pi 3/4/564ビットOS)、Apple Silicon Macarm64
### 以下のオプションのいずれかを使用してCheckCleをインストールします。
1. CheckCle ワンクリックインストール - ターミナルにコピーして実行するだけ
```bash
curl -fsSL https://checkcle.io/install.sh | bash
```
2. docker runでインストール。以下の準備済みdocker runコマンドをコピーするだけ
```bash
docker run -d \
--name checkcle \
--restart unless-stopped \
-p 8090:8090 \
-v /opt/pb_data:/mnt/pb_data \
--ulimit nofile=4096:8192 \
operacle/checkcle:latest
```
3. Docker compose設定でインストール
```bash
version: '3.9'
services:
checkcle:
image: operacle/checkcle:latest
container_name: checkcle
restart: unless-stopped
ports:
- "8090:8090" # Webアプリケーション
volumes:
- /opt/pb_data:/mnt/pb_data # ホストディレクトリをコンテナパスにマップ
ulimits:
nofile:
soft: 4096
hard: 8192
```
3. 管理Web画面
デフォルトURL: http://0.0.0.0:8090
ユーザー: admin@example.com
パスワード: Admin123456
4. https://docs.checkcle.io のクイックスタートガイドに従ってください
###
![checkcle-collapse-black](https://pub-4a4062303020445f8f289a2fee84f9e8.r2.dev/images/uptime-monitoring.png)
![Service Detail Page](https://pub-4a4062303020445f8f289a2fee84f9e8.r2.dev/images/uptime-service-detail.png)
![checkcle-server-instance](https://pub-4a4062303020445f8f289a2fee84f9e8.r2.dev/images/instance-server-monitoring.png)
![Schedule Maintenance](https://pub-4a4062303020445f8f289a2fee84f9e8.r2.dev/images/checkcle-schedule-maintenance.png)
![SSL Monitoring](https://pub-4a4062303020445f8f289a2fee84f9e8.r2.dev/images/ssl-monitoring.png)
## 📝 開発ロードマップ
- ✅ ヘルスチェック & アップタイム監視(HTTP)
- ✅ ライブ統計情報付きダッシュボードUI
- ✅ マルチユーザーシステム認証(管理者)
- ✅ 通知(Telegram
- ✅ Dockerコンテナ化
- ✅ CheckCle ウェブサイト
- ✅ CheckCle デモサーバー
- ✅ SSL & ドメイン監視
- ✅ メンテナンススケジュール
- ✅ インシデント管理
- [ ] インフラストラクチャサーバー監視
- ✅ 運用ステータス / パブリックステータスページ
- ✅ アップタイム監視(HTTP、TCP、PING、DNS)フル機能
- ✅ 分散地域監視エージェント [サポート運用](https://github.com/operacle/Distributed-Regional-Monitoring)
- ✅ システム設定パネル&メール設定
- ✅ ユーザー権限ロール
- [ ] 通知(Email/Slack/Discord/Signal
- ✅ データ保持 & 自動圧縮(データ & データベース圧縮のマルチオプション)
- ✅ 完全なドキュメント付きオープンソースリリース
## 🌟 CheckCleはコミュニティのため?
- **情熱を持って開発**: コミュニティのためのオープンソース愛好家によって作成
- **フリー & オープンソース**: 隠れたコストなしで完全に無料で使用可能
- **協力 & つながり**: オープンソースに情熱を持つ同志と出会う
---
## 🤝 貢献の方法
CheckCleの改善にご協力いただける方法をご紹介します:
- 🐞 **バグ報告** 不具合を発見しましたか?[GitHub Issue](https://github.com/operacle/checkcle/issues)を開いてお知らせください。
- 🌟 **機能提案** アイデアはありますか?[ディスカッション](https://github.com/operacle/checkcle/discussions)を開始するか、機能リクエストのissueを開いてください。
- 🛠 **プルリクエスト送信** – コードの改善、バグ修正、機能追加、ドキュメントの改良を行ってください。
- 📝 **ドキュメント改善** タイポ修正でも助かります!
- 🌍 **口コミ宣伝** – リポジトリに⭐をつけ、SNSでシェアし、他の人を貢献に招待してください!
---
## 🌍 つながりを保つ
- ウェブサイト: [checkcle.io](https://checkcle.io)
- ドキュメント: [docs.checkcle.io](https://docs.checkcle.io)
- GitHubリポジトリ: ⭐ [CheckCle](https://github.com/operacle/checkcle.git)
- コミュニティチャンネル: ディスカッションやissuesで参加してください!
- Discord: コミュニティに参加 [@discord](https://discord.gg/xs9gbubGwX)
- X: [@tlengoss](https://x.com/tlengoss)
## 📜 ライセンス
CheckCleはMITライセンスの下でリリースされています。
---
## 👥 貢献者
[![](https://contrib.rocks/image?repo=operacle/checkcle)](https://github.com/operacle/checkcle/graphs/contributors)
## スター履歴
[![Star History Chart](https://api.star-history.com/svg?repos=operacle/checkcle&type=Date)](https://www.star-history.com/#operacle/checkcle&Date)
CheckCleで情報を把握し、オンラインを維持しましょう! 🌐
+182
View File
@@ -0,0 +1,182 @@
## 🌐 Select Language
<table align="center">
<tr>
<td align="center">
<a href="../README.md">
<img src="https://flagcdn.com/24x18/gb.png" alt="English" />
<br/><strong>English</strong>
</a>
</td>
<td align="center">
<a href="README_km.md">
<img src="https://flagcdn.com/24x18/kh.png" alt="Khmer" />
<br/><strong>ខ្មែរ</strong>
</a>
</td>
<td align="center">
<a href="README_ja.md">
<img src="https://flagcdn.com/24x18/jp.png" alt="Japanese" />
<br/><strong>Japanese</strong>
</a>
</td>
</tr>
</table>
<p align="center">
Thank you to all our contributors, users, and supporters for making this project thrive.
</p>
<p align="center">
🚀 <strong>Stay tuned for more updates, features, and improvements.</strong>
</p>
![CheckCle Platform](https://pub-4a4062303020445f8f289a2fee84f9e8.r2.dev/images/server-detail-page.png)
# 🚀 តើឆេកខលគឺជាអ្វី?
ឆេកខល (CheckCle) គឺជាប្រពន័្ធប្រភពបើកចំហមួយដែលបើកអោយប្រើប្រាស់ទូទាំងពីភពលោកដោយឥតគិតថ្លៃ ដែលមានក្នុងមុខងារចំបងក្នុងការត្រួតពិនិត្យវ៉េបសាយ កម្មវិធីបច្ចេកវិទ្យា​និងហេដ្ឋារចនាសម្ព័ន្ធនៃម៉ាសុីនមេជាដើម។ វាផ្តល់ឱ្យអ្នកអភិវឌ្ឍន៍ អ្នកគ្រប់គ្រងប្រព័ន្ធ និងក្រុម DevOps នូវការពត័ស៊ីជម្រៅ និងទិន្នន័យនៃហេដ្ឋារចនាសម្ព័ន្ធ មិនថាជាម៉ាស៊ីនមេ កម្មវិធី ឬសេវាកម្មនោះទេ។ ជាមួយ CheckCle អ្នកមើលឃើញជាក្រាប វិភាគ​ និងហេតុការផ្សេងៗ​ និងធានាបាននូវការជូនដំណើរការនៅហេតុការផ្សេងៗដែលបានកើតឡើង។
## 🎯 តេស្តសាកល្បងផ្ទាល់
👉 **Try it now:** [CheckCle Live Demo](https://demo.checkcle.io)
## 🌟 មុខងារចម្បង
### ពិនិត្យប្រព័ន្ធវ៉េបសាយ និងការត្រួតពិនិត្យហេដ្ឋារចនាសម្ព័ន្ធនៃម៉ាស៊ីនមេ
- ត្រួតពិនិត្យពិធីកម្មវិធី HTTP, DNS, and Ping protocols
- ត្រួតពិនិត្យពិធីកម្មវិធី TCP-based, API services (e.g., FTP, SMTP, HTTP)
- តាមដាននិងវិភាគលម្អិត, response times, and performance issues
- តាមដាននិងវិភាគលម្អិតពីដំបន់ឬប្រទេសផ្សេងៗបាន
- ប្រវត្តិឧប្បត្តិហេតុ (UP/DOWN/WARNING/PAUSE)
- តាមដាន​ SSL & Domain (Domain, Issuer, Expiration Date, Days Left, Status, Last Notified)
- តាមដាននិងត្រួតពិនិត្យម៉ាស៊ីនមេ, Supports Linux (🐧 Debian, Ubuntu, CentOS, Red Hat, etc.) and Windows (Beta). And Servers metrics like CPU, RAM, disk usage, and network activity) with an one-line installation angent script.
- រៀបចំកាលវិភាគ និងការគ្រប់គ្រងឧប្បត្តិហេតុ
- ទំព័រស្ថានភាពប្រតិបត្តិការ
- ការជូនដំណឹងតាមរយៈ email, Telegram, Discord, and Slack
- របាយការណ៍ & វិភាគលម្អិត
- ផ្ទាំងការកំណត់ប្រព័ន្ធគ្រប់គ្រង (User Management, Data Retention, Multi-language, Themes (Dark & Light Mode), Notification and channels and alert templates).
## #️⃣ ការចាប់ផ្តើម
### ដំណើរការលើស្ថាបត្យកម្មដូចជា
* ✅ x86_64 PCs, laptops, servers (amd64)
* ✅ Modern Raspberry Pi 3/4/5 with (64-bit OS), Apple Silicon Macs (arm64)
### អ្នកអាចដំឡើង CheckCle ដោយជ្រើសរើសជម្រើសមួយក្នុងចំណោមជម្រើសខាងក្រោម។
1. CheckCle One-Click Installation - Just copy and run on terminal
```bash
curl -fsSL https://checkcle.io/install.sh | bash
```
2. Install with docker run. Just copy ready docker run command below
```bash
docker run -d \
--name checkcle \
--restart unless-stopped \
-p 8090:8090 \
-v /opt/pb_data:/mnt/pb_data \
--ulimit nofile=4096:8192 \
operacle/checkcle:latest
```
3. Install with Docker compose Configuration.
```bash
version: '3.9'
services:
checkcle:
image: operacle/checkcle:latest
container_name: checkcle
restart: unless-stopped
ports:
- "8090:8090" # Web Application
volumes:
- /opt/pb_data:/mnt/pb_data # Host directory mapped to container path
ulimits:
nofile:
soft: 4096
hard: 8192
```
3. ចូលទៅកាន់ផ្ទាំងគ្រប់គ្រង
Default URL: http://0.0.0.0:8090
User: admin@example.com
Passwd: Admin123456
4. ឯកសារ​ និងរបៀបប្រើប្រាស់ផ្សេង at https://docs.checkcle.io
###
![checkcle-collapse-black](https://pub-4a4062303020445f8f289a2fee84f9e8.r2.dev/images/uptime-monitoring.png)
![Service Detail Page](https://pub-4a4062303020445f8f289a2fee84f9e8.r2.dev/images/uptime-service-detail.png)
![checkcle-server-instance](https://pub-4a4062303020445f8f289a2fee84f9e8.r2.dev/images/instance-server-monitoring.png)
![Schedule Maintenance](https://pub-4a4062303020445f8f289a2fee84f9e8.r2.dev/images/checkcle-schedule-maintenance.png)
![SSL Monitoring](https://pub-4a4062303020445f8f289a2fee84f9e8.r2.dev/images/ssl-monitoring.png)
## 📝 គម្រោងក្នុងការអភិវឌ្ឍន៍ប្រព័ន្ធគ្រប់គ្រង
- ✅ Health check & uptime monitoring (HTTP)
- ✅ Dashboard UI with live stats
- ✅ Auth with Multi-users system (admin)
- ✅ Notifications (Telegram)
- ✅ Docker containerization
- ✅ CheckCle Website
- ✅ CheckCle Demo Server
- ✅ SSL & Domain Monitoring
- ✅ Schedule Maintenance
- ✅ Incident Management
- [ ] Infrastructure Server Monitoring
- ✅ Operational Status / Public Status Pages
- ✅ Uptime monitoring (HTTP, TCP, PING, DNS) Full functionality
- ✅ Distributed Regional Monitoring Agent [Support Operation](https://github.com/operacle/Distributed-Regional-Monitoring)
- ✅ System Setting Panel and Mail Settings
- ✅ User Permission Roles
- [ ] Notifications (Email/Slack/Discord/Signal)
- ✅ Data Retention & Automate Strink (Muti Options to Shrink Data & Database )
- ✅ Open-source release with full documentation
## 🌟 ឆេកខលសម្រាប់សហគមន៍
- **Built with Passion**: Created by an open-source enthusiast for the community
- **Free & Open Source**: Completely free to use with no hidden costs
- **Collaborate & Connect**: Meet like-minded people passionate about Open Source
---
## 🤝 របៀបក្នុងការចូលរួមអភិវឌ្ឍន៍ប្រព័ន្ធកូដបើកជំហ
នេះគឺជាវិធីមួយចំនួនដែលអ្នកអាចជួយកែលម្អ CheckCle:
- 🐞 **រាយការណ៍កំហុស** - រកឃើញកំហុសថ្មី? អនុញ្ញាតឱ្យយើងដឹងដោយបើកផងដោយបង្កើត [GitHub Issue](https://github.com/operacle/checkcle/issues).
- 🌟 **ផ្តល់យោបល់** – មានគំនិតថ្មី?​ អនុញ្ញាតឱ្យយើងដឹងដោយបើកផងដោយបង្កើត [Discussion](https://github.com/operacle/checkcle/discussions) or open a Feature Request issue.
- 🛠 **បញ្ជូនសំណើអភិវឌ្ឍន៍កូដ** – កែលម្អកូដ ជួសជុលកំហុស បន្ថែមមុខងារ ឬកែលម្អឯកសារ។
- 📝 **ធ្វើអោយឯកសារប្រសើរឡើង** – សូម្បីតែការកែលម្អបន្តិចបន្តួចក៏អាចជួយបាន!
- 🌍 **ផ្សព្វផ្សាយ** – ដាក់ផ្កាយ ⭐ repo ចែករំលែកវានៅលើសង្គមហើយអញ្ជើញអ្នកផ្សេងទៀតឱ្យចូលរួមអភិវឌ្ឍន៍!
---
## 🌍 រក្សាទំនាក់ទំនង
- វ៉េបសាយ: [checkcle.io](https://checkcle.io)
- ឯកសារ: [docs.checkcle.io](https://docs.checkcle.io)
- ឃ្លាំងផ្ទុកកូដ: ⭐ [CheckCle](https://github.com/operacle/checkcle.git)
- ទំនាក់ទំនងក្រុម: Engage via discussions and issues!
- បណ្តាញសង្គម: Join our community [@discord](https://discord.gg/xs9gbubGwX)
- X: [@tlengoss](https://x.com/tlengoss)
## 📜 License
CheckCle ត្រូវបានចេញផ្សាយក្រោមអាជ្ញាប័ណ្ណ MIT ។
---
## 👥 អ្នករួមចំណែក
[![](https://contrib.rocks/image?repo=operacle/checkcle)](https://github.com/operacle/checkcle/graphs/contributors)
## ស្ថានភាពផ្តាយ
[![Star History Chart](https://api.star-history.com/svg?repos=operacle/checkcle&type=Date)](https://www.star-history.com/#operacle/checkcle&Date)
កុំភ្លេចប្រើប្រាស់ឆេកខលសម្រាប់ការងាររបស់អ្នក! 🌐