From e31bde18363325a9ad8781f78f9080ced5c1c96a Mon Sep 17 00:00:00 2001 From: KKRainbow <443152178@qq.com> Date: Fri, 7 Aug 2026 16:25:33 +0800 Subject: [PATCH] feat(web): add standalone WASM config generator (#2480) Add a small Vite workspace that builds and publishes independently of the dashboard. Reuse frontend-lib for the form and expose the existing NetworkConfig conversions through wasm-bindgen. Initialize the Aura theme in the standalone entry, detect the browser language, and provide a persistent selector in the form header. Present Generate Config and Copy Config as the page actions. Keep the shared network secret field fluid so both form columns align. Bundle the generator under dist/config-generator in the dashboard artifact while preserving its standalone build output. Build the optimized WASM module with the project and remove the API-backed generator route from the dashboard. --- .cargo/config.toml | 8 + .github/actions/prepare-build/action.yml | 1 + .github/actions/prepare-pnpm/action.yml | 27 ++++ .github/workflows/core.yml | 1 + .gitignore | 2 + Cargo.lock | 3 + easytier-core/Cargo.toml | 15 +- easytier-core/src/config/browser.rs | 27 ++++ easytier-core/src/config/mod.rs | 8 +- easytier-web/config-generator/index.html | 12 ++ easytier-web/config-generator/package.json | 24 +++ easytier-web/config-generator/src/App.vue | 151 ++++++++++++++++++ easytier-web/config-generator/src/main.ts | 23 +++ easytier-web/config-generator/tsconfig.json | 8 + easytier-web/config-generator/vite.config.ts | 7 + .../frontend-lib/src/components/Config.vue | 5 +- easytier-web/frontend-lib/src/locales/cn.yaml | 6 + easytier-web/frontend-lib/src/locales/en.yaml | 6 + .../src/components/ConfigGenerator.vue | 88 ---------- easytier-web/frontend/src/main.ts | 5 - pnpm-lock.yaml | 28 ++++ pnpm-workspace.yaml | 1 + 22 files changed, 359 insertions(+), 97 deletions(-) create mode 100644 easytier-core/src/config/browser.rs create mode 100644 easytier-web/config-generator/index.html create mode 100644 easytier-web/config-generator/package.json create mode 100644 easytier-web/config-generator/src/App.vue create mode 100644 easytier-web/config-generator/src/main.ts create mode 100644 easytier-web/config-generator/tsconfig.json create mode 100644 easytier-web/config-generator/vite.config.ts delete mode 100644 easytier-web/frontend/src/components/ConfigGenerator.vue diff --git a/.cargo/config.toml b/.cargo/config.toml index 2548b156..7e62bd5c 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -9,6 +9,14 @@ rustflags = ["-C", "link-arg=-fuse-ld=mold"] [target.'cfg(all(windows, target_env = "msvc"))'] rustflags = ["-C", "target-feature=+crt-static"] +[target.wasm32-unknown-unknown] +rustflags = [ + "-C", + "opt-level=z", + "--cfg", + 'getrandom_backend="wasm_js"', +] + # region # region CI diff --git a/.github/actions/prepare-build/action.yml b/.github/actions/prepare-build/action.yml index 7b11ee93..115dc941 100644 --- a/.github/actions/prepare-build/action.yml +++ b/.github/actions/prepare-build/action.yml @@ -50,6 +50,7 @@ runs: if: ${{ inputs.pnpm == 'true' }} uses: ./.github/actions/prepare-pnpm with: + token: ${{ inputs.token }} build-filter: ${{ inputs.pnpm-build-filter }} - name: Install GUI dependencies (Linux) diff --git a/.github/actions/prepare-pnpm/action.yml b/.github/actions/prepare-pnpm/action.yml index 8bc756a9..4cebfec6 100644 --- a/.github/actions/prepare-pnpm/action.yml +++ b/.github/actions/prepare-pnpm/action.yml @@ -3,6 +3,9 @@ author: Luna description: 'Setup Node.js, pnpm, and install dependencies' inputs: + token: + description: 'GitHub token, used by setup-protoc action' + required: false build-filter: description: 'The filter argument for pnpm build (e.g. ./easytier-web/*)' required: false @@ -11,6 +14,22 @@ inputs: runs: using: "composite" steps: + - uses: actions-rust-lang/setup-rust-toolchain@v1 + with: + toolchain: 1.95 + target: wasm32-unknown-unknown + cache: false + rustflags: '' + + - uses: taiki-e/install-action@v2 + with: + tool: wasm-pack + + - uses: arduino/setup-protoc@v3 + with: + version: '35.1' + repo-token: ${{ inputs.token }} + - name: Setup Node.js uses: actions/setup-node@v5 with: @@ -46,3 +65,11 @@ runs: echo "No build filter provided, building all packages" pnpm -r --workspace-concurrency=1 build fi + + - name: Bundle config generator with web frontend + shell: bash + run: | + if [ -f easytier-web/frontend/dist/index.html ] && [ -f easytier-web/config-generator/dist/index.html ]; then + mkdir -p easytier-web/frontend/dist/config-generator + cp -R easytier-web/config-generator/dist/. easytier-web/frontend/dist/config-generator/ + fi diff --git a/.github/workflows/core.yml b/.github/workflows/core.yml index 185fd2cd..7e8f4ee3 100644 --- a/.github/workflows/core.yml +++ b/.github/workflows/core.yml @@ -47,6 +47,7 @@ jobs: - name: Setup Frontend Environment uses: ./.github/actions/prepare-pnpm with: + token: ${{ github.token }} build-filter: './easytier-web/*' - name: Archive artifact diff --git a/.gitignore b/.gitignore index b17d8c5e..6a270837 100644 --- a/.gitignore +++ b/.gitignore @@ -35,6 +35,8 @@ easytier-panic.log # web node_modules easytier-web/frontend-lib/src/generated/ +easytier-web/config-generator/dist/ +easytier-web/config-generator/src/generated/ .vite diff --git a/Cargo.lock b/Cargo.lock index 41d9dfd2..5f73a262 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2434,6 +2434,8 @@ dependencies = [ "derive_builder", "easytier-proto", "futures", + "getrandom 0.2.15", + "getrandom 0.3.2", "guarden 0.2.0", "hmac", "http-body-util", @@ -2468,6 +2470,7 @@ dependencies = [ "tracing", "url", "uuid", + "wasm-bindgen", "webpki-roots 0.26.3", "wildmatch", "x25519-dalek", diff --git a/easytier-core/Cargo.toml b/easytier-core/Cargo.toml index b923dc54..c67d412a 100644 --- a/easytier-core/Cargo.toml +++ b/easytier-core/Cargo.toml @@ -14,6 +14,9 @@ license-file = "../LICENSE" [lib] crate-type = ["rlib", "cdylib"] +[package.metadata.wasm-pack.profile.release] +wasm-opt = ["-Oz", "--enable-bulk-memory", "--enable-nontrapping-float-to-int"] + [dependencies] anyhow = "1.0" ariadne = { version = "0.5", optional = true } @@ -55,7 +58,6 @@ serde = { version = "1.0", features = ["derive"] } serde_json = "1" sha2 = "0.10.8" smoltcp = { git = "https://github.com/smoltcp-rs/smoltcp.git", rev = "0a926767a68bc88d5512afefa7529c5ecdade4ea", optional = true, default-features = false } -snow = "0.10.0" stun_codec = "0.3.4" thiserror = "1.0" tracing = "0.1" @@ -81,9 +83,20 @@ aes-gcm = { version = "0.10.3", optional = true } chacha20poly1305 = { version = "0.10.1", optional = true } openssl = { version = "0.10", optional = true, features = ["vendored"] } +[target.'cfg(all(target_arch = "wasm32", target_os = "unknown"))'.dependencies] +getrandom-02 = { package = "getrandom", version = "0.2.15", features = ["js"] } +getrandom-03 = { package = "getrandom", version = "0.3.2", features = ["wasm_js"] } +snow = { version = "0.10.0", default-features = false, features = ["default-resolver", "default-resolver-crypto"] } +uuid = { version = "1.5.0", features = ["js"] } +wasm-bindgen = "0.2" + +[target.'cfg(not(all(target_arch = "wasm32", target_os = "unknown")))'.dependencies] +snow = "0.10.0" + [features] default = ["aes-gcm", "endpoint-discovery", "extended-services", "management", "tcp-hole-punch"] aes-gcm = ["dep:aes-gcm"] +browser-config = ["config-write", "easytier-proto/api", "easytier-proto/json-rpc"] chacha20 = ["dep:chacha20poly1305"] openssl-crypto = ["dep:openssl"] ring-crypto = ["dep:ring"] diff --git a/easytier-core/src/config/browser.rs b/easytier-core/src/config/browser.rs new file mode 100644 index 00000000..2913ee83 --- /dev/null +++ b/easytier-core/src/config/browser.rs @@ -0,0 +1,27 @@ +use wasm_bindgen::prelude::*; + +use super::{ + api_input::{NetworkConfig, NetworkConfigExt}, + toml::{ConfigLoader, TomlConfig}, +}; + +fn js_error(error: impl std::fmt::Debug) -> JsValue { + JsValue::from_str(&format!("{error:?}")) +} + +#[wasm_bindgen] +pub fn generate_config(config_json: &str) -> Result { + let config: NetworkConfig = serde_json::from_str(config_json).map_err(js_error)?; + config + .gen_config() + .map(|config| config.dump()) + .map_err(js_error) +} + +#[wasm_bindgen] +pub fn parse_config(toml_config: &str) -> Result { + let config = TomlConfig::new_from_str(toml_config) + .and_then(|config| NetworkConfig::new_from_config(&config)) + .map_err(js_error)?; + serde_json::to_string(&config).map_err(js_error) +} diff --git a/easytier-core/src/config/mod.rs b/easytier-core/src/config/mod.rs index fc48a73a..88314f41 100644 --- a/easytier-core/src/config/mod.rs +++ b/easytier-core/src/config/mod.rs @@ -2,8 +2,14 @@ #[cfg(feature = "management")] pub mod api; -#[cfg(feature = "management")] +#[cfg(any(feature = "management", feature = "browser-config"))] pub mod api_input; +#[cfg(all( + feature = "browser-config", + target_arch = "wasm32", + target_os = "unknown" +))] +mod browser; mod encryption; pub mod gateway; pub mod peers; diff --git a/easytier-web/config-generator/index.html b/easytier-web/config-generator/index.html new file mode 100644 index 00000000..a31f93e2 --- /dev/null +++ b/easytier-web/config-generator/index.html @@ -0,0 +1,12 @@ + + + + + + EasyTier Config Generator + + +
+ + + diff --git a/easytier-web/config-generator/package.json b/easytier-web/config-generator/package.json new file mode 100644 index 00000000..e7cb3a66 --- /dev/null +++ b/easytier-web/config-generator/package.json @@ -0,0 +1,24 @@ +{ + "name": "easytier-config-generator", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "build:config-wasm": "wasm-pack build ../../easytier-core --target web --out-dir ../easytier-web/config-generator/src/generated/config-wasm --out-name easytier_config --release --no-pack -- --no-default-features --features browser-config", + "dev": "pnpm build:config-wasm && pnpm --dir ../frontend-lib build && vite", + "build": "pnpm build:config-wasm && pnpm --dir ../frontend-lib build && vue-tsc --noEmit && vite build", + "preview": "vite preview" + }, + "dependencies": { + "@primeuix/themes": "^1.2.3", + "easytier-frontend-lib": "workspace:*", + "primevue": "^4.3.9", + "vue": "^3.5.12" + }, + "devDependencies": { + "@vitejs/plugin-vue": "^5.1.4", + "typescript": "~5.6.2", + "vite": "^5.4.21", + "vue-tsc": "^2.1.10" + } +} diff --git a/easytier-web/config-generator/src/App.vue b/easytier-web/config-generator/src/App.vue new file mode 100644 index 00000000..747e8598 --- /dev/null +++ b/easytier-web/config-generator/src/App.vue @@ -0,0 +1,151 @@ + + + diff --git a/easytier-web/frontend/src/main.ts b/easytier-web/frontend/src/main.ts index 13414677..1e08d12e 100644 --- a/easytier-web/frontend/src/main.ts +++ b/easytier-web/frontend/src/main.ts @@ -16,7 +16,6 @@ import DeviceManagement from './components/DeviceManagement.vue' import Dashboard from './components/Dashboard.vue' import DialogService from 'primevue/dialogservice'; import ToastService from 'primevue/toastservice'; -import ConfigGenerator from './components/ConfigGenerator.vue' const routes = [ { @@ -67,10 +66,6 @@ const routes = [ return { name: 'login' } } } - }, - { - path: '/config_generator', - component: ConfigGenerator, } ] diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e3ad8a56..18672c14 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -145,6 +145,34 @@ importers: specifier: ^2.1.10 version: 2.2.12(typescript@5.6.3) + easytier-web/config-generator: + dependencies: + '@primeuix/themes': + specifier: ^1.2.3 + version: 1.2.3 + easytier-frontend-lib: + specifier: workspace:* + version: link:../frontend-lib + primevue: + specifier: ^4.3.9 + version: 4.3.9(vue@3.5.21(typescript@5.6.3)) + vue: + specifier: ^3.5.12 + version: 3.5.21(typescript@5.6.3) + devDependencies: + '@vitejs/plugin-vue': + specifier: ^5.1.4 + version: 5.2.4(vite@5.4.21(@types/node@22.18.1))(vue@3.5.21(typescript@5.6.3)) + typescript: + specifier: ~5.6.2 + version: 5.6.3 + vite: + specifier: ^5.4.21 + version: 5.4.21(@types/node@22.18.1) + vue-tsc: + specifier: ^2.1.10 + version: 2.2.12(typescript@5.6.3) + easytier-web/frontend: dependencies: '@modyfi/vite-plugin-yaml': diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 25d3909b..66fefd5f 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -1,4 +1,5 @@ packages: + - 'easytier-web/config-generator' - 'easytier-web/frontend' - 'easytier-web/frontend-lib' - 'easytier-gui'