From c280ab305a4768f8c20b0757cd5dcc1dee1fd29a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 4 Jul 2025 08:25:00 +0000 Subject: [PATCH 1/6] Initial plan From b705f7101f6f8cf2f71385c666a06a0621a36759 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 4 Jul 2025 08:35:00 +0000 Subject: [PATCH 2/6] Add maintenance windows feature to status page Co-authored-by: ben-z <5977478+ben-z@users.noreply.github.com> --- MAINTENANCE_WINDOWS.md | 83 +++++++++++++++++ src/App.tsx | 6 ++ src/constants.ts | 25 ++++- src/maintenance.tsx | 206 +++++++++++++++++++++++++++++++++++++++++ src/utils.ts | 26 ++++++ 5 files changed, 345 insertions(+), 1 deletion(-) create mode 100644 MAINTENANCE_WINDOWS.md create mode 100644 src/maintenance.tsx diff --git a/MAINTENANCE_WINDOWS.md b/MAINTENANCE_WINDOWS.md new file mode 100644 index 0000000..e2b31cf --- /dev/null +++ b/MAINTENANCE_WINDOWS.md @@ -0,0 +1,83 @@ +# Maintenance Windows Feature + +This document describes the maintenance windows feature added to the WATcloud status page. + +## Overview + +The maintenance windows feature displays scheduled maintenance, ongoing maintenance, and completed maintenance windows on the status page. This helps users understand when planned outages or service interruptions might occur. + +## Components + +### MaintenanceWindows Component (`src/maintenance.tsx`) + +The main component that displays maintenance window information with: + +- **Visual Status Indicators**: Different emoji icons for each status type +- **Status-based Styling**: Color-coded backgrounds and text +- **Responsive Design**: Works on desktop and mobile devices +- **Dark Mode Support**: Adapts to the theme selection +- **Time Calculations**: Shows relative time ("starts in", "ends in") +- **Sorting**: Prioritizes ongoing maintenance, then upcoming, then completed + +### Types and Constants (`src/constants.ts`) + +Added the following types and enums: + +- `MaintenanceStatus`: Enum for upcoming, ongoing, completed +- `MaintenanceWindow`: Interface defining the data structure +- `MAINTENANCE_SYMBOLS`: Emoji symbols for each status + +### Utility Functions (`src/utils.ts`) + +Added `timeUntil()` function to calculate time until a future date. + +## Data Structure + +Each maintenance window contains: + +```typescript +interface MaintenanceWindow { + id: string; // Unique identifier + title: string; // Display title + description: string; // Detailed description + startTime: Date; // When maintenance starts + endTime: Date; // When maintenance ends + status: MaintenanceStatus; // Current status (calculated) + affectedServices?: string[]; // Optional list of affected services + detailsUrl?: string; // Optional link to more details +} +``` + +## Features + +1. **Automatic Status Detection**: Status is calculated based on current time vs. start/end times +2. **Smart Sorting**: Ongoing maintenance appears first, then upcoming, then completed +3. **Responsive Design**: Adapts to different screen sizes +4. **Theme Support**: Works with light, dark, and auto themes +5. **Empty State**: Shows appropriate message when no maintenance is scheduled +6. **External Links**: Optional "More Details" links to announcements +7. **Time Display**: Shows both absolute times and relative times +8. **Service Information**: Lists affected services when available + +## Usage + +The component is automatically included in the main status page. To modify maintenance windows: + +1. **For Demo/Testing**: Update the `SAMPLE_MAINTENANCE_WINDOWS` array in `src/maintenance.tsx` +2. **For Production**: Replace the sample data with an API call or external data source + +## Integration Points + +- Integrated into `App.tsx` between the Options and Healthchecks.io sections +- Uses existing utility functions and styling patterns +- Follows the same design language as other status sections + +## Future Enhancements + +The current implementation uses static sample data. Future enhancements could include: + +- Integration with a calendar service (Google Calendar, Outlook, etc.) +- API endpoint for dynamic maintenance window management +- Admin interface for adding/editing maintenance windows +- Email notifications for upcoming maintenance +- Integration with existing monitoring tools \ No newline at end of file diff --git a/src/App.tsx b/src/App.tsx index ad415cb..82a5088 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -4,6 +4,7 @@ import { HealthchecksioStatus } from './healthchecksio' import { useState } from 'react' import { SentryStatus } from './sentry' import { OptionGroup } from './option-group' +import { MaintenanceWindows } from './maintenance' function updateQueryParams(key: string, val: string, queryParams: URLSearchParams) { queryParams.set(key, val); @@ -101,6 +102,11 @@ function App() { +
+

