@serve.zone/dcrouter 6.6.1 → 6.8.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.
@@ -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
 
@@ -187,7 +198,7 @@ export class OpsViewRemoteIngress extends DeesElement {
187
198
  name: edge.name,
188
199
  status: this.getEdgeStatusHtml(edge),
189
200
  publicIp: this.getEdgePublicIp(edge.id),
190
- ports: this.getPortsHtml(edge.listenPorts),
201
+ ports: this.getPortsHtml(edge),
191
202
  tunnels: this.getEdgeTunnelCount(edge.id),
192
203
  lastHeartbeat: this.getLastHeartbeat(edge.id),
193
204
  })}
@@ -198,42 +209,137 @@ export class OpsViewRemoteIngress extends DeesElement {
198
209
  type: ['header'],
199
210
  actionFunc: async () => {
200
211
  const { DeesModal } = await import('@design.estate/dees-catalog');
201
- const result = await DeesModal.createAndShow({
212
+ const modal = await DeesModal.createAndShow({
202
213
  heading: 'Create Edge Node',
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=${'Listen Ports (comma-separated)'} .required=${true} .value=${'443,25'}></dees-input-text>
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
  `,
210
- menuOptions: [],
222
+ menuOptions: [
223
+ {
224
+ name: 'Cancel',
225
+ iconName: 'lucide:x',
226
+ action: async (modalArg: any) => await modalArg.destroy(),
227
+ },
228
+ {
229
+ name: 'Create',
230
+ iconName: 'lucide:plus',
231
+ action: async (modalArg: any) => {
232
+ const form = modalArg.shadowRoot?.querySelector('.content')?.querySelector('dees-form');
233
+ if (!form) return;
234
+ const formData = await form.collectFormData();
235
+ const name = formData.name;
236
+ if (!name) return;
237
+ const portsStr = formData.listenPorts?.trim();
238
+ const listenPorts = portsStr
239
+ ? portsStr.split(',').map((p: string) => parseInt(p.trim(), 10)).filter((p: number) => !isNaN(p))
240
+ : undefined;
241
+ const autoDerivePorts = formData.autoDerivePorts !== false;
242
+ const tags = formData.tags
243
+ ? formData.tags.split(',').map((t: string) => t.trim()).filter(Boolean)
244
+ : undefined;
245
+ await appstate.remoteIngressStatePart.dispatchAction(
246
+ appstate.createRemoteIngressAction,
247
+ { name, listenPorts, autoDerivePorts, tags },
248
+ );
249
+ await modalArg.destroy();
250
+ },
251
+ },
252
+ ],
211
253
  });
212
- if (result) {
213
- const formData = result as any;
214
- const ports = (formData.name ? formData.listenPorts : '443')
215
- .split(',')
216
- .map((p: string) => parseInt(p.trim(), 10))
217
- .filter((p: number) => !isNaN(p));
218
- const tags = formData.tags
219
- ? formData.tags.split(',').map((t: string) => t.trim()).filter(Boolean)
220
- : undefined;
221
- await appstate.remoteIngressStatePart.dispatchAction(
222
- appstate.createRemoteIngressAction,
254
+ },
255
+ },
256
+ {
257
+ name: 'Enable',
258
+ iconName: 'lucide:play',
259
+ type: ['inRow', 'contextmenu'] as any,
260
+ actionRelevancyCheckFunc: (actionData: any) => !actionData.item.enabled,
261
+ actionFunc: async (actionData: any) => {
262
+ const edge = actionData.item as interfaces.data.IRemoteIngress;
263
+ await appstate.remoteIngressStatePart.dispatchAction(
264
+ appstate.toggleRemoteIngressAction,
265
+ { id: edge.id, enabled: true },
266
+ );
267
+ },
268
+ },
269
+ {
270
+ name: 'Disable',
271
+ iconName: 'lucide:pause',
272
+ type: ['inRow', 'contextmenu'] as any,
273
+ actionRelevancyCheckFunc: (actionData: any) => actionData.item.enabled,
274
+ actionFunc: async (actionData: any) => {
275
+ const edge = actionData.item as interfaces.data.IRemoteIngress;
276
+ await appstate.remoteIngressStatePart.dispatchAction(
277
+ appstate.toggleRemoteIngressAction,
278
+ { id: edge.id, enabled: false },
279
+ );
280
+ },
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: [
223
300
  {
224
- name: formData.name,
225
- listenPorts: ports,
226
- tags,
301
+ name: 'Cancel',
302
+ iconName: 'lucide:x',
303
+ action: async (modalArg: any) => await modalArg.destroy(),
227
304
  },
228
- );
229
- }
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
+ });
230
335
  },
231
336
  },
232
337
  {
233
338
  name: 'Regenerate Secret',
234
339
  iconName: 'lucide:key',
235
- type: ['row'],
236
- action: async (edge: interfaces.data.IRemoteIngress) => {
340
+ type: ['inRow', 'contextmenu'] as any,
341
+ actionFunc: async (actionData: any) => {
342
+ const edge = actionData.item as interfaces.data.IRemoteIngress;
237
343
  await appstate.remoteIngressStatePart.dispatchAction(
238
344
  appstate.regenerateRemoteIngressSecretAction,
239
345
  edge.id,
@@ -243,8 +349,9 @@ export class OpsViewRemoteIngress extends DeesElement {
243
349
  {
244
350
  name: 'Delete',
245
351
  iconName: 'lucide:trash2',
246
- type: ['row'],
247
- action: async (edge: interfaces.data.IRemoteIngress) => {
352
+ type: ['inRow', 'contextmenu'] as any,
353
+ actionFunc: async (actionData: any) => {
354
+ const edge = actionData.item as interfaces.data.IRemoteIngress;
248
355
  await appstate.remoteIngressStatePart.dispatchAction(
249
356
  appstate.deleteRemoteIngressAction,
250
357
  edge.id,
@@ -277,8 +384,13 @@ export class OpsViewRemoteIngress extends DeesElement {
277
384
  return status?.publicIp || '-';
278
385
  }
279
386
 
280
- private getPortsHtml(ports: number[]): TemplateResult {
281
- return html`<div class="portsDisplay">${ports.map(p => html`<span class="portBadge">${p}</span>`)}</div>`;
387
+ private getPortsHtml(edge: interfaces.data.IRemoteIngress): TemplateResult {
388
+ const manualPorts = edge.manualPorts || [];
389
+ const derivedPorts = edge.derivedPorts || [];
390
+ if (manualPorts.length === 0 && derivedPorts.length === 0) {
391
+ return html`<span style="color: var(--text-muted, #6b7280); font-size: 12px;">none</span>`;
392
+ }
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>`;
282
394
  }
283
395
 
284
396
  private getEdgeTunnelCount(edgeId: string): number {