From c5632dae77f8f2a480f1dab82c023e2397a7ac8d Mon Sep 17 00:00:00 2001 From: Juergen Kunz Date: Sat, 27 Dec 2025 12:33:14 +0000 Subject: [PATCH] feat(admin-ui): introduce view layer and refactor admin UI to use view components, consolidate demos, and update interfaces --- changelog.md | 10 + package.json | 2 +- pnpm-lock.yaml | 19 +- ts_web/00_commitinfo_data.ts | 2 +- .../upladmin-statuspage-config.ts | 73 +---- ts_web/index.ts | 1 + ts_web/interfaces/index.ts | 139 +++++++-- ts_web/pages/adminpage-config.ts | 83 ----- ts_web/pages/adminpage-dashboard.ts | 156 ---------- ts_web/pages/adminpage-incidents.ts | 166 ---------- ts_web/pages/adminpage-monitors.ts | 213 ------------- ts_web/pages/index.ts | 6 +- ts_web/pages/upladmin-app/upladmin-app.ts | 35 ++- ts_web/views/index.ts | 4 + .../upladmin-config-view.ts | 124 ++++++++ .../upladmin-dashboard-view.ts | 60 ++++ .../upladmin-incidents-view.ts | 292 ++++++++++++++++++ .../upladmin-monitors-view.ts | 244 +++++++++++++++ 18 files changed, 875 insertions(+), 754 deletions(-) delete mode 100644 ts_web/pages/adminpage-config.ts delete mode 100644 ts_web/pages/adminpage-dashboard.ts delete mode 100644 ts_web/pages/adminpage-incidents.ts delete mode 100644 ts_web/pages/adminpage-monitors.ts create mode 100644 ts_web/views/index.ts create mode 100644 ts_web/views/upladmin-config-view/upladmin-config-view.ts create mode 100644 ts_web/views/upladmin-dashboard-view/upladmin-dashboard-view.ts create mode 100644 ts_web/views/upladmin-incidents-view/upladmin-incidents-view.ts create mode 100644 ts_web/views/upladmin-monitors-view/upladmin-monitors-view.ts diff --git a/changelog.md b/changelog.md index 6f94fd8..49fd2db 100644 --- a/changelog.md +++ b/changelog.md @@ -1,5 +1,15 @@ # Changelog +## 2025-12-27 - 1.3.0 - feat(admin-ui) +introduce view layer and refactor admin UI to use view components, consolidate demos, and update interfaces + +- Added a new views/ layer with view components: upladmin-dashboard-view, upladmin-monitors-view, upladmin-incidents-view, upladmin-config-view and exported them from ts_web/views/index.ts +- Refactored upladmin-app to use the new view components (updated menu routes/content to view tags) +- Removed multiple demo page files under ts_web/pages (consolidated demo surface into the view-based app) +- Updated upladmin-statuspage-config: changed activeSection to a property, removed the side navigation markup/CSS and simplified layout to be view-driven +- Reworked ts_web/interfaces to re-export core types from @uptime.link/interfaces and added UI-specific interfaces and form types +- Bumped dependency @uptime.link/interfaces to ^2.1.0 in package.json + ## 2025-12-26 - 1.2.0 - feat(elements) add upladmin-option-card component and migrate option/status UIs to use it; refactor monitor form multitoggle subscriptions and event handling; improve theme color handling and dark-mode styles; add demos, Playwright snapshots, and migration plan diff --git a/package.json b/package.json index a1bfb07..2d4cde1 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,7 @@ "@design.estate/dees-domtools": "^2.3.6", "@design.estate/dees-element": "^2.1.3", "@design.estate/dees-wcctools": "^3.2.0", - "@uptime.link/interfaces": "^2.0.21" + "@uptime.link/interfaces": "^2.1.0" }, "devDependencies": { "@git.zone/tsbuild": "^4.0.2", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9a2ed32..434fa2b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -21,8 +21,8 @@ importers: specifier: ^3.2.0 version: 3.2.0 '@uptime.link/interfaces': - specifier: ^2.0.21 - version: 2.0.21 + specifier: ^2.1.0 + version: 2.1.0 devDependencies: '@git.zone/tsbuild': specifier: ^4.0.2 @@ -65,9 +65,6 @@ packages: peerDependencies: '@push.rocks/smartserve': '>=1.1.0' - '@apiglobal/typedrequest-interfaces@2.0.1': - resolution: {integrity: sha512-Oi7pNU4vKo5UvcCJmqkH43Us237Ws/Pp/WDYnwnonRnTmIMd+6QjNfN/gXcPnP6tbamk8r8Xzcz9mgnSDM2ysw==} - '@aws-crypto/crc32@5.2.0': resolution: {integrity: sha512-nLbCWqQNgUiwwtFsen1AdzAtvuLRsQS8rYgMuxCrdKf9kOssamGLuPwyTY9wyYblNr9+1XM8v6zoDTPPSIeANg==} engines: {node: '>=16.0.0'} @@ -1512,8 +1509,8 @@ packages: '@ungap/structured-clone@1.3.0': resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==} - '@uptime.link/interfaces@2.0.21': - resolution: {integrity: sha512-sy7WBzHOxU7Kt0BGofK0R3CS8D8QtbTXB00i75QKYXkEesdLd91SbFL80wTupKfjzeldE0ejUVSgvtlZEr8XlQ==} + '@uptime.link/interfaces@2.1.0': + resolution: {integrity: sha512-KlsAUp4Vvb4TeFNBDq4MZ9v4RaeLPr1GRlj4mkNmGLSfEiSasj1FWGLEj8iRdfeUuxMu0WRpAvWe2Ol0MRzoqg==} '@webcontainer/api@1.2.0': resolution: {integrity: sha512-tzoKBd4lLdhHy5GHFpUkl+ndoSba8JqmB7x0ZQFnWfjbcbQOvKQfxA8MEMUYhgqjWHnbrWdAfnBEHz5f5lYG5A==} @@ -2976,8 +2973,6 @@ snapshots: '@push.rocks/smartstring': 4.1.0 '@push.rocks/smarturl': 3.1.0 - '@apiglobal/typedrequest-interfaces@2.0.1': {} - '@aws-crypto/crc32@5.2.0': dependencies: '@aws-crypto/util': 5.2.0 @@ -5254,10 +5249,10 @@ snapshots: '@ungap/structured-clone@1.3.0': {} - '@uptime.link/interfaces@2.0.21': + '@uptime.link/interfaces@2.1.0': dependencies: - '@apiglobal/typedrequest-interfaces': 2.0.1 - '@tsclass/tsclass': 4.4.4 + '@api.global/typedrequest-interfaces': 3.0.19 + '@tsclass/tsclass': 9.3.0 '@webcontainer/api@1.2.0': {} diff --git a/ts_web/00_commitinfo_data.ts b/ts_web/00_commitinfo_data.ts index e9b3e06..eccbd12 100644 --- a/ts_web/00_commitinfo_data.ts +++ b/ts_web/00_commitinfo_data.ts @@ -3,6 +3,6 @@ */ export const commitinfo = { name: '@uptime.link/statuspage-admin', - version: '1.2.0', + version: '1.3.0', description: 'Admin components for managing UptimeLink status pages, monitors, and incidents.' } diff --git a/ts_web/elements/upladmin-statuspage-config/upladmin-statuspage-config.ts b/ts_web/elements/upladmin-statuspage-config/upladmin-statuspage-config.ts index 5e24c85..99429ba 100644 --- a/ts_web/elements/upladmin-statuspage-config/upladmin-statuspage-config.ts +++ b/ts_web/elements/upladmin-statuspage-config/upladmin-statuspage-config.ts @@ -33,7 +33,7 @@ export class UpladminStatuspageConfig extends DeesElement { @state() accessor formData: IStatusPageConfig = {}; - @state() + @property({ type: String }) accessor activeSection: string = 'branding'; @state() @@ -49,67 +49,10 @@ export class UpladminStatuspageConfig extends DeesElement { } .config-container { - display: grid; - grid-template-columns: 220px 1fr; - gap: ${unsafeCSS(sharedStyles.spacing.lg)}; + display: block; min-height: 500px; } - @media (max-width: 768px) { - .config-container { - grid-template-columns: 1fr; - } - } - - .config-nav { - background: ${sharedStyles.colors.background.secondary}; - border: 1px solid ${sharedStyles.colors.border.default}; - border-radius: ${unsafeCSS(sharedStyles.borderRadius.lg)}; - padding: ${unsafeCSS(sharedStyles.spacing.sm)}; - height: fit-content; - } - - .nav-item { - display: flex; - align-items: center; - gap: 12px; - width: 100%; - padding: 14px 16px; - font-size: 14px; - font-weight: 500; - font-family: ${unsafeCSS(sharedStyles.fonts.base)}; - color: ${sharedStyles.colors.text.secondary}; - background: transparent; - border: none; - border-radius: ${unsafeCSS(sharedStyles.borderRadius.base)}; - cursor: pointer; - text-align: left; - transition: all ${unsafeCSS(sharedStyles.durations.fast)} ${unsafeCSS(sharedStyles.easings.default)}; - } - - .nav-item:hover { - background: ${sharedStyles.colors.background.muted}; - color: ${sharedStyles.colors.text.primary}; - } - - .nav-item.active { - background: ${sharedStyles.colors.accent.primary}; - color: white; - } - - .nav-item.active dees-icon { - --icon-color: white; - } - - .nav-item dees-icon { - --icon-color: ${sharedStyles.colors.text.muted}; - transition: color ${unsafeCSS(sharedStyles.durations.fast)} ${unsafeCSS(sharedStyles.easings.default)}; - } - - .nav-item:hover dees-icon { - --icon-color: ${sharedStyles.colors.text.primary}; - } - .config-content { background: ${sharedStyles.colors.background.secondary}; border: 1px solid ${sharedStyles.colors.border.default}; @@ -343,18 +286,6 @@ export class UpladminStatuspageConfig extends DeesElement { return html`
- -
diff --git a/ts_web/index.ts b/ts_web/index.ts index 649e341..dafa035 100644 --- a/ts_web/index.ts +++ b/ts_web/index.ts @@ -1,4 +1,5 @@ export * from './elements/index.js'; +export * from './views/index.js'; export * from './pages/index.js'; export * from './interfaces/index.js'; export * from './services/index.js'; diff --git a/ts_web/interfaces/index.ts b/ts_web/interfaces/index.ts index 21e0e40..f11f158 100644 --- a/ts_web/interfaces/index.ts +++ b/ts_web/interfaces/index.ts @@ -1,19 +1,63 @@ -// Re-export interfaces from the public catalog for consistency +// ============================================ +// Re-export shared types from @uptime.link/interfaces +// ============================================ -// Status types -export type TStatusType = 'operational' | 'degraded' | 'partial_outage' | 'major_outage' | 'maintenance' | 'initializing' | 'error' | 'paused'; -export type TCheckType = 'assumption' | 'function' | 'pwa' | 'pagerank'; -export type TStatusMode = 'auto' | 'manual'; +import { data } from '@uptime.link/interfaces'; -// Check configuration interface +// Re-export core types +export type TStatusType = data.TStatusType; +export type TCheckType = data.TCheckType; +export type TStatusMode = data.TStatusMode; +export type TIncidentSeverity = data.TIncidentSeverity; +export type TIncidentStatus = data.TIncidentStatus; +export type TCheckResultStatus = data.TCheckResultStatus; +export type TCheckLastResult = data.TCheckLastResult; + +// Re-export check configuration interfaces +export type ICheckBase = data.ICheckBase; +export type IAssumptionCheckConfig = data.IAssumptionCheckConfig; +export type IFunctionCheckConfig = data.IFunctionCheckConfig; +export type IPwaCheckConfig = data.IPwaCheckConfig; +export type IPageRankCheckConfig = data.IPageRankCheckConfig; +export type TCheckConfig = data.TCheckConfig; + +// Re-export check execution interfaces +export type IAssumptionCheck = data.IAssumptionCheck; +export type IFunctionCheck = data.IFunctionCheck; +export type IPwaCheck = data.IPwaCheck; +export type IPageRankCheck = data.IPageRankCheck; +export type ICheckCollection = data.ICheckCollection; + +// Re-export incident interfaces +export type IIncident = data.IIncident; +export type IIncidentUpdateBase = data.IIncidentUpdate; + +// Re-export status page config +export type IServiceGroup = data.IServiceGroup; + +// ============================================ +// Extended/UI-specific Interfaces +// ============================================ + +/** + * Flat check configuration for forms. + * Maps to TCheckConfig discriminated union when saving. + */ export interface ICheckConfig { domain: string; // Assumption check fields expectedTitle?: string; expectedStatusCode?: string; expectedDescription?: string; + assumedStatus?: TStatusType; // Function check fields functionDef?: string; + functionUrl?: string; + expectedStatusCodeNum?: number; + timeoutMs?: number; + // PWA check fields + targetUrl?: string; + lighthouseThreshold?: number; // PageRank check fields searchTerm?: string; checkBing?: boolean; @@ -22,6 +66,10 @@ export interface ICheckConfig { googleMinRank?: number; } +/** + * Service status for display and management. + * Extended with UI-specific fields. + */ export interface IServiceStatus { id: string; name: string; @@ -45,6 +93,9 @@ export interface IServiceStatus { intervalMs?: number; } +/** + * Status history point. + */ export interface IStatusHistoryPoint { timestamp: number; status: TStatusType; @@ -52,28 +103,9 @@ export interface IStatusHistoryPoint { errorRate?: number; } -export interface IIncidentUpdate { - id: string; - timestamp: number; - status: 'investigating' | 'identified' | 'monitoring' | 'resolved' | 'postmortem'; - message: string; - author?: string; -} - -export interface IIncidentDetails { - id: string; - title: string; - status: 'investigating' | 'identified' | 'monitoring' | 'resolved' | 'postmortem'; - severity: 'critical' | 'major' | 'minor' | 'maintenance'; - affectedServices: string[]; - startTime: number; - endTime?: number; - updates: IIncidentUpdate[]; - impact: string; - rootCause?: string; - resolution?: string; -} - +/** + * Overall status with service counts for dashboard. + */ export interface IOverallStatus { status: TStatusType; message: string; @@ -82,6 +114,9 @@ export interface IOverallStatus { totalServices: number; } +/** + * Status page configuration. + */ export interface IStatusPageConfig { apiEndpoint?: string; refreshInterval?: number; @@ -100,7 +135,41 @@ export interface IStatusPageConfig { legalUrl?: string; } -// Admin-specific interfaces +/** + * Incident update entry (UI version with timestamp). + */ +export interface IIncidentUpdate { + id: string; + timestamp: number; + status: TIncidentStatus; + message: string; + author?: string; +} + +/** + * Incident details for display. + */ +export interface IIncidentDetails { + id: string; + title: string; + status: TIncidentStatus; + severity: TIncidentSeverity; + affectedServices: string[]; + startTime: number; + endTime?: number; + updates: IIncidentUpdate[]; + impact: string; + rootCause?: string; + resolution?: string; +} + +// ============================================ +// Form Interfaces (UI-specific) +// ============================================ + +/** + * Monitor form data for creating/editing monitors. + */ export interface IMonitorFormData { id?: string; name: string; @@ -118,19 +187,25 @@ export interface IMonitorFormData { intervalMs: number; } +/** + * Incident form data for creating/editing incidents. + */ export interface IIncidentFormData { id?: string; title: string; - severity: 'critical' | 'major' | 'minor' | 'maintenance'; - status: 'investigating' | 'identified' | 'monitoring' | 'resolved' | 'postmortem'; + severity: TIncidentSeverity; + status: TIncidentStatus; affectedServices: string[]; impact: string; rootCause?: string; resolution?: string; } +/** + * Incident update form data. + */ export interface IIncidentUpdateFormData { - status: 'investigating' | 'identified' | 'monitoring' | 'resolved' | 'postmortem'; + status: TIncidentStatus; message: string; author?: string; } diff --git a/ts_web/pages/adminpage-config.ts b/ts_web/pages/adminpage-config.ts deleted file mode 100644 index 12e5a05..0000000 --- a/ts_web/pages/adminpage-config.ts +++ /dev/null @@ -1,83 +0,0 @@ -import { html, cssManager } from "@design.estate/dees-element"; -import type { IStatusPageConfig } from '../interfaces/index.js'; -import '../elements/index.js'; - -export const adminpageConfig = () => html` - - -
- { - const config = wrapperElement.querySelector('upladmin-statuspage-config') as any; - - const configData: IStatusPageConfig = { - companyName: 'CloudFlow Inc.', - companyLogo: '', - supportEmail: 'support@cloudflow.io', - statusPageUrl: 'https://status.cloudflow.io', - legalUrl: 'https://cloudflow.io/terms', - apiEndpoint: 'https://api.cloudflow.io/status', - refreshInterval: 60, - showHistoricalDays: 90, - theme: 'auto', - language: 'en', - timeZone: 'UTC', - dateFormat: 'relative', - enableWebSocket: true, - enableNotifications: false, - whitelabel: false, - }; - - config.config = configData; - }} - > - - -
-`; - -export const adminpageConfigWhitelabel = () => html` - - -
- { - const config = wrapperElement.querySelector('upladmin-statuspage-config') as any; - - const configData: IStatusPageConfig = { - companyName: 'Enterprise Corp', - companyLogo: 'https://via.placeholder.com/200x60/1a1a2e/ffffff?text=ENTERPRISE', - supportEmail: 'support@enterprise.com', - statusPageUrl: 'https://status.enterprise.com', - legalUrl: 'https://enterprise.com/legal', - apiEndpoint: 'https://api.enterprise.com/v2/status', - refreshInterval: 30, - showHistoricalDays: 180, - theme: 'dark', - language: 'en', - timeZone: 'America/New_York', - dateFormat: 'absolute', - enableWebSocket: true, - enableNotifications: true, - whitelabel: true, - }; - - config.config = configData; - }} - > - - -
-`; diff --git a/ts_web/pages/adminpage-dashboard.ts b/ts_web/pages/adminpage-dashboard.ts deleted file mode 100644 index a21e619..0000000 --- a/ts_web/pages/adminpage-dashboard.ts +++ /dev/null @@ -1,156 +0,0 @@ -import { html, cssManager } from "@design.estate/dees-element"; -import type { IServiceStatus, IIncidentDetails } from '../interfaces/index.js'; -import '../elements/index.js'; - -export const adminpageDashboard = () => html` - - -
- { - const dashboard = wrapperElement.querySelector('upladmin-dashboard') as any; - - // Demo monitors - const monitors: IServiceStatus[] = [ - { - id: 'api-server', - name: 'api-server', - displayName: 'API Server', - description: 'Main REST API endpoint', - currentStatus: 'operational', - lastChecked: Date.now(), - uptime30d: 99.98, - uptime90d: 99.95, - responseTime: 45, - category: 'Core Services', - }, - { - id: 'web-app', - name: 'web-app', - displayName: 'Web Application', - description: 'Customer-facing web application', - currentStatus: 'operational', - lastChecked: Date.now(), - uptime30d: 99.99, - uptime90d: 99.97, - responseTime: 120, - category: 'Core Services', - }, - { - id: 'database-primary', - name: 'database-primary', - displayName: 'Primary Database', - description: 'PostgreSQL primary node', - currentStatus: 'operational', - lastChecked: Date.now(), - uptime30d: 99.999, - uptime90d: 99.998, - responseTime: 5, - category: 'Infrastructure', - }, - { - id: 'cdn', - name: 'cdn', - displayName: 'Content Delivery Network', - description: 'Global CDN for static assets', - currentStatus: 'degraded', - lastChecked: Date.now(), - uptime30d: 99.5, - uptime90d: 99.8, - responseTime: 200, - category: 'Infrastructure', - }, - { - id: 'email-service', - name: 'email-service', - displayName: 'Email Service', - description: 'Transactional email delivery', - currentStatus: 'operational', - lastChecked: Date.now(), - uptime30d: 99.9, - uptime90d: 99.85, - responseTime: 500, - category: 'External Services', - }, - { - id: 'payment-gateway', - name: 'payment-gateway', - displayName: 'Payment Gateway', - description: 'Payment processing integration', - currentStatus: 'maintenance', - lastChecked: Date.now(), - uptime30d: 99.95, - uptime90d: 99.9, - responseTime: 350, - category: 'External Services', - }, - ]; - - // Demo incidents - const incidents: IIncidentDetails[] = [ - { - id: 'inc-001', - title: 'CDN Performance Degradation', - status: 'monitoring', - severity: 'minor', - affectedServices: ['cdn'], - startTime: Date.now() - 2 * 60 * 60 * 1000, - impact: 'Some users may experience slower loading times for images and static assets.', - updates: [ - { - id: 'upd-001', - timestamp: Date.now() - 2 * 60 * 60 * 1000, - status: 'investigating', - message: 'We are investigating reports of slow asset loading.', - author: 'Platform Team', - }, - { - id: 'upd-002', - timestamp: Date.now() - 1 * 60 * 60 * 1000, - status: 'identified', - message: 'We have identified the issue as a problem with one of our CDN edge nodes.', - author: 'Platform Team', - }, - { - id: 'upd-003', - timestamp: Date.now() - 30 * 60 * 1000, - status: 'monitoring', - message: 'Traffic has been rerouted to healthy nodes. Monitoring for stability.', - author: 'Platform Team', - }, - ], - }, - { - id: 'inc-002', - title: 'Payment Gateway Scheduled Maintenance', - status: 'investigating', - severity: 'maintenance', - affectedServices: ['payment-gateway'], - startTime: Date.now() - 30 * 60 * 1000, - impact: 'Payment processing is temporarily unavailable during the maintenance window.', - updates: [ - { - id: 'upd-004', - timestamp: Date.now() - 30 * 60 * 1000, - status: 'investigating', - message: 'Scheduled maintenance has begun. Expected duration: 2 hours.', - author: 'DevOps Team', - }, - ], - }, - ]; - - dashboard.monitors = monitors; - dashboard.incidents = incidents; - }} - > - - -
-`; diff --git a/ts_web/pages/adminpage-incidents.ts b/ts_web/pages/adminpage-incidents.ts deleted file mode 100644 index 12ef48a..0000000 --- a/ts_web/pages/adminpage-incidents.ts +++ /dev/null @@ -1,166 +0,0 @@ -import { html, cssManager } from "@design.estate/dees-element"; -import type { IServiceStatus, IIncidentDetails } from '../interfaces/index.js'; -import '../elements/index.js'; - -export const adminpageIncidents = () => html` - - -
- { - const incidentList = wrapperElement.querySelector('upladmin-incident-list') as any; - - const incidents: IIncidentDetails[] = [ - { - id: 'inc-001', - title: 'CDN Performance Degradation', - status: 'monitoring', - severity: 'minor', - affectedServices: ['cdn'], - startTime: Date.now() - 2 * 60 * 60 * 1000, - impact: 'Some users may experience slower loading times for images and static assets.', - updates: [ - { id: 'upd-001', timestamp: Date.now() - 2 * 60 * 60 * 1000, status: 'investigating', message: 'We are investigating reports of slow asset loading.', author: 'Platform Team' }, - { id: 'upd-002', timestamp: Date.now() - 1 * 60 * 60 * 1000, status: 'identified', message: 'We have identified the issue as a problem with one of our CDN edge nodes.', author: 'Platform Team' }, - { id: 'upd-003', timestamp: Date.now() - 30 * 60 * 1000, status: 'monitoring', message: 'Traffic has been rerouted to healthy nodes. Monitoring for stability.', author: 'Platform Team' }, - ], - }, - { - id: 'inc-002', - title: 'Payment Gateway Scheduled Maintenance', - status: 'investigating', - severity: 'maintenance', - affectedServices: ['payment-gateway'], - startTime: Date.now() - 30 * 60 * 1000, - impact: 'Payment processing is temporarily unavailable during the maintenance window.', - updates: [ - { id: 'upd-004', timestamp: Date.now() - 30 * 60 * 1000, status: 'investigating', message: 'Scheduled maintenance has begun. Expected duration: 2 hours.', author: 'DevOps Team' }, - ], - }, - { - id: 'inc-003', - title: 'Search Engine Partial Outage', - status: 'identified', - severity: 'major', - affectedServices: ['search-engine', 'api-server'], - startTime: Date.now() - 45 * 60 * 1000, - impact: 'Search functionality is degraded. Some queries may timeout or return incomplete results.', - updates: [ - { id: 'upd-005', timestamp: Date.now() - 45 * 60 * 1000, status: 'investigating', message: 'We are aware of issues with search functionality.', author: 'Engineering Team' }, - { id: 'upd-006', timestamp: Date.now() - 20 * 60 * 1000, status: 'identified', message: 'Root cause identified: disk space exhaustion on search cluster nodes.', author: 'Engineering Team' }, - ], - }, - { - id: 'inc-004', - title: 'API Server Outage', - status: 'resolved', - severity: 'critical', - affectedServices: ['api-server', 'web-app'], - startTime: Date.now() - 24 * 60 * 60 * 1000, - endTime: Date.now() - 23 * 60 * 60 * 1000, - impact: 'Complete service unavailability for all API-dependent services.', - rootCause: 'Database connection pool exhaustion due to a query performance regression.', - resolution: 'Rolled back recent deployment and optimized database queries.', - updates: [ - { id: 'upd-007', timestamp: Date.now() - 24 * 60 * 60 * 1000, status: 'investigating', message: 'We are aware of service unavailability and actively investigating.', author: 'Platform Team' }, - { id: 'upd-008', timestamp: Date.now() - 23.5 * 60 * 60 * 1000, status: 'identified', message: 'Root cause identified as database connection pool exhaustion.', author: 'Platform Team' }, - { id: 'upd-009', timestamp: Date.now() - 23 * 60 * 60 * 1000, status: 'resolved', message: 'Service has been restored. All systems operational.', author: 'Platform Team' }, - ], - }, - { - id: 'inc-005', - title: 'Email Delivery Delays', - status: 'resolved', - severity: 'minor', - affectedServices: ['email-service'], - startTime: Date.now() - 48 * 60 * 60 * 1000, - endTime: Date.now() - 46 * 60 * 60 * 1000, - impact: 'Email notifications may be delayed by up to 30 minutes.', - rootCause: 'Third-party email provider experiencing capacity issues.', - resolution: 'Provider resolved their capacity issues.', - updates: [ - { id: 'upd-010', timestamp: Date.now() - 48 * 60 * 60 * 1000, status: 'investigating', message: 'Investigating reports of delayed email delivery.', author: 'Support Team' }, - { id: 'upd-011', timestamp: Date.now() - 46 * 60 * 60 * 1000, status: 'resolved', message: 'Email delivery has returned to normal.', author: 'Support Team' }, - ], - }, - ]; - - incidentList.incidents = incidents; - }} - > - - -
-`; - -export const adminpageIncidentForm = () => html` - - -
- { - const incidentForm = wrapperElement.querySelector('upladmin-incident-form') as any; - - const services: IServiceStatus[] = [ - { id: 'api-server', name: 'api-server', displayName: 'API Server', currentStatus: 'operational', lastChecked: Date.now(), uptime30d: 99.98, uptime90d: 99.95, responseTime: 45 }, - { id: 'web-app', name: 'web-app', displayName: 'Web Application', currentStatus: 'operational', lastChecked: Date.now(), uptime30d: 99.99, uptime90d: 99.97, responseTime: 120 }, - { id: 'database-primary', name: 'database-primary', displayName: 'Primary Database', currentStatus: 'operational', lastChecked: Date.now(), uptime30d: 99.999, uptime90d: 99.998, responseTime: 5 }, - { id: 'cdn', name: 'cdn', displayName: 'Content Delivery Network', currentStatus: 'degraded', lastChecked: Date.now(), uptime30d: 99.5, uptime90d: 99.8, responseTime: 200 }, - { id: 'email-service', name: 'email-service', displayName: 'Email Service', currentStatus: 'operational', lastChecked: Date.now(), uptime30d: 99.9, uptime90d: 99.85, responseTime: 500 }, - { id: 'payment-gateway', name: 'payment-gateway', displayName: 'Payment Gateway', currentStatus: 'maintenance', lastChecked: Date.now(), uptime30d: 99.95, uptime90d: 99.9, responseTime: 350 }, - ]; - - incidentForm.availableServices = services; - }} - > - - -
-`; - -export const adminpageIncidentUpdate = () => html` - - -
- { - const incidentUpdate = wrapperElement.querySelector('upladmin-incident-update') as any; - - incidentUpdate.incident = { - id: 'inc-001', - title: 'CDN Performance Degradation', - status: 'monitoring', - severity: 'minor', - affectedServices: ['cdn'], - startTime: Date.now() - 2 * 60 * 60 * 1000, - impact: 'Some users may experience slower loading times for images and static assets.', - updates: [ - { id: 'upd-001', timestamp: Date.now() - 2 * 60 * 60 * 1000, status: 'investigating', message: 'We are investigating reports of slow asset loading.', author: 'Platform Team' }, - { id: 'upd-002', timestamp: Date.now() - 1 * 60 * 60 * 1000, status: 'identified', message: 'We have identified the issue as a problem with one of our CDN edge nodes.', author: 'Platform Team' }, - { id: 'upd-003', timestamp: Date.now() - 30 * 60 * 1000, status: 'monitoring', message: 'Traffic has been rerouted to healthy nodes. Monitoring for stability.', author: 'Platform Team' }, - ], - }; - }} - > - - -
-`; diff --git a/ts_web/pages/adminpage-monitors.ts b/ts_web/pages/adminpage-monitors.ts deleted file mode 100644 index 9f9a715..0000000 --- a/ts_web/pages/adminpage-monitors.ts +++ /dev/null @@ -1,213 +0,0 @@ -import { html, cssManager } from "@design.estate/dees-element"; -import type { IServiceStatus } from '../interfaces/index.js'; -import '../elements/index.js'; - -export const adminpageMonitors = () => html` - - -
- { - const monitorList = wrapperElement.querySelector('upladmin-monitor-list') as any; - - const monitors: IServiceStatus[] = [ - { - id: 'api-server', - name: 'api-server', - displayName: 'API Server', - description: 'Main REST API endpoint', - currentStatus: 'operational', - lastChecked: Date.now(), - uptime30d: 99.98, - uptime90d: 99.95, - responseTime: 45, - category: 'Core Services', - }, - { - id: 'web-app', - name: 'web-app', - displayName: 'Web Application', - description: 'Customer-facing web application', - currentStatus: 'operational', - lastChecked: Date.now(), - uptime30d: 99.99, - uptime90d: 99.97, - responseTime: 120, - category: 'Core Services', - }, - { - id: 'database-primary', - name: 'database-primary', - displayName: 'Primary Database', - description: 'PostgreSQL primary node', - currentStatus: 'operational', - lastChecked: Date.now(), - uptime30d: 99.999, - uptime90d: 99.998, - responseTime: 5, - category: 'Infrastructure', - }, - { - id: 'database-replica', - name: 'database-replica', - displayName: 'Database Replica', - description: 'PostgreSQL read replica', - currentStatus: 'operational', - lastChecked: Date.now(), - uptime30d: 99.99, - uptime90d: 99.95, - responseTime: 8, - category: 'Infrastructure', - }, - { - id: 'cdn', - name: 'cdn', - displayName: 'Content Delivery Network', - description: 'Global CDN for static assets', - currentStatus: 'degraded', - lastChecked: Date.now(), - uptime30d: 99.5, - uptime90d: 99.8, - responseTime: 200, - category: 'Infrastructure', - }, - { - id: 'redis-cache', - name: 'redis-cache', - displayName: 'Redis Cache', - description: 'In-memory caching layer', - currentStatus: 'operational', - lastChecked: Date.now(), - uptime30d: 99.99, - uptime90d: 99.98, - responseTime: 2, - category: 'Infrastructure', - }, - { - id: 'email-service', - name: 'email-service', - displayName: 'Email Service', - description: 'Transactional email delivery', - currentStatus: 'operational', - lastChecked: Date.now(), - uptime30d: 99.9, - uptime90d: 99.85, - responseTime: 500, - category: 'External Services', - }, - { - id: 'payment-gateway', - name: 'payment-gateway', - displayName: 'Payment Gateway', - description: 'Payment processing integration', - currentStatus: 'maintenance', - lastChecked: Date.now(), - uptime30d: 99.95, - uptime90d: 99.9, - responseTime: 350, - category: 'External Services', - }, - { - id: 'sms-service', - name: 'sms-service', - displayName: 'SMS Service', - description: 'SMS notifications and 2FA', - currentStatus: 'operational', - lastChecked: Date.now(), - uptime30d: 99.8, - uptime90d: 99.75, - responseTime: 800, - category: 'External Services', - }, - { - id: 'search-engine', - name: 'search-engine', - displayName: 'Search Engine', - description: 'Elasticsearch cluster', - currentStatus: 'partial_outage', - lastChecked: Date.now(), - uptime30d: 98.5, - uptime90d: 99.2, - responseTime: 150, - category: 'Core Services', - }, - ]; - - monitorList.monitors = monitors; - }} - > - - -
-`; - -export const adminpageMonitorForm = () => html` - - -
- { - const monitorForm = wrapperElement.querySelector('upladmin-monitor-form') as any; - - const availableMonitors: IServiceStatus[] = [ - { id: 'api-server', name: 'api-server', displayName: 'API Server', currentStatus: 'operational', lastChecked: Date.now(), uptime30d: 99.98, uptime90d: 99.95, responseTime: 45 }, - { id: 'database-primary', name: 'database-primary', displayName: 'Primary Database', currentStatus: 'operational', lastChecked: Date.now(), uptime30d: 99.999, uptime90d: 99.998, responseTime: 5 }, - ]; - - monitorForm.availableMonitors = availableMonitors; - monitorForm.categories = ['Core Services', 'Infrastructure', 'External Services', 'Web Services']; - }} - > - - -
-`; - -export const adminpageMonitorFormEdit = () => html` - - -
- { - const monitorForm = wrapperElement.querySelector('upladmin-monitor-form') as any; - - const availableMonitors: IServiceStatus[] = [ - { id: 'api-server', name: 'api-server', displayName: 'API Server', currentStatus: 'operational', lastChecked: Date.now(), uptime30d: 99.98, uptime90d: 99.95, responseTime: 45 }, - { id: 'database-primary', name: 'database-primary', displayName: 'Primary Database', currentStatus: 'operational', lastChecked: Date.now(), uptime30d: 99.999, uptime90d: 99.998, responseTime: 5 }, - ]; - - monitorForm.availableMonitors = availableMonitors; - monitorForm.categories = ['Core Services', 'Infrastructure', 'External Services', 'Web Services']; - monitorForm.monitor = { - id: 'cdn', - name: 'cdn', - displayName: 'Content Delivery Network', - description: 'Global CDN for static assets and media files', - category: 'Infrastructure', - dependencies: ['api-server'], - currentStatus: 'degraded', - }; - }} - > - - -
-`; diff --git a/ts_web/pages/index.ts b/ts_web/pages/index.ts index 3712880..d83399d 100644 --- a/ts_web/pages/index.ts +++ b/ts_web/pages/index.ts @@ -1,5 +1,3 @@ -export * from './adminpage-dashboard.js'; -export * from './adminpage-monitors.js'; -export * from './adminpage-incidents.js'; -export * from './adminpage-config.js'; +// Consolidated app demo - showcases all views through dees-appui export { demoFunc as adminpageApp } from './upladmin-app/upladmin-app.demo.js'; +export { UpladminApp } from './upladmin-app/upladmin-app.js'; diff --git a/ts_web/pages/upladmin-app/upladmin-app.ts b/ts_web/pages/upladmin-app/upladmin-app.ts index d0a548a..34f49b7 100644 --- a/ts_web/pages/upladmin-app/upladmin-app.ts +++ b/ts_web/pages/upladmin-app/upladmin-app.ts @@ -11,14 +11,11 @@ import type { DeesAppuiBase } from '@design.estate/dees-catalog'; import { adminState } from '../../services/admin-state.js'; import { demoFunc } from './upladmin-app.demo.js'; -// Import components directly -import '../../elements/upladmin-dashboard/upladmin-dashboard.js'; -import '../../elements/upladmin-monitor-list/upladmin-monitor-list.js'; -import '../../elements/upladmin-monitor-form/upladmin-monitor-form.js'; -import '../../elements/upladmin-incident-list/upladmin-incident-list.js'; -import '../../elements/upladmin-incident-form/upladmin-incident-form.js'; -import '../../elements/upladmin-incident-update/upladmin-incident-update.js'; -import '../../elements/upladmin-statuspage-config/upladmin-statuspage-config.js'; +// Import view components +import '../../views/upladmin-dashboard-view/upladmin-dashboard-view.js'; +import '../../views/upladmin-monitors-view/upladmin-monitors-view.js'; +import '../../views/upladmin-incidents-view/upladmin-incidents-view.js'; +import '../../views/upladmin-config-view/upladmin-config-view.js'; declare global { interface HTMLElementTagNameMap { @@ -176,14 +173,14 @@ export class UpladminApp extends DeesElement { id: 'dashboard', name: 'Dashboard', iconName: 'lucide:layoutDashboard', - content: 'upladmin-dashboard', + content: 'upladmin-dashboard-view', route: 'dashboard', }, { id: 'monitors', name: 'Monitors', iconName: 'lucide:activity', - content: 'upladmin-monitor-list', + content: 'upladmin-monitors-view', route: 'monitors', badge: adminState.monitors.length, }, @@ -191,7 +188,7 @@ export class UpladminApp extends DeesElement { id: 'monitor-form', name: 'Monitor', iconName: 'lucide:activity', - content: 'upladmin-monitor-form', + content: 'upladmin-monitors-view', route: 'monitors/:id', cache: false, }, @@ -199,7 +196,7 @@ export class UpladminApp extends DeesElement { id: 'incidents', name: 'Incidents', iconName: 'lucide:alertCircle', - content: 'upladmin-incident-list', + content: 'upladmin-incidents-view', route: 'incidents', badge: adminState.getActiveIncidents().length, badgeVariant: adminState.getActiveIncidents().length > 0 ? 'warning' : 'default', @@ -208,7 +205,7 @@ export class UpladminApp extends DeesElement { id: 'incident-form', name: 'Incident', iconName: 'lucide:alertCircle', - content: 'upladmin-incident-form', + content: 'upladmin-incidents-view', route: 'incidents/:id', cache: false, }, @@ -216,7 +213,7 @@ export class UpladminApp extends DeesElement { id: 'incident-update', name: 'Post Update', iconName: 'lucide:messageSquarePlus', - content: 'upladmin-incident-update', + content: 'upladmin-incidents-view', route: 'incidents/:id/update', cache: false, }, @@ -224,9 +221,17 @@ export class UpladminApp extends DeesElement { id: 'config', name: 'Settings', iconName: 'lucide:settings', - content: 'upladmin-statuspage-config', + content: 'upladmin-config-view', route: 'config', }, + { + id: 'config-section', + name: 'Settings', + iconName: 'lucide:settings', + content: 'upladmin-config-view', + route: 'config/:section', + cache: false, + }, ], mainMenu: { diff --git a/ts_web/views/index.ts b/ts_web/views/index.ts new file mode 100644 index 0000000..822df1e --- /dev/null +++ b/ts_web/views/index.ts @@ -0,0 +1,4 @@ +export * from './upladmin-dashboard-view/upladmin-dashboard-view.js'; +export * from './upladmin-monitors-view/upladmin-monitors-view.js'; +export * from './upladmin-incidents-view/upladmin-incidents-view.js'; +export * from './upladmin-config-view/upladmin-config-view.js'; diff --git a/ts_web/views/upladmin-config-view/upladmin-config-view.ts b/ts_web/views/upladmin-config-view/upladmin-config-view.ts new file mode 100644 index 0000000..73bd8e9 --- /dev/null +++ b/ts_web/views/upladmin-config-view/upladmin-config-view.ts @@ -0,0 +1,124 @@ +import { + DeesElement, + customElement, + html, + state, + css, + cssManager, +} from '@design.estate/dees-element'; +import type { DeesAppuiBase } from '@design.estate/dees-catalog'; + +// View lifecycle interfaces (defined locally as they're not exported from dees-catalog) +interface IViewActivationContext { + appui: DeesAppuiBase; + viewId: string; + params?: Record; +} + +interface IViewLifecycle { + onActivate?: (context: IViewActivationContext) => void | Promise; + onDeactivate?: () => void | Promise; +} +import { adminState } from '../../services/admin-state.js'; +import '../../elements/upladmin-statuspage-config/upladmin-statuspage-config.js'; + +type TConfigSection = 'branding' | 'urls' | 'behavior' | 'advanced'; + +@customElement('upladmin-config-view') +export class UpladminConfigView extends DeesElement implements IViewLifecycle { + @state() + accessor activeSection: TConfigSection = 'branding'; + + @state() + accessor loading: boolean = false; + + private appuiRef: DeesAppuiBase | null = null; + + public static styles = [ + cssManager.defaultStyles, + css` + :host { + display: block; + height: 100%; + } + `, + ]; + + async onActivate(context: IViewActivationContext): Promise { + this.appuiRef = context.appui; + + // Check route params for section + if (context.params?.section) { + const section = context.params.section as TConfigSection; + if (['branding', 'urls', 'behavior', 'advanced'].includes(section)) { + this.activeSection = section; + } + } + + // Set secondary menu for configuration sections + this.updateSecondaryMenu(); + + // No content tabs for config + context.appui.setContentTabs([]); + } + + private updateSecondaryMenu(): void { + if (!this.appuiRef) return; + + this.appuiRef.setSecondaryMenu({ + heading: 'Configuration', + groups: [ + { + name: 'Settings', + iconName: 'lucide:settings', + items: [ + { + key: 'branding', + iconName: 'lucide:palette', + action: () => this.setSection('branding'), + }, + { + key: 'urls', + iconName: 'lucide:link', + action: () => this.setSection('urls'), + }, + { + key: 'behavior', + iconName: 'lucide:sliders', + action: () => this.setSection('behavior'), + }, + { + key: 'advanced', + iconName: 'lucide:wrench', + action: () => this.setSection('advanced'), + }, + ], + }, + ], + }); + + // Select current section + this.appuiRef.setSecondaryMenuSelection(this.activeSection); + } + + private setSection(section: TConfigSection): void { + this.activeSection = section; + this.appuiRef?.setSecondaryMenuSelection(section); + } + + private handleConfigSave = (e: CustomEvent): void => { + console.log('Config saved:', e.detail); + // In a real implementation, this would save to the backend + }; + + render() { + return html` + + `; + } +} diff --git a/ts_web/views/upladmin-dashboard-view/upladmin-dashboard-view.ts b/ts_web/views/upladmin-dashboard-view/upladmin-dashboard-view.ts new file mode 100644 index 0000000..5bc390f --- /dev/null +++ b/ts_web/views/upladmin-dashboard-view/upladmin-dashboard-view.ts @@ -0,0 +1,60 @@ +import { + DeesElement, + customElement, + html, + state, + css, + cssManager, +} from '@design.estate/dees-element'; +import type { DeesAppuiBase } from '@design.estate/dees-catalog'; + +// View lifecycle interfaces (defined locally as they're not exported from dees-catalog) +interface IViewActivationContext { + appui: DeesAppuiBase; + viewId: string; + params?: Record; +} + +interface IViewLifecycle { + onActivate?: (context: IViewActivationContext) => void | Promise; + onDeactivate?: () => void | Promise; +} +import { adminState } from '../../services/admin-state.js'; +import '../../elements/upladmin-dashboard/upladmin-dashboard.js'; + +@customElement('upladmin-dashboard-view') +export class UpladminDashboardView extends DeesElement implements IViewLifecycle { + @state() + accessor loading: boolean = true; + + public static styles = [ + cssManager.defaultStyles, + css` + :host { + display: block; + height: 100%; + } + `, + ]; + + async onActivate(context: IViewActivationContext): Promise { + // Dashboard has no secondary menu - clear any existing + context.appui.clearSecondaryMenu(); + + // No content tabs for dashboard + context.appui.setContentTabs([]); + + // Load data + this.loading = false; + } + + render() { + return html` + + `; + } +} diff --git a/ts_web/views/upladmin-incidents-view/upladmin-incidents-view.ts b/ts_web/views/upladmin-incidents-view/upladmin-incidents-view.ts new file mode 100644 index 0000000..a24e1d2 --- /dev/null +++ b/ts_web/views/upladmin-incidents-view/upladmin-incidents-view.ts @@ -0,0 +1,292 @@ +import { + DeesElement, + customElement, + html, + state, + css, + cssManager, +} from '@design.estate/dees-element'; +import type { DeesAppuiBase } from '@design.estate/dees-catalog'; + +// View lifecycle interfaces (defined locally as they're not exported from dees-catalog) +interface IViewActivationContext { + appui: DeesAppuiBase; + viewId: string; + params?: Record; +} + +interface IViewLifecycle { + onActivate?: (context: IViewActivationContext) => void | Promise; + onDeactivate?: () => void | Promise; +} +import { adminState } from '../../services/admin-state.js'; +import type { IIncidentDetails, TIncidentSeverity, TIncidentStatus } from '../../interfaces/index.js'; +import '../../elements/upladmin-incident-list/upladmin-incident-list.js'; +import '../../elements/upladmin-incident-form/upladmin-incident-form.js'; +import '../../elements/upladmin-incident-update/upladmin-incident-update.js'; + +type TViewMode = 'list' | 'form' | 'update'; +type TTimeFilter = 'current' | 'past' | 'all'; + +@customElement('upladmin-incidents-view') +export class UpladminIncidentsView extends DeesElement implements IViewLifecycle { + @state() + accessor currentMode: TViewMode = 'list'; + + @state() + accessor selectedIncidentId: string | null = null; + + @state() + accessor timeFilter: TTimeFilter = 'current'; + + @state() + accessor severityFilter: TIncidentSeverity | 'all' = 'all'; + + @state() + accessor loading: boolean = false; + + private appuiRef: DeesAppuiBase | null = null; + + public static styles = [ + cssManager.defaultStyles, + css` + :host { + display: block; + height: 100%; + } + `, + ]; + + async onActivate(context: IViewActivationContext): Promise { + this.appuiRef = context.appui; + + // Check route params and view ID + if (context.params?.id) { + if (context.viewId === 'incident-update') { + this.currentMode = 'update'; + this.selectedIncidentId = context.params.id; + } else { + this.currentMode = 'form'; + this.selectedIncidentId = context.params.id === 'create' ? null : context.params.id; + } + } else { + this.currentMode = 'list'; + this.selectedIncidentId = null; + } + + // Set secondary menu + this.updateSecondaryMenu(); + + // No content tabs - incident-list has internal tabs + context.appui.setContentTabs([]); + } + + private updateSecondaryMenu(): void { + if (!this.appuiRef) return; + + const activeCount = adminState.getActiveIncidents().length; + const pastCount = adminState.incidents.filter((i) => i.status === 'resolved' || i.status === 'postmortem').length; + + this.appuiRef.setSecondaryMenu({ + heading: 'Incidents', + groups: [ + { + name: 'Filter', + iconName: 'lucide:filter', + items: [ + { + key: 'current', + iconName: 'lucide:alertCircle', + action: () => this.setTimeFilter('current'), + badge: activeCount, + badgeVariant: activeCount > 0 ? 'error' : 'default', + }, + { + key: 'past', + iconName: 'lucide:history', + action: () => this.setTimeFilter('past'), + badge: pastCount, + }, + { + key: 'all', + iconName: 'lucide:list', + action: () => this.setTimeFilter('all'), + badge: adminState.incidents.length, + }, + ], + }, + { + name: 'Severity', + iconName: 'lucide:alertTriangle', + collapsed: true, + items: [ + { + key: 'critical', + iconName: 'lucide:xCircle', + action: () => this.setSeverityFilter('critical'), + }, + { + key: 'major', + iconName: 'lucide:alertOctagon', + action: () => this.setSeverityFilter('major'), + }, + { + key: 'minor', + iconName: 'lucide:alertTriangle', + action: () => this.setSeverityFilter('minor'), + }, + { + key: 'maintenance', + iconName: 'lucide:wrench', + action: () => this.setSeverityFilter('maintenance'), + }, + ], + }, + { + name: 'Actions', + iconName: 'lucide:zap', + items: [ + { + key: 'create', + iconName: 'lucide:plus', + action: () => this.showForm(null), + }, + ], + }, + ], + }); + + // Select current filter + this.appuiRef.setSecondaryMenuSelection(this.timeFilter); + } + + private setTimeFilter(filter: TTimeFilter): void { + this.timeFilter = filter; + this.severityFilter = 'all'; + this.appuiRef?.setSecondaryMenuSelection(filter); + } + + private setSeverityFilter(severity: TIncidentSeverity): void { + this.severityFilter = severity; + this.appuiRef?.setSecondaryMenuSelection(severity); + } + + private showForm(incidentId: string | null): void { + this.currentMode = 'form'; + this.selectedIncidentId = incidentId; + } + + private showUpdate(incidentId: string): void { + this.currentMode = 'update'; + this.selectedIncidentId = incidentId; + } + + private showList(): void { + this.currentMode = 'list'; + this.selectedIncidentId = null; + } + + private get filteredIncidents(): IIncidentDetails[] { + let incidents = adminState.incidents; + + // Apply time filter + if (this.timeFilter === 'current') { + incidents = incidents.filter( + (i) => i.status !== 'resolved' && i.status !== 'postmortem' + ); + } else if (this.timeFilter === 'past') { + incidents = incidents.filter( + (i) => i.status === 'resolved' || i.status === 'postmortem' + ); + } + + // Apply severity filter + if (this.severityFilter !== 'all') { + incidents = incidents.filter((i) => i.severity === this.severityFilter); + } + + return incidents; + } + + private handleIncidentSave = (e: CustomEvent): void => { + console.log('Incident saved:', e.detail); + this.showList(); + }; + + private handleUpdateSave = (e: CustomEvent): void => { + console.log('Update saved:', e.detail); + this.showList(); + }; + + private handleCancel = (): void => { + this.showList(); + }; + + private handleIncidentEdit = (e: CustomEvent): void => { + const incident = e.detail?.incident as IIncidentDetails; + if (incident) { + this.showForm(incident.id); + } + }; + + private handleIncidentAddUpdate = (e: CustomEvent): void => { + const incident = e.detail?.incident as IIncidentDetails; + if (incident) { + this.showUpdate(incident.id); + } + }; + + render() { + if (this.currentMode === 'update') { + const incident = this.selectedIncidentId + ? adminState.incidents.find((i) => i.id === this.selectedIncidentId) + : null; + + return html` + + `; + } + + if (this.currentMode === 'form') { + const incident = this.selectedIncidentId + ? adminState.incidents.find((i) => i.id === this.selectedIncidentId) + : null; + + return html` + + `; + } + + return html` + this.showForm(null)} + @incidentEdit=${this.handleIncidentEdit} + @incidentAddUpdate=${this.handleIncidentAddUpdate} + > + `; + } +} diff --git a/ts_web/views/upladmin-monitors-view/upladmin-monitors-view.ts b/ts_web/views/upladmin-monitors-view/upladmin-monitors-view.ts new file mode 100644 index 0000000..c482a96 --- /dev/null +++ b/ts_web/views/upladmin-monitors-view/upladmin-monitors-view.ts @@ -0,0 +1,244 @@ +import { + DeesElement, + customElement, + html, + state, + css, + cssManager, +} from '@design.estate/dees-element'; +import type { DeesAppuiBase } from '@design.estate/dees-catalog'; + +// View lifecycle interfaces (defined locally as they're not exported from dees-catalog) +interface IViewActivationContext { + appui: DeesAppuiBase; + viewId: string; + params?: Record; +} + +interface IViewLifecycle { + onActivate?: (context: IViewActivationContext) => void | Promise; + onDeactivate?: () => void | Promise; +} +import { adminState } from '../../services/admin-state.js'; +import type { IServiceStatus, TStatusType } from '../../interfaces/index.js'; +import '../../elements/upladmin-monitor-list/upladmin-monitor-list.js'; +import '../../elements/upladmin-monitor-form/upladmin-monitor-form.js'; + +type TViewMode = 'list' | 'form'; +type TStatusFilter = 'all' | 'issues' | 'maintenance'; + +@customElement('upladmin-monitors-view') +export class UpladminMonitorsView extends DeesElement implements IViewLifecycle { + @state() + accessor currentMode: TViewMode = 'list'; + + @state() + accessor selectedMonitorId: string | null = null; + + @state() + accessor statusFilter: TStatusFilter = 'all'; + + @state() + accessor categoryFilter: string = 'all'; + + @state() + accessor loading: boolean = false; + + private appuiRef: DeesAppuiBase | null = null; + + public static styles = [ + cssManager.defaultStyles, + css` + :host { + display: block; + height: 100%; + } + `, + ]; + + async onActivate(context: IViewActivationContext): Promise { + this.appuiRef = context.appui; + + // Check route params for edit mode + if (context.params?.id) { + this.currentMode = 'form'; + this.selectedMonitorId = context.params.id === 'create' ? null : context.params.id; + } else { + this.currentMode = 'list'; + this.selectedMonitorId = null; + } + + // Set secondary menu with categories + this.updateSecondaryMenu(); + + // Set content tabs for status filtering + context.appui.setContentTabs([ + { + key: 'All', + iconName: 'lucide:activity', + action: () => this.setStatusFilter('all'), + }, + { + key: 'Issues', + iconName: 'lucide:alertTriangle', + action: () => this.setStatusFilter('issues'), + }, + { + key: 'Maintenance', + iconName: 'lucide:wrench', + action: () => this.setStatusFilter('maintenance'), + }, + ]); + } + + private updateSecondaryMenu(): void { + if (!this.appuiRef) return; + + const categories = this.getCategories(); + + this.appuiRef.setSecondaryMenu({ + heading: 'Monitors', + groups: [ + { + name: 'Categories', + iconName: 'lucide:folder', + items: [ + { + key: 'all', + iconName: 'lucide:list', + action: () => this.setCategoryFilter('all'), + badge: adminState.monitors.length, + }, + ...categories.map((cat) => ({ + key: cat, + iconName: 'lucide:folder', + action: () => this.setCategoryFilter(cat), + badge: adminState.monitors.filter((m) => m.category === cat).length, + })), + ], + }, + { + name: 'Actions', + iconName: 'lucide:zap', + items: [ + { + key: 'add', + iconName: 'lucide:plus', + action: () => this.showForm(null), + }, + ], + }, + ], + }); + + // Select current category + this.appuiRef.setSecondaryMenuSelection(this.categoryFilter); + } + + private getCategories(): string[] { + const cats = new Set(); + for (const m of adminState.monitors) { + if (m.category) cats.add(m.category); + } + return Array.from(cats).sort(); + } + + private setStatusFilter(filter: TStatusFilter): void { + this.statusFilter = filter; + } + + private setCategoryFilter(category: string): void { + this.categoryFilter = category; + this.appuiRef?.setSecondaryMenuSelection(category); + } + + private showForm(monitorId: string | null): void { + this.currentMode = 'form'; + this.selectedMonitorId = monitorId; + } + + private showList(): void { + this.currentMode = 'list'; + this.selectedMonitorId = null; + } + + private get filteredMonitors(): IServiceStatus[] { + let monitors = adminState.monitors; + + // Apply category filter + if (this.categoryFilter !== 'all') { + monitors = monitors.filter((m) => m.category === this.categoryFilter); + } + + // Apply status filter + if (this.statusFilter === 'issues') { + monitors = monitors.filter((m) => + ['degraded', 'partial_outage', 'major_outage', 'error'].includes(m.currentStatus) + ); + } else if (this.statusFilter === 'maintenance') { + monitors = monitors.filter((m) => m.currentStatus === 'maintenance'); + } + + return monitors; + } + + private handleMonitorSave = (e: CustomEvent): void => { + // Handle save logic + console.log('Monitor saved:', e.detail); + this.showList(); + }; + + private handleCancel = (): void => { + this.showList(); + }; + + private handleMonitorEdit = (e: CustomEvent): void => { + const monitor = e.detail?.monitor as IServiceStatus; + if (monitor) { + this.showForm(monitor.id); + } + }; + + render() { + if (this.currentMode === 'form') { + const monitor = this.selectedMonitorId + ? adminState.monitors.find((m) => m.id === this.selectedMonitorId) + : null; + + return html` + + `; + } + + return html` + this.showForm(null)} + @monitorEdit=${this.handleMonitorEdit} + > + `; + } +}