Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 25365678e0 | |||
| 96d215fc66 | |||
| 648ba9e61d | |||
| fcc1d9fede | |||
| 336e8aa4cc | |||
| c8f19cf783 | |||
| 12b2cc11da | |||
| ffcc35be64 |
26
changelog.md
26
changelog.md
@@ -1,5 +1,31 @@
|
||||
# Changelog
|
||||
|
||||
## 2026-04-04 - 12.10.0 - feat(routes)
|
||||
add TLS configuration controls for route create and edit flows
|
||||
|
||||
- Adds TLS mode and certificate selection to the route create and edit dialogs, including support for custom PEM key/certificate input.
|
||||
- Allows route updates to explicitly remove nested TLS settings by treating null action properties as deletions during route patch merging.
|
||||
- Bumps @design.estate/dees-catalog to ^3.55.6 and @serve.zone/catalog to ^2.11.1.
|
||||
|
||||
## 2026-04-04 - 12.9.4 - fix(deps)
|
||||
bump @push.rocks/smartdb to ^2.3.1
|
||||
|
||||
- updates the @push.rocks/smartdb dependency from ^2.1.1 to ^2.3.1
|
||||
|
||||
## 2026-04-04 - 12.9.3 - fix(route-management)
|
||||
include stored VPN routes in domain resolution and align programmatic route types with dcrouter configs
|
||||
|
||||
- Scans enabled stored/programmatic routes for VPN domain matches when resolving client access domains.
|
||||
- Replaces generic smartproxy route typings with IDcRouterRouteConfig across route management and stored route models.
|
||||
- Updates @push.rocks/smartproxy to ^27.4.0.
|
||||
|
||||
## 2026-04-04 - 12.9.2 - fix(config-ui)
|
||||
handle missing HTTP/3 config safely and standardize overview section headings
|
||||
|
||||
- Prevents route augmentation logic from failing when HTTP/3 configuration is undefined by using optional chaining.
|
||||
- Updates the operations overview to use dees-heading components for activity, email, DNS, RADIUS, and VPN section headings.
|
||||
- Bumps @push.rocks/smartproxy from ^27.2.0 to ^27.3.1.
|
||||
|
||||
## 2026-04-04 - 12.9.1 - fix(monitoring)
|
||||
update SmartProxy and use direct connection protocol metrics access
|
||||
|
||||
|
||||
10
package.json
10
package.json
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@serve.zone/dcrouter",
|
||||
"private": false,
|
||||
"version": "12.9.1",
|
||||
"version": "12.10.0",
|
||||
"description": "A multifaceted routing service handling mail and SMS delivery functions.",
|
||||
"type": "module",
|
||||
"exports": {
|
||||
@@ -35,14 +35,14 @@
|
||||
"@api.global/typedserver": "^8.4.6",
|
||||
"@api.global/typedsocket": "^4.1.2",
|
||||
"@apiclient.xyz/cloudflare": "^7.1.0",
|
||||
"@design.estate/dees-catalog": "^3.55.5",
|
||||
"@design.estate/dees-catalog": "^3.55.6",
|
||||
"@design.estate/dees-element": "^2.2.4",
|
||||
"@push.rocks/lik": "^6.4.0",
|
||||
"@push.rocks/projectinfo": "^5.1.0",
|
||||
"@push.rocks/qenv": "^6.1.3",
|
||||
"@push.rocks/smartacme": "^9.4.0",
|
||||
"@push.rocks/smartdata": "^7.1.3",
|
||||
"@push.rocks/smartdb": "^2.1.1",
|
||||
"@push.rocks/smartdb": "^2.3.1",
|
||||
"@push.rocks/smartdns": "^7.9.0",
|
||||
"@push.rocks/smartfs": "^1.5.0",
|
||||
"@push.rocks/smartguard": "^3.1.0",
|
||||
@@ -53,7 +53,7 @@
|
||||
"@push.rocks/smartnetwork": "^4.5.2",
|
||||
"@push.rocks/smartpath": "^6.0.0",
|
||||
"@push.rocks/smartpromise": "^4.2.3",
|
||||
"@push.rocks/smartproxy": "^27.2.0",
|
||||
"@push.rocks/smartproxy": "^27.4.0",
|
||||
"@push.rocks/smartradius": "^1.1.1",
|
||||
"@push.rocks/smartrequest": "^5.0.1",
|
||||
"@push.rocks/smartrx": "^3.0.10",
|
||||
@@ -61,7 +61,7 @@
|
||||
"@push.rocks/smartunique": "^3.0.9",
|
||||
"@push.rocks/smartvpn": "1.19.1",
|
||||
"@push.rocks/taskbuffer": "^8.0.2",
|
||||
"@serve.zone/catalog": "^2.11.0",
|
||||
"@serve.zone/catalog": "^2.11.1",
|
||||
"@serve.zone/interfaces": "^5.3.0",
|
||||
"@serve.zone/remoteingress": "^4.15.3",
|
||||
"@tsclass/tsclass": "^9.5.0",
|
||||
|
||||
55
pnpm-lock.yaml
generated
55
pnpm-lock.yaml
generated
@@ -24,8 +24,8 @@ importers:
|
||||
specifier: ^7.1.0
|
||||
version: 7.1.0
|
||||
'@design.estate/dees-catalog':
|
||||
specifier: ^3.55.5
|
||||
version: 3.55.5(@tiptap/pm@2.27.2)
|
||||
specifier: ^3.55.6
|
||||
version: 3.55.6(@tiptap/pm@2.27.2)
|
||||
'@design.estate/dees-element':
|
||||
specifier: ^2.2.4
|
||||
version: 2.2.4
|
||||
@@ -45,8 +45,8 @@ importers:
|
||||
specifier: ^7.1.3
|
||||
version: 7.1.3(socks@2.8.7)
|
||||
'@push.rocks/smartdb':
|
||||
specifier: ^2.1.1
|
||||
version: 2.1.1(@tiptap/pm@2.27.2)
|
||||
specifier: ^2.3.1
|
||||
version: 2.3.1(@tiptap/pm@2.27.2)
|
||||
'@push.rocks/smartdns':
|
||||
specifier: ^7.9.0
|
||||
version: 7.9.0
|
||||
@@ -78,8 +78,8 @@ importers:
|
||||
specifier: ^4.2.3
|
||||
version: 4.2.3
|
||||
'@push.rocks/smartproxy':
|
||||
specifier: ^27.2.0
|
||||
version: 27.2.0
|
||||
specifier: ^27.4.0
|
||||
version: 27.4.0
|
||||
'@push.rocks/smartradius':
|
||||
specifier: ^1.1.1
|
||||
version: 1.1.1
|
||||
@@ -102,8 +102,8 @@ importers:
|
||||
specifier: ^8.0.2
|
||||
version: 8.0.2
|
||||
'@serve.zone/catalog':
|
||||
specifier: ^2.11.0
|
||||
version: 2.11.0(@tiptap/pm@2.27.2)
|
||||
specifier: ^2.11.1
|
||||
version: 2.11.1(@tiptap/pm@2.27.2)
|
||||
'@serve.zone/interfaces':
|
||||
specifier: ^5.3.0
|
||||
version: 5.3.0
|
||||
@@ -350,8 +350,8 @@ packages:
|
||||
'@configvault.io/interfaces@1.0.17':
|
||||
resolution: {integrity: sha512-bEcCUR2VBDJsTin8HQh8Uw/mlYl2v8A3jMIaQ+MTB9Hrqd6CZL2dL7iJdWyFl/3EIX+LDxWFR+Oq7liIq7w+1Q==}
|
||||
|
||||
'@design.estate/dees-catalog@3.55.5':
|
||||
resolution: {integrity: sha512-NAMUkTVqdZZmwI/g1xKOxOYM9QUd9FHODh6MYkP6LhLjD0NOGh3bITCnNN9Z3x8/mI7vQQOlSe9tyTtxCP1itQ==}
|
||||
'@design.estate/dees-catalog@3.55.6':
|
||||
resolution: {integrity: sha512-aBuofV18v2X9U+WXQcwx/uOMofDECYRoqGovpB2cD2gpf5eCvaD2Y6HZetocKeW/VeOFlzwgykeSxAY8KvfiKQ==}
|
||||
|
||||
'@design.estate/dees-comms@1.0.30':
|
||||
resolution: {integrity: sha512-KchMlklJfKAjQiJiR0xmofXtQ27VgZtBIxcMwPE9d+h3jJRv+lPZxzBQVOM0eyM0uS44S5vJMZ11IeV4uDXSHg==}
|
||||
@@ -1141,8 +1141,8 @@ packages:
|
||||
'@push.rocks/smartdata@7.1.3':
|
||||
resolution: {integrity: sha512-7vQJ9pdRk450yn2m9tmGPdSRlQVmxFPZjHD4sGYsfqCQPg+GLFusu+H16zpf+jKzAq4F2ZBMPaYymJHXvXiVcw==}
|
||||
|
||||
'@push.rocks/smartdb@2.1.1':
|
||||
resolution: {integrity: sha512-bm+xYpuzSgS+EacNP3NppwNvpw9OZN3gmtVUgBdqyLLKYX0329bDN5X63V6vdrglFBV/+MKox43l8BQBwVdfjw==}
|
||||
'@push.rocks/smartdb@2.3.1':
|
||||
resolution: {integrity: sha512-l5/qqv+vXgXJezwcXs1BxsHbGPSSMw8y9lI832yM+WcO0PH4grTYSGNyZjx6hql8pJxSjMiixdnxK9XvIQukzA==}
|
||||
|
||||
'@push.rocks/smartdelay@3.0.5':
|
||||
resolution: {integrity: sha512-mUuI7kj2f7ztjpic96FvRIlf2RsKBa5arw81AHNsndbxO6asRcxuWL8dTVxouEIK8YsBUlj0AsrCkHhMbLQdHw==}
|
||||
@@ -1279,8 +1279,8 @@ packages:
|
||||
'@push.rocks/smartpromise@4.2.3':
|
||||
resolution: {integrity: sha512-Ycg/TJR+tMt+S3wSFurOpEoW6nXv12QBtKXgBcjMZ4RsdO28geN46U09osPn9N9WuwQy1PkmTV5J/V4F9U8qEw==}
|
||||
|
||||
'@push.rocks/smartproxy@27.2.0':
|
||||
resolution: {integrity: sha512-F7stwFDv2BLyDK5WY7WocNbsxrjfuT+X1R0O3Ii4fw1weRDekOyxUPmeK/QcWxBdp+LgsSuVvBEv3sFpQHipvw==}
|
||||
'@push.rocks/smartproxy@27.4.0':
|
||||
resolution: {integrity: sha512-r6ym6+FpiHHLFgCdY0KfCe8s2eLuLUU/daXIcKwsmMHNY3BOlBIxwN5CL17+v2Hvh7Lg2JWBY+1L7/7ehYXj1w==}
|
||||
|
||||
'@push.rocks/smartpuppeteer@2.0.5':
|
||||
resolution: {integrity: sha512-yK/qSeWVHIGWRp3c8S5tfdGP6WCKllZC4DR8d8CQlEjszOSBmHtlTdyyqOMBZ/BA4kd+eU5f3A1r4K2tGYty1g==}
|
||||
@@ -1583,8 +1583,8 @@ packages:
|
||||
'@selderee/plugin-htmlparser2@0.11.0':
|
||||
resolution: {integrity: sha512-P33hHGdldxGabLFjPPpaTxVolMrzrcegejx+0GxjrIb9Zv48D8yAIA/QTDR2dFl7Uz7urX8aX6+5bCZslr+gWQ==}
|
||||
|
||||
'@serve.zone/catalog@2.11.0':
|
||||
resolution: {integrity: sha512-4DFDewp1PFRhw5P+yQAoAw+i6gG2lfR3h+uPgbNxB5jCfW14eNDXi3nuwTMBQWRHL9jv8o0BokASjV9A0+q66g==}
|
||||
'@serve.zone/catalog@2.11.1':
|
||||
resolution: {integrity: sha512-KeeShLELKANWEAY3Z4h+Kx1bmatWLa0nvtw8wa7iHLxV6Vm3rRJNdRyh91ozuWPgFYMC48V8/4REA8zJsdDcbg==}
|
||||
|
||||
'@serve.zone/interfaces@5.3.0':
|
||||
resolution: {integrity: sha512-venO7wtDR9ixzD9NhdERBGjNKbFA5LL0yHw4eqGh0UpmvtXVc3SFG0uuHDilOKMZqZ8bttV88qVsFy1aSTJrtA==}
|
||||
@@ -3303,6 +3303,10 @@ packages:
|
||||
resolution: {integrity: sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==}
|
||||
engines: {node: 18 || 20 || >=22}
|
||||
|
||||
minimatch@10.2.5:
|
||||
resolution: {integrity: sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==}
|
||||
engines: {node: 18 || 20 || >=22}
|
||||
|
||||
minimatch@3.1.5:
|
||||
resolution: {integrity: sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==}
|
||||
|
||||
@@ -4351,7 +4355,7 @@ snapshots:
|
||||
'@api.global/typedrequest-interfaces': 3.0.19
|
||||
'@api.global/typedsocket': 4.1.2(@push.rocks/smartserve@2.0.3)
|
||||
'@cloudflare/workers-types': 4.20260317.1
|
||||
'@design.estate/dees-catalog': 3.55.5(@tiptap/pm@2.27.2)
|
||||
'@design.estate/dees-catalog': 3.55.6(@tiptap/pm@2.27.2)
|
||||
'@design.estate/dees-comms': 1.0.30
|
||||
'@push.rocks/lik': 6.4.0
|
||||
'@push.rocks/smartdelay': 3.0.5
|
||||
@@ -4880,7 +4884,7 @@ snapshots:
|
||||
dependencies:
|
||||
'@api.global/typedrequest-interfaces': 3.0.19
|
||||
|
||||
'@design.estate/dees-catalog@3.55.5(@tiptap/pm@2.27.2)':
|
||||
'@design.estate/dees-catalog@3.55.6(@tiptap/pm@2.27.2)':
|
||||
dependencies:
|
||||
'@design.estate/dees-domtools': 2.5.4
|
||||
'@design.estate/dees-element': 2.2.4
|
||||
@@ -6140,11 +6144,12 @@ snapshots:
|
||||
- supports-color
|
||||
- vue
|
||||
|
||||
'@push.rocks/smartdb@2.1.1(@tiptap/pm@2.27.2)':
|
||||
'@push.rocks/smartdb@2.3.1(@tiptap/pm@2.27.2)':
|
||||
dependencies:
|
||||
'@api.global/typedserver': 8.4.6(@tiptap/pm@2.27.2)
|
||||
'@design.estate/dees-element': 2.2.4
|
||||
'@push.rocks/smartrust': 1.3.2
|
||||
bson: 7.2.0
|
||||
transitivePeerDependencies:
|
||||
- '@nuxt/kit'
|
||||
- '@tiptap/pm'
|
||||
@@ -6518,14 +6523,14 @@ snapshots:
|
||||
|
||||
'@push.rocks/smartpromise@4.2.3': {}
|
||||
|
||||
'@push.rocks/smartproxy@27.2.0':
|
||||
'@push.rocks/smartproxy@27.4.0':
|
||||
dependencies:
|
||||
'@push.rocks/smartcrypto': 2.0.4
|
||||
'@push.rocks/smartlog': 3.2.1
|
||||
'@push.rocks/smartnftables': 1.1.0
|
||||
'@push.rocks/smartrust': 1.3.2
|
||||
'@tsclass/tsclass': 9.5.0
|
||||
minimatch: 10.2.4
|
||||
minimatch: 10.2.5
|
||||
|
||||
'@push.rocks/smartpuppeteer@2.0.5(typescript@6.0.2)':
|
||||
dependencies:
|
||||
@@ -6917,9 +6922,9 @@ snapshots:
|
||||
domhandler: 5.0.3
|
||||
selderee: 0.11.0
|
||||
|
||||
'@serve.zone/catalog@2.11.0(@tiptap/pm@2.27.2)':
|
||||
'@serve.zone/catalog@2.11.1(@tiptap/pm@2.27.2)':
|
||||
dependencies:
|
||||
'@design.estate/dees-catalog': 3.55.5(@tiptap/pm@2.27.2)
|
||||
'@design.estate/dees-catalog': 3.55.6(@tiptap/pm@2.27.2)
|
||||
'@design.estate/dees-domtools': 2.5.4
|
||||
'@design.estate/dees-element': 2.2.4
|
||||
'@design.estate/dees-wcctools': 3.8.0
|
||||
@@ -9063,6 +9068,10 @@ snapshots:
|
||||
dependencies:
|
||||
brace-expansion: 5.0.5
|
||||
|
||||
minimatch@10.2.5:
|
||||
dependencies:
|
||||
brace-expansion: 5.0.5
|
||||
|
||||
minimatch@3.1.5:
|
||||
dependencies:
|
||||
brace-expansion: 1.1.13
|
||||
|
||||
@@ -3,6 +3,6 @@
|
||||
*/
|
||||
export const commitinfo = {
|
||||
name: '@serve.zone/dcrouter',
|
||||
version: '12.9.1',
|
||||
version: '12.10.0',
|
||||
description: 'A multifaceted routing service handling mail and SMS delivery functions.'
|
||||
}
|
||||
|
||||
@@ -2164,6 +2164,26 @@ export class DcRouter {
|
||||
}
|
||||
}
|
||||
|
||||
// Also scan stored/programmatic routes
|
||||
const storedRoutes = this.routeConfigManager?.getStoredRoutes();
|
||||
if (storedRoutes) {
|
||||
for (const [, stored] of storedRoutes) {
|
||||
if (!stored.enabled) continue;
|
||||
const dcRoute = stored.route as import('../ts_interfaces/data/remoteingress.js').IDcRouterRouteConfig;
|
||||
if (!dcRoute.vpn?.enabled) continue;
|
||||
|
||||
const routeTags = dcRoute.vpn.allowedServerDefinedClientTags;
|
||||
if (!routeTags?.length || clientTags.some(t => routeTags.includes(t))) {
|
||||
const domains = (stored.route.match as any)?.domains;
|
||||
if (Array.isArray(domains)) {
|
||||
for (const d of domains) {
|
||||
domainsToResolve.add(d.replace(/^\*\./, ''));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Resolve DNS A records for matched domains (with caching)
|
||||
for (const domain of domainsToResolve) {
|
||||
const resolvedIps = await this.resolveVpnDomainIPs(domain);
|
||||
|
||||
@@ -83,7 +83,7 @@ export class RouteConfigManager {
|
||||
// =========================================================================
|
||||
|
||||
public async createRoute(
|
||||
route: plugins.smartproxy.IRouteConfig,
|
||||
route: IDcRouterRouteConfig,
|
||||
createdBy: string,
|
||||
enabled = true,
|
||||
metadata?: IRouteMetadata,
|
||||
@@ -123,7 +123,7 @@ export class RouteConfigManager {
|
||||
public async updateRoute(
|
||||
id: string,
|
||||
patch: {
|
||||
route?: Partial<plugins.smartproxy.IRouteConfig>;
|
||||
route?: Partial<IDcRouterRouteConfig>;
|
||||
enabled?: boolean;
|
||||
metadata?: Partial<IRouteMetadata>;
|
||||
},
|
||||
@@ -132,7 +132,18 @@ export class RouteConfigManager {
|
||||
if (!stored) return false;
|
||||
|
||||
if (patch.route) {
|
||||
stored.route = { ...stored.route, ...patch.route } as plugins.smartproxy.IRouteConfig;
|
||||
const mergedAction = patch.route.action
|
||||
? { ...stored.route.action, ...patch.route.action }
|
||||
: stored.route.action;
|
||||
// Handle explicit null to remove nested action properties (e.g., tls: null)
|
||||
if (patch.route.action) {
|
||||
for (const [key, val] of Object.entries(patch.route.action)) {
|
||||
if (val === null) {
|
||||
delete (mergedAction as any)[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
stored.route = { ...stored.route, ...patch.route, action: mergedAction } as IDcRouterRouteConfig;
|
||||
}
|
||||
if (patch.enabled !== undefined) {
|
||||
stored.enabled = patch.enabled;
|
||||
@@ -386,7 +397,7 @@ export class RouteConfigManager {
|
||||
for (const stored of this.storedRoutes.values()) {
|
||||
if (stored.enabled) {
|
||||
let route = stored.route;
|
||||
if (http3Config && http3Config.enabled !== false) {
|
||||
if (http3Config?.enabled !== false) {
|
||||
route = augmentRouteWithHttp3(route, { enabled: true, ...http3Config });
|
||||
}
|
||||
enabledRoutes.push(injectVpn(route));
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import * as plugins from '../../plugins.js';
|
||||
import { DcRouterDb } from '../classes.dcrouter-db.js';
|
||||
import type { IRouteMetadata } from '../../../ts_interfaces/data/route-management.js';
|
||||
import type { IDcRouterRouteConfig } from '../../../ts_interfaces/data/remoteingress.js';
|
||||
|
||||
const getDb = () => DcRouterDb.getInstance().getDb();
|
||||
|
||||
@@ -11,7 +12,7 @@ export class StoredRouteDoc extends plugins.smartdata.SmartDataDbDoc<StoredRoute
|
||||
public id!: string;
|
||||
|
||||
@plugins.smartdata.svDb()
|
||||
public route!: plugins.smartproxy.IRouteConfig;
|
||||
public route!: IDcRouterRouteConfig;
|
||||
|
||||
@plugins.smartdata.svDb()
|
||||
public enabled!: boolean;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { IRouteConfig } from '@push.rocks/smartproxy';
|
||||
import type { IDcRouterRouteConfig } from './remoteingress.js';
|
||||
|
||||
// Derive IRouteSecurity from IRouteConfig since it's not directly exported
|
||||
export type IRouteSecurity = NonNullable<IRouteConfig['security']>;
|
||||
@@ -77,7 +78,7 @@ export interface IRouteMetadata {
|
||||
* A merged route combining hardcoded and programmatic sources.
|
||||
*/
|
||||
export interface IMergedRoute {
|
||||
route: IRouteConfig;
|
||||
route: IDcRouterRouteConfig;
|
||||
source: 'hardcoded' | 'programmatic';
|
||||
enabled: boolean;
|
||||
overridden: boolean;
|
||||
@@ -118,7 +119,7 @@ export interface IApiTokenInfo {
|
||||
*/
|
||||
export interface IStoredRoute {
|
||||
id: string;
|
||||
route: IRouteConfig;
|
||||
route: IDcRouterRouteConfig;
|
||||
enabled: boolean;
|
||||
createdAt: number;
|
||||
updatedAt: number;
|
||||
|
||||
@@ -2,6 +2,7 @@ import * as plugins from '../plugins.js';
|
||||
import type * as authInterfaces from '../data/auth.js';
|
||||
import type { IMergedRoute, IRouteWarning, IRouteMetadata } from '../data/route-management.js';
|
||||
import type { IRouteConfig } from '@push.rocks/smartproxy';
|
||||
import type { IDcRouterRouteConfig } from '../data/remoteingress.js';
|
||||
|
||||
// ============================================================================
|
||||
// Route Management Endpoints
|
||||
@@ -36,7 +37,7 @@ export interface IReq_CreateRoute extends plugins.typedrequestInterfaces.impleme
|
||||
request: {
|
||||
identity?: authInterfaces.IIdentity;
|
||||
apiToken?: string;
|
||||
route: IRouteConfig;
|
||||
route: IDcRouterRouteConfig;
|
||||
enabled?: boolean;
|
||||
metadata?: IRouteMetadata;
|
||||
};
|
||||
@@ -59,7 +60,7 @@ export interface IReq_UpdateRoute extends plugins.typedrequestInterfaces.impleme
|
||||
identity?: authInterfaces.IIdentity;
|
||||
apiToken?: string;
|
||||
id: string;
|
||||
route?: Partial<IRouteConfig>;
|
||||
route?: Partial<IDcRouterRouteConfig>;
|
||||
enabled?: boolean;
|
||||
metadata?: Partial<IRouteMetadata>;
|
||||
};
|
||||
|
||||
@@ -3,6 +3,6 @@
|
||||
*/
|
||||
export const commitinfo = {
|
||||
name: '@serve.zone/dcrouter',
|
||||
version: '12.9.1',
|
||||
version: '12.10.0',
|
||||
description: 'A multifaceted routing service handling mail and SMS delivery functions.'
|
||||
}
|
||||
|
||||
@@ -64,13 +64,6 @@ export class OpsViewOverview extends DeesElement {
|
||||
cssManager.defaultStyles,
|
||||
shared.viewHostCss,
|
||||
css`
|
||||
h2 {
|
||||
margin: 32px 0 16px 0;
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
color: ${cssManager.bdTheme('#333', '#ccc')};
|
||||
}
|
||||
|
||||
.chartGrid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
@@ -123,6 +116,7 @@ export class OpsViewOverview extends DeesElement {
|
||||
|
||||
${this.renderVpnStats()}
|
||||
|
||||
<dees-heading level="hr">Activity Charts</dees-heading>
|
||||
<div class="chartGrid">
|
||||
<dees-chart-area
|
||||
.label=${'Email Traffic (24h)'}
|
||||
@@ -330,7 +324,7 @@ export class OpsViewOverview extends DeesElement {
|
||||
];
|
||||
|
||||
return html`
|
||||
<h2>Email Statistics</h2>
|
||||
<dees-heading level="hr">Email Statistics</dees-heading>
|
||||
<dees-statsgrid .tiles=${tiles}></dees-statsgrid>
|
||||
`;
|
||||
}
|
||||
@@ -379,7 +373,7 @@ export class OpsViewOverview extends DeesElement {
|
||||
];
|
||||
|
||||
return html`
|
||||
<h2>DNS Statistics</h2>
|
||||
<dees-heading level="hr">DNS Statistics</dees-heading>
|
||||
<dees-statsgrid .tiles=${tiles}></dees-statsgrid>
|
||||
`;
|
||||
}
|
||||
@@ -430,7 +424,7 @@ export class OpsViewOverview extends DeesElement {
|
||||
];
|
||||
|
||||
return html`
|
||||
<h2>RADIUS Statistics</h2>
|
||||
<dees-heading level="hr">RADIUS Statistics</dees-heading>
|
||||
<dees-statsgrid .tiles=${tiles}></dees-statsgrid>
|
||||
`;
|
||||
}
|
||||
@@ -470,7 +464,7 @@ export class OpsViewOverview extends DeesElement {
|
||||
];
|
||||
|
||||
return html`
|
||||
<h2>VPN Statistics</h2>
|
||||
<dees-heading level="hr">VPN Statistics</dees-heading>
|
||||
<dees-statsgrid .tiles=${tiles}></dees-statsgrid>
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -13,6 +13,40 @@ import {
|
||||
type TemplateResult,
|
||||
} from '@design.estate/dees-element';
|
||||
|
||||
// TLS dropdown options shared by create and edit dialogs
|
||||
const tlsModeOptions = [
|
||||
{ key: 'none', option: '(none — no TLS)' },
|
||||
{ key: 'passthrough', option: 'Passthrough' },
|
||||
{ key: 'terminate', option: 'Terminate' },
|
||||
{ key: 'terminate-and-reencrypt', option: 'Terminate & Re-encrypt' },
|
||||
];
|
||||
const tlsCertOptions = [
|
||||
{ key: 'auto', option: 'Auto (ACME/Let\'s Encrypt)' },
|
||||
{ key: 'custom', option: 'Custom certificate' },
|
||||
];
|
||||
|
||||
/**
|
||||
* Toggle TLS form field visibility based on selected TLS mode and certificate type.
|
||||
*/
|
||||
function setupTlsVisibility(formEl: any) {
|
||||
const updateVisibility = async () => {
|
||||
const data = await formEl.collectFormData();
|
||||
const contentEl = formEl.closest('.content') || formEl.parentElement;
|
||||
if (!contentEl) return;
|
||||
const tlsModeValue = data.tlsMode;
|
||||
const modeKey = typeof tlsModeValue === 'string' ? tlsModeValue : tlsModeValue?.key;
|
||||
const needsCert = modeKey === 'terminate' || modeKey === 'terminate-and-reencrypt';
|
||||
const certGroup = contentEl.querySelector('.tlsCertificateGroup') as HTMLElement;
|
||||
if (certGroup) certGroup.style.display = needsCert ? 'flex' : 'none';
|
||||
const tlsCertValue = data.tlsCertificate;
|
||||
const certKey = typeof tlsCertValue === 'string' ? tlsCertValue : tlsCertValue?.key;
|
||||
const customGroup = contentEl.querySelector('.tlsCustomCertGroup') as HTMLElement;
|
||||
if (customGroup) customGroup.style.display = (needsCert && certKey === 'custom') ? 'flex' : 'none';
|
||||
};
|
||||
formEl.changeSubject.subscribe(() => updateVisibility());
|
||||
updateVisibility();
|
||||
}
|
||||
|
||||
@customElement('ops-view-routes')
|
||||
export class OpsViewRoutes extends DeesElement {
|
||||
@state() accessor routeState: appstate.IRouteManagementState = {
|
||||
@@ -423,7 +457,18 @@ export class OpsViewRoutes extends DeesElement {
|
||||
: '';
|
||||
const currentTargetPort = firstTarget?.port != null ? String(firstTarget.port) : '';
|
||||
|
||||
await DeesModal.createAndShow({
|
||||
// Compute current TLS state for pre-population
|
||||
const currentTls = (route.action as any).tls;
|
||||
const currentTlsMode = currentTls?.mode || 'none';
|
||||
const currentTlsCert = currentTls
|
||||
? (currentTls.certificate === 'auto' || !currentTls.certificate ? 'auto' : 'custom')
|
||||
: 'auto';
|
||||
const currentCustomKey = (typeof currentTls?.certificate === 'object') ? currentTls.certificate.key : '';
|
||||
const currentCustomCert = (typeof currentTls?.certificate === 'object') ? currentTls.certificate.cert : '';
|
||||
const needsCert = currentTlsMode === 'terminate' || currentTlsMode === 'terminate-and-reencrypt';
|
||||
const isCustom = currentTlsCert === 'custom';
|
||||
|
||||
const editModal = await DeesModal.createAndShow({
|
||||
heading: `Edit Route: ${route.name}`,
|
||||
content: html`
|
||||
<dees-form>
|
||||
@@ -435,6 +480,14 @@ export class OpsViewRoutes extends DeesElement {
|
||||
<dees-input-dropdown .key=${'networkTargetRef'} .label=${'Network Target'} .options=${targetOptions} .selectedOption=${targetOptions.find((o) => o.key === (merged.metadata?.networkTargetRef || '')) || null}></dees-input-dropdown>
|
||||
<dees-input-text .key=${'targetHost'} .label=${'Target Host (if no target selected)'} .value=${currentTargetHost}></dees-input-text>
|
||||
<dees-input-text .key=${'targetPort'} .label=${'Target Port (if no target selected)'} .value=${currentTargetPort}></dees-input-text>
|
||||
<dees-input-dropdown .key=${'tlsMode'} .label=${'TLS Mode'} .options=${tlsModeOptions} .selectedOption=${tlsModeOptions.find((o) => o.key === currentTlsMode) || tlsModeOptions[0]}></dees-input-dropdown>
|
||||
<div class="tlsCertificateGroup" style="display: ${needsCert ? 'flex' : 'none'}; flex-direction: column; gap: 16px;">
|
||||
<dees-input-dropdown .key=${'tlsCertificate'} .label=${'Certificate'} .options=${tlsCertOptions} .selectedOption=${tlsCertOptions.find((o) => o.key === currentTlsCert) || tlsCertOptions[0]}></dees-input-dropdown>
|
||||
<div class="tlsCustomCertGroup" style="display: ${needsCert && isCustom ? 'flex' : 'none'}; flex-direction: column; gap: 16px;">
|
||||
<dees-input-text .key=${'tlsCertKey'} .label=${'Private Key (PEM)'} .value=${currentCustomKey}></dees-input-text>
|
||||
<dees-input-text .key=${'tlsCertCert'} .label=${'Certificate (PEM)'} .value=${currentCustomCert}></dees-input-text>
|
||||
</div>
|
||||
</div>
|
||||
</dees-form>
|
||||
`,
|
||||
menuOptions: [
|
||||
@@ -476,6 +529,25 @@ export class OpsViewRoutes extends DeesElement {
|
||||
...(priority != null && !isNaN(priority) ? { priority } : {}),
|
||||
};
|
||||
|
||||
// Build TLS config from form
|
||||
const tlsModeValue = formData.tlsMode as any;
|
||||
const tlsModeKey = typeof tlsModeValue === 'string' ? tlsModeValue : tlsModeValue?.key;
|
||||
if (tlsModeKey && tlsModeKey !== 'none') {
|
||||
const tls: any = { mode: tlsModeKey };
|
||||
if (tlsModeKey !== 'passthrough') {
|
||||
const tlsCertValue = formData.tlsCertificate as any;
|
||||
const tlsCertKey = typeof tlsCertValue === 'string' ? tlsCertValue : tlsCertValue?.key;
|
||||
if (tlsCertKey === 'custom' && formData.tlsCertKey && formData.tlsCertCert) {
|
||||
tls.certificate = { key: formData.tlsCertKey, cert: formData.tlsCertCert };
|
||||
} else {
|
||||
tls.certificate = 'auto';
|
||||
}
|
||||
}
|
||||
updatedRoute.action.tls = tls;
|
||||
} else {
|
||||
updatedRoute.action.tls = null; // explicit removal
|
||||
}
|
||||
|
||||
const metadata: any = {};
|
||||
const profileRefValue = formData.securityProfileRef as any;
|
||||
const profileKey = typeof profileRefValue === 'string' ? profileRefValue : profileRefValue?.key;
|
||||
@@ -501,6 +573,12 @@ export class OpsViewRoutes extends DeesElement {
|
||||
},
|
||||
],
|
||||
});
|
||||
// Setup conditional TLS field visibility after modal renders
|
||||
const editForm = editModal?.shadowRoot?.querySelector('.content')?.querySelector('dees-form') as any;
|
||||
if (editForm) {
|
||||
await editForm.updateComplete;
|
||||
setupTlsVisibility(editForm);
|
||||
}
|
||||
}
|
||||
|
||||
private async showCreateRouteDialog() {
|
||||
@@ -524,7 +602,7 @@ export class OpsViewRoutes extends DeesElement {
|
||||
})),
|
||||
];
|
||||
|
||||
await DeesModal.createAndShow({
|
||||
const createModal = await DeesModal.createAndShow({
|
||||
heading: 'Add Programmatic Route',
|
||||
content: html`
|
||||
<dees-form>
|
||||
@@ -536,6 +614,14 @@ export class OpsViewRoutes extends DeesElement {
|
||||
<dees-input-dropdown .key=${'networkTargetRef'} .label=${'Network Target'} .options=${targetOptions}></dees-input-dropdown>
|
||||
<dees-input-text .key=${'targetHost'} .label=${'Target Host (if no target selected)'} .value=${'localhost'}></dees-input-text>
|
||||
<dees-input-text .key=${'targetPort'} .label=${'Target Port (if no target selected)'}></dees-input-text>
|
||||
<dees-input-dropdown .key=${'tlsMode'} .label=${'TLS Mode'} .options=${tlsModeOptions} .selectedOption=${tlsModeOptions[0]}></dees-input-dropdown>
|
||||
<div class="tlsCertificateGroup" style="display: none; flex-direction: column; gap: 16px;">
|
||||
<dees-input-dropdown .key=${'tlsCertificate'} .label=${'Certificate'} .options=${tlsCertOptions} .selectedOption=${tlsCertOptions[0]}></dees-input-dropdown>
|
||||
<div class="tlsCustomCertGroup" style="display: none; flex-direction: column; gap: 16px;">
|
||||
<dees-input-text .key=${'tlsCertKey'} .label=${'Private Key (PEM)'}></dees-input-text>
|
||||
<dees-input-text .key=${'tlsCertCert'} .label=${'Certificate (PEM)'}></dees-input-text>
|
||||
</div>
|
||||
</div>
|
||||
</dees-form>
|
||||
`,
|
||||
menuOptions: [
|
||||
@@ -577,6 +663,23 @@ export class OpsViewRoutes extends DeesElement {
|
||||
...(priority != null && !isNaN(priority) ? { priority } : {}),
|
||||
};
|
||||
|
||||
// Build TLS config from form
|
||||
const tlsModeValue = formData.tlsMode as any;
|
||||
const tlsModeKey = typeof tlsModeValue === 'string' ? tlsModeValue : tlsModeValue?.key;
|
||||
if (tlsModeKey && tlsModeKey !== 'none') {
|
||||
const tls: any = { mode: tlsModeKey };
|
||||
if (tlsModeKey !== 'passthrough') {
|
||||
const tlsCertValue = formData.tlsCertificate as any;
|
||||
const tlsCertKey = typeof tlsCertValue === 'string' ? tlsCertValue : tlsCertValue?.key;
|
||||
if (tlsCertKey === 'custom' && formData.tlsCertKey && formData.tlsCertCert) {
|
||||
tls.certificate = { key: formData.tlsCertKey, cert: formData.tlsCertCert };
|
||||
} else {
|
||||
tls.certificate = 'auto';
|
||||
}
|
||||
}
|
||||
route.action.tls = tls;
|
||||
}
|
||||
|
||||
// Build metadata if profile/target selected
|
||||
const metadata: any = {};
|
||||
const profileRefValue = formData.securityProfileRef as any;
|
||||
@@ -602,6 +705,12 @@ export class OpsViewRoutes extends DeesElement {
|
||||
},
|
||||
],
|
||||
});
|
||||
// Setup conditional TLS field visibility after modal renders
|
||||
const createForm = createModal?.shadowRoot?.querySelector('.content')?.querySelector('dees-form') as any;
|
||||
if (createForm) {
|
||||
await createForm.updateComplete;
|
||||
setupTlsVisibility(createForm);
|
||||
}
|
||||
}
|
||||
|
||||
private refreshData() {
|
||||
|
||||
Reference in New Issue
Block a user