mirror of
https://github.com/outbackdingo/firezone.git
synced 2026-01-27 10:18:54 +00:00
chore(rust/gui-client): migrate to Tauri v2 (#6996)
Closes #4883 Refs #7005 Adds support for Ubuntu 24.04, drops support for Ubuntu 20.04 Known issues: - On Ubuntu 22.04, sometimes GNOME shows the wrong tray icon - On Ubuntu 24.04, the first time you open the tray menu, GNOME takes a long time to open the menu. --------- Signed-off-by: Reactor Scram <ReactorScram@users.noreply.github.com>
This commit is contained in:
@@ -15,7 +15,7 @@ runs:
|
||||
shell: bash
|
||||
- name: Install Tauri build deps
|
||||
if: ${{ runner.os == 'Linux' }}
|
||||
run: sudo apt-get --yes install build-essential libwebkit2gtk-4.0-dev libssl-dev libgtk-3-dev libayatana-appindicator3-dev librsvg2-dev
|
||||
run: sudo apt-get --yes install build-essential curl file libwebkit2gtk-4.1-dev libayatana-appindicator3-dev librsvg2-dev libssl-dev libxdo-dev wget
|
||||
shell: bash
|
||||
- name: Install gnome-keyring
|
||||
if: ${{ runner.os == 'Linux' }}
|
||||
@@ -49,3 +49,7 @@ runs:
|
||||
# Currently the MSI does this and it's a little janky.
|
||||
run: Start-Process WebView2Installer.exe -ArgumentList "/install" -Wait
|
||||
shell: pwsh
|
||||
# Otherwise one of the Tauri macros panics in static analysis
|
||||
- name: Create `rust/gui-client/dist`
|
||||
run: mkdir "$GITHUB_WORKSPACE/rust/gui-client/dist"
|
||||
shell: bash
|
||||
2
.github/codespellrc
vendored
2
.github/codespellrc
vendored
@@ -1,3 +1,3 @@
|
||||
[codespell]
|
||||
skip = ./**/*.svg,./elixir/deps,./**/*.min.js,./kotlin/android/app/build,./kotlin/android/build,./e2e/pnpm-lock.yaml,./website/.next,./website/pnpm-lock.yaml,./rust/connlib/tunnel/testcases,./rust/target,Cargo.lock,./website/docs/reference/api/*.mdx,./**/erl_crash.dump,./cover,./vendor,*.json,seeds.exs,./**/node_modules,./deps,./priv/static,./priv/plts,./**/priv/static,./.git,./_build,*.cast,./**/proptest-regressions
|
||||
skip = ./**/*.svg,./elixir/deps,./**/*.min.js,./kotlin/android/app/build,./kotlin/android/build,./e2e/pnpm-lock.yaml,./website/.next,./website/pnpm-lock.yaml,./rust/connlib/tunnel/testcases,./rust/gui-client/dist,./rust/target,Cargo.lock,./website/docs/reference/api/*.mdx,./**/erl_crash.dump,./cover,./vendor,*.json,seeds.exs,./**/node_modules,./deps,./priv/static,./priv/plts,./**/priv/static,./.git,./_build,*.cast,./**/proptest-regressions
|
||||
ignore-words-list = optin,crate,keypair,keypairs,iif,statics,wee,anull,commitish,inout,fo,superceded
|
||||
|
||||
14
.github/workflows/_rust.yml
vendored
14
.github/workflows/_rust.yml
vendored
@@ -52,7 +52,7 @@ jobs:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: ./.github/actions/setup-rust
|
||||
id: setup-rust
|
||||
- uses: ./.github/actions/setup-tauri
|
||||
- uses: ./.github/actions/setup-tauri-v2
|
||||
timeout-minutes: 5
|
||||
- uses: taiki-e/install-action@cargo-udeps
|
||||
env:
|
||||
@@ -76,8 +76,8 @@ jobs:
|
||||
matrix:
|
||||
# TODO: https://github.com/rust-lang/cargo/issues/5220
|
||||
runs-on: [
|
||||
ubuntu-20.04,
|
||||
ubuntu-22.04,
|
||||
ubuntu-24.04,
|
||||
macos-12,
|
||||
macos-13,
|
||||
macos-14,
|
||||
@@ -89,7 +89,7 @@ jobs:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: ./.github/actions/setup-rust
|
||||
id: setup-rust
|
||||
- uses: ./.github/actions/setup-tauri
|
||||
- uses: ./.github/actions/setup-tauri-v2
|
||||
- uses: taiki-e/install-action@v2
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
@@ -132,8 +132,8 @@ jobs:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
runs-on: [
|
||||
ubuntu-20.04,
|
||||
ubuntu-22.04,
|
||||
ubuntu-24.04,
|
||||
windows-2019,
|
||||
windows-2022
|
||||
]
|
||||
@@ -147,7 +147,7 @@ jobs:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: ./.github/actions/setup-node
|
||||
- uses: ./.github/actions/setup-rust
|
||||
- uses: ./.github/actions/setup-tauri
|
||||
- uses: ./.github/actions/setup-tauri-v2
|
||||
timeout-minutes: 5
|
||||
with:
|
||||
runtime: true
|
||||
@@ -176,13 +176,13 @@ jobs:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
# TODO: Add Windows as part of issue #3782
|
||||
runs-on: [ubuntu-20.04, ubuntu-22.04]
|
||||
runs-on: [ubuntu-22.04, ubuntu-24.04]
|
||||
test: [linux-group, token-path]
|
||||
runs-on: ${{ matrix.runs-on }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: ./.github/actions/setup-rust
|
||||
- uses: ./.github/actions/setup-tauri
|
||||
- uses: ./.github/actions/setup-tauri-v2
|
||||
timeout-minutes: 5
|
||||
- run: scripts/tests/${{ matrix.test }}.sh
|
||||
name: "test script"
|
||||
|
||||
95
.github/workflows/_tauri.yml
vendored
95
.github/workflows/_tauri.yml
vendored
@@ -29,59 +29,46 @@ jobs:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- runs-on: ubuntu-20.04
|
||||
# mark:next-gui-version
|
||||
binary-dest-path: firezone-client-gui-linux_1.3.10_x86_64
|
||||
rename-script: ../../scripts/build/tauri-rename-ubuntu.sh
|
||||
upload-script: ../../scripts/build/tauri-upload-ubuntu.sh
|
||||
# mark:next-gui-version
|
||||
syms-artifact: rust/gui-client/firezone-client-gui-linux_1.3.10_x86_64.dwp
|
||||
# mark:next-gui-version
|
||||
pkg-artifact: rust/gui-client/firezone-client-gui-linux_1.3.10_x86_64.deb
|
||||
- runs-on: ubuntu-22.04
|
||||
arch: x86_64
|
||||
os: linux
|
||||
pkg-extension: deb
|
||||
syms-extension: dwp
|
||||
- runs-on: ubuntu-22.04-arm
|
||||
# mark:next-gui-version
|
||||
binary-dest-path: firezone-client-gui-linux_1.3.10_aarch64
|
||||
rename-script: ../../scripts/build/tauri-rename-ubuntu.sh
|
||||
upload-script: ../../scripts/build/tauri-upload-ubuntu.sh
|
||||
# mark:next-gui-version
|
||||
syms-artifact: rust/gui-client/firezone-client-gui-linux_1.3.10_aarch64.dwp
|
||||
# mark:next-gui-version
|
||||
pkg-artifact: rust/gui-client/firezone-client-gui-linux_1.3.10_aarch64.deb
|
||||
arch: aarch64
|
||||
os: linux
|
||||
pkg-extension: deb
|
||||
syms-extension: dwp
|
||||
- runs-on: windows-2019
|
||||
# mark:next-gui-version
|
||||
binary-dest-path: firezone-client-gui-windows_1.3.10_x86_64
|
||||
rename-script: ../../scripts/build/tauri-rename-windows.sh
|
||||
upload-script: ../../scripts/build/tauri-upload-windows.sh
|
||||
# mark:next-gui-version
|
||||
syms-artifact: rust/gui-client/firezone-client-gui-windows_1.3.10_x86_64.pdb
|
||||
# mark:next-gui-version
|
||||
pkg-artifact: rust/gui-client/firezone-client-gui-windows_1.3.10_x86_64.msi
|
||||
arch: x86_64
|
||||
os: windows
|
||||
pkg-extension: msi
|
||||
syms-extension: pdb
|
||||
env:
|
||||
BINARY_DEST_PATH: ${{ matrix.binary-dest-path }}
|
||||
# mark:next-gui-version
|
||||
ARTIFACT_SRC: ./rust/gui-client/firezone-client-gui-${{ matrix.os }}_1.3.10_${{ matrix.arch }}
|
||||
# mark:next-gui-version
|
||||
ARTIFACT_DST: firezone-client-gui-${{ matrix.os }}_1.3.10_${{ matrix.arch }}
|
||||
AZURE_KEY_VAULT_URI: ${{ secrets.AZURE_KEY_VAULT_URI }}
|
||||
AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }}
|
||||
AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }}
|
||||
AZURE_CLIENT_SECRET: ${{ secrets.AZURE_CLIENT_SECRET }}
|
||||
AZURE_CERT_NAME: ${{ secrets.AZURE_CERT_NAME }}
|
||||
# mark:next-gui-version
|
||||
BINARY_DEST_PATH: firezone-client-gui-${{ matrix.os }}_1.3.10_${{ matrix.arch }}
|
||||
# Seems like there's no way to de-dupe env vars that depend on each other
|
||||
# mark:next-gui-version
|
||||
FIREZONE_GUI_VERSION: 1.3.10
|
||||
RENAME_SCRIPT: ../../scripts/build/tauri-rename-${{ matrix.os }}.sh
|
||||
TARGET_DIR: ../target
|
||||
UPLOAD_SCRIPT: ../../scripts/build/tauri-upload-${{ matrix.os }}.sh
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: ./.github/actions/setup-node
|
||||
- uses: ./.github/actions/setup-rust
|
||||
- uses: ./.github/actions/setup-tauri
|
||||
- uses: ./.github/actions/setup-tauri-v2
|
||||
# Installing new packages can take time
|
||||
timeout-minutes: 10
|
||||
# the arm64 images don't have the GH cli installed.
|
||||
# Remove this when https://github.com/actions/runner-images/issues/10192 is resolved.
|
||||
- name: Ubuntu arm workaround
|
||||
if: ${{ matrix.runs-on == 'ubuntu-22.04-arm' }}
|
||||
run: |
|
||||
(type -p wget >/dev/null || (sudo apt update && sudo apt-get install wget -y)) \
|
||||
&& sudo mkdir -p -m 755 /etc/apt/keyrings \
|
||||
&& wget -qO- https://cli.github.com/packages/githubcli-archive-keyring.gpg | sudo tee /etc/apt/keyrings/githubcli-archive-keyring.gpg > /dev/null \
|
||||
&& sudo chmod go+r /etc/apt/keyrings/githubcli-archive-keyring.gpg \
|
||||
&& echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" | sudo tee /etc/apt/sources.list.d/github-cli.list > /dev/null \
|
||||
&& sudo apt update \
|
||||
&& sudo apt install gh -y
|
||||
- name: Install pnpm deps
|
||||
run: pnpm install
|
||||
- name: Install AzureSignTool
|
||||
@@ -89,7 +76,15 @@ jobs:
|
||||
shell: bash
|
||||
# AzureSignTool >= 5 needs .NET 8. windows-2019 runner only has .NET 7.
|
||||
run: dotnet tool install --global AzureSignTool --version 4.0.1
|
||||
- name: Check if swap needed
|
||||
if: ${{ runner.os == 'Linux' }}
|
||||
run: free -m
|
||||
- name: Enable swap
|
||||
if: ${{ runner.os == 'Linux' }}
|
||||
run: sudo fallocate -l 8G /swapfile && sudo chmod 600 /swapfile && sudo mkswap /swapfile && sudo swapon /swapfile && free -m
|
||||
- name: Build release exe and MSI / deb
|
||||
env:
|
||||
CARGO_PROFILE_RELEASE_LTO: thin # Fat LTO is getting too slow / RAM-hungry on Tauri builds
|
||||
# Signs the exe before bundling it into the MSI
|
||||
run: pnpm build
|
||||
- name: Ensure unmodified Git workspace
|
||||
@@ -102,32 +97,28 @@ jobs:
|
||||
- name: Sign the MSI
|
||||
if: ${{ runner.os == 'Windows' }}
|
||||
shell: bash
|
||||
# mark:next-gui-version
|
||||
run: ../../scripts/build/sign.sh ../target/release/bundle/msi/Firezone_1.3.10_x64_en-US.msi
|
||||
run: ../../scripts/build/sign.sh ../target/release/bundle/msi/Firezone_${{ env.FIREZONE_GUI_VERSION }}_x64_en-US.msi
|
||||
- name: Rename artifacts and compute SHA256
|
||||
shell: bash
|
||||
run: ${{ matrix.rename-script }}
|
||||
run: ${{ env.RENAME_SCRIPT }}
|
||||
- name: Upload debug symbols
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ${{ matrix.binary-dest-path }}-syms
|
||||
path: |
|
||||
${{ matrix.syms-artifact }}
|
||||
name: ${{ env.ARTIFACT_DST }}-syms
|
||||
path: ${{ env.ARTIFACT_SRC }}.${{ matrix.syms-extension }}
|
||||
if-no-files-found: error
|
||||
- name: Upload package
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ${{ matrix.binary-dest-path }}-pkg
|
||||
path: |
|
||||
${{ matrix.pkg-artifact }}
|
||||
name: ${{ env.ARTIFACT_DST }}-pkg
|
||||
path: ${{ env.ARTIFACT_SRC }}.${{ matrix.pkg-extension }}
|
||||
if-no-files-found: error
|
||||
- name: Upload Release Assets
|
||||
# Only upload the windows build to the drafted release on main builds
|
||||
# Only upload the GUI Client build to the drafted release on main builds
|
||||
if: ${{ github.ref_name == 'main' }}
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
REPOSITORY: ${{ github.repository }}
|
||||
# mark:next-gui-version
|
||||
TAG_NAME: gui-client-1.3.10
|
||||
TAG_NAME: gui-client-${{ env.FIREZONE_GUI_VERSION }}
|
||||
shell: bash
|
||||
run: ${{ matrix.upload-script }}
|
||||
run: ${{ env.UPLOAD_SCRIPT }}
|
||||
|
||||
1713
rust/Cargo.lock
generated
1713
rust/Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
6
rust/gui-client/.gitignore
vendored
6
rust/gui-client/.gitignore
vendored
@@ -26,3 +26,9 @@ src/output.css
|
||||
|
||||
# JS output from the TypeScript compiler
|
||||
src/**/*.js
|
||||
|
||||
# Vite output
|
||||
dist
|
||||
|
||||
# Some new generated files in Tauri v2
|
||||
src-tauri/gen
|
||||
|
||||
@@ -8,7 +8,7 @@ To compile natively for x86_64 Linux:
|
||||
|
||||
1. [Install rustup](https://rustup.rs/)
|
||||
1. Install [pnpm](https://pnpm.io/installation)
|
||||
1. `sudo apt-get install at-spi2-core gcc libwebkit2gtk-4.0-dev libssl-dev libgtk-3-dev libayatana-appindicator3-dev librsvg2-dev pkg-config xvfb`
|
||||
1. `sudo apt-get install build-essential curl file libayatana-appindicator3-dev librsvg2-dev libssl-dev libwebkit2gtk-4.1-dev libxdo-dev wget`
|
||||
|
||||
## Setup (Windows)
|
||||
|
||||
|
||||
@@ -4,11 +4,11 @@ setlocal enabledelayedexpansion
|
||||
REM Copy frontend dependencies
|
||||
copy "node_modules\flowbite\dist\flowbite.min.js" "src\"
|
||||
|
||||
REM Compile TypeScript
|
||||
call pnpm tsc
|
||||
|
||||
REM Compile CSS
|
||||
call pnpm tailwindcss -i src\input.css -o src\output.css
|
||||
|
||||
REM bundle web assets
|
||||
call pnpm vite build
|
||||
|
||||
REM Compile Rust and bundle
|
||||
call pnpm tauri build
|
||||
|
||||
@@ -8,12 +8,12 @@ BUNDLES_DIR=../target/release/bundle/deb
|
||||
# Copy frontend dependencies
|
||||
cp node_modules/flowbite/dist/flowbite.min.js src/
|
||||
|
||||
# Compile TypeScript
|
||||
pnpm tsc
|
||||
|
||||
# Compile CSS
|
||||
pnpm tailwindcss -i src/input.css -o src/output.css
|
||||
|
||||
# Bundle all web assets
|
||||
pnpm vite build
|
||||
|
||||
# Get rid of any existing debs, since we need to discover the path later
|
||||
rm -rf "$BUNDLES_DIR"
|
||||
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
{
|
||||
"name": "firezone-gui-client",
|
||||
"private": true,
|
||||
"version": "0.1.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"build": "run-script-os",
|
||||
"build:win32": "call build.bat",
|
||||
@@ -11,15 +15,16 @@
|
||||
"tauri": "tauri"
|
||||
},
|
||||
"dependencies": {
|
||||
"@tauri-apps/api": "^1.6",
|
||||
"@tauri-apps/api": ">=2.0.0",
|
||||
"flowbite": "^2.5.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tauri-apps/cli": "^1.6",
|
||||
"@tauri-apps/cli": ">=2.0.0",
|
||||
"@types/node": "22",
|
||||
"http-server": "^14.1.1",
|
||||
"run-script-os": "^1.1.6",
|
||||
"tailwindcss": "^3.4.14",
|
||||
"typescript": "^5.6.3"
|
||||
"typescript": "^5.6.3",
|
||||
"vite": "^5.3.1"
|
||||
}
|
||||
}
|
||||
|
||||
1000
rust/gui-client/pnpm-lock.yaml
generated
1000
rust/gui-client/pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -97,6 +97,7 @@ pub async fn clear_gui_logs() -> Result<()> {
|
||||
/// * `stem` - A directory containing all the log files inside the zip archive, to avoid creating a ["tar bomb"](https://www.linfo.org/tarbomb.html). This comes from the automatically-generated name of the archive, even if the user changes it to e.g. `logs.zip`
|
||||
pub async fn export_logs_to(path: PathBuf, stem: PathBuf) -> Result<()> {
|
||||
tracing::info!("Exporting logs to {path:?}");
|
||||
let start = std::time::Instant::now();
|
||||
// Use a temp path so that if the export fails we don't end up with half a zip file
|
||||
let temp_path = path.with_extension(".zip-partial");
|
||||
|
||||
@@ -113,6 +114,7 @@ pub async fn export_logs_to(path: PathBuf, stem: PathBuf) -> Result<()> {
|
||||
})
|
||||
.await
|
||||
.context("Failed to join zip export task")??;
|
||||
tracing::debug!(elapsed_s = ?start.elapsed(), "Exported logs");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
@@ -32,6 +32,15 @@ pub struct AppState {
|
||||
pub release: Option<Release>,
|
||||
}
|
||||
|
||||
impl Default for AppState {
|
||||
fn default() -> AppState {
|
||||
AppState {
|
||||
connlib: ConnlibState::Loading,
|
||||
release: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AppState {
|
||||
pub fn into_menu(self) -> Menu {
|
||||
let quit_text = match &self.connlib {
|
||||
@@ -130,7 +139,7 @@ impl SignedIn {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq)]
|
||||
#[derive(Clone, PartialEq)]
|
||||
pub struct Icon {
|
||||
pub base: IconBase,
|
||||
pub update_ready: bool,
|
||||
@@ -144,7 +153,7 @@ pub(crate) fn icon_terminating() -> Icon {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq)]
|
||||
#[derive(Clone, PartialEq)]
|
||||
pub enum IconBase {
|
||||
/// Must be equivalent to the default app icon, since we assume this is set when we start
|
||||
Busy,
|
||||
|
||||
@@ -8,16 +8,14 @@ pub const INTERNET_RESOURCE_DESCRIPTION: &str = "All network traffic";
|
||||
|
||||
/// A menu that can either be assigned to the system tray directly or used as a submenu in another menu.
|
||||
///
|
||||
/// Equivalent to `tauri::SystemTrayMenu`
|
||||
#[derive(Debug, Default, PartialEq, Serialize)]
|
||||
/// Equivalent to `tauri::menu::Menu` or `tauri::menu::Submenu`
|
||||
#[derive(Clone, Debug, Default, PartialEq, Serialize)]
|
||||
pub struct Menu {
|
||||
pub entries: Vec<Entry>,
|
||||
}
|
||||
|
||||
/// Something that can be shown in a menu, including text items, separators, and submenus
|
||||
///
|
||||
/// Equivalent to `tauri::SystemTrayMenuEntry`
|
||||
#[derive(Debug, PartialEq, Serialize)]
|
||||
#[derive(Clone, Debug, PartialEq, Serialize)]
|
||||
pub enum Entry {
|
||||
Item(Item),
|
||||
Separator,
|
||||
@@ -27,7 +25,7 @@ pub enum Entry {
|
||||
/// Something that shows text and may be clickable
|
||||
///
|
||||
/// Equivalent to `tauri::CustomMenuItem`
|
||||
#[derive(Debug, PartialEq, Serialize)]
|
||||
#[derive(Clone, Debug, PartialEq, Serialize)]
|
||||
pub struct Item {
|
||||
/// An event to send to the app when the item is clicked.
|
||||
///
|
||||
@@ -40,7 +38,7 @@ pub struct Item {
|
||||
}
|
||||
|
||||
/// Events that the menu can send to the app
|
||||
#[derive(Debug, Deserialize, PartialEq, Serialize)]
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
|
||||
pub enum Event {
|
||||
/// Marks this Resource as favorite
|
||||
AddFavorite(ResourceId),
|
||||
@@ -73,7 +71,7 @@ pub enum Event {
|
||||
DisableInternetResource,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, PartialEq, Serialize)]
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
|
||||
pub enum Window {
|
||||
About,
|
||||
Settings,
|
||||
|
||||
@@ -8,14 +8,14 @@ default-run = "firezone-gui-client"
|
||||
authors = ["Firezone, Inc."]
|
||||
|
||||
[build-dependencies]
|
||||
anyhow = { version = "1.0" }
|
||||
tauri-build = { version = "1.5", features = [] }
|
||||
anyhow = { version = "1.0.89" }
|
||||
tauri-build = { version = "2.0.1", features = [] }
|
||||
|
||||
[dependencies]
|
||||
anyhow = { version = "1.0" }
|
||||
anyhow = { version = "1.0.89" }
|
||||
atomicwrites = { workspace = true }
|
||||
chrono = { workspace = true }
|
||||
clap = { version = "4.5", features = ["derive", "env"] }
|
||||
clap = { version = "4.5.20", features = ["derive", "env"] }
|
||||
connlib-client-shared = { workspace = true }
|
||||
connlib-model = { workspace = true }
|
||||
firezone-bin-shared = { workspace = true }
|
||||
@@ -28,14 +28,16 @@ rand = "0.8.5"
|
||||
rustls = { workspace = true }
|
||||
sadness-generator = "0.6.0"
|
||||
secrecy = { workspace = true }
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
serde = { version = "1.0.210", features = ["derive"] }
|
||||
serde_json = "1.0.132"
|
||||
subtle = "2.5.0"
|
||||
# `notification` is not needed on Windows, but if we remove it, Tauri's bundler will just add it back. #6597
|
||||
tauri = { version = "1.7.1", features = ["dialog", "icon-png", "notification", "shell-open-api", "system-tray"] }
|
||||
tauri-runtime = "0.14.5"
|
||||
tauri-utils = "1.6.0"
|
||||
thiserror = { version = "1.0", default-features = false }
|
||||
tauri = { version = "2.0.3", features = ["tray-icon", "image-png"] }
|
||||
tauri-plugin-dialog = "2.0.1"
|
||||
tauri-plugin-notification = "2.0.1"
|
||||
tauri-plugin-shell = "2.0.1"
|
||||
tauri-runtime = "2.1.0"
|
||||
tauri-utils = "2.0.1"
|
||||
thiserror = { version = "1.0.64", default-features = false }
|
||||
tokio = { workspace = true, features = ["signal", "time", "macros", "rt", "rt-multi-thread"] }
|
||||
tokio-util = { version = "0.7.11", features = ["codec"] }
|
||||
tracing = { workspace = true }
|
||||
|
||||
@@ -21,7 +21,8 @@ use firezone_headless_client::LogFilterReloader;
|
||||
use firezone_telemetry as telemetry;
|
||||
use secrecy::{ExposeSecret as _, SecretString};
|
||||
use std::{str::FromStr, time::Duration};
|
||||
use tauri::{Manager, SystemTrayEvent};
|
||||
use tauri::Manager;
|
||||
use tauri_plugin_shell::ShellExt as _;
|
||||
use tokio::sync::{mpsc, oneshot};
|
||||
use tracing::instrument;
|
||||
|
||||
@@ -61,7 +62,7 @@ impl GuiIntegration for TauriIntegration {
|
||||
fn set_welcome_window_visible(&self, visible: bool) -> Result<()> {
|
||||
let win = self
|
||||
.app
|
||||
.get_window("welcome")
|
||||
.get_webview_window("welcome")
|
||||
.context("Couldn't get handle to Welcome window")?;
|
||||
|
||||
if visible {
|
||||
@@ -73,7 +74,7 @@ impl GuiIntegration for TauriIntegration {
|
||||
}
|
||||
|
||||
fn open_url<P: AsRef<str>>(&self, url: P) -> Result<()> {
|
||||
Ok(tauri::api::shell::open(&self.app.shell_scope(), url, None)?)
|
||||
Ok(self.app.shell().open(url.as_ref(), None)?)
|
||||
}
|
||||
|
||||
fn set_tray_icon(&mut self, icon: common::system_tray::Icon) -> Result<()> {
|
||||
@@ -85,11 +86,11 @@ impl GuiIntegration for TauriIntegration {
|
||||
}
|
||||
|
||||
fn show_notification(&self, title: &str, body: &str) -> Result<()> {
|
||||
os::show_notification(title, body)
|
||||
os::show_notification(&self.app, title, body)
|
||||
}
|
||||
|
||||
fn show_update_notification(&self, ctlr_tx: CtlrTx, title: &str, url: url::Url) -> Result<()> {
|
||||
os::show_update_notification(ctlr_tx, title, url)
|
||||
os::show_update_notification(&self.app, ctlr_tx, title, url)
|
||||
}
|
||||
|
||||
fn show_window(&self, window: common::system_tray::Window) -> Result<()> {
|
||||
@@ -100,7 +101,7 @@ impl GuiIntegration for TauriIntegration {
|
||||
|
||||
let win = self
|
||||
.app
|
||||
.get_window(id)
|
||||
.get_webview_window(id)
|
||||
.context("Couldn't get handle to `{id}` window")?;
|
||||
|
||||
// Needed to bring shown windows to the front
|
||||
@@ -139,16 +140,16 @@ pub(crate) fn run(
|
||||
inject_faults: cli.inject_faults,
|
||||
};
|
||||
|
||||
let (setup_result_tx, mut setup_result_rx) = oneshot::channel::<Result<(), Error>>();
|
||||
let (tray_tx, tray_rx) = oneshot::channel();
|
||||
let app = tauri::Builder::default()
|
||||
.manage(managed)
|
||||
.on_window_event(|event| {
|
||||
if let tauri::WindowEvent::CloseRequested { api, .. } = event.event() {
|
||||
.on_window_event(|window, event| {
|
||||
if let tauri::WindowEvent::CloseRequested { api, .. } = event {
|
||||
// Keep the frontend running but just hide this webview
|
||||
// Per https://tauri.app/v1/guides/features/system-tray/#preventing-the-app-from-closing
|
||||
// Closing the window fully seems to deallocate it or something.
|
||||
|
||||
event.window().hide().unwrap();
|
||||
window.hide().unwrap();
|
||||
api.prevent_close();
|
||||
}
|
||||
})
|
||||
@@ -163,23 +164,9 @@ pub(crate) fn run(
|
||||
settings::get_advanced_settings,
|
||||
crate::client::welcome::sign_in,
|
||||
])
|
||||
.system_tray(system_tray::loading())
|
||||
.on_system_tray_event(|app, event| {
|
||||
if let SystemTrayEvent::MenuItemClick { id, .. } = event {
|
||||
tracing::debug!(?id, "SystemTrayEvent::MenuItemClick");
|
||||
let event = match serde_json::from_str::<TrayMenuEvent>(&id) {
|
||||
Ok(x) => x,
|
||||
Err(e) => {
|
||||
tracing::error!("{e}");
|
||||
return;
|
||||
}
|
||||
};
|
||||
match handle_system_tray_event(app, event) {
|
||||
Ok(_) => {}
|
||||
Err(e) => tracing::error!("{e}"),
|
||||
}
|
||||
}
|
||||
})
|
||||
.plugin(tauri_plugin_dialog::init())
|
||||
.plugin(tauri_plugin_notification::init())
|
||||
.plugin(tauri_plugin_shell::init())
|
||||
.setup(move |app| {
|
||||
let setup_inner = move || {
|
||||
// Check for updates
|
||||
@@ -241,16 +228,20 @@ pub(crate) fn run(
|
||||
|
||||
assert_eq!(
|
||||
firezone_bin_shared::BUNDLE_ID,
|
||||
app.handle().config().tauri.bundle.identifier,
|
||||
app.handle().config().identifier,
|
||||
"BUNDLE_ID should match bundle ID in tauri.conf.json"
|
||||
);
|
||||
|
||||
let app_handle = app.handle();
|
||||
let tray = tray_rx.blocking_recv().expect("tray_rx failed");
|
||||
let tray = system_tray::Tray::new(app.handle().clone(), tray);
|
||||
let integration = TauriIntegration { app: app.handle().clone(), tray };
|
||||
|
||||
let app_handle = app.handle().clone();
|
||||
let _ctlr_task = tokio::spawn(async move {
|
||||
// Spawn two nested Tasks so the outer can catch panics from the inner
|
||||
let task = tokio::spawn(run_controller(
|
||||
app_handle.clone(),
|
||||
ctlr_tx,
|
||||
integration,
|
||||
ctlr_rx,
|
||||
advanced_settings,
|
||||
reloader,
|
||||
@@ -285,25 +276,27 @@ pub(crate) fn run(
|
||||
|
||||
// In a normal Rust application, Sentry's guard would flush on drop: https://docs.sentry.io/platforms/rust/configuration/draining/
|
||||
// But due to a limit in `tao` we cannot return from the event loop and must call `std::process::exit` (or Tauri's wrapper), so we explicitly flush here.
|
||||
// TODO: This limit may not exist in Tauri v2
|
||||
telemetry.stop();
|
||||
|
||||
tracing::info!(?exit_code);
|
||||
app_handle.exit(exit_code);
|
||||
// In Tauri v1, calling `App::exit` internally exited the process.
|
||||
// In Tauri v2, that doesn't happen, but `App::run` still doesn't return, so we have to bail out of the process manually.
|
||||
std::process::exit(exit_code);
|
||||
});
|
||||
Ok(())
|
||||
};
|
||||
|
||||
setup_result_tx.send(setup_inner()).expect("should be able to send setup result");
|
||||
let result = setup_inner();
|
||||
if let Err(error) = &result {
|
||||
tracing::error!(?error, "Tauri setup failed");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
result
|
||||
});
|
||||
tracing::debug!("Building Tauri app...");
|
||||
let app = app.build(tauri::generate_context!());
|
||||
|
||||
setup_result_rx
|
||||
.try_recv()
|
||||
.context("couldn't receive result of setup")??;
|
||||
|
||||
let app = match app {
|
||||
Ok(x) => x,
|
||||
Err(error) => {
|
||||
@@ -318,6 +311,36 @@ pub(crate) fn run(
|
||||
}
|
||||
};
|
||||
|
||||
let tray = tauri::tray::TrayIconBuilder::new()
|
||||
.icon(system_tray::icon_to_tauri_icon(
|
||||
&firezone_gui_client_common::system_tray::Icon::default(),
|
||||
))
|
||||
.menu(&system_tray::build_app_state(
|
||||
app.handle(),
|
||||
&firezone_gui_client_common::system_tray::AppState::default().into_menu(),
|
||||
)?)
|
||||
.on_menu_event(|app, event| {
|
||||
let id = &event.id.0;
|
||||
tracing::debug!(?id, "SystemTrayEvent::MenuItemClick");
|
||||
let event = match serde_json::from_str::<TrayMenuEvent>(id) {
|
||||
Ok(x) => x,
|
||||
Err(e) => {
|
||||
tracing::error!("{e}");
|
||||
return;
|
||||
}
|
||||
};
|
||||
match handle_system_tray_event(app, event) {
|
||||
Ok(_) => {}
|
||||
Err(e) => tracing::error!("{e}"),
|
||||
}
|
||||
})
|
||||
.tooltip("Firezone")
|
||||
.build(&app)
|
||||
.context("Cannot build Tauri tray icon")?;
|
||||
if tray_tx.send(tray).is_err() {
|
||||
panic!("Couldn't send tray through the channel");
|
||||
}
|
||||
|
||||
app.run(|_app_handle, event| {
|
||||
if let tauri::RunEvent::ExitRequested { api, .. } = event {
|
||||
// Don't exit if we close our main window
|
||||
@@ -326,6 +349,7 @@ pub(crate) fn run(
|
||||
api.prevent_exit();
|
||||
}
|
||||
});
|
||||
tracing::warn!("app.run returned, this is normally unreachable even in Tauri v2");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -439,8 +463,8 @@ fn handle_system_tray_event(app: &tauri::AppHandle, event: TrayMenuEvent) -> Res
|
||||
|
||||
// TODO: Move this into `impl Controller`
|
||||
async fn run_controller(
|
||||
app: tauri::AppHandle,
|
||||
ctlr_tx: CtlrTx,
|
||||
integration: TauriIntegration,
|
||||
rx: mpsc::Receiver<ControllerRequest>,
|
||||
advanced_settings: AdvancedSettings,
|
||||
log_filter_reloader: LogFilterReloader,
|
||||
@@ -448,11 +472,11 @@ async fn run_controller(
|
||||
updates_rx: mpsc::Receiver<Option<updates::Notification>>,
|
||||
) -> Result<(), Error> {
|
||||
tracing::debug!("Entered `run_controller`");
|
||||
let tray = system_tray::Tray::new(app.tray_handle());
|
||||
|
||||
let controller = firezone_gui_client_common::controller::Builder {
|
||||
advanced_settings,
|
||||
ctlr_tx,
|
||||
integration: TauriIntegration { app, tray },
|
||||
integration,
|
||||
log_filter_reloader,
|
||||
rx,
|
||||
telemetry,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use anyhow::{Context as _, Result};
|
||||
use firezone_bin_shared::BUNDLE_ID;
|
||||
use tauri::api::notification::Notification;
|
||||
use tauri::AppHandle;
|
||||
use tauri_plugin_notification::NotificationExt as _;
|
||||
|
||||
pub(crate) async fn set_autostart(enabled: bool) -> Result<()> {
|
||||
let dir = dirs::config_local_dir()
|
||||
@@ -35,17 +35,19 @@ pub(crate) async fn set_autostart(enabled: bool) -> Result<()> {
|
||||
/// Since clickable notifications don't work on Linux yet, the update text
|
||||
/// must be different on different platforms
|
||||
pub(crate) fn show_update_notification(
|
||||
app: &AppHandle,
|
||||
_ctlr_tx: super::CtlrTx,
|
||||
title: &str,
|
||||
download_url: url::Url,
|
||||
) -> Result<()> {
|
||||
show_notification(title, download_url.to_string().as_ref())?;
|
||||
show_notification(app, title, download_url.to_string().as_ref())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Show a notification in the bottom right of the screen
|
||||
pub(crate) fn show_notification(title: &str, body: &str) -> Result<()> {
|
||||
Notification::new(BUNDLE_ID)
|
||||
pub(crate) fn show_notification(app: &AppHandle, title: &str, body: &str) -> Result<()> {
|
||||
app.notification()
|
||||
.builder()
|
||||
.title(title)
|
||||
.body(body)
|
||||
.show()?;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use super::{ControllerRequest, CtlrTx};
|
||||
use anyhow::{Context, Result};
|
||||
use firezone_bin_shared::BUNDLE_ID;
|
||||
use tauri::AppHandle;
|
||||
|
||||
pub(crate) async fn set_autostart(_enabled: bool) -> Result<()> {
|
||||
todo!()
|
||||
@@ -9,6 +10,7 @@ pub(crate) async fn set_autostart(_enabled: bool) -> Result<()> {
|
||||
/// Since clickable notifications don't work on Linux yet, the update text
|
||||
/// must be different on different platforms
|
||||
pub(crate) fn show_update_notification(
|
||||
_app: &AppHandle,
|
||||
ctlr_tx: CtlrTx,
|
||||
title: &str,
|
||||
download_url: url::Url,
|
||||
@@ -29,7 +31,7 @@ pub(crate) fn show_update_notification(
|
||||
///
|
||||
/// TODO: Warn about silent failure if the AppID is not installed:
|
||||
/// <https://github.com/tauri-apps/winrt-notification/issues/17#issuecomment-1988715694>
|
||||
pub(crate) fn show_notification(title: &str, body: &str) -> Result<()> {
|
||||
pub(crate) fn show_notification(_app: &AppHandle, title: &str, body: &str) -> Result<()> {
|
||||
tracing::debug!(?title, ?body, "show_notification");
|
||||
|
||||
tauri_winrt_notification::Toast::new(BUNDLE_ID)
|
||||
|
||||
@@ -10,7 +10,11 @@ use firezone_gui_client_common::{
|
||||
compositor::{self, Image},
|
||||
system_tray::{AppState, ConnlibState, Entry, Icon, IconBase, Item, Menu},
|
||||
};
|
||||
use tauri::{SystemTray, SystemTrayHandle};
|
||||
use tauri::AppHandle;
|
||||
|
||||
type IsMenuItem = dyn tauri::menu::IsMenuItem<tauri::Wry>;
|
||||
type TauriMenu = tauri::menu::Menu<tauri::Wry>;
|
||||
type TauriSubmenu = tauri::menu::Submenu<tauri::Wry>;
|
||||
|
||||
// Figma is the source of truth for the tray icon layers
|
||||
// <https://www.figma.com/design/THvQQ1QxKlsk47H9DZ2bhN/Core-Library?node-id=1250-772&t=nHBOzOnSY5Ol4asV-0>
|
||||
@@ -22,23 +26,14 @@ const UPDATE_READY_LAYER: &[u8] = include_bytes!("../../../icons/tray/Update rea
|
||||
|
||||
const TOOLTIP: &str = "Firezone";
|
||||
|
||||
pub(crate) fn loading() -> SystemTray {
|
||||
let state = AppState {
|
||||
connlib: ConnlibState::Loading,
|
||||
release: None,
|
||||
};
|
||||
SystemTray::new()
|
||||
.with_icon(icon_to_tauri_icon(&Icon::default()))
|
||||
.with_menu(build_app_state(state))
|
||||
.with_tooltip(TOOLTIP)
|
||||
}
|
||||
|
||||
pub(crate) struct Tray {
|
||||
handle: SystemTrayHandle,
|
||||
app: AppHandle,
|
||||
handle: tauri::tray::TrayIcon,
|
||||
last_icon_set: Icon,
|
||||
last_menu_set: Option<Menu>,
|
||||
}
|
||||
|
||||
fn icon_to_tauri_icon(that: &Icon) -> tauri::Icon {
|
||||
pub(crate) fn icon_to_tauri_icon(that: &Icon) -> tauri::image::Image<'static> {
|
||||
let layers = match that.base {
|
||||
IconBase::Busy => &[LOGO_GREY_BASE, BUSY_LAYER][..],
|
||||
IconBase::SignedIn => &[LOGO_BASE][..],
|
||||
@@ -52,19 +47,17 @@ fn icon_to_tauri_icon(that: &Icon) -> tauri::Icon {
|
||||
image_to_tauri_icon(composed)
|
||||
}
|
||||
|
||||
fn image_to_tauri_icon(val: Image) -> tauri::Icon {
|
||||
tauri::Icon::Rgba {
|
||||
rgba: val.rgba,
|
||||
width: val.width,
|
||||
height: val.height,
|
||||
}
|
||||
fn image_to_tauri_icon(val: Image) -> tauri::image::Image<'static> {
|
||||
tauri::image::Image::new_owned(val.rgba, val.width, val.height)
|
||||
}
|
||||
|
||||
impl Tray {
|
||||
pub(crate) fn new(handle: SystemTrayHandle) -> Self {
|
||||
pub(crate) fn new(app: AppHandle, handle: tauri::tray::TrayIcon) -> Self {
|
||||
Self {
|
||||
app,
|
||||
handle,
|
||||
last_icon_set: Default::default(),
|
||||
last_menu_set: None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -84,9 +77,24 @@ impl Tray {
|
||||
update_ready: state.release.is_some(),
|
||||
};
|
||||
|
||||
self.handle.set_tooltip(TOOLTIP)?;
|
||||
self.handle.set_menu(build_app_state(state))?;
|
||||
let menu = state.into_menu();
|
||||
let menu_clone = menu.clone();
|
||||
let app = self.app.clone();
|
||||
let handle = self.handle.clone();
|
||||
|
||||
if Some(&menu) == self.last_menu_set.as_ref() {
|
||||
tracing::debug!("Skipping redundant menu update");
|
||||
} else {
|
||||
self.app
|
||||
.run_on_main_thread(move || {
|
||||
if let Err(error) = update(handle, &app, &menu) {
|
||||
tracing::error!(?error, "Error while updating tray icon");
|
||||
}
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
self.set_icon(new_icon)?;
|
||||
self.last_menu_set = Some(menu_clone);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -94,52 +102,82 @@ impl Tray {
|
||||
// Only needed for the stress test
|
||||
// Otherwise it would be inlined
|
||||
pub(crate) fn set_icon(&mut self, icon: Icon) -> Result<()> {
|
||||
if icon != self.last_icon_set {
|
||||
// Don't call `set_icon` too often. On Linux it writes a PNG to `/run/user/$UID/tao/tray-icon-*.png` every single time.
|
||||
// <https://github.com/tauri-apps/tao/blob/tao-v0.16.7/src/platform_impl/linux/system_tray.rs#L119>
|
||||
// Yes, even if you use `Icon::File` and tell Tauri that the icon is already
|
||||
// on disk.
|
||||
self.handle.set_icon(icon_to_tauri_icon(&icon))?;
|
||||
self.last_icon_set = icon;
|
||||
if icon == self.last_icon_set {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// Don't call `set_icon` too often. On Linux it writes a PNG to `/run/user/$UID/tao/tray-icon-*.png` every single time.
|
||||
// <https://github.com/tauri-apps/tao/blob/tao-v0.16.7/src/platform_impl/linux/system_tray.rs#L119>
|
||||
// Yes, even if you use `Icon::File` and tell Tauri that the icon is already
|
||||
// on disk.
|
||||
let handle = self.handle.clone();
|
||||
self.last_icon_set = icon.clone();
|
||||
self.app.run_on_main_thread(move || {
|
||||
// These closures can't return any value for some reason
|
||||
handle.set_icon(Some(icon_to_tauri_icon(&icon))).unwrap();
|
||||
})?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn build_app_state(that: AppState) -> tauri::SystemTrayMenu {
|
||||
build_menu(&that.into_menu())
|
||||
fn update(handle: tauri::tray::TrayIcon, app: &AppHandle, menu: &Menu) -> Result<()> {
|
||||
handle.set_tooltip(Some(TOOLTIP))?;
|
||||
handle.set_menu(Some(build_app_state(app, menu)?))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn build_app_state(app: &AppHandle, menu: &Menu) -> Result<TauriMenu> {
|
||||
build_menu(app, menu)
|
||||
}
|
||||
|
||||
/// Builds this abstract `Menu` into a real menu that we can use in Tauri.
|
||||
///
|
||||
/// This recurses but we never go deeper than 3 or 4 levels so it's fine.
|
||||
pub(crate) fn build_menu(that: &Menu) -> tauri::SystemTrayMenu {
|
||||
let mut menu = tauri::SystemTrayMenu::new();
|
||||
///
|
||||
/// Note that Menus and Submenus are different in Tauri. Using a Submenu as a Menu
|
||||
/// may crash on Windows. <https://github.com/tauri-apps/tauri/issues/11363>
|
||||
pub(crate) fn build_menu(app: &AppHandle, that: &Menu) -> Result<TauriMenu> {
|
||||
let mut menu = tauri::menu::MenuBuilder::new(app);
|
||||
for entry in &that.entries {
|
||||
menu = match entry {
|
||||
Entry::Item(item) => menu.add_item(build_item(item)),
|
||||
Entry::Separator => menu.add_native_item(tauri::SystemTrayMenuItem::Separator),
|
||||
Entry::Submenu { title, inner } => {
|
||||
menu.add_submenu(tauri::SystemTraySubmenu::new(title, build_menu(inner)))
|
||||
}
|
||||
};
|
||||
menu = menu.item(&*build_entry(app, entry)?);
|
||||
}
|
||||
menu
|
||||
Ok(menu.build()?)
|
||||
}
|
||||
|
||||
/// Builds this abstract `Item` into a real item that we can use in Tauri.
|
||||
fn build_item(that: &Item) -> tauri::CustomMenuItem {
|
||||
let mut item = tauri::CustomMenuItem::new(
|
||||
serde_json::to_string(&that.event)
|
||||
.expect("`serde_json` should always be able to serialize tray menu events"),
|
||||
&that.title,
|
||||
);
|
||||
|
||||
if that.event.is_none() {
|
||||
item = item.disabled();
|
||||
pub(crate) fn build_submenu(app: &AppHandle, title: &str, that: &Menu) -> Result<TauriSubmenu> {
|
||||
let mut menu = tauri::menu::SubmenuBuilder::new(app, title);
|
||||
for entry in &that.entries {
|
||||
menu = menu.item(&*build_entry(app, entry)?);
|
||||
}
|
||||
if let Some(true) = that.checked {
|
||||
item = item.selected();
|
||||
}
|
||||
item
|
||||
Ok(menu.build()?)
|
||||
}
|
||||
|
||||
fn build_entry(app: &AppHandle, entry: &Entry) -> Result<Box<IsMenuItem>> {
|
||||
let entry = match entry {
|
||||
Entry::Item(item) => build_item(app, item)?,
|
||||
Entry::Separator => Box::new(tauri::menu::PredefinedMenuItem::separator(app)?),
|
||||
Entry::Submenu { title, inner } => Box::new(build_submenu(app, title, inner)?),
|
||||
};
|
||||
Ok(entry)
|
||||
}
|
||||
|
||||
fn build_item(app: &AppHandle, item: &Item) -> Result<Box<IsMenuItem>> {
|
||||
let item: Box<IsMenuItem> = if let Some(checked) = item.checked {
|
||||
let mut tauri_item = tauri::menu::CheckMenuItemBuilder::new(&item.title).checked(checked);
|
||||
if let Some(event) = &item.event {
|
||||
tauri_item = tauri_item.id(serde_json::to_string(event)?);
|
||||
} else {
|
||||
tauri_item = tauri_item.enabled(false);
|
||||
}
|
||||
Box::new(tauri_item.build(app)?)
|
||||
} else {
|
||||
let mut tauri_item = tauri::menu::MenuItemBuilder::new(&item.title);
|
||||
if let Some(event) = &item.event {
|
||||
tauri_item = tauri_item.id(serde_json::to_string(event)?);
|
||||
} else {
|
||||
tauri_item = tauri_item.enabled(false);
|
||||
}
|
||||
Box::new(tauri_item.build(app)?)
|
||||
};
|
||||
Ok(item)
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ use firezone_gui_client_common::{
|
||||
logging as common,
|
||||
};
|
||||
use std::path::PathBuf;
|
||||
use tauri_plugin_dialog::DialogExt as _;
|
||||
|
||||
#[tauri::command]
|
||||
pub(crate) async fn clear_logs(managed: tauri::State<'_, Managed>) -> Result<(), String> {
|
||||
@@ -22,8 +23,11 @@ pub(crate) async fn clear_logs(managed: tauri::State<'_, Managed>) -> Result<(),
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub(crate) async fn export_logs(managed: tauri::State<'_, Managed>) -> Result<(), String> {
|
||||
show_export_dialog(managed.ctlr_tx.clone()).map_err(|e| e.to_string())
|
||||
pub(crate) async fn export_logs(
|
||||
app: tauri::AppHandle,
|
||||
managed: tauri::State<'_, Managed>,
|
||||
) -> Result<(), String> {
|
||||
show_export_dialog(&app, managed.ctlr_tx.clone()).map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
@@ -32,7 +36,7 @@ pub(crate) async fn count_logs() -> Result<common::FileCount, String> {
|
||||
}
|
||||
|
||||
/// Pops up the "Save File" dialog
|
||||
fn show_export_dialog(ctlr_tx: CtlrTx) -> Result<()> {
|
||||
fn show_export_dialog(app: &tauri::AppHandle, ctlr_tx: CtlrTx) -> Result<()> {
|
||||
let now = chrono::Local::now();
|
||||
let datetime_string = now.format("%Y_%m_%d-%H-%M");
|
||||
let stem = PathBuf::from(format!("firezone_logs_{datetime_string}"));
|
||||
@@ -41,12 +45,13 @@ fn show_export_dialog(ctlr_tx: CtlrTx) -> Result<()> {
|
||||
bail!("zip filename isn't valid Unicode");
|
||||
};
|
||||
|
||||
tauri::api::dialog::FileDialogBuilder::new()
|
||||
tauri_plugin_dialog::FileDialogBuilder::new(app.dialog().clone())
|
||||
.add_filter("Zip", &["zip"])
|
||||
.set_file_name(filename)
|
||||
.save_file(move |file_path| match file_path {
|
||||
None => {}
|
||||
Some(path) => {
|
||||
let path = path.into_path().unwrap();
|
||||
// blocking_send here because we're in a sync callback within Tauri somewhere
|
||||
ctlr_tx
|
||||
.blocking_send(ControllerRequest::ExportLogs { path, stem })
|
||||
|
||||
@@ -6,5 +6,11 @@
|
||||
mod client;
|
||||
|
||||
fn main() -> anyhow::Result<()> {
|
||||
// Mitigates a bug in Ubuntu 22.04 - Under Wayland, some features of the window decorations like minimizing, closing the windows, etc., doesn't work unless you double-click the titlebar first.
|
||||
// SAFETY: No other thread is running yet
|
||||
unsafe {
|
||||
std::env::set_var("GDK_BACKEND", "x11");
|
||||
}
|
||||
|
||||
client::run()
|
||||
}
|
||||
|
||||
@@ -1,24 +1,13 @@
|
||||
{
|
||||
"build": {
|
||||
"build": {
|
||||
"beforeDevCommand": "",
|
||||
"beforeBuildCommand": "",
|
||||
"devPath": "../src",
|
||||
"distDir": "../src",
|
||||
"withGlobalTauri": true
|
||||
"frontendDist": "../dist"
|
||||
},
|
||||
"package": {
|
||||
"productName": "firezone-client-gui"
|
||||
},
|
||||
"tauri": {
|
||||
"allowlist": {
|
||||
"all": false,
|
||||
"shell": {
|
||||
"all": false,
|
||||
"open": false
|
||||
}
|
||||
},
|
||||
"bundle": {
|
||||
"active": true,
|
||||
"bundle": {
|
||||
"active": true,
|
||||
"shortDescription": "Firezone",
|
||||
"linux": {
|
||||
"deb": {
|
||||
"files": {
|
||||
"/usr/lib/systemd/system/firezone-client-ipc.service": "./deb_files/firezone-client-ipc.service",
|
||||
@@ -30,40 +19,49 @@
|
||||
"/usr/lib/systemd/system/firezone-client-ipc.service": "./deb_files/firezone-client-ipc.service",
|
||||
"/usr/lib/sysusers.d/firezone-client-ipc.conf": "./deb_files/sysusers.conf"
|
||||
}
|
||||
},
|
||||
"targets": ["deb", "msi", "rpm"],
|
||||
"identifier": "dev.firezone.client",
|
||||
"icon": [
|
||||
"icons/128x128.png",
|
||||
"icons/128x128@2x.png",
|
||||
"icons/32x32.png",
|
||||
"icons/icon.ico",
|
||||
"icons/icon.png"
|
||||
],
|
||||
"publisher": "Firezone",
|
||||
"shortDescription": "Firezone",
|
||||
"windows": {
|
||||
"wix": {
|
||||
"bannerPath": "./win_files/banner.png",
|
||||
"componentRefs": ["FirezoneClientIpcService"],
|
||||
"dialogImagePath": "./win_files/install_dialog.png",
|
||||
"fragmentPaths": ["./win_files/service.wxs"],
|
||||
"template": "./win_files/main.wxs"
|
||||
}
|
||||
}
|
||||
},
|
||||
"targets": [
|
||||
"deb",
|
||||
"msi",
|
||||
"rpm"
|
||||
],
|
||||
"windows": {
|
||||
"wix": {
|
||||
"bannerPath": "./win_files/banner.png",
|
||||
"componentRefs": [
|
||||
"FirezoneClientIpcService"
|
||||
],
|
||||
"dialogImagePath": "./win_files/install_dialog.png",
|
||||
"fragmentPaths": [
|
||||
"./win_files/service.wxs"
|
||||
],
|
||||
"template": "./win_files/main.wxs"
|
||||
}
|
||||
},
|
||||
"icon": [
|
||||
"icons/128x128.png",
|
||||
"icons/128x128@2x.png",
|
||||
"icons/32x32.png",
|
||||
"icons/icon.ico",
|
||||
"icons/icon.png"
|
||||
],
|
||||
"publisher": "Firezone"
|
||||
},
|
||||
"mainBinaryName": "firezone-client-gui",
|
||||
"identifier": "dev.firezone.client",
|
||||
"plugins": {},
|
||||
"productName": "firezone-client-gui",
|
||||
"app": {
|
||||
"withGlobalTauri": true,
|
||||
"security": {
|
||||
"csp": null
|
||||
},
|
||||
"systemTray": {
|
||||
"iconPath": "icons/tray/Busy layer.png",
|
||||
"iconAsTemplate": true
|
||||
},
|
||||
"windows": [
|
||||
{
|
||||
"label": "about",
|
||||
"title": "About Firezone",
|
||||
"url": "about.html",
|
||||
"url": "src/about.html",
|
||||
"fullscreen": false,
|
||||
"resizable": true,
|
||||
"width": 640,
|
||||
@@ -73,7 +71,7 @@
|
||||
{
|
||||
"label": "settings",
|
||||
"title": "Settings",
|
||||
"url": "settings.html",
|
||||
"url": "src/settings.html",
|
||||
"fullscreen": false,
|
||||
"resizable": true,
|
||||
"width": 640,
|
||||
@@ -83,7 +81,7 @@
|
||||
{
|
||||
"label": "welcome",
|
||||
"title": "Welcome",
|
||||
"url": "welcome.html",
|
||||
"url": "src/welcome.html",
|
||||
"fullscreen": false,
|
||||
"resizable": true,
|
||||
"width": 640,
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
"build": {
|
||||
"beforeBundleCommand": "bash -c '../../scripts/build/sign.sh ../target/release/Firezone.exe ../target/release/firezone-client-ipc.exe'"
|
||||
},
|
||||
"package": {
|
||||
"productName": "Firezone"
|
||||
}
|
||||
"mainBinaryName": "Firezone",
|
||||
"productName": "Firezone"
|
||||
}
|
||||
|
||||
@@ -8,6 +8,9 @@ component.
|
||||
<?elseif $(sys.BUILDARCH)="x64"?>
|
||||
<?define Win64 = "yes" ?>
|
||||
<?define PlatformProgramFilesFolder = "ProgramFiles64Folder" ?>
|
||||
<?elseif $(sys.BUILDARCH)="arm64"?>
|
||||
<?define Win64 = "yes" ?>
|
||||
<?define PlatformProgramFilesFolder = "ProgramFiles64Folder" ?>
|
||||
<?else?>
|
||||
<?error Unsupported value of sys.BUILDARCH=$(sys.BUILDARCH)?>
|
||||
<?endif?>
|
||||
@@ -33,6 +36,11 @@ component.
|
||||
<!-- reinstall all files; rewrite all registry entries; reinstall all shortcuts -->
|
||||
<Property Id="REINSTALLMODE" Value="amus" />
|
||||
|
||||
<!-- Auto launch app after installation, useful for passive mode which usually used in updates -->
|
||||
<Property Id="AUTOLAUNCHAPP" Secure="yes" />
|
||||
<!-- Property to forward cli args to the launched app to not lose those of the pre-update instance -->
|
||||
<Property Id="LAUNCHAPPARGS" Secure="yes" />
|
||||
|
||||
{{#if allow_downgrades}}
|
||||
<MajorUpgrade Schedule="afterInstallInitialize" AllowDowngrades="yes" />
|
||||
{{else}}
|
||||
@@ -60,6 +68,12 @@ component.
|
||||
<Property Id="ARPNOREPAIR" Value="yes" Secure="yes" /> <!-- Remove repair -->
|
||||
<SetProperty Id="ARPNOMODIFY" Value="1" After="InstallValidate" Sequence="execute"/>
|
||||
|
||||
{{#if homepage}}
|
||||
<Property Id="ARPURLINFOABOUT" Value="{{homepage}}"/>
|
||||
<Property Id="ARPHELPLINK" Value="{{homepage}}"/>
|
||||
<Property Id="ARPURLUPDATEINFO" Value="{{homepage}}"/>
|
||||
{{/if}}
|
||||
|
||||
<!-- initialize with previous InstallDir -->
|
||||
<Property Id="INSTALLDIR">
|
||||
<RegistrySearch Id="PrevInstallDirReg" Root="HKCU" Key="Software\\{{manufacturer}}\\{{product_name}}" Name="InstallDir" Type="raw"/>
|
||||
@@ -68,8 +82,7 @@ component.
|
||||
<!-- launch app checkbox -->
|
||||
<Property Id="WIXUI_EXITDIALOGOPTIONALCHECKBOXTEXT" Value="!(loc.LaunchApp)" />
|
||||
<Property Id="WIXUI_EXITDIALOGOPTIONALCHECKBOX" Value="1"/>
|
||||
<Property Id="WixShellExecTarget" Value="[!Path]" />
|
||||
<CustomAction Id="LaunchApplication" BinaryKey="WixCA" DllEntry="WixShellExec" Impersonate="yes" />
|
||||
<CustomAction Id="LaunchApplication" Impersonate="yes" FileKey="Path" ExeCommand="[LAUNCHAPPARGS]" Return="asyncNoWait" />
|
||||
|
||||
<UI>
|
||||
<!-- launch app checkbox -->
|
||||
@@ -115,9 +128,31 @@ component.
|
||||
<RegistryKey Root="HKCU" Key="Software\\{{manufacturer}}\\{{product_name}}">
|
||||
<RegistryValue Name="InstallDir" Type="string" Value="[INSTALLDIR]" KeyPath="yes" />
|
||||
</RegistryKey>
|
||||
<!-- Change the Root to HKCU for perUser installations -->
|
||||
{{#each deep_link_protocols as |protocol| ~}}
|
||||
<RegistryKey Root="HKLM" Key="Software\Classes\\{{protocol}}">
|
||||
<RegistryValue Type="string" Name="URL Protocol" Value=""/>
|
||||
<RegistryValue Type="string" Value="URL:{{bundle_id}} protocol"/>
|
||||
<RegistryKey Key="DefaultIcon">
|
||||
<RegistryValue Type="string" Value=""[!Path]",0" />
|
||||
</RegistryKey>
|
||||
<RegistryKey Key="shell\open\command">
|
||||
<RegistryValue Type="string" Value=""[!Path]" "%1"" />
|
||||
</RegistryKey>
|
||||
</RegistryKey>
|
||||
{{/each~}}
|
||||
</Component>
|
||||
<Component Id="Path" Guid="{{path_component_guid}}" Win64="$(var.Win64)">
|
||||
<File Id="Path" Source="{{app_exe_source}}" KeyPath="yes" Checksum="yes"/>
|
||||
<File Id="Path" Source="{{main_binary_path}}" KeyPath="yes" Checksum="yes"/>
|
||||
{{#each file_associations as |association| ~}}
|
||||
{{#each association.ext as |ext| ~}}
|
||||
<ProgId Id="{{../../product_name}}.{{ext}}" Advertise="yes" Description="{{association.description}}">
|
||||
<Extension Id="{{ext}}" Advertise="yes">
|
||||
<Verb Id="open" Command="Open with {{../../product_name}}" Argument=""%1"" />
|
||||
</Extension>
|
||||
</ProgId>
|
||||
{{/each~}}
|
||||
{{/each~}}
|
||||
</Component>
|
||||
{{#each binaries as |bin| ~}}
|
||||
<!--<Component Id="{{ bin.id }}" Guid="{{bin.guid}}" Win64="$(var.Win64)">
|
||||
@@ -310,6 +345,10 @@ component.
|
||||
</InstallExecuteSequence>
|
||||
{{/if}}
|
||||
|
||||
<InstallExecuteSequence>
|
||||
<Custom Action="LaunchApplication" After="InstallFinalize">AUTOLAUNCHAPP AND NOT Installed</Custom>
|
||||
</InstallExecuteSequence>
|
||||
|
||||
<SetProperty Id="ARPINSTALLLOCATION" Value="[INSTALLDIR]" After="CostFinalize"/>
|
||||
</Product>
|
||||
</Wix>
|
||||
|
||||
@@ -6,8 +6,8 @@
|
||||
<link rel="stylesheet" href="output.css" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>About Firezone</title>
|
||||
<script src="./flowbite.min.js" defer></script>
|
||||
<script type="module" src="about.js" defer></script>
|
||||
<script type="module" src="./flowbite.min.js" defer></script>
|
||||
<script type="module" src="about.ts" defer></script>
|
||||
</head>
|
||||
|
||||
<body class="bg-neutral-100 text-neutral-900">
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
import "./tauri_stub.js";
|
||||
|
||||
const invoke = window.__TAURI__.tauri.invoke;
|
||||
import { invoke } from "@tauri-apps/api/core";
|
||||
|
||||
const cargoVersionSpan = <HTMLSpanElement>(
|
||||
document.getElementById("cargo-version")
|
||||
@@ -8,25 +6,27 @@ const cargoVersionSpan = <HTMLSpanElement>(
|
||||
const gitVersionSpan = <HTMLSpanElement>document.getElementById("git-version");
|
||||
|
||||
function get_cargo_version() {
|
||||
invoke("get_cargo_version")
|
||||
.then((cargoVersion: string) => {
|
||||
cargoVersionSpan.innerText = cargoVersion;
|
||||
})
|
||||
.catch((e: Error) => {
|
||||
cargoVersionSpan.innerText = "Unknown";
|
||||
console.error(e);
|
||||
});
|
||||
try {
|
||||
invoke("get_cargo_version")
|
||||
.then((cargoVersion: unknown) => {
|
||||
cargoVersionSpan.innerText = cargoVersion as string;
|
||||
});
|
||||
} catch(e) {
|
||||
cargoVersionSpan.innerText = "Unknown";
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
function get_git_version() {
|
||||
invoke("get_git_version")
|
||||
.then((gitVersion: string) => {
|
||||
gitVersionSpan.innerText = gitVersion;
|
||||
})
|
||||
.catch((e: Error) => {
|
||||
gitVersionSpan.innerText = "Unknown";
|
||||
console.error(e);
|
||||
});
|
||||
try {
|
||||
invoke("get_git_version")
|
||||
.then((gitVersion: unknown) => {
|
||||
gitVersionSpan.innerText = gitVersion as string;
|
||||
});
|
||||
} catch(e) {
|
||||
gitVersionSpan.innerText = "Unknown";
|
||||
console.error(e);
|
||||
};
|
||||
}
|
||||
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
|
||||
@@ -6,8 +6,8 @@
|
||||
<link rel="stylesheet" href="output.css" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Settings</title>
|
||||
<script src="./flowbite.min.js" defer></script>
|
||||
<script type="module" src="settings.js" defer></script>
|
||||
<script type="module" src="./flowbite.min.js" defer></script>
|
||||
<script type="module" src="settings.ts" defer></script>
|
||||
</head>
|
||||
|
||||
<body class="bg-neutral-100 text-neutral-900">
|
||||
|
||||
@@ -1,8 +1,4 @@
|
||||
// Purpose: TypeScript file for the settings page.
|
||||
import "./tauri_stub.js";
|
||||
|
||||
const invoke = window.__TAURI__.tauri.invoke;
|
||||
const listen = window.__TAURI__.event.listen;
|
||||
import { invoke } from "@tauri-apps/api/core";
|
||||
|
||||
// Custom types
|
||||
interface Settings {
|
||||
@@ -81,95 +77,91 @@ async function applyAdvancedSettings() {
|
||||
console.log("Applying advanced settings");
|
||||
lockAdvancedSettingsForm();
|
||||
|
||||
invoke("apply_advanced_settings", {
|
||||
settings: {
|
||||
auth_base_url: authBaseUrlInput.value,
|
||||
api_url: apiUrlInput.value,
|
||||
log_filter: logFilterInput.value,
|
||||
},
|
||||
})
|
||||
.catch((e: Error) => {
|
||||
console.error(e);
|
||||
})
|
||||
.finally(() => {
|
||||
unlockAdvancedSettingsForm();
|
||||
try {
|
||||
await invoke("apply_advanced_settings", {
|
||||
settings: {
|
||||
auth_base_url: authBaseUrlInput.value,
|
||||
api_url: apiUrlInput.value,
|
||||
log_filter: logFilterInput.value,
|
||||
},
|
||||
});
|
||||
} catch(e) {
|
||||
console.error(e);
|
||||
} finally {
|
||||
unlockAdvancedSettingsForm();
|
||||
}
|
||||
}
|
||||
|
||||
async function resetAdvancedSettings() {
|
||||
console.log("Resetting advanced settings");
|
||||
lockAdvancedSettingsForm();
|
||||
|
||||
invoke("reset_advanced_settings")
|
||||
.then((settings: Settings) => {
|
||||
authBaseUrlInput.value = settings.auth_base_url;
|
||||
apiUrlInput.value = settings.api_url;
|
||||
logFilterInput.value = settings.log_filter;
|
||||
})
|
||||
.catch((e: Error) => {
|
||||
console.error(e);
|
||||
})
|
||||
.finally(() => {
|
||||
unlockAdvancedSettingsForm();
|
||||
});
|
||||
try {
|
||||
let settings = await invoke("reset_advanced_settings") as Settings;
|
||||
authBaseUrlInput.value = settings.auth_base_url;
|
||||
apiUrlInput.value = settings.api_url;
|
||||
logFilterInput.value = settings.log_filter;
|
||||
} catch(e) {
|
||||
console.error(e);
|
||||
} finally {
|
||||
unlockAdvancedSettingsForm();
|
||||
}
|
||||
}
|
||||
|
||||
async function getAdvancedSettings() {
|
||||
console.log("Getting advanced settings");
|
||||
lockAdvancedSettingsForm();
|
||||
|
||||
invoke("get_advanced_settings")
|
||||
.then((settings: Settings) => {
|
||||
authBaseUrlInput.value = settings.auth_base_url;
|
||||
apiUrlInput.value = settings.api_url;
|
||||
logFilterInput.value = settings.log_filter;
|
||||
})
|
||||
.catch((e: Error) => {
|
||||
console.error(e);
|
||||
})
|
||||
.finally(() => {
|
||||
unlockAdvancedSettingsForm();
|
||||
});
|
||||
try {
|
||||
let settings = await invoke("get_advanced_settings") as Settings;
|
||||
authBaseUrlInput.value = settings.auth_base_url;
|
||||
apiUrlInput.value = settings.api_url;
|
||||
logFilterInput.value = settings.log_filter;
|
||||
} catch(e) {
|
||||
console.error(e);
|
||||
} finally {
|
||||
unlockAdvancedSettingsForm();
|
||||
}
|
||||
}
|
||||
|
||||
async function exportLogs() {
|
||||
console.log("Exporting logs");
|
||||
lockLogsForm();
|
||||
|
||||
invoke("export_logs")
|
||||
.catch((e: Error) => {
|
||||
try {
|
||||
await invoke("export_logs");
|
||||
} catch(e) {
|
||||
console.error(e);
|
||||
})
|
||||
.finally(() => {
|
||||
unlockLogsForm();
|
||||
});
|
||||
} finally {
|
||||
unlockLogsForm();
|
||||
}
|
||||
}
|
||||
|
||||
async function clearLogs() {
|
||||
console.log("Clearing logs");
|
||||
lockLogsForm();
|
||||
|
||||
invoke("clear_logs")
|
||||
.catch((e: Error) => {
|
||||
console.error(e);
|
||||
})
|
||||
.finally(() => {
|
||||
countLogs();
|
||||
unlockLogsForm();
|
||||
});
|
||||
try {
|
||||
await invoke("clear_logs");
|
||||
} catch(e) {
|
||||
console.error(e);
|
||||
} finally {
|
||||
countLogs();
|
||||
unlockLogsForm();
|
||||
};
|
||||
}
|
||||
|
||||
async function countLogs() {
|
||||
invoke("count_logs")
|
||||
.then((fileCount) => {
|
||||
console.log(fileCount);
|
||||
const megabytes = Math.round(fileCount.bytes / 100000) / 10;
|
||||
logCountOutput.innerText = `${fileCount.files} files, ${megabytes} MB`;
|
||||
})
|
||||
.catch((e: Error) => {
|
||||
console.error(e);
|
||||
logCountOutput.innerText = `Error counting logs: ${e.message}`;
|
||||
});
|
||||
try {
|
||||
let fileCount = await invoke("count_logs") as FileCount;
|
||||
console.log(fileCount);
|
||||
const megabytes = Math.round(fileCount.bytes / 100000) / 10;
|
||||
logCountOutput.innerText = `${fileCount.files} files, ${megabytes} MB`;
|
||||
} catch(e) {
|
||||
let error = e as Error;
|
||||
console.error(e);
|
||||
logCountOutput.innerText = `Error counting logs: ${error.message}`;
|
||||
};
|
||||
}
|
||||
|
||||
// Setup event listeners
|
||||
@@ -177,16 +169,16 @@ form.addEventListener("submit", (e) => {
|
||||
e.preventDefault();
|
||||
applyAdvancedSettings();
|
||||
});
|
||||
resetAdvancedSettingsBtn.addEventListener("click", (e) => {
|
||||
resetAdvancedSettingsBtn.addEventListener("click", (_e) => {
|
||||
resetAdvancedSettings();
|
||||
});
|
||||
exportLogsBtn.addEventListener("click", (e) => {
|
||||
exportLogsBtn.addEventListener("click", (_e) => {
|
||||
exportLogs();
|
||||
});
|
||||
clearLogsBtn.addEventListener("click", (e) => {
|
||||
clearLogsBtn.addEventListener("click", (_e) => {
|
||||
clearLogs();
|
||||
});
|
||||
logsTabBtn.addEventListener("click", (e) => {
|
||||
logsTabBtn.addEventListener("click", (_e) => {
|
||||
countLogs();
|
||||
});
|
||||
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
// Stub Tauri API for TypeScript. Helpful when developing without Tauri running.
|
||||
|
||||
interface TauriEvent {
|
||||
type: string;
|
||||
payload: any;
|
||||
}
|
||||
|
||||
export {};
|
||||
declare global {
|
||||
interface Window {
|
||||
__TAURI__: {
|
||||
tauri: {
|
||||
invoke: (cmd: string, args?: any) => Promise<any>;
|
||||
};
|
||||
event: {
|
||||
listen: (cmd: string, callback: (event: TauriEvent) => void) => void;
|
||||
};
|
||||
};
|
||||
}
|
||||
}
|
||||
window.__TAURI__ = window.__TAURI__ || {
|
||||
tauri: {
|
||||
invoke: (_cmd: string, _args?: any) => {
|
||||
return Promise.reject("Tauri API not initialized");
|
||||
},
|
||||
},
|
||||
event: {
|
||||
listen: (_cmd: string, _callback: (event: TauriEvent) => void) => {
|
||||
console.error("Tauri API not initialized");
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -6,8 +6,8 @@
|
||||
<link rel="stylesheet" href="output.css" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Welcome to Firezone</title>
|
||||
<script src="./flowbite.min.js" defer></script>
|
||||
<script type="module" src="welcome.js" defer></script>
|
||||
<script type="module" src="./flowbite.min.js" defer></script>
|
||||
<script type="module" src="welcome.ts" defer></script>
|
||||
</head>
|
||||
|
||||
<body class="bg-neutral-100 text-neutral-900">
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
import "./tauri_stub.js";
|
||||
|
||||
const invoke = window.__TAURI__.tauri.invoke;
|
||||
import { invoke } from "@tauri-apps/api/core";
|
||||
|
||||
const signInBtn = <HTMLButtonElement>(
|
||||
document.getElementById("sign-in")
|
||||
@@ -15,4 +13,4 @@ async function sign_in() {
|
||||
});
|
||||
}
|
||||
|
||||
signInBtn.addEventListener("click", (e) => sign_in());
|
||||
signInBtn.addEventListener("click", (_e) => sign_in());
|
||||
|
||||
@@ -1,11 +1,23 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "es6",
|
||||
"module": "es6",
|
||||
"esModuleInterop": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"target": "ES2020",
|
||||
"useDefineForClassFields": true,
|
||||
"module": "ESNext",
|
||||
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
||||
"skipLibCheck": true,
|
||||
|
||||
/* Bundler mode */
|
||||
"moduleResolution": "bundler",
|
||||
"allowImportingTsExtensions": true,
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"noEmit": true,
|
||||
|
||||
/* Linting */
|
||||
"strict": true,
|
||||
"moduleResolution": "node"
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"noFallthroughCasesInSwitch": true
|
||||
},
|
||||
"include": ["./src/**/*"],
|
||||
"exclude": ["node_modules"]
|
||||
|
||||
40
rust/gui-client/vite.config.ts
Normal file
40
rust/gui-client/vite.config.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
import { defineConfig } from "vite";
|
||||
import { resolve } from 'path';
|
||||
|
||||
// @ts-expect-error process is a nodejs global
|
||||
const host = process.env.TAURI_DEV_HOST;
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig(async () => ({
|
||||
build: {
|
||||
rollupOptions: {
|
||||
input: {
|
||||
about: resolve(__dirname, "src/about.html"),
|
||||
settings: resolve(__dirname, "src/settings.html"),
|
||||
welcome: resolve(__dirname, "src/welcome.html"),
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// Vite options tailored for Tauri development and only applied in `tauri dev` or `tauri build`
|
||||
//
|
||||
// 1. prevent vite from obscuring rust errors
|
||||
clearScreen: false,
|
||||
// 2. tauri expects a fixed port, fail if that port is not available
|
||||
server: {
|
||||
port: 1420,
|
||||
strictPort: true,
|
||||
host: host || false,
|
||||
hmr: host
|
||||
? {
|
||||
protocol: "ws",
|
||||
host,
|
||||
port: 1421,
|
||||
}
|
||||
: undefined,
|
||||
watch: {
|
||||
// 3. tell vite to ignore watching `src-tauri`
|
||||
ignored: ["**/src-tauri/**"],
|
||||
},
|
||||
},
|
||||
}));
|
||||
@@ -1,4 +1,7 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# Runs from `rust/gui-client` or `rust/tauri-client`
|
||||
|
||||
set -euox pipefail
|
||||
|
||||
SERVICE_NAME=firezone-client-ipc
|
||||
@@ -9,21 +12,21 @@ function debug_exit() {
|
||||
}
|
||||
|
||||
# For debugging
|
||||
ls ../target/release ../target/release/bundle/deb
|
||||
ls "$TARGET_DIR/release" "$TARGET_DIR/release/bundle/deb"
|
||||
|
||||
# Used for release artifact
|
||||
# In release mode the name comes from tauri.conf.json
|
||||
# Using a glob for the source, there will only be one exe and one deb anyway
|
||||
cp ../target/release/firezone-client-gui "$BINARY_DEST_PATH"
|
||||
cp ../target/release/firezone-gui-client.dwp "$BINARY_DEST_PATH.dwp"
|
||||
cp ../target/release/bundle/deb/firezone-client-gui.deb "$BINARY_DEST_PATH.deb"
|
||||
cp "$TARGET_DIR/release/firezone-client-gui" "$BINARY_DEST_PATH"
|
||||
cp "$TARGET_DIR/release/firezone-gui-client.dwp" "$BINARY_DEST_PATH.dwp"
|
||||
cp "$TARGET_DIR/release/bundle/deb/firezone-client-gui.deb" "$BINARY_DEST_PATH.deb"
|
||||
# TODO: Debug symbols for Linux
|
||||
|
||||
function make_hash() {
|
||||
sha256sum "$1" >"$1.sha256sum.txt"
|
||||
}
|
||||
|
||||
# Windows uses x64, Debian amd64. Standardize on x86_64 naming here since that's
|
||||
# Windows calls it `x64`, Debian `amd64`. Standardize on `x86_64` here since that's
|
||||
# what Rust uses.
|
||||
make_hash "$BINARY_DEST_PATH"
|
||||
make_hash "$BINARY_DEST_PATH.dwp"
|
||||
@@ -35,6 +38,8 @@ sudo apt-get install "$DEB_PATH"
|
||||
|
||||
# Debug-print the files. The icons and both binaries should be in here
|
||||
dpkg --listfiles firezone-client-gui
|
||||
# Print the deps
|
||||
dpkg --info "$DEB_PATH"
|
||||
|
||||
# Confirm that both binaries and at least one icon were installed
|
||||
which firezone-client-gui firezone-client-ipc
|
||||
@@ -2,13 +2,13 @@
|
||||
set -euox pipefail
|
||||
|
||||
# For debugging
|
||||
ls ../target/release ../target/release/bundle/msi
|
||||
ls "$TARGET_DIR/release" "$TARGET_DIR/release/bundle/msi"
|
||||
|
||||
# Used for release artifact
|
||||
# In release mode the name comes from tauri.conf.json
|
||||
cp ../target/release/Firezone.exe "$BINARY_DEST_PATH.exe"
|
||||
cp ../target/release/bundle/msi/*.msi "$BINARY_DEST_PATH.msi"
|
||||
cp ../target/release/firezone_gui_client.pdb "$BINARY_DEST_PATH.pdb"
|
||||
cp "$TARGET_DIR/release/Firezone.exe" "$BINARY_DEST_PATH.exe"
|
||||
cp "$TARGET_DIR"/release/bundle/msi/*.msi "$BINARY_DEST_PATH.msi"
|
||||
cp "$TARGET_DIR/release/firezone_gui_client.pdb" "$BINARY_DEST_PATH.pdb"
|
||||
|
||||
function make_hash() {
|
||||
sha256sum "$1" >"$1.sha256sum.txt"
|
||||
|
||||
@@ -22,6 +22,9 @@ export default function GUI({ title }: { title: string }) {
|
||||
<ChangeItem pull="7123">
|
||||
Reports the version to the Portal correctly.
|
||||
</ChangeItem>
|
||||
<ChangeItem pull="6996">
|
||||
Supports Ubuntu 24.04, no longer supports Ubuntu 20.04.
|
||||
</ChangeItem>
|
||||
</Unreleased>
|
||||
<Entry version="1.3.9" date={new Date("2024-10-09")}>
|
||||
<ChangeItem enable={title === "Linux GUI"} pull="6987">
|
||||
|
||||
Reference in New Issue
Block a user