import { commitinfo } from '../00_commitinfo_data.js'; import * as plugins from '../plugins.js'; import * as appstate from '../appstate.js'; import { appRouter } from '../router.js'; import { DeesElement, css, cssManager, customElement, html, state } from '@design.estate/dees-element'; import { CloudlyViewBackups } from './views/backups/index.js'; import { CloudlyViewBaseOs } from './views/baseos/index.js'; import { CloudlyViewClusters } from './views/clusters/index.js'; import { CloudlyViewDbs } from './views/dbs/index.js'; import { CloudlyViewDeployments } from './views/deployments/index.js'; import { CloudlyViewDns } from './views/dns/index.js'; import { CloudlyViewDomains } from './views/domains/index.js'; import { CloudlyViewImages } from './views/images/index.js'; import { CloudlyViewLogs } from './views/logs/index.js'; import { CloudlyViewMails } from './views/mails/index.js'; import { CloudlyViewOverview } from './views/overview/index.js'; import { CloudlyViewS3 } from './views/s3/index.js'; import { CloudlyViewSecretBundles } from './views/secretbundles/index.js'; import { CloudlyViewSecretGroups } from './views/secretgroups/index.js'; import { CloudlyViewServices } from './views/services/index.js'; import { CloudlyViewExternalRegistries } from './views/externalregistries/index.js'; import { CloudlyViewSettings } from './views/settings/index.js'; import { CloudlyViewTasks } from './views/tasks/index.js'; declare global { interface HTMLElementTagNameMap { 'cvault-dashboard': CloudlyDashboard; } } interface ICloudlyView extends plugins.deesCatalog.IView { slug?: string; subViews?: ICloudlyView[]; } @customElement('cloudly-dashboard') export class CloudlyDashboard extends DeesElement { @state() private accessor identity: plugins.interfaces.data.IIdentity | null = null; @state() private accessor data: appstate.IDataState = { secretGroups: [], secretBundles: [], clusters: [], }; @state() private accessor uiState: appstate.IUiState = { activeView: 'overview', activeSubview: null, }; // Keep view tabs stable across renders to preserve active selection private readonly viewTabs: ICloudlyView[] = [ { slug: 'overview', name: 'Overview', iconName: 'lucide:LayoutDashboard', element: CloudlyViewOverview, }, { slug: 'platform', name: 'Platform', iconName: 'lucide:Settings', subViews: [ { slug: 'settings', name: 'Settings', iconName: 'lucide:Settings', element: CloudlyViewSettings }, { slug: 'baseos', name: 'BaseOS', iconName: 'lucide:HardDriveDownload', element: CloudlyViewBaseOs }, { slug: 'fleet', name: 'Fleet', iconName: 'lucide:Truck', element: CloudlyViewBackups }, ], }, { slug: 'runtime', name: 'Runtime', iconName: 'lucide:Network', subViews: [ { slug: 'clusters', name: 'Clusters', iconName: 'lucide:Network', element: CloudlyViewClusters }, { slug: 'services', name: 'Services', iconName: 'lucide:Layers', element: CloudlyViewServices }, { slug: 'images', name: 'Images', iconName: 'lucide:Image', element: CloudlyViewImages }, { slug: 'deployments', name: 'Deployments', iconName: 'lucide:Rocket', element: CloudlyViewDeployments }, { slug: 'tasks', name: 'Tasks', iconName: 'lucide:ListChecks', element: CloudlyViewTasks }, ], }, { slug: 'registry', name: 'Registry & Build', iconName: 'lucide:Package', subViews: [ { slug: 'externalregistries', name: 'ExternalRegistries', iconName: 'lucide:Package', element: CloudlyViewExternalRegistries }, { slug: 'testing', name: 'Testing & Building', iconName: 'lucide:HardHat', element: CloudlyViewServices }, ], }, { slug: 'secrets', name: 'Secrets', iconName: 'lucide:ShieldCheck', subViews: [ { slug: 'secretgroups', name: 'SecretGroups', iconName: 'lucide:ShieldCheck', element: CloudlyViewSecretGroups }, { slug: 'secretbundles', name: 'SecretBundles', iconName: 'lucide:LockKeyhole', element: CloudlyViewSecretBundles }, ], }, { slug: 'domains', name: 'Domains & Messaging', iconName: 'lucide:Globe2', subViews: [ { slug: 'domains', name: 'Domains', iconName: 'lucide:Globe2', element: CloudlyViewDomains }, { slug: 'dns', name: 'DNS', iconName: 'lucide:Globe', element: CloudlyViewDns }, { slug: 'mails', name: 'Mails', iconName: 'lucide:Mail', element: CloudlyViewMails }, ], }, { slug: 'storage', name: 'Storage', iconName: 'lucide:Database', subViews: [ { slug: 's3', name: 's3', iconName: 'lucide:Cloud', element: CloudlyViewS3 }, { slug: 'dbs', name: 'DBs', iconName: 'lucide:Database', element: CloudlyViewDbs }, { slug: 'backups', name: 'Backups', iconName: 'lucide:Save', element: CloudlyViewBackups }, ], }, { slug: 'logs', name: 'Logs', iconName: 'lucide:FileText', element: CloudlyViewLogs, }, ]; private slugFor(view: ICloudlyView): string { return view.slug ?? view.name.toLowerCase().replace(/\s+/g, ''); } private findParent(view: ICloudlyView): ICloudlyView | undefined { return this.viewTabs.find((viewTab) => viewTab.subViews?.includes(view)); } private findViewBySlug(viewSlug: string, subviewSlug: string | null): ICloudlyView | undefined { const topLevelView = this.viewTabs.find((view) => this.slugFor(view) === viewSlug); if (!topLevelView) return undefined; if (subviewSlug && topLevelView.subViews) { return topLevelView.subViews.find((subview) => this.slugFor(subview) === subviewSlug) ?? topLevelView; } return topLevelView; } private get currentViewTab(): ICloudlyView { return this.findViewBySlug(this.uiState.activeView, this.uiState.activeSubview) ?? this.viewTabs[0]; } constructor() { super(); document.title = `cloudly v${commitinfo.version}`; const subcription = appstate.dataState .select((stateArg) => stateArg) .subscribe((dataArg) => { this.data = dataArg; }); this.rxSubscriptions.push(subcription); const uiSubscription = appstate.uiStatePart .select((stateArg) => stateArg) .subscribe((uiState) => { this.uiState = uiState; this.syncAppdashView(uiState.activeView, uiState.activeSubview); }); this.rxSubscriptions.push(uiSubscription); const loginSubscription = appstate.loginStatePart .select((stateArg) => stateArg?.identity ?? null) .subscribe((identityArg) => { const hadIdentity = !!this.identity; this.identity = identityArg ?? null; if (!identityArg && hadIdentity) { void this.switchToLoginContent('Session expired. Please sign in again.'); } }); this.rxSubscriptions.push(loginSubscription); } private syncAppdashView(viewSlug: string, subviewSlug: string | null): void { const appDash = this.shadowRoot?.querySelector('dees-simple-appdash') as any; if (!appDash) return; const targetView = this.findViewBySlug(viewSlug, subviewSlug); if (!targetView || appDash.selectedView === targetView) return; appDash.loadView(targetView); } public static styles = [ cssManager.defaultStyles, css` .maincontainer { position: relative; width: 100vw; height: 100vh; } h1 { font-weight: 400; font-size: 24px; font-family: 'Cal Sans'; } `, ]; public render() { return html`