diff --git a/src/App.css b/src/App.css index 03082c0..b49bca9 100644 --- a/src/App.css +++ b/src/App.css @@ -883,3 +883,153 @@ width: 100%; } } + +/* Proportional spacing with readable text limits on very large/small screens. */ +.main-content:has(.node-details-page) { + padding: 1.2%; +} + +.node-details-page { + width: 100%; + max-width: none; + container-type: inline-size; + font-size: 75%; +} + +.node-details-page > .page-header { + gap: 1%; + margin-bottom: 1%; +} + +.node-details-page > .page-header h1 { + font-size: clamp(1.25rem, 2.2cqw, 1.75rem); +} + +.node-details-page .secondary-button { + min-height: 2.2em; + padding: 0.35em 0.8em; + font-size: clamp(0.65rem, 0.9cqw, 0.75rem); +} + +.node-metrics-dashboard { + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + column-gap: 0.8%; + row-gap: 0.8cqw; + align-items: start; +} + +.node-metrics-dashboard > section { + min-width: 0; + margin: 0; + padding: 2.5%; + border: 1px solid var(--border); + border-radius: 5px; + background: var(--bg); + overflow-wrap: anywhere; +} + +.node-metrics-dashboard .page-header { + flex-direction: row; + flex-wrap: wrap; + align-items: flex-start; + gap: 0.3em 2%; + margin-bottom: 2%; + padding-bottom: 2%; + border-bottom: 1px solid var(--border); +} + +.node-metrics-dashboard h2 { + margin-bottom: 0.25em; + font-size: clamp(0.7rem, 1cqw, 0.85rem); + font-weight: 600; + line-height: 1.25; +} + +.node-metrics-dashboard p, +.node-metrics-dashboard a { + font-size: clamp(0.625rem, 0.8cqw, 0.7rem); + line-height: 1.3; +} + +.node-metrics-dashboard .page-actions { + align-items: center; + gap: 0.3em; +} + +.node-metrics-dashboard .database-connection-metrics { + grid-template-columns: repeat(2, minmax(0, 1fr)); + column-gap: 2%; + row-gap: 0.35cqw; +} + +.node-metrics-dashboard .database-connection-metric { + min-width: 0; + gap: 0.2em; + padding: 4%; + border-radius: 4px; +} + +.node-metrics-dashboard .database-connection-metric h3 { + font-size: clamp(0.625rem, 0.8cqw, 0.7rem); + line-height: 1.25; +} + +.node-metrics-dashboard .database-connection-metric strong { + font-size: clamp(0.9rem, 1.3cqw, 1.15rem); + line-height: 1.2; + font-variant-numeric: tabular-nums; +} + +.node-replication-panel { + grid-column: span 2; +} + +.node-replication-panel .nodes-table-wrap { + margin-top: 1%; +} + +.node-replication-panel .nodes-table { + min-width: 0; + font-size: clamp(0.625rem, 0.8cqw, 0.7rem); +} + +.node-replication-panel .nodes-table th, +.node-replication-panel .nodes-table td, +.node-replication-panel .nodes-table caption { + padding: 1% 1.5%; + font-size: inherit; + white-space: normal; + overflow-wrap: anywhere; +} + +.node-metrics-dashboard .status-message { + box-sizing: border-box; + max-width: 100%; + margin-bottom: 2%; + padding: 1% 2%; +} + +@container (min-width: 1200px) { + .node-metrics-dashboard { + grid-template-columns: repeat(4, minmax(0, 1fr)); + } +} + +@container (max-width: 780px) { + .node-metrics-dashboard { + grid-template-columns: repeat(2, minmax(0, 1fr)); + row-gap: 1.2cqw; + } +} + +@container (max-width: 460px) { + .node-metrics-dashboard { + grid-template-columns: minmax(0, 1fr); + row-gap: 2cqw; + } + + .node-replication-panel { + grid-column: auto; + } +} diff --git a/src/App.tsx b/src/App.tsx index 9e49bd6..8f7f84d 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,16 +1,8 @@ import { useEffect, useState } from 'react' import { Navigate, Route, Routes, useMatch, useNavigate, useParams } from 'react-router-dom' import './App.css' -import NodeConnections from './NodeConnections' -import NodeActiveConnections from './NodeActiveConnections' -import NodePerformance from './NodePerformance' -import NodeTupleActivity from './NodeTupleActivity' -import NodeWalGenerated from './NodeWalGenerated' -import NodeWalSize from './NodeWalSize' +import NodeDetailsDashboard from './NodeDetailsDashboard' import NodeDeadTuples from './NodeDeadTuples' -import NodeLocksDeadlocks from './NodeLocksDeadlocks' -import NodeDatabaseSize from './NodeDatabaseSize' -import NodeIndexSize from './NodeIndexSize' import NodeLargestTables from './NodeLargestTables' import NodeHardware from './NodeHardware' import ClusterNodeCard from './ClusterNodeCard' @@ -619,7 +611,7 @@ function Workspace({ page: activePage }: { page: PageId | 'node-details' | 'larg )} {activePage === 'node-details' && clusterDetails && ( -
+

{clusterDetails.nodes.find((node) => node.uuid === nodeId)?.node_name ?? 'Node not found'}

{nodeId && clusterDetails.nodes.some((node) => node.uuid === nodeId) && ( - <> - node.uuid === nodeId)!.node_name} - /> - node.uuid === nodeId)!.node_name} - /> - node.uuid === nodeId)!.node_name} - /> - node.uuid === nodeId)!.node_name} - /> - node.uuid === nodeId)!.node_name} - /> - node.uuid === nodeId)!.node_name} - /> - node.uuid === nodeId)!.node_name} - /> - node.uuid === nodeId)!.node_name} - /> - node.uuid === nodeId)!.node_name} - /> - - + node.uuid === nodeId)!.node_name} + /> )}
)} diff --git a/src/NodeDetailsDashboard.tsx b/src/NodeDetailsDashboard.tsx new file mode 100644 index 0000000..05c29d6 --- /dev/null +++ b/src/NodeDetailsDashboard.tsx @@ -0,0 +1,29 @@ +import NodeActiveConnections from './NodeActiveConnections' +import NodePerformance from './NodePerformance' +import NodeDatabaseSize from './NodeDatabaseSize' +import NodeIndexSize from './NodeIndexSize' +import NodeTupleActivity from './NodeTupleActivity' +import NodeWalGenerated from './NodeWalGenerated' +import NodeWalSize from './NodeWalSize' +import NodeDeadTuples from './NodeDeadTuples' +import NodeLocksDeadlocks from './NodeLocksDeadlocks' +import NodeConnections from './NodeConnections' + +export default function NodeDetailsDashboard(props: { nodeId: string; nodeName: string; clusterId: string }) { + return ( +
+ + + + + + + + + +
+ +
+
+ ) +}