diff --git a/.forgejo/workflows/tfclient.yml b/.forgejo/workflows/tfclient.yml index e7892d2..2018344 100644 --- a/.forgejo/workflows/tfclient.yml +++ b/.forgejo/workflows/tfclient.yml @@ -44,12 +44,42 @@ jobs: profile: minimal toolchain: stable override: true + - name: Install aarch64 target + run: rustup target add aarch64-unknown-linux-gnu - name: Install additional dependencies run: apt update && apt-get install -y libclang-dev clang sshpass rsync gcc-arm-linux-gnueabi - name: Compile release binary uses: https://github.com/actions-rs/cargo@v1 with: command: build - args: --release --bin tfclient + args: --release --bin tfclient --target aarch64-unknown-linux-gnu - name: Upload binary run: sshpass -p "${{ secrets.TRIFID_DLCDN_PASSWORD }}" rsync --mkpath -e 'ssh -p ${{ secrets.TRIFID_DLCDN_PORT }} -o StrictHostKeyChecking=no' target/release/tfclient ${{ secrets.TRIFID_DLCDN_USER }}@${{ secrets.TRIFID_DLCDN_IP }}:${{ secrets.TRIFID_DLCDN_PATH }}/tfclient/arm64/$GITHUB_SHA/tfclient + build_win64: + runs_on: docker + steps: + - name: Checkout code + uses: actions/checkout@v2 + - name: Setup Go toolchain + uses: actions/setup-go@v4 + with: + go-version: '1.20' + - name: Setup Rust toolchain + uses: https://github.com/actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: stable + override: true + - name: Install cross-compilation toolchain + run: rustup target add x86_64-pc-windows-gnu + - name: Install additional dependencies + run: apt update && apt-get install -y libclang-dev clang sshpass rsync mingw-w64 zip + - name: Compile release binary + uses: https://github.com/actions-rs/cargo@v1 + with: + command: build + args: --release --bin tfclient --target x86_64-pc-windows-gnu + - name: Compile release bundle + run: ./build_windows.sh + - name: Upload binary + run: sshpass -p "${{ secrets.TRIFID_DLCDN_PASSWORD }}" rsync --mkpath -e 'ssh -p ${{ secrets.TRIFID_DLCDN_PORT }} -o StrictHostKeyChecking=no' target/x86_64-pc-windows-gnu/release/tfclient.zip ${{ secrets.TRIFID_DLCDN_USER }}@${{ secrets.TRIFID_DLCDN_IP }}:${{ secrets.TRIFID_DLCDN_PATH }}/tfclient/win64/$GITHUB_SHA/tfclient.zip diff --git a/.gitignore b/.gitignore index 52e52a0..26d0056 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,4 @@ packages/void_amd64_tfclient/work packages/void_amd64_tfclient/*.xbps *.pem *.xbps +tfweb/build diff --git a/.idea/dataSources.xml b/.idea/dataSources.xml index 1fec8e5..7074b84 100644 --- a/.idea/dataSources.xml +++ b/.idea/dataSources.xml @@ -1,7 +1,7 @@ <?xml version="1.0" encoding="UTF-8"?> <project version="4"> <component name="DataSourceManagerImpl" format="xml" multifile-model="true"> - <data-source source="LOCAL" name="trifidapi@localhost" uuid="39c81b89-3fc4-493f-b203-7a00527cffe6"> + <data-source source="LOCAL" name="trifid@localhost" uuid="39c81b89-3fc4-493f-b203-7a00527cffe6"> <driver-ref>postgresql</driver-ref> <synchronize>true</synchronize> <jdbc-driver>org.postgresql.Driver</jdbc-driver> diff --git a/build_windows.sh b/build_windows.sh new file mode 100755 index 0000000..6bddd87 --- /dev/null +++ b/build_windows.sh @@ -0,0 +1,18 @@ +#!/bin/bash +set -e +cargo build --target x86_64-pc-windows-gnu --release --bin tfclient +rm -rf target/x86_64-pc-windows-gnu/release/tfclient_dist/ +mkdir -p target/x86_64-pc-windows-gnu/release/tfclient_dist/ +cp target/x86_64-pc-windows-gnu/release/tfclient.exe target/x86_64-pc-windows-gnu/release/tfclient_dist/tfclient.exe +echo "[*] Downloading WinTun" +mkdir -p target/x86_64-pc-windows-gnu/release/tfclient_dist/dist/windows/ +wget https://www.wintun.net/builds/wintun-0.14.1.zip -O /tmp/wintun.zip +echo "[*] Unzipping WinTun" +unzip -d target/x86_64-pc-windows-gnu/release/tfclient_dist/dist/windows/ /tmp/wintun.zip +echo "[*] Copying nebula.dll" +cp target/x86_64-pc-windows-gnu/release/nebula.dll target/x86_64-pc-windows-gnu/release/tfclient_dist/nebula.dll +echo "[*] Building zip bundle" +cd target/x86_64-pc-windows-gnu/release +zip -r tfclient.zip tfclient_dist/* +cd ../../../ +echo "[*] Windows production build success!" diff --git a/docs/docs/tfclient/img/firewall_main.png b/docs/docs/tfclient/img/firewall_main.png new file mode 100644 index 0000000..193ced3 Binary files /dev/null and b/docs/docs/tfclient/img/firewall_main.png differ diff --git a/docs/docs/tfclient/img/new_rule_1.png b/docs/docs/tfclient/img/new_rule_1.png new file mode 100644 index 0000000..eee6c2e Binary files /dev/null and b/docs/docs/tfclient/img/new_rule_1.png differ diff --git a/docs/docs/tfclient/img/new_rule_2.png b/docs/docs/tfclient/img/new_rule_2.png new file mode 100644 index 0000000..bf00bd9 Binary files /dev/null and b/docs/docs/tfclient/img/new_rule_2.png differ diff --git a/docs/docs/tfclient/img/new_rule_3.png b/docs/docs/tfclient/img/new_rule_3.png new file mode 100644 index 0000000..1d2590b Binary files /dev/null and b/docs/docs/tfclient/img/new_rule_3.png differ diff --git a/docs/docs/tfclient/troubleshooting.md b/docs/docs/tfclient/troubleshooting.md new file mode 100644 index 0000000..6cd06e0 --- /dev/null +++ b/docs/docs/tfclient/troubleshooting.md @@ -0,0 +1,11 @@ +--- +sidebar_position: 3 +--- + +# Troubleshooting + +This page lists common issues with tfclient and how to resolve them. This page will be updated occasionally as needed. + +## On Windows, traffic can leave but cannot enter + +This is [a known issue with Nebula](https://github.com/slackhq/nebula/issues/621), and by extension tfclient. [A workaround is available - see here for details.](/docs/tfclient/windows_firewall) diff --git a/docs/docs/tfclient/windows_firewall.md b/docs/docs/tfclient/windows_firewall.md new file mode 100644 index 0000000..10a3c10 --- /dev/null +++ b/docs/docs/tfclient/windows_firewall.md @@ -0,0 +1,45 @@ +--- +sidebar_position: 2 +--- + +# Windows Firewall Setup + +:::info +This is only needed on Windows-based platforms. On macOS and Linux, the firewall works correctly out-of-the-box. +::: + +On Windows-based platforms, Nebula (and by extension tfclient) cannot properly configure Windows Defender Firewall automatically, thus preventing any inbound traffic from reaching your machine over your network (outbound traffic will still work). To fix this, you need to add a firewall rule to Windows Defender Firewall. + +It is recommended that you add a rule allowing **all inbound traffic from your network.** Nebula has a built-in firewall, thus it is preferred to allow the Nebula firewall to function as intended and leave Windows Defender Firewall essentially disabled for your VPN's subnet, allowing Nebula to function in it's place. + +## Adding the inbound rule + +1. Search for "Windows Defender Firewall with Advanced Security" and open it. This requires administrator privileges. You should see the window below. + + + +2. Select "Inbound Rules" on the left, and then click "New Rule" to open the New Inbound Rule Wizard. You should see the window below. + + + +3. Select the Custom option, and click Next. +4. Click the Next button for the Program step. +5. Click the Next button for the Protocol and Port step. +6. Select the "These IP addresses" checkbox under "Which remote IP addresses does this rule apply to?" as shown below. + + + +7. Select the Add button next to "These IP addresses" to open the window shown below. + + + +8. In the box under "This IP address or subnet" enter the CIDR notation of your network i.e. `10.16.0.0/15`. Ask your network administrator for this value if you do not know it. +9. Click the "OK" button. +10. Click the "Next" button. +11. Under "What action should be taken when a connection matches the specified conditions?", select "Allow the connection", then click Next. +12. Click "Next" on the Profile page. +13. Enter a title for the rule. We recommend using something like `tfclient/nebula/YOUR_NETWORK_NAME/YOUR_NETWORK_CIDR`, such as `tfclient/nebula/corporate/10.16.0.0/15`. +14. If desired, enter additional descriptions in the second text box. +15. Select the "Finish" option. + +The changes should take effect immediately, and traffic should begin flowing 5-10 seconds after applying the rule (give Nebula time to perform handshakes and establish tunnels). If it does not take effect immediately, rebooting the affected machine typically resolves any issues. diff --git a/nebula-ffi/build.rs b/nebula-ffi/build.rs index bca0dad..d6167fe 100644 --- a/nebula-ffi/build.rs +++ b/nebula-ffi/build.rs @@ -3,6 +3,22 @@ use std::path::PathBuf; use bindgen::CargoCallbacks; use std::path::Path; +fn get_cargo_target_dir() -> Result<std::path::PathBuf, Box<dyn std::error::Error>> { + let out_dir = std::path::PathBuf::from(std::env::var("OUT_DIR")?); + let profile = std::env::var("PROFILE")?; + let mut target_dir = None; + let mut sub_path = out_dir.as_path(); + while let Some(parent) = sub_path.parent() { + if parent.ends_with(&profile) { + target_dir = Some(parent); + break; + } + sub_path = parent; + } + let target_dir = target_dir.ok_or("not found")?; + Ok(target_dir.to_path_buf()) +} + fn main() { // Find compiler: @@ -29,11 +45,11 @@ fn main() { let out_dir = env::var("OUT_DIR").unwrap(); let out_path = PathBuf::from(out_dir); - let out_file = LIBRARY_PREFIX.to_owned() + "nebula" + LIBRARY_EXTENSION; + let out_file = lib_name(); let out = out_path.join(out_file); let mut command = process::Command::new(compiler); - command.args(["build", "-buildmode", "c-archive", "-o", out.display().to_string().as_str(), "main.go"]); + command.args(["build", "-buildmode", link_type().as_str(), "-o", out.display().to_string().as_str(), "main.go"]); command.env("CGO_ENABLED", "1"); command.env("CC", c_compiler.path()); command.env("GOARCH", goarch()); @@ -49,8 +65,9 @@ fn main() { } println!("Go compile success"); + copy_if_windows(); - println!("cargo:rustc-link-lib=static=nebula"); + print_link(); println!("cargo:rustc-link-search=native={}", env::var("OUT_DIR").unwrap()); //let out_path = PathBuf::from(env::var("OUT_DIR").unwrap()); @@ -63,7 +80,7 @@ fn main() { println!("Generating bindings"); let bindings = bindgen::Builder::default() - .header(out_path.join("libnebula.h").display().to_string()) + .header(out_path.join(header_name()).display().to_string()) .parse_callbacks(Box::new(CargoCallbacks)) .generate() .expect("Error generating CFFI bindings"); @@ -74,15 +91,30 @@ fn main() { .expect("Couldn't write bindings!"); } -#[cfg(target_family = "unix")] -const LIBRARY_EXTENSION: & str = ".a"; -#[cfg(target_family = "unix")] -const LIBRARY_PREFIX: & str = "lib"; +fn lib_name() -> String { + if env::var("CARGO_CFG_TARGET_FAMILY").unwrap() == "windows" { + "nebula.dll".to_string() + } else { + "libnebula.a".to_string() + } +} +fn header_name() -> String { + if env::var("CARGO_CFG_TARGET_FAMILY").unwrap() == "windows" { + "nebula.h".to_string() + } else { + "libnebula.h".to_string() + } +} -#[cfg(target_family = "windows")] -const LIBRARY_EXTENSION: &str = ".lib"; -#[cfg(target_family = "windows")] -const LIBRARY_PREFIX: &str = ""; +fn copy_if_windows() { + let target_dir = get_cargo_target_dir().unwrap(); + let target_file = target_dir.join(lib_name()); + let out_dir = env::var("OUT_DIR").unwrap(); + let out_path = PathBuf::from(out_dir); + let out_file = lib_name(); + let out = out_path.join(out_file); + std::fs::copy(out, target_file).unwrap(); +} fn goarch() -> String { match env::var("CARGO_CFG_TARGET_ARCH").unwrap().as_str() { @@ -109,4 +141,20 @@ fn goos() -> String { "netbsd" => "netbsd", os => panic!("unsupported operating system {os}") }.to_string() +} + +fn print_link() { + if env::var("CARGO_CFG_TARGET_FAMILY").unwrap() == "windows" { + println!("cargo:rustc-link-lib=dylib=nebula"); + } else { + println!("cargo:rustc-link-lib=static=nebula"); + } +} + +fn link_type() -> String { + if env::var("CARGO_CFG_TARGET_FAMILY").unwrap() == "windows" { + "c-shared".to_string() + } else { + "c-archive".to_string() + } } \ No newline at end of file diff --git a/tfclient/src/socketworker.rs b/tfclient/src/socketworker.rs index 4927b50..9200439 100644 --- a/tfclient/src/socketworker.rs +++ b/tfclient/src/socketworker.rs @@ -124,6 +124,8 @@ fn handle_client( ) -> Result<(), io::Error> { info!("Handling connection from {}", stream.peer_addr()?); + stream.set_nonblocking(false)?; + let mut client = Client { state: ClientState::WaitHello, reader: BufReader::new(&stream), diff --git a/tfweb/package.json b/tfweb/package.json index 79fb14c..4cad20e 100644 --- a/tfweb/package.json +++ b/tfweb/package.json @@ -12,6 +12,7 @@ }, "devDependencies": { "@sveltejs/adapter-auto": "^2.0.0", + "@sveltejs/adapter-node": "^1.3.1", "@sveltejs/kit": "^1.5.0", "@typescript-eslint/eslint-plugin": "^5.45.0", "@typescript-eslint/parser": "^5.45.0", diff --git a/tfweb/src/components/AdminLayout.svelte b/tfweb/src/components/AdminLayout.svelte index e5cfada..00f10db 100644 --- a/tfweb/src/components/AdminLayout.svelte +++ b/tfweb/src/components/AdminLayout.svelte @@ -1,5 +1,4 @@ <script lang="ts"> - import {t} from "svelte-i18n"; import Sidebar from "$components/Sidebar.svelte"; export let selected; @@ -14,4 +13,4 @@ <slot></slot> </div> </div> -</div> \ No newline at end of file +</div> diff --git a/tfweb/src/components/Sidebar.svelte b/tfweb/src/components/Sidebar.svelte index 321c28a..21d5a23 100644 --- a/tfweb/src/components/Sidebar.svelte +++ b/tfweb/src/components/Sidebar.svelte @@ -1,5 +1,5 @@ <script lang="ts"> - import {t} from "svelte-i18n"; + import {t} from "$lib/i18n/translations"; export let selected; </script> @@ -39,7 +39,7 @@ <hr> <div class="nav-item"> - <button class="nav-link py-2 px-4" on:click={() => {window.localStorage.setItem("mfa", "")}}> + <button class="nav-link py-2 px-4" on:click={() => {window.localStorage.setItem("mfa", ""); window.location.href = "/2fa"}}> <i class="me-2 fas fa-right-from-bracket fa-fw"></i> {$t("common.logout")} </button> diff --git a/tfweb/src/lib/Tooltips.js b/tfweb/src/lib/Tooltips.js new file mode 100644 index 0000000..1501f7d --- /dev/null +++ b/tfweb/src/lib/Tooltips.js @@ -0,0 +1,17 @@ +import {browser} from "$app/environment"; + +export function updateTooltips() { + if (browser) { + setTimeout(() => { + const tooltipTriggerList = document.querySelectorAll('[data-bs-toggle="tooltip"]') + const tooltipList = [...tooltipTriggerList].map(tooltipTriggerEl => { + let tooltip = new document.B.Tooltip(tooltipTriggerEl, {trigger: 'hover'}) + tooltipTriggerEl.addEventListener('click', () => { + tooltip.hide(); + tooltip.dispose(); + }) + }); + console.log(tooltipList); + }); + } +} diff --git a/tfweb/src/lib/i18n/locales/en.json b/tfweb/src/lib/i18n/locales/en.json deleted file mode 100644 index 3239234..0000000 --- a/tfweb/src/lib/i18n/locales/en.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "itworks": { - - }, - "login": { - - }, - "signup": { - - }, - "ml": { - - }, - "2fasetup": { - - }, - "2fa": { - - }, - "networkcreate": { - - }, - "hosts": { - - }, - "roles": { - - }, - "common": { - - } -} diff --git a/tfweb/src/lib/i18n/locales/en/2fasetup.json b/tfweb/src/lib/i18n/locales/en/2fasetup.json index 257c9d0..941f1c5 100644 --- a/tfweb/src/lib/i18n/locales/en/2fasetup.json +++ b/tfweb/src/lib/i18n/locales/en/2fasetup.json @@ -6,8 +6,8 @@ "verify": "Verify by entering the 6-digit code shown in your authenticator into the box below.", "button": "Enable 2FA", "error": { - "generic": "Unable to enable 2fa: {err}", + "generic": "Unable to enable 2fa: {{err}}", "api": "Unable to contact the server. Try again later.", "ERR_UNAUTHORIZED": "Incorrect 2FA code" } -} \ No newline at end of file +} diff --git a/tfweb/src/lib/i18n/locales/en/common.json b/tfweb/src/lib/i18n/locales/en/common.json index 982510f..1d4c724 100644 --- a/tfweb/src/lib/i18n/locales/en/common.json +++ b/tfweb/src/lib/i18n/locales/en/common.json @@ -1,5 +1,5 @@ { - "title": "{title} | Trifid Web UI", + "title": "{{title}} | Trifid Web UI", "page": { "itworks": "It Works!", "admin": "Admin Panel", @@ -16,4 +16,4 @@ }, "logout": "Log out", "loading": "Dashboard is loading" -} \ No newline at end of file +} diff --git a/tfweb/src/lib/i18n/locales/en/hosts.json b/tfweb/src/lib/i18n/locales/en/hosts.json index ae775ee..8897e3e 100644 --- a/tfweb/src/lib/i18n/locales/en/hosts.json +++ b/tfweb/src/lib/i18n/locales/en/hosts.json @@ -7,6 +7,7 @@ "edit": "Edit", "enrollbtn": "Enroll", "deletehost": "Delete", + "blockhost": "Block", "config": "Configuration", "add": "Add", "create": { @@ -23,19 +24,28 @@ } }, "enroll": { - "title": "Enrolling host {host}", + "title": "Enrolling host {{host}}", "explainer": "Enrolling this host will allow it to communicate on your network. You'll need to have compatible client software installed first.", "done": "Done, return me to the hosts page", "cancel": "Nevermind", - "code": "Input {code} as your enrollment code into any trifid-compatible client software to enroll. It will expire in {expires} minutes." + "code": "Input {code} as your enrollment code into any trifid-compatible client software to enroll. It will expire in {{expires}} minutes." }, "delete": { - "title": "Are you sure you want to delete the host {host}?", + "title": "Are you sure you want to delete the host {{host}}?", "explainer": "This action cannot be undone. This host will still be able to connect to the network unless you block it first.", "confirm": "I'm sure", "cancel": "Nevermind", "error": { "ERR_DB_ERROR": "Removal failed" } + }, + "block": { + "title": "Are you sure you want to block the host {{host}}?", + "explainer": "The host will not be able to connect to the network once it is blocked. Re-enroll this host to unblock it.", + "confirm": "I'm sure", + "cancel": "Nevermind", + "error": { + "ERR_DB_ERROR": "Block failed" + } } -} \ No newline at end of file +} diff --git a/tfweb/src/lib/i18n/locales/en/itworks.json b/tfweb/src/lib/i18n/locales/en/itworks.json index 5006b5b..0f288e9 100644 --- a/tfweb/src/lib/i18n/locales/en/itworks.json +++ b/tfweb/src/lib/i18n/locales/en/itworks.json @@ -1,7 +1,7 @@ { "header": "It works!", "body": "If you're seeing this page, tfweb is installed and (probably) correctly configured.", - "linkbody": "Perhaps you meant to visit the {link0} or {link1}?", + "linkbody": "Perhaps you meant to visit the {{link0}} or {{link1}}?", "linkbody.link0": "admin panel", "linkbody.link1": "create an account" -} \ No newline at end of file +} diff --git a/tfweb/src/lib/i18n/locales/en/login.json b/tfweb/src/lib/i18n/locales/en/login.json index d052a10..b3d8c01 100644 --- a/tfweb/src/lib/i18n/locales/en/login.json +++ b/tfweb/src/lib/i18n/locales/en/login.json @@ -5,13 +5,13 @@ "button": "Log in", "email": "Check your email", "emailbody": "We sent you an email with a link to complete logging in.", - "emailbody2": "Didn't work? Check your junk inbox or click {link0} to try again.", + "emailbody2": "Didn't work? Check your junk inbox or click {{link0}} to try again.", "emailbody2.link0": "here", "error": { "invalidEmail": "That email address isn't valid. Try again.", - "generic": "There was an error logging you in. Try again or contact support with the error code {err}", + "generic": "There was an error logging you in. Try again or contact support with the error code {{err}}", "usermissing": "That user does not exist." }, - "need": "Don't have an account? {link0}", + "need": "Don't have an account? {{link0}}", "need.link0": "Signup" -} \ No newline at end of file +} diff --git a/tfweb/src/lib/i18n/locales/en/networkcreate.json b/tfweb/src/lib/i18n/locales/en/networkcreate.json index 7f3838a..5268d80 100644 --- a/tfweb/src/lib/i18n/locales/en/networkcreate.json +++ b/tfweb/src/lib/i18n/locales/en/networkcreate.json @@ -3,10 +3,10 @@ "explain": "This defines what IP addresses will be assigned to your devices. The range you enter below must fall within either the RFC 1918 Private Address Space or the RFC 6598 Shared Address Space. Enter your network via CIDR notation.", "label": "Network range", "button": "Create network", - "valid": "Valid - {numIps} addresses ({start} to {end})", + "valid": "Valid - {{numIps:number}} addresses ({{start}} to {{end}})", "invalid": "Invalid", "error": { - "generic": "Unable to create network: {err}", + "generic": "Unable to create network: {{err}}", "api": "Unable to contact the server. Try again later." } -} \ No newline at end of file +} diff --git a/tfweb/src/lib/i18n/locales/en/roles.json b/tfweb/src/lib/i18n/locales/en/roles.json index 5e6074e..4096c34 100644 --- a/tfweb/src/lib/i18n/locales/en/roles.json +++ b/tfweb/src/lib/i18n/locales/en/roles.json @@ -1,6 +1,6 @@ { "delete": { - "title": "Are you sure you want to delete the role {rule}?", + "title": "Are you sure you want to delete the role {{rule}}?", "explainer": "This action cannot be undone. This role must be removed from all hosts, lighthouses, and relays prior to deleting it.", "confirm": "I'm sure", "cancel": "Nevermind", @@ -15,6 +15,8 @@ "rules": "Rule count", "description": "Description", "actions": "Actions", + "editrole": "Edit", + "deleterole": "Delete", "add": { "title": "Create Role", "any": "Any", @@ -46,7 +48,7 @@ } }, "edit": { - "title": "Editing role {rule}", + "title": "Editing role {{rule}}", "any": "Any", "name": "Role name", "desc": "Role description", @@ -74,4 +76,4 @@ "cancel": "Cancel" } } -} \ No newline at end of file +} diff --git a/tfweb/src/lib/i18n/locales/en/signup.json b/tfweb/src/lib/i18n/locales/en/signup.json index 3ddf34a..bc60369 100644 --- a/tfweb/src/lib/i18n/locales/en/signup.json +++ b/tfweb/src/lib/i18n/locales/en/signup.json @@ -5,14 +5,14 @@ "button": "Create account", "email": "Check your email", "emailbody": "We sent you an email with a link to complete signing up.", - "emailbody2": "Didn't work? Check your junk inbox or click {link0} to try again.", + "emailbody2": "Didn't work? Check your junk inbox or click {{link0}} to try again.", "emailbody2.link0": "here", "error": { "invalidEmail": "That email address isn't valid. Try again.", - "generic": "There was an error logging you in. Try again or contact support with the error code {err}", - "userexists": "That user already exists. Try {link0}?", + "generic": "There was an error logging you in. Try again or contact support with the error code {{err}}", + "userexists": "That user already exists. Try {{link0}}?", "userexists.link0": "logging in" }, - "already": "Already have an account? {link0}", + "already": "Already have an account? {{link0}}", "already.link0": "Login" -} \ No newline at end of file +} diff --git a/tfweb/src/lib/i18n/locales/nl.json b/tfweb/src/lib/i18n/locales/nl.json deleted file mode 100644 index a0736be..0000000 --- a/tfweb/src/lib/i18n/locales/nl.json +++ /dev/null @@ -1,223 +0,0 @@ -{ - "itworks": { - "header": "Het werkt!", - "body": "Als u deze pagina ziet, is tfweb geïnstalleerd en (waarschijnlijk) correct geconfigureerd.", - "linkbody": "Misschien wilde je het {link0} of {link1}?", - "linkbody.link0": "beheerdersdashboard bezoeken", - "linkbody.link1": "een account aanmaken" - }, - "login": { - "title": "Log in to your account", - "subtitle": "We'll send you an email with a \"magic link\".", - "label": "What is your email?", - "button": "Log in", - "email": "Check your email", - "emailbody": "We sent you an email with a link to complete logging in.", - "emailbody2": "Didn't work? Check your junk inbox or click {link0} to try again.", - "emailbody2.link0": "here", - "error": { - "invalidEmail": "That email address isn't valid. Try again.", - "generic": "There was an error logging you in. Try again or contact support with the error code {err}", - "usermissing": "That user does not exist." - }, - "need": "Don't have an account? {link0}", - "need.link0": "Signup" - }, - "signup": { - "title": "Create an account", - "subtitle": "We'll send you an email with a \"magic link\"", - "label": "What is your email?", - "button": "Create account", - "email": "Check your email", - "emailbody": "We sent you an email with a link to complete signing up.", - "emailbody2": "Didn't work? Check your junk inbox or click {link0} to try again.", - "emailbody2.link0": "here", - "error": { - "invalidEmail": "That email address isn't valid. Try again.", - "generic": "There was an error logging you in. Try again or contact support with the error code {err}", - "userexists": "That user already exists. Try {link0}?", - "userexists.link0": "logging in" - }, - "already": "Already have an account? {link0}", - "already.link0": "Login" - }, - "ml": { - "header": "Authenticated!", - "body": "Redirecting to admin page...", - "error": { - "notoken": "magic link token missing", - "badtoken": "token is invalid or has expired" - } - }, - "2fasetup": { - "title": "Configure two-factor authentication", - "body": "Trifid requires all accounts to use TOTP two-factor authentication to help ensure the security of your network.", - "scan": "Scan this QR code with an authenticator app, like Authy or Google Authenticator.", - "code": "Or, enter the TOTP secret below into an authenticator app.", - "verify": "Verify by entering the 6-digit code shown in your authenticator into the box below.", - "button": "Enable 2FA", - "error": { - "generic": "Unable to enable 2fa: {err}", - "api": "Unable to contact the server. Try again later.", - "ERR_UNAUTHORIZED": "Incorrect 2FA code" - } - }, - "2fa": { - "title": "Authenticate with TOTP", - "subtitle": "Enter the 6-digit code displayed in your authenticator app", - "label": "TOTP Code", - "button": "Verify", - "error": { - "ERR_UNAUTHORIZED": "Invalid 2FA code" - } - }, - "networkcreate": { - "title": "Create your network", - "explain": "This defines what IP addresses will be assigned to your devices. The range you enter below must fall within either the RFC 1918 Private Address Space or the RFC 6598 Shared Address Space. Enter your network via CIDR notation.", - "label": "Network range", - "button": "Create network", - "valid": "Valid - {numIps} addresses ({start} to {end})", - "invalid": "Invalid", - "error": { - "generic": "Unable to create network: {err}", - "api": "Unable to contact the server. Try again later." - } - }, - "hosts": { - "name": "Name", - "role": "Role", - "lastseen": "Last Seen", - "actions": "Actions", - "ipaddr": "IP Address", - "edit": "Edit", - "enrollbtn": "Enroll", - "deletehost": "Delete", - "config": "Configuration", - "add": "Add", - "create": { - "name": "Host name", - "btn": "Add host", - "cancel": "Cancel", - "role": "Role", - "ip": "IP Address", - "error": { - "needsname": "Host name is required.", - "needsrole": "Role is required.", - "invalidip": "Invalid IP address", - "ERR_DUPLICATE_VALUE": "IP address already in use" - } - }, - "enroll": { - "title": "Enrolling host {host}", - "explainer": "Enrolling this host will allow it to communicate on your network. You'll need to have compatible client software installed first.", - "done": "Done, return me to the hosts page", - "cancel": "Nevermind", - "code": "Input {code} as your enrollment code into any trifid-compatible client software to enroll. It will expire in {expires} minutes." - }, - "delete": { - "title": "Are you sure you want to delete the host {host}?", - "explainer": "This action cannot be undone. This host will still be able to connect to the network unless you block it first.", - "confirm": "I'm sure", - "cancel": "Nevermind", - "error": { - "ERR_DB_ERROR": "Removal failed" - } - } - }, - "roles": { - "delete": { - "title": "Are you sure you want to delete the role {rule}?", - "explainer": "This action cannot be undone. This role must be removed from all hosts, lighthouses, and relays prior to deleting it.", - "confirm": "I'm sure", - "cancel": "Nevermind", - "error": { - "ERR_DB_ERROR": "Removal failed (role was probably still attached to a host)" - } - }, - "create": "Add", - "explain": "Roles control how hosts, lighthouses, and relays communicate through firewall rules.", - "noroles": "You don't have any roles. You'll need to add at least one before you can add any hosts.", - "name": "Name", - "rules": "Rule count", - "description": "Description", - "actions": "Actions", - "add": { - "title": "Create Role", - "any": "Any", - "name": "Role name", - "desc": "Role description", - "button": "Create role", - "cancel": "Cancel", - "rules": "Inbound firewall rules", - "rulesexplainer": "Inbound traffic is denied by default. Add rules to allow traffic from hosts belonging to specific roles.", - "rulescols": { - "description": "Description", - "protocol": "Protocol", - "portrange": "Port range", - "allowedrole": "Allowed role", - "actions": "Actions" - }, - "rulesadd": "Add rule", - "editrule": { - "protocol": "Protocol", - "range": "Port or port range", - "role": "Allowed role", - "desc": "Description", - "add": "Add rule", - "edit": "Save edit", - "cancel": "Cancel" - }, - "error": { - "needsname": "Role Name is required" - } - }, - "edit": { - "title": "Editing role {rule}", - "any": "Any", - "name": "Role name", - "desc": "Role description", - "button": "Save", - "cancel": "Cancel", - "rules": "Inbound firewall rules", - "rulesexplainer": "Inbound traffic is denied by default. Add rules to allow traffic from hosts belonging to specific roles.", - "rulescols": { - "description": "Description", - "protocol": "Protocol", - "portrange": "Port range", - "allowedrole": "Allowed role", - "actions": "Actions" - }, - "ruleremove": "Remove rule", - "ruleedit": "Edit rule", - "rulesadd": "Add rule", - "editrule": { - "protocol": "Protocol", - "range": "Port or port range", - "role": "Allowed role", - "desc": "Description", - "add": "Add rule", - "edit": "Save edit", - "cancel": "Cancel" - } - } - }, - "common": { - "title": "{title} | Trifid Web UI", - "page": { - "itworks": "It Works!", - "admin": "Admin Panel", - "login": "Login", - "ml": "Verify Magic Link", - "2fasetup": "Configure TOTP", - "2fa": "2-Factor Authentication", - "networkcreate": "Create Network", - "hosts": "Hosts", - "roles": "Roles", - "lighthouses": "Lighthouses", - "relays": "Relays", - "addhost": "Add Host" - }, - "logout": "Log out", - "loading": "Dashboard is loading" - } -} diff --git a/tfweb/src/lib/i18n/translations.ts b/tfweb/src/lib/i18n/translations.ts index 1e70fb8..5ebf6aa 100644 --- a/tfweb/src/lib/i18n/translations.ts +++ b/tfweb/src/lib/i18n/translations.ts @@ -1,27 +1,61 @@ import i18n from 'sveltekit-i18n'; -import type {Config} from 'sveltekit-i18n'; - -function buildLoader(locale: string, key: string, file: string) { - return { - locale: locale, - key: key, - loader: async () => ( await import (file)).default - } -} +import type {Config} from 'sveltekit-i18n' const config: Config = ({ loaders: [ - buildLoader('en', '2fa', './locales/en/2fa.json'), - buildLoader('en', '2fasetup', './locales/en/2fasetup.json'), - buildLoader('en', 'common', './locales/en/common.json'), - buildLoader('en', 'hosts', './locales/en/hosts.json'), - buildLoader('en', 'itworks', './locales/en/itworks.json'), - buildLoader('en', 'login', './locales/en/login.json'), - buildLoader('en', 'ml', './locales/en/ml.json'), - buildLoader('en', 'networkcreate', './locales/en/networkcreate.json'), - buildLoader('en', 'roles', './locales/en/roles.json'), - buildLoader('en', 'signup', './locales/en/signup.json'), + // ENGLISH // + { + locale: 'en', + key: '2fa', + loader: async () => (await import('./locales/en/2fa.json')).default + }, + { + locale: 'en', + key: '2fasetup', + loader: async () => (await import('./locales/en/2fasetup.json')).default + }, + { + locale: 'en', + key: 'common', + loader: async () => (await import('./locales/en/common.json')).default + }, + { + locale: 'en', + key: 'hosts', + loader: async () => (await import('./locales/en/hosts.json')).default + }, + { + locale: 'en', + key: 'itworks', + loader: async () => (await import('./locales/en/itworks.json')).default + }, + { + locale: 'en', + key: 'login', + loader: async () => (await import('./locales/en/login.json')).default + }, + { + locale: 'en', + key: 'ml', + loader: async () => (await import('./locales/en/ml.json')).default + }, + { + locale: 'en', + key: 'networkcreate', + loader: async () => (await import('./locales/en/networkcreate.json')).default + }, + { + locale: 'en', + key: 'roles', + loader: async () => (await import('./locales/en/roles.json')).default + }, + { + locale: 'en', + key: 'signup', + loader: async () => (await import('./locales/en/signup.json')).default + }, + // END ENGLISH // ] }) -export const { t, locale, locales, loading, loadTranslations } = new i18n(config); \ No newline at end of file +export const { t, locale, locales, loading, loadTranslations } = new i18n(config); diff --git a/tfweb/src/routes/+layout.svelte b/tfweb/src/routes/+layout.svelte index 410aed6..c6b794d 100644 --- a/tfweb/src/routes/+layout.svelte +++ b/tfweb/src/routes/+layout.svelte @@ -6,12 +6,13 @@ import {onMount} from "svelte"; onMount(async () => { + const popper = await import("@popperjs/core"); const bootstrap = await import("bootstrap/dist/js/bootstrap.js"); - document.onload(() => { - const tooltipTriggerList = document.querySelectorAll('[data-bs-toggle="tooltip"]') - const tooltipList = [...tooltipTriggerList].map(tooltipTriggerEl => new bootstrap.Tooltip(tooltipTriggerEl)) - }); + document.B = bootstrap; + document.P = popper; + + }) </script> diff --git a/tfweb/src/routes/+layout.ts b/tfweb/src/routes/+layout.ts index 5e907ba..e675e5a 100644 --- a/tfweb/src/routes/+layout.ts +++ b/tfweb/src/routes/+layout.ts @@ -1,9 +1,21 @@ import type { LayoutLoad } from './$types' import {loadTranslations} from "$lib/i18n/translations"; +import {browser} from "$app/environment"; export const load: LayoutLoad = async ({url}) => { const { pathname } = url; - const initLocale = 'en'; + + let locale = 'en'; + + // determine locale + if (browser) { + if (window.localStorage.getItem("locale") != null) { + locale = window.localStorage.getItem("locale"); + } + window.localStorage.setItem("locale", locale); + } + + const initLocale = locale; await loadTranslations(initLocale, pathname); return {}; } diff --git a/tfweb/src/routes/+page.svelte b/tfweb/src/routes/+page.svelte index 439e53a..390adaa 100644 --- a/tfweb/src/routes/+page.svelte +++ b/tfweb/src/routes/+page.svelte @@ -14,12 +14,12 @@ </script> <svelte:head> - <title>{$t("common.title", {values: {title: $t("common.page.admin")}})}</title> + <title>{$t("common.title", {title: $t("common.page.admin")})}</title> </svelte:head> <LoadingWrapper isLoading={currentlyLoading} isError={isError} error={error}> <h1>{$t('itworks.header')}</h1> <p>{$t('itworks.body')}</p> <!-- eslint-disable-next-line svelte/no-at-html-tags --> - <p>{@html $t('itworks.linkbody', {values:{link0:'<a href="/admin">'+$t('itworks.linkbody.link0')+'</a>',link1:'<a href="/signup">'+$t('itworks.linkbody.link1')+'</a>'}})}</p> + <p>{@html $t('itworks.linkbody', {link0:'<a href="/admin">'+$t('itworks.linkbody.link0')+'</a>',link1:'<a href="/signup">'+$t('itworks.linkbody.link1')+'</a>'})}</p> </LoadingWrapper> diff --git a/tfweb/src/routes/2fa/+page.svelte b/tfweb/src/routes/2fa/+page.svelte index 3d12337..1383d17 100644 --- a/tfweb/src/routes/2fa/+page.svelte +++ b/tfweb/src/routes/2fa/+page.svelte @@ -67,7 +67,7 @@ </script> <svelte:head> - <title>{$t("common.title", {values: {title: $t("common.page.2fa")}})}</title> + <title>{$t("common.title", {title: $t("common.page.2fa")})}</title> </svelte:head> <LoadingWrapper isLoading={currentlyLoading} isError={isError} error={error}> diff --git a/tfweb/src/routes/2fasetup/+page.svelte b/tfweb/src/routes/2fasetup/+page.svelte index 6100de9..6b3e8dd 100644 --- a/tfweb/src/routes/2fasetup/+page.svelte +++ b/tfweb/src/routes/2fasetup/+page.svelte @@ -102,7 +102,7 @@ </script> <svelte:head> - <title>{$t("common.title", {values: {title: $t("common.page.2fasetup")}})}</title> + <title>{$t("common.title", {title: $t("common.page.2fasetup")})}</title> </svelte:head> <LoadingWrapper isLoading={currentlyLoading} isError={isError} error={error}> diff --git a/tfweb/src/routes/admin/+page.svelte b/tfweb/src/routes/admin/+page.svelte index c119818..28ff1a5 100644 --- a/tfweb/src/routes/admin/+page.svelte +++ b/tfweb/src/routes/admin/+page.svelte @@ -49,7 +49,7 @@ return; } else { isError = true; - error = $t("networkcreate.error.generic", {values:{err:resp_json.errors[0].code}}); + error = $t("networkcreate.error.generic", {err:resp_json.errors[0].code}); loading = false; return; } @@ -68,7 +68,7 @@ </script> <svelte:head> - <title>{$t("common.title", {values: {title: $t("common.page.admin")}})}</title> + <title>{$t("common.title", {title: $t("common.page.admin")})}</title> </svelte:head> <LoadingWrapper isLoading={currentlyLoading} isError={isError} error={error}> diff --git a/tfweb/src/routes/hosts/+page.svelte b/tfweb/src/routes/hosts/+page.svelte index 49019c9..89fa2d3 100644 --- a/tfweb/src/routes/hosts/+page.svelte +++ b/tfweb/src/routes/hosts/+page.svelte @@ -10,6 +10,7 @@ import type {Host} from "$lib/api/models/Host.ts"; import type {Role} from "$lib/api/models/Role.ts"; import AdminLayout from "$components/AdminLayout.svelte"; + import {updateTooltips} from "$lib/Tooltips"; let loading = true; let isError = false; @@ -56,7 +57,7 @@ return; } else { isError = true; - error = $t("networkcreate.error.generic", {values:{err:resp_json.errors[0].code}}); + error = $t("networkcreate.error.generic", {err:resp_json.errors[0].code}); loading = false; return; } @@ -83,6 +84,7 @@ roles = (await rolesApi.rolesList()).data!; loading = false; + updateTooltips(); }); function getRoleName(byId: string): string { @@ -96,7 +98,7 @@ </script> <svelte:head> - <title>{$t("common.title", {values: {title: $t("common.page.hosts")}})}</title> + <title>{$t("common.title", {title: $t("common.page.hosts")})}</title> </svelte:head> <LoadingWrapper isLoading={currentlyLoading} isError={isError} error={error}> @@ -118,16 +120,22 @@ {#each hosts as host} {#if !(host.isLighthouse || host.isRelay)} <tr> - <td><a href="/hosts/{host.id}/edit">{host.name}</a></td> + <td> + <a href="/hosts/{host.id}/edit">{host.name}</a> + {#if host.isBlocked} + <u class="text-danger" data-bs-toggle="tooltip" data-bs-title="Re-enroll to unblock"><i class="fas fa-fw fa-ban"></i> Blocked</u> + {/if} + </td> <td>{host.metadata?.lastSeenAt}</td> <td>{host.ipAddress}</td> <td><a href="/roles/{host.roleID}/edit">{getRoleName(host.roleID)}</a></td> <td> <div class="btn-group"> - <a href="/hosts/{host.id}/enroll" title="{$t('hosts.enrollbtn')}" class="btn btn-success"><i class="fas fa-arrows-rotate fa-fw"></i></a> - <a href="/hosts/{host.id}/edit" title="{$t('hosts.edit')}" class="btn btn-primary"><i class="fas fa-pencil fa-fw"></i></a> - <a href="/hosts/{host.id}/edit/config" title="{$t('hosts.config')}" class="btn btn-info"><i class="fas fa-gear fa-fw"></i></a> - <a href="/hosts/{host.id}/delete" title="{$t('hosts.deletehost')}" class="btn btn-danger"><i class="fas fa-trash fa-fw"></i></a> + <a href="/hosts/{host.id}/enroll" title="{$t('hosts.enrollbtn')}" data-bs-toggle="tooltip" class="btn btn-success"><i class="fas fa-arrows-rotate fa-fw"></i></a> + <a href="/hosts/{host.id}/edit" title="{$t('hosts.edit')}" data-bs-toggle="tooltip" class="btn btn-primary"><i class="fas fa-pencil fa-fw"></i></a> + <a href="/hosts/{host.id}/edit/config" title="{$t('hosts.config')}" data-bs-toggle="tooltip" class="btn btn-info"><i class="fas fa-gear fa-fw"></i></a> + <a href="/hosts/{host.id}/delete" title="{$t('hosts.deletehost')}" data-bs-toggle="tooltip" class="btn btn-danger"><i class="fas fa-trash fa-fw"></i></a> + <a href="/hosts/{host.id}/block" title="{$t('hosts.blockhost')}" data-bs-toggle="tooltip" class="btn btn-secondary"><i class="fas fa-ban fa-fw"></i></a> </div> </td> </tr> diff --git a/tfweb/src/routes/hosts/[host_id]/block/+page.svelte b/tfweb/src/routes/hosts/[host_id]/block/+page.svelte new file mode 100644 index 0000000..8c0c776 --- /dev/null +++ b/tfweb/src/routes/hosts/[host_id]/block/+page.svelte @@ -0,0 +1,136 @@ +<script lang="ts"> + import {loading as tLoading, t} from "$lib/i18n/translations"; + import LoadingWrapper from "$components/LoadingWrapper.svelte"; + import {onMount} from "svelte"; + import {APIResult, isAuthedMFA, isAuthedSession} from "$lib/auth.ts"; + import {Logger, logSetup} from "$lib/logger"; + import type {APIError} from "$lib/auth.ts"; + import {PUBLIC_BASE_URL} from "$env/static/public"; + import {Configuration, NetworksApi, HostsApi, FirewallRuleProtocolEnum, ResponseError} from "$lib/api"; + import type {FirewallRule} from "$lib/api"; + import AdminBar from "$components/AdminLayout.svelte"; + import {page} from "$app/stores"; + import AdminLayout from "$components/AdminLayout.svelte"; + import {load} from "../../../+layout"; + import type {HostGet200Response} from "$lib/api"; + + let loading = true; + let fullPageLoading = true; + let isError = false; + let error = ''; + $: currentlyLoading = $tLoading || fullPageLoading; + + logSetup(); + let logger = new Logger("hosts/block/+page.svelte"); + + let hosts; + let host: HostGet200Response = { + data: { + name: 'Loading' + } + }; + + let formErr = ''; + let hasFormErr = false; + + onMount(async () => { + let session_load_info = await isAuthedSession(); + if (session_load_info[0] == APIResult.Failed) { + let err = session_load_info[1] as APIError; + logger.error(`session load failed: ${err.code} ${err.message}`); + window.location.href = '/login'; + return; + } + + let mfa_load_info = await isAuthedMFA(); + if (mfa_load_info[0] == APIResult.Failed) { + let err = mfa_load_info[1] as APIError; + logger.error(`mfa load failed: ${err.code} ${err.message}`); + window.location.href = '/2fa'; + return; + } + + // pull networks + const configuration = new Configuration({ + basePath: PUBLIC_BASE_URL, + accessToken: window.localStorage.getItem("session") + " " + window.localStorage.getItem("mfa") + }); + + const networksApi = new NetworksApi(configuration); + let networks; + try { + networks = await networksApi.networksList(); + } catch (e) { + let resp_json = await e.response.json(); + if (resp_json.errors[0].code == "ERR_NO_ORG") { + window.location.href = "/networkcreate"; + return; + } else { + isError = true; + error = $t("networkcreate.error.generic", {err:resp_json.errors[0].code}); + loading = false; + return; + } + } + console.log(networks); + + if (networks.data?.length == 0) { + window.location.href = '/networkcreate'; + return; + } + + const hostsApi = new HostsApi(configuration); + hosts = await hostsApi.hostsList(); + + // pull our role + host = await hostsApi.hostGet({ + hostID: $page.params.host_id + }); + + fullPageLoading = false; + loading = false; + }); + + async function blockRole() { + loading = true; + const configuration = new Configuration({ + basePath: PUBLIC_BASE_URL, + accessToken: window.localStorage.getItem("session") + " " + window.localStorage.getItem("mfa") + }); + const hostsApi = new HostsApi(configuration); + try { + await hostsApi.hostBlock({hostID: $page.params.host_id}); + } catch (e) { + let body = await (<ResponseError>e).response.json(); + + console.log(body); + + formErr = $t("hosts.block.error." + body.errors[0].code); + hasFormErr = true; + loading = false; + + return; + } + window.location.href = "/hosts"; + } +</script> + +<svelte:head> + <title>{$t("common.title", {title: $t("common.page.hosts")})}</title> +</svelte:head> + +<LoadingWrapper isLoading={currentlyLoading} isError={isError} error={error}> + <AdminLayout selected="hosts"> + <h3>{$t("hosts.block.title", {host: host.data.name})}</h3> + <p>{$t("hosts.block.explainer")}</p> + {#if loading} + <button disabled class="btn btn-danger"><i class="fas fa-gear fa-spin"></i></button> + {:else} + <button on:click={blockRole} class="btn btn-danger">{$t("hosts.block.confirm")}</button> + {/if} + <button on:click={() => {window.location.href = "/hosts"}} class="btn btn-outline-info">{$t("hosts.block.cancel")}</button> + {#if hasFormErr} + <p class="text-danger">{formErr}</p> + {/if} + </AdminLayout> +</LoadingWrapper> diff --git a/tfweb/src/routes/hosts/[host_id]/delete/+page.svelte b/tfweb/src/routes/hosts/[host_id]/delete/+page.svelte index 991883f..59f1f08 100644 --- a/tfweb/src/routes/hosts/[host_id]/delete/+page.svelte +++ b/tfweb/src/routes/hosts/[host_id]/delete/+page.svelte @@ -67,7 +67,7 @@ return; } else { isError = true; - error = $t("networkcreate.error.generic", {values:{err:resp_json.errors[0].code}}); + error = $t("networkcreate.error.generic", {err:resp_json.errors[0].code}); loading = false; return; } @@ -116,12 +116,12 @@ </script> <svelte:head> - <title>{$t("common.title", {values: {title: $t("common.page.hosts")}})}</title> + <title>{$t("common.title", {title: $t("common.page.hosts")})}</title> </svelte:head> <LoadingWrapper isLoading={currentlyLoading} isError={isError} error={error}> <AdminLayout selected="hosts"> - <h3>{$t("hosts.delete.title", {values: {host: host.data.name}})}</h3> + <h3>{$t("hosts.delete.title", {host: host.data.name})}</h3> <p>{$t("hosts.delete.explainer")}</p> {#if loading} <button disabled class="btn btn-danger"><i class="fas fa-gear fa-spin"></i></button> diff --git a/tfweb/src/routes/hosts/[host_id]/enroll/+page.svelte b/tfweb/src/routes/hosts/[host_id]/enroll/+page.svelte index 13a2ca5..2e26504 100644 --- a/tfweb/src/routes/hosts/[host_id]/enroll/+page.svelte +++ b/tfweb/src/routes/hosts/[host_id]/enroll/+page.svelte @@ -68,7 +68,7 @@ return; } else { isError = true; - error = $t("networkcreate.error.generic", {values:{err:resp_json.errors[0].code}}); + error = $t("networkcreate.error.generic", {err:resp_json.errors[0].code}); loading = false; return; } @@ -100,17 +100,17 @@ </script> <svelte:head> - <title>{$t("common.title", {values: {title: $t("common.page.hosts")}})}</title> + <title>{$t("common.title", {title: $t("common.page.hosts")})}</title> </svelte:head> <LoadingWrapper isLoading={currentlyLoading} isError={isError} error={error}> <AdminLayout selected="hosts"> - <h3>{$t("hosts.enroll.title", {values: {host: host.data.name}})}</h3> + <h3>{$t("hosts.enroll.title", {host: host.data.name})}</h3> <p>{$t("hosts.enroll.explainer")}</p> {#if loading} <button disabled class="btn btn-success"><i class="fas fa-gear fa-spin"></i></button> {:else} - <p>{$t("hosts.enroll.code", {values:{code: code.code, expires: code.lifetimeSeconds / 60}})}</p> + <p>{$t("hosts.enroll.code", {code: code.code, expires: code.lifetimeSeconds / 60})}</p> <button on:click={window.location.href = "/hosts"} class="btn btn-success">{$t("hosts.enroll.done")}</button> {/if} diff --git a/tfweb/src/routes/hosts/add/+page.svelte b/tfweb/src/routes/hosts/add/+page.svelte index a2e0d20..f820d10 100644 --- a/tfweb/src/routes/hosts/add/+page.svelte +++ b/tfweb/src/routes/hosts/add/+page.svelte @@ -59,7 +59,7 @@ return; } else { isError = true; - error = $t("networkcreate.error.generic", {values:{err:resp_json.errors[0].code}}); + error = $t("networkcreate.error.generic", {err:resp_json.errors[0].code}); loading = false; return; } @@ -164,7 +164,7 @@ </script> <svelte:head> - <title>{$t("common.title", {values: {title: $t("common.page.hosts")}})}</title> + <title>{$t("common.title", {title: $t("common.page.hosts")})}</title> </svelte:head> <LoadingWrapper isLoading={currentlyLoading} isError={isError} error={error}> diff --git a/tfweb/src/routes/lighthouses/+page.svelte b/tfweb/src/routes/lighthouses/+page.svelte index a550d6f..f2f84cd 100644 --- a/tfweb/src/routes/lighthouses/+page.svelte +++ b/tfweb/src/routes/lighthouses/+page.svelte @@ -51,7 +51,7 @@ return; } else { isError = true; - error = $t("networkcreate.error.generic", {values:{err:resp_json.errors[0].code}}); + error = $t("networkcreate.error.generic", {err:resp_json.errors[0].code}); loading = false; return; } @@ -68,7 +68,7 @@ </script> <svelte:head> - <title>{$t("common.title", {values: {title: $t("common.page.lighthouses")}})}</title> + <title>{$t("common.title", {title: $t("common.page.lighthouses")})}</title> </svelte:head> <LoadingWrapper isLoading={currentlyLoading} isError={isError} error={error}> diff --git a/tfweb/src/routes/login/+page.svelte b/tfweb/src/routes/login/+page.svelte index fca9858..7f602bb 100644 --- a/tfweb/src/routes/login/+page.svelte +++ b/tfweb/src/routes/login/+page.svelte @@ -55,7 +55,7 @@ if (err.code == "ERR_USER_DOES_NOT_EXIST") { errForm = $t('login.error.usermissing'); } else { - errForm = $t('login.error.generic', {values: {err: (auth_result[1] as APIError).code}}); + errForm = $t('login.error.generic', {err: (auth_result[1] as APIError).code}); } loading = false; @@ -68,7 +68,7 @@ </script> <svelte:head> - <title>{$t("common.title", {values: {title: $t("common.page.login")}})}</title> + <title>{$t("common.title", {title: $t("common.page.login")})}</title> </svelte:head> <LoadingWrapper isLoading={currentlyLoading} isError={isError} error={error}> @@ -80,7 +80,7 @@ <p class="card-text">{$t('login.emailbody')}</p> <!-- eslint-disable-next-line svelte/no-at-html-tags --> - <p class="card-text">{@html $t('login.emailbody2', {values:{link0:'<a href="/admin?definitely_not_taking_advantage_of_the_redirect">'+$t('login.emailbody2.link0')+'</a>'}})}</p> + <p class="card-text">{@html $t('login.emailbody2', {link0:'<a href="/admin?definitely_not_taking_advantage_of_the_redirect">'+$t('login.emailbody2.link0')+'</a>'})}</p> {:else} <h4 class="card-title">{$t('login.title')}</h4> <h6 class="card-subtitle">{$t('login.subtitle')}</h6> @@ -99,7 +99,7 @@ {/if} </form> <!-- eslint-disable-next-line svelte/no-at-html-tags --> - <p class="block mt-2 mb-0">{@html $t('login.need', {values:{link0:'<a href="/signup">'+$t('login.need.link0')+'</a>'}})}</p> + <p class="block mt-2 mb-0">{@html $t('login.need', {link0:'<a href="/signup">'+$t('login.need.link0')+'</a>'})}</p> {/if} </div> </div> diff --git a/tfweb/src/routes/magic-link/+page.svelte b/tfweb/src/routes/magic-link/+page.svelte index 5684231..01b73fc 100644 --- a/tfweb/src/routes/magic-link/+page.svelte +++ b/tfweb/src/routes/magic-link/+page.svelte @@ -45,7 +45,7 @@ </script> <svelte:head> - <title>{$t("common.title", {values: {title: $t("common.page.login")}})}</title> + <title>{$t("common.title", {title: $t("common.page.login")})}</title> </svelte:head> <LoadingWrapper isLoading={currentlyLoading} isError={isError} error={error}> diff --git a/tfweb/src/routes/networkcreate/+page.svelte b/tfweb/src/routes/networkcreate/+page.svelte index 6bfd48a..b403fe7 100644 --- a/tfweb/src/routes/networkcreate/+page.svelte +++ b/tfweb/src/routes/networkcreate/+page.svelte @@ -1,5 +1,5 @@ <script lang="ts"> - import {isLoading, number, t} from "svelte-i18n"; + import {loading as tLoading, t} from "$lib/i18n/translations"; import LoadingWrapper from "$components/LoadingWrapper.svelte"; import {onMount} from "svelte"; import {APIResult, authTotp, isAuthedMFA, isAuthedSession} from "$lib/auth.ts"; @@ -47,7 +47,7 @@ let resp_json = await e.response.json(); if (resp_json.errors[0].code != "ERR_NO_ORG") { isError = true; - error = $t("networkcreate.error.generic", {values:{err:resp_json.errors[0].code}}); + error = $t("networkcreate.error.generic", {err:resp_json.errors[0].code}); loading = false; return; } @@ -63,7 +63,7 @@ let cidr = "100.100.0.0/22"; let valid = true; - let sub = $t("networkcreate.valid", {values:{numIps:$number(1024), start: "100.64.0.0", end: "100.127.255.255"}}) + let sub = $t("networkcreate.valid", {numIps:1024, start: "100.64.0.0", end: "100.127.255.255"}) const regex = new RegExp(/^([0-9]{1,3}\.){3}[0-9]{1,3}(\/([0-9]|[1-2][0-9]|3[0-2]))?$/im); @@ -138,7 +138,7 @@ let first = [first_num >>> 24 & 0xFF, first_num >>> 16 & 0xFF, first_num >>> 8 & 0xFF, first_num & 0xFF].join("."); let last = [last_num >>> 24 & 0xFF, last_num >>> 16 & 0xFF, last_num >>> 8 & 0xFF, last_num & 0xFF].join("."); - sub = $t("networkcreate.valid", {values:{numIps:$number(addressCount), start: first, end: last}}); + sub = $t("networkcreate.valid", {numIps:addressCount, start: first, end: last}); valid = true; } @@ -161,7 +161,7 @@ return; } - error = $t('networkcreate.error.generic', {values:{err:etext}}); + error = $t('networkcreate.error.generic', {err:etext}); loading = false; return; } @@ -173,7 +173,7 @@ </script> <svelte:head> - <title>{$t("common.title", {values: {title: $t("common.page.networkcreate")}})}</title> + <title>{$t("common.title", {title: $t("common.page.networkcreate")})}</title> </svelte:head> <LoadingWrapper isLoading={currentlyLoading} isError={isError} error={error}> diff --git a/tfweb/src/routes/relays/+page.svelte b/tfweb/src/routes/relays/+page.svelte index 2f15e96..444f3af 100644 --- a/tfweb/src/routes/relays/+page.svelte +++ b/tfweb/src/routes/relays/+page.svelte @@ -51,7 +51,7 @@ return; } else { isError = true; - error = $t("networkcreate.error.generic", {values:{err:resp_json.errors[0].code}}); + error = $t("networkcreate.error.generic", {err:resp_json.errors[0].code}); loading = false; return; } @@ -68,7 +68,7 @@ </script> <svelte:head> - <title>{$t("common.title", {values: {title: $t("common.page.relays")}})}</title> + <title>{$t("common.title", {title: $t("common.page.relays")})}</title> </svelte:head> <LoadingWrapper isLoading={currentlyLoading} isError={isError} error={error}> diff --git a/tfweb/src/routes/roles/+page.svelte b/tfweb/src/routes/roles/+page.svelte index 898532d..d7e2cb5 100644 --- a/tfweb/src/routes/roles/+page.svelte +++ b/tfweb/src/routes/roles/+page.svelte @@ -8,6 +8,7 @@ import {PUBLIC_BASE_URL} from "$env/static/public"; import {Configuration, NetworksApi, RolesApi} from "$lib/api"; import AdminLayout from "$components/AdminLayout.svelte"; + import {updateTooltips} from "$lib/Tooltips"; let loading = true; let isError = false; @@ -53,7 +54,7 @@ return; } else { isError = true; - error = $t("networkcreate.error.generic", {values:{err:resp_json.errors[0].code}}); + error = $t("networkcreate.error.generic", {err:resp_json.errors[0].code}); loading = false; return; } @@ -71,6 +72,7 @@ console.log(roles); loading = false; + updateTooltips(); }) async function roleAdd() { @@ -80,7 +82,7 @@ </script> <svelte:head> - <title>{$t("common.title", {values: {title: $t("common.page.roles")}})}</title> + <title>{$t("common.title", {title: $t("common.page.roles")})}</title> </svelte:head> <LoadingWrapper isLoading={currentlyLoading} isError={isError} error={error}> @@ -115,8 +117,8 @@ <td>{role.description}</td> <td> <div class="btn-group"> - <a href="/roles/{role.id}/edit" class="btn btn-primary"><i class="fas fa-pencil fa-fw"></i></a> - <a href="/roles/{role.id}/delete" class="btn btn-danger"><i class="fas fa-trash fa-fw"></i></a> + <a href="/roles/{role.id}/edit" title="{$t('roles.editrole')}" data-bs-toggle="tooltip" class="btn btn-primary"><i class="fas fa-pencil fa-fw"></i></a> + <a href="/roles/{role.id}/delete" title="{$t('roles.deleterole')}" data-bs-toggle="tooltip" class="btn btn-danger"><i class="fas fa-trash fa-fw"></i></a> </div> </td> diff --git a/tfweb/src/routes/roles/[role_id]/delete/+page.svelte b/tfweb/src/routes/roles/[role_id]/delete/+page.svelte index 183acbe..5d13428 100644 --- a/tfweb/src/routes/roles/[role_id]/delete/+page.svelte +++ b/tfweb/src/routes/roles/[role_id]/delete/+page.svelte @@ -68,7 +68,7 @@ return; } else { isError = true; - error = $t("networkcreate.error.generic", {values:{err:resp_json.errors[0].code}}); + error = $t("networkcreate.error.generic", {err:resp_json.errors[0].code}); loading = false; return; } @@ -117,12 +117,12 @@ </script> <svelte:head> - <title>{$t("common.title", {values: {title: $t("common.page.roles")}})}</title> + <title>{$t("common.title", {title: $t("common.page.roles")})}</title> </svelte:head> <LoadingWrapper isLoading={currentlyLoading} isError={isError} error={error}> <AdminLayout selected="roles"> - <h3>{$t("roles.delete.title", {values: {rule: role.data.name}})}</h3> + <h3>{$t("roles.delete.title", {rule: role.data.name})}</h3> <p>{$t("roles.delete.explainer")}</p> {#if loading} <button disabled class="btn btn-danger"><i class="fas fa-gear fa-spin"></i></button> diff --git a/tfweb/src/routes/roles/[role_id]/edit/+page.svelte b/tfweb/src/routes/roles/[role_id]/edit/+page.svelte index 625afab..d96f4c9 100644 --- a/tfweb/src/routes/roles/[role_id]/edit/+page.svelte +++ b/tfweb/src/routes/roles/[role_id]/edit/+page.svelte @@ -276,7 +276,7 @@ return; } else { isError = true; - error = $t("networkcreate.error.generic", {values:{err:resp_json.errors[0].code}}); + error = $t("networkcreate.error.generic", {err:resp_json.errors[0].code}); loading = false; return; } @@ -346,12 +346,12 @@ </script> <svelte:head> - <title>{$t("common.title", {values: {title: $t("common.page.roles")}})}</title> + <title>{$t("common.title", {title: $t("common.page.roles")})}</title> </svelte:head> <LoadingWrapper isLoading={currentlyLoading} isError={isError} error={error}> <AdminLayout selected="roles"> - <h3>{$t("roles.edit.title", {values: {rule: roleName}})}</h3> + <h3>{$t("roles.edit.title", {rule: roleName})}</h3> <form> <label class="mt-2 form-label" for="roleDesc">{$t("roles.edit.desc")}</label> <input class="form-control" bind:value={roleDescription} type="text" id="roleDesc" /> diff --git a/tfweb/src/routes/roles/add/+page.svelte b/tfweb/src/routes/roles/add/+page.svelte index 29a8b14..5a28983 100644 --- a/tfweb/src/routes/roles/add/+page.svelte +++ b/tfweb/src/routes/roles/add/+page.svelte @@ -58,7 +58,7 @@ return; } else { isError = true; - error = $t("networkcreate.error.generic", {values:{err:resp_json.errors[0].code}}); + error = $t("networkcreate.error.generic", {err:resp_json.errors[0].code}); loading = false; return; } @@ -306,7 +306,7 @@ </script> <svelte:head> - <title>{$t("common.title", {values: {title: $t("common.page.roles")}})}</title> + <title>{$t("common.title", {title: $t("common.page.roles")})}</title> </svelte:head> <LoadingWrapper isLoading={currentlyLoading} isError={isError} error={error}> diff --git a/tfweb/src/routes/signup/+page.svelte b/tfweb/src/routes/signup/+page.svelte index 0f4eff5..c425d71 100644 --- a/tfweb/src/routes/signup/+page.svelte +++ b/tfweb/src/routes/signup/+page.svelte @@ -53,9 +53,9 @@ let err = auth_result[1] as APIError; if (err.code == "ERR_USER_EXISTS") { - errForm = $t('signup.error.userexists', {values:{link0:'<a href="/login">'+$t('signup.error.userexists.link0')+'</a>'}}); + errForm = $t('signup.error.userexists', {link0:'<a href="/login">'+$t('signup.error.userexists.link0')+'</a>'}); } else { - errForm = $t('signup.error.generic', {values: {err: (auth_result[1] as APIError).code}}); + errForm = $t('signup.error.generic', {err: (auth_result[1] as APIError).code}); } loading = false; @@ -68,7 +68,7 @@ </script> <svelte:head> - <title>{$t("common.title", {values: {title: $t("common.page.signup")}})}</title> + <title>{$t("common.title", {title: $t("common.page.signup")})}</title> </svelte:head> <LoadingWrapper isLoading={currentlyLoading} isError={isError} error={error}> @@ -80,7 +80,7 @@ <p class="card-text">{$t('signup.emailbody')}</p> <!-- eslint-disable-next-line svelte/no-at-html-tags --> - <p class="card-text">{@html $t('signup.emailbody2', {values:{link0:'<a href="/admin?definitely_not_taking_advantage_of_the_redirect">'+$t('signup.emailbody2.link0')+'</a>'}})}</p> + <p class="card-text">{@html $t('signup.emailbody2', {link0:'<a href="/admin?definitely_not_taking_advantage_of_the_redirect">'+$t('signup.emailbody2.link0')+'</a>'})}</p> {:else} <h4 class="card-title">{$t('signup.title')}</h4> <h6 class="card-subtitle">{$t('signup.subtitle')}</h6> @@ -100,7 +100,7 @@ {/if} </form> <!-- eslint-disable-next-line svelte/no-at-html-tags --> - <p class="block mt-2 mb-0">{@html $t('signup.already', {values:{link0:'<a href="/login">'+$t('signup.already.link0')+'</a>'}})}</p> + <p class="block mt-2 mb-0">{@html $t('signup.already', {link0:'<a href="/login">'+$t('signup.already.link0')+'</a>'})}</p> {/if} </div> diff --git a/tfweb/svelte.config.js b/tfweb/svelte.config.js index bfd912f..844aaa4 100644 --- a/tfweb/svelte.config.js +++ b/tfweb/svelte.config.js @@ -1,4 +1,4 @@ -import adapter from '@sveltejs/adapter-auto'; +import adapter from '@sveltejs/adapter-node'; import { vitePreprocess } from '@sveltejs/kit/vite'; /** @type {import('@sveltejs/kit').Config} */ diff --git a/tfweb/yarn.lock b/tfweb/yarn.lock index 3e986fc..310af27 100644 --- a/tfweb/yarn.lock +++ b/tfweb/yarn.lock @@ -222,6 +222,46 @@ resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.11.8.tgz#6b79032e760a0899cd4204710beede972a3a185f" integrity sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A== +"@rollup/plugin-commonjs@^25.0.0": + version "25.0.5" + resolved "https://registry.yarnpkg.com/@rollup/plugin-commonjs/-/plugin-commonjs-25.0.5.tgz#0bac8f985a5de151b4b09338847f8c7f20a28a29" + integrity sha512-xY8r/A9oisSeSuLCTfhssyDjo9Vp/eDiRLXkg1MXCcEEgEjPmLU+ZyDB20OOD0NlyDa/8SGbK5uIggF5XTx77w== + dependencies: + "@rollup/pluginutils" "^5.0.1" + commondir "^1.0.1" + estree-walker "^2.0.2" + glob "^8.0.3" + is-reference "1.2.1" + magic-string "^0.27.0" + +"@rollup/plugin-json@^6.0.0": + version "6.0.1" + resolved "https://registry.yarnpkg.com/@rollup/plugin-json/-/plugin-json-6.0.1.tgz#7e2efcf5ed549963f1444e010611d22f463931c0" + integrity sha512-RgVfl5hWMkxN1h/uZj8FVESvPuBJ/uf6ly6GTj0GONnkfoBN5KC0MSz+PN2OLDgYXMhtG0mWpTrkiOjoxAIevw== + dependencies: + "@rollup/pluginutils" "^5.0.1" + +"@rollup/plugin-node-resolve@^15.0.1": + version "15.2.3" + resolved "https://registry.yarnpkg.com/@rollup/plugin-node-resolve/-/plugin-node-resolve-15.2.3.tgz#e5e0b059bd85ca57489492f295ce88c2d4b0daf9" + integrity sha512-j/lym8nf5E21LwBT4Df1VD6hRO2L2iwUeUmP7litikRsVp1H6NWx20NEp0Y7su+7XGc476GnXXc4kFeZNGmaSQ== + dependencies: + "@rollup/pluginutils" "^5.0.1" + "@types/resolve" "1.20.2" + deepmerge "^4.2.2" + is-builtin-module "^3.2.1" + is-module "^1.0.0" + resolve "^1.22.1" + +"@rollup/pluginutils@^5.0.1": + version "5.0.5" + resolved "https://registry.yarnpkg.com/@rollup/pluginutils/-/pluginutils-5.0.5.tgz#bbb4c175e19ebfeeb8c132c2eea0ecb89941a66c" + integrity sha512-6aEYR910NyP73oHiJglti74iRyOwgFU4x3meH/H8OJx6Ry0j6cOVZ5X/wTvub7G7Ao6qaHBEaNsV3GLJkSsF+Q== + dependencies: + "@types/estree" "^1.0.0" + estree-walker "^2.0.2" + picomatch "^2.3.1" + "@sveltejs/adapter-auto@^2.0.0": version "2.1.0" resolved "https://registry.yarnpkg.com/@sveltejs/adapter-auto/-/adapter-auto-2.1.0.tgz#cb36fd7081e3c4b1c9a9192b1a23c8c82dce8a1b" @@ -229,6 +269,16 @@ dependencies: import-meta-resolve "^3.0.0" +"@sveltejs/adapter-node@^1.3.1": + version "1.3.1" + resolved "https://registry.yarnpkg.com/@sveltejs/adapter-node/-/adapter-node-1.3.1.tgz#b3d8dee1ae09d008b2add093e84cf085f3333bbc" + integrity sha512-A0VgRQDCDPzdLNoiAbcOxGw4zT1Mc+n1LwT1OmO350R7WxrEqdMUChPPOd1iMfIDWlP4ie6E2d/WQf5es2d4Zw== + dependencies: + "@rollup/plugin-commonjs" "^25.0.0" + "@rollup/plugin-json" "^6.0.0" + "@rollup/plugin-node-resolve" "^15.0.1" + rollup "^3.7.0" + "@sveltejs/kit@^1.5.0": version "1.20.0" resolved "https://registry.yarnpkg.com/@sveltejs/kit/-/kit-1.20.0.tgz#5deed969badda2cd9d4c68c580362c492425be75" @@ -283,6 +333,11 @@ resolved "https://registry.yarnpkg.com/@types/cookie/-/cookie-0.5.1.tgz#b29aa1f91a59f35e29ff8f7cb24faf1a3a750554" integrity sha512-COUnqfB2+ckwXXSFInsFdOAWQzCCx+a5hq2ruyj+Vjund94RJQd4LG2u9hnvJrTgunKAaax7ancBYlDrNYxA0g== +"@types/estree@*", "@types/estree@^1.0.0": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.2.tgz#ff02bc3dc8317cd668dfec247b750ba1f1d62453" + integrity sha512-VeiPZ9MMwXjO32/Xu7+OwflfmeoRwkE/qzndw42gGtgJwZopBnzy2gD//NN1+go1mADzkDcqf/KnFRSjTJ8xJA== + "@types/json-schema@^7.0.9": version "7.0.12" resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.12.tgz#d70faba7039d5fca54c83c7dbab41051d2b6f6cb" @@ -305,6 +360,11 @@ dependencies: "@types/node" "*" +"@types/resolve@1.20.2": + version "1.20.2" + resolved "https://registry.yarnpkg.com/@types/resolve/-/resolve-1.20.2.tgz#97d26e00cd4a0423b4af620abecf3e6f442b7975" + integrity sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q== + "@types/semver@^7.3.12": version "7.5.0" resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.5.0.tgz#591c1ce3a702c45ee15f47a42ade72c2fd78978a" @@ -472,6 +532,13 @@ brace-expansion@^1.1.7: balanced-match "^1.0.0" concat-map "0.0.1" +brace-expansion@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-2.0.1.tgz#1edc459e0f0c548486ecf9fc99f2221364b9a0ae" + integrity sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA== + dependencies: + balanced-match "^1.0.0" + braces@^3.0.2, braces@~3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" @@ -484,6 +551,11 @@ buffer-crc32@^0.2.5: resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242" integrity sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ== +builtin-modules@^3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-3.3.0.tgz#cae62812b89801e9656336e46223e030386be7b6" + integrity sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw== + busboy@^1.6.0: version "1.6.0" resolved "https://registry.yarnpkg.com/busboy/-/busboy-1.6.0.tgz#966ea36a9502e43cdb9146962523b92f531f6893" @@ -545,6 +617,11 @@ color-name@~1.1.4: resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== +commondir@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b" + integrity sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg== + concat-map@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" @@ -581,7 +658,7 @@ deep-is@^0.1.3: resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831" integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ== -deepmerge@^4.3.1: +deepmerge@^4.2.2, deepmerge@^4.3.1: version "4.3.1" resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.3.1.tgz#44b5f2147cd3b00d4b56137685966f26fd25dd4a" integrity sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A== @@ -782,6 +859,11 @@ estraverse@^5.1.0, estraverse@^5.2.0: resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.3.0.tgz#2eea5290702f26ab8fe5370370ff86c965d21123" integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA== +estree-walker@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-2.0.2.tgz#52f010178c2a4c117a7757cfe942adb7d2da4cac" + integrity sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w== + esutils@^2.0.2, esutils@^2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" @@ -904,6 +986,17 @@ glob@^7.1.3: once "^1.3.0" path-is-absolute "^1.0.0" +glob@^8.0.3: + version "8.1.0" + resolved "https://registry.yarnpkg.com/glob/-/glob-8.1.0.tgz#d388f656593ef708ee3e34640fdfb99a9fd1c33e" + integrity sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^5.0.1" + once "^1.3.0" + globals@^13.19.0: version "13.20.0" resolved "https://registry.yarnpkg.com/globals/-/globals-13.20.0.tgz#ea276a1e508ffd4f1612888f9d1bad1e2717bf82" @@ -953,6 +1046,11 @@ has-flag@^4.0.0: resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== +has@^1.0.3: + version "1.0.4" + resolved "https://registry.yarnpkg.com/has/-/has-1.0.4.tgz#2eb2860e000011dae4f1406a86fe80e530fb2ec6" + integrity sha512-qdSAmqLF6209RFj4VVItywPMbm3vWylknmB3nvNiUIs72xAimcM8nVYxYr7ncvZq5qzk9MKIZR8ijqD/1QuYjQ== + ignore@^5.2.0: version "5.2.4" resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.2.4.tgz#a291c0c6178ff1b960befe47fcdec301674a6324" @@ -996,6 +1094,20 @@ is-binary-path@~2.1.0: dependencies: binary-extensions "^2.0.0" +is-builtin-module@^3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/is-builtin-module/-/is-builtin-module-3.2.1.tgz#f03271717d8654cfcaf07ab0463faa3571581169" + integrity sha512-BSLE3HnV2syZ0FK0iMA/yUGplUeMmNz4AW5fnTunbCIqZi4vG3WjJT9FHMy5D69xmAYBHXQhJdALdpwVxV501A== + dependencies: + builtin-modules "^3.3.0" + +is-core-module@^2.13.0: + version "2.13.0" + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.13.0.tgz#bb52aa6e2cbd49a30c2ba68c42bf3435ba6072db" + integrity sha512-Z7dk6Qo8pOCp3l4tsX2C5ZVas4V+UxwQodwZhLopL91TX8UyyHEXafPcyoeeWuLrwzHcr3igO78wNLwHJHsMCQ== + dependencies: + has "^1.0.3" + is-extglob@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" @@ -1013,6 +1125,11 @@ is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3, is-glob@~4.0.1: dependencies: is-extglob "^2.1.1" +is-module@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-module/-/is-module-1.0.0.tgz#3258fb69f78c14d5b815d664336b4cffb6441591" + integrity sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g== + is-number@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" @@ -1023,6 +1140,13 @@ is-path-inside@^3.0.3: resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-3.0.3.tgz#d231362e53a07ff2b0e0ea7fed049161ffd16283" integrity sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ== +is-reference@1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/is-reference/-/is-reference-1.2.1.tgz#8b2dac0b371f4bc994fdeaba9eb542d03002d0b7" + integrity sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ== + dependencies: + "@types/estree" "*" + isexe@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" @@ -1138,6 +1262,13 @@ minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2: dependencies: brace-expansion "^1.1.7" +minimatch@^5.0.1: + version "5.1.6" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-5.1.6.tgz#1cfcb8cf5522ea69952cd2af95ae09477f122a96" + integrity sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g== + dependencies: + brace-expansion "^2.0.1" + minimist@^1.2.0, minimist@^1.2.6: version "1.2.8" resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c" @@ -1259,6 +1390,11 @@ path-key@^3.1.0: resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== +path-parse@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" + integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== + path-type@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" @@ -1348,6 +1484,15 @@ resolve-from@^4.0.0: resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== +resolve@^1.22.1: + version "1.22.6" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.6.tgz#dd209739eca3aef739c626fea1b4f3c506195362" + integrity sha512-njhxM7mV12JfufShqGy3Rz8j11RPdLy4xi15UurGJeoHLfJpVXKdh3ueuOqbYUcDZnffr6X739JBo5LzyahEsw== + dependencies: + is-core-module "^2.13.0" + path-parse "^1.0.7" + supports-preserve-symlinks-flag "^1.0.0" + reusify@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" @@ -1374,6 +1519,13 @@ rollup@^3.21.0: optionalDependencies: fsevents "~2.3.2" +rollup@^3.7.0: + version "3.29.4" + resolved "https://registry.yarnpkg.com/rollup/-/rollup-3.29.4.tgz#4d70c0f9834146df8705bfb69a9a19c9e1109981" + integrity sha512-oWzmBZwvYrU0iJHtDmhsm662rC15FRXmcjCk1xD771dFDx5jJ02ufAQQTn0etB2emNk4J9EZg/yWKpsn9BWGRw== + optionalDependencies: + fsevents "~2.3.2" + run-parallel@^1.1.9: version "1.2.0" resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee" @@ -1496,6 +1648,11 @@ supports-color@^7.1.0: dependencies: has-flag "^4.0.0" +supports-preserve-symlinks-flag@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" + integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== + svelte-check@^3.0.1: version "3.4.3" resolved "https://registry.yarnpkg.com/svelte-check/-/svelte-check-3.4.3.tgz#591c66568d227b22e6dab21de1dfc250ce2109d2" diff --git a/trifid-api/Cargo.toml b/trifid-api/Cargo.toml index 27be92e..eae1626 100644 --- a/trifid-api/Cargo.toml +++ b/trifid-api/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "trifid-api" -version = "0.2.2" +version = "0.2.3" edition = "2021" description = "Pure-rust Defined Networking compatible management server" license = "GPL-3.0-or-later" diff --git a/trifid-api/src/routes/v1/dnclient.rs b/trifid-api/src/routes/v1/dnclient.rs index 8ed8cfc..828bdd6 100644 --- a/trifid-api/src/routes/v1/dnclient.rs +++ b/trifid-api/src/routes/v1/dnclient.rs @@ -232,7 +232,7 @@ pub async fn dnclient( } }; - let mut config_is_different = current_cfg == cfg; + let mut config_is_different = current_cfg != cfg; if config_is_different { // check if it is a certificate issue