feat(opsserver): introduce OpsServer (TypedRequest API) and new lightweight web UI; replace legacy Angular UI and add typed interfaces
This commit is contained in:
197
ts_web/elements/ob-view-network.ts
Normal file
197
ts_web/elements/ob-view-network.ts
Normal file
@@ -0,0 +1,197 @@
|
||||
import * as plugins from '../plugins.js';
|
||||
import * as shared from './shared/index.js';
|
||||
import * as appstate from '../appstate.js';
|
||||
import {
|
||||
DeesElement,
|
||||
customElement,
|
||||
html,
|
||||
state,
|
||||
css,
|
||||
cssManager,
|
||||
type TemplateResult,
|
||||
} from '@design.estate/dees-element';
|
||||
|
||||
@customElement('ob-view-network')
|
||||
export class ObViewNetwork extends DeesElement {
|
||||
@state()
|
||||
accessor networkState: appstate.INetworkState = {
|
||||
targets: [],
|
||||
stats: null,
|
||||
trafficStats: null,
|
||||
dnsRecords: [],
|
||||
domains: [],
|
||||
certificates: [],
|
||||
};
|
||||
|
||||
@state()
|
||||
accessor currentTab: 'proxy' | 'dns' | 'domains' | 'domain-detail' = 'proxy';
|
||||
|
||||
@state()
|
||||
accessor selectedDomain: string = '';
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
const networkSub = appstate.networkStatePart
|
||||
.select((s) => s)
|
||||
.subscribe((newState) => {
|
||||
this.networkState = newState;
|
||||
});
|
||||
this.rxSubscriptions.push(networkSub);
|
||||
}
|
||||
|
||||
public static styles = [
|
||||
cssManager.defaultStyles,
|
||||
shared.viewHostCss,
|
||||
css``,
|
||||
];
|
||||
|
||||
async connectedCallback() {
|
||||
super.connectedCallback();
|
||||
await Promise.all([
|
||||
appstate.networkStatePart.dispatchAction(appstate.fetchNetworkTargetsAction, null),
|
||||
appstate.networkStatePart.dispatchAction(appstate.fetchNetworkStatsAction, null),
|
||||
appstate.networkStatePart.dispatchAction(appstate.fetchTrafficStatsAction, null),
|
||||
appstate.networkStatePart.dispatchAction(appstate.fetchDnsRecordsAction, null),
|
||||
appstate.networkStatePart.dispatchAction(appstate.fetchDomainsAction, null),
|
||||
appstate.networkStatePart.dispatchAction(appstate.fetchCertificatesAction, null),
|
||||
]);
|
||||
}
|
||||
|
||||
public render(): TemplateResult {
|
||||
switch (this.currentTab) {
|
||||
case 'dns':
|
||||
return this.renderDnsView();
|
||||
case 'domains':
|
||||
return this.renderDomainsView();
|
||||
case 'domain-detail':
|
||||
return this.renderDomainDetailView();
|
||||
default:
|
||||
return this.renderProxyView();
|
||||
}
|
||||
}
|
||||
|
||||
private renderProxyView(): TemplateResult {
|
||||
const stats = this.networkState.stats;
|
||||
return html`
|
||||
<ob-sectionheading>Network</ob-sectionheading>
|
||||
<sz-network-proxy-view
|
||||
.proxyStatus=${stats?.proxy?.running ? 'running' : 'stopped'}
|
||||
.routeCount=${String(stats?.proxy?.routes || 0)}
|
||||
.certificateCount=${String(stats?.proxy?.certificates || 0)}
|
||||
.targetCount=${String(this.networkState.targets.length)}
|
||||
.targets=${this.networkState.targets.map((t) => ({
|
||||
type: t.type,
|
||||
name: t.name,
|
||||
domain: t.domain,
|
||||
target: `${t.targetHost}:${t.targetPort}`,
|
||||
status: t.status,
|
||||
}))}
|
||||
.logs=${[]}
|
||||
@refresh=${() => {
|
||||
appstate.networkStatePart.dispatchAction(appstate.fetchNetworkTargetsAction, null);
|
||||
appstate.networkStatePart.dispatchAction(appstate.fetchNetworkStatsAction, null);
|
||||
}}
|
||||
></sz-network-proxy-view>
|
||||
`;
|
||||
}
|
||||
|
||||
private renderDnsView(): TemplateResult {
|
||||
return html`
|
||||
<ob-sectionheading>DNS Records</ob-sectionheading>
|
||||
<sz-network-dns-view
|
||||
.records=${this.networkState.dnsRecords}
|
||||
@sync=${() => {
|
||||
appstate.networkStatePart.dispatchAction(appstate.syncDnsAction, null);
|
||||
}}
|
||||
@delete=${(e: CustomEvent) => {
|
||||
console.log('Delete DNS record:', e.detail);
|
||||
}}
|
||||
></sz-network-dns-view>
|
||||
`;
|
||||
}
|
||||
|
||||
private renderDomainsView(): TemplateResult {
|
||||
const certs = this.networkState.certificates;
|
||||
return html`
|
||||
<ob-sectionheading>Domains</ob-sectionheading>
|
||||
<sz-network-domains-view
|
||||
.domains=${this.networkState.domains.map((d) => {
|
||||
const cert = certs.find((c) => c.certDomain === d.domain);
|
||||
let certStatus: 'valid' | 'expiring' | 'expired' | 'pending' = 'pending';
|
||||
if (cert) {
|
||||
if (!cert.isValid) certStatus = 'expired';
|
||||
else if (cert.expiresAt && cert.expiresAt - Date.now() < 30 * 24 * 60 * 60 * 1000)
|
||||
certStatus = 'expiring';
|
||||
else certStatus = 'valid';
|
||||
}
|
||||
return {
|
||||
domain: d.domain,
|
||||
provider: 'cloudflare',
|
||||
serviceCount: d.services?.length || 0,
|
||||
certificateStatus: certStatus,
|
||||
};
|
||||
})}
|
||||
@sync=${() => {
|
||||
appstate.networkStatePart.dispatchAction(appstate.fetchDomainsAction, null);
|
||||
}}
|
||||
@view=${(e: CustomEvent) => {
|
||||
this.selectedDomain = e.detail.domain || e.detail;
|
||||
this.currentTab = 'domain-detail';
|
||||
}}
|
||||
></sz-network-domains-view>
|
||||
`;
|
||||
}
|
||||
|
||||
private renderDomainDetailView(): TemplateResult {
|
||||
const domainDetail = this.networkState.domains.find(
|
||||
(d) => d.domain === this.selectedDomain,
|
||||
);
|
||||
const cert = this.networkState.certificates.find(
|
||||
(c) => c.certDomain === this.selectedDomain,
|
||||
);
|
||||
|
||||
return html`
|
||||
<ob-sectionheading>Domain Details</ob-sectionheading>
|
||||
<sz-domain-detail-view
|
||||
.domain=${domainDetail
|
||||
? {
|
||||
id: this.selectedDomain,
|
||||
name: this.selectedDomain,
|
||||
status: 'active',
|
||||
verified: true,
|
||||
createdAt: '',
|
||||
}
|
||||
: null}
|
||||
.certificate=${cert
|
||||
? {
|
||||
id: cert.domainId,
|
||||
domain: cert.certDomain,
|
||||
issuer: 'Let\'s Encrypt',
|
||||
validFrom: cert.issuedAt ? new Date(cert.issuedAt).toISOString() : '',
|
||||
validUntil: cert.expiresAt ? new Date(cert.expiresAt).toISOString() : '',
|
||||
daysRemaining: cert.expiresAt
|
||||
? Math.floor((cert.expiresAt - Date.now()) / (24 * 60 * 60 * 1000))
|
||||
: 0,
|
||||
status: cert.isValid ? 'valid' : 'expired',
|
||||
autoRenew: true,
|
||||
}
|
||||
: null}
|
||||
.dnsRecords=${this.networkState.dnsRecords
|
||||
.filter((r) => r.domain?.includes(this.selectedDomain))
|
||||
.map((r) => ({
|
||||
id: r.id || '',
|
||||
type: r.type,
|
||||
name: r.domain,
|
||||
value: r.value,
|
||||
ttl: 3600,
|
||||
}))}
|
||||
@renew-certificate=${() => {
|
||||
appstate.networkStatePart.dispatchAction(appstate.renewCertificateAction, {
|
||||
domain: this.selectedDomain,
|
||||
});
|
||||
}}
|
||||
></sz-domain-detail-view>
|
||||
`;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user