Compare commits

...

4 Commits

Author SHA1 Message Date
94f53f0259 v12.8.1
Some checks failed
Docker (tags) / security (push) Failing after 3s
Docker (tags) / test (push) Has been skipped
Docker (tags) / release (push) Has been skipped
Docker (tags) / metadata (push) Has been skipped
2026-04-04 11:00:03 +00:00
1004f8579f fix(ops-view-routes): correct route form dropdown selection handling for security profiles and network targets 2026-04-04 11:00:03 +00:00
a77ec6884a v12.8.0
Some checks failed
Docker (tags) / security (push) Failing after 2s
Docker (tags) / test (push) Has been skipped
Docker (tags) / release (push) Has been skipped
Docker (tags) / metadata (push) Has been skipped
2026-04-03 19:08:46 +00:00
6112e4e884 feat(certificates): add force renew option for domain certificate reprovisioning 2026-04-03 19:08:46 +00:00
13 changed files with 72 additions and 43 deletions

View File

@@ -1,5 +1,19 @@
# Changelog
## 2026-04-04 - 12.8.1 - fix(ops-view-routes)
correct route form dropdown selection handling for security profiles and network targets
- Update route edit and create forms to use selectedOption for dropdowns backed by the newer dees-catalog version
- Normalize submitted dropdown values to extract option keys before storing securityProfileRef and networkTargetRef
- Refresh documentation to reflect expanded stats coverage for network, RADIUS, and VPN metrics
## 2026-04-03 - 12.8.0 - feat(certificates)
add force renew option for domain certificate reprovisioning
- pass an optional forceRenew flag through certificate reprovision requests from the UI to the ops handler
- use smartacme forceRenew support and return renewal-specific success messages
- update the SmartAcme dependency to version ^9.4.0
## 2026-04-03 - 12.7.0 - feat(opsserver)
add RADIUS and VPN metrics to combined ops stats and overview dashboards, and stream live log buffer entries in follow mode

View File

