import { DeesElement, html, customElement, type TemplateResult, css, state, property, cssManager, } from '@design.estate/dees-element'; import { type IStatsTile } from '@design.estate/dees-catalog'; import * as appstate from '../../appstate.js'; import * as interfaces from '../../../dist_ts_interfaces/index.js'; import { viewHostCss } from '../shared/css.js'; import { redirectMatchesOwner, type TRouteOwnerFilter, } from './route-view-filters.js'; declare global { interface HTMLElementTagNameMap { 'ops-view-redirects': OpsViewRedirects; } } @customElement('ops-view-redirects') export class OpsViewRedirects extends DeesElement { @property({ type: Boolean, reflect: true }) accessor embedded = false; @property({ type: String }) accessor ownerFilter: TRouteOwnerFilter = 'all'; @state() accessor routeState: appstate.IRouteManagementState = appstate.routeManagementStatePart.getState()!; constructor() { super(); const routeSub = appstate.routeManagementStatePart.select().subscribe((routeState) => { this.routeState = routeState; }); this.rxSubscriptions.push(routeSub); const loginSub = appstate.loginStatePart .select((state) => state.isLoggedIn) .subscribe((isLoggedIn) => { if (isLoggedIn) { void this.refreshData(); } }); this.rxSubscriptions.push(loginSub); } async connectedCallback() { await super.connectedCallback(); await this.refreshData(); } public static styles = [ cssManager.defaultStyles, viewHostCss, css` :host([embedded]) { margin: 0; max-width: none; padding: 0; } .redirectsContainer { display: flex; flex-direction: column; gap: 24px; } .empty-state { text-align: center; padding: 48px 24px; color: ${cssManager.bdTheme('#6b7280', '#9ca3af')}; } .empty-state p { margin: 8px 0; } `, ]; public render(): TemplateResult { const redirects = (this.routeState.httpRedirects || []).filter((redirect) => { return redirectMatchesOwner(redirect, this.routeState.mergedRoutes, this.ownerFilter); }); const activeCount = redirects.filter((redirect) => redirect.status === 'active').length; const coveredCount = redirects.filter((redirect) => redirect.status === 'covered').length; const skippedCount = redirects.filter((redirect) => redirect.status === 'skipped').length; const remoteIngressCount = redirects.filter((redirect) => redirect.remoteIngress).length; const statsTiles: IStatsTile[] = [ { id: 'totalRedirects', title: 'Total Redirects', type: 'number', value: redirects.length, icon: 'lucide:CornerDownRight', description: 'Derived HTTP to HTTPS scopes', color: '#3b82f6', }, { id: 'activeRedirects', title: 'Active', type: 'number', value: activeCount, icon: 'lucide:CircleCheck', description: 'Generated at runtime', color: '#22c55e', }, { id: 'coveredRedirects', title: 'Covered', type: 'number', value: coveredCount, icon: 'lucide:ShieldCheck', description: 'Handled by explicit HTTP routes', color: '#8b5cf6', }, { id: 'skippedRedirects', title: 'Skipped', type: 'number', value: skippedCount, icon: 'lucide:AlertTriangle', description: 'Overlaps explicit HTTP routes', color: skippedCount > 0 ? '#f59e0b' : '#6b7280', }, { id: 'remoteIngressRedirects', title: 'Remote Ingress', type: 'number', value: remoteIngressCount, icon: 'lucide:Globe', description: 'Also exposed to edge nodes', color: '#0ea5e9', }, ]; return html` ${this.embedded ? '' : html`Redirects`}
${this.embedded ? '' : html``} ${redirects.length > 0 ? html` ({ Status: this.formatStatus(redirect.status), 'Domain Pattern': redirect.domainPattern, Path: redirect.pathPattern || '*', From: this.formatHttpTemplate(redirect, 'http'), To: this.formatHttpTemplate(redirect, 'https'), Code: redirect.statusCode, Priority: redirect.priority, 'Source HTTPS Route': redirect.sourceRouteNames.join(', ') || '-', 'Covered By': redirect.coveredByRouteNames.join(', ') || '-', Notes: this.formatNotes(redirect), })} .dataActions=${this.getTableActions()} > ` : html` ({})} .dataActions=${this.getTableActions()} >

No derived redirects

Enable HTTPS routes with explicit domains to generate HTTP to HTTPS redirects.

`}
`; } private async refreshData(): Promise { await appstate.routeManagementStatePart.dispatchAction(appstate.fetchHttpRedirectsAction, null); } private getTableActions() { return this.embedded ? [] : [ { name: 'Refresh', iconName: 'lucide:refreshCw', type: ['header' as const], actionFunc: async () => this.refreshData(), }, ]; } private formatStatus(status: interfaces.data.THttpRedirectStatus): string { return status.charAt(0).toUpperCase() + status.slice(1); } private formatHttpTemplate(redirect: interfaces.data.IHttpRedirectInfo, protocol: 'http' | 'https'): string { return `${protocol}://${redirect.domainPattern}${redirect.pathPattern || '{path}'}`; } private formatNotes(redirect: interfaces.data.IHttpRedirectInfo): string { const notes = redirect.notes ? [redirect.notes] : []; if (redirect.remoteIngress) { notes.push('Remote Ingress enabled'); } return notes.join(' ') || 'Generated from HTTPS route'; } }