feat(opsserver): add health, audit, cluster health, and durable credential management hardening

This commit is contained in:
2026-04-30 07:10:21 +00:00
parent c3e5cabe3d
commit f4e5f02d0c
34 changed files with 1722 additions and 320 deletions
+132 -43
View File
@@ -3,12 +3,12 @@ import * as appstate from '../appstate.js';
import * as shared from './shared/index.js';
import {
DeesElement,
customElement,
html,
state,
css,
cssManager,
customElement,
DeesElement,
html,
state,
type TemplateResult,
} from '@design.estate/dees-element';
import { type IStatsTile } from '@design.estate/dees-catalog';
@@ -16,7 +16,7 @@ import { type IStatsTile } from '@design.estate/dees-catalog';
@customElement('objst-view-config')
export class ObjstViewConfig extends DeesElement {
@state()
accessor configState: appstate.IConfigState = { config: null };
accessor configState: appstate.IConfigState = { config: null, clusterHealth: null };
constructor() {
super();
@@ -107,11 +107,26 @@ export class ObjstViewConfig extends DeesElement {
border-radius: 4px;
background: ${cssManager.bdTheme('#e8e8e8', '#252540')};
}
.driveList .driveStatus {
margin-left: 12px;
padding: 4px 8px;
border-radius: 999px;
font-size: 12px;
font-weight: 600;
color: ${cssManager.bdTheme('#1b5e20', '#a5d6a7')};
background: ${cssManager.bdTheme('#e8f5e9', '#1b5e2030')};
}
.driveList .driveMeta {
margin-left: 12px;
color: ${cssManager.bdTheme('#666', '#999')};
font-size: 13px;
}
`,
];
public render(): TemplateResult {
const config = this.configState.config;
const clusterHealth = this.configState.clusterHealth;
const serverTiles: IStatsTile[] = [
{
@@ -164,19 +179,20 @@ export class ObjstViewConfig extends DeesElement {
},
];
const clusterEnabled = clusterHealth?.enabled ?? config?.clusterEnabled ?? false;
const clusterTiles: IStatsTile[] = [
{
id: 'clusterStatus',
title: 'Cluster Status',
value: config?.clusterEnabled ? 'Enabled' : 'Disabled',
value: clusterEnabled ? 'Enabled' : 'Disabled',
type: 'text',
icon: 'lucide:network',
color: config?.clusterEnabled ? '#4caf50' : '#ff9800',
color: clusterEnabled ? '#4caf50' : '#ff9800',
},
{
id: 'nodeId',
title: 'Node ID',
value: config?.clusterNodeId || '(auto)',
value: clusterHealth?.nodeId || config?.clusterNodeId || '(auto)',
type: 'text',
icon: 'lucide:fingerprint',
color: '#607d8b',
@@ -216,13 +232,31 @@ export class ObjstViewConfig extends DeesElement {
icon: 'lucide:timer',
color: '#ff5722',
},
{
id: 'quorum',
title: 'Quorum',
value: clusterHealth?.enabled
? clusterHealth.quorumHealthy ? 'Healthy' : 'Degraded'
: 'Standalone',
type: 'text',
icon: 'lucide:activity',
color: clusterHealth?.quorumHealthy ? '#4caf50' : '#ff9800',
},
{
id: 'peers',
title: 'Peers',
value: clusterHealth?.peers?.length ?? 0,
type: 'number',
icon: 'lucide:share2',
color: '#3f51b5',
},
];
const erasureTiles: IStatsTile[] = [
{
id: 'dataShards',
title: 'Data Shards',
value: config?.erasureDataShards ?? 4,
value: clusterHealth?.erasure?.dataShards ?? config?.erasureDataShards ?? 4,
type: 'number',
icon: 'lucide:layers',
color: '#2196f3',
@@ -230,7 +264,7 @@ export class ObjstViewConfig extends DeesElement {
{
id: 'parityShards',
title: 'Parity Shards',
value: config?.erasureParityShards ?? 2,
value: clusterHealth?.erasure?.parityShards ?? config?.erasureParityShards ?? 2,
type: 'number',
icon: 'lucide:shieldCheck',
color: '#4caf50',
@@ -238,25 +272,31 @@ export class ObjstViewConfig extends DeesElement {
{
id: 'chunkSize',
title: 'Chunk Size',
value: this.formatBytes(config?.erasureChunkSizeBytes ?? 4194304),
value: this.formatBytes(
clusterHealth?.erasure?.chunkSizeBytes ?? config?.erasureChunkSizeBytes ?? 4194304,
),
type: 'text',
icon: 'lucide:puzzle',
color: '#9c27b0',
description: `${config?.erasureDataShards ?? 4}+${config?.erasureParityShards ?? 2} = ${Math.round(((config?.erasureParityShards ?? 2) / (config?.erasureDataShards ?? 4)) * 100)}% overhead`,
description: `${clusterHealth?.erasure?.dataShards ?? config?.erasureDataShards ?? 4}+${
clusterHealth?.erasure?.parityShards ?? config?.erasureParityShards ?? 2
}`,
},
];
const drivePaths = config?.drivePaths?.length
const drivePaths = clusterHealth?.drives?.length
? clusterHealth.drives.map((drive) => drive.path)
: config?.drivePaths?.length
? config.drivePaths
: config?.storageDirectory
? [config.storageDirectory]
: ['/data'];
? [config.storageDirectory]
: ['/data'];
const driveTiles: IStatsTile[] = [
{
id: 'driveCount',
title: 'Drive Count',
value: drivePaths.length,
value: clusterHealth?.drives?.length ?? drivePaths.length,
type: 'number',
icon: 'lucide:hardDrive',
color: '#3f51b5',
@@ -274,48 +314,97 @@ export class ObjstViewConfig extends DeesElement {
return html`
<objst-sectionheading>Server Configuration</objst-sectionheading>
<dees-statsgrid
.tiles=${serverTiles}
.gridActions=${[refreshAction]}
.tiles="${serverTiles}"
.gridActions="${[refreshAction]}"
></dees-statsgrid>
<div class="sectionSpacer">
<objst-sectionheading>Cluster Configuration</objst-sectionheading>
</div>
<dees-statsgrid .tiles=${clusterTiles}></dees-statsgrid>
<dees-statsgrid .tiles="${clusterTiles}"></dees-statsgrid>
${config?.clusterEnabled ? html`
<div class="sectionSpacer">
<objst-sectionheading>Erasure Coding</objst-sectionheading>
</div>
<dees-statsgrid .tiles=${erasureTiles}></dees-statsgrid>
` : ''}
${clusterEnabled
? html`
<div class="sectionSpacer">
<objst-sectionheading>Erasure Coding</objst-sectionheading>
</div>
<dees-statsgrid .tiles="${erasureTiles}"></dees-statsgrid>
`
: ''}
<div class="sectionSpacer">
<objst-sectionheading>Storage Drives</objst-sectionheading>
</div>
<dees-statsgrid .tiles=${driveTiles}></dees-statsgrid>
<dees-statsgrid .tiles="${driveTiles}"></dees-statsgrid>
<div class="driveList">
${drivePaths.map((path, i) => html`
<div class="driveItem">
<div class="driveIndex">${i + 1}</div>
<span class="drivePath">${path}</span>
</div>
`)}
${(clusterHealth?.drives?.length
? clusterHealth.drives
: drivePaths.map((path, index) => ({ path, index, status: 'configured' }))).map((
drive,
i,
) =>
html`
<div class="driveItem">
<div class="driveIndex">${i + 1}</div>
<span class="drivePath">${drive.path}</span>
<span class="driveStatus">${drive.status}</span>
${drive.usedBytes !== undefined && drive.totalBytes !== undefined
? html`
<span class="driveMeta">${this.formatBytes(drive.usedBytes)} / ${this
.formatBytes(drive.totalBytes)}</span>
`
: ''}
</div>
`
)}
</div>
<div class="infoPanel">
<h2>Configuration Reference</h2>
<p>Cluster and drive settings are applied at server startup. To change them, set the environment variables and restart the server.</p>
<div class="row"><span class="label">OBJST_CLUSTER_ENABLED</span><span class="value">Enable clustering (true/false)</span></div>
<div class="row"><span class="label">OBJST_CLUSTER_NODE_ID</span><span class="value">Unique node identifier</span></div>
<div class="row"><span class="label">OBJST_CLUSTER_QUIC_PORT</span><span class="value">QUIC transport port (default: 4433)</span></div>
<div class="row"><span class="label">OBJST_CLUSTER_SEED_NODES</span><span class="value">Comma-separated seed node addresses</span></div>
<div class="row"><span class="label">OBJST_DRIVE_PATHS</span><span class="value">Comma-separated drive mount paths</span></div>
<div class="row"><span class="label">OBJST_ERASURE_DATA_SHARDS</span><span class="value">Data shards for erasure coding (default: 4)</span></div>
<div class="row"><span class="label">OBJST_ERASURE_PARITY_SHARDS</span><span class="value">Parity shards for erasure coding (default: 2)</span></div>
<div class="row"><span class="label">OBJST_ERASURE_CHUNK_SIZE</span><span class="value">Chunk size in bytes (default: 4194304)</span></div>
<div class="row"><span class="label">OBJST_HEARTBEAT_INTERVAL_MS</span><span class="value">Heartbeat interval in ms (default: 5000)</span></div>
<div class="row"><span class="label">OBJST_HEARTBEAT_TIMEOUT_MS</span><span class="value">Heartbeat timeout in ms (default: 30000)</span></div>
<p>
Cluster and drive settings are applied at server startup. To change them, set the environment
variables and restart the server.
</p>
<div class="row">
<span class="label">OBJST_CLUSTER_ENABLED</span><span class="value"
>Enable clustering (true/false)</span>
</div>
<div class="row">
<span class="label">OBJST_CLUSTER_NODE_ID</span><span class="value"
>Unique node identifier</span>
</div>
<div class="row">
<span class="label">OBJST_CLUSTER_QUIC_PORT</span><span class="value"
>QUIC transport port (default: 4433)</span>
</div>
<div class="row">
<span class="label">OBJST_CLUSTER_SEED_NODES</span><span class="value"
>Comma-separated seed node addresses</span>
</div>
<div class="row">
<span class="label">OBJST_DRIVE_PATHS</span><span class="value"
>Comma-separated drive mount paths</span>
</div>
<div class="row">
<span class="label">OBJST_ERASURE_DATA_SHARDS</span><span class="value"
>Data shards for erasure coding (default: 4)</span>
</div>
<div class="row">
<span class="label">OBJST_ERASURE_PARITY_SHARDS</span><span class="value"
>Parity shards for erasure coding (default: 2)</span>
</div>
<div class="row">
<span class="label">OBJST_ERASURE_CHUNK_SIZE</span><span class="value"
>Chunk size in bytes (default: 4194304)</span>
</div>
<div class="row">
<span class="label">OBJST_HEARTBEAT_INTERVAL_MS</span><span class="value"
>Heartbeat interval in ms (default: 5000)</span>
</div>
<div class="row">
<span class="label">OBJST_HEARTBEAT_TIMEOUT_MS</span><span class="value"
>Heartbeat timeout in ms (default: 30000)</span>
</div>
</div>
`;
}