import * as appstate from '../appstate.js'; import * as interfaces from '../../ts_interfaces/index.js'; import { DeesElement, customElement, html, state, css, cssManager, } from '@design.estate/dees-element'; /** * Acknowledgeable inbox for platform configuration events (config conflicts, * not application runtime errors) — rendered as a sub-view of the Logs section. */ @customElement('ops-platform-events') export class OpsPlatformEvents extends DeesElement { @state() accessor eventState: appstate.IConfigEventState = appstate.configEventStatePart.getState()!; @state() accessor expandedIds: Set = new Set(); private refreshTimer: ReturnType | null = null; constructor() { super(); const subscription = appstate.configEventStatePart .select((stateArg) => stateArg) .subscribe((eventState) => { this.eventState = eventState; }); this.rxSubscriptions.push(subscription); } async connectedCallback() { super.connectedCallback(); await appstate.configEventStatePart.dispatchAction(appstate.fetchConfigEventsAction, null); this.refreshTimer = setInterval(() => { void appstate.configEventStatePart.dispatchAction(appstate.fetchConfigEventsAction, null); }, 30_000); } async disconnectedCallback() { await super.disconnectedCallback(); if (this.refreshTimer) { clearInterval(this.refreshTimer); this.refreshTimer = null; } } public static styles = [ cssManager.defaultStyles, css` :host { display: block; } .toolbar { display: flex; align-items: center; gap: 12px; margin-bottom: 12px; } .toolbar .spacer { flex: 1; } .toggleButton, .ackButton, .loadMoreButton { border: 1px solid var(--dees-color-border-default); background: transparent; color: var(--dees-color-text-secondary); border-radius: 6px; padding: 4px 10px; font-size: 12px; cursor: pointer; transition: all 0.15s ease; } .toggleButton:hover, .ackButton:hover, .loadMoreButton:hover { background: var(--dees-color-hover); color: var(--dees-color-text-primary); } .toggleButton.active { border-color: var(--dees-color-border-strong); color: var(--dees-color-text-primary); } .eventList { display: flex; flex-direction: column; gap: 8px; } .eventCard { border: 1px solid var(--dees-color-border-subtle); border-radius: 8px; background: ${cssManager.bdTheme('#ffffff', '#111113')}; } .eventHeader { display: flex; align-items: center; gap: 10px; padding: 10px 12px; cursor: pointer; } .severityBadge { font-size: 11px; font-weight: 600; padding: 2px 8px; border-radius: 4px; text-transform: uppercase; letter-spacing: 0.02em; flex-shrink: 0; } .severityBadge.error { background: ${cssManager.bdTheme('rgba(220, 38, 38, 0.1)', 'rgba(220, 38, 38, 0.2)')}; color: ${cssManager.bdTheme('#dc2626', '#f87171')}; } .severityBadge.warning { background: ${cssManager.bdTheme('rgba(217, 119, 6, 0.1)', 'rgba(217, 119, 6, 0.2)')}; color: ${cssManager.bdTheme('#d97706', '#fbbf24')}; } .categoryChip { font-size: 11px; padding: 2px 8px; border-radius: 4px; background: ${cssManager.bdTheme('#eef2ff', '#1e1b4b')}; color: ${cssManager.bdTheme('#4338ca', '#a5b4fc')}; flex-shrink: 0; } .eventTitle { flex: 1; font-size: 13px; color: var(--dees-color-text-primary); min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .eventTime { font-size: 11px; color: var(--dees-color-text-muted); flex-shrink: 0; } .ackState { font-size: 11px; color: var(--dees-color-text-muted); flex-shrink: 0; } .eventBody { border-top: 1px solid var(--dees-color-border-subtle); padding: 10px 12px; font-size: 12px; color: var(--dees-color-text-secondary); display: flex; flex-direction: column; gap: 8px; } .contextGrid { display: grid; grid-template-columns: max-content 1fr; gap: 4px 16px; font-family: monospace; font-size: 12px; } .contextKey { color: var(--dees-color-text-muted); } .contextValue { color: var(--dees-color-text-primary); word-break: break-all; } .emptyState { padding: 24px; text-align: center; font-size: 13px; color: var(--dees-color-text-muted); border: 1px dashed var(--dees-color-border-subtle); border-radius: 8px; } .pageFooter { display: flex; justify-content: center; align-items: center; gap: 12px; padding: 14px 0 4px; font-size: 12px; color: var(--dees-color-text-muted); } `, ]; public render() { const events = this.eventState.events; return html`
${events.some((eventArg) => !eventArg.acknowledgedAt) ? html` ` : ''}
${events.length === 0 ? html`
${this.eventState.showAcknowledged ? 'No platform configuration events recorded.' : 'No unacknowledged platform configuration events.'}
` : html`
${events.map((eventArg) => this.renderEvent(eventArg))}
`} ${events.length > 0 ? html` ` : ''} `; } private renderEvent(eventArg: interfaces.data.IOpsConfigEvent) { const expanded = this.expandedIds.has(eventArg.id); const contextEntries = Object.entries(eventArg.context || {}).filter(([, value]) => value); return html`
this.toggleExpanded(eventArg.id)}> ${eventArg.severity} ${eventArg.category} ${eventArg.title} ${new Date(eventArg.createdAt).toLocaleString()} ${eventArg.acknowledgedAt ? html`acknowledged by ${eventArg.acknowledgedBy || 'unknown'}` : html``}
${expanded ? html`
${eventArg.detail}
${contextEntries.length > 0 ? html`
${contextEntries.map(([key, value]) => html` ${key} ${value} `)}
` : ''}
` : ''}
`; } private toggleExpanded(id: string) { const next = new Set(this.expandedIds); if (next.has(id)) { next.delete(id); } else { next.add(id); } this.expandedIds = next; } private async setShowAcknowledged(showAcknowledged: boolean) { await appstate.configEventStatePart.dispatchAction(appstate.fetchConfigEventsAction, { showAcknowledged, }); } private async loadMore() { if (!this.eventState.nextCursor) return; await appstate.configEventStatePart.dispatchAction(appstate.fetchConfigEventsAction, { cursor: this.eventState.nextCursor, append: true, }); } private async acknowledge(ids: string[]) { await appstate.configEventStatePart.dispatchAction(appstate.acknowledgeConfigEventsAction, { ids, }); } }