feat: stat cards, new logging
This commit is contained in:
parent
a7fb919cda
commit
d5caddcb22
95
logs/combined.log
Normal file
95
logs/combined.log
Normal file
|
@ -0,0 +1,95 @@
|
|||
2023-11-12T01:48:57.462Z info: Not running database migrations, use drizzle-kit push to migrate
|
||||
2023-11-12T01:48:57.463Z info: 🚀 Hostforge v1.0.0 ready!
|
||||
2023-11-12T01:48:57.469Z info: Server listening on port 3000
|
||||
2023-11-12T01:49:11.893Z warn: SIGTERM received, shutting down...
|
||||
2023-11-12T01:49:17.625Z [32minfo[39m: Not running database migrations, use drizzle-kit push to migrate
|
||||
2023-11-12T01:49:17.627Z [32minfo[39m: 🚀 Hostforge v1.0.0 ready!
|
||||
2023-11-12T01:49:17.635Z [32minfo[39m: Server listening on port 3000
|
||||
2023-11-12T01:50:31.777Z [33mwarn[39m: SIGTERM received, shutting down...
|
||||
{"level":"info","message":"Not running database migrations, use drizzle-kit push to migrate","module":"database"}
|
||||
{"level":"info","message":"🚀 Hostforge v1.0.0 ready!"}
|
||||
{"level":"info","message":"Server listening on port 3000"}
|
||||
{"level":"warn","message":"SIGTERM received, shutting down..."}
|
||||
{"level":"info","message":"Not running database migrations, use drizzle-kit push to migrate","module":"database"}
|
||||
{"level":"info","message":"🚀 Hostforge v1.0.0 ready!"}
|
||||
{"level":"info","message":"Server listening on port 3000"}
|
||||
{"level":"warn","message":"SIGTERM received, shutting down..."}
|
||||
{"level":"info","message":"Not running database migrations, use drizzle-kit push to migrate","module":"database"}
|
||||
{"level":"info","message":"🚀 Hostforge v1.0.0 ready!"}
|
||||
{"level":"info","message":"Server listening on port 3000"}
|
||||
{"level":"warn","message":"SIGTERM received, shutting down..."}
|
||||
{"level":"info","message":"Not running database migrations, use drizzle-kit push to migrate","module":"database"}
|
||||
{"level":"info","message":"🚀 Hostforge v1.0.0 ready!"}
|
||||
{"level":"info","message":"Server listening on port 3000"}
|
||||
{"level":"warn","message":"SIGTERM received, shutting down..."}
|
||||
{"level":"info","message":"Not running database migrations, use drizzle-kit push to migrate","module":"database"}
|
||||
{"level":"info","message":"🚀 Hostforge v1.0.0 ready!"}
|
||||
{"level":"info","message":"Server listening on port 3000"}
|
||||
{"level":"warn","message":"SIGTERM received, shutting down..."}
|
||||
{"level":"info","message":"Not running database migrations, use drizzle-kit push to migrate","module":"database"}
|
||||
{"level":"info","message":"🚀 Hostforge v1.0.0 ready!"}
|
||||
{"level":"info","message":"Server listening on port 3000"}
|
||||
{"level":"warn","message":"SIGTERM received, shutting down..."}
|
||||
{"level":"info","message":"Not running database migrations, use drizzle-kit push to migrate","module":"database"}
|
||||
{"level":"info","message":"🚀 Hostforge v1.0.0 ready!"}
|
||||
{"level":"info","message":"Server listening on port 3000"}
|
||||
{"level":"warn","message":"SIGTERM received, shutting down..."}
|
||||
{"level":"info","message":"Not running database migrations, use drizzle-kit push to migrate","module":"database"}
|
||||
{"level":"info","message":"🚀 Hostforge v1.0.0 ready!"}
|
||||
{"level":"info","message":"Server listening on port 3000"}
|
||||
{"level":"warn","message":"SIGTERM received, shutting down..."}
|
||||
{"level":"info","message":"Not running database migrations, use drizzle-kit push to migrate","module":"database"}
|
||||
{"level":"info","message":"🚀 Hostforge v1.0.0 ready!"}
|
||||
{"level":"info","message":"Server listening on port 3000"}
|
||||
{"level":"warn","message":"SIGTERM received, shutting down..."}
|
||||
{"level":"info","message":"Not running database migrations, use drizzle-kit push to migrate","module":"database"}
|
||||
{"level":"info","message":"🚀 Hostforge v1.0.0 ready!"}
|
||||
{"level":"info","message":"Server listening on port 3000"}
|
||||
{"level":"warn","message":"SIGTERM received, shutting down..."}
|
||||
{"level":"info","message":"Not running database migrations, use drizzle-kit push to migrate","module":"database"}
|
||||
{"level":"info","message":"🚀 Hostforge v1.0.0 ready!"}
|
||||
{"level":"info","message":"Server listening on port 3000"}
|
||||
{"level":"warn","message":"SIGTERM received, shutting down..."}
|
||||
{"level":"info","message":"Not running database migrations, use drizzle-kit push to migrate","module":"database"}
|
||||
{"level":"info","message":"🚀 Hostforge v1.0.0 ready!"}
|
||||
{"level":"info","message":"Server listening on port 3000"}
|
||||
{"level":"warn","message":"SIGTERM received, shutting down..."}
|
||||
{"level":"info","message":"Not running database migrations, use drizzle-kit push to migrate","module":"database"}
|
||||
{"level":"info","message":"Not running database migrations, use drizzle-kit push to migrate","module":"database"}
|
||||
{"level":"info","message":"🚀 Hostforge v1.0.0 ready!"}
|
||||
{"level":"info","message":"Server listening on port 3000"}
|
||||
{"level":"warn","message":"SIGTERM received, shutting down..."}
|
||||
{"level":"info","message":"Not running database migrations, use drizzle-kit push to migrate","module":"database"}
|
||||
{"level":"info","message":"Not running database migrations, use drizzle-kit push to migrate","module":"database"}
|
||||
{"level":"info","message":"🚀 Hostforge"}
|
||||
{"level":"info","message":"Server listening on localhost:3000"}
|
||||
{"level":"info","message":"Version: 0.1.0"}
|
||||
{"level":"info","message":"Environment: development"}
|
||||
{"level":"info","message":"Build commit: unknown"}
|
||||
{"level":"warn","message":"SIGTERM received, shutting down..."}
|
||||
{"level":"info","message":"Not running database migrations, use drizzle-kit push to migrate","module":"database"}
|
||||
{"level":"info","message":"🚀 Hostforge"}
|
||||
{"level":"info","message":" │ Server listening on localhost:3000"}
|
||||
{"level":"info","message":" │ Version: 0.1.0"}
|
||||
{"level":"info","message":" │ Environment: development"}
|
||||
{"level":"info","message":" ╰ Build commit: unknown"}
|
||||
{"level":"warn","message":"SIGTERM received, shutting down..."}
|
||||
{"level":"info","message":"Not running database migrations, use drizzle-kit push to migrate","module":"database"}
|
||||
{"level":"info","message":"🚀 Hostforge"}
|
||||
{"level":"info","message":"│ Server listening on localhost:3000"}
|
||||
{"level":"info","message":"│ Version: 0.1.0"}
|
||||
{"level":"info","message":"│ Environment: development"}
|
||||
{"level":"info","message":"╰ Build commit: unknown"}
|
||||
{"level":"warn","message":"SIGTERM received, shutting down..."}
|
||||
{"level":"info","message":"Not running database migrations, use drizzle-kit push to migrate","module":"database"}
|
||||
{"level":"info","message":"🚀 Hostforge"}
|
||||
{"level":"info","message":"│ Server listening on localhost:3000"}
|
||||
{"level":"info","message":"│ Version: 0.1.0"}
|
||||
{"level":"info","message":"│ Environment: development"}
|
||||
{"level":"info","message":"╰ Build commit: unknown"}
|
||||
{"level":"info","message":"Not running database migrations, use drizzle-kit push to migrate","module":"database"}
|
||||
{"level":"info","message":"🚀 Hostforge"}
|
||||
{"level":"info","message":"│ Server listening on 0.0.0.0:3000"}
|
||||
{"level":"info","message":"│ Version: 0.1.0"}
|
||||
{"level":"info","message":"│ Environment: development"}
|
||||
{"level":"info","message":"╰ Build commit: unknown"}
|
0
logs/error.log
Normal file
0
logs/error.log
Normal file
|
@ -1,8 +1,4 @@
|
|||
/** @type {import("next").NextConfig} */
|
||||
const config = {
|
||||
experimental: {
|
||||
instrumentationHook: true,
|
||||
},
|
||||
};
|
||||
const config = {};
|
||||
|
||||
export default config;
|
||||
|
|
|
@ -30,8 +30,7 @@
|
|||
"argon2": "^0.31.2",
|
||||
"better-sqlite3": "^9.0.0",
|
||||
"bufferutil": "^4.0.8",
|
||||
"bunyan": "^1.8.15",
|
||||
"bunyan-format": "^0.2.1",
|
||||
"chalk": "^5.3.0",
|
||||
"class-variance-authority": "^0.7.0",
|
||||
"clsx": "^2.0.0",
|
||||
"date-fns": "^2.30.0",
|
||||
|
@ -43,7 +42,6 @@
|
|||
"next-themes": "^0.2.1",
|
||||
"node-os-utils": "^1.3.7",
|
||||
"react": "18.2.0",
|
||||
"react-animated-numbers": "^0.16.0",
|
||||
"react-dom": "18.2.0",
|
||||
"react-icons": "^4.11.0",
|
||||
"recharts": "^2.9.3",
|
||||
|
@ -53,13 +51,12 @@
|
|||
"tailwindcss-animate": "^1.0.7",
|
||||
"ts-permissions": "^1.0.0",
|
||||
"ua-parser-js": "^1.0.37",
|
||||
"winston": "^3.11.0",
|
||||
"ws": "^8.14.2",
|
||||
"zod": "^3.22.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/better-sqlite3": "^7.6.7",
|
||||
"@types/bunyan": "^1.8.11",
|
||||
"@types/bunyan-format": "^0.2.8",
|
||||
"@types/eslint": "^8.44.7",
|
||||
"@types/node": "^20.9.0",
|
||||
"@types/node-os-utils": "^1.3.4",
|
||||
|
@ -80,6 +77,7 @@
|
|||
"tailwindcss": "^3.3.5",
|
||||
"tscpaths": "^0.0.9",
|
||||
"tsup": "^7.2.0",
|
||||
"typed-emitter": "^2.1.0",
|
||||
"typescript": "^5.2.2"
|
||||
},
|
||||
"ct3aMetadata": {
|
||||
|
|
359
pnpm-lock.yaml
359
pnpm-lock.yaml
|
@ -50,12 +50,9 @@ dependencies:
|
|||
bufferutil:
|
||||
specifier: ^4.0.8
|
||||
version: 4.0.8
|
||||
bunyan:
|
||||
specifier: ^1.8.15
|
||||
version: 1.8.15
|
||||
bunyan-format:
|
||||
specifier: ^0.2.1
|
||||
version: 0.2.1
|
||||
chalk:
|
||||
specifier: ^5.3.0
|
||||
version: 5.3.0
|
||||
class-variance-authority:
|
||||
specifier: ^0.7.0
|
||||
version: 0.7.0
|
||||
|
@ -89,9 +86,6 @@ dependencies:
|
|||
react:
|
||||
specifier: 18.2.0
|
||||
version: 18.2.0
|
||||
react-animated-numbers:
|
||||
specifier: ^0.16.0
|
||||
version: 0.16.0(react-dom@18.2.0)(react@18.2.0)
|
||||
react-dom:
|
||||
specifier: 18.2.0
|
||||
version: 18.2.0(react@18.2.0)
|
||||
|
@ -119,6 +113,9 @@ dependencies:
|
|||
ua-parser-js:
|
||||
specifier: ^1.0.37
|
||||
version: 1.0.37
|
||||
winston:
|
||||
specifier: ^3.11.0
|
||||
version: 3.11.0
|
||||
ws:
|
||||
specifier: ^8.14.2
|
||||
version: 8.14.2(bufferutil@4.0.8)
|
||||
|
@ -130,12 +127,6 @@ devDependencies:
|
|||
'@types/better-sqlite3':
|
||||
specifier: ^7.6.7
|
||||
version: 7.6.7
|
||||
'@types/bunyan':
|
||||
specifier: ^1.8.11
|
||||
version: 1.8.11
|
||||
'@types/bunyan-format':
|
||||
specifier: ^0.2.8
|
||||
version: 0.2.8
|
||||
'@types/eslint':
|
||||
specifier: ^8.44.7
|
||||
version: 8.44.7
|
||||
|
@ -196,6 +187,9 @@ devDependencies:
|
|||
tsup:
|
||||
specifier: ^7.2.0
|
||||
version: 7.2.0(postcss@8.4.31)(typescript@5.2.2)
|
||||
typed-emitter:
|
||||
specifier: ^2.1.0
|
||||
version: 2.1.0
|
||||
typescript:
|
||||
specifier: ^5.2.2
|
||||
version: 5.2.2
|
||||
|
@ -413,6 +407,19 @@ packages:
|
|||
dependencies:
|
||||
regenerator-runtime: 0.14.0
|
||||
|
||||
/@colors/colors@1.6.0:
|
||||
resolution: {integrity: sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA==}
|
||||
engines: {node: '>=0.1.90'}
|
||||
dev: false
|
||||
|
||||
/@dabh/diagnostics@2.0.3:
|
||||
resolution: {integrity: sha512-hrlQOIi7hAfzsMqlGSFyVucrx38O+j6wiGOf//H2ecvIEqYN4ADBSS2iLMh5UFyDunCNniUIPk/q3riFv45xRA==}
|
||||
dependencies:
|
||||
colorspace: 1.1.4
|
||||
enabled: 2.0.0
|
||||
kuler: 2.0.0
|
||||
dev: false
|
||||
|
||||
/@drizzle-team/studio@0.0.5:
|
||||
resolution: {integrity: sha512-ps5qF0tMxWRVu+V5gvCRrQNqlY92aTnIKdq27gm9LZMSdaKYZt6AVvSK1dlUMzs6Rt0Jm80b+eWct6xShBKhIw==}
|
||||
dev: true
|
||||
|
@ -1144,54 +1151,6 @@ packages:
|
|||
react: 18.2.0
|
||||
dev: false
|
||||
|
||||
/@react-spring/animated@9.7.3(react@18.2.0):
|
||||
resolution: {integrity: sha512-5CWeNJt9pNgyvuSzQH+uy2pvTg8Y4/OisoscZIR8/ZNLIOI+CatFBhGZpDGTF/OzdNFsAoGk3wiUYTwoJ0YIvw==}
|
||||
peerDependencies:
|
||||
react: ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||
dependencies:
|
||||
'@react-spring/shared': 9.7.3(react@18.2.0)
|
||||
'@react-spring/types': 9.7.3
|
||||
react: 18.2.0
|
||||
dev: false
|
||||
|
||||
/@react-spring/core@9.7.3(react@18.2.0):
|
||||
resolution: {integrity: sha512-IqFdPVf3ZOC1Cx7+M0cXf4odNLxDC+n7IN3MDcVCTIOSBfqEcBebSv+vlY5AhM0zw05PDbjKrNmBpzv/AqpjnQ==}
|
||||
peerDependencies:
|
||||
react: ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||
dependencies:
|
||||
'@react-spring/animated': 9.7.3(react@18.2.0)
|
||||
'@react-spring/shared': 9.7.3(react@18.2.0)
|
||||
'@react-spring/types': 9.7.3
|
||||
react: 18.2.0
|
||||
dev: false
|
||||
|
||||
/@react-spring/shared@9.7.3(react@18.2.0):
|
||||
resolution: {integrity: sha512-NEopD+9S5xYyQ0pGtioacLhL2luflh6HACSSDUZOwLHoxA5eku1UPuqcJqjwSD6luKjjLfiLOspxo43FUHKKSA==}
|
||||
peerDependencies:
|
||||
react: ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||
dependencies:
|
||||
'@react-spring/types': 9.7.3
|
||||
react: 18.2.0
|
||||
dev: false
|
||||
|
||||
/@react-spring/types@9.7.3:
|
||||
resolution: {integrity: sha512-Kpx/fQ/ZFX31OtlqVEFfgaD1ACzul4NksrvIgYfIFq9JpDHFwQkMVZ10tbo0FU/grje4rcL4EIrjekl3kYwgWw==}
|
||||
dev: false
|
||||
|
||||
/@react-spring/web@9.7.3(react-dom@18.2.0)(react@18.2.0):
|
||||
resolution: {integrity: sha512-BXt6BpS9aJL/QdVqEIX9YoUy8CE6TJrU0mNCqSoxdXlIeNcEBWOfIyE6B14ENNsyQKS3wOWkiJfco0tCr/9tUg==}
|
||||
peerDependencies:
|
||||
react: ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||
react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||
dependencies:
|
||||
'@react-spring/animated': 9.7.3(react@18.2.0)
|
||||
'@react-spring/core': 9.7.3(react@18.2.0)
|
||||
'@react-spring/shared': 9.7.3(react@18.2.0)
|
||||
'@react-spring/types': 9.7.3
|
||||
react: 18.2.0
|
||||
react-dom: 18.2.0(react@18.2.0)
|
||||
dev: false
|
||||
|
||||
/@rushstack/eslint-patch@1.5.1:
|
||||
resolution: {integrity: sha512-6i/8UoL0P5y4leBIGzvkZdS85RDMG9y1ihZzmTZQ5LdHUYmZ7pKFoj8X0236s3lusPs1Fa5HTQUpwI+UfTcmeA==}
|
||||
dev: true
|
||||
|
@ -1331,18 +1290,6 @@ packages:
|
|||
dependencies:
|
||||
'@types/node': 20.9.0
|
||||
|
||||
/@types/bunyan-format@0.2.8:
|
||||
resolution: {integrity: sha512-Wd5TIv2RroJ0VmfvkhnnlEZgm4E1D8b9akSuVtlSQVIVqH9jFpoE6Nkj4Q7f7TTKp6wtlvex3FALshTCjmfi+A==}
|
||||
dependencies:
|
||||
'@types/node': 20.9.0
|
||||
dev: true
|
||||
|
||||
/@types/bunyan@1.8.11:
|
||||
resolution: {integrity: sha512-758fRH7umIMk5qt5ELmRMff4mLDlN+xyYzC+dkPTdKwbSkJFvz6xwyScrytPU0QIBbRRwbiE8/BIg8bpajerNQ==}
|
||||
dependencies:
|
||||
'@types/node': 20.9.0
|
||||
dev: true
|
||||
|
||||
/@types/cross-spawn@6.0.3:
|
||||
resolution: {integrity: sha512-BDAkU7WHHRHnvBf5z89lcvACsvkz/n7Tv+HyD/uW76O29HoH1Tk/W6iQrepaZVbisvlEek4ygwT8IW7ow9XLAA==}
|
||||
dependencies:
|
||||
|
@ -1478,6 +1425,10 @@ packages:
|
|||
resolution: {integrity: sha512-+d+WYC1BxJ6yVOgUgzK8gWvp5qF8ssV5r4nsDcZWKRWcDQLQ619tvWAxJQYGgBrO1MnLJC7a5GtiYsAoQ47dJg==}
|
||||
dev: true
|
||||
|
||||
/@types/triple-beam@1.3.5:
|
||||
resolution: {integrity: sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw==}
|
||||
dev: false
|
||||
|
||||
/@types/ua-parser-js@0.7.39:
|
||||
resolution: {integrity: sha512-P/oDfpofrdtF5xw433SPALpdSchtJmY7nsJItf8h3KXqOslkbySh8zq4dSWXH2oTjRvJ5PczVEoCZPow6GicLg==}
|
||||
dev: true
|
||||
|
@ -1717,14 +1668,6 @@ packages:
|
|||
dependencies:
|
||||
color-convert: 2.0.1
|
||||
|
||||
/ansicolors@0.2.1:
|
||||
resolution: {integrity: sha512-tOIuy1/SK/dr94ZA0ckDohKXNeBNqZ4us6PjMVLs5h1w2GBB6uPtOknp2+VF4F/zcy9LI70W+Z+pE2Soajky1w==}
|
||||
dev: false
|
||||
|
||||
/ansistyles@0.1.3:
|
||||
resolution: {integrity: sha512-6QWEyvMgIXX0eO972y7YPBLSBsq7UWKFAoNNTLGaOJ9bstcEL9sCbcjf96dVfNDdUsRoGOK82vWFJlKApXds7g==}
|
||||
dev: false
|
||||
|
||||
/any-promise@1.3.0:
|
||||
resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==}
|
||||
|
||||
|
@ -2127,25 +2070,6 @@ packages:
|
|||
load-tsconfig: 0.2.5
|
||||
dev: true
|
||||
|
||||
/bunyan-format@0.2.1:
|
||||
resolution: {integrity: sha512-xQs2LwWskjQdv7bVkMNwvMi7HnvDQoX4587H90nDGQGPPwHrmxsihBOIYHMVwjLMMOokITKPyFcbFneblvMEjQ==}
|
||||
dependencies:
|
||||
ansicolors: 0.2.1
|
||||
ansistyles: 0.1.3
|
||||
xtend: 2.1.2
|
||||
dev: false
|
||||
|
||||
/bunyan@1.8.15:
|
||||
resolution: {integrity: sha512-0tECWShh6wUysgucJcBAoYegf3JJoZWibxdqhTm7OHPeT42qdjkZ29QCMcKwbgU1kiH+auSIasNRXMLWXafXig==}
|
||||
engines: {'0': node >=0.10.0}
|
||||
hasBin: true
|
||||
optionalDependencies:
|
||||
dtrace-provider: 0.8.8
|
||||
moment: 2.29.4
|
||||
mv: 2.1.1
|
||||
safe-json-stringify: 1.2.0
|
||||
dev: false
|
||||
|
||||
/busboy@1.6.0:
|
||||
resolution: {integrity: sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==}
|
||||
engines: {node: '>=10.16.0'}
|
||||
|
@ -2219,7 +2143,6 @@ packages:
|
|||
/chalk@5.3.0:
|
||||
resolution: {integrity: sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==}
|
||||
engines: {node: ^12.17.0 || ^14.13 || >=16.0.0}
|
||||
dev: true
|
||||
|
||||
/checkpoint-client@1.1.27:
|
||||
resolution: {integrity: sha512-xstymfUalJOv6ZvTtmkwP4ORJN36ikT4PvrIoLe3wstbYf87XIXCcZrSmbFQOjyB0v1qbBnCsAscDpfdZlCkFA==}
|
||||
|
@ -2357,11 +2280,32 @@ packages:
|
|||
/color-name@1.1.4:
|
||||
resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==}
|
||||
|
||||
/color-string@1.9.1:
|
||||
resolution: {integrity: sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==}
|
||||
dependencies:
|
||||
color-name: 1.1.4
|
||||
simple-swizzle: 0.2.2
|
||||
dev: false
|
||||
|
||||
/color-support@1.1.3:
|
||||
resolution: {integrity: sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==}
|
||||
hasBin: true
|
||||
dev: false
|
||||
|
||||
/color@3.2.1:
|
||||
resolution: {integrity: sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA==}
|
||||
dependencies:
|
||||
color-convert: 1.9.3
|
||||
color-string: 1.9.1
|
||||
dev: false
|
||||
|
||||
/colorspace@1.1.4:
|
||||
resolution: {integrity: sha512-BgvKJiuVu1igBUF2kEjRCZXol6wiiGbY5ipL/oVPwm0BL9sIpMIzM8IK7vwuxIIzOXMV3Ey5w+vxhm0rR/TN8w==}
|
||||
dependencies:
|
||||
color: 3.2.1
|
||||
text-hex: 1.0.0
|
||||
dev: false
|
||||
|
||||
/combined-stream@1.0.8:
|
||||
resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==}
|
||||
engines: {node: '>= 0.8'}
|
||||
|
@ -2857,15 +2801,6 @@ packages:
|
|||
pg: 8.11.3
|
||||
dev: false
|
||||
|
||||
/dtrace-provider@0.8.8:
|
||||
resolution: {integrity: sha512-b7Z7cNtHPhH9EJhNNbbeqTcXB8LGFFZhq1PGgEvpeHlzd36bhbdTWoE/Ba/YguqpBSlAPKnARWhVlhunCMwfxg==}
|
||||
engines: {node: '>=0.10'}
|
||||
requiresBuild: true
|
||||
dependencies:
|
||||
nan: 2.18.0
|
||||
dev: false
|
||||
optional: true
|
||||
|
||||
/ecdsa-sig-formatter@1.0.11:
|
||||
resolution: {integrity: sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==}
|
||||
dependencies:
|
||||
|
@ -2884,6 +2819,10 @@ packages:
|
|||
resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==}
|
||||
dev: true
|
||||
|
||||
/enabled@2.0.0:
|
||||
resolution: {integrity: sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ==}
|
||||
dev: false
|
||||
|
||||
/end-of-stream@1.4.4:
|
||||
resolution: {integrity: sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==}
|
||||
dependencies:
|
||||
|
@ -3520,6 +3459,10 @@ packages:
|
|||
dependencies:
|
||||
reusify: 1.0.4
|
||||
|
||||
/fecha@4.2.3:
|
||||
resolution: {integrity: sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw==}
|
||||
dev: false
|
||||
|
||||
/file-entry-cache@6.0.1:
|
||||
resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==}
|
||||
engines: {node: ^10.12.0 || >=12.0.0}
|
||||
|
@ -3591,6 +3534,10 @@ packages:
|
|||
resolution: {integrity: sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ==}
|
||||
dev: true
|
||||
|
||||
/fn.name@1.1.0:
|
||||
resolution: {integrity: sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==}
|
||||
dev: false
|
||||
|
||||
/for-each@0.3.3:
|
||||
resolution: {integrity: sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==}
|
||||
dependencies:
|
||||
|
@ -3757,18 +3704,6 @@ packages:
|
|||
resolution: {integrity: sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==}
|
||||
dev: false
|
||||
|
||||
/glob@6.0.4:
|
||||
resolution: {integrity: sha512-MKZeRNyYZAVVVG1oZeLaWie1uweH40m9AZwIwxyPbTSX4hHrVYSzLg0Ro5Z5R7XKkIX+Cc6oD1rqeDJnwsB8/A==}
|
||||
requiresBuild: true
|
||||
dependencies:
|
||||
inflight: 1.0.6
|
||||
inherits: 2.0.4
|
||||
minimatch: 3.1.2
|
||||
once: 1.4.0
|
||||
path-is-absolute: 1.0.1
|
||||
dev: false
|
||||
optional: true
|
||||
|
||||
/glob@7.1.6:
|
||||
resolution: {integrity: sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==}
|
||||
dependencies:
|
||||
|
@ -4114,6 +4049,10 @@ packages:
|
|||
/is-arrayish@0.2.1:
|
||||
resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==}
|
||||
|
||||
/is-arrayish@0.3.2:
|
||||
resolution: {integrity: sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==}
|
||||
dev: false
|
||||
|
||||
/is-async-function@2.0.0:
|
||||
resolution: {integrity: sha512-Y1JXKrfykRJGdlDwdKlLpLyMIiWqWvuSd17TvZk68PLAOGOoF4Xyav1z0Xhoi+gCYjZVeC5SI+hYFOfvXmGRCA==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
@ -4562,6 +4501,10 @@ packages:
|
|||
engines: {node: '>= 8'}
|
||||
dev: false
|
||||
|
||||
/kuler@2.0.0:
|
||||
resolution: {integrity: sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==}
|
||||
dev: false
|
||||
|
||||
/language-subtag-registry@0.3.22:
|
||||
resolution: {integrity: sha512-tN0MCzyWnoz/4nHS6uxdlFWoUZT7ABptwKPQ52Ea7URk6vll88bWBVhodtnlfEuCcKWNGoc+uGbw1cwa9IKh/w==}
|
||||
dev: true
|
||||
|
@ -4697,6 +4640,18 @@ packages:
|
|||
wrap-ansi: 6.2.0
|
||||
dev: false
|
||||
|
||||
/logform@2.6.0:
|
||||
resolution: {integrity: sha512-1ulHeNPp6k/LD8H91o7VYFBng5i1BDE7HoKxVbZiGFidS1Rj65qcywLxX+pVfAPoQJEjRdvKcusKwOupHCVOVQ==}
|
||||
engines: {node: '>= 12.0.0'}
|
||||
dependencies:
|
||||
'@colors/colors': 1.6.0
|
||||
'@types/triple-beam': 1.3.5
|
||||
fecha: 4.2.3
|
||||
ms: 2.1.3
|
||||
safe-stable-stringify: 2.4.3
|
||||
triple-beam: 1.4.1
|
||||
dev: false
|
||||
|
||||
/loose-envify@1.4.0:
|
||||
resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==}
|
||||
hasBin: true
|
||||
|
@ -4895,27 +4850,12 @@ packages:
|
|||
resolution: {integrity: sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==}
|
||||
dev: false
|
||||
|
||||
/mkdirp@0.5.6:
|
||||
resolution: {integrity: sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==}
|
||||
hasBin: true
|
||||
requiresBuild: true
|
||||
dependencies:
|
||||
minimist: 1.2.8
|
||||
dev: false
|
||||
optional: true
|
||||
|
||||
/mkdirp@1.0.4:
|
||||
resolution: {integrity: sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==}
|
||||
engines: {node: '>=10'}
|
||||
hasBin: true
|
||||
dev: false
|
||||
|
||||
/moment@2.29.4:
|
||||
resolution: {integrity: sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==}
|
||||
requiresBuild: true
|
||||
dev: false
|
||||
optional: true
|
||||
|
||||
/mongodb-connection-string-url@2.6.0:
|
||||
resolution: {integrity: sha512-WvTZlI9ab0QYtTYnuMLgobULWhokRjtC7db9LtcVfJ+Hsnyr5eo6ZtNAt3Ly24XZScGMelOcGtm7lSn0332tPQ==}
|
||||
dependencies:
|
||||
|
@ -5010,17 +4950,6 @@ packages:
|
|||
- supports-color
|
||||
dev: false
|
||||
|
||||
/mv@2.1.1:
|
||||
resolution: {integrity: sha512-at/ZndSy3xEGJ8i0ygALh8ru9qy7gWW1cmkaqBN29JmMlIvM//MEO9y1sk/avxuwnPcfhkejkLsuPxH81BrkSg==}
|
||||
engines: {node: '>=0.8.0'}
|
||||
requiresBuild: true
|
||||
dependencies:
|
||||
mkdirp: 0.5.6
|
||||
ncp: 2.0.0
|
||||
rimraf: 2.4.5
|
||||
dev: false
|
||||
optional: true
|
||||
|
||||
/mz@2.7.0:
|
||||
resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==}
|
||||
dependencies:
|
||||
|
@ -5028,12 +4957,6 @@ packages:
|
|||
object-assign: 4.1.1
|
||||
thenify-all: 1.6.0
|
||||
|
||||
/nan@2.18.0:
|
||||
resolution: {integrity: sha512-W7tfG7vMOGtD30sHoZSSc/JVYiyDPEyQVso/Zz+/uQd0B0L46gtC+pHha5FFMRpil6fm/AoEcRWyOVi4+E/f8w==}
|
||||
requiresBuild: true
|
||||
dev: false
|
||||
optional: true
|
||||
|
||||
/nanoid@3.3.7:
|
||||
resolution: {integrity: sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==}
|
||||
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
|
||||
|
@ -5070,13 +4993,6 @@ packages:
|
|||
resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==}
|
||||
dev: true
|
||||
|
||||
/ncp@2.0.0:
|
||||
resolution: {integrity: sha512-zIdGUrPRFTUELUvr3Gmc7KZ2Sw/h1PiVM0Af/oHB6zgnV1ikqSfRk+TOufi79aHYCW3NiOXmr1BP5nWbzojLaA==}
|
||||
hasBin: true
|
||||
requiresBuild: true
|
||||
dev: false
|
||||
optional: true
|
||||
|
||||
/new-github-issue-url@0.2.1:
|
||||
resolution: {integrity: sha512-md4cGoxuT4T4d/HDOXbrUHkTKrp/vp+m3aOA7XXVYwNsUNMK49g3SQicTSeV5GIz/5QVGAeYRAOlyp9OvlgsYA==}
|
||||
engines: {node: '>=10'}
|
||||
|
@ -5292,10 +5208,6 @@ packages:
|
|||
/object-inspect@1.13.1:
|
||||
resolution: {integrity: sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==}
|
||||
|
||||
/object-keys@0.4.0:
|
||||
resolution: {integrity: sha512-ncrLw+X55z7bkl5PnUvHwFK9FcGuFYo9gtjws2XtSzL+aZ8tm830P60WJ0dSmFVaSalWieW5MD7kEdnXda9yJw==}
|
||||
dev: false
|
||||
|
||||
/object-keys@1.1.1:
|
||||
resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
@ -5371,6 +5283,12 @@ packages:
|
|||
dependencies:
|
||||
wrappy: 1.0.2
|
||||
|
||||
/one-time@1.0.0:
|
||||
resolution: {integrity: sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g==}
|
||||
dependencies:
|
||||
fn.name: 1.1.0
|
||||
dev: false
|
||||
|
||||
/onetime@5.1.2:
|
||||
resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==}
|
||||
engines: {node: '>=6'}
|
||||
|
@ -5901,18 +5819,6 @@ packages:
|
|||
strip-json-comments: 2.0.1
|
||||
dev: false
|
||||
|
||||
/react-animated-numbers@0.16.0(react-dom@18.2.0)(react@18.2.0):
|
||||
resolution: {integrity: sha512-MUoOsf8fLzwyUL9l6NEMma+29QtfbeYmt8x2LLt4IeLHQWJQfGa4WIUXB/VDVBXEhg74BhCRytdyvhHR3IiHsw==}
|
||||
peerDependencies:
|
||||
react: '>=16.8.0'
|
||||
react-dom: '>=16.8.0'
|
||||
dependencies:
|
||||
'@react-spring/web': 9.7.3(react-dom@18.2.0)(react@18.2.0)
|
||||
react: 18.2.0
|
||||
react-dom: 18.2.0(react@18.2.0)
|
||||
react-intersection-observer: 8.34.0(react@18.2.0)
|
||||
dev: false
|
||||
|
||||
/react-dom@18.2.0(react@18.2.0):
|
||||
resolution: {integrity: sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==}
|
||||
peerDependencies:
|
||||
|
@ -5931,14 +5837,6 @@ packages:
|
|||
react: 18.2.0
|
||||
dev: false
|
||||
|
||||
/react-intersection-observer@8.34.0(react@18.2.0):
|
||||
resolution: {integrity: sha512-TYKh52Zc0Uptp5/b4N91XydfSGKubEhgZRtcg1rhTKABXijc4Sdr1uTp5lJ8TN27jwUsdXxjHXtHa0kPj704sw==}
|
||||
peerDependencies:
|
||||
react: ^15.0.0 || ^16.0.0 || ^17.0.0|| ^18.0.0
|
||||
dependencies:
|
||||
react: 18.2.0
|
||||
dev: false
|
||||
|
||||
/react-is@16.13.1:
|
||||
resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==}
|
||||
|
||||
|
@ -6222,15 +6120,6 @@ packages:
|
|||
resolution: {integrity: sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA==}
|
||||
dev: false
|
||||
|
||||
/rimraf@2.4.5:
|
||||
resolution: {integrity: sha512-J5xnxTyqaiw06JjMftq7L9ouA448dw/E7dKghkP9WpKNuwmARNNg+Gk8/u5ryb9N/Yo2+z3MCwuqFK/+qPOPfQ==}
|
||||
hasBin: true
|
||||
requiresBuild: true
|
||||
dependencies:
|
||||
glob: 6.0.4
|
||||
dev: false
|
||||
optional: true
|
||||
|
||||
/rimraf@3.0.2:
|
||||
resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==}
|
||||
hasBin: true
|
||||
|
@ -6250,6 +6139,14 @@ packages:
|
|||
dependencies:
|
||||
queue-microtask: 1.2.3
|
||||
|
||||
/rxjs@7.8.1:
|
||||
resolution: {integrity: sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==}
|
||||
requiresBuild: true
|
||||
dependencies:
|
||||
tslib: 2.6.2
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/safe-array-concat@1.0.1:
|
||||
resolution: {integrity: sha512-6XbUAseYE2KtOuGueyeobCySj9L4+66Tn6KQMOPQJrAJEowYKW/YR/MGJZl7FdydUdaFu4LYyDZjxf4/Nmo23Q==}
|
||||
engines: {node: '>=0.4'}
|
||||
|
@ -6267,12 +6164,6 @@ packages:
|
|||
resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==}
|
||||
dev: false
|
||||
|
||||
/safe-json-stringify@1.2.0:
|
||||
resolution: {integrity: sha512-gH8eh2nZudPQO6TytOvbxnuhYBOvDBBLW52tz5q6X58lJcd/tkmqFR+5Z9adS8aJtURSXWThWy/xJtJwixErvg==}
|
||||
requiresBuild: true
|
||||
dev: false
|
||||
optional: true
|
||||
|
||||
/safe-regex-test@1.0.0:
|
||||
resolution: {integrity: sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA==}
|
||||
dependencies:
|
||||
|
@ -6286,6 +6177,11 @@ packages:
|
|||
ret: 0.1.15
|
||||
dev: true
|
||||
|
||||
/safe-stable-stringify@2.4.3:
|
||||
resolution: {integrity: sha512-e2bDA2WJT0wxseVd4lsDP4+3ONX6HpMXQa1ZhFQ7SU+GjvORCmShbCMltrtIDfkYhVHrOcPtj+KhmDBdPdZD1g==}
|
||||
engines: {node: '>=10'}
|
||||
dev: false
|
||||
|
||||
/safer-buffer@2.1.2:
|
||||
resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==}
|
||||
dev: false
|
||||
|
@ -6394,6 +6290,12 @@ packages:
|
|||
simple-concat: 1.0.1
|
||||
dev: false
|
||||
|
||||
/simple-swizzle@0.2.2:
|
||||
resolution: {integrity: sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==}
|
||||
dependencies:
|
||||
is-arrayish: 0.3.2
|
||||
dev: false
|
||||
|
||||
/sisteransi@1.0.5:
|
||||
resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==}
|
||||
|
||||
|
@ -6565,6 +6467,10 @@ packages:
|
|||
resolution: {integrity: sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==}
|
||||
dev: false
|
||||
|
||||
/stack-trace@0.0.10:
|
||||
resolution: {integrity: sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg==}
|
||||
dev: false
|
||||
|
||||
/static-extend@0.1.2:
|
||||
resolution: {integrity: sha512-72E9+uLc27Mt718pMHt9VMNiAL4LMsmDbBva8mxWUCkT07fSzEGMYUCk0XWY6lp0j6RBAG4cJ3mWuZv2OE3s0g==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
@ -6887,6 +6793,10 @@ packages:
|
|||
supports-hyperlinks: 2.3.0
|
||||
dev: false
|
||||
|
||||
/text-hex@1.0.0:
|
||||
resolution: {integrity: sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==}
|
||||
dev: false
|
||||
|
||||
/text-table@0.2.0:
|
||||
resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==}
|
||||
dev: true
|
||||
|
@ -6973,6 +6883,11 @@ packages:
|
|||
hasBin: true
|
||||
dev: true
|
||||
|
||||
/triple-beam@1.4.1:
|
||||
resolution: {integrity: sha512-aZbgViZrg1QNcG+LULa7nhZpJTZSLm/mXnHXnbAbjmN5aSa0y7V+wvv6+4WaBtpISJzThKy+PIPxc1Nq1EJ9mg==}
|
||||
engines: {node: '>= 14.0.0'}
|
||||
dev: false
|
||||
|
||||
/ts-api-utils@1.0.3(typescript@5.2.2):
|
||||
resolution: {integrity: sha512-wNMeqtMz5NtwpT/UZGY5alT+VoKdSsOOP/kqHFcUW1P/VRhH2wJ48+DN2WwUliNbQ976ETwDL0Ifd2VVvgonvg==}
|
||||
engines: {node: '>=16.13.0'}
|
||||
|
@ -7014,7 +6929,6 @@ packages:
|
|||
|
||||
/tslib@2.6.2:
|
||||
resolution: {integrity: sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==}
|
||||
dev: false
|
||||
|
||||
/tsup@7.2.0(postcss@8.4.31)(typescript@5.2.2):
|
||||
resolution: {integrity: sha512-vDHlczXbgUvY3rWvqFEbSqmC1L7woozbzngMqTtL2PGBODTtWlRwGDDawhvWzr5c1QjKe4OAKqJGfE1xeXUvtQ==}
|
||||
|
@ -7133,6 +7047,12 @@ packages:
|
|||
for-each: 0.3.3
|
||||
is-typed-array: 1.1.12
|
||||
|
||||
/typed-emitter@2.1.0:
|
||||
resolution: {integrity: sha512-g/KzbYKbH5C2vPkaXGu8DJlHrGKHLsM25Zg9WuC9pMGfuvT+X25tZQWo5fK1BjBm8+UrVE9LDCvaY0CQk+fXDA==}
|
||||
optionalDependencies:
|
||||
rxjs: 7.8.1
|
||||
dev: true
|
||||
|
||||
/typescript@5.2.2:
|
||||
resolution: {integrity: sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==}
|
||||
engines: {node: '>=14.17'}
|
||||
|
@ -7372,6 +7292,32 @@ packages:
|
|||
string-width: 4.2.3
|
||||
dev: false
|
||||
|
||||
/winston-transport@4.6.0:
|
||||
resolution: {integrity: sha512-wbBA9PbPAHxKiygo7ub7BYRiKxms0tpfU2ljtWzb3SjRjv5yl6Ozuy/TkXf00HTAt+Uylo3gSkNwzc4ME0wiIg==}
|
||||
engines: {node: '>= 12.0.0'}
|
||||
dependencies:
|
||||
logform: 2.6.0
|
||||
readable-stream: 3.6.2
|
||||
triple-beam: 1.4.1
|
||||
dev: false
|
||||
|
||||
/winston@3.11.0:
|
||||
resolution: {integrity: sha512-L3yR6/MzZAOl0DsysUXHVjOwv8mKZ71TrA/41EIduGpOOV5LQVodqN+QdQ6BS6PJ/RdIshZhq84P/fStEZkk7g==}
|
||||
engines: {node: '>= 12.0.0'}
|
||||
dependencies:
|
||||
'@colors/colors': 1.6.0
|
||||
'@dabh/diagnostics': 2.0.3
|
||||
async: 3.2.5
|
||||
is-stream: 2.0.1
|
||||
logform: 2.6.0
|
||||
one-time: 1.0.0
|
||||
readable-stream: 3.6.2
|
||||
safe-stable-stringify: 2.4.3
|
||||
stack-trace: 0.0.10
|
||||
triple-beam: 1.4.1
|
||||
winston-transport: 4.6.0
|
||||
dev: false
|
||||
|
||||
/wordwrap@1.0.0:
|
||||
resolution: {integrity: sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==}
|
||||
dev: true
|
||||
|
@ -7403,13 +7349,6 @@ packages:
|
|||
bufferutil: 4.0.8
|
||||
dev: false
|
||||
|
||||
/xtend@2.1.2:
|
||||
resolution: {integrity: sha512-vMNKzr2rHP9Dp/e1NQFnLQlwlhp9L/LfvnsVdHxN1f+uggyVI3i08uD14GPvCToPkdsRfyPqIyYGmIk58V98ZQ==}
|
||||
engines: {node: '>=0.4'}
|
||||
dependencies:
|
||||
object-keys: 0.4.0
|
||||
dev: false
|
||||
|
||||
/xtend@4.0.2:
|
||||
resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==}
|
||||
engines: {node: '>=0.4'}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/* tailwind does not have text-shadow support, so we do it ourselves */
|
||||
.stat-card > p {
|
||||
.stat-card {
|
||||
/* -webkit-text-stroke: 1px theme("colors.card.DEFAULT"); */
|
||||
text-shadow:
|
||||
1px 1px 0 theme("colors.card.DEFAULT"),
|
||||
|
|
|
@ -3,21 +3,22 @@
|
|||
import { Card, CardContent, CardHeader, CardTitle } from "~/components/ui/card";
|
||||
import { ResponsiveContainer, AreaChart, Area } from "recharts";
|
||||
import styles from "./StatCard.module.css";
|
||||
// import AnimatedNumber from "react-animated-numbers";
|
||||
|
||||
import dynamic from "next/dynamic";
|
||||
const AnimatedNumber = dynamic(() => import("react-animated-numbers"), {
|
||||
ssr: false,
|
||||
});
|
||||
import { AnimatedNumber } from "~/components/AnimatedPercent";
|
||||
|
||||
export function StatCard<T extends Record<string, number>>(props: {
|
||||
title: string;
|
||||
value: number;
|
||||
unit: string;
|
||||
subvalue: string;
|
||||
|
||||
icon: React.FC<{ className: string }>;
|
||||
data: T[];
|
||||
dataKey: keyof T & string;
|
||||
|
||||
value: number;
|
||||
unit?: string;
|
||||
subvalue?: string;
|
||||
|
||||
secondaryValue?: number;
|
||||
secondaryUnit?: string;
|
||||
secondarySubvalue?: string;
|
||||
}) {
|
||||
const rechartsColorId = `color${props.dataKey}`;
|
||||
const Icon = props.icon;
|
||||
|
@ -79,21 +80,36 @@ export function StatCard<T extends Record<string, number>>(props: {
|
|||
</ResponsiveContainer>
|
||||
</div>
|
||||
|
||||
<div className={`relative z-10 ${styles["stat-card"]}`}>
|
||||
<p className="stroke stroke-card text-2xl font-bold">
|
||||
<AnimatedNumber
|
||||
animateToNumber={128391}
|
||||
includeComma
|
||||
fontStyle={{
|
||||
fontSize: 24,
|
||||
// fontWeight: "inherit",
|
||||
}}
|
||||
/>
|
||||
<div className="relative z-10">
|
||||
<div className="stroke flex flex-row stroke-card text-2xl font-bold">
|
||||
<div>
|
||||
<AnimatedNumber number={props.value} />
|
||||
{props.unit}
|
||||
</p>
|
||||
<p className="stroke stroke-card text-sm text-muted-foreground">
|
||||
|
||||
{props.subvalue !== undefined && (
|
||||
<p
|
||||
className={`stroke stroke-card text-sm text-muted-foreground ${styles["stat-card"]} font-normal`}
|
||||
>
|
||||
{props.subvalue}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{props.secondaryValue !== undefined && (
|
||||
<div className="ml-auto mr-0 text-right">
|
||||
<AnimatedNumber number={props.secondaryValue} />
|
||||
{props.secondaryUnit}
|
||||
|
||||
{props.secondarySubvalue && (
|
||||
<p
|
||||
className={`stroke stroke-card text-sm text-muted-foreground ${styles["stat-card"]} text-right font-normal`}
|
||||
>
|
||||
{props.secondarySubvalue}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
|
|
@ -3,7 +3,12 @@
|
|||
import { api } from "~/trpc/react";
|
||||
import { StatCard } from "./StatCard";
|
||||
import { RouterOutputs } from "~/trpc/shared";
|
||||
import { RiPulseFill } from "react-icons/ri";
|
||||
import {
|
||||
FaMicrochip,
|
||||
FaMemory,
|
||||
FaHardDrive,
|
||||
FaEthernet,
|
||||
} from "react-icons/fa6";
|
||||
import { useState } from "react";
|
||||
|
||||
const TEST_DATA = [
|
||||
|
@ -32,35 +37,50 @@ export function SystemStatistics(props: { initialData: StatData }) {
|
|||
<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}%`}
|
||||
value={data.cpu.usage}
|
||||
unit="%"
|
||||
subvalue={`of ${data.cpu.cores} CPUs`}
|
||||
icon={RiPulseFill}
|
||||
icon={FaMicrochip}
|
||||
data={TEST_DATA}
|
||||
dataKey="cpu"
|
||||
/>
|
||||
|
||||
<StatCard
|
||||
title="Memory Usage"
|
||||
value={`${((data.memory.used / data.memory.total) * 100).toFixed(2)}%`}
|
||||
value={data.memory.used / data.memory.total}
|
||||
unit="%"
|
||||
subvalue={`${data.memory.used.toFixed(2)} / ${data.memory.total.toFixed(
|
||||
2,
|
||||
)} GB`}
|
||||
icon={RiPulseFill}
|
||||
icon={FaMemory}
|
||||
data={TEST_DATA}
|
||||
dataKey="cpu"
|
||||
/>
|
||||
|
||||
<StatCard
|
||||
title="Disk Usage"
|
||||
value={`${((data.storage.used / data.storage.total) * 100).toFixed(
|
||||
2,
|
||||
)}%`}
|
||||
value={data.storage.used / data.storage.total}
|
||||
unit="%"
|
||||
subvalue={`${data.storage.used.toFixed(
|
||||
2,
|
||||
)} / ${data.storage.total.toFixed(2)} GB`}
|
||||
icon={RiPulseFill}
|
||||
icon={FaHardDrive}
|
||||
data={TEST_DATA}
|
||||
dataKey="cpu"
|
||||
/>
|
||||
|
||||
<StatCard
|
||||
title="Network Usage"
|
||||
// TX
|
||||
value={data.network.tx}
|
||||
// unit="Mbps"
|
||||
subvalue="Mbps / TX"
|
||||
// RX
|
||||
secondaryValue={data.network.rx}
|
||||
// secondaryUnit="Mbps"
|
||||
secondarySubvalue="RX / Mbps"
|
||||
// misc
|
||||
icon={FaEthernet}
|
||||
data={TEST_DATA}
|
||||
dataKey="cpu"
|
||||
/>
|
||||
|
|
33
src/components/AnimatedPercent.module.css
Normal file
33
src/components/AnimatedPercent.module.css
Normal file
|
@ -0,0 +1,33 @@
|
|||
@property --percent {
|
||||
syntax: "<number>";
|
||||
initial-value: 0;
|
||||
inherits: false;
|
||||
}
|
||||
@property --temp {
|
||||
syntax: "<number>";
|
||||
initial-value: 0;
|
||||
inherits: false;
|
||||
}
|
||||
@property --v1 {
|
||||
syntax: "<integer>";
|
||||
initial-value: 0;
|
||||
inherits: false;
|
||||
}
|
||||
@property --v2 {
|
||||
syntax: "<integer>";
|
||||
initial-value: 0;
|
||||
inherits: false;
|
||||
}
|
||||
|
||||
.animated-percent {
|
||||
transition: --percent 1.5s;
|
||||
transition-timing-function: cubic-bezier(0.38, 0, 0, 1);
|
||||
--temp: calc(var(--percent) * 100);
|
||||
--v1: max(var(--temp) - 0.5, 0);
|
||||
--v2: max((var(--temp) - var(--v1)) * 100 - 0.5, 0);
|
||||
counter-reset: v1 var(--v1) v2 var(--v2);
|
||||
}
|
||||
|
||||
.animated-percent::before {
|
||||
content: counter(v1) "." counter(v2, decimal-leading-zero);
|
||||
}
|
27
src/components/AnimatedPercent.tsx
Normal file
27
src/components/AnimatedPercent.tsx
Normal file
|
@ -0,0 +1,27 @@
|
|||
"use client";
|
||||
|
||||
import { useEffect, useRef } from "react";
|
||||
import { cn } from "~/utils/utils";
|
||||
import styles from "./AnimatedPercent.module.css";
|
||||
|
||||
export type AnimatedNumberProps = {
|
||||
number: number;
|
||||
className?: string;
|
||||
};
|
||||
|
||||
export function AnimatedNumber(props: AnimatedNumberProps) {
|
||||
const divRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
divRef.current?.style.setProperty("--percent", props.number.toString());
|
||||
}, [props.number]);
|
||||
|
||||
return (
|
||||
<div className={cn("relative inline-block", props.className)}>
|
||||
<div className="absolute inset-0 text-transparent">
|
||||
{props.number.toFixed(2)}
|
||||
</div>
|
||||
<div className={styles["animated-percent"]} ref={divRef} />
|
||||
</div>
|
||||
);
|
||||
}
|
|
@ -29,7 +29,7 @@ export const env = createEnv({
|
|||
* `NEXT_PUBLIC_`.
|
||||
*/
|
||||
client: {
|
||||
// NEXT_PUBLIC_CLIENTVAR: z.string().min(1),
|
||||
NEXT_PUBLIC_BUILD_COMMIT_SHA: z.string().default("unknown"),
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -43,6 +43,7 @@ export const env = createEnv({
|
|||
SESSION_SECRET: process.env.SESSION_SECRET,
|
||||
HOSTNAME: process.env.HOSTNAME,
|
||||
PORT: process.env.PORT,
|
||||
NEXT_PUBLIC_BUILD_COMMIT_SHA: process.env.NEXT_PUBLIC_BUILD_COMMIT_SHA,
|
||||
},
|
||||
/**
|
||||
* Run `build` or `dev` with `SKIP_ENV_VALIDATION` to skip env validation. This is especially
|
||||
|
|
|
@ -1,39 +0,0 @@
|
|||
// import pacakge from "../package.json";
|
||||
import { env } from "~/env";
|
||||
// const { version } = pacakge;
|
||||
const version = "1.0.0";
|
||||
|
||||
export async function register() {
|
||||
if (process.env.NEXT_RUNTIME === "nodejs") {
|
||||
const logger = (await import("./server/utils/logger")).default;
|
||||
const { migrate } = await import("drizzle-orm/better-sqlite3/migrator");
|
||||
const { db } = await import("./server/db");
|
||||
const { mkdir, stat } = await import("fs/promises");
|
||||
const path = await import("path");
|
||||
|
||||
// check if database folder exists
|
||||
try {
|
||||
const dir = path.dirname(env.DATABASE_PATH);
|
||||
await stat(dir);
|
||||
} catch (e) {
|
||||
await mkdir(path.dirname(env.DATABASE_PATH), { recursive: true });
|
||||
logger.debug(
|
||||
`Created database folder ${path.dirname(env.DATABASE_PATH)}`,
|
||||
);
|
||||
}
|
||||
|
||||
if (env.NODE_ENV === "production") {
|
||||
logger.child({ module: "database" }).info("⚙️ Migrating database");
|
||||
migrate(db, { migrationsFolder: "./migrations" });
|
||||
logger.child({ module: "database" }).info("✅ Database migrated");
|
||||
} else {
|
||||
logger
|
||||
.child({ module: "database" })
|
||||
.info(
|
||||
"Not running database migrations, use drizzle-kit push to migrate",
|
||||
);
|
||||
}
|
||||
|
||||
logger.info(`🚀 Hostforge v${version} ready!`);
|
||||
}
|
||||
}
|
|
@ -1,52 +1,20 @@
|
|||
import { authenticatedProcedure, createTRPCRouter } from "../../trpc";
|
||||
import os from "os";
|
||||
import osu from "node-os-utils";
|
||||
import { observable } from "@trpc/server/observable";
|
||||
|
||||
async function fetchSystemInfo() {
|
||||
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,
|
||||
},
|
||||
};
|
||||
}
|
||||
import { BasicServerStats, stats } from "~/server/modules/stats";
|
||||
|
||||
export const systemRouter = createTRPCRouter({
|
||||
currentStats: authenticatedProcedure.query(async ({ ctx }) => {
|
||||
return fetchSystemInfo();
|
||||
return stats.getCurrentStats();
|
||||
}),
|
||||
|
||||
liveStats: authenticatedProcedure.subscription(async ({ ctx }) => {
|
||||
return observable<Awaited<ReturnType<typeof fetchSystemInfo>>>(
|
||||
(observer) => {
|
||||
console.log("subscription got");
|
||||
fetchSystemInfo().then(observer.next.bind(observer));
|
||||
|
||||
const interval = setInterval(async () => {
|
||||
observer.next(await fetchSystemInfo());
|
||||
}, 1000);
|
||||
return observable<BasicServerStats>((observer) => {
|
||||
const update = observer.next.bind(observer);
|
||||
|
||||
stats.events.on("onUpdate", update);
|
||||
return () => {
|
||||
clearInterval(interval);
|
||||
stats.events.off("onUpdate", update);
|
||||
};
|
||||
},
|
||||
);
|
||||
});
|
||||
}),
|
||||
});
|
||||
|
|
|
@ -56,8 +56,6 @@ export const createTRPCContext = async (opts: {
|
|||
req: NextRequest;
|
||||
resHeaders: Headers;
|
||||
}) => {
|
||||
console.log("contexting ", opts.req.url);
|
||||
|
||||
// disable caching
|
||||
opts.resHeaders.set("Cache-Control", "no-store");
|
||||
|
||||
|
|
225
src/server/modules/stats/index.ts
Normal file
225
src/server/modules/stats/index.ts
Normal file
|
@ -0,0 +1,225 @@
|
|||
import EventEmitter from "events";
|
||||
import TypedEmitter from "typed-emitter";
|
||||
import osu from "node-os-utils";
|
||||
import os from "os";
|
||||
import baseLogger from "../../utils/logger";
|
||||
|
||||
export type BasicServerStats = {
|
||||
collectedAt: Date;
|
||||
|
||||
cpu: {
|
||||
/**
|
||||
* The CPU usage, each core = 100%.
|
||||
* In decimal form, so 0.5 = 50%.
|
||||
*/
|
||||
usage: number;
|
||||
|
||||
/**
|
||||
* The number of cores the CPU has.
|
||||
*/
|
||||
cores: number;
|
||||
};
|
||||
|
||||
storage: {
|
||||
/**
|
||||
* The amount of storage used in GB.
|
||||
*/
|
||||
used: number;
|
||||
|
||||
/**
|
||||
* The total amount of storage in GB.
|
||||
*/
|
||||
total: number;
|
||||
};
|
||||
|
||||
memory: {
|
||||
/**
|
||||
* The amount of memory used in GB.
|
||||
*/
|
||||
used: number;
|
||||
|
||||
/**
|
||||
* The total amount of memory in GB.
|
||||
*/
|
||||
total: number;
|
||||
};
|
||||
|
||||
network: {
|
||||
/**
|
||||
* The tx (upload) speed in bytes per second.
|
||||
*/
|
||||
tx: number;
|
||||
|
||||
/**
|
||||
* The rx (download) speed in bytes per second.
|
||||
*/
|
||||
rx: number;
|
||||
};
|
||||
};
|
||||
|
||||
type StatEvents = {
|
||||
/**
|
||||
* Whenever new stats are collected
|
||||
* @param stats The new stats.
|
||||
* @returns
|
||||
*/
|
||||
onUpdate: (stats: BasicServerStats) => void;
|
||||
|
||||
/**
|
||||
* Built-in Node.JS event, emitted whenever a new listener is added.
|
||||
*/
|
||||
newListener: (
|
||||
event: string | symbol,
|
||||
listener: (...args: any[]) => void,
|
||||
) => void;
|
||||
|
||||
/**
|
||||
* Built-in Node.JS event, emitted whenever a listener is removed.
|
||||
*/
|
||||
removeListener: (
|
||||
event: string | symbol,
|
||||
listener: (...args: any[]) => void,
|
||||
) => void;
|
||||
};
|
||||
|
||||
/**
|
||||
* Manages the stats for the current server.
|
||||
*/
|
||||
export class StatManager {
|
||||
private logger = baseLogger.child({ module: "StatManager" });
|
||||
|
||||
/**
|
||||
* The current stats for the server.
|
||||
*/
|
||||
private currentStats: BasicServerStats = {
|
||||
collectedAt: new Date(0),
|
||||
|
||||
cpu: {
|
||||
usage: 0,
|
||||
cores: 0,
|
||||
},
|
||||
|
||||
storage: {
|
||||
used: 0,
|
||||
total: 0,
|
||||
},
|
||||
|
||||
memory: {
|
||||
used: 0,
|
||||
total: 0,
|
||||
},
|
||||
|
||||
network: {
|
||||
tx: 0,
|
||||
rx: 0,
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* The event emitter for the stat manager.
|
||||
*/
|
||||
public readonly events = new EventEmitter() as TypedEmitter<StatEvents>;
|
||||
|
||||
/**
|
||||
* When live stats are needed, this is the interval that is used.
|
||||
*/
|
||||
private liveInterval: NodeJS.Timeout | null = null;
|
||||
|
||||
constructor() {
|
||||
this.update();
|
||||
|
||||
// collect stats every hour
|
||||
setInterval(
|
||||
async () => {
|
||||
await this.update();
|
||||
await this.updateDatabase();
|
||||
},
|
||||
60 * 60 * 1000,
|
||||
);
|
||||
|
||||
// whenever a new listener is added, start the live interval
|
||||
this.events.on("newListener", (event) => {
|
||||
if (event === "onUpdate") {
|
||||
this.liveInterval ??= setInterval(async () => {
|
||||
await this.update();
|
||||
}, 3 * 1000);
|
||||
}
|
||||
});
|
||||
|
||||
// unregister the event when the listener is removed
|
||||
this.events.on("removeListener", (event) => {
|
||||
if (event === "onUpdate" && this.events.listenerCount("onUpdate") === 0) {
|
||||
if (this.liveInterval === null) return;
|
||||
|
||||
clearInterval(this.liveInterval);
|
||||
this.liveInterval = null;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the current stats for the server.
|
||||
*/
|
||||
async getCurrentStats() {
|
||||
// return the current stats if they were collected within the last 5 minutes
|
||||
if (Date.now() - this.currentStats.collectedAt.getTime() < 5 * 60 * 1000) {
|
||||
return this.currentStats;
|
||||
}
|
||||
|
||||
// otherwise, update the stats and return them
|
||||
return this.update();
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the stats for the current server and pushes them to the database.
|
||||
*/
|
||||
async update() {
|
||||
const [cpuUsage, storage, memory, network] = await Promise.all([
|
||||
osu.cpu.usage(),
|
||||
osu.drive.info("/"),
|
||||
osu.mem.info(),
|
||||
osu.netstat.inOut(),
|
||||
]);
|
||||
|
||||
if (typeof network === "string") {
|
||||
this.logger.warn(
|
||||
"Failed to get network stats, got string instead of object: ",
|
||||
network,
|
||||
);
|
||||
}
|
||||
|
||||
this.currentStats = {
|
||||
collectedAt: new Date(),
|
||||
|
||||
cpu: {
|
||||
usage: cpuUsage / 100,
|
||||
cores: os.cpus().length,
|
||||
},
|
||||
|
||||
storage: {
|
||||
used: parseInt(storage.usedGb),
|
||||
total: parseInt(storage.totalGb),
|
||||
},
|
||||
|
||||
memory: {
|
||||
used: memory.usedMemMb / 1024,
|
||||
total: memory.totalMemMb / 1024,
|
||||
},
|
||||
|
||||
network: {
|
||||
tx: typeof network === "string" ? -1 : network.total.outputMb,
|
||||
rx: typeof network === "string" ? -1 : network.total.inputMb,
|
||||
},
|
||||
};
|
||||
|
||||
this.events.emit("onUpdate", this.currentStats);
|
||||
return this.currentStats;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the database with the current stats.
|
||||
*/
|
||||
async updateDatabase() {}
|
||||
}
|
||||
|
||||
export const stats = new StatManager();
|
|
@ -1,18 +1,39 @@
|
|||
// import { config } from "dotenv";
|
||||
// config();
|
||||
import "dotenv/config";
|
||||
import next from "next";
|
||||
import { env } from "~/env";
|
||||
import { createServer } from "http";
|
||||
import logger from "./utils/logger";
|
||||
// import ws from "ws";
|
||||
import { WebSocketServer } from "ws";
|
||||
import { applyWSSHandler } from "@trpc/server/adapters/ws";
|
||||
import { appRouter } from "./api/root";
|
||||
import { createTRPCContext } from "./api/trpc";
|
||||
import { incomingRequestToNextRequest } from "./utils/serverUtils";
|
||||
import { migrate } from "drizzle-orm/better-sqlite3/migrator";
|
||||
import { db } from "./db";
|
||||
import { mkdir, stat } from "fs/promises";
|
||||
import path from "path";
|
||||
import { version } from "../../package.json";
|
||||
|
||||
// check if database folder exists
|
||||
try {
|
||||
const dir = path.dirname(env.DATABASE_PATH);
|
||||
await stat(dir);
|
||||
} catch (e) {
|
||||
await mkdir(path.dirname(env.DATABASE_PATH), { recursive: true });
|
||||
logger.debug(`Created database folder ${path.dirname(env.DATABASE_PATH)}`);
|
||||
}
|
||||
|
||||
// migrate the database
|
||||
if (env.NODE_ENV === "production") {
|
||||
logger.child({ module: "database" }).info("⚙️ Migrating database");
|
||||
migrate(db, { migrationsFolder: "./migrations" });
|
||||
logger.child({ module: "database" }).info("✅ Database migrated");
|
||||
} else {
|
||||
logger
|
||||
.child({ module: "database" })
|
||||
.info("Not running database migrations, use drizzle-kit push to migrate");
|
||||
}
|
||||
|
||||
async function startApp() {
|
||||
// initialize the next app
|
||||
const app = next({
|
||||
dev: env.NODE_ENV !== "production",
|
||||
|
@ -31,8 +52,6 @@ async function startApp() {
|
|||
|
||||
// create the http server
|
||||
const server = createServer((req, res) => {
|
||||
console.log("req", req.url);
|
||||
|
||||
getHandler(req, res).catch((error) => {
|
||||
logger.error(error);
|
||||
res.statusCode = 500;
|
||||
|
@ -46,8 +65,6 @@ async function startApp() {
|
|||
wss,
|
||||
router: appRouter,
|
||||
createContext: ({ req }) => {
|
||||
console.log("createContext", req.url);
|
||||
|
||||
return createTRPCContext({
|
||||
req: incomingRequestToNextRequest(req),
|
||||
resHeaders: new Headers(),
|
||||
|
@ -65,24 +82,21 @@ async function startApp() {
|
|||
|
||||
// handle the upgrade
|
||||
server.on("upgrade", (req, socket, head) => {
|
||||
console.log("upgrade", req.url);
|
||||
|
||||
// send trpc requests to the trpc server
|
||||
if (req.url?.startsWith("/api/trpc")) {
|
||||
console.log("🚚 passing upgrade to tRPC");
|
||||
wss.handleUpgrade(req, socket, head, (ws) => {
|
||||
wss.emit("connection", ws, req);
|
||||
});
|
||||
} else {
|
||||
console.log("🆙 ws for next.js recieved");
|
||||
void upgradeHandler(req, socket, head);
|
||||
}
|
||||
});
|
||||
|
||||
// start the server
|
||||
server.listen(env.PORT, () => {
|
||||
logger.info(`Server listening on port ${env.PORT}`);
|
||||
server.listen(env.PORT, env.HOSTNAME, () => {
|
||||
logger.info(`🚀 Hostforge`);
|
||||
logger.info(`│ Server listening on ${env.HOSTNAME}:${env.PORT}`);
|
||||
logger.info(`│ Version: ${version}`);
|
||||
logger.info(`│ Environment: ${env.NODE_ENV}`);
|
||||
logger.info(`╰ Build commit: ${env.NEXT_PUBLIC_BUILD_COMMIT_SHA}`);
|
||||
});
|
||||
}
|
||||
|
||||
void startApp();
|
||||
|
|
|
@ -1,10 +1,28 @@
|
|||
import bunyan from "bunyan";
|
||||
import bunyanFormat from "bunyan-format";
|
||||
import { createLogger, format, transports } from "winston";
|
||||
import chalk from "chalk";
|
||||
|
||||
const formatOut = bunyanFormat({ outputMode: "short" });
|
||||
const logger = bunyan.createLogger({
|
||||
name: "hostforge",
|
||||
streams: [{ stream: formatOut }],
|
||||
const logger = createLogger({
|
||||
transports: [
|
||||
new transports.Console({
|
||||
// HH:MM:SS.mmm level hostforge.<module>: message
|
||||
format: format.combine(
|
||||
format.colorize(),
|
||||
format.timestamp({
|
||||
format() {
|
||||
return chalk.gray(new Date().toISOString().split("T")[1]);
|
||||
},
|
||||
}),
|
||||
|
||||
format.printf(({ level, message, timestamp, module }) => {
|
||||
return `${timestamp} ${level} ${chalk.cyan(
|
||||
(module ?? "main") + ":",
|
||||
)} ${message}`;
|
||||
}),
|
||||
),
|
||||
}),
|
||||
new transports.File({ filename: "logs/error.log", level: "error" }),
|
||||
new transports.File({ filename: "logs/combined.log" }),
|
||||
],
|
||||
});
|
||||
|
||||
export default logger;
|
||||
|
|
Loading…
Reference in a new issue