@serve.zone/dcrouter 6.7.0 → 6.9.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.
- package/dist_serve/bundle.js +2423 -2389
- package/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/opsserver/handlers/certificate.handler.d.ts +12 -0
- package/dist_ts/opsserver/handlers/certificate.handler.js +127 -1
- package/dist_ts/opsserver/handlers/remoteingress.handler.js +25 -9
- package/dist_ts/remoteingress/classes.remoteingress-manager.d.ts +11 -2
- package/dist_ts/remoteingress/classes.remoteingress-manager.js +31 -7
- package/dist_ts_interfaces/data/remoteingress.d.ts +7 -1
- package/dist_ts_interfaces/requests/certificate.d.ts +50 -0
- package/dist_ts_interfaces/requests/remoteingress.d.ts +2 -0
- package/dist_ts_web/00_commitinfo_data.js +1 -1
- package/dist_ts_web/appstate.d.ts +31 -0
- package/dist_ts_web/appstate.js +73 -1
- package/dist_ts_web/elements/ops-view-certificates.d.ts +1 -0
- package/dist_ts_web/elements/ops-view-certificates.js +123 -1
- package/dist_ts_web/elements/ops-view-remoteingress.js +73 -8
- package/package.json +1 -1
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/opsserver/handlers/certificate.handler.ts +180 -0
- package/ts/opsserver/handlers/remoteingress.handler.ts +24 -7
- package/ts/remoteingress/classes.remoteingress-manager.ts +29 -5
- package/ts_web/00_commitinfo_data.ts +1 -1
- package/ts_web/appstate.ts +110 -0
- package/ts_web/elements/ops-view-certificates.ts +125 -0
- package/ts_web/elements/ops-view-remoteingress.ts +74 -7
|
@@ -241,6 +241,61 @@ export class OpsViewCertificates extends DeesElement {
|
|
|
241
241
|
: '',
|
|
242
242
|
})}
|
|
243
243
|
.dataActions=${[
|
|
244
|
+
{
|
|
245
|
+
name: 'Import Certificate',
|
|
246
|
+
iconName: 'lucide:upload',
|
|
247
|
+
type: ['header'],
|
|
248
|
+
actionFunc: async () => {
|
|
249
|
+
const { DeesModal } = await import('@design.estate/dees-catalog');
|
|
250
|
+
await DeesModal.createAndShow({
|
|
251
|
+
heading: 'Import Certificate',
|
|
252
|
+
content: html`
|
|
253
|
+
<dees-form>
|
|
254
|
+
<dees-input-fileupload
|
|
255
|
+
key="certJsonFile"
|
|
256
|
+
label="Certificate JSON (.tsclass.cert.json)"
|
|
257
|
+
accept=".json"
|
|
258
|
+
.multiple=${false}
|
|
259
|
+
required
|
|
260
|
+
></dees-input-fileupload>
|
|
261
|
+
</dees-form>
|
|
262
|
+
`,
|
|
263
|
+
menuOptions: [
|
|
264
|
+
{
|
|
265
|
+
name: 'Import',
|
|
266
|
+
iconName: 'lucide:upload',
|
|
267
|
+
action: async (modal) => {
|
|
268
|
+
const { DeesToast } = await import('@design.estate/dees-catalog');
|
|
269
|
+
try {
|
|
270
|
+
const form = modal.shadowRoot.querySelector('dees-form') as any;
|
|
271
|
+
const formData = await form.collectFormData();
|
|
272
|
+
const files = formData.certJsonFile;
|
|
273
|
+
if (!files || files.length === 0) {
|
|
274
|
+
DeesToast.show({ message: 'Please select a JSON file.', type: 'warning', duration: 3000 });
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
277
|
+
const file = files[0];
|
|
278
|
+
const text = await file.text();
|
|
279
|
+
const cert = JSON.parse(text);
|
|
280
|
+
if (!cert.domainName || !cert.publicKey || !cert.privateKey) {
|
|
281
|
+
DeesToast.show({ message: 'Invalid cert JSON: missing domainName, publicKey, or privateKey.', type: 'error', duration: 4000 });
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
284
|
+
await appstate.certificateStatePart.dispatchAction(
|
|
285
|
+
appstate.importCertificateAction,
|
|
286
|
+
cert,
|
|
287
|
+
);
|
|
288
|
+
DeesToast.show({ message: `Certificate imported for ${cert.domainName}`, type: 'success', duration: 3000 });
|
|
289
|
+
modal.destroy();
|
|
290
|
+
} catch (err) {
|
|
291
|
+
DeesToast.show({ message: `Import failed: ${err.message}`, type: 'error', duration: 4000 });
|
|
292
|
+
}
|
|
293
|
+
},
|
|
294
|
+
},
|
|
295
|
+
],
|
|
296
|
+
});
|
|
297
|
+
},
|
|
298
|
+
},
|
|
244
299
|
{
|
|
245
300
|
name: 'Reprovision',
|
|
246
301
|
iconName: 'lucide:RefreshCw',
|
|
@@ -268,6 +323,63 @@ export class OpsViewCertificates extends DeesElement {
|
|
|
268
323
|
});
|
|
269
324
|
},
|
|
270
325
|
},
|
|
326
|
+
{
|
|
327
|
+
name: 'Export',
|
|
328
|
+
iconName: 'lucide:download',
|
|
329
|
+
type: ['contextmenu'],
|
|
330
|
+
actionFunc: async (actionData: { item: interfaces.requests.ICertificateInfo }) => {
|
|
331
|
+
const { DeesToast } = await import('@design.estate/dees-catalog');
|
|
332
|
+
const cert = actionData.item;
|
|
333
|
+
try {
|
|
334
|
+
const response = await appstate.fetchCertificateExport(cert.domain);
|
|
335
|
+
if (response.success && response.cert) {
|
|
336
|
+
const safeDomain = cert.domain.replace(/\*/g, '_wildcard');
|
|
337
|
+
this.downloadJsonFile(`${safeDomain}.tsclass.cert.json`, response.cert);
|
|
338
|
+
DeesToast.show({ message: `Certificate exported for ${cert.domain}`, type: 'success', duration: 3000 });
|
|
339
|
+
} else {
|
|
340
|
+
DeesToast.show({ message: response.message || 'Export failed', type: 'error', duration: 4000 });
|
|
341
|
+
}
|
|
342
|
+
} catch (err) {
|
|
343
|
+
DeesToast.show({ message: `Export failed: ${err.message}`, type: 'error', duration: 4000 });
|
|
344
|
+
}
|
|
345
|
+
},
|
|
346
|
+
},
|
|
347
|
+
{
|
|
348
|
+
name: 'Delete',
|
|
349
|
+
iconName: 'lucide:trash-2',
|
|
350
|
+
type: ['contextmenu'],
|
|
351
|
+
actionFunc: async (actionData: { item: interfaces.requests.ICertificateInfo }) => {
|
|
352
|
+
const cert = actionData.item;
|
|
353
|
+
const { DeesModal, DeesToast } = await import('@design.estate/dees-catalog');
|
|
354
|
+
await DeesModal.createAndShow({
|
|
355
|
+
heading: `Delete Certificate: ${cert.domain}`,
|
|
356
|
+
content: html`
|
|
357
|
+
<div style="padding: 20px; font-size: 14px;">
|
|
358
|
+
<p>Are you sure you want to delete the certificate data for <strong>${cert.domain}</strong>?</p>
|
|
359
|
+
<p style="color: #f59e0b; margin-top: 12px;">Note: The certificate may remain in proxy memory until the next restart or reprovisioning.</p>
|
|
360
|
+
</div>
|
|
361
|
+
`,
|
|
362
|
+
menuOptions: [
|
|
363
|
+
{
|
|
364
|
+
name: 'Delete',
|
|
365
|
+
iconName: 'lucide:trash-2',
|
|
366
|
+
action: async (modal) => {
|
|
367
|
+
try {
|
|
368
|
+
await appstate.certificateStatePart.dispatchAction(
|
|
369
|
+
appstate.deleteCertificateAction,
|
|
370
|
+
cert.domain,
|
|
371
|
+
);
|
|
372
|
+
DeesToast.show({ message: `Certificate deleted for ${cert.domain}`, type: 'success', duration: 3000 });
|
|
373
|
+
modal.destroy();
|
|
374
|
+
} catch (err) {
|
|
375
|
+
DeesToast.show({ message: `Delete failed: ${err.message}`, type: 'error', duration: 4000 });
|
|
376
|
+
}
|
|
377
|
+
},
|
|
378
|
+
},
|
|
379
|
+
],
|
|
380
|
+
});
|
|
381
|
+
},
|
|
382
|
+
},
|
|
271
383
|
{
|
|
272
384
|
name: 'View Details',
|
|
273
385
|
iconName: 'lucide:Search',
|
|
@@ -309,6 +421,19 @@ export class OpsViewCertificates extends DeesElement {
|
|
|
309
421
|
`;
|
|
310
422
|
}
|
|
311
423
|
|
|
424
|
+
private downloadJsonFile(filename: string, data: any): void {
|
|
425
|
+
const json = JSON.stringify(data, null, 2);
|
|
426
|
+
const blob = new Blob([json], { type: 'application/json' });
|
|
427
|
+
const url = URL.createObjectURL(blob);
|
|
428
|
+
const a = document.createElement('a');
|
|
429
|
+
a.href = url;
|
|
430
|
+
a.download = filename;
|
|
431
|
+
document.body.appendChild(a);
|
|
432
|
+
a.click();
|
|
433
|
+
document.body.removeChild(a);
|
|
434
|
+
URL.revokeObjectURL(url);
|
|
435
|
+
}
|
|
436
|
+
|
|
312
437
|
private renderRoutePills(routeNames: string[]): TemplateResult {
|
|
313
438
|
const maxShow = 3;
|
|
314
439
|
const visible = routeNames.slice(0, maxShow);
|
|
@@ -114,6 +114,17 @@ export class OpsViewRemoteIngress extends DeesElement {
|
|
|
114
114
|
background: ${cssManager.bdTheme('#eff6ff', '#172554')};
|
|
115
115
|
color: ${cssManager.bdTheme('#1e40af', '#60a5fa')};
|
|
116
116
|
}
|
|
117
|
+
|
|
118
|
+
.portBadge.manual {
|
|
119
|
+
background: ${cssManager.bdTheme('#eff6ff', '#172554')};
|
|
120
|
+
color: ${cssManager.bdTheme('#1e40af', '#60a5fa')};
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
.portBadge.derived {
|
|
124
|
+
background: ${cssManager.bdTheme('#ecfdf5', '#022c22')};
|
|
125
|
+
color: ${cssManager.bdTheme('#047857', '#34d399')};
|
|
126
|
+
border: 1px dashed ${cssManager.bdTheme('#6ee7b7', '#065f46')};
|
|
127
|
+
}
|
|
117
128
|
`,
|
|
118
129
|
];
|
|
119
130
|
|
|
@@ -203,7 +214,8 @@ export class OpsViewRemoteIngress extends DeesElement {
|
|
|
203
214
|
content: html`
|
|
204
215
|
<dees-form>
|
|
205
216
|
<dees-input-text .key=${'name'} .label=${'Name'} .required=${true}></dees-input-text>
|
|
206
|
-
<dees-input-text .key=${'listenPorts'} .label=${'
|
|
217
|
+
<dees-input-text .key=${'listenPorts'} .label=${'Additional Manual Ports (comma-separated, optional)'}></dees-input-text>
|
|
218
|
+
<dees-input-checkbox .key=${'autoDerivePorts'} .label=${'Auto-derive ports from routes'} .value=${true}></dees-input-checkbox>
|
|
207
219
|
<dees-input-text .key=${'tags'} .label=${'Tags (comma-separated, optional)'}></dees-input-text>
|
|
208
220
|
</dees-form>
|
|
209
221
|
`,
|
|
@@ -226,12 +238,13 @@ export class OpsViewRemoteIngress extends DeesElement {
|
|
|
226
238
|
const listenPorts = portsStr
|
|
227
239
|
? portsStr.split(',').map((p: string) => parseInt(p.trim(), 10)).filter((p: number) => !isNaN(p))
|
|
228
240
|
: undefined;
|
|
241
|
+
const autoDerivePorts = formData.autoDerivePorts !== false;
|
|
229
242
|
const tags = formData.tags
|
|
230
243
|
? formData.tags.split(',').map((t: string) => t.trim()).filter(Boolean)
|
|
231
244
|
: undefined;
|
|
232
245
|
await appstate.remoteIngressStatePart.dispatchAction(
|
|
233
246
|
appstate.createRemoteIngressAction,
|
|
234
|
-
{ name, listenPorts, tags },
|
|
247
|
+
{ name, listenPorts, autoDerivePorts, tags },
|
|
235
248
|
);
|
|
236
249
|
await modalArg.destroy();
|
|
237
250
|
},
|
|
@@ -266,6 +279,61 @@ export class OpsViewRemoteIngress extends DeesElement {
|
|
|
266
279
|
);
|
|
267
280
|
},
|
|
268
281
|
},
|
|
282
|
+
{
|
|
283
|
+
name: 'Edit',
|
|
284
|
+
iconName: 'lucide:pencil',
|
|
285
|
+
type: ['inRow', 'contextmenu'] as any,
|
|
286
|
+
actionFunc: async (actionData: any) => {
|
|
287
|
+
const edge = actionData.item as interfaces.data.IRemoteIngress;
|
|
288
|
+
const { DeesModal } = await import('@design.estate/dees-catalog');
|
|
289
|
+
await DeesModal.createAndShow({
|
|
290
|
+
heading: `Edit Edge: ${edge.name}`,
|
|
291
|
+
content: html`
|
|
292
|
+
<dees-form>
|
|
293
|
+
<dees-input-text .key=${'name'} .label=${'Name'} .value=${edge.name}></dees-input-text>
|
|
294
|
+
<dees-input-text .key=${'listenPorts'} .label=${'Manual Ports (comma-separated)'} .value=${(edge.listenPorts || []).join(', ')}></dees-input-text>
|
|
295
|
+
<dees-input-checkbox .key=${'autoDerivePorts'} .label=${'Auto-derive ports from routes'} .value=${edge.autoDerivePorts !== false}></dees-input-checkbox>
|
|
296
|
+
<dees-input-text .key=${'tags'} .label=${'Tags (comma-separated)'} .value=${(edge.tags || []).join(', ')}></dees-input-text>
|
|
297
|
+
</dees-form>
|
|
298
|
+
`,
|
|
299
|
+
menuOptions: [
|
|
300
|
+
{
|
|
301
|
+
name: 'Cancel',
|
|
302
|
+
iconName: 'lucide:x',
|
|
303
|
+
action: async (modalArg: any) => await modalArg.destroy(),
|
|
304
|
+
},
|
|
305
|
+
{
|
|
306
|
+
name: 'Save',
|
|
307
|
+
iconName: 'lucide:check',
|
|
308
|
+
action: async (modalArg: any) => {
|
|
309
|
+
const form = modalArg.shadowRoot?.querySelector('.content')?.querySelector('dees-form');
|
|
310
|
+
if (!form) return;
|
|
311
|
+
const formData = await form.collectFormData();
|
|
312
|
+
const portsStr = formData.listenPorts?.trim();
|
|
313
|
+
const listenPorts = portsStr
|
|
314
|
+
? portsStr.split(',').map((p: string) => parseInt(p.trim(), 10)).filter((p: number) => !isNaN(p))
|
|
315
|
+
: [];
|
|
316
|
+
const autoDerivePorts = formData.autoDerivePorts !== false;
|
|
317
|
+
const tags = formData.tags
|
|
318
|
+
? formData.tags.split(',').map((t: string) => t.trim()).filter(Boolean)
|
|
319
|
+
: [];
|
|
320
|
+
await appstate.remoteIngressStatePart.dispatchAction(
|
|
321
|
+
appstate.updateRemoteIngressAction,
|
|
322
|
+
{
|
|
323
|
+
id: edge.id,
|
|
324
|
+
name: formData.name || edge.name,
|
|
325
|
+
listenPorts,
|
|
326
|
+
autoDerivePorts,
|
|
327
|
+
tags,
|
|
328
|
+
},
|
|
329
|
+
);
|
|
330
|
+
await modalArg.destroy();
|
|
331
|
+
},
|
|
332
|
+
},
|
|
333
|
+
],
|
|
334
|
+
});
|
|
335
|
+
},
|
|
336
|
+
},
|
|
269
337
|
{
|
|
270
338
|
name: 'Regenerate Secret',
|
|
271
339
|
iconName: 'lucide:key',
|
|
@@ -317,13 +385,12 @@ export class OpsViewRemoteIngress extends DeesElement {
|
|
|
317
385
|
}
|
|
318
386
|
|
|
319
387
|
private getPortsHtml(edge: interfaces.data.IRemoteIngress): TemplateResult {
|
|
320
|
-
const
|
|
321
|
-
const
|
|
322
|
-
|
|
323
|
-
if (ports.length === 0) {
|
|
388
|
+
const manualPorts = edge.manualPorts || [];
|
|
389
|
+
const derivedPorts = edge.derivedPorts || [];
|
|
390
|
+
if (manualPorts.length === 0 && derivedPorts.length === 0) {
|
|
324
391
|
return html`<span style="color: var(--text-muted, #6b7280); font-size: 12px;">none</span>`;
|
|
325
392
|
}
|
|
326
|
-
return html`<div class="portsDisplay">${
|
|
393
|
+
return html`<div class="portsDisplay">${manualPorts.map(p => html`<span class="portBadge manual">${p}</span>`)}${derivedPorts.map(p => html`<span class="portBadge derived">${p}</span>`)}${derivedPorts.length > 0 ? html`<span style="font-size: 11px; color: var(--text-muted, #6b7280); align-self: center;">(auto)</span>` : ''}</div>`;
|
|
327
394
|
}
|
|
328
395
|
|
|
329
396
|
private getEdgeTunnelCount(edgeId: string): number {
|