diff --git a/.vscode/settings.json b/.vscode/settings.json
index ef8ad26..69e62d3 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -14,14 +14,17 @@
"humantime",
"Immer",
"jnlp",
+ "notistack",
"oneshot",
"openconnect",
+ "pkexec",
"prelogin",
"prelogon",
"prelogonuserauthcookie",
"repr",
"rustc",
"tauri",
+ "tempdir",
"thiserror",
"unlisten",
"userauthcookie",
diff --git a/Cargo.lock b/Cargo.lock
index f402a12..c797961 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -2300,6 +2300,17 @@ dependencies = [
"pin-project-lite",
]
+[[package]]
+name = "os_info"
+version = "3.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "006e42d5b888366f1880eda20371fedde764ed2213dc8496f49622fa0c99cd5e"
+dependencies = [
+ "log",
+ "serde",
+ "winapi",
+]
+
[[package]]
name = "overload"
version = "0.1.1"
@@ -3398,6 +3409,7 @@ dependencies = [
"objc",
"once_cell",
"open",
+ "os_info",
"percent-encoding",
"rand 0.8.5",
"raw-window-handle",
diff --git a/README.md b/README.md
index 8d49c80..3012017 100644
--- a/README.md
+++ b/README.md
@@ -1,3 +1,7 @@
+
+
+
+
## Development
### Build the service
diff --git a/com.yuezk.gp.policy b/com.yuezk.gp.policy
new file mode 100644
index 0000000..7e34309
--- /dev/null
+++ b/com.yuezk.gp.policy
@@ -0,0 +1,19 @@
+
+
+
+ The GlobalProtect-openconnect Project
+ https://github.com/yuezk/GlobalProtect-openconnect
+ gpgui
+
+ Update the /etc/gpservice/gp.conf
+ Authentication is required to update the GlobalProtect service configuration
+
+ auth_admin
+ auth_admin
+ auth_admin
+
+ /usr/bin/tee
+ /etc/gpservice/gp.conf
+ true
+
+
diff --git a/gpgui/package.json b/gpgui/package.json
index 4d4af5c..b0741b2 100644
--- a/gpgui/package.json
+++ b/gpgui/package.json
@@ -19,6 +19,7 @@
"jotai": "^2.2.1",
"jotai-immer": "^0.2.0",
"jotai-optics": "^0.3.0",
+ "notistack": "^3.0.1",
"optics-ts": "^2.4.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
diff --git a/gpgui/pnpm-lock.yaml b/gpgui/pnpm-lock.yaml
index f7c4dbf..1ee253d 100644
--- a/gpgui/pnpm-lock.yaml
+++ b/gpgui/pnpm-lock.yaml
@@ -35,6 +35,9 @@ dependencies:
jotai-optics:
specifier: ^0.3.0
version: 0.3.0(jotai@2.2.1)(optics-ts@2.4.0)
+ notistack:
+ specifier: ^3.0.1
+ version: 3.0.1(csstype@3.1.2)(react-dom@18.2.0)(react@18.2.0)
optics-ts:
specifier: ^2.4.0
version: 2.4.0
@@ -1160,6 +1163,14 @@ packages:
/function-bind@1.1.1:
resolution: {integrity: sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==}
+ /goober@2.1.13(csstype@3.1.2):
+ resolution: {integrity: sha512-jFj3BQeleOoy7t93E9rZ2de+ScC4lQICLwiAQmKMg9F6roKGaLSHoCDYKkWlSafg138jejvq/mTdvmnwDQgqoQ==}
+ peerDependencies:
+ csstype: ^3.0.10
+ dependencies:
+ csstype: 3.1.2
+ dev: false
+
/has-flag@3.0.0:
resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==}
engines: {node: '>=4'}
@@ -1257,6 +1268,21 @@ packages:
hasBin: true
dev: true
+ /notistack@3.0.1(csstype@3.1.2)(react-dom@18.2.0)(react@18.2.0):
+ resolution: {integrity: sha512-ntVZXXgSQH5WYfyU+3HfcXuKaapzAJ8fBLQ/G618rn3yvSzEbnOB8ZSOwhX+dAORy/lw+GC2N061JA0+gYWTVA==}
+ engines: {node: '>=12.0.0', npm: '>=6.0.0'}
+ peerDependencies:
+ react: ^16.8.0 || ^17.0.0 || ^18.0.0
+ react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0
+ dependencies:
+ clsx: 1.2.1
+ goober: 2.1.13(csstype@3.1.2)
+ react: 18.2.0
+ react-dom: 18.2.0(react@18.2.0)
+ transitivePeerDependencies:
+ - csstype
+ dev: false
+
/object-assign@4.1.1:
resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==}
engines: {node: '>=0.10.0'}
diff --git a/gpgui/src-tauri/Cargo.toml b/gpgui/src-tauri/Cargo.toml
index c4102ab..5057aa4 100644
--- a/gpgui/src-tauri/Cargo.toml
+++ b/gpgui/src-tauri/Cargo.toml
@@ -16,7 +16,7 @@ tauri-build = { version = "1.3", features = [] }
[dependencies]
gpcommon = { path = "../../gpcommon" }
-tauri = { version = "1.3", features = ["http-all", "process-exit", "shell-open", "window-all", "window-data-url"] }
+tauri = { version = "1.3", features = ["fs-write-file", "http-all", "os-all", "process-exit", "shell-open", "window-all", "window-data-url"] }
tauri-plugin-log = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v1", features = [
"colored",
] }
diff --git a/gpgui/src-tauri/src/commands.rs b/gpgui/src-tauri/src/commands.rs
index e715020..28530b2 100644
--- a/gpgui/src-tauri/src/commands.rs
+++ b/gpgui/src-tauri/src/commands.rs
@@ -6,9 +6,9 @@ use crate::{
};
use gpcommon::{Client, ServerApiError, VpnStatus};
use serde_json::Value;
-use std::sync::Arc;
+use std::{process::Stdio, sync::Arc};
use tauri::{AppHandle, State};
-use tokio::fs;
+use tokio::{fs, io::AsyncWriteExt, process::Command};
#[tauri::command]
pub(crate) async fn service_online<'a>(client: State<'a, Arc>) -> Result {
@@ -62,8 +62,8 @@ pub(crate) fn os_version() -> String {
}
#[tauri::command]
-pub(crate) fn openssl_config() -> String {
- get_openssl_conf()
+pub(crate) async fn openssl_config() -> Result {
+ Ok(get_openssl_conf())
}
#[tauri::command]
@@ -75,6 +75,40 @@ pub(crate) async fn update_openssl_config(app_handle: AppHandle) -> tauri::Resul
Ok(())
}
+#[tauri::command]
+pub(crate) async fn openconnect_config() -> tauri::Result {
+ let file = "/etc/gpservice/gp.conf";
+ let content = fs::read_to_string(file).await?;
+ Ok(content)
+}
+
+#[tauri::command]
+pub(crate) async fn update_openconnect_config(content: String) -> tauri::Result {
+ let file = "/etc/gpservice/gp.conf";
+ let mut child = Command::new("pkexec")
+ .arg("tee")
+ .arg(file)
+ .stdin(Stdio::piped())
+ .stdout(Stdio::null())
+ .spawn()?;
+
+ let mut stdin = child.stdin.take().unwrap();
+
+ tokio::spawn(async move {
+ stdin.write_all(content.as_bytes()).await.unwrap();
+ drop(stdin);
+ });
+
+ let exit_status = child.wait().await?;
+
+ exit_status.code().ok_or_else(|| {
+ tauri::Error::Io(std::io::Error::new(
+ std::io::ErrorKind::Other,
+ "Process exited without a code",
+ ))
+ })
+}
+
#[tauri::command]
pub(crate) async fn store_get<'a>(
hint: KeyHint<'_>,
diff --git a/gpgui/src-tauri/src/main.rs b/gpgui/src-tauri/src/main.rs
index 0b23c2f..6f0f2f5 100644
--- a/gpgui/src-tauri/src/main.rs
+++ b/gpgui/src-tauri/src/main.rs
@@ -31,6 +31,8 @@ fn main() {
commands::os_version,
commands::openssl_config,
commands::update_openssl_config,
+ commands::openconnect_config,
+ commands::update_openconnect_config,
commands::store_get,
commands::store_set,
commands::store_save,
diff --git a/gpgui/src-tauri/tauri.conf.json b/gpgui/src-tauri/tauri.conf.json
index fe19e5a..80fc81f 100644
--- a/gpgui/src-tauri/tauri.conf.json
+++ b/gpgui/src-tauri/tauri.conf.json
@@ -25,6 +25,13 @@
},
"process": {
"exit": true
+ },
+ "fs": {
+ "scope": ["$TEMP/gp.conf"],
+ "writeFile": true
+ },
+ "os": {
+ "all": true
}
},
"bundle": {
diff --git a/gpgui/src/atoms/settings.ts b/gpgui/src/atoms/settings.ts
index 7ea0449..44c6e83 100644
--- a/gpgui/src/atoms/settings.ts
+++ b/gpgui/src/atoms/settings.ts
@@ -79,6 +79,12 @@ export const opensslConfigAtom = atomWithDefault(async () => {
return settingsService.getOpenSSLConfig();
});
+export const openconnectConfigAtom = atomWithDefault>(
+ () => {
+ return settingsService.getOpenconnectConfig();
+ }
+);
+
export const saveSettingsAtom = atom(null, async (get, set) => {
const clientOS = get(clientOSAtom);
const osVersion = get(osVersionAtom);
@@ -95,4 +101,11 @@ export const saveSettingsAtom = atom(null, async (get, set) => {
if (customOpenSSL) {
await settingsService.updateOpenSSLConfig();
}
+
+ const initialOpenconnectConfig = await settingsService.getOpenconnectConfig();
+ const openconnectConfig = await get(openconnectConfigAtom);
+
+ if (initialOpenconnectConfig !== openconnectConfig) {
+ await settingsService.updateOpenconnectConfig(openconnectConfig);
+ }
});
diff --git a/gpgui/src/components/AppShell/index.tsx b/gpgui/src/components/AppShell/index.tsx
index 0873263..4749efb 100644
--- a/gpgui/src/components/AppShell/index.tsx
+++ b/gpgui/src/components/AppShell/index.tsx
@@ -1,4 +1,5 @@
import { Box, CssBaseline, ThemeProvider } from "@mui/material";
+import { SnackbarProvider } from "notistack";
import React, { Suspense } from "react";
import { createRoot } from "react-dom/client";
import "./styles.css";
@@ -27,8 +28,10 @@ function AppShell({ children }: { children: React.ReactNode }) {
return (
-
- }>{children}
+
+
+ }>{children}
+
);
diff --git a/gpgui/src/components/AppShell/useGlobalTheme.ts b/gpgui/src/components/AppShell/useGlobalTheme.ts
index 4627be7..2521f5a 100644
--- a/gpgui/src/components/AppShell/useGlobalTheme.ts
+++ b/gpgui/src/components/AppShell/useGlobalTheme.ts
@@ -7,7 +7,7 @@ export default function useGlobalTheme() {
() =>
createTheme({
palette: {
- mode: prefersDarkMode ? "dark" : "light",
+ mode: prefersDarkMode ? "light" : "light",
},
components: {
MuiButton: {
diff --git a/gpgui/src/components/MainMenu/index.tsx b/gpgui/src/components/MainMenu/index.tsx
index e55b576..db87928 100644
--- a/gpgui/src/components/MainMenu/index.tsx
+++ b/gpgui/src/components/MainMenu/index.tsx
@@ -4,7 +4,7 @@ import {
LockReset,
Menu as MenuIcon,
Settings,
- VpnLock,
+ SyncAlt,
} from "@mui/icons-material";
import { Box, Divider, IconButton, Menu, MenuItem } from "@mui/material";
import { alpha, styled } from "@mui/material/styles";
@@ -73,7 +73,7 @@ export default function MainMenu() {
onClick={handleClose}
>