import * as appstate from '../appstate.js';
import * as interfaces from '../../dist_ts_interfaces/index.js';
import { viewHostCss } from './shared/css.js';
import { type IStatsTile } from '@design.estate/dees-catalog';
import {
DeesElement,
css,
cssManager,
customElement,
html,
state,
type TemplateResult,
} from '@design.estate/dees-element';
@customElement('ops-view-routes')
export class OpsViewRoutes extends DeesElement {
@state() accessor routeState: appstate.IRouteManagementState = {
mergedRoutes: [],
warnings: [],
apiTokens: [],
isLoading: false,
error: null,
lastUpdated: 0,
};
constructor() {
super();
const sub = appstate.routeManagementStatePart
.select((s) => s)
.subscribe((routeState) => {
this.routeState = routeState;
});
this.rxSubscriptions.push(sub);
// Re-fetch routes when user logs in (fixes race condition where
// the view is created before authentication completes)
const loginSub = appstate.loginStatePart
.select((s) => s.isLoggedIn)
.subscribe((isLoggedIn) => {
if (isLoggedIn) {
appstate.routeManagementStatePart.dispatchAction(appstate.fetchMergedRoutesAction, null);
}
});
this.rxSubscriptions.push(loginSub);
}
public static styles = [
cssManager.defaultStyles,
viewHostCss,
css`
.routesContainer {
display: flex;
flex-direction: column;
gap: 24px;
}
.warnings-bar {
background: ${cssManager.bdTheme('rgba(255, 170, 0, 0.08)', 'rgba(255, 170, 0, 0.1)')};
border: 1px solid ${cssManager.bdTheme('rgba(255, 170, 0, 0.25)', 'rgba(255, 170, 0, 0.3)')};
border-radius: 8px;
padding: 12px 16px;
}
.warning-item {
display: flex;
align-items: center;
gap: 8px;
padding: 4px 0;
font-size: 13px;
color: ${cssManager.bdTheme('#b45309', '#fa0')};
}
.warning-icon {
flex-shrink: 0;
}
.empty-state {
text-align: center;
padding: 48px 24px;
color: ${cssManager.bdTheme('#6b7280', '#666')};
}
.empty-state p {
margin: 8px 0;
}
`,
];
public render(): TemplateResult {
const { mergedRoutes, warnings } = this.routeState;
const hardcodedCount = mergedRoutes.filter((mr) => mr.source === 'hardcoded').length;
const programmaticCount = mergedRoutes.filter((mr) => mr.source === 'programmatic').length;
const disabledCount = mergedRoutes.filter((mr) => !mr.enabled).length;
const statsTiles: IStatsTile[] = [
{
id: 'totalRoutes',
title: 'Total Routes',
type: 'number',
value: mergedRoutes.length,
icon: 'lucide:route',
description: 'All configured routes',
color: '#3b82f6',
},
{
id: 'hardcoded',
title: 'Hardcoded',
type: 'number',
value: hardcodedCount,
icon: 'lucide:lock',
description: 'Routes from constructor config',
color: '#8b5cf6',
},
{
id: 'programmatic',
title: 'Programmatic',
type: 'number',
value: programmaticCount,
icon: 'lucide:code',
description: 'Routes added via API',
color: '#0ea5e9',
},
{
id: 'disabled',
title: 'Disabled',
type: 'number',
value: disabledCount,
icon: 'lucide:pauseCircle',
description: 'Currently disabled routes',
color: disabledCount > 0 ? '#ef4444' : '#6b7280',
},
];
// Map merged routes to sz-route-list-view format
const szRoutes = mergedRoutes.map((mr) => {
const tags = [...(mr.route.tags || [])];
tags.push(mr.source);
if (!mr.enabled) tags.push('disabled');
if (mr.overridden) tags.push('overridden');
return {
...mr.route,
enabled: mr.enabled,
tags,
id: mr.storedRouteId || mr.route.name || undefined,
};
});
return html`
No routes configured
Add a programmatic route or check your constructor configuration.
Source: hardcoded
Status: ${merged.enabled ? 'Enabled' : 'Disabled (overridden)'}
Hardcoded routes cannot be edited or deleted, but they can be disabled via an override.
Source: programmatic
Status: ${merged.enabled ? 'Enabled' : 'Disabled'}
ID: ${merged.storedRouteId}