Merge branch 'operacle:develop' into develop
This commit is contained in:
@@ -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
|
||||
@@ -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'
|
||||
@@ -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
|
||||
@@ -118,28 +114,6 @@ services:
|
||||

|
||||
|
||||
|
||||
## 📝 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
|
||||
@@ -147,6 +121,30 @@ services:
|
||||
|
||||
---
|
||||
|
||||
## 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://github.com/operacle/checkcle/graphs/contributors)
|
||||
|
||||
---
|
||||
|
||||
## 🤝 Ways to Contribute
|
||||
|
||||
Here are some ways you can help improve CheckCle:
|
||||
@@ -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://github.com/operacle/checkcle/graphs/contributors)
|
||||
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -162,7 +162,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" />
|
||||
|
||||
@@ -95,7 +95,7 @@ export const AboutSystem: React.FC = () => {
|
||||
<FileText className={`h-5 w-5 ${theme === 'dark' ? 'text-white/80' : 'text-gray-700'}`} />
|
||||
<span>{t('viewDocumentation')}</span>
|
||||
</Button>
|
||||
<Button variant="outline" className="flex items-center justify-start gap-3 h-12 hover:bg-muted/50 transition-all duration-200" onClick={() => window.open("https://x.com/tlengoss", "_blank")}>
|
||||
<Button variant="outline" className="flex items-center justify-start gap-3 h-12 hover:bg-muted/50 transition-all duration-200" onClick={() => window.open("https://x.com/checkcle_oss", "_blank")}>
|
||||
<Twitter className={`h-5 w-5 ${theme === 'dark' ? 'text-white/80' : 'text-gray-700'}`} />
|
||||
<span>{t('followOnX')}</span>
|
||||
</Button>
|
||||
|
||||
@@ -33,11 +33,13 @@ import { AddSSLCertificateForm } from "./AddSSLCertificateForm";
|
||||
import { EditSSLCertificateForm } from "./EditSSLCertificateForm";
|
||||
import { SSLCertificateActions } from "./SSLCertificateActions";
|
||||
import { SSLCertificateDetailDialog } from "./SSLCertificateDetailDialog";
|
||||
import { SSLPagination } from "./SSLPagination";
|
||||
import { fetchSSLCertificates, addSSLCertificate, deleteSSLCertificate } from "@/services/sslCertificateService";
|
||||
import { pb } from "@/lib/pocketbase";
|
||||
import { SSLCertificate } from "@/types/ssl.types";
|
||||
import { useLanguage } from "@/contexts/LanguageContext";
|
||||
import { useTheme } from "@/contexts/ThemeContext";
|
||||
import { useSSLPagination } from "@/hooks/useSSLPagination";
|
||||
import { toast } from "sonner";
|
||||
|
||||
export const SSLCertificatesTable = () => {
|
||||
@@ -56,6 +58,16 @@ export const SSLCertificatesTable = () => {
|
||||
queryFn: fetchSSLCertificates,
|
||||
});
|
||||
|
||||
const {
|
||||
paginatedCertificates,
|
||||
currentPage,
|
||||
totalPages,
|
||||
pageSize,
|
||||
totalItems,
|
||||
handlePageChange,
|
||||
handlePageSizeChange,
|
||||
} = useSSLPagination({ certificates });
|
||||
|
||||
if (isLoading) return <div>Loading...</div>;
|
||||
if (isError) return <div>Error loading certificates</div>;
|
||||
|
||||
@@ -67,7 +79,6 @@ export const SSLCertificatesTable = () => {
|
||||
setShowAddDialog(false);
|
||||
toast.success(t('certificateAdded'));
|
||||
} catch (error) {
|
||||
// console.error("Error adding certificate:", error);
|
||||
toast.error(t('failedToAddCertificate'));
|
||||
} finally {
|
||||
setIsSubmitting(false);
|
||||
@@ -89,7 +100,6 @@ export const SSLCertificatesTable = () => {
|
||||
setSelectedCertificate(null);
|
||||
toast.success(t('certificateUpdated'));
|
||||
} catch (error) {
|
||||
// console.error("Error updating certificate:", error);
|
||||
toast.error(t('failedToUpdateCertificate'));
|
||||
} finally {
|
||||
setIsSubmitting(false);
|
||||
@@ -107,7 +117,6 @@ export const SSLCertificatesTable = () => {
|
||||
setSelectedCertificate(null);
|
||||
toast.success(t('certificateDeleted'));
|
||||
} catch (error) {
|
||||
// console.error("Error deleting certificate:", error);
|
||||
toast.error(t('failedToDeleteCertificate'));
|
||||
} finally {
|
||||
setIsSubmitting(false);
|
||||
@@ -136,57 +145,68 @@ export const SSLCertificatesTable = () => {
|
||||
{t('noCertificatesFound')}
|
||||
</div>
|
||||
) : (
|
||||
<div className={`${theme === 'dark' ? 'bg-gray-900' : 'bg-white'} rounded-lg border border-border shadow-sm`}>
|
||||
<Table>
|
||||
<TableHeader className={`${theme === 'dark' ? 'bg-gray-800' : 'bg-gray-50'}`}>
|
||||
<TableRow className={`${theme === 'dark' ? 'border-gray-700 hover:bg-gray-800' : 'border-gray-200 hover:bg-gray-100'}`}>
|
||||
<TableHead className={`${theme === 'dark' ? 'text-gray-300' : 'text-gray-700'} font-medium text-base py-4`}>{t('domain')}</TableHead>
|
||||
<TableHead className={`${theme === 'dark' ? 'text-gray-300' : 'text-gray-700'} font-medium text-base py-4`}>{t('status')}</TableHead>
|
||||
<TableHead className={`${theme === 'dark' ? 'text-gray-300' : 'text-gray-700'} font-medium text-base py-4`}>{t('issuer')}</TableHead>
|
||||
<TableHead className={`${theme === 'dark' ? 'text-gray-300' : 'text-gray-700'} font-medium text-base py-4`}>{t('validUntil')}</TableHead>
|
||||
<TableHead className={`${theme === 'dark' ? 'text-gray-300' : 'text-gray-700'} font-medium text-base py-4`}>{t('daysLeft')}</TableHead>
|
||||
<TableHead className={`${theme === 'dark' ? 'text-gray-300' : 'text-gray-700'} font-medium text-base py-4`}>Check Interval</TableHead>
|
||||
<TableHead className={`${theme === 'dark' ? 'text-gray-300' : 'text-gray-700'} font-medium text-base py-4 text-right w-[50px]`}>Actions</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{certificates.map((certificate) => (
|
||||
<TableRow
|
||||
key={certificate.id}
|
||||
className="hover:bg-muted/50 cursor-pointer"
|
||||
onClick={() => openViewDialog(certificate)}
|
||||
>
|
||||
<TableCell className="font-medium">
|
||||
{certificate.domain}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<SSLStatusBadge status={certificate.status} />
|
||||
</TableCell>
|
||||
<TableCell>{certificate.issuer_o || certificate.issuer_cn || 'Unknown'}</TableCell>
|
||||
<TableCell>
|
||||
{certificate.valid_till ? new Date(certificate.valid_till).toLocaleDateString() : 'N/A'}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<span className={certificate.days_left <= 7 ? 'text-red-600 font-semibold' : certificate.days_left <= 30 ? 'text-yellow-600 font-semibold' : 'text-green-600'}>
|
||||
{certificate.days_left} {t('days')}
|
||||
</span>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
{certificate.check_interval || 1} {t('days')}
|
||||
</TableCell>
|
||||
<TableCell className="text-right" onClick={(e) => e.stopPropagation()}>
|
||||
<SSLCertificateActions
|
||||
certificate={certificate}
|
||||
onView={openViewDialog}
|
||||
onEdit={openEditDialog}
|
||||
onDelete={openDeleteDialog}
|
||||
/>
|
||||
</TableCell>
|
||||
<>
|
||||
<div className={`${theme === 'dark' ? 'bg-gray-900' : 'bg-white'} rounded-lg border border-border shadow-sm`}>
|
||||
<Table>
|
||||
<TableHeader className={`${theme === 'dark' ? 'bg-gray-800' : 'bg-gray-50'}`}>
|
||||
<TableRow className={`${theme === 'dark' ? 'border-gray-700 hover:bg-gray-800' : 'border-gray-200 hover:bg-gray-100'}`}>
|
||||
<TableHead className={`${theme === 'dark' ? 'text-gray-300' : 'text-gray-700'} font-medium text-base py-4`}>{t('domain')}</TableHead>
|
||||
<TableHead className={`${theme === 'dark' ? 'text-gray-300' : 'text-gray-700'} font-medium text-base py-4`}>{t('status')}</TableHead>
|
||||
<TableHead className={`${theme === 'dark' ? 'text-gray-300' : 'text-gray-700'} font-medium text-base py-4`}>{t('issuer')}</TableHead>
|
||||
<TableHead className={`${theme === 'dark' ? 'text-gray-300' : 'text-gray-700'} font-medium text-base py-4`}>{t('validUntil')}</TableHead>
|
||||
<TableHead className={`${theme === 'dark' ? 'text-gray-300' : 'text-gray-700'} font-medium text-base py-4`}>{t('daysLeft')}</TableHead>
|
||||
<TableHead className={`${theme === 'dark' ? 'text-gray-300' : 'text-gray-700'} font-medium text-base py-4`}>Check Interval</TableHead>
|
||||
<TableHead className={`${theme === 'dark' ? 'text-gray-300' : 'text-gray-700'} font-medium text-base py-4 text-right w-[50px]`}>Actions</TableHead>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{paginatedCertificates.map((certificate) => (
|
||||
<TableRow
|
||||
key={certificate.id}
|
||||
className="hover:bg-muted/50 cursor-pointer"
|
||||
onClick={() => openViewDialog(certificate)}
|
||||
>
|
||||
<TableCell className="font-medium">
|
||||
{certificate.domain}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<SSLStatusBadge status={certificate.status} />
|
||||
</TableCell>
|
||||
<TableCell>{certificate.issuer_o || certificate.issuer_cn || 'Unknown'}</TableCell>
|
||||
<TableCell>
|
||||
{certificate.valid_till ? new Date(certificate.valid_till).toLocaleDateString() : 'N/A'}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<span className={certificate.days_left <= 7 ? 'text-red-600 font-semibold' : certificate.days_left <= 30 ? 'text-yellow-600 font-semibold' : 'text-green-600'}>
|
||||
{certificate.days_left} {t('days')}
|
||||
</span>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
{certificate.check_interval || 1} {t('days')}
|
||||
</TableCell>
|
||||
<TableCell className="text-right" onClick={(e) => e.stopPropagation()}>
|
||||
<SSLCertificateActions
|
||||
certificate={certificate}
|
||||
onView={openViewDialog}
|
||||
onEdit={openEditDialog}
|
||||
onDelete={openDeleteDialog}
|
||||
/>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
|
||||
<SSLPagination
|
||||
currentPage={currentPage}
|
||||
totalPages={totalPages}
|
||||
pageSize={pageSize}
|
||||
totalItems={totalItems}
|
||||
onPageChange={handlePageChange}
|
||||
onPageSizeChange={handlePageSizeChange}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* View Certificate Dialog */}
|
||||
@@ -256,4 +276,4 @@ export const SSLCertificatesTable = () => {
|
||||
</AlertDialog>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
};
|
||||
@@ -30,12 +30,12 @@ export const SSLDomainContent = () => {
|
||||
queryKey: ['ssl-certificates'],
|
||||
queryFn: async () => {
|
||||
try {
|
||||
console.log("Fetching SSL certificates from SSLDomainContent...");
|
||||
// console.log("Fetching SSL certificates from SSLDomainContent...");
|
||||
const result = await fetchSSLCertificates();
|
||||
console.log("Received SSL certificates:", result);
|
||||
// console.log("Received SSL certificates:", result);
|
||||
return result;
|
||||
} catch (error) {
|
||||
console.error("Error fetching certificates:", error);
|
||||
// console.error("Error fetching certificates:", error);
|
||||
toast.error(t('failedToLoadCertificates'));
|
||||
throw error;
|
||||
}
|
||||
@@ -61,7 +61,7 @@ export const SSLDomainContent = () => {
|
||||
// Edit certificate mutation - Updated to ensure thresholds are properly updated
|
||||
const editMutation = useMutation({
|
||||
mutationFn: async (certificate: SSLCertificate) => {
|
||||
console.log("Updating certificate with data:", certificate);
|
||||
// console.log("Updating certificate with data:", certificate);
|
||||
|
||||
// Create the update data object
|
||||
const updateData = {
|
||||
@@ -70,12 +70,12 @@ export const SSLDomainContent = () => {
|
||||
notification_channel: certificate.notification_channel,
|
||||
};
|
||||
|
||||
console.log("Update data to be sent:", updateData);
|
||||
// console.log("Update data to be sent:", updateData);
|
||||
|
||||
// Update certificate in the database using PocketBase directly
|
||||
const updated = await pb.collection('ssl_certificates').update(certificate.id, updateData);
|
||||
|
||||
console.log("PocketBase update response:", updated);
|
||||
// console.log("PocketBase update response:", updated);
|
||||
|
||||
// After updating the settings, refresh the certificate to ensure it's up to date
|
||||
// This will also check if notification needs to be sent based on updated thresholds
|
||||
@@ -90,7 +90,7 @@ export const SSLDomainContent = () => {
|
||||
toast.success(t('sslCertificateUpdated'));
|
||||
},
|
||||
onError: (error) => {
|
||||
console.error("Error updating SSL certificate:", error);
|
||||
// console.error("Error updating SSL certificate:", error);
|
||||
toast.error(error instanceof Error ? error.message : t('failedToUpdateCertificate'));
|
||||
}
|
||||
});
|
||||
@@ -103,7 +103,7 @@ export const SSLDomainContent = () => {
|
||||
toast.success(t('sslCertificateDeleted'));
|
||||
},
|
||||
onError: (error) => {
|
||||
console.error("Error deleting SSL certificate:", error);
|
||||
// console.error("Error deleting SSL certificate:", error);
|
||||
toast.error(error instanceof Error ? error.message : t('failedToDeleteCertificate'));
|
||||
}
|
||||
});
|
||||
@@ -117,7 +117,7 @@ export const SSLDomainContent = () => {
|
||||
// Removed individual success toast notification
|
||||
},
|
||||
onError: (error) => {
|
||||
console.error("Error refreshing SSL certificate:", error);
|
||||
// console.error("Error refreshing SSL certificate:", error);
|
||||
setRefreshingId(null);
|
||||
|
||||
// Still refresh the data to show any partial information that was saved
|
||||
@@ -142,7 +142,7 @@ export const SSLDomainContent = () => {
|
||||
}
|
||||
},
|
||||
onError: (error) => {
|
||||
console.error("Error refreshing all certificates:", error);
|
||||
// console.error("Error refreshing all certificates:", error);
|
||||
toast.error(t('failedToCheckCertificate'));
|
||||
setIsRefreshingAll(false);
|
||||
|
||||
|
||||
@@ -0,0 +1,131 @@
|
||||
|
||||
import {
|
||||
Pagination,
|
||||
PaginationContent,
|
||||
PaginationItem,
|
||||
PaginationLink,
|
||||
PaginationNext,
|
||||
PaginationPrevious
|
||||
} from "@/components/ui/pagination";
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue
|
||||
} from "@/components/ui/select";
|
||||
import { SSLPageSize } from "@/hooks/useSSLPagination";
|
||||
import { useLanguage } from "@/contexts/LanguageContext";
|
||||
|
||||
interface SSLPaginationProps {
|
||||
currentPage: number;
|
||||
totalPages: number;
|
||||
pageSize: SSLPageSize;
|
||||
totalItems: number;
|
||||
onPageChange: (page: number) => void;
|
||||
onPageSizeChange: (size: SSLPageSize) => void;
|
||||
}
|
||||
|
||||
export function SSLPagination({
|
||||
currentPage,
|
||||
totalPages,
|
||||
pageSize,
|
||||
totalItems,
|
||||
onPageChange,
|
||||
onPageSizeChange,
|
||||
}: SSLPaginationProps) {
|
||||
const { t } = useLanguage();
|
||||
|
||||
// Generate page numbers to display
|
||||
const getPageNumbers = () => {
|
||||
const pages = [];
|
||||
const maxVisiblePages = 5;
|
||||
const halfVisible = Math.floor(maxVisiblePages / 2);
|
||||
|
||||
let startPage = Math.max(1, currentPage - halfVisible);
|
||||
let endPage = Math.min(totalPages, startPage + maxVisiblePages - 1);
|
||||
|
||||
// Adjust start page if we don't have enough pages at the end
|
||||
if (endPage - startPage + 1 < maxVisiblePages) {
|
||||
startPage = Math.max(1, endPage - maxVisiblePages + 1);
|
||||
}
|
||||
|
||||
for (let i = startPage; i <= endPage; i++) {
|
||||
pages.push(i);
|
||||
}
|
||||
|
||||
return pages;
|
||||
};
|
||||
|
||||
const startItem = Math.min((currentPage - 1) * pageSize + 1, totalItems);
|
||||
const endItem = Math.min(currentPage * pageSize, totalItems);
|
||||
|
||||
return (
|
||||
<div className="flex items-center justify-between py-4 px-4 border-t border-border">
|
||||
<div className="flex items-center space-x-2">
|
||||
<span className="text-sm text-muted-foreground">
|
||||
{t("rowsPerPage") || "Rows per page"}:
|
||||
</span>
|
||||
<Select
|
||||
value={pageSize.toString()}
|
||||
onValueChange={(value) => onPageSizeChange(parseInt(value) as SSLPageSize)}
|
||||
>
|
||||
<SelectTrigger className="h-8 w-[70px]">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="10">10</SelectItem>
|
||||
<SelectItem value="30">30</SelectItem>
|
||||
<SelectItem value="50">50</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<span className="text-sm text-muted-foreground">
|
||||
{totalItems > 0
|
||||
? `${startItem}-${endItem} of ${totalItems} ${t("certificates") || "certificates"}`
|
||||
: `0 ${t("certificates") || "certificates"}`
|
||||
}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{totalPages > 1 && (
|
||||
<Pagination>
|
||||
<PaginationContent>
|
||||
<PaginationItem>
|
||||
<PaginationPrevious
|
||||
onClick={() => onPageChange(Math.max(1, currentPage - 1))}
|
||||
className={
|
||||
currentPage === 1
|
||||
? "pointer-events-none opacity-50"
|
||||
: "cursor-pointer"
|
||||
}
|
||||
/>
|
||||
</PaginationItem>
|
||||
|
||||
{getPageNumbers().map((page) => (
|
||||
<PaginationItem key={page}>
|
||||
<PaginationLink
|
||||
isActive={page === currentPage}
|
||||
onClick={() => onPageChange(page)}
|
||||
className="cursor-pointer"
|
||||
>
|
||||
{page}
|
||||
</PaginationLink>
|
||||
</PaginationItem>
|
||||
))}
|
||||
|
||||
<PaginationItem>
|
||||
<PaginationNext
|
||||
onClick={() => onPageChange(Math.min(totalPages, currentPage + 1))}
|
||||
className={
|
||||
currentPage === totalPages
|
||||
? "pointer-events-none opacity-50"
|
||||
: "cursor-pointer"
|
||||
}
|
||||
/>
|
||||
</PaginationItem>
|
||||
</PaginationContent>
|
||||
</Pagination>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
|
||||
import { useState, useMemo } from 'react';
|
||||
import { SSLCertificate } from '@/types/ssl.types';
|
||||
|
||||
export type SSLPageSize = 10 | 30 | 50;
|
||||
|
||||
interface UseSSLPaginationProps {
|
||||
certificates: SSLCertificate[];
|
||||
initialPageSize?: SSLPageSize;
|
||||
}
|
||||
|
||||
export const useSSLPagination = ({
|
||||
certificates,
|
||||
initialPageSize = 10
|
||||
}: UseSSLPaginationProps) => {
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
const [pageSize, setPageSize] = useState<SSLPageSize>(initialPageSize);
|
||||
|
||||
const { paginatedCertificates, totalPages } = useMemo(() => {
|
||||
const totalItems = certificates.length;
|
||||
const pages = Math.ceil(totalItems / pageSize);
|
||||
const startIndex = (currentPage - 1) * pageSize;
|
||||
const endIndex = startIndex + pageSize;
|
||||
|
||||
return {
|
||||
paginatedCertificates: certificates.slice(startIndex, endIndex),
|
||||
totalPages: Math.max(1, pages)
|
||||
};
|
||||
}, [certificates, currentPage, pageSize]);
|
||||
|
||||
// Reset to first page when page size changes or certificates change
|
||||
const handlePageSizeChange = (newPageSize: SSLPageSize) => {
|
||||
setPageSize(newPageSize);
|
||||
setCurrentPage(1);
|
||||
};
|
||||
|
||||
// Reset to first page if current page exceeds total pages
|
||||
const handlePageChange = (page: number) => {
|
||||
const validPage = Math.min(Math.max(1, page), totalPages);
|
||||
setCurrentPage(validPage);
|
||||
};
|
||||
|
||||
return {
|
||||
paginatedCertificates,
|
||||
currentPage,
|
||||
totalPages,
|
||||
pageSize,
|
||||
totalItems: certificates.length,
|
||||
handlePageChange,
|
||||
handlePageSizeChange,
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,119 @@
|
||||
# 📍 Development Roadmap
|
||||
|
||||
Welcome to the official development roadmap for this project. This document outlines our plans, priorities, and direction for upcoming releases. It is intended to keep contributors, users, and stakeholders informed about the project’s evolution.
|
||||
|
||||
> 🔄 **Note**: This roadmap is subject to change based on feedback, urgency, or shifts in priorities.
|
||||
|
||||
---
|
||||
|
||||
## ✅ Goals and Vision
|
||||
|
||||
Our mission is to deliver a robust, scalable, and user-friendly solution for "uptime monitoring and server infrastructure insights".
|
||||
|
||||
**Core objectives:**
|
||||
- Provide high performance with low resource usage
|
||||
- Ensure scalability and modularity
|
||||
- Focus on excellent user experience (UX)
|
||||
- Maintain open-source transparency and community-driven features
|
||||
|
||||
---
|
||||
|
||||
## 🧩 Key Main Features
|
||||
|
||||
#### Delivery Status:
|
||||
- ✅ Setup authentication and user management
|
||||
- ✅ Core monitoring dashboard
|
||||
- ✅ Agent communication with backend
|
||||
- ✅ 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)
|
||||
- ✅ Distributed Regional Monitoring Agent
|
||||
- ✅ System Setting Panel and Mail Settings
|
||||
- ✅ Data Retention & Automate Shrink
|
||||
- ✅ Open-source release with documentation
|
||||
|
||||
---
|
||||
## 🚦 Roadmap Stages
|
||||
|
||||
The roadmap is divided into the following stages:
|
||||
|
||||
| Stage | Description |
|
||||
|-------|-------------|
|
||||
| 🎯 Planned | Approved features or improvements that are on the schedule |
|
||||
| 🔧 In Progress | Actively being developed |
|
||||
| ✅ Completed | Fully implemented and tested |
|
||||
| ⏳ Backlog | On hold or pending prioritization |
|
||||
| 🧪 Experimental | Testing new concepts or prototypes |
|
||||
|
||||
---
|
||||
|
||||
## 🗂 Milestone Overview
|
||||
|
||||
### 📦 v1.0.0-1.3.0 – Initial Release _(Target: Q2 2025)_
|
||||
**Status:** ✅ Completed
|
||||
**Goal:** MVP feature completion, API stability, and core use-case readiness.
|
||||
|
||||
#### Key Deliverables: https://github.com/operacle/checkcle/releases
|
||||
|
||||
---
|
||||
|
||||
### 🚀 v1.4 - 1.5 – Feature Enhancements _(Target: Q3 2025)_
|
||||
**Status:** 🔧 In Progress
|
||||
#### Tentative Features:
|
||||
- [ ] ✅ Server and Service Table row clickable to detail page.
|
||||
- [ ] ✅ Implement pagination for the SSL dashboard table
|
||||
- [ ] 🔧 Server Agent (RPM, Docker container, and general binary package)
|
||||
- [ ] 🔧 Notification System (Webhook, Telegram, Discord, Slack, Email, Google Chat)
|
||||
- [ ] 🔧 Improve Uptime Service and Server connection update based on status and notification.
|
||||
- [ ] 🔧 Improve SSL perform the initial check automatically after creation
|
||||
- [ ] 🔧 Rate limiting and abuse protection
|
||||
- [ ] 🎯 Improve the Operational status page
|
||||
- [ ] 🎯 Improve the Schedule and Incident for automation
|
||||
- [ ] 🎯 Server Windows Agent
|
||||
- [ ] 🎯 More Uptime Service Type (HTTP keyword, HTTP json)
|
||||
- [ ] 🎯 Server support with Multiple Network Interfaces
|
||||
- [ ] 🎯 Server support with cpu temperature
|
||||
- [ ] 🎯 Server upport with multiple disks
|
||||
- [ ] 🎯 Add 2FA support
|
||||
- [ ] 🎯 Rate limiting and abuse protection
|
||||
|
||||
---
|
||||
|
||||
## 🧠 Ideas / Community Wishlist
|
||||
|
||||
These are community-suggested or experimental features under review:
|
||||
|
||||
- [ ] Grouping uptime services
|
||||
- [ ] OAuth2 integration
|
||||
- [ ] Mobile PWA support
|
||||
- [ ] Cloud-native Helm/Kubernetes deployment
|
||||
|
||||
You’re welcome to propose features via [GitHub Discussions](https://github.com/operacle/checkcle/discussions) or open an issue with the `feature-request` template.
|
||||
|
||||
---
|
||||
|
||||
## 📌 How to Contribute
|
||||
|
||||
We encourage contributions from the community!
|
||||
To get involved:
|
||||
|
||||
- Review [CONTRIBUTING.md](../CONTRIBUTING.md) | https://docs.checkcle.io
|
||||
- Check out the [Open Issues](https://github.com/operacle/checkcle/issues)
|
||||
- Join us in shaping the roadmap via [Discussions](https://github.com/operacle/checkcle/discussions)
|
||||
|
||||
---
|
||||
|
||||
## 📅 Last Updated
|
||||
_This roadmap was last updated on **July 26, 2025**._
|
||||
|
||||
---
|
||||
|
||||
With ❤️ from the CheckCle
|
||||
[Website](https://checkcle.io) | [GitHub](https://github.com/operacle/checkcle)
|
||||
|
||||
+1
-1
@@ -162,7 +162,7 @@ CheckCleの改善にご協力いただける方法をご紹介します:
|
||||
- GitHubリポジトリ: ⭐ [CheckCle](https://github.com/operacle/checkcle.git)
|
||||
- コミュニティチャンネル: ディスカッションやissuesで参加してください!
|
||||
- Discord: コミュニティに参加 [@discord](https://discord.gg/xs9gbubGwX)
|
||||
- X: [@tlengoss](https://x.com/tlengoss)
|
||||
- X: [@checkcle_oss](https://x.com/checkcle_oss)
|
||||
|
||||
## 📜 ライセンス
|
||||
|
||||
|
||||
+1
-1
@@ -163,7 +163,7 @@ services:
|
||||
- ឃ្លាំងផ្ទុកកូដ: ⭐ [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)
|
||||
- X: [@checkcle_oss](https://x.com/checkcle_oss)
|
||||
|
||||
## 📜 License
|
||||
|
||||
|
||||
Reference in New Issue
Block a user