@serve.zone/dcrouter 5.2.0 → 5.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/dist_serve/bundle.js +2444 -2300
  2. package/dist_ts/00_commitinfo_data.js +1 -1
  3. package/dist_ts/classes.dcrouter.d.ts +12 -0
  4. package/dist_ts/classes.dcrouter.js +53 -6
  5. package/dist_ts/opsserver/classes.opsserver.d.ts +1 -0
  6. package/dist_ts/opsserver/classes.opsserver.js +3 -1
  7. package/dist_ts/opsserver/handlers/certificate.handler.d.ts +11 -0
  8. package/dist_ts/opsserver/handlers/certificate.handler.js +170 -0
  9. package/dist_ts/opsserver/handlers/index.d.ts +1 -0
  10. package/dist_ts/opsserver/handlers/index.js +2 -1
  11. package/dist_ts_interfaces/requests/certificate.d.ts +44 -0
  12. package/dist_ts_interfaces/requests/certificate.js +3 -0
  13. package/dist_ts_interfaces/requests/index.d.ts +1 -0
  14. package/dist_ts_interfaces/requests/index.js +2 -1
  15. package/dist_ts_web/00_commitinfo_data.js +1 -1
  16. package/dist_ts_web/appstate.d.ts +17 -0
  17. package/dist_ts_web/appstate.js +71 -2
  18. package/dist_ts_web/elements/index.d.ts +1 -0
  19. package/dist_ts_web/elements/index.js +2 -1
  20. package/dist_ts_web/elements/ops-dashboard.js +6 -1
  21. package/dist_ts_web/elements/ops-view-certificates.d.ts +20 -0
  22. package/dist_ts_web/elements/ops-view-certificates.js +379 -0
  23. package/dist_ts_web/router.d.ts +1 -1
  24. package/dist_ts_web/router.js +2 -2
  25. package/package.json +2 -2
  26. package/ts/00_commitinfo_data.ts +1 -1
  27. package/ts/classes.dcrouter.ts +63 -10
  28. package/ts/opsserver/classes.opsserver.ts +2 -0
  29. package/ts/opsserver/handlers/certificate.handler.ts +186 -0
  30. package/ts/opsserver/handlers/index.ts +2 -1
  31. package/ts_web/00_commitinfo_data.ts +1 -1
  32. package/ts_web/appstate.ts +98 -2
  33. package/ts_web/elements/index.ts +1 -0
  34. package/ts_web/elements/ops-dashboard.ts +5 -0
  35. package/ts_web/elements/ops-view-certificates.ts +355 -0
  36. package/ts_web/router.ts +1 -1