Scheduled Maintenance

+

Planned maintenance windows and outages

+ +

Healthchecks.io

Monitoring data from healthchecks.io

diff --git a/src/constants.ts b/src/constants.ts index 1f03917..df49027 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -29,4 +29,27 @@ export const STATUS_SYMBOLS = { [Status.Good]: '🟢', [Status.Neutral]: '🟡', [Status.Bad]: '🔴', -} as const; \ No newline at end of file +} as const; + +export enum MaintenanceStatus { + Upcoming = 'upcoming', + Ongoing = 'ongoing', + Completed = 'completed', +} + +export const MAINTENANCE_SYMBOLS = { + [MaintenanceStatus.Upcoming]: '🔔', + [MaintenanceStatus.Ongoing]: '🔧', + [MaintenanceStatus.Completed]: '✅', +} as const; + +export interface MaintenanceWindow { + id: string; + title: string; + description: string; + startTime: Date; + endTime: Date; + status: MaintenanceStatus; + affectedServices?: string[]; + detailsUrl?: string; +} \ No newline at end of file diff --git a/src/maintenance.tsx b/src/maintenance.tsx new file mode 100644 index 0000000..a4eafed --- /dev/null +++ b/src/maintenance.tsx @@ -0,0 +1,206 @@ +import { MaintenanceWindow, MaintenanceStatus, MAINTENANCE_SYMBOLS } from "./constants"; +import { timeUntil, cn } from "./utils"; + +// Sample maintenance windows data - in a real implementation, this could come from an API +const SAMPLE_MAINTENANCE_WINDOWS: MaintenanceWindow[] = [ + { + id: "maint-001", + title: "Scheduled Network Maintenance", + description: "Upgrading network infrastructure in the compute cluster. Some services may experience brief interruptions.", + startTime: new Date(Date.now() + 2 * 24 * 60 * 60 * 1000), // 2 days from now + endTime: new Date(Date.now() + 2 * 24 * 60 * 60 * 1000 + 4 * 60 * 60 * 1000), // 4 hours later + status: MaintenanceStatus.Upcoming, + affectedServices: ["Compute Cluster", "File Storage"], + detailsUrl: "https://cloud.watonomous.ca/docs/compute-cluster/announcements" + }, + { + id: "maint-002", + title: "Database Optimization", + description: "Performing routine database maintenance and optimization tasks.", + startTime: new Date(Date.now() - 2 * 60 * 60 * 1000), // 2 hours ago + endTime: new Date(Date.now() + 1 * 60 * 60 * 1000), // 1 hour from now + status: MaintenanceStatus.Ongoing, + affectedServices: ["Web Dashboard", "API"], + } +]; + +function getMaintenanceStatus(window: MaintenanceWindow): MaintenanceStatus { + const now = new Date(); + if (now < window.startTime) { + return MaintenanceStatus.Upcoming; + } else if (now > window.endTime) { + return MaintenanceStatus.Completed; + } else { + return MaintenanceStatus.Ongoing; + } +} + +function getStatusClassName(status: MaintenanceStatus): string { + switch (status) { + case MaintenanceStatus.Upcoming: + return "border-blue-200 bg-blue-50 dark:border-blue-800 dark:bg-blue-950"; + case MaintenanceStatus.Ongoing: + return "border-amber-200 bg-amber-50 dark:border-amber-800 dark:bg-amber-950"; + case MaintenanceStatus.Completed: + return "border-green-200 bg-green-50 dark:border-green-800 dark:bg-green-950"; + default: + return "border-gray-200 bg-gray-50 dark:border-gray-700 dark:bg-gray-900"; + } +} + +function getStatusTextClassName(status: MaintenanceStatus): string { + switch (status) { + case MaintenanceStatus.Upcoming: + return "text-blue-800 dark:text-blue-200"; + case MaintenanceStatus.Ongoing: + return "text-amber-800 dark:text-amber-200"; + case MaintenanceStatus.Completed: + return "text-green-800 dark:text-green-200"; + default: + return "text-gray-800 dark:text-gray-200"; + } +} + +function formatDateTime(date: Date): string { + return date.toLocaleString(undefined, { + year: 'numeric', + month: 'short', + day: 'numeric', + hour: '2-digit', + minute: '2-digit', + timeZoneName: 'short' + }); +} + +function formatDuration(startTime: Date, endTime: Date): string { + const durationMs = endTime.getTime() - startTime.getTime(); + const hours = Math.floor(durationMs / (1000 * 60 * 60)); + const minutes = Math.floor((durationMs % (1000 * 60 * 60)) / (1000 * 60)); + + if (hours > 0) { + return minutes > 0 ? `${hours}h ${minutes}m` : `${hours}h`; + } + return `${minutes}m`; +} + +interface MaintenanceWindowsProps { + showCompleted?: boolean; +} + +export function MaintenanceWindows({ showCompleted = false }: MaintenanceWindowsProps) { + // Filter maintenance windows based on preferences and update their status + const maintenanceWindows = SAMPLE_MAINTENANCE_WINDOWS + .map(window => ({ + ...window, + status: getMaintenanceStatus(window) + })) + .filter(window => showCompleted || window.status !== MaintenanceStatus.Completed) + .sort((a, b) => { + // Sort by status priority (ongoing first, then upcoming, then completed) + const statusPriority = { + [MaintenanceStatus.Ongoing]: 0, + [MaintenanceStatus.Upcoming]: 1, + [MaintenanceStatus.Completed]: 2 + }; + + if (statusPriority[a.status] !== statusPriority[b.status]) { + return statusPriority[a.status] - statusPriority[b.status]; + } + + // Within same status, sort by start time + return a.startTime.getTime() - b.startTime.getTime(); + }); + + if (maintenanceWindows.length === 0) { + return ( +
+

