feat(ops-ui): add column filters to operations tables across admin views

This commit is contained in:
2026-04-08 07:11:21 +00:00
parent 8ab7343606
commit 37eab7c7b1
12 changed files with 21 additions and 2 deletions

View File

@@ -1,5 +1,11 @@
# Changelog # Changelog
## 2026-04-08 - 13.2.0 - feat(ops-ui)
add column filters to operations tables across admin views
- Enable table column filters for API tokens, certificates, network requests, top IPs, backends, network targets, remote ingress edges, security views, source profiles, target profiles, and VPN clients.
- Improves filtering and exploration of operational data throughout the admin interface without changing backend behavior.
## 2026-04-08 - 13.1.3 - fix(certificate-handler) ## 2026-04-08 - 13.1.3 - fix(certificate-handler)
preserve wildcard coverage during forced certificate renewals and propagate renewed certs to sibling domains preserve wildcard coverage during forced certificate renewals and propagate renewed certs to sibling domains

View File

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

View File

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

View File

@@ -109,6 +109,7 @@ export class OpsViewApiTokens extends DeesElement {
.data=${apiTokens} .data=${apiTokens}
.dataName=${'token'} .dataName=${'token'}
.searchable=${true} .searchable=${true}
.showColumnFilters=${true}
.displayFunction=${(token: interfaces.data.IApiTokenInfo) => ({ .displayFunction=${(token: interfaces.data.IApiTokenInfo) => ({
name: token.name, name: token.name,
scopes: this.renderScopePills(token.scopes), scopes: this.renderScopePills(token.scopes),

View File

@@ -228,6 +228,7 @@ export class OpsViewCertificates extends DeesElement {
return html` return html`
<dees-table <dees-table
.data=${this.certState.certificates} .data=${this.certState.certificates}
.showColumnFilters=${true}
.displayFunction=${(cert: interfaces.requests.ICertificateInfo) => ({ .displayFunction=${(cert: interfaces.requests.ICertificateInfo) => ({
Domain: cert.domain, Domain: cert.domain,
Routes: this.renderRoutePills(cert.routeNames), Routes: this.renderRoutePills(cert.routeNames),

View File

@@ -323,6 +323,7 @@ export class OpsViewNetwork extends DeesElement {
<!-- Requests Table --> <!-- Requests Table -->
<dees-table <dees-table
.data=${this.networkRequests} .data=${this.networkRequests}
.showColumnFilters=${true}
.displayFunction=${(req: INetworkRequest) => ({ .displayFunction=${(req: INetworkRequest) => ({
Time: new Date(req.timestamp).toLocaleTimeString(), Time: new Date(req.timestamp).toLocaleTimeString(),
Protocol: html`<span class="protocolBadge ${req.protocol}">${req.protocol.toUpperCase()}</span>`, Protocol: html`<span class="protocolBadge ${req.protocol}">${req.protocol.toUpperCase()}</span>`,
@@ -603,6 +604,7 @@ export class OpsViewNetwork extends DeesElement {
return html` return html`
<dees-table <dees-table
.data=${this.networkState.topIPs} .data=${this.networkState.topIPs}
.showColumnFilters=${true}
.displayFunction=${(ipData: { ip: string; count: number }) => { .displayFunction=${(ipData: { ip: string; count: number }) => {
const bw = bandwidthByIP.get(ipData.ip); const bw = bandwidthByIP.get(ipData.ip);
return { return {
@@ -630,6 +632,7 @@ export class OpsViewNetwork extends DeesElement {
return html` return html`
<dees-table <dees-table
.data=${backends} .data=${backends}
.showColumnFilters=${true}
.displayFunction=${(item: interfaces.data.IBackendInfo) => { .displayFunction=${(item: interfaces.data.IBackendInfo) => {
const totalErrors = item.connectErrors + item.handshakeErrors + item.requestErrors; const totalErrors = item.connectErrors + item.handshakeErrors + item.requestErrors;
const protocolClass = item.protocol.toLowerCase().replace(/[^a-z0-9]/g, ''); const protocolClass = item.protocol.toLowerCase().replace(/[^a-z0-9]/g, '');

View File

@@ -71,6 +71,7 @@ export class OpsViewNetworkTargets extends DeesElement {
.heading1=${'Network Targets'} .heading1=${'Network Targets'}
.heading2=${'Reusable host:port destinations for routes'} .heading2=${'Reusable host:port destinations for routes'}
.data=${targets} .data=${targets}
.showColumnFilters=${true}
.displayFunction=${(target: interfaces.data.INetworkTarget) => ({ .displayFunction=${(target: interfaces.data.INetworkTarget) => ({
Name: target.name, Name: target.name,
Host: Array.isArray(target.host) ? target.host.join(', ') : target.host, Host: Array.isArray(target.host) ? target.host.join(', ') : target.host,

View File

@@ -220,6 +220,7 @@ export class OpsViewRemoteIngress extends DeesElement {
.heading1=${'Edge Nodes'} .heading1=${'Edge Nodes'}
.heading2=${'Manage remote ingress edge registrations'} .heading2=${'Manage remote ingress edge registrations'}
.data=${this.riState.edges} .data=${this.riState.edges}
.showColumnFilters=${true}
.displayFunction=${(edge: interfaces.data.IRemoteIngress) => ({ .displayFunction=${(edge: interfaces.data.IRemoteIngress) => ({
name: edge.name, name: edge.name,
status: this.getEdgeStatusHtml(edge), status: this.getEdgeStatusHtml(edge),

View File

@@ -206,6 +206,7 @@ export class OpsViewSecurity extends DeesElement {
.heading1=${'Security Events'} .heading1=${'Security Events'}
.heading2=${'Last 24 hours'} .heading2=${'Last 24 hours'}
.data=${this.getSecurityEvents(metrics)} .data=${this.getSecurityEvents(metrics)}
.showColumnFilters=${true}
.displayFunction=${(item) => ({ .displayFunction=${(item) => ({
'Time': new Date(item.timestamp).toLocaleTimeString(), 'Time': new Date(item.timestamp).toLocaleTimeString(),
'Event': item.event, 'Event': item.event,
@@ -241,6 +242,7 @@ export class OpsViewSecurity extends DeesElement {
.heading1=${'Blocked IP Addresses'} .heading1=${'Blocked IP Addresses'}
.heading2=${'IPs blocked due to suspicious activity'} .heading2=${'IPs blocked due to suspicious activity'}
.data=${blockedIPs.map((ip) => ({ ip }))} .data=${blockedIPs.map((ip) => ({ ip }))}
.showColumnFilters=${true}
.displayFunction=${(item) => ({ .displayFunction=${(item) => ({
'IP Address': item.ip, 'IP Address': item.ip,
'Reason': 'Suspicious activity', 'Reason': 'Suspicious activity',
@@ -314,6 +316,7 @@ export class OpsViewSecurity extends DeesElement {
.heading1=${'Login History'} .heading1=${'Login History'}
.heading2=${'Recent authentication attempts'} .heading2=${'Recent authentication attempts'}
.data=${loginHistory} .data=${loginHistory}
.showColumnFilters=${true}
.displayFunction=${(item) => ({ .displayFunction=${(item) => ({
'Time': new Date(item.timestamp).toLocaleString(), 'Time': new Date(item.timestamp).toLocaleString(),
'Username': item.username, 'Username': item.username,

View File

@@ -71,6 +71,7 @@ export class OpsViewSourceProfiles extends DeesElement {
.heading1=${'Source Profiles'} .heading1=${'Source Profiles'}
.heading2=${'Reusable source configurations for routes'} .heading2=${'Reusable source configurations for routes'}
.data=${profiles} .data=${profiles}
.showColumnFilters=${true}
.displayFunction=${(profile: interfaces.data.ISourceProfile) => ({ .displayFunction=${(profile: interfaces.data.ISourceProfile) => ({
Name: profile.name, Name: profile.name,
Description: profile.description || '-', Description: profile.description || '-',

View File

@@ -84,6 +84,7 @@ export class OpsViewTargetProfiles extends DeesElement {
.heading1=${'Target Profiles'} .heading1=${'Target Profiles'}
.heading2=${'Define what resources VPN clients can access'} .heading2=${'Define what resources VPN clients can access'}
.data=${profiles} .data=${profiles}
.showColumnFilters=${true}
.displayFunction=${(profile: interfaces.data.ITargetProfile) => ({ .displayFunction=${(profile: interfaces.data.ITargetProfile) => ({
Name: profile.name, Name: profile.name,
Description: profile.description || '-', Description: profile.description || '-',

View File

@@ -305,6 +305,7 @@ export class OpsViewVpn extends DeesElement {
.heading1=${'VPN Clients'} .heading1=${'VPN Clients'}
.heading2=${'Manage WireGuard and SmartVPN client registrations'} .heading2=${'Manage WireGuard and SmartVPN client registrations'}
.data=${clients} .data=${clients}
.showColumnFilters=${true}
.displayFunction=${(client: interfaces.data.IVpnClient) => { .displayFunction=${(client: interfaces.data.IVpnClient) => {
const conn = this.getConnectedInfo(client); const conn = this.getConnectedInfo(client);
let statusHtml; let statusHtml;