import * as plugins from '../plugins.js'; import * as shared from './shared/index.js'; import * as appstate from '../appstate.js'; import { appRouter } from '../router.js'; import { DeesElement, customElement, html, state, css, cssManager, type TemplateResult, } from '@design.estate/dees-element'; import type { IConfigField, IConfigSectionAction } from '@serve.zone/catalog'; @customElement('ops-view-config') export class OpsViewConfig extends DeesElement { @state() accessor configState: appstate.IConfigState = { config: null, isLoading: false, error: null, }; constructor() { super(); const subscription = appstate.configStatePart .select((stateArg) => stateArg) .subscribe((configState) => { this.configState = configState; }); this.rxSubscriptions.push(subscription); } public static styles = [ cssManager.defaultStyles, shared.viewHostCss, css` .loadingMessage { text-align: center; padding: 40px; color: ${cssManager.bdTheme('#71717a', '#a1a1aa')}; } .errorMessage { background: ${cssManager.bdTheme('#fee2e2', 'rgba(239,68,68,0.1)')}; border: 1px solid ${cssManager.bdTheme('#fecaca', 'rgba(239,68,68,0.3)')}; border-radius: 8px; padding: 16px; color: ${cssManager.bdTheme('#dc2626', '#ef4444')}; margin: 16px 0; } `, ]; public render() { return html` Configuration ${this.configState.isLoading ? html`

Loading configuration...

` : this.configState.error ? html`
Error loading configuration: ${this.configState.error}
` : this.configState.config ? this.renderConfig() : html`
No configuration loaded
`} `; } private renderConfig(): TemplateResult { const cfg = this.configState.config!; return html` { if (e.detail?.view) { appRouter.navigateToView(e.detail.view); } }} > ${this.renderSystemSection(cfg.system)} ${this.renderSmartProxySection(cfg.smartProxy)} ${this.renderEmailSection(cfg.email)} ${this.renderDnsSection(cfg.dns)} ${this.renderTlsSection(cfg.tls)} ${this.renderCacheSection(cfg.cache)} ${this.renderRadiusSection(cfg.radius)} ${this.renderRemoteIngressSection(cfg.remoteIngress)} `; } private renderSystemSection(sys: appstate.IConfigState['config']['system']): TemplateResult { const fields: IConfigField[] = [ { key: 'Base Directory', value: sys.baseDir }, { key: 'Data Directory', value: sys.dataDir }, { key: 'Public IP', value: sys.publicIp }, { key: 'Proxy IPs', value: sys.proxyIps.length > 0 ? sys.proxyIps : null, type: 'pills' }, { key: 'Uptime', value: this.formatUptime(sys.uptime) }, { key: 'Storage Backend', value: sys.storageBackend, type: 'badge' }, { key: 'Storage Path', value: sys.storagePath }, ]; return html` `; } private renderSmartProxySection(proxy: appstate.IConfigState['config']['smartProxy']): TemplateResult { const fields: IConfigField[] = [ { key: 'Route Count', value: proxy.routeCount }, ]; if (proxy.acme) { fields.push( { key: 'ACME Enabled', value: proxy.acme.enabled, type: 'boolean' }, { key: 'Account Email', value: proxy.acme.accountEmail || null }, { key: 'Use Production', value: proxy.acme.useProduction, type: 'boolean' }, { key: 'Auto Renew', value: proxy.acme.autoRenew, type: 'boolean' }, { key: 'Renew Threshold', value: `${proxy.acme.renewThresholdDays} days` }, ); } const actions: IConfigSectionAction[] = [ { label: 'View Routes', icon: 'lucide:arrow-right', event: 'navigate', detail: { view: 'routes' } }, ]; return html` `; } private renderEmailSection(email: appstate.IConfigState['config']['email']): TemplateResult { const fields: IConfigField[] = [ { key: 'Ports', value: email.ports.length > 0 ? email.ports.map(String) : null, type: 'pills' }, { key: 'Hostname', value: email.hostname }, { key: 'Domains', value: email.domains.length > 0 ? email.domains : null, type: 'pills' }, { key: 'Email Routes', value: email.emailRouteCount }, { key: 'Received Emails Path', value: email.receivedEmailsPath }, ]; if (email.portMapping) { const mappingStr = Object.entries(email.portMapping) .map(([ext, int]) => `${ext} → ${int}`) .join(', '); fields.splice(1, 0, { key: 'Port Mapping', value: mappingStr, type: 'code' }); } const actions: IConfigSectionAction[] = [ { label: 'View Emails', icon: 'lucide:arrow-right', event: 'navigate', detail: { view: 'emails' } }, ]; return html` `; } private renderDnsSection(dns: appstate.IConfigState['config']['dns']): TemplateResult { const fields: IConfigField[] = [ { key: 'Port', value: dns.port }, { key: 'NS Domains', value: dns.nsDomains.length > 0 ? dns.nsDomains : null, type: 'pills' }, { key: 'Scopes', value: dns.scopes.length > 0 ? dns.scopes : null, type: 'pills' }, { key: 'Record Count', value: dns.recordCount }, { key: 'DNS Challenge', value: dns.dnsChallenge, type: 'boolean' }, ]; return html` `; } private renderTlsSection(tls: appstate.IConfigState['config']['tls']): TemplateResult { const fields: IConfigField[] = [ { key: 'Contact Email', value: tls.contactEmail }, { key: 'Domain', value: tls.domain }, { key: 'Source', value: tls.source, type: 'badge' }, { key: 'Certificate Path', value: tls.certPath }, { key: 'Key Path', value: tls.keyPath }, ]; const status = tls.source === 'none' ? 'not-configured' : 'enabled'; const actions: IConfigSectionAction[] = [ { label: 'View Certificates', icon: 'lucide:arrow-right', event: 'navigate', detail: { view: 'certificates' } }, ]; return html` `; } private renderCacheSection(cache: appstate.IConfigState['config']['cache']): TemplateResult { const fields: IConfigField[] = [ { key: 'Storage Path', value: cache.storagePath }, { key: 'DB Name', value: cache.dbName }, { key: 'Default TTL', value: `${cache.defaultTTLDays} days` }, { key: 'Cleanup Interval', value: `${cache.cleanupIntervalHours} hours` }, ]; if (cache.ttlConfig && Object.keys(cache.ttlConfig).length > 0) { for (const [key, val] of Object.entries(cache.ttlConfig)) { fields.push({ key: `TTL: ${key}`, value: `${val} days` }); } } return html` `; } private renderRadiusSection(radius: appstate.IConfigState['config']['radius']): TemplateResult { const fields: IConfigField[] = [ { key: 'Auth Port', value: radius.authPort }, { key: 'Accounting Port', value: radius.acctPort }, { key: 'Bind Address', value: radius.bindAddress }, { key: 'Client Count', value: radius.clientCount }, ]; if (radius.vlanDefaultVlan !== null) { fields.push( { key: 'Default VLAN', value: radius.vlanDefaultVlan }, { key: 'Allow Unknown MACs', value: radius.vlanAllowUnknownMacs, type: 'boolean' }, { key: 'VLAN Mappings', value: radius.vlanMappingCount }, ); } const status = radius.enabled ? 'enabled' : 'not-configured'; return html` `; } private renderRemoteIngressSection(ri: appstate.IConfigState['config']['remoteIngress']): TemplateResult { const fields: IConfigField[] = [ { key: 'Tunnel Port', value: ri.tunnelPort }, { key: 'Hub Domain', value: ri.hubDomain }, { key: 'TLS Configured', value: ri.tlsConfigured, type: 'boolean' }, ]; const actions: IConfigSectionAction[] = [ { label: 'View Remote Ingress', icon: 'lucide:arrow-right', event: 'navigate', detail: { view: 'remoteingress' } }, ]; return html` `; } private formatUptime(seconds: number): string { const days = Math.floor(seconds / 86400); const hours = Math.floor((seconds % 86400) / 3600); const mins = Math.floor((seconds % 3600) / 60); const parts: string[] = []; if (days > 0) parts.push(`${days}d`); if (hours > 0) parts.push(`${hours}h`); parts.push(`${mins}m`); return parts.join(' '); } }