Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
93 changes: 43 additions & 50 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,9 @@ CheckCle is an Open Source solution for seamless, real-time monitoring of full-s
User: admin@example.com | Passwd: Admin123456

## 🌟 Core Features
### 📝 Roadmap : [DEVELOPMENT_ROADMAP](docs/DEVELOPMENT_ROADMAP.md)

### Uptime Services & Infrastructure Server Monitoring
### Uptime Services & Infrastructure Server Monitoring
- Monitor HTTP, DNS, and Ping protocols
- Monitor TCP-based, API services (e.g., FTP, SMTP, HTTP)
- Track detail uptime, response times, and performance issues
Expand All @@ -63,26 +64,9 @@ CheckCle is an Open Source solution for seamless, real-time monitoring of full-s
* ✅ x86_64 PCs, laptops, servers (amd64)
* ✅ Modern Raspberry Pi 3/4/5 with (64-bit OS), Apple Silicon Macs (arm64)

### Install CheckCle using one of the options below.
### Install CheckCle using one of the following methods:


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.
1. Install with Docker Compose Configuration (Recommended)
```bash

version: '3.9'
Expand All @@ -102,6 +86,18 @@ services:
hard: 8192

```
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. Admin Web Management

Default URL: http://0.0.0.0:8090
Expand All @@ -118,35 +114,37 @@ services:
![Schedule Maintenance](https://pub-4a4062303020445f8f289a2fee84f9e8.r2.dev/images/checkcle-schedule-maintenance.png)


## 📝 Development Roadmap

- ✅ 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
- ✅ 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

## 🌟 CheckCle for Communities?
- **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

---

## Sponsors
🙏 We're incredibly thankful to our sponsors. Your contributions enable us to maintain infrastructure (hosting, domains), run tests, and continue developing valuable features.

### 🥈 Silver Uptime Ally

<a href="https://github.com/sponsors/tolaleng">
<img src="https://avatars.githubusercontent.com/u/135371107?v=4" width="75" height="75" style="border-radius: 50%" />
</a>

### 🧡 Ping Supporter

<a href="https://github.com/sponsors/tolaleng">
<img src="https://avatars.githubusercontent.com/u/4575656?v=4" width="75" height="75" style="border-radius: 50%" />
</a>

---

## 👥 Contributors
Thank you for contributing and continuously making CheckCle better, you're awesome 🫶

[![](https://contrib.rocks/image?repo=operacle/checkcle)](https://github.com/operacle/checkcle/graphs/contributors)

---

## 🤝 Ways to Contribute

Here are some ways you can help improve CheckCle:
Expand All @@ -155,25 +153,20 @@ Here are some ways you can help improve CheckCle:
- 🌟 **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!
- 🌍 **Spread the Word** – Star ⭐ the [CheckCle](https://github.com/operacle/checkcle.git) repo, share it on socials, and invite others to contribute!

---

## 🌍 Stay Connected
- Website: [checkcle.io](https://checkcle.io)
- Documentation: [docs.checkcle.io](https://docs.checkcle.io)
- GitHub Repository: ⭐ [CheckCle](https://github.com/operacle/checkcle.git)
- Reddit Community: [reddit](https://www.reddit.com/r/checkcle)
- Discord: Join our community [@discord](https://discord.gg/xs9gbubGwX)
- X: [@tlengoss](https://x.com/tlengoss)
- Chat on Discord: Join our community [@discord](https://discord.gg/xs9gbubGwX)
- Follow us on X: [@checkcle_oss](https://x.com/checkcle_oss)

## 📜 License

CheckCle is released under the MIT License.

---
## 👥 Contributors
Thank you for contributing and continuously making CheckCle better, you're awesome 🫶

[![](https://contrib.rocks/image?repo=operacle/checkcle)](https://github.com/operacle/checkcle/graphs/contributors)

Binary file modified application/bun.lockb
Binary file not shown.
4 changes: 2 additions & 2 deletions application/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>CheckCle is an open-source monitoring stack</title>
<title>CheckCle</title>
<meta name="description" content="An open-source monitoring platform offering real-time insights into server and service health, incident management, and operational transparency." />
<meta name="author" content="Tola Leng" />

Expand All @@ -14,7 +14,7 @@
<meta property="og:image" content="https://pub-4a4062303020445f8f289a2fee84f9e8.r2.dev/images/checkcle-black.png" />

<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:site" content="@tlengoss" />
<meta name="twitter:site" content="@checkcle_oss" />
<meta name="twitter:image" content="https://pub-4a4062303020445f8f289a2fee84f9e8.r2.dev/images/checkcle-black.png" />
</head>

Expand Down
111 changes: 111 additions & 0 deletions application/src/api/realtime/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,117 @@ export default async function handler(req) {
description: "Signal message sent successfully (simulated)"
}
};
}
// Handle WeChat Work (WeCom) notifications
else if (type === "wecom") {
const { webhookUrl, message } = body;

if (!webhookUrl || !message) {
console.error("Missing required parameters for WeChat Work notification", {
hasWebhookUrl: !!webhookUrl,
hasMessage: !!message
});

return {
status: 400,
json: {
ok: false,
error_code: 400,
description: "Missing required WeChat Work parameters"
}
};
}

try {
console.log("Attempting to call WeChat Work webhook API");
console.log("Calling WeChat Work webhook URL: [REDACTED]");

// Parse the message to get the JSON payload
let messagePayload;
try {
messagePayload = JSON.parse(message);
} catch (e) {
console.error("Error parsing WeChat Work message payload:", e);
return {
status: 400,
json: {
ok: false,
error_code: 400,
description: "Invalid WeChat Work message format"
}
};
}

const response = await fetch(webhookUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(messagePayload),
});

if (!response.ok) {
const errorText = await response.text();
console.error(`WeChat Work API error (${response.status}):`, errorText);

try {
// Try to parse error as JSON if possible
const errorJson = JSON.parse(errorText);
return {
status: response.status,
json: errorJson
};
} catch (e) {
// If parsing fails, return the raw error
return {
status: response.status,
json: {
ok: false,
error_code: response.status,
description: `WeChat Work API error: ${errorText}`
}
};
}
}

const result = await response.json();
console.log("WeChat Work API response:", JSON.stringify(result, null, 2));

// WeChat Work API returns errcode: 0 for success
if (result.errcode !== 0) {
console.error("WeChat Work API error:", result);
return {
status: 400,
json: {
ok: false,
error_code: result.errcode,
description: result.errmsg || "Unknown WeChat Work API error"
}
};
}

console.log("Successfully sent message to WeChat Work!");
return {
status: 200,
json: {
ok: true,
result: result,
description: "Message sent successfully to WeChat Work"
}
};
} catch (error) {
console.error("Error calling WeChat Work API:", error);

// Return detailed error information
return {
status: 500,
json: {
ok: false,
error_code: 500,
description: `Error sending WeChat Work message: ${error instanceof Error ? error.message : "Unknown error"}`
}
};
}
} else {
// Return error for unsupported notification type
console.error("Unsupported notification type:", type);
Expand Down
5 changes: 4 additions & 1 deletion application/src/components/dashboard/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,9 @@ export const Header = ({
<DropdownMenuItem onClick={() => setLanguage("ja")} className={language === "ja" ? "bg-accent" : ""}>
{t("japanese")}
</DropdownMenuItem>
<DropdownMenuItem onClick={() => setLanguage("zh-CN")} className={language === "zh-CN" ? "bg-accent" : ""}>
{t("simplifiedChinese")}
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>

Expand Down Expand Up @@ -162,7 +165,7 @@ export const Header = ({
variant="outline"
size="icon"
className="rounded-full w-8 h-8 border-border"
onClick={() => window.open("https://x.com/tlengoss", "_blank")}
onClick={() => window.open("https://x.com/checkcle_oss", "_blank")}
>
<span className="sr-only">X (Twitter)</span>
<Twitter className="w-4 h-4" />
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@

import { useState } from 'react';
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
import { Button } from '@/components/ui/button';
Expand All @@ -6,7 +7,7 @@ import { Label } from '@/components/ui/label';
import { Textarea } from '@/components/ui/textarea';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
import { Badge } from '@/components/ui/badge';
import { Plus, X, Server, Shield, AlertTriangle } from 'lucide-react';
import { Plus, X, Server } from 'lucide-react';
import { StatusPageComponentRecord } from '@/types/statusPageComponents.types';
import { useQuery } from '@tanstack/react-query';
import { serviceService } from '@/services/serviceService';
Expand All @@ -17,12 +18,6 @@ interface ComponentsSelectorProps {
onComponentDelete?: (componentId: string) => void;
}

const componentTypes = [
{ value: 'uptime', label: 'Uptime Service', icon: Server },
{ value: 'ssl', label: 'SSL Certificate', icon: Shield },
{ value: 'incident', label: 'Incident Monitoring', icon: AlertTriangle },
];

export const ComponentsSelector = ({ selectedComponents, onComponentsChange, onComponentDelete }: ComponentsSelectorProps) => {
const [showAddForm, setShowAddForm] = useState(false);
const [newComponent, setNewComponent] = useState({
Expand Down Expand Up @@ -169,7 +164,7 @@ export const ComponentsSelector = ({ selectedComponents, onComponentsChange, onC
<SelectTrigger>
<SelectValue placeholder="Select an uptime service" />
</SelectTrigger>
<SelectContent className="z-50 bg-white border shadow-lg">
<SelectContent>
{services.map((service) => (
<SelectItem key={service.id} value={service.id}>
<div className="flex items-center gap-2">
Expand Down Expand Up @@ -206,37 +201,6 @@ export const ComponentsSelector = ({ selectedComponents, onComponentsChange, onC
</div>
</div>
)}

<div className="mt-4">
<Label>Quick Add Templates</Label>
<div className="grid grid-cols-1 sm:grid-cols-3 gap-2 mt-2">
{componentTypes.map((type) => {
const Icon = type.icon;
return (
<Button
key={type.value}
variant="outline"
size="sm"
onClick={() => {
const component: Partial<StatusPageComponentRecord> = {
name: type.label,
description: `Monitor ${type.label.toLowerCase()}`,
service_id: '',
server_id: '',
display_order: selectedComponents.length + 1,
operational_status_id: '',
};
onComponentsChange([...selectedComponents, component]);
}}
className="justify-start"
>
<Icon className="h-4 w-4 mr-2" />
{type.label}
</Button>
);
})}
</div>
</div>
</CardContent>
</Card>
);
Expand Down
Loading