Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| ea32babaac | |||
| a4ddedaf46 | |||
| 7ce09c53ca | |||
| 69be2295f1 | |||
| 018efa32f6 |
25
changelog.md
25
changelog.md
@@ -1,5 +1,30 @@
|
||||
# Changelog
|
||||
|
||||
## 2026-02-17 - 6.6.1 - fix(icons)
|
||||
standardize icon identifiers to lucide-prefixed names across operational views
|
||||
|
||||
- Replaced legacy/ambiguous icon names with 'lucide:...' identifiers in four UI modules: ts_web/elements/ops-view-certificates.ts, ops-view-network.ts, ops-view-overview.ts, and ops-view-security.ts.
|
||||
- Updated common action/menu icons (e.g. arrowsRotate -> lucide:RefreshCw, magnifyingGlass -> lucide:Search, copy -> lucide:Copy, fileExport -> lucide:FileOutput).
|
||||
- Mapped dashboard/tile icons to lucide equivalents (e.g. server -> lucide:Server, networkWired/sitemap -> lucide:Network, download/upload -> lucide:Download/Upload, microchip/memory -> lucide:Cpu/MemoryStick).
|
||||
- Normalized alert and status icons to lucide names (e.g. triangleExclamation -> lucide:TriangleAlert, shield/userShield -> lucide:Shield/ShieldCheck, clock/clockRotateLeft -> lucide:Clock/History).
|
||||
|
||||
## 2026-02-17 - 6.6.0 - feat(remoteingress)
|
||||
derive effective remote ingress listen ports from route configs and expose them via ops API
|
||||
|
||||
- Derive listen ports from SmartProxy route configs with remoteIngress.enabled; supports optional edgeFilter to target edges by id or tags.
|
||||
- Add RemoteIngressManager.setRoutes(), derivePortsForEdge(), and getEffectiveListenPorts() which falls back to manual listenPorts when present.
|
||||
- dcrouter now supplies route configs to RemoteIngressManager during initialization and when updating SmartProxy configuration to keep derived ports in sync.
|
||||
- Ops API now returns effectiveListenPorts for edges; createRemoteIngress.listenPorts is optional and createEdge defaults listenPorts to an empty array.
|
||||
- Bump dependency @serve.zone/remoteingress to ^3.0.4 to align types/behavior.
|
||||
|
||||
## 2026-02-16 - 6.5.0 - feat(ops-view-remoteingress)
|
||||
add 'Create Edge Node' header action to remote ingress table and remove duplicate createNewAction
|
||||
|
||||
- Add a 'Create Edge Node' header action in dataActions that opens DeesModal to collect name, listenPorts and tags
|
||||
- Parse comma-separated listenPorts into integer array and normalize optional tags
|
||||
- Dispatch appstate.createRemoteIngressAction with the collected payload
|
||||
- Remove the previously duplicated createNewAction prop from the dees-table
|
||||
|
||||
## 2026-02-16 - 6.4.5 - fix(remoteingress)
|
||||
mark remote ingress data actions as row actions and bump @design.estate/dees-catalog dependency
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@serve.zone/dcrouter",
|
||||
"private": false,
|
||||
"version": "6.4.5",
|
||||
"version": "6.6.1",
|
||||
"description": "A multifaceted routing service handling mail and SMS delivery functions.",
|
||||
"type": "module",
|
||||
"exports": {
|
||||
@@ -56,7 +56,7 @@
|
||||
"@push.rocks/smartstate": "^2.0.30",
|
||||
"@push.rocks/smartunique": "^3.0.9",
|
||||
"@serve.zone/interfaces": "^5.3.0",
|
||||
"@serve.zone/remoteingress": "^3.0.2",
|
||||
"@serve.zone/remoteingress": "^3.0.4",
|
||||
"@tsclass/tsclass": "^9.3.0",
|
||||
"lru-cache": "^11.2.6",
|
||||
"uuid": "^13.0.0"
|
||||
|
||||
10
pnpm-lock.yaml
generated
10
pnpm-lock.yaml
generated
@@ -96,8 +96,8 @@ importers:
|
||||
specifier: ^5.3.0
|
||||
version: 5.3.0
|
||||
'@serve.zone/remoteingress':
|
||||
specifier: ^3.0.2
|
||||
version: 3.0.2
|
||||
specifier: ^3.0.4
|
||||
version: 3.0.4
|
||||
'@tsclass/tsclass':
|
||||
specifier: ^9.3.0
|
||||
version: 9.3.0
|
||||
@@ -1340,8 +1340,8 @@ packages:
|
||||
'@serve.zone/interfaces@5.3.0':
|
||||
resolution: {integrity: sha512-venO7wtDR9ixzD9NhdERBGjNKbFA5LL0yHw4eqGh0UpmvtXVc3SFG0uuHDilOKMZqZ8bttV88qVsFy1aSTJrtA==}
|
||||
|
||||
'@serve.zone/remoteingress@3.0.2':
|
||||
resolution: {integrity: sha512-FnwNVO0Dn9xuNv0t81u6pjCizSeCyMjkRKm6wN5qycCdGFoJmFbBamHqV01KtK1KcgDTd7LX+PowSqKReNrBGw==}
|
||||
'@serve.zone/remoteingress@3.0.4':
|
||||
resolution: {integrity: sha512-ZD66Y8fvW7SjealziOlhaC7+Y/3gxQkZlj/X8rxgVHmGhlc/YQtn6H6LNVazbM88BXK5ns004Qo6ongAB6Ho0Q==}
|
||||
|
||||
'@sindresorhus/is@5.6.0':
|
||||
resolution: {integrity: sha512-TV7t8GKYaJWsn00tFDqBw8+Uqmr8A0fRU1tvTQhyZzGv0sJCGRQL3JGMI3ucuKo3XIZdUP+Lx7/gh2t3lewy7g==}
|
||||
@@ -6830,7 +6830,7 @@ snapshots:
|
||||
'@push.rocks/smartlog-interfaces': 3.0.2
|
||||
'@tsclass/tsclass': 9.3.0
|
||||
|
||||
'@serve.zone/remoteingress@3.0.2':
|
||||
'@serve.zone/remoteingress@3.0.4':
|
||||
dependencies:
|
||||
'@push.rocks/qenv': 6.1.3
|
||||
'@push.rocks/smartrust': 1.2.1
|
||||
|
||||
@@ -3,6 +3,6 @@
|
||||
*/
|
||||
export const commitinfo = {
|
||||
name: '@serve.zone/dcrouter',
|
||||
version: '6.4.5',
|
||||
version: '6.6.1',
|
||||
description: 'A multifaceted routing service handling mail and SMS delivery functions.'
|
||||
}
|
||||
|
||||
@@ -955,10 +955,15 @@ export class DcRouter {
|
||||
|
||||
// Update configuration
|
||||
this.options.smartProxyConfig = config;
|
||||
|
||||
|
||||
// Update routes on RemoteIngressManager so derived ports stay in sync
|
||||
if (this.remoteIngressManager && config.routes) {
|
||||
this.remoteIngressManager.setRoutes(config.routes as any[]);
|
||||
}
|
||||
|
||||
// Start new SmartProxy with updated configuration (will include email routes if configured)
|
||||
await this.setupSmartProxy();
|
||||
|
||||
|
||||
console.log('SmartProxy configuration updated');
|
||||
}
|
||||
|
||||
@@ -1587,6 +1592,10 @@ export class DcRouter {
|
||||
this.remoteIngressManager = new RemoteIngressManager(this.storageManager);
|
||||
await this.remoteIngressManager.initialize();
|
||||
|
||||
// Pass current routes so the manager can derive edge ports from remoteIngress-tagged routes
|
||||
const currentRoutes = this.options.smartProxyConfig?.routes || [];
|
||||
this.remoteIngressManager.setRoutes(currentRoutes as any[]);
|
||||
|
||||
// Create and start the tunnel manager
|
||||
this.tunnelManager = new TunnelManager(this.remoteIngressManager, {
|
||||
tunnelPort: this.options.remoteIngressConfig.tunnelPort ?? 8443,
|
||||
|
||||
@@ -20,10 +20,11 @@ export class RemoteIngressHandler {
|
||||
if (!manager) {
|
||||
return { edges: [] };
|
||||
}
|
||||
// Return edges without secrets
|
||||
// Return edges without secrets, enriched with effective listen ports
|
||||
const edges = manager.getAllEdges().map((e) => ({
|
||||
...e,
|
||||
secret: '********', // Never expose secrets via API
|
||||
effectiveListenPorts: manager.getEffectiveListenPorts(e),
|
||||
}));
|
||||
return { edges };
|
||||
},
|
||||
@@ -47,7 +48,7 @@ export class RemoteIngressHandler {
|
||||
|
||||
const edge = await manager.createEdge(
|
||||
dataArg.name,
|
||||
dataArg.listenPorts,
|
||||
dataArg.listenPorts || [],
|
||||
dataArg.tags,
|
||||
);
|
||||
|
||||
|
||||
@@ -1,9 +1,30 @@
|
||||
import * as plugins from '../plugins.js';
|
||||
import type { StorageManager } from '../storage/classes.storagemanager.js';
|
||||
import type { IRemoteIngress } from '../../ts_interfaces/data/remoteingress.js';
|
||||
import type { IRemoteIngress, IDcRouterRouteConfig } from '../../ts_interfaces/data/remoteingress.js';
|
||||
|
||||
const STORAGE_PREFIX = '/remote-ingress/';
|
||||
|
||||
/**
|
||||
* Flatten a port range (number | number[] | Array<{from, to}>) to a sorted unique number array.
|
||||
*/
|
||||
function extractPorts(portRange: number | number[] | Array<{ from: number; to: number }>): number[] {
|
||||
const ports = new Set<number>();
|
||||
if (typeof portRange === 'number') {
|
||||
ports.add(portRange);
|
||||
} else if (Array.isArray(portRange)) {
|
||||
for (const entry of portRange) {
|
||||
if (typeof entry === 'number') {
|
||||
ports.add(entry);
|
||||
} else if (typeof entry === 'object' && 'from' in entry && 'to' in entry) {
|
||||
for (let p = entry.from; p <= entry.to; p++) {
|
||||
ports.add(p);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return [...ports].sort((a, b) => a - b);
|
||||
}
|
||||
|
||||
/**
|
||||
* Manages CRUD for remote ingress edge registrations.
|
||||
* Persists edge configs via StorageManager and provides
|
||||
@@ -12,6 +33,7 @@ const STORAGE_PREFIX = '/remote-ingress/';
|
||||
export class RemoteIngressManager {
|
||||
private storageManager: StorageManager;
|
||||
private edges: Map<string, IRemoteIngress> = new Map();
|
||||
private routes: IDcRouterRouteConfig[] = [];
|
||||
|
||||
constructor(storageManager: StorageManager) {
|
||||
this.storageManager = storageManager;
|
||||
@@ -30,12 +52,60 @@ export class RemoteIngressManager {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Store the current route configs for port derivation.
|
||||
*/
|
||||
public setRoutes(routes: IDcRouterRouteConfig[]): void {
|
||||
this.routes = routes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Derive listen ports for an edge from routes tagged with remoteIngress.enabled.
|
||||
* When a route specifies edgeFilter, only edges whose id or tags match get that route's ports.
|
||||
* When edgeFilter is absent, the route applies to all edges.
|
||||
*/
|
||||
public derivePortsForEdge(edgeId: string, edgeTags?: string[]): number[] {
|
||||
const ports = new Set<number>();
|
||||
|
||||
for (const route of this.routes) {
|
||||
if (!route.remoteIngress?.enabled) continue;
|
||||
|
||||
// Apply edge filter if present
|
||||
const filter = route.remoteIngress.edgeFilter;
|
||||
if (filter && filter.length > 0) {
|
||||
const idMatch = filter.includes(edgeId);
|
||||
const tagMatch = edgeTags?.some((tag) => filter.includes(tag)) ?? false;
|
||||
if (!idMatch && !tagMatch) continue;
|
||||
}
|
||||
|
||||
// Extract ports from the route match
|
||||
if (route.match?.ports) {
|
||||
for (const p of extractPorts(route.match.ports)) {
|
||||
ports.add(p);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return [...ports].sort((a, b) => a - b);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the effective listen ports for an edge.
|
||||
* Returns manual listenPorts if non-empty, otherwise derives ports from tagged routes.
|
||||
*/
|
||||
public getEffectiveListenPorts(edge: IRemoteIngress): number[] {
|
||||
if (edge.listenPorts && edge.listenPorts.length > 0) {
|
||||
return edge.listenPorts;
|
||||
}
|
||||
return this.derivePortsForEdge(edge.id, edge.tags);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new edge registration.
|
||||
*/
|
||||
public async createEdge(
|
||||
name: string,
|
||||
listenPorts: number[],
|
||||
listenPorts: number[] = [],
|
||||
tags?: string[],
|
||||
): Promise<IRemoteIngress> {
|
||||
const id = plugins.uuid.v4();
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import type { IRouteConfig } from '@push.rocks/smartproxy';
|
||||
|
||||
/**
|
||||
* A stored remote ingress edge registration.
|
||||
*/
|
||||
@@ -23,3 +25,25 @@ export interface IRemoteIngressStatus {
|
||||
lastHeartbeat: number | null;
|
||||
connectedAt: number | null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Route-level remote ingress configuration.
|
||||
* When attached to a route, signals that traffic for this route
|
||||
* should be accepted from remote edge nodes.
|
||||
*/
|
||||
export interface IRouteRemoteIngress {
|
||||
/** Whether this route receives traffic from edge nodes */
|
||||
enabled: boolean;
|
||||
/** Optional filter: only edges whose id or tags match get this route's ports.
|
||||
* When absent, the route applies to all edges. */
|
||||
edgeFilter?: string[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Extended route config used within dcrouter.
|
||||
* Adds the optional `remoteIngress` property to SmartProxy's IRouteConfig.
|
||||
* SmartProxy ignores unknown properties at runtime.
|
||||
*/
|
||||
export type IDcRouterRouteConfig = IRouteConfig & {
|
||||
remoteIngress?: IRouteRemoteIngress;
|
||||
};
|
||||
|
||||
@@ -17,7 +17,7 @@ export interface IReq_CreateRemoteIngress extends plugins.typedrequestInterfaces
|
||||
request: {
|
||||
identity?: authInterfaces.IIdentity;
|
||||
name: string;
|
||||
listenPorts: number[];
|
||||
listenPorts?: number[];
|
||||
tags?: string[];
|
||||
};
|
||||
response: {
|
||||
|
||||
@@ -3,6 +3,6 @@
|
||||
*/
|
||||
export const commitinfo = {
|
||||
name: '@serve.zone/dcrouter',
|
||||
version: '6.4.5',
|
||||
version: '6.6.1',
|
||||
description: 'A multifaceted routing service handling mail and SMS delivery functions.'
|
||||
}
|
||||
|
||||
@@ -175,7 +175,7 @@ export class OpsViewCertificates extends DeesElement {
|
||||
title: 'Total Certificates',
|
||||
value: summary.total,
|
||||
type: 'number',
|
||||
icon: 'shieldHalved',
|
||||
icon: 'lucide:ShieldHalf',
|
||||
color: '#3b82f6',
|
||||
},
|
||||
{
|
||||
@@ -183,7 +183,7 @@ export class OpsViewCertificates extends DeesElement {
|
||||
title: 'Valid',
|
||||
value: summary.valid,
|
||||
type: 'number',
|
||||
icon: 'check',
|
||||
icon: 'lucide:Check',
|
||||
color: '#22c55e',
|
||||
},
|
||||
{
|
||||
@@ -191,7 +191,7 @@ export class OpsViewCertificates extends DeesElement {
|
||||
title: 'Expiring Soon',
|
||||
value: summary.expiring,
|
||||
type: 'number',
|
||||
icon: 'clock',
|
||||
icon: 'lucide:Clock',
|
||||
color: '#f59e0b',
|
||||
},
|
||||
{
|
||||
@@ -199,7 +199,7 @@ export class OpsViewCertificates extends DeesElement {
|
||||
title: 'Failed / Expired',
|
||||
value: summary.failed + summary.expired,
|
||||
type: 'number',
|
||||
icon: 'triangleExclamation',
|
||||
icon: 'lucide:TriangleAlert',
|
||||
color: '#ef4444',
|
||||
},
|
||||
];
|
||||
@@ -211,7 +211,7 @@ export class OpsViewCertificates extends DeesElement {
|
||||
.gridActions=${[
|
||||
{
|
||||
name: 'Refresh',
|
||||
iconName: 'arrowsRotate',
|
||||
iconName: 'lucide:RefreshCw',
|
||||
action: async () => {
|
||||
await appstate.certificateStatePart.dispatchAction(
|
||||
appstate.fetchCertificateOverviewAction,
|
||||
@@ -243,7 +243,7 @@ export class OpsViewCertificates extends DeesElement {
|
||||
.dataActions=${[
|
||||
{
|
||||
name: 'Reprovision',
|
||||
iconName: 'arrowsRotate',
|
||||
iconName: 'lucide:RefreshCw',
|
||||
type: ['inRow'],
|
||||
actionFunc: async (actionData: { item: interfaces.requests.ICertificateInfo }) => {
|
||||
const cert = actionData.item;
|
||||
@@ -270,7 +270,7 @@ export class OpsViewCertificates extends DeesElement {
|
||||
},
|
||||
{
|
||||
name: 'View Details',
|
||||
iconName: 'magnifyingGlass',
|
||||
iconName: 'lucide:Search',
|
||||
type: ['doubleClick', 'contextmenu'],
|
||||
actionFunc: async (actionData: { item: interfaces.requests.ICertificateInfo }) => {
|
||||
const cert = actionData.item;
|
||||
@@ -289,7 +289,7 @@ export class OpsViewCertificates extends DeesElement {
|
||||
menuOptions: [
|
||||
{
|
||||
name: 'Copy Domain',
|
||||
iconName: 'copy',
|
||||
iconName: 'lucide:Copy',
|
||||
action: async () => {
|
||||
await navigator.clipboard.writeText(cert.domain);
|
||||
},
|
||||
|
||||
@@ -287,7 +287,7 @@ export class OpsViewNetwork extends DeesElement {
|
||||
.dataActions=${[
|
||||
{
|
||||
name: 'View Details',
|
||||
iconName: 'magnifyingGlass',
|
||||
iconName: 'lucide:Search',
|
||||
type: ['inRow', 'doubleClick', 'contextmenu'],
|
||||
actionFunc: async (actionData) => {
|
||||
await this.showRequestDetails(actionData.item);
|
||||
@@ -336,7 +336,7 @@ export class OpsViewNetwork extends DeesElement {
|
||||
menuOptions: [
|
||||
{
|
||||
name: 'Copy Request ID',
|
||||
iconName: 'copy',
|
||||
iconName: 'lucide:Copy',
|
||||
action: async () => {
|
||||
await navigator.clipboard.writeText(request.id);
|
||||
}
|
||||
@@ -429,13 +429,13 @@ export class OpsViewNetwork extends DeesElement {
|
||||
title: 'Active Connections',
|
||||
value: activeConnections,
|
||||
type: 'number',
|
||||
icon: 'plug',
|
||||
icon: 'lucide:Plug',
|
||||
color: activeConnections > 100 ? '#f59e0b' : '#22c55e',
|
||||
description: `Total: ${this.networkState.requestsTotal || this.statsState.serverStats?.totalConnections || 0}`,
|
||||
actions: [
|
||||
{
|
||||
name: 'View Details',
|
||||
iconName: 'magnifyingGlass',
|
||||
iconName: 'lucide:Search',
|
||||
action: async () => {
|
||||
},
|
||||
},
|
||||
@@ -446,7 +446,7 @@ export class OpsViewNetwork extends DeesElement {
|
||||
title: 'Requests/sec',
|
||||
value: reqPerSec,
|
||||
type: 'trend',
|
||||
icon: 'chartLine',
|
||||
icon: 'lucide:ChartLine',
|
||||
color: '#3b82f6',
|
||||
trendData: trendData,
|
||||
description: `Total: ${this.formatNumber(this.networkState.requestsTotal || 0)} requests`,
|
||||
@@ -457,7 +457,7 @@ export class OpsViewNetwork extends DeesElement {
|
||||
value: this.formatBitsPerSecond(throughput.in),
|
||||
unit: '',
|
||||
type: 'number',
|
||||
icon: 'download',
|
||||
icon: 'lucide:Download',
|
||||
color: '#22c55e',
|
||||
description: `Total: ${this.formatBytes(this.networkState.totalBytes?.in || 0)}`,
|
||||
},
|
||||
@@ -467,7 +467,7 @@ export class OpsViewNetwork extends DeesElement {
|
||||
value: this.formatBitsPerSecond(throughput.out),
|
||||
unit: '',
|
||||
type: 'number',
|
||||
icon: 'upload',
|
||||
icon: 'lucide:Upload',
|
||||
color: '#8b5cf6',
|
||||
description: `Total: ${this.formatBytes(this.networkState.totalBytes?.out || 0)}`,
|
||||
},
|
||||
@@ -480,7 +480,7 @@ export class OpsViewNetwork extends DeesElement {
|
||||
.gridActions=${[
|
||||
{
|
||||
name: 'Export Data',
|
||||
iconName: 'fileExport',
|
||||
iconName: 'lucide:FileOutput',
|
||||
action: async () => {
|
||||
console.log('Export feature coming soon');
|
||||
},
|
||||
|
||||
@@ -163,7 +163,7 @@ export class OpsViewOverview extends DeesElement {
|
||||
title: 'Server Status',
|
||||
value: this.statsState.serverStats.uptime ? 'Online' : 'Offline',
|
||||
type: 'text',
|
||||
icon: 'server',
|
||||
icon: 'lucide:Server',
|
||||
color: this.statsState.serverStats.uptime ? '#22c55e' : '#ef4444',
|
||||
description: `Uptime: ${this.formatUptime(this.statsState.serverStats.uptime)}`,
|
||||
},
|
||||
@@ -172,7 +172,7 @@ export class OpsViewOverview extends DeesElement {
|
||||
title: 'Active Connections',
|
||||
value: this.statsState.serverStats.activeConnections,
|
||||
type: 'number',
|
||||
icon: 'networkWired',
|
||||
icon: 'lucide:Network',
|
||||
color: '#3b82f6',
|
||||
description: `Total: ${this.statsState.serverStats.totalConnections}`,
|
||||
},
|
||||
@@ -181,7 +181,7 @@ export class OpsViewOverview extends DeesElement {
|
||||
title: 'Throughput In',
|
||||
value: this.formatBitsPerSecond(this.statsState.serverStats.throughput?.bytesInPerSecond || 0),
|
||||
type: 'text',
|
||||
icon: 'download',
|
||||
icon: 'lucide:Download',
|
||||
color: '#22c55e',
|
||||
description: `Total: ${this.formatBytes(this.statsState.serverStats.throughput?.bytesIn || 0)}`,
|
||||
},
|
||||
@@ -190,7 +190,7 @@ export class OpsViewOverview extends DeesElement {
|
||||
title: 'Throughput Out',
|
||||
value: this.formatBitsPerSecond(this.statsState.serverStats.throughput?.bytesOutPerSecond || 0),
|
||||
type: 'text',
|
||||
icon: 'upload',
|
||||
icon: 'lucide:Upload',
|
||||
color: '#8b5cf6',
|
||||
description: `Total: ${this.formatBytes(this.statsState.serverStats.throughput?.bytesOut || 0)}`,
|
||||
},
|
||||
@@ -199,7 +199,7 @@ export class OpsViewOverview extends DeesElement {
|
||||
title: 'CPU Usage',
|
||||
value: cpuUsage,
|
||||
type: 'gauge',
|
||||
icon: 'microchip',
|
||||
icon: 'lucide:Cpu',
|
||||
gaugeOptions: {
|
||||
min: 0,
|
||||
max: 100,
|
||||
@@ -215,7 +215,7 @@ export class OpsViewOverview extends DeesElement {
|
||||
title: 'Memory Usage',
|
||||
value: memoryUsage,
|
||||
type: 'percentage',
|
||||
icon: 'memory',
|
||||
icon: 'lucide:MemoryStick',
|
||||
color: memoryUsage > 80 ? '#ef4444' : memoryUsage > 60 ? '#f59e0b' : '#22c55e',
|
||||
description: this.statsState.serverStats.memoryUsage.actualUsageBytes !== undefined && this.statsState.serverStats.memoryUsage.maxMemoryMB !== undefined
|
||||
? `${this.formatBytes(this.statsState.serverStats.memoryUsage.actualUsageBytes)} / ${this.formatBytes(this.statsState.serverStats.memoryUsage.maxMemoryMB * 1024 * 1024)}`
|
||||
@@ -229,7 +229,7 @@ export class OpsViewOverview extends DeesElement {
|
||||
.gridActions=${[
|
||||
{
|
||||
name: 'Refresh',
|
||||
iconName: 'arrowsRotate',
|
||||
iconName: 'lucide:RefreshCw',
|
||||
action: async () => {
|
||||
await appstate.statsStatePart.dispatchAction(appstate.fetchAllStatsAction, null);
|
||||
},
|
||||
@@ -251,7 +251,7 @@ export class OpsViewOverview extends DeesElement {
|
||||
title: 'Emails Sent',
|
||||
value: this.statsState.emailStats.sent,
|
||||
type: 'number',
|
||||
icon: 'paperPlane',
|
||||
icon: 'lucide:Send',
|
||||
color: '#22c55e',
|
||||
description: `Delivery rate: ${(deliveryRate * 100).toFixed(1)}%`,
|
||||
},
|
||||
@@ -260,7 +260,7 @@ export class OpsViewOverview extends DeesElement {
|
||||
title: 'Emails Received',
|
||||
value: this.statsState.emailStats.received,
|
||||
type: 'number',
|
||||
icon: 'envelope',
|
||||
icon: 'lucide:Mail',
|
||||
color: '#3b82f6',
|
||||
},
|
||||
{
|
||||
@@ -268,7 +268,7 @@ export class OpsViewOverview extends DeesElement {
|
||||
title: 'Queued',
|
||||
value: this.statsState.emailStats.queued,
|
||||
type: 'number',
|
||||
icon: 'clock',
|
||||
icon: 'lucide:Clock',
|
||||
color: '#f59e0b',
|
||||
description: 'Pending delivery',
|
||||
},
|
||||
@@ -277,7 +277,7 @@ export class OpsViewOverview extends DeesElement {
|
||||
title: 'Failed',
|
||||
value: this.statsState.emailStats.failed,
|
||||
type: 'number',
|
||||
icon: 'triangleExclamation',
|
||||
icon: 'lucide:TriangleAlert',
|
||||
color: '#ef4444',
|
||||
description: `Bounce rate: ${(bounceRate * 100).toFixed(1)}%`,
|
||||
},
|
||||
@@ -300,7 +300,7 @@ export class OpsViewOverview extends DeesElement {
|
||||
title: 'DNS Queries',
|
||||
value: this.statsState.dnsStats.totalQueries,
|
||||
type: 'number',
|
||||
icon: 'globe',
|
||||
icon: 'lucide:Globe',
|
||||
color: '#3b82f6',
|
||||
description: 'Total queries handled',
|
||||
},
|
||||
@@ -309,7 +309,7 @@ export class OpsViewOverview extends DeesElement {
|
||||
title: 'Cache Hit Rate',
|
||||
value: cacheHitRate,
|
||||
type: 'percentage',
|
||||
icon: 'database',
|
||||
icon: 'lucide:Database',
|
||||
color: cacheHitRate > 80 ? '#22c55e' : cacheHitRate > 60 ? '#f59e0b' : '#ef4444',
|
||||
description: `${this.statsState.dnsStats.cacheHits} hits / ${this.statsState.dnsStats.cacheMisses} misses`,
|
||||
},
|
||||
@@ -318,7 +318,7 @@ export class OpsViewOverview extends DeesElement {
|
||||
title: 'Active Domains',
|
||||
value: this.statsState.dnsStats.activeDomains,
|
||||
type: 'number',
|
||||
icon: 'sitemap',
|
||||
icon: 'lucide:Network',
|
||||
color: '#8b5cf6',
|
||||
},
|
||||
{
|
||||
@@ -327,7 +327,7 @@ export class OpsViewOverview extends DeesElement {
|
||||
value: this.statsState.dnsStats.averageResponseTime.toFixed(1),
|
||||
unit: 'ms',
|
||||
type: 'number',
|
||||
icon: 'clockRotateLeft',
|
||||
icon: 'lucide:History',
|
||||
color: this.statsState.dnsStats.averageResponseTime < 50 ? '#22c55e' : '#f59e0b',
|
||||
},
|
||||
];
|
||||
|
||||
@@ -192,6 +192,43 @@ export class OpsViewRemoteIngress extends DeesElement {
|
||||
lastHeartbeat: this.getLastHeartbeat(edge.id),
|
||||
})}
|
||||
.dataActions=${[
|
||||
{
|
||||
name: 'Create Edge Node',
|
||||
iconName: 'lucide:plus',
|
||||
type: ['header'],
|
||||
actionFunc: async () => {
|
||||
const { DeesModal } = await import('@design.estate/dees-catalog');
|
||||
const result = await DeesModal.createAndShow({
|
||||
heading: 'Create Edge Node',
|
||||
content: html`
|
||||
<dees-form>
|
||||
<dees-input-text .key=${'name'} .label=${'Name'} .required=${true}></dees-input-text>
|
||||
<dees-input-text .key=${'listenPorts'} .label=${'Listen Ports (comma-separated)'} .required=${true} .value=${'443,25'}></dees-input-text>
|
||||
<dees-input-text .key=${'tags'} .label=${'Tags (comma-separated, optional)'}></dees-input-text>
|
||||
</dees-form>
|
||||
`,
|
||||
menuOptions: [],
|
||||
});
|
||||
if (result) {
|
||||
const formData = result as any;
|
||||
const ports = (formData.name ? formData.listenPorts : '443')
|
||||
.split(',')
|
||||
.map((p: string) => parseInt(p.trim(), 10))
|
||||
.filter((p: number) => !isNaN(p));
|
||||
const tags = formData.tags
|
||||
? formData.tags.split(',').map((t: string) => t.trim()).filter(Boolean)
|
||||
: undefined;
|
||||
await appstate.remoteIngressStatePart.dispatchAction(
|
||||
appstate.createRemoteIngressAction,
|
||||
{
|
||||
name: formData.name,
|
||||
listenPorts: ports,
|
||||
tags,
|
||||
},
|
||||
);
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'Regenerate Secret',
|
||||
iconName: 'lucide:key',
|
||||
@@ -215,38 +252,6 @@ export class OpsViewRemoteIngress extends DeesElement {
|
||||
},
|
||||
},
|
||||
]}
|
||||
.createNewAction=${async () => {
|
||||
const { DeesModal } = await import('@design.estate/dees-catalog');
|
||||
const result = await DeesModal.createAndShow({
|
||||
heading: 'Create Edge Node',
|
||||
content: html`
|
||||
<dees-form>
|
||||
<dees-input-text .key=${'name'} .label=${'Name'} .required=${true}></dees-input-text>
|
||||
<dees-input-text .key=${'listenPorts'} .label=${'Listen Ports (comma-separated)'} .required=${true} .value=${'443,25'}></dees-input-text>
|
||||
<dees-input-text .key=${'tags'} .label=${'Tags (comma-separated, optional)'}></dees-input-text>
|
||||
</dees-form>
|
||||
`,
|
||||
menuOptions: [],
|
||||
});
|
||||
if (result) {
|
||||
const formData = result as any;
|
||||
const ports = (formData.name ? formData.listenPorts : '443')
|
||||
.split(',')
|
||||
.map((p: string) => parseInt(p.trim(), 10))
|
||||
.filter((p: number) => !isNaN(p));
|
||||
const tags = formData.tags
|
||||
? formData.tags.split(',').map((t: string) => t.trim()).filter(Boolean)
|
||||
: undefined;
|
||||
await appstate.remoteIngressStatePart.dispatchAction(
|
||||
appstate.createRemoteIngressAction,
|
||||
{
|
||||
name: formData.name,
|
||||
listenPorts: ports,
|
||||
tags,
|
||||
},
|
||||
);
|
||||
}
|
||||
}}
|
||||
></dees-table>
|
||||
</div>
|
||||
`;
|
||||
|
||||
@@ -256,7 +256,7 @@ export class OpsViewSecurity extends DeesElement {
|
||||
title: 'Threat Level',
|
||||
value: threatScore,
|
||||
type: 'gauge',
|
||||
icon: 'shield',
|
||||
icon: 'lucide:Shield',
|
||||
gaugeOptions: {
|
||||
min: 0,
|
||||
max: 100,
|
||||
@@ -273,7 +273,7 @@ export class OpsViewSecurity extends DeesElement {
|
||||
title: 'Blocked Threats',
|
||||
value: metrics.blockedIPs.length + metrics.spamDetected,
|
||||
type: 'number',
|
||||
icon: 'userShield',
|
||||
icon: 'lucide:ShieldCheck',
|
||||
color: '#ef4444',
|
||||
description: 'Total threats blocked today',
|
||||
},
|
||||
@@ -282,7 +282,7 @@ export class OpsViewSecurity extends DeesElement {
|
||||
title: 'Active Sessions',
|
||||
value: 0,
|
||||
type: 'number',
|
||||
icon: 'users',
|
||||
icon: 'lucide:Users',
|
||||
color: '#22c55e',
|
||||
description: 'Current authenticated sessions',
|
||||
},
|
||||
@@ -291,7 +291,7 @@ export class OpsViewSecurity extends DeesElement {
|
||||
title: 'Auth Failures',
|
||||
value: metrics.authenticationFailures,
|
||||
type: 'number',
|
||||
icon: 'lockOpen',
|
||||
icon: 'lucide:LockOpen',
|
||||
color: metrics.authenticationFailures > 10 ? '#ef4444' : '#f59e0b',
|
||||
description: 'Failed login attempts today',
|
||||
},
|
||||
@@ -355,7 +355,7 @@ export class OpsViewSecurity extends DeesElement {
|
||||
title: 'Authentication Failures',
|
||||
value: metrics.authenticationFailures,
|
||||
type: 'number',
|
||||
icon: 'lockOpen',
|
||||
icon: 'lucide:LockOpen',
|
||||
color: metrics.authenticationFailures > 10 ? '#ef4444' : '#f59e0b',
|
||||
description: 'Failed authentication attempts today',
|
||||
},
|
||||
@@ -364,7 +364,7 @@ export class OpsViewSecurity extends DeesElement {
|
||||
title: 'Successful Logins',
|
||||
value: 0,
|
||||
type: 'number',
|
||||
icon: 'lock',
|
||||
icon: 'lucide:Lock',
|
||||
color: '#22c55e',
|
||||
description: 'Successful logins today',
|
||||
},
|
||||
@@ -399,7 +399,7 @@ export class OpsViewSecurity extends DeesElement {
|
||||
title: 'Malware Detection',
|
||||
value: metrics.malwareDetected,
|
||||
type: 'number',
|
||||
icon: 'virusSlash',
|
||||
icon: 'lucide:BugOff',
|
||||
color: metrics.malwareDetected > 0 ? '#ef4444' : '#22c55e',
|
||||
description: 'Malware detected',
|
||||
},
|
||||
@@ -408,7 +408,7 @@ export class OpsViewSecurity extends DeesElement {
|
||||
title: 'Phishing Detection',
|
||||
value: metrics.phishingDetected,
|
||||
type: 'number',
|
||||
icon: 'fishFins',
|
||||
icon: 'lucide:Fish',
|
||||
color: metrics.phishingDetected > 0 ? '#ef4444' : '#22c55e',
|
||||
description: 'Phishing attempts detected',
|
||||
},
|
||||
@@ -417,7 +417,7 @@ export class OpsViewSecurity extends DeesElement {
|
||||
title: 'Suspicious Activities',
|
||||
value: metrics.suspiciousActivities,
|
||||
type: 'number',
|
||||
icon: 'triangleExclamation',
|
||||
icon: 'lucide:TriangleAlert',
|
||||
color: metrics.suspiciousActivities > 5 ? '#ef4444' : '#f59e0b',
|
||||
description: 'Suspicious activities detected',
|
||||
},
|
||||
@@ -426,7 +426,7 @@ export class OpsViewSecurity extends DeesElement {
|
||||
title: 'Spam Detection',
|
||||
value: metrics.spamDetected,
|
||||
type: 'number',
|
||||
icon: 'ban',
|
||||
icon: 'lucide:Ban',
|
||||
color: '#f59e0b',
|
||||
description: 'Spam emails blocked',
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user