Merge pull request #22 from operacle/develop
feat: release maintenance and incident management features; apply hotfixes
This commit is contained in:
+3
-1
@@ -21,4 +21,6 @@ dist-ssr
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
*.sw?
|
||||
application/src/lib/pocketbase.ts
|
||||
Dockerfile
|
||||
|
||||
-39
@@ -1,39 +0,0 @@
|
||||
# Stage 1: Build the frontend
|
||||
FROM node:18-alpine AS frontend-builder
|
||||
WORKDIR /app
|
||||
COPY application/package*.json ./
|
||||
RUN npm install
|
||||
COPY application/ ./
|
||||
RUN npm run build
|
||||
|
||||
# Stage 2: Download PocketBase binary
|
||||
FROM alpine:3.17 AS pb-builder
|
||||
WORKDIR /pb
|
||||
ARG PB_VERSION=0.27.2
|
||||
RUN apk add --no-cache wget unzip \
|
||||
&& wget https://github.com/pocketbase/pocketbase/releases/download/v${PB_VERSION}/pocketbase_${PB_VERSION}_linux_amd64.zip \
|
||||
&& unzip pocketbase_${PB_VERSION}_linux_amd64.zip \
|
||||
&& chmod +x /pb/pocketbase
|
||||
|
||||
# Stage 3: Final runtime image
|
||||
FROM alpine:3.17
|
||||
WORKDIR /app
|
||||
|
||||
# Copy PocketBase binary
|
||||
COPY --from=pb-builder /pb/pocketbase /app/pocketbase
|
||||
|
||||
# Copy frontend build to PocketBase public directory
|
||||
COPY --from=frontend-builder /app/dist /app/pb_public
|
||||
|
||||
# Copy backend files
|
||||
COPY server/pb_migrations /app/pb_migrations
|
||||
COPY server/pb_data /app/pb_data
|
||||
|
||||
# Mark pb_data as a volume for runtime persistence
|
||||
VOLUME /app/pb_data
|
||||
|
||||
# Expose default PocketBase port
|
||||
EXPOSE 8090
|
||||
|
||||
# Launch PocketBase and bind to 0.0.0.0 for external access
|
||||
CMD ["/app/pocketbase", "serve", "--http=0.0.0.0:8090", "--dir", "/app/pb_data"]
|
||||
@@ -24,22 +24,23 @@ CheckCle is an Open Source solution for seamless, real-time monitoring of full-s
|
||||
|
||||
## #️⃣ Getting Started
|
||||
|
||||
### Installation with Docker Compose
|
||||
### Installation with Docker Run and Compose
|
||||
1. Copy ready docker run command
|
||||
```bash
|
||||
# Create Docker Volume for data persistence
|
||||
|
||||
docker volume create pb_data
|
||||
|
||||
|
||||
# Docker Run Command
|
||||
|
||||
docker run --name checkcle --restart unless-stopped -p 8090:8090 -v pb_data:/app/pb_data --ulimit nofile=4096:8192 operacle/checkcle:latest
|
||||
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
|
||||
|
||||
```
|
||||
2. Docker Compose - Recommended
|
||||
```bash
|
||||
|
||||
version: '3.9'
|
||||
|
||||
services:
|
||||
checkcle:
|
||||
image: operacle/checkcle:latest
|
||||
@@ -48,15 +49,12 @@ services:
|
||||
ports:
|
||||
- "8090:8090" # Web Application
|
||||
volumes:
|
||||
- pb_data:/app/pb_data # Ensure persistent data across rebuilds
|
||||
- /opt/pb_data:/mnt/pb_data # Host directory mapped to container path
|
||||
ulimits:
|
||||
nofile:
|
||||
soft: 4096
|
||||
hard: 8192
|
||||
|
||||
volumes:
|
||||
pb_data: # Docker-managed volume for data persistence
|
||||
|
||||
```
|
||||
3. Admin Web Management
|
||||
|
||||
@@ -64,12 +62,12 @@ volumes:
|
||||
User: admin@example.com
|
||||
Passwd: Admin123456
|
||||
|
||||
4. Follow the Quick Start Guide at https://docs.checkcle.com (Coming Soon)
|
||||
4. Follow the Quick Start Guide at https://docs.checkcle.io
|
||||
|
||||
###
|
||||

|
||||

|
||||

|
||||

