Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 40b25cf02d | |||
| c5632dae77 | |||
| 87ac6e506f | |||
| 6a7c69bad1 |
67
.nogit/theme-test.html
Normal file
67
.nogit/theme-test.html
Normal file
@@ -0,0 +1,67 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Theme Test</title>
|
||||
<script type="module" src="../dist_bundle/bundle.js"></script>
|
||||
<style>
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 24px;
|
||||
font-family: system-ui, sans-serif;
|
||||
}
|
||||
.section {
|
||||
margin-bottom: 32px;
|
||||
}
|
||||
.title {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
.grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(5, 1fr);
|
||||
gap: 12px;
|
||||
}
|
||||
#theme-toggle {
|
||||
position: fixed;
|
||||
top: 16px;
|
||||
right: 16px;
|
||||
padding: 8px 16px;
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<button id="theme-toggle">Toggle Theme</button>
|
||||
|
||||
<div class="section">
|
||||
<div class="title">Severity Variants</div>
|
||||
<div class="grid">
|
||||
<upladmin-option-card variant="critical" icon="lucide:AlertCircle" label="Critical" description="Severe impact"></upladmin-option-card>
|
||||
<upladmin-option-card variant="major" icon="lucide:AlertTriangle" label="Major" description="Significant impact"></upladmin-option-card>
|
||||
<upladmin-option-card variant="minor" icon="lucide:Info" label="Minor" description="Limited impact"></upladmin-option-card>
|
||||
<upladmin-option-card variant="maintenance" icon="lucide:Wrench" label="Maintenance" description="Scheduled work" selected></upladmin-option-card>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<div class="title">Status Variants</div>
|
||||
<div class="grid">
|
||||
<upladmin-option-card variant="investigating" icon="lucide:Search" label="Investigating" description="Looking into it"></upladmin-option-card>
|
||||
<upladmin-option-card variant="identified" icon="lucide:Target" label="Identified" description="Root cause found"></upladmin-option-card>
|
||||
<upladmin-option-card variant="monitoring" icon="lucide:Eye" label="Monitoring" description="Fix applied" selected></upladmin-option-card>
|
||||
<upladmin-option-card variant="resolved" icon="lucide:CheckCircle" label="Resolved" description="Issue fixed"></upladmin-option-card>
|
||||
<upladmin-option-card variant="postmortem" icon="lucide:FileText" label="Postmortem" description="Analysis complete"></upladmin-option-card>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
document.getElementById('theme-toggle').addEventListener('click', () => {
|
||||
const isDark = document.documentElement.style.colorScheme === 'dark';
|
||||
document.documentElement.style.colorScheme = isDark ? 'light' : 'dark';
|
||||
document.body.style.background = isDark ? '#ffffff' : '#09090b';
|
||||
document.body.style.color = isDark ? '#09090b' : '#fafafa';
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
BIN
.playwright-mcp/dark-mode-emulated.png
Normal file
BIN
.playwright-mcp/dark-mode-emulated.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 30 KiB |
BIN
.playwright-mcp/dark-mode-option-cards.png
Normal file
BIN
.playwright-mcp/dark-mode-option-cards.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 30 KiB |
BIN
.playwright-mcp/light-mode-option-cards.png
Normal file
BIN
.playwright-mcp/light-mode-option-cards.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 32 KiB |
19
changelog.md
19
changelog.md
@@ -1,5 +1,24 @@
|
||||
# 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
|
||||
|
||||
- Add new upladmin-option-card web component (implementation, index export, and demo).
|
||||
- Replace inline option/status card markup with <upladmin-option-card> in incident-form and incident-update.
|
||||
- Refactor upladmin-monitor-form: subscribe/unsubscribe multitoggle change subjects, handle lifecycle (firstUpdated/disconnected), and adjust event handlers (@newValue/@change usage).
|
||||
- Swap hardcoded color tokens for cssManager.bdTheme for better light/dark theming; add dark-mode media tweak for filter select.
|
||||
- Add Playwright snapshot images (.playwright-mcp) and a detailed readme.plan.md describing interface migration tasks.
|
||||
|
||||
## 2025-12-24 - 1.1.0 - feat(monitor)
|
||||
add extended monitor statuses, check configuration, status overrides/paused indicators, and incident update templates
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@uptime.link/statuspage-admin",
|
||||
"version": "1.1.0",
|
||||
"version": "1.3.0",
|
||||
"private": false,
|
||||
"description": "Admin components for managing UptimeLink status pages, monitors, and incidents.",
|
||||
"main": "dist_ts_web/index.js",
|
||||
@@ -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",
|
||||
|
||||
19
pnpm-lock.yaml
generated
19
pnpm-lock.yaml
generated
@@ -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': {}
|
||||
|
||||
|
||||
396
readme.plan.md
Normal file
396
readme.plan.md
Normal file
@@ -0,0 +1,396 @@
|
||||
# Plan: Migrate Shared Interfaces to @uptime.link/interfaces
|
||||
|
||||
## Overview
|
||||
|
||||
Move shared type definitions from `catalog_admin/ts_web/interfaces/` to the canonical `../interfaces` package (`@uptime.link/interfaces`) to ensure consistency across all uptime.link packages.
|
||||
|
||||
## Decisions (Resolved)
|
||||
|
||||
1. **Check Config**: Use base type + discriminated union variants (elegant, type-safe)
|
||||
2. **Incident Status**: Create unified type in shared package, migrate both packages
|
||||
3. **Form Interfaces**: Keep local in catalog_admin (UI-specific)
|
||||
4. **Versioning**: Manual releases - will notify when ready to publish
|
||||
|
||||
---
|
||||
|
||||
## Task 1: Add Base Types to Shared Package
|
||||
|
||||
**File: `../interfaces/ts/data/types.ts`** (new)
|
||||
|
||||
```typescript
|
||||
// Status types for monitors/services
|
||||
export type TStatusType =
|
||||
| 'operational'
|
||||
| 'degraded'
|
||||
| 'partial_outage'
|
||||
| 'major_outage'
|
||||
| 'maintenance'
|
||||
| 'initializing'
|
||||
| 'error'
|
||||
| 'paused';
|
||||
|
||||
// Check types (discriminant values)
|
||||
export type TCheckType = 'assumption' | 'function' | 'pwa' | 'pagerank';
|
||||
|
||||
// Status mode for monitors
|
||||
export type TStatusMode = 'auto' | 'manual';
|
||||
|
||||
// Incident severity
|
||||
export type TIncidentSeverity = 'critical' | 'major' | 'minor' | 'maintenance';
|
||||
|
||||
// Incident status (unified)
|
||||
export type TIncidentStatus =
|
||||
| 'investigating'
|
||||
| 'identified'
|
||||
| 'monitoring'
|
||||
| 'resolved'
|
||||
| 'postmortem';
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 2: Refactor Check Interfaces with Base + Variants
|
||||
|
||||
**File: `../interfaces/ts/data/checks/index.ts`** (refactor)
|
||||
|
||||
```typescript
|
||||
import { TStatusType, TCheckType } from '../types.js';
|
||||
|
||||
// ============================================
|
||||
// Base Interface
|
||||
// ============================================
|
||||
|
||||
export interface ICheckBase {
|
||||
id: string;
|
||||
name: string;
|
||||
description?: string;
|
||||
enabled: boolean;
|
||||
intervalMs?: number;
|
||||
lastRun?: number;
|
||||
lastResult?: 'success' | 'failure' | 'pending';
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// Discriminated Variants
|
||||
// ============================================
|
||||
|
||||
export interface IAssumptionCheck extends ICheckBase {
|
||||
checkType: 'assumption';
|
||||
assumedStatus: TStatusType;
|
||||
}
|
||||
|
||||
export interface IFunctionCheck extends ICheckBase {
|
||||
checkType: 'function';
|
||||
functionUrl: string;
|
||||
expectedStatusCode?: number;
|
||||
timeoutMs?: number;
|
||||
headers?: Record<string, string>;
|
||||
}
|
||||
|
||||
export interface IPwaCheck extends ICheckBase {
|
||||
checkType: 'pwa';
|
||||
targetUrl: string;
|
||||
lighthouseThreshold?: number;
|
||||
categories?: ('performance' | 'accessibility' | 'best-practices' | 'seo')[];
|
||||
}
|
||||
|
||||
export interface IPageRankCheck extends ICheckBase {
|
||||
checkType: 'pagerank';
|
||||
targetUrl: string;
|
||||
minimumRank?: number;
|
||||
searchEngine?: 'google' | 'bing';
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// Union Type (for UI and generic handling)
|
||||
// ============================================
|
||||
|
||||
export type TCheck =
|
||||
| IAssumptionCheck
|
||||
| IFunctionCheck
|
||||
| IPwaCheck
|
||||
| IPageRankCheck;
|
||||
|
||||
// ============================================
|
||||
// Check Collection
|
||||
// ============================================
|
||||
|
||||
export interface ICheckCollection {
|
||||
id: string;
|
||||
name: string;
|
||||
checks: TCheck[];
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 3: Create Unified Incident Interface
|
||||
|
||||
**File: `../interfaces/ts/data/incident.ts`** (refactor)
|
||||
|
||||
```typescript
|
||||
import { TIncidentSeverity, TIncidentStatus } from './types.js';
|
||||
|
||||
export interface IIncidentUpdate {
|
||||
id: string;
|
||||
incidentId: string;
|
||||
status: TIncidentStatus;
|
||||
message: string;
|
||||
createdAt: number;
|
||||
createdBy?: string;
|
||||
}
|
||||
|
||||
export interface IIncident {
|
||||
id: string;
|
||||
title: string;
|
||||
description: string;
|
||||
severity: TIncidentSeverity;
|
||||
status: TIncidentStatus;
|
||||
|
||||
// Affected services
|
||||
affectedServiceIds: string[];
|
||||
|
||||
// Timeline
|
||||
createdAt: number;
|
||||
updatedAt: number;
|
||||
resolvedAt?: number;
|
||||
|
||||
// Updates history
|
||||
updates: IIncidentUpdate[];
|
||||
|
||||
// Metadata
|
||||
createdBy?: string;
|
||||
isScheduled?: boolean;
|
||||
scheduledStartTime?: number;
|
||||
scheduledEndTime?: number;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 4: Add Service Status Interface
|
||||
|
||||
**File: `../interfaces/ts/data/servicestatus.ts`** (new)
|
||||
|
||||
```typescript
|
||||
import { TStatusType, TStatusMode, TCheckType } from './types.js';
|
||||
|
||||
export interface IServiceStatus {
|
||||
id: string;
|
||||
name: string;
|
||||
displayName: string;
|
||||
description?: string;
|
||||
|
||||
// Current state
|
||||
currentStatus: TStatusType;
|
||||
lastChecked: number;
|
||||
responseTime: number;
|
||||
|
||||
// Uptime metrics
|
||||
uptime30d: number;
|
||||
uptime90d: number;
|
||||
|
||||
// Organization
|
||||
category?: string;
|
||||
dependencies?: string[];
|
||||
|
||||
// Status management
|
||||
statusMode: TStatusMode;
|
||||
manualStatus?: TStatusType;
|
||||
paused: boolean;
|
||||
|
||||
// Check configuration (references check collection)
|
||||
checkType?: TCheckType;
|
||||
checkCollectionId?: string;
|
||||
intervalMs?: number;
|
||||
}
|
||||
|
||||
export interface IStatusHistoryPoint {
|
||||
timestamp: number;
|
||||
status: TStatusType;
|
||||
responseTime?: number;
|
||||
}
|
||||
|
||||
export interface IOverallStatus {
|
||||
status: TStatusType;
|
||||
message?: string;
|
||||
lastUpdated: number;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 5: Add Status Page Config
|
||||
|
||||
**File: `../interfaces/ts/data/statuspageconfig.ts`** (new)
|
||||
|
||||
```typescript
|
||||
import { IOverallStatus } from './servicestatus.js';
|
||||
|
||||
export interface IStatusPageConfig {
|
||||
id: string;
|
||||
name: string;
|
||||
slug: string;
|
||||
|
||||
// Branding
|
||||
logoUrl?: string;
|
||||
faviconUrl?: string;
|
||||
primaryColor?: string;
|
||||
|
||||
// Content
|
||||
headerTitle: string;
|
||||
headerDescription?: string;
|
||||
|
||||
// Features
|
||||
showHistoricalUptime: boolean;
|
||||
showResponseTime: boolean;
|
||||
showSubscribeButton: boolean;
|
||||
|
||||
// Service grouping
|
||||
serviceGroups: IServiceGroup[];
|
||||
|
||||
// Overall status override
|
||||
overallStatus?: IOverallStatus;
|
||||
}
|
||||
|
||||
export interface IServiceGroup {
|
||||
id: string;
|
||||
name: string;
|
||||
description?: string;
|
||||
serviceIds: string[];
|
||||
expanded: boolean;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 6: Update Shared Package Exports
|
||||
|
||||
**File: `../interfaces/ts/data/index.ts`** (update)
|
||||
|
||||
```typescript
|
||||
export * from './types.js';
|
||||
export * from './checks/index.js';
|
||||
export * from './incident.js';
|
||||
export * from './servicestatus.js';
|
||||
export * from './statuspageconfig.js';
|
||||
// ... existing exports
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 7: Update catalog_admin Interfaces
|
||||
|
||||
**File: `catalog_admin/ts_web/interfaces/index.ts`** (refactor)
|
||||
|
||||
```typescript
|
||||
// Re-export shared types from @uptime.link/interfaces
|
||||
export {
|
||||
// Types
|
||||
TStatusType,
|
||||
TCheckType,
|
||||
TStatusMode,
|
||||
TIncidentSeverity,
|
||||
TIncidentStatus,
|
||||
|
||||
// Check interfaces
|
||||
ICheckBase,
|
||||
IAssumptionCheck,
|
||||
IFunctionCheck,
|
||||
IPwaCheck,
|
||||
IPageRankCheck,
|
||||
TCheck,
|
||||
ICheckCollection,
|
||||
|
||||
// Incident interfaces
|
||||
IIncident,
|
||||
IIncidentUpdate,
|
||||
|
||||
// Service/Status interfaces
|
||||
IServiceStatus,
|
||||
IStatusHistoryPoint,
|
||||
IOverallStatus,
|
||||
IStatusPageConfig,
|
||||
IServiceGroup,
|
||||
} from '@uptime.link/interfaces';
|
||||
|
||||
// ============================================
|
||||
// Form Interfaces (UI-specific, kept local)
|
||||
// ============================================
|
||||
|
||||
import type { TStatusType, TCheckType, TStatusMode, TIncidentSeverity } from '@uptime.link/interfaces';
|
||||
|
||||
export interface IMonitorFormData {
|
||||
name: string;
|
||||
displayName: string;
|
||||
description?: string;
|
||||
category?: string;
|
||||
checkType: TCheckType;
|
||||
intervalMs: number;
|
||||
statusMode: TStatusMode;
|
||||
paused: boolean;
|
||||
// Check-specific fields (form flattens the discriminated union)
|
||||
assumedStatus?: TStatusType;
|
||||
functionUrl?: string;
|
||||
expectedStatusCode?: number;
|
||||
targetUrl?: string;
|
||||
lighthouseThreshold?: number;
|
||||
minimumRank?: number;
|
||||
}
|
||||
|
||||
export interface IIncidentFormData {
|
||||
title: string;
|
||||
description: string;
|
||||
severity: TIncidentSeverity;
|
||||
affectedServiceIds: string[];
|
||||
isScheduled: boolean;
|
||||
scheduledStartTime?: number;
|
||||
scheduledEndTime?: number;
|
||||
}
|
||||
|
||||
export interface IIncidentUpdateFormData {
|
||||
status: string;
|
||||
message: string;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 8: Update Component Imports
|
||||
|
||||
Scan and update all components that import from local interfaces to ensure they work with the new types. Key files:
|
||||
|
||||
- `ts_web/elements/upladmin-monitor-form/upladmin-monitor-form.ts`
|
||||
- `ts_web/elements/upladmin-monitor-list/upladmin-monitor-list.ts`
|
||||
- `ts_web/elements/upladmin-incident-form/upladmin-incident-form.ts`
|
||||
- `ts_web/elements/upladmin-incident-list/upladmin-incident-list.ts`
|
||||
- `ts_web/elements/upladmin-incident-update/upladmin-incident-update.ts`
|
||||
|
||||
---
|
||||
|
||||
## Task 9: Build and Test
|
||||
|
||||
1. Build `../interfaces`: `cd ../interfaces && pnpm build`
|
||||
2. **Notify for release** of `@uptime.link/interfaces`
|
||||
3. Update dependency: `pnpm update @uptime.link/interfaces`
|
||||
4. Build catalog_admin: `pnpm build`
|
||||
5. Verify no type errors
|
||||
6. Test UI components manually
|
||||
|
||||
---
|
||||
|
||||
## Files Summary
|
||||
|
||||
### New files in `../interfaces`:
|
||||
- `ts/data/types.ts`
|
||||
- `ts/data/servicestatus.ts`
|
||||
- `ts/data/statuspageconfig.ts`
|
||||
|
||||
### Modified files in `../interfaces`:
|
||||
- `ts/data/checks/index.ts` (refactor to base + variants)
|
||||
- `ts/data/incident.ts` (unified interface)
|
||||
- `ts/data/index.ts` (add exports)
|
||||
|
||||
### Modified files in `catalog_admin`:
|
||||
- `ts_web/interfaces/index.ts` (re-export from shared + local form types)
|
||||
- Component files (if import paths change)
|
||||
@@ -3,6 +3,6 @@
|
||||
*/
|
||||
export const commitinfo = {
|
||||
name: '@uptime.link/statuspage-admin',
|
||||
version: '1.1.0',
|
||||
version: '1.3.0',
|
||||
description: 'Admin components for managing UptimeLink status pages, monitors, and incidents.'
|
||||
}
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
// Shared components
|
||||
export * from './upladmin-option-card/index.js';
|
||||
|
||||
// Monitor components
|
||||
export * from './upladmin-monitor-form/index.js';
|
||||
export * from './upladmin-monitor-list/index.js';
|
||||
|
||||
@@ -168,51 +168,6 @@ export class UpladminIncidentForm extends DeesElement {
|
||||
gap: ${unsafeCSS(sharedStyles.spacing.sm)};
|
||||
}
|
||||
|
||||
.option-card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 18px 14px;
|
||||
background: ${sharedStyles.colors.background.primary};
|
||||
border: 2px solid ${sharedStyles.colors.border.default};
|
||||
border-radius: ${unsafeCSS(sharedStyles.borderRadius.base)};
|
||||
cursor: pointer;
|
||||
transition: all ${unsafeCSS(sharedStyles.durations.fast)} ${unsafeCSS(sharedStyles.easings.default)};
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.option-card:hover {
|
||||
border-color: ${sharedStyles.colors.border.strong};
|
||||
background: ${sharedStyles.colors.background.muted};
|
||||
}
|
||||
|
||||
.option-card.selected {
|
||||
border-color: ${sharedStyles.colors.accent.primary};
|
||||
background: ${cssManager.bdTheme('rgba(59, 130, 246, 0.05)', 'rgba(96, 165, 250, 0.1)')};
|
||||
}
|
||||
|
||||
.option-card input {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.option-label {
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
color: ${sharedStyles.colors.text.primary};
|
||||
}
|
||||
|
||||
.option-desc {
|
||||
font-size: 11px;
|
||||
color: ${sharedStyles.colors.text.muted};
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
.severity-critical dees-icon { --icon-color: ${sharedStyles.colors.status.majorOutage}; }
|
||||
.severity-major dees-icon { --icon-color: ${sharedStyles.colors.status.partialOutage}; }
|
||||
.severity-minor dees-icon { --icon-color: ${sharedStyles.colors.status.degraded}; }
|
||||
.severity-maintenance dees-icon { --icon-color: ${sharedStyles.colors.status.maintenance}; }
|
||||
|
||||
.field-label {
|
||||
display: block;
|
||||
font-size: 13px;
|
||||
@@ -363,20 +318,14 @@ export class UpladminIncidentForm extends DeesElement {
|
||||
<label class="field-label required">Severity</label>
|
||||
<div class="option-grid">
|
||||
${severityOptions.map(opt => html`
|
||||
<label
|
||||
class="option-card severity-${opt.value} ${this.formData.severity === opt.value ? 'selected' : ''}"
|
||||
@click="${() => this.handleSeverityChange(opt.value)}"
|
||||
>
|
||||
<input
|
||||
type="radio"
|
||||
name="severity"
|
||||
value="${opt.value}"
|
||||
?checked="${this.formData.severity === opt.value}"
|
||||
/>
|
||||
<dees-icon .icon=${this.severityIcons[opt.value]} .iconSize=${24}></dees-icon>
|
||||
<span class="option-label">${opt.label}</span>
|
||||
<span class="option-desc">${opt.desc}</span>
|
||||
</label>
|
||||
<upladmin-option-card
|
||||
.variant=${opt.value}
|
||||
.icon=${this.severityIcons[opt.value]}
|
||||
.label=${opt.label}
|
||||
.description=${opt.desc}
|
||||
?selected=${this.formData.severity === opt.value}
|
||||
@click=${() => this.handleSeverityChange(opt.value)}
|
||||
></upladmin-option-card>
|
||||
`)}
|
||||
</div>
|
||||
</div>
|
||||
@@ -385,19 +334,13 @@ export class UpladminIncidentForm extends DeesElement {
|
||||
<label class="field-label required">Status</label>
|
||||
<div class="option-grid">
|
||||
${statusOptions.map(opt => html`
|
||||
<label
|
||||
class="option-card ${this.formData.status === opt.value ? 'selected' : ''}"
|
||||
@click="${() => this.handleStatusChange(opt.value)}"
|
||||
>
|
||||
<input
|
||||
type="radio"
|
||||
name="status"
|
||||
value="${opt.value}"
|
||||
?checked="${this.formData.status === opt.value}"
|
||||
/>
|
||||
<dees-icon .icon=${this.statusIcons[opt.value]} .iconSize=${24}></dees-icon>
|
||||
<span class="option-label">${opt.label}</span>
|
||||
</label>
|
||||
<upladmin-option-card
|
||||
.variant=${opt.value}
|
||||
.icon=${this.statusIcons[opt.value]}
|
||||
.label=${opt.label}
|
||||
?selected=${this.formData.status === opt.value}
|
||||
@click=${() => this.handleStatusChange(opt.value)}
|
||||
></upladmin-option-card>
|
||||
`)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -260,8 +260,8 @@ export class UpladminIncidentList extends DeesElement {
|
||||
|
||||
.incident-status.postmortem {
|
||||
background: ${cssManager.bdTheme('rgba(168, 85, 247, 0.1)', 'rgba(168, 85, 247, 0.2)')};
|
||||
color: #a855f7;
|
||||
--icon-color: #a855f7;
|
||||
color: ${cssManager.bdTheme('#9333ea', '#a855f7')};
|
||||
--icon-color: ${cssManager.bdTheme('#9333ea', '#a855f7')};
|
||||
}
|
||||
|
||||
.incident-meta {
|
||||
|
||||
@@ -157,52 +157,6 @@ export class UpladminIncidentUpdate extends DeesElement {
|
||||
gap: ${unsafeCSS(sharedStyles.spacing.sm)};
|
||||
}
|
||||
|
||||
.status-option {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 18px 14px;
|
||||
background: ${sharedStyles.colors.background.primary};
|
||||
border: 2px solid ${sharedStyles.colors.border.default};
|
||||
border-radius: ${unsafeCSS(sharedStyles.borderRadius.base)};
|
||||
cursor: pointer;
|
||||
transition: all ${unsafeCSS(sharedStyles.durations.fast)} ${unsafeCSS(sharedStyles.easings.default)};
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.status-option:hover {
|
||||
border-color: ${sharedStyles.colors.border.strong};
|
||||
background: ${sharedStyles.colors.background.muted};
|
||||
}
|
||||
|
||||
.status-option.selected {
|
||||
border-color: ${sharedStyles.colors.accent.primary};
|
||||
background: ${cssManager.bdTheme('rgba(59, 130, 246, 0.05)', 'rgba(96, 165, 250, 0.1)')};
|
||||
}
|
||||
|
||||
.status-option input {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.status-option.investigating dees-icon { --icon-color: ${sharedStyles.colors.status.partialOutage}; }
|
||||
.status-option.identified dees-icon { --icon-color: ${sharedStyles.colors.status.degraded}; }
|
||||
.status-option.monitoring dees-icon { --icon-color: ${sharedStyles.colors.status.maintenance}; }
|
||||
.status-option.resolved dees-icon { --icon-color: ${sharedStyles.colors.status.operational}; }
|
||||
.status-option.postmortem dees-icon { --icon-color: #a855f7; }
|
||||
|
||||
.status-label {
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
color: ${sharedStyles.colors.text.primary};
|
||||
}
|
||||
|
||||
.status-desc {
|
||||
font-size: 11px;
|
||||
color: ${sharedStyles.colors.text.muted};
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
.field-label {
|
||||
display: block;
|
||||
font-size: 13px;
|
||||
@@ -358,20 +312,14 @@ export class UpladminIncidentUpdate extends DeesElement {
|
||||
<label class="field-label required">Status</label>
|
||||
<div class="status-grid">
|
||||
${statusOptions.map(opt => html`
|
||||
<label
|
||||
class="status-option ${opt.value} ${this.formData.status === opt.value ? 'selected' : ''}"
|
||||
@click="${() => this.handleStatusChange(opt.value)}"
|
||||
>
|
||||
<input
|
||||
type="radio"
|
||||
name="status"
|
||||
value="${opt.value}"
|
||||
?checked="${this.formData.status === opt.value}"
|
||||
/>
|
||||
<dees-icon .icon=${this.statusIcons[opt.value]} .iconSize=${24}></dees-icon>
|
||||
<span class="status-label">${opt.label}</span>
|
||||
<span class="status-desc">${opt.desc}</span>
|
||||
</label>
|
||||
<upladmin-option-card
|
||||
.variant=${opt.value}
|
||||
.icon=${this.statusIcons[opt.value]}
|
||||
.label=${opt.label}
|
||||
.description=${opt.desc}
|
||||
?selected=${this.formData.status === opt.value}
|
||||
@click=${() => this.handleStatusChange(opt.value)}
|
||||
></upladmin-option-card>
|
||||
`)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
unsafeCSS,
|
||||
state,
|
||||
} from '@design.estate/dees-element';
|
||||
import type { DeesInputMultitoggle } from '@design.estate/dees-catalog';
|
||||
import * as sharedStyles from '../../styles/shared.styles.js';
|
||||
import type { IMonitorFormData, IServiceStatus, ICheckConfig, TStatusType, TCheckType, TStatusMode } from '../../interfaces/index.js';
|
||||
import { demoFunc } from './upladmin-monitor-form.demo.js';
|
||||
@@ -60,6 +61,14 @@ export class UpladminMonitorForm extends DeesElement {
|
||||
pagerank: 'PageRank',
|
||||
};
|
||||
|
||||
private getCheckTypeLabel(): string {
|
||||
return this.checkTypeLabels[this.formData.checkType] || 'Assumption';
|
||||
}
|
||||
|
||||
private getStatusModeLabel(): string {
|
||||
return this.formData.statusMode === 'auto' ? 'Auto' : 'Manual';
|
||||
}
|
||||
|
||||
private intervalOptions = [
|
||||
{ value: 60000, label: '1 min' },
|
||||
{ value: 300000, label: '5 min' },
|
||||
@@ -183,6 +192,8 @@ export class UpladminMonitorForm extends DeesElement {
|
||||
`
|
||||
];
|
||||
|
||||
private subscriptions: Array<{ unsubscribe: () => void }> = [];
|
||||
|
||||
async connectedCallback() {
|
||||
await super.connectedCallback();
|
||||
if (this.monitor) {
|
||||
@@ -190,10 +201,47 @@ export class UpladminMonitorForm extends DeesElement {
|
||||
}
|
||||
}
|
||||
|
||||
async disconnectedCallback() {
|
||||
await super.disconnectedCallback();
|
||||
this.subscriptions.forEach(sub => sub.unsubscribe());
|
||||
this.subscriptions = [];
|
||||
}
|
||||
|
||||
async firstUpdated() {
|
||||
await this.updateComplete;
|
||||
this.setupMultitoggleSubscriptions();
|
||||
}
|
||||
|
||||
updated(changedProperties: Map<string, unknown>) {
|
||||
if (changedProperties.has('monitor') && this.monitor) {
|
||||
this.formData = { ...this.monitor };
|
||||
}
|
||||
// Re-setup subscriptions after each render in case elements changed
|
||||
this.setupMultitoggleSubscriptions();
|
||||
}
|
||||
|
||||
private subscribedElements = new WeakSet<Element>();
|
||||
|
||||
private setupMultitoggleSubscriptions() {
|
||||
// Subscribe to check type toggle (only if not already subscribed)
|
||||
const checkTypeToggle = this.shadowRoot?.querySelector('#checkTypeToggle') as DeesInputMultitoggle;
|
||||
if (checkTypeToggle && !this.subscribedElements.has(checkTypeToggle)) {
|
||||
this.subscribedElements.add(checkTypeToggle);
|
||||
const sub = checkTypeToggle.changeSubject.subscribe(() => {
|
||||
this.handleCheckTypeChange(checkTypeToggle.selectedOption);
|
||||
});
|
||||
this.subscriptions.push(sub);
|
||||
}
|
||||
|
||||
// Subscribe to status mode toggle (only if not already subscribed)
|
||||
const statusModeToggle = this.shadowRoot?.querySelector('#statusModeToggle') as DeesInputMultitoggle;
|
||||
if (statusModeToggle && !this.subscribedElements.has(statusModeToggle)) {
|
||||
this.subscribedElements.add(statusModeToggle);
|
||||
const sub = statusModeToggle.changeSubject.subscribe(() => {
|
||||
this.handleStatusModeChange(statusModeToggle.selectedOption);
|
||||
});
|
||||
this.subscriptions.push(sub);
|
||||
}
|
||||
}
|
||||
|
||||
public render(): TemplateResult {
|
||||
@@ -266,10 +314,10 @@ export class UpladminMonitorForm extends DeesElement {
|
||||
<!-- Check Type -->
|
||||
<div class="form-section">
|
||||
<dees-input-multitoggle
|
||||
id="checkTypeToggle"
|
||||
.label=${'Check Type'}
|
||||
.options=${Object.values(this.checkTypeLabels)}
|
||||
.selectedOption=${this.checkTypeLabels[this.formData.checkType]}
|
||||
@changeSubject=${(e: CustomEvent) => this.handleCheckTypeChange(e.detail.selectedOption)}
|
||||
.options=${['Assumption', 'Function', 'PWA', 'PageRank']}
|
||||
.selectedOption=${this.getCheckTypeLabel()}
|
||||
></dees-input-multitoggle>
|
||||
</div>
|
||||
|
||||
@@ -293,18 +341,18 @@ export class UpladminMonitorForm extends DeesElement {
|
||||
.label=${'Pause Monitor'}
|
||||
.description=${'When paused, status will show as "paused" and checks won\'t run'}
|
||||
.value=${this.formData.paused}
|
||||
@changeSubject=${(e: CustomEvent) => this.updateField('paused', e.detail.value)}
|
||||
@newValue=${(e: CustomEvent) => this.updateField('paused', e.detail)}
|
||||
></dees-input-checkbox>
|
||||
|
||||
<!-- Status Override (Edit mode only) -->
|
||||
${isEdit ? html`
|
||||
<div class="form-section">
|
||||
<dees-input-multitoggle
|
||||
id="statusModeToggle"
|
||||
.label=${'Status Mode'}
|
||||
.description=${'Auto uses check results, Manual lets you override'}
|
||||
.options=${['Auto', 'Manual']}
|
||||
.selectedOption=${this.formData.statusMode === 'auto' ? 'Auto' : 'Manual'}
|
||||
@changeSubject=${(e: CustomEvent) => this.handleStatusModeChange(e.detail.selectedOption)}
|
||||
.selectedOption=${this.getStatusModeLabel()}
|
||||
></dees-input-multitoggle>
|
||||
|
||||
${this.formData.statusMode === 'manual' ? html`
|
||||
@@ -319,7 +367,7 @@ export class UpladminMonitorForm extends DeesElement {
|
||||
]}
|
||||
.selectedOption=${this.formData.manualStatus || 'operational'}
|
||||
.direction=${'horizontal'}
|
||||
@changeSubject=${(e: CustomEvent) => this.updateField('manualStatus', e.detail.selectedOption)}
|
||||
@change=${(e: CustomEvent) => this.updateField('manualStatus', e.detail.value)}
|
||||
></dees-input-radiogroup>
|
||||
` : ''}
|
||||
</div>
|
||||
@@ -435,12 +483,12 @@ export class UpladminMonitorForm extends DeesElement {
|
||||
<dees-input-checkbox
|
||||
.label=${'Google'}
|
||||
.value=${config.checkGoogle !== false}
|
||||
@changeSubject=${(e: CustomEvent) => this.updateCheckConfig('checkGoogle', e.detail.value)}
|
||||
@newValue=${(e: CustomEvent) => this.updateCheckConfig('checkGoogle', e.detail)}
|
||||
></dees-input-checkbox>
|
||||
<dees-input-checkbox
|
||||
.label=${'Bing'}
|
||||
.value=${config.checkBing === true}
|
||||
@changeSubject=${(e: CustomEvent) => this.updateCheckConfig('checkBing', e.detail.value)}
|
||||
@newValue=${(e: CustomEvent) => this.updateCheckConfig('checkBing', e.detail)}
|
||||
></dees-input-checkbox>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
|
||||
@@ -128,6 +128,12 @@ export class UpladminMonitorList extends DeesElement {
|
||||
box-shadow: 0 0 0 3px ${cssManager.bdTheme('rgba(59, 130, 246, 0.1)', 'rgba(96, 165, 250, 0.15)')};
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
.filter-select {
|
||||
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='%23a1a1aa' d='M2.5 4.5L6 8l3.5-3.5'/%3E%3C/svg%3E");
|
||||
}
|
||||
}
|
||||
|
||||
.table-container {
|
||||
padding: 0;
|
||||
}
|
||||
@@ -189,20 +195,20 @@ export class UpladminMonitorList extends DeesElement {
|
||||
|
||||
.status-badge.initializing {
|
||||
background: ${cssManager.bdTheme('rgba(107, 114, 128, 0.1)', 'rgba(107, 114, 128, 0.15)')};
|
||||
color: #6b7280;
|
||||
--icon-color: #6b7280;
|
||||
color: ${cssManager.bdTheme('#6b7280', '#9ca3af')};
|
||||
--icon-color: ${cssManager.bdTheme('#6b7280', '#9ca3af')};
|
||||
}
|
||||
|
||||
.status-badge.error {
|
||||
background: ${cssManager.bdTheme('rgba(220, 38, 38, 0.1)', 'rgba(220, 38, 38, 0.15)')};
|
||||
color: #dc2626;
|
||||
--icon-color: #dc2626;
|
||||
color: ${cssManager.bdTheme('#dc2626', '#f87171')};
|
||||
--icon-color: ${cssManager.bdTheme('#dc2626', '#f87171')};
|
||||
}
|
||||
|
||||
.status-badge.paused {
|
||||
background: ${cssManager.bdTheme('rgba(139, 92, 246, 0.1)', 'rgba(139, 92, 246, 0.15)')};
|
||||
color: #8b5cf6;
|
||||
--icon-color: #8b5cf6;
|
||||
color: ${cssManager.bdTheme('#8b5cf6', '#a78bfa')};
|
||||
--icon-color: ${cssManager.bdTheme('#8b5cf6', '#a78bfa')};
|
||||
}
|
||||
|
||||
/* Status indicators for override and pause */
|
||||
@@ -224,12 +230,12 @@ export class UpladminMonitorList extends DeesElement {
|
||||
|
||||
.status-indicator.override {
|
||||
background: ${cssManager.bdTheme('rgba(234, 179, 8, 0.15)', 'rgba(234, 179, 8, 0.2)')};
|
||||
--icon-color: #eab308;
|
||||
--icon-color: ${cssManager.bdTheme('#d97706', '#fbbf24')};
|
||||
}
|
||||
|
||||
.status-indicator.paused {
|
||||
background: ${cssManager.bdTheme('rgba(139, 92, 246, 0.15)', 'rgba(139, 92, 246, 0.2)')};
|
||||
--icon-color: #8b5cf6;
|
||||
--icon-color: ${cssManager.bdTheme('#8b5cf6', '#a78bfa')};
|
||||
}
|
||||
|
||||
.monitor-info {
|
||||
|
||||
1
ts_web/elements/upladmin-option-card/index.ts
Normal file
1
ts_web/elements/upladmin-option-card/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from './upladmin-option-card.js';
|
||||
@@ -0,0 +1,118 @@
|
||||
import { html, css, cssManager } from '@design.estate/dees-element';
|
||||
import type { UpladminOptionCard } from './upladmin-option-card.js';
|
||||
|
||||
export const demoFunc = () => html`
|
||||
<style>
|
||||
.demo-container {
|
||||
padding: 24px;
|
||||
background: ${cssManager.bdTheme('#fafafa', '#0a0a0a')};
|
||||
min-height: 100vh;
|
||||
}
|
||||
.demo-section {
|
||||
margin-bottom: 32px;
|
||||
}
|
||||
.demo-title {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: ${cssManager.bdTheme('#09090b', '#fafafa')};
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
.demo-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(130px, 1fr));
|
||||
gap: 12px;
|
||||
}
|
||||
</style>
|
||||
<div class="demo-container">
|
||||
<div class="demo-section">
|
||||
<div class="demo-title">Severity Variants</div>
|
||||
<div class="demo-grid">
|
||||
<upladmin-option-card
|
||||
variant="critical"
|
||||
icon="lucide:AlertCircle"
|
||||
label="Critical"
|
||||
description="Severe impact"
|
||||
></upladmin-option-card>
|
||||
<upladmin-option-card
|
||||
variant="major"
|
||||
icon="lucide:AlertTriangle"
|
||||
label="Major"
|
||||
description="Significant impact"
|
||||
></upladmin-option-card>
|
||||
<upladmin-option-card
|
||||
variant="minor"
|
||||
icon="lucide:Info"
|
||||
label="Minor"
|
||||
description="Limited impact"
|
||||
></upladmin-option-card>
|
||||
<upladmin-option-card
|
||||
variant="maintenance"
|
||||
icon="lucide:Wrench"
|
||||
label="Maintenance"
|
||||
description="Scheduled work"
|
||||
selected
|
||||
></upladmin-option-card>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="demo-section">
|
||||
<div class="demo-title">Status Variants</div>
|
||||
<div class="demo-grid">
|
||||
<upladmin-option-card
|
||||
variant="investigating"
|
||||
icon="lucide:Search"
|
||||
label="Investigating"
|
||||
description="Looking into it"
|
||||
></upladmin-option-card>
|
||||
<upladmin-option-card
|
||||
variant="identified"
|
||||
icon="lucide:Target"
|
||||
label="Identified"
|
||||
description="Root cause found"
|
||||
></upladmin-option-card>
|
||||
<upladmin-option-card
|
||||
variant="monitoring"
|
||||
icon="lucide:Eye"
|
||||
label="Monitoring"
|
||||
description="Fix applied"
|
||||
selected
|
||||
></upladmin-option-card>
|
||||
<upladmin-option-card
|
||||
variant="resolved"
|
||||
icon="lucide:CheckCircle"
|
||||
label="Resolved"
|
||||
description="Issue fixed"
|
||||
></upladmin-option-card>
|
||||
<upladmin-option-card
|
||||
variant="postmortem"
|
||||
icon="lucide:FileText"
|
||||
label="Postmortem"
|
||||
description="Analysis complete"
|
||||
></upladmin-option-card>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="demo-section">
|
||||
<div class="demo-title">States</div>
|
||||
<div class="demo-grid">
|
||||
<upladmin-option-card
|
||||
variant="primary"
|
||||
icon="lucide:Star"
|
||||
label="Normal"
|
||||
></upladmin-option-card>
|
||||
<upladmin-option-card
|
||||
variant="primary"
|
||||
icon="lucide:Star"
|
||||
label="Selected"
|
||||
selected
|
||||
></upladmin-option-card>
|
||||
<upladmin-option-card
|
||||
variant="primary"
|
||||
icon="lucide:Star"
|
||||
label="Disabled"
|
||||
disabled
|
||||
></upladmin-option-card>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
178
ts_web/elements/upladmin-option-card/upladmin-option-card.ts
Normal file
178
ts_web/elements/upladmin-option-card/upladmin-option-card.ts
Normal file
@@ -0,0 +1,178 @@
|
||||
import {
|
||||
DeesElement,
|
||||
property,
|
||||
html,
|
||||
customElement,
|
||||
type TemplateResult,
|
||||
css,
|
||||
cssManager,
|
||||
unsafeCSS,
|
||||
} from '@design.estate/dees-element';
|
||||
import * as sharedStyles from '../../styles/shared.styles.js';
|
||||
import { demoFunc } from './upladmin-option-card.demo.js';
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'upladmin-option-card': UpladminOptionCard;
|
||||
}
|
||||
}
|
||||
|
||||
export type TOptionVariant =
|
||||
// Severity variants
|
||||
| 'critical' | 'major' | 'minor' | 'maintenance'
|
||||
// Status variants
|
||||
| 'investigating' | 'identified' | 'monitoring' | 'resolved' | 'postmortem'
|
||||
// Generic variants
|
||||
| 'default' | 'primary' | 'success' | 'warning' | 'danger' | 'info';
|
||||
|
||||
@customElement('upladmin-option-card')
|
||||
export class UpladminOptionCard extends DeesElement {
|
||||
public static demo = demoFunc;
|
||||
|
||||
@property({ type: String })
|
||||
accessor icon: string = '';
|
||||
|
||||
@property({ type: String })
|
||||
accessor label: string = '';
|
||||
|
||||
@property({ type: String })
|
||||
accessor description: string = '';
|
||||
|
||||
@property({ type: String, reflect: true })
|
||||
accessor variant: TOptionVariant = 'default';
|
||||
|
||||
@property({ type: Boolean, reflect: true })
|
||||
accessor selected: boolean = false;
|
||||
|
||||
@property({ type: Boolean, reflect: true })
|
||||
accessor disabled: boolean = false;
|
||||
|
||||
public static styles = [
|
||||
cssManager.defaultStyles,
|
||||
css`
|
||||
:host {
|
||||
display: block;
|
||||
font-family: ${unsafeCSS(sharedStyles.fonts.base)};
|
||||
}
|
||||
|
||||
.option-card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 18px 14px;
|
||||
background: ${cssManager.bdTheme('#ffffff', '#09090b')};
|
||||
border: 2px solid ${cssManager.bdTheme('#e4e4e7', '#27272a')};
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
transition: all 0.1s ease;
|
||||
text-align: center;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.option-card:hover:not(.disabled) {
|
||||
border-color: ${cssManager.bdTheme('#d4d4d8', '#3f3f46')};
|
||||
background: ${cssManager.bdTheme('#f4f4f5', '#18181b')};
|
||||
}
|
||||
|
||||
:host([selected]) .option-card {
|
||||
border-color: ${cssManager.bdTheme('#3b82f6', '#60a5fa')};
|
||||
background: ${cssManager.bdTheme('rgba(59, 130, 246, 0.05)', 'rgba(96, 165, 250, 0.1)')};
|
||||
}
|
||||
|
||||
:host([disabled]) .option-card {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.option-label {
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
color: ${cssManager.bdTheme('#09090b', '#fafafa')};
|
||||
}
|
||||
|
||||
.option-desc {
|
||||
font-size: 11px;
|
||||
color: ${cssManager.bdTheme('#a1a1aa', '#71717a')};
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
/* Variant icon colors - all using bdTheme for proper light/dark support */
|
||||
|
||||
/* Severity variants */
|
||||
:host([variant="critical"]) dees-icon {
|
||||
--icon-color: ${cssManager.bdTheme('#dc2626', '#f87171')};
|
||||
}
|
||||
:host([variant="major"]) dees-icon {
|
||||
--icon-color: ${cssManager.bdTheme('#ea580c', '#fb923c')};
|
||||
}
|
||||
:host([variant="minor"]) dees-icon {
|
||||
--icon-color: ${cssManager.bdTheme('#ca8a04', '#fbbf24')};
|
||||
}
|
||||
:host([variant="maintenance"]) dees-icon {
|
||||
--icon-color: ${cssManager.bdTheme('#2563eb', '#60a5fa')};
|
||||
}
|
||||
|
||||
/* Status variants */
|
||||
:host([variant="investigating"]) dees-icon {
|
||||
--icon-color: ${cssManager.bdTheme('#ea580c', '#fb923c')};
|
||||
}
|
||||
:host([variant="identified"]) dees-icon {
|
||||
--icon-color: ${cssManager.bdTheme('#ca8a04', '#fbbf24')};
|
||||
}
|
||||
:host([variant="monitoring"]) dees-icon {
|
||||
--icon-color: ${cssManager.bdTheme('#2563eb', '#60a5fa')};
|
||||
}
|
||||
:host([variant="resolved"]) dees-icon {
|
||||
--icon-color: ${cssManager.bdTheme('#16a34a', '#22c55e')};
|
||||
}
|
||||
:host([variant="postmortem"]) dees-icon {
|
||||
--icon-color: ${cssManager.bdTheme('#9333ea', '#a855f7')};
|
||||
}
|
||||
|
||||
/* Generic variants */
|
||||
:host([variant="default"]) dees-icon {
|
||||
--icon-color: ${cssManager.bdTheme('#71717a', '#a1a1aa')};
|
||||
}
|
||||
:host([variant="primary"]) dees-icon {
|
||||
--icon-color: ${cssManager.bdTheme('#3b82f6', '#60a5fa')};
|
||||
}
|
||||
:host([variant="success"]) dees-icon {
|
||||
--icon-color: ${cssManager.bdTheme('#16a34a', '#22c55e')};
|
||||
}
|
||||
:host([variant="warning"]) dees-icon {
|
||||
--icon-color: ${cssManager.bdTheme('#ca8a04', '#fbbf24')};
|
||||
}
|
||||
:host([variant="danger"]) dees-icon {
|
||||
--icon-color: ${cssManager.bdTheme('#dc2626', '#f87171')};
|
||||
}
|
||||
:host([variant="info"]) dees-icon {
|
||||
--icon-color: ${cssManager.bdTheme('#2563eb', '#60a5fa')};
|
||||
}
|
||||
|
||||
dees-icon {
|
||||
color: var(--icon-color);
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
||||
public render(): TemplateResult {
|
||||
return html`
|
||||
<div class="option-card ${this.disabled ? 'disabled' : ''}" @click="${this.handleClick}">
|
||||
${this.icon ? html`<dees-icon .icon=${this.icon} .iconSize=${24}></dees-icon>` : ''}
|
||||
${this.label ? html`<span class="option-label">${this.label}</span>` : ''}
|
||||
${this.description ? html`<span class="option-desc">${this.description}</span>` : ''}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
private handleClick() {
|
||||
if (this.disabled) return;
|
||||
|
||||
this.dispatchEvent(new CustomEvent('select', {
|
||||
detail: { selected: !this.selected },
|
||||
bubbles: true,
|
||||
composed: true,
|
||||
}));
|
||||
}
|
||||
}
|
||||
@@ -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`
|
||||
<div class="config-container">
|
||||
<nav class="config-nav">
|
||||
${sections.map(section => html`
|
||||
<button
|
||||
class="nav-item ${this.activeSection === section.id ? 'active' : ''}"
|
||||
@click="${() => this.activeSection = section.id}"
|
||||
>
|
||||
<dees-icon .icon=${section.icon} .iconSize=${18}></dees-icon>
|
||||
<span>${section.label}</span>
|
||||
</button>
|
||||
`)}
|
||||
</nav>
|
||||
|
||||
<div class="config-content">
|
||||
<div class="content-header">
|
||||
<div>
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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`
|
||||
<style>
|
||||
.demo-page-wrapper {
|
||||
min-height: 100vh;
|
||||
background: ${cssManager.bdTheme('#fafafa', '#0a0a0a')};
|
||||
padding: 24px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="demo-page-wrapper">
|
||||
<dees-demowrapper
|
||||
.runAfterRender=${async (wrapperElement: any) => {
|
||||
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;
|
||||
}}
|
||||
>
|
||||
<upladmin-statuspage-config></upladmin-statuspage-config>
|
||||
</dees-demowrapper>
|
||||
</div>
|
||||
`;
|
||||
|
||||
export const adminpageConfigWhitelabel = () => html`
|
||||
<style>
|
||||
.demo-page-wrapper {
|
||||
min-height: 100vh;
|
||||
background: ${cssManager.bdTheme('#fafafa', '#0a0a0a')};
|
||||
padding: 24px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="demo-page-wrapper">
|
||||
<dees-demowrapper
|
||||
.runAfterRender=${async (wrapperElement: any) => {
|
||||
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;
|
||||
}}
|
||||
>
|
||||
<upladmin-statuspage-config></upladmin-statuspage-config>
|
||||
</dees-demowrapper>
|
||||
</div>
|
||||
`;
|
||||
@@ -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`
|
||||
<style>
|
||||
.demo-page-wrapper {
|
||||
min-height: 100vh;
|
||||
background: ${cssManager.bdTheme('#fafafa', '#0a0a0a')};
|
||||
padding: 24px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="demo-page-wrapper">
|
||||
<dees-demowrapper
|
||||
.runAfterRender=${async (wrapperElement: any) => {
|
||||
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;
|
||||
}}
|
||||
>
|
||||
<upladmin-dashboard></upladmin-dashboard>
|
||||
</dees-demowrapper>
|
||||
</div>
|
||||
`;
|
||||
@@ -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`
|
||||
<style>
|
||||
.demo-page-wrapper {
|
||||
min-height: 100vh;
|
||||
background: ${cssManager.bdTheme('#fafafa', '#0a0a0a')};
|
||||
padding: 24px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="demo-page-wrapper">
|
||||
<dees-demowrapper
|
||||
.runAfterRender=${async (wrapperElement: any) => {
|
||||
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;
|
||||
}}
|
||||
>
|
||||
<upladmin-incident-list></upladmin-incident-list>
|
||||
</dees-demowrapper>
|
||||
</div>
|
||||
`;
|
||||
|
||||
export const adminpageIncidentForm = () => html`
|
||||
<style>
|
||||
.demo-page-wrapper {
|
||||
min-height: 100vh;
|
||||
background: ${cssManager.bdTheme('#fafafa', '#0a0a0a')};
|
||||
padding: 24px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="demo-page-wrapper">
|
||||
<dees-demowrapper
|
||||
.runAfterRender=${async (wrapperElement: any) => {
|
||||
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;
|
||||
}}
|
||||
>
|
||||
<upladmin-incident-form></upladmin-incident-form>
|
||||
</dees-demowrapper>
|
||||
</div>
|
||||
`;
|
||||
|
||||
export const adminpageIncidentUpdate = () => html`
|
||||
<style>
|
||||
.demo-page-wrapper {
|
||||
min-height: 100vh;
|
||||
background: ${cssManager.bdTheme('#fafafa', '#0a0a0a')};
|
||||
padding: 24px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="demo-page-wrapper">
|
||||
<dees-demowrapper
|
||||
.runAfterRender=${async (wrapperElement: any) => {
|
||||
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' },
|
||||
],
|
||||
};
|
||||
}}
|
||||
>
|
||||
<upladmin-incident-update></upladmin-incident-update>
|
||||
</dees-demowrapper>
|
||||
</div>
|
||||
`;
|
||||
@@ -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`
|
||||
<style>
|
||||
.demo-page-wrapper {
|
||||
min-height: 100vh;
|
||||
background: ${cssManager.bdTheme('#fafafa', '#0a0a0a')};
|
||||
padding: 24px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="demo-page-wrapper">
|
||||
<dees-demowrapper
|
||||
.runAfterRender=${async (wrapperElement: any) => {
|
||||
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;
|
||||
}}
|
||||
>
|
||||
<upladmin-monitor-list></upladmin-monitor-list>
|
||||
</dees-demowrapper>
|
||||
</div>
|
||||
`;
|
||||
|
||||
export const adminpageMonitorForm = () => html`
|
||||
<style>
|
||||
.demo-page-wrapper {
|
||||
min-height: 100vh;
|
||||
background: ${cssManager.bdTheme('#fafafa', '#0a0a0a')};
|
||||
padding: 24px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="demo-page-wrapper">
|
||||
<dees-demowrapper
|
||||
.runAfterRender=${async (wrapperElement: any) => {
|
||||
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'];
|
||||
}}
|
||||
>
|
||||
<upladmin-monitor-form></upladmin-monitor-form>
|
||||
</dees-demowrapper>
|
||||
</div>
|
||||
`;
|
||||
|
||||
export const adminpageMonitorFormEdit = () => html`
|
||||
<style>
|
||||
.demo-page-wrapper {
|
||||
min-height: 100vh;
|
||||
background: ${cssManager.bdTheme('#fafafa', '#0a0a0a')};
|
||||
padding: 24px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="demo-page-wrapper">
|
||||
<dees-demowrapper
|
||||
.runAfterRender=${async (wrapperElement: any) => {
|
||||
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',
|
||||
};
|
||||
}}
|
||||
>
|
||||
<upladmin-monitor-form></upladmin-monitor-form>
|
||||
</dees-demowrapper>
|
||||
</div>
|
||||
`;
|
||||
@@ -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';
|
||||
|
||||
@@ -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: {
|
||||
|
||||
4
ts_web/views/index.ts
Normal file
4
ts_web/views/index.ts
Normal file
@@ -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';
|
||||
124
ts_web/views/upladmin-config-view/upladmin-config-view.ts
Normal file
124
ts_web/views/upladmin-config-view/upladmin-config-view.ts
Normal file
@@ -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<string, string>;
|
||||
}
|
||||
|
||||
interface IViewLifecycle {
|
||||
onActivate?: (context: IViewActivationContext) => void | Promise<void>;
|
||||
onDeactivate?: () => void | Promise<void>;
|
||||
}
|
||||
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<void> {
|
||||
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`
|
||||
<upladmin-statuspage-config
|
||||
.config=${adminState.config || {}}
|
||||
.activeSection=${this.activeSection}
|
||||
.loading=${this.loading}
|
||||
@configSave=${this.handleConfigSave}
|
||||
></upladmin-statuspage-config>
|
||||
`;
|
||||
}
|
||||
}
|
||||
@@ -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<string, string>;
|
||||
}
|
||||
|
||||
interface IViewLifecycle {
|
||||
onActivate?: (context: IViewActivationContext) => void | Promise<void>;
|
||||
onDeactivate?: () => void | Promise<void>;
|
||||
}
|
||||
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<void> {
|
||||
// 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`
|
||||
<upladmin-dashboard
|
||||
.monitors=${adminState.monitors}
|
||||
.incidents=${adminState.incidents}
|
||||
.loading=${this.loading}
|
||||
></upladmin-dashboard>
|
||||
`;
|
||||
}
|
||||
}
|
||||
292
ts_web/views/upladmin-incidents-view/upladmin-incidents-view.ts
Normal file
292
ts_web/views/upladmin-incidents-view/upladmin-incidents-view.ts
Normal file
@@ -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<string, string>;
|
||||
}
|
||||
|
||||
interface IViewLifecycle {
|
||||
onActivate?: (context: IViewActivationContext) => void | Promise<void>;
|
||||
onDeactivate?: () => void | Promise<void>;
|
||||
}
|
||||
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<void> {
|
||||
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`
|
||||
<upladmin-incident-update
|
||||
.incident=${incident}
|
||||
.loading=${this.loading}
|
||||
@updateSave=${this.handleUpdateSave}
|
||||
@cancel=${this.handleCancel}
|
||||
></upladmin-incident-update>
|
||||
`;
|
||||
}
|
||||
|
||||
if (this.currentMode === 'form') {
|
||||
const incident = this.selectedIncidentId
|
||||
? adminState.incidents.find((i) => i.id === this.selectedIncidentId)
|
||||
: null;
|
||||
|
||||
return html`
|
||||
<upladmin-incident-form
|
||||
.incident=${incident
|
||||
? {
|
||||
id: incident.id,
|
||||
title: incident.title,
|
||||
severity: incident.severity,
|
||||
status: incident.status,
|
||||
affectedServices: incident.affectedServices,
|
||||
impact: incident.impact,
|
||||
rootCause: incident.rootCause,
|
||||
resolution: incident.resolution,
|
||||
}
|
||||
: null}
|
||||
.availableServices=${adminState.monitors}
|
||||
.loading=${this.loading}
|
||||
@incidentSave=${this.handleIncidentSave}
|
||||
@cancel=${this.handleCancel}
|
||||
></upladmin-incident-form>
|
||||
`;
|
||||
}
|
||||
|
||||
return html`
|
||||
<upladmin-incident-list
|
||||
.incidents=${this.filteredIncidents}
|
||||
.loading=${this.loading}
|
||||
@incidentAdd=${() => this.showForm(null)}
|
||||
@incidentEdit=${this.handleIncidentEdit}
|
||||
@incidentAddUpdate=${this.handleIncidentAddUpdate}
|
||||
></upladmin-incident-list>
|
||||
`;
|
||||
}
|
||||
}
|
||||
244
ts_web/views/upladmin-monitors-view/upladmin-monitors-view.ts
Normal file
244
ts_web/views/upladmin-monitors-view/upladmin-monitors-view.ts
Normal file
@@ -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<string, string>;
|
||||
}
|
||||
|
||||
interface IViewLifecycle {
|
||||
onActivate?: (context: IViewActivationContext) => void | Promise<void>;
|
||||
onDeactivate?: () => void | Promise<void>;
|
||||
}
|
||||
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<void> {
|
||||
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<string>();
|
||||
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`
|
||||
<upladmin-monitor-form
|
||||
.monitor=${monitor
|
||||
? {
|
||||
id: monitor.id,
|
||||
name: monitor.name,
|
||||
displayName: monitor.displayName,
|
||||
description: monitor.description,
|
||||
category: monitor.category,
|
||||
dependencies: monitor.dependencies,
|
||||
statusMode: monitor.statusMode || 'auto',
|
||||
manualStatus: monitor.manualStatus,
|
||||
paused: monitor.paused || false,
|
||||
checkType: monitor.checkType || 'assumption',
|
||||
checkConfig: monitor.checkConfig || { domain: '' },
|
||||
intervalMs: monitor.intervalMs || 60000,
|
||||
}
|
||||
: null}
|
||||
.availableMonitors=${adminState.monitors}
|
||||
.categories=${this.getCategories()}
|
||||
.loading=${this.loading}
|
||||
@monitorSave=${this.handleMonitorSave}
|
||||
@cancel=${this.handleCancel}
|
||||
></upladmin-monitor-form>
|
||||
`;
|
||||
}
|
||||
|
||||
return html`
|
||||
<upladmin-monitor-list
|
||||
.monitors=${this.filteredMonitors}
|
||||
.loading=${this.loading}
|
||||
@monitorAdd=${() => this.showForm(null)}
|
||||
@monitorEdit=${this.handleMonitorEdit}
|
||||
></upladmin-monitor-list>
|
||||
`;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user