@serve.zone/dcrouter 16.2.1 → 16.3.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 (38) hide show
  1. package/deno.json +1 -1
  2. package/dist_serve/bundle.js +1024 -960
  3. package/dist_ts/00_commitinfo_data.js +1 -1
  4. package/dist_ts/config/helpers.http-redirects.d.ts +1 -1
  5. package/dist_ts/config/helpers.http-redirects.js +4 -3
  6. package/dist_ts_interfaces/data/route-management.d.ts +2 -0
  7. package/dist_ts_interfaces/data/route-management.js +6 -1
  8. package/dist_ts_web/00_commitinfo_data.js +1 -1
  9. package/dist_ts_web/elements/access/ops-view-apitokens.js +3 -3
  10. package/dist_ts_web/elements/access/ops-view-gatewayclients.js +3 -3
  11. package/dist_ts_web/elements/network/ops-view-network-activity.js +6 -6
  12. package/dist_ts_web/elements/network/ops-view-redirects.d.ts +2 -0
  13. package/dist_ts_web/elements/network/ops-view-redirects.js +14 -3
  14. package/dist_ts_web/elements/network/ops-view-remoteingress.js +3 -3
  15. package/dist_ts_web/elements/network/ops-view-routes.d.ts +6 -7
  16. package/dist_ts_web/elements/network/ops-view-routes.js +178 -132
  17. package/dist_ts_web/elements/network/ops-view-special-forwards.d.ts +2 -0
  18. package/dist_ts_web/elements/network/ops-view-special-forwards.js +38 -10
  19. package/dist_ts_web/elements/network/ops-view-vpn.js +3 -3
  20. package/dist_ts_web/elements/network/route-view-filters.d.ts +6 -0
  21. package/dist_ts_web/elements/network/route-view-filters.js +29 -0
  22. package/dist_ts_web/elements/security/ops-view-security-blocked.js +6 -6
  23. package/package.json +2 -2
  24. package/readme.md +1 -1
  25. package/ts/00_commitinfo_data.ts +1 -1
  26. package/ts/config/helpers.http-redirects.ts +6 -3
  27. package/ts_web/00_commitinfo_data.ts +1 -1
  28. package/ts_web/elements/access/ops-view-apitokens.ts +2 -2
  29. package/ts_web/elements/access/ops-view-gatewayclients.ts +2 -2
  30. package/ts_web/elements/network/ops-view-network-activity.ts +5 -5
  31. package/ts_web/elements/network/ops-view-redirects.ts +9 -1
  32. package/ts_web/elements/network/ops-view-remoteingress.ts +2 -2
  33. package/ts_web/elements/network/ops-view-routes.ts +197 -138
  34. package/ts_web/elements/network/ops-view-special-forwards.ts +35 -7
  35. package/ts_web/elements/network/ops-view-vpn.ts +2 -2
  36. package/ts_web/elements/network/route-view-filters.ts +43 -0
  37. package/ts_web/elements/security/ops-view-security-blocked.ts +5 -5
  38. package/ts_web/readme.md +4 -4
@@ -4,12 +4,18 @@ import {
4
4
  cssManager,
5
5
  customElement,
6
6
  html,
7
+ property,
7
8
  state,
8
9
  type TemplateResult,
9
10
  } from '@design.estate/dees-element';
10
11
  import * as appstate from '../../appstate.js';
11
12
  import * as interfaces from '../../../dist_ts_interfaces/index.js';
12
13
  import { viewHostCss } from '../shared/css.js';
14
+ import {
15
+ getRouteOwner,
16
+ routeMatchesOwner,
17
+ type TRouteOwnerFilter,
18
+ } from './route-view-filters.js';
13
19
 
14
20
  const managedRouteKind = 'letsencrypt-http01-forward' as const;
15
21
 
