SQL Server Activity Monitor — live observability dashboard for Microsoft SQL Server.
Equivalent to SSMS Activity Monitor with animated charts, real-time tables, multi-connection tabs, and configurable widget layout.
Event-driven real-time web application — server pushes metrics via WebSocket on a fixed 2-second poll cycle. Clients receive and render without polling.
Single-page application (React) backed by a stateless Node.js + Express server. No database tier; all persistent state is SQL Server (read-only DMV queries) or localStorage (user preferences).
- Push, don't pull. Server emits on every poll cycle; clients never request metrics.
- Read-only. All SQL queries target DMVs (
sys.dm_*,sys.master_files). No schema changes, no writes. Requires onlyVIEW SERVER STATE. - Multi-connection. Multiple SQL Server instances tracked simultaneously via connection-scoped state; only the active tab renders a Dashboard.
- Minimal backend state. Connections held in memory on the Node process. The only server-side persistence is
data/db-size-history.jsonfor daily DB size snapshots. Reconnect restores connection list from/api/connections. - Theme-first. All colors flow through CSS custom properties. No hardcoded color values in component code.
- Render isolation. Only the active dashboard renders. Inactive connections accumulate no chart ResizeObservers or animation timers.
| Layer | Choice |
|---|---|
| Framework | React 18 (functional components, hooks) |
| Build | Vite 5 |
| Styling | Tailwind CSS + CSS custom properties (design tokens) |
| Charts | ApexCharts (react-apexcharts) |
| Virtualization | @tanstack/react-virtual |
| Real-time | socket.io-client |
| State | useReducer + React Context |
| Testing | Vitest + @testing-library/react + jsdom |
| Layer | Choice |
|---|---|
| Runtime | Node.js 20 LTS |
| HTTP | Express 4 |
| WebSocket | Socket.io 4 |
| SQL driver | mssql (Tedious) |
| Auth | Windows Integrated or SQL Auth via .env |
SQL Server 2012+. Queries target only public DMVs accessible via VIEW SERVER STATE. No schema, no stored procs, no temp tables.
- Enterprise observability aesthetic — Grafana/Datadog/Azure Monitor register. Dense information, minimal decoration.
- Status-only color — KPI values are neutral by default. Color (warning amber, critical red) appears only when a threshold is breached. No decorative coloration.
- Compact notation — large numbers formatted as
82.9M,4.3kthroughout. Full value on hover. - Dark and light mode — all components adapt via
data-theme="dark"on<html>and CSS custom property overrides. No component-level theme logic.
All color, spacing, and semantic tokens defined in src/index.css :root:
--body-bg, --body-text, --header-bg
--card-bg, --card-shadow, --card-radius
--badge-bg, --badge-text
--divider, --row-hover, --section-hover
--text-primary, --text-secondary, --text-muted
--c-ok, --c-warn, --c-crit, --c-info
--sort-active
--val-cpu, --val-wait, --val-io, --val-batch
--dot-live, --dot-dead, --dot-warn, --dot-idle
--status-run-*, --status-susp-*, --status-sleep-*, --status-bgnd-*
Shared CSS utility classes for cross-cutting concerns:
.mc/.mc-click— metric card base + hover lift.vt-th/.vt-td— virtualized table header/cell.wia-th/.wia-td/.wia-row-*— sp_WhoIsActive table.form-label,.form-input,.form-select,.form-textarea— modal form tokens.form-segmented,.form-segmented-btn,.form-error— modal controls.chart-wrap— fixed 224px chart container, no flex growth.op-scroll— thin custom scrollbar
Severity colors accessed via src/lib/thresholds.js → metricStatusColor(key, value) → var(--c-crit) / var(--c-warn) / null.
Palettes (src/lib/palettes.js): named color schemes (Enterprise, Dark, Midnight, etc.) applied by writing CSS variable overrides to :root. Stored in localStorage.
┌─────────────────────────────────────────────────────┐
│ Header (fixed, z-50) │
│ server name · last-update · status dot · controls │
├─────────────────────────────────────────────────────┤
│ TabBar (one tab per connection) │
├─────────────────────────────────────────────────────┤
│ main (p-6, max-w-1920, auto margins) │
│ ┌──────────────────────────────────────────────┐ │
│ │ KPI Bar (6-col CSS Grid) │ │
│ │ CPU · Wait · Sessions · DB I/O · Mem · PLE │ │
│ ├──────────────────────────────────────────────┤ │
│ │ Chart Grid (CSS Grid, auto-fill minmax 280px) │ │
│ │ up to 7 ApexCharts area charts │ │
│ ├──────────────────────────────────────────────┤ │
│ │ Memory Health panel │ │
│ ├──────────────────────────────────────────────┤ │
│ │ Jobs Panel · Sessions Panel (fixed height) │ │
│ ├──────────────────────────────────────────────┤ │
│ │ CollapsibleSection × N (widget order driven) │ │
│ │ DB Sizes · DB Size Trends · Processes │ │
│ │ Resource Waits · File I/O · Recent/Active │ │
│ │ sp_WhoIsActive · Blocking · Deadlocks │ │
│ └──────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────┘
Disabled widgets are fully unmounted (not display:none). Widget order for sections is user-configurable via drag-and-drop in the sidebar and persisted to localStorage.
- One active Dashboard at a time. Switching tabs unmounts the previous Dashboard, destroying all chart instances and timers. No background re-rendering.
- Fixed chart height —
.chart-wrapenforcesheight/min-height/max-height: 224px; flex-shrink:0; overflow:hiddenand ApexCharts renders atheight={224}(pixel). Prevents flex-grow feedback loops. - Chart resize prevention —
redrawOnWindowResize: false,redrawOnParentResize: falseon all ApexCharts instances. - Sections collapse state persisted per connection via
localStoragekeysqlmon-collapsed-{connId}. - Sort state persisted in
AppContextper connection per table (never resets on re-render).
- Semantic HTML (
<table>,<thead>,<th>,<button>,<input>). - Status badges use both color and text label (CRITICAL / WARNING), not color alone.
- SVG sparklines marked
aria-hidden. - Tooltips via CSS positioning, not
titleattributes.
src/
├── main.jsx # React root, AppProvider wrapper
├── App.jsx # Route shell: header, tabbar, single active Dashboard
├── index.css # Design tokens, utility classes, dark mode overrides
├── context/
│ └── AppContext.jsx # Global useReducer store, localStorage persistence
├── hooks/
│ └── useSocket.js # Socket.io lifecycle, dispatches UPDATE_METRICS
├── lib/
│ ├── widgetRegistry.js # Widget manifest, layout load/save/default
│ ├── thresholds.js # Warn/crit thresholds, metricStatusColor()
│ ├── palettes.js # Named color palettes, applyPalette()
│ ├── fmt.js # fmtNum, fmtBytes, fmtMs, fmtJobDuration
│ ├── tableCols.js # Column definitions for all VirtualTable instances
│ └── cn.js # clsx-compatible className helper
├── components/
│ ├── Dashboard.jsx # Orchestrator: reads state, renders widget tree
│ ├── KPIBar.jsx # 6-card KPI strip with sparklines and tooltips
│ ├── ChartCard.jsx # Single ApexCharts area card
│ ├── MemoryHealth.jsx # Memory health gauges and detail table
│ ├── JobsPanel.jsx # SQL Agent jobs (fixed height, virtualized)
│ ├── SessionsPanel.jsx # Connected sessions grouped by database
│ ├── WhoIsActive.jsx # sp_WhoIsActive results with expand-row
│ ├── DbSizes.jsx # Database size cards with fill bars
│ ├── DbSizeTrend.jsx # 10-day DB size trend chart + growth table
│ ├── VirtualTable.jsx # Generic virtualized sortable table
│ ├── CollapsibleSection.jsx # Expandable/collapsible section wrapper
│ ├── TabBar.jsx # Connection tabs
│ ├── Header.jsx # App header
│ ├── ConnectModal.jsx # New connection form
│ ├── WidgetSidebar.jsx # Widget show/hide and section reorder
│ └── ui/ # Primitive components: Button, Badge, Dialog
└── test/ + __tests__/ # Vitest test files (79 tests)
Single useReducer store in AppContext. No external state library.
State shape:
{
connections: {
[id]: {
id, label, server, color, appIntent,
metrics: null | <latest poll payload>,
history: { cpu[], wait[], io[], batch[], netMb[], compilations[] }, // ring buffer, max 60
diskHistory: { [volume_mount_point]: number[] }, // free_pct ring buffer, max 60
lastUpdate: timestamp | null,
jobsFilter, jobsSearch, jobsSort,
sortState: { proc, waits, fileio, recent, active, blocking, deadlocks },
expandedSessionGroups: Set,
collapsedSections: Set,
}
},
activeConnId: string | null,
palette: string,
widgetLayout: { id, enabled }[],
}
Actions:
ADD_CONN/REMOVE_CONN/SET_ACTIVEUPDATE_METRICS— merges new poll data, pushes all 6 history seriesSET_PALETTETOGGLE_SECTION/SET_TABLE_SORTSET_JOBS_FILTER/SET_JOBS_SEARCH/SET_JOBS_SORTTOGGLE_SESSION_GROUPTOGGLE_WIDGET/REORDER_WIDGETS/RESET_WIDGET_LAYOUT
All localStorage writes happen inside the reducer (palette, widget layout, collapsed sections).
Dashboardwrapped inReact.memo. Only re-renders when its connection's slice of state changes.SectionBadgedefined at module level asmemo(...). Defined inside Dashboard would create a new component type identity on every Dashboard render, causing CollapsibleSection to unmount+remount its badge on every metrics update.- Widget visibility via conditional render (
on(id) && <Widget />), notdisplay:none. Disabled widgets produce zero DOM, zero event listeners, zero chart instances. useMemoforlayoutMap,orderedSections,enabledCharts, series and options in ChartCard and KPIBar.useCallbackfor sort handlers, kill handlers.- History ring buffer capped at 60 readings (2 minutes) via
pushHist().
The 2-second poll cycle dispatches UPDATE_METRICS to AppContext, which creates a new connections object on every tick. Without explicit memoization, this would cascade into a full re-render of the entire widget tree every 2 seconds — causing progressive main-thread saturation and eventual "Page Unresponsive" in the browser.
Memoization boundaries prevent cascade re-renders:
| Component | Memoization | Trigger condition |
|---|---|---|
ChartCard |
memo() |
Re-renders only when history, value, color, or yMax changes |
KPICard |
memo() |
Re-renders only when primary, statusVal, history, or subtitle changes |
MemoryHealth |
memo() |
Re-renders only when conn.metrics.serverPerf values change |
Dashboard sortedData |
7× useMemo |
Each table re-sorts only when its own source array or sort state changes |
SessionsPanelInner groups |
useMemo([processes, expandedGroups]) |
Group rebuild skipped if processes unchanged |
JobsPanelInner filtered list |
useMemo([jobs, jobsFilter, jobsSearch, jobsSort]) |
Filter+sort skipped if inputs unchanged |
WhoIsActive filtered rows |
useMemo([rows, search]) |
Filter skipped if rows and search unchanged |
SparklineMemo (KPIBar) |
memo() + useMemo for slice |
history.slice(-20) only recomputed when history array changes |
sortRows() is module-level — defined once, never recreated. Called by each useMemo via stable reference.
rowStyle callbacks are module-level constants (BLOCKING_ROW_STYLE, DEADLOCK_ROW_STYLE) — not inline lambdas. Inline lambdas would create a new function reference per render, invalidating VirtualTable's internal memoization.
Tooltip content is module-level (TT_CPU, TT_WAIT, TT_IO, etc.) — inline array literals create new object references every render, forcing KPICard memo() to see changed props and re-render even when metric values are identical.
useSocket subscribe guard — AppContext reducer returns a new connections object on every UPDATE_METRICS, so the [connections] effect dependency fires every 2 seconds. A subscribedRef (Set) gates the socket.emit('subscribe') call: it only emits for IDs not already in the Set, making the effect body O(1) on the hot path with no socket traffic.
Result: on a 2s tick where only one metric changes, React re-renders only the components whose specific props changed. The full 17-widget cascade that caused "Page Unresponsive" after extended runtime is eliminated.
- No chart resize accumulation across tab switches (chart instances destroyed with Dashboard).
- No chart height feedback loop (fixed pixel height +
flex-shrink:0+overflow:hidden). - Virtualized tables for all data-heavy sections via
@tanstack/react-virtual(rows rendered = viewport rows only, regardless of result count). - Memoized sort/filter on all tabular data — recomputes only when source data or sort/filter state changes, not on every poll tick.
- Sparklines rendered as inline SVG (
<polyline>+<circle>), no chart library overhead. - Stable reference discipline: no inline array/object literals or lambda functions in JSX props of
memo()-wrapped components.
- Accept connection credentials via REST, create mssql pool, store in memory.
- Poll each registered connection every 2 seconds, running all metric queries in parallel via
Promise.all. - Emit
metricsevent per connection to all subscribed Socket.io clients. - Handle graceful pool teardown on disconnect.
server.js
├── DB history helpers loadDbHistory(), saveDbHistory(), pruneDbHistory()
│ takeDbSizeSnapshot() — daily sys.master_files snapshot
├── Connection store Map<id, { pool, label, server, handle,
│ snapshotHandle, prevIO, prevNet }>
├── buildConfig() Normalize form input → mssql config object
├── Q{} Named SQL query strings (cpu, overview, ioSnapshot,
│ processes, resourceWaits, dataFileIO,
│ recentExpensive, activeExpensive, blocking, deadlocks,
│ serverPerf, jobs, dbSizes, diskDrives)
├── collectMetrics() Promise.all(14 queries) → extra OS drives merge → emit 'metrics'
└── REST routes + Socket.io event handlers
REST (JSON):
POST /api/connect { server, database, authType, user, password,
encrypt, trustServerCert, label, appIntent }
→ { id, label, server, color, appIntent }
GET /api/connections → [{ id, label, server, color, appIntent }]
DELETE /api/disconnect/:id → { ok: true }
GET /api/connections/:id/db-size-history
→ { "YYYY-MM-DD": { "DbName": { data_bytes, log_bytes, total_bytes } } }
Socket.io events:
Client → Server:
subscribe connId Start receiving metrics for this connection
unsubscribe connId Stop receiving (does not stop server poll)
Server → Client:
metrics { connId, cpu_percent, waiting_tasks, db_io_mb, batch_requests,
processes[], resourceWaits[], fileIO[], recentExpensive[],
activeExpensive[], blocking[], deadlocks[], jobs[], dbSizes[],
diskDrives[], whoIsActive[], serverPerf{} }
poll_error { connId, message }
Each client joins a Socket.io room named by connId. The server emits to io.to(connId).
- All per-connection queries run in
Promise.all— parallel, not serial. Single round-trip per poll cycle. - Delta metrics (I/O MB/s, network MB/s) computed server-side from
prevIOmap. Clients receive pre-computed rates. requestTimeout: 15000— slow queries don't block the poll cycle; they time out and the next cycle starts fresh.- Pool size:
max: 5, min: 1. Idle timeout: 30s. Each connection gets its own isolated pool. POLL_MSconfigurable via.env(POLL_INTERVAL_MS). Default 2000ms.
- Authentication: Windows Integrated (
trustedConnection: true) or SQL Auth (user+passwordfrom request body, never logged). - Authorization: No app-level auth. Intended for internal network / local use. Add a reverse proxy with auth for external exposure.
- SQL injection: All queries use parameterless DMV reads. No user input interpolated into SQL.
- Credential storage: Server holds credentials only in the mssql pool config object (in-process memory). Never written to disk or logged.
.envholds default/dev credentials only; production credentials come from the POST body. - Minimum SQL permission required:
VIEW SERVER STATEon the target instance.
| Panel | Source DMV |
|---|---|
| CPU % | sys.dm_os_ring_buffers (ProcessUtilization field, XML parse) |
| Waiting Tasks | sys.dm_exec_requests (status IN 'suspended','waiting') |
| Database I/O MB/s | sys.dm_io_virtual_file_stats delta between polls |
| Batch Requests/sec | sys.dm_os_performance_counters |
| Network I/O MB/s | sys.dm_os_performance_counters |
| SQL Compilations/sec | sys.dm_os_performance_counters |
| Memory (committed, target, PLE) | sys.dm_os_performance_counters + sys.dm_os_sys_info |
| Active Processes | sys.dm_exec_sessions + sys.dm_exec_requests + sys.dm_exec_sql_text |
| Resource Waits | sys.dm_os_wait_stats (benign wait types filtered) |
| Data File I/O | sys.dm_io_virtual_file_stats + sys.master_files |
| Recent Expensive Queries (1h) | sys.dm_exec_query_stats + sys.dm_exec_sql_text |
| Active Expensive Queries | sys.dm_exec_requests + sys.dm_exec_sql_text |
| Blocking Chains | sys.dm_exec_requests (blocking_session_id chain) |
| Deadlock History | sys.dm_xe_session_ring_buffer_targets (system_health XE session) |
| SQL Agent Jobs | msdb.dbo.sysjobs + msdb.dbo.sysjobhistory |
| Database Sizes | sys.master_files + FILEPROPERTY |
| sp_WhoIsActive | sp_WhoIsActive (if installed) |
| Drive Space | sys.dm_os_volume_stats CROSS APPLY sys.master_files + sys.dm_os_enumerate_fixed_drives / xp_fixeddrives |
| DB Size Trends | sys.master_files (daily snapshot → data/db-size-history.json) |
Drive space is collected on every poll cycle via Q.diskDrives — a single aggregated query that joins sys.master_files with sys.dm_os_volume_stats. One row per logical volume that hosts at least one SQL Server file.
OS-drive visibility — production servers often have all SQL Server files on non-OS drives (D:, E:, etc.), leaving C:\ out of the DMV results. After the main query, collectMetrics runs a supplemental query to include any drives not already covered:
- SQL Server 2019+:
sys.dm_os_enumerate_fixed_drives— returns all fixed drives with full total + free bytes. - SQL Server 2012–2017 fallback:
EXEC xp_fixeddrives— returns free MB only; total size unavailable.
Drives added via the fallback path have total_bytes = 0. DriveCard detects this and renders a compact OS DRIVE card (free space shown, no utilization bar or thresholds — there are no SQL files on the drive to protect).
Query result columns:
| Column | Description |
|---|---|
volume_mount_point |
Drive letter / mount point (e.g. C:\, D:\) |
total_bytes / available_bytes / used_bytes |
Raw sizes as FLOAT |
used_pct / free_pct |
Pre-computed percentages (DECIMAL 5,1) |
has_tempdb |
1 if TempDB files live on this volume |
has_log |
1 if any transaction log files live here |
has_data |
1 if any data (ROWS) files live here (excluding TempDB) |
database_count |
COUNT(DISTINCT database_id) on this volume |
file_count |
Total SQL files on this volume |
Drive type classification (driveType() in thresholds.js):
C:\ (or C:/) → system
has_tempdb → tempdb
has_log only → log
all others → data
Threshold engine (DRIVE_THRESHOLDS in thresholds.js):
All thresholds are free-space percentages (lower = more full = worse):
| Drive type | Warning | Critical | Emergency |
|---|---|---|---|
system C:\ |
< 20% free | < 10% free | < 5% free |
data |
< 15% free | < 8% free | — |
log |
< 25% free | < 15% free | — |
tempdb |
< 25% free | < 15% free | — |
driveStatusLevel(drive) returns { color, label, level }:
- level 0 →
C_OK(#16a34a) HEALTHY - level 1 →
C_WARN(#ea580c) WARNING - level 2 →
C_CRIT(#dc2626) CRITICAL - level 3 →
C_EMERGENCY(#7f1d1d) EMERGENCY
History and trend (diskHistory in AppContext):
diskHistory: { [volume_mount_point]: number[] } — per-volume ring buffer of free_pct readings, capped at HISTORY_MAX = 60 (2 minutes). Built in UPDATE_METRICS reducer alongside the existing metric history arrays.
Trend calculation (module-level calcTrend() in DriveMonitor.jsx):
- Take last 30 readings (last 60s)
slope = (last − first) / count— change in free_pct per readingslopePerHour = slope × 1800(1800 readings/hr at 2s intervals)- ETA to full:
free_pct / |slope| × 2s— only projected when slope < −0.005%/reading (suppresses noise)
ETA display: < 1 hr / X hr / X days / > 1 week.
DriveMonitor component rendering rules:
- Renders
nullwhendiskDrivesarray is empty (safe for connections that fail the DMV query) - Default sort:
free_pctascending (most critical drive first) - All per-drive cards memoized with
memo()+useMemo([history])for trend — prevents sort/header re-renders from recomputing trend on all drives
Tracks how database sizes change over time using daily snapshots stored in a local JSON file (data/db-size-history.json). This is the only persistent state written by the server process (read-only against SQL Server is maintained — the snapshot query reads sys.master_files only).
Snapshot query (takeDbSizeSnapshot() in server.js):
SELECT DB_NAME(mf.database_id) AS database_name,
SUM(CASE WHEN type_desc='ROWS' THEN size*8192 ELSE 0 END) AS data_bytes,
SUM(CASE WHEN type_desc='LOG' THEN size*8192 ELSE 0 END) AS log_bytes,
SUM(size)*8192 AS total_bytes
FROM sys.master_files mf
WHERE mf.database_id > 0 AND mf.state = 0
GROUP BY mf.database_idCapture schedule:
- Snapshot taken immediately on first connection to a server.
- Hourly interval (
setInterval, 1hr) checks if today's snapshot exists; takes it if not. This means exactly one snapshot per calendar day regardless of how long the connection has been open. snapshotHandlestored on theconnobject alongsidehandleand cleared in the disconnect handler.
Persistence (data/db-size-history.json):
{
"SERVER\\INSTANCE": {
"YYYY-MM-DD": {
"DatabaseName": { "data_bytes": 0, "log_bytes": 0, "total_bytes": 0 }
}
}
}- Keyed by
conn.server(the raw server string from the connect form — stable across restarts, unlike the ephemeralconnIdUUID). - History pruned to last 10 calendar days on every write via
pruneDbHistory(). - File created automatically in
data/(created at startup if absent).
REST endpoint:
GET /api/connections/:id/db-size-history
→ { "YYYY-MM-DD": { "DbName": { data_bytes, log_bytes, total_bytes }, ... }, ... }
DbSizeTrend component (src/components/DbSizeTrend.jsx):
- Fetches history on mount and re-fetches every 5 minutes.
- Derives chart series in a single
useMemo([history]): sorts databases by current total size, maps each to a GB-unit series array aligned on the sorted date axis. - ApexCharts multi-series line chart — one series per database (capped at 15 visible in chart), smooth curve, markers at each data point, date-formatted x-axis (
MM-DD). - Search filter — input filters both the chart series and the growth table simultaneously.
- Growth table — one row per database showing: Total Size, Data, Log, 10-Day Growth (signed, amber for growth / green for reclaim), Daily Avg, and a SPIKE badge when any single-day delta exceeds 3× the average daily growth for that database.
- Shows graceful empty state when no history exists yet ("Snapshots captured daily — data will appear after the first day").
State shape addition (AppContext):
No reducer changes needed — DbSizeTrend manages its own local state via useState/useEffect and fetches directly from the REST endpoint. History data is not pushed via Socket.io since it is low-frequency (daily) and fetched on demand.
- KPI Bar — 6 cards: CPU, Waiting Tasks, Sessions, Database I/O, SQL Memory, Page Life Expectancy. Each card shows: current value, 30s delta trend, micro sparkline (last 20 readings), status badge (WARN/CRITICAL only), threshold tooltip on hover.
- Live Charts — 7 area charts (CPU, Waiting Tasks, DB I/O, Batch Requests, Network I/O, Compilations, Memory). 60-point rolling window (2 minutes). Fixed 224px height.
- Memory Health — committed vs target vs max memory bars, PLE gauge, memory clerk breakdown.
- SQL Agent Jobs — filterable by status (all/running/failed/succeeded), searchable, sortable. Virtualized.
- Sessions Panel — active connections grouped by database. Toggle groups. Virtualized.
- sp_WhoIsActive — expandable rows showing full SQL text. Manual refresh. Search filter.
- Database Sizes — per-database size cards with data/log fill bars and low-disk alerts.
- Collapsible Sections — all tabular sections collapse/expand, state persisted per connection.
- Widget Sidebar — toggle any widget on/off; drag sections to reorder. Layout persisted to
localStorage. - Drive Space Monitor — one card per logical volume hosting SQL Server files, plus OS-only drives sourced from
sys.dm_os_enumerate_fixed_drivesorxp_fixeddrives. Utilization bar, Total/Used/Free sizes, type badge (SYSTEM/DATA/LOG/TEMPDB/OS DRIVE), trend arrow with %/hr rate and ETA-to-full forecast. Expandable detail with DB/file counts and threshold reference. Sort by % Free (default, most critical first), Drive letter, or Size. Header shows aggregate critical/warning badge counts. Threshold-based color coding with four severity levels (Healthy/Warning/Critical/Emergency). OS drives with no SQL files render a compact card showing free space only. - Database Size Trends — 10-day rolling history of database sizes. Daily snapshots persisted to
data/db-size-history.json(one snapshot per calendar day, taken on connect and checked hourly). ApexCharts multi-series line chart with one series per database (up to 15 in chart), database search filter, and growth table showing Total/Data/Log sizes, 10-day growth, daily average, and spike detection. - Multi-connection Tabs — monitor multiple SQL Server instances simultaneously.
- Themes/Palettes — Enterprise (default), Dark, Midnight, Forest, Ocean, Rose, Slate. CSS var swap, persisted.
- No hardcoded color values in component code. All colors via CSS custom properties or semantic tokens from
thresholds.js. - No logic duplicated across components. Shared formatters in
src/lib/fmt.js. Shared column definitions insrc/lib/tableCols.js. Shared threshold logic insrc/lib/thresholds.js. - Module-level component definitions only. No components defined inside render functions (prevents React identity churn → unmount/remount cascades).
- CSS token classes (
.vt-th,.form-input, etc.) for patterns used in 2+ components. Inline styles only for values that are dynamic or component-specific.
-
Test runner: Vitest 4 + jsdom environment
-
Coverage: 79 tests across 8 files
lib/fmt.test.js— formatting utilitieslib/widgetRegistry.test.js— registry integrity, layout persistencecontext/reducer.test.jsx— all reducer actions including history cap and localStorage side effectscomponents/JobsPanel.test.jsx— filter, search, sort, status badgescomponents/SessionsPanel.test.jsx— grouping, expand, process countcomponents/WhoIsActive.test.jsx— fetch, refresh, expand row, search, error statecomponents/WidgetSidebar.test.jsx— toggle, reorder, reset, closeintegration/persistence.test.js— localStorage round-trips for layout and palette
-
Key test patterns:
renderWithContext(ui)wraps withAppProviderfor reducer access@tanstack/react-virtualmocked to return all rows (no viewport math in jsdom)socket.io-clientmocked to noopResizeObserverstubbedfetchstubbed per test
npm start # Production: builds Vite → dist/, starts server.js
npm run dev # Vite dev server (HMR) + server.js via concurrently
npm run build # Vite production build to dist/
npm test # Vitest watch
npm run test:run # Vitest single pass (CI)
npm run test:coverage # Coverage reportEnvironment config via .env:
PORT=3000
POLL_INTERVAL_MS=2000
AUTH_TYPE=windows|sql
DB_USER=
DB_PASS=
A production-grade internal SQL Server observability tool that:
- Shows live metrics within 2 seconds of a condition occurring on the server
- Handles multiple simultaneous SQL Server connections with zero cross-connection interference
- Renders smoothly at any window size without chart height drift, resize accumulation, or layout thrashing
- Adapts fully to dark/light/custom themes with no component-level theme logic
- Allows operators to customize the dashboard layout (widget visibility, section order) with preferences persisted across sessions
- Requires no database schema changes and only
VIEW SERVER STATEpermission - Maintains 79+ passing tests across all core logic, state management, and UI components
- Proactively alerts operations teams to drive space risks before they cause SQL Server service disruption, with per-volume thresholds tuned to drive type (system/data/log/tempdb) and live trend forecasting
- Surfaces database size growth trends over 10 days with daily average rates and spike detection, enabling capacity planning before storage is exhausted