diff --git a/bun.lock b/bun.lock index 5b9919a..c2d0a8e 100644 --- a/bun.lock +++ b/bun.lock @@ -5,7 +5,7 @@ "": { "name": "horandez-and-detroit", "dependencies": { - "@moduletrace/bun-sqlite": "^1.1.10", + "@moduletrace/bun-sqlite": "^1.1.12", "@moduletrace/bunext": "^1.1.0", "gray-matter": "^4.0.3", "html-to-react": "^1.7.0", @@ -17,6 +17,7 @@ "nodemailer": "^9.0.5", "onnxruntime-node": "^1.27.0", "openai": "^7.4.0", + "qrcode": "^1.5.4", "react-code-blocks": "^0.1.6", "react-markdown": "^10.1.0", "react-responsive-modal": "^7.2.0", @@ -36,6 +37,7 @@ "@types/mdx": "^2.0.14", "@types/node": "^26.2.0", "@types/nodemailer": "^8.0.1", + "@types/qrcode": "^1.5.6", "@types/react": "^19.2.18", "@types/react-dom": "^19.2.4", "@types/turndown": "^5.0.6", @@ -220,7 +222,7 @@ "@mixmark-io/domino": ["@mixmark-io/domino@2.2.0", "", {}, "sha512-Y28PR25bHXUg88kCV7nivXrP2Nj2RueZ3/l/jdx6J9f8J4nsEGcgX0Qe6lt7Pa+J79+kPiJU3LguR6O/6zrLOw=="], - "@moduletrace/bun-sqlite": ["@moduletrace/bun-sqlite@1.1.10", "https://git.tben.me/api/packages/Moduletrace/npm/%40moduletrace%2Fbun-sqlite/-/1.1.10/bun-sqlite-1.1.10.tgz", { "dependencies": { "@inquirer/prompts": "^8.3.0", "chalk": "^5.6.2", "commander": "^14.0.3", "inquirer": "^13.3.2", "lodash": "^4.17.23", "mysql": "^2.18.1", "sqlite-vec": "^0.1.7-alpha.2" }, "peerDependencies": { "typescript": "^5" }, "bin": { "bun-sqlite": "dist/commands/index.js" } }, "sha512-Lvx9Om03vbDen/bZoLfcsFLJEHXjkJLD1646uobzbroLxv4OFPBQlsnlz+k5ljsMU/N+L5gvUy5cI13H1rMdFA=="], + "@moduletrace/bun-sqlite": ["@moduletrace/bun-sqlite@1.1.12", "https://git.tben.me/api/packages/Moduletrace/npm/%40moduletrace%2Fbun-sqlite/-/1.1.12/bun-sqlite-1.1.12.tgz", { "dependencies": { "@inquirer/prompts": "^8.3.0", "chalk": "^5.6.2", "commander": "^14.0.3", "inquirer": "^13.3.2", "lodash": "^4.17.23", "mysql": "^2.18.1", "sqlite-vec": "^0.1.7-alpha.2" }, "peerDependencies": { "typescript": "^5" }, "bin": { "bun-sqlite": "dist/commands/index.js" } }, "sha512-2/4NFgI/uvdc3SOYd2lPGIni+8x2jHWwEhSLZaCWpG7666Hbg3Yg6eakZmZ43Blm/7hEap8B3Bt1Jo2wCBk1cA=="], "@moduletrace/bunext": ["@moduletrace/bunext@1.1.0", "https://git.tben.me/api/packages/Moduletrace/npm/%40moduletrace%2Fbunext/-/1.1.0/bunext-1.1.0.tgz", { "dependencies": { "@tailwindcss/postcss": "^4.2.2", "@types/bun": "latest", "@types/node": "^24.10.0", "@types/react": "^19.2.2", "@types/react-dom": "^19.2.2", "bun-plugin-tailwind": "^0.1.2", "chalk": "^5.6.2", "chokidar": "^5.0.0", "commander": "^14.0.2", "esbuild": "^0.27.4", "lightningcss-wasm": "^1.32.0", "lodash": "^4.17.23", "micromatch": "^4.0.8", "ora": "^9.0.0", "postcss": "^8.5.8", "react": "^19.2.4", "react-dom": "^19.2.4", "tailwindcss": "^4.2.2", "typescript": "^5.0.0" }, "bin": { "bunext": "dist/commands/index.js" } }, "sha512-nlqRpO9BmkfTwmh/FAoxDrIdogGxVz91cK/vLt0d5o0A8Pm9VhPdaWnYtJWvPEdMiPwThYpAwVUBjT3bgg+lAA=="], @@ -314,6 +316,8 @@ "@types/prismjs": ["@types/prismjs@1.26.6", "", {}, "sha512-vqlvI7qlMvcCBbVe0AKAb4f97//Hy0EBTaiW8AalRnG/xAN5zOiWWyrNqNXeq8+KAuvRewjCVY1+IPxk4RdNYw=="], + "@types/qrcode": ["@types/qrcode@1.5.6", "", { "dependencies": { "@types/node": "*" } }, "sha512-te7NQcV2BOvdj2b1hCAHzAoMNuj65kNBMz0KBaxM6c3VGBOhU0dURQKOtH8CFNI/dsKkwlv32p26qYQTWoB5bw=="], + "@types/react": ["@types/react@19.2.18", "", { "dependencies": { "csstype": "^3.2.2" } }, "sha512-AnzbBERsrLKtk2XSfTbYRLjQPdy116Sty4q+T+Bp3IC4l6jNBvreVPAHmpq9qhXQM7CXZPjLVmGMw9sy+hxQ3w=="], "@types/react-dom": ["@types/react-dom@19.2.4", "", { "peerDependencies": { "@types/react": "^19.2.0" } }, "sha512-Bsc+QHgp+P/F02XDzNCY9jnZNCUuLki36KT7VKrTXXLdHf+vHMNZnW1rVu5DNW/rCK+fya3DATySbLM4yhtKUw=="], @@ -378,6 +382,8 @@ "ansi-regex": ["ansi-regex@6.2.2", "", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="], + "ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], + "argparse": ["argparse@1.0.10", "", { "dependencies": { "sprintf-js": "~1.0.2" } }, "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg=="], "array-find-index": ["array-find-index@1.0.2", "", {}, "sha512-M1HQyIXcBGtVywBt8WVdim+lrNaK7VHp99Qt5pSNziXznKHViIBbXWtfRTpEFpF/c4FdfxNAsCCwPp5phBYJtw=="], @@ -434,10 +440,16 @@ "cli-width": ["cli-width@4.1.0", "", {}, "sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ=="], + "cliui": ["cliui@6.0.0", "", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.0", "wrap-ansi": "^6.2.0" } }, "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ=="], + "codepage": ["codepage@1.15.0", "", {}, "sha512-3g6NUTPd/YtuuGrhMnOMRjFc+LJw/bnMp3+0r/Wcz3IXUuCosKRJvMphm5+Q+bvTVGcJJuRvVLuYba+WojaFaA=="], "collapse-white-space": ["collapse-white-space@2.1.0", "", {}, "sha512-loKTxY1zCOuG4j9f6EPnuyyYkf58RnhhWTvRoZEokgB+WbdXehfjFviyOVYkqzEWz1Q5kRiZdBYS5SwxbQYwzw=="], + "color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="], + + "color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="], + "comma-separated-tokens": ["comma-separated-tokens@2.0.3", "", {}, "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg=="], "commander": ["commander@14.0.3", "", {}, "sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw=="], @@ -456,6 +468,8 @@ "debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], + "decamelize": ["decamelize@1.2.0", "", {}, "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA=="], + "decode-named-character-reference": ["decode-named-character-reference@1.3.0", "", { "dependencies": { "character-entities": "^2.0.0" } }, "sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q=="], "define-data-property": ["define-data-property@1.1.4", "", { "dependencies": { "es-define-property": "^1.0.0", "es-errors": "^1.3.0", "gopd": "^1.0.1" } }, "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A=="], @@ -468,6 +482,8 @@ "devlop": ["devlop@1.1.0", "", { "dependencies": { "dequal": "^2.0.0" } }, "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA=="], + "dijkstrajs": ["dijkstrajs@1.0.3", "", {}, "sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA=="], + "dingbat-to-unicode": ["dingbat-to-unicode@1.0.1", "", {}, "sha512-98l0sW87ZT58pU4i61wa2OHwxbiYSbuxsCBozaVnYX2iCnr3bLM3fIes1/ej7h1YdOKuKt/MLs706TVnALA65w=="], "dom-serializer": ["dom-serializer@2.0.0", "", { "dependencies": { "domelementtype": "^2.3.0", "domhandler": "^5.0.2", "entities": "^4.2.0" } }, "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg=="], @@ -480,6 +496,8 @@ "duck": ["duck@0.1.12", "", { "dependencies": { "underscore": "^1.13.1" } }, "sha512-wkctla1O6VfP89gQ+J/yDesM0S7B7XLXjKGzXxMDVFg7uEn706niAtyYovKbyq1oT9YwDcly721/iUWoc8MVRg=="], + "emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], + "enhanced-resolve": ["enhanced-resolve@5.24.5", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.3.3" } }, "sha512-L1l8TNvomm6UVW5B253AGxQagSQr+vGwhMlrrfRS2qmhx46AMpMVJKQYLvWYbysTMY8VoicOvzHzoHMbyzB+4A=="], "entities": ["entities@4.5.0", "", {}, "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="], @@ -528,7 +546,7 @@ "fill-range": ["fill-range@7.1.1", "", { "dependencies": { "to-regex-range": "^5.0.1" } }, "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg=="], - "find-up": ["find-up@1.1.2", "", { "dependencies": { "path-exists": "^2.0.0", "pinkie-promise": "^2.0.0" } }, "sha512-jvElSjyuo4EMQGoTwo1uJU5pQMwTW5lS1x05zzfJuTIyLR3zwO27LYrxNg+dlvKpGOuGy/MzBdXh80g0ve5+HA=="], + "find-up": ["find-up@4.1.0", "", { "dependencies": { "locate-path": "^5.0.0", "path-exists": "^4.0.0" } }, "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw=="], "format": ["format@0.2.2", "", {}, "sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww=="], @@ -536,6 +554,8 @@ "function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="], + "get-caller-file": ["get-caller-file@2.0.5", "", {}, "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg=="], + "get-east-asian-width": ["get-east-asian-width@1.6.0", "", {}, "sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA=="], "get-stdin": ["get-stdin@4.0.1", "", {}, "sha512-F5aQMywwJ2n85s4hJPTT9RPxGmubonuB10MNYo17/xph174n2MIR33HRguhzVag10O/npM7SPk73LMZNP+FaWw=="], @@ -608,6 +628,8 @@ "is-finite": ["is-finite@1.1.0", "", {}, "sha512-cdyMtqX/BOqqNBBiKlIVkytNHm49MtMlYyn1zxzvJKWmFMlGzm+ry5BBfYyeY9YmNKbRSo/o7OX9w9ale0wg3w=="], + "is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="], + "is-hexadecimal": ["is-hexadecimal@2.0.1", "", {}, "sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg=="], "is-interactive": ["is-interactive@2.0.0", "", {}, "sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ=="], @@ -662,6 +684,8 @@ "load-json-file": ["load-json-file@1.1.0", "", { "dependencies": { "graceful-fs": "^4.1.2", "parse-json": "^2.2.0", "pify": "^2.0.0", "pinkie-promise": "^2.0.0", "strip-bom": "^2.0.0" } }, "sha512-cy7ZdNRXdablkXYNI049pthVeXFurRyb9+hA/dZzerZ0pGTx42z+y+ssxBaVV2l70t1muq5IdKhn4UtcoGUY9A=="], + "locate-path": ["locate-path@5.0.0", "", { "dependencies": { "p-locate": "^4.1.0" } }, "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g=="], + "lodash": ["lodash@4.18.1", "", {}, "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q=="], "lodash.camelcase": ["lodash.camelcase@4.3.0", "", {}, "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA=="], @@ -834,6 +858,12 @@ "ora": ["ora@9.4.1", "", { "dependencies": { "chalk": "^5.6.2", "cli-cursor": "^5.0.0", "cli-spinners": "^3.2.0", "is-interactive": "^2.0.0", "is-unicode-supported": "^2.1.0", "log-symbols": "^7.0.1", "stdin-discarder": "^0.3.2", "string-width": "^8.1.0" } }, "sha512-6VlU9MLXbjVQD04AZCMX28hVtA5bUoadvUqO76MUCVA0ilwJbMiHsITRPfyVm6p/BC0Av/BXMujx39WCe1LEqw=="], + "p-limit": ["p-limit@2.3.0", "", { "dependencies": { "p-try": "^2.0.0" } }, "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w=="], + + "p-locate": ["p-locate@4.1.0", "", { "dependencies": { "p-limit": "^2.2.0" } }, "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A=="], + + "p-try": ["p-try@2.2.0", "", {}, "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ=="], + "pako": ["pako@1.0.11", "", {}, "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw=="], "parse-entities": ["parse-entities@4.0.2", "", { "dependencies": { "@types/unist": "^2.0.0", "character-entities-legacy": "^3.0.0", "character-reference-invalid": "^2.0.0", "decode-named-character-reference": "^1.0.0", "is-alphanumerical": "^2.0.0", "is-decimal": "^2.0.0", "is-hexadecimal": "^2.0.0" } }, "sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw=="], @@ -844,7 +874,7 @@ "parse5": ["parse5@7.3.0", "", { "dependencies": { "entities": "^6.0.0" } }, "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw=="], - "path-exists": ["path-exists@2.1.0", "", { "dependencies": { "pinkie-promise": "^2.0.0" } }, "sha512-yTltuKuhtNeFJKa1PiRzfLAU5182q1y4Eb4XCJ3PBqyzEDkAZRzBrKKBct682ls9reBVHf9udYLN5Nd+K1B9BQ=="], + "path-exists": ["path-exists@4.0.0", "", {}, "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="], "path-is-absolute": ["path-is-absolute@1.0.1", "", {}, "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg=="], @@ -862,6 +892,8 @@ "pinkie-promise": ["pinkie-promise@2.0.1", "", { "dependencies": { "pinkie": "^2.0.0" } }, "sha512-0Gni6D4UcLTbv9c57DfxDGdr41XfgUjqWZu492f0cIGr16zDU06BWP/RAEvOuo7CQ0CNjHaLlM59YJJFm3NWlw=="], + "pngjs": ["pngjs@5.0.0", "", {}, "sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw=="], + "postcss": ["postcss@8.5.26", "", { "dependencies": { "nanoid": "^3.3.17", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-u82N74LFzG8ca+dD8puPnplTXoGH4fTPpVGuIbt36G3qvNlkvfD0lEAZSxaly3KX8TS/L1A1gsCEmvKmBcVbkQ=="], "postcss-value-parser": ["postcss-value-parser@4.2.0", "", {}, "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ=="], @@ -872,6 +904,8 @@ "property-information": ["property-information@7.2.0", "", {}, "sha512-IAtzIB6sUiWaJYrX9smp3V46pBGbBeLFRGdh25kg1334VcBlD8HzhPeNIWQH9zhGmo2itIe25EHt9dQP7G5hmg=="], + "qrcode": ["qrcode@1.5.4", "", { "dependencies": { "dijkstrajs": "^1.0.1", "pngjs": "^5.0.0", "yargs": "^15.3.1" }, "bin": { "qrcode": "bin/qrcode" } }, "sha512-1ca71Zgiu6ORjHqFBDpnSMTR2ReToX4l1Au1VFLyVeBTFavzQnv5JxMFr3ukHVKpSrSA2MCk0lNJSykjUfz7Zg=="], + "react": ["react@19.2.8", "", {}, "sha512-PWaYA1L/q9u2u7xYQi+Y3L3Yfnie7XyLeaJICV1MGD6LprsBxcAqGjYyr0eY3p+QdsA+x/Irkt4Qif8D63+Sbw=="], "react-code-blocks": ["react-code-blocks@0.1.6", "", { "dependencies": { "@babel/runtime": "^7.10.4", "react-syntax-highlighter": "^15.5.0", "styled-components": "^6.1.0", "tslib": "^2.6.0" }, "peerDependencies": { "react": ">=16" } }, "sha512-ENNuxG07yO+OuX1ChRje3ieefPRz6yrIpHmebQlaFQgzcAHbUfVeTINpOpoI9bSRSObeYo/OdHsporeToZ7fcg=="], @@ -924,6 +958,10 @@ "repeating": ["repeating@2.0.1", "", { "dependencies": { "is-finite": "^1.0.0" } }, "sha512-ZqtSMuVybkISo2OWvqvm7iHSWngvdaW3IpsT9/uP8v4gMi591LY6h35wdOfvQdWCKFWZWm2Y1Opp4kV7vQKT6A=="], + "require-directory": ["require-directory@2.1.1", "", {}, "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q=="], + + "require-main-filename": ["require-main-filename@2.0.0", "", {}, "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg=="], + "resolve": ["resolve@1.22.12", "", { "dependencies": { "es-errors": "^1.3.0", "is-core-module": "^2.16.1", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { "resolve": "bin/resolve" } }, "sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA=="], "restore-cursor": ["restore-cursor@5.1.0", "", { "dependencies": { "onetime": "^7.0.0", "signal-exit": "^4.1.0" } }, "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA=="], @@ -944,6 +982,8 @@ "serialize-error": ["serialize-error@8.1.0", "", { "dependencies": { "type-fest": "^0.20.2" } }, "sha512-3NnuWfM6vBYoy5gZFvHiYsVbafvI9vZv/+jlIigFn4oP4zjNPK3LhcY0xSCgeb1a5L8jO71Mit9LlNoi2UfDDQ=="], + "set-blocking": ["set-blocking@2.0.0", "", {}, "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw=="], + "setimmediate": ["setimmediate@1.0.5", "", {}, "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA=="], "sharp": ["sharp@0.35.3", "", { "dependencies": { "@img/colour": "^1.1.0", "detect-libc": "^2.1.2", "semver": "^7.8.5" }, "optionalDependencies": { "@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" } }, "sha512-ej0zVHuZGHCiABXcNxeYhpRnPNPAcvbG8RMdBAhDAxLKkCRVSpK3Iyu7qbqw3JMzoj0REeM6f3tJLtVwl0023Q=="], @@ -1068,18 +1108,28 @@ "web-namespaces": ["web-namespaces@2.0.1", "", {}, "sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ=="], + "which-module": ["which-module@2.0.1", "", {}, "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ=="], + "wmf": ["wmf@1.0.2", "", {}, "sha512-/p9K7bEh0Dj6WbXg4JG0xvLQmIadrner1bi45VMJTfnbVHsc7yIajZyoSoK60/dtVBs12Fm6WkUI5/3WAVsNMw=="], "word": ["word@0.3.0", "", {}, "sha512-OELeY0Q61OXpdUfTp+oweA/vtLVg5VDOXh+3he3PNzLGG/y0oylSOC1xRVj0+l4vQ3tj/bB1HVHv1ocXkQceFA=="], + "wrap-ansi": ["wrap-ansi@6.2.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA=="], + "xlsx": ["xlsx@0.18.5", "", { "dependencies": { "adler-32": "~1.3.0", "cfb": "~1.2.1", "codepage": "~1.15.0", "crc-32": "~1.2.1", "ssf": "~0.11.2", "wmf": "~1.0.1", "word": "~0.3.0" }, "bin": { "xlsx": "bin/xlsx.njs" } }, "sha512-dmg3LCjBPHZnQp5/F/+nnTa+miPJxUXB6vtk42YjBBKayDNagxGEeIdWApkYPOf3Z3pm3k62Knjzp7lMeTEtFQ=="], "xmlbuilder": ["xmlbuilder@10.1.1", "", {}, "sha512-OyzrcFLL/nb6fMGHbiRDuPup9ljBycsdCypwuyg5AAHvyWzGfChJpCXMG88AGTIMFhGZ9RccFN1e6lhg3hkwKg=="], "xtend": ["xtend@4.0.2", "", {}, "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ=="], + "y18n": ["y18n@4.0.3", "", {}, "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ=="], + "yaml": ["yaml@2.9.0", "", { "bin": { "yaml": "bin.mjs" } }, "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA=="], + "yargs": ["yargs@15.4.1", "", { "dependencies": { "cliui": "^6.0.0", "decamelize": "^1.2.0", "find-up": "^4.1.0", "get-caller-file": "^2.0.1", "require-directory": "^2.1.1", "require-main-filename": "^2.0.0", "set-blocking": "^2.0.0", "string-width": "^4.2.0", "which-module": "^2.0.0", "y18n": "^4.0.0", "yargs-parser": "^18.1.2" } }, "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A=="], + + "yargs-parser": ["yargs-parser@18.1.3", "", { "dependencies": { "camelcase": "^5.0.0", "decamelize": "^1.2.0" } }, "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ=="], + "yoctocolors": ["yoctocolors@2.2.0", "", {}, "sha512-xYqdZFUK/VYazNl/oCDYN+3WloWQwMfZxBoiNt6qNyk+xfOdi598muWE42rNZFp1kNOiqW936q5RhUdnpqElSg=="], "zwitch": ["zwitch@2.0.4", "", {}, "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A=="], @@ -1100,6 +1150,10 @@ "@tailwindcss/oxide-wasm32-wasi/tslib": ["tslib@2.8.1", "", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], + "cliui/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], + + "cliui/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], + "decode-named-character-reference/character-entities": ["character-entities@2.0.2", "", {}, "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ=="], "jszip/readable-stream": ["readable-stream@2.3.8", "", { "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", "isarray": "~1.0.0", "process-nextick-args": "~2.0.0", "safe-buffer": "~5.1.1", "string_decoder": "~1.1.1", "util-deprecate": "~1.0.1" } }, "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA=="], @@ -1118,14 +1172,32 @@ "react-syntax-highlighter/refractor": ["refractor@3.6.0", "", { "dependencies": { "hastscript": "^6.0.0", "parse-entities": "^2.0.0", "prismjs": "~1.27.0" } }, "sha512-MY9W41IOWxxk31o+YvFCNyNzdkc9M20NoZK5vq6jkv4I/uh2zkWcfudj0Q1fovjUQJrNewS9NMzeTtqPf+n5EA=="], + "read-pkg-up/find-up": ["find-up@1.1.2", "", { "dependencies": { "path-exists": "^2.0.0", "pinkie-promise": "^2.0.0" } }, "sha512-jvElSjyuo4EMQGoTwo1uJU5pQMwTW5lS1x05zzfJuTIyLR3zwO27LYrxNg+dlvKpGOuGy/MzBdXh80g0ve5+HA=="], + + "wrap-ansi/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], + + "wrap-ansi/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], + + "yargs/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], + + "yargs-parser/camelcase": ["camelcase@5.3.1", "", {}, "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg=="], + "@moduletrace/bunext/@types/node/undici-types": ["undici-types@7.18.2", "", {}, "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w=="], + "cliui/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], + "react-syntax-highlighter/refractor/hastscript": ["hastscript@6.0.0", "", { "dependencies": { "@types/hast": "^2.0.0", "comma-separated-tokens": "^1.0.0", "hast-util-parse-selector": "^2.0.0", "property-information": "^5.0.0", "space-separated-tokens": "^1.0.0" } }, "sha512-nDM6bvd7lIqDUiYEiu5Sl/+6ReP0BMk/2f4U/Rooccxkj0P5nm+acM5PrGJ/t5I8qPGiqZSE6hVAwZEdZIvP4w=="], "react-syntax-highlighter/refractor/parse-entities": ["parse-entities@2.0.0", "", { "dependencies": { "character-entities": "^1.0.0", "character-entities-legacy": "^1.0.0", "character-reference-invalid": "^1.0.0", "is-alphanumerical": "^1.0.0", "is-decimal": "^1.0.0", "is-hexadecimal": "^1.0.0" } }, "sha512-kkywGpCcRYhqQIchaWqZ875wzpS/bMKhz5HnN3p7wveJTkTtyAB/AlnS0f8DFSqYW1T82t6yEAkEcB+A1I3MbQ=="], "react-syntax-highlighter/refractor/prismjs": ["prismjs@1.27.0", "", {}, "sha512-t13BGPUlFDR7wRB5kQDG4jjl7XeuH6jbJGt11JHPL96qwsEHNX2+68tFXqc1/k+/jALsbSWJKUOT/hcYAZ5LkA=="], + "read-pkg-up/find-up/path-exists": ["path-exists@2.1.0", "", { "dependencies": { "pinkie-promise": "^2.0.0" } }, "sha512-yTltuKuhtNeFJKa1PiRzfLAU5182q1y4Eb4XCJ3PBqyzEDkAZRzBrKKBct682ls9reBVHf9udYLN5Nd+K1B9BQ=="], + + "wrap-ansi/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], + + "yargs/string-width/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], + "react-syntax-highlighter/refractor/hastscript/@types/hast": ["@types/hast@2.3.10", "", { "dependencies": { "@types/unist": "^2" } }, "sha512-McWspRw8xx8J9HurkVBfYj0xKoE25tOFlHGdx4MJ5xORQrMGZNqJhVQWaIbm6Oyla5kYOXtDiopzKRJzEOkwJw=="], "react-syntax-highlighter/refractor/hastscript/comma-separated-tokens": ["comma-separated-tokens@1.0.8", "", {}, "sha512-GHuDRO12Sypu2cV70d1dkA2EUmXHgntrzbpvOB+Qy+49ypNfGgFQIC2fhhXbnyrJRynDCAARsT7Ou0M6hirpfw=="], @@ -1146,6 +1218,8 @@ "react-syntax-highlighter/refractor/parse-entities/is-hexadecimal": ["is-hexadecimal@1.0.4", "", {}, "sha512-gyPJuv83bHMpocVYoqof5VDiZveEoGoFL8m3BXNb2VW8Xs+rz9kqO8LOQ5DH6EsuvilT1ApazU0pyl+ytbPtlw=="], + "yargs/string-width/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], + "react-syntax-highlighter/refractor/hastscript/@types/hast/@types/unist": ["@types/unist@2.0.11", "", {}, "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA=="], "react-syntax-highlighter/refractor/parse-entities/is-alphanumerical/is-alphabetical": ["is-alphabetical@1.0.4", "", {}, "sha512-DwzsA04LQ10FHTZuL0/grVDk4rFoVH1pjAToYwBrHSxcrBIGQuXrQMtD5U1b0U2XVgKZCTLLP8u2Qxqhy3l2Vg=="], diff --git a/db/schema.ts b/db/schema.ts index 13d199d..02a6f2c 100644 --- a/db/schema.ts +++ b/db/schema.ts @@ -182,6 +182,11 @@ const schema: BUN_SQLITE_DatabaseSchemaType = { fieldName: "allowed_ips", dataType: "TEXT", }, + { + fieldName: "allow_all_ips", + dataType: "INTEGER", + options: [0, 1], + }, { fieldName: "notes", dataType: "TEXT", diff --git a/db/types/db.ts b/db/types/db.ts index 38150ef..fae67fd 100644 --- a/db/types/db.ts +++ b/db/types/db.ts @@ -113,6 +113,7 @@ export type BUN_SQLITE_WGUI_CLIENTS = { public_ip_address?: string; public_key?: string; allowed_ips?: string; + allow_all_ips?: 0 | 1 | ""; notes?: string; } diff --git a/package.json b/package.json index 9ef5c25..e71dbef 100644 --- a/package.json +++ b/package.json @@ -7,6 +7,7 @@ "dev": "sudo bun src/server.ts", "build": "bunx bunext build", "db:schema": "bunx bun-sqlite schema -t", + "db:admin": "bunx bun-sqlite admin", "deploy:prod": "./deploy/deploy-prod.sh", "start": "NODE_ENV=production bun src/server.ts" }, @@ -18,6 +19,7 @@ "@types/mdx": "^2.0.14", "@types/node": "^26.2.0", "@types/nodemailer": "^8.0.1", + "@types/qrcode": "^1.5.6", "@types/react": "^19.2.18", "@types/react-dom": "^19.2.4", "@types/turndown": "^5.0.6", @@ -28,7 +30,7 @@ "typescript": "^7.0.2" }, "dependencies": { - "@moduletrace/bun-sqlite": "^1.1.10", + "@moduletrace/bun-sqlite": "^1.1.12", "@moduletrace/bunext": "^1.1.0", "gray-matter": "^4.0.3", "html-to-react": "^1.7.0", @@ -40,6 +42,7 @@ "nodemailer": "^9.0.5", "onnxruntime-node": "^1.27.0", "openai": "^7.4.0", + "qrcode": "^1.5.4", "react-code-blocks": "^0.1.6", "react-markdown": "^10.1.0", "react-responsive-modal": "^7.2.0", diff --git a/src/components/general/client-row.tsx b/src/components/general/client-row.tsx index d6d8547..fca74fc 100644 --- a/src/components/general/client-row.tsx +++ b/src/components/general/client-row.tsx @@ -1,5 +1,10 @@ +import { useState } from "react"; +import { Pencil, Trash2 } from "lucide-react"; +import Link from "@/src/components/twui/layout/Link"; +import Row from "@/src/components/twui/layout/Row"; import Span from "@/src/components/twui/layout/Span"; import formatUnixTimestamp from "@/src/utils/format-unix-timestamp"; +import adminCrudHandler from "@/src/functions/frontend/admin-crud-handler"; import type { BUN_SQLITE_WGUI_CLIENTS } from "@/db/types/db"; type Props = { @@ -7,12 +12,45 @@ type Props = { }; export default function ClientRow({ client }: Props) { + const [deleting, setDeleting] = useState(false); + + const host_id = Number(client.host_id || 0); + + async function handleDelete() { + if ( + !window.confirm( + `Delete client "${client?.name || `#${client?.id}`}"? The client will be removed from the host and its config file deleted.`, + ) + ) { + return; + } + + setDeleting(true); + + const res = await adminCrudHandler({ + action: "delete", + table: "clients", + id: client?.id, + }); + + if (!res.success) { + setDeleting(false); + window.alert(res.msg || "Could not delete client"); + return; + } + + window.location.reload(); + } + return ( - + {client.name || "—"} - + {client.wg_ip_address || "—"} @@ -30,6 +68,28 @@ export default function ClientRow({ client }: Props) { {formatUnixTimestamp({ timestamp: client.created_at })} + + + + + + + + ); } \ No newline at end of file diff --git a/src/components/twui/editors/AceEditor.tsx b/src/components/twui/editors/AceEditor.tsx index 26132a9..8c01afc 100644 --- a/src/components/twui/editors/AceEditor.tsx +++ b/src/components/twui/editors/AceEditor.tsx @@ -1,8 +1,8 @@ import React, { type RefObject } from "react"; import { twMerge } from "tailwind-merge"; import AceEditorModes from "./ace-editor-modes"; -// @ts-ignore -import { AceEditorOptions } from "@moduletrace/datasquirel/dist/package-shared/types"; +import type { AceEditorOptions } from "../types"; +import type { Ace as AceAjax } from "ace-builds"; export type AceEditorComponentType = { editorRef?: RefObject; @@ -60,6 +60,9 @@ export default function AceEditor({ React.useEffect(() => { if (!ready) return; + // @ts-ignore + const ace = window.ace as AceAjax; + if (!ace?.edit || !editorElementRef.current) { setTimeout(() => { setRefresh((prev) => prev + 1); @@ -95,13 +98,13 @@ export default function AceEditor({ editor.commands.addCommand({ name: "myCommand", bindKey: { win: "Ctrl-Enter", mac: "Command-Enter" }, - exec: function (editor) { + exec: function (editor: any) { if (ctrlEnterFn) ctrlEnterFn(editor); }, readOnly: true, }); - editor.getSession().on("change", function (e) { + editor.getSession().on("change", function () { if (onChange) { clearTimeout(timeout); diff --git a/src/functions/backend/setup/generate-wireguard-client-qr.ts b/src/functions/backend/setup/generate-wireguard-client-qr.ts new file mode 100644 index 0000000..b1df426 --- /dev/null +++ b/src/functions/backend/setup/generate-wireguard-client-qr.ts @@ -0,0 +1,35 @@ +import QRCode from "qrcode"; + +type Params = { + config?: string | null; + width?: number; +}; + +export default async function generateWireguardClientQR({ + config, + width = 320, +}: Params) { + if (!config) { + return { + qr_data_uri: null, + }; + } + + try { + const svg = await QRCode.toString(config, { + type: "svg", + errorCorrectionLevel: "M", + width, + }); + + const qr_data_uri = `data:image/svg+xml;base64,${Buffer.from(svg).toString("base64")}`; + + return { + qr_data_uri, + }; + } catch (error) { + return { + qr_data_uri: null, + }; + } +} diff --git a/src/functions/backend/setup/grab-client-dir-names.ts b/src/functions/backend/setup/grab-client-dir-names.ts new file mode 100644 index 0000000..78766ff --- /dev/null +++ b/src/functions/backend/setup/grab-client-dir-names.ts @@ -0,0 +1,77 @@ +import type { BUN_SQLITE_WGUI_CLIENTS } from "@/db/types/db"; +import path from "path"; +import grabDirNames from "@/src/utils/grab-dir-names"; +import { execSync } from "child_process"; + +type Params = { + client: BUN_SQLITE_WGUI_CLIENTS; + host_id?: number; +}; + +const { + WGUI_LIB_HOSTS_CONFIGS_DIR, + WGUI_LIB_HOST_CLIENTS_DIR_NAME, + WIREGUARD_PRIVATE_KEY_FILE_NAME, + WIREGUARD_PUBLIC_KEY_FILE_NAME, + WIREGUARD_CLIENT_CONFIG_FILE_NAME, +} = grabDirNames(); + +export default function grabClientDirnames({ + client, + host_id: passed_host_id, +}: Params) { + const host_id = passed_host_id || 0; + + const CLIENT_WG_IP = client?.wg_ip_address; + + const HOST_CONFIG_DIR = path.join( + WGUI_LIB_HOSTS_CONFIGS_DIR, + String(host_id), + ); + + const HOST_CLIENTS_DIR = path.join( + HOST_CONFIG_DIR, + WGUI_LIB_HOST_CLIENTS_DIR_NAME, + ); + + const CLIENT_DIR = path.join(HOST_CLIENTS_DIR, `${client.id}`); + + const CLIENT_PRIVATE_KEY_FILE = path.join( + CLIENT_DIR, + WIREGUARD_PRIVATE_KEY_FILE_NAME, + ); + const CLIENT_PUBLIC_KEY_FILE = path.join( + CLIENT_DIR, + WIREGUARD_PUBLIC_KEY_FILE_NAME, + ); + const CLIENT_CONFIG_FILE = path.join( + CLIENT_DIR, + WIREGUARD_CLIENT_CONFIG_FILE_NAME, + ); + + const CLIENT_PRIVATE_KEY = execSync(`cat ${CLIENT_PRIVATE_KEY_FILE}`, { + encoding: "utf-8", + }).trim(); + + const CLIENT_PUBLIC_KEY = execSync(`cat ${CLIENT_PUBLIC_KEY_FILE}`, { + encoding: "utf-8", + }).trim(); + + const HOST_PUBLIC_KEY = execSync( + `cat ${path.join(HOST_CONFIG_DIR, WIREGUARD_PUBLIC_KEY_FILE_NAME)}`, + { encoding: "utf-8" }, + ).trim(); + + return { + CLIENT_WG_IP, + HOST_CLIENTS_DIR, + HOST_CONFIG_DIR, + CLIENT_DIR, + CLIENT_PRIVATE_KEY_FILE, + CLIENT_PUBLIC_KEY_FILE, + CLIENT_CONFIG_FILE, + CLIENT_PRIVATE_KEY, + CLIENT_PUBLIC_KEY, + HOST_PUBLIC_KEY, + }; +} diff --git a/src/functions/backend/setup/manage-wireguard-host.ts b/src/functions/backend/setup/manage-wireguard-host.ts index a52d526..4a92dae 100644 --- a/src/functions/backend/setup/manage-wireguard-host.ts +++ b/src/functions/backend/setup/manage-wireguard-host.ts @@ -4,6 +4,7 @@ import deriveWireguardInterfaceName from "@/src/utils/derive-wireguard-interface import type { APIResponseObject } from "@moduletrace/bunext/types"; import { execSync } from "node:child_process"; import path from "node:path"; +import grabHostDirnames from "./grab-host-dir-names"; const { WGUI_LIB_HOSTS_CONFIGS_DIR, WGUI_WG_QUICK_MANAGE_SCRIPT } = grabDirNames(); @@ -19,11 +20,9 @@ export default function manageWireguardHost({ action, host_id = AppData["WireguardHostID"], }: Params): APIResponseObject { - const INTERFACE_NAME = deriveWireguardInterfaceName({ host_id }); - const HOST_CONFIG_PATH = path.join( - WGUI_LIB_HOSTS_CONFIGS_DIR, - `${INTERFACE_NAME}.conf`, - ); + const { HOST_CONFIG_FILE, INTERFACE_NAME } = grabHostDirnames({ + host: { id: host_id }, + }); const IS_ROOT = typeof process.getuid === "function" && process.getuid() === 0; @@ -35,7 +34,7 @@ export default function manageWireguardHost({ }; } - const MANAGE_COMMAND = `${WGUI_WG_QUICK_MANAGE_SCRIPT} ${action} ${INTERFACE_NAME} ${HOST_CONFIG_PATH}`; + const MANAGE_COMMAND = `${WGUI_WG_QUICK_MANAGE_SCRIPT} ${action} ${INTERFACE_NAME} ${HOST_CONFIG_FILE}`; try { const output = execSync(MANAGE_COMMAND, { encoding: "utf-8" }).trim(); diff --git a/src/functions/backend/setup/read-wireguard-client-config.ts b/src/functions/backend/setup/read-wireguard-client-config.ts new file mode 100644 index 0000000..b5aaa3a --- /dev/null +++ b/src/functions/backend/setup/read-wireguard-client-config.ts @@ -0,0 +1,53 @@ +import path from "node:path"; +import grabDirNames from "@/src/utils/grab-dir-names"; +import grabHostDirnames from "./grab-host-dir-names"; + +const { WIREGUARD_CLIENT_CONFIG_FILE_NAME } = grabDirNames(); + +type Params = { + client_id?: string | number; + host_id?: string | number; +}; + +export default async function readWireguardClientConfig({ + client_id, + host_id, +}: Params) { + if (!client_id) { + return { + config: null, + config_path: null, + }; + } + + const { HOST_CLIENTS_DIR } = grabHostDirnames({ + host: host_id ? { id: Number(host_id) } : undefined, + }); + + const config_path = path.join( + HOST_CLIENTS_DIR, + String(client_id), + WIREGUARD_CLIENT_CONFIG_FILE_NAME, + ); + + try { + const config_file = Bun.file(config_path); + + if (!(await config_file.exists())) { + return { + config: null, + config_path, + }; + } + + return { + config: await config_file.text(), + config_path, + }; + } catch (error) { + return { + config: null, + config_path, + }; + } +} \ No newline at end of file diff --git a/src/functions/backend/setup/setup-wireguard-client.ts b/src/functions/backend/setup/setup-wireguard-client.ts index 82a5400..a78b9f2 100644 --- a/src/functions/backend/setup/setup-wireguard-client.ts +++ b/src/functions/backend/setup/setup-wireguard-client.ts @@ -12,6 +12,7 @@ import setupWireguardHost from "./setup-wireguard-host"; import type { APIResponseObject } from "@moduletrace/bunext/types"; import BunSQLite from "@moduletrace/bun-sqlite"; import type { TableType, User } from "@/src/types"; +import grabClientDirnames from "./grab-client-dir-names"; const { WGUI_LIB_HOSTS_CONFIGS_DIR, @@ -25,6 +26,12 @@ type Params = { client: BUN_SQLITE_WGUI_CLIENTS; host?: BUN_SQLITE_WGUI_HOSTS; user: User; + /** + * Skip the host setup/restart at the end. Use when + * regenerating multiple clients and restarting the + * host once afterwards instead. + */ + skip_host_setup?: boolean; }; /** @@ -38,6 +45,7 @@ export default async function setupWireguardClient({ client, host, user, + skip_host_setup, }: Params): Promise { const host_id = host?.id || AppData["WireguardHostID"]; @@ -50,7 +58,18 @@ export default async function setupWireguardClient({ const variables = variables_res.payload; - const CLIENT_WG_IP = client?.wg_ip_address; + const { + CLIENT_WG_IP, + CLIENT_CONFIG_FILE, + CLIENT_DIR, + CLIENT_PRIVATE_KEY_FILE, + CLIENT_PUBLIC_KEY_FILE, + HOST_CLIENTS_DIR, + HOST_CONFIG_DIR, + CLIENT_PRIVATE_KEY, + CLIENT_PUBLIC_KEY, + HOST_PUBLIC_KEY, + } = grabClientDirnames({ client, host_id }); if (!CLIENT_WG_IP) { return { @@ -59,18 +78,6 @@ export default async function setupWireguardClient({ }; } - const HOST_CONFIG_DIR = path.join( - WGUI_LIB_HOSTS_CONFIGS_DIR, - String(host_id), - ); - - const HOST_CLIENTS_DIR = path.join( - HOST_CONFIG_DIR, - WGUI_LIB_HOST_CLIENTS_DIR_NAME, - ); - - const CLIENT_DIR = path.join(HOST_CLIENTS_DIR, `${client.id}`); - const HOST_WG_IP = host?.id ? host?.wg_ip_address : variables?.find((v) => v.key == "main_host_wg_ip_address")?.value; @@ -82,19 +89,6 @@ export default async function setupWireguardClient({ }; } - const CLIENT_PRIVATE_KEY_FILE = path.join( - CLIENT_DIR, - WIREGUARD_PRIVATE_KEY_FILE_NAME, - ); - const CLIENT_PUBLIC_KEY_FILE = path.join( - CLIENT_DIR, - WIREGUARD_PUBLIC_KEY_FILE_NAME, - ); - const CLIENT_CONFIG_FILE = path.join( - CLIENT_DIR, - WIREGUARD_CLIENT_CONFIG_FILE_NAME, - ); - let pre_sh = ``; pre_sh += `mkdir -p ${CLIENT_DIR}\n`; @@ -112,20 +106,10 @@ export default async function setupWireguardClient({ } try { - const CLIENT_PRIVATE_KEY = execSync(`cat ${CLIENT_PRIVATE_KEY_FILE}`, { - encoding: "utf-8", - }).trim(); - - const CLIENT_PUBLIC_KEY = execSync(`cat ${CLIENT_PUBLIC_KEY_FILE}`, { - encoding: "utf-8", - }).trim(); - - const HOST_PUBLIC_KEY = execSync( - `cat ${path.join(HOST_CONFIG_DIR, WIREGUARD_PUBLIC_KEY_FILE_NAME)}`, - { encoding: "utf-8" }, - ).trim(); - const PUBLIC_IP_ADDRESS = + host?.public_ip_address || + variables?.find((v) => v.key == "main_host_public_ip_address") + ?.value || (await grabHostPublicIPAddress()) || client?.public_ip_address || HOST_WG_IP; @@ -138,26 +122,38 @@ export default async function setupWireguardClient({ sh += `[Interface]\n`; sh += `Address = ${CLIENT_WG_IP}/32\n`; sh += `PrivateKey = ${CLIENT_PRIVATE_KEY}\n`; - sh += `DNS = 1.1.1.1\n`; + // sh += `DNS = 1.1.1.1\n`; sh += `\n`; sh += `[Peer]\n`; sh += `PublicKey = ${HOST_PUBLIC_KEY}\n`; sh += `Endpoint = ${PUBLIC_IP_ADDRESS}:51820\n`; - if (client?.allowed_ips) { + if (client?.allow_all_ips == 1) { + sh += `AllowedIPs = 0.0.0.0/0, ::/0\n`; + } else if (client?.allowed_ips) { sh += `AllowedIPs = ${client.allowed_ips}\n`; } + sh += `PersistentKeepalive = 25\n`; + sh += `EOF\n`; sh += `\n`; - const exec = execSync(sh, { encoding: "utf-8" }); + const exec = execSync(sh, { + encoding: "utf-8", + }); const CLIENT_CONFIG = execSync(`cat ${CLIENT_CONFIG_FILE}`, { encoding: "utf-8", }); - const host_setup_res = await setupWireguardHost({ host, user }); + const host_setup_res = skip_host_setup + ? { success: true } + : await setupWireguardHost({ + host, + user, + is_update_after_client_setup: true, + }); if (!host_setup_res.success) { return { @@ -172,6 +168,11 @@ export default async function setupWireguardClient({ stringRes: CLIENT_CONFIG, }; } catch (error: any) { + console.log( + `Error setting up wireguard client ${client.id} for host ${host_id} =>`, + error.message, + ); + return { success: false, msg: error.message, diff --git a/src/functions/backend/setup/setup-wireguard-host.ts b/src/functions/backend/setup/setup-wireguard-host.ts index fc8d1e4..7696d2e 100644 --- a/src/functions/backend/setup/setup-wireguard-host.ts +++ b/src/functions/backend/setup/setup-wireguard-host.ts @@ -12,6 +12,8 @@ import type { TableType, User } from "@/src/types"; import checkPrivateIPAvailability from "./check-private-ip-availability"; import manageWireguardHost from "./manage-wireguard-host"; import grabHostDirnames from "./grab-host-dir-names"; +import path from "node:path"; +import grabClientDirnames from "./grab-client-dir-names"; const { WIREGUARD_PRIVATE_KEY_FILE_NAME, WIREGUARD_PUBLIC_KEY_FILE_NAME } = grabDirNames(); @@ -20,12 +22,14 @@ type Params = { host?: BUN_SQLITE_WGUI_HOSTS; wg_subnet_ip?: string; user: User; + is_update_after_client_setup?: boolean; }; export default async function setupWireguardHost({ host, wg_subnet_ip, user, + is_update_after_client_setup, }: Params): Promise { const { HOST_CLIENTS_DIR, @@ -57,7 +61,7 @@ export default async function setupWireguardHost({ query: { query: { host_id: { - value: HOST_ID, + value: String(HOST_ID), }, }, }, @@ -78,35 +82,39 @@ export default async function setupWireguardHost({ }; } - const is_ip_available = await checkPrivateIPAvailability({ - ip_address: HOST_WG_IP, - }); + const is_ip_available = is_update_after_client_setup + ? { success: true } + : await checkPrivateIPAvailability({ + ip_address: HOST_WG_IP, + }); - if (!is_ip_available.success) { + if (!is_ip_available.success && !is_update_after_client_setup) { return { success: false, msg: `IP not available`, }; } - let pre_sh = ``; + if (!is_update_after_client_setup) { + let pre_sh = ``; - pre_sh += `set -e\n`; - pre_sh += `mkdir -p ${HOST_CONFIG_DIR}\n`; - pre_sh += `mkdir -p ${HOST_CLIENTS_DIR}\n`; - pre_sh += `mkdir -p ${HOST_IPTABLES_DIR}\n`; - pre_sh += `cd ${HOST_CONFIG_DIR}\n`; - pre_sh += `if [ ! -f ${WIREGUARD_PRIVATE_KEY_FILE_NAME} ]; then\n`; - pre_sh += ` wg genkey | tee ${WIREGUARD_PRIVATE_KEY_FILE_NAME} | wg pubkey > ${WIREGUARD_PUBLIC_KEY_FILE_NAME}\n`; - pre_sh += `fi\n`; + pre_sh += `set -e\n`; + pre_sh += `mkdir -p ${HOST_CONFIG_DIR}\n`; + pre_sh += `mkdir -p ${HOST_CLIENTS_DIR}\n`; + pre_sh += `mkdir -p ${HOST_IPTABLES_DIR}\n`; + pre_sh += `cd ${HOST_CONFIG_DIR}\n`; + pre_sh += `if [ ! -f ${WIREGUARD_PRIVATE_KEY_FILE_NAME} ]; then\n`; + pre_sh += ` wg genkey | tee ${WIREGUARD_PRIVATE_KEY_FILE_NAME} | wg pubkey > ${WIREGUARD_PUBLIC_KEY_FILE_NAME}\n`; + pre_sh += `fi\n`; - try { - const exec_pre_setup = execSync(pre_sh, { encoding: "utf-8" }); - } catch (error: any) { - return { - success: false, - msg: error.message, - }; + try { + const exec_pre_setup = execSync(pre_sh, { encoding: "utf-8" }); + } catch (error: any) { + return { + success: false, + msg: error.message, + }; + } } try { @@ -118,7 +126,7 @@ export default async function setupWireguardHost({ encoding: "utf-8", }).trim(); - if (HOST_ID == 0) { + if (HOST_ID == 0 && !is_update_after_client_setup) { const update_variables = await BunSQLite.insert< BUN_SQLITE_WGUI_VARIABLES, TableType @@ -140,7 +148,7 @@ export default async function setupWireguardHost({ if (!update_variables.success) { throw new Error(`Couldn't update host variables`); } - } else if (HOST_ID) { + } else if (HOST_ID && !is_update_after_client_setup) { const update_host = await BunSQLite.insert< BUN_SQLITE_WGUI_HOSTS, TableType @@ -208,10 +216,15 @@ export default async function setupWireguardHost({ if (clients[0]) { for (let i = 0; i < clients.length; i++) { const client = clients[i]; - if (!client?.id || !client.public_key) continue; + if (!client?.id) continue; + + const { CLIENT_PUBLIC_KEY } = grabClientDirnames({ + client, + host_id: HOST_ID, + }); sh += `[Peer]\n`; - sh += `PublicKey = ${client.public_key}\n`; + sh += `PublicKey = ${CLIENT_PUBLIC_KEY}\n`; sh += `AllowedIPs = ${client.allowed_ips}\n`; sh += `\n`; } @@ -219,6 +232,8 @@ export default async function setupWireguardHost({ sh += `EOF\n`; + sh += `chmod 600 ${INTERFACE_NAME}.conf\n`; + sh += `\n`; const exec = execSync(sh, { encoding: "utf-8" }); @@ -240,6 +255,11 @@ export default async function setupWireguardHost({ msg: [exec.trim(), manage_res.msg].join("\n\n"), }; } catch (error: any) { + console.log( + `Error setting up wireguard host ${HOST_ID} =>`, + error.message, + ); + return { success: false, msg: error.message, diff --git a/src/functions/backend/setup/sync-wireguard-hosts.ts b/src/functions/backend/setup/sync-wireguard-hosts.ts index 317e7db..4ddb89d 100644 --- a/src/functions/backend/setup/sync-wireguard-hosts.ts +++ b/src/functions/backend/setup/sync-wireguard-hosts.ts @@ -8,6 +8,7 @@ import type { } from "@/db/types/db"; import type { TableType } from "@/src/types"; import { AppData } from "@/src/data/app-data"; +import bunext from "@moduletrace/bunext"; const HOST_CONFIG_FILE_NAME_PATTERN = /^wgui(\d+)\.conf$/; @@ -50,7 +51,7 @@ export default async function syncWireguardHosts() { const res = manageWireguardHost({ action: "up", host_id }); - console.log( + bunext.bunextLog.info( `[wgui] wireguard host ${host_config_file_name}: ${ res.success ? `up` : `failed — ${res.msg}` }`, diff --git a/src/functions/backend/setup/update-wireguard-host-public-ip.ts b/src/functions/backend/setup/update-wireguard-host-public-ip.ts new file mode 100644 index 0000000..b948c8f --- /dev/null +++ b/src/functions/backend/setup/update-wireguard-host-public-ip.ts @@ -0,0 +1,174 @@ +import type { + BUN_SQLITE_WGUI_CLIENTS, + BUN_SQLITE_WGUI_HOSTS, + BUN_SQLITE_WGUI_VARIABLES, +} from "@/db/types/db"; +import { AppData } from "@/src/data/app-data"; +import BunSQLite from "/home/archben/Projects/Git/moduletrace/bun-sqlite"; +// import BunSQLite from "@moduletrace/bun-sqlite"; +import type { APIResponseObject } from "@moduletrace/bunext/types"; +import type { TableType, User } from "@/src/types"; +import setupWireguardClient from "./setup-wireguard-client"; +import setupWireguardHost from "./setup-wireguard-host"; + +const IP_V4_REGEX = /^\d{1,3}(\.\d{1,3}){3}$/; + +type Params = { + host_id?: string | number | null; + public_ip_address?: string | null; + user: User; +}; + +/** + * Update the public IP address of a host and regenerate + * every client config of that host with the new Endpoint. + * @param param0 + * @returns + */ +export default async function updateWireguardHostPublicIP({ + host_id, + public_ip_address, + user, +}: Params): Promise { + const final_host_id = Number(host_id || AppData["WireguardHostID"]); + const ip = (public_ip_address || "").trim(); + + if (!ip || !IP_V4_REGEX.test(ip)) { + return { + success: false, + msg: `Invalid public IP address provided`, + }; + } + + let host: BUN_SQLITE_WGUI_HOSTS | undefined; + + try { + if (final_host_id == AppData["WireguardHostID"]) { + const update_variables = await BunSQLite.insert< + BUN_SQLITE_WGUI_VARIABLES, + TableType + >({ + table: "variables", + data: [ + { + key: "main_host_public_ip_address", + value: ip, + }, + ], + update_on_duplicate: true, + }); + + // console.log("update_variables", update_variables); + + // if (!update_variables.success) { + // throw new Error(`Couldn't update host public IP variable`); + // } + } else { + const host_res = await BunSQLite.select< + BUN_SQLITE_WGUI_HOSTS, + TableType + >({ + table: "hosts", + targetId: final_host_id, + }); + + host = host_res.singleRes || undefined; + + if (!host) { + return { + success: false, + msg: `Host record not found`, + }; + } + + const update_host = await BunSQLite.insert< + BUN_SQLITE_WGUI_HOSTS, + TableType + >({ + table: "hosts", + data: [ + { + id: host.id, + user_id: user.id, + public_ip_address: ip, + }, + ], + update_on_duplicate: true, + }); + + if (!update_host.success) { + throw new Error(`Couldn't update host record`); + } + + host.public_ip_address = ip; + } + } catch (error: any) { + return { + success: false, + msg: error.message, + }; + } + + const host_clients_res = await BunSQLite.select< + BUN_SQLITE_WGUI_CLIENTS, + TableType + >({ + table: "clients", + query: { + query: { + host_id: { + value: String(final_host_id), + }, + }, + }, + }); + + const clients = host_clients_res.payload || []; + + let updated_count = 0; + const client_errors: string[] = []; + + for (let i = 0; i < clients.length; i++) { + const client = clients[i]; + + if (!client?.id) continue; + + const client_setup_res = await setupWireguardClient({ + client, + host, + user, + skip_host_setup: true, + }); + + console.log("client_setup_res", client_setup_res); + + if (client_setup_res.success) { + updated_count++; + } else { + client_errors.push( + `Client ${client.name || `#${client.id}`}: ${client_setup_res.msg}`, + ); + } + } + + const host_setup_res = await setupWireguardHost({ + host, + user, + is_update_after_client_setup: true, + }); + + if (!host_setup_res.success) { + return { + success: false, + msg: `${updated_count} client config(s) updated, but the host could not be restarted: ${host_setup_res.msg}`, + numberRes: updated_count, + }; + } + + return { + success: true, + msg: `Host IP updated to ${ip}. ${updated_count} client config(s) regenerated.`, + numberRes: updated_count, + stringRes: client_errors[0] ? client_errors.join("; ") : null, + }; +} diff --git a/src/pages/__root.tsx b/src/pages/__root.tsx index f0d5504..01028cb 100755 --- a/src/pages/__root.tsx +++ b/src/pages/__root.tsx @@ -84,6 +84,8 @@ export function Head({ serverRes, ctx }: BunextPageHeadFCProps) { href="https://fonts.gstatic.com" crossOrigin="" /> +