mirror of
				https://github.com/yuezk/GlobalProtect-openconnect.git
				synced 2025-05-20 07:26:58 -04:00 
			
		
		
		
	refactor: support edit the gp.conf
This commit is contained in:
		
							
								
								
									
										3
									
								
								.vscode/settings.json
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								.vscode/settings.json
									
									
									
									
										vendored
									
									
								
							| @@ -14,14 +14,17 @@ | |||||||
|     "humantime", |     "humantime", | ||||||
|     "Immer", |     "Immer", | ||||||
|     "jnlp", |     "jnlp", | ||||||
|  |     "notistack", | ||||||
|     "oneshot", |     "oneshot", | ||||||
|     "openconnect", |     "openconnect", | ||||||
|  |     "pkexec", | ||||||
|     "prelogin", |     "prelogin", | ||||||
|     "prelogon", |     "prelogon", | ||||||
|     "prelogonuserauthcookie", |     "prelogonuserauthcookie", | ||||||
|     "repr", |     "repr", | ||||||
|     "rustc", |     "rustc", | ||||||
|     "tauri", |     "tauri", | ||||||
|  |     "tempdir", | ||||||
|     "thiserror", |     "thiserror", | ||||||
|     "unlisten", |     "unlisten", | ||||||
|     "userauthcookie", |     "userauthcookie", | ||||||
|   | |||||||
							
								
								
									
										12
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										12
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							| @@ -2300,6 +2300,17 @@ dependencies = [ | |||||||
|  "pin-project-lite", |  "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]] | [[package]] | ||||||
| name = "overload" | name = "overload" | ||||||
| version = "0.1.1" | version = "0.1.1" | ||||||
| @@ -3398,6 +3409,7 @@ dependencies = [ | |||||||
|  "objc", |  "objc", | ||||||
|  "once_cell", |  "once_cell", | ||||||
|  "open", |  "open", | ||||||
|  |  "os_info", | ||||||
|  "percent-encoding", |  "percent-encoding", | ||||||
|  "rand 0.8.5", |  "rand 0.8.5", | ||||||
|  "raw-window-handle", |  "raw-window-handle", | ||||||
|   | |||||||
| @@ -1,3 +1,7 @@ | |||||||
|  | <p align="center"> | ||||||
|  |   <img src="https://github.com/yuezk/GlobalProtect-openconnect/assets/3297602/9242df9c-217d-42ab-8c21-8f9f69cd4eb5"> | ||||||
|  | </p> | ||||||
|  |  | ||||||
| ## Development | ## Development | ||||||
|  |  | ||||||
| ### Build the service | ### Build the service | ||||||
|   | |||||||
							
								
								
									
										19
									
								
								com.yuezk.gp.policy
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								com.yuezk.gp.policy
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,19 @@ | |||||||
|  | <?xml version="1.0" encoding="UTF-8"?> | ||||||
|  | <!DOCTYPE policyconfig PUBLIC "-//freedesktop//DTD PolicyKit Policy Configuration 1.0//EN" "http://www.freedesktop.org/standards/PolicyKit/1/policyconfig.dtd"> | ||||||
|  | <policyconfig> | ||||||
|  |   <vendor>The GlobalProtect-openconnect Project</vendor> | ||||||
|  |   <vendor_url>https://github.com/yuezk/GlobalProtect-openconnect</vendor_url> | ||||||
|  |   <icon_name>gpgui</icon_name> | ||||||
|  |   <action id="com.yuezk.gp.update-gpconf"> | ||||||
|  |     <description>Update the /etc/gpservice/gp.conf</description> | ||||||
|  |     <message>Authentication is required to update the GlobalProtect service configuration</message> | ||||||
|  |     <defaults> | ||||||
|  |       <allow_any>auth_admin</allow_any> | ||||||
|  |       <allow_inactive>auth_admin</allow_inactive> | ||||||
|  |       <allow_active>auth_admin</allow_active> | ||||||
|  |     </defaults> | ||||||
|  |     <annotate key="org.freedesktop.policykit.exec.path">/usr/bin/tee</annotate> | ||||||
|  |     <annotate key="org.freedesktop.policykit.exec.argv1">/etc/gpservice/gp.conf</annotate> | ||||||
|  |     <annotate key="org.freedesktop.policykit.exec.allow_gui">true</annotate> | ||||||
|  |   </action> | ||||||
|  | </policyconfig> | ||||||
| @@ -19,6 +19,7 @@ | |||||||
|     "jotai": "^2.2.1", |     "jotai": "^2.2.1", | ||||||
|     "jotai-immer": "^0.2.0", |     "jotai-immer": "^0.2.0", | ||||||
|     "jotai-optics": "^0.3.0", |     "jotai-optics": "^0.3.0", | ||||||
|  |     "notistack": "^3.0.1", | ||||||
|     "optics-ts": "^2.4.0", |     "optics-ts": "^2.4.0", | ||||||
|     "react": "^18.2.0", |     "react": "^18.2.0", | ||||||
|     "react-dom": "^18.2.0", |     "react-dom": "^18.2.0", | ||||||
|   | |||||||
							
								
								
									
										26
									
								
								gpgui/pnpm-lock.yaml
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										26
									
								
								gpgui/pnpm-lock.yaml
									
									
									
										generated
									
									
									
								
							| @@ -35,6 +35,9 @@ dependencies: | |||||||
|   jotai-optics: |   jotai-optics: | ||||||
|     specifier: ^0.3.0 |     specifier: ^0.3.0 | ||||||
|     version: 0.3.0(jotai@2.2.1)(optics-ts@2.4.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: |   optics-ts: | ||||||
|     specifier: ^2.4.0 |     specifier: ^2.4.0 | ||||||
|     version: 2.4.0 |     version: 2.4.0 | ||||||
| @@ -1160,6 +1163,14 @@ packages: | |||||||
|   /function-bind@1.1.1: |   /function-bind@1.1.1: | ||||||
|     resolution: {integrity: sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==} |     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: |   /has-flag@3.0.0: | ||||||
|     resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==} |     resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==} | ||||||
|     engines: {node: '>=4'} |     engines: {node: '>=4'} | ||||||
| @@ -1257,6 +1268,21 @@ packages: | |||||||
|     hasBin: true |     hasBin: true | ||||||
|     dev: 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: |   /object-assign@4.1.1: | ||||||
|     resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} |     resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} | ||||||
|     engines: {node: '>=0.10.0'} |     engines: {node: '>=0.10.0'} | ||||||
|   | |||||||
| @@ -16,7 +16,7 @@ tauri-build = { version = "1.3", features = [] } | |||||||
|  |  | ||||||
| [dependencies] | [dependencies] | ||||||
| gpcommon = { path = "../../gpcommon" } | 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 = [ | tauri-plugin-log = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v1", features = [ | ||||||
|     "colored", |     "colored", | ||||||
| ] } | ] } | ||||||
|   | |||||||
| @@ -6,9 +6,9 @@ use crate::{ | |||||||
| }; | }; | ||||||
| use gpcommon::{Client, ServerApiError, VpnStatus}; | use gpcommon::{Client, ServerApiError, VpnStatus}; | ||||||
| use serde_json::Value; | use serde_json::Value; | ||||||
| use std::sync::Arc; | use std::{process::Stdio, sync::Arc}; | ||||||
| use tauri::{AppHandle, State}; | use tauri::{AppHandle, State}; | ||||||
| use tokio::fs; | use tokio::{fs, io::AsyncWriteExt, process::Command}; | ||||||
|  |  | ||||||
| #[tauri::command] | #[tauri::command] | ||||||
| pub(crate) async fn service_online<'a>(client: State<'a, Arc<Client>>) -> Result<bool, ()> { | pub(crate) async fn service_online<'a>(client: State<'a, Arc<Client>>) -> Result<bool, ()> { | ||||||
| @@ -62,8 +62,8 @@ pub(crate) fn os_version() -> String { | |||||||
| } | } | ||||||
|  |  | ||||||
| #[tauri::command] | #[tauri::command] | ||||||
| pub(crate) fn openssl_config() -> String { | pub(crate) async fn openssl_config() -> Result<String, ()> { | ||||||
|     get_openssl_conf() |     Ok(get_openssl_conf()) | ||||||
| } | } | ||||||
|  |  | ||||||
| #[tauri::command] | #[tauri::command] | ||||||
| @@ -75,6 +75,40 @@ pub(crate) async fn update_openssl_config(app_handle: AppHandle) -> tauri::Resul | |||||||
|     Ok(()) |     Ok(()) | ||||||
| } | } | ||||||
|  |  | ||||||
|  | #[tauri::command] | ||||||
|  | pub(crate) async fn openconnect_config() -> tauri::Result<String> { | ||||||
|  |     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<i32> { | ||||||
|  |     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] | #[tauri::command] | ||||||
| pub(crate) async fn store_get<'a>( | pub(crate) async fn store_get<'a>( | ||||||
|     hint: KeyHint<'_>, |     hint: KeyHint<'_>, | ||||||
|   | |||||||
| @@ -31,6 +31,8 @@ fn main() { | |||||||
|             commands::os_version, |             commands::os_version, | ||||||
|             commands::openssl_config, |             commands::openssl_config, | ||||||
|             commands::update_openssl_config, |             commands::update_openssl_config, | ||||||
|  |             commands::openconnect_config, | ||||||
|  |             commands::update_openconnect_config, | ||||||
|             commands::store_get, |             commands::store_get, | ||||||
|             commands::store_set, |             commands::store_set, | ||||||
|             commands::store_save, |             commands::store_save, | ||||||
|   | |||||||
| @@ -25,6 +25,13 @@ | |||||||
|       }, |       }, | ||||||
|       "process": { |       "process": { | ||||||
|         "exit": true |         "exit": true | ||||||
|  |       }, | ||||||
|  |       "fs": { | ||||||
|  |         "scope": ["$TEMP/gp.conf"], | ||||||
|  |         "writeFile": true | ||||||
|  |       }, | ||||||
|  |       "os": { | ||||||
|  |         "all": true | ||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
|     "bundle": { |     "bundle": { | ||||||
|   | |||||||
| @@ -79,6 +79,12 @@ export const opensslConfigAtom = atomWithDefault(async () => { | |||||||
|   return settingsService.getOpenSSLConfig(); |   return settingsService.getOpenSSLConfig(); | ||||||
| }); | }); | ||||||
|  |  | ||||||
|  | export const openconnectConfigAtom = atomWithDefault<string | Promise<string>>( | ||||||
|  |   () => { | ||||||
|  |     return settingsService.getOpenconnectConfig(); | ||||||
|  |   } | ||||||
|  | ); | ||||||
|  |  | ||||||
| export const saveSettingsAtom = atom(null, async (get, set) => { | export const saveSettingsAtom = atom(null, async (get, set) => { | ||||||
|   const clientOS = get(clientOSAtom); |   const clientOS = get(clientOSAtom); | ||||||
|   const osVersion = get(osVersionAtom); |   const osVersion = get(osVersionAtom); | ||||||
| @@ -95,4 +101,11 @@ export const saveSettingsAtom = atom(null, async (get, set) => { | |||||||
|   if (customOpenSSL) { |   if (customOpenSSL) { | ||||||
|     await settingsService.updateOpenSSLConfig(); |     await settingsService.updateOpenSSLConfig(); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  |   const initialOpenconnectConfig = await settingsService.getOpenconnectConfig(); | ||||||
|  |   const openconnectConfig = await get(openconnectConfigAtom); | ||||||
|  |  | ||||||
|  |   if (initialOpenconnectConfig !== openconnectConfig) { | ||||||
|  |     await settingsService.updateOpenconnectConfig(openconnectConfig); | ||||||
|  |   } | ||||||
| }); | }); | ||||||
|   | |||||||
| @@ -1,4 +1,5 @@ | |||||||
| import { Box, CssBaseline, ThemeProvider } from "@mui/material"; | import { Box, CssBaseline, ThemeProvider } from "@mui/material"; | ||||||
|  | import { SnackbarProvider } from "notistack"; | ||||||
| import React, { Suspense } from "react"; | import React, { Suspense } from "react"; | ||||||
| import { createRoot } from "react-dom/client"; | import { createRoot } from "react-dom/client"; | ||||||
| import "./styles.css"; | import "./styles.css"; | ||||||
| @@ -27,8 +28,10 @@ function AppShell({ children }: { children: React.ReactNode }) { | |||||||
|   return ( |   return ( | ||||||
|     <React.StrictMode> |     <React.StrictMode> | ||||||
|       <ThemeProvider theme={theme}> |       <ThemeProvider theme={theme}> | ||||||
|  |         <SnackbarProvider> | ||||||
|           <CssBaseline /> |           <CssBaseline /> | ||||||
|           <Suspense fallback={<Loading />}>{children}</Suspense> |           <Suspense fallback={<Loading />}>{children}</Suspense> | ||||||
|  |         </SnackbarProvider> | ||||||
|       </ThemeProvider> |       </ThemeProvider> | ||||||
|     </React.StrictMode> |     </React.StrictMode> | ||||||
|   ); |   ); | ||||||
|   | |||||||
| @@ -7,7 +7,7 @@ export default function useGlobalTheme() { | |||||||
|     () => |     () => | ||||||
|       createTheme({ |       createTheme({ | ||||||
|         palette: { |         palette: { | ||||||
|           mode: prefersDarkMode ? "dark" : "light", |           mode: prefersDarkMode ? "light" : "light", | ||||||
|         }, |         }, | ||||||
|         components: { |         components: { | ||||||
|           MuiButton: { |           MuiButton: { | ||||||
|   | |||||||
| @@ -4,7 +4,7 @@ import { | |||||||
|   LockReset, |   LockReset, | ||||||
|   Menu as MenuIcon, |   Menu as MenuIcon, | ||||||
|   Settings, |   Settings, | ||||||
|   VpnLock, |   SyncAlt, | ||||||
| } from "@mui/icons-material"; | } from "@mui/icons-material"; | ||||||
| import { Box, Divider, IconButton, Menu, MenuItem } from "@mui/material"; | import { Box, Divider, IconButton, Menu, MenuItem } from "@mui/material"; | ||||||
| import { alpha, styled } from "@mui/material/styles"; | import { alpha, styled } from "@mui/material/styles"; | ||||||
| @@ -73,7 +73,7 @@ export default function MainMenu() { | |||||||
|           onClick={handleClose} |           onClick={handleClose} | ||||||
|         > |         > | ||||||
|           <MenuItem onClick={openGatewaySwitcher}> |           <MenuItem onClick={openGatewaySwitcher}> | ||||||
|             <VpnLock /> |             <SyncAlt /> | ||||||
|             Switch Gateway |             Switch Gateway | ||||||
|           </MenuItem> |           </MenuItem> | ||||||
|           <MenuItem onClick={() => openSettings()}> |           <MenuItem onClick={() => openSettings()}> | ||||||
|   | |||||||
							
								
								
									
										59
									
								
								gpgui/src/components/settings/OpenConnect.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										59
									
								
								gpgui/src/components/settings/OpenConnect.tsx
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,59 @@ | |||||||
|  | import { TabPanel } from "@mui/lab"; | ||||||
|  | import { Alert, Box, Link, TextField, Typography } from "@mui/material"; | ||||||
|  | import { useAtom } from "jotai"; | ||||||
|  | import { openconnectConfigAtom } from "../../atoms/settings"; | ||||||
|  |  | ||||||
|  | export default function OpenConnect() { | ||||||
|  |   const [openconnectConfig, setOpenconnectConfig] = useAtom( | ||||||
|  |     openconnectConfigAtom | ||||||
|  |   ); | ||||||
|  |  | ||||||
|  |   return ( | ||||||
|  |     <TabPanel | ||||||
|  |       value="openconnect" | ||||||
|  |       sx={{ flex: 1, display: "flex", flexDirection: "column" }} | ||||||
|  |     > | ||||||
|  |       <Alert severity="info"> | ||||||
|  |         You can edit the OpenConnect parameters here. More information can be | ||||||
|  |         found{" "} | ||||||
|  |         <Link | ||||||
|  |           target="_blank" | ||||||
|  |           href="https://github.com/yuezk/GlobalProtect-openconnect/wiki/Configuration" | ||||||
|  |         > | ||||||
|  |           here | ||||||
|  |         </Link> | ||||||
|  |         . | ||||||
|  |       </Alert> | ||||||
|  |  | ||||||
|  |       <Box mt={2} sx={{ flex: 1, display: "flex", flexDirection: "column" }}> | ||||||
|  |         <Typography variant="subtitle1"> | ||||||
|  |           File location: /etc/gpservice/gp.conf | ||||||
|  |         </Typography> | ||||||
|  |         <TextField | ||||||
|  |           fullWidth | ||||||
|  |           multiline | ||||||
|  |           value={openconnectConfig} | ||||||
|  |           onChange={(event) => setOpenconnectConfig(event.target.value)} | ||||||
|  |           sx={{ | ||||||
|  |             flex: 1, | ||||||
|  |             display: "flex", | ||||||
|  |             "& .MuiInputBase-root": { | ||||||
|  |               flex: "1 1 auto", | ||||||
|  |               display: "flex", | ||||||
|  |               flexDirection: "column", | ||||||
|  |               height: 0, | ||||||
|  |  | ||||||
|  |               "& textarea": { | ||||||
|  |                 whiteSpace: "pre", | ||||||
|  |                 fontFamily: "monospace", | ||||||
|  |                 overflow: "auto !important", | ||||||
|  |                 fontSize: 14, | ||||||
|  |                 lineHeight: 1.2, | ||||||
|  |               }, | ||||||
|  |             }, | ||||||
|  |           }} | ||||||
|  |         /> | ||||||
|  |       </Box> | ||||||
|  |     </TabPanel> | ||||||
|  |   ); | ||||||
|  | } | ||||||
| @@ -1,10 +1,12 @@ | |||||||
| import { Devices, Https } from "@mui/icons-material"; | import { Devices, Https, VpnLock } from "@mui/icons-material"; | ||||||
| import { TabContext, TabList } from "@mui/lab"; | import { TabContext, TabList } from "@mui/lab"; | ||||||
| import { Box, Button, DialogActions, Tab } from "@mui/material"; | import { Box, Button, DialogActions, Tab } from "@mui/material"; | ||||||
| import { useSetAtom } from "jotai"; | import { useSetAtom } from "jotai"; | ||||||
|  | import { useSnackbar } from "notistack"; | ||||||
| import { useState } from "react"; | import { useState } from "react"; | ||||||
| import { saveSettingsAtom } from "../../atoms/settings"; | import { saveSettingsAtom } from "../../atoms/settings"; | ||||||
| import settingsService, { TabValue } from "../../services/settingsService"; | import settingsService, { TabValue } from "../../services/settingsService"; | ||||||
|  | import OpenConnect from "./OpenConnect"; | ||||||
| import OpenSSL from "./OpenSSL"; | import OpenSSL from "./OpenSSL"; | ||||||
| import Simulation from "./Simulation"; | import Simulation from "./Simulation"; | ||||||
|  |  | ||||||
| @@ -15,6 +17,7 @@ const activeTab = new URLSearchParams(window.location.search).get( | |||||||
| export default function SettingsPanel() { | export default function SettingsPanel() { | ||||||
|   const [value, setValue] = useState<TabValue>(activeTab); |   const [value, setValue] = useState<TabValue>(activeTab); | ||||||
|   const saveSettings = useSetAtom(saveSettingsAtom); |   const saveSettings = useSetAtom(saveSettingsAtom); | ||||||
|  |   const { enqueueSnackbar } = useSnackbar(); | ||||||
|  |  | ||||||
|   const handleChange = (event: React.SyntheticEvent, newValue: string) => { |   const handleChange = (event: React.SyntheticEvent, newValue: string) => { | ||||||
|     setValue(newValue as TabValue); |     setValue(newValue as TabValue); | ||||||
| @@ -25,8 +28,14 @@ export default function SettingsPanel() { | |||||||
|   }; |   }; | ||||||
|  |  | ||||||
|   const save = async () => { |   const save = async () => { | ||||||
|  |     try { | ||||||
|       await saveSettings(); |       await saveSettings(); | ||||||
|  |       enqueueSnackbar("Settings saved", { variant: "success" }); | ||||||
|       await closeWindow(); |       await closeWindow(); | ||||||
|  |     } catch (err) { | ||||||
|  |       console.warn("Failed to save settings", err); | ||||||
|  |       enqueueSnackbar("Failed to save settings", { variant: "error" }); | ||||||
|  |     } | ||||||
|   }; |   }; | ||||||
|  |  | ||||||
|   return ( |   return ( | ||||||
| @@ -43,16 +52,33 @@ export default function SettingsPanel() { | |||||||
|               value="simulation" |               value="simulation" | ||||||
|               icon={<Devices />} |               icon={<Devices />} | ||||||
|               iconPosition="start" |               iconPosition="start" | ||||||
|  |               sx={{ justifyContent: "start" }} | ||||||
|  |             /> | ||||||
|  |             <Tab | ||||||
|  |               label="OpenConnect" | ||||||
|  |               value="openconnect" | ||||||
|  |               icon={<VpnLock />} | ||||||
|  |               iconPosition="start" | ||||||
|  |               sx={{ justifyContent: "start" }} | ||||||
|             /> |             /> | ||||||
|             <Tab |             <Tab | ||||||
|               label="OpenSSL" |               label="OpenSSL" | ||||||
|               value="openssl" |               value="openssl" | ||||||
|               icon={<Https />} |               icon={<Https />} | ||||||
|               iconPosition="start" |               iconPosition="start" | ||||||
|  |               sx={{ justifyContent: "start" }} | ||||||
|             /> |             /> | ||||||
|           </TabList> |           </TabList> | ||||||
|           <Box sx={{ flex: 1 }}> |           <Box | ||||||
|  |             sx={{ | ||||||
|  |               flex: 1, | ||||||
|  |               display: "flex", | ||||||
|  |               flexDirection: "column", | ||||||
|  |               "& .MuiTabPanel-root[hidden]": { display: "none" }, | ||||||
|  |             }} | ||||||
|  |           > | ||||||
|             <Simulation /> |             <Simulation /> | ||||||
|  |             <OpenConnect /> | ||||||
|             <OpenSSL /> |             <OpenSSL /> | ||||||
|           </Box> |           </Box> | ||||||
|         </TabContext> |         </TabContext> | ||||||
|   | |||||||
| @@ -1,8 +1,11 @@ | |||||||
|  | import { tempdir } from "@tauri-apps/api/os"; | ||||||
| import { UserAttentionType, WebviewWindow } from "@tauri-apps/api/window"; | import { UserAttentionType, WebviewWindow } from "@tauri-apps/api/window"; | ||||||
| import invokeCommand from "../utils/invokeCommand"; | import invokeCommand from "../utils/invokeCommand"; | ||||||
| import { appStorage } from "./storageService"; | import { appStorage } from "./storageService"; | ||||||
|  | import { fs } from "@tauri-apps/api"; | ||||||
|  | import { Command } from "@tauri-apps/api/shell"; | ||||||
|  |  | ||||||
| export type TabValue = "simulation" | "openssl"; | export type TabValue = "simulation" | "openssl" | "openconnect"; | ||||||
| const SETTINGS_WINDOW_LABEL = "settings"; | const SETTINGS_WINDOW_LABEL = "settings"; | ||||||
|  |  | ||||||
| async function openSettings(options?: { tab?: TabValue }) { | async function openSettings(options?: { tab?: TabValue }) { | ||||||
| @@ -17,7 +20,7 @@ async function openSettings(options?: { tab?: TabValue }) { | |||||||
|   new WebviewWindow(SETTINGS_WINDOW_LABEL, { |   new WebviewWindow(SETTINGS_WINDOW_LABEL, { | ||||||
|     url: `pages/settings/index.html?tab=${tab}`, |     url: `pages/settings/index.html?tab=${tab}`, | ||||||
|     title: "GlobalProtect Settings", |     title: "GlobalProtect Settings", | ||||||
|     width: 650, |     width: 680, | ||||||
|     height: 480, |     height: 480, | ||||||
|     center: true, |     center: true, | ||||||
|     resizable: false, |     resizable: false, | ||||||
| @@ -128,6 +131,23 @@ async function updateOpenSSLConfig() { | |||||||
|   return invokeCommand("update_openssl_config"); |   return invokeCommand("update_openssl_config"); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | async function getOpenconnectConfig(): Promise<string> { | ||||||
|  |   try { | ||||||
|  |     const content = await invokeCommand<string>("openconnect_config"); | ||||||
|  |     return content; | ||||||
|  |   } catch (e) { | ||||||
|  |     console.error(e); | ||||||
|  |     return "# Failed to read /etc/gpservice/gp.conf"; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | async function updateOpenconnectConfig(content: string) { | ||||||
|  |   const exitCode = await invokeCommand("update_openconnect_config", { content }); | ||||||
|  |   if (exitCode) { | ||||||
|  |     throw new Error(`Failed to update openconnect config: ${exitCode}`); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
| export default { | export default { | ||||||
|   openSettings, |   openSettings, | ||||||
|   closeSettings, |   closeSettings, | ||||||
| @@ -137,4 +157,6 @@ export default { | |||||||
|   determineOsVersion, |   determineOsVersion, | ||||||
|   getOpenSSLConfig, |   getOpenSSLConfig, | ||||||
|   updateOpenSSLConfig, |   updateOpenSSLConfig, | ||||||
|  |   getOpenconnectConfig, | ||||||
|  |   updateOpenconnectConfig, | ||||||
| }; | }; | ||||||
|   | |||||||
							
								
								
									
										
											BIN
										
									
								
								screenshot-light.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								screenshot-light.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 66 KiB | 
		Reference in New Issue
	
	Block a user