Compare commits
26 Commits
MickLesk-p
...
feat/447
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
944a527972 | ||
|
|
34eade3971 | ||
|
|
9b77fc7ddb | ||
|
|
db12ac4219 | ||
|
|
f66d1db861 | ||
|
|
886c3e37ff | ||
|
|
38deb09aa9 | ||
|
|
6d326dce1f | ||
|
|
6c8e177d3e | ||
|
|
879a548345 | ||
|
|
64cd81d5ba | ||
|
|
61e75949c8 | ||
|
|
a5d24bfad7 | ||
|
|
04595c0093 | ||
|
|
06fdb4889d | ||
|
|
38d4f9f918 | ||
|
|
63dc7c6983 | ||
|
|
d57c6059fc | ||
|
|
eb152f9fae | ||
|
|
1a8e98fec0 | ||
|
|
83a1c7ea31 | ||
|
|
79c63a7d3d | ||
|
|
753721eee0 | ||
|
|
09607296af | ||
|
|
c88040084a | ||
|
|
2573eb7314 |
2
.github/pull_request_template.md
vendored
2
.github/pull_request_template.md
vendored
@@ -4,7 +4,7 @@
|
||||
|
||||
|
||||
## 🔗 Related PR / Issue
|
||||
Link: #
|
||||
Fixes: #
|
||||
|
||||
|
||||
## ✅ Prerequisites (**X** in brackets)
|
||||
|
||||
190
package-lock.json
generated
190
package-lock.json
generated
@@ -15,7 +15,7 @@
|
||||
"@radix-ui/react-slot": "^1.2.4",
|
||||
"@t3-oss/env-nextjs": "^0.13.10",
|
||||
"@tailwindcss/typography": "^0.5.19",
|
||||
"@tanstack/react-query": "^5.90.16",
|
||||
"@tanstack/react-query": "^5.90.18",
|
||||
"@trpc/client": "^11.8.1",
|
||||
"@trpc/react-query": "^11.8.1",
|
||||
"@trpc/server": "^11.8.1",
|
||||
@@ -33,7 +33,7 @@
|
||||
"dotenv": "^17.2.3",
|
||||
"jsonwebtoken": "^9.0.3",
|
||||
"lucide-react": "^0.562.0",
|
||||
"next": "^16.1.1",
|
||||
"next": "^16.1.3",
|
||||
"node-cron": "^4.2.1",
|
||||
"node-pty": "^1.1.0",
|
||||
"react": "^19.2.3",
|
||||
@@ -57,19 +57,19 @@
|
||||
"@types/bcryptjs": "^3.0.0",
|
||||
"@types/better-sqlite3": "^7.6.13",
|
||||
"@types/jsonwebtoken": "^9.0.10",
|
||||
"@types/node": "^24.10.4",
|
||||
"@types/node": "^24.10.9",
|
||||
"@types/node-cron": "^3.0.11",
|
||||
"@types/react": "^19.2.8",
|
||||
"@types/react-dom": "^19.2.3",
|
||||
"@vitejs/plugin-react": "^5.1.2",
|
||||
"@vitest/coverage-v8": "^4.0.17",
|
||||
"@vitest/ui": "^4.0.17",
|
||||
"baseline-browser-mapping": "^2.9.14",
|
||||
"baseline-browser-mapping": "^2.9.15",
|
||||
"eslint": "^9.39.2",
|
||||
"eslint-config-next": "^16.1.1",
|
||||
"eslint-config-next": "^16.1.3",
|
||||
"jsdom": "^27.4.0",
|
||||
"postcss": "^8.5.6",
|
||||
"prettier": "^3.7.4",
|
||||
"prettier": "^3.8.0",
|
||||
"prettier-plugin-tailwindcss": "^0.7.2",
|
||||
"prisma": "^7.2.0",
|
||||
"tailwindcss": "^4.1.18",
|
||||
@@ -195,6 +195,7 @@
|
||||
"integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@babel/code-frame": "^7.27.1",
|
||||
"@babel/generator": "^7.28.5",
|
||||
@@ -310,7 +311,7 @@
|
||||
"version": "7.27.1",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz",
|
||||
"integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==",
|
||||
"devOptional": true,
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
@@ -320,7 +321,7 @@
|
||||
"version": "7.28.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz",
|
||||
"integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==",
|
||||
"devOptional": true,
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
@@ -354,7 +355,7 @@
|
||||
"version": "7.28.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.5.tgz",
|
||||
"integrity": "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==",
|
||||
"devOptional": true,
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/types": "^7.28.5"
|
||||
@@ -445,7 +446,7 @@
|
||||
"version": "7.28.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.5.tgz",
|
||||
"integrity": "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==",
|
||||
"devOptional": true,
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/helper-string-parser": "^7.27.1",
|
||||
@@ -590,6 +591,7 @@
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
@@ -633,6 +635,7 @@
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
@@ -642,7 +645,8 @@
|
||||
"resolved": "https://registry.npmjs.org/@electric-sql/pglite/-/pglite-0.3.2.tgz",
|
||||
"integrity": "sha512-zfWWa+V2ViDCY/cmUfRqeWY1yLto+EpxjXnZzenB1TyxsTiXaTWeZFIZw6mac52BsuQm0RjCnisjBtdBaXOI6w==",
|
||||
"devOptional": true,
|
||||
"license": "Apache-2.0"
|
||||
"license": "Apache-2.0",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/@electric-sql/pglite-socket": {
|
||||
"version": "0.0.6",
|
||||
@@ -1951,15 +1955,15 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@next/env": {
|
||||
"version": "16.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@next/env/-/env-16.1.1.tgz",
|
||||
"integrity": "sha512-3oxyM97Sr2PqiVyMyrZUtrtM3jqqFxOQJVuKclDsgj/L728iZt/GyslkN4NwarledZATCenbk4Offjk1hQmaAA==",
|
||||
"version": "16.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@next/env/-/env-16.1.3.tgz",
|
||||
"integrity": "sha512-BLP14oBOvZWXgfdJf9ao+VD8O30uE+x7PaV++QtACLX329WcRSJRO5YJ+Bcvu0Q+c/lei41TjSiFf6pXqnpbQA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@next/eslint-plugin-next": {
|
||||
"version": "16.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@next/eslint-plugin-next/-/eslint-plugin-next-16.1.1.tgz",
|
||||
"integrity": "sha512-Ovb/6TuLKbE1UiPcg0p39Ke3puyTCIKN9hGbNItmpQsp+WX3qrjO3WaMVSi6JHr9X1NrmthqIguVHodMJbh/dw==",
|
||||
"version": "16.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@next/eslint-plugin-next/-/eslint-plugin-next-16.1.3.tgz",
|
||||
"integrity": "sha512-MqBh3ltFAy0AZCRFVdjVjjeV7nEszJDaVIpDAnkQcn8U9ib6OEwkSnuK6xdYxMGPhV/Y4IlY6RbDipPOpLfBqQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -1967,9 +1971,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@next/swc-darwin-arm64": {
|
||||
"version": "16.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-16.1.1.tgz",
|
||||
"integrity": "sha512-JS3m42ifsVSJjSTzh27nW+Igfha3NdBOFScr9C80hHGrWx55pTrVL23RJbqir7k7/15SKlrLHhh/MQzqBBYrQA==",
|
||||
"version": "16.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-16.1.3.tgz",
|
||||
"integrity": "sha512-CpOD3lmig6VflihVoGxiR/l5Jkjfi4uLaOR4ziriMv0YMDoF6cclI+p5t2nstM8TmaFiY6PCTBgRWB57/+LiBA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -1983,9 +1987,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@next/swc-darwin-x64": {
|
||||
"version": "16.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-16.1.1.tgz",
|
||||
"integrity": "sha512-hbyKtrDGUkgkyQi1m1IyD3q4I/3m9ngr+V93z4oKHrPcmxwNL5iMWORvLSGAf2YujL+6HxgVvZuCYZfLfb4bGw==",
|
||||
"version": "16.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-16.1.3.tgz",
|
||||
"integrity": "sha512-aF4us2JXh0zn3hNxvL1Bx3BOuh8Lcw3p3Xnurlvca/iptrDH1BrpObwkw9WZra7L7/0qB9kjlREq3hN/4x4x+Q==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -1999,9 +2003,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@next/swc-linux-arm64-gnu": {
|
||||
"version": "16.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-16.1.1.tgz",
|
||||
"integrity": "sha512-/fvHet+EYckFvRLQ0jPHJCUI5/B56+2DpI1xDSvi80r/3Ez+Eaa2Yq4tJcRTaB1kqj/HrYKn8Yplm9bNoMJpwQ==",
|
||||
"version": "16.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-16.1.3.tgz",
|
||||
"integrity": "sha512-8VRkcpcfBtYvhGgXAF7U3MBx6+G1lACM1XCo1JyaUr4KmAkTNP8Dv2wdMq7BI+jqRBw3zQE7c57+lmp7jCFfKA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -2015,9 +2019,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@next/swc-linux-arm64-musl": {
|
||||
"version": "16.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-16.1.1.tgz",
|
||||
"integrity": "sha512-MFHrgL4TXNQbBPzkKKur4Fb5ICEJa87HM7fczFs2+HWblM7mMLdco3dvyTI+QmLBU9xgns/EeeINSZD6Ar+oLg==",
|
||||
"version": "16.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-16.1.3.tgz",
|
||||
"integrity": "sha512-UbFx69E2UP7MhzogJRMFvV9KdEn4sLGPicClwgqnLht2TEi204B71HuVfps3ymGAh0c44QRAF+ZmvZZhLLmhNg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -2031,9 +2035,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@next/swc-linux-x64-gnu": {
|
||||
"version": "16.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-16.1.1.tgz",
|
||||
"integrity": "sha512-20bYDfgOQAPUkkKBnyP9PTuHiJGM7HzNBbuqmD0jiFVZ0aOldz+VnJhbxzjcSabYsnNjMPsE0cyzEudpYxsrUQ==",
|
||||
"version": "16.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-16.1.3.tgz",
|
||||
"integrity": "sha512-SzGTfTjR5e9T+sZh5zXqG/oeRQufExxBF6MssXS7HPeZFE98JDhCRZXpSyCfWrWrYrzmnw/RVhlP2AxQm+wkRQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -2047,9 +2051,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@next/swc-linux-x64-musl": {
|
||||
"version": "16.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-16.1.1.tgz",
|
||||
"integrity": "sha512-9pRbK3M4asAHQRkwaXwu601oPZHghuSC8IXNENgbBSyImHv/zY4K5udBusgdHkvJ/Tcr96jJwQYOll0qU8+fPA==",
|
||||
"version": "16.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-16.1.3.tgz",
|
||||
"integrity": "sha512-HlrDpj0v+JBIvQex1mXHq93Mht5qQmfyci+ZNwGClnAQldSfxI6h0Vupte1dSR4ueNv4q7qp5kTnmLOBIQnGow==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -2063,9 +2067,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@next/swc-win32-arm64-msvc": {
|
||||
"version": "16.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-16.1.1.tgz",
|
||||
"integrity": "sha512-bdfQkggaLgnmYrFkSQfsHfOhk/mCYmjnrbRCGgkMcoOBZ4n+TRRSLmT/CU5SATzlBJ9TpioUyBW/vWFXTqQRiA==",
|
||||
"version": "16.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-16.1.3.tgz",
|
||||
"integrity": "sha512-3gFCp83/LSduZMSIa+lBREP7+5e7FxpdBoc9QrCdmp+dapmTK9I+SLpY60Z39GDmTXSZA4huGg9WwmYbr6+WRw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -2079,9 +2083,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@next/swc-win32-x64-msvc": {
|
||||
"version": "16.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-16.1.1.tgz",
|
||||
"integrity": "sha512-Ncwbw2WJ57Al5OX0k4chM68DKhEPlrXBaSXDCi2kPi5f4d8b3ejr3RRJGfKBLrn2YJL5ezNS7w2TZLHSti8CMw==",
|
||||
"version": "16.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-16.1.3.tgz",
|
||||
"integrity": "sha512-1SZVfFT8zmMB+Oblrh5OKDvUo5mYQOkX2We6VGzpg7JUVZlqe4DYOFGKYZKTweSx1gbMixyO1jnFT4thU+nNHQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -3705,9 +3709,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@tanstack/query-core": {
|
||||
"version": "5.90.16",
|
||||
"resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.90.16.tgz",
|
||||
"integrity": "sha512-MvtWckSVufs/ja463/K4PyJeqT+HMlJWtw6PrCpywznd2NSgO3m4KwO9RqbFqGg6iDE8vVMFWMeQI4Io3eEYww==",
|
||||
"version": "5.90.18",
|
||||
"resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.90.18.tgz",
|
||||
"integrity": "sha512-rbGx6bHgPNVzutP7BEr+53UPKohpckqlMAad+To9UxTbeaQ+kC/1SDRj+QzkwbQ7qhLT/1IKp34yS6thda6fzA==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "github",
|
||||
@@ -3715,12 +3719,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@tanstack/react-query": {
|
||||
"version": "5.90.16",
|
||||
"resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.90.16.tgz",
|
||||
"integrity": "sha512-bpMGOmV4OPmif7TNMteU/Ehf/hoC0Kf98PDc0F4BZkFrEapRMEqI/V6YS0lyzwSV6PQpY1y4xxArUIfBW5LVxQ==",
|
||||
"version": "5.90.18",
|
||||
"resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.90.18.tgz",
|
||||
"integrity": "sha512-KqNZX0C5IFz4639zR1ilnQ288tQdJrMNLtzmlzyJ14xauBkhtLEy3mPU/V4KiHsr41eL1ILZbDP36TB12lYfCQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@tanstack/query-core": "5.90.16"
|
||||
"@tanstack/query-core": "5.90.18"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
@@ -3828,6 +3833,7 @@
|
||||
"https://trpc.io/sponsor"
|
||||
],
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"peerDependencies": {
|
||||
"@trpc/server": "11.8.1",
|
||||
"typescript": ">=5.7.2"
|
||||
@@ -3858,6 +3864,7 @@
|
||||
"https://trpc.io/sponsor"
|
||||
],
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"peerDependencies": {
|
||||
"typescript": ">=5.7.2"
|
||||
}
|
||||
@@ -3878,8 +3885,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz",
|
||||
"integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/babel__core": {
|
||||
"version": "7.20.5",
|
||||
@@ -4039,9 +4045,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "24.10.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.4.tgz",
|
||||
"integrity": "sha512-vnDVpYPMzs4wunl27jHrfmwojOGKya0xyM3sH+UE5iv5uPS6vX7UIoh6m+vQc5LGBq52HBKPIn/zcSZVzeDEZg==",
|
||||
"version": "24.10.9",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.9.tgz",
|
||||
"integrity": "sha512-ne4A0IpG3+2ETuREInjPNhUGis1SFjv1d5asp8MzEAGtOZeTeHVDOYqOgqfhvseqg/iXty2hjBf1zAOb7RNiNw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"undici-types": "~7.16.0"
|
||||
@@ -4065,6 +4071,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.8.tgz",
|
||||
"integrity": "sha512-3MbSL37jEchWZz2p2mjntRZtPt837ij10ApxKfgmXCTuHWagYg7iA5bqPw6C8BMPfwidlvfPI/fxOc42HLhcyg==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"csstype": "^3.2.2"
|
||||
}
|
||||
@@ -4075,6 +4082,7 @@
|
||||
"integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==",
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"peerDependencies": {
|
||||
"@types/react": "^19.2.0"
|
||||
}
|
||||
@@ -4148,6 +4156,7 @@
|
||||
"integrity": "sha512-npiaib8XzbjtzS2N4HlqPvlpxpmZ14FjSJrteZpPxGUaYPlvhzlzUZ4mZyABo0EFrOWnvyd0Xxroq//hKhtAWg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@typescript-eslint/scope-manager": "8.53.0",
|
||||
"@typescript-eslint/types": "8.53.0",
|
||||
@@ -4802,6 +4811,7 @@
|
||||
"integrity": "sha512-hRDjg6dlDz7JlZAvjbiCdAJ3SDG+NH8tjZe21vjxfvT2ssYAn72SRXMge3dKKABm3bIJ3C+3wdunIdur8PHEAw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@vitest/utils": "4.0.17",
|
||||
"fflate": "^0.8.2",
|
||||
@@ -4859,6 +4869,7 @@
|
||||
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"acorn": "bin/acorn"
|
||||
},
|
||||
@@ -4909,7 +4920,6 @@
|
||||
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
@@ -5266,9 +5276,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/baseline-browser-mapping": {
|
||||
"version": "2.9.14",
|
||||
"resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.14.tgz",
|
||||
"integrity": "sha512-B0xUquLkiGLgHhpPBqvl7GWegWBUNuujQ6kXd/r1U38ElPT6Ok8KZ8e+FpUGEc2ZoRQUzq/aUnaKFc/svWUGSg==",
|
||||
"version": "2.9.15",
|
||||
"resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.15.tgz",
|
||||
"integrity": "sha512-kX8h7K2srmDyYnXRIppo4AH/wYgzWVCs+eKr3RusRSQ5PvRYoEFmR/I0PbdTjKFAoKqp5+kbxnNTFO9jOfSVJg==",
|
||||
"license": "Apache-2.0",
|
||||
"bin": {
|
||||
"baseline-browser-mapping": "dist/cli.js"
|
||||
@@ -5371,6 +5381,7 @@
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"baseline-browser-mapping": "^2.8.25",
|
||||
"caniuse-lite": "^1.0.30001754",
|
||||
@@ -6096,8 +6107,7 @@
|
||||
"resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz",
|
||||
"integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/dotenv": {
|
||||
"version": "17.2.3",
|
||||
@@ -6456,6 +6466,7 @@
|
||||
"integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@eslint-community/eslint-utils": "^4.8.0",
|
||||
"@eslint-community/regexpp": "^4.12.1",
|
||||
@@ -6511,13 +6522,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-config-next": {
|
||||
"version": "16.1.1",
|
||||
"resolved": "https://registry.npmjs.org/eslint-config-next/-/eslint-config-next-16.1.1.tgz",
|
||||
"integrity": "sha512-55nTpVWm3qeuxoQKLOjQVciKZJUphKrNM0fCcQHAIOGl6VFXgaqeMfv0aKJhs7QtcnlAPhNVqsqRfRjeKBPIUA==",
|
||||
"version": "16.1.3",
|
||||
"resolved": "https://registry.npmjs.org/eslint-config-next/-/eslint-config-next-16.1.3.tgz",
|
||||
"integrity": "sha512-q2Z87VSsoJcv+vgR+Dm8NPRf+rErXcRktuBR5y3umo/j5zLjIWH7rqBCh3X804gUGKbOrqbgsLUkqDE35C93Gw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@next/eslint-plugin-next": "16.1.1",
|
||||
"@next/eslint-plugin-next": "16.1.3",
|
||||
"eslint-import-resolver-node": "^0.3.6",
|
||||
"eslint-import-resolver-typescript": "^3.5.2",
|
||||
"eslint-plugin-import": "^2.32.0",
|
||||
@@ -6641,6 +6652,7 @@
|
||||
"integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@rtsao/scc": "^1.1.0",
|
||||
"array-includes": "^3.1.9",
|
||||
@@ -7654,6 +7666,7 @@
|
||||
"integrity": "sha512-BIdolzGpDO9MQ4nu3AUuDwHZZ+KViNm+EZ75Ae55eMXMqLVhDFqEMXxtUe9Qh8hjL+pIna/frs2j6Y2yD5Ua/g==",
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=16.9.0"
|
||||
}
|
||||
@@ -8427,6 +8440,7 @@
|
||||
"integrity": "sha512-mjzqwWRD9Y1J1KUi7W97Gja1bwOOM5Ug0EZ6UDK3xS7j7mndrkwozHtSblfomlzyB4NepioNt+B2sOSzczVgtQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@acemir/cssom": "^0.9.28",
|
||||
"@asamuzakjp/dom-selector": "^6.7.6",
|
||||
@@ -9051,7 +9065,6 @@
|
||||
"integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"lz-string": "bin/bin.js"
|
||||
}
|
||||
@@ -10198,12 +10211,12 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/next": {
|
||||
"version": "16.1.1",
|
||||
"resolved": "https://registry.npmjs.org/next/-/next-16.1.1.tgz",
|
||||
"integrity": "sha512-QI+T7xrxt1pF6SQ/JYFz95ro/mg/1Znk5vBebsWwbpejj1T0A23hO7GYEaVac9QUOT2BIMiuzm0L99ooq7k0/w==",
|
||||
"version": "16.1.3",
|
||||
"resolved": "https://registry.npmjs.org/next/-/next-16.1.3.tgz",
|
||||
"integrity": "sha512-gthG3TRD+E3/mA0uDQb9lqBmx1zVosq5kIwxNN6+MRNd085GzD+9VXMPUs+GGZCbZ+GDZdODUq4Pm7CTXK6ipw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@next/env": "16.1.1",
|
||||
"@next/env": "16.1.3",
|
||||
"@swc/helpers": "0.5.15",
|
||||
"baseline-browser-mapping": "^2.8.3",
|
||||
"caniuse-lite": "^1.0.30001579",
|
||||
@@ -10217,14 +10230,14 @@
|
||||
"node": ">=20.9.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@next/swc-darwin-arm64": "16.1.1",
|
||||
"@next/swc-darwin-x64": "16.1.1",
|
||||
"@next/swc-linux-arm64-gnu": "16.1.1",
|
||||
"@next/swc-linux-arm64-musl": "16.1.1",
|
||||
"@next/swc-linux-x64-gnu": "16.1.1",
|
||||
"@next/swc-linux-x64-musl": "16.1.1",
|
||||
"@next/swc-win32-arm64-msvc": "16.1.1",
|
||||
"@next/swc-win32-x64-msvc": "16.1.1",
|
||||
"@next/swc-darwin-arm64": "16.1.3",
|
||||
"@next/swc-darwin-x64": "16.1.3",
|
||||
"@next/swc-linux-arm64-gnu": "16.1.3",
|
||||
"@next/swc-linux-arm64-musl": "16.1.3",
|
||||
"@next/swc-linux-x64-gnu": "16.1.3",
|
||||
"@next/swc-linux-x64-musl": "16.1.3",
|
||||
"@next/swc-win32-arm64-msvc": "16.1.3",
|
||||
"@next/swc-win32-x64-msvc": "16.1.3",
|
||||
"sharp": "^0.34.4"
|
||||
},
|
||||
"peerDependencies": {
|
||||
@@ -10805,11 +10818,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/prettier": {
|
||||
"version": "3.7.4",
|
||||
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.7.4.tgz",
|
||||
"integrity": "sha512-v6UNi1+3hSlVvv8fSaoUbggEM5VErKmmpGA7Pl3HF8V6uKY7rvClBOJlH6yNwQtfTueNkGVpOv/mtWL9L4bgRA==",
|
||||
"version": "3.8.0",
|
||||
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.0.tgz",
|
||||
"integrity": "sha512-yEPsovQfpxYfgWNhCfECjG5AQaO+K3dp6XERmOepyPDVqcJm+bjyCVO3pmU+nAPe0N5dDvekfGezt/EIiRe1TA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"prettier": "bin/prettier.cjs"
|
||||
},
|
||||
@@ -10905,7 +10919,6 @@
|
||||
"integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"ansi-regex": "^5.0.1",
|
||||
"ansi-styles": "^5.0.0",
|
||||
@@ -10921,7 +10934,6 @@
|
||||
"integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
@@ -10936,6 +10948,7 @@
|
||||
"devOptional": true,
|
||||
"hasInstallScript": true,
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@prisma/config": "7.2.0",
|
||||
"@prisma/dev": "0.17.0",
|
||||
@@ -11124,6 +11137,7 @@
|
||||
"resolved": "https://registry.npmjs.org/react/-/react-19.2.3.tgz",
|
||||
"integrity": "sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
@@ -11133,6 +11147,7 @@
|
||||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.3.tgz",
|
||||
"integrity": "sha512-yELu4WmLPw5Mr/lmeEpox5rw3RETacE++JgHqQzd2dg+YbJuat3jH4ingc+WPZhxaoFzdv9y33G+F7Nl5O0GBg==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"scheduler": "^0.27.0"
|
||||
},
|
||||
@@ -11145,8 +11160,7 @@
|
||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
|
||||
"integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/react-markdown": {
|
||||
"version": "10.1.0",
|
||||
@@ -12369,7 +12383,8 @@
|
||||
"version": "4.1.18",
|
||||
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.18.tgz",
|
||||
"integrity": "sha512-4+Z+0yiYyEtUVCScyfHCxOYP06L5Ne+JiHhY2IjR2KWMIWhJOYZKLSGZaP5HkZ8+bY0cxfzwDE5uOmzFXyIwxw==",
|
||||
"license": "MIT"
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/tapable": {
|
||||
"version": "2.3.0",
|
||||
@@ -12471,6 +12486,7 @@
|
||||
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
@@ -13247,6 +13263,7 @@
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
|
||||
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"tsc": "bin/tsc",
|
||||
"tsserver": "bin/tsserver"
|
||||
@@ -13522,6 +13539,7 @@
|
||||
"integrity": "sha512-mm1rxUsmOxzrwnX5arGS+U4T25RdvpPjPN4yR0u9pUBov9+zGVtO84tif1eY4r6zWxVxu3KzIyknJy3rxfRZZg==",
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"peerDependencies": {
|
||||
"typescript": ">=5"
|
||||
},
|
||||
@@ -13565,6 +13583,7 @@
|
||||
"integrity": "sha512-NL8jTlbo0Tn4dUEXEsUg8KeyG/Lkmc4Fnzb8JXN/Ykm9G4HNImjtABMJgkQoVjOBN/j2WAwDTRytdqJbZsah7w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"esbuild": "^0.25.0",
|
||||
"fdir": "^6.5.0",
|
||||
@@ -13658,6 +13677,7 @@
|
||||
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
@@ -13671,6 +13691,7 @@
|
||||
"integrity": "sha512-FQMeF0DJdWY0iOnbv466n/0BudNdKj1l5jYgl5JVTwjSsZSlqyXFt/9+1sEyhR6CLowbZpV7O1sCHrzBhucKKg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@vitest/expect": "4.0.17",
|
||||
"@vitest/mocker": "4.0.17",
|
||||
@@ -14014,6 +14035,7 @@
|
||||
"resolved": "https://registry.npmjs.org/zod/-/zod-4.3.5.tgz",
|
||||
"integrity": "sha512-k7Nwx6vuWx1IJ9Bjuf4Zt1PEllcwe7cls3VNzm4CQ1/hgtFUK2bRNG3rvnpPUhFjmqJKAKtjV576KnUkHocg/g==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/colinhacks"
|
||||
}
|
||||
|
||||
12
package.json
12
package.json
@@ -31,7 +31,7 @@
|
||||
"@radix-ui/react-slot": "^1.2.4",
|
||||
"@t3-oss/env-nextjs": "^0.13.10",
|
||||
"@tailwindcss/typography": "^0.5.19",
|
||||
"@tanstack/react-query": "^5.90.16",
|
||||
"@tanstack/react-query": "^5.90.18",
|
||||
"@trpc/client": "^11.8.1",
|
||||
"@trpc/react-query": "^11.8.1",
|
||||
"@trpc/server": "^11.8.1",
|
||||
@@ -49,7 +49,7 @@
|
||||
"dotenv": "^17.2.3",
|
||||
"jsonwebtoken": "^9.0.3",
|
||||
"lucide-react": "^0.562.0",
|
||||
"next": "^16.1.1",
|
||||
"next": "^16.1.3",
|
||||
"node-cron": "^4.2.1",
|
||||
"node-pty": "^1.1.0",
|
||||
"react": "^19.2.3",
|
||||
@@ -73,19 +73,19 @@
|
||||
"@types/bcryptjs": "^3.0.0",
|
||||
"@types/better-sqlite3": "^7.6.13",
|
||||
"@types/jsonwebtoken": "^9.0.10",
|
||||
"@types/node": "^24.10.4",
|
||||
"@types/node": "^24.10.9",
|
||||
"@types/node-cron": "^3.0.11",
|
||||
"@types/react": "^19.2.8",
|
||||
"@types/react-dom": "^19.2.3",
|
||||
"@vitejs/plugin-react": "^5.1.2",
|
||||
"@vitest/coverage-v8": "^4.0.17",
|
||||
"@vitest/ui": "^4.0.17",
|
||||
"baseline-browser-mapping": "^2.9.14",
|
||||
"baseline-browser-mapping": "^2.9.15",
|
||||
"eslint": "^9.39.2",
|
||||
"eslint-config-next": "^16.1.1",
|
||||
"eslint-config-next": "^16.1.3",
|
||||
"jsdom": "^27.4.0",
|
||||
"postcss": "^8.5.6",
|
||||
"prettier": "^3.7.4",
|
||||
"prettier": "^3.8.0",
|
||||
"prettier-plugin-tailwindcss": "^0.7.2",
|
||||
"prisma": "^7.2.0",
|
||||
"tailwindcss": "^4.1.18",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env bash
|
||||
SCRIPT_DIR="$(dirname "$0")"
|
||||
source "$SCRIPT_DIR/../core/build.func"
|
||||
# Copyright (c) 2021-2025 tteck
|
||||
# Copyright (c) 2021-2026 tteck
|
||||
# Author: tteck (tteckster)
|
||||
# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE
|
||||
# Source: https://www.debian.org/
|
||||
@@ -40,5 +40,5 @@ start
|
||||
build_container
|
||||
description
|
||||
|
||||
msg_ok "Completed Successfully!\n"
|
||||
msg_ok "Completed successfully!\n"
|
||||
echo -e "${CREATING}${GN}${APP} setup has been successfully initialized!${CL}"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Copyright (c) 2021-2025 tteck
|
||||
# Copyright (c) 2021-2026 tteck
|
||||
# Author: tteck (tteckster)
|
||||
# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE
|
||||
# Source: https://www.debian.org/
|
||||
|
||||
@@ -1610,6 +1610,7 @@ class ScriptExecutionHandler {
|
||||
// TerminalHandler removed - not used by current application
|
||||
|
||||
app.prepare().then(() => {
|
||||
console.log('> Next.js app prepared successfully');
|
||||
const httpServer = createServer(async (req, res) => {
|
||||
try {
|
||||
// Be sure to pass `true` as the second argument to `url.parse`.
|
||||
@@ -1715,4 +1716,9 @@ app.prepare().then(() => {
|
||||
autoSyncModule.setupGracefulShutdown();
|
||||
}
|
||||
});
|
||||
}).catch((err) => {
|
||||
console.error('> Failed to start server:', err.message);
|
||||
console.error('> If you see "Could not find a production build", run: npm run build');
|
||||
console.error('> Full error:', err);
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
@@ -58,6 +58,11 @@ export function ConfigurationModal({
|
||||
// Advanced mode state
|
||||
const [advancedVars, setAdvancedVars] = useState<EnvVars>({});
|
||||
|
||||
// Discovered SSH keys on the Proxmox host (advanced mode only)
|
||||
const [discoveredSshKeys, setDiscoveredSshKeys] = useState<string[]>([]);
|
||||
const [discoveredSshKeysLoading, setDiscoveredSshKeysLoading] = useState(false);
|
||||
const [discoveredSshKeysError, setDiscoveredSshKeysError] = useState<string | null>(null);
|
||||
|
||||
// Validation errors
|
||||
const [errors, setErrors] = useState<Record<string, string>>({});
|
||||
|
||||
@@ -119,6 +124,38 @@ export function ConfigurationModal({
|
||||
}
|
||||
}, [actualScript, server, mode, resources, slug]);
|
||||
|
||||
// Discover SSH keys on the Proxmox host when advanced mode is open
|
||||
useEffect(() => {
|
||||
if (!server?.id || !isOpen || mode !== 'advanced') {
|
||||
setDiscoveredSshKeys([]);
|
||||
setDiscoveredSshKeysError(null);
|
||||
return;
|
||||
}
|
||||
let cancelled = false;
|
||||
setDiscoveredSshKeysLoading(true);
|
||||
setDiscoveredSshKeysError(null);
|
||||
fetch(`/api/servers/${server.id}/discover-ssh-keys`)
|
||||
.then((res) => {
|
||||
if (!res.ok) throw new Error(res.status === 404 ? 'Server not found' : res.statusText);
|
||||
return res.json();
|
||||
})
|
||||
.then((data: { keys?: string[] }) => {
|
||||
if (!cancelled && Array.isArray(data.keys)) setDiscoveredSshKeys(data.keys);
|
||||
})
|
||||
.catch((err) => {
|
||||
if (!cancelled) {
|
||||
setDiscoveredSshKeys([]);
|
||||
setDiscoveredSshKeysError(err instanceof Error ? err.message : 'Could not detect keys');
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
if (!cancelled) setDiscoveredSshKeysLoading(false);
|
||||
});
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, [server?.id, isOpen, mode]);
|
||||
|
||||
// Validation functions
|
||||
const validateIPv4 = (ip: string): boolean => {
|
||||
if (!ip) return true; // Empty is allowed (auto)
|
||||
@@ -275,6 +312,16 @@ export function ConfigurationModal({
|
||||
if ((hasPassword || hasSSHKey) && envVars.var_ssh !== 'no') {
|
||||
envVars.var_ssh = 'yes';
|
||||
}
|
||||
|
||||
// Normalize var_tags: accept both comma and semicolon, output comma-separated
|
||||
const rawTags = envVars.var_tags;
|
||||
if (typeof rawTags === 'string' && rawTags.trim() !== '') {
|
||||
envVars.var_tags = rawTags
|
||||
.split(/[,;]/)
|
||||
.map((s) => s.trim())
|
||||
.filter(Boolean)
|
||||
.join(',');
|
||||
}
|
||||
}
|
||||
|
||||
// Remove empty string values (but keep 0, false, etc.)
|
||||
@@ -644,13 +691,13 @@ export function ConfigurationModal({
|
||||
</div>
|
||||
<div className="col-span-2">
|
||||
<label className="block text-sm font-medium text-foreground mb-2">
|
||||
Tags (comma-separated)
|
||||
Tags (comma or semicolon separated)
|
||||
</label>
|
||||
<Input
|
||||
type="text"
|
||||
value={typeof advancedVars.var_tags === 'boolean' ? '' : String(advancedVars.var_tags ?? '')}
|
||||
onChange={(e) => updateAdvancedVar('var_tags', e.target.value)}
|
||||
placeholder="community-script"
|
||||
placeholder="e.g. tag1; tag2"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -677,11 +724,40 @@ export function ConfigurationModal({
|
||||
<label className="block text-sm font-medium text-foreground mb-2">
|
||||
SSH Authorized Key
|
||||
</label>
|
||||
{discoveredSshKeysLoading && (
|
||||
<p className="text-sm text-muted-foreground mb-2">Detecting SSH keys...</p>
|
||||
)}
|
||||
{discoveredSshKeysError && !discoveredSshKeysLoading && (
|
||||
<p className="text-sm text-muted-foreground mb-2">Could not detect keys on host</p>
|
||||
)}
|
||||
{discoveredSshKeys.length > 0 && !discoveredSshKeysLoading && (
|
||||
<div className="mb-2">
|
||||
<label htmlFor="discover-ssh-key" className="sr-only">Use detected key</label>
|
||||
<select
|
||||
id="discover-ssh-key"
|
||||
className="w-full rounded-md border border-input bg-background px-3 py-2 text-sm text-foreground focus:ring-2 focus:ring-ring focus:outline-none mb-2"
|
||||
value=""
|
||||
onChange={(e) => {
|
||||
const idx = e.target.value;
|
||||
if (idx === '') return;
|
||||
const key = discoveredSshKeys[Number(idx)];
|
||||
if (key) updateAdvancedVar('var_ssh_authorized_key', key);
|
||||
}}
|
||||
>
|
||||
<option value="">— Select or paste below —</option>
|
||||
{discoveredSshKeys.map((key, i) => (
|
||||
<option key={i} value={i}>
|
||||
{key.length > 44 ? `${key.slice(0, 44)}...` : key}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
)}
|
||||
<Input
|
||||
type="text"
|
||||
value={typeof advancedVars.var_ssh_authorized_key === 'boolean' ? '' : String(advancedVars.var_ssh_authorized_key ?? '')}
|
||||
onChange={(e) => updateAdvancedVar('var_ssh_authorized_key', e.target.value)}
|
||||
placeholder="ssh-rsa AAAA..."
|
||||
placeholder="Or paste a public key: ssh-rsa AAAA..."
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -8,7 +8,9 @@ import { ScriptDetailModal } from "./ScriptDetailModal";
|
||||
import { CategorySidebar } from "./CategorySidebar";
|
||||
import { FilterBar, type FilterState } from "./FilterBar";
|
||||
import { ViewToggle } from "./ViewToggle";
|
||||
import { ConfirmationModal } from "./ConfirmationModal";
|
||||
import { Button } from "./ui/button";
|
||||
import { RefreshCw } from "lucide-react";
|
||||
import type { ScriptCard as ScriptCardType } from "~/types/script";
|
||||
import type { Server } from "~/types/server";
|
||||
import { getDefaultFilters, mergeFiltersWithDefaults } from "./filterUtils";
|
||||
@@ -32,8 +34,15 @@ export function DownloadedScriptsTab({
|
||||
const [filters, setFilters] = useState<FilterState>(getDefaultFilters());
|
||||
const [saveFiltersEnabled, setSaveFiltersEnabled] = useState(false);
|
||||
const [isLoadingFilters, setIsLoadingFilters] = useState(true);
|
||||
const [updateAllConfirmOpen, setUpdateAllConfirmOpen] = useState(false);
|
||||
const [updateResult, setUpdateResult] = useState<{
|
||||
successCount: number;
|
||||
failCount: number;
|
||||
failed: { slug: string; error: string }[];
|
||||
} | null>(null);
|
||||
const gridRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const utils = api.useUtils();
|
||||
const {
|
||||
data: scriptCardsData,
|
||||
isLoading: githubLoading,
|
||||
@@ -50,6 +59,30 @@ export function DownloadedScriptsTab({
|
||||
{ enabled: !!selectedSlug },
|
||||
);
|
||||
|
||||
const loadMultipleScriptsMutation = api.scripts.loadMultipleScripts.useMutation({
|
||||
onSuccess: (data) => {
|
||||
void utils.scripts.getAllDownloadedScripts.invalidate();
|
||||
void utils.scripts.getScriptCardsWithCategories.invalidate();
|
||||
setUpdateResult({
|
||||
successCount: data.successful?.length ?? 0,
|
||||
failCount: data.failed?.length ?? 0,
|
||||
failed: (data.failed ?? []).map((f) => ({
|
||||
slug: f.slug,
|
||||
error: f.error ?? "Unknown error",
|
||||
})),
|
||||
});
|
||||
setTimeout(() => setUpdateResult(null), 8000);
|
||||
},
|
||||
onError: (error) => {
|
||||
setUpdateResult({
|
||||
successCount: 0,
|
||||
failCount: 1,
|
||||
failed: [{ slug: "Request failed", error: error.message }],
|
||||
});
|
||||
setTimeout(() => setUpdateResult(null), 8000);
|
||||
},
|
||||
});
|
||||
|
||||
// Load SAVE_FILTER setting, saved filters, and view mode on component mount
|
||||
useEffect(() => {
|
||||
const loadSettings = async () => {
|
||||
@@ -416,6 +449,21 @@ export function DownloadedScriptsTab({
|
||||
setSelectedSlug(null);
|
||||
};
|
||||
|
||||
const handleUpdateAllClick = () => {
|
||||
setUpdateResult(null);
|
||||
setUpdateAllConfirmOpen(true);
|
||||
};
|
||||
|
||||
const handleUpdateAllConfirm = () => {
|
||||
setUpdateAllConfirmOpen(false);
|
||||
const slugs = downloadedScripts
|
||||
.map((s) => s.slug)
|
||||
.filter((slug): slug is string => Boolean(slug));
|
||||
if (slugs.length > 0) {
|
||||
loadMultipleScriptsMutation.mutate({ slugs });
|
||||
}
|
||||
};
|
||||
|
||||
if (githubLoading || localLoading) {
|
||||
return (
|
||||
<div className="flex items-center justify-center py-12">
|
||||
@@ -508,6 +556,43 @@ export function DownloadedScriptsTab({
|
||||
|
||||
{/* Main Content */}
|
||||
<div className="order-1 min-w-0 flex-1 lg:order-2" ref={gridRef}>
|
||||
{/* Update all downloaded scripts */}
|
||||
<div className="mb-4 flex flex-wrap items-center gap-3">
|
||||
<Button
|
||||
onClick={handleUpdateAllClick}
|
||||
disabled={loadMultipleScriptsMutation.isPending}
|
||||
variant="secondary"
|
||||
size="default"
|
||||
className="flex items-center gap-2"
|
||||
>
|
||||
{loadMultipleScriptsMutation.isPending ? (
|
||||
<>
|
||||
<RefreshCw className="h-4 w-4 animate-spin" />
|
||||
<span>Updating...</span>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<RefreshCw className="h-4 w-4" />
|
||||
<span>Update all downloaded scripts</span>
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
{updateResult && (
|
||||
<span className="text-muted-foreground text-sm">
|
||||
Updated {updateResult.successCount} successfully
|
||||
{updateResult.failCount > 0
|
||||
? `, ${updateResult.failCount} failed`
|
||||
: ""}
|
||||
.
|
||||
{updateResult.failCount > 0 && updateResult.failed.length > 0 && (
|
||||
<span className="ml-1" title={updateResult.failed.map((f) => `${f.slug}: ${f.error}`).join("\n")}>
|
||||
(hover for details)
|
||||
</span>
|
||||
)}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Enhanced Filter Bar */}
|
||||
<FilterBar
|
||||
filters={filters}
|
||||
@@ -621,6 +706,17 @@ export function DownloadedScriptsTab({
|
||||
onClose={handleCloseModal}
|
||||
onInstallScript={onInstallScript}
|
||||
/>
|
||||
|
||||
<ConfirmationModal
|
||||
isOpen={updateAllConfirmOpen}
|
||||
onClose={() => setUpdateAllConfirmOpen(false)}
|
||||
onConfirm={handleUpdateAllConfirm}
|
||||
title="Update all downloaded scripts"
|
||||
message={`Update all ${downloadedScripts.length} downloaded scripts? This may take several minutes.`}
|
||||
variant="simple"
|
||||
confirmButtonText="Update all"
|
||||
cancelButtonText="Cancel"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
96
src/app/api/servers/[id]/discover-ssh-keys/route.ts
Normal file
96
src/app/api/servers/[id]/discover-ssh-keys/route.ts
Normal file
@@ -0,0 +1,96 @@
|
||||
import type { NextRequest } from 'next/server';
|
||||
import { NextResponse } from 'next/server';
|
||||
import { getDatabase } from '../../../../../server/database-prisma';
|
||||
import { getSSHExecutionService } from '../../../../../server/ssh-execution-service';
|
||||
import type { Server } from '~/types/server';
|
||||
|
||||
const DISCOVER_TIMEOUT_MS = 10_000;
|
||||
|
||||
/** Match lines that look like SSH public keys (same as build.func) */
|
||||
const SSH_PUBKEY_RE = /^(ssh-(rsa|ed25519)|ecdsa-sha2-nistp256|sk-(ssh-ed25519|ecdsa-sha2-nistp256))\s+/;
|
||||
|
||||
/**
|
||||
* Run a command on the Proxmox host and return buffered stdout.
|
||||
* Resolves when the process exits or rejects on timeout/spawn error.
|
||||
*/
|
||||
function runRemoteCommand(
|
||||
server: Server,
|
||||
command: string,
|
||||
timeoutMs: number
|
||||
): Promise<{ stdout: string; exitCode: number }> {
|
||||
const ssh = getSSHExecutionService();
|
||||
return new Promise((resolve, reject) => {
|
||||
const chunks: string[] = [];
|
||||
let settled = false;
|
||||
|
||||
const finish = (stdout: string, exitCode: number) => {
|
||||
if (settled) return;
|
||||
settled = true;
|
||||
clearTimeout(timer);
|
||||
resolve({ stdout, exitCode });
|
||||
};
|
||||
|
||||
const timer = setTimeout(() => {
|
||||
if (settled) return;
|
||||
settled = true;
|
||||
reject(new Error('SSH discover keys timeout'));
|
||||
}, timeoutMs);
|
||||
|
||||
ssh
|
||||
.executeCommand(
|
||||
server,
|
||||
command,
|
||||
(data: string) => chunks.push(data),
|
||||
() => {},
|
||||
(code: number) => finish(chunks.join(''), code)
|
||||
)
|
||||
.catch((err) => {
|
||||
if (!settled) {
|
||||
settled = true;
|
||||
clearTimeout(timer);
|
||||
reject(err);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export async function GET(
|
||||
_request: NextRequest,
|
||||
{ params }: { params: Promise<{ id: string }> }
|
||||
) {
|
||||
try {
|
||||
const { id: idParam } = await params;
|
||||
const id = parseInt(idParam);
|
||||
if (isNaN(id)) {
|
||||
return NextResponse.json({ error: 'Invalid server ID' }, { status: 400 });
|
||||
}
|
||||
|
||||
const db = getDatabase();
|
||||
const server = await db.getServerById(id) as Server | null;
|
||||
|
||||
if (!server) {
|
||||
return NextResponse.json({ error: 'Server not found' }, { status: 404 });
|
||||
}
|
||||
|
||||
// Same paths as native build.func ssh_discover_default_files()
|
||||
const remoteScript = `bash -c 'for f in /root/.ssh/authorized_keys /root/.ssh/authorized_keys2 /root/.ssh/*.pub /etc/ssh/authorized_keys /etc/ssh/authorized_keys.d/* 2>/dev/null; do [ -f "$f" ] && [ -r "$f" ] && grep -E "^(ssh-(rsa|ed25519)|ecdsa-sha2-nistp256|sk-)" "$f" 2>/dev/null; done | sort -u'`;
|
||||
|
||||
const { stdout } = await runRemoteCommand(server, remoteScript, DISCOVER_TIMEOUT_MS);
|
||||
|
||||
const keys = stdout
|
||||
.split(/\r?\n/)
|
||||
.map((line) => line.trim())
|
||||
.filter((line) => line.length > 0 && SSH_PUBKEY_RE.test(line));
|
||||
|
||||
return NextResponse.json({ keys });
|
||||
} catch (error) {
|
||||
console.error('Error discovering SSH keys:', error);
|
||||
return NextResponse.json(
|
||||
{
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
},
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -238,6 +238,27 @@ export const versionRouter = createTRPCRouter({
|
||||
// Clear/create the log file
|
||||
await writeFile(logPath, '', 'utf-8');
|
||||
|
||||
// Always fetch the latest update.sh from GitHub before running
|
||||
// This ensures we always use the newest update script, avoiding
|
||||
// the "chicken-and-egg" problem where old scripts can't update properly
|
||||
const updateScriptUrl = 'https://raw.githubusercontent.com/community-scripts/ProxmoxVE-Local/main/update.sh';
|
||||
try {
|
||||
const response = await fetch(updateScriptUrl);
|
||||
if (response.ok) {
|
||||
const latestScript = await response.text();
|
||||
await writeFile(updateScriptPath, latestScript, { mode: 0o755 });
|
||||
// Log that we fetched the latest script
|
||||
await writeFile(logPath, '[INFO] Fetched latest update.sh from GitHub\n', { flag: 'a' });
|
||||
} else {
|
||||
// If fetch fails, log warning but continue with local script
|
||||
await writeFile(logPath, `[WARNING] Could not fetch latest update.sh (HTTP ${response.status}), using local version\n`, { flag: 'a' });
|
||||
}
|
||||
} catch (fetchError) {
|
||||
// If fetch fails, log warning but continue with local script
|
||||
const errorMsg = fetchError instanceof Error ? fetchError.message : 'Unknown error';
|
||||
await writeFile(logPath, `[WARNING] Could not fetch latest update.sh: ${errorMsg}, using local version\n`, { flag: 'a' });
|
||||
}
|
||||
|
||||
// Spawn the update script as a detached process using nohup
|
||||
// This allows it to run independently and kill the parent Node.js process
|
||||
// Redirect output to log file
|
||||
|
||||
Reference in New Issue
Block a user