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
This commit is contained in:
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 |
@@ -1,5 +1,14 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## 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)
|
## 2025-12-24 - 1.1.0 - feat(monitor)
|
||||||
add extended monitor statuses, check configuration, status overrides/paused indicators, and incident update templates
|
add extended monitor statuses, check configuration, status overrides/paused indicators, and incident update templates
|
||||||
|
|
||||||
|
|||||||
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 = {
|
export const commitinfo = {
|
||||||
name: '@uptime.link/statuspage-admin',
|
name: '@uptime.link/statuspage-admin',
|
||||||
version: '1.1.0',
|
version: '1.2.0',
|
||||||
description: 'Admin components for managing UptimeLink status pages, monitors, and incidents.'
|
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
|
// Monitor components
|
||||||
export * from './upladmin-monitor-form/index.js';
|
export * from './upladmin-monitor-form/index.js';
|
||||||
export * from './upladmin-monitor-list/index.js';
|
export * from './upladmin-monitor-list/index.js';
|
||||||
|
|||||||
@@ -168,51 +168,6 @@ export class UpladminIncidentForm extends DeesElement {
|
|||||||
gap: ${unsafeCSS(sharedStyles.spacing.sm)};
|
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 {
|
.field-label {
|
||||||
display: block;
|
display: block;
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
@@ -363,20 +318,14 @@ export class UpladminIncidentForm extends DeesElement {
|
|||||||
<label class="field-label required">Severity</label>
|
<label class="field-label required">Severity</label>
|
||||||
<div class="option-grid">
|
<div class="option-grid">
|
||||||
${severityOptions.map(opt => html`
|
${severityOptions.map(opt => html`
|
||||||
<label
|
<upladmin-option-card
|
||||||
class="option-card severity-${opt.value} ${this.formData.severity === opt.value ? 'selected' : ''}"
|
.variant=${opt.value}
|
||||||
@click="${() => this.handleSeverityChange(opt.value)}"
|
.icon=${this.severityIcons[opt.value]}
|
||||||
>
|
.label=${opt.label}
|
||||||
<input
|
.description=${opt.desc}
|
||||||
type="radio"
|
?selected=${this.formData.severity === opt.value}
|
||||||
name="severity"
|
@click=${() => this.handleSeverityChange(opt.value)}
|
||||||
value="${opt.value}"
|
></upladmin-option-card>
|
||||||
?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>
|
|
||||||
`)}
|
`)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -385,19 +334,13 @@ export class UpladminIncidentForm extends DeesElement {
|
|||||||
<label class="field-label required">Status</label>
|
<label class="field-label required">Status</label>
|
||||||
<div class="option-grid">
|
<div class="option-grid">
|
||||||
${statusOptions.map(opt => html`
|
${statusOptions.map(opt => html`
|
||||||
<label
|
<upladmin-option-card
|
||||||
class="option-card ${this.formData.status === opt.value ? 'selected' : ''}"
|
.variant=${opt.value}
|
||||||
@click="${() => this.handleStatusChange(opt.value)}"
|
.icon=${this.statusIcons[opt.value]}
|
||||||
>
|
.label=${opt.label}
|
||||||
<input
|
?selected=${this.formData.status === opt.value}
|
||||||
type="radio"
|
@click=${() => this.handleStatusChange(opt.value)}
|
||||||
name="status"
|
></upladmin-option-card>
|
||||||
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>
|
|
||||||
`)}
|
`)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -260,8 +260,8 @@ export class UpladminIncidentList extends DeesElement {
|
|||||||
|
|
||||||
.incident-status.postmortem {
|
.incident-status.postmortem {
|
||||||
background: ${cssManager.bdTheme('rgba(168, 85, 247, 0.1)', 'rgba(168, 85, 247, 0.2)')};
|
background: ${cssManager.bdTheme('rgba(168, 85, 247, 0.1)', 'rgba(168, 85, 247, 0.2)')};
|
||||||
color: #a855f7;
|
color: ${cssManager.bdTheme('#9333ea', '#a855f7')};
|
||||||
--icon-color: #a855f7;
|
--icon-color: ${cssManager.bdTheme('#9333ea', '#a855f7')};
|
||||||
}
|
}
|
||||||
|
|
||||||
.incident-meta {
|
.incident-meta {
|
||||||
|
|||||||
@@ -157,52 +157,6 @@ export class UpladminIncidentUpdate extends DeesElement {
|
|||||||
gap: ${unsafeCSS(sharedStyles.spacing.sm)};
|
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 {
|
.field-label {
|
||||||
display: block;
|
display: block;
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
@@ -358,20 +312,14 @@ export class UpladminIncidentUpdate extends DeesElement {
|
|||||||
<label class="field-label required">Status</label>
|
<label class="field-label required">Status</label>
|
||||||
<div class="status-grid">
|
<div class="status-grid">
|
||||||
${statusOptions.map(opt => html`
|
${statusOptions.map(opt => html`
|
||||||
<label
|
<upladmin-option-card
|
||||||
class="status-option ${opt.value} ${this.formData.status === opt.value ? 'selected' : ''}"
|
.variant=${opt.value}
|
||||||
@click="${() => this.handleStatusChange(opt.value)}"
|
.icon=${this.statusIcons[opt.value]}
|
||||||
>
|
.label=${opt.label}
|
||||||
<input
|
.description=${opt.desc}
|
||||||
type="radio"
|
?selected=${this.formData.status === opt.value}
|
||||||
name="status"
|
@click=${() => this.handleStatusChange(opt.value)}
|
||||||
value="${opt.value}"
|
></upladmin-option-card>
|
||||||
?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>
|
|
||||||
`)}
|
`)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import {
|
|||||||
unsafeCSS,
|
unsafeCSS,
|
||||||
state,
|
state,
|
||||||
} from '@design.estate/dees-element';
|
} from '@design.estate/dees-element';
|
||||||
|
import type { DeesInputMultitoggle } from '@design.estate/dees-catalog';
|
||||||
import * as sharedStyles from '../../styles/shared.styles.js';
|
import * as sharedStyles from '../../styles/shared.styles.js';
|
||||||
import type { IMonitorFormData, IServiceStatus, ICheckConfig, TStatusType, TCheckType, TStatusMode } from '../../interfaces/index.js';
|
import type { IMonitorFormData, IServiceStatus, ICheckConfig, TStatusType, TCheckType, TStatusMode } from '../../interfaces/index.js';
|
||||||
import { demoFunc } from './upladmin-monitor-form.demo.js';
|
import { demoFunc } from './upladmin-monitor-form.demo.js';
|
||||||
@@ -60,6 +61,14 @@ export class UpladminMonitorForm extends DeesElement {
|
|||||||
pagerank: 'PageRank',
|
pagerank: 'PageRank',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
private getCheckTypeLabel(): string {
|
||||||
|
return this.checkTypeLabels[this.formData.checkType] || 'Assumption';
|
||||||
|
}
|
||||||
|
|
||||||
|
private getStatusModeLabel(): string {
|
||||||
|
return this.formData.statusMode === 'auto' ? 'Auto' : 'Manual';
|
||||||
|
}
|
||||||
|
|
||||||
private intervalOptions = [
|
private intervalOptions = [
|
||||||
{ value: 60000, label: '1 min' },
|
{ value: 60000, label: '1 min' },
|
||||||
{ value: 300000, label: '5 min' },
|
{ value: 300000, label: '5 min' },
|
||||||
@@ -183,6 +192,8 @@ export class UpladminMonitorForm extends DeesElement {
|
|||||||
`
|
`
|
||||||
];
|
];
|
||||||
|
|
||||||
|
private subscriptions: Array<{ unsubscribe: () => void }> = [];
|
||||||
|
|
||||||
async connectedCallback() {
|
async connectedCallback() {
|
||||||
await super.connectedCallback();
|
await super.connectedCallback();
|
||||||
if (this.monitor) {
|
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>) {
|
updated(changedProperties: Map<string, unknown>) {
|
||||||
if (changedProperties.has('monitor') && this.monitor) {
|
if (changedProperties.has('monitor') && this.monitor) {
|
||||||
this.formData = { ...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 {
|
public render(): TemplateResult {
|
||||||
@@ -266,10 +314,10 @@ export class UpladminMonitorForm extends DeesElement {
|
|||||||
<!-- Check Type -->
|
<!-- Check Type -->
|
||||||
<div class="form-section">
|
<div class="form-section">
|
||||||
<dees-input-multitoggle
|
<dees-input-multitoggle
|
||||||
|
id="checkTypeToggle"
|
||||||
.label=${'Check Type'}
|
.label=${'Check Type'}
|
||||||
.options=${Object.values(this.checkTypeLabels)}
|
.options=${['Assumption', 'Function', 'PWA', 'PageRank']}
|
||||||
.selectedOption=${this.checkTypeLabels[this.formData.checkType]}
|
.selectedOption=${this.getCheckTypeLabel()}
|
||||||
@changeSubject=${(e: CustomEvent) => this.handleCheckTypeChange(e.detail.selectedOption)}
|
|
||||||
></dees-input-multitoggle>
|
></dees-input-multitoggle>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -293,18 +341,18 @@ export class UpladminMonitorForm extends DeesElement {
|
|||||||
.label=${'Pause Monitor'}
|
.label=${'Pause Monitor'}
|
||||||
.description=${'When paused, status will show as "paused" and checks won\'t run'}
|
.description=${'When paused, status will show as "paused" and checks won\'t run'}
|
||||||
.value=${this.formData.paused}
|
.value=${this.formData.paused}
|
||||||
@changeSubject=${(e: CustomEvent) => this.updateField('paused', e.detail.value)}
|
@newValue=${(e: CustomEvent) => this.updateField('paused', e.detail)}
|
||||||
></dees-input-checkbox>
|
></dees-input-checkbox>
|
||||||
|
|
||||||
<!-- Status Override (Edit mode only) -->
|
<!-- Status Override (Edit mode only) -->
|
||||||
${isEdit ? html`
|
${isEdit ? html`
|
||||||
<div class="form-section">
|
<div class="form-section">
|
||||||
<dees-input-multitoggle
|
<dees-input-multitoggle
|
||||||
|
id="statusModeToggle"
|
||||||
.label=${'Status Mode'}
|
.label=${'Status Mode'}
|
||||||
.description=${'Auto uses check results, Manual lets you override'}
|
.description=${'Auto uses check results, Manual lets you override'}
|
||||||
.options=${['Auto', 'Manual']}
|
.options=${['Auto', 'Manual']}
|
||||||
.selectedOption=${this.formData.statusMode === 'auto' ? 'Auto' : 'Manual'}
|
.selectedOption=${this.getStatusModeLabel()}
|
||||||
@changeSubject=${(e: CustomEvent) => this.handleStatusModeChange(e.detail.selectedOption)}
|
|
||||||
></dees-input-multitoggle>
|
></dees-input-multitoggle>
|
||||||
|
|
||||||
${this.formData.statusMode === 'manual' ? html`
|
${this.formData.statusMode === 'manual' ? html`
|
||||||
@@ -319,7 +367,7 @@ export class UpladminMonitorForm extends DeesElement {
|
|||||||
]}
|
]}
|
||||||
.selectedOption=${this.formData.manualStatus || 'operational'}
|
.selectedOption=${this.formData.manualStatus || 'operational'}
|
||||||
.direction=${'horizontal'}
|
.direction=${'horizontal'}
|
||||||
@changeSubject=${(e: CustomEvent) => this.updateField('manualStatus', e.detail.selectedOption)}
|
@change=${(e: CustomEvent) => this.updateField('manualStatus', e.detail.value)}
|
||||||
></dees-input-radiogroup>
|
></dees-input-radiogroup>
|
||||||
` : ''}
|
` : ''}
|
||||||
</div>
|
</div>
|
||||||
@@ -435,12 +483,12 @@ export class UpladminMonitorForm extends DeesElement {
|
|||||||
<dees-input-checkbox
|
<dees-input-checkbox
|
||||||
.label=${'Google'}
|
.label=${'Google'}
|
||||||
.value=${config.checkGoogle !== false}
|
.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>
|
||||||
<dees-input-checkbox
|
<dees-input-checkbox
|
||||||
.label=${'Bing'}
|
.label=${'Bing'}
|
||||||
.value=${config.checkBing === true}
|
.value=${config.checkBing === true}
|
||||||
@changeSubject=${(e: CustomEvent) => this.updateCheckConfig('checkBing', e.detail.value)}
|
@newValue=${(e: CustomEvent) => this.updateCheckConfig('checkBing', e.detail)}
|
||||||
></dees-input-checkbox>
|
></dees-input-checkbox>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-row">
|
<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)')};
|
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 {
|
.table-container {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
@@ -189,20 +195,20 @@ export class UpladminMonitorList extends DeesElement {
|
|||||||
|
|
||||||
.status-badge.initializing {
|
.status-badge.initializing {
|
||||||
background: ${cssManager.bdTheme('rgba(107, 114, 128, 0.1)', 'rgba(107, 114, 128, 0.15)')};
|
background: ${cssManager.bdTheme('rgba(107, 114, 128, 0.1)', 'rgba(107, 114, 128, 0.15)')};
|
||||||
color: #6b7280;
|
color: ${cssManager.bdTheme('#6b7280', '#9ca3af')};
|
||||||
--icon-color: #6b7280;
|
--icon-color: ${cssManager.bdTheme('#6b7280', '#9ca3af')};
|
||||||
}
|
}
|
||||||
|
|
||||||
.status-badge.error {
|
.status-badge.error {
|
||||||
background: ${cssManager.bdTheme('rgba(220, 38, 38, 0.1)', 'rgba(220, 38, 38, 0.15)')};
|
background: ${cssManager.bdTheme('rgba(220, 38, 38, 0.1)', 'rgba(220, 38, 38, 0.15)')};
|
||||||
color: #dc2626;
|
color: ${cssManager.bdTheme('#dc2626', '#f87171')};
|
||||||
--icon-color: #dc2626;
|
--icon-color: ${cssManager.bdTheme('#dc2626', '#f87171')};
|
||||||
}
|
}
|
||||||
|
|
||||||
.status-badge.paused {
|
.status-badge.paused {
|
||||||
background: ${cssManager.bdTheme('rgba(139, 92, 246, 0.1)', 'rgba(139, 92, 246, 0.15)')};
|
background: ${cssManager.bdTheme('rgba(139, 92, 246, 0.1)', 'rgba(139, 92, 246, 0.15)')};
|
||||||
color: #8b5cf6;
|
color: ${cssManager.bdTheme('#8b5cf6', '#a78bfa')};
|
||||||
--icon-color: #8b5cf6;
|
--icon-color: ${cssManager.bdTheme('#8b5cf6', '#a78bfa')};
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Status indicators for override and pause */
|
/* Status indicators for override and pause */
|
||||||
@@ -224,12 +230,12 @@ export class UpladminMonitorList extends DeesElement {
|
|||||||
|
|
||||||
.status-indicator.override {
|
.status-indicator.override {
|
||||||
background: ${cssManager.bdTheme('rgba(234, 179, 8, 0.15)', 'rgba(234, 179, 8, 0.2)')};
|
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 {
|
.status-indicator.paused {
|
||||||
background: ${cssManager.bdTheme('rgba(139, 92, 246, 0.15)', 'rgba(139, 92, 246, 0.2)')};
|
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 {
|
.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,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user