RSS Git Download  Clone
Raw Blame History 33kB 666 lines
HTML rendered
@if (!current) {
    <div class="p3xr-monitoring-loading">
        <mat-icon>hourglass_empty</mat-icon>
        <span>{{ strings().label?.loading || 'Loading...' }}</span>
    </div>
}

@if (current) {
    <!-- Overview -->
    <p3xr-ng-accordion [title]="strings().page?.monitor?.title || 'Monitoring'" accordionKey="monitor-overview">
        <div actions>
            <p3xr-ng-button
                (click)="togglePause(); $event.stopPropagation()"
                [label]="paused ? (strings().intention?.resume || 'Resume') : (strings().intention?.pause || 'Pause')"
                [mdIcon]="paused ? 'play_arrow' : 'pause'">
            </p3xr-ng-button>
            <p3xr-ng-button
                (click)="exportOverview(); $event.stopPropagation()"
                [label]="strings().intention?.export || 'Export'"
                mdIcon="download">
            </p3xr-ng-button>
            <p3xr-ng-button
                (click)="exportAll(); $event.stopPropagation()"
                [label]="strings().page?.analysis?.exportAll || 'Export All'"
                mdIcon="archive">
            </p3xr-ng-button>
        </div>
        <div content>
            <mat-list>
                <mat-list-item>
                    <div class="p3xr-settings-pair-row">
                        <div class="p3xr-settings-row-label">Redis {{ current.server.version }} · {{ current.server.mode }}</div>
                        <div class="p3xr-settings-row-value p3xr-mono">{{ uptimeFormatted }}</div>
                    </div>
                </mat-list-item>
                <mat-divider></mat-divider>
                <mat-list-item>
                    <div class="p3xr-settings-pair-row">
                        <div class="p3xr-settings-row-label">{{ strings().page?.monitor?.memory || 'Memory' }}</div>
                        <div class="p3xr-settings-row-value p3xr-mono">{{ current.memory.usedHuman }}</div>
                    </div>
                </mat-list-item>
                <mat-divider></mat-divider>
                <mat-list-item>
                    <div class="p3xr-settings-pair-row">
                        <div class="p3xr-settings-row-label">{{ strings().page?.monitor?.rss || 'RSS' }}</div>
                        <div class="p3xr-settings-row-value p3xr-mono">{{ current.memory.rssHuman }}</div>
                    </div>
                </mat-list-item>
                <mat-divider></mat-divider>
                <mat-list-item>
                    <div class="p3xr-settings-pair-row">
                        <div class="p3xr-settings-row-label">{{ strings().page?.monitor?.peak || 'Peak' }}</div>
                        <div class="p3xr-settings-row-value p3xr-mono">{{ current.memory.peakHuman }}</div>
                    </div>
                </mat-list-item>
                <mat-divider></mat-divider>
                <mat-list-item>
                    <div class="p3xr-settings-pair-row">
                        <div class="p3xr-settings-row-label">{{ strings().page?.monitor?.fragmentation || 'Fragmentation' }}</div>
                        <div class="p3xr-settings-row-value p3xr-mono">{{ current.memory.fragRatio }}x</div>
                    </div>
                </mat-list-item>
                <mat-divider></mat-divider>
                <mat-list-item>
                    <div class="p3xr-settings-pair-row">
                        <div class="p3xr-settings-row-label">{{ strings().page?.monitor?.opsPerSec || 'Ops/sec' }}</div>
                        <div class="p3xr-settings-row-value p3xr-mono">{{ current.stats.opsPerSec }}</div>
                    </div>
                </mat-list-item>
                <mat-divider></mat-divider>
                <mat-list-item>
                    <div class="p3xr-settings-pair-row">
                        <div class="p3xr-settings-row-label">{{ strings().page?.monitor?.totalCommands || 'Total Commands' }}</div>
                        <div class="p3xr-settings-row-value p3xr-mono">{{ current.stats.totalCommands }}</div>
                    </div>
                </mat-list-item>
                <mat-divider></mat-divider>
                <mat-list-item>
                    <div class="p3xr-settings-pair-row">
                        <div class="p3xr-settings-row-label">{{ strings().page?.monitor?.clients || 'Clients' }}</div>
                        <div class="p3xr-settings-row-value p3xr-mono">{{ current.clients.connected }}</div>
                    </div>
                </mat-list-item>
                <mat-divider></mat-divider>
                <mat-list-item>
                    <div class="p3xr-settings-pair-row">
                        <div class="p3xr-settings-row-label">{{ strings().page?.monitor?.blocked || 'Blocked' }}</div>
                        <div class="p3xr-settings-row-value p3xr-mono">{{ current.clients.blocked }}</div>
                    </div>
                </mat-list-item>
                <mat-divider></mat-divider>
                <mat-list-item>
                    <div class="p3xr-settings-pair-row">
                        <div class="p3xr-settings-row-label">{{ strings().page?.monitor?.hitsMisses || 'Hit Rate' }}</div>
                        <div class="p3xr-settings-row-value p3xr-mono">{{ current.stats.hitRate }}%</div>
                    </div>
                </mat-list-item>
                <mat-divider></mat-divider>
                <mat-list-item>
                    <div class="p3xr-settings-pair-row">
                        <div class="p3xr-settings-row-label">{{ strings().page?.monitor?.hitsAndMisses || 'Hits / Misses' }}</div>
                        <div class="p3xr-settings-row-value p3xr-mono">{{ current.stats.hits }} / {{ current.stats.misses }}</div>
                    </div>
                </mat-list-item>
                <mat-divider></mat-divider>
                <mat-list-item>
                    <div class="p3xr-settings-pair-row">
                        <div class="p3xr-settings-row-label">{{ strings().page?.monitor?.networkIo || 'Network I/O' }}</div>
                        <div class="p3xr-settings-row-value p3xr-mono">{{ current.stats.inputKbps | number:'1.1-1' }} / {{ current.stats.outputKbps | number:'1.1-1' }} KB/s</div>
                    </div>
                </mat-list-item>
                <mat-divider></mat-divider>
                <mat-list-item>
                    <div class="p3xr-settings-pair-row">
                        <div class="p3xr-settings-row-label">{{ strings().page?.monitor?.expired || 'Expired' }}</div>
                        <div class="p3xr-settings-row-value p3xr-mono">{{ current.stats.expiredKeys }}</div>
                    </div>
                </mat-list-item>
                <mat-divider></mat-divider>
                <mat-list-item>
                    <div class="p3xr-settings-pair-row">
                        <div class="p3xr-settings-row-label">{{ strings().page?.monitor?.evicted || 'Evicted' }}</div>
                        <div class="p3xr-settings-row-value p3xr-mono">{{ current.stats.evictedKeys }}</div>
                    </div>
                </mat-list-item>
            </mat-list>
        </div>
    </p3xr-ng-accordion>

    <!-- Server Info -->
    @if (serverInfo) {
        <br />
        <p3xr-ng-accordion [title]="strings().page?.monitor?.serverInfo || 'Server Info'" accordionKey="monitor-server-info">
            <div actions>
                <p3xr-ng-button
                    (click)="exportServerInfo(); $event.stopPropagation()"
                    [label]="strings().intention?.export || 'Export'"
                    mdIcon="download">
                </p3xr-ng-button>
            </div>
            <div content>
                <mat-list>
                    @if (serverInfo.os) {
                        <mat-list-item>
                            <div class="p3xr-settings-pair-row">
                                <div class="p3xr-settings-row-label">{{ strings().page?.monitor?.os || 'OS' }}</div>
                                <div class="p3xr-settings-row-value p3xr-mono">{{ serverInfo.os }}</div>
                            </div>
                        </mat-list-item>
                        <mat-divider></mat-divider>
                    }
                    @if (serverInfo.port) {
                        <mat-list-item>
                            <div class="p3xr-settings-pair-row">
                                <div class="p3xr-settings-row-label">{{ strings().page?.monitor?.port || 'Port' }}</div>
                                <div class="p3xr-settings-row-value p3xr-mono">{{ serverInfo.port }}</div>
                            </div>
                        </mat-list-item>
                        <mat-divider></mat-divider>
                    }
                    @if (serverInfo.pid) {
                        <mat-list-item>
                            <div class="p3xr-settings-pair-row">
                                <div class="p3xr-settings-row-label">{{ strings().page?.monitor?.pid || 'Process ID' }}</div>
                                <div class="p3xr-settings-row-value p3xr-mono">{{ serverInfo.pid }}</div>
                            </div>
                        </mat-list-item>
                        <mat-divider></mat-divider>
                    }
                    @if (serverInfo.configFile) {
                        <mat-list-item>
                            <div class="p3xr-settings-pair-row">
                                <div class="p3xr-settings-row-label">{{ strings().page?.monitor?.configFile || 'Config File' }}</div>
                                <div class="p3xr-settings-row-value p3xr-mono">{{ serverInfo.configFile }}</div>
                            </div>
                        </mat-list-item>
                        <mat-divider></mat-divider>
                    }
                    <mat-list-item>
                        <div class="p3xr-settings-pair-row">
                            <div class="p3xr-settings-row-label">{{ strings().page?.monitor?.cpuSys || 'System' }} CPU</div>
                            <div class="p3xr-settings-row-value p3xr-mono">{{ serverInfo.cpuSys }}</div>
                        </div>
                    </mat-list-item>
                    <mat-divider></mat-divider>
                    <mat-list-item>
                        <div class="p3xr-settings-pair-row">
                            <div class="p3xr-settings-row-label">{{ strings().page?.monitor?.cpuUser || 'User' }} CPU</div>
                            <div class="p3xr-settings-row-value p3xr-mono">{{ serverInfo.cpuUser }}</div>
                        </div>
                    </mat-list-item>
                </mat-list>
            </div>
        </p3xr-ng-accordion>
    }

    <!-- Persistence & Replication -->
    @if (persistenceInfo) {
        <br />
        <p3xr-ng-accordion [title]="strings().page?.monitor?.persistence || 'Persistence'" accordionKey="monitor-persistence">
            <div actions>
                <p3xr-ng-button
                    (click)="exportPersistence(); $event.stopPropagation()"
                    [label]="strings().intention?.export || 'Export'"
                    mdIcon="download">
                </p3xr-ng-button>
            </div>
            <div content>
                <mat-list>
                    <mat-list-item>
                        <div class="p3xr-settings-pair-row">
                            <div class="p3xr-settings-row-label">{{ strings().page?.monitor?.rdbLastSave || 'RDB Last Save' }}</div>
                            <div class="p3xr-settings-row-value p3xr-mono">{{ persistenceInfo.rdbLastSave }}</div>
                        </div>
                    </mat-list-item>
                    <mat-divider></mat-divider>
                    <mat-list-item>
                        <div class="p3xr-settings-pair-row">
                            <div class="p3xr-settings-row-label">{{ strings().page?.monitor?.rdbStatus || 'RDB Status' }}</div>
                            <div class="p3xr-settings-row-value p3xr-mono">{{ persistenceInfo.rdbStatus }}</div>
                        </div>
                    </mat-list-item>
                    <mat-divider></mat-divider>
                    <mat-list-item>
                        <div class="p3xr-settings-pair-row">
                            <div class="p3xr-settings-row-label">{{ strings().page?.monitor?.rdbChanges || 'Changes Since Last Save' }}</div>
                            <div class="p3xr-settings-row-value p3xr-mono">{{ persistenceInfo.rdbChanges }}</div>
                        </div>
                    </mat-list-item>
                    <mat-divider></mat-divider>
                    <mat-list-item>
                        <div class="p3xr-settings-pair-row">
                            <div class="p3xr-settings-row-label">{{ strings().page?.monitor?.aofEnabled || 'AOF Enabled' }}</div>
                            <div class="p3xr-settings-row-value p3xr-mono">{{ persistenceInfo.aofEnabled }}</div>
                        </div>
                    </mat-list-item>
                    @if (persistenceInfo.aofSize) {
                        <mat-divider></mat-divider>
                        <mat-list-item>
                            <div class="p3xr-settings-pair-row">
                                <div class="p3xr-settings-row-label">{{ strings().page?.monitor?.aofSize || 'AOF Size' }}</div>
                                <div class="p3xr-settings-row-value p3xr-mono">{{ persistenceInfo.aofSize }}</div>
                            </div>
                        </mat-list-item>
                    }
                </mat-list>
            </div>
        </p3xr-ng-accordion>
    }

    @if (replicationInfo) {
        <br />
        <p3xr-ng-accordion [title]="strings().page?.monitor?.replication || 'Replication'" accordionKey="monitor-replication">
            <div actions>
                <p3xr-ng-button
                    (click)="exportReplication(); $event.stopPropagation()"
                    [label]="strings().intention?.export || 'Export'"
                    mdIcon="download">
                </p3xr-ng-button>
            </div>
            <div content>
                <mat-list>
                    <mat-list-item>
                        <div class="p3xr-settings-pair-row">
                            <div class="p3xr-settings-row-label">{{ strings().page?.monitor?.role || 'Role' }}</div>
                            <div class="p3xr-settings-row-value p3xr-mono">{{ replicationInfo.role }}</div>
                        </div>
                    </mat-list-item>
                    @if (replicationInfo.replicas !== undefined) {
                        <mat-divider></mat-divider>
                        <mat-list-item>
                            <div class="p3xr-settings-pair-row">
                                <div class="p3xr-settings-row-label">{{ strings().page?.monitor?.replicas || 'Connected Replicas' }}</div>
                                <div class="p3xr-settings-row-value p3xr-mono">{{ replicationInfo.replicas }}</div>
                            </div>
                        </mat-list-item>
                    }
                    @if (replicationInfo.masterHost) {
                        <mat-divider></mat-divider>
                        <mat-list-item>
                            <div class="p3xr-settings-pair-row">
                                <div class="p3xr-settings-row-label">{{ strings().page?.monitor?.masterHost || 'Master Host' }}</div>
                                <div class="p3xr-settings-row-value p3xr-mono">{{ replicationInfo.masterHost }}:{{ replicationInfo.masterPort }}</div>
                            </div>
                        </mat-list-item>
                    }
                    @if (replicationInfo.linkStatus) {
                        <mat-divider></mat-divider>
                        <mat-list-item>
                            <div class="p3xr-settings-pair-row">
                                <div class="p3xr-settings-row-label">{{ strings().page?.monitor?.linkStatus || 'Link Status' }}</div>
                                <div class="p3xr-settings-row-value p3xr-mono">{{ replicationInfo.linkStatus }}</div>
                            </div>
                        </mat-list-item>
                    }
                </mat-list>
            </div>
        </p3xr-ng-accordion>
    }

    <!-- Keyspace -->
    @if (keyspaceEntries.length > 0) {
        <br />
        <p3xr-ng-accordion [title]="strings().page?.monitor?.keyspace || 'Keyspace'" accordionKey="monitor-keyspace">
            <div actions>
                <p3xr-ng-button
                    (click)="exportKeyspace(); $event.stopPropagation()"
                    [label]="strings().intention?.export || 'Export'"
                    mdIcon="download">
                </p3xr-ng-button>
            </div>
            <div content>
                <mat-list>
                    @for (entry of keyspaceEntries; track entry.db; let last = $last) {
                        <mat-list-item>
                            <div class="p3xr-settings-pair-row">
                                <div class="p3xr-settings-row-label">{{ entry.db }}</div>
                                <div class="p3xr-settings-row-value p3xr-mono">{{ strings().page?.monitor?.keys || 'Keys' }}: {{ entry.keys }} · {{ strings().page?.monitor?.expires || 'Expires' }}: {{ entry.expires }}</div>
                            </div>
                        </mat-list-item>
                        @if (!last) { <mat-divider></mat-divider> }
                    }
                </mat-list>
            </div>
        </p3xr-ng-accordion>
    }

    <!-- Modules -->
    <br />
    <p3xr-ng-accordion [title]="strings().page?.monitor?.modules || 'Loaded Modules'" accordionKey="monitor-modules">
        <div actions>
            <p3xr-ng-button
                (click)="exportModules(); $event.stopPropagation()"
                [label]="strings().intention?.export || 'Export'"
                mdIcon="download">
            </p3xr-ng-button>
        </div>
        <div content>
            @if (modulesList.length === 0) {
                <div style="padding: 16px; opacity: 0.5;">{{ strings().page?.monitor?.noModules || 'No modules loaded' }}</div>
            }
            @if (modulesList.length > 0) {
                <mat-list>
                    @for (mod of modulesList; track mod.name; let last = $last) {
                        <mat-list-item>
                            <div class="p3xr-settings-pair-row">
                                <div class="p3xr-settings-row-label">{{ mod.name }}</div>
                                <div class="p3xr-settings-row-value p3xr-mono">v{{ mod.ver }}</div>
                            </div>
                        </mat-list-item>
                        @if (!last) { <mat-divider></mat-divider> }
                    }
                </mat-list>
            }
        </div>
    </p3xr-ng-accordion>

    <br />

    <!-- Charts -->
    <p3xr-ng-accordion [title]="(strings().page?.monitor?.memory || 'Memory') + ' (MB)'" accordionKey="monitor-chart-memory">
        <div actions>
            <p3xr-ng-button
                (click)="exportChart(memoryChartRef, 'memory'); $event.stopPropagation()"
                [label]="strings().intention?.export || 'Export'"
                mdIcon="download">
            </p3xr-ng-button>
        </div>
        <div content>
            <div #memoryChart class="p3xr-monitoring-chart"></div>
        </div>
    </p3xr-ng-accordion>

    <br />

    <p3xr-ng-accordion [title]="strings().page?.monitor?.opsPerSec || 'Ops/sec'" accordionKey="monitor-chart-ops">
        <div actions>
            <p3xr-ng-button
                (click)="exportChart(opsChartRef, 'ops'); $event.stopPropagation()"
                [label]="strings().intention?.export || 'Export'"
                mdIcon="download">
            </p3xr-ng-button>
        </div>
        <div content>
            <div #opsChart class="p3xr-monitoring-chart"></div>
        </div>
    </p3xr-ng-accordion>

    <br />

    <p3xr-ng-accordion [title]="strings().page?.monitor?.clients || 'Clients'" accordionKey="monitor-chart-clients">
        <div actions>
            <p3xr-ng-button
                (click)="exportChart(clientsChartRef, 'clients'); $event.stopPropagation()"
                [label]="strings().intention?.export || 'Export'"
                mdIcon="download">
            </p3xr-ng-button>
        </div>
        <div content>
            <div #clientsChart class="p3xr-monitoring-chart"></div>
        </div>
    </p3xr-ng-accordion>

    <br />

    <p3xr-ng-accordion [title]="(strings().page?.monitor?.networkIo || 'Network I/O') + ' (KB/s)'" accordionKey="monitor-chart-network">
        <div actions>
            <p3xr-ng-button
                (click)="exportChart(networkChartRef, 'network'); $event.stopPropagation()"
                [label]="strings().intention?.export || 'Export'"
                mdIcon="download">
            </p3xr-ng-button>
        </div>
        <div content>
            <div #networkChart class="p3xr-monitoring-chart"></div>
        </div>
    </p3xr-ng-accordion>

    <!-- Slow Log -->
    <br />
    <p3xr-ng-accordion [title]="strings().page?.monitor?.slowLog || 'Slow Log'" accordionKey="monitor-slowlog">
        <div actions>
            @if (!isReadonly) {
                <p3xr-ng-button
                    (click)="resetSlowLog(); $event.stopPropagation()"
                    label="Reset"
                    mdIcon="delete_sweep">
                </p3xr-ng-button>
            }
            <p3xr-ng-button
                (click)="exportSlowLog(); $event.stopPropagation()"
                [label]="strings().intention?.export || 'Export'"
                mdIcon="download">
            </p3xr-ng-button>
        </div>
        <div content>
            @if (current.slowlog.length === 0) {
                <div style="padding: 12px 16px; opacity: 0.6;">
                    {{ strings().page?.monitor?.noSlowQueries || 'No slow queries recorded' }}
                </div>
            }
            <mat-list>
                @for (entry of current.slowlog; track entry.id) {
                    <mat-list-item>
                        <div class="p3xr-monitoring-slowlog-row">
                            <kbd class="p3xr-kbd p3xr-kbd-small">{{ entry.duration }}µs</kbd>
                            <span class="p3xr-monitoring-slowlog-cmd">{{ entry.command }}</span>
                        </div>
                    </mat-list-item>
                    <mat-divider></mat-divider>
                }
            </mat-list>
        </div>
    </p3xr-ng-accordion>

    <!-- Client List -->
    <br />
    <p3xr-ng-accordion [title]="strings().page?.monitor?.clientList || 'Client List'" accordionKey="monitor-clients-list">
        <div actions>
            <p3xr-ng-button
                (click)="toggleAutoRefreshClients(); $event.stopPropagation()"
                [label]="strings().label?.autoRefresh || 'Auto'"
                [mdIcon]="autoRefreshClients ? 'check_box' : 'check_box_outline_blank'">
            </p3xr-ng-button>
            @if (!autoRefreshClients) {
                <p3xr-ng-button
                    (click)="loadClientList(); $event.stopPropagation()"
                    [label]="strings().intention?.refresh || 'Refresh'"
                    mdIcon="refresh">
                </p3xr-ng-button>
            }
            <p3xr-ng-button
                (click)="exportClientList(); $event.stopPropagation()"
                [label]="strings().intention?.export || 'Export'"
                mdIcon="download">
            </p3xr-ng-button>
        </div>
        <div content>
            @if (clientList.length === 0 && clientListLoaded) {
                <div style="padding: 16px; opacity: 0.5;">{{ strings().page?.monitor?.noClients || 'No clients' }}</div>
            }
            @if (clientList.length === 0 && !clientListLoaded) {
                <div style="padding: 16px; opacity: 0.5;">{{ strings().label?.loading || 'Loading...' }}</div>
            }
            @if (clientList.length > 0) {
                <mat-list>
                    @for (client of clientList; track client.id) {
                        <mat-list-item>
                            <div class="p3xr-monitoring-client-row">
                                <span class="p3xr-mono p3xr-monitoring-client-addr">{{ client.addr }}</span>
                                @if (client.name) {
                                    <span class="p3xr-monitoring-client-name">({{ client.name }})</span>
                                }
                                <span class="p3xr-monitoring-client-info">
                                    db{{ client.db }} · {{ client.cmd }} · {{ client.idle }}s
                                </span>
                                @if (!isReadonly) {
                                    <mat-icon class="p3xr-monitoring-client-kill" (click)="killClient(client.id, $event)"
                                            [matTooltip]="strings().page?.monitor?.killClient || 'Kill client'">close</mat-icon>
                                }
                            </div>
                        </mat-list-item>
                        <mat-divider></mat-divider>
                    }
                </mat-list>
            }
        </div>
    </p3xr-ng-accordion>

    <!-- Memory Top Keys -->
    <br />
    <p3xr-ng-accordion [title]="strings().page?.monitor?.topKeys || 'Top Keys by Memory'" accordionKey="monitor-top-keys">
        <div actions>
            <p3xr-ng-button
                (click)="toggleAutoRefreshTopKeys(); $event.stopPropagation()"
                [label]="strings().label?.autoRefresh || 'Auto'"
                [mdIcon]="autoRefreshTopKeys ? 'check_box' : 'check_box_outline_blank'">
            </p3xr-ng-button>
            @if (!autoRefreshTopKeys) {
                <p3xr-ng-button
                    (click)="loadTopKeys(); $event.stopPropagation()"
                    [label]="strings().intention?.refresh || 'Refresh'"
                    mdIcon="refresh">
                </p3xr-ng-button>
            }
            <p3xr-ng-button
                (click)="exportTopKeys(); $event.stopPropagation()"
                [label]="strings().intention?.export || 'Export'"
                mdIcon="download">
            </p3xr-ng-button>
        </div>
        <div content>
            @if (topKeys.length === 0 && topKeysLoaded) {
                <div style="padding: 16px; opacity: 0.5;">{{ strings().page?.monitor?.noKeys || 'No keys' }}</div>
            }
            @if (topKeys.length === 0 && !topKeysLoaded) {
                <div style="padding: 16px; opacity: 0.5;">{{ strings().label?.loading || 'Loading...' }}</div>
            }
            @if (topKeys.length > 0) {
                <mat-list>
                    @for (entry of topKeys; track entry.key; let i = $index) {
                        <mat-list-item>
                            <div class="p3xr-settings-pair-row">
                                <div class="p3xr-settings-row-label">
                                    <span style="opacity: 0.4; margin-right: 8px;">#{{ i + 1 }}</span>
                                    <span class="p3xr-mono" style="font-size: 13px;">{{ entry.key }}</span>
                                </div>
                                <div class="p3xr-settings-row-value p3xr-mono">{{ formatBytes(entry.bytes) }}</div>
                            </div>
                        </mat-list-item>
                        <mat-divider></mat-divider>
                    }
                </mat-list>
            }
        </div>
    </p3xr-ng-accordion>

    @if (isCluster && state.redisVersion().isAtLeast(8, 2)) {
        <br />
        <p3xr-ng-accordion [title]="strings().page?.monitor?.slotStats || 'Cluster Slot Stats'" accordionKey="monitor-slot-stats">
            <div actions>
                <p3xr-ng-button
                    (click)="loadSlotStats(); $event.stopPropagation()"
                    [label]="strings().intention?.refresh || 'Refresh'"
                    mdIcon="refresh">
                </p3xr-ng-button>
            </div>
            <div content>
                <div style="padding: 8px 16px; display: flex; gap: 8px; align-items: center;">
                    <mat-form-field subscriptSizing="dynamic" style="width: 180px;">
                        <mat-label>Metric</mat-label>
                        <select matNativeControl [(ngModel)]="slotStatsMetric" (change)="loadSlotStats()">
                            <option value="KEY-COUNT">Key Count</option>
                            <option value="CPU-USEC">CPU (μs)</option>
                            <option value="MEMORY-BYTES">Memory (bytes)</option>
                        </select>
                    </mat-form-field>
                </div>
                @if (slotStats.length === 0 && slotStatsLoaded) {
                    <div style="padding: 16px; opacity: 0.5;">No slot data</div>
                }
                @if (slotStats.length > 0) {
                    <mat-list>
                        @for (entry of slotStats; track entry.slot; let i = $index) {
                            <mat-list-item>
                                <div class="p3xr-settings-pair-row">
                                    <div class="p3xr-settings-row-label">
                                        <span style="opacity: 0.4; margin-right: 8px;">#{{ i + 1 }}</span>
                                        <span class="p3xr-mono">Slot {{ entry.slot }}</span>
                                    </div>
                                    <div class="p3xr-settings-row-value p3xr-mono">
                                        @if (slotStatsMetric === 'KEY-COUNT') { {{ entry['key-count'] }} keys }
                                        @if (slotStatsMetric === 'CPU-USEC') { {{ entry['cpu-usec'] }} μs }
                                        @if (slotStatsMetric === 'MEMORY-BYTES') { {{ formatBytes(entry['memory-bytes']) }} }
                                    </div>
                                </div>
                            </mat-list-item>
                            <mat-divider></mat-divider>
                        }
                    </mat-list>
                }
            </div>
        </p3xr-ng-accordion>
    }

    <!-- Cluster Slot Map -->
    @if (isCluster) {
        <br />
        <p3xr-ng-accordion [title]="strings().page?.monitor?.clusterSlotMap || 'Cluster Slot Map'" accordionKey="monitor-cluster-slots">
            <div actions>
                <p3xr-ng-button
                    (click)="toggleAutoRefreshShards(); $event.stopPropagation()"
                    [label]="strings().label?.autoRefresh || 'Auto'"
                    [mdIcon]="autoRefreshShards ? 'check_box' : 'check_box_outline_blank'">
                </p3xr-ng-button>
                @if (!autoRefreshShards) {
                    <p3xr-ng-button
                        (click)="loadClusterShards(); $event.stopPropagation()"
                        [label]="strings().intention?.refresh || 'Refresh'"
                        mdIcon="refresh">
                    </p3xr-ng-button>
                }
                <p3xr-ng-button
                    (click)="exportClusterSlots(); $event.stopPropagation()"
                    [label]="strings().intention?.export || 'Export'"
                    mdIcon="download">
                </p3xr-ng-button>
            </div>
            <div content>
                @if (!clusterShards) {
                    <div style="padding: 12px 16px; opacity: 0.6;">
                        {{ strings().page?.monitor?.noClusterData || 'No cluster data available' }}
                    </div>
                } @else {
                    <mat-list>
                        @for (shard of clusterShards; track shard.master.id) {
                            <mat-list-item>
                                <div class="p3xr-settings-pair-row">
                                    <div class="p3xr-settings-row-label">
                                        <span style="font-weight: 500;">{{ shard.master.host }}:{{ shard.master.port }}</span>
                                        <span class="p3xr-analysis-sub">
                                            {{ formatSlotRanges(shard) }}
                                        </span>
                                    </div>
                                    <div class="p3xr-settings-row-value p3xr-mono">
                                        {{ getSlotCount(shard) }} {{ strings().page?.monitor?.totalSlots || 'slots' }}
                                        @if (shard.replicas.length > 0) {
                                            <span style="opacity: 0.5; margin-left: 8px;">({{ formatReplicas(shard) }})</span>
                                        }
                                    </div>
                                </div>
                            </mat-list-item>
                            <mat-divider></mat-divider>
                        }
                    </mat-list>
                    <div style="padding: 8px 16px; opacity: 0.6; font-size: 12px;">
                        16384 slots across {{ clusterShards.length }} masters
                    </div>
                }
            </div>
        </p3xr-ng-accordion>
    }
}