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.
+