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:
		| @@ -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", | ||||
|   | ||||
							
								
								
									
										26
									
								
								gpgui/pnpm-lock.yaml
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										26
									
								
								gpgui/pnpm-lock.yaml
									
									
									
										generated
									
									
									
								
							| @@ -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'} | ||||
|   | ||||
| @@ -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", | ||||
| ] } | ||||
|   | ||||
| @@ -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<Client>>) -> Result<bool, ()> { | ||||
| @@ -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<String, ()> { | ||||
|     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<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] | ||||
| pub(crate) async fn store_get<'a>( | ||||
|     hint: KeyHint<'_>, | ||||
|   | ||||
| @@ -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, | ||||
|   | ||||
| @@ -25,6 +25,13 @@ | ||||
|       }, | ||||
|       "process": { | ||||
|         "exit": true | ||||
|       }, | ||||
|       "fs": { | ||||
|         "scope": ["$TEMP/gp.conf"], | ||||
|         "writeFile": true | ||||
|       }, | ||||
|       "os": { | ||||
|         "all": true | ||||
|       } | ||||
|     }, | ||||
|     "bundle": { | ||||
|   | ||||
| @@ -79,6 +79,12 @@ export const opensslConfigAtom = atomWithDefault(async () => { | ||||
|   return settingsService.getOpenSSLConfig(); | ||||
| }); | ||||
|  | ||||
| export const openconnectConfigAtom = atomWithDefault<string | Promise<string>>( | ||||
|   () => { | ||||
|     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); | ||||
|   } | ||||
| }); | ||||
|   | ||||
| @@ -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 ( | ||||
|     <React.StrictMode> | ||||
|       <ThemeProvider theme={theme}> | ||||
|         <CssBaseline /> | ||||
|         <Suspense fallback={<Loading />}>{children}</Suspense> | ||||
|         <SnackbarProvider> | ||||
|           <CssBaseline /> | ||||
|           <Suspense fallback={<Loading />}>{children}</Suspense> | ||||
|         </SnackbarProvider> | ||||
|       </ThemeProvider> | ||||
|     </React.StrictMode> | ||||
|   ); | ||||
|   | ||||
| @@ -7,7 +7,7 @@ export default function useGlobalTheme() { | ||||
|     () => | ||||
|       createTheme({ | ||||
|         palette: { | ||||
|           mode: prefersDarkMode ? "dark" : "light", | ||||
|           mode: prefersDarkMode ? "light" : "light", | ||||
|         }, | ||||
|         components: { | ||||
|           MuiButton: { | ||||
|   | ||||
| @@ -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} | ||||
|         > | ||||
|           <MenuItem onClick={openGatewaySwitcher}> | ||||
|             <VpnLock /> | ||||
|             <SyncAlt /> | ||||
|             Switch Gateway | ||||
|           </MenuItem> | ||||
|           <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 { Box, Button, DialogActions, Tab } from "@mui/material"; | ||||
| import { useSetAtom } from "jotai"; | ||||
| import { useSnackbar } from "notistack"; | ||||
| import { useState } from "react"; | ||||
| import { saveSettingsAtom } from "../../atoms/settings"; | ||||
| import settingsService, { TabValue } from "../../services/settingsService"; | ||||
| import OpenConnect from "./OpenConnect"; | ||||
| import OpenSSL from "./OpenSSL"; | ||||
| import Simulation from "./Simulation"; | ||||
|  | ||||
| @@ -15,6 +17,7 @@ const activeTab = new URLSearchParams(window.location.search).get( | ||||
| export default function SettingsPanel() { | ||||
|   const [value, setValue] = useState<TabValue>(activeTab); | ||||
|   const saveSettings = useSetAtom(saveSettingsAtom); | ||||
|   const { enqueueSnackbar } = useSnackbar(); | ||||
|  | ||||
|   const handleChange = (event: React.SyntheticEvent, newValue: string) => { | ||||
|     setValue(newValue as TabValue); | ||||
| @@ -25,8 +28,14 @@ export default function SettingsPanel() { | ||||
|   }; | ||||
|  | ||||
|   const save = async () => { | ||||
|     await saveSettings(); | ||||
|     await closeWindow(); | ||||
|     try { | ||||
|       await saveSettings(); | ||||
|       enqueueSnackbar("Settings saved", { variant: "success" }); | ||||
|       await closeWindow(); | ||||
|     } catch (err) { | ||||
|       console.warn("Failed to save settings", err); | ||||
|       enqueueSnackbar("Failed to save settings", { variant: "error" }); | ||||
|     } | ||||
|   }; | ||||
|  | ||||
|   return ( | ||||
| @@ -43,16 +52,33 @@ export default function SettingsPanel() { | ||||
|               value="simulation" | ||||
|               icon={<Devices />} | ||||
|               iconPosition="start" | ||||
|               sx={{ justifyContent: "start" }} | ||||
|             /> | ||||
|             <Tab | ||||
|               label="OpenConnect" | ||||
|               value="openconnect" | ||||
|               icon={<VpnLock />} | ||||
|               iconPosition="start" | ||||
|               sx={{ justifyContent: "start" }} | ||||
|             /> | ||||
|             <Tab | ||||
|               label="OpenSSL" | ||||
|               value="openssl" | ||||
|               icon={<Https />} | ||||
|               iconPosition="start" | ||||
|               sx={{ justifyContent: "start" }} | ||||
|             /> | ||||
|           </TabList> | ||||
|           <Box sx={{ flex: 1 }}> | ||||
|           <Box | ||||
|             sx={{ | ||||
|               flex: 1, | ||||
|               display: "flex", | ||||
|               flexDirection: "column", | ||||
|               "& .MuiTabPanel-root[hidden]": { display: "none" }, | ||||
|             }} | ||||
|           > | ||||
|             <Simulation /> | ||||
|             <OpenConnect /> | ||||
|             <OpenSSL /> | ||||
|           </Box> | ||||
|         </TabContext> | ||||
|   | ||||
| @@ -1,8 +1,11 @@ | ||||
| import { tempdir } from "@tauri-apps/api/os"; | ||||
| import { UserAttentionType, WebviewWindow } from "@tauri-apps/api/window"; | ||||
| import invokeCommand from "../utils/invokeCommand"; | ||||
| 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"; | ||||
|  | ||||
| async function openSettings(options?: { tab?: TabValue }) { | ||||
| @@ -17,7 +20,7 @@ async function openSettings(options?: { tab?: TabValue }) { | ||||
|   new WebviewWindow(SETTINGS_WINDOW_LABEL, { | ||||
|     url: `pages/settings/index.html?tab=${tab}`, | ||||
|     title: "GlobalProtect Settings", | ||||
|     width: 650, | ||||
|     width: 680, | ||||
|     height: 480, | ||||
|     center: true, | ||||
|     resizable: false, | ||||
| @@ -128,6 +131,23 @@ async function updateOpenSSLConfig() { | ||||
|   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 { | ||||
|   openSettings, | ||||
|   closeSettings, | ||||
| @@ -137,4 +157,6 @@ export default { | ||||
|   determineOsVersion, | ||||
|   getOpenSSLConfig, | ||||
|   updateOpenSSLConfig, | ||||
|   getOpenconnectConfig, | ||||
|   updateOpenconnectConfig, | ||||
| }; | ||||
|   | ||||
		Reference in New Issue
	
	Block a user