From ba6dc41d95d6c927f700dcd62c6136950d42d0a4 Mon Sep 17 00:00:00 2001 From: Tola Leng Date: Fri, 6 Jun 2025 18:02:18 +0800 Subject: [PATCH 01/16] =?UTF-8?q?=F0=9F=93=9D=20Correct=20and=20standard?= =?UTF-8?q?=20version=20matches=20the=20official=20SPDX=20MIT=20License?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- LICENSE.md | 32 ++++++++++++++++++-------------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/LICENSE.md b/LICENSE.md index 134eb81..3d3d1dc 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -1,17 +1,21 @@ -The MIT License (MIT) -Copyright (c) 2025, Tola Leng +MIT License -Permission is hereby granted, free of charge, to any person obtaining a copy of this software -and associated documentation files (the "Software"), to deal in the Software without restriction, -including without limitation the rights to use, copy, modify, merge, publish, distribute, -sublicense, and/or sell copies of the Software, and to permit persons to whom the Software -is furnished to do so, subject to the following conditions: +Copyright (c) 2025 Tola Leng -The above copyright notice and this permission notice shall be included in all copies or -substantial portions of the Software. +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the “Software”), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING -BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, -DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. From ba47c1d953bf479f6ccaa84c7c2503273258d682 Mon Sep 17 00:00:00 2001 From: Tola Leng Date: Fri, 6 Jun 2025 18:43:14 +0800 Subject: [PATCH 02/16] feat: Implement Operational Page in menu --- application/src/components/dashboard/Sidebar.tsx | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/application/src/components/dashboard/Sidebar.tsx b/application/src/components/dashboard/Sidebar.tsx index 24b4c6d..f543936 100644 --- a/application/src/components/dashboard/Sidebar.tsx +++ b/application/src/components/dashboard/Sidebar.tsx @@ -1,4 +1,3 @@ - import { Globe, Boxes, Radar, Calendar, BarChart2, LineChart, FileText, Settings, User, UserCog, Bell, FileClock, Database, RefreshCw, Info, ChevronDown, BookOpen } from "lucide-react"; import { useTheme } from "@/contexts/ThemeContext"; import { Link, useLocation } from "react-router-dom"; @@ -71,10 +70,10 @@ export const Sidebar = ({ {!collapsed && {t("scheduleIncident")}} -
+ {!collapsed && {t("operationalPage")}} -
+
{!collapsed && {t("reports")}} From dd2c64420d1e2fc6c89ef46710d2063ea1a53952 Mon Sep 17 00:00:00 2001 From: Tola Leng Date: Fri, 6 Jun 2025 18:46:50 +0800 Subject: [PATCH 03/16] feat: Implement a modern and professional Operational Page --- .../operational-page/ComponentsSelector.tsx | 243 ++++++++++++ .../CreateOperationalPageDialog.tsx | 280 ++++++++++++++ .../EditOperationalPageDialog.tsx | 353 ++++++++++++++++++ .../operational-page/OperationalPageCard.tsx | 101 +++++ .../OperationalPageContent.tsx | 211 +++++++++++ .../operational-page/StatusBadge.tsx | 47 +++ 6 files changed, 1235 insertions(+) create mode 100644 application/src/components/operational-page/ComponentsSelector.tsx create mode 100644 application/src/components/operational-page/CreateOperationalPageDialog.tsx create mode 100644 application/src/components/operational-page/EditOperationalPageDialog.tsx create mode 100644 application/src/components/operational-page/OperationalPageCard.tsx create mode 100644 application/src/components/operational-page/OperationalPageContent.tsx create mode 100644 application/src/components/operational-page/StatusBadge.tsx diff --git a/application/src/components/operational-page/ComponentsSelector.tsx b/application/src/components/operational-page/ComponentsSelector.tsx new file mode 100644 index 0000000..e291a9f --- /dev/null +++ b/application/src/components/operational-page/ComponentsSelector.tsx @@ -0,0 +1,243 @@ +import { useState } from 'react'; +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'; +import { Button } from '@/components/ui/button'; +import { Input } from '@/components/ui/input'; +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 { StatusPageComponentRecord } from '@/types/statusPageComponents.types'; +import { useQuery } from '@tanstack/react-query'; +import { serviceService } from '@/services/serviceService'; + +interface ComponentsSelectorProps { + selectedComponents: Partial[]; + onComponentsChange: (components: Partial[]) => void; + 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({ + name: '', + description: '', + service_id: '', + server_id: '', + display_order: selectedComponents.length + 1, + }); + + // Fetch uptime services for the dropdown + const { data: services = [] } = useQuery({ + queryKey: ['services'], + queryFn: serviceService.getServices, + }); + + const addComponent = () => { + if (!newComponent.name.trim()) return; + + const component: Partial = { + ...newComponent, + operational_status_id: '', // Will be set when page is created + }; + + onComponentsChange([...selectedComponents, component]); + setNewComponent({ + name: '', + description: '', + service_id: '', + server_id: '', + display_order: selectedComponents.length + 2, + }); + setShowAddForm(false); + }; + + const removeComponent = async (index: number) => { + const component = selectedComponents[index]; + + // If component has an ID, it exists in database and needs to be deleted + if (component.id && onComponentDelete) { + await onComponentDelete(component.id); + } else { + // For new components not yet saved, just remove from local state + const updated = selectedComponents.filter((_, i) => i !== index); + onComponentsChange(updated); + } + }; + + return ( + + + + + Status Page Components + + + Add monitoring components like uptime services, SSL certificates, and incident tracking + + + + {selectedComponents.length > 0 && ( +
+ +
+ {selectedComponents.map((component, index) => ( +
+
+
{component.name}
+ {component.description && ( +
{component.description}
+ )} +
+ {component.service_id && ( + + Service: {services.find(s => s.id === component.service_id)?.name || component.service_id} + + )} + {component.server_id && ( + + Server: {component.server_id} + + )} +
+
+ +
+ ))} +
+
+ )} + + {!showAddForm ? ( + + ) : ( +
+
+
+ + setNewComponent({ ...newComponent, name: e.target.value })} + /> +
+
+ + setNewComponent({ ...newComponent, display_order: parseInt(e.target.value) || 1 })} + /> +
+
+ +
+ +