@@ -1,7 +1,7 @@
{
"name": "@serve.zone/dcrouter",
"private": false,
"version": "12.7.0",
"version": "12.8.1",
"description": "A multifaceted routing service handling mail and SMS delivery functions.",
"type": "module",
"exports": {
@@ -35,12 +35,12 @@
"@api.global/typedserver": "^8.4.6",
"@api.global/typedsocket": "^4.1.2",
"@apiclient.xyz/cloudflare": "^7.1.0",
"@design.estate/dees-catalog": "^3.52.3",
"@design.estate/dees-catalog": "^3.55.1",
"@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.3.1",
"@push.rocks/smartacme": "^9.4.0",
"@push.rocks/smartdata": "^7.1.3",
"@push.rocks/smartdb": "^2.1.1",
"@push.rocks/smartdns": "^7.9.0",

24
pnpm-lock.yaml generated
View File

@@ -24,8 +24,8 @@ importers:
specifier: ^7.1.0
version: 7.1.0
'@design.estate/dees-catalog':
specifier: ^3.52.3
version: 3.52.3(@tiptap/pm@2.27.2)
specifier: ^3.55.1
version: 3.55.1(@tiptap/pm@2.27.2)
'@design.estate/dees-element':
specifier: ^2.2.4
version: 2.2.4
@@ -39,8 +39,8 @@ importers:
specifier: ^6.1.3
version: 6.1.3
'@push.rocks/smartacme':
specifier: ^9.3.1
version: 9.3.1(socks@2.8.7)
specifier: ^9.4.0
version: 9.4.0(socks@2.8.7)
'@push.rocks/smartdata':
specifier: ^7.1.3
version: 7.1.3(socks@2.8.7)
@@ -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.52.3':
resolution: {integrity: sha512-4/vybRZQtdkpa3IOZQ/EbL6gGjIMO+430db43RRfdw+HPXird7Jl+oIXz6pDh+rGah2H2+Srb/c+eve46xAhtQ==}
'@design.estate/dees-catalog@3.55.1':
resolution: {integrity: sha512-UXBC68Dg+L2cGJynvrXyaJATlSLnmiA5SMrE1SWVcp2H1niXqHph6KVi9+E52YSLoEmsCS23cf+XjUjoRNgZTw==}
'@design.estate/dees-comms@1.0.30':
resolution: {integrity: sha512-KchMlklJfKAjQiJiR0xmofXtQ27VgZtBIxcMwPE9d+h3jJRv+lPZxzBQVOM0eyM0uS44S5vJMZ11IeV4uDXSHg==}
@@ -1108,8 +1108,8 @@ packages:
'@push.rocks/qenv@6.1.3':
resolution: {integrity: sha512-+z2hsAU/7CIgpYLFqvda8cn9rUBMHqLdQLjsFfRn5jPoD7dJ5rFlpkbhfM4Ws8mHMniwWaxGKo+q/YBhtzRBLg==}
'@push.rocks/smartacme@9.3.1':
resolution: {integrity: sha512-Cl1DVQ+rfpaYkk6VVm/KYVeUYzWfXzSfTXybHfCZ5SuiACuTVHZ6jK8TouELaV1RgrdYnIp0MrbiY2Kqi8ayAw==}
'@push.rocks/smartacme@9.4.0':
resolution: {integrity: sha512-mSqsI859mHI9fCZxLfayzPf/WvukDFzVHOh02vXq3ujxbb5M+ArMnXe0MmC2egR9GeXmQTm3DTENaETX5ffMtw==}
'@push.rocks/smartarchive@4.2.4':
resolution: {integrity: sha512-uiqVAXPxmr8G5rv3uZvZFMOCt8l7cZC3nzvsy4YQqKf/VkPhKIEX+b7LkAeNlxPSYUiBQUkNRoawg9+5BaMcHg==}
@@ -4342,7 +4342,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.52.3(@tiptap/pm@2.27.2)
'@design.estate/dees-catalog': 3.55.1(@tiptap/pm@2.27.2)
'@design.estate/dees-comms': 1.0.30
'@push.rocks/lik': 6.4.0
'@push.rocks/smartdelay': 3.0.5
@@ -4871,7 +4871,7 @@ snapshots:
dependencies:
'@api.global/typedrequest-interfaces': 3.0.19
'@design.estate/dees-catalog@3.52.3(@tiptap/pm@2.27.2)':
'@design.estate/dees-catalog@3.55.1(@tiptap/pm@2.27.2)':
dependencies:
'@design.estate/dees-domtools': 2.5.4
'@design.estate/dees-element': 2.2.4
@@ -5960,7 +5960,7 @@ snapshots:
'@push.rocks/smartlog': 3.2.1
'@push.rocks/smartpath': 6.0.0
'@push.rocks/smartacme@9.3.1(socks@2.8.7)':
'@push.rocks/smartacme@9.4.0(socks@2.8.7)':
dependencies:
'@apiclient.xyz/cloudflare': 7.1.0
'@peculiar/x509': 2.0.0
@@ -6909,7 +6909,7 @@ snapshots:
'@serve.zone/catalog@2.11.0(@tiptap/pm@2.27.2)':
dependencies:
'@design.estate/dees-catalog': 3.52.3(@tiptap/pm@2.27.2)
'@design.estate/dees-catalog': 3.55.1(@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

View File

@@ -25,7 +25,7 @@ For reporting bugs, issues, or security vulnerabilities, please visit [community
- [Remote Ingress](#remote-ingress)
- [VPN Access Control](#vpn-access-control)
- [Certificate Management](#certificate-management)
- [Storage & Caching](#storage--caching)
- [Storage & Database](#storage--database)
- [Security Features](#security-features)
- [OpsServer Dashboard](#opsserver-dashboard)
- [API Client](#api-client)

View File

@@ -3,6 +3,6 @@
*/
export const commitinfo = {
name: '@serve.zone/dcrouter',
version: '12.7.0',
version: '12.8.1',
description: 'A multifaceted routing service handling mail and SMS delivery functions.'
}

View File

@@ -43,7 +43,7 @@ export class CertificateHandler {
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_ReprovisionCertificateDomain>(
'reprovisionCertificateDomain',
async (dataArg) => {
return this.reprovisionCertificateDomain(dataArg.domain);
return this.reprovisionCertificateDomain(dataArg.domain, dataArg.forceRenew);
}
)
);
@@ -318,7 +318,7 @@ export class CertificateHandler {
/**
* Domain-based reprovisioning — clears backoff first, then triggers provision
*/
private async reprovisionCertificateDomain(domain: string): Promise<{ success: boolean; message?: string }> {
private async reprovisionCertificateDomain(domain: string, forceRenew?: boolean): Promise<{ success: boolean; message?: string }> {
const dcRouter = this.opsServerRef.dcRouterRef;
const smartProxy = dcRouter.smartProxy;
@@ -337,8 +337,8 @@ export class CertificateHandler {
// Try to provision via SmartAcme directly
if (dcRouter.smartAcme) {
try {
await dcRouter.smartAcme.getCertificateForDomain(domain);
return { success: true, message: `Certificate reprovisioning triggered for domain '${domain}'` };
await dcRouter.smartAcme.getCertificateForDomain(domain, { forceRenew: forceRenew ?? false });
return { success: true, message: forceRenew ? `Certificate force-renewed for domain '${domain}'` : `Certificate reprovisioning triggered for domain '${domain}'` };
} catch (err: unknown) {
return { success: false, message: (err as Error).message || `Failed to reprovision certificate for ${domain}` };
}

View File

@@ -80,6 +80,8 @@ interface IIdentity {
| `IQueueStatus` | Queue name, size, processing/failed/retrying counts |
| `IHealthStatus` | Healthy flag, uptime, per-service status map |
| `INetworkMetrics` | Bandwidth, connection counts, top endpoints |
| `IRadiusStats` | Running, uptime, auth requests/accepts/rejects, sessions, data transfer |
| `IVpnStats` | Running, subnet, registered/connected clients, WireGuard port |
| `ILogEntry` | Timestamp, level, category, message, metadata |
#### Route Management Interfaces
@@ -135,7 +137,8 @@ TypedRequest interfaces for the OpsServer API, organized by domain:
| `IReq_GetActiveConnections` | `getActiveConnections` | Active connection list |
| `IReq_GetQueueStatus` | `getQueueStatus` | Email queue status |
| `IReq_GetHealthStatus` | `getHealthStatus` | System health check |
| `IReq_GetCombinedMetrics` | `getCombinedMetrics` | All metrics in one request |
| `IReq_GetNetworkStats` | `getNetworkStats` | Network throughput and connection analytics |
| `IReq_GetCombinedMetrics` | `getCombinedMetrics` | All metrics in one request (server, email, DNS, security, network, RADIUS, VPN) |
#### ⚙️ Configuration
| Interface | Method | Description |

View File

@@ -68,6 +68,7 @@ export interface IReq_ReprovisionCertificateDomain extends plugins.typedrequestI
request: {
identity: authInterfaces.IIdentity;
domain: string;
forceRenew?: boolean;
};
response: {
success: boolean;

View File

@@ -3,6 +3,6 @@
*/
export const commitinfo = {
name: '@serve.zone/dcrouter',
version: '12.7.0',
version: '12.8.1',
description: 'A multifaceted routing service handling mail and SMS delivery functions.'
}

View File

@@ -605,8 +605,8 @@ export const fetchCertificateOverviewAction = certificateStatePart.createAction(
}
});
export const reprovisionCertificateAction = certificateStatePart.createAction<string>(
async (statePartArg, domain, actionContext): Promise<ICertificateState> => {
export const reprovisionCertificateAction = certificateStatePart.createAction<{ domain: string; forceRenew?: boolean }>(
async (statePartArg, dataArg, actionContext): Promise<ICertificateState> => {
const context = getActionContext();
const currentState = statePartArg.getState()!;
@@ -617,7 +617,8 @@ export const reprovisionCertificateAction = certificateStatePart.createAction<st
await request.fire({
identity: context.identity!,
domain,
domain: dataArg.domain,
forceRenew: dataArg.forceRenew,
});
// Re-fetch overview after reprovisioning

View File

@@ -312,14 +312,16 @@ export class OpsViewCertificates extends DeesElement {
return;
}
const doReprovision = async () => {
const doReprovision = async (forceRenew = false) => {
await appstate.certificateStatePart.dispatchAction(
appstate.reprovisionCertificateAction,
cert.domain,
{ domain: cert.domain, forceRenew },
);
const { DeesToast } = await import('@design.estate/dees-catalog');
DeesToast.show({
message: `Reprovisioning triggered for ${cert.domain}`,
message: forceRenew
? `Force renewal triggered for ${cert.domain}`
: `Reprovisioning triggered for ${cert.domain}`,
type: 'success',
duration: 3000,
});
@@ -336,7 +338,7 @@ export class OpsViewCertificates extends DeesElement {
name: 'Force Renew',
action: async (modalArg: any) => {
await modalArg.destroy();
await doReprovision();
await doReprovision(true);
},
},
],

View File

@@ -431,8 +431,8 @@ export class OpsViewRoutes extends DeesElement {
<dees-input-text .key=${'ports'} .label=${'Ports (comma-separated)'} .value=${currentPorts} .required=${true}></dees-input-text>
<dees-input-list .key=${'domains'} .label=${'Domains'} .placeholder=${'Add domain...'} .value=${currentDomains}></dees-input-list>
<dees-input-text .key=${'priority'} .label=${'Priority (higher = matched first)'} .value=${route.priority != null ? String(route.priority) : ''}></dees-input-text>
<dees-input-dropdown .key=${'securityProfileRef'} .label=${'Security Profile'} .options=${profileOptions} .selectedKey=${merged.metadata?.securityProfileRef || ''}></dees-input-dropdown>
<dees-input-dropdown .key=${'networkTargetRef'} .label=${'Network Target'} .options=${targetOptions} .selectedKey=${merged.metadata?.networkTargetRef || ''}></dees-input-dropdown>
<dees-input-dropdown .key=${'securityProfileRef'} .label=${'Security Profile'} .options=${profileOptions} .selectedOption=${profileOptions.find((o) => o.key === (merged.metadata?.securityProfileRef || '')) || null}></dees-input-dropdown>
<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-form>
@@ -477,11 +477,15 @@ export class OpsViewRoutes extends DeesElement {
};
const metadata: any = {};
if (formData.securityProfileRef) {
metadata.securityProfileRef = formData.securityProfileRef;
const profileRefValue = formData.securityProfileRef as any;
const profileKey = typeof profileRefValue === 'string' ? profileRefValue : profileRefValue?.key;
if (profileKey) {
metadata.securityProfileRef = profileKey;
}
if (formData.networkTargetRef) {
metadata.networkTargetRef = formData.networkTargetRef;
const targetRefValue = formData.networkTargetRef as any;
const targetKey = typeof targetRefValue === 'string' ? targetRefValue : targetRefValue?.key;
if (targetKey) {
metadata.networkTargetRef = targetKey;
}
await appstate.routeManagementStatePart.dispatchAction(
@@ -528,8 +532,8 @@ export class OpsViewRoutes extends DeesElement {
<dees-input-text .key=${'ports'} .label=${'Ports (comma-separated)'} .required=${true}></dees-input-text>
<dees-input-list .key=${'domains'} .label=${'Domains'} .placeholder=${'Add domain...'}></dees-input-list>
<dees-input-text .key=${'priority'} .label=${'Priority (higher = matched first)'}></dees-input-text>
<dees-input-dropdown .key=${'securityProfileRef'} .label=${'Security Profile'} .options=${profileOptions} .selectedKey=${''}></dees-input-dropdown>
<dees-input-dropdown .key=${'networkTargetRef'} .label=${'Network Target'} .options=${targetOptions} .selectedKey=${''}></dees-input-dropdown>
<dees-input-dropdown .key=${'securityProfileRef'} .label=${'Security Profile'} .options=${profileOptions}></dees-input-dropdown>
<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-form>
@@ -575,11 +579,15 @@ export class OpsViewRoutes extends DeesElement {
// Build metadata if profile/target selected
const metadata: any = {};
if (formData.securityProfileRef) {
metadata.securityProfileRef = formData.securityProfileRef;
const profileRefValue = formData.securityProfileRef as any;
const profileKey = typeof profileRefValue === 'string' ? profileRefValue : profileRefValue?.key;
if (profileKey) {
metadata.securityProfileRef = profileKey;
}
if (formData.networkTargetRef) {
metadata.networkTargetRef = formData.networkTargetRef;
const targetRefValue = formData.networkTargetRef as any;
const targetKey = typeof targetRefValue === 'string' ? targetRefValue : targetRefValue?.key;
if (targetKey) {
metadata.networkTargetRef = targetKey;
}
await appstate.routeManagementStatePart.dispatchAction(

View File

@@ -131,7 +131,7 @@ The app uses `@push.rocks/smartstate` v2.3+ with multiple state parts, scheduled
| State Part | Mode | Description |
|-----------|------|-------------|
| `loginStatePart` | Persistent (IndexedDB) | JWT identity and login status |
| `statsStatePart` | Soft (memory) | Server, email, DNS, security metrics |
| `statsStatePart` | Soft (memory) | Server, email, DNS, security, RADIUS, VPN metrics |
| `configStatePart` | Soft | Current system configuration |
| `uiStatePart` | Soft | Active view, sidebar, auto-refresh, theme |
| `logStatePart` | Soft | Recent logs, streaming status, filters |