diff --git a/i18n/locales/en.json b/i18n/locales/en.json index b6651f4bb..d127ec8ab 100644 --- a/i18n/locales/en.json +++ b/i18n/locales/en.json @@ -53,6 +53,7 @@ "Linux": "Linux", "Other": "Other", "Device": "Device", + "Tool": "Tools", "Player": "Player", "Windows": "Windows", "DirectoryService": "Directory Service", diff --git a/i18n/locales/zh.json b/i18n/locales/zh.json index c15c300e5..e79f523f7 100644 --- a/i18n/locales/zh.json +++ b/i18n/locales/zh.json @@ -58,6 +58,7 @@ "Device": "设备", "Web": "Web", "Other": "其他", + "Tool": "工具", "Favorite": "收藏", "OfflinePlayer": "离线播放器", "Player": "播放器" diff --git a/package.json b/package.json index c173cb2eb..e7365da1d 100644 --- a/package.json +++ b/package.json @@ -30,6 +30,7 @@ "reset": "rm -rf node_modules .nuxt dist && cd src-tauri && cargo clean" }, "dependencies": { + "@cyolosecurity/asciinema-player": "3.6.3-3", "@nuxt/fonts": "0.12.1", "@nuxt/icon": "2.0.0", "@nuxt/ui": "^4.2.1", @@ -44,6 +45,9 @@ "@tauri-apps/plugin-process": "^2.3.1", "@tauri-apps/plugin-shell": "^2.3.3", "@tauri-apps/plugin-store": "^2.4.1", + "fflate": "^0.8.2", + "guacamole-common-js-jumpserver": "1.1.0-c", + "js-untar": "^2.0.0", "mitt": "^3.0.1", "nuxt": "^4.2.1", "pinia": "^3.0.4", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f4cd0bd9e..b426f7734 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -11,6 +11,9 @@ importers: .: dependencies: + '@cyolosecurity/asciinema-player': + specifier: 3.6.3-3 + version: 3.6.3-3 '@nuxt/fonts': specifier: 0.12.1 version: 0.12.1(@netlify/blobs@9.1.2)(db0@0.3.4)(ioredis@5.8.2)(magicast@0.5.1)(vite@7.2.7(@types/node@24.1.0)(jiti@2.6.1)(lightningcss@1.30.2)(sass-embedded@1.93.2)(sass@1.93.2)(terser@5.43.1)(yaml@2.8.2)) @@ -53,6 +56,15 @@ importers: '@tauri-apps/plugin-store': specifier: ^2.4.1 version: 2.4.1 + fflate: + specifier: ^0.8.2 + version: 0.8.3 + guacamole-common-js-jumpserver: + specifier: 1.1.0-c + version: 1.1.0-c + js-untar: + specifier: ^2.0.0 + version: 2.0.0 mitt: specifier: ^3.0.1 version: 3.0.1 @@ -329,6 +341,10 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 + '@babel/runtime@7.29.2': + resolution: {integrity: sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==} + engines: {node: '>=6.9.0'} + '@babel/template@7.27.2': resolution: {integrity: sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==} engines: {node: '>=6.9.0'} @@ -379,6 +395,9 @@ packages: resolution: {integrity: sha512-+tv3z+SPp+gqTIcImN9o0hqE9xyfQjI1XD9pL6NuKjua9B1y7mNYv0S9cP+QEbA4ppVgGZEmKOvHX5G5Ei1CVA==} engines: {node: '>=18.0.0'} + '@cyolosecurity/asciinema-player@3.6.3-3': + resolution: {integrity: sha512-5hImQEuQ7rqb1RpFz9BoJ+KLwyEMZUmPNAayoUovunUX+ERGZgOUjyT1QYCU4MifphJ+rJA0Z2nsJ4mX2zbivA==} + '@dxup/nuxt@0.2.2': resolution: {integrity: sha512-RNpJjDZs9+JcT9N87AnOuHsNM75DEd58itADNd/s1LIF6BZbTLZV0xxilJZb55lntn4TYvscTaXLCBX2fq9CXg==} @@ -1150,7 +1169,7 @@ packages: '@nuxt/content': ^3.0.0 joi: ^18.0.0 superstruct: ^2.0.0 - typescript: ^5.6.3 + typescript: npm:tslite@latest valibot: ^1.0.0 vue-router: ^4.5.0 yup: ^1.7.0 @@ -1200,7 +1219,6 @@ packages: '@oxc-minify/binding-darwin-arm64@0.102.0': resolution: {integrity: sha512-BDLiH41ZctNND38+GCEL3ZxFn9j7qMZJLrr6SLWMt8xlG4Sl64xTkZ0zeUy4RdVEatKKZdrRIhFZ2e5wPDQT6Q==} engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm64] os: [darwin] '@oxc-minify/binding-darwin-arm64@0.96.0': @@ -1238,36 +1256,42 @@ packages: engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] + libc: [glibc] '@oxc-minify/binding-linux-arm64-musl@0.96.0': resolution: {integrity: sha512-rNqoFWOWaxwMmUY5fspd/h5HfvgUlA3sv9CUdA2MpnHFiyoJNovR7WU8tGh+Yn0qOAs0SNH0a05gIthHig14IA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] + libc: [musl] '@oxc-minify/binding-linux-riscv64-gnu@0.96.0': resolution: {integrity: sha512-3paajIuzGnukHwSI3YBjYVqbd72pZd8NJxaayaNFR0AByIm8rmIT5RqFXbq8j2uhtpmNdZRXiu0em1zOmIScWA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [riscv64] os: [linux] + libc: [glibc] '@oxc-minify/binding-linux-s390x-gnu@0.96.0': resolution: {integrity: sha512-9ESrpkB2XG0lQ89JlsxlZa86iQCOs+jkDZLl6O+u5wb7ynUy21bpJJ1joauCOSYIOUlSy3+LbtJLiqi7oSQt5Q==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [s390x] os: [linux] + libc: [glibc] '@oxc-minify/binding-linux-x64-gnu@0.96.0': resolution: {integrity: sha512-UMM1jkns+p+WwwmdjC5giI3SfR2BCTga18x3C0cAu6vDVf4W37uTZeTtSIGmwatTBbgiq++Te24/DE0oCdm1iQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] + libc: [glibc] '@oxc-minify/binding-linux-x64-musl@0.96.0': resolution: {integrity: sha512-8b1naiC7MdP7xeMi7cQ5tb9W1rZAP9Qz/jBRqp1Y5EOZ1yhSGnf1QWuZ/0pCc+XiB9vEHXEY3Aki/H+86m2eOg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] + libc: [musl] '@oxc-minify/binding-wasm32-wasi@0.96.0': resolution: {integrity: sha512-bjGDjkGzo3GWU9Vg2qiFUrfoo5QxojPNV/2RHTlbIB5FWkkV4ExVjsfyqihFiAuj0NXIZqd2SAiEq9htVd3RFw==} @@ -1363,72 +1387,84 @@ packages: engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] + libc: [glibc] '@oxc-parser/binding-linux-arm64-gnu@0.96.0': resolution: {integrity: sha512-PHH4ETR1t0fymxuhpQNj3Z9t/78/zZa2Lj3Z3I0ZOd+/Ex+gtdhGoB5xYyy7lcYGAPMfZ+Gmr+dTCr1GYNZ3BA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] + libc: [glibc] '@oxc-parser/binding-linux-arm64-musl@0.95.0': resolution: {integrity: sha512-Pvi1lGe/G+mJZ3hUojMP/aAHAzHA25AEtVr8/iuz7UV72t/15NOgJYr9kELMUMNjPqpr3vKUgXTFmTtAxp11Qw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] + libc: [musl] '@oxc-parser/binding-linux-arm64-musl@0.96.0': resolution: {integrity: sha512-fjDPbZjkqaDSTBe0FM8nZ9zBw4B/NF/I0gH7CfvNDwIj9smISaNFypYeomkvubORpnbX9ORhvhYwg3TxQ60OGA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] + libc: [musl] '@oxc-parser/binding-linux-riscv64-gnu@0.95.0': resolution: {integrity: sha512-pUEVHIOVNDfhk4sTlLhn6mrNENhE4/dAwemxIfqpcSyBlYG0xYZND1F3jjR2yWY6DakXZ6VSuDbtiv1LPNlOLw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [riscv64] os: [linux] + libc: [glibc] '@oxc-parser/binding-linux-riscv64-gnu@0.96.0': resolution: {integrity: sha512-59KAHd/6/LmjkdSAuJn0piKmwSavMasWNUKuYLX/UnqI5KkGIp14+LBwwaBG6KzOtIq1NrRCnmlL4XSEaNkzTg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [riscv64] os: [linux] + libc: [glibc] '@oxc-parser/binding-linux-s390x-gnu@0.95.0': resolution: {integrity: sha512-5+olaepHTE3J/+w7g0tr3nocvv5BKilAJnzj4L8tWBCLEZbL6olJcGVoldUO+3cgg1SO1xJywP5BuLhT0mDUDw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [s390x] os: [linux] + libc: [glibc] '@oxc-parser/binding-linux-s390x-gnu@0.96.0': resolution: {integrity: sha512-VtupojtgahY8XmLwpVpM3C1WQEgMD1JxpB8lzUtdSLwosWaaz1EAl+VXWNuxTTZusNuLBtmR+F0qql22ISi/9g==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [s390x] os: [linux] + libc: [glibc] '@oxc-parser/binding-linux-x64-gnu@0.95.0': resolution: {integrity: sha512-8huzHlK/N98wrnYKxIcYsK8ZGBWomQchu/Mzi6m+CtbhjWOv9DmK0jQ2fUWImtluQVpTwS0uZT06d3g7XIkJrA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] + libc: [glibc] '@oxc-parser/binding-linux-x64-gnu@0.96.0': resolution: {integrity: sha512-8XSY9aUYY+5I4I1mhSEWmYqdUrJi3J5cCAInvEVHyTnDAPkhb+tnLGVZD696TpW+lFOLrTFF2V5GMWJVafqIUA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] + libc: [glibc] '@oxc-parser/binding-linux-x64-musl@0.95.0': resolution: {integrity: sha512-bWnrLfGDcx/fab0+UQnFbVFbiykof/btImbYf+cI2pU/1Egb2x+OKSmM5Qt0nEUiIpM5fgJmYXxTopybSZOKYA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] + libc: [musl] '@oxc-parser/binding-linux-x64-musl@0.96.0': resolution: {integrity: sha512-IIVNtqhA0uxKkD8Y6aZinKO/sOD5O62VlduE54FnUU2rzZEszrZQLL8nMGVZhTdPaKW5M1aeLmjcdnOs6er1Jg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] + libc: [musl] '@oxc-parser/binding-wasm32-wasi@0.95.0': resolution: {integrity: sha512-0JLyqkZu1HnQIZ4e5LBGOtzqua1QwFEUOoMSycdoerXqayd4LK2b7WMfAx8eCIf+jGm1Uj6f3R00nlsx8g1faQ==} @@ -1485,7 +1521,6 @@ packages: '@oxc-transform/binding-darwin-arm64@0.102.0': resolution: {integrity: sha512-xmsBCk/NwE0khy8h6wLEexiS5abCp1ZqJUNHsAovJdGgIW21oGwhiC3VYg1vNLbq+zEXwOHuphVuNEYfBwyNTw==} engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm64] os: [darwin] '@oxc-transform/binding-darwin-arm64@0.95.0': @@ -1553,72 +1588,84 @@ packages: engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] + libc: [glibc] '@oxc-transform/binding-linux-arm64-gnu@0.96.0': resolution: {integrity: sha512-kaqvUzNu8LL4aBSXqcqGVLFG13GmJEplRI2+yqzkgAItxoP/LfFMdEIErlTWLGyBwd0OLiNMHrOvkcCQRWadVg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] + libc: [glibc] '@oxc-transform/binding-linux-arm64-musl@0.95.0': resolution: {integrity: sha512-GL0ffCPW8JlFI0/jeSgCY665yDdojHxA0pbYG+k8oEHOWCYZUZK9AXL+r0oerNEWYJ8CRB+L5Yq87ZtU/YUitw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] + libc: [musl] '@oxc-transform/binding-linux-arm64-musl@0.96.0': resolution: {integrity: sha512-EiG/L3wEkPgTm4p906ufptyblBgtiQWTubGg/JEw82f8uLRroayr5zhbUqx40EgH037a3SfJthIyLZi7XPRFJw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] + libc: [musl] '@oxc-transform/binding-linux-riscv64-gnu@0.95.0': resolution: {integrity: sha512-tbH7LaClSmN3YFVo1UjMSe7D6gkb5f+CMIbj9i873UUZomVRmAjC4ygioObfzM+sj/tX0WoTXx5L1YOfQkHL6Q==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [riscv64] os: [linux] + libc: [glibc] '@oxc-transform/binding-linux-riscv64-gnu@0.96.0': resolution: {integrity: sha512-r01CY6OxKGtVeYnvH4mGmtkQMlLkXdPWWNXwo5o7fE2s/fgZPMpqh8bAuXEhuMXipZRJrjxTk1+ZQ4KCHpMn3Q==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [riscv64] os: [linux] + libc: [glibc] '@oxc-transform/binding-linux-s390x-gnu@0.95.0': resolution: {integrity: sha512-8jMqiURWa0iTiPMg7BWaln89VdhhWzNlPyKM90NaFVVhBIKCr2UEhrQWdpBw/E9C8uWf/4VabBEhfPMK+0yS4w==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [s390x] os: [linux] + libc: [glibc] '@oxc-transform/binding-linux-s390x-gnu@0.96.0': resolution: {integrity: sha512-4djg2vYLGbVeS8YiA2K4RPPpZE4fxTGCX5g/bOMbCYyirDbmBAIop4eOAj8vOA9i1CcWbDtmp+PVJ1dSw7f3IQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [s390x] os: [linux] + libc: [glibc] '@oxc-transform/binding-linux-x64-gnu@0.95.0': resolution: {integrity: sha512-D5ULJ2uWipsTgfvHIvqmnGkCtB3Fyt2ZN7APRjVO+wLr+HtmnaWddKsLdrRWX/m/6nQ2xQdoQekdJrokYK9LtQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] + libc: [glibc] '@oxc-transform/binding-linux-x64-gnu@0.96.0': resolution: {integrity: sha512-f6pcWVz57Y8jXa2OS7cz3aRNuks34Q3j61+3nQ4xTE8H1KbalcEvHNmM92OEddaJ8QLs9YcE0kUC6eDTbY34+A==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] + libc: [glibc] '@oxc-transform/binding-linux-x64-musl@0.95.0': resolution: {integrity: sha512-DmCGU+FzRezES5wVAGVimZGzYIjMOapXbWpxuz8M8p3nMrfdBEQ5/tpwBp2vRlIohhABy4vhHJByl4c64ENCGQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] + libc: [musl] '@oxc-transform/binding-linux-x64-musl@0.96.0': resolution: {integrity: sha512-NSiRtFvR7Pbhv3mWyPMkTK38czIjcnK0+K5STo3CuzZRVbX1TM17zGdHzKBUHZu7v6IQ6/XsQ3ELa1BlEHPGWQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] + libc: [musl] '@oxc-transform/binding-wasm32-wasi@0.95.0': resolution: {integrity: sha512-tSo1EU4Whd1gXyae7cwSDouhppkuz6Jkd5LY8Uch9VKsHVSRhDLDW19Mq6VSwtyPxDPTJnJ2jYJWm+n8SYXiXQ==} @@ -1683,36 +1730,42 @@ packages: engines: {node: '>= 10.0.0'} cpu: [arm] os: [linux] + libc: [glibc] '@parcel/watcher-linux-arm-musl@2.5.1': resolution: {integrity: sha512-6E+m/Mm1t1yhB8X412stiKFG3XykmgdIOqhjWj+VL8oHkKABfu/gjFj8DvLrYVHSBNC+/u5PeNrujiSQ1zwd1Q==} engines: {node: '>= 10.0.0'} cpu: [arm] os: [linux] + libc: [musl] '@parcel/watcher-linux-arm64-glibc@2.5.1': resolution: {integrity: sha512-LrGp+f02yU3BN9A+DGuY3v3bmnFUggAITBGriZHUREfNEzZh/GO06FF5u2kx8x+GBEUYfyTGamol4j3m9ANe8w==} engines: {node: '>= 10.0.0'} cpu: [arm64] os: [linux] + libc: [glibc] '@parcel/watcher-linux-arm64-musl@2.5.1': resolution: {integrity: sha512-cFOjABi92pMYRXS7AcQv9/M1YuKRw8SZniCDw0ssQb/noPkRzA+HBDkwmyOJYp5wXcsTrhxO0zq1U11cK9jsFg==} engines: {node: '>= 10.0.0'} cpu: [arm64] os: [linux] + libc: [musl] '@parcel/watcher-linux-x64-glibc@2.5.1': resolution: {integrity: sha512-GcESn8NZySmfwlTsIur+49yDqSny2IhPeZfXunQi48DMugKeZ7uy1FX83pO0X22sHntJ4Ub+9k34XQCX+oHt2A==} engines: {node: '>= 10.0.0'} cpu: [x64] os: [linux] + libc: [glibc] '@parcel/watcher-linux-x64-musl@2.5.1': resolution: {integrity: sha512-n0E2EQbatQ3bXhcH2D1XIAANAcTZkQICBPVaxMeaCVBtOpBZpWJuf7LwyWPSBDITb7In8mqQgJ7gH8CILCURXg==} engines: {node: '>= 10.0.0'} cpu: [x64] os: [linux] + libc: [musl] '@parcel/watcher-wasm@2.5.1': resolution: {integrity: sha512-RJxlQQLkaMMIuWRozy+z2vEqbaQlCuaCgVZIUCzQLYggY22LZbP5Y1+ia+FD724Ids9e+XIyOLXLrLgQSHIthw==} @@ -1900,56 +1953,67 @@ packages: resolution: {integrity: sha512-PsNAbcyv9CcecAUagQefwX8fQn9LQ4nZkpDboBOttmyffnInRy8R8dSg6hxxl2Re5QhHBf6FYIDhIj5v982ATQ==} cpu: [arm] os: [linux] + libc: [glibc] '@rollup/rollup-linux-arm-musleabihf@4.52.5': resolution: {integrity: sha512-Fw4tysRutyQc/wwkmcyoqFtJhh0u31K+Q6jYjeicsGJJ7bbEq8LwPWV/w0cnzOqR2m694/Af6hpFayLJZkG2VQ==} cpu: [arm] os: [linux] + libc: [musl] '@rollup/rollup-linux-arm64-gnu@4.52.5': resolution: {integrity: sha512-a+3wVnAYdQClOTlyapKmyI6BLPAFYs0JM8HRpgYZQO02rMR09ZcV9LbQB+NL6sljzG38869YqThrRnfPMCDtZg==} cpu: [arm64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-arm64-musl@4.52.5': resolution: {integrity: sha512-AvttBOMwO9Pcuuf7m9PkC1PUIKsfaAJ4AYhy944qeTJgQOqJYJ9oVl2nYgY7Rk0mkbsuOpCAYSs6wLYB2Xiw0Q==} cpu: [arm64] os: [linux] + libc: [musl] '@rollup/rollup-linux-loong64-gnu@4.52.5': resolution: {integrity: sha512-DkDk8pmXQV2wVrF6oq5tONK6UHLz/XcEVow4JTTerdeV1uqPeHxwcg7aFsfnSm9L+OO8WJsWotKM2JJPMWrQtA==} cpu: [loong64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-ppc64-gnu@4.52.5': resolution: {integrity: sha512-W/b9ZN/U9+hPQVvlGwjzi+Wy4xdoH2I8EjaCkMvzpI7wJUs8sWJ03Rq96jRnHkSrcHTpQe8h5Tg3ZzUPGauvAw==} cpu: [ppc64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-riscv64-gnu@4.52.5': resolution: {integrity: sha512-sjQLr9BW7R/ZiXnQiWPkErNfLMkkWIoCz7YMn27HldKsADEKa5WYdobaa1hmN6slu9oWQbB6/jFpJ+P2IkVrmw==} cpu: [riscv64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-riscv64-musl@4.52.5': resolution: {integrity: sha512-hq3jU/kGyjXWTvAh2awn8oHroCbrPm8JqM7RUpKjalIRWWXE01CQOf/tUNWNHjmbMHg/hmNCwc/Pz3k1T/j/Lg==} cpu: [riscv64] os: [linux] + libc: [musl] '@rollup/rollup-linux-s390x-gnu@4.52.5': resolution: {integrity: sha512-gn8kHOrku8D4NGHMK1Y7NA7INQTRdVOntt1OCYypZPRt6skGbddska44K8iocdpxHTMMNui5oH4elPH4QOLrFQ==} cpu: [s390x] os: [linux] + libc: [glibc] '@rollup/rollup-linux-x64-gnu@4.52.5': resolution: {integrity: sha512-hXGLYpdhiNElzN770+H2nlx+jRog8TyynpTVzdlc6bndktjKWyZyiCsuDAlpd+j+W+WNqfcyAWz9HxxIGfZm1Q==} cpu: [x64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-x64-musl@4.52.5': resolution: {integrity: sha512-arCGIcuNKjBoKAXD+y7XomR9gY6Mw7HnFBv5Rw7wQRvwYLR7gBAgV7Mb2QTyjXfTveBNFAtPt46/36vV9STLNg==} cpu: [x64] os: [linux] + libc: [musl] '@rollup/rollup-openharmony-arm64@4.52.5': resolution: {integrity: sha512-QoFqB6+/9Rly/RiPjaomPLmR/13cgkIGfA40LHly9zcH1S0bN2HVFYk3a1eAyHQyjs3ZJYlXvIGtcCs5tko9Cw==} @@ -2044,24 +2108,28 @@ packages: engines: {node: '>= 10'} cpu: [arm64] os: [linux] + libc: [glibc] '@tailwindcss/oxide-linux-arm64-musl@4.1.17': resolution: {integrity: sha512-HvZLfGr42i5anKtIeQzxdkw/wPqIbpeZqe7vd3V9vI3RQxe3xU1fLjss0TjyhxWcBaipk7NYwSrwTwK1hJARMg==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] + libc: [musl] '@tailwindcss/oxide-linux-x64-gnu@4.1.17': resolution: {integrity: sha512-M3XZuORCGB7VPOEDH+nzpJ21XPvK5PyjlkSFkFziNHGLc5d6g3di2McAAblmaSUNl8IOmzYwLx9NsE7bplNkwQ==} engines: {node: '>= 10'} cpu: [x64] os: [linux] + libc: [glibc] '@tailwindcss/oxide-linux-x64-musl@4.1.17': resolution: {integrity: sha512-k7f+pf9eXLEey4pBlw+8dgfJHY4PZ5qOUFDyNf7SI6lHjQ9Zt7+NcscjpwdCEbYi6FI5c2KDTDWyf2iHcCSyyQ==} engines: {node: '>= 10'} cpu: [x64] os: [linux] + libc: [musl] '@tailwindcss/oxide-wasm32-wasi@4.1.17': resolution: {integrity: sha512-cEytGqSSoy7zK4JRWiTCx43FsKP/zGr0CsuMawhH67ONlH+T79VteQeJQRO/X7L0juEUA8ZyuYikcRBf0vsxhg==} @@ -2143,30 +2211,35 @@ packages: engines: {node: '>= 10'} cpu: [arm64] os: [linux] + libc: [glibc] '@tauri-apps/cli-linux-arm64-musl@2.9.5': resolution: {integrity: sha512-/gRBMnphS9E8riZ0LIbBhZ9Oy16A2rx/g3DGR0DcDBvUtkLfbL0lMu4s+sY85nkn9An15+cZ1ZK6d7AIqWahLA==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] + libc: [musl] '@tauri-apps/cli-linux-riscv64-gnu@2.9.5': resolution: {integrity: sha512-NOzjPF9YIBodjdkFcJmqINT0k3YDoR5ANM/jg6Z6s3Zmk8ScN6inI60jTxcfgfWyITiKsPy7GJyYou3Cm2XNzw==} engines: {node: '>= 10'} cpu: [riscv64] os: [linux] + libc: [glibc] '@tauri-apps/cli-linux-x64-gnu@2.9.5': resolution: {integrity: sha512-SfGbwgvTphM5y+J91NyU/psleMUlyyPkZyDCFg8WU1HX8DpKUT3Vwhb/W1xpUBGb56tJgGCO46FCVkr8w4Areg==} engines: {node: '>= 10'} cpu: [x64] os: [linux] + libc: [glibc] '@tauri-apps/cli-linux-x64-musl@2.9.5': resolution: {integrity: sha512-ZfeoiASAOGDzyvN+TDAg8A1pCeS082h4uc0vZKvtWUN+9QBIMfz0yJwltAv+SN/afap6NS6DVkbPV3UVuI9V5A==} engines: {node: '>= 10'} cpu: [x64] os: [linux] + libc: [musl] '@tauri-apps/cli-win32-arm64-msvc@2.9.5': resolution: {integrity: sha512-ulg7irow+ekjaK4inFHVq7m1KQebDSYNb17DFKV+h+x7qnLZymz2gHK7df2u4YyEjqvzwRd3AJpU3HNxRurSFQ==} @@ -2258,26 +2331,26 @@ packages: peerDependencies: '@typescript-eslint/parser': ^8.49.0 eslint: ^8.57.0 || ^9.0.0 - typescript: '>=4.8.4 <6.0.0' + typescript: npm:tslite@latest '@typescript-eslint/parser@8.49.0': resolution: {integrity: sha512-N9lBGA9o9aqb1hVMc9hzySbhKibHmB+N3IpoShyV6HyQYRGIhlrO5rQgttypi+yEeKsKI4idxC8Jw6gXKD4THA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 - typescript: '>=4.8.4 <6.0.0' + typescript: npm:tslite@latest '@typescript-eslint/project-service@8.46.3': resolution: {integrity: sha512-Fz8yFXsp2wDFeUElO88S9n4w1I4CWDTXDqDr9gYvZgUpwXQqmZBr9+NTTql5R3J7+hrJZPdpiWaB9VNhAKYLuQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - typescript: '>=4.8.4 <6.0.0' + typescript: npm:tslite@latest '@typescript-eslint/project-service@8.49.0': resolution: {integrity: sha512-/wJN0/DKkmRUMXjZUXYZpD1NEQzQAAn9QWfGwo+Ai8gnzqH7tvqS7oNVdTjKqOcPyVIdZdyCMoqN66Ia789e7g==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - typescript: '>=4.8.4 <6.0.0' + typescript: npm:tslite@latest '@typescript-eslint/scope-manager@8.46.3': resolution: {integrity: sha512-FCi7Y1zgrmxp3DfWfr+3m9ansUUFoy8dkEdeQSgA9gbm8DaHYvZCdkFRQrtKiedFf3Ha6VmoqoAaP68+i+22kg==} @@ -2291,20 +2364,20 @@ packages: resolution: {integrity: sha512-GLupljMniHNIROP0zE7nCcybptolcH8QZfXOpCfhQDAdwJ/ZTlcaBOYebSOZotpti/3HrHSw7D3PZm75gYFsOA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - typescript: '>=4.8.4 <6.0.0' + typescript: npm:tslite@latest '@typescript-eslint/tsconfig-utils@8.49.0': resolution: {integrity: sha512-8prixNi1/6nawsRYxet4YOhnbW+W9FK/bQPxsGB1D3ZrDzbJ5FXw5XmzxZv82X3B+ZccuSxo/X8q9nQ+mFecWA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - typescript: '>=4.8.4 <6.0.0' + typescript: npm:tslite@latest '@typescript-eslint/type-utils@8.49.0': resolution: {integrity: sha512-KTExJfQ+svY8I10P4HdxKzWsvtVnsuCifU5MvXrRwoP2KOlNZ9ADNEWWsQTJgMxLzS5VLQKDjkCT/YzgsnqmZg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 - typescript: '>=4.8.4 <6.0.0' + typescript: npm:tslite@latest '@typescript-eslint/types@8.46.3': resolution: {integrity: sha512-G7Ok9WN/ggW7e/tOf8TQYMaxgID3Iujn231hfi0Pc7ZheztIJVpO44ekY00b7akqc6nZcvregk0Jpah3kep6hA==} @@ -2318,27 +2391,27 @@ packages: resolution: {integrity: sha512-f/NvtRjOm80BtNM5OQtlaBdM5BRFUv7gf381j9wygDNL+qOYSNOgtQ/DCndiYi80iIOv76QqaTmp4fa9hwI0OA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - typescript: '>=4.8.4 <6.0.0' + typescript: npm:tslite@latest '@typescript-eslint/typescript-estree@8.49.0': resolution: {integrity: sha512-jrLdRuAbPfPIdYNppHJ/D0wN+wwNfJ32YTAm10eJVsFmrVpXQnDWBn8niCSMlWjvml8jsce5E/O+86IQtTbJWA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - typescript: '>=4.8.4 <6.0.0' + typescript: npm:tslite@latest '@typescript-eslint/utils@8.46.3': resolution: {integrity: sha512-VXw7qmdkucEx9WkmR3ld/u6VhRyKeiF1uxWwCy/iuNfokjJ7VhsgLSOTjsol8BunSw190zABzpwdNsze2Kpo4g==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 - typescript: '>=4.8.4 <6.0.0' + typescript: npm:tslite@latest '@typescript-eslint/utils@8.49.0': resolution: {integrity: sha512-N3W7rJw7Rw+z1tRsHZbK395TWSYvufBXumYtEGzypgMUthlg0/hmCImeA8hgO2d2G4pd7ftpxxul2J8OdtdaFA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 - typescript: '>=4.8.4 <6.0.0' + typescript: npm:tslite@latest '@typescript-eslint/visitor-keys@8.46.3': resolution: {integrity: sha512-uk574k8IU0rOF/AjniX8qbLSGURJVUCeM5e4MIMKBFFi8weeiLrG1fyQejyLXQpRZbU/1BuQasleV/RfHC3hHg==} @@ -2392,41 +2465,49 @@ packages: resolution: {integrity: sha512-34gw7PjDGB9JgePJEmhEqBhWvCiiWCuXsL9hYphDF7crW7UgI05gyBAi6MF58uGcMOiOqSJ2ybEeCvHcq0BCmQ==} cpu: [arm64] os: [linux] + libc: [glibc] '@unrs/resolver-binding-linux-arm64-musl@1.11.1': resolution: {integrity: sha512-RyMIx6Uf53hhOtJDIamSbTskA99sPHS96wxVE/bJtePJJtpdKGXO1wY90oRdXuYOGOTuqjT8ACccMc4K6QmT3w==} cpu: [arm64] os: [linux] + libc: [musl] '@unrs/resolver-binding-linux-ppc64-gnu@1.11.1': resolution: {integrity: sha512-D8Vae74A4/a+mZH0FbOkFJL9DSK2R6TFPC9M+jCWYia/q2einCubX10pecpDiTmkJVUH+y8K3BZClycD8nCShA==} cpu: [ppc64] os: [linux] + libc: [glibc] '@unrs/resolver-binding-linux-riscv64-gnu@1.11.1': resolution: {integrity: sha512-frxL4OrzOWVVsOc96+V3aqTIQl1O2TjgExV4EKgRY09AJ9leZpEg8Ak9phadbuX0BA4k8U5qtvMSQQGGmaJqcQ==} cpu: [riscv64] os: [linux] + libc: [glibc] '@unrs/resolver-binding-linux-riscv64-musl@1.11.1': resolution: {integrity: sha512-mJ5vuDaIZ+l/acv01sHoXfpnyrNKOk/3aDoEdLO/Xtn9HuZlDD6jKxHlkN8ZhWyLJsRBxfv9GYM2utQ1SChKew==} cpu: [riscv64] os: [linux] + libc: [musl] '@unrs/resolver-binding-linux-s390x-gnu@1.11.1': resolution: {integrity: sha512-kELo8ebBVtb9sA7rMe1Cph4QHreByhaZ2QEADd9NzIQsYNQpt9UkM9iqr2lhGr5afh885d/cB5QeTXSbZHTYPg==} cpu: [s390x] os: [linux] + libc: [glibc] '@unrs/resolver-binding-linux-x64-gnu@1.11.1': resolution: {integrity: sha512-C3ZAHugKgovV5YvAMsxhq0gtXuwESUKc5MhEtjBpLoHPLYM+iuwSj3lflFwK3DPm68660rZ7G8BMcwSro7hD5w==} cpu: [x64] os: [linux] + libc: [glibc] '@unrs/resolver-binding-linux-x64-musl@1.11.1': resolution: {integrity: sha512-rV0YSoyhK2nZ4vEswT/QwqzqQXw5I6CjoaYMOX0TqBlWhojUf8P94mvI7nuJTeaCkkds3QE4+zS8Ko+GdXuZtA==} cpu: [x64] os: [linux] + libc: [musl] '@unrs/resolver-binding-wasm32-wasi@1.11.1': resolution: {integrity: sha512-5u4RkfxJm+Ng7IWgkzi3qrFOvLvQYnPBmjmZQ8+szTK/b31fQCnleNl1GgEt7nIsZRIf5PLhPwT0WM+q45x/UQ==} @@ -2472,7 +2553,7 @@ packages: engines: {node: '>=18'} peerDependencies: eslint: '>=8.57.0' - typescript: '>=5.0.0' + typescript: npm:tslite@latest vitest: '*' peerDependenciesMeta: typescript: @@ -2549,7 +2630,7 @@ packages: '@vue/language-core@3.1.8': resolution: {integrity: sha512-PfwAW7BLopqaJbneChNL6cUOTL3GL+0l8paYP5shhgY5toBNidWnMXWM+qDwL7MC9+zDtzCF2enT8r6VPu64iw==} peerDependencies: - typescript: '*' + typescript: npm:tslite@latest peerDependenciesMeta: typescript: optional: true @@ -3485,7 +3566,7 @@ packages: engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: '>=9.0.0' - typescript: '>=4.5' + typescript: npm:tslite@latest peerDependenciesMeta: typescript: optional: true @@ -3719,6 +3800,9 @@ packages: resolution: {integrity: sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==} engines: {node: ^12.20 || >= 14.13} + fflate@0.8.3: + resolution: {integrity: sha512-tbZNuJrLwGUp3zshBtdy4W+ORxZuIh8a5ilyIEQDC5rY1f3U20JMry0Ll3WBzU58EZKsEuJFXhb5gwv8CsPvgA==} + file-entry-cache@8.0.0: resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} engines: {node: '>=16.0.0'} @@ -3911,6 +3995,9 @@ packages: graphemer@1.4.0: resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} + guacamole-common-js-jumpserver@1.1.0-c: + resolution: {integrity: sha512-FKu9Cez9iy1xPy4GU+SaMDjqLR0+k1XsDO7dqi9JjJgz4pAoxQIdX2Thci8ZFGwTGdZzhVBpAqs7pebuktRSZQ==} + gzip-size@7.0.0: resolution: {integrity: sha512-O1Ld7Dr+nqPnmGpdhzLmMTQ4vAsD+rHwMm1NLUmoUFFymBOMKxCCrtDxqdBRYXdeEPEi3SyoR4TizJLQrnKBNA==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} @@ -4119,6 +4206,9 @@ packages: js-tokens@9.0.1: resolution: {integrity: sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==} + js-untar@2.0.0: + resolution: {integrity: sha512-7CsDLrYQMbLxDt2zl9uKaPZSdmJMvGGQ7wo9hoB3J+z/VcO2w63bXFgHVnjF1+S9wD3zAu8FBVj7EYWjTQ3Z7g==} + js-yaml@4.1.0: resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} hasBin: true @@ -4238,24 +4328,28 @@ packages: engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] + libc: [glibc] lightningcss-linux-arm64-musl@1.30.2: resolution: {integrity: sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] + libc: [musl] lightningcss-linux-x64-gnu@1.30.2: resolution: {integrity: sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] + libc: [glibc] lightningcss-linux-x64-musl@1.30.2: resolution: {integrity: sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] + libc: [musl] lightningcss-win32-arm64-msvc@1.30.2: resolution: {integrity: sha512-FZn+vaj7zLv//D/192WFFVA0RgHawIcHqLX9xuWiQt7P0PtdFEVaxgF9rjM/IRYHQXNnk61/H/gb2Ei+kUQ4xQ==} @@ -4923,7 +5017,7 @@ packages: pinia@3.0.4: resolution: {integrity: sha512-l7pqLUFTI/+ESXn6k3nu30ZIzW5E2WZF/LaHJEpoq6ElcLD+wduZoB2kBN19du6K/4FDpPMazY2wJr+IndBtQw==} peerDependencies: - typescript: '>=4.5.0' + typescript: npm:tslite@latest vue: ^3.5.11 peerDependenciesMeta: typescript: @@ -5341,48 +5435,56 @@ packages: engines: {node: '>=14.0.0'} cpu: [arm64] os: [linux] + libc: glibc sass-embedded-linux-arm@1.93.2: resolution: {integrity: sha512-N3+D/ToHtzwLDO+lSH05Wo6/KRxFBPnbjVHASOlHzqJnK+g5cqex7IFAp6ozzlRStySk61Rp6d+YGrqZ6/P0PA==} engines: {node: '>=14.0.0'} cpu: [arm] os: [linux] + libc: glibc sass-embedded-linux-musl-arm64@1.93.2: resolution: {integrity: sha512-+3EHuDPkMiAX5kytsjEC1bKZCawB9J6pm2eBIzzLMPWbf5xdx++vO1DpT7hD4bm4ZGn0eVHgSOKIfP6CVz6tVg==} engines: {node: '>=14.0.0'} cpu: [arm64] os: [linux] + libc: musl sass-embedded-linux-musl-arm@1.93.2: resolution: {integrity: sha512-XBTvx66yRenvEsp3VaJCb3HQSyqCsUh7R+pbxcN5TuzueybZi0LXvn9zneksdXcmjACMlMpIVXi6LyHPQkYc8A==} engines: {node: '>=14.0.0'} cpu: [arm] os: [linux] + libc: musl sass-embedded-linux-musl-riscv64@1.93.2: resolution: {integrity: sha512-0sB5kmVZDKTYzmCSlTUnjh6mzOhzmQiW/NNI5g8JS4JiHw2sDNTvt1dsFTuqFkUHyEOY3ESTsfHHBQV8Ip4bEA==} engines: {node: '>=14.0.0'} cpu: [riscv64] os: [linux] + libc: musl sass-embedded-linux-musl-x64@1.93.2: resolution: {integrity: sha512-t3ejQ+1LEVuHy7JHBI2tWHhoMfhedUNDjGJR2FKaLgrtJntGnyD1RyX0xb3nuqL/UXiEAtmTmZY+Uh3SLUe1Hg==} engines: {node: '>=14.0.0'} cpu: [x64] os: [linux] + libc: musl sass-embedded-linux-riscv64@1.93.2: resolution: {integrity: sha512-e7AndEwAbFtXaLy6on4BfNGTr3wtGZQmypUgYpSNVcYDO+CWxatKVY4cxbehMPhxG9g5ru+eaMfynvhZt7fLaA==} engines: {node: '>=14.0.0'} cpu: [riscv64] os: [linux] + libc: glibc sass-embedded-linux-x64@1.93.2: resolution: {integrity: sha512-U3EIUZQL11DU0xDDHXexd4PYPHQaSQa2hzc4EzmhHqrAj+TyfYO94htjWOd+DdTPtSwmLp+9cTWwPZBODzC96w==} engines: {node: '>=14.0.0'} cpu: [x64] os: [linux] + libc: glibc sass-embedded-unknown-all@1.93.2: resolution: {integrity: sha512-7VnaOmyewcXohiuoFagJ3SK5ddP9yXpU0rzz+pZQmS1/+5O6vzyFCUoEt3HDRaLctH4GT3nUGoK1jg0ae62IfQ==} @@ -5441,10 +5543,20 @@ packages: serialize-javascript@6.0.2: resolution: {integrity: sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==} + seroval-plugins@1.5.4: + resolution: {integrity: sha512-S0xQPhUTefAhNvNWFg0c1J8qJArHt5KdtJ/cFAofo06KD1MVSeFWyl4iiu+ApDIuw0WhjpOfCdgConOfAnLgkw==} + engines: {node: '>=10'} + peerDependencies: + seroval: ^1.0 + seroval@1.3.2: resolution: {integrity: sha512-RbcPH1n5cfwKrru7v7+zrZvjLurgHhGyso3HTyGtRivGWgYjbOmGuivCQaORNELjNONoK35nj28EoWul9sb1zQ==} engines: {node: '>=10'} + seroval@1.5.4: + resolution: {integrity: sha512-46uFvgrXTVxZcUorgSSRZ4y+ieqLLQRMlG4bnCZKW3qI6BZm7Rg4ntMW4p1mILEEBZWrFlcpp0AyIIlM6jD9iw==} + engines: {node: '>=10'} + serve-placeholder@2.0.2: resolution: {integrity: sha512-/TMG8SboeiQbZJWRlfTCqMs2DD3SZgWp0kDQePz9yUuCnDfDh/92gf7/PxGhzXTKBIPASIHxFcZndoNbp6QOLQ==} @@ -5504,6 +5616,9 @@ packages: smob@1.5.0: resolution: {integrity: sha512-g6T+p7QO8npa+/hNx9ohv1E5pVCmWrVCUzUXJyLdMmftX6ER0oiWY/w9knEonLpnOp6b6FenKnMfR8gqwWdwig==} + solid-js@1.9.13: + resolution: {integrity: sha512-6hJeJMOcEX8ktqjpDoJZEmld3ijvcvWBDtiXBm7f4332SiFN66QeAQI1REQshvyUoISsSeJ4PHDauKYbwao9JQ==} + source-map-js@1.2.1: resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} engines: {node: '>=0.10.0'} @@ -5745,12 +5860,12 @@ packages: resolution: {integrity: sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==} engines: {node: '>=18.12'} peerDependencies: - typescript: '>=4.8.4' + typescript: npm:tslite@latest ts-declaration-location@1.0.7: resolution: {integrity: sha512-EDyGAwH1gO0Ausm9gV6T2nUvBgXT5kGoCMJPllOaooZ+4VvJiKBdZE7wK18N1deEowhcUptS+5GXZK8U/fvpwA==} peerDependencies: - typescript: '>=4.0.0' + typescript: npm:tslite@latest tslib@2.8.1: resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} @@ -6019,7 +6134,7 @@ packages: optionator: ^0.9.4 oxlint: '>=1' stylelint: '>=16' - typescript: '*' + typescript: npm:tslite@latest vite: '>=5.4.20' vls: '*' vti: '*' @@ -6145,7 +6260,7 @@ packages: vue@3.5.25: resolution: {integrity: sha512-YLVdgv2K13WJ6n+kD5owehKtEXwdwXuj2TTyJMsO7pSeKw2bfRNZGjhB7YzrpbMYj5b5QsUebHpOqR3R3ziy/g==} peerDependencies: - typescript: '*' + typescript: npm:tslite@latest peerDependenciesMeta: typescript: optional: true @@ -6496,6 +6611,8 @@ snapshots: transitivePeerDependencies: - supports-color + '@babel/runtime@7.29.2': {} + '@babel/template@7.27.2': dependencies: '@babel/code-frame': 7.27.1 @@ -6557,6 +6674,11 @@ snapshots: dependencies: mime: 3.0.0 + '@cyolosecurity/asciinema-player@3.6.3-3': + dependencies: + '@babel/runtime': 7.29.2 + solid-js: 1.9.13 + '@dxup/nuxt@0.2.2(magicast@0.5.1)': dependencies: '@dxup/unimport': 0.1.2 @@ -10239,6 +10361,8 @@ snapshots: web-streams-polyfill: 3.3.3 optional: true + fflate@0.8.3: {} + file-entry-cache@8.0.0: dependencies: flat-cache: 4.0.1 @@ -10473,6 +10597,8 @@ snapshots: graphemer@1.4.0: {} + guacamole-common-js-jumpserver@1.1.0-c: {} + gzip-size@7.0.0: dependencies: duplexer: 0.1.2 @@ -10663,6 +10789,8 @@ snapshots: js-tokens@9.0.1: {} + js-untar@2.0.0: {} + js-yaml@4.1.0: dependencies: argparse: 2.0.1 @@ -12377,8 +12505,14 @@ snapshots: dependencies: randombytes: 2.1.0 + seroval-plugins@1.5.4(seroval@1.5.4): + dependencies: + seroval: 1.5.4 + seroval@1.3.2: {} + seroval@1.5.4: {} + serve-placeholder@2.0.2: dependencies: defu: 6.1.4 @@ -12456,6 +12590,12 @@ snapshots: smob@1.5.0: {} + solid-js@1.9.13: + dependencies: + csstype: 3.1.3 + seroval: 1.5.4 + seroval-plugins: 1.5.4(seroval@1.5.4) + source-map-js@1.2.1: {} source-map-support@0.5.21: diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index 18abbb2f8..3698b0108 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -10,6 +10,7 @@ dependencies = [ "axum", "chrono", "env_logger", + "flate2", "font-kit", "image", "keyring", diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index fb67b48da..5ae71bf99 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -26,6 +26,7 @@ image = "0.25.8" oauth2 = "5.0.0" axum = "0.7" chrono = "0.4.41" +flate2 = "1.1.5" font-kit = "0.14" anyhow = "1.0.100" serde_json = "1.0" diff --git a/src-tauri/src/commands/get_connect_methods.rs b/src-tauri/src/commands/get_connect_methods.rs index 237f4607d..496a26e70 100644 --- a/src-tauri/src/commands/get_connect_methods.rs +++ b/src-tauri/src/commands/get_connect_methods.rs @@ -35,4 +35,4 @@ pub async fn get_connect_methods(app: AppHandle, site: String, bearer_token: Str "get-connect-methods-success", json!({ "status": connect_methods_data.status, "data": connect_methods_data.data }), ); -} \ No newline at end of file +} diff --git a/src-tauri/src/commands/get_setting.rs b/src-tauri/src/commands/get_setting.rs index c7c5e0c1d..137f74e0a 100644 --- a/src-tauri/src/commands/get_setting.rs +++ b/src-tauri/src/commands/get_setting.rs @@ -22,7 +22,7 @@ pub async fn get_setting(app: AppHandle, site: String, bearer_token: String) { if !setting_data.success { error!("获取 Setting 数据失败"); - + let _ = app.emit( "get-setting-failure", json!({ "status": setting_data.status }), diff --git a/src-tauri/src/commands/logout.rs b/src-tauri/src/commands/logout.rs index 181658994..b96047e1d 100644 --- a/src-tauri/src/commands/logout.rs +++ b/src-tauri/src/commands/logout.rs @@ -16,7 +16,7 @@ pub async fn logout(app: AppHandle, _name: String, site: String) -> Result<(), S async fn revoke_and_clear_tokens(_app: &AppHandle, site: &str) -> anyhow::Result<()> { let token_service = TokenService::new(site.to_string()); - + if let Some(entry) = token_service.load().await? { if let Some(refresh) = entry.refresh_token { let client_id = entry diff --git a/src-tauri/src/commands/mod.rs b/src-tauri/src/commands/mod.rs index bb7c126de..04666c2ef 100644 --- a/src-tauri/src/commands/mod.rs +++ b/src-tauri/src/commands/mod.rs @@ -15,4 +15,5 @@ pub(crate) mod requests; pub mod set_favorite; pub mod unfavorite; pub mod update_config; +pub mod video_player; pub mod window_controls; diff --git a/src-tauri/src/commands/pull_up.rs b/src-tauri/src/commands/pull_up.rs index 7c71d635a..301208012 100644 --- a/src-tauri/src/commands/pull_up.rs +++ b/src-tauri/src/commands/pull_up.rs @@ -139,7 +139,7 @@ pub fn pull_up(_app: AppHandle, url: String) -> Result<(), String> { // 使用通道来在后台线程和主线程之间通信 let (tx, rx) = std::sync::mpsc::channel::(); - + // 在后台线程中读取 stderr,检查是否有错误输出 thread::spawn(move || { let reader = BufReader::new(stderr); @@ -208,14 +208,14 @@ pub fn pull_up(_app: AppHandle, url: String) -> Result<(), String> { thread::sleep(Duration::from_millis(100)); } - + // 最后再检查一次是否有错误消息(防止在循环结束后才收到错误) if let Ok(error_msg) = rx.try_recv() { let err_msg = format!("Client error: {}", error_msg); error!("{}", err_msg); return Err(err_msg); } - + // 如果进程仍在运行,这是正常的,让它在后台继续运行 // 后续的错误会通过事件发送给前端 diff --git a/src-tauri/src/commands/requests.rs b/src-tauri/src/commands/requests.rs index 80e4b8efa..0b36cbe3c 100644 --- a/src-tauri/src/commands/requests.rs +++ b/src-tauri/src/commands/requests.rs @@ -19,7 +19,6 @@ pub trait MaybeJson { } } - #[derive(Debug, Serialize)] pub struct ApiResponse { pub status: u16, @@ -42,7 +41,6 @@ where } } - impl MaybeJson for () { fn apply(self, rb: reqwest::RequestBuilder) -> reqwest::RequestBuilder { rb diff --git a/src-tauri/src/commands/video_player.rs b/src-tauri/src/commands/video_player.rs new file mode 100644 index 000000000..c076ed890 --- /dev/null +++ b/src-tauri/src/commands/video_player.rs @@ -0,0 +1,159 @@ +use flate2::read::GzDecoder; +use serde::Serialize; +use std::fs::{self, File}; +use std::io::{BufReader, Read, Write}; +use std::path::{Path, PathBuf}; +use std::time::{SystemTime, UNIX_EPOCH}; +use tauri::{AppHandle, Emitter, Manager}; + +const STREAM_CHUNK_SIZE: usize = 64 * 1024; + +#[derive(Clone, Serialize)] +struct FileChunkPayload { + chunk: String, +} + +#[derive(Clone, Serialize)] +struct FileErrorPayload { + message: String, +} + +fn player_temp_dir(app: &AppHandle) -> Result { + let dir = app + .path() + .app_data_dir() + .map_err(|e| format!("Failed to resolve app data dir: {}", e))? + .join("videoplayer"); + + if !dir.exists() { + fs::create_dir_all(&dir).map_err(|e| format!("Failed to create temp dir: {}", e))?; + } + + Ok(dir) +} + +fn sanitize_filename(raw: &str) -> String { + let name = Path::new(raw) + .file_name() + .and_then(|value| value.to_str()) + .filter(|value| !value.trim().is_empty()) + .unwrap_or("video-player.tmp"); + + name.chars() + .map(|ch| match ch { + 'a'..='z' | 'A'..='Z' | '0'..='9' | '.' | '_' | '-' => ch, + _ => '_', + }) + .collect() +} + +fn unique_temp_path(app: &AppHandle, file_name: &str) -> Result { + let stamp = SystemTime::now() + .duration_since(UNIX_EPOCH) + .map(|d| d.as_millis()) + .unwrap_or(0); + let safe_name = sanitize_filename(file_name); + Ok(player_temp_dir(app)?.join(format!("{}_{}", stamp, safe_name))) +} + +#[tauri::command] +pub fn write_video_player_gzip_file( + app: AppHandle, + buffer: Vec, + file_name: String, +) -> Result { + let mut decoder = GzDecoder::new(buffer.as_slice()); + let output_path = unique_temp_path(&app, &file_name)?; + let mut output_file = + File::create(&output_path).map_err(|e| format!("Failed to create temp file: {}", e))?; + + let mut chunk = [0_u8; STREAM_CHUNK_SIZE]; + + loop { + let read = decoder + .read(&mut chunk) + .map_err(|e| format!("Failed to decompress gzip file: {}", e))?; + + if read == 0 { + break; + } + + output_file + .write_all(&chunk[..read]) + .map_err(|e| format!("Failed to write temp file: {}", e))?; + } + + output_file + .flush() + .map_err(|e| format!("Failed to flush temp file: {}", e))?; + + Ok(output_path.to_string_lossy().to_string()) +} + +#[tauri::command] +pub fn read_video_player_text_stream( + app: AppHandle, + event_id: String, + file_path: String, +) -> Result<(), String> { + let chunk_event = format!("videoplayer://{}/chunk", event_id); + let end_event = format!("videoplayer://{}/end", event_id); + let error_event = format!("videoplayer://{}/error", event_id); + + let file = match File::open(&file_path) { + Ok(file) => file, + Err(e) => { + let _ = app.emit( + &error_event, + FileErrorPayload { + message: format!("Failed to open file: {}", e), + }, + ); + return Err(format!("Failed to open file: {}", e)); + } + }; + + let mut reader = BufReader::new(file); + let mut chunk = vec![0_u8; STREAM_CHUNK_SIZE]; + + loop { + match reader.read(&mut chunk) { + Ok(0) => break, + Ok(read) => { + let payload = FileChunkPayload { + chunk: String::from_utf8_lossy(&chunk[..read]).to_string(), + }; + + if let Err(e) = app.emit(&chunk_event, payload) { + return Err(format!("Failed to emit file chunk: {}", e)); + } + } + Err(e) => { + let message = format!("Failed to read file: {}", e); + let _ = app.emit( + &error_event, + FileErrorPayload { + message: message.clone(), + }, + ); + return Err(message); + } + } + } + + app.emit(&end_event, ()) + .map_err(|e| format!("Failed to emit stream end: {}", e))?; + + Ok(()) +} + +#[tauri::command] +pub fn delete_video_player_file(file_path: String) -> Result<(), String> { + let path = PathBuf::from(file_path); + + if !path.exists() { + return Ok(()); + } + + fs::remove_file(&path).map_err(|e| format!("Failed to delete file: {}", e)) +} diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 5987d0d09..b8e7c3018 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -23,6 +23,9 @@ use crate::commands::rename_asset::rename; use crate::commands::set_favorite::set_favorite; use crate::commands::unfavorite::unfavorite; use crate::commands::update_config::update_config_selection; +use crate::commands::video_player::{ + delete_video_player_file, read_video_player_text_stream, write_video_player_gzip_file, +}; use crate::commands::window_controls::{close_window, minimize_window, toggle_maximize_window}; use crate::utils::is_auth_callback; @@ -197,6 +200,9 @@ pub fn run() { update_config_selection, init_http_callback_server, get_connect_methods, + write_video_player_gzip_file, + read_video_player_text_stream, + delete_video_player_file, ]) .run(tauri::generate_context!()) .expect("error while running tauri application"); diff --git a/src-tauri/src/service/connect_methods.rs b/src-tauri/src/service/connect_methods.rs index cd2b9dbea..66f52b018 100644 --- a/src-tauri/src/service/connect_methods.rs +++ b/src-tauri/src/service/connect_methods.rs @@ -17,4 +17,4 @@ impl ConnectMethodsService { let url = format!("{}/api/v1/terminal/components/connect-methods/", self.origin); get_with_response(&url, &self.bearer_token).await } -} \ No newline at end of file +} diff --git a/src-tauri/src/service/http_callback.rs b/src-tauri/src/service/http_callback.rs index a5265d0e4..f9f23bf22 100644 --- a/src-tauri/src/service/http_callback.rs +++ b/src-tauri/src/service/http_callback.rs @@ -31,7 +31,7 @@ pub async fn start_http_callback_server(app_handle: AppHandle) { #[cfg(debug_assertions)] { let app_handle = Arc::new(app_handle); - + let app_handle_clone = app_handle.clone(); let app = Router::new() .route( diff --git a/src-tauri/src/service/version.rs b/src-tauri/src/service/version.rs index a5ee510a0..505f25cb0 100644 --- a/src-tauri/src/service/version.rs +++ b/src-tauri/src/service/version.rs @@ -17,4 +17,3 @@ impl VersionService { get_with_response(&url, "").await } } - diff --git a/ui/components/SideBar/sideBar.vue b/ui/components/SideBar/sideBar.vue index f1827c711..c85bbc176 100644 --- a/ui/components/SideBar/sideBar.vue +++ b/ui/components/SideBar/sideBar.vue @@ -62,6 +62,16 @@ const sideBarItems = computed(() => { icon: "gravity-ui:star", to: localePath("favorite"), disabled: isLoading.value + }, + { + label: t("Menu.Tool"), + type: "label" + }, + { + label: t("Menu.Player"), + icon: "line-md:play", + to: localePath("videoplayer"), + disabled: isLoading.value } ]; }); diff --git a/ui/components/VideoPlayer/Dropzone.vue b/ui/components/VideoPlayer/Dropzone.vue new file mode 100644 index 000000000..45a9b3c3f --- /dev/null +++ b/ui/components/VideoPlayer/Dropzone.vue @@ -0,0 +1,54 @@ + + + diff --git a/ui/components/VideoPlayer/Playlist.vue b/ui/components/VideoPlayer/Playlist.vue new file mode 100644 index 000000000..496fa52cd --- /dev/null +++ b/ui/components/VideoPlayer/Playlist.vue @@ -0,0 +1,135 @@ + + + diff --git a/ui/components/VideoPlayer/players/AsciinemaPlayer.vue b/ui/components/VideoPlayer/players/AsciinemaPlayer.vue new file mode 100644 index 000000000..a21edf82f --- /dev/null +++ b/ui/components/VideoPlayer/players/AsciinemaPlayer.vue @@ -0,0 +1,85 @@ + + + + + diff --git a/ui/components/VideoPlayer/players/GuaPlayer.vue b/ui/components/VideoPlayer/players/GuaPlayer.vue new file mode 100644 index 000000000..f5d53bbaa --- /dev/null +++ b/ui/components/VideoPlayer/players/GuaPlayer.vue @@ -0,0 +1,535 @@ + + + + + diff --git a/ui/components/VideoPlayer/players/Mp4Player.vue b/ui/components/VideoPlayer/players/Mp4Player.vue new file mode 100644 index 000000000..45cc55461 --- /dev/null +++ b/ui/components/VideoPlayer/players/Mp4Player.vue @@ -0,0 +1,11 @@ + + + diff --git a/ui/composables/useVideoPlayerParser.ts b/ui/composables/useVideoPlayerParser.ts new file mode 100644 index 000000000..925d9cbee --- /dev/null +++ b/ui/composables/useVideoPlayerParser.ts @@ -0,0 +1,321 @@ +// @ts-expect-error library ships without useful ESM typings +import untar from "js-untar"; +import { gunzipSync } from "fflate"; + +export type VideoPlayerItemType = "mp4" | "cast" | "gua" | "part"; + +export interface VideoPlayerMeta { + account?: string + user?: string + asset?: string + protocol?: string + command_amount?: number + date_end?: string + date_start?: string + duration?: string + files?: VideoPlayerFileMeta[] +} + +export interface VideoPlayerFileMeta { + name?: string + start?: number + end?: number + duration?: number +} + +interface EffectiveItemMeta extends VideoPlayerMeta { + fileStart?: number + fileEnd?: number + fileDuration?: number +} + +export interface VideoPlayerItem { + id: string + name: string + source: string + type: VideoPlayerItemType + meta: VideoPlayerMeta + recordingId: string + recordingLabel: string + partIndex?: number + partTotal?: number + tempPath?: string +} + +interface ParseResult { + items: VideoPlayerItem[] +} + +interface UntarEntry { + name: string + buffer: ArrayBuffer +} + +const REGEXP = /\.(json|replay|cast|part)(\.mp4|\.json|\.gz)?$/; + +function createId(prefix: string) { + return `${prefix}-${Date.now()}-${Math.random().toString(36).slice(2)}`; +} + +function stripArchiveExtension(fileName: string) { + return fileName + .replace(/\.tar$/i, "") + .replace(/\.cast\.gz$/i, "") + .replace(/\.replay\.gz$/i, "") + .replace(/\.part\.gz$/i, "") + .replace(/\.mp4$/i, ""); +} + +function safeParseJson(buffer: ArrayBuffer): VideoPlayerMeta | null { + try { + const text = new TextDecoder("utf-8").decode(new Uint8Array(buffer)); + return JSON.parse(text) as VideoPlayerMeta; + } catch { + return null; + } +} + +function formatMillisDuration(millis?: number) { + if (!millis || millis < 0) return undefined; + + const totalSeconds = Math.floor(millis / 1000); + const hours = Math.floor(totalSeconds / 3600); + const minutes = Math.floor((totalSeconds % 3600) / 60); + const seconds = totalSeconds % 60; + + return `${hours}:${`${minutes}`.padStart(2, "0")}:${`${seconds}`.padStart(2, "0")}`; +} + +function formatTimestamp(millis?: number) { + if (!millis || millis < 0) return undefined; + + const date = new Date(millis); + + if (Number.isNaN(date.getTime())) return undefined; + + const year = date.getFullYear(); + const month = `${date.getMonth() + 1}`.padStart(2, "0"); + const day = `${date.getDate()}`.padStart(2, "0"); + const hours = `${date.getHours()}`.padStart(2, "0"); + const minutes = `${date.getMinutes()}`.padStart(2, "0"); + const seconds = `${date.getSeconds()}`.padStart(2, "0"); + + return `${year}/${month}/${day} ${hours}:${minutes}:${seconds}`; +} + +function resolveItemMeta(meta: VideoPlayerMeta | null, entryName: string): EffectiveItemMeta { + if (!meta) return {}; + + const fileMeta = meta.files?.find(file => file.name === entryName); + + return { + ...meta, + date_start: formatTimestamp(fileMeta?.start) || meta.date_start, + date_end: formatTimestamp(fileMeta?.end) || meta.date_end, + duration: formatMillisDuration(fileMeta?.duration) || meta.duration, + fileStart: fileMeta?.start, + fileEnd: fileMeta?.end, + fileDuration: fileMeta?.duration + }; +} + +function toMp4Url(buffer: ArrayBuffer) { + const blob = new Blob([new Uint8Array(buffer)], { type: "video/mp4" }); + return URL.createObjectURL(blob); +} + +function toCastUrl(buffer: ArrayBuffer) { + const blob = new Blob([buffer], { type: "application/json" }); + return URL.createObjectURL(blob); +} + +function toGzipUrl(buffer: ArrayBuffer) { + const blob = new Blob([buffer], { type: "application/gzip" }); + return URL.createObjectURL(blob); +} + +function withMeta(item: Omit, meta: VideoPlayerMeta | null): VideoPlayerItem { + return { + id: createId(item.name), + meta: meta || {}, + ...item + }; +} + +export function useVideoPlayerParser() { + async function buildItemFromEntry( + entry: UntarEntry, + meta: VideoPlayerMeta | null + ): Promise { + const match = entry.name.match(REGEXP); + const kind = match?.[1]; + const effectiveMeta = resolveItemMeta(meta, entry.name); + + switch (kind) { + case "replay": { + const isGua = entry.name.split(".")[2] === "gz"; + + if (isGua) { + return withMeta( + { + name: entry.name, + source: toGzipUrl(entry.buffer), + type: "gua" + }, + effectiveMeta + ); + } + + return withMeta( + { + name: entry.name, + source: toMp4Url(entry.buffer), + type: "mp4" + }, + effectiveMeta + ); + } + case "cast": { + const output = gunzipSync(new Uint8Array(entry.buffer)); + return withMeta( + { + name: entry.name, + source: toCastUrl(output.buffer.slice(output.byteOffset, output.byteOffset + output.byteLength)), + type: "cast" + }, + effectiveMeta + ); + } + case "part": { + return withMeta( + { + name: entry.name, + source: toGzipUrl(entry.buffer), + type: "part" + }, + effectiveMeta + ); + } + default: + return null; + } + } + + async function parseTarFile(file: File): Promise { + const recordingId = createId(file.name); + const recordingLabel = stripArchiveExtension(file.name); + const extractedFiles = await untar(await file.arrayBuffer()).progress(() => {}); + let meta: VideoPlayerMeta | null = null; + const items: VideoPlayerItem[] = []; + + for (const entry of extractedFiles as UntarEntry[]) { + const match = entry.name.match(REGEXP); + + if (!match) continue; + + if (match[0] === ".replay.json" || match[1] === "json") { + meta = safeParseJson(entry.buffer) || meta; + } + } + + for (const entry of extractedFiles as UntarEntry[]) { + const match = entry.name.match(REGEXP); + + if (!match || match[1] === "json" || match[0] === ".replay.json") continue; + + const item = await buildItemFromEntry(entry, meta); + + if (item) { + items.push({ + ...item, + recordingId, + recordingLabel + }); + } + } + + const hasExplicitParts = (meta?.files?.length || 0) > 1; + + if (hasExplicitParts) { + items.forEach((item, index) => { + if (item.type === "part") { + item.partIndex = index + 1; + item.partTotal = items.length; + } + }); + } + + return { items }; + } + + async function parseSingleFile(file: File): Promise { + const fileName = file.name; + const recordingId = createId(fileName); + const recordingLabel = stripArchiveExtension(fileName); + const items: VideoPlayerItem[] = []; + + if (fileName.endsWith(".mp4")) { + items.push(withMeta({ + name: fileName, + source: toMp4Url(await file.arrayBuffer()), + type: "mp4", + recordingId, + recordingLabel + }, null)); + return { items }; + } + + if (fileName.endsWith(".cast.gz")) { + const output = gunzipSync(new Uint8Array(await file.arrayBuffer())); + items.push(withMeta({ + name: fileName, + source: toCastUrl(output.buffer.slice(output.byteOffset, output.byteOffset + output.byteLength)), + type: "cast", + recordingId, + recordingLabel + }, null)); + return { items }; + } + + if (fileName.endsWith(".replay.gz")) { + items.push(withMeta({ + name: fileName, + source: URL.createObjectURL(file), + type: "gua", + recordingId, + recordingLabel + }, null)); + return { items }; + } + + if (fileName.endsWith(".part.gz")) { + items.push(withMeta({ + name: fileName, + source: URL.createObjectURL(file), + type: "part", + recordingId, + recordingLabel + }, null)); + return { items }; + } + + return { items }; + } + + async function parseFiles(files: File[]) { + const items: VideoPlayerItem[] = []; + + for (const file of files) { + const result = file.name.includes(".tar") + ? await parseTarFile(file) + : await parseSingleFile(file); + items.push(...result.items); + } + + return items; + } + + return { + parseFiles + }; +} diff --git a/ui/composables/useVideoPlayerTauri.ts b/ui/composables/useVideoPlayerTauri.ts new file mode 100644 index 000000000..214023bf1 --- /dev/null +++ b/ui/composables/useVideoPlayerTauri.ts @@ -0,0 +1,54 @@ +interface StreamHandlers { + onChunk: (chunk: string) => void + onEnd: () => void + onError: (message: string) => void +} + +export function useVideoPlayerTauri() { + const invoke = useTauriCoreInvoke; + const listen = useTauriEventListen; + + async function writeGzipFile(buffer: ArrayBuffer, fileName: string) { + return await invoke("write_video_player_gzip_file", { + buffer: Array.from(new Uint8Array(buffer)), + fileName + }); + } + + async function deleteTempFile(filePath: string) { + await invoke("delete_video_player_file", { filePath }); + } + + async function streamTextFile(filePath: string, handlers: StreamHandlers) { + const eventId = `${Date.now()}-${Math.random().toString(36).slice(2)}`; + const chunkEvent = `videoplayer://${eventId}/chunk`; + const endEvent = `videoplayer://${eventId}/end`; + const errorEvent = `videoplayer://${eventId}/error`; + + const unlistenChunk = await listen(chunkEvent, (event: any) => { + handlers.onChunk(event.payload?.chunk || ""); + }); + const unlistenEnd = await listen(endEvent, () => { + handlers.onEnd(); + }); + const unlistenError = await listen(errorEvent, (event: any) => { + handlers.onError(event.payload?.message || "Unknown stream error"); + }); + + try { + await invoke("read_video_player_text_stream", { eventId, filePath }); + } catch (error: any) { + handlers.onError(error?.toString?.() || String(error)); + } finally { + unlistenChunk(); + unlistenEnd(); + unlistenError(); + } + } + + return { + writeGzipFile, + deleteTempFile, + streamTextFile + }; +} diff --git a/ui/pages/videoplayer.vue b/ui/pages/videoplayer.vue new file mode 100644 index 000000000..4421113af --- /dev/null +++ b/ui/pages/videoplayer.vue @@ -0,0 +1,355 @@ + + + + + diff --git a/ui/types/video-player.d.ts b/ui/types/video-player.d.ts new file mode 100644 index 000000000..20d7e6302 --- /dev/null +++ b/ui/types/video-player.d.ts @@ -0,0 +1,17 @@ +declare module "js-untar" { + interface UntarEntry { + name: string + buffer: ArrayBuffer + } + + interface UntarResult extends Promise { + progress(callback: (percent: number) => void): UntarResult + } + + export default function untar(buffer: ArrayBuffer): UntarResult +} + +declare module "guacamole-common-js-jumpserver/dist/guacamole-common" { + const Guacamole: any + export = Guacamole +}