|
||||
|
||||
## 📝 Development Roadmap
|
||||
|
||||
@@ -80,15 +78,16 @@ volumes:
|
||||
- [x] Docker containerization
|
||||
- [x] CheckCle Website
|
||||
- [x] CheckCle Demo Server
|
||||
- [x] SSL & Domain Monitoring - added in release of https://github.com/operacle/checkcle/releases/tag/v1.1.0
|
||||
- [x] SSL & Domain Monitoring
|
||||
- [x] Schedule Maintenance
|
||||
- [x] Incident Management
|
||||
- [ ] Uptime monitoring (PING - Inprogress)
|
||||
- [ ] Infrastructure Server Monitoring
|
||||
- [ ] Schedule Maintenance & Incident Management
|
||||
- [ ] Operational Status / Public Status Pages
|
||||
- [ ] Uptime monitoring (TCP, PING, DNS)
|
||||
- [ ] User Permission Roles & Service Group
|
||||
- [ ] Notifications (Email/Slack/Discord/Signal)
|
||||
- [ ] Open-source release with full documentation
|
||||
- [x] Open-source release with full documentation
|
||||
|
||||
## 🌟 CheckCle for Communities?
|
||||
- **Built with Passion**: Created by an open-source enthusiast for the community
|
||||
@@ -111,6 +110,7 @@ Here are some ways you can help improve CheckCle:
|
||||
|
||||
## 🌍 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)
|
||||
- Community Channels: Engage via discussions and issues!
|
||||
- Discord: Join our community [@discord](https://discord.gg/xs9gbubGwX)
|
||||
|
||||
@@ -43,22 +43,22 @@ export const ServiceRow = ({
|
||||
className={`border-b ${theme === 'dark' ? 'border-gray-800 hover:bg-gray-900/60' : 'border-gray-200 hover:bg-gray-50'} cursor-pointer`}
|
||||
onClick={handleRowClick}
|
||||
>
|
||||
<TableCell className="font-medium py-4" onClick={(e) => e.stopPropagation()}>
|
||||
<TableCell className="font-medium py-4">
|
||||
<ServiceRowHeader service={service} />
|
||||
</TableCell>
|
||||
<TableCell onClick={(e) => e.stopPropagation()} className={`text-base py-4 ${theme === 'dark' ? 'text-gray-300' : 'text-gray-700'}`}>
|
||||
<TableCell className={`text-base py-4 ${theme === 'dark' ? 'text-gray-300' : 'text-gray-700'}`}>
|
||||
{service.type}
|
||||
</TableCell>
|
||||
<TableCell onClick={(e) => e.stopPropagation()} className="py-4">
|
||||
<TableCell className="py-4">
|
||||
<StatusBadge status={service.status} size="md" />
|
||||
</TableCell>
|
||||
<TableCell onClick={(e) => e.stopPropagation()} className="py-4">
|
||||
<TableCell className="py-4">
|
||||
<ServiceRowResponseTime responseTime={service.responseTime} />
|
||||
</TableCell>
|
||||
<TableCell className="w-52 py-4" onClick={(e) => e.stopPropagation()}>
|
||||
<TableCell className="w-52 py-4">
|
||||
<UptimeBar uptime={service.uptime} status={service.status} serviceId={service.id} />
|
||||
</TableCell>
|
||||
<TableCell onClick={(e) => e.stopPropagation()} className="py-4">
|
||||
<TableCell className="py-4">
|
||||
<LastCheckedTime
|
||||
lastCheckedTime={displayTimestamp}
|
||||
status={service.status}
|
||||
@@ -77,4 +77,4 @@ export const ServiceRow = ({
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
);
|
||||
};
|
||||
};
|
||||
@@ -1,4 +1,3 @@
|
||||
|
||||
import { useState } from "react";
|
||||
import { useToast } from "@/hooks/use-toast";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
@@ -24,6 +23,7 @@ export function useServiceActions(initialServices: Service[]) {
|
||||
};
|
||||
|
||||
const handleViewDetail = (service: Service) => {
|
||||
console.log(`Navigating to service detail for service ID: ${service.id}`);
|
||||
navigate(`/service/${service.id}`);
|
||||
};
|
||||
|
||||
@@ -202,4 +202,4 @@ export function useServiceActions(initialServices: Service[]) {
|
||||
confirmDelete,
|
||||
handleMuteAlerts
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,9 @@
|
||||
|
||||
import React from "react";
|
||||
import { Card } from "@/components/ui/card";
|
||||
import { SSLCertificate } from "@/types/ssl.types";
|
||||
import { useLanguage } from "@/contexts/LanguageContext";
|
||||
import { OverviewCard } from "@/components/schedule-incident/common/OverviewCard";
|
||||
import { Shield, ShieldAlert, ShieldX } from "lucide-react";
|
||||
|
||||
interface SSLCertificateStatusCardsProps {
|
||||
certificates: SSLCertificate[];
|
||||
@@ -16,48 +18,30 @@ export const SSLCertificateStatusCards = ({ certificates }: SSLCertificateStatus
|
||||
const expiredCount = certificates.filter(cert => cert.status === 'expired').length;
|
||||
|
||||
return (
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||
<Card className="p-6 flex items-center space-x-4">
|
||||
<div className="bg-green-100 dark:bg-green-900/20 p-3 rounded-full">
|
||||
<div className="h-12 w-12 rounded-full bg-green-500/20 flex items-center justify-center">
|
||||
<div className="h-8 w-8 rounded-full bg-green-500 flex items-center justify-center text-white">
|
||||
✓
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-sm font-medium text-muted-foreground">{t('validCertificates')}</p>
|
||||
<p className="text-3xl font-bold">{validCount}</p>
|
||||
</div>
|
||||
</Card>
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
|
||||
<OverviewCard
|
||||
title={t('validCertificates')}
|
||||
value={validCount}
|
||||
icon={<Shield className="h-6 w-6 text-white" />}
|
||||
color="green"
|
||||
className="hover:scale-105 transition-transform duration-200"
|
||||
/>
|
||||
|
||||
<Card className="p-6 flex items-center space-x-4">
|
||||
<div className="bg-yellow-100 dark:bg-yellow-900/20 p-3 rounded-full">
|
||||
<div className="h-12 w-12 rounded-full bg-yellow-500/20 flex items-center justify-center">
|
||||
<div className="h-8 w-8 rounded-full bg-yellow-500 flex items-center justify-center text-white">
|
||||
!
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-sm font-medium text-muted-foreground">{t('expiringSoon')}</p>
|
||||
<p className="text-3xl font-bold">{expiringCount}</p>
|
||||
</div>
|
||||
</Card>
|
||||
<OverviewCard
|
||||
title={t('expiringSoon')}
|
||||
value={expiringCount}
|
||||
icon={<ShieldAlert className="h-6 w-6 text-white" />}
|
||||
color="amber"
|
||||
className="hover:scale-105 transition-transform duration-200"
|
||||
/>
|
||||
|
||||
<Card className="p-6 flex items-center space-x-4">
|
||||
<div className="bg-red-100 dark:bg-red-900/20 p-3 rounded-full">
|
||||
<div className="h-12 w-12 rounded-full bg-red-500/20 flex items-center justify-center">
|
||||
<div className="h-8 w-8 rounded-full bg-red-500 flex items-center justify-center text-white">
|
||||
✗
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-sm font-medium text-muted-foreground">{t('expired')}</p>
|
||||
<p className="text-3xl font-bold">{expiredCount}</p>
|
||||
</div>
|
||||
</Card>
|
||||
<OverviewCard
|
||||
title={t('expired')}
|
||||
value={expiredCount}
|
||||
icon={<ShieldX className="h-6 w-6 text-white" />}
|
||||
color="red"
|
||||
className="hover:scale-105 transition-transform duration-200"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -37,7 +37,7 @@ export const LanguageProvider = ({ children }: { children: ReactNode }) => {
|
||||
}
|
||||
|
||||
return key;
|
||||
git fetch origin };
|
||||
};
|
||||
|
||||
return (
|
||||
<LanguageContext.Provider value={{ language, setLanguage, t }}>
|
||||
|
||||
@@ -1,16 +1,17 @@
|
||||
import PocketBase from 'pocketbase';
|
||||
|
||||
// Auto-detect base URL from browser location
|
||||
// Dynamically detect API base URL from current host (for use in browser)
|
||||
const dynamicBaseUrl =
|
||||
typeof window !== 'undefined'
|
||||
? window.location.origin
|
||||
? `${window.location.protocol}//${window.location.hostname}:8090`
|
||||
: 'http://localhost:8090';
|
||||
|
||||
// Define API endpoints
|
||||
// Define available API endpoints
|
||||
export const API_ENDPOINTS = {
|
||||
REMOTE: dynamicBaseUrl
|
||||
};
|
||||
|
||||
// Get the current endpoint from localStorage or use remote as default
|
||||
export const getCurrentEndpoint = (): string => {
|
||||
if (typeof window !== 'undefined') {
|
||||
const savedEndpoint = localStorage.getItem('pocketbase_endpoint');
|
||||
@@ -19,19 +20,26 @@ export const getCurrentEndpoint = (): string => {
|
||||
return API_ENDPOINTS.REMOTE;
|
||||
};
|
||||
|
||||
// Set the API endpoint and reinitialize PocketBase
|
||||
export const setApiEndpoint = (endpoint: string): void => {
|
||||
if (typeof window !== 'undefined') {
|
||||
localStorage.setItem('pocketbase_endpoint', endpoint);
|
||||
window.location.reload();
|
||||
window.location.reload(); // Reload to reinitialize PocketBase with new endpoint
|
||||
}
|
||||
};
|
||||
|
||||
// Initialize the PocketBase client with the current API URL
|
||||
export const pb = new PocketBase(getCurrentEndpoint());
|
||||
|
||||
export const isAuthenticated = () => pb.authStore.isValid;
|
||||
// Helper to check if user is authenticated
|
||||
export const isAuthenticated = () => {
|
||||
return pb.authStore.isValid;
|
||||
};
|
||||
|
||||
// Export the auth store for use in components
|
||||
export const authStore = pb.authStore;
|
||||
|
||||
// Configure PocketBase to persist authentication between page reloads
|
||||
if (typeof window !== 'undefined') {
|
||||
const storedAuthData = localStorage.getItem('pocketbase_auth');
|
||||
if (storedAuthData) {
|
||||
@@ -44,15 +52,13 @@ if (typeof window !== 'undefined') {
|
||||
}
|
||||
}
|
||||
|
||||
// Subscribe to authStore changes to persist authentication
|
||||
pb.authStore.onChange(() => {
|
||||
if (pb.authStore.isValid) {
|
||||
localStorage.setItem(
|
||||
'pocketbase_auth',
|
||||
JSON.stringify({
|
||||
token: pb.authStore.token,
|
||||
model: pb.authStore.model
|
||||
})
|
||||
);
|
||||
localStorage.setItem('pocketbase_auth', JSON.stringify({
|
||||
token: pb.authStore.token,
|
||||
model: pb.authStore.model
|
||||
}));
|
||||
} else {
|
||||
localStorage.removeItem('pocketbase_auth');
|
||||
}
|
||||
|
||||
+3
-5
@@ -1,3 +1,4 @@
|
||||
version: '3.9'
|
||||
services:
|
||||
checkcle:
|
||||
image: operacle/checkcle:latest
|
||||
@@ -6,11 +7,8 @@ services:
|
||||
ports:
|
||||
- "8090:8090" # Web Application
|
||||
volumes:
|
||||
- pb_data:/app/pb_data # Ensure persistent data across rebuilds
|
||||
- /opt/pb_data:/mnt/pb_data # Host directory mapped to container path
|
||||
ulimits:
|
||||
nofile:
|
||||
soft: 4096
|
||||
hard: 8192
|
||||
|
||||
volumes:
|
||||
pb_data: # Docker-managed volume for data persistence
|
||||
hard: 8192
|
||||
@@ -1,3 +1,5 @@
|
||||
version: '3.9'
|
||||
|
||||
services:
|
||||
checkcle:
|
||||
build:
|
||||
@@ -6,13 +8,10 @@ services:
|
||||
container_name: checkcle
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "8090:8090" # Allow access to frontend + PocketBase
|
||||
- "8090:8090"
|
||||
volumes:
|
||||
- pb_data:/app/pb_data # Ensure persistent data across rebuilds
|
||||
- /var/pb_data:/mnt/pb_data # Updated mount target to match CMD in Dockerfile
|
||||
ulimits:
|
||||
nofile:
|
||||
soft: 4096
|
||||
hard: 8192
|
||||
|
||||
volumes:
|
||||
pb_data: # Docker-managed volume for persistence
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
version: '3.9'
|
||||
|
||||
services:
|
||||
checkcle:
|
||||
image: operacle/checkcle:latest
|
||||
@@ -6,11 +8,8 @@ services:
|
||||
ports:
|
||||
- "8090:8090" # Web Application
|
||||
volumes:
|
||||
- pb_data:/app/pb_data # Ensure persistent data across rebuilds
|
||||
- /opt/pb_data:/mnt/pb_data # Host directory mapped to container path
|
||||
ulimits:
|
||||
nofile:
|
||||
soft: 4096
|
||||
hard: 8192
|
||||
|
||||
volumes:
|
||||
pb_data: # Docker-managed volume for data persistence
|
||||
hard: 8192
|
||||
Reference in New Issue
Block a user