Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b62a322c54 | |||
| a3a64e9a02 | |||
| 491e51f40b | |||
| b46247d9cb | |||
| 9c0e46ff4e | |||
| f62bc4a526 |
17
changelog.md
17
changelog.md
@@ -1,5 +1,22 @@
|
||||
# Changelog
|
||||
|
||||
## 2026-04-03 - 12.5.2 - fix(repo)
|
||||
no changes to commit
|
||||
|
||||
|
||||
## 2026-04-03 - 12.5.1 - fix(ops-view-network)
|
||||
centralize traffic chart timing constants for consistent rolling window updates
|
||||
|
||||
- Defines shared constants for the chart window, update interval, and maximum buffered data points
|
||||
- Replaces hardcoded traffic history sizes and timer intervals with derived values across initialization, history loading, and live updates
|
||||
- Keeps the chart rolling window configuration aligned with the in-memory traffic buffer
|
||||
|
||||
## 2026-04-02 - 12.5.0 - feat(ops-view-routes)
|
||||
add priority support and list-based domain editing for routes
|
||||
|
||||
- Adds a priority field to route create and edit forms so route matching order can be configured.
|
||||
- Replaces comma-separated domain text input with a list-based domain editor and updates form handling to persist domains as arrays.
|
||||
|
||||
## 2026-04-02 - 12.4.0 - feat(routes)
|
||||
add route edit and delete actions to the ops routes view
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@serve.zone/dcrouter",
|
||||
"private": false,
|
||||
"version": "12.4.0",
|
||||
"version": "12.5.2",
|
||||
"description": "A multifaceted routing service handling mail and SMS delivery functions.",
|
||||
"type": "module",
|
||||
"exports": {
|
||||
|
||||
@@ -3,6 +3,6 @@
|
||||
*/
|
||||
export const commitinfo = {
|
||||
name: '@serve.zone/dcrouter',
|
||||
version: '12.4.0',
|
||||
version: '12.5.2',
|
||||
description: 'A multifaceted routing service handling mail and SMS delivery functions.'
|
||||
}
|
||||
|
||||
@@ -3,6 +3,6 @@
|
||||
*/
|
||||
export const commitinfo = {
|
||||
name: '@serve.zone/dcrouter',
|
||||
version: '12.4.0',
|
||||
version: '12.5.2',
|
||||
description: 'A multifaceted routing service handling mail and SMS delivery functions.'
|
||||
}
|
||||
|
||||
@@ -28,6 +28,13 @@ interface INetworkRequest {
|
||||
|
||||
@customElement('ops-view-network')
|
||||
export class OpsViewNetwork extends DeesElement {
|
||||
/** How far back the traffic chart shows */
|
||||
private static readonly CHART_WINDOW_MS = 5 * 60 * 1000; // 5 minutes
|
||||
/** How often a new data point is added */
|
||||
private static readonly UPDATE_INTERVAL_MS = 1000; // 1 second
|
||||
/** Derived: max data points the buffer holds */
|
||||
private static readonly MAX_DATA_POINTS = OpsViewNetwork.CHART_WINDOW_MS / OpsViewNetwork.UPDATE_INTERVAL_MS;
|
||||
|
||||
@state()
|
||||
accessor statsState = appstate.statsStatePart.getState()!;
|
||||
|
||||
@@ -46,7 +53,7 @@ export class OpsViewNetwork extends DeesElement {
|
||||
|
||||
// Track if we need to update the chart to avoid unnecessary re-renders
|
||||
private lastChartUpdate = 0;
|
||||
private chartUpdateThreshold = 1000; // Minimum ms between chart updates
|
||||
private chartUpdateThreshold = OpsViewNetwork.UPDATE_INTERVAL_MS; // Minimum ms between chart updates
|
||||
|
||||
private trafficUpdateTimer: any = null;
|
||||
private requestsPerSecHistory: number[] = []; // Track requests/sec over time for trend
|
||||
@@ -104,13 +111,11 @@ export class OpsViewNetwork extends DeesElement {
|
||||
|
||||
private initializeTrafficData() {
|
||||
const now = Date.now();
|
||||
// Fixed 5 minute time range
|
||||
const range = 5 * 60 * 1000; // 5 minutes
|
||||
const bucketSize = range / 60; // 60 data points
|
||||
const { MAX_DATA_POINTS, UPDATE_INTERVAL_MS } = OpsViewNetwork;
|
||||
|
||||
// Initialize with empty data points for both in and out
|
||||
const emptyData = Array.from({ length: 60 }, (_, i) => {
|
||||
const time = now - ((59 - i) * bucketSize);
|
||||
const emptyData = Array.from({ length: MAX_DATA_POINTS }, (_, i) => {
|
||||
const time = now - ((MAX_DATA_POINTS - 1 - i) * UPDATE_INTERVAL_MS);
|
||||
return {
|
||||
x: new Date(time).toISOString(),
|
||||
y: 0,
|
||||
@@ -143,23 +148,23 @@ export class OpsViewNetwork extends DeesElement {
|
||||
y: Math.round((p.out * 8) / 1000000 * 10) / 10,
|
||||
}));
|
||||
|
||||
// Use history as the chart data, keeping the most recent 60 points (5 min window)
|
||||
const sliceStart = Math.max(0, historyIn.length - 60);
|
||||
const { MAX_DATA_POINTS, UPDATE_INTERVAL_MS } = OpsViewNetwork;
|
||||
|
||||
// Use history as the chart data, keeping the most recent points within the window
|
||||
const sliceStart = Math.max(0, historyIn.length - MAX_DATA_POINTS);
|
||||
this.trafficDataIn = historyIn.slice(sliceStart);
|
||||
this.trafficDataOut = historyOut.slice(sliceStart);
|
||||
|
||||
// If fewer than 60 points, pad the front with zeros
|
||||
if (this.trafficDataIn.length < 60) {
|
||||
// If fewer than MAX_DATA_POINTS, pad the front with zeros
|
||||
if (this.trafficDataIn.length < MAX_DATA_POINTS) {
|
||||
const now = Date.now();
|
||||
const range = 5 * 60 * 1000;
|
||||
const bucketSize = range / 60;
|
||||
const padCount = 60 - this.trafficDataIn.length;
|
||||
const padCount = MAX_DATA_POINTS - this.trafficDataIn.length;
|
||||
const firstTimestamp = this.trafficDataIn.length > 0
|
||||
? new Date(this.trafficDataIn[0].x).getTime()
|
||||
: now;
|
||||
|
||||
const padIn = Array.from({ length: padCount }, (_, i) => ({
|
||||
x: new Date(firstTimestamp - ((padCount - i) * bucketSize)).toISOString(),
|
||||
x: new Date(firstTimestamp - ((padCount - i) * UPDATE_INTERVAL_MS)).toISOString(),
|
||||
y: 0,
|
||||
}));
|
||||
const padOut = padIn.map(p => ({ ...p }));
|
||||
@@ -296,7 +301,7 @@ export class OpsViewNetwork extends DeesElement {
|
||||
}
|
||||
]}
|
||||
.realtimeMode=${true}
|
||||
.rollingWindow=${300000}
|
||||
.rollingWindow=${OpsViewNetwork.CHART_WINDOW_MS}
|
||||
.yAxisFormatter=${(val: number) => `${val} Mbit/s`}
|
||||
></dees-chart-area>
|
||||
|
||||
@@ -709,9 +714,8 @@ export class OpsViewNetwork extends DeesElement {
|
||||
private startTrafficUpdateTimer() {
|
||||
this.stopTrafficUpdateTimer(); // Clear any existing timer
|
||||
this.trafficUpdateTimer = setInterval(() => {
|
||||
// Add a new data point every second
|
||||
this.addTrafficDataPoint();
|
||||
}, 1000); // Update every second
|
||||
}, OpsViewNetwork.UPDATE_INTERVAL_MS);
|
||||
}
|
||||
|
||||
private addTrafficDataPoint() {
|
||||
@@ -742,7 +746,7 @@ export class OpsViewNetwork extends DeesElement {
|
||||
};
|
||||
|
||||
// In-place mutation then reassign for Lit reactivity (avoids 4 intermediate arrays)
|
||||
if (this.trafficDataIn.length >= 60) {
|
||||
if (this.trafficDataIn.length >= OpsViewNetwork.MAX_DATA_POINTS) {
|
||||
this.trafficDataIn.shift();
|
||||
this.trafficDataOut.shift();
|
||||
}
|
||||
|
||||
@@ -414,9 +414,9 @@ export class OpsViewRoutes extends DeesElement {
|
||||
const currentPorts = Array.isArray(route.match.ports)
|
||||
? route.match.ports.map((p: any) => typeof p === 'number' ? String(p) : `${p.from}-${p.to}`).join(', ')
|
||||
: String(route.match.ports);
|
||||
const currentDomains = route.match.domains
|
||||
? (Array.isArray(route.match.domains) ? route.match.domains.join(', ') : route.match.domains)
|
||||
: '';
|
||||
const currentDomains: string[] = route.match.domains
|
||||
? (Array.isArray(route.match.domains) ? route.match.domains : [route.match.domains])
|
||||
: [];
|
||||
const firstTarget = route.action.targets?.[0];
|
||||
const currentTargetHost = firstTarget
|
||||
? (Array.isArray(firstTarget.host) ? firstTarget.host[0] : firstTarget.host)
|
||||
@@ -429,7 +429,8 @@ export class OpsViewRoutes extends DeesElement {
|
||||
<dees-form>
|
||||
<dees-input-text .key=${'name'} .label=${'Route Name'} .value=${route.name || ''} .required=${true}></dees-input-text>
|
||||
<dees-input-text .key=${'ports'} .label=${'Ports (comma-separated)'} .value=${currentPorts} .required=${true}></dees-input-text>
|
||||
<dees-input-text .key=${'domains'} .label=${'Domains (comma-separated, optional)'} .value=${currentDomains}></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-text .key=${'targetHost'} .label=${'Target Host (if no target selected)'} .value=${currentTargetHost}></dees-input-text>
|
||||
@@ -452,15 +453,16 @@ export class OpsViewRoutes extends DeesElement {
|
||||
if (!formData.name || !formData.ports) return;
|
||||
|
||||
const ports = formData.ports.split(',').map((p: string) => parseInt(p.trim(), 10)).filter((p: number) => !isNaN(p));
|
||||
const domains = formData.domains
|
||||
? formData.domains.split(',').map((d: string) => d.trim()).filter(Boolean)
|
||||
: undefined;
|
||||
const domains: string[] = Array.isArray(formData.domains)
|
||||
? formData.domains.filter(Boolean)
|
||||
: [];
|
||||
const priority = formData.priority ? parseInt(formData.priority, 10) : undefined;
|
||||
|
||||
const updatedRoute: any = {
|
||||
name: formData.name,
|
||||
match: {
|
||||
ports,
|
||||
...(domains && domains.length > 0 ? { domains } : {}),
|
||||
...(domains.length > 0 ? { domains } : {}),
|
||||
},
|
||||
action: {
|
||||
type: 'forward',
|
||||
@@ -471,6 +473,7 @@ export class OpsViewRoutes extends DeesElement {
|
||||
},
|
||||
],
|
||||
},
|
||||
...(priority != null && !isNaN(priority) ? { priority } : {}),
|
||||
};
|
||||
|
||||
const metadata: any = {};
|
||||
@@ -523,7 +526,8 @@ export class OpsViewRoutes extends DeesElement {
|
||||
<dees-form>
|
||||
<dees-input-text .key=${'name'} .label=${'Route Name'} .required=${true}></dees-input-text>
|
||||
<dees-input-text .key=${'ports'} .label=${'Ports (comma-separated)'} .required=${true}></dees-input-text>
|
||||
<dees-input-text .key=${'domains'} .label=${'Domains (comma-separated, optional)'}></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-text .key=${'targetHost'} .label=${'Target Host (if no target selected)'} .value=${'localhost'}></dees-input-text>
|
||||
@@ -546,15 +550,16 @@ export class OpsViewRoutes extends DeesElement {
|
||||
if (!formData.name || !formData.ports) return;
|
||||
|
||||
const ports = formData.ports.split(',').map((p: string) => parseInt(p.trim(), 10)).filter((p: number) => !isNaN(p));
|
||||
const domains = formData.domains
|
||||
? formData.domains.split(',').map((d: string) => d.trim()).filter(Boolean)
|
||||
: undefined;
|
||||
const domains: string[] = Array.isArray(formData.domains)
|
||||
? formData.domains.filter(Boolean)
|
||||
: [];
|
||||
const priority = formData.priority ? parseInt(formData.priority, 10) : undefined;
|
||||
|
||||
const route: any = {
|
||||
name: formData.name,
|
||||
match: {
|
||||
ports,
|
||||
...(domains && domains.length > 0 ? { domains } : {}),
|
||||
...(domains.length > 0 ? { domains } : {}),
|
||||
},
|
||||
action: {
|
||||
type: 'forward',
|
||||
@@ -565,6 +570,7 @@ export class OpsViewRoutes extends DeesElement {
|
||||
},
|
||||
],
|
||||
},
|
||||
...(priority != null && !isNaN(priority) ? { priority } : {}),
|
||||
};
|
||||
|
||||
// Build metadata if profile/target selected
|
||||
|
||||
Reference in New Issue
Block a user