pi-mega-compact 0.14.1 → 0.15.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/extensions/dashboard-server/api-contracts/endpoints/registry.js +6 -0
- package/dist/extensions/dashboard-server/resolve-snapshot.js +20 -8
- package/dist/extensions/dashboard-server/routes-game.js +60 -0
- package/dist/extensions/dashboard-server/routes.js +1 -1
- package/dist/extensions/dashboard-server/server.js +3 -1
- package/extensions/dashboard-client/dist/assets/AreaChart-rJdaOUlj.js +2 -0
- package/extensions/dashboard-client/dist/assets/AreaChart-rJdaOUlj.js.map +1 -0
- package/extensions/dashboard-client/dist/assets/BarChart-DOfc8me9.js +2 -0
- package/extensions/dashboard-client/dist/assets/BarChart-DOfc8me9.js.map +1 -0
- package/extensions/dashboard-client/dist/assets/{CacheTab-VNFZSdRr.js → CacheTab-EhqkEglo.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{CacheTab-VNFZSdRr.js.map → CacheTab-EhqkEglo.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{EventsTab-Dr7RDBy2.js → EventsTab-BUACwWXf.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{EventsTab-Dr7RDBy2.js.map → EventsTab-BUACwWXf.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{HealthTab-B8VZ8gbb.js → HealthTab-CDEYVRBb.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{HealthTab-B8VZ8gbb.js.map → HealthTab-CDEYVRBb.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{MaintenanceTab-DMtrXRcn.js → MaintenanceTab-DtanIeCQ.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{MaintenanceTab-DMtrXRcn.js.map → MaintenanceTab-DtanIeCQ.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{MemoryMapTab-PFrMdLOm.js → MemoryMapTab-BGEhL7OG.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{MemoryMapTab-PFrMdLOm.js.map → MemoryMapTab-BGEhL7OG.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/MetricsTab-Cktozn4s.js +2 -0
- package/extensions/dashboard-client/dist/assets/MetricsTab-Cktozn4s.js.map +1 -0
- package/extensions/dashboard-client/dist/assets/OverviewTab-08v6Ayuf.js +16 -0
- package/extensions/dashboard-client/dist/assets/OverviewTab-08v6Ayuf.js.map +1 -0
- package/extensions/dashboard-client/dist/assets/{ReposTab-D6pEUtEJ.js → ReposTab-Bg72Ozdc.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{ReposTab-D6pEUtEJ.js.map → ReposTab-Bg72Ozdc.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/SessionsTab-BcTK9Lg6.js +2 -0
- package/extensions/dashboard-client/dist/assets/{SessionsTab-CcFzDJ7W.js.map → SessionsTab-BcTK9Lg6.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{SetupTab-YCt5NGB5.js → SetupTab-Bt-Ru1xp.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{SetupTab-YCt5NGB5.js.map → SetupTab-Bt-Ru1xp.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{TimeSavedCard-BsLNhOOj.js → TimeSavedCard-ic_LjJ79.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{TimeSavedCard-BsLNhOOj.js.map → TimeSavedCard-ic_LjJ79.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{TurnsTab-C1jpVZrH.js → TurnsTab-D9AZTMgG.js} +4 -4
- package/extensions/dashboard-client/dist/assets/{TurnsTab-C1jpVZrH.js.map → TurnsTab-D9AZTMgG.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/WikiTab-hRcJPi_m.js +2 -0
- package/extensions/dashboard-client/dist/assets/{WikiTab-RHoVt95g.js.map → WikiTab-hRcJPi_m.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{button-DSZI4aZ5.js → button-BdAtIbVu.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{button-DSZI4aZ5.js.map → button-BdAtIbVu.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{card-CsZzIyPW.js → card-BFYc_QrK.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{card-CsZzIyPW.js.map → card-BFYc_QrK.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/generateCategoricalChart-BwzyqytT.js +70 -0
- package/extensions/dashboard-client/dist/assets/generateCategoricalChart-BwzyqytT.js.map +1 -0
- package/extensions/dashboard-client/dist/assets/{index-B1_J_Y0b.js → index-pmtEr3sb.js} +8 -8
- package/extensions/dashboard-client/dist/assets/index-pmtEr3sb.js.map +1 -0
- package/extensions/dashboard-client/dist/assets/index-za_gmEmQ.css +1 -0
- package/extensions/dashboard-client/dist/assets/{switch-DOIA1Nxu.js → switch-DEyGlFwJ.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{switch-DOIA1Nxu.js.map → switch-DEyGlFwJ.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{toggle-CyS6PHCO.js → toggle-pDMBQYQj.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{toggle-CyS6PHCO.js.map → toggle-pDMBQYQj.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{useSSE-CY7UIUzm.js → useSSE-BKaC4dao.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{useSSE-CY7UIUzm.js.map → useSSE-BKaC4dao.js.map} +1 -1
- package/extensions/dashboard-client/dist/index.html +2 -2
- package/extensions/dashboard-client/package-lock.json +56 -39
- package/extensions/dashboard-client/package.json +3 -0
- package/extensions/dashboard-client/src/api/client.ts +11 -0
- package/extensions/dashboard-client/src/components/SessionContextGauges.tsx +14 -8
- package/extensions/dashboard-client/src/components/WidgetDetailModal.tsx +69 -0
- package/extensions/dashboard-client/src/components/charts/PerfBarChart.tsx +89 -0
- package/extensions/dashboard-client/src/components/charts/PerfLineChart.tsx +108 -0
- package/extensions/dashboard-client/src/components/ui/SortableCard.tsx +75 -0
- package/extensions/dashboard-client/src/hooks/useCardPositions.ts +74 -0
- package/extensions/dashboard-client/src/styles/session-gauges.css +28 -15
- package/extensions/dashboard-client/src/tabs/OverviewTab.tsx +310 -79
- package/extensions/dashboard-client/src/types/card.ts +40 -0
- package/extensions/dashboard-server/api-contracts/endpoints/registry.ts +10 -0
- package/extensions/dashboard-server/api-contracts/endpoints/types.ts +36 -0
- package/extensions/dashboard-server/api-contracts/endpoints.ts +3 -0
- package/extensions/dashboard-server/api-contracts/index.ts +3 -0
- package/extensions/dashboard-server/resolve-snapshot.ts +22 -10
- package/extensions/dashboard-server/routes-game.ts +72 -0
- package/extensions/dashboard-server/routes.ts +1 -0
- package/extensions/dashboard-server/server.ts +2 -0
- package/package.json +1 -1
- package/extensions/dashboard-client/dist/assets/AreaChart-BUUketyS.js +0 -70
- package/extensions/dashboard-client/dist/assets/AreaChart-BUUketyS.js.map +0 -1
- package/extensions/dashboard-client/dist/assets/MetricsTab-Dd1kXuvg.js +0 -2
- package/extensions/dashboard-client/dist/assets/MetricsTab-Dd1kXuvg.js.map +0 -1
- package/extensions/dashboard-client/dist/assets/OverviewTab-DOpObVA9.js +0 -2
- package/extensions/dashboard-client/dist/assets/OverviewTab-DOpObVA9.js.map +0 -1
- package/extensions/dashboard-client/dist/assets/SessionsTab-CcFzDJ7W.js +0 -2
- package/extensions/dashboard-client/dist/assets/WikiTab-RHoVt95g.js +0 -2
- package/extensions/dashboard-client/dist/assets/index-B1_J_Y0b.js.map +0 -1
- package/extensions/dashboard-client/dist/assets/index-C5TGTOd6.css +0 -1
|
@@ -8,6 +8,9 @@
|
|
|
8
8
|
"name": "pi-mega-compact-dashboard-client",
|
|
9
9
|
"version": "0.8.9",
|
|
10
10
|
"dependencies": {
|
|
11
|
+
"@dnd-kit/core": "^6.3.1",
|
|
12
|
+
"@dnd-kit/sortable": "^10.0.0",
|
|
13
|
+
"@dnd-kit/utilities": "^3.2.2",
|
|
11
14
|
"@radix-ui/react-select": "^2.1.0",
|
|
12
15
|
"@radix-ui/react-switch": "^1.1.0",
|
|
13
16
|
"@radix-ui/react-tabs": "^1.1.0",
|
|
@@ -335,6 +338,59 @@
|
|
|
335
338
|
"node": ">=6.9.0"
|
|
336
339
|
}
|
|
337
340
|
},
|
|
341
|
+
"node_modules/@dnd-kit/accessibility": {
|
|
342
|
+
"version": "3.1.1",
|
|
343
|
+
"resolved": "https://registry.npmjs.org/@dnd-kit/accessibility/-/accessibility-3.1.1.tgz",
|
|
344
|
+
"integrity": "sha512-2P+YgaXF+gRsIihwwY1gCsQSYnu9Zyj2py8kY5fFvUM1qm2WA2u639R6YNVfU4GWr+ZM5mqEsfHZZLoRONbemw==",
|
|
345
|
+
"license": "MIT",
|
|
346
|
+
"dependencies": {
|
|
347
|
+
"tslib": "^2.0.0"
|
|
348
|
+
},
|
|
349
|
+
"peerDependencies": {
|
|
350
|
+
"react": ">=16.8.0"
|
|
351
|
+
}
|
|
352
|
+
},
|
|
353
|
+
"node_modules/@dnd-kit/core": {
|
|
354
|
+
"version": "6.3.1",
|
|
355
|
+
"resolved": "https://registry.npmjs.org/@dnd-kit/core/-/core-6.3.1.tgz",
|
|
356
|
+
"integrity": "sha512-xkGBRQQab4RLwgXxoqETICr6S5JlogafbhNsidmrkVv2YRs5MLwpjoF2qpiGjQt8S9AoxtIV603s0GIUpY5eYQ==",
|
|
357
|
+
"license": "MIT",
|
|
358
|
+
"dependencies": {
|
|
359
|
+
"@dnd-kit/accessibility": "^3.1.1",
|
|
360
|
+
"@dnd-kit/utilities": "^3.2.2",
|
|
361
|
+
"tslib": "^2.0.0"
|
|
362
|
+
},
|
|
363
|
+
"peerDependencies": {
|
|
364
|
+
"react": ">=16.8.0",
|
|
365
|
+
"react-dom": ">=16.8.0"
|
|
366
|
+
}
|
|
367
|
+
},
|
|
368
|
+
"node_modules/@dnd-kit/sortable": {
|
|
369
|
+
"version": "10.0.0",
|
|
370
|
+
"resolved": "https://registry.npmjs.org/@dnd-kit/sortable/-/sortable-10.0.0.tgz",
|
|
371
|
+
"integrity": "sha512-+xqhmIIzvAYMGfBYYnbKuNicfSsk4RksY2XdmJhT+HAC01nix6fHCztU68jooFiMUB01Ky3F0FyOvhG/BZrWkg==",
|
|
372
|
+
"license": "MIT",
|
|
373
|
+
"dependencies": {
|
|
374
|
+
"@dnd-kit/utilities": "^3.2.2",
|
|
375
|
+
"tslib": "^2.0.0"
|
|
376
|
+
},
|
|
377
|
+
"peerDependencies": {
|
|
378
|
+
"@dnd-kit/core": "^6.3.0",
|
|
379
|
+
"react": ">=16.8.0"
|
|
380
|
+
}
|
|
381
|
+
},
|
|
382
|
+
"node_modules/@dnd-kit/utilities": {
|
|
383
|
+
"version": "3.2.2",
|
|
384
|
+
"resolved": "https://registry.npmjs.org/@dnd-kit/utilities/-/utilities-3.2.2.tgz",
|
|
385
|
+
"integrity": "sha512-+MKAJEOfaBe5SmV6t34p80MMKhjvUz0vRrvVJbPT0WElzaOJ/1xs+D+KDv+tD/NE5ujfrChEcshd4fLn0wpiqg==",
|
|
386
|
+
"license": "MIT",
|
|
387
|
+
"dependencies": {
|
|
388
|
+
"tslib": "^2.0.0"
|
|
389
|
+
},
|
|
390
|
+
"peerDependencies": {
|
|
391
|
+
"react": ">=16.8.0"
|
|
392
|
+
}
|
|
393
|
+
},
|
|
338
394
|
"node_modules/@esbuild/aix-ppc64": {
|
|
339
395
|
"version": "0.21.5",
|
|
340
396
|
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz",
|
|
@@ -1595,9 +1651,6 @@
|
|
|
1595
1651
|
"arm"
|
|
1596
1652
|
],
|
|
1597
1653
|
"dev": true,
|
|
1598
|
-
"libc": [
|
|
1599
|
-
"glibc"
|
|
1600
|
-
],
|
|
1601
1654
|
"license": "MIT",
|
|
1602
1655
|
"optional": true,
|
|
1603
1656
|
"os": [
|
|
@@ -1612,9 +1665,6 @@
|
|
|
1612
1665
|
"arm"
|
|
1613
1666
|
],
|
|
1614
1667
|
"dev": true,
|
|
1615
|
-
"libc": [
|
|
1616
|
-
"musl"
|
|
1617
|
-
],
|
|
1618
1668
|
"license": "MIT",
|
|
1619
1669
|
"optional": true,
|
|
1620
1670
|
"os": [
|
|
@@ -1629,9 +1679,6 @@
|
|
|
1629
1679
|
"arm64"
|
|
1630
1680
|
],
|
|
1631
1681
|
"dev": true,
|
|
1632
|
-
"libc": [
|
|
1633
|
-
"glibc"
|
|
1634
|
-
],
|
|
1635
1682
|
"license": "MIT",
|
|
1636
1683
|
"optional": true,
|
|
1637
1684
|
"os": [
|
|
@@ -1646,9 +1693,6 @@
|
|
|
1646
1693
|
"arm64"
|
|
1647
1694
|
],
|
|
1648
1695
|
"dev": true,
|
|
1649
|
-
"libc": [
|
|
1650
|
-
"musl"
|
|
1651
|
-
],
|
|
1652
1696
|
"license": "MIT",
|
|
1653
1697
|
"optional": true,
|
|
1654
1698
|
"os": [
|
|
@@ -1663,9 +1707,6 @@
|
|
|
1663
1707
|
"loong64"
|
|
1664
1708
|
],
|
|
1665
1709
|
"dev": true,
|
|
1666
|
-
"libc": [
|
|
1667
|
-
"glibc"
|
|
1668
|
-
],
|
|
1669
1710
|
"license": "MIT",
|
|
1670
1711
|
"optional": true,
|
|
1671
1712
|
"os": [
|
|
@@ -1680,9 +1721,6 @@
|
|
|
1680
1721
|
"loong64"
|
|
1681
1722
|
],
|
|
1682
1723
|
"dev": true,
|
|
1683
|
-
"libc": [
|
|
1684
|
-
"musl"
|
|
1685
|
-
],
|
|
1686
1724
|
"license": "MIT",
|
|
1687
1725
|
"optional": true,
|
|
1688
1726
|
"os": [
|
|
@@ -1697,9 +1735,6 @@
|
|
|
1697
1735
|
"ppc64"
|
|
1698
1736
|
],
|
|
1699
1737
|
"dev": true,
|
|
1700
|
-
"libc": [
|
|
1701
|
-
"glibc"
|
|
1702
|
-
],
|
|
1703
1738
|
"license": "MIT",
|
|
1704
1739
|
"optional": true,
|
|
1705
1740
|
"os": [
|
|
@@ -1714,9 +1749,6 @@
|
|
|
1714
1749
|
"ppc64"
|
|
1715
1750
|
],
|
|
1716
1751
|
"dev": true,
|
|
1717
|
-
"libc": [
|
|
1718
|
-
"musl"
|
|
1719
|
-
],
|
|
1720
1752
|
"license": "MIT",
|
|
1721
1753
|
"optional": true,
|
|
1722
1754
|
"os": [
|
|
@@ -1731,9 +1763,6 @@
|
|
|
1731
1763
|
"riscv64"
|
|
1732
1764
|
],
|
|
1733
1765
|
"dev": true,
|
|
1734
|
-
"libc": [
|
|
1735
|
-
"glibc"
|
|
1736
|
-
],
|
|
1737
1766
|
"license": "MIT",
|
|
1738
1767
|
"optional": true,
|
|
1739
1768
|
"os": [
|
|
@@ -1748,9 +1777,6 @@
|
|
|
1748
1777
|
"riscv64"
|
|
1749
1778
|
],
|
|
1750
1779
|
"dev": true,
|
|
1751
|
-
"libc": [
|
|
1752
|
-
"musl"
|
|
1753
|
-
],
|
|
1754
1780
|
"license": "MIT",
|
|
1755
1781
|
"optional": true,
|
|
1756
1782
|
"os": [
|
|
@@ -1765,9 +1791,6 @@
|
|
|
1765
1791
|
"s390x"
|
|
1766
1792
|
],
|
|
1767
1793
|
"dev": true,
|
|
1768
|
-
"libc": [
|
|
1769
|
-
"glibc"
|
|
1770
|
-
],
|
|
1771
1794
|
"license": "MIT",
|
|
1772
1795
|
"optional": true,
|
|
1773
1796
|
"os": [
|
|
@@ -1782,9 +1805,6 @@
|
|
|
1782
1805
|
"x64"
|
|
1783
1806
|
],
|
|
1784
1807
|
"dev": true,
|
|
1785
|
-
"libc": [
|
|
1786
|
-
"glibc"
|
|
1787
|
-
],
|
|
1788
1808
|
"license": "MIT",
|
|
1789
1809
|
"optional": true,
|
|
1790
1810
|
"os": [
|
|
@@ -1799,9 +1819,6 @@
|
|
|
1799
1819
|
"x64"
|
|
1800
1820
|
],
|
|
1801
1821
|
"dev": true,
|
|
1802
|
-
"libc": [
|
|
1803
|
-
"musl"
|
|
1804
|
-
],
|
|
1805
1822
|
"license": "MIT",
|
|
1806
1823
|
"optional": true,
|
|
1807
1824
|
"os": [
|
|
@@ -11,6 +11,9 @@
|
|
|
11
11
|
"typecheck": "tsc --noEmit"
|
|
12
12
|
},
|
|
13
13
|
"dependencies": {
|
|
14
|
+
"@dnd-kit/core": "^6.3.1",
|
|
15
|
+
"@dnd-kit/sortable": "^10.0.0",
|
|
16
|
+
"@dnd-kit/utilities": "^3.2.2",
|
|
14
17
|
"@radix-ui/react-select": "^2.1.0",
|
|
15
18
|
"@radix-ui/react-switch": "^1.1.0",
|
|
16
19
|
"@radix-ui/react-tabs": "^1.1.0",
|
|
@@ -25,6 +25,7 @@ import type {
|
|
|
25
25
|
GameScoresQuery,
|
|
26
26
|
PerfResponse,
|
|
27
27
|
PerfQuery,
|
|
28
|
+
PerfSamplesResponse,
|
|
28
29
|
AchievementRow,
|
|
29
30
|
SessionsResponse,
|
|
30
31
|
SessionTimeseriesResponse,
|
|
@@ -192,6 +193,16 @@ export function fetchPerf(params: PerfQuery = {}): Promise<PerfResponse> {
|
|
|
192
193
|
);
|
|
193
194
|
}
|
|
194
195
|
|
|
196
|
+
/** GET /api/perf/samples — raw perf samples for one kind (chart drill-down). */
|
|
197
|
+
export function fetchPerfSamples(
|
|
198
|
+
kind: string,
|
|
199
|
+
minutes?: number,
|
|
200
|
+
): Promise<PerfSamplesResponse> {
|
|
201
|
+
return getJson<PerfSamplesResponse>(
|
|
202
|
+
`${ENDPOINTS.perfSamples.path}${query({ kind, minutes })}`,
|
|
203
|
+
);
|
|
204
|
+
}
|
|
205
|
+
|
|
195
206
|
export function fetchAchievements(): Promise<AchievementRow[]> {
|
|
196
207
|
return getJson<AchievementRow[]>(ENDPOINTS.achievements.path);
|
|
197
208
|
}
|
|
@@ -78,7 +78,7 @@ interface GaugeData {
|
|
|
78
78
|
isSelf: boolean;
|
|
79
79
|
}
|
|
80
80
|
|
|
81
|
-
/** A single
|
|
81
|
+
/** A single readable gauge: header label, fill bar, tokens/window sublabel. */
|
|
82
82
|
function SessionGauge({ data }: { data: GaugeData }): React.ReactElement {
|
|
83
83
|
const pct = data.percent ?? 0;
|
|
84
84
|
const fillWidth = Math.max(pct, 1);
|
|
@@ -89,7 +89,11 @@ function SessionGauge({ data }: { data: GaugeData }): React.ReactElement {
|
|
|
89
89
|
return (
|
|
90
90
|
<div className={`session-gauge ${data.isSelf ? "session-gauge-self" : ""}`}>
|
|
91
91
|
<div className="session-gauge-head">
|
|
92
|
-
|
|
92
|
+
{/* Full repo/session name by default; truncate only as a last
|
|
93
|
+
* resort (ellipsis + hover title) when space is genuinely tight. */}
|
|
94
|
+
<span className="session-gauge-label truncate" title={data.label}>
|
|
95
|
+
{data.label}
|
|
96
|
+
</span>
|
|
93
97
|
<span className="session-gauge-pct">{pct}%</span>
|
|
94
98
|
</div>
|
|
95
99
|
<div
|
|
@@ -103,7 +107,7 @@ function SessionGauge({ data }: { data: GaugeData }): React.ReactElement {
|
|
|
103
107
|
<div className="gauge-fill" style={{ width: `${fillWidth}%` }} />
|
|
104
108
|
</div>
|
|
105
109
|
<p className="gauge-label session-gauge-sub">{sublabel}</p>
|
|
106
|
-
{data.sublabel && <p className="session-gauge-meta">{data.sublabel}</p>}
|
|
110
|
+
{data.sublabel && <p className="session-gauge-meta truncate" title={data.sublabel}>{data.sublabel}</p>}
|
|
107
111
|
</div>
|
|
108
112
|
);
|
|
109
113
|
}
|
|
@@ -154,7 +158,7 @@ export function SessionContextGauges({
|
|
|
154
158
|
const showLoading = loading && !sessions;
|
|
155
159
|
|
|
156
160
|
return (
|
|
157
|
-
<Card className="electric-hover">
|
|
161
|
+
<Card className="electric-hover session-gauges-card w-full">
|
|
158
162
|
<CardHeader>
|
|
159
163
|
<CardTitle>Context per Session (live)</CardTitle>
|
|
160
164
|
</CardHeader>
|
|
@@ -166,10 +170,12 @@ export function SessionContextGauges({
|
|
|
166
170
|
) : showLoading ? (
|
|
167
171
|
<div className="text-sm text-muted-foreground">Loading sessions…</div>
|
|
168
172
|
) : (
|
|
169
|
-
<div className="
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
+
<div className="session-gauges-scroll">
|
|
174
|
+
<div className="session-gauges-grid">
|
|
175
|
+
{gauges.map((g) => (
|
|
176
|
+
<SessionGauge key={g.key} data={g} />
|
|
177
|
+
))}
|
|
178
|
+
</div>
|
|
173
179
|
</div>
|
|
174
180
|
)}
|
|
175
181
|
</CardContent>
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dashboard-client/src/components/WidgetDetailModal.tsx — reusable drill-down modal.
|
|
3
|
+
*
|
|
4
|
+
* Wraps the RepoDetailModal overlay pattern (fixed backdrop, escape-key close,
|
|
5
|
+
* click-backdrop close) into a generic container so Overview cards can open a
|
|
6
|
+
* bar/line chart over time. Children render inside a scrollable panel.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type React from "react";
|
|
10
|
+
import { useEffect } from "react";
|
|
11
|
+
import { X } from "lucide-react";
|
|
12
|
+
|
|
13
|
+
export interface WidgetDetailModalProps {
|
|
14
|
+
/** Modal heading shown in the panel header. */
|
|
15
|
+
title: string;
|
|
16
|
+
/** Whether the modal is open (mounted). */
|
|
17
|
+
open: boolean;
|
|
18
|
+
/** Called when the user closes via Escape, close button, or backdrop click. */
|
|
19
|
+
onClose: () => void;
|
|
20
|
+
/** Modal body (e.g. a chart component). */
|
|
21
|
+
children: React.ReactNode;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function WidgetDetailModal({
|
|
25
|
+
title,
|
|
26
|
+
open,
|
|
27
|
+
onClose,
|
|
28
|
+
children,
|
|
29
|
+
}: WidgetDetailModalProps): React.ReactElement {
|
|
30
|
+
// Close on Escape key while the panel is open.
|
|
31
|
+
useEffect(() => {
|
|
32
|
+
if (!open) return;
|
|
33
|
+
const handler = (e: KeyboardEvent): void => {
|
|
34
|
+
if (e.key === "Escape") onClose();
|
|
35
|
+
};
|
|
36
|
+
window.addEventListener("keydown", handler);
|
|
37
|
+
return () => window.removeEventListener("keydown", handler);
|
|
38
|
+
}, [open, onClose]);
|
|
39
|
+
|
|
40
|
+
if (!open) return <></>;
|
|
41
|
+
|
|
42
|
+
return (
|
|
43
|
+
<div
|
|
44
|
+
className="fixed inset-0 z-50 flex items-center justify-center bg-black/60 p-4"
|
|
45
|
+
onClick={onClose}
|
|
46
|
+
role="dialog"
|
|
47
|
+
aria-modal="true"
|
|
48
|
+
aria-label={title}
|
|
49
|
+
>
|
|
50
|
+
<div
|
|
51
|
+
className="max-h-[80vh] w-full max-w-3xl overflow-y-auto rounded-lg border border-border bg-bg-card p-5"
|
|
52
|
+
onClick={(e) => e.stopPropagation()}
|
|
53
|
+
>
|
|
54
|
+
<header className="mb-4 flex items-center justify-between gap-4">
|
|
55
|
+
<h2 className="font-heading text-lg font-semibold">{title}</h2>
|
|
56
|
+
<button
|
|
57
|
+
type="button"
|
|
58
|
+
className="text-muted-foreground transition-colors hover:text-foreground"
|
|
59
|
+
onClick={onClose}
|
|
60
|
+
aria-label="Close"
|
|
61
|
+
>
|
|
62
|
+
<X size={22} strokeWidth={2} />
|
|
63
|
+
</button>
|
|
64
|
+
</header>
|
|
65
|
+
{children}
|
|
66
|
+
</div>
|
|
67
|
+
</div>
|
|
68
|
+
);
|
|
69
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dashboard-client/src/components/charts/PerfBarChart.tsx — reusable perf bar chart.
|
|
3
|
+
*
|
|
4
|
+
* Recharts BarChart with the dashboard dark theme (AXIS "#8b949e",
|
|
5
|
+
* GRID "#30363d", dark tooltip). Renders `{label, value}` buckets.
|
|
6
|
+
* Handles empty data with a "No data available" message.
|
|
7
|
+
*
|
|
8
|
+
* PREVENT-PI-004: recharts is bundled into this localhost-served static
|
|
9
|
+
* dashboard bundle; it makes no runtime network calls.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import type React from "react";
|
|
13
|
+
import {
|
|
14
|
+
ResponsiveContainer,
|
|
15
|
+
BarChart,
|
|
16
|
+
Bar,
|
|
17
|
+
XAxis,
|
|
18
|
+
YAxis,
|
|
19
|
+
CartesianGrid,
|
|
20
|
+
Tooltip,
|
|
21
|
+
} from "recharts";
|
|
22
|
+
|
|
23
|
+
export interface PerfBarChartProps {
|
|
24
|
+
/** Buckets with a display label + numeric value. */
|
|
25
|
+
data: Array<{ label: string; value: number }>;
|
|
26
|
+
/** Bar color (default dashboard green). */
|
|
27
|
+
color?: string;
|
|
28
|
+
/** Chart height in px (default 280). */
|
|
29
|
+
height?: number;
|
|
30
|
+
/** Optional formatter for values (e.g. "$" prefix, thousands separators). */
|
|
31
|
+
valueFormatter?: (v: number) => string;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const AXIS_COLOR = "#8b949e";
|
|
35
|
+
const GRID_COLOR = "#30363d";
|
|
36
|
+
const TOOLTIP_STYLE = {
|
|
37
|
+
background: "#161b22",
|
|
38
|
+
border: "1px solid #30363d",
|
|
39
|
+
borderRadius: "6px",
|
|
40
|
+
color: "#e6edf3",
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
export function PerfBarChart({
|
|
44
|
+
data,
|
|
45
|
+
color = "#3fb950",
|
|
46
|
+
height = 280,
|
|
47
|
+
valueFormatter,
|
|
48
|
+
}: PerfBarChartProps): React.ReactElement {
|
|
49
|
+
const fmt = valueFormatter ?? ((v: number) => String(v));
|
|
50
|
+
|
|
51
|
+
if (!data || data.length === 0) {
|
|
52
|
+
return <p className="py-8 text-center text-sm text-muted-foreground">No data available</p>;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return (
|
|
56
|
+
<ResponsiveContainer width="100%" height={height}>
|
|
57
|
+
<BarChart data={data} margin={{ top: 8, right: 16, left: 0, bottom: 0 }}>
|
|
58
|
+
<CartesianGrid strokeDasharray="3 3" stroke={GRID_COLOR} />
|
|
59
|
+
<XAxis
|
|
60
|
+
dataKey="label"
|
|
61
|
+
stroke={AXIS_COLOR}
|
|
62
|
+
tick={{ fontSize: 11 }}
|
|
63
|
+
interval={0}
|
|
64
|
+
angle={-20}
|
|
65
|
+
textAnchor="end"
|
|
66
|
+
height={56}
|
|
67
|
+
/>
|
|
68
|
+
<YAxis
|
|
69
|
+
dataKey="value"
|
|
70
|
+
stroke={AXIS_COLOR}
|
|
71
|
+
tick={{ fontSize: 11 }}
|
|
72
|
+
width={56}
|
|
73
|
+
tickFormatter={(v: number) => fmt(v)}
|
|
74
|
+
/>
|
|
75
|
+
<Tooltip
|
|
76
|
+
formatter={(value) => [fmt(value as number), "value"] as [string, string]}
|
|
77
|
+
contentStyle={TOOLTIP_STYLE}
|
|
78
|
+
labelStyle={{ color: AXIS_COLOR, fontSize: 12 }}
|
|
79
|
+
/>
|
|
80
|
+
<Bar
|
|
81
|
+
dataKey="value"
|
|
82
|
+
fill={color}
|
|
83
|
+
radius={[4, 4, 0, 0]}
|
|
84
|
+
isAnimationActive={false}
|
|
85
|
+
/>
|
|
86
|
+
</BarChart>
|
|
87
|
+
</ResponsiveContainer>
|
|
88
|
+
);
|
|
89
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dashboard-client/src/components/charts/PerfLineChart.tsx — reusable perf line chart.
|
|
3
|
+
*
|
|
4
|
+
* Recharts LineChart with the dashboard dark theme (AXIS "#8b949e",
|
|
5
|
+
* GRID "#30363d", dark tooltip). Renders `{ts, value}` samples over time.
|
|
6
|
+
* Handles empty data with a "No data available" message.
|
|
7
|
+
*
|
|
8
|
+
* PREVENT-PI-004: recharts is bundled into this localhost-served static
|
|
9
|
+
* dashboard bundle; it makes no runtime network calls.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import type React from "react";
|
|
13
|
+
import {
|
|
14
|
+
ResponsiveContainer,
|
|
15
|
+
LineChart,
|
|
16
|
+
Line,
|
|
17
|
+
XAxis,
|
|
18
|
+
YAxis,
|
|
19
|
+
CartesianGrid,
|
|
20
|
+
Tooltip,
|
|
21
|
+
} from "recharts";
|
|
22
|
+
|
|
23
|
+
export interface PerfLineChartProps {
|
|
24
|
+
/** Time-series samples ascending by ts (epoch ms). */
|
|
25
|
+
data: Array<{ ts: number; value: number }>;
|
|
26
|
+
/** Optional series label shown in the tooltip. */
|
|
27
|
+
label?: string;
|
|
28
|
+
/** Line + gradient color (default dashboard blue). */
|
|
29
|
+
color?: string;
|
|
30
|
+
/** Chart height in px (default 280). */
|
|
31
|
+
height?: number;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const AXIS_COLOR = "#8b949e";
|
|
35
|
+
const GRID_COLOR = "#30363d";
|
|
36
|
+
const TOOLTIP_STYLE = {
|
|
37
|
+
background: "#161b22",
|
|
38
|
+
border: "1px solid #30363d",
|
|
39
|
+
borderRadius: "6px",
|
|
40
|
+
color: "#e6edf3",
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
function fmtTs(ts: number): string {
|
|
44
|
+
return new Date(ts).toLocaleTimeString();
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function fmtVal(v: number): string {
|
|
48
|
+
return Number.isFinite(v) ? String(v) : String(v);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function PerfLineChart({
|
|
52
|
+
data,
|
|
53
|
+
label = "value",
|
|
54
|
+
color = "#58a6ff",
|
|
55
|
+
height = 280,
|
|
56
|
+
}: PerfLineChartProps): React.ReactElement {
|
|
57
|
+
if (!data || data.length === 0) {
|
|
58
|
+
return <p className="py-8 text-center text-sm text-muted-foreground">No data available</p>;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const gradId = `perf-line-${color.replace(/[^a-zA-Z0-9]/g, "")}`;
|
|
62
|
+
|
|
63
|
+
return (
|
|
64
|
+
<ResponsiveContainer width="100%" height={height}>
|
|
65
|
+
<LineChart data={data} margin={{ top: 8, right: 16, left: 0, bottom: 0 }}>
|
|
66
|
+
<defs>
|
|
67
|
+
<linearGradient id={gradId} x1="0" y1="0" x2="0" y2="1">
|
|
68
|
+
<stop offset="5%" stopColor={color} stopOpacity={0.7} />
|
|
69
|
+
<stop offset="95%" stopColor={color} stopOpacity={0.15} />
|
|
70
|
+
</linearGradient>
|
|
71
|
+
</defs>
|
|
72
|
+
<CartesianGrid strokeDasharray="3 3" stroke={GRID_COLOR} />
|
|
73
|
+
<XAxis
|
|
74
|
+
dataKey="ts"
|
|
75
|
+
type="number"
|
|
76
|
+
domain={["dataMin", "dataMax"]}
|
|
77
|
+
scale="time"
|
|
78
|
+
tickFormatter={(ts: number) => fmtTs(ts)}
|
|
79
|
+
stroke={AXIS_COLOR}
|
|
80
|
+
tick={{ fontSize: 11 }}
|
|
81
|
+
/>
|
|
82
|
+
<YAxis
|
|
83
|
+
dataKey="value"
|
|
84
|
+
stroke={AXIS_COLOR}
|
|
85
|
+
tick={{ fontSize: 11 }}
|
|
86
|
+
width={56}
|
|
87
|
+
tickFormatter={(v: number) => fmtVal(v)}
|
|
88
|
+
/>
|
|
89
|
+
<Tooltip
|
|
90
|
+
labelFormatter={(ts: number) => fmtTs(ts)}
|
|
91
|
+
formatter={(value) => [fmtVal(value as number), label] as [string, string]}
|
|
92
|
+
contentStyle={TOOLTIP_STYLE}
|
|
93
|
+
labelStyle={{ color: AXIS_COLOR, fontSize: 12 }}
|
|
94
|
+
/>
|
|
95
|
+
<Line
|
|
96
|
+
type="monotone"
|
|
97
|
+
dataKey="value"
|
|
98
|
+
name={label}
|
|
99
|
+
stroke={color}
|
|
100
|
+
strokeWidth={2}
|
|
101
|
+
dot={false}
|
|
102
|
+
fill={`url(#${gradId})`}
|
|
103
|
+
isAnimationActive={false}
|
|
104
|
+
/>
|
|
105
|
+
</LineChart>
|
|
106
|
+
</ResponsiveContainer>
|
|
107
|
+
);
|
|
108
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dashboard-client/src/components/ui/SortableCard.tsx — sortable wrapper for
|
|
3
|
+
* Overview grid cards.
|
|
4
|
+
*
|
|
5
|
+
* Drag isolation (Plexus PR #59 pattern): the sortable listeners live ONLY on
|
|
6
|
+
* the grip icon. The card surface is not directly draggable — it keeps its
|
|
7
|
+
* onClick (drill-down) and stays keyboard-accessible.
|
|
8
|
+
*/
|
|
9
|
+
import type React from "react";
|
|
10
|
+
import { useSortable } from "@dnd-kit/sortable";
|
|
11
|
+
import { CSS } from "@dnd-kit/utilities";
|
|
12
|
+
import { GripVertical } from "lucide-react";
|
|
13
|
+
import type { CardId } from "../../types/card";
|
|
14
|
+
|
|
15
|
+
export interface SortableCardProps {
|
|
16
|
+
id: CardId;
|
|
17
|
+
children: React.ReactNode;
|
|
18
|
+
onClick?: () => void;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function SortableCard({
|
|
22
|
+
id,
|
|
23
|
+
children,
|
|
24
|
+
onClick,
|
|
25
|
+
}: SortableCardProps): React.ReactElement {
|
|
26
|
+
const {
|
|
27
|
+
attributes,
|
|
28
|
+
listeners,
|
|
29
|
+
setNodeRef,
|
|
30
|
+
transform,
|
|
31
|
+
transition,
|
|
32
|
+
isDragging,
|
|
33
|
+
} = useSortable({ id });
|
|
34
|
+
|
|
35
|
+
const style: React.CSSProperties = {
|
|
36
|
+
transform: CSS.Transform.toString(transform),
|
|
37
|
+
transition,
|
|
38
|
+
opacity: isDragging ? 0.5 : 1,
|
|
39
|
+
zIndex: isDragging ? 10 : undefined,
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const handleKeyDown = (e: React.KeyboardEvent) => {
|
|
43
|
+
if (onClick && (e.key === "Enter" || e.key === " ")) {
|
|
44
|
+
e.preventDefault();
|
|
45
|
+
onClick();
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
return (
|
|
50
|
+
<div ref={setNodeRef} style={style} className="relative">
|
|
51
|
+
<button
|
|
52
|
+
type="button"
|
|
53
|
+
aria-label={`Reorder ${id} card`}
|
|
54
|
+
className="absolute right-2 top-2 z-10 cursor-grab rounded p-1 text-muted-foreground/50 hover:bg-foreground/5 hover:text-muted-foreground active:cursor-grabbing"
|
|
55
|
+
{...attributes}
|
|
56
|
+
{...listeners}
|
|
57
|
+
>
|
|
58
|
+
<GripVertical size={16} />
|
|
59
|
+
</button>
|
|
60
|
+
{onClick ? (
|
|
61
|
+
<div
|
|
62
|
+
role="button"
|
|
63
|
+
tabIndex={0}
|
|
64
|
+
onClick={onClick}
|
|
65
|
+
onKeyDown={handleKeyDown}
|
|
66
|
+
className="cursor-pointer rounded-lg outline-none transition-all duration-200 hover:ring-1 hover:ring-primary/60 focus-visible:ring-1 focus-visible:ring-primary/60"
|
|
67
|
+
>
|
|
68
|
+
{children}
|
|
69
|
+
</div>
|
|
70
|
+
) : (
|
|
71
|
+
<div>{children}</div>
|
|
72
|
+
)}
|
|
73
|
+
</div>
|
|
74
|
+
);
|
|
75
|
+
}
|