diff --git a/.gitignore b/.gitignore index 473256d..14621f0 100644 --- a/.gitignore +++ b/.gitignore @@ -33,5 +33,6 @@ docs/_book test/ node_modules/ +dist/ .direnv \ No newline at end of file diff --git a/TODO b/TODO index 211b9b2..ce82031 100644 --- a/TODO +++ b/TODO @@ -1,3 +1,9 @@ +- [x] metrics +- [] Web UI + - [] i18n +- [] swagger - [] Nix Module - [] RBAC -- [] Audit \ No newline at end of file +- [] Audit + + diff --git a/web/package-lock.json b/web/package-lock.json index 5ad5be0..22992ee 100644 --- a/web/package-lock.json +++ b/web/package-lock.json @@ -8,6 +8,7 @@ "name": "recored-ui", "version": "0.0.0", "dependencies": { + "axios": "^1.6.8", "pinia": "^2.1.7", "vue": "^3.4.21", "vue-router": "^4.3.0" @@ -15,11 +16,14 @@ "devDependencies": { "@tsconfig/node20": "^20.1.2", "@types/node": "^20.11.28", + "@vicons/fa": "^0.12.0", "@vitejs/plugin-vue": "^5.0.4", "@vitejs/plugin-vue-jsx": "^3.1.0", "@vue/tsconfig": "^0.5.1", + "naive-ui": "^2.38.1", "npm-run-all2": "^6.1.2", "typescript": "~5.4.0", + "vfonts": "^0.0.3", "vite": "^5.1.6", "vue-tsc": "^2.0.6" } @@ -421,6 +425,18 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/runtime": { + "version": "7.24.1", + "resolved": "https://registry.npmmirror.com/@babel/runtime/-/runtime-7.24.1.tgz", + "integrity": "sha512-+BIznRzyqBf+2wCTxcKE3wDjfGeCoVE61KSHGpkzqrLi8qxqFwBeUFyId2cxkTmm55fzDGnm0+yCxaxygrLUnQ==", + "dev": true, + "dependencies": { + "regenerator-runtime": "^0.14.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@babel/template": { "version": "7.24.0", "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.24.0.tgz", @@ -470,6 +486,30 @@ "node": ">=6.9.0" } }, + "node_modules/@css-render/plugin-bem": { + "version": "0.15.12", + "resolved": "https://registry.npmmirror.com/@css-render/plugin-bem/-/plugin-bem-0.15.12.tgz", + "integrity": "sha512-Lq2jSOZn+wYQtsyaFj6QRz2EzAnd3iW5fZeHO1WSXQdVYwvwGX0ZiH3X2JQgtgYLT1yeGtrwrqJdNdMEUD2xTw==", + "dev": true, + "peerDependencies": { + "css-render": "~0.15.12" + } + }, + "node_modules/@css-render/vue3-ssr": { + "version": "0.15.12", + "resolved": "https://registry.npmmirror.com/@css-render/vue3-ssr/-/vue3-ssr-0.15.12.tgz", + "integrity": "sha512-AQLGhhaE0F+rwybRCkKUdzBdTEM/5PZBYy+fSYe1T9z9+yxMuV/k7ZRqa4M69X+EI1W8pa4kc9Iq2VjQkZx4rg==", + "dev": true, + "peerDependencies": { + "vue": "^3.0.11" + } + }, + "node_modules/@emotion/hash": { + "version": "0.8.0", + "resolved": "https://registry.npmmirror.com/@emotion/hash/-/hash-0.8.0.tgz", + "integrity": "sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow==", + "dev": true + }, "node_modules/@esbuild/aix-ppc64": { "version": "0.20.2", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.20.2.tgz", @@ -885,6 +925,12 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@juggle/resize-observer": { + "version": "3.4.0", + "resolved": "https://registry.npmmirror.com/@juggle/resize-observer/-/resize-observer-3.4.0.tgz", + "integrity": "sha512-dfLbk+PwWvFzSxwk3n5ySL0hfBog779o8h68wK/7/APo/7cgyWp5jcXockbxdk5kFRkbeXWm4Fbi9FrdN381sA==", + "dev": true + }, "node_modules/@rollup/rollup-android-arm-eabi": { "version": "4.14.0", "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.14.0.tgz", @@ -1092,6 +1138,27 @@ "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", "dev": true }, + "node_modules/@types/katex": { + "version": "0.16.7", + "resolved": "https://registry.npmmirror.com/@types/katex/-/katex-0.16.7.tgz", + "integrity": "sha512-HMwFiRujE5PjrgwHQ25+bsLJgowjGjm5Z8FVSf0N6PwgJrwxH0QxzHYDcKsTfV3wva0vzrpqMTJS2jXPr5BMEQ==", + "dev": true + }, + "node_modules/@types/lodash": { + "version": "4.17.0", + "resolved": "https://registry.npmmirror.com/@types/lodash/-/lodash-4.17.0.tgz", + "integrity": "sha512-t7dhREVv6dbNj0q17X12j7yDG4bD/DHYX7o5/DbDxobP0HnGPgpRz2Ej77aL7TZT3DSw13fqUTj8J4mMnqa7WA==", + "dev": true + }, + "node_modules/@types/lodash-es": { + "version": "4.17.12", + "resolved": "https://registry.npmmirror.com/@types/lodash-es/-/lodash-es-4.17.12.tgz", + "integrity": "sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ==", + "dev": true, + "dependencies": { + "@types/lodash": "*" + } + }, "node_modules/@types/node": { "version": "20.12.3", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.3.tgz", @@ -1101,6 +1168,12 @@ "undici-types": "~5.26.4" } }, + "node_modules/@vicons/fa": { + "version": "0.12.0", + "resolved": "https://registry.npmmirror.com/@vicons/fa/-/fa-0.12.0.tgz", + "integrity": "sha512-g2PIeJLsTHUjt6bK63LxqC0uYQB7iu+xViJOxvp1s8b9/akpXVPVWjDTTsP980/0KYyMMe4U7F/aUo7wY+MsXA==", + "dev": true + }, "node_modules/@vitejs/plugin-vue": { "version": "5.0.4", "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-5.0.4.tgz", @@ -1369,6 +1442,27 @@ "node": ">=4" } }, + "node_modules/async-validator": { + "version": "4.2.5", + "resolved": "https://registry.npmmirror.com/async-validator/-/async-validator-4.2.5.tgz", + "integrity": "sha512-7HhHjtERjqlNbZtqNqy2rckN/SpOOlmDliet+lP7k+eKZEjPk3DgyeU9lIXLdeLz0uBbbVp+9Qdow9wJWgwwfg==", + "dev": true + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmmirror.com/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + }, + "node_modules/axios": { + "version": "1.6.8", + "resolved": "https://registry.npmmirror.com/axios/-/axios-1.6.8.tgz", + "integrity": "sha512-v/ZHtJDU39mDpyBoFVkETcd/uNdxrWRrg3bKpOKzXFA6Bvqopts6ALSMU3y6ijYxbw2B+wPrIv46egTzJXCLGQ==", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -1477,6 +1571,17 @@ "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", "dev": true }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmmirror.com/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/computeds": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/computeds/-/computeds-0.0.1.tgz", @@ -1503,11 +1608,48 @@ "node": ">= 8" } }, + "node_modules/css-render": { + "version": "0.15.12", + "resolved": "https://registry.npmmirror.com/css-render/-/css-render-0.15.12.tgz", + "integrity": "sha512-eWzS66patiGkTTik+ipO9qNGZ+uNuGyTmnz6/+EJIiFg8+3yZRpnMwgFo8YdXhQRsiePzehnusrxVvugNjXzbw==", + "dev": true, + "dependencies": { + "@emotion/hash": "~0.8.0", + "csstype": "~3.0.5" + } + }, + "node_modules/css-render/node_modules/csstype": { + "version": "3.0.11", + "resolved": "https://registry.npmmirror.com/csstype/-/csstype-3.0.11.tgz", + "integrity": "sha512-sa6P2wJ+CAbgyy4KFssIb/JNMLxFvKF1pCYCSXS8ZMuqZnMsrxqI2E5sPyoTpxoPU/gVZMzr2zjOfg8GIZOMsw==", + "dev": true + }, "node_modules/csstype": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==" }, + "node_modules/date-fns": { + "version": "2.30.0", + "resolved": "https://registry.npmmirror.com/date-fns/-/date-fns-2.30.0.tgz", + "integrity": "sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==", + "dev": true, + "dependencies": { + "@babel/runtime": "^7.21.0" + }, + "engines": { + "node": ">=0.11" + } + }, + "node_modules/date-fns-tz": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/date-fns-tz/-/date-fns-tz-2.0.1.tgz", + "integrity": "sha512-fJCG3Pwx8HUoLhkepdsP7Z5RsucUi+ZBOxyM5d0ZZ6c4SdYustq0VMmOu6Wf7bli+yS/Jwp91TOCqn9jMcVrUA==", + "dev": true, + "peerDependencies": { + "date-fns": "2.x" + } + }, "node_modules/de-indent": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/de-indent/-/de-indent-1.0.2.tgz", @@ -1531,6 +1673,14 @@ } } }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/electron-to-chromium": { "version": "1.4.724", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.724.tgz", @@ -1609,6 +1759,38 @@ "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==" }, + "node_modules/evtd": { + "version": "0.2.4", + "resolved": "https://registry.npmmirror.com/evtd/-/evtd-0.2.4.tgz", + "integrity": "sha512-qaeGN5bx63s/AXgQo8gj6fBkxge+OoLddLniox5qtLAEY5HSnuSlISXVPxnSae1dWblvTh4/HoMIB+mbMsvZzw==", + "dev": true + }, + "node_modules/follow-redirects": { + "version": "1.15.6", + "resolved": "https://registry.npmmirror.com/follow-redirects/-/follow-redirects-1.15.6.tgz", + "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", @@ -1659,6 +1841,15 @@ "he": "bin/he" } }, + "node_modules/highlight.js": { + "version": "11.9.0", + "resolved": "https://registry.npmmirror.com/highlight.js/-/highlight.js-11.9.0.tgz", + "integrity": "sha512-fJ7cW7fQGCYAkgv4CPfwFHrfd/cLS4Hau96JuJ+ZTOWhjnhoeN1ub1tFmALm/+lW5z4WCAuAV9bm05AP0mS6Gw==", + "dev": true, + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/html-tags": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/html-tags/-/html-tags-3.3.1.tgz", @@ -1716,6 +1907,18 @@ "node": ">=6" } }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmmirror.com/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "dev": true + }, + "node_modules/lodash-es": { + "version": "4.17.21", + "resolved": "https://registry.npmmirror.com/lodash-es/-/lodash-es-4.17.21.tgz", + "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==", + "dev": true + }, "node_modules/lru-cache": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", @@ -1745,6 +1948,25 @@ "node": ">= 0.10.0" } }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmmirror.com/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmmirror.com/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/minimatch": { "version": "9.0.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz", @@ -1772,6 +1994,36 @@ "integrity": "sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==", "dev": true }, + "node_modules/naive-ui": { + "version": "2.38.1", + "resolved": "https://registry.npmmirror.com/naive-ui/-/naive-ui-2.38.1.tgz", + "integrity": "sha512-AnU1FQ7K/CbhguAX++V4kCFjk7h7RvWt4nvZPRjORMpq+fUIlzD+EcQ5Cv1VqDloNF8+eMv4Akc2Ogacc9S+5A==", + "dev": true, + "dependencies": { + "@css-render/plugin-bem": "^0.15.12", + "@css-render/vue3-ssr": "^0.15.12", + "@types/katex": "^0.16.2", + "@types/lodash": "^4.14.198", + "@types/lodash-es": "^4.17.9", + "async-validator": "^4.2.5", + "css-render": "^0.15.12", + "csstype": "^3.1.3", + "date-fns": "^2.30.0", + "date-fns-tz": "^2.0.0", + "evtd": "^0.2.4", + "highlight.js": "^11.8.0", + "lodash": "^4.17.21", + "lodash-es": "^4.17.21", + "seemly": "^0.3.8", + "treemate": "^0.3.11", + "vdirs": "^0.1.8", + "vooks": "^0.2.12", + "vueuc": "^0.4.58" + }, + "peerDependencies": { + "vue": "^3.0.0" + } + }, "node_modules/nanoid": { "version": "3.3.7", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", @@ -1950,6 +2202,11 @@ "node": "^10 || ^12 || >=14" } }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" + }, "node_modules/read-package-json-fast": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/read-package-json-fast/-/read-package-json-fast-3.0.2.tgz", @@ -1963,6 +2220,12 @@ "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, + "node_modules/regenerator-runtime": { + "version": "0.14.1", + "resolved": "https://registry.npmmirror.com/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==", + "dev": true + }, "node_modules/rollup": { "version": "4.14.0", "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.14.0.tgz", @@ -1997,6 +2260,12 @@ "fsevents": "~2.3.2" } }, + "node_modules/seemly": { + "version": "0.3.8", + "resolved": "https://registry.npmmirror.com/seemly/-/seemly-0.3.8.tgz", + "integrity": "sha512-MW8Qs6vbzo0pHmDpFSYPna+lwpZ6Zk1ancbajw/7E8TKtHdV+1DfZZD+kKJEhG/cAoB/i+LiT+5msZOqj0DwRA==", + "dev": true + }, "node_modules/semver": { "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", @@ -2071,6 +2340,12 @@ "node": ">=4" } }, + "node_modules/treemate": { + "version": "0.3.11", + "resolved": "https://registry.npmmirror.com/treemate/-/treemate-0.3.11.tgz", + "integrity": "sha512-M8RGFoKtZ8dF+iwJfAJTOH/SM4KluKOKRJpjCMhI8bG3qB74zrFoArKZ62ll0Fr3mqkMJiQOmWYkdYgDeITYQg==", + "dev": true + }, "node_modules/typescript": { "version": "5.4.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.3.tgz", @@ -2120,6 +2395,24 @@ "browserslist": ">= 4.21.0" } }, + "node_modules/vdirs": { + "version": "0.1.8", + "resolved": "https://registry.npmmirror.com/vdirs/-/vdirs-0.1.8.tgz", + "integrity": "sha512-H9V1zGRLQZg9b+GdMk8MXDN2Lva0zx72MPahDKc30v+DtwKjfyOSXWRIX4t2mhDubM1H09gPhWeth/BJWPHGUw==", + "dev": true, + "dependencies": { + "evtd": "^0.2.2" + }, + "peerDependencies": { + "vue": "^3.0.11" + } + }, + "node_modules/vfonts": { + "version": "0.0.3", + "resolved": "https://registry.npmmirror.com/vfonts/-/vfonts-0.0.3.tgz", + "integrity": "sha512-nguyw8L6Un8eelg1vQ31vIU2ESxqid7EYmy8V+MDeMaHBqaRSkg3dTBToC1PR00D89UzS/SLkfYPnx0Wf23IQQ==", + "dev": true + }, "node_modules/vite": { "version": "5.2.7", "resolved": "https://registry.npmjs.org/vite/-/vite-5.2.7.tgz", @@ -2175,6 +2468,18 @@ } } }, + "node_modules/vooks": { + "version": "0.2.12", + "resolved": "https://registry.npmmirror.com/vooks/-/vooks-0.2.12.tgz", + "integrity": "sha512-iox0I3RZzxtKlcgYaStQYKEzWWGAduMmq+jS7OrNdQo1FgGfPMubGL3uGHOU9n97NIvfFDBGnpSvkWyb/NSn/Q==", + "dev": true, + "dependencies": { + "evtd": "^0.2.2" + }, + "peerDependencies": { + "vue": "^3.0.0" + } + }, "node_modules/vue": { "version": "3.4.21", "resolved": "https://registry.npmjs.org/vue/-/vue-3.4.21.tgz", @@ -2269,6 +2574,24 @@ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", "dev": true }, + "node_modules/vueuc": { + "version": "0.4.58", + "resolved": "https://registry.npmmirror.com/vueuc/-/vueuc-0.4.58.tgz", + "integrity": "sha512-Wnj/N8WbPRSxSt+9ji1jtDHPzda5h2OH/0sFBhvdxDRuyCZbjGg3/cKMaKqEoe+dErTexG2R+i6Q8S/Toq1MYg==", + "dev": true, + "dependencies": { + "@css-render/vue3-ssr": "^0.15.10", + "@juggle/resize-observer": "^3.3.1", + "css-render": "^0.15.10", + "evtd": "^0.2.4", + "seemly": "^0.3.6", + "vdirs": "^0.1.4", + "vooks": "^0.2.4" + }, + "peerDependencies": { + "vue": "^3.0.11" + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", diff --git a/web/package.json b/web/package.json index cbf915d..3e8d99d 100644 --- a/web/package.json +++ b/web/package.json @@ -4,13 +4,14 @@ "private": true, "type": "module", "scripts": { - "dev": "vite", + "dev": "NODE_ENV=dev vite", "build": "run-p type-check \"build-only {@}\" --", "preview": "vite preview", "build-only": "vite build", "type-check": "vue-tsc --build --force" }, "dependencies": { + "axios": "^1.6.8", "pinia": "^2.1.7", "vue": "^3.4.21", "vue-router": "^4.3.0" @@ -18,11 +19,14 @@ "devDependencies": { "@tsconfig/node20": "^20.1.2", "@types/node": "^20.11.28", + "@vicons/fa": "^0.12.0", "@vitejs/plugin-vue": "^5.0.4", "@vitejs/plugin-vue-jsx": "^3.1.0", "@vue/tsconfig": "^0.5.1", + "naive-ui": "^2.38.1", "npm-run-all2": "^6.1.2", "typescript": "~5.4.0", + "vfonts": "^0.0.3", "vite": "^5.1.6", "vue-tsc": "^2.0.6" } diff --git a/web/src/apis/api.ts b/web/src/apis/api.ts new file mode 100644 index 0000000..c6fe2d7 --- /dev/null +++ b/web/src/apis/api.ts @@ -0,0 +1,113 @@ +import axios, { type AxiosInstance, type AxiosRequestConfig, type AxiosResponse, type InternalAxiosRequestConfig } from "axios"; +import { useLoadingBar, useNotification } from 'naive-ui' +import { type Record } from '@/stores/records'; +import { type Domain } from "@/stores/domains"; + +type Result = { + success: boolean + message: string + data: T +} + +export class Request { + private instance: AxiosInstance; + private baseConfig: AxiosRequestConfig = { baseURL: "api/v1" } + private loadingBar = useLoadingBar() + private notification = useNotification() + private messages = new Map( + // TODO: i18n + [ + [400, { + title: "请求错误 (400)", + content: "参数提交错误" + }], + [401, { + title: "未授权 (401)", + content: "请刷新页面重新登录" + }], + [403, { + title: "拒绝访问 (403)", + content: "你没有权限!" + }], + [404, { + title: "查无此项 (404)", + content: "没有该项内容" + }], + [500, { + title: "服务器错误 (500)", + content: "请检查系统日志" + }] + ] + ) + constructor(config: AxiosRequestConfig) { + this.instance = axios.create(Object.assign(this.baseConfig, config)) + this.setupInceptors() + } + + private setupInceptors() { + this.setupRequestInterceptors() + this.setupResponseInterceptors() + } + + private setupRequestInterceptors() { + const fulFilled = (res: InternalAxiosRequestConfig) => { + this.loadingBar.start() + return res + } + const onError = (err: any) => { + this.loadingBar.error() + return Promise.reject(err) + } + + this.instance.interceptors.request.use(fulFilled, onError) + } + + private setupResponseInterceptors() { + const fulFilled = (res: AxiosResponse) => { + this.loadingBar.finish() + return res + } + const onError = (err: any) => { + this.loadingBar.error() + + const msg = this.messages.get(err.response.status) + if (msg) { + this.notification.error(msg) + } else { + console.log(err.response) + this.notification.error({ + title: "未知错误", + content: "请打开控制台了解详情" + }) + } + + return Promise.reject(err.response) + } + + this.instance.interceptors.response.use(fulFilled, onError) + } + + public request(config: AxiosRequestConfig): Promise { + return this.instance.request(config) + } + + public get(url: string, config?: AxiosRequestConfig): Promise>> { + return this.instance.get(url, config) + } + + public post(url: string, data?: T, config?: AxiosRequestConfig): Promise>> { + return this.instance.post(url, data, config) + } + + public put(url: string, data?: T, config?: AxiosRequestConfig): Promise>> { + return this.instance.put(url, data, config) + } + + public delete(url: string, config?: AxiosRequestConfig): Promise>> { + return this.instance.delete(url, config) + } +} + +export default new Request({}) \ No newline at end of file diff --git a/web/src/router/index.ts b/web/src/router/index.ts index a49ae50..9221fd9 100644 --- a/web/src/router/index.ts +++ b/web/src/router/index.ts @@ -1,8 +1,8 @@ -import { createRouter, createWebHistory } from 'vue-router' +import { createRouter, createWebHashHistory } from 'vue-router' import HomeView from '../views/HomeView.vue' const router = createRouter({ - history: createWebHistory(import.meta.env.BASE_URL), + history: createWebHashHistory(), routes: [ { path: '/', diff --git a/web/src/stores/counter.ts b/web/src/stores/counter.ts deleted file mode 100644 index b6757ba..0000000 --- a/web/src/stores/counter.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { ref, computed } from 'vue' -import { defineStore } from 'pinia' - -export const useCounterStore = defineStore('counter', () => { - const count = ref(0) - const doubleCount = computed(() => count.value * 2) - function increment() { - count.value++ - } - - return { count, doubleCount, increment } -}) diff --git a/web/src/stores/domains.ts b/web/src/stores/domains.ts index 277cef9..cf0a4aa 100644 --- a/web/src/stores/domains.ts +++ b/web/src/stores/domains.ts @@ -1,5 +1,6 @@ import { defineStore } from 'pinia' import { ref, computed } from 'vue' +import api from '@/apis/api' export type Domain = { id: number; @@ -42,33 +43,35 @@ export const useDomainStore = defineStore('domains', () => { const domains = ref([]) const domainsGetter = computed(() => domains.value) - function loadDomains() { + async function loadDomains() { // TODO: load from api - domains.value = import.meta.env.DEV ? domainDevData : [] + domains.value = import.meta.env.DEV ? + domainDevData : + (await api.get('/domains')).data.data } - function addDomain(domain: Domain) { + async function addDomain(domain: Domain) { // TODO: load from api if (!import.meta.env.DEV) { - //domain = + domain = (await api.post("/domains", domain)).data.data } domains.value.push(domain) } - function updateDomain(domain: Domain) { + async function updateDomain(domain: Domain) { // TODO: load from api if (!import.meta.env.DEV) { - //domain = + await api.put("/domains", domain) } domains.value = domains.value.map(e => e.id === domain.id ? domain : e) } - function removeDomain(domain: Domain) { + async function removeDomain(domain: Domain) { // TODO: load from api if (!import.meta.env.DEV) { - //domain = + await api.delete(`/domains/${domain.id}`) } domains.value = domains.value.filter(e => e.id !== domain.id) diff --git a/web/src/stores/records.ts b/web/src/stores/records.ts index 738ec9d..e0d8ecc 100644 --- a/web/src/stores/records.ts +++ b/web/src/stores/records.ts @@ -1,3 +1,4 @@ +import api from '@/apis/api'; import { defineStore } from 'pinia' import { ref, computed } from 'vue' @@ -197,33 +198,35 @@ export const useRecordStore = defineStore('records', () => { const records = ref([]) const recordsGetter = computed(() => records.value) - function loadRecords(domain: string) { + async function loadRecords(domain: string) { // TODO: load from api - records.value = import.meta.env.DEV ? recordDevData.get(domain) : [] + records.value = import.meta.env.DEV ? + recordDevData.get(domain) : + (await api.get(`/records/${domain}`)).data.data } - function addRecord(domain: string, record: Record) { + async function addRecord(domain: string, record: Record) { // TODO: load from api if (!import.meta.env.DEV) { - //record = + record = (await api.post(`/records/${domain}`, record)).data.data } records.value?.push(record) } - function updateRecord(domain: string, record: Record) { + async function updateRecord(domain: string, record: Record) { // TODO: load from api if (!import.meta.env.DEV) { - //record = + await api.put(`/records/${domain}`, record) } records.value = records.value?.map(e => e.id === record.id ? record : e) } - function removeRecord(domain: string, record: Record) { + async function removeRecord(domain: string, record: Record) { // TODO: load from api if (!import.meta.env.DEV) { - //record = + await api.delete(`/records/${domain}/${record.id}`) } records.value = records.value?.filter(e => e.id !== record.id)