import * as plugins from '../plugins.js'; import * as appstate from '../appstate.js'; import * as shared from './shared/index.js'; import { css, cssManager, customElement, DeesElement, html, state, type TemplateResult, } from '@design.estate/dees-element'; import { type IStatsTile } from '@design.estate/dees-catalog'; @customElement('objst-view-config') export class ObjstViewConfig extends DeesElement { @state() accessor configState: appstate.IConfigState = { config: null, clusterHealth: null }; constructor() { super(); const sub = appstate.configStatePart .select((s) => s) .subscribe((configState) => { this.configState = configState; }); this.rxSubscriptions.push(sub); } async connectedCallback() { super.connectedCallback(); appstate.configStatePart.dispatchAction(appstate.fetchConfigAction, null); } public static styles = [ cssManager.defaultStyles, shared.viewHostCss, css` .sectionSpacer { margin-top: 32px; } .infoPanel { margin-top: 32px; padding: 24px; border-radius: 8px; background: ${cssManager.bdTheme('#f5f5f5', '#1a1a2e')}; border: 1px solid ${cssManager.bdTheme('#e0e0e0', '#2a2a4a')}; } .infoPanel h2 { margin: 0 0 16px 0; font-size: 18px; font-weight: 600; color: ${cssManager.bdTheme('#333', '#ccc')}; } .infoPanel p { margin: 0 0 16px 0; font-size: 14px; color: ${cssManager.bdTheme('#666', '#999')}; line-height: 1.5; } .infoPanel .row { display: flex; align-items: center; margin-bottom: 8px; font-size: 14px; } .infoPanel .label { min-width: 260px; font-family: monospace; font-weight: 500; color: ${cssManager.bdTheme('#1565c0', '#64b5f6')}; padding: 4px 8px; border-radius: 4px; background: ${cssManager.bdTheme('#e3f2fd', '#1a237e30')}; } .infoPanel .value { color: ${cssManager.bdTheme('#666', '#999')}; margin-left: 12px; } .driveList { margin-top: 16px; } .driveList .driveItem { display: flex; align-items: center; margin-bottom: 8px; font-size: 14px; } .driveList .driveIndex { width: 32px; height: 32px; border-radius: 6px; background: ${cssManager.bdTheme('#e8eaf6', '#1a237e40')}; color: ${cssManager.bdTheme('#3f51b5', '#7986cb')}; display: flex; align-items: center; justify-content: center; font-weight: 600; font-size: 13px; margin-right: 12px; } .driveList .drivePath { font-family: monospace; color: ${cssManager.bdTheme('#333', '#e0e0e0')}; padding: 6px 12px; 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[] = [ { id: 'objstPort', title: 'Storage API Port', value: config?.objstPort ?? '--', type: 'number', icon: 'lucide:network', color: '#2196f3', }, { id: 'uiPort', title: 'UI Port', value: config?.uiPort ?? '--', type: 'number', icon: 'lucide:monitor', color: '#00bcd4', }, { id: 'region', title: 'Region', value: config?.region ?? '--', type: 'text', icon: 'lucide:globe', color: '#607d8b', }, { id: 'storageDir', title: 'Storage Directory', value: config?.storageDirectory ?? '--', type: 'text', icon: 'lucide:hardDrive', color: '#9c27b0', }, { id: 'auth', title: 'Authentication', value: config?.authEnabled ? 'Enabled' : 'Disabled', type: 'text', icon: 'lucide:shield', color: config?.authEnabled ? '#4caf50' : '#f44336', }, { id: 'cors', title: 'CORS', value: config?.corsEnabled ? 'Enabled' : 'Disabled', type: 'text', icon: 'lucide:globe2', color: config?.corsEnabled ? '#4caf50' : '#ff9800', }, ]; const clusterEnabled = clusterHealth?.enabled ?? config?.clusterEnabled ?? false; const clusterTiles: IStatsTile[] = [ { id: 'clusterStatus', title: 'Cluster Status', value: clusterEnabled ? 'Enabled' : 'Disabled', type: 'text', icon: 'lucide:network', color: clusterEnabled ? '#4caf50' : '#ff9800', }, { id: 'nodeId', title: 'Node ID', value: clusterHealth?.nodeId || config?.clusterNodeId || '(auto)', type: 'text', icon: 'lucide:fingerprint', color: '#607d8b', }, { id: 'quicPort', title: 'QUIC Port', value: config?.clusterQuicPort ?? 4433, type: 'number', icon: 'lucide:radio', color: '#00bcd4', }, { id: 'seedNodes', title: 'Seed Nodes', value: config?.clusterSeedNodes?.length ?? 0, type: 'number', icon: 'lucide:gitBranch', color: '#3f51b5', description: config?.clusterSeedNodes?.length ? config.clusterSeedNodes.join(', ') : 'No seed nodes configured', }, { id: 'heartbeatInterval', title: 'Heartbeat Interval', value: `${config?.clusterHeartbeatIntervalMs ?? 5000}ms`, type: 'text', icon: 'lucide:heartPulse', color: '#e91e63', }, { id: 'heartbeatTimeout', title: 'Heartbeat Timeout', value: `${config?.clusterHeartbeatTimeoutMs ?? 30000}ms`, type: 'text', 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: clusterHealth?.erasure?.dataShards ?? config?.erasureDataShards ?? 4, type: 'number', icon: 'lucide:layers', color: '#2196f3', }, { id: 'parityShards', title: 'Parity Shards', value: clusterHealth?.erasure?.parityShards ?? config?.erasureParityShards ?? 2, type: 'number', icon: 'lucide:shieldCheck', color: '#4caf50', }, { id: 'chunkSize', title: 'Chunk Size', value: this.formatBytes( clusterHealth?.erasure?.chunkSizeBytes ?? config?.erasureChunkSizeBytes ?? 4194304, ), type: 'text', icon: 'lucide:puzzle', color: '#9c27b0', description: `${clusterHealth?.erasure?.dataShards ?? config?.erasureDataShards ?? 4}+${ clusterHealth?.erasure?.parityShards ?? config?.erasureParityShards ?? 2 }`, }, ]; const drivePaths = clusterHealth?.drives?.length ? clusterHealth.drives.map((drive) => drive.path) : config?.drivePaths?.length ? config.drivePaths : config?.storageDirectory ? [config.storageDirectory] : ['/data']; const driveTiles: IStatsTile[] = [ { id: 'driveCount', title: 'Drive Count', value: clusterHealth?.drives?.length ?? drivePaths.length, type: 'number', icon: 'lucide:hardDrive', color: '#3f51b5', }, ]; const refreshAction = { name: 'Refresh', iconName: 'lucide:refreshCw', action: async () => { await appstate.configStatePart.dispatchAction(appstate.fetchConfigAction, null); }, }; return html` Server Configuration
Cluster Configuration
${clusterEnabled ? html`
Erasure Coding
` : ''}
Storage Drives
${(clusterHealth?.drives?.length ? clusterHealth.drives : drivePaths.map((path, index) => ({ path, index, status: 'configured' }))).map(( drive, i, ) => html`
${i + 1}
${drive.path} ${drive.status} ${drive.usedBytes !== undefined && drive.totalBytes !== undefined ? html` ${this.formatBytes(drive.usedBytes)} / ${this .formatBytes(drive.totalBytes)} ` : ''}
` )}

Configuration Reference

Cluster and drive settings are applied at server startup. To change them, set the environment variables and restart the server.

OBJST_CLUSTER_ENABLEDEnable clustering (true/false)
OBJST_CLUSTER_NODE_IDUnique node identifier
OBJST_CLUSTER_QUIC_PORTQUIC transport port (default: 4433)
OBJST_CLUSTER_SEED_NODESComma-separated seed node addresses
OBJST_DRIVE_PATHSComma-separated drive mount paths
OBJST_ERASURE_DATA_SHARDSData shards for erasure coding (default: 4)
OBJST_ERASURE_PARITY_SHARDSParity shards for erasure coding (default: 2)
OBJST_ERASURE_CHUNK_SIZEChunk size in bytes (default: 4194304)
OBJST_HEARTBEAT_INTERVAL_MSHeartbeat interval in ms (default: 5000)
OBJST_HEARTBEAT_TIMEOUT_MSHeartbeat timeout in ms (default: 30000)
`; } private formatBytes(bytes: number): string { if (bytes === 0) return '0 B'; const units = ['B', 'KB', 'MB', 'GB', 'TB']; const i = Math.floor(Math.log(bytes) / Math.log(1024)); return `${(bytes / Math.pow(1024, i)).toFixed(i === 0 ? 0 : 1)} ${units[i]}`; } }