@@ -0,0 +1,355 @@
1
+ import {
2
+ DeesElement,
3
+ html,
4
+ customElement,
5
+ type TemplateResult,
6
+ css,
7
+ state,
8
+ cssManager,
9
+ } from '@design.estate/dees-element';
10
+ import * as appstate from '../appstate.js';
11
+ import * as interfaces from '../../dist_ts_interfaces/index.js';
12
+ import { viewHostCss } from './shared/css.js';
13
+ import { type IStatsTile } from '@design.estate/dees-catalog';
14
+
15
+ declare global {
16
+ interface HTMLElementTagNameMap {
17
+ 'ops-view-certificates': OpsViewCertificates;
18
+ }
19
+ }
20
+
21
+ @customElement('ops-view-certificates')
22
+ export class OpsViewCertificates extends DeesElement {
23
+ @state()
24
+ accessor certState: appstate.ICertificateState = appstate.certificateStatePart.getState();
25
+
26
+ constructor() {
27
+ super();
28
+ const sub = appstate.certificateStatePart.state.subscribe((newState) => {
29
+ this.certState = newState;
30
+ });
31
+ this.rxSubscriptions.push(sub);
32
+ }
33
+
34
+ async connectedCallback() {
35
+ await super.connectedCallback();
36
+ await appstate.certificateStatePart.dispatchAction(appstate.fetchCertificateOverviewAction, null);
37
+ }
38
+
39
+ public static styles = [
40
+ cssManager.defaultStyles,
41
+ viewHostCss,
42
+ css`
43
+ .certificatesContainer {
44
+ display: flex;
45
+ flex-direction: column;
46
+ gap: 24px;
47
+ }
48
+
49
+ .statusBadge {
50
+ display: inline-flex;
51
+ align-items: center;
52
+ padding: 3px 10px;
53
+ border-radius: 12px;
54
+ font-size: 12px;
55
+ font-weight: 600;
56
+ letter-spacing: 0.02em;
57
+ text-transform: uppercase;
58
+ }
59
+
60
+ .statusBadge.valid {
61
+ background: ${cssManager.bdTheme('#dcfce7', '#14532d')};
62
+ color: ${cssManager.bdTheme('#166534', '#4ade80')};
63
+ }
64
+
65
+ .statusBadge.expiring {
66
+ background: ${cssManager.bdTheme('#fff7ed', '#431407')};
67
+ color: ${cssManager.bdTheme('#9a3412', '#fb923c')};
68
+ }
69
+
70
+ .statusBadge.expired,
71
+ .statusBadge.failed {
72
+ background: ${cssManager.bdTheme('#fef2f2', '#450a0a')};
73
+ color: ${cssManager.bdTheme('#991b1b', '#f87171')};
74
+ }
75
+
76
+ .statusBadge.provisioning {
77
+ background: ${cssManager.bdTheme('#eff6ff', '#172554')};
78
+ color: ${cssManager.bdTheme('#1e40af', '#60a5fa')};
79
+ }
80
+
81
+ .statusBadge.unknown {
82
+ background: ${cssManager.bdTheme('#f3f4f6', '#1f2937')};
83
+ color: ${cssManager.bdTheme('#4b5563', '#9ca3af')};
84
+ }
85
+
86
+ .sourceBadge {
87
+ display: inline-flex;
88
+ align-items: center;
89
+ padding: 3px 8px;
90
+ border-radius: 4px;
91
+ font-size: 11px;
92
+ font-weight: 500;
93
+ background: ${cssManager.bdTheme('#f3f4f6', '#1f2937')};
94
+ color: ${cssManager.bdTheme('#374151', '#d1d5db')};
95
+ }
96
+
97
+ .domainPills {
98
+ display: flex;
99
+ flex-wrap: wrap;
100
+ gap: 4px;
101
+ }
102
+
103
+ .domainPill {
104
+ display: inline-flex;
105
+ align-items: center;
106
+ padding: 2px 8px;
107
+ border-radius: 4px;
108
+ font-size: 12px;
109
+ background: ${cssManager.bdTheme('#e0e7ff', '#1e1b4b')};
110
+ color: ${cssManager.bdTheme('#3730a3', '#a5b4fc')};
111
+ }
112
+
113
+ .moreCount {
114
+ font-size: 11px;
115
+ color: ${cssManager.bdTheme('#6b7280', '#9ca3af')};
116
+ padding: 2px 6px;
117
+ }
118
+
119
+ .errorText {
120
+ font-size: 12px;
121
+ color: ${cssManager.bdTheme('#991b1b', '#f87171')};
122
+ max-width: 200px;
123
+ overflow: hidden;
124
+ text-overflow: ellipsis;
125
+ white-space: nowrap;
126
+ }
127
+
128
+ .expiryInfo {
129
+ font-size: 12px;
130
+ }
131
+
132
+ .expiryInfo .daysLeft {
133
+ font-size: 11px;
134
+ color: ${cssManager.bdTheme('#6b7280', '#9ca3af')};
135
+ }
136
+
137
+ .expiryInfo .daysLeft.warn {
138
+ color: ${cssManager.bdTheme('#9a3412', '#fb923c')};
139
+ }
140
+
141
+ .expiryInfo .daysLeft.danger {
142
+ color: ${cssManager.bdTheme('#991b1b', '#f87171')};
143
+ }
144
+ `,
145
+ ];
146
+
147
+ public render(): TemplateResult {
148
+ const { summary } = this.certState;
149
+
150
+ return html`
151
+ <ops-sectionheading>Certificates</ops-sectionheading>
152
+
153
+ <div class="certificatesContainer">
154
+ ${this.renderStatsTiles(summary)}
155
+ ${this.renderCertificateTable()}
156
+ </div>
157
+ `;
158
+ }
159
+
160
+ private renderStatsTiles(summary: appstate.ICertificateState['summary']): TemplateResult {
161
+ const tiles: IStatsTile[] = [
162
+ {
163
+ id: 'total',
164
+ title: 'Total Certificates',
165
+ value: summary.total,
166
+ type: 'number',
167
+ icon: 'shieldHalved',
168
+ color: '#3b82f6',
169
+ },
170
+ {
171
+ id: 'valid',
172
+ title: 'Valid',
173
+ value: summary.valid,
174
+ type: 'number',
175
+ icon: 'check',
176
+ color: '#22c55e',
177
+ },
178
+ {
179
+ id: 'expiring',
180
+ title: 'Expiring Soon',
181
+ value: summary.expiring,
182
+ type: 'number',
183
+ icon: 'clock',
184
+ color: '#f59e0b',
185
+ },
186
+ {
187
+ id: 'problems',
188
+ title: 'Failed / Expired',
189
+ value: summary.failed + summary.expired,
190
+ type: 'number',
191
+ icon: 'triangleExclamation',
192
+ color: '#ef4444',
193
+ },
194
+ ];
195
+
196
+ return html`
197
+ <dees-statsgrid
198
+ .tiles=${tiles}
199
+ .minTileWidth=${200}
200
+ .gridActions=${[
201
+ {
202
+ name: 'Refresh',
203
+ iconName: 'arrowsRotate',
204
+ action: async () => {
205
+ await appstate.certificateStatePart.dispatchAction(
206
+ appstate.fetchCertificateOverviewAction,
207
+ null
208
+ );
209
+ },
210
+ },
211
+ ]}
212
+ ></dees-statsgrid>
213
+ `;
214
+ }
215
+
216
+ private renderCertificateTable(): TemplateResult {
217
+ return html`
218
+ <dees-table
219
+ .data=${this.certState.certificates}
220
+ .displayFunction=${(cert: interfaces.requests.ICertificateInfo) => ({
221
+ Route: cert.routeName,
222
+ Domains: this.renderDomainPills(cert.domains),
223
+ Status: this.renderStatusBadge(cert.status),
224
+ Source: this.renderSourceBadge(cert.source),
225
+ Expires: this.renderExpiry(cert.expiryDate),
226
+ Error: cert.error
227
+ ? html`<span class="errorText" title="${cert.error}">${cert.error}</span>`
228
+ : '',
229
+ })}
230
+ .dataActions=${[
231
+ {
232
+ name: 'Reprovision',
233
+ iconName: 'arrowsRotate',
234
+ type: ['inRow'],
235
+ actionFunc: async (actionData: { item: interfaces.requests.ICertificateInfo }) => {
236
+ const cert = actionData.item;
237
+ if (!cert.canReprovision) {
238
+ const { DeesToast } = await import('@design.estate/dees-catalog');
239
+ DeesToast.show({
240
+ message: 'This certificate source does not support reprovisioning.',
241
+ type: 'warning',
242
+ duration: 3000,
243
+ });
244
+ return;
245
+ }
246
+ await appstate.certificateStatePart.dispatchAction(
247
+ appstate.reprovisionCertificateAction,
248
+ cert.routeName,
249
+ );
250
+ const { DeesToast } = await import('@design.estate/dees-catalog');
251
+ DeesToast.show({
252
+ message: `Reprovisioning triggered for ${cert.routeName}`,
253
+ type: 'success',
254
+ duration: 3000,
255
+ });
256
+ },
257
+ },
258
+ {
259
+ name: 'View Details',
260
+ iconName: 'magnifyingGlass',
261
+ type: ['doubleClick', 'contextmenu'],
262
+ actionFunc: async (actionData: { item: interfaces.requests.ICertificateInfo }) => {
263
+ const cert = actionData.item;
264
+ const { DeesModal } = await import('@design.estate/dees-catalog');
265
+ await DeesModal.createAndShow({
266
+ heading: `Certificate: ${cert.routeName}`,
267
+ content: html`
268
+ <div style="padding: 20px;">
269
+ <dees-dataview-codebox
270
+ .heading=${'Certificate Details'}
271
+ progLang="json"
272
+ .codeToDisplay=${JSON.stringify(cert, null, 2)}
273
+ ></dees-dataview-codebox>
274
+ </div>
275
+ `,
276
+ menuOptions: [
277
+ {
278
+ name: 'Copy Route Name',
279
+ iconName: 'copy',
280
+ action: async () => {
281
+ await navigator.clipboard.writeText(cert.routeName);
282
+ },
283
+ },
284
+ ],
285
+ });
286
+ },
287
+ },
288
+ ]}
289
+ heading1="Certificate Status"
290
+ heading2="TLS certificates across all routes"
291
+ searchable
292
+ .pagination=${true}
293
+ .paginationSize=${50}
294
+ dataName="certificate"
295
+ ></dees-table>
296
+ `;
297
+ }
298
+
299
+ private renderDomainPills(domains: string[]): TemplateResult {
300
+ const maxShow = 3;
301
+ const visible = domains.slice(0, maxShow);
302
+ const remaining = domains.length - maxShow;
303
+
304
+ return html`
305
+ <span class="domainPills">
306
+ ${visible.map((d) => html`<span class="domainPill">${d}</span>`)}
307
+ ${remaining > 0 ? html`<span class="moreCount">+${remaining} more</span>` : ''}
308
+ </span>
309
+ `;
310
+ }
311
+
312
+ private renderStatusBadge(status: interfaces.requests.TCertificateStatus): TemplateResult {
313
+ return html`<span class="statusBadge ${status}">${status}</span>`;
314
+ }
315
+
316
+ private renderSourceBadge(source: interfaces.requests.TCertificateSource): TemplateResult {
317
+ const labels: Record<string, string> = {
318
+ acme: 'ACME',
319
+ 'provision-function': 'Custom',
320
+ static: 'Static',
321
+ none: 'None',
322
+ };
323
+ return html`<span class="sourceBadge">${labels[source] || source}</span>`;
324
+ }
325
+
326
+ private renderExpiry(expiryDate?: string): TemplateResult {
327
+ if (!expiryDate) {
328
+ return html`<span style="color: ${cssManager.bdTheme('#9ca3af', '#4b5563')}">--</span>`;
329
+ }
330
+
331
+ const expiry = new Date(expiryDate);
332
+ const now = new Date();
333
+ const daysLeft = Math.ceil((expiry.getTime() - now.getTime()) / (1000 * 60 * 60 * 24));
334
+
335
+ const dateStr = expiry.toLocaleDateString();
336
+ let daysClass = '';
337
+ let daysText = '';
338
+
339
+ if (daysLeft < 0) {
340
+ daysClass = 'danger';
341
+ daysText = `(expired)`;
342
+ } else if (daysLeft < 30) {
343
+ daysClass = 'warn';
344
+ daysText = `(${daysLeft}d left)`;
345
+ } else {
346
+ daysText = `(${daysLeft}d left)`;
347
+ }
348
+
349
+ return html`
350
+ <span class="expiryInfo">
351
+ ${dateStr} <span class="daysLeft ${daysClass}">${daysText}</span>
352
+ </span>
353
+ `;
354
+ }
355
+ }
package/ts_web/router.ts CHANGED
@@ -3,7 +3,7 @@ import * as appstate from './appstate.js';
3
3
 
4
4
  const SmartRouter = plugins.domtools.plugins.smartrouter.SmartRouter;
5
5
 
6
- export const validViews = ['overview', 'network', 'emails', 'logs', 'configuration', 'security'] as const;
6
+ export const validViews = ['overview', 'network', 'emails', 'logs', 'configuration', 'security', 'certificates'] as const;
7
7
  export const validEmailFolders = ['queued', 'sent', 'failed', 'security'] as const;
8
8
 
9
9
  export type TValidView = typeof validViews[number];