wip: system stats, move to ws
This commit is contained in:
parent
87dd81688b
commit
2e86e827ca
10
package.json
10
package.json
|
@ -5,7 +5,7 @@
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "next build",
|
"build": "next build",
|
||||||
"db:push": "drizzle-kit push:sqlite",
|
"db:push": "drizzle-kit push:sqlite",
|
||||||
"dev": "next dev",
|
"dev": "tsx watch src/server/server.ts",
|
||||||
"lint": "next lint",
|
"lint": "next lint",
|
||||||
"start": "next start"
|
"start": "next start"
|
||||||
},
|
},
|
||||||
|
@ -24,15 +24,18 @@
|
||||||
"@trpc/server": "^10.43.1",
|
"@trpc/server": "^10.43.1",
|
||||||
"argon2": "^0.31.2",
|
"argon2": "^0.31.2",
|
||||||
"better-sqlite3": "^9.0.0",
|
"better-sqlite3": "^9.0.0",
|
||||||
|
"bufferutil": "^4.0.8",
|
||||||
"bunyan": "^1.8.15",
|
"bunyan": "^1.8.15",
|
||||||
"bunyan-format": "^0.2.1",
|
"bunyan-format": "^0.2.1",
|
||||||
"class-variance-authority": "^0.7.0",
|
"class-variance-authority": "^0.7.0",
|
||||||
"clsx": "^2.0.0",
|
"clsx": "^2.0.0",
|
||||||
"date-fns": "^2.30.0",
|
"date-fns": "^2.30.0",
|
||||||
|
"dotenv": "^16.3.1",
|
||||||
"drizzle-orm": "^0.28.6",
|
"drizzle-orm": "^0.28.6",
|
||||||
"ipaddr.js": "^2.1.0",
|
"ipaddr.js": "^2.1.0",
|
||||||
"next": "^14.0.1",
|
"next": "^14.0.1",
|
||||||
"next-themes": "^0.2.1",
|
"next-themes": "^0.2.1",
|
||||||
|
"node-os-utils": "^1.3.7",
|
||||||
"react": "18.2.0",
|
"react": "18.2.0",
|
||||||
"react-dom": "18.2.0",
|
"react-dom": "18.2.0",
|
||||||
"react-icons": "^4.11.0",
|
"react-icons": "^4.11.0",
|
||||||
|
@ -42,7 +45,9 @@
|
||||||
"tailwind-merge": "^2.0.0",
|
"tailwind-merge": "^2.0.0",
|
||||||
"tailwindcss-animate": "^1.0.7",
|
"tailwindcss-animate": "^1.0.7",
|
||||||
"ts-permissions": "^1.0.0",
|
"ts-permissions": "^1.0.0",
|
||||||
|
"uWebSockets.js": "github:uNetworking/uWebSockets.js#v20.33.0",
|
||||||
"ua-parser-js": "^1.0.37",
|
"ua-parser-js": "^1.0.37",
|
||||||
|
"ws": "^8.14.2",
|
||||||
"zod": "^3.22.4"
|
"zod": "^3.22.4"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
@ -51,9 +56,11 @@
|
||||||
"@types/bunyan-format": "^0.2.7",
|
"@types/bunyan-format": "^0.2.7",
|
||||||
"@types/eslint": "^8.44.6",
|
"@types/eslint": "^8.44.6",
|
||||||
"@types/node": "^20.8.10",
|
"@types/node": "^20.8.10",
|
||||||
|
"@types/node-os-utils": "^1.3.4",
|
||||||
"@types/react": "^18.2.35",
|
"@types/react": "^18.2.35",
|
||||||
"@types/react-dom": "^18.2.14",
|
"@types/react-dom": "^18.2.14",
|
||||||
"@types/ua-parser-js": "^0.7.38",
|
"@types/ua-parser-js": "^0.7.38",
|
||||||
|
"@types/ws": "^8.5.9",
|
||||||
"@typescript-eslint/eslint-plugin": "^6.9.1",
|
"@typescript-eslint/eslint-plugin": "^6.9.1",
|
||||||
"@typescript-eslint/parser": "^6.9.1",
|
"@typescript-eslint/parser": "^6.9.1",
|
||||||
"autoprefixer": "^10.4.16",
|
"autoprefixer": "^10.4.16",
|
||||||
|
@ -64,6 +71,7 @@
|
||||||
"prettier": "^3.0.3",
|
"prettier": "^3.0.3",
|
||||||
"prettier-plugin-tailwindcss": "^0.5.6",
|
"prettier-plugin-tailwindcss": "^0.5.6",
|
||||||
"tailwindcss": "^3.3.5",
|
"tailwindcss": "^3.3.5",
|
||||||
|
"tsx": "^3.14.0",
|
||||||
"typescript": "^5.2.2"
|
"typescript": "^5.2.2"
|
||||||
},
|
},
|
||||||
"ct3aMetadata": {
|
"ct3aMetadata": {
|
||||||
|
|
|
@ -47,6 +47,9 @@ dependencies:
|
||||||
better-sqlite3:
|
better-sqlite3:
|
||||||
specifier: ^9.0.0
|
specifier: ^9.0.0
|
||||||
version: 9.0.0
|
version: 9.0.0
|
||||||
|
bufferutil:
|
||||||
|
specifier: ^4.0.8
|
||||||
|
version: 4.0.8
|
||||||
bunyan:
|
bunyan:
|
||||||
specifier: ^1.8.15
|
specifier: ^1.8.15
|
||||||
version: 1.8.15
|
version: 1.8.15
|
||||||
|
@ -62,6 +65,9 @@ dependencies:
|
||||||
date-fns:
|
date-fns:
|
||||||
specifier: ^2.30.0
|
specifier: ^2.30.0
|
||||||
version: 2.30.0
|
version: 2.30.0
|
||||||
|
dotenv:
|
||||||
|
specifier: ^16.3.1
|
||||||
|
version: 16.3.1
|
||||||
drizzle-orm:
|
drizzle-orm:
|
||||||
specifier: ^0.28.6
|
specifier: ^0.28.6
|
||||||
version: 0.28.6(@types/better-sqlite3@7.6.6)(better-sqlite3@9.0.0)(pg@8.11.3)
|
version: 0.28.6(@types/better-sqlite3@7.6.6)(better-sqlite3@9.0.0)(pg@8.11.3)
|
||||||
|
@ -74,6 +80,9 @@ dependencies:
|
||||||
next-themes:
|
next-themes:
|
||||||
specifier: ^0.2.1
|
specifier: ^0.2.1
|
||||||
version: 0.2.1(next@14.0.1)(react-dom@18.2.0)(react@18.2.0)
|
version: 0.2.1(next@14.0.1)(react-dom@18.2.0)(react@18.2.0)
|
||||||
|
node-os-utils:
|
||||||
|
specifier: ^1.3.7
|
||||||
|
version: 1.3.7
|
||||||
react:
|
react:
|
||||||
specifier: 18.2.0
|
specifier: 18.2.0
|
||||||
version: 18.2.0
|
version: 18.2.0
|
||||||
|
@ -101,9 +110,15 @@ dependencies:
|
||||||
ts-permissions:
|
ts-permissions:
|
||||||
specifier: ^1.0.0
|
specifier: ^1.0.0
|
||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
|
uWebSockets.js:
|
||||||
|
specifier: github:uNetworking/uWebSockets.js#v20.33.0
|
||||||
|
version: github.com/uNetworking/uWebSockets.js/c10b47c350cc97c299c6b4a07a98abb628704c71
|
||||||
ua-parser-js:
|
ua-parser-js:
|
||||||
specifier: ^1.0.37
|
specifier: ^1.0.37
|
||||||
version: 1.0.37
|
version: 1.0.37
|
||||||
|
ws:
|
||||||
|
specifier: ^8.14.2
|
||||||
|
version: 8.14.2(bufferutil@4.0.8)
|
||||||
zod:
|
zod:
|
||||||
specifier: ^3.22.4
|
specifier: ^3.22.4
|
||||||
version: 3.22.4
|
version: 3.22.4
|
||||||
|
@ -124,6 +139,9 @@ devDependencies:
|
||||||
'@types/node':
|
'@types/node':
|
||||||
specifier: ^20.8.10
|
specifier: ^20.8.10
|
||||||
version: 20.8.10
|
version: 20.8.10
|
||||||
|
'@types/node-os-utils':
|
||||||
|
specifier: ^1.3.4
|
||||||
|
version: 1.3.4
|
||||||
'@types/react':
|
'@types/react':
|
||||||
specifier: ^18.2.35
|
specifier: ^18.2.35
|
||||||
version: 18.2.35
|
version: 18.2.35
|
||||||
|
@ -133,6 +151,9 @@ devDependencies:
|
||||||
'@types/ua-parser-js':
|
'@types/ua-parser-js':
|
||||||
specifier: ^0.7.38
|
specifier: ^0.7.38
|
||||||
version: 0.7.38
|
version: 0.7.38
|
||||||
|
'@types/ws':
|
||||||
|
specifier: ^8.5.9
|
||||||
|
version: 8.5.9
|
||||||
'@typescript-eslint/eslint-plugin':
|
'@typescript-eslint/eslint-plugin':
|
||||||
specifier: ^6.9.1
|
specifier: ^6.9.1
|
||||||
version: 6.9.1(@typescript-eslint/parser@6.9.1)(eslint@8.53.0)(typescript@5.2.2)
|
version: 6.9.1(@typescript-eslint/parser@6.9.1)(eslint@8.53.0)(typescript@5.2.2)
|
||||||
|
@ -163,6 +184,9 @@ devDependencies:
|
||||||
tailwindcss:
|
tailwindcss:
|
||||||
specifier: ^3.3.5
|
specifier: ^3.3.5
|
||||||
version: 3.3.5
|
version: 3.3.5
|
||||||
|
tsx:
|
||||||
|
specifier: ^3.14.0
|
||||||
|
version: 3.14.0
|
||||||
typescript:
|
typescript:
|
||||||
specifier: ^5.2.2
|
specifier: ^5.2.2
|
||||||
version: 5.2.2
|
version: 5.2.2
|
||||||
|
@ -1328,6 +1352,10 @@ packages:
|
||||||
resolution: {integrity: sha512-AuHIyzR5Hea7ij0P9q7vx7xu4z0C28ucwjAZC0ja7JhINyCnOw8/DnvAPQQ9TfOlCtZAmCERKQX9+o1mgQhuOQ==}
|
resolution: {integrity: sha512-AuHIyzR5Hea7ij0P9q7vx7xu4z0C28ucwjAZC0ja7JhINyCnOw8/DnvAPQQ9TfOlCtZAmCERKQX9+o1mgQhuOQ==}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/@types/node-os-utils@1.3.4:
|
||||||
|
resolution: {integrity: sha512-BCUYrbdoO4FUbx6MB9atLNFnkxdliFaxdiTJMIPPiecXIApc5zf4NIqV5G1jWv/ReZvtYyHLs40RkBjHX+vykA==}
|
||||||
|
dev: true
|
||||||
|
|
||||||
/@types/node@17.0.45:
|
/@types/node@17.0.45:
|
||||||
resolution: {integrity: sha512-w+tIMs3rq2afQdsPJlODhoUEKzFP1ayaoyl1CcnwtIlsVe7K7bA1NGm4s3PraqTLlXnbIN84zuBlxBWo1u9BLw==}
|
resolution: {integrity: sha512-w+tIMs3rq2afQdsPJlODhoUEKzFP1ayaoyl1CcnwtIlsVe7K7bA1NGm4s3PraqTLlXnbIN84zuBlxBWo1u9BLw==}
|
||||||
dev: false
|
dev: false
|
||||||
|
@ -1382,6 +1410,12 @@ packages:
|
||||||
'@types/webidl-conversions': 7.0.2
|
'@types/webidl-conversions': 7.0.2
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/@types/ws@8.5.9:
|
||||||
|
resolution: {integrity: sha512-jbdrY0a8lxfdTp/+r7Z4CkycbOFN8WX+IOchLJr3juT/xzbJ8URyTVSJ/hvNdadTgM1mnedb47n+Y31GsFnQlg==}
|
||||||
|
dependencies:
|
||||||
|
'@types/node': 20.8.10
|
||||||
|
dev: true
|
||||||
|
|
||||||
/@typescript-eslint/eslint-plugin@6.9.1(@typescript-eslint/parser@6.9.1)(eslint@8.53.0)(typescript@5.2.2):
|
/@typescript-eslint/eslint-plugin@6.9.1(@typescript-eslint/parser@6.9.1)(eslint@8.53.0)(typescript@5.2.2):
|
||||||
resolution: {integrity: sha512-w0tiiRc9I4S5XSXXrMHOWgHgxbrBn1Ro+PmiYhSg2ZVdxrAJtQgzU5o2m1BfP6UOn7Vxcc6152vFjQfmZR4xEg==}
|
resolution: {integrity: sha512-w0tiiRc9I4S5XSXXrMHOWgHgxbrBn1Ro+PmiYhSg2ZVdxrAJtQgzU5o2m1BfP6UOn7Vxcc6152vFjQfmZR4xEg==}
|
||||||
engines: {node: ^16.0.0 || >=18.0.0}
|
engines: {node: ^16.0.0 || >=18.0.0}
|
||||||
|
@ -1919,6 +1953,14 @@ packages:
|
||||||
ieee754: 1.2.1
|
ieee754: 1.2.1
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/bufferutil@4.0.8:
|
||||||
|
resolution: {integrity: sha512-4T53u4PdgsXqKaIctwF8ifXlRTTmEPJ8iEPWFdGZvcf7sbwYo6FKFEX9eNNAnzFZ7EzJAQ3CJeOtCRA4rDp7Pw==}
|
||||||
|
engines: {node: '>=6.14.2'}
|
||||||
|
requiresBuild: true
|
||||||
|
dependencies:
|
||||||
|
node-gyp-build: 4.6.1
|
||||||
|
dev: false
|
||||||
|
|
||||||
/bunyan-format@0.2.1:
|
/bunyan-format@0.2.1:
|
||||||
resolution: {integrity: sha512-xQs2LwWskjQdv7bVkMNwvMi7HnvDQoX4587H90nDGQGPPwHrmxsihBOIYHMVwjLMMOokITKPyFcbFneblvMEjQ==}
|
resolution: {integrity: sha512-xQs2LwWskjQdv7bVkMNwvMi7HnvDQoX4587H90nDGQGPPwHrmxsihBOIYHMVwjLMMOokITKPyFcbFneblvMEjQ==}
|
||||||
dependencies:
|
dependencies:
|
||||||
|
@ -2442,6 +2484,11 @@ packages:
|
||||||
engines: {node: '>=12'}
|
engines: {node: '>=12'}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/dotenv@16.3.1:
|
||||||
|
resolution: {integrity: sha512-IPzF4w4/Rd94bA9imS68tZBaYyBWSCE47V1RGuMrB94iyTOIEwRmVL2x/4An+6mETpLrKJ5hQkB8W4kFAadeIQ==}
|
||||||
|
engines: {node: '>=12'}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/dreamopt@0.8.0:
|
/dreamopt@0.8.0:
|
||||||
resolution: {integrity: sha512-vyJTp8+mC+G+5dfgsY+r3ckxlz+QMX40VjPQsZc5gxVAxLmi64TBoVkP54A/pRAXMXsbu2GMMBrZPxNv23waMg==}
|
resolution: {integrity: sha512-vyJTp8+mC+G+5dfgsY+r3ckxlz+QMX40VjPQsZc5gxVAxLmi64TBoVkP54A/pRAXMXsbu2GMMBrZPxNv23waMg==}
|
||||||
engines: {node: '>=0.4.0'}
|
engines: {node: '>=0.4.0'}
|
||||||
|
@ -4519,6 +4566,15 @@ packages:
|
||||||
whatwg-url: 5.0.0
|
whatwg-url: 5.0.0
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/node-gyp-build@4.6.1:
|
||||||
|
resolution: {integrity: sha512-24vnklJmyRS8ViBNI8KbtK/r/DmXQMRiOMXTNz2nrTnAYUwjmEEbnnpB/+kt+yWRv73bPsSPRFddrcIbAxSiMQ==}
|
||||||
|
hasBin: true
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/node-os-utils@1.3.7:
|
||||||
|
resolution: {integrity: sha512-fvnX9tZbR7WfCG5BAy3yO/nCLyjVWD6MghEq0z5FDfN+ZXpLWNITBdbifxQkQ25ebr16G0N7eRWJisOcMEHG3Q==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/node-releases@2.0.13:
|
/node-releases@2.0.13:
|
||||||
resolution: {integrity: sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ==}
|
resolution: {integrity: sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ==}
|
||||||
dev: true
|
dev: true
|
||||||
|
@ -6031,6 +6087,17 @@ packages:
|
||||||
resolution: {integrity: sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==}
|
resolution: {integrity: sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/tsx@3.14.0:
|
||||||
|
resolution: {integrity: sha512-xHtFaKtHxM9LOklMmJdI3BEnQq/D5F73Of2E1GDrITi9sgoVkvIsrQUTY1G8FlmGtA+awCI4EBlTRRYxkL2sRg==}
|
||||||
|
hasBin: true
|
||||||
|
dependencies:
|
||||||
|
esbuild: 0.18.20
|
||||||
|
get-tsconfig: 4.7.2
|
||||||
|
source-map-support: 0.5.21
|
||||||
|
optionalDependencies:
|
||||||
|
fsevents: 2.3.3
|
||||||
|
dev: true
|
||||||
|
|
||||||
/tunnel-agent@0.6.0:
|
/tunnel-agent@0.6.0:
|
||||||
resolution: {integrity: sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==}
|
resolution: {integrity: sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==}
|
||||||
dependencies:
|
dependencies:
|
||||||
|
@ -6320,6 +6387,21 @@ packages:
|
||||||
/wrappy@1.0.2:
|
/wrappy@1.0.2:
|
||||||
resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==}
|
resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==}
|
||||||
|
|
||||||
|
/ws@8.14.2(bufferutil@4.0.8):
|
||||||
|
resolution: {integrity: sha512-wEBG1ftX4jcglPxgFCMJmZ2PLtSbJ2Peg6TmpJFTbe9GZYOQCDPdMYu/Tm0/bGZkw8paZnJY45J4K2PZrLYq8g==}
|
||||||
|
engines: {node: '>=10.0.0'}
|
||||||
|
peerDependencies:
|
||||||
|
bufferutil: ^4.0.1
|
||||||
|
utf-8-validate: '>=5.0.2'
|
||||||
|
peerDependenciesMeta:
|
||||||
|
bufferutil:
|
||||||
|
optional: true
|
||||||
|
utf-8-validate:
|
||||||
|
optional: true
|
||||||
|
dependencies:
|
||||||
|
bufferutil: 4.0.8
|
||||||
|
dev: false
|
||||||
|
|
||||||
/xtend@2.1.2:
|
/xtend@2.1.2:
|
||||||
resolution: {integrity: sha512-vMNKzr2rHP9Dp/e1NQFnLQlwlhp9L/LfvnsVdHxN1f+uggyVI3i08uD14GPvCToPkdsRfyPqIyYGmIk58V98ZQ==}
|
resolution: {integrity: sha512-vMNKzr2rHP9Dp/e1NQFnLQlwlhp9L/LfvnsVdHxN1f+uggyVI3i08uD14GPvCToPkdsRfyPqIyYGmIk58V98ZQ==}
|
||||||
engines: {node: '>=0.4'}
|
engines: {node: '>=0.4'}
|
||||||
|
@ -6354,3 +6436,9 @@ packages:
|
||||||
|
|
||||||
/zod@3.22.4:
|
/zod@3.22.4:
|
||||||
resolution: {integrity: sha512-iC+8Io04lddc+mVqQ9AZ7OQ2MrUKGN+oIQyq1vemgt46jwCwLfhq7/pwnBnNXXXZb8VTVLKwp9EDkx+ryxIWmg==}
|
resolution: {integrity: sha512-iC+8Io04lddc+mVqQ9AZ7OQ2MrUKGN+oIQyq1vemgt46jwCwLfhq7/pwnBnNXXXZb8VTVLKwp9EDkx+ryxIWmg==}
|
||||||
|
|
||||||
|
github.com/uNetworking/uWebSockets.js/c10b47c350cc97c299c6b4a07a98abb628704c71:
|
||||||
|
resolution: {tarball: https://codeload.github.com/uNetworking/uWebSockets.js/tar.gz/c10b47c350cc97c299c6b4a07a98abb628704c71}
|
||||||
|
name: uWebSockets.js
|
||||||
|
version: 20.33.0
|
||||||
|
dev: false
|
||||||
|
|
|
@ -2,48 +2,81 @@
|
||||||
|
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from "~/components/ui/card";
|
import { Card, CardContent, CardHeader, CardTitle } from "~/components/ui/card";
|
||||||
import { RiPulseFill } from "react-icons/ri";
|
import { RiPulseFill } from "react-icons/ri";
|
||||||
import { LineChart, Line, ResponsiveContainer } from "recharts";
|
import { ResponsiveContainer, AreaChart, Area } from "recharts";
|
||||||
import styles from "./StatCard.module.css";
|
import styles from "./StatCard.module.css";
|
||||||
|
|
||||||
const TEST_DATA = [
|
export function StatCard<T extends { [key: string]: number }>(props: {
|
||||||
{ cpu: 0.05 },
|
title: string;
|
||||||
{ cpu: 0.1 },
|
value: string;
|
||||||
{ cpu: 0.08 },
|
subvalue: string;
|
||||||
{ cpu: 0.09 },
|
icon: React.FC<{ className: string }>;
|
||||||
{ cpu: 0.2 },
|
data: T[];
|
||||||
{ cpu: 0.1 },
|
dataKey: keyof T & string;
|
||||||
{ cpu: 0.12 },
|
}) {
|
||||||
{ cpu: 0.3 },
|
const rechartsColorId = `color${props.dataKey}`;
|
||||||
];
|
const Icon = props.icon;
|
||||||
|
|
||||||
export function StatCard() {
|
|
||||||
return (
|
return (
|
||||||
<Card>
|
<Card className="relative overflow-clip">
|
||||||
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
||||||
<CardTitle>CPU Usage</CardTitle>
|
<CardTitle>{props.title}</CardTitle>
|
||||||
<RiPulseFill className="text-2xl text-muted-foreground" />
|
<Icon className="text-2xl text-muted-foreground" />
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
|
|
||||||
<CardContent className="relative">
|
<CardContent>
|
||||||
{/* chart background */}
|
{/* chart background */}
|
||||||
<div className="absolute inset-0 z-0 h-full w-full">
|
<div className="absolute inset-0 z-0 h-full w-full">
|
||||||
<ResponsiveContainer width={"100%"} height={"100%"}>
|
<ResponsiveContainer
|
||||||
<LineChart data={TEST_DATA}>
|
width={"100%"}
|
||||||
<Line
|
height={"50%"}
|
||||||
|
className="absolute bottom-0 left-0"
|
||||||
|
>
|
||||||
|
<AreaChart
|
||||||
|
data={props.data}
|
||||||
|
margin={{
|
||||||
|
top: 0,
|
||||||
|
right: 0,
|
||||||
|
left: 0,
|
||||||
|
bottom: 0,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<defs>
|
||||||
|
<linearGradient
|
||||||
|
id={rechartsColorId}
|
||||||
|
x1="0"
|
||||||
|
y1="0"
|
||||||
|
x2="0"
|
||||||
|
y2="1"
|
||||||
|
>
|
||||||
|
<stop
|
||||||
|
offset="5%"
|
||||||
|
stopColor="hsl(var(--primary))"
|
||||||
|
stopOpacity={0.8}
|
||||||
|
/>
|
||||||
|
<stop
|
||||||
|
offset="95%"
|
||||||
|
stopColor="hsl(var(--primary))"
|
||||||
|
stopOpacity={0.4}
|
||||||
|
/>
|
||||||
|
</linearGradient>
|
||||||
|
</defs>
|
||||||
|
|
||||||
|
<Area
|
||||||
type="monotone"
|
type="monotone"
|
||||||
dataKey={"cpu"}
|
dataKey={props.dataKey}
|
||||||
stroke="hsl(var(--border))"
|
stroke="hsl(var(--primary))"
|
||||||
|
fill={`url(#${rechartsColorId})`}
|
||||||
strokeWidth={2}
|
strokeWidth={2}
|
||||||
dot={false}
|
dot={false}
|
||||||
/>
|
/>
|
||||||
</LineChart>
|
</AreaChart>
|
||||||
</ResponsiveContainer>
|
</ResponsiveContainer>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className={`relative z-10 ${styles["stat-card"]}`}>
|
<div className={`relative z-10 ${styles["stat-card"]}`}>
|
||||||
<p className="stroke stroke-card text-2xl font-bold">4.5%</p>
|
<p className="stroke stroke-card text-2xl font-bold">{props.value}</p>
|
||||||
<p className="stroke stroke-card text-sm text-muted-foreground">
|
<p className="stroke stroke-card text-sm text-muted-foreground">
|
||||||
of 8 CPUs
|
{props.subvalue}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
|
|
66
src/app/home/SystemStatistics.tsx
Normal file
66
src/app/home/SystemStatistics.tsx
Normal file
|
@ -0,0 +1,66 @@
|
||||||
|
"use client";
|
||||||
|
|
||||||
|
import { api } from "~/trpc/react";
|
||||||
|
import { StatCard } from "./StatCard";
|
||||||
|
import { RouterOutputs } from "~/trpc/shared";
|
||||||
|
import { RiPulseFill } from "react-icons/ri";
|
||||||
|
|
||||||
|
const TEST_DATA = [
|
||||||
|
{ cpu: 0.05 },
|
||||||
|
{ cpu: 0.1 },
|
||||||
|
{ cpu: 0.08 },
|
||||||
|
{ cpu: 0.09 },
|
||||||
|
{ cpu: 0.2 },
|
||||||
|
{ cpu: 0.1 },
|
||||||
|
{ cpu: 0.12 },
|
||||||
|
{ cpu: 0.3 },
|
||||||
|
];
|
||||||
|
|
||||||
|
export function SystemStatistics(props: {
|
||||||
|
initialData: RouterOutputs["system"]["current"];
|
||||||
|
}) {
|
||||||
|
const { data } = api.system.current.useQuery(undefined, {
|
||||||
|
initialData: props.initialData,
|
||||||
|
refetchInterval: 5_000,
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="m-8 grid grid-cols-1 gap-8 sm:grid-cols-2 lg:grid-cols-4">
|
||||||
|
<StatCard
|
||||||
|
title="CPU Usage"
|
||||||
|
value={`${data.cpu.usage ?? 0}%`}
|
||||||
|
subvalue={`of ${data.cpu.cores} CPUs`}
|
||||||
|
icon={RiPulseFill}
|
||||||
|
data={TEST_DATA}
|
||||||
|
dataKey="cpu"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<StatCard
|
||||||
|
title="Memory Usage"
|
||||||
|
value={`${((data.memory.used / data.memory.total) * 100).toFixed(2)}%`}
|
||||||
|
subvalue={`${data.memory.used.toFixed(2)} / ${data.memory.total.toFixed(
|
||||||
|
2,
|
||||||
|
)} GB`}
|
||||||
|
icon={RiPulseFill}
|
||||||
|
data={TEST_DATA}
|
||||||
|
dataKey="cpu"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<StatCard
|
||||||
|
title="Disk Usage"
|
||||||
|
value={`${((data.storage.used / data.storage.total) * 100).toFixed(
|
||||||
|
2,
|
||||||
|
)}%`}
|
||||||
|
subvalue={`${data.storage.used.toFixed(
|
||||||
|
2,
|
||||||
|
)} / ${data.storage.total.toFixed(2)} GB`}
|
||||||
|
icon={RiPulseFill}
|
||||||
|
data={TEST_DATA}
|
||||||
|
dataKey="cpu"
|
||||||
|
/>
|
||||||
|
{/* <StatCard />
|
||||||
|
<StatCard />
|
||||||
|
<StatCard /> */}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
|
@ -1,17 +1,15 @@
|
||||||
import { api } from "~/trpc/server";
|
import { api } from "~/trpc/server";
|
||||||
import Test from "./RSC";
|
import Test from "./RSC";
|
||||||
import { StatCard } from "./StatCard";
|
import { StatCard } from "./StatCard";
|
||||||
|
import { SystemStatistics } from "./SystemStatistics";
|
||||||
|
|
||||||
export default async function DashboardHome() {
|
export default async function DashboardHome() {
|
||||||
|
const initialStats = await api.system.current.query();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="mx-auto max-w-[1500px]">
|
<div className="mx-auto max-w-[1500px]">
|
||||||
<Test />
|
<Test />
|
||||||
<div className="m-8 grid grid-cols-1 gap-8 sm:grid-cols-2 lg:grid-cols-4">
|
<SystemStatistics initialData={initialStats} />
|
||||||
<StatCard />
|
|
||||||
<StatCard />
|
|
||||||
<StatCard />
|
|
||||||
<StatCard />
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,7 +14,9 @@ export const env = createEnv({
|
||||||
.default("development"),
|
.default("development"),
|
||||||
|
|
||||||
SQLITE_UUIDV7_EXT_PATH: z.string().optional(),
|
SQLITE_UUIDV7_EXT_PATH: z.string().optional(),
|
||||||
SESSION_SECRET: z.string(),
|
SESSION_SECRET: z.string().min(8),
|
||||||
|
HOSTNAME: z.string().default("localhost"),
|
||||||
|
PORT: z.number().default(3000),
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -35,6 +37,8 @@ export const env = createEnv({
|
||||||
NODE_ENV: process.env.NODE_ENV,
|
NODE_ENV: process.env.NODE_ENV,
|
||||||
SQLITE_UUIDV7_EXT_PATH: process.env.SQLITE_UUIDV7_EXT_PATH,
|
SQLITE_UUIDV7_EXT_PATH: process.env.SQLITE_UUIDV7_EXT_PATH,
|
||||||
SESSION_SECRET: process.env.SESSION_SECRET,
|
SESSION_SECRET: process.env.SESSION_SECRET,
|
||||||
|
HOSTNAME: process.env.HOSTNAME,
|
||||||
|
PORT: process.env.PORT,
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* Run `build` or `dev` with `SKIP_ENV_VALIDATION` to skip env validation. This is especially
|
* Run `build` or `dev` with `SKIP_ENV_VALIDATION` to skip env validation. This is especially
|
||||||
|
|
|
@ -1,37 +0,0 @@
|
||||||
import { createPermissionsClass } from "ts-permissions";
|
|
||||||
|
|
||||||
enum ProjectPermissionsEnum {
|
|
||||||
/**
|
|
||||||
* Can do anything **including** deleting the project.
|
|
||||||
*/
|
|
||||||
Owner,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Can do anything **except** delete the project.
|
|
||||||
*/
|
|
||||||
Administrator,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Can add/remove members and grant permissions (lower than their own) to members, with an exception to Owners.
|
|
||||||
*/
|
|
||||||
ManageMembers,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Can create and view services.
|
|
||||||
*/
|
|
||||||
CreateService,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Can only view services.
|
|
||||||
*/
|
|
||||||
ViewServices,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Can edit and delete services.
|
|
||||||
*/
|
|
||||||
ManageServices,
|
|
||||||
}
|
|
||||||
|
|
||||||
export const ProjectPermissions = createPermissionsClass<
|
|
||||||
typeof ProjectPermissionsEnum
|
|
||||||
>(ProjectPermissionsEnum, "ProjectPermissions");
|
|
|
@ -1,6 +1,7 @@
|
||||||
import { createTRPCRouter } from "~/server/api/trpc";
|
import { createTRPCRouter } from "~/server/api/trpc";
|
||||||
import { setupProcedure } from "./routers/setup";
|
import { setupProcedure } from "./routers/setup";
|
||||||
import { authRouter } from "./routers/auth";
|
import { authRouter } from "./routers/auth";
|
||||||
|
import { systemRouter } from "./routers/system";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This is the primary router for your server.
|
* This is the primary router for your server.
|
||||||
|
@ -10,6 +11,7 @@ import { authRouter } from "./routers/auth";
|
||||||
export const appRouter = createTRPCRouter({
|
export const appRouter = createTRPCRouter({
|
||||||
setup: setupProcedure,
|
setup: setupProcedure,
|
||||||
auth: authRouter,
|
auth: authRouter,
|
||||||
|
system: systemRouter,
|
||||||
});
|
});
|
||||||
|
|
||||||
// export type definition of API
|
// export type definition of API
|
||||||
|
|
30
src/server/api/routers/system/index.ts
Normal file
30
src/server/api/routers/system/index.ts
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
import { authenticatedProcedure, createTRPCRouter } from "../../trpc";
|
||||||
|
import os from "os";
|
||||||
|
import osu from "node-os-utils";
|
||||||
|
|
||||||
|
export const systemRouter = createTRPCRouter({
|
||||||
|
current: authenticatedProcedure.query(async ({ ctx }) => {
|
||||||
|
const [cpuUsage, storage, memory] = await Promise.all([
|
||||||
|
osu.cpu.usage(),
|
||||||
|
osu.drive.info("/"),
|
||||||
|
osu.mem.info(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
return {
|
||||||
|
cpu: {
|
||||||
|
usage: cpuUsage,
|
||||||
|
cores: os.cpus().length,
|
||||||
|
},
|
||||||
|
|
||||||
|
storage: {
|
||||||
|
used: parseInt(storage.usedGb),
|
||||||
|
total: parseInt(storage.totalGb),
|
||||||
|
},
|
||||||
|
|
||||||
|
memory: {
|
||||||
|
used: memory.usedMemMb / 1024,
|
||||||
|
total: memory.totalMemMb / 1024,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
});
|
74
src/server/server.ts
Normal file
74
src/server/server.ts
Normal file
|
@ -0,0 +1,74 @@
|
||||||
|
import "dotenv/config";
|
||||||
|
|
||||||
|
import next from "next";
|
||||||
|
import { env } from "~/env.mjs";
|
||||||
|
import { createServer } from "http";
|
||||||
|
import logger from "./utils/logger";
|
||||||
|
import ws from "ws";
|
||||||
|
import { applyWSSHandler } from "@trpc/server/adapters/ws";
|
||||||
|
import { appRouter } from "./api/root";
|
||||||
|
import { createTRPCContext } from "./api/trpc";
|
||||||
|
import { incomingRequestToNextRequest } from "./utils/serverUtils";
|
||||||
|
|
||||||
|
async function startApp() {
|
||||||
|
// initialize the next app
|
||||||
|
const app = next({
|
||||||
|
dev: env.NODE_ENV !== "production",
|
||||||
|
hostname: env.HOSTNAME,
|
||||||
|
port: env.PORT,
|
||||||
|
});
|
||||||
|
|
||||||
|
await app.prepare();
|
||||||
|
|
||||||
|
// get the handles
|
||||||
|
const getHandler = app.getRequestHandler();
|
||||||
|
const upgradeHandler = app.getUpgradeHandler();
|
||||||
|
|
||||||
|
// create the http server
|
||||||
|
const server = createServer((req, res) => {
|
||||||
|
try {
|
||||||
|
// handle the request
|
||||||
|
getHandler(req, res);
|
||||||
|
} catch (error) {
|
||||||
|
logger.error(error);
|
||||||
|
res.statusCode = 500;
|
||||||
|
res.end("Internal Server Error");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// create the websocket server
|
||||||
|
const wss = new ws.Server({ noServer: true });
|
||||||
|
const trpcHandler = applyWSSHandler({
|
||||||
|
wss,
|
||||||
|
router: appRouter,
|
||||||
|
createContext: ({ req }) =>
|
||||||
|
createTRPCContext({
|
||||||
|
req: incomingRequestToNextRequest(req),
|
||||||
|
resHeaders: new Headers(),
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
process.on("SIGTERM", () => {
|
||||||
|
trpcHandler.broadcastReconnectNotification();
|
||||||
|
server.close(() => {
|
||||||
|
process.exit(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// handle the upgrade
|
||||||
|
server.on("upgrade", (req, socket, head) => {
|
||||||
|
// send trpc requests to the trpc server
|
||||||
|
if (req.url?.startsWith("/api/trpc")) {
|
||||||
|
wss.handleUpgrade(req, socket, head, () => {});
|
||||||
|
} else {
|
||||||
|
upgradeHandler(req, socket, head);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// start the server
|
||||||
|
server.listen(env.PORT, () => {
|
||||||
|
logger.info(`Server listening on port ${env.PORT}`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
startApp();
|
39
src/server/utils/serverUtils.ts
Normal file
39
src/server/utils/serverUtils.ts
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
import assert from "assert";
|
||||||
|
import { IncomingMessage } from "http";
|
||||||
|
import { NextRequest } from "next/server";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Turns an node:http IncomingMessage into a next.js request
|
||||||
|
* @param req The incoming request
|
||||||
|
*/
|
||||||
|
export function incomingRequestToNextRequest(req: IncomingMessage) {
|
||||||
|
// copy headers to a new Headers object
|
||||||
|
const headers = new Headers();
|
||||||
|
for (const [key, value] of Object.entries(req.headers)) {
|
||||||
|
headers.set(key, value as string);
|
||||||
|
}
|
||||||
|
|
||||||
|
// fix the body
|
||||||
|
let body: ReadableStream<Uint8Array> | undefined = undefined;
|
||||||
|
if (req.method === "POST") {
|
||||||
|
body = new ReadableStream({
|
||||||
|
start(controller) {
|
||||||
|
req.on("data", (chunk) => {
|
||||||
|
controller.enqueue(chunk);
|
||||||
|
});
|
||||||
|
|
||||||
|
req.on("end", () => {
|
||||||
|
controller.close();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// create the web request
|
||||||
|
assert(req.url, "req.url is undefined");
|
||||||
|
return new NextRequest(req.url, {
|
||||||
|
method: req.method,
|
||||||
|
body,
|
||||||
|
headers,
|
||||||
|
});
|
||||||
|
}
|
9
tsconfig.server.json
Normal file
9
tsconfig.server.json
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
{
|
||||||
|
"extends": "./tsconfig.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"module": "commonjs",
|
||||||
|
"outDir": "dist",
|
||||||
|
"noEmit": false
|
||||||
|
},
|
||||||
|
"include": ["./src/**/*"]
|
||||||
|
}
|
Loading…
Reference in a new issue