@@ -40,6 +46,9 @@ function getTarget(route: interfaces.data.IMergedRoute): { host: string; port: n
40
46
 
41
47
  @customElement('ops-view-special-forwards')
42
48
  export class OpsViewSpecialForwards extends DeesElement {
49
+ @property({ type: String })
50
+ accessor ownerFilter: TRouteOwnerFilter = 'all';
51
+
43
52
  @state()
44
53
  accessor routeState: appstate.IRouteManagementState = appstate.routeManagementStatePart.getState()!;
45
54
 
@@ -98,7 +107,7 @@ export class OpsViewSpecialForwards extends DeesElement {
98
107
  public render(): TemplateResult {
99
108
  const forwards = this.routeState.mergedRoutes.filter((route) => {
100
109
  return route.metadata?.managedRouteKind === managedRouteKind
101
- && route.metadata.ownerType !== 'gatewayClient';
110
+ && routeMatchesOwner(route, this.ownerFilter);
102
111
  });
103
112
 
104
113
  return html`
@@ -120,9 +129,15 @@ export class OpsViewSpecialForwards extends DeesElement {
120
129
  .showColumnFilters=${true}
121
130
  .displayFunction=${(route: interfaces.data.IMergedRoute) => {
122
131
  const target = getTarget(route);
132
+ const owner = getRouteOwner(route);
123
133
  return {
124
134
  Status: route.enabled ? 'Enabled' : 'Disabled',
125
135
  Type: 'Let\'s Encrypt HTTP-01',
136
+ Owner: owner === 'operator'
137
+ ? 'User'
138
+ : owner === 'gatewayClient'
139
+ ? 'Gateway Client'
140
+ : 'System',
126
141
  Name: route.route.name || route.id,
127
142
  Domains: getDomains(route).join(', '),
128
143
  Path: route.route.match.path || '-',
@@ -132,12 +147,16 @@ export class OpsViewSpecialForwards extends DeesElement {
132
147
  };
133
148
  }}
134
149
  .dataActions=${[
135
- {
136
- name: 'Add HTTP-01 Forward',
137
- iconName: 'lucide:plus',
138
- type: ['header' as const],
139
- actionFunc: async () => this.showForwardDialog(),
140
- },
150
+ ...(this.ownerFilter === 'all' || this.ownerFilter === 'operator'
151
+ ? [
152
+ {
153
+ name: 'Add HTTP-01 Forward',
154
+ iconName: 'lucide:plus',
155
+ type: ['header' as const],
156
+ actionFunc: async () => this.showForwardDialog(),
157
+ },
158
+ ]
159
+ : []),
141
160
  {
142
161
  name: 'Refresh',
143
162
  iconName: 'lucide:refreshCw',
@@ -148,6 +167,9 @@ export class OpsViewSpecialForwards extends DeesElement {
148
167
  name: 'Edit',
149
168
  iconName: 'lucide:pencil',
150
169
  type: ['inRow', 'contextmenu'] as any,
170
+ actionRelevancyCheckFunc: (route: interfaces.data.IMergedRoute) => {
171
+ return getRouteOwner(route) === 'operator';
172
+ },
151
173
  actionFunc: async (actionData: any) => {
152
174
  await this.showForwardDialog(actionData.item as interfaces.data.IMergedRoute);
153
175
  },
@@ -156,6 +178,9 @@ export class OpsViewSpecialForwards extends DeesElement {
156
178
  name: 'Enable / Disable',
157
179
  iconName: 'lucide:power',
158
180
  type: ['inRow', 'contextmenu'] as any,
181
+ actionRelevancyCheckFunc: (route: interfaces.data.IMergedRoute) => {
182
+ return getRouteOwner(route) === 'operator';
183
+ },
159
184
  actionFunc: async (actionData: any) => {
160
185
  const route = actionData.item as interfaces.data.IMergedRoute;
161
186
  await appstate.routeManagementStatePart.dispatchAction(
@@ -168,6 +193,9 @@ export class OpsViewSpecialForwards extends DeesElement {
168
193
  name: 'Delete',
169
194
  iconName: 'lucide:trash2',
170
195
  type: ['inRow', 'contextmenu'] as any,
196
+ actionRelevancyCheckFunc: (route: interfaces.data.IMergedRoute) => {
197
+ return getRouteOwner(route) === 'operator';
198
+ },
171
199
  actionFunc: async (actionData: any) => {
172
200
  await this.confirmDelete(actionData.item as interfaces.data.IMergedRoute);
173
201
  },
@@ -515,7 +515,7 @@ export class OpsViewVpn extends DeesElement {
515
515
  name: 'Enable',
516
516
  iconName: 'lucide:power',
517
517
  type: ['contextmenu', 'inRow'],
518
- actionRelevancyCheckFunc: (actionData: any) => !actionData.item.enabled,
518
+ actionRelevancyCheckFunc: (client: any) => !client.enabled,
519
519
  actionFunc: async (actionData: any) => {
520
520
  const client = actionData.item as interfaces.data.IVpnClient;
521
521
  await appstate.vpnStatePart.dispatchAction(appstate.toggleVpnClientAction, {
@@ -528,7 +528,7 @@ export class OpsViewVpn extends DeesElement {
528
528
  name: 'Disable',
529
529
  iconName: 'lucide:power',
530
530
  type: ['contextmenu', 'inRow'],
531
- actionRelevancyCheckFunc: (actionData: any) => actionData.item.enabled,
531
+ actionRelevancyCheckFunc: (client: any) => client.enabled,
532
532
  actionFunc: async (actionData: any) => {
533
533
  const client = actionData.item as interfaces.data.IVpnClient;
534
534
  await appstate.vpnStatePart.dispatchAction(appstate.toggleVpnClientAction, {
@@ -0,0 +1,43 @@
1
+ import {
2
+ canonicalizeSourceRouteId,
3
+ type IHttpRedirectInfo,
4
+ type IMergedRoute,
5
+ } from '../../../dist_ts_interfaces/data/route-management.js';
6
+
7
+ export type TRouteOwnerFilter = 'all' | 'operator' | 'gatewayClient' | 'system';
8
+ export type TResolvedRouteOwner = Exclude<TRouteOwnerFilter, 'all'>;
9
+
10
+ export function getRouteOwner(route: IMergedRoute): TResolvedRouteOwner {
11
+ if (route.metadata?.ownerType === 'gatewayClient') return 'gatewayClient';
12
+ if (route.metadata?.ownerType === 'system') return 'system';
13
+ if (route.metadata?.ownerType === 'operator') return 'operator';
14
+ return route.origin === 'api' ? 'operator' : 'system';
15
+ }
16
+
17
+ export function routeMatchesOwner(
18
+ route: IMergedRoute,
19
+ ownerFilter: TRouteOwnerFilter,
20
+ ): boolean {
21
+ return ownerFilter === 'all' || getRouteOwner(route) === ownerFilter;
22
+ }
23
+
24
+ function getCanonicalRouteIds(route: IMergedRoute): string[] {
25
+ const configuredRouteId = (route.route as { id?: string }).id;
26
+ return [route.id, configuredRouteId]
27
+ .filter((routeId): routeId is string => Boolean(routeId))
28
+ .map(canonicalizeSourceRouteId);
29
+ }
30
+
31
+ export function redirectMatchesOwner(
32
+ redirect: IHttpRedirectInfo,
33
+ mergedRoutes: IMergedRoute[],
34
+ ownerFilter: TRouteOwnerFilter,
35
+ ): boolean {
36
+ if (ownerFilter === 'all') return true;
37
+
38
+ const sourceRouteIds = new Set(redirect.sourceRouteIds.map(canonicalizeSourceRouteId));
39
+ return mergedRoutes.some((route) => {
40
+ return getCanonicalRouteIds(route).some((routeId) => sourceRouteIds.has(routeId))
41
+ && getRouteOwner(route) === ownerFilter;
42
+ });
43
+ }
@@ -268,7 +268,7 @@ export class OpsViewSecurityBlocked extends DeesElement {
268
268
  name: 'Enable',
269
269
  iconName: 'lucide:play',
270
270
  type: ['contextmenu'] as any,
271
- actionRelevancyCheckFunc: (actionData: any) => !actionData.item.enabled,
271
+ actionRelevancyCheckFunc: (entry: any) => !entry.enabled,
272
272
  actionFunc: async (actionData: any) => {
273
273
  const rule = actionData.item as interfaces.data.ISecurityBlockRule;
274
274
  await appstate.securityPolicyStatePart.dispatchAction(appstate.updateSecurityBlockRuleAction, {
@@ -281,7 +281,7 @@ export class OpsViewSecurityBlocked extends DeesElement {
281
281
  name: 'Disable',
282
282
  iconName: 'lucide:pause',
283
283
  type: ['contextmenu'] as any,
284
- actionRelevancyCheckFunc: (actionData: any) => actionData.item.enabled,
284
+ actionRelevancyCheckFunc: (entry: any) => entry.enabled,
285
285
  actionFunc: async (actionData: any) => {
286
286
  const rule = actionData.item as interfaces.data.ISecurityBlockRule;
287
287
  await appstate.securityPolicyStatePart.dispatchAction(appstate.updateSecurityBlockRuleAction, {
@@ -331,7 +331,7 @@ export class OpsViewSecurityBlocked extends DeesElement {
331
331
  name: 'Block Network Range',
332
332
  iconName: 'lucide:network',
333
333
  type: ['contextmenu'] as any,
334
- actionRelevancyCheckFunc: (actionData: any) => Boolean(actionData.item.networkRange),
334
+ actionRelevancyCheckFunc: (entry: any) => Boolean(entry.networkRange),
335
335
  actionFunc: async (actionData: any) => {
336
336
  const record = actionData.item as interfaces.data.IIpIntelligenceRecord;
337
337
  await this.showRuleDialog(undefined, {
@@ -345,7 +345,7 @@ export class OpsViewSecurityBlocked extends DeesElement {
345
345
  name: 'Block ASN',
346
346
  iconName: 'lucide:radio-tower',
347
347
  type: ['contextmenu'] as any,
348
- actionRelevancyCheckFunc: (actionData: any) => Boolean(actionData.item.asn),
348
+ actionRelevancyCheckFunc: (entry: any) => Boolean(entry.asn),
349
349
  actionFunc: async (actionData: any) => {
350
350
  const record = actionData.item as interfaces.data.IIpIntelligenceRecord;
351
351
  await this.showRuleDialog(undefined, {
@@ -359,7 +359,7 @@ export class OpsViewSecurityBlocked extends DeesElement {
359
359
  name: 'Block Organization',
360
360
  iconName: 'lucide:building-2',
361
361
  type: ['contextmenu'] as any,
362
- actionRelevancyCheckFunc: (actionData: any) => Boolean(actionData.item.asnOrg || actionData.item.registrantOrg),
362
+ actionRelevancyCheckFunc: (entry: any) => Boolean(entry.asnOrg || entry.registrantOrg),
363
363
  actionFunc: async (actionData: any) => {
364
364
  const record = actionData.item as interfaces.data.IIpIntelligenceRecord;
365
365
  await this.showRuleDialog(undefined, {
package/ts_web/readme.md CHANGED
@@ -29,12 +29,12 @@ For reporting bugs, issues, or security vulnerabilities, please visit [community
29
29
 
30
30
  ## Routes Navigation
31
31
 
32
- The `/network/routes` view uses two levels of tabs:
32
+ The `/network/routes` view uses two adjacent, independent multitoggles:
33
33
 
34
- - primary sections: **Standard Routes**, **Special Forwards**, and **Redirects**
35
- - ownership filters within Standard Routes: **All**, **Operator**, **Gateway Client**, and **System**
34
+ - type: **All**, **Standard**, **Redirect**, and **Special**
35
+ - owner: **All**, **User**, **Gateway Client**, and **System**
36
36
 
37
- Special Forwards create server-managed Let's Encrypt HTTP-01 routes for exact domains and a selected inline or Network Target backend. Redirects remain a derived runtime view. Tab state is stored in the `tab` and `owner` query parameters so deep links and browser history remain stable; the legacy `/network/redirects` URL is normalized to `/network/routes?tab=redirects`.
37
+ The owner selection filters every displayed route projection. **All** keeps Standard, Special, and Redirect routes in separate sections rather than mixing unlike rows. Special routes include managed Let's Encrypt HTTP-01 forwards for exact domains and a selected inline or Network Target backend; non-user routes are read-only. Redirects remain a derived runtime view and inherit ownership from their canonical source routes. Filter state is stored in the `tab` and `owner` query parameters so deep links and browser history remain stable; the legacy `/network/redirects` URL is normalized to `/network/routes?tab=redirects`.
38
38
 
39
39
  ## Runtime Communication
40
40