import {
DeesElement,
html,
customElement,
type TemplateResult,
css,
state,
cssManager,
} from '@design.estate/dees-element';
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';
declare global {
interface HTMLElementTagNameMap {
'ops-view-certificates': OpsViewCertificates;
}
}
@customElement('ops-view-certificates')
export class OpsViewCertificates extends DeesElement {
@state()
accessor certState: appstate.ICertificateState = appstate.certificateStatePart.getState()!;
constructor() {
super();
const sub = appstate.certificateStatePart.select().subscribe((newState) => {
this.certState = newState;
});
this.rxSubscriptions.push(sub);
}
async connectedCallback() {
await super.connectedCallback();
await appstate.certificateStatePart.dispatchAction(appstate.fetchCertificateOverviewAction, null);
}
public static styles = [
cssManager.defaultStyles,
viewHostCss,
css`
.certificatesContainer {
display: flex;
flex-direction: column;
gap: 24px;
}
.statusBadge {
display: inline-flex;
align-items: center;
padding: 3px 10px;
border-radius: 12px;
font-size: 12px;
font-weight: 600;
letter-spacing: 0.02em;
text-transform: uppercase;
}
.statusBadge.valid {
background: ${cssManager.bdTheme('#dcfce7', '#14532d')};
color: ${cssManager.bdTheme('#166534', '#4ade80')};
}
.statusBadge.expiring {
background: ${cssManager.bdTheme('#fff7ed', '#431407')};
color: ${cssManager.bdTheme('#9a3412', '#fb923c')};
}
.statusBadge.expired,
.statusBadge.failed {
background: ${cssManager.bdTheme('#fef2f2', '#450a0a')};
color: ${cssManager.bdTheme('#991b1b', '#f87171')};
}
.statusBadge.provisioning {
background: ${cssManager.bdTheme('#eff6ff', '#172554')};
color: ${cssManager.bdTheme('#1e40af', '#60a5fa')};
}
.statusBadge.unknown {
background: ${cssManager.bdTheme('#f3f4f6', '#1f2937')};
color: ${cssManager.bdTheme('#4b5563', '#9ca3af')};
}
.sourceBadge {
display: inline-flex;
align-items: center;
padding: 3px 8px;
border-radius: 4px;
font-size: 11px;
font-weight: 500;
background: ${cssManager.bdTheme('#f3f4f6', '#1f2937')};
color: ${cssManager.bdTheme('#374151', '#d1d5db')};
}
.routePills {
display: flex;
flex-wrap: wrap;
gap: 4px;
}
.routePill {
display: inline-flex;
align-items: center;
padding: 2px 8px;
border-radius: 4px;
font-size: 12px;
background: ${cssManager.bdTheme('#e0e7ff', '#1e1b4b')};
color: ${cssManager.bdTheme('#3730a3', '#a5b4fc')};
}
.moreCount {
font-size: 11px;
color: ${cssManager.bdTheme('#6b7280', '#9ca3af')};
padding: 2px 6px;
}
.errorText {
font-size: 12px;
color: ${cssManager.bdTheme('#991b1b', '#f87171')};
max-width: 200px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.backoffIndicator {
display: inline-flex;
align-items: center;
gap: 4px;
font-size: 11px;
color: ${cssManager.bdTheme('#9a3412', '#fb923c')};
padding: 2px 6px;
border-radius: 4px;
background: ${cssManager.bdTheme('#fff7ed', '#431407')};
}
.expiryInfo {
font-size: 12px;
}
.expiryInfo .daysLeft {
font-size: 11px;
color: ${cssManager.bdTheme('#6b7280', '#9ca3af')};
}
.expiryInfo .daysLeft.warn {
color: ${cssManager.bdTheme('#9a3412', '#fb923c')};
}
.expiryInfo .daysLeft.danger {
color: ${cssManager.bdTheme('#991b1b', '#f87171')};
}
`,
];
public render(): TemplateResult {
const { summary } = this.certState;
return html`
The certificate for ${cert.domain} is still valid${cert.expiryDate ? ` until ${new Date(cert.expiryDate).toLocaleDateString()}` : ''}. Do you want to force renew it now?
`, menuOptions: [ { name: 'Cancel', action: async (modalArg: any) => modalArg.destroy() }, { name: 'Force Renew', action: async (modalArg: any) => { await modalArg.destroy(); await doReprovision(); }, }, ], }); } else { await doReprovision(); } }, }, { name: 'Export', iconName: 'lucide:download', type: ['inRow', 'contextmenu'], actionFunc: async (actionData: { item: interfaces.requests.ICertificateInfo }) => { const { DeesToast } = await import('@design.estate/dees-catalog'); const cert = actionData.item; try { const response = await appstate.fetchCertificateExport(cert.domain); if (response.success && response.cert) { const safeDomain = cert.domain.replace(/\*/g, '_wildcard'); this.downloadJsonFile(`${safeDomain}.tsclass.cert.json`, response.cert); DeesToast.show({ message: `Certificate exported for ${cert.domain}`, type: 'success', duration: 3000 }); } else { DeesToast.show({ message: response.message || 'Export failed', type: 'error', duration: 4000 }); } } catch (err: unknown) { DeesToast.show({ message: `Export failed: ${(err as Error).message}`, type: 'error', duration: 4000 }); } }, }, { name: 'Delete', iconName: 'lucide:trash-2', type: ['inRow', 'contextmenu'], actionFunc: async (actionData: { item: interfaces.requests.ICertificateInfo }) => { const cert = actionData.item; const { DeesModal, DeesToast } = await import('@design.estate/dees-catalog'); await DeesModal.createAndShow({ heading: `Delete Certificate: ${cert.domain}`, content: html`Are you sure you want to delete the certificate data for ${cert.domain}?
Note: The certificate may remain in proxy memory until the next restart or reprovisioning.