Skip to content
Merged
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
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ services:

###
![checkcle-collapse-black](https://pub-4a4062303020445f8f289a2fee84f9e8.r2.dev/images/checkcle-black.png)
![Service Detail Page](https://pub-4a4062303020445f8f289a2fee84f9e8.r2.dev/images/checkcle-detailpage.png)
![Service Detail Page](https://pub-4a4062303020445f8f289a2fee84f9e8.r2.dev/images/uptime-service-detail.png)
![Schedule Maintenance](https://pub-4a4062303020445f8f289a2fee84f9e8.r2.dev/images/maintenance-dahboard.png)

## 📝 Development Roadmap
Expand All @@ -92,10 +92,10 @@ services:
- ✅ SSL & Domain Monitoring
- ✅ Schedule Maintenance
- ✅ Incident Management
- [ ] Uptime monitoring (PING - Inprogress)
- [ ] Infrastructure Server Monitoring
- ✅ Operational Status / Public Status Pages
- [ ] Uptime monitoring (TCP, PING, DNS)
- ✅ 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)
Expand Down
166 changes: 61 additions & 105 deletions application/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,115 +1,71 @@

import React from 'react';
import { BrowserRouter as Router, Routes, Route, Navigate } from 'react-router-dom';
import React, { useState } from 'react';
import { BrowserRouter, Routes, Route } from 'react-router-dom';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { ThemeProvider } from '@/contexts/ThemeContext';
import { LanguageProvider } from '@/contexts/LanguageContext';
import { SidebarProvider } from '@/contexts/SidebarContext';
import { Toaster } from '@/components/ui/toaster';
import { ErrorBoundary } from '@/components/ErrorBoundary';

// Pages
import Index from '@/pages/Index';
import Login from '@/pages/Login';
import Dashboard from '@/pages/Dashboard';
import ServiceDetail from '@/pages/ServiceDetail';
import Settings from '@/pages/Settings';
import Profile from '@/pages/Profile';
import SslDomain from '@/pages/SslDomain';
import ScheduleIncident from '@/pages/ScheduleIncident';
import OperationalPage from '@/pages/OperationalPage';
import PublicStatusPage from '@/pages/PublicStatusPage';
import RegionalMonitoring from '@/pages/RegionalMonitoring';
import NotFound from '@/pages/NotFound';
import { Toaster } from '@/components/ui/sonner';

import { authService } from '@/services/authService';
import { ThemeProvider } from './contexts/ThemeContext';
import { LanguageProvider } from './contexts/LanguageContext';
import { SidebarProvider } from './contexts/SidebarContext';

const queryClient = new QueryClient({
defaultOptions: {
queries: {
retry: 2,
staleTime: 5 * 60 * 1000, // 5 minutes
},
},
});

// Protected Route wrapper
const ProtectedRoute = ({ children }: { children: React.ReactNode }) => {
const isAuthenticated = authService.isAuthenticated();
return isAuthenticated ? <>{children}</> : <Navigate to="/login" replace />;
};
import Index from './pages/Index';
import Login from './pages/Login';
import Dashboard from './pages/Dashboard';
import InstanceMonitoring from './pages/InstanceMonitoring';
import ContainerMonitoring from './pages/ContainerMonitoring';
import ServiceDetail from './pages/ServiceDetail';
import SslDomain from './pages/SslDomain';
import ScheduleIncident from './pages/ScheduleIncident';
import OperationalPage from './pages/OperationalPage';
import RegionalMonitoring from './pages/RegionalMonitoring';
import Settings from './pages/Settings';
import Profile from './pages/Profile';
import NotFound from './pages/NotFound';
import PublicStatusPage from './pages/PublicStatusPage';
import { ProtectedRoute } from './components/auth/ProtectedRoute';
import ServerDetail from './pages/ServerDetail';

function App() {
const [queryClient] = useState(() => new QueryClient({
defaultOptions: {
queries: {
staleTime: 60 * 1000
}
}
}));

return (
<ErrorBoundary>
<QueryClientProvider client={queryClient}>
<ThemeProvider>
<LanguageProvider>
<SidebarProvider>
<Router>
<Routes>
<Route path="/" element={<Index />} />
<Route path="/login" element={<Login />} />
<Route path="/public/:slug" element={<PublicStatusPage />} />

{/* Protected Routes */}
<Route path="/dashboard" element={
<ProtectedRoute>
<Dashboard />
</ProtectedRoute>
} />

<Route path="/service/:id" element={
<ProtectedRoute>
<ServiceDetail />
</ProtectedRoute>
} />

<Route path="/settings" element={
<ProtectedRoute>
<Settings />
</ProtectedRoute>
} />

<Route path="/profile" element={
<ProtectedRoute>
<Profile />
</ProtectedRoute>
} />

<Route path="/ssl-domain" element={
<ProtectedRoute>
<SslDomain />
</ProtectedRoute>
} />

<Route path="/schedule-incident" element={
<ProtectedRoute>
<ScheduleIncident />
</ProtectedRoute>
} />

<Route path="/operational-page" element={
<ProtectedRoute>
<OperationalPage />
</ProtectedRoute>
} />

<Route path="/regional-monitoring" element={
<ProtectedRoute>
<RegionalMonitoring />
</ProtectedRoute>
} />

<Route path="*" element={<NotFound />} />
</Routes>
</Router>
<BrowserRouter>
<ThemeProvider>
<LanguageProvider>
<SidebarProvider>
<QueryClientProvider client={queryClient}>
<Toaster />
</SidebarProvider>
</LanguageProvider>
</ThemeProvider>
</QueryClientProvider>
</ErrorBoundary>
<Routes>
{/* Public routes */}
<Route path="/public/:slug" element={<PublicStatusPage />} />

{/* Protected routes */}
<Route path="/login" element={<Login />} />
<Route path="/" element={<ProtectedRoute><Index /></ProtectedRoute>} />
<Route path="/dashboard" element={<ProtectedRoute><Dashboard /></ProtectedRoute>} />
<Route path="/instance-monitoring" element={<ProtectedRoute><InstanceMonitoring /></ProtectedRoute>} />
<Route path="/server-detail/:serverId" element={<ProtectedRoute><ServerDetail /></ProtectedRoute>} />
<Route path="/container-monitoring" element={<ProtectedRoute><ContainerMonitoring /></ProtectedRoute>} />
<Route path="/container-monitoring/:serverId" element={<ProtectedRoute><ContainerMonitoring /></ProtectedRoute>} />
<Route path="/service/:id" element={<ProtectedRoute><ServiceDetail /></ProtectedRoute>} />
<Route path="/ssl-domain" element={<ProtectedRoute><SslDomain /></ProtectedRoute>} />
<Route path="/schedule-incident" element={<ProtectedRoute><ScheduleIncident /></ProtectedRoute>} />
<Route path="/operational-page" element={<ProtectedRoute><OperationalPage /></ProtectedRoute>} />
<Route path="/regional-monitoring" element={<ProtectedRoute><RegionalMonitoring /></ProtectedRoute>} />
<Route path="/settings" element={<ProtectedRoute><Settings /></ProtectedRoute>} />
<Route path="/profile" element={<ProtectedRoute><Profile /></ProtectedRoute>} />
<Route path="*" element={<NotFound />} />
</Routes>
</QueryClientProvider>
</SidebarProvider>
</LanguageProvider>
</ThemeProvider>
</BrowserRouter>
);
}

Expand Down
16 changes: 16 additions & 0 deletions application/src/components/auth/ProtectedRoute.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@

import { ReactNode } from 'react';
import { Navigate } from 'react-router-dom';
import { authService } from '@/services/authService';

interface ProtectedRouteProps {
children: ReactNode;
}

export const ProtectedRoute = ({ children }: ProtectedRouteProps) => {
if (!authService.isAuthenticated()) {
return <Navigate to="/login" replace />;
}

return <>{children}</>;
};
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@

import { Globe, Boxes, Radar, Calendar, BarChart2, LineChart, MapPin, Settings, User, Bell, Database, Info, BookOpen } from "lucide-react";

export const mainMenuItems = [
Expand All @@ -11,11 +12,11 @@ export const mainMenuItems = [
},
{
id: 'instance-monitoring',
path: null,
path: '/instance-monitoring',
icon: Boxes,
translationKey: 'instanceMonitoring',
color: 'text-blue-400',
hasNavigation: false
hasNavigation: true
},
{
id: 'ssl-domain',
Expand Down
97 changes: 97 additions & 0 deletions application/src/components/docker/DockerContainersTable.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@

import { useState } from "react";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Table, TableBody } from "@/components/ui/table";
import { DockerContainer } from "@/types/docker.types";
import { DockerMetricsDialog } from "./DockerMetricsDialog";
import {
DockerTableSearch,
DockerTableHeader,
DockerTableRow,
DockerEmptyState
} from "./table";

interface DockerContainersTableProps {
containers: DockerContainer[];
isLoading: boolean;
onRefresh: () => void;
}

export const DockerContainersTable = ({ containers, isLoading, onRefresh }: DockerContainersTableProps) => {
const [searchTerm, setSearchTerm] = useState("");
const [selectedContainer, setSelectedContainer] = useState<DockerContainer | null>(null);
const [metricsDialogOpen, setMetricsDialogOpen] = useState(false);

const filteredContainers = containers.filter(container =>
container.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
container.docker_id.toLowerCase().includes(searchTerm.toLowerCase()) ||
container.hostname.toLowerCase().includes(searchTerm.toLowerCase())
);

const handleContainerAction = (action: string, containerId: string, containerName: string) => {
console.log(`${action} action for container ${containerName} (${containerId})`);
// TODO: Implement container actions
};

const handleRowClick = (container: DockerContainer) => {
setSelectedContainer(container);
setMetricsDialogOpen(true);
};

const handleViewMetrics = (container: DockerContainer) => {
setSelectedContainer(container);
setMetricsDialogOpen(true);
};

return (
<>
<Card className="w-full bg-transparent border-0 shadow-none">
<CardHeader className="pb-4 px-0">
<div className="flex flex-col gap-4">
<div className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4">
<CardTitle className="text-lg sm:text-xl font-semibold">Docker Containers</CardTitle>
<DockerTableSearch
searchTerm={searchTerm}
onSearchChange={setSearchTerm}
onRefresh={onRefresh}
isLoading={isLoading}
/>
</div>
</div>
</CardHeader>
<CardContent className="p-0">
<div className="overflow-x-auto">
<div className="min-w-full inline-block align-middle">
<div className="overflow-hidden border border-border rounded-lg shadow-sm bg-card">
<Table>
<DockerTableHeader />
<TableBody>
{filteredContainers.length === 0 ? (
<DockerEmptyState searchTerm={searchTerm} />
) : (
filteredContainers.map((container) => (
<DockerTableRow
key={container.id}
container={container}
onRowClick={handleRowClick}
onContainerAction={handleContainerAction}
onViewMetrics={handleViewMetrics}
/>
))
)}
</TableBody>
</Table>
</div>
</div>
</div>
</CardContent>
</Card>

<DockerMetricsDialog
container={selectedContainer}
open={metricsDialogOpen}
onOpenChange={setMetricsDialogOpen}
/>
</>
);
};
Loading