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

@if (current) {
    <!-- Overview -->
    <p3xr-ng-accordion [title]="strings().page?.monitor?.title" accordionKey="monitor-overview">
        <div actions>
            <p3xr-ng-button
                (click)="togglePause(); $event.stopPropagation()"
                [label]="paused ? (strings().intention?.resume) : (strings().intention?.pause)"
                [mdIcon]="paused ? 'play_arrow' : 'pause'">
            </p3xr-ng-button>
            <p3xr-ng-button
                (click)="exportOverview(); $event.stopPropagation()"
                [label]="strings().intention?.export"
                mdIcon="download">
            </p3xr-ng-button>
            <p3xr-ng-button
                (click)="exportAll(); $event.stopPropagation()"
                [label]="strings().page?.analysis?.exportAll"
                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 }}</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 }}</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 }}</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 }}</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 }}</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 }}</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 }}</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 }}</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 }}</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 }}</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 }}</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 }}</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 }}</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" accordionKey="monitor-server-info">
            <div actions>
                <p3xr-ng-button
                    (click)="exportServerInfo(); $event.stopPropagation()"
                    [label]="strings().intention?.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 }}</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 }}</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 }}</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 }}</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 }} 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 }} 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" accordionKey="monitor-persistence">
            <div actions>
                <p3xr-ng-button
                    (click)="exportPersistence(); $event.stopPropagation()"
                    [label]="strings().intention?.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 }}</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 }}</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 }}</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 }}</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 }}</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" accordionKey="monitor-replication">
            <div actions>
                <p3xr-ng-button
                    (click)="exportReplication(); $event.stopPropagation()"
                    [label]="strings().intention?.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 }}</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 }}</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 }}</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 }}</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" accordionKey="monitor-keyspace">
            <div actions>
                <p3xr-ng-button
                    (click)="exportKeyspace(); $event.stopPropagation()"
                    [label]="strings().intention?.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 }}: {{ entry.keys }} · {{ strings().page?.monitor?.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" accordionKey="monitor-modules">
        <div actions>
            <p3xr-ng-button
                (click)="exportModules(); $event.stopPropagation()"
                [label]="strings().intention?.export"
                mdIcon="download">
            </p3xr-ng-button>
        </div>
        <div content>
            @if (modulesList.length === 0) {
                <div style="padding: 16px; opacity: 0.5;">{{ strings().page?.monitor?.noModules }}</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) + ' (MB)'" accordionKey="monitor-chart-memory">
        <div actions>
            <p3xr-ng-button
                (click)="exportChart(memoryChartRef, 'memory'); $event.stopPropagation()"
                [label]="strings().intention?.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" accordionKey="monitor-chart-ops">
        <div actions>
            <p3xr-ng-button
                (click)="exportChart(opsChartRef, 'ops'); $event.stopPropagation()"
                [label]="strings().intention?.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" accordionKey="monitor-chart-clients">
        <div actions>
            <p3xr-ng-button
                (click)="exportChart(clientsChartRef, 'clients'); $event.stopPropagation()"
                [label]="strings().intention?.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) + ' (KB/s)'" accordionKey="monitor-chart-network">
        <div actions>
            <p3xr-ng-button
                (click)="exportChart(networkChartRef, 'network'); $event.stopPropagation()"
                [label]="strings().intention?.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" 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"
                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 }}
                </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" accordionKey="monitor-clients-list">
        <div actions>
            <p3xr-ng-button
                (click)="toggleAutoRefreshClients(); $event.stopPropagation()"
                [label]="strings().label?.autoRefresh"
                [mdIcon]="autoRefreshClients ? 'check_box' : 'check_box_outline_blank'">
            </p3xr-ng-button>
            @if (!autoRefreshClients) {
                <p3xr-ng-button
                    (click)="loadClientList(); $event.stopPropagation()"
                    [label]="strings().intention?.refresh"
                    mdIcon="refresh">
                </p3xr-ng-button>
            }
            <p3xr-ng-button
                (click)="exportClientList(); $event.stopPropagation()"
                [label]="strings().intention?.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 }}</div>
            }
            @if (clientList.length === 0 && !clientListLoaded) {
                <div style="padding: 16px; opacity: 0.5;">{{ strings().label?.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">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" accordionKey="monitor-top-keys">
        <div actions>
            <p3xr-ng-button
                (click)="toggleAutoRefreshTopKeys(); $event.stopPropagation()"
                [label]="strings().label?.autoRefresh"
                [mdIcon]="autoRefreshTopKeys ? 'check_box' : 'check_box_outline_blank'">
            </p3xr-ng-button>
            @if (!autoRefreshTopKeys) {
                <p3xr-ng-button
                    (click)="loadTopKeys(); $event.stopPropagation()"
                    [label]="strings().intention?.refresh"
                    mdIcon="refresh">
                </p3xr-ng-button>
            }
            <p3xr-ng-button
                (click)="exportTopKeys(); $event.stopPropagation()"
                [label]="strings().intention?.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 }}</div>
            }
            @if (topKeys.length === 0 && !topKeysLoaded) {
                <div style="padding: 16px; opacity: 0.5;">{{ strings().label?.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" accordionKey="monitor-slot-stats">
            <div actions>
                <p3xr-ng-button
                    (click)="loadSlotStats(); $event.stopPropagation()"
                    [label]="strings().intention?.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" accordionKey="monitor-cluster-slots">
            <div actions>
                <p3xr-ng-button
                    (click)="toggleAutoRefreshShards(); $event.stopPropagation()"
                    [label]="strings().label?.autoRefresh"
                    [mdIcon]="autoRefreshShards ? 'check_box' : 'check_box_outline_blank'">
                </p3xr-ng-button>
                @if (!autoRefreshShards) {
                    <p3xr-ng-button
                        (click)="loadClusterShards(); $event.stopPropagation()"
                        [label]="strings().intention?.refresh"
                        mdIcon="refresh">
                    </p3xr-ng-button>
                }
                <p3xr-ng-button
                    (click)="exportClusterSlots(); $event.stopPropagation()"
                    [label]="strings().intention?.export"
                    mdIcon="download">
                </p3xr-ng-button>
            </div>
            <div content>
                @if (!clusterShards) {
                    <div style="padding: 12px 16px; opacity: 0.6;">
                        {{ strings().page?.monitor?.noClusterData }}
                    </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 }}
                                        @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>
    }
}