diff --git a/dashcaddy-api/.eslintignore b/dashcaddy-api/.eslintignore
index 3063216..10a4929 100644
--- a/dashcaddy-api/.eslintignore
+++ b/dashcaddy-api/.eslintignore
@@ -3,3 +3,4 @@ coverage/
dist/
build/
*.min.js
+static-sites/
diff --git a/dashcaddy-api/package-lock.json b/dashcaddy-api/package-lock.json
index 3b962a5..aaa4e26 100644
--- a/dashcaddy-api/package-lock.json
+++ b/dashcaddy-api/package-lock.json
@@ -19,13 +19,13 @@
"js-yaml": "^4.1.1",
"jsonwebtoken": "^9.0.2",
"lru-cache": "^10.4.3",
- "nodemailer": "^8.0.4",
+ "nodemailer": "^9.0.5",
"otplib": "^12.0.1",
"pdfkit": "^0.15.2",
"png-to-ico": "^2.1.8",
"proper-lockfile": "^4.1.2",
"qrcode": "^1.5.3",
- "sharp": "^0.33.5",
+ "sharp": "^0.35.3",
"ssh2-sftp-client": "^11.0.0",
"validator": "^13.11.0",
"webdav": "^5.7.1",
@@ -564,9 +564,9 @@
}
},
"node_modules/@emnapi/runtime": {
- "version": "1.8.1",
- "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.8.1.tgz",
- "integrity": "sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg==",
+ "version": "1.11.3",
+ "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.3.tgz",
+ "integrity": "sha512-Xz4Tpyki7XyrpbUK1jR1AhdAdaXyhhY4lZ3neLodmhpuWfy2PAQN5B46sAiU4liOXGLkHypn/qU+jvfWSCYYLA==",
"license": "MIT",
"optional": true,
"dependencies": {
@@ -771,10 +771,19 @@
"dev": true,
"license": "BSD-3-Clause"
},
+ "node_modules/@img/colour": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.1.0.tgz",
+ "integrity": "sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ }
+ },
"node_modules/@img/sharp-darwin-arm64": {
- "version": "0.33.5",
- "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.33.5.tgz",
- "integrity": "sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ==",
+ "version": "0.35.3",
+ "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.35.3.tgz",
+ "integrity": "sha512-RMnFX7YQsMoh7lWfcM4NEHHymBX/rLuKNPVM84XE9ONPcaSCDgE7CHIHpSgPcO2xcRthgBy1HfNO319mwhIAkg==",
"cpu": [
"arm64"
],
@@ -784,19 +793,19 @@
"darwin"
],
"engines": {
- "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ "node": ">=20.9.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
- "@img/sharp-libvips-darwin-arm64": "1.0.4"
+ "@img/sharp-libvips-darwin-arm64": "1.3.2"
}
},
"node_modules/@img/sharp-darwin-x64": {
- "version": "0.33.5",
- "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.33.5.tgz",
- "integrity": "sha512-fyHac4jIc1ANYGRDxtiqelIbdWkIuQaI84Mv45KvGRRxSAa7o7d1ZKAOBaYbnepLC1WqxfpimdeWfvqqSGwR2Q==",
+ "version": "0.35.3",
+ "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.35.3.tgz",
+ "integrity": "sha512-Xo+5uFBtLN0BKqieTxiFzFPQAUlBbbH5iBKyRX/z1JrbnYsHTfKJnUfL8+p2TPXr1pXqao4eeL4Rl144uDpK9w==",
"cpu": [
"x64"
],
@@ -806,19 +815,38 @@
"darwin"
],
"engines": {
- "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ "node": ">=20.9.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
- "@img/sharp-libvips-darwin-x64": "1.0.4"
+ "@img/sharp-libvips-darwin-x64": "1.3.2"
+ }
+ },
+ "node_modules/@img/sharp-freebsd-wasm32": {
+ "version": "0.35.3",
+ "resolved": "https://registry.npmjs.org/@img/sharp-freebsd-wasm32/-/sharp-freebsd-wasm32-0.35.3.tgz",
+ "integrity": "sha512-lUxcqWIj2wMQ9BrwNjngcr1gWUr5xgaGThBRqPPalIC2n67Cqj1uPh8NnA/ZhAg8hUbKl+kVHKwgUIwe6ZYPrg==",
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "dependencies": {
+ "@img/sharp-wasm32": "0.35.3"
+ },
+ "engines": {
+ "node": ">=20.9.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
}
},
"node_modules/@img/sharp-libvips-darwin-arm64": {
- "version": "1.0.4",
- "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.0.4.tgz",
- "integrity": "sha512-XblONe153h0O2zuFfTAbQYAX2JhYmDHeWikp1LM9Hul9gVPjFY427k6dFEcOL72O01QxQsWi761svJ/ev9xEDg==",
+ "version": "1.3.2",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.3.2.tgz",
+ "integrity": "sha512-9J6ypZFpQBj4YnePGoq/S38w6nz+vqg5WZLrLGY4YuSemdMq47GMLBPO42MzwdGwpg/agZ7xzZcFHa48xlywfg==",
"cpu": [
"arm64"
],
@@ -832,9 +860,9 @@
}
},
"node_modules/@img/sharp-libvips-darwin-x64": {
- "version": "1.0.4",
- "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.0.4.tgz",
- "integrity": "sha512-xnGR8YuZYfJGmWPvmlunFaWJsb9T/AO2ykoP3Fz/0X5XV2aoYBPkX6xqCQvUTKKiLddarLaxpzNe+b1hjeWHAQ==",
+ "version": "1.3.2",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.3.2.tgz",
+ "integrity": "sha512-m2pW1n6cns9VaubNwsZ+c3CRYjxNQWgJ5gPlnL1nbBcpkBvFm6SCFN5o0psFHI8w9n11NKhFkeEDns98tiqbEw==",
"cpu": [
"x64"
],
@@ -848,9 +876,9 @@
}
},
"node_modules/@img/sharp-libvips-linux-arm": {
- "version": "1.0.5",
- "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.0.5.tgz",
- "integrity": "sha512-gvcC4ACAOPRNATg/ov8/MnbxFDJqf/pDePbBnuBDcjsI8PssmjoKMAz4LtLaVi+OnSb5FK/yIOamqDwGmXW32g==",
+ "version": "1.3.2",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.3.2.tgz",
+ "integrity": "sha512-1eMLzy92I4J6rmi4mAT8yC3HxOtniyGELlzGbNMLLeqe052ahFQ0h6LFq+lh5DsDIdYViIDst08abvSbcEdLXQ==",
"cpu": [
"arm"
],
@@ -864,9 +892,9 @@
}
},
"node_modules/@img/sharp-libvips-linux-arm64": {
- "version": "1.0.4",
- "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.0.4.tgz",
- "integrity": "sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA==",
+ "version": "1.3.2",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.3.2.tgz",
+ "integrity": "sha512-dqVSFynCox4C/J8kT16V7SIFAns0IjgLwkvYT7p8LQVmJ5OS5b6tI9IGflxTeuBS//zXeFIUbwt5dwxyZ17cnA==",
"cpu": [
"arm64"
],
@@ -879,10 +907,42 @@
"url": "https://opencollective.com/libvips"
}
},
+ "node_modules/@img/sharp-libvips-linux-ppc64": {
+ "version": "1.3.2",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.3.2.tgz",
+ "integrity": "sha512-3z0NHDxD6n5I9gc05U1eW1AyRm+Gznzq3naMrthPNqE6oYykcogW0l/jfpJdjYnuNl8R7yI9pNbE1XiUeyq0Aw==",
+ "cpu": [
+ "ppc64"
+ ],
+ "license": "LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-libvips-linux-riscv64": {
+ "version": "1.3.2",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.3.2.tgz",
+ "integrity": "sha512-bsb4rI+NldGOsXuej2r8OdSS8+zXDVaCWxyWrcv6kneTOlgAHtZABRzBBCwdsPiD90J4myNJuHpg6kA20ImW/w==",
+ "cpu": [
+ "riscv64"
+ ],
+ "license": "LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
"node_modules/@img/sharp-libvips-linux-s390x": {
- "version": "1.0.4",
- "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.0.4.tgz",
- "integrity": "sha512-u7Wz6ntiSSgGSGcjZ55im6uvTrOxSIS8/dgoVMoiGE9I6JAfU50yH5BoDlYA1tcuGS7g/QNtetJnxA6QEsCVTA==",
+ "version": "1.3.2",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.3.2.tgz",
+ "integrity": "sha512-/ABshyj8gCpyIrNXnHn4LorDJ0HHm1VhXPBlxZ8zAtfVPAaSafXPGn+sUSIRiwaSBy0mmFjSjiXI5mkcwdChKQ==",
"cpu": [
"s390x"
],
@@ -896,9 +956,9 @@
}
},
"node_modules/@img/sharp-libvips-linux-x64": {
- "version": "1.0.4",
- "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.0.4.tgz",
- "integrity": "sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw==",
+ "version": "1.3.2",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.3.2.tgz",
+ "integrity": "sha512-ITPEtgffGJ0S6G9dRyw/366tJQqFRcHWPHhC+Stpg3Z8AEMrDrTr2lhdz4f/Y/HMbRh//7Z5mBzEpVdi62Oc3w==",
"cpu": [
"x64"
],
@@ -912,9 +972,9 @@
}
},
"node_modules/@img/sharp-libvips-linuxmusl-arm64": {
- "version": "1.0.4",
- "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.0.4.tgz",
- "integrity": "sha512-9Ti+BbTYDcsbp4wfYib8Ctm1ilkugkA/uscUn6UXK1ldpC1JjiXbLfFZtRlBhjPZ5o1NCLiDbg8fhUPKStHoTA==",
+ "version": "1.3.2",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.3.2.tgz",
+ "integrity": "sha512-zE9EdiUzUmg5mDT5a1rk5fYJ6GWPloTwWBYDS14naqHsL+EaMpDj1AWnpLgh3u0YCORv2Tt50wrcrpYqkP97Kw==",
"cpu": [
"arm64"
],
@@ -928,9 +988,9 @@
}
},
"node_modules/@img/sharp-libvips-linuxmusl-x64": {
- "version": "1.0.4",
- "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.0.4.tgz",
- "integrity": "sha512-viYN1KX9m+/hGkJtvYYp+CCLgnJXwiQB39damAO7WMdKWlIhmYTfHjwSbQeUK/20vY154mwezd9HflVFM1wVSw==",
+ "version": "1.3.2",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.3.2.tgz",
+ "integrity": "sha512-m0lrLiUt+lBYnCFr8qV/65yMR4E/c7/wf78I5eKTdkEakFAlZ9QlzEM3QIhhAwVeUhLAHLcCq7a7Vszq/oFNZQ==",
"cpu": [
"x64"
],
@@ -944,9 +1004,9 @@
}
},
"node_modules/@img/sharp-linux-arm": {
- "version": "0.33.5",
- "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.33.5.tgz",
- "integrity": "sha512-JTS1eldqZbJxjvKaAkxhZmBqPRGmxgu+qFKSInv8moZ2AmT5Yib3EQ1c6gp493HvrvV8QgdOXdyaIBrhvFhBMQ==",
+ "version": "0.35.3",
+ "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.35.3.tgz",
+ "integrity": "sha512-affVWCTLooy8TSxbDx2qkzuDeaWLNVBA+P//FNBirHsXpP2fuBhk5AuboYUnrDnzoXes8GFjpTx0SBFOCRg+FA==",
"cpu": [
"arm"
],
@@ -956,19 +1016,19 @@
"linux"
],
"engines": {
- "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ "node": ">=20.9.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
- "@img/sharp-libvips-linux-arm": "1.0.5"
+ "@img/sharp-libvips-linux-arm": "1.3.2"
}
},
"node_modules/@img/sharp-linux-arm64": {
- "version": "0.33.5",
- "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.33.5.tgz",
- "integrity": "sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA==",
+ "version": "0.35.3",
+ "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.35.3.tgz",
+ "integrity": "sha512-QgKDspHPnrU+GQ55XPhGwyhC8acLVOOSyAvo1oVfFmrIXLkDNmGWzAfDZ4xK8oSA1qBQrALcHX0G5UZni/SuFQ==",
"cpu": [
"arm64"
],
@@ -978,19 +1038,63 @@
"linux"
],
"engines": {
- "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ "node": ">=20.9.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
- "@img/sharp-libvips-linux-arm64": "1.0.4"
+ "@img/sharp-libvips-linux-arm64": "1.3.2"
+ }
+ },
+ "node_modules/@img/sharp-linux-ppc64": {
+ "version": "0.35.3",
+ "resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.35.3.tgz",
+ "integrity": "sha512-sMd8rDxmpLOwv/7N44klFjOD5DUO7FLdjiXDI0hoxYaf7Ar262dQIEkosE98bps+5HPLtp/EvNqeqQtOycP/IA==",
+ "cpu": [
+ "ppc64"
+ ],
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=20.9.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-linux-ppc64": "1.3.2"
+ }
+ },
+ "node_modules/@img/sharp-linux-riscv64": {
+ "version": "0.35.3",
+ "resolved": "https://registry.npmjs.org/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.35.3.tgz",
+ "integrity": "sha512-0Eob78yjlYPfL5vMNWAW55l3R9Y6BQS/gOfe0ZcP9mEz9ohhKSt4im1hayiknXgf8AWrFqMvJcKIdmLmEe7yeQ==",
+ "cpu": [
+ "riscv64"
+ ],
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=20.9.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-linux-riscv64": "1.3.2"
}
},
"node_modules/@img/sharp-linux-s390x": {
- "version": "0.33.5",
- "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.33.5.tgz",
- "integrity": "sha512-y/5PCd+mP4CA/sPDKl2961b+C9d+vPAveS33s6Z3zfASk2j5upL6fXVPZi7ztePZ5CuH+1kW8JtvxgbuXHRa4Q==",
+ "version": "0.35.3",
+ "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.35.3.tgz",
+ "integrity": "sha512-KgAxQ0DxpNOq1rG2t5cgTgShJFGSuU7XO45cqC+1NVOuZnP6tlgZRuSYOfNupGkHID0o3cJOsw4DVeJpMovcGw==",
"cpu": [
"s390x"
],
@@ -1000,19 +1104,19 @@
"linux"
],
"engines": {
- "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ "node": ">=20.9.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
- "@img/sharp-libvips-linux-s390x": "1.0.4"
+ "@img/sharp-libvips-linux-s390x": "1.3.2"
}
},
"node_modules/@img/sharp-linux-x64": {
- "version": "0.33.5",
- "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.33.5.tgz",
- "integrity": "sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA==",
+ "version": "0.35.3",
+ "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.35.3.tgz",
+ "integrity": "sha512-8pqvxubL2PGdhlPy6GLqzDYMUjyRmKAwKHYKixpdJYBUK7PJ0C029XdsnpFIdgRZG68fZiGdHVWcKPvtiPB4cA==",
"cpu": [
"x64"
],
@@ -1022,19 +1126,19 @@
"linux"
],
"engines": {
- "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ "node": ">=20.9.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
- "@img/sharp-libvips-linux-x64": "1.0.4"
+ "@img/sharp-libvips-linux-x64": "1.3.2"
}
},
"node_modules/@img/sharp-linuxmusl-arm64": {
- "version": "0.33.5",
- "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.33.5.tgz",
- "integrity": "sha512-XrHMZwGQGvJg2V/oRSUfSAfjfPxO+4DkiRh6p2AFjLQztWUuY/o8Mq0eMQVIY7HJ1CDQUJlxGGZRw1a5bqmd1g==",
+ "version": "0.35.3",
+ "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.35.3.tgz",
+ "integrity": "sha512-Vz0iQjzzcSX3HCbfwFfCSG/9SCIqyO0mH2sXyiHaAYfBk0cRsCWXRyQYX0ovCK/PAQBbTzQ0dsPQHh5MAFL59w==",
"cpu": [
"arm64"
],
@@ -1044,19 +1148,19 @@
"linux"
],
"engines": {
- "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ "node": ">=20.9.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
- "@img/sharp-libvips-linuxmusl-arm64": "1.0.4"
+ "@img/sharp-libvips-linuxmusl-arm64": "1.3.2"
}
},
"node_modules/@img/sharp-linuxmusl-x64": {
- "version": "0.33.5",
- "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.33.5.tgz",
- "integrity": "sha512-WT+d/cgqKkkKySYmqoZ8y3pxx7lx9vVejxW/W4DOFMYVSkErR+w7mf2u8m/y4+xHe7yY9DAXQMWQhpnMuFfScw==",
+ "version": "0.35.3",
+ "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.35.3.tgz",
+ "integrity": "sha512-6O1NPKcDVj9QEdg7Hx549EX8U0rp6yXQERqru6yRN7fGBn32UvIRJUlWnk+8xDCiG76hXVBbX82NZ/ZKr0euIg==",
"cpu": [
"x64"
],
@@ -1066,38 +1170,73 @@
"linux"
],
"engines": {
- "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ "node": ">=20.9.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
- "@img/sharp-libvips-linuxmusl-x64": "1.0.4"
+ "@img/sharp-libvips-linuxmusl-x64": "1.3.2"
}
},
"node_modules/@img/sharp-wasm32": {
- "version": "0.33.5",
- "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.33.5.tgz",
- "integrity": "sha512-ykUW4LVGaMcU9lu9thv85CbRMAwfeadCJHRsg2GmeRa/cJxsVY9Rbd57JcMxBkKHag5U/x7TSBpScF4U8ElVzg==",
- "cpu": [
- "wasm32"
- ],
+ "version": "0.35.3",
+ "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.35.3.tgz",
+ "integrity": "sha512-cZ0XkcYGpHZkqW6iCkqTcmUC0CD9DhD5d/qeZlZkfRBn6GnHniZXLUo5+9xw8Iv76YE6LQFN9YNBlKREcCG76w==",
"license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT",
"optional": true,
"dependencies": {
- "@emnapi/runtime": "^1.2.0"
+ "@emnapi/runtime": "^1.11.1"
},
"engines": {
- "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ "node": ">=20.9.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-webcontainers-wasm32": {
+ "version": "0.35.3",
+ "resolved": "https://registry.npmjs.org/@img/sharp-webcontainers-wasm32/-/sharp-webcontainers-wasm32-0.35.3.tgz",
+ "integrity": "sha512-2rnq7bX3NzeR2T4YWgz8qiG4h3TSdMe+vN1iQXpJleSJ3SM5zQ8Fy2SyyXAWlbxpEZ2Y+Z4u1BePgJEYbSy80Q==",
+ "cpu": [
+ "wasm32"
+ ],
+ "license": "Apache-2.0",
+ "optional": true,
+ "dependencies": {
+ "@img/sharp-wasm32": "0.35.3"
+ },
+ "engines": {
+ "node": ">=20.9.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-win32-arm64": {
+ "version": "0.35.3",
+ "resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.35.3.tgz",
+ "integrity": "sha512-4bPwFdMbeC4JQ8L8LOyWp6nsHcboP5fxkp6iPOXz2Vg49R42TuMs2whkJ5OAP4/Ul035qOzy0AecOF9VOscn4w==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "Apache-2.0 AND LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=20.9.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@img/sharp-win32-ia32": {
- "version": "0.33.5",
- "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.33.5.tgz",
- "integrity": "sha512-T36PblLaTwuVJ/zw/LaH0PdZkRz5rd3SmMHX8GSmR7vtNSP5Z6bQkExdSK7xGWyxLw4sUknBuugTelgw2faBbQ==",
+ "version": "0.35.3",
+ "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.35.3.tgz",
+ "integrity": "sha512-r53mXsBN6lFUDiST764SvgwUdHAqM4rPAiDzAmf4fLoB6X/rkfyTrLCg6+g17wJJiCmB3JYgHuUldCWUIRFSXw==",
"cpu": [
"ia32"
],
@@ -1107,16 +1246,16 @@
"win32"
],
"engines": {
- "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ "node": "^20.9.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@img/sharp-win32-x64": {
- "version": "0.33.5",
- "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.33.5.tgz",
- "integrity": "sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg==",
+ "version": "0.35.3",
+ "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.35.3.tgz",
+ "integrity": "sha512-D4y1vNeZrIIJCN+uHaWVtH86B+aCrdMYYjicy9pXHvbGZeGYLLSd3wdVuC37FxVXlU1ARsk84eKWfWMXGYEqvA==",
"cpu": [
"x64"
],
@@ -1126,7 +1265,7 @@
"win32"
],
"engines": {
- "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ "node": ">=20.9.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
@@ -2623,19 +2762,6 @@
"dev": true,
"license": "MIT"
},
- "node_modules/color": {
- "version": "4.2.3",
- "resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz",
- "integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==",
- "license": "MIT",
- "dependencies": {
- "color-convert": "^2.0.1",
- "color-string": "^1.9.0"
- },
- "engines": {
- "node": ">=12.5.0"
- }
- },
"node_modules/color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
@@ -2654,16 +2780,6 @@
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
"license": "MIT"
},
- "node_modules/color-string": {
- "version": "1.9.1",
- "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz",
- "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==",
- "license": "MIT",
- "dependencies": {
- "color-name": "^1.0.0",
- "simple-swizzle": "^0.2.2"
- }
- },
"node_modules/combined-stream": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
@@ -6002,9 +6118,9 @@
}
},
"node_modules/minimatch": {
- "version": "3.1.2",
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
- "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
+ "version": "3.1.5",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz",
+ "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==",
"dev": true,
"license": "ISC",
"dependencies": {
@@ -6127,9 +6243,9 @@
}
},
"node_modules/nodemailer": {
- "version": "8.0.11",
- "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-8.0.11.tgz",
- "integrity": "sha512-nrO/pDAUKl+wXX+lx16tDLbnm0fW6sK/x8mgohaCpg+CdCEl482bD4tCuAZk2DyliruiNTIZxRCoWkDqJEnAiA==",
+ "version": "9.0.5",
+ "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-9.0.5.tgz",
+ "integrity": "sha512-wvjiKvjczmsN7U/8006JOdXubgBk2XFAbioDMbT+sM7cPs0QrhJTa6KBRX7P5REGGkDcLUz/EarWidb8G8C1jQ==",
"license": "MIT-0",
"engines": {
"node": ">=6.0.0"
@@ -7242,48 +7358,58 @@
"license": "ISC"
},
"node_modules/sharp": {
- "version": "0.33.5",
- "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.33.5.tgz",
- "integrity": "sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw==",
- "hasInstallScript": true,
+ "version": "0.35.3",
+ "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.35.3.tgz",
+ "integrity": "sha512-ej0zVHuZGHCiABXcNxeYhpRnPNPAcvbG8RMdBAhDAxLKkCRVSpK3Iyu7qbqw3JMzoj0REeM6f3tJLtVwl0023Q==",
"license": "Apache-2.0",
"dependencies": {
- "color": "^4.2.3",
- "detect-libc": "^2.0.3",
- "semver": "^7.6.3"
+ "@img/colour": "^1.1.0",
+ "detect-libc": "^2.1.2",
+ "semver": "^7.8.5"
},
"engines": {
- "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ "node": ">=20.9.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
- "@img/sharp-darwin-arm64": "0.33.5",
- "@img/sharp-darwin-x64": "0.33.5",
- "@img/sharp-libvips-darwin-arm64": "1.0.4",
- "@img/sharp-libvips-darwin-x64": "1.0.4",
- "@img/sharp-libvips-linux-arm": "1.0.5",
- "@img/sharp-libvips-linux-arm64": "1.0.4",
- "@img/sharp-libvips-linux-s390x": "1.0.4",
- "@img/sharp-libvips-linux-x64": "1.0.4",
- "@img/sharp-libvips-linuxmusl-arm64": "1.0.4",
- "@img/sharp-libvips-linuxmusl-x64": "1.0.4",
- "@img/sharp-linux-arm": "0.33.5",
- "@img/sharp-linux-arm64": "0.33.5",
- "@img/sharp-linux-s390x": "0.33.5",
- "@img/sharp-linux-x64": "0.33.5",
- "@img/sharp-linuxmusl-arm64": "0.33.5",
- "@img/sharp-linuxmusl-x64": "0.33.5",
- "@img/sharp-wasm32": "0.33.5",
- "@img/sharp-win32-ia32": "0.33.5",
- "@img/sharp-win32-x64": "0.33.5"
+ "@img/sharp-darwin-arm64": "0.35.3",
+ "@img/sharp-darwin-x64": "0.35.3",
+ "@img/sharp-freebsd-wasm32": "0.35.3",
+ "@img/sharp-libvips-darwin-arm64": "1.3.2",
+ "@img/sharp-libvips-darwin-x64": "1.3.2",
+ "@img/sharp-libvips-linux-arm": "1.3.2",
+ "@img/sharp-libvips-linux-arm64": "1.3.2",
+ "@img/sharp-libvips-linux-ppc64": "1.3.2",
+ "@img/sharp-libvips-linux-riscv64": "1.3.2",
+ "@img/sharp-libvips-linux-s390x": "1.3.2",
+ "@img/sharp-libvips-linux-x64": "1.3.2",
+ "@img/sharp-libvips-linuxmusl-arm64": "1.3.2",
+ "@img/sharp-libvips-linuxmusl-x64": "1.3.2",
+ "@img/sharp-linux-arm": "0.35.3",
+ "@img/sharp-linux-arm64": "0.35.3",
+ "@img/sharp-linux-ppc64": "0.35.3",
+ "@img/sharp-linux-riscv64": "0.35.3",
+ "@img/sharp-linux-s390x": "0.35.3",
+ "@img/sharp-linux-x64": "0.35.3",
+ "@img/sharp-linuxmusl-arm64": "0.35.3",
+ "@img/sharp-linuxmusl-x64": "0.35.3",
+ "@img/sharp-webcontainers-wasm32": "0.35.3",
+ "@img/sharp-win32-arm64": "0.35.3",
+ "@img/sharp-win32-ia32": "0.35.3",
+ "@img/sharp-win32-x64": "0.35.3"
+ },
+ "peerDependenciesMeta": {
+ "@types/node": {
+ "optional": true
+ }
}
},
"node_modules/sharp/node_modules/semver": {
- "version": "7.7.3",
- "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz",
- "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==",
+ "version": "7.8.5",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz",
+ "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==",
"license": "ISC",
"bin": {
"semver": "bin/semver.js"
@@ -7393,21 +7519,6 @@
"integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==",
"license": "ISC"
},
- "node_modules/simple-swizzle": {
- "version": "0.2.4",
- "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.4.tgz",
- "integrity": "sha512-nAu1WFPQSMNr2Zn9PGSZK9AGn4t/y97lEm+MXTtUDwfP0ksAIX4nO+6ruD9Jwut4C49SB1Ws+fbXsm/yScWOHw==",
- "license": "MIT",
- "dependencies": {
- "is-arrayish": "^0.3.1"
- }
- },
- "node_modules/simple-swizzle/node_modules/is-arrayish": {
- "version": "0.3.4",
- "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.4.tgz",
- "integrity": "sha512-m6UrgzFVUYawGBh1dUsWR5M2Clqic9RVXC/9f8ceNlv2IcO9j9J/z8UoCLPqtsPBFNzEpfR3xftohbfqDx8EQA==",
- "license": "MIT"
- },
"node_modules/sisteransi": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz",
diff --git a/dashcaddy-api/package.json b/dashcaddy-api/package.json
index 38726aa..2c47ec6 100644
--- a/dashcaddy-api/package.json
+++ b/dashcaddy-api/package.json
@@ -33,13 +33,13 @@
"js-yaml": "^4.1.1",
"jsonwebtoken": "^9.0.2",
"lru-cache": "^10.4.3",
- "nodemailer": "^8.0.4",
+ "nodemailer": "^9.0.5",
"otplib": "^12.0.1",
"pdfkit": "^0.15.2",
"png-to-ico": "^2.1.8",
"proper-lockfile": "^4.1.2",
"qrcode": "^1.5.3",
- "sharp": "^0.33.5",
+ "sharp": "^0.35.3",
"ssh2-sftp-client": "^11.0.0",
"validator": "^13.11.0",
"webdav": "^5.7.1",
diff --git a/dashcaddy-api/routes/apps/deploy.js b/dashcaddy-api/routes/apps/deploy.js
index e35fb94..eb100af 100644
--- a/dashcaddy-api/routes/apps/deploy.js
+++ b/dashcaddy-api/routes/apps/deploy.js
@@ -95,7 +95,7 @@ module.exports = function({ docker, caddy, credentialManager, servicesStateManag
log.info('deploy', 'DashCA: For full features, copy certificate files to ' + destPath);
log.info('deploy', 'DashCA: Static site deployment completed successfully');
} catch (error) {
- log.error('deploy', 'DashCA deployment error', { error: error.message });
+ log.error('deploy', error, null, { note: 'DashCA deployment error' });
throw new Error(`DashCA deployment failed: ${error.message}`);
}
}
@@ -231,7 +231,7 @@ module.exports = function({ docker, caddy, credentialManager, servicesStateManag
await portLockManager.releasePorts(lockId);
log.info('deploy', 'Port locks released after error', { lockId });
} catch (releaseError) {
- log.error('deploy', 'Failed to release port locks', { lockId, error: releaseError.message });
+ log.error('deploy', releaseError, null, { note: 'Failed to release port locks', lockId });
}
}
throw deployError;
@@ -425,7 +425,7 @@ module.exports = function({ docker, caddy, credentialManager, servicesStateManag
} catch (error) {
try { await logError('app-deploy', error, { appId, config }); } catch (_) { /* logError failure should not mask original error */ }
const msg = error?.message || String(error || 'Unknown error');
- log.error('deploy', 'Deployment failed', { appId, error: msg });
+ log.error('deploy', error, null, { note: 'Deployment failed', appId });
const template = ctx.APP_TEMPLATES[appId];
try { ctx.notification.send('deploymentFailed', 'Deployment Failed', `Failed to deploy **${template?.name || appId}**.\nError: ${msg}`, 'error'); } catch (_) {}
errorResponse(res, 500, ctx.safeErrorMessage(error));
diff --git a/dashcaddy-api/routes/apps/restore.js b/dashcaddy-api/routes/apps/restore.js
index 6f3b536..c9d5ea6 100644
--- a/dashcaddy-api/routes/apps/restore.js
+++ b/dashcaddy-api/routes/apps/restore.js
@@ -297,7 +297,7 @@ module.exports = function({ docker, caddy, servicesStateManager, asyncHandler, e
// P0-5 fix: was `errorResponse(res, 500, err.message)` which leaks internal
// error details (paths, stack traces, library error codes) to the client.
// Log the actual error server-side and return a generic message.
- log.error('apps-revert', 'Revert failed', { error: err.message, stack: err.stack });
+ log.error('apps-revert', err, null, { note: 'Revert failed', stack: err.stack });
errorResponse(res, 500, 'Revert failed');
}
}, 'apps-revert'));
diff --git a/dashcaddy-api/routes/apps/templates.js b/dashcaddy-api/routes/apps/templates.js
index fb5cafd..df152b2 100644
--- a/dashcaddy-api/routes/apps/templates.js
+++ b/dashcaddy-api/routes/apps/templates.js
@@ -148,7 +148,7 @@ module.exports = function({
}
} catch (error) {
results.caddy = `failed: ${error.message}`;
- log.error('caddy', 'Caddy update error', { error: error.message });
+ log.error('caddy', error, null, { note: 'Caddy update error' });
}
try {
diff --git a/dashcaddy-api/routes/arr/helpers.js b/dashcaddy-api/routes/arr/helpers.js
index 7354ef8..244e61d 100644
--- a/dashcaddy-api/routes/arr/helpers.js
+++ b/dashcaddy-api/routes/arr/helpers.js
@@ -37,7 +37,7 @@ module.exports = function({ docker, credentialManager, fetchT, log }) {
stream.on('error', () => resolve(null));
});
} catch (error) {
- log.error('docker', 'Failed to get API key', { containerName, error: error.message });
+ log.error('docker', error, null, { note: 'Failed to get API key', containerName });
return null;
}
}
@@ -71,7 +71,7 @@ module.exports = function({ docker, credentialManager, fetchT, log }) {
stream.on('error', () => resolve(null));
});
} catch (error) {
- log.error('docker', 'Failed to get Plex token', { error: error.message });
+ log.error('docker', error, null, { note: 'Failed to get Plex token' });
return null;
}
}
@@ -123,7 +123,7 @@ module.exports = function({ docker, credentialManager, fetchT, log }) {
const sessionCookie = setCookie.split(';')[0];
return { cookie: sessionCookie, plexToken };
} catch (e) {
- log.error('arr', 'Could not get Seerr session', { error: e.message });
+ log.error('arr', e, null, { note: 'Could not get Seerr session' });
return null;
}
}
diff --git a/dashcaddy-api/routes/containers.js b/dashcaddy-api/routes/containers.js
index 95ebd97..35ecb3c 100644
--- a/dashcaddy-api/routes/containers.js
+++ b/dashcaddy-api/routes/containers.js
@@ -162,7 +162,7 @@ module.exports = function({ docker, log, asyncHandler, workflowEngine }) {
await newContainer.start();
} catch (startError) {
// Clean up the failed container so it doesn't block future attempts
- log.error('docker', 'Failed to start new container', { containerName, error: startError.message });
+ log.error('docker', startError, null, { note: 'Failed to start new container', containerName });
if (newContainer) {
try { await newContainer.remove({ force: true }); } catch (e) { /* already gone */ }
}
diff --git a/dashcaddy-api/routes/dns.js b/dashcaddy-api/routes/dns.js
index ed1528c..ba907be 100644
--- a/dashcaddy-api/routes/dns.js
+++ b/dashcaddy-api/routes/dns.js
@@ -110,7 +110,7 @@ module.exports = function({
...(result.instructions ? { manual: true, instructions: result.instructions } : {})
});
} catch (error) {
- log.error('dns', 'Universal DNS record creation error', { error: error.message });
+ log.error('dns', error, null, { note: 'Universal DNS record creation error' });
errorResponse(res, safeErrorMessage(error), 500);
}
}, 'dns-universal-create'));
@@ -136,7 +136,7 @@ module.exports = function({
...(result.instructions ? { manual: true, instructions: result.instructions } : {})
});
} catch (error) {
- log.error('dns', 'Universal DNS record deletion error', { error: error.message });
+ log.error('dns', error, null, { note: 'Universal DNS record deletion error' });
errorResponse(res, safeErrorMessage(error), 500);
}
}, 'dns-universal-delete'));
@@ -167,7 +167,7 @@ module.exports = function({
throw new NotFoundError('No records found for domain');
}
} catch (error) {
- log.error('dns', 'Universal DNS resolve error', { error: error.message });
+ log.error('dns', error, null, { note: 'Universal DNS resolve error' });
errorResponse(res, safeErrorMessage(error), error.statusCode || 500);
}
}, 'dns-universal-resolve'));
@@ -283,7 +283,7 @@ module.exports = function({
// Error handled by middleware
}
} catch (error) {
- log.error('dns', 'DNS record creation error', { error: error.message });
+ log.error('dns', error, null, { note: 'DNS record creation error' });
errorResponse(res, safeErrorMessage(error), 500, { details: error.cause?.code || 'fetch failed' });
}
}, 'dns-create-record'));
@@ -328,7 +328,7 @@ module.exports = function({
// Error handled by middleware
}
} catch (error) {
- log.error('dns', 'DNS resolve error', { error: error.message });
+ log.error('dns', error, null, { note: 'DNS resolve error' });
// Error handled by middleware
}
}, 'dns-resolve'));
@@ -465,7 +465,7 @@ module.exports = function({
});
} catch (error) {
- log.error('dns', 'DNS logs proxy error', { error: error.message });
+ log.error('dns', error, null, { note: 'DNS logs proxy error' });
// Error handled by middleware
}
}, 'dns-logs'));
@@ -723,7 +723,7 @@ module.exports = function({
// Error handled by middleware
}
} catch (error) {
- log.error('dns', 'DNS update check error', { error: error.message });
+ log.error('dns', error, null, { note: 'DNS update check error' });
// Error handled by middleware
}
}, 'dns-check-update'));
@@ -791,7 +791,7 @@ module.exports = function({
manualUpdateRequired: true
});
} catch (error) {
- log.error('dns', 'DNS update error', { error: error.message });
+ log.error('dns', error, null, { note: 'DNS update error' });
// Error handled by middleware
}
}, 'dns-update'));
diff --git a/dashcaddy-api/routes/exec.js b/dashcaddy-api/routes/exec.js
index 034a04b..8069f4c 100644
--- a/dashcaddy-api/routes/exec.js
+++ b/dashcaddy-api/routes/exec.js
@@ -165,7 +165,7 @@ async function handleExec(ws, containerId, log, auth) {
});
} catch (err) {
- log.error('exec', 'Failed to start exec session', { containerId, error: err.message });
+ log.error('exec', err, null, { note: 'Failed to start exec session', containerId });
if (ws.readyState === ws.OPEN) {
ws.send(JSON.stringify({ type: 'error', message: err.message }));
ws.close();
diff --git a/dashcaddy-api/routes/recipes/deploy.js b/dashcaddy-api/routes/recipes/deploy.js
index 46b063c..e4b4b22 100644
--- a/dashcaddy-api/routes/recipes/deploy.js
+++ b/dashcaddy-api/routes/recipes/deploy.js
@@ -149,7 +149,7 @@ module.exports = function({ docker, credentialManager: _credentialManager, servi
ok(res, response);
} catch (error) {
- log.error('recipe', 'Recipe deployment failed', { recipeId, error: error.message });
+ log.error('recipe', error, null, { note: 'Recipe deployment failed', recipeId });
// Cleanup: remove partially deployed containers
for (const deployed of deployedComponents) {
diff --git a/dashcaddy-api/routes/services.js b/dashcaddy-api/routes/services.js
index 0015dab..36cd69b 100644
--- a/dashcaddy-api/routes/services.js
+++ b/dashcaddy-api/routes/services.js
@@ -421,7 +421,7 @@ module.exports = function({
resyncHealthChecker?.().catch(() => {});
success(res, { message: `Service "${name}" added to dashboard` });
} catch (error) {
- log.error('deploy', 'Error adding service', { error: error.message });
+ log.error('deploy', error, null, { note: 'Error adding service' });
if (error.message.includes('already exists')) {
errorResponse(res, safeErrorMessage(error), 409);
} else {
diff --git a/dashcaddy-api/routes/sites.js b/dashcaddy-api/routes/sites.js
index 50f2672..cd80b65 100644
--- a/dashcaddy-api/routes/sites.js
+++ b/dashcaddy-api/routes/sites.js
@@ -46,7 +46,7 @@ module.exports = function({ asyncHandler, ok, caddy, dns, fetchT, buildDomain, a
if (!response.ok) {
const errorText = await response.text();
- log.error('caddy', 'Caddy reload failed', { error: errorText });
+ log.error('caddy', new Error(`Caddy reload failed: ${errorText.slice(0, 500)}`));
throw new Error('Caddy reload failed. Check server logs for details.');
}
diff --git a/dashcaddy-api/routes/themes.js b/dashcaddy-api/routes/themes.js
index 073a238..338dbc4 100644
--- a/dashcaddy-api/routes/themes.js
+++ b/dashcaddy-api/routes/themes.js
@@ -31,7 +31,7 @@ module.exports = function({ asyncHandler, log }) {
themes[slug] = data;
}
} catch (e) {
- log.error('themes', 'Failed to read themes', { error: e.message });
+ log.error('themes', e, null, { note: 'Failed to read themes' });
}
return themes;
}
diff --git a/dashcaddy-api/server.js b/dashcaddy-api/server.js
index 2fefd29..986908f 100644
--- a/dashcaddy-api/server.js
+++ b/dashcaddy-api/server.js
@@ -68,30 +68,29 @@ process.on('uncaughtException', (error) => {
attachExecWS(server, log, authManager);
log.info('server', 'WebSocket exec handler attached (auth enforced)');
- // DC-076: Attach dashboard WebSocket for real-time updates
+ // DC-076: Attach dashboard WebSocket for real-time updates.
+ // createApp() returns the live manager instances — use those instead
+ // of re-requiring the modules (which yields singletons for some
+ // managers and raw classes / namespace objects for others; calling
+ // .on() on a class threw on every boot and silently killed the WS).
try {
+ const { ctx } = app.locals;
const createDashboardWS = require('./src/websocket/dashboard-ws');
- const resourceMonitor = require('./src/managers/resource-monitor');
- const healthChecker = require('./src/monitoring/health-checker');
- const updateManager = require('./src/managers/update-manager');
- const dependencyManager = require('./src/managers/dependency-manager');
- const autoRestartManager = require('./src/managers/auto-restart-manager');
- const configDriftDetector = require('./src/managers/config-drift-detector');
- const sslMonitor = require('./src/monitoring/ssl-monitor');
createDashboardWS(server, {
- resourceMonitor,
- healthChecker,
- updateManager,
- dependencyManager,
- autoRestartManager,
- driftDetector: configDriftDetector,
- sslMonitor,
+ resourceMonitor: ctx.resourceMonitor,
+ healthChecker: ctx.healthChecker,
+ updateManager: ctx.updateManager,
+ dependencyManager: ctx.dependencyManager,
+ autoRestartManager: ctx.autoRestartManager,
+ driftDetector: ctx.driftDetector,
+ sslMonitor: ctx.sslMonitor,
+ dnsPropagationChecker: ctx.dnsPropagationChecker,
log,
});
log.info('server', 'Dashboard WebSocket attached at /api/v1/ws');
} catch (err) {
- log.error('server', 'Dashboard WebSocket failed to attach', { error: err.message });
+ log.error('server', err, null, { feature: 'dashboard-ws' });
}
// Start feature modules
@@ -136,7 +135,7 @@ process.on('uncaughtException', (error) => {
workflowEngine = new WorkflowEngine(workflowCtx);
log.info('server', 'Workflow engine initialized');
} catch (err) {
- log.error('server', 'Workflow engine failed to initialize', { error: err.message });
+ log.error('server', err, null, { note: 'Workflow engine failed to initialize' });
}
}
@@ -145,7 +144,7 @@ process.on('uncaughtException', (error) => {
// Clean up stale port locks
portLockManager.cleanupStaleLocks()
.then(() => log.info('server', 'Port lock cleanup completed'))
- .catch(err => log.error('server', 'Port lock cleanup failed', { error: err.message }));
+ .catch(err => log.error('server', err, null, { note: 'Port lock cleanup failed' }));
// Resource monitoring
try {
@@ -156,7 +155,7 @@ process.on('uncaughtException', (error) => {
}
log.info('server', 'Resource monitoring started');
} catch (err) {
- log.error('server', 'Resource monitoring failed to start', { error: err.message });
+ log.error('server', err, null, { note: 'Resource monitoring failed to start' });
}
// Backup manager
@@ -164,7 +163,7 @@ process.on('uncaughtException', (error) => {
backupManager.start();
log.info('server', 'Backup manager started');
} catch (err) {
- log.error('server', 'Backup manager failed to start', { error: err.message });
+ log.error('server', err, null, { note: 'Backup manager failed to start' });
}
// Security event workers (Caddy access log, fail2ban, shared_bans)
@@ -175,7 +174,7 @@ process.on('uncaughtException', (error) => {
startSecurityWorkers({ log });
log.info('server', 'Security event workers started');
} catch (err) {
- log.error('server', 'Security event workers failed to start', { error: err.message });
+ log.error('server', err, null, { note: 'Security event workers failed to start' });
}
// Connect workflow engine to update manager for pre-update events
@@ -206,7 +205,7 @@ process.on('uncaughtException', (error) => {
healthChecker.start();
log.info('server', 'Health checker started');
} catch (err) {
- log.error('server', 'Health checker failed to start', { error: err.message });
+ log.error('server', err, null, { note: 'Health checker failed to start' });
}
})();
@@ -215,7 +214,7 @@ process.on('uncaughtException', (error) => {
updateManager.start();
log.info('server', 'Update manager started');
} catch (err) {
- log.error('server', 'Update manager failed to start', { error: err.message });
+ log.error('server', err, null, { note: 'Update manager failed to start' });
}
// Self-updater
@@ -234,7 +233,7 @@ process.on('uncaughtException', (error) => {
})
.catch(() => {});
} catch (err) {
- log.error('server', 'Self-updater failed to start', { error: err.message });
+ log.error('server', err, null, { note: 'Self-updater failed to start' });
}
// Docker maintenance (optional)
@@ -257,7 +256,7 @@ process.on('uncaughtException', (error) => {
}
});
} catch (err) {
- log.error('server', 'Docker maintenance failed to start', { error: err.message });
+ log.error('server', err, null, { note: 'Docker maintenance failed to start' });
}
}
@@ -271,7 +270,7 @@ process.on('uncaughtException', (error) => {
log.info('digest', `Daily digest generated for ${date}`);
});
} catch (err) {
- log.error('server', 'Log digest failed to start', { error: err.message });
+ log.error('server', err, null, { note: 'Log digest failed to start' });
}
}
diff --git a/dashcaddy-api/src/app.js b/dashcaddy-api/src/app.js
index d943994..8d9e66f 100644
--- a/dashcaddy-api/src/app.js
+++ b/dashcaddy-api/src/app.js
@@ -318,7 +318,7 @@ async function createApp() {
const { writeJsonFile } = require('./utilities/fs-helpers');
await writeJsonFile(config.TOTP_CONFIG_FILE, totpConfig);
} catch (e) {
- log.error('config', 'Could not save TOTP config', { error: e.message });
+ log.error('config', e, null, { note: 'Could not save TOTP config' });
}
}
@@ -437,7 +437,7 @@ async function createApp() {
ctx.workflowEngine = workflowEngine;
log.info('app', 'Workflow engine initialized');
} catch (err) {
- log.error('app', 'Failed to initialize workflow engine', { error: err.message });
+ log.error('app', err, null, { note: 'Failed to initialize workflow engine' });
}
}
@@ -501,12 +501,12 @@ async function createApp() {
if (ctx.notification && ctx.resourceMonitor) {
ctx.resourceMonitor.on('alert', (alertData) => {
ctx.notification.sendAlert(alertData).catch(err => {
- log.error('notification', 'Failed to send alert', { error: err.message });
+ log.error('notification', err, null, { note: 'Failed to send alert' });
});
});
ctx.resourceMonitor.on('auto-restart', (data) => {
ctx.notification.sendServiceEvent('auto-restart', data).catch(err => {
- log.error('notification', 'Failed to send auto-restart notification', { error: err.message });
+ log.error('notification', err, null, { note: 'Failed to send auto-restart notification' });
});
});
}
@@ -514,12 +514,12 @@ async function createApp() {
if (ctx.notification && ctx.backupManager) {
ctx.backupManager.on('backup-complete', (data) => {
ctx.notification.send('backup-complete', data).catch(err => {
- log.error('notification', 'Failed to send backup-complete', { error: err.message });
+ log.error('notification', err, null, { note: 'Failed to send backup-complete' });
});
});
ctx.backupManager.on('backup-failed', (data) => {
ctx.notification.send('backup-failed', data).catch(err => {
- log.error('notification', 'Failed to send backup-failed', { error: err.message });
+ log.error('notification', err, null, { note: 'Failed to send backup-failed' });
});
});
}
@@ -1097,6 +1097,10 @@ async function createApp() {
app.use('/api', notFoundHandler);
app.use(errorMiddleware);
+ // Expose ctx on the app for entry points (server.js dashboard-WS wiring)
+ // without changing the returned shape for existing callers/tests.
+ app.locals.ctx = ctx;
+
return { app, log, config: config.siteConfig, licenseManager };
}
diff --git a/dashcaddy-api/src/config/migrations.js b/dashcaddy-api/src/config/migrations.js
index 81952a0..9882d92 100644
--- a/dashcaddy-api/src/config/migrations.js
+++ b/dashcaddy-api/src/config/migrations.js
@@ -97,7 +97,7 @@ function loadAndMigrate(configFile, log) {
raw = JSON.parse(fs.readFileSync(configFile, 'utf8'));
} catch (e) {
if (log && log.error) {
- log.error('config-migration', 'Failed to parse config.json, using defaults', { error: e.message });
+ log.error('config-migration', e, null, { note: 'Failed to parse config.json, using defaults' });
}
raw = null;
}
diff --git a/dashcaddy-api/src/config/site.js b/dashcaddy-api/src/config/site.js
index 43e11aa..b8bac9a 100644
--- a/dashcaddy-api/src/config/site.js
+++ b/dashcaddy-api/src/config/site.js
@@ -62,7 +62,7 @@ function loadSiteConfig(CONFIG_FILE, log) {
}
} catch (e) {
if (log && log.error) {
- log.error('config', 'Failed to load site config', { error: e.message });
+ log.error('config', e, null, { note: 'Failed to load site config' });
}
}
}
diff --git a/dashcaddy-api/src/context/dns.js b/dashcaddy-api/src/context/dns.js
index 0ee966f..c8eae23 100644
--- a/dashcaddy-api/src/context/dns.js
+++ b/dashcaddy-api/src/context/dns.js
@@ -74,7 +74,7 @@ async function refreshDnsToken(username, password, server, fetchT, log) {
return { success: false, error: result.errorMessage || 'Login failed' };
} catch (error) {
- log.error('dns', 'DNS token refresh error', { error: error.message });
+ log.error('dns', error, null, { note: 'DNS token refresh error' });
return { success: false, error: error.message };
}
}
@@ -141,7 +141,7 @@ async function ensureValidDnsToken(siteConfig, credentialManager, fetchT, log) {
return await refreshDnsToken(username, password, server || primaryIp, fetchT, log);
}
} catch (err) {
- log.error('dns', 'Credential manager error', { error: err.message });
+ log.error('dns', err, null, { note: 'Credential manager error' });
}
return {
@@ -237,7 +237,7 @@ async function getTokenForServer(targetServer, siteConfig, credentialManager, fe
return await authenticateToServer(username, password);
}
} catch (err) {
- log.error('dns', 'Credential manager error', { server: targetServer, error: err.message });
+ log.error('dns', err, null, { note: 'Credential manager error', server: targetServer });
}
return { success: false, error: 'No DNS credentials configured' };
diff --git a/dashcaddy-api/src/context/index.js b/dashcaddy-api/src/context/index.js
index 5ef0ff9..de535c6 100644
--- a/dashcaddy-api/src/context/index.js
+++ b/dashcaddy-api/src/context/index.js
@@ -121,7 +121,7 @@ function assembleContext({
try {
fs.writeFileSync(TAILSCALE_CONFIG_FILE, JSON.stringify(meta, null, 2), 'utf8');
} catch (e) {
- log.error('tailscale-coord', 'Failed to write tailscale-config.json', { error: e.message });
+ log.error('tailscale-coord', e, null, { note: 'Failed to write tailscale-config.json' });
}
}
async function getCoordClient() {
diff --git a/dashcaddy-api/src/context/provider-dns.js b/dashcaddy-api/src/context/provider-dns.js
index 6f3d073..57cedd5 100644
--- a/dashcaddy-api/src/context/provider-dns.js
+++ b/dashcaddy-api/src/context/provider-dns.js
@@ -103,7 +103,7 @@ function createProviderDnsContext(siteConfig, buildDomain, credentialManager, fe
}
return { success: false, error: result.errorMessage || 'Login failed' };
} catch (error) {
- log.error('dns', 'DNS token refresh error', { error: error.message });
+ log.error('dns', error, null, { note: 'DNS token refresh error' });
return { success: false, error: error.message };
}
}
diff --git a/dashcaddy-api/src/dns/dns-propagation.js b/dashcaddy-api/src/dns/dns-propagation.js
index f00416b..92307af 100644
--- a/dashcaddy-api/src/dns/dns-propagation.js
+++ b/dashcaddy-api/src/dns/dns-propagation.js
@@ -172,9 +172,7 @@ class DNSPropagationChecker extends EventEmitter {
expectedIp,
totalTime: result.totalTime
}, 'success').catch(err => {
- this.log.error('dns-propagation', 'Failed to send propagation notification', {
- error: err.message
- });
+ this.log.error('dns-propagation', err, null, { note: 'Failed to send propagation notification' });
});
}
} else {
@@ -187,9 +185,7 @@ class DNSPropagationChecker extends EventEmitter {
expectedIp,
totalTime: result.totalTime
}, 'warning').catch(err => {
- this.log.error('dns-propagation', 'Failed to send timeout notification', {
- error: err.message
- });
+ this.log.error('dns-propagation', err, null, { note: 'Failed to send timeout notification' });
});
}
}
diff --git a/dashcaddy-api/src/dns/dns-providers/technitium.js b/dashcaddy-api/src/dns/dns-providers/technitium.js
index 7f519cb..f2e5c5a 100644
--- a/dashcaddy-api/src/dns/dns-providers/technitium.js
+++ b/dashcaddy-api/src/dns/dns-providers/technitium.js
@@ -118,7 +118,7 @@ class TechnitiumDNSProvider extends BaseDNSProvider {
return await this._doLogin(username, password);
}
} catch (err) {
- log.error('technitium', 'Global credential error', { error: err.message });
+ log.error('technitium', err, null, { note: 'Global credential error' });
}
return {
@@ -164,7 +164,7 @@ class TechnitiumDNSProvider extends BaseDNSProvider {
return { success: false, error: result.errorMessage || 'Login failed' };
} catch (error) {
- log.error('technitium', 'Login error', { error: error.message });
+ log.error('technitium', error, null, { note: 'Login error' });
return { success: false, error: error.message };
}
}
@@ -363,7 +363,7 @@ class TechnitiumDNSProvider extends BaseDNSProvider {
const parsed = this._parseLogText(logText, limit);
return { success: true, logs: parsed };
} catch (error) {
- log.error('technitium', 'Failed to fetch DNS logs', { error: error.message });
+ log.error('technitium', error, null, { note: 'Failed to fetch DNS logs' });
throw new Error(`Failed to get DNS logs: ${error.message}`);
}
}
@@ -449,7 +449,7 @@ class TechnitiumDNSProvider extends BaseDNSProvider {
throw new Error(result.errorMessage || 'Restart failed');
} catch (error) {
- log.error('technitium', 'DNS restart error', { error: error.message });
+ log.error('technitium', error, null, { note: 'DNS restart error' });
throw new Error(`Failed to restart DNS server: ${error.message}`);
}
}
@@ -483,7 +483,7 @@ class TechnitiumDNSProvider extends BaseDNSProvider {
throw new Error(result.errorMessage || 'Update check failed');
} catch (error) {
- log.error('technitium', 'Update check error', { error: error.message });
+ log.error('technitium', error, null, { note: 'Update check error' });
throw new Error(`Failed to check for updates: ${error.message}`);
}
}
diff --git a/dashcaddy-api/src/managers/auto-restart-manager.js b/dashcaddy-api/src/managers/auto-restart-manager.js
index 04663c3..c70b607 100644
--- a/dashcaddy-api/src/managers/auto-restart-manager.js
+++ b/dashcaddy-api/src/managers/auto-restart-manager.js
@@ -86,7 +86,7 @@ class AutoRestartManager extends EventEmitter {
}
this.log.info('auto-restart', 'Policies loaded', { count: this.policies.size });
} catch (err) {
- this.log.error('auto-restart', 'Failed to load policies', { error: err.message });
+ this.log.error('auto-restart', err, null, { note: 'Failed to load policies' });
}
// Listen to health checker status transitions
@@ -246,7 +246,7 @@ class AutoRestartManager extends EventEmitter {
...eventData,
});
} catch (notifErr) {
- this.log.error('auto-restart', 'Notification failed', { error: notifErr.message });
+ this.log.error('auto-restart', notifErr, null, { note: 'Notification failed' });
}
return { action: 'max-reached', ...eventData };
@@ -312,7 +312,7 @@ class AutoRestartManager extends EventEmitter {
...successData,
});
} catch (notifErr) {
- this.log.error('auto-restart', 'Notification failed', { error: notifErr.message });
+ this.log.error('auto-restart', notifErr, null, { note: 'Notification failed' });
}
this.log.info('auto-restart', 'Container restarted', {
@@ -349,7 +349,7 @@ class AutoRestartManager extends EventEmitter {
...failData,
});
} catch (notifErr) {
- this.log.error('auto-restart', 'Notification failed', { error: notifErr.message });
+ this.log.error('auto-restart', notifErr, null, { note: 'Notification failed' });
}
this.log.error('auto-restart', 'Restart failed', {
@@ -478,7 +478,7 @@ class AutoRestartManager extends EventEmitter {
}
await writeJsonFile(this.policiesFile, obj);
} catch (err) {
- this.log.error('auto-restart', 'Failed to save policies', { error: err.message });
+ this.log.error('auto-restart', err, null, { note: 'Failed to save policies' });
}
}
diff --git a/dashcaddy-api/src/managers/config-drift-detector.js b/dashcaddy-api/src/managers/config-drift-detector.js
index 899fc7e..bb6ff12 100644
--- a/dashcaddy-api/src/managers/config-drift-detector.js
+++ b/dashcaddy-api/src/managers/config-drift-detector.js
@@ -75,7 +75,7 @@ class ConfigDriftDetector extends EventEmitter {
const data = await this.servicesStateManager.read();
services = Array.isArray(data) ? data : (data.services || []);
} catch (err) {
- this.log.error('drift', 'Failed to read services', { error: err.message });
+ this.log.error('drift', err, null, { note: 'Failed to read services' });
}
// Gather live Docker containers
@@ -83,7 +83,7 @@ class ConfigDriftDetector extends EventEmitter {
try {
containers = await this.docker.client.listContainers({ all: true });
} catch (err) {
- this.log.error('drift', 'Failed to list containers', { error: err.message });
+ this.log.error('drift', err, null, { note: 'Failed to list containers' });
}
// Build lookup maps
diff --git a/dashcaddy-api/src/managers/notification-manager.js b/dashcaddy-api/src/managers/notification-manager.js
index d5a3f0f..aa0050d 100644
--- a/dashcaddy-api/src/managers/notification-manager.js
+++ b/dashcaddy-api/src/managers/notification-manager.js
@@ -51,7 +51,7 @@ class NotificationManager extends EventEmitter {
this.config = this._mergeConfig(DEFAULT_CONFIG, data);
}
} catch (error) {
- this.log.error('notification', 'Failed to load config', { error: error.message });
+ this.log.error('notification', error, null, { note: 'Failed to load config' });
}
}
@@ -89,7 +89,7 @@ class NotificationManager extends EventEmitter {
fs.writeFileSync(this.NOTIFICATIONS_FILE, JSON.stringify(this.config, null, 2));
return true;
} catch (error) {
- this.log.error('notification', 'Failed to save config', { error: error.message });
+ this.log.error('notification', error, null, { note: 'Failed to save config' });
throw error;
}
}
@@ -429,7 +429,7 @@ class NotificationManager extends EventEmitter {
const interval = (this.config.healthCheck?.intervalMinutes || 5) * 60 * 1000;
this.healthDaemonInterval = setInterval(() => {
this.checkHealth().catch(err => {
- this.log.error('notification', 'Health check failed', { error: err.message });
+ this.log.error('notification', err, null, { note: 'Health check failed' });
});
}, interval);
@@ -488,7 +488,7 @@ class NotificationManager extends EventEmitter {
lastCheck: this.config.healthCheck.lastCheck
};
} catch (error) {
- this.log.error('notification', 'Health check error', { error: error.message });
+ this.log.error('notification', error, null, { note: 'Health check error' });
throw error;
}
}
diff --git a/dashcaddy-api/src/monitoring/disk-space-monitor.js b/dashcaddy-api/src/monitoring/disk-space-monitor.js
index e685301..962204b 100644
--- a/dashcaddy-api/src/monitoring/disk-space-monitor.js
+++ b/dashcaddy-api/src/monitoring/disk-space-monitor.js
@@ -331,7 +331,7 @@ class DiskSpaceMonitor extends EventEmitter {
result.error = err.message;
result.completedAt = new Date().toISOString();
if (this.log) {
- this.log.error('disk', 'Disk cleanup failed', { error: err.message, level });
+ this.log.error('disk', err, null, { note: 'Disk cleanup failed', level });
}
return result;
}
diff --git a/dashcaddy-api/src/monitoring/ssl-monitor.js b/dashcaddy-api/src/monitoring/ssl-monitor.js
index 6935482..15bff9b 100644
--- a/dashcaddy-api/src/monitoring/ssl-monitor.js
+++ b/dashcaddy-api/src/monitoring/ssl-monitor.js
@@ -143,7 +143,7 @@ class SSLMonitor extends EventEmitter {
try {
servicesData = await this.ctx.servicesStateManager.read();
} catch (err) {
- this.log.error('ssl-monitor', 'Failed to read services', { error: err.message });
+ this.log.error('ssl-monitor', err, null, { note: 'Failed to read services' });
return this.getStatus();
}
@@ -212,13 +212,13 @@ class SSLMonitor extends EventEmitter {
// Initial check (non-blocking)
this.checkAll().catch(err => {
- this.log.error('ssl-monitor', 'Initial SSL check failed', { error: err.message });
+ this.log.error('ssl-monitor', err, null, { note: 'Initial SSL check failed' });
});
// Schedule periodic checks
this.intervalHandle = setInterval(() => {
this.checkAll().catch(err => {
- this.log.error('ssl-monitor', 'Periodic SSL check failed', { error: err.message });
+ this.log.error('ssl-monitor', err, null, { note: 'Periodic SSL check failed' });
});
}, this.config.intervalMs);
@@ -299,7 +299,7 @@ class SSLMonitor extends EventEmitter {
clearInterval(this.intervalHandle);
this.intervalHandle = setInterval(() => {
this.checkAll().catch(err => {
- this.log.error('ssl-monitor', 'Periodic SSL check failed', { error: err.message });
+ this.log.error('ssl-monitor', err, null, { note: 'Periodic SSL check failed' });
});
}, this.config.intervalMs);
}
@@ -355,7 +355,7 @@ class SSLMonitor extends EventEmitter {
validTo: certResult.validTo
}, level <= THRESHOLDS.CRITICAL ? 'error' : 'warning');
} catch (err) {
- this.log.error('ssl-monitor', 'Failed to send SSL notification', { error: err.message });
+ this.log.error('ssl-monitor', err, null, { note: 'Failed to send SSL notification' });
}
}
} else if (level === null) {
diff --git a/dashcaddy-api/src/plugins/plugin-manager.js b/dashcaddy-api/src/plugins/plugin-manager.js
index 5d081ca..31a7f43 100644
--- a/dashcaddy-api/src/plugins/plugin-manager.js
+++ b/dashcaddy-api/src/plugins/plugin-manager.js
@@ -81,7 +81,7 @@ class PluginManager extends EventEmitter {
workflowActions: [...this.workflowActions.keys()],
});
} catch (err) {
- this.log.error('plugins', 'Failed to scan plugin directory', { error: err.message });
+ this.log.error('plugins', err, null, { note: 'Failed to scan plugin directory' });
this.loaded = true; // Don't crash — just run without plugins
}
}
diff --git a/dashcaddy-api/src/utilities/error-handler.js b/dashcaddy-api/src/utilities/error-handler.js
index 790bd81..37a5419 100644
--- a/dashcaddy-api/src/utilities/error-handler.js
+++ b/dashcaddy-api/src/utilities/error-handler.js
@@ -7,15 +7,9 @@
* ./error-logger.js and its ./error.log file have been retired.
*/
-const path = require('path');
const { AppError } = require('./errors');
-const { LIMITS } = require('./constants');
const { logError: unifiedLogError, safeErrorMessage } = require('../utils/logging');
const { errorResponse } = require('../utils/responses');
-const platformPaths = require('../../platform-paths');
-
-const ERROR_LOG_FILE = process.env.ERROR_LOG_FILE || path.join(platformPaths.dataDir, 'error.log');
-const MAX_ERROR_LOG_SIZE = LIMITS.ERROR_LOG_SIZE;
/**
* Global error handling middleware
@@ -24,11 +18,10 @@ const MAX_ERROR_LOG_SIZE = LIMITS.ERROR_LOG_SIZE;
function errorMiddleware(err, req, res, next) {
// Log all errors with request context (unified, same file the rest of the app uses)
unifiedLogError(
- ERROR_LOG_FILE,
- MAX_ERROR_LOG_SIZE,
req.path,
err,
{
+ req,
method: req.method,
ip: req.ip,
userId: req.user?.id,
diff --git a/dashcaddy-api/src/utilities/startup-validator.js b/dashcaddy-api/src/utilities/startup-validator.js
index 5215abd..7c73c97 100644
--- a/dashcaddy-api/src/utilities/startup-validator.js
+++ b/dashcaddy-api/src/utilities/startup-validator.js
@@ -228,7 +228,7 @@ async function syncHealthCheckerServices({ log, SERVICES_FILE, servicesStateMana
log.info('health', 'Health checker synced', { added, updated, removed });
}
} catch (error) {
- log.error('health', 'Error syncing health checker', { error: error.message });
+ log.error('health', error, null, { note: 'Error syncing health checker' });
}
}
diff --git a/dashcaddy-api/src/utils/logging.js b/dashcaddy-api/src/utils/logging.js
index 85d0bc9..df67c88 100644
--- a/dashcaddy-api/src/utils/logging.js
+++ b/dashcaddy-api/src/utils/logging.js
@@ -417,6 +417,14 @@ function safeErrorMessage(error) {
// Supports: logError(context, error, extra) → existing route call pattern
async function logErrorWrapper(ctx, err, extra) {
+ // Guard against legacy call shapes that used to corrupt error.log:
+ // the old 5-arg form logError(file, maxSize, path, err, meta) made ctx
+ // a file path and turned maxSize (a number) into the "error". Detect and
+ // normalize so the real error always reaches error.log.
+ if (typeof ctx === 'string' && /^\/.*\.(log|json)$/.test(ctx) && typeof err === 'number') {
+ // Legacy shape: (file, size, reqPath, error, meta) → shift args.
+ [ctx, err, extra] = [arguments[2], arguments[3], { ...arguments[4], req: undefined }];
+ }
const req = extra?.req;
const payload = extra ? { ...extra } : {};
if (payload.req) delete payload.req;
diff --git a/status/dist/core.js b/status/dist/core.js
index a0b71f4..0197537 100644
--- a/status/dist/core.js
+++ b/status/dist/core.js
@@ -1,4 +1,4 @@
-(function(l){"use strict";class a{constructor(){this.errors=[],this.maxErrors=50}logError(b,h,u={}){const y={timestamp:new Date().toISOString(),context:b,message:h instanceof Error?h.message:h,stack:h instanceof Error?h.stack:null,metadata:u};this.errors.push(y),this.errors.length>this.maxErrors&&this.errors.shift(),console.error(`[Onboarding Error] ${b}:`,h,u)}recoverFromError(b,h){switch(this.classifyError(b)){case"ELEMENT_NOT_FOUND":return this.logError("Element Not Found",b,{currentStep:h}),{action:"SKIP_STEP",nextStep:h+1,message:"Target element not found, skipping to next step"};case"STORAGE_UNAVAILABLE":return this.logError("Storage Unavailable",b),{action:"USE_MEMORY_STORAGE",message:"Local storage unavailable, using in-memory storage"};case"DRIVER_NOT_LOADED":return this.logError("Driver.js Not Loaded",b),{action:"ABORT_TOUR",message:"Driver.js library not loaded, cannot start tour"};case"INVALID_TOOLTIP":return this.logError("Invalid Tooltip Configuration",b,{currentStep:h}),{action:"SKIP_STEP",nextStep:h+1,message:"Invalid tooltip configuration, skipping"};case"THEME_DETECTION_FAILED":return this.logError("Theme Detection Failed",b),{action:"USE_DEFAULT_THEME",message:"Using default dark theme"};default:return this.logError("Unknown Error",b,{currentStep:h}),{action:"ABORT_TOUR",message:"Unexpected error occurred, aborting tour"}}}classifyError(b){const h=b.message||b.toString();return h.includes("element")&&h.includes("not found")?"ELEMENT_NOT_FOUND":h.includes("storage")||h.includes("quota")?"STORAGE_UNAVAILABLE":h.includes("driver")||h.includes("undefined")?"DRIVER_NOT_LOADED":h.includes("invalid")||h.includes("validation")?"INVALID_TOOLTIP":h.includes("theme")?"THEME_DETECTION_FAILED":"UNKNOWN"}getErrors(){return[...this.errors]}clearErrors(){this.errors=[]}getStatistics(){const b={total:this.errors.length,byContext:{},byType:{},recent:this.errors.slice(-10)};return this.errors.forEach(h=>{b.byContext[h.context]=(b.byContext[h.context]||0)+1;const u=this.classifyError({message:h.message});b.byType[u]=(b.byType[u]||0)+1}),b}handleDriverLoadFailure(){this.logError("Driver.js Load Failure","Driver.js library failed to load");const b=document.createElement("div");return b.id="onboarding-fallback",b.style.cssText=`
+(function(c){"use strict";class s{constructor(){this.errors=[],this.maxErrors=50}logError(h,b,y={}){const v={timestamp:new Date().toISOString(),context:h,message:b instanceof Error?b.message:b,stack:b instanceof Error?b.stack:null,metadata:y};this.errors.push(v),this.errors.length>this.maxErrors&&this.errors.shift(),console.error(`[Onboarding Error] ${h}:`,b,y)}recoverFromError(h,b){switch(this.classifyError(h)){case"ELEMENT_NOT_FOUND":return this.logError("Element Not Found",h,{currentStep:b}),{action:"SKIP_STEP",nextStep:b+1,message:"Target element not found, skipping to next step"};case"STORAGE_UNAVAILABLE":return this.logError("Storage Unavailable",h),{action:"USE_MEMORY_STORAGE",message:"Local storage unavailable, using in-memory storage"};case"DRIVER_NOT_LOADED":return this.logError("Driver.js Not Loaded",h),{action:"ABORT_TOUR",message:"Driver.js library not loaded, cannot start tour"};case"INVALID_TOOLTIP":return this.logError("Invalid Tooltip Configuration",h,{currentStep:b}),{action:"SKIP_STEP",nextStep:b+1,message:"Invalid tooltip configuration, skipping"};case"THEME_DETECTION_FAILED":return this.logError("Theme Detection Failed",h),{action:"USE_DEFAULT_THEME",message:"Using default dark theme"};default:return this.logError("Unknown Error",h,{currentStep:b}),{action:"ABORT_TOUR",message:"Unexpected error occurred, aborting tour"}}}classifyError(h){const b=h.message||h.toString();return b.includes("element")&&b.includes("not found")?"ELEMENT_NOT_FOUND":b.includes("storage")||b.includes("quota")?"STORAGE_UNAVAILABLE":b.includes("driver")||b.includes("undefined")?"DRIVER_NOT_LOADED":b.includes("invalid")||b.includes("validation")?"INVALID_TOOLTIP":b.includes("theme")?"THEME_DETECTION_FAILED":"UNKNOWN"}getErrors(){return[...this.errors]}clearErrors(){this.errors=[]}getStatistics(){const h={total:this.errors.length,byContext:{},byType:{},recent:this.errors.slice(-10)};return this.errors.forEach(b=>{h.byContext[b.context]=(h.byContext[b.context]||0)+1;const y=this.classifyError({message:b.message});h.byType[y]=(h.byType[y]||0)+1}),h}handleDriverLoadFailure(){this.logError("Driver.js Load Failure","Driver.js library failed to load");const h=document.createElement("div");return h.id="onboarding-fallback",h.style.cssText=`
position: fixed;
bottom: 20px;
right: 20px;
@@ -10,32 +10,32 @@
z-index: 9999;
max-width: 300px;
font-size: 14px;
- `,b.innerHTML=`
+ `,h.innerHTML=`
Welcome to DashCaddy!
The interactive tour is unavailable, but you can explore the dashboard freely.
Check the documentation for help getting started.
- `,document.body.appendChild(b),setTimeout(()=>{b.parentNode&&b.parentNode.removeChild(b)},1e4),!0}handleStorageUnavailable(){this.logError("Storage Unavailable","Local storage is not available");const b={data:{},getItem(h){return this.data[h]||null},setItem(h,u){this.data[h]=u},removeItem(h){delete this.data[h]},clear(){this.data={}}};return console.warn("[ErrorHandler] Using in-memory storage - progress will not persist"),b}sendToErrorTracking(b){}}l.ErrorHandler=a,console.log("[ErrorHandler] Module loaded")})(window),(function(){try{var a=typeof localStorage<"u"&&localStorage.getItem("dashcaddy-health-settings")||null;if(a){var f=JSON.parse(a);f.statsPollingInterval&&f.statsPollingInterval>=5&&f.statsPollingInterval<=3600&&(window.__DC_STATS_OVERRIDE=f.statsPollingInterval*1e3)}}catch{}})();const DC={NAME:"DashCaddy",POLL:{DASHBOARD:1e4,LOGS:3e3,STATS:typeof window<"u"&&window.__DC_STATS_OVERRIDE||5e3,WEATHER:6e5,HEALTH:1e3,DEPLOY_SSL:5e3},DELAYS:{BTN_RESET:2e3,RELOAD:5e3,MODAL_CLOSE:500,PORT_CHECK:500,DEPLOY_INIT:3e3},DEFAULTS:{DNS_PORT:"5380",SERVICE_PORT:"8080",TTL:300,CADDYFILE:"C:\\caddy\\Caddyfile"}},errorHandler=new ErrorHandler,_cachedCfg=JSON.parse(localStorage.getItem("dashcaddy_site_config")||"null"),SITE={tld:_cachedCfg&&_cachedCfg.tld||".home",dnsIp:"",dnsPort:DC.DEFAULTS.DNS_PORT,dnsServers:{},configurationType:_cachedCfg&&_cachedCfg.configurationType||"homelab",domain:_cachedCfg&&_cachedCfg.domain||"",defaults:_cachedCfg&&_cachedCfg.defaults||{},routingMode:_cachedCfg&&_cachedCfg.routingMode||"subdomain",onboardingCompleted:!1};window.__dashcaddySiteConfigLoaded=(async function(){try{const b=await fetch("/api/v1/config");if(b.ok){const h=await b.json();if(h.tld&&(SITE.tld=h.tld.startsWith(".")?h.tld:"."+h.tld),h.dns&&(SITE.dnsIp=h.dns.ip||"",SITE.dnsPort=h.dns.port||DC.DEFAULTS.DNS_PORT),h.dnsServers&&typeof h.dnsServers=="object")for(const[y,c]of Object.entries(h.dnsServers))y!=="__proto__"&&y!=="constructor"&&y!=="prototype"&&(SITE.dnsServers[y]=c);h.configurationType&&(SITE.configurationType=h.configurationType),h.domain&&(SITE.domain=h.domain),h.defaults&&(SITE.defaults=h.defaults),h.routingMode&&(SITE.routingMode=h.routingMode),SITE.onboardingCompleted=h.onboardingCompleted===!0,localStorage.setItem("dashcaddy_site_config",JSON.stringify({tld:SITE.tld,configurationType:SITE.configurationType,domain:SITE.domain,routingMode:SITE.routingMode})),renderDnsCards();const u=document.getElementById("manage-tokens");u&&(u.style.display=Object.keys(SITE.dnsServers).length?"":"none")}}catch{}document.querySelectorAll("[data-tld]").forEach(b=>b.textContent=SITE.tld);const a=document.getElementById("edit-tld-suffix");a&&(a.textContent=SITE.tld);const f=document.getElementById("external-proxy-ip");f&&SITE.dnsIp&&(f.value=SITE.dnsIp,f.placeholder=SITE.dnsIp)})();function buildDomain(l){return l+SITE.tld}function buildServiceUrl(l){return SITE.routingMode==="subdirectory"&&SITE.domain?"https://"+SITE.domain+"/"+l:SITE.configurationType==="public"&&SITE.domain?"https://"+l+"."+SITE.domain:"https://"+buildDomain(l)}function getDnsServerAddr(l){const a=SITE.dnsServers[l];return a?`${a.ip}:${a.port}`:buildDomain(l)}function getPrimaryDnsId(){if(!SITE.dnsIp)return null;for(const[l,a]of Object.entries(SITE.dnsServers))if(a.ip===SITE.dnsIp)return l;return null}function renderDnsCards(){const l=document.querySelector(".top");if(!l)return;const a=Object.keys(SITE.dnsServers);if(!a.length)return;const f='',b=l.firstElementChild;a.forEach(h=>{const u=escapeHtml(h),y=escapeHtml((SITE.dnsServers[h].name||h).toUpperCase()),c=document.createElement("div");c.className="card",c.setAttribute("data-app",h),c.setAttribute("data-status","off"),c.innerHTML=`--
`,l.insertBefore(c,b)}),window.DCI18n&&window.DCI18n.isLoaded()&&window.DCI18n.applyTranslations()}window.renderDnsCards=renderDnsCards;let csrfToken=null;async function getCSRFToken(){if(csrfToken)return csrfToken;try{const l=await fetch("/api/v1/csrf-token");if(!l.ok)throw new Error("Failed to fetch CSRF token");return csrfToken=(await l.json()).token,csrfToken}catch(l){throw errorHandler.logError("[CSRF] Get Token",l,{function:"getCSRFToken"}),l}}async function secureFetch(l,a={}){const f=(a.method||"GET").toUpperCase(),b=!["GET","HEAD","OPTIONS"].includes(f);if(b)try{const u=await getCSRFToken();a.headers={...a.headers,"X-CSRF-Token":u}}catch(u){errorHandler.logError("[CSRF] Add to Request",u,{function:"secureFetch"})}a.signal||(a={...a,signal:AbortSignal.timeout(15e3)}),a.credentials=a.credentials||"same-origin";const h=await fetch(l,a);if(b&&h.status===403)try{const u=await h.clone().json();if(u.error&&(u.error.includes("DC-100")||u.error.includes("DC-101"))){csrfToken=null;const y=await getCSRFToken();return a.headers={...a.headers,"X-CSRF-Token":y},a.signal=AbortSignal.timeout(15e3),fetch(l,a)}}catch{}return h}async function postJSON(l,a){const f=await secureFetch(l,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(a)}),b=await f.json();if(!f.ok||b.success===!1)throw new Error(b.error||`Request failed (${f.status})`);return b}async function getJSON(l){const a=await secureFetch(l);if(!a.ok){let f=`Request failed (${a.status})`;try{f=(await a.json()).error||f}catch{}throw new Error(f)}return a.json()}async function deleteAPI(l){const a=await secureFetch(l,{method:"DELETE"}),f=await a.json();if(!a.ok||f.success===!1)throw new Error(f.error||`Delete failed (${a.status})`);return f}async function withButton(l,a,f,b={}){const h=l.innerHTML,{successText:u="\u2705",resetDelay:y=DC.DELAYS.BTN_RESET}=b;l.disabled=!0,l.innerHTML=a;try{const c=await f();return l.innerHTML=u,setTimeout(()=>{l.innerHTML=h,l.disabled=!1},y),c}catch(c){throw l.innerHTML=h,l.disabled=!1,c}}function openModal(l){document.getElementById(l)?.classList.add("show")}function closeModal(l){document.getElementById(l)?.classList.remove("show")}function wireModal(l,...a){l&&(l.addEventListener("click",f=>{f.target===l&&l.classList.remove("show")}),a.forEach(f=>{f&&typeof f.addEventListener=="function"&&f.addEventListener("click",()=>l.classList.remove("show"))}))}function showNotification(l,a="info",f=3e3){const b=document.querySelector(".deploy-notification");b&&b.remove();const h={info:{bg:"#2196F3",fg:"#fff"},success:{bg:"var(--ok-bg)",fg:"var(--ok-fg)"},error:{bg:"#f44336",fg:"#fff"},warning:{bg:"#ff9800",fg:"#fff"}},u=h[a]||h.info,y=document.createElement("div");y.className="deploy-notification",y.textContent=l,y.style.cssText=`
+ `,document.body.appendChild(h),setTimeout(()=>{h.parentNode&&h.parentNode.removeChild(h)},1e4),!0}handleStorageUnavailable(){this.logError("Storage Unavailable","Local storage is not available");const h={data:{},getItem(b){return this.data[b]||null},setItem(b,y){this.data[b]=y},removeItem(b){delete this.data[b]},clear(){this.data={}}};return console.warn("[ErrorHandler] Using in-memory storage - progress will not persist"),h}sendToErrorTracking(h){}}c.ErrorHandler=s,console.log("[ErrorHandler] Module loaded")})(window),(function(){try{var s=typeof localStorage<"u"&&localStorage.getItem("dashcaddy-health-settings")||null;if(s){var m=JSON.parse(s);m.statsPollingInterval&&m.statsPollingInterval>=5&&m.statsPollingInterval<=3600&&(window.__DC_STATS_OVERRIDE=m.statsPollingInterval*1e3)}}catch{}})();const DC={NAME:"DashCaddy",POLL:{DASHBOARD:1e4,LOGS:3e3,STATS:typeof window<"u"&&window.__DC_STATS_OVERRIDE||5e3,WEATHER:6e5,HEALTH:1e3,DEPLOY_SSL:5e3},DELAYS:{BTN_RESET:2e3,RELOAD:5e3,MODAL_CLOSE:500,PORT_CHECK:500,DEPLOY_INIT:3e3},DEFAULTS:{DNS_PORT:"5380",SERVICE_PORT:"8080",TTL:300,CADDYFILE:"C:\\caddy\\Caddyfile"}},errorHandler=new ErrorHandler,_cachedCfg=JSON.parse(localStorage.getItem("dashcaddy_site_config")||"null"),SITE={tld:_cachedCfg&&_cachedCfg.tld||".home",dnsIp:"",dnsPort:DC.DEFAULTS.DNS_PORT,dnsServers:{},configurationType:_cachedCfg&&_cachedCfg.configurationType||"homelab",domain:_cachedCfg&&_cachedCfg.domain||"",defaults:_cachedCfg&&_cachedCfg.defaults||{},routingMode:_cachedCfg&&_cachedCfg.routingMode||"subdomain",onboardingCompleted:!1};window.__dashcaddySiteConfigLoaded=(async function(){try{const h=await fetch("/api/v1/config");if(h.ok){const b=await h.json();if(b.tld&&(SITE.tld=b.tld.startsWith(".")?b.tld:"."+b.tld),b.dns&&(SITE.dnsIp=b.dns.ip||"",SITE.dnsPort=b.dns.port||DC.DEFAULTS.DNS_PORT),b.dnsServers&&typeof b.dnsServers=="object")for(const[v,u]of Object.entries(b.dnsServers))v!=="__proto__"&&v!=="constructor"&&v!=="prototype"&&(SITE.dnsServers[v]=u);b.configurationType&&(SITE.configurationType=b.configurationType),b.domain&&(SITE.domain=b.domain),b.defaults&&(SITE.defaults=b.defaults),b.routingMode&&(SITE.routingMode=b.routingMode),SITE.onboardingCompleted=b.onboardingCompleted===!0,localStorage.setItem("dashcaddy_site_config",JSON.stringify({tld:SITE.tld,configurationType:SITE.configurationType,domain:SITE.domain,routingMode:SITE.routingMode})),renderDnsCards();const y=document.getElementById("manage-tokens");y&&(y.style.display=Object.keys(SITE.dnsServers).length?"":"none")}}catch{}document.querySelectorAll("[data-tld]").forEach(h=>h.textContent=SITE.tld);const s=document.getElementById("edit-tld-suffix");s&&(s.textContent=SITE.tld);const m=document.getElementById("external-proxy-ip");m&&SITE.dnsIp&&(m.value=SITE.dnsIp,m.placeholder=SITE.dnsIp)})();function buildDomain(c){return c+SITE.tld}function buildServiceUrl(c){return SITE.routingMode==="subdirectory"&&SITE.domain?"https://"+SITE.domain+"/"+c:SITE.configurationType==="public"&&SITE.domain?"https://"+c+"."+SITE.domain:"https://"+buildDomain(c)}function getDnsServerAddr(c){const s=SITE.dnsServers[c];return s?`${s.ip}:${s.port}`:buildDomain(c)}function getPrimaryDnsId(){if(!SITE.dnsIp)return null;for(const[c,s]of Object.entries(SITE.dnsServers))if(s.ip===SITE.dnsIp)return c;return null}function renderDnsCards(){const c=document.querySelector(".top");if(!c)return;const s=Object.keys(SITE.dnsServers);if(!s.length)return;const m='',h=c.firstElementChild;s.forEach(b=>{const y=escapeHtml(b),v=escapeHtml((SITE.dnsServers[b].name||b).toUpperCase()),u=document.createElement("div");u.className="card",u.setAttribute("data-app",b),u.setAttribute("data-status","off"),u.innerHTML=`--
`,c.insertBefore(u,h)}),window.DCI18n&&window.DCI18n.isLoaded()&&window.DCI18n.applyTranslations()}window.renderDnsCards=renderDnsCards;let csrfToken=null;async function getCSRFToken(){if(csrfToken)return csrfToken;try{const c=await fetch("/api/v1/csrf-token");if(!c.ok)throw new Error("Failed to fetch CSRF token");return csrfToken=(await c.json()).token,csrfToken}catch(c){throw errorHandler.logError("[CSRF] Get Token",c,{function:"getCSRFToken"}),c}}async function secureFetch(c,s={}){const m=(s.method||"GET").toUpperCase(),h=!["GET","HEAD","OPTIONS"].includes(m);if(h)try{const y=await getCSRFToken();s.headers={...s.headers,"X-CSRF-Token":y}}catch(y){errorHandler.logError("[CSRF] Add to Request",y,{function:"secureFetch"})}s.signal||(s={...s,signal:AbortSignal.timeout(15e3)}),s.credentials=s.credentials||"same-origin";const b=await fetch(c,s);if(h&&b.status===403)try{const y=await b.clone().json();if(y.error&&(y.error.includes("DC-100")||y.error.includes("DC-101"))){csrfToken=null;const v=await getCSRFToken();return s.headers={...s.headers,"X-CSRF-Token":v},s.signal=AbortSignal.timeout(15e3),fetch(c,s)}}catch{}return b}async function postJSON(c,s){const m=await secureFetch(c,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(s)}),h=await m.json();if(!m.ok||h.success===!1)throw new Error(h.error||`Request failed (${m.status})`);return h}async function getJSON(c){const s=await secureFetch(c);if(!s.ok){let m=`Request failed (${s.status})`;try{m=(await s.json()).error||m}catch{}throw new Error(m)}return s.json()}async function deleteAPI(c){const s=await secureFetch(c,{method:"DELETE"}),m=await s.json();if(!s.ok||m.success===!1)throw new Error(m.error||`Delete failed (${s.status})`);return m}async function withButton(c,s,m,h={}){const b=c.innerHTML,{successText:y="\u2705",resetDelay:v=DC.DELAYS.BTN_RESET}=h;c.disabled=!0,c.innerHTML=s;try{const u=await m();return c.innerHTML=y,setTimeout(()=>{c.innerHTML=b,c.disabled=!1},v),u}catch(u){throw c.innerHTML=b,c.disabled=!1,u}}function openModal(c){document.getElementById(c)?.classList.add("show")}function closeModal(c){document.getElementById(c)?.classList.remove("show")}function wireModal(c,...s){c&&(c.addEventListener("click",m=>{m.target===c&&c.classList.remove("show")}),s.forEach(m=>{m&&typeof m.addEventListener=="function"&&m.addEventListener("click",()=>c.classList.remove("show"))}))}function showNotification(c,s="info",m=3e3){const h=document.querySelector(".deploy-notification");h&&h.remove();const b={info:{bg:"#2196F3",fg:"#fff"},success:{bg:"var(--ok-bg)",fg:"var(--ok-fg)"},error:{bg:"#f44336",fg:"#fff"},warning:{bg:"#ff9800",fg:"#fff"}},y=b[s]||b.info,v=document.createElement("div");v.className="deploy-notification",v.textContent=c,v.style.cssText=`
position: fixed; top: 20px; right: 20px;
- background: ${u.bg}; color: ${u.fg};
+ background: ${y.bg}; color: ${y.fg};
padding: 16px 24px; border-radius: 8px;
box-shadow: 0 4px 12px rgba(0,0,0,.3);
z-index: 10000; animation: slideIn 0.3s ease-out;
max-width: 400px; white-space: pre-line; font-size: 14px;
- `,document.body.appendChild(y),f>0&&setTimeout(()=>y.remove(),f)}function timeAgo(l){const a=Date.now()-new Date(l).getTime();return a<6e4?"just now":a<36e5?Math.floor(a/6e4)+"m ago":a<864e5?Math.floor(a/36e5)+"h ago":Math.floor(a/864e5)+"d ago"}function safeGet(l,a=null){try{const f=localStorage.getItem(l);return f!==null?f:a}catch{return a}}function safeSet(l,a){try{localStorage.setItem(l,a)}catch{}}function safeRemove(l){try{localStorage.removeItem(l)}catch{}}function safeSessionGet(l,a=null){try{const f=sessionStorage.getItem(l);return f!==null?f:a}catch{return a}}function safeSessionSet(l,a){try{sessionStorage.setItem(l,a)}catch{}}function safeGetJSON(l,a=null){try{const f=localStorage.getItem(l);return f?JSON.parse(f):a}catch{return a}}function escapeHtml(l){return String(l??"").replace(/&/g,"&").replace(//g,">").replace(/"/g,""").replace(/'/g,"'")}function injectModal(l,a){document.getElementById(l)||document.body.insertAdjacentHTML("beforeend",a)}const DC_BUS={_handlers:{},on(l,a){var f;((f=this._handlers)[l]||(f[l]=[])).push(a)},off(l,a){this._handlers[l]=this._handlers[l]?.filter(f=>f!==a)},emit(l,a){this._handlers[l]?.forEach(f=>f(a))}},AppState={_apps:[],getApps(){return this._apps},setApps(l){this._apps=l,window.APPS=l,DC_BUS.emit("apps:changed",l)},findApp(l){return this._apps.find(a=>a.id===l)},addApp(l){this._apps.push(l),window.APPS=this._apps,DC_BUS.emit("apps:changed",this._apps)},removeApp(l){const a=this._apps.findIndex(f=>f.id===l);return a>-1&&(this._apps.splice(a,1),window.APPS=this._apps,DC_BUS.emit("apps:changed",this._apps)),a>-1},updateApp(l,a){const f=this._apps.find(b=>b.id===l);if(f){for(const[b,h]of Object.entries(a))b!=="__proto__"&&b!=="constructor"&&b!=="prototype"&&(f[b]=h);DC_BUS.emit("apps:changed",this._apps)}return f}};(function(){function l(){const b=document.createElement("div");return b.className="skeleton-card",b.innerHTML='',b}function a(b){const h=document.getElementById("cards");if(!(!h||h.querySelector(".card"))){b=b||6;for(let u=0;u.4,A={};return A.hover=k?v(w,B,.35):v(w,$,.08),A["card-hover"]=v(w,A.hover,.5),A.base=v(B,w,.6),A["fg-muted"]=v(E,B,.35),A.success=S,A.error=T,A.warning=k?"#d68a00":"#f39c12",A}function e(C,B){var $=B.lightBg||B.bg&&i(B.bg)>.4,E=B.accent||B["accent-strong"]||"#888888",w=d(E);return $?":root."+C+` body {
+ `,document.body.appendChild(v),m>0&&setTimeout(()=>v.remove(),m)}function timeAgo(c){const s=Date.now()-new Date(c).getTime();return s<6e4?"just now":s<36e5?Math.floor(s/6e4)+"m ago":s<864e5?Math.floor(s/36e5)+"h ago":Math.floor(s/864e5)+"d ago"}function safeGet(c,s=null){try{const m=localStorage.getItem(c);return m!==null?m:s}catch{return s}}function safeSet(c,s){try{localStorage.setItem(c,s)}catch{}}function safeRemove(c){try{localStorage.removeItem(c)}catch{}}function safeSessionGet(c,s=null){try{const m=sessionStorage.getItem(c);return m!==null?m:s}catch{return s}}function safeSessionSet(c,s){try{sessionStorage.setItem(c,s)}catch{}}function safeGetJSON(c,s=null){try{const m=localStorage.getItem(c);return m?JSON.parse(m):s}catch{return s}}function escapeHtml(c){return String(c??"").replace(/&/g,"&").replace(//g,">").replace(/"/g,""").replace(/'/g,"'")}function injectModal(c,s){document.getElementById(c)||document.body.insertAdjacentHTML("beforeend",s)}const DC_BUS={_handlers:{},on(c,s){var m;((m=this._handlers)[c]||(m[c]=[])).push(s)},off(c,s){this._handlers[c]=this._handlers[c]?.filter(m=>m!==s)},emit(c,s){this._handlers[c]?.forEach(m=>m(s))}},AppState={_apps:[],getApps(){return this._apps},setApps(c){this._apps=c,window.APPS=c,DC_BUS.emit("apps:changed",c)},findApp(c){return this._apps.find(s=>s.id===c)},addApp(c){this._apps.push(c),window.APPS=this._apps,DC_BUS.emit("apps:changed",this._apps)},removeApp(c){const s=this._apps.findIndex(m=>m.id===c);return s>-1&&(this._apps.splice(s,1),window.APPS=this._apps,DC_BUS.emit("apps:changed",this._apps)),s>-1},updateApp(c,s){const m=this._apps.find(h=>h.id===c);if(m){for(const[h,b]of Object.entries(s))h!=="__proto__"&&h!=="constructor"&&h!=="prototype"&&(m[h]=b);DC_BUS.emit("apps:changed",this._apps)}return m}};(function(){function c(){const h=document.createElement("div");return h.className="skeleton-card",h.innerHTML='',h}function s(h){const b=document.getElementById("cards");if(!(!b||b.querySelector(".card"))){h=h||6;for(let y=0;y.4,A={};return A.hover=k?g(x,B,.35):g(x,$,.08),A["card-hover"]=g(x,A.hover,.5),A.base=g(B,x,.6),A["fg-muted"]=g(E,B,.35),A.success=S,A.error=T,A.warning=k?"#d68a00":"#f39c12",A}function e(C,B){var $=B.lightBg||B.bg&&d(B.bg)>.4,E=B.accent||B["accent-strong"]||"#888888",x=a(E);return $?":root."+C+` body {
background:
- radial-gradient(1200px 800px at 10% -10%, rgba(`+w.r+","+w.g+","+w.b+`, .08), transparent 60%),
- radial-gradient(1000px 700px at 110% 10%, rgba(`+w.r+","+w.g+","+w.b+`, .05), transparent 55%),
+ radial-gradient(1200px 800px at 10% -10%, rgba(`+x.r+","+x.g+","+x.b+`, .08), transparent 60%),
+ radial-gradient(1000px 700px at 110% 10%, rgba(`+x.r+","+x.g+","+x.b+`, .05), transparent 55%),
var(--bg);
}
`:":root."+C+` body {
background:
- radial-gradient(1200px 900px at 8% -12%, rgba(`+w.r+","+w.g+","+w.b+`, .10), transparent 60%),
- radial-gradient(1000px 700px at 110% -10%, rgba(`+w.r+","+w.g+","+w.b+`, .07), transparent 55%),
+ radial-gradient(1200px 900px at 8% -12%, rgba(`+x.r+","+x.g+","+x.b+`, .10), transparent 60%),
+ radial-gradient(1000px 700px at 110% -10%, rgba(`+x.r+","+x.g+","+x.b+`, .07), transparent 55%),
var(--bg);
}
-`}function s(C,B){var $=B.lightBg||B.bg&&i(B.bg)>.4;return $?":root."+C+` button:hover {
+`}function i(C,B){var $=B.lightBg||B.bg&&d(B.bg)>.4;return $?":root."+C+` button:hover {
background: color-mix(in srgb, var(--accent-strong) 12%, white 88%);
border-color: rgba(0, 0, 0, .15);
box-shadow: 0 1px 6px rgba(0, 0, 0, .08), inset 0 1px 0 rgba(255, 255, 255, .8);
@@ -44,20 +44,20 @@
background: color-mix(in srgb, var(--accent) 18%, transparent);
border-color: color-mix(in srgb, var(--accent) 35%, var(--border));
}
-`}function n(){return window.matchMedia("(prefers-color-scheme: dark)").matches?"dark":"light"}function r(){u.forEach(function(C){document.documentElement.style.removeProperty("--"+C)})}function g(C,B){var $=C.toLowerCase().replace(/[^a-z0-9]+/g,"-").replace(/^-|-$/g,"");$||($="custom"),b.indexOf($)!==-1&&($=$+"-custom");for(var E=safeGetJSON(a,{}),w=$,S=2;E[$]&&$!==B;)$=w+"-"+S++;return $}function m(C){var B=document.getElementById("user-theme-styles");B&&B.remove(),h.length=b.length,Object.keys(p).forEach(function(T){b.indexOf(T)===-1&&delete p[T]});var $=C||safeGetJSON(a,{}),E=Object.keys($);if(E=E.filter(function(T){return b.indexOf(T)===-1}),!!E.length){var w="";E.forEach(function(T){var k=$[T];h.indexOf(T)===-1&&h.push(T);var A={};u.forEach(function(O){k[O]&&(A[O]=k[O])}),A["card-bg"]=k["card-base"]||k.bg,k.lightBg&&(A.lightBg=!0);var D=t(A);c.forEach(function(O){!A[O]&&D[O]&&(A[O]=D[O])}),p[T]=A,w+=":root."+T+` {
-`,u.forEach(function(O){A[O]&&(w+=" --"+O+": "+A[O]+`;
-`)}),w+=`}
-`,w+=e(T,A),w+=s(T,A)});var S=document.createElement("style");S.id="user-theme-styles",S.textContent=w,document.head.appendChild(S)}}function x(){secureFetch("/api/v1/themes").then(function(C){return C.json()}).then(function(C){if(!(!C.success||!C.themes)){var B=C.themes,$=safeGetJSON(a,{});if(JSON.stringify(B)!==JSON.stringify($)){safeSet(a,JSON.stringify(B)),m(B);var E=safeGet(l);E&&h.indexOf(E)!==-1&&L(E)}}}).catch(function(){})}function I(){var C=safeGetJSON(f);if(C){var B=C.name||"Custom",$=g(B),E={name:B};u.forEach(function(T){C[T]&&(E[T]=C[T])});var w=safeGetJSON(a,{});w[$]=E,safeSet(a,JSON.stringify(w)),safeGet(l)==="custom"&&safeSet(l,$),safeRemove(f);var S={};u.forEach(function(T){E[T]&&(S[T]=E[T])}),fetch("/api/v1/themes/"+$,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:B,colors:S})}).catch(function(){})}}function L(C){document.documentElement.classList.add("theme-transitioning"),h.forEach(function(w){w!=="dark"&&document.documentElement.classList.remove(w)}),r(),C!=="dark"&&document.documentElement.classList.add(C),safeSet(l,C);var B=p[C],$=document.querySelector('meta[name="theme-color"]');$&&B&&$.setAttribute("content",B.bg);var E=B&&B.lightBg;!E&&B&&B.bg&&(E=i(B.bg)>.4),E?document.documentElement.classList.add("light-bg"):document.documentElement.classList.remove("light-bg"),setTimeout(function(){document.documentElement.classList.remove("theme-transitioning")},300)}I(),m();var P=safeGet(l);P==="red"&&(P="black",safeSet(l,"black")),P&&P!=="dark"&&h.indexOf(P)===-1&&(P=null),L(P||n()),x(),window.matchMedia("(prefers-color-scheme: dark)").addEventListener("change",function(C){safeGet(l)||L(C.matches?"dark":"light")}),window.THEMES=h,window.BUILTIN_THEMES=b,window.THEME_COLORS=p,window.THEME_PROPS=u,window.BASE_PROPS=y,window.DERIVED_PROPS=c,window.USER_THEMES_KEY=a,window.applyTheme=L,window.clearCustomProperties=r,window.injectUserThemeStyles=m,window.syncThemesFromServer=x,window.slugifyThemeName=g,window.getActiveTheme=function(){return safeGet(l)||n()},window.deriveExtendedColors=t,window.hexToRgb=d,window.rgbToHex=o,window.blendColors=v})(),(function(){let l=null;async function a(){if(l)return l;try{const d=await fetch("/api/v1/auth/login/methods",{cache:"no-store"});if(!d.ok)throw new Error(`methods HTTP ${d.status}`);const o=await d.json();return l=Array.isArray(o.providers)?o.providers:[],l}catch(d){return console.warn("[auth-gate] methods fetch failed; falling back to TOTP-only",d),[]}}function f(d){const o=document.getElementById("totp-overlay");if(!o)return;const v=o.querySelector(".totp-card");if(!v)return;const i=v.innerHTML;v.dataset.originalBody||(v.dataset.originalBody=i);const t=d.map(e=>{const s=e.config&&(e.config.label||e.name)||e.name;return`