diff --git a/logs/combined.log b/logs/combined.log new file mode 100644 index 0000000..17329b7 --- /dev/null +++ b/logs/combined.log @@ -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 info: Not running database migrations, use drizzle-kit push to migrate +2023-11-12T01:49:17.627Z info: 🚀 Hostforge v1.0.0 ready! +2023-11-12T01:49:17.635Z info: Server listening on port 3000 +2023-11-12T01:50:31.777Z warn: 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"} diff --git a/logs/error.log b/logs/error.log new file mode 100644 index 0000000..e69de29 diff --git a/next.config.mjs b/next.config.mjs index f498f8d..e2315c0 100644 --- a/next.config.mjs +++ b/next.config.mjs @@ -1,8 +1,4 @@ /** @type {import("next").NextConfig} */ -const config = { - experimental: { - instrumentationHook: true, - }, -}; +const config = {}; export default config; diff --git a/package.json b/package.json index 8bf7cc9..1807546 100644 --- a/package.json +++ b/package.json @@ -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": { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 77292ef..718a383 100644 --- a/pnpm-lock.yaml +++ b/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'} diff --git a/src/app/home/StatCard.module.css b/src/app/home/StatCard.module.css index 3595072..1be022f 100644 --- a/src/app/home/StatCard.module.css +++ b/src/app/home/StatCard.module.css @@ -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"), diff --git a/src/app/home/StatCard.tsx b/src/app/home/StatCard.tsx index 2d0552c..71c07bb 100644 --- a/src/app/home/StatCard.tsx +++ b/src/app/home/StatCard.tsx @@ -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>(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>(props: { -
-

- - {props.unit} -

-

- {props.subvalue} -

+
+
+
+ + {props.unit} + + {props.subvalue !== undefined && ( +

+ {props.subvalue} +

+ )} +
+ + {props.secondaryValue !== undefined && ( +
+ + {props.secondaryUnit} + + {props.secondarySubvalue && ( +

+ {props.secondarySubvalue} +

+ )} +
+ )} +
diff --git a/src/app/home/SystemStatistics.tsx b/src/app/home/SystemStatistics.tsx index 2bf903e..6f1b632 100644 --- a/src/app/home/SystemStatistics.tsx +++ b/src/app/home/SystemStatistics.tsx @@ -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 }) {
+ + diff --git a/src/components/AnimatedPercent.module.css b/src/components/AnimatedPercent.module.css new file mode 100644 index 0000000..30fae5c --- /dev/null +++ b/src/components/AnimatedPercent.module.css @@ -0,0 +1,33 @@ +@property --percent { + syntax: ""; + initial-value: 0; + inherits: false; +} +@property --temp { + syntax: ""; + initial-value: 0; + inherits: false; +} +@property --v1 { + syntax: ""; + initial-value: 0; + inherits: false; +} +@property --v2 { + syntax: ""; + 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); +} diff --git a/src/components/AnimatedPercent.tsx b/src/components/AnimatedPercent.tsx new file mode 100644 index 0000000..edd803e --- /dev/null +++ b/src/components/AnimatedPercent.tsx @@ -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(null); + + useEffect(() => { + divRef.current?.style.setProperty("--percent", props.number.toString()); + }, [props.number]); + + return ( +
+
+ {props.number.toFixed(2)} +
+
+
+ ); +} diff --git a/src/env.ts b/src/env.ts index 1bb348d..e122664 100644 --- a/src/env.ts +++ b/src/env.ts @@ -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 diff --git a/src/instrumentation.ts b/src/instrumentation.ts deleted file mode 100644 index d1f09e3..0000000 --- a/src/instrumentation.ts +++ /dev/null @@ -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!`); - } -} diff --git a/src/server/api/routers/system/index.ts b/src/server/api/routers/system/index.ts index 6a6e123..ff0e0cc 100644 --- a/src/server/api/routers/system/index.ts +++ b/src/server/api/routers/system/index.ts @@ -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>>( - (observer) => { - console.log("subscription got"); - fetchSystemInfo().then(observer.next.bind(observer)); + return observable((observer) => { + const update = observer.next.bind(observer); - const interval = setInterval(async () => { - observer.next(await fetchSystemInfo()); - }, 1000); - - return () => { - clearInterval(interval); - }; - }, - ); + stats.events.on("onUpdate", update); + return () => { + stats.events.off("onUpdate", update); + }; + }); }), }); diff --git a/src/server/api/trpc.ts b/src/server/api/trpc.ts index c6244dd..2c6c73a 100644 --- a/src/server/api/trpc.ts +++ b/src/server/api/trpc.ts @@ -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"); diff --git a/src/server/modules/stats/index.ts b/src/server/modules/stats/index.ts new file mode 100644 index 0000000..95fbd42 --- /dev/null +++ b/src/server/modules/stats/index.ts @@ -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; + + /** + * 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(); diff --git a/src/server/server.ts b/src/server/server.ts index 9c6ef8b..0f68951 100644 --- a/src/server/server.ts +++ b/src/server/server.ts @@ -1,88 +1,102 @@ -// 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"; -async function startApp() { - // initialize the next app - const app = next({ - dev: env.NODE_ENV !== "production", - hostname: env.HOSTNAME, - port: env.PORT, - // dir: path.join(__dirname, "../.."), - customServer: true, - isNodeDebugging: true, - }); - - await app.prepare(); - - // get the handles - const getHandler = app.getRequestHandler(); - const upgradeHandler = app.getUpgradeHandler(); - - // 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; - res.end("Internal Server Error"); - }); - }); - - // create the websocket server - const wss = new WebSocketServer({ noServer: true }); - const trpcHandler = applyWSSHandler({ - wss, - router: appRouter, - createContext: ({ req }) => { - console.log("createContext", req.url); - - return createTRPCContext({ - req: incomingRequestToNextRequest(req), - resHeaders: new Headers(), - }); - }, - }); - - process.on("SIGTERM", () => { - logger.warn("SIGTERM received, shutting down..."); - trpcHandler.broadcastReconnectNotification(); - server.close(() => { - process.exit(0); - }); - }); - - // 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}`); - }); +// 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)}`); } -void startApp(); +// 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"); +} + +// initialize the next app +const app = next({ + dev: env.NODE_ENV !== "production", + hostname: env.HOSTNAME, + port: env.PORT, + // dir: path.join(__dirname, "../.."), + customServer: true, + isNodeDebugging: true, +}); + +await app.prepare(); + +// get the handles +const getHandler = app.getRequestHandler(); +const upgradeHandler = app.getUpgradeHandler(); + +// create the http server +const server = createServer((req, res) => { + getHandler(req, res).catch((error) => { + logger.error(error); + res.statusCode = 500; + res.end("Internal Server Error"); + }); +}); + +// create the websocket server +const wss = new WebSocketServer({ noServer: true }); +const trpcHandler = applyWSSHandler({ + wss, + router: appRouter, + createContext: ({ req }) => { + return createTRPCContext({ + req: incomingRequestToNextRequest(req), + resHeaders: new Headers(), + }); + }, +}); + +process.on("SIGTERM", () => { + logger.warn("SIGTERM received, shutting down..."); + 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, (ws) => { + wss.emit("connection", ws, req); + }); + } else { + void upgradeHandler(req, socket, head); + } +}); + +// start the server +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}`); +}); diff --git a/src/server/utils/logger.ts b/src/server/utils/logger.ts index fedcac7..4fa87e6 100644 --- a/src/server/utils/logger.ts +++ b/src/server/utils/logger.ts @@ -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.: 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;