+ No scheduled maintenance windows at this time. +

+
+ ); + } + + return ( +
+
+ {maintenanceWindows.map((window) => ( +
+
+
+
+ + {MAINTENANCE_SYMBOLS[window.status]} + +

{window.title}

+ + {window.status} + +
+ +

+ {window.description} +

+ +
+
+
+ Start: {formatDateTime(window.startTime)} +
+
+ End: {formatDateTime(window.endTime)} +
+
+ Duration: {formatDuration(window.startTime, window.endTime)} +
+
+ + {window.status === MaintenanceStatus.Upcoming && ( +
+ Starts in: {timeUntil(window.startTime)} +
+ )} + + {window.status === MaintenanceStatus.Ongoing && ( +
+ Ends in: {timeUntil(window.endTime)} +
+ )} + + {window.affectedServices && window.affectedServices.length > 0 && ( +
+ Affected Services:{" "} + {window.affectedServices.join(", ")} +
+ )} +
+
+ + {window.detailsUrl && ( + + )} +
+
+ ))} +
+
+ ); +} \ No newline at end of file diff --git a/src/utils.ts b/src/utils.ts index 9570218..9950982 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -98,6 +98,32 @@ export function timeSince(date: Date): string { return v + ' day' + (v === 1 ? '' : 's'); } +export function timeUntil(date: Date): string { + let v: number = Math.floor((date.getTime() - new Date().getTime()) / 1000); + + if (v <= 0) { + return 'now'; + } + + if (v < 60) { + // v is seconds + return v + ' second' + (v === 1 ? '' : 's'); + } + + v = Math.floor(v / 60); // v is now minutes + if (v < 60) { + return v + ' minute' + (v === 1 ? '' : 's'); + } + + v = Math.floor(v / 60); // v is now hours + if (v < 24) { + return v + ' hour' + (v === 1 ? '' : 's'); + } + + v = Math.floor(v / 24); // v is now days + return v + ' day' + (v === 1 ? '' : 's'); +} + export function timeSinceShort(date: Date): string { const seconds = Math.floor((new Date().getTime() - date.getTime()) / 1000); const units: [number, string][] = [ From fe06d9efb6d9d1f86fda41cbacdc8776930c0693 Mon Sep 17 00:00:00 2001 From: Alex Boden Date: Fri, 4 Jul 2025 18:15:17 +0000 Subject: [PATCH 3/6] Refactor layout and styles for improved UI; enhance maintenance windows display --- src/App.css | 16 ++++--- src/App.tsx | 95 ++++++++++++++++++++++++---------------- src/maintenance.tsx | 104 ++++++++++++++++++++++++++++---------------- 3 files changed, 134 insertions(+), 81 deletions(-) diff --git a/src/App.css b/src/App.css index 4e33d7b..68a52fe 100644 --- a/src/App.css +++ b/src/App.css @@ -1,11 +1,12 @@ #root { margin: 0 auto; padding: 2rem; - text-align: center; + text-align: left; + min-height: 100vh; } .main-logo > svg { - height: 5rem; + height: 6rem; mask-image: linear-gradient( 60deg, black 25%, @@ -14,6 +15,7 @@ ); mask-size: 400%; mask-position: 0%; + transition: mask-position 1s ease; } .main-logo > svg:hover { mask-position: 100%; @@ -23,8 +25,10 @@ } .main-logo { - @apply text-inherit; - &:hover { - @apply text-inherit; - } + color: inherit; + display: inline-block; +} + +.main-logo:hover { + color: inherit; } \ No newline at end of file diff --git a/src/App.tsx b/src/App.tsx index 82a5088..97c0af3 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -61,63 +61,84 @@ function App() { } return ( - <> -
-
+
+
+
-

Infrastructure Status

-

An overview of the health of WATcloud

+

Infrastructure Status

+

An overview of the health of WATcloud

-
-
-

Quick Links

-
    -
  • Support Resources
  • -
  • Announcements
  • -
  • Documentation
  • -
  • Legacy Status Page
  • + +
    + -
    -

    Options

    -
    - Theme: +
    +

    Options

    +
    + Theme: +
    + setShowInternal(!showInternal)} className="mr-2" /> + +
    - - setShowInternal(!showInternal)} /> - -
    -
    -

    Scheduled Maintenance

    -

    Planned maintenance windows and outages

    - -
    -
    -

    Healthchecks.io

    -

    Monitoring data from healthchecks.io

    - -
    -
    -

    Sentry

    -

    Monitoring data from watonomous.sentry.io

    - + +
    +
    +
    + + + +
    +

    Scheduled Maintenance

    +

    Planned maintenance windows and outages

    + +
    + +
    +
    + + + +
    +

    Healthchecks.io

    +

    Monitoring data from healthchecks.io

    + +
    + +
    +
    + + + +
    +

    Sentry

    +

    Monitoring data from watonomous.sentry.io

    + +
    - +
    ) } diff --git a/src/maintenance.tsx b/src/maintenance.tsx index a4eafed..7c53543 100644 --- a/src/maintenance.tsx +++ b/src/maintenance.tsx @@ -114,86 +114,114 @@ export function MaintenanceWindows({ showCompleted = false }: MaintenanceWindows if (maintenanceWindows.length === 0) { return (
    -

    - No scheduled maintenance windows at this time. -

    +
    +
    + + + +
    +

    + No scheduled maintenance windows at this time. +

    +

    + All systems are operating normally. +

    +
    ); } return (
    -
    +
    {maintenanceWindows.map((window) => (
    -
    - +
    + {MAINTENANCE_SYMBOLS[window.status]} -

    {window.title}

    - - {window.status} - +
    +

    {window.title}

    + + {window.status} + +
    -

    +

    {window.description}

    -
    -
    -
    - Start: {formatDateTime(window.startTime)} +
    +
    +
    + Start Time + {formatDateTime(window.startTime)}
    -
    - End: {formatDateTime(window.endTime)} +
    + End Time + {formatDateTime(window.endTime)}
    -
    - Duration: {formatDuration(window.startTime, window.endTime)} +
    + Duration + {formatDuration(window.startTime, window.endTime)}
    - {window.status === MaintenanceStatus.Upcoming && ( -
    - Starts in: {timeUntil(window.startTime)} -
    - )} - - {window.status === MaintenanceStatus.Ongoing && ( -
    - Ends in: {timeUntil(window.endTime)} + {(window.status === MaintenanceStatus.Upcoming || window.status === MaintenanceStatus.Ongoing) && ( +
    + + {window.status === MaintenanceStatus.Upcoming ? "Starts in: " : "Ends in: "} + + + {window.status === MaintenanceStatus.Upcoming + ? timeUntil(window.startTime) + : timeUntil(window.endTime)} +
    )} {window.affectedServices && window.affectedServices.length > 0 && ( -
    - Affected Services:{" "} - {window.affectedServices.join(", ")} +
    + Affected Services +
    + {window.affectedServices.map((service, index) => ( + + {service} + + ))} +
    )}
    {window.detailsUrl && ( -
    + )} From 6f8d67adbce084f7b06aff0f5faf40e6b22ac42d Mon Sep 17 00:00:00 2001 From: Alex Boden Date: Fri, 4 Jul 2025 18:30:16 +0000 Subject: [PATCH 4/6] Refactor maintenance windows display for improved readability and layout --- src/App.tsx | 29 ++------- src/maintenance.tsx | 153 +++++++++++++++++--------------------------- 2 files changed, 67 insertions(+), 115 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index 97c0af3..28e33c3 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -104,37 +104,22 @@ function App() {
    -
    +
    -
    - - - -
    -

    Scheduled Maintenance

    -

    Planned maintenance windows and outages

    +

    Scheduled Maintenance

    +

    Planned maintenance windows and outages

    -
    - - - -
    -

    Healthchecks.io

    -

    Monitoring data from healthchecks.io

    +

    Healthchecks.io

    +

    Monitoring data from healthchecks.io

    -
    - - - -
    -

    Sentry

    -

    Monitoring data from watonomous.sentry.io

    +

    Sentry

    +

    Monitoring data from watonomous.sentry.io

    diff --git a/src/maintenance.tsx b/src/maintenance.tsx index 7c53543..ce8868c 100644 --- a/src/maintenance.tsx +++ b/src/maintenance.tsx @@ -114,17 +114,9 @@ export function MaintenanceWindows({ showCompleted = false }: MaintenanceWindows if (maintenanceWindows.length === 0) { return (
    -
    -
    - - - -
    -

    - No scheduled maintenance windows at this time. -

    -

    - All systems are operating normally. +

    +

    + No scheduled maintenance windows at this time. All systems are operating normally.

    @@ -133,99 +125,74 @@ export function MaintenanceWindows({ showCompleted = false }: MaintenanceWindows return (
    -
    +
    {maintenanceWindows.map((window) => (
    -
    -
    -
    - - {MAINTENANCE_SYMBOLS[window.status]} - -
    -

    {window.title}

    - - {window.status} - -
    -
    - -

    - {window.description} -

    - -
    -
    -
    - Start Time - {formatDateTime(window.startTime)} -
    -
    - End Time - {formatDateTime(window.endTime)} -
    -
    - Duration - {formatDuration(window.startTime, window.endTime)} -
    -
    - - {(window.status === MaintenanceStatus.Upcoming || window.status === MaintenanceStatus.Ongoing) && ( -
    - - {window.status === MaintenanceStatus.Upcoming ? "Starts in: " : "Ends in: "} - - - {window.status === MaintenanceStatus.Upcoming - ? timeUntil(window.startTime) - : timeUntil(window.endTime)} - -
    - )} - - {window.affectedServices && window.affectedServices.length > 0 && ( -
    - Affected Services -
    - {window.affectedServices.map((service, index) => ( - - {service} - - ))} -
    -
    - )} -
    +
    +
    + + {MAINTENANCE_SYMBOLS[window.status]} + +

    {window.title}

    + + {window.status} +
    - {window.detailsUrl && ( - + + Details + )}
    + +

    {window.description}

    + +
    + Start: {formatDateTime(window.startTime)} + End: {formatDateTime(window.endTime)} + Duration: {formatDuration(window.startTime, window.endTime)} +
    + + {(window.status === MaintenanceStatus.Upcoming || window.status === MaintenanceStatus.Ongoing) && ( +
    + + {window.status === MaintenanceStatus.Upcoming ? "Starts in: " : "Ends in: "} + + + {window.status === MaintenanceStatus.Upcoming + ? timeUntil(window.startTime) + : timeUntil(window.endTime)} + +
    + )} + + {window.affectedServices && window.affectedServices.length > 0 && ( +
    + Affected Services: + {window.affectedServices.map((service, index) => ( + + {service} + + ))} +
    + )}
    ))}
    From 9979215be2ec3f8523111c00b86a32cf95acb83c Mon Sep 17 00:00:00 2001 From: Alex Boden Date: Fri, 4 Jul 2025 18:35:36 +0000 Subject: [PATCH 5/6] Refactor Quick Links and Options layout for improved responsiveness and readability --- src/App.tsx | 45 +++++++++++++++++++++++++-------------------- 1 file changed, 25 insertions(+), 20 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index 28e33c3..9495b62 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -74,28 +74,33 @@ function App() {
    -
    -
    -

    Quick Links

    - +
    +
    +

    Quick Links

    +
    + Support + • + Announcements + • + Docs + • + Legacy +
    -
    -

    Options

    +
    +

    Options

    - Theme: - +
    + Theme: + +
    setShowInternal(!showInternal)} className="mr-2" /> From fd7c621a37acb2432644b4cf82adb0671b214591 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 4 Jul 2025 18:53:33 +0000 Subject: [PATCH 6/6] Restore original title size and layout structure for header and compact Quick Links/Options sections Co-authored-by: alexboden <43760105+alexboden@users.noreply.github.com> --- src/App.tsx | 80 +++++++++++++++++++++++++---------------------------- 1 file changed, 37 insertions(+), 43 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index 9495b62..37a19c8 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -61,51 +61,45 @@ function App() { } return ( -
    -
    -
    + <> +
    +
    -

    Infrastructure Status

    -

    An overview of the health of WATcloud

    +

    Infrastructure Status

    +

    An overview of the health of WATcloud

    - -
    -
    -

    Quick Links

    -
    - Support - • - Announcements - • - Docs - • - Legacy -
    +
    + -
    -

    Options

    -
    -
    - Theme: - -
    -
    - setShowInternal(!showInternal)} className="mr-2" /> - -
    +
    +

    Options

    +
    + Theme: +
    + + setShowInternal(!showInternal)} /> + +
    @@ -116,19 +110,19 @@ function App() {
    -
    -

    Healthchecks.io

    -

    Monitoring data from healthchecks.io

    +
    +

    Healthchecks.io

    +

    Monitoring data from healthchecks.io

    -
    -

    Sentry

    -

    Monitoring data from watonomous.sentry.io

    +
    +

    Sentry

    +

    Monitoring data from watonomous.sentry.io

    -
    + ) }