From f2c5d542e896dec2f04bfa8285ee75ce8b3f3895 Mon Sep 17 00:00:00 2001 From: Vikas Date: Tue, 21 Apr 2026 14:27:14 +0530 Subject: [PATCH] Add maintenance notification UI implementation Signed-off-by: Vikas --- .secrets.baseline | 4 +- web/src/components/App.jsx | 12 +- web/src/components/Header.jsx | 1 + web/src/components/MaintenanceConfig.jsx | 234 +++++++++ web/src/components/MaintenanceManager.jsx | 484 ++++++++++++++++++ .../components/MaintenanceNotification.jsx | 107 ++++ web/src/services/request.js | 76 +++ web/src/styles/maintenance-config.scss | 97 ++++ web/src/styles/maintenance-manager.scss | 68 +++ web/src/styles/maintenance-notification.scss | 16 + 10 files changed, 1096 insertions(+), 3 deletions(-) create mode 100644 web/src/components/MaintenanceConfig.jsx create mode 100644 web/src/components/MaintenanceManager.jsx create mode 100644 web/src/components/MaintenanceNotification.jsx create mode 100644 web/src/styles/maintenance-config.scss create mode 100644 web/src/styles/maintenance-manager.scss create mode 100644 web/src/styles/maintenance-notification.scss diff --git a/.secrets.baseline b/.secrets.baseline index 88c34c81..a3c6ce6e 100644 --- a/.secrets.baseline +++ b/.secrets.baseline @@ -3,7 +3,7 @@ "files": "go.mod|go.sum|^.secrets.baseline$", "lines": null }, - "generated_at": "2025-11-27T10:58:34Z", + "generated_at": "2026-04-17T11:13:23Z", "plugins_used": [ { "name": "AWSKeyDetector" @@ -340,7 +340,7 @@ "hashed_secret": "6947818ac409551f11fbaa78f0ea6391960aa5b8", "is_secret": false, "is_verified": false, - "line_number": 115, + "line_number": 113, "type": "Secret Keyword", "verified_result": null } diff --git a/web/src/components/App.jsx b/web/src/components/App.jsx index 5ed76415..0fc61a71 100644 --- a/web/src/components/App.jsx +++ b/web/src/components/App.jsx @@ -20,6 +20,8 @@ import Events from "./Events"; import Keys from "./Keys"; import { Theme } from "@carbon/react"; import Feedbacks from "./Feedbacks"; +import MaintenanceNotification from "./MaintenanceNotification"; +import MaintenanceManager from "./MaintenanceManager"; const RouterClass = React.memo(({ isAdmin }) => { return ( @@ -84,6 +86,12 @@ const RouterClass = React.memo(({ isAdmin }) => { element={} /> )} + {isAdmin && ( + } + /> + )} ); }); @@ -111,6 +119,7 @@ const App = () => { "/events", "/keys", "/feedbacks", + "/maintenance", ].includes(window.location.pathname) ) { window.location.href = "/login"; @@ -118,7 +127,8 @@ const App = () => { } return ( - {auth === true && } + {auth && } + {auth && }
diff --git a/web/src/components/Header.jsx b/web/src/components/Header.jsx index 63314461..06401854 100644 --- a/web/src/components/Header.jsx +++ b/web/src/components/Header.jsx @@ -178,6 +178,7 @@ const HeaderNav = ({ onSideNavToggle }) => { {isAdmin && } {isAdmin && } {isAdmin && } + {isAdmin && } diff --git a/web/src/components/MaintenanceConfig.jsx b/web/src/components/MaintenanceConfig.jsx new file mode 100644 index 00000000..51edecc2 --- /dev/null +++ b/web/src/components/MaintenanceConfig.jsx @@ -0,0 +1,234 @@ +import React, { useState, useEffect } from "react"; +import { + Toggle, + TextArea, + Button, + InlineNotification, + Loading, + Form, + Stack, +} from "@carbon/react"; +import { getMaintenanceStatus, updateMaintenanceConfig } from "../services/request"; +import "../styles/maintenance-config.scss"; + +const MaintenanceConfig = () => { + const [enabled, setEnabled] = useState(false); + const [startDate, setStartDate] = useState(""); + const [endDate, setEndDate] = useState(""); + const [message, setMessage] = useState(""); + const [loading, setLoading] = useState(true); + const [saving, setSaving] = useState(false); + const [notification, setNotification] = useState(null); + + // Helper function to format UTC date for datetime-local input + const formatDateForInput = (isoString) => { + if (!isoString) return ""; + // datetime-local expects format: YYYY-MM-DDTHH:mm + // We keep it in UTC by using the ISO string directly + return isoString.slice(0, 16); + }; + + // Helper function to convert datetime-local value to UTC ISO string + const convertToUTC = (dateTimeLocal) => { + if (!dateTimeLocal) return null; + // Treat the input as UTC time + return new Date(dateTimeLocal + ':00Z').toISOString(); + }; + + // Fetch current configuration + useEffect(() => { + fetchConfig(); + }, []); + + const fetchConfig = async () => { + setLoading(true); + try { + const response = await getMaintenanceStatus(); + if (response.type === "GET_MAINTENANCE_STATUS" && response.payload) { + const config = response.payload; + setEnabled(config.enabled || false); + setStartDate(config.start_time ? formatDateForInput(config.start_time) : ""); + setEndDate(config.end_time ? formatDateForInput(config.end_time) : ""); + setMessage(config.message || ""); + } + } catch (error) { + console.error("Error fetching maintenance config:", error); + showNotification("error", "Failed to load maintenance configuration"); + } finally { + setLoading(false); + } + }; + + const showNotification = (kind, subtitle) => { + setNotification({ kind, subtitle }); + setTimeout(() => setNotification(null), 5000); + }; + + const handleSave = async () => { + // Validation + if (enabled) { + if (!startDate || !endDate) { + showNotification("error", "Start time and end time are required when maintenance is enabled"); + return; + } + if (new Date(convertToUTC(endDate)) <= new Date(convertToUTC(startDate))) { + showNotification("error", "End time must be after start time"); + return; + } + if (!message.trim()) { + showNotification("error", "Message is required when maintenance is enabled"); + return; + } + } + + setSaving(true); + try { + const payload = { + enabled, + start_time: enabled ? convertToUTC(startDate) : null, + end_time: enabled ? convertToUTC(endDate) : null, + message: enabled ? message.trim() : "", + }; + + console.log("Sending payload:", payload); + + const response = await updateMaintenanceConfig(payload); + + if (response.type === "UPDATE_MAINTENANCE_CONFIG") { + const successMsg = enabled + ? "Successfully updated maintenance notification" + : "Successfully disabled maintenance notification"; + showNotification("success", successMsg); + // Refresh the config to show updated values + await fetchConfig(); + } else if (response.type === "API_ERROR") { + const errorMsg = response.payload?.response?.data?.error || response.payload?.message || "Failed to update maintenance configuration"; + showNotification("error", errorMsg); + } else { + showNotification("error", "Failed to update maintenance configuration"); + } + } catch (error) { + console.error("Error updating maintenance config:", error); + const errorMsg = error.response?.data?.error || error.message || "Failed to update maintenance configuration"; + showNotification("error", errorMsg); + } finally { + setSaving(false); + } + }; + + if (loading) { + return ( +
+ +
+ ); + } + + return ( +
+

Maintenance Notification Configuration

+

+ Configure maintenance notifications that will be displayed to all users. + Notifications will appear 24 hours before the maintenance start time and remain visible until the end time. + All times are in UTC timezone. +

+ + {notification && ( + setNotification(null)} + lowContrast + /> + )} + +
+ + setEnabled(checked)} + /> + + {enabled && ( + <> +
+ + setStartDate(e.target.value)} + onBlur={(e) => setStartDate(e.target.value)} + step="60" + min={new Date().toISOString().slice(0, 16)} + /> +
+ Format: YYYY-MM-DDTHH:mm (e.g., {new Date().getFullYear()}-04-15T10:00) - UTC timezone +
+
+ +
+ + setEndDate(e.target.value)} + onBlur={(e) => setEndDate(e.target.value)} + step="60" + min={new Date().toISOString().slice(0, 16)} + /> +
+ Format: YYYY-MM-DDTHH:mm (e.g., {new Date().getFullYear()}-04-15T14:00) - UTC timezone +
+
+ +