├── .github └── workflows │ ├── arm64.env │ ├── mipsel.env │ ├── openwrt_ipk.yaml │ └── x64.env ├── .gitignore ├── README.md ├── luci-app-tailscaler ├── Makefile ├── luasrc │ ├── controller │ │ └── tailscaler.lua │ └── view │ │ └── tailscaler │ │ └── main.htm └── root │ └── etc │ ├── config │ └── tailscaler │ ├── init.d │ └── tailscaler │ └── uci-defaults │ └── 50_luci-tailscaler └── web ├── .gitignore ├── .vscode └── extensions.json ├── README.md ├── index.html ├── package.json ├── pnpm-lock.yaml ├── src ├── App.vue ├── main.ts ├── types │ └── response.d.ts └── vite-env.d.ts ├── tsconfig.json ├── tsconfig.node.json └── vite.config.ts /.github/workflows/arm64.env: -------------------------------------------------------------------------------- 1 | SDK_NAME=openwrt-sdk-21.02.3-mvebu-cortexa53_gcc-8.4.0_musl.Linux-x86_64 2 | SDK_URL=https://downloads.openwrt.org/releases/21.02.3/targets/mvebu/cortexa53/ 3 | SDK_ARCH=aarch64_cortex-a53 4 | -------------------------------------------------------------------------------- /.github/workflows/mipsel.env: -------------------------------------------------------------------------------- 1 | SDK_NAME=openwrt-sdk-21.02.3-ramips-mt7620_gcc-8.4.0_musl.Linux-x86_64 2 | SDK_URL=https://downloads.openwrt.org/releases/21.02.3/targets/ramips/mt7620/ 3 | SDK_ARCH=mipsel_24kc 4 | -------------------------------------------------------------------------------- /.github/workflows/openwrt_ipk.yaml: -------------------------------------------------------------------------------- 1 | # 工作流程的名字 2 | # https://github.com/subosito/flutter-action 3 | name: build Openwrt Ipk 4 | on: 5 | workflow_dispatch: 6 | inputs: 7 | version: 8 | description: "ipk version" 9 | required: true 10 | default: "0.0.1" 11 | target: 12 | description: 'build target ["arm64", "x64", "mipsel"]' 13 | required: true 14 | default: "x64" 15 | env: 16 | TZ: Asia/Shanghai 17 | IPK_NAME: luci-app-tailscaler 18 | 19 | permissions: 20 | contents: write 21 | # 这个工作流程需要执行的任务 22 | jobs: 23 | matrix: 24 | runs-on: ubuntu-latest 25 | outputs: 26 | matrix: ${{ steps.set-matrix.outputs.matrix }} 27 | steps: 28 | - name: Detect build target 29 | id: set-matrix 30 | env: 31 | MATRIX_TARGET: ${{ github.event.inputs.target }} 32 | run: | 33 | if [ "x${MATRIX_TARGET}" = "x" -o "x${MATRIX_TARGET}" = "xall" ]; then \ 34 | echo "matrix={\"target\":[\"arm64\", \"x64\", \"mipsel\"]}" >> $GITHUB_OUTPUT; \ 35 | else \ 36 | targets=""; \ 37 | for target in ${MATRIX_TARGET}; do \ 38 | targets="$targets, \"$target\""; 39 | done; \ 40 | echo "matrix={\"target\":[${targets#, }]}" >> $GITHUB_OUTPUT; \ 41 | fi 42 | build: 43 | needs: matrix 44 | runs-on: ubuntu-latest 45 | name: Build IPKs for ${{ matrix.target }} 46 | strategy: 47 | matrix: ${{fromJson(needs.matrix.outputs.matrix)}} 48 | steps: 49 | # 拉取项目代码 50 | - name: Checkout the code 51 | uses: actions/checkout@v3 52 | 53 | # 安装 nodejs 54 | # https://github.com/marketplace/actions/setup-node-js-environment 55 | - name: Setup Nodejs 56 | uses: actions/setup-node@v3 57 | 58 | # 安装 pnpm 59 | - run: | 60 | npm install pnpm -g 61 | 62 | # 打包 web 项目 63 | - run: | 64 | cd web 65 | pnpm install 66 | pnpm run build 67 | 68 | # 复制 web至 luci 69 | - run: | 70 | mkdir ${IPK_NAME}/htdocs 71 | cp -r web/dist/luci-static ${IPK_NAME}/htdocs 72 | 73 | # 写入版本号 74 | - run: | 75 | sed -i 's/PKG_VERSION:=\(.*\)/PKG_VERSION:=${{ inputs.version }}/g' ${IPK_NAME}/Makefile 76 | 77 | - name: Import Env 78 | env: 79 | MATRIX_TARGET: ${{ matrix.target }} 80 | run: cat .github/workflows/${MATRIX_TARGET}.env >> "$GITHUB_ENV" 81 | 82 | - name: Initialization environment 83 | env: 84 | DEBIAN_FRONTEND: noninteractive 85 | run: | 86 | sudo swapoff -a || true 87 | sudo rm -rf /swapfile /etc/apt/sources.list.d/* /usr/share/dotnet /usr/local/lib/android /opt/ghc 88 | sudo -E apt-get -qq update 89 | sudo -E apt-get -qq install build-essential asciidoc binutils bzip2 gawk gettext git libncurses5-dev libz-dev patch unzip zlib1g-dev libc6-dev-i386 subversion flex uglifyjs gcc-multilib g++-multilib p7zip p7zip-full msmtp libssl-dev texinfo libglib2.0-dev xmlto qemu-utils upx libelf-dev autoconf automake libtool autopoint device-tree-compiler antlr3 gperf rsync 90 | sudo -E apt-get -qq autoremove --purge 91 | sudo -E apt-get -qq clean 92 | sudo timedatectl set-timezone "$TZ" 93 | # curl -fsSL https://raw.githubusercontent.com/P3TERX/dotfiles/master/.bashrc >> ~/.bashrc 94 | 95 | - name: Download SDK 96 | run: wget ${SDK_URL}${SDK_NAME}.tar.xz 97 | 98 | - name: Unpack SDK 99 | run: | 100 | mkdir ~/openwrt-sdk 101 | tar -xJf `pwd`/${SDK_NAME}.tar.xz --strip-components=1 -C ~/openwrt-sdk 102 | ln -s ${HOME}/openwrt-sdk ${SDK_NAME} 103 | 104 | - name: Clean Code 105 | run: | 106 | rm -f ${SDK_NAME}/package/linux/modules/* 107 | rm -f ${SDK_NAME}/package/kernel/linux/modules/* 108 | grep -lFr '$(call KernelPackage,' ${SDK_NAME}/package/linux | xargs -rn1 sed -i 's/ FILES:=/ XFILES:=/g' || true 109 | grep -lFr '$(call KernelPackage,' ${SDK_NAME}/package/kernel | xargs -rn1 sed -i 's/ FILES:=/ XFILES:=/g' || true 110 | find ${SDK_NAME}/target/linux -name 'modules.mk' | xargs -rn1 sed -i 's/ FILES:=/ XFILES:=/g' || true 111 | grep 'src-git base ' ${SDK_NAME}/feeds.conf.default > ${SDK_NAME}/feeds.conf 112 | grep 'src-git luci ' ${SDK_NAME}/feeds.conf.default >> ${SDK_NAME}/feeds.conf 113 | 114 | - name: Write Config 115 | run: | 116 | cat <${SDK_NAME}/.config 117 | # CONFIG_SIGNED_PACKAGES is not set 118 | CONFIG_LUCI_LANG_zh_Hans=y 119 | CONFIG_LUCI_LANG_zh-cn=y 120 | EOF 121 | 122 | - name: Defconfig 123 | id: defconfig 124 | run: | 125 | cd ~/openwrt-sdk 126 | make defconfig 127 | sed -i 's/^CONFIG_PACKAGE_\(.*\)=m$/# CONFIG_PACKAGE_\1 is not set/' .config 128 | grep '^CONFIG_PACKAGE_' .config 129 | echo "status=success" >> $GITHUB_OUTPUT 130 | 131 | - name: "Install" 132 | run: | 133 | cd ~/openwrt-sdk 134 | ./scripts/feeds update -a 135 | ./scripts/feeds install -a 136 | 137 | - name: Copy Package 138 | run: | 139 | cp -r ./${IPK_NAME}/ ~/openwrt-sdk/package 140 | 141 | - name: Build IPK 142 | run: | 143 | cd ~/openwrt-sdk 144 | chmod 755 -R ./package/${IPK_NAME} 145 | make -j16 V=s package/${IPK_NAME}/compile 146 | 147 | # 提交下载 148 | - name: Upload bin directory 149 | uses: actions/upload-artifact@main 150 | with: 151 | name: ${{ env.SDK_ARCH }} 152 | path: ${{ env.SDK_NAME }}/bin/packages/${{ env.SDK_ARCH }} 153 | -------------------------------------------------------------------------------- /.github/workflows/x64.env: -------------------------------------------------------------------------------- 1 | SDK_NAME=openwrt-sdk-21.02.3-x86-64_gcc-8.4.0_musl.Linux-x86_64 2 | SDK_URL=https://downloads.openwrt.org/releases/21.02.3/targets/x86/64/ 3 | SDK_ARCH=x86_64 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # openwrt-tailscale 2 | github action build IPK 3 | 4 | https://openwrt.org/docs/guide-user/services/vpn/tailscale/start 的调用 5 | 因为 tailscale 占用了 config/tailscale 和 init.d/tailscale , 故使用 luci-app-tailscaler 作为插件名称 6 | 7 | 启动任务 8 | ``` 9 | /etc/init.d/tailscaler start 10 | ``` 11 | 关闭任务 12 | ``` 13 | /etc/init.d/tailscaler stop 14 | ``` 15 | -------------------------------------------------------------------------------- /luci-app-tailscaler/Makefile: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (C) 2008-2014 The LuCI Team 3 | # 4 | # This is free software, licensed under the Apache License, Version 2.0 . 5 | # 6 | 7 | include $(TOPDIR)/rules.mk 8 | 9 | LUCI_TITLE:=LuCI support for tailscale 10 | PKG_VERSION:=0.0.1 11 | LUCI_DEPENDS:=+tailscale 12 | 13 | define Package/luci-app-tailscaler/conffiles 14 | /etc/config/tailscaler 15 | endef 16 | 17 | include $(TOPDIR)/feeds/luci/luci.mk 18 | 19 | # call BuildPackage - OpenWrt buildroot signature -------------------------------------------------------------------------------- /luci-app-tailscaler/luasrc/controller/tailscaler.lua: -------------------------------------------------------------------------------- 1 | local http = require "luci.http" 2 | module("luci.controller.tailscaler", package.seeall) 3 | 4 | function index() 5 | if not nixio.fs.access("/etc/config/tailscaler") then 6 | return 7 | end 8 | entry({"admin", "services", "tailscaler"}, call("tailscale_template"), _("Tailscale"), 21).dependent = true 9 | entry({"admin", "services", "tailscaler", "config"}, call("tailscale_config")) 10 | entry({"admin", "services", "tailscaler", "status"}, call("tailscale_status")) 11 | entry({"admin", "services", "tailscaler", "logout"}, call("tailscale_logout")) 12 | end 13 | 14 | function tailscale_template() 15 | luci.template.render("tailscaler/main") 16 | end 17 | 18 | 19 | function getTailscaleConfig() 20 | local uci = require "luci.model.uci".cursor() 21 | local enabled = uci:get_first("tailscaler", "settings", "enabled") 22 | local acceptRoutes = uci:get_first("tailscaler", "settings", "acceptRoutes") 23 | local hostname = uci:get_first("tailscaler", "settings", "hostname") 24 | local advertiseRoutes = uci:get_first("tailscaler", "settings", "advertiseRoutes") 25 | local loginServer = uci:get_first("tailscaler", "settings", "loginServer") 26 | local authkey = uci:get_first("tailscaler", "settings", "authkey") 27 | local result = { 28 | enabled = (enabled == "1"), 29 | acceptRoutes = (acceptRoutes == "1"), 30 | advertiseRoutes = advertiseRoutes, 31 | hostname = hostname, 32 | loginServer = loginServer, 33 | authkey = authkey, 34 | } 35 | return result 36 | end 37 | 38 | function submitTailscaleConfig(req) 39 | local uci = require "luci.model.uci".cursor() 40 | -- enabled 41 | if req.enabled ~= nil then 42 | uci:set("tailscaler","@settings[0]","enabled",req.enabled) 43 | end 44 | -- login server url 45 | if req.loginServer ~= nil then 46 | uci:set("tailscaler","@settings[0]","loginServer",req.loginServer) 47 | end 48 | -- authkey 49 | if req.authkey ~= nil then 50 | uci:set("tailscaler","@settings[0]","authkey",req.authkey) 51 | end 52 | -- hostname 53 | if req.hostname ~= nil then 54 | uci:set("tailscaler","@settings[0]","hostname",req.hostname) 55 | end 56 | -- acceptRoutes 57 | if req.acceptRoutes ~= nil then 58 | uci:set("tailscaler","@settings[0]","acceptRoutes",req.acceptRoutes) 59 | end 60 | -- advertiseRoutes 61 | if req.acceptRoutes ~= nil then 62 | uci:set("tailscaler","@settings[0]","advertiseRoutes",req.advertiseRoutes) 63 | end 64 | uci:commit("tailscaler") 65 | end 66 | 67 | function tailscale_config() 68 | local http = require "luci.http" 69 | http.prepare_content("application/json") 70 | local method = http.getenv("REQUEST_METHOD") 71 | if method == "post" or method == "POST" then 72 | local content = http.content() 73 | local jsonc = require "luci.jsonc" 74 | local json_parse = jsonc.parse 75 | local req = json_parse(content) 76 | if req == nil or next(req) == nil then 77 | luci.http.write_json({ 78 | error = "invalid request" 79 | }) 80 | return 81 | end 82 | submitTailscaleConfig(req) 83 | if req.enabled == true then 84 | luci.util.exec("/etc/init.d/tailscaler start") 85 | else 86 | luci.util.exec("/etc/init.d/tailscaler stop") 87 | end 88 | end 89 | local response = getTailscaleConfig() 90 | luci.http.write_json(response) 91 | end 92 | 93 | function tailscale_status() 94 | local sys = require "luci.sys" 95 | local http = require "luci.http" 96 | -- http.prepare_content("text/plain;charset=utf-8") 97 | http.prepare_content("application/json") 98 | local text = sys.exec("tailscale status --json") 99 | http.write(text) 100 | end 101 | 102 | function tailscale_logout() 103 | local sys = require "luci.sys" 104 | local http = require "luci.http" 105 | http.prepare_content("application/json") 106 | local text = sys.exec("tailscale logout") 107 | http.write(text) 108 | end -------------------------------------------------------------------------------- /luci-app-tailscaler/luasrc/view/tailscaler/main.htm: -------------------------------------------------------------------------------- 1 | <%+header%> 2 |
3 | 4 | 5 | <%+footer%> -------------------------------------------------------------------------------- /luci-app-tailscaler/root/etc/config/tailscaler: -------------------------------------------------------------------------------- 1 | config settings 2 | option enabled '0' 3 | option acceptRoutes '0' 4 | option advertiseRoutes '' 5 | option hostname '' 6 | option loginServer '' 7 | option authKey '' 8 | -------------------------------------------------------------------------------- /luci-app-tailscaler/root/etc/init.d/tailscaler: -------------------------------------------------------------------------------- 1 | #!/bin/sh /etc/rc.common 2 | 3 | START=95 4 | USE_PROCD=1 5 | 6 | 7 | get_config() { 8 | config_get_bool enabled $1 enabled 1 9 | config_get_bool acceptRoutes $1 acceptRoutes 0 10 | config_get loginServer $1 loginServer "" 11 | config_get authkey $1 authkey "" 12 | config_get hostname $1 hostname "" 13 | config_get advertiseRoutes $1 advertiseRoutes "" 14 | } 15 | start_service() { 16 | logger -t tailscaler 'start_service' 17 | config_load tailscaler 18 | config_foreach get_config settings 19 | if [ "$enabled" != 1 ]; then 20 | stop_service 21 | return 1 22 | fi 23 | # 24 | logger -t tailscaler 'start tailscale' 25 | /etc/init.d/tailscale running || /etc/init.d/tailscale start 26 | logger -t tailscaler 'start tailscaler' 27 | # 28 | procd_open_instance 29 | procd_set_param command /usr/sbin/tailscale up --reset 30 | if [ -n "$loginServer" ]; then 31 | procd_append_param command --login-server "$loginServer" 32 | fi 33 | if [ -n "$authkey" ]; then 34 | procd_append_param command --authkey "$authkey" 35 | fi 36 | if [ -n "$hostname" ]; then 37 | procd_append_param command --hostname "$hostname" 38 | fi 39 | if [ "$acceptRoutes" = 1 ]; then 40 | procd_append_param command --accept-routes 41 | fi 42 | if [ -n "$advertiseRoutes" ];then 43 | procd_append_param command --advertise-routes "$advertiseRoutes" 44 | fi 45 | procd_set_param stdout 1 46 | procd_set_param stderr 1 47 | procd_close_instance 48 | logger -t tailscaler 'end tailscaler' 49 | } 50 | stop_service() { 51 | /etc/init.d/tailscale stop 52 | /etc/init.d/tailscale running && sleep 2 53 | } 54 | -------------------------------------------------------------------------------- /luci-app-tailscaler/root/etc/uci-defaults/50_luci-tailscaler: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | rm -f /tmp/luci-indexcache 3 | exit 0 4 | 5 | -------------------------------------------------------------------------------- /web/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /web/.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": ["Vue.volar", "Vue.vscode-typescript-vue-plugin"] 3 | } 4 | -------------------------------------------------------------------------------- /web/README.md: -------------------------------------------------------------------------------- 1 | # Vue 3 + TypeScript + Vite 2 | 3 | This template should help get you started developing with Vue 3 and TypeScript in Vite. The template uses Vue 3 ` 12 | 13 | 14 | -------------------------------------------------------------------------------- /web/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "web", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "vue-tsc && vite build", 9 | "preview": "vite preview" 10 | }, 11 | "dependencies": { 12 | "vue": "^3.3.4" 13 | }, 14 | "devDependencies": { 15 | "@vitejs/plugin-vue": "^4.2.3", 16 | "typescript": "^5.0.2", 17 | "vite": "^4.4.5", 18 | "vue-tsc": "^1.8.5" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /web/pnpm-lock.yaml: -------------------------------------------------------------------------------- 1 | lockfileVersion: '6.0' 2 | 3 | dependencies: 4 | vue: 5 | specifier: ^3.3.4 6 | version: 3.3.4 7 | 8 | devDependencies: 9 | '@vitejs/plugin-vue': 10 | specifier: ^4.2.3 11 | version: 4.2.3(vite@4.4.5)(vue@3.3.4) 12 | typescript: 13 | specifier: ^5.0.2 14 | version: 5.0.2 15 | vite: 16 | specifier: ^4.4.5 17 | version: 4.4.5 18 | vue-tsc: 19 | specifier: ^1.8.5 20 | version: 1.8.5(typescript@5.0.2) 21 | 22 | packages: 23 | 24 | /@babel/helper-string-parser@7.22.5: 25 | resolution: {integrity: sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==} 26 | engines: {node: '>=6.9.0'} 27 | 28 | /@babel/helper-validator-identifier@7.22.20: 29 | resolution: {integrity: sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==} 30 | engines: {node: '>=6.9.0'} 31 | 32 | /@babel/parser@7.23.0: 33 | resolution: {integrity: sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw==} 34 | engines: {node: '>=6.0.0'} 35 | hasBin: true 36 | dependencies: 37 | '@babel/types': 7.23.0 38 | 39 | /@babel/types@7.23.0: 40 | resolution: {integrity: sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg==} 41 | engines: {node: '>=6.9.0'} 42 | dependencies: 43 | '@babel/helper-string-parser': 7.22.5 44 | '@babel/helper-validator-identifier': 7.22.20 45 | to-fast-properties: 2.0.0 46 | 47 | /@esbuild/android-arm64@0.18.20: 48 | resolution: {integrity: sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==} 49 | engines: {node: '>=12'} 50 | cpu: [arm64] 51 | os: [android] 52 | requiresBuild: true 53 | dev: true 54 | optional: true 55 | 56 | /@esbuild/android-arm@0.18.20: 57 | resolution: {integrity: sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==} 58 | engines: {node: '>=12'} 59 | cpu: [arm] 60 | os: [android] 61 | requiresBuild: true 62 | dev: true 63 | optional: true 64 | 65 | /@esbuild/android-x64@0.18.20: 66 | resolution: {integrity: sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==} 67 | engines: {node: '>=12'} 68 | cpu: [x64] 69 | os: [android] 70 | requiresBuild: true 71 | dev: true 72 | optional: true 73 | 74 | /@esbuild/darwin-arm64@0.18.20: 75 | resolution: {integrity: sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==} 76 | engines: {node: '>=12'} 77 | cpu: [arm64] 78 | os: [darwin] 79 | requiresBuild: true 80 | dev: true 81 | optional: true 82 | 83 | /@esbuild/darwin-x64@0.18.20: 84 | resolution: {integrity: sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==} 85 | engines: {node: '>=12'} 86 | cpu: [x64] 87 | os: [darwin] 88 | requiresBuild: true 89 | dev: true 90 | optional: true 91 | 92 | /@esbuild/freebsd-arm64@0.18.20: 93 | resolution: {integrity: sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==} 94 | engines: {node: '>=12'} 95 | cpu: [arm64] 96 | os: [freebsd] 97 | requiresBuild: true 98 | dev: true 99 | optional: true 100 | 101 | /@esbuild/freebsd-x64@0.18.20: 102 | resolution: {integrity: sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==} 103 | engines: {node: '>=12'} 104 | cpu: [x64] 105 | os: [freebsd] 106 | requiresBuild: true 107 | dev: true 108 | optional: true 109 | 110 | /@esbuild/linux-arm64@0.18.20: 111 | resolution: {integrity: sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==} 112 | engines: {node: '>=12'} 113 | cpu: [arm64] 114 | os: [linux] 115 | requiresBuild: true 116 | dev: true 117 | optional: true 118 | 119 | /@esbuild/linux-arm@0.18.20: 120 | resolution: {integrity: sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==} 121 | engines: {node: '>=12'} 122 | cpu: [arm] 123 | os: [linux] 124 | requiresBuild: true 125 | dev: true 126 | optional: true 127 | 128 | /@esbuild/linux-ia32@0.18.20: 129 | resolution: {integrity: sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==} 130 | engines: {node: '>=12'} 131 | cpu: [ia32] 132 | os: [linux] 133 | requiresBuild: true 134 | dev: true 135 | optional: true 136 | 137 | /@esbuild/linux-loong64@0.18.20: 138 | resolution: {integrity: sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==} 139 | engines: {node: '>=12'} 140 | cpu: [loong64] 141 | os: [linux] 142 | requiresBuild: true 143 | dev: true 144 | optional: true 145 | 146 | /@esbuild/linux-mips64el@0.18.20: 147 | resolution: {integrity: sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==} 148 | engines: {node: '>=12'} 149 | cpu: [mips64el] 150 | os: [linux] 151 | requiresBuild: true 152 | dev: true 153 | optional: true 154 | 155 | /@esbuild/linux-ppc64@0.18.20: 156 | resolution: {integrity: sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==} 157 | engines: {node: '>=12'} 158 | cpu: [ppc64] 159 | os: [linux] 160 | requiresBuild: true 161 | dev: true 162 | optional: true 163 | 164 | /@esbuild/linux-riscv64@0.18.20: 165 | resolution: {integrity: sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==} 166 | engines: {node: '>=12'} 167 | cpu: [riscv64] 168 | os: [linux] 169 | requiresBuild: true 170 | dev: true 171 | optional: true 172 | 173 | /@esbuild/linux-s390x@0.18.20: 174 | resolution: {integrity: sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==} 175 | engines: {node: '>=12'} 176 | cpu: [s390x] 177 | os: [linux] 178 | requiresBuild: true 179 | dev: true 180 | optional: true 181 | 182 | /@esbuild/linux-x64@0.18.20: 183 | resolution: {integrity: sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==} 184 | engines: {node: '>=12'} 185 | cpu: [x64] 186 | os: [linux] 187 | requiresBuild: true 188 | dev: true 189 | optional: true 190 | 191 | /@esbuild/netbsd-x64@0.18.20: 192 | resolution: {integrity: sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==} 193 | engines: {node: '>=12'} 194 | cpu: [x64] 195 | os: [netbsd] 196 | requiresBuild: true 197 | dev: true 198 | optional: true 199 | 200 | /@esbuild/openbsd-x64@0.18.20: 201 | resolution: {integrity: sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==} 202 | engines: {node: '>=12'} 203 | cpu: [x64] 204 | os: [openbsd] 205 | requiresBuild: true 206 | dev: true 207 | optional: true 208 | 209 | /@esbuild/sunos-x64@0.18.20: 210 | resolution: {integrity: sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==} 211 | engines: {node: '>=12'} 212 | cpu: [x64] 213 | os: [sunos] 214 | requiresBuild: true 215 | dev: true 216 | optional: true 217 | 218 | /@esbuild/win32-arm64@0.18.20: 219 | resolution: {integrity: sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==} 220 | engines: {node: '>=12'} 221 | cpu: [arm64] 222 | os: [win32] 223 | requiresBuild: true 224 | dev: true 225 | optional: true 226 | 227 | /@esbuild/win32-ia32@0.18.20: 228 | resolution: {integrity: sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==} 229 | engines: {node: '>=12'} 230 | cpu: [ia32] 231 | os: [win32] 232 | requiresBuild: true 233 | dev: true 234 | optional: true 235 | 236 | /@esbuild/win32-x64@0.18.20: 237 | resolution: {integrity: sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==} 238 | engines: {node: '>=12'} 239 | cpu: [x64] 240 | os: [win32] 241 | requiresBuild: true 242 | dev: true 243 | optional: true 244 | 245 | /@jridgewell/sourcemap-codec@1.4.15: 246 | resolution: {integrity: sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==} 247 | 248 | /@vitejs/plugin-vue@4.2.3(vite@4.4.5)(vue@3.3.4): 249 | resolution: {integrity: sha512-R6JDUfiZbJA9cMiguQ7jxALsgiprjBeHL5ikpXfJCH62pPHtI+JdJ5xWj6Ev73yXSlYl86+blXn1kZHQ7uElxw==} 250 | engines: {node: ^14.18.0 || >=16.0.0} 251 | peerDependencies: 252 | vite: ^4.0.0 253 | vue: ^3.2.25 254 | dependencies: 255 | vite: 4.4.5 256 | vue: 3.3.4 257 | dev: true 258 | 259 | /@volar/language-core@1.9.2: 260 | resolution: {integrity: sha512-9GTes/IUPOl0YoV5RQWhCP5a4EDFFfJZGwZn1xA5ug1FO0G6GOVoJI6tQatujtcQmDOQlOM5/0NewnlumygPkQ==} 261 | dependencies: 262 | '@volar/source-map': 1.9.2 263 | dev: true 264 | 265 | /@volar/source-map@1.9.2: 266 | resolution: {integrity: sha512-rYTvV/HMf2CSRkd6oiVxcjX4rnSxEsVfJmw1KTmD4VTBXlz1+b16VIysQX4+1p/eZd2TyCeFblyylIxbZ+YOGg==} 267 | dependencies: 268 | muggle-string: 0.3.1 269 | dev: true 270 | 271 | /@volar/typescript@1.9.2: 272 | resolution: {integrity: sha512-l4DA+S3ZVOWGACDdRNVSYZ41nuTWOH8OMS/yVeFV2fTmr/IuD37+3wzzGnjIPwCUa0w+fpg8vJPalzYetmlFTQ==} 273 | dependencies: 274 | '@volar/language-core': 1.9.2 275 | dev: true 276 | 277 | /@vue/compiler-core@3.3.4: 278 | resolution: {integrity: sha512-cquyDNvZ6jTbf/+x+AgM2Arrp6G4Dzbb0R64jiG804HRMfRiFXWI6kqUVqZ6ZR0bQhIoQjB4+2bhNtVwndW15g==} 279 | dependencies: 280 | '@babel/parser': 7.23.0 281 | '@vue/shared': 3.3.4 282 | estree-walker: 2.0.2 283 | source-map-js: 1.0.2 284 | 285 | /@vue/compiler-dom@3.3.4: 286 | resolution: {integrity: sha512-wyM+OjOVpuUukIq6p5+nwHYtj9cFroz9cwkfmP9O1nzH68BenTTv0u7/ndggT8cIQlnBeOo6sUT/gvHcIkLA5w==} 287 | dependencies: 288 | '@vue/compiler-core': 3.3.4 289 | '@vue/shared': 3.3.4 290 | 291 | /@vue/compiler-sfc@3.3.4: 292 | resolution: {integrity: sha512-6y/d8uw+5TkCuzBkgLS0v3lSM3hJDntFEiUORM11pQ/hKvkhSKZrXW6i69UyXlJQisJxuUEJKAWEqWbWsLeNKQ==} 293 | dependencies: 294 | '@babel/parser': 7.23.0 295 | '@vue/compiler-core': 3.3.4 296 | '@vue/compiler-dom': 3.3.4 297 | '@vue/compiler-ssr': 3.3.4 298 | '@vue/reactivity-transform': 3.3.4 299 | '@vue/shared': 3.3.4 300 | estree-walker: 2.0.2 301 | magic-string: 0.30.4 302 | postcss: 8.4.31 303 | source-map-js: 1.0.2 304 | 305 | /@vue/compiler-ssr@3.3.4: 306 | resolution: {integrity: sha512-m0v6oKpup2nMSehwA6Uuu+j+wEwcy7QmwMkVNVfrV9P2qE5KshC6RwOCq8fjGS/Eak/uNb8AaWekfiXxbBB6gQ==} 307 | dependencies: 308 | '@vue/compiler-dom': 3.3.4 309 | '@vue/shared': 3.3.4 310 | 311 | /@vue/language-core@1.8.5(typescript@5.0.2): 312 | resolution: {integrity: sha512-DKQNiNQzNV7nrkZQujvjfX73zqKdj2+KoM4YeKl+ft3f+crO3JB4ycPnmgaRMNX/ULJootdQPGHKFRl5cXxwaw==} 313 | peerDependencies: 314 | typescript: '*' 315 | peerDependenciesMeta: 316 | typescript: 317 | optional: true 318 | dependencies: 319 | '@volar/language-core': 1.9.2 320 | '@volar/source-map': 1.9.2 321 | '@vue/compiler-dom': 3.3.4 322 | '@vue/reactivity': 3.3.4 323 | '@vue/shared': 3.3.4 324 | minimatch: 9.0.3 325 | muggle-string: 0.3.1 326 | typescript: 5.0.2 327 | vue-template-compiler: 2.7.14 328 | dev: true 329 | 330 | /@vue/reactivity-transform@3.3.4: 331 | resolution: {integrity: sha512-MXgwjako4nu5WFLAjpBnCj/ieqcjE2aJBINUNQzkZQfzIZA4xn+0fV1tIYBJvvva3N3OvKGofRLvQIwEQPpaXw==} 332 | dependencies: 333 | '@babel/parser': 7.23.0 334 | '@vue/compiler-core': 3.3.4 335 | '@vue/shared': 3.3.4 336 | estree-walker: 2.0.2 337 | magic-string: 0.30.4 338 | 339 | /@vue/reactivity@3.3.4: 340 | resolution: {integrity: sha512-kLTDLwd0B1jG08NBF3R5rqULtv/f8x3rOFByTDz4J53ttIQEDmALqKqXY0J+XQeN0aV2FBxY8nJDf88yvOPAqQ==} 341 | dependencies: 342 | '@vue/shared': 3.3.4 343 | 344 | /@vue/runtime-core@3.3.4: 345 | resolution: {integrity: sha512-R+bqxMN6pWO7zGI4OMlmvePOdP2c93GsHFM/siJI7O2nxFRzj55pLwkpCedEY+bTMgp5miZ8CxfIZo3S+gFqvA==} 346 | dependencies: 347 | '@vue/reactivity': 3.3.4 348 | '@vue/shared': 3.3.4 349 | 350 | /@vue/runtime-dom@3.3.4: 351 | resolution: {integrity: sha512-Aj5bTJ3u5sFsUckRghsNjVTtxZQ1OyMWCr5dZRAPijF/0Vy4xEoRCwLyHXcj4D0UFbJ4lbx3gPTgg06K/GnPnQ==} 352 | dependencies: 353 | '@vue/runtime-core': 3.3.4 354 | '@vue/shared': 3.3.4 355 | csstype: 3.1.2 356 | 357 | /@vue/server-renderer@3.3.4(vue@3.3.4): 358 | resolution: {integrity: sha512-Q6jDDzR23ViIb67v+vM1Dqntu+HUexQcsWKhhQa4ARVzxOY2HbC7QRW/ggkDBd5BU+uM1sV6XOAP0b216o34JQ==} 359 | peerDependencies: 360 | vue: 3.3.4 361 | dependencies: 362 | '@vue/compiler-ssr': 3.3.4 363 | '@vue/shared': 3.3.4 364 | vue: 3.3.4 365 | 366 | /@vue/shared@3.3.4: 367 | resolution: {integrity: sha512-7OjdcV8vQ74eiz1TZLzZP4JwqM5fA94K6yntPS5Z25r9HDuGNzaGdgvwKYq6S+MxwF0TFRwe50fIR/MYnakdkQ==} 368 | 369 | /@vue/typescript@1.8.5(typescript@5.0.2): 370 | resolution: {integrity: sha512-domFBbNr3PEcjGBeB+cmgUM3cI6pJsJezguIUKZ1rphkfIkICyoMjCd3TitoP32yo2KABLiaXcGFzgFfQf6B3w==} 371 | dependencies: 372 | '@volar/typescript': 1.9.2 373 | '@vue/language-core': 1.8.5(typescript@5.0.2) 374 | transitivePeerDependencies: 375 | - typescript 376 | dev: true 377 | 378 | /balanced-match@1.0.2: 379 | resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} 380 | dev: true 381 | 382 | /brace-expansion@2.0.1: 383 | resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==} 384 | dependencies: 385 | balanced-match: 1.0.2 386 | dev: true 387 | 388 | /csstype@3.1.2: 389 | resolution: {integrity: sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==} 390 | 391 | /de-indent@1.0.2: 392 | resolution: {integrity: sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg==} 393 | dev: true 394 | 395 | /esbuild@0.18.20: 396 | resolution: {integrity: sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==} 397 | engines: {node: '>=12'} 398 | hasBin: true 399 | requiresBuild: true 400 | optionalDependencies: 401 | '@esbuild/android-arm': 0.18.20 402 | '@esbuild/android-arm64': 0.18.20 403 | '@esbuild/android-x64': 0.18.20 404 | '@esbuild/darwin-arm64': 0.18.20 405 | '@esbuild/darwin-x64': 0.18.20 406 | '@esbuild/freebsd-arm64': 0.18.20 407 | '@esbuild/freebsd-x64': 0.18.20 408 | '@esbuild/linux-arm': 0.18.20 409 | '@esbuild/linux-arm64': 0.18.20 410 | '@esbuild/linux-ia32': 0.18.20 411 | '@esbuild/linux-loong64': 0.18.20 412 | '@esbuild/linux-mips64el': 0.18.20 413 | '@esbuild/linux-ppc64': 0.18.20 414 | '@esbuild/linux-riscv64': 0.18.20 415 | '@esbuild/linux-s390x': 0.18.20 416 | '@esbuild/linux-x64': 0.18.20 417 | '@esbuild/netbsd-x64': 0.18.20 418 | '@esbuild/openbsd-x64': 0.18.20 419 | '@esbuild/sunos-x64': 0.18.20 420 | '@esbuild/win32-arm64': 0.18.20 421 | '@esbuild/win32-ia32': 0.18.20 422 | '@esbuild/win32-x64': 0.18.20 423 | dev: true 424 | 425 | /estree-walker@2.0.2: 426 | resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} 427 | 428 | /fsevents@2.3.3: 429 | resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} 430 | engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} 431 | os: [darwin] 432 | requiresBuild: true 433 | dev: true 434 | optional: true 435 | 436 | /he@1.2.0: 437 | resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==} 438 | hasBin: true 439 | dev: true 440 | 441 | /lru-cache@6.0.0: 442 | resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==} 443 | engines: {node: '>=10'} 444 | dependencies: 445 | yallist: 4.0.0 446 | dev: true 447 | 448 | /magic-string@0.30.4: 449 | resolution: {integrity: sha512-Q/TKtsC5BPm0kGqgBIF9oXAs/xEf2vRKiIB4wCRQTJOQIByZ1d+NnUOotvJOvNpi5RNIgVOMC3pOuaP1ZTDlVg==} 450 | engines: {node: '>=12'} 451 | dependencies: 452 | '@jridgewell/sourcemap-codec': 1.4.15 453 | 454 | /minimatch@9.0.3: 455 | resolution: {integrity: sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==} 456 | engines: {node: '>=16 || 14 >=14.17'} 457 | dependencies: 458 | brace-expansion: 2.0.1 459 | dev: true 460 | 461 | /muggle-string@0.3.1: 462 | resolution: {integrity: sha512-ckmWDJjphvd/FvZawgygcUeQCxzvohjFO5RxTjj4eq8kw359gFF3E1brjfI+viLMxss5JrHTDRHZvu2/tuy0Qg==} 463 | dev: true 464 | 465 | /nanoid@3.3.6: 466 | resolution: {integrity: sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==} 467 | engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} 468 | hasBin: true 469 | 470 | /picocolors@1.0.0: 471 | resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==} 472 | 473 | /postcss@8.4.31: 474 | resolution: {integrity: sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==} 475 | engines: {node: ^10 || ^12 || >=14} 476 | dependencies: 477 | nanoid: 3.3.6 478 | picocolors: 1.0.0 479 | source-map-js: 1.0.2 480 | 481 | /rollup@3.29.4: 482 | resolution: {integrity: sha512-oWzmBZwvYrU0iJHtDmhsm662rC15FRXmcjCk1xD771dFDx5jJ02ufAQQTn0etB2emNk4J9EZg/yWKpsn9BWGRw==} 483 | engines: {node: '>=14.18.0', npm: '>=8.0.0'} 484 | hasBin: true 485 | optionalDependencies: 486 | fsevents: 2.3.3 487 | dev: true 488 | 489 | /semver@7.5.4: 490 | resolution: {integrity: sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==} 491 | engines: {node: '>=10'} 492 | hasBin: true 493 | dependencies: 494 | lru-cache: 6.0.0 495 | dev: true 496 | 497 | /source-map-js@1.0.2: 498 | resolution: {integrity: sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==} 499 | engines: {node: '>=0.10.0'} 500 | 501 | /to-fast-properties@2.0.0: 502 | resolution: {integrity: sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==} 503 | engines: {node: '>=4'} 504 | 505 | /typescript@5.0.2: 506 | resolution: {integrity: sha512-wVORMBGO/FAs/++blGNeAVdbNKtIh1rbBL2EyQ1+J9lClJ93KiiKe8PmFIVdXhHcyv44SL9oglmfeSsndo0jRw==} 507 | engines: {node: '>=12.20'} 508 | hasBin: true 509 | dev: true 510 | 511 | /vite@4.4.5: 512 | resolution: {integrity: sha512-4m5kEtAWHYr0O1Fu7rZp64CfO1PsRGZlD3TAB32UmQlpd7qg15VF7ROqGN5CyqN7HFuwr7ICNM2+fDWRqFEKaA==} 513 | engines: {node: ^14.18.0 || >=16.0.0} 514 | hasBin: true 515 | peerDependencies: 516 | '@types/node': '>= 14' 517 | less: '*' 518 | lightningcss: ^1.21.0 519 | sass: '*' 520 | stylus: '*' 521 | sugarss: '*' 522 | terser: ^5.4.0 523 | peerDependenciesMeta: 524 | '@types/node': 525 | optional: true 526 | less: 527 | optional: true 528 | lightningcss: 529 | optional: true 530 | sass: 531 | optional: true 532 | stylus: 533 | optional: true 534 | sugarss: 535 | optional: true 536 | terser: 537 | optional: true 538 | dependencies: 539 | esbuild: 0.18.20 540 | postcss: 8.4.31 541 | rollup: 3.29.4 542 | optionalDependencies: 543 | fsevents: 2.3.3 544 | dev: true 545 | 546 | /vue-template-compiler@2.7.14: 547 | resolution: {integrity: sha512-zyA5Y3ArvVG0NacJDkkzJuPQDF8RFeRlzV2vLeSnhSpieO6LK2OVbdLPi5MPPs09Ii+gMO8nY4S3iKQxBxDmWQ==} 548 | dependencies: 549 | de-indent: 1.0.2 550 | he: 1.2.0 551 | dev: true 552 | 553 | /vue-tsc@1.8.5(typescript@5.0.2): 554 | resolution: {integrity: sha512-Jr8PTghJIwp69MFsEZoADDcv2l+lXA8juyN/5AYA5zxyZNvIHjSbgKgkYIYc1qnihrOyIG1VOnfk4ZE0jqn8bw==} 555 | hasBin: true 556 | peerDependencies: 557 | typescript: '*' 558 | dependencies: 559 | '@vue/language-core': 1.8.5(typescript@5.0.2) 560 | '@vue/typescript': 1.8.5(typescript@5.0.2) 561 | semver: 7.5.4 562 | typescript: 5.0.2 563 | dev: true 564 | 565 | /vue@3.3.4: 566 | resolution: {integrity: sha512-VTyEYn3yvIeY1Py0WaYGZsXnz3y5UnGi62GjVEqvEGPl6nxbOrCXbVOTQWBEJUqAyTUk2uJ5JLVnYJ6ZzGbrSw==} 567 | dependencies: 568 | '@vue/compiler-dom': 3.3.4 569 | '@vue/compiler-sfc': 3.3.4 570 | '@vue/runtime-dom': 3.3.4 571 | '@vue/server-renderer': 3.3.4(vue@3.3.4) 572 | '@vue/shared': 3.3.4 573 | 574 | /yallist@4.0.0: 575 | resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} 576 | dev: true 577 | -------------------------------------------------------------------------------- /web/src/App.vue: -------------------------------------------------------------------------------- 1 | 138 | 269 | -------------------------------------------------------------------------------- /web/src/main.ts: -------------------------------------------------------------------------------- 1 | import { createApp } from 'vue' 2 | import App from './App.vue' 3 | 4 | createApp(App).mount('#app') 5 | -------------------------------------------------------------------------------- /web/src/types/response.d.ts: -------------------------------------------------------------------------------- 1 | declare interface ResponseRunning { 2 | running?: boolean 3 | } 4 | declare interface ResponseConfig { 5 | enabled?: boolean 6 | acceptRoutes?: boolean 7 | hostname?: string 8 | advertiseRoutes?: string 9 | loginServer?: string 10 | authkey?: string 11 | } 12 | 13 | // 14 | declare interface ResponseStatus { 15 | Version?: string 16 | // 服务状态 17 | BackendState?: string 18 | // 是否有绑定链接 19 | AuthURL?: string 20 | TailscaleIPs?: string[] 21 | Health?: string[] 22 | MagicDNSSuffix?: string 23 | // 当前节点 24 | Self?: ResponseStatusSelf 25 | // 已绑定用户 26 | User?: { 27 | [key: string]: ResponseStatusUser 28 | } 29 | } 30 | declare interface ResponseStatusUser { 31 | ID?: number 32 | LoginName?: string 33 | DisplayName?: string 34 | ProfilePicURL?: string 35 | Roles?: any[] 36 | } 37 | declare interface ResponseStatusSelf { 38 | ID: string 39 | PublicKey: string 40 | HostName: string 41 | DNSName: string 42 | OS: string 43 | UserID: number 44 | TailscaleIPs: string[] 45 | Addrs: string[] 46 | CurAddr: string 47 | Relay: string 48 | RxBytes: number 49 | TxBytes: number 50 | Created: string 51 | LastWrite: string 52 | LastSeen: string 53 | LastHandshake: string 54 | Online: boolean 55 | KeepAlive: boolean 56 | ExitNode: boolean 57 | ExitNodeOption: boolean 58 | Active: boolean 59 | PeerAPIURL: any 60 | InNetworkMap: boolean 61 | InMagicSock: boolean 62 | InEngine: boolean 63 | } 64 | 65 | 66 | 67 | -------------------------------------------------------------------------------- /web/src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /web/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2020", 4 | "useDefineForClassFields": true, 5 | "module": "ESNext", 6 | "lib": ["ES2020", "DOM", "DOM.Iterable"], 7 | "skipLibCheck": true, 8 | 9 | /* Bundler mode */ 10 | "moduleResolution": "bundler", 11 | "allowImportingTsExtensions": true, 12 | "resolveJsonModule": true, 13 | "isolatedModules": true, 14 | "noEmit": true, 15 | "jsx": "preserve", 16 | 17 | /* Linting */ 18 | "strict": true, 19 | "noUnusedLocals": true, 20 | "noUnusedParameters": true, 21 | "noFallthroughCasesInSwitch": true 22 | }, 23 | "include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"], 24 | "references": [{ "path": "./tsconfig.node.json" }] 25 | } 26 | -------------------------------------------------------------------------------- /web/tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "skipLibCheck": true, 5 | "module": "ESNext", 6 | "moduleResolution": "bundler", 7 | "allowSyntheticDefaultImports": true 8 | }, 9 | "include": ["vite.config.ts"] 10 | } 11 | -------------------------------------------------------------------------------- /web/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import vue from '@vitejs/plugin-vue' 3 | function rename() { 4 | return { 5 | name: 'rename', generateBundle: function (options, bundle, isWrite) { 6 | for (var key in bundle) { 7 | if (key.indexOf('?') != -1) { 8 | var newkey = key.substring(0, key.indexOf('?')); 9 | bundle[key].fileName = newkey; 10 | console.log('rename: ', key, newkey); 11 | } 12 | } 13 | } 14 | }; 15 | }; 16 | // https://vitejs.dev/config/ 17 | export default defineConfig({ 18 | base: "/", 19 | server: { 20 | proxy: { 21 | '/cgi-bin': { 22 | target: "http://192.168.100.1", 23 | changeOrigin: false, 24 | ws: true, 25 | }, 26 | }, 27 | }, 28 | build: { 29 | target: "es2015", 30 | outDir: "dist", 31 | assetsDir: "", 32 | cssCodeSplit: false, 33 | assetsInlineLimit: 0, 34 | chunkSizeWarningLimit: 4096, 35 | rollupOptions: { 36 | output: { 37 | entryFileNames: `luci-static/tailscaler/[name].js?v=[hash]`, 38 | chunkFileNames: `luci-static/tailscaler/chunk.[hash].js`, 39 | assetFileNames: `luci-static/tailscaler/[name].[ext]?v=[hash]` 40 | }, 41 | plugins: [ 42 | rename() 43 | ], 44 | external: [] 45 | } 46 | }, 47 | css: { 48 | preprocessorOptions: { 49 | scss: { 50 | charset: false, 51 | additionalData: ` 52 | @import "./src/style/scss/transition.scss"; 53 | `, 54 | } 55 | } 56 | }, 57 | plugins: [ 58 | vue(), 59 | ] 60 | }) 61 | --------------------------------------------------------------------------------