mirror of
https://github.com/yuezk/GlobalProtect-openconnect.git
synced 2025-04-02 18:31:50 -04:00
refactor: support edit the gp.conf
This commit is contained in:
parent
601f422863
commit
a10539e9c3
3
.vscode/settings.json
vendored
3
.vscode/settings.json
vendored
@ -14,14 +14,17 @@
|
||||
"humantime",
|
||||
"Immer",
|
||||
"jnlp",
|
||||
"notistack",
|
||||
"oneshot",
|
||||
"openconnect",
|
||||
"pkexec",
|
||||
"prelogin",
|
||||
"prelogon",
|
||||
"prelogonuserauthcookie",
|
||||
"repr",
|
||||
"rustc",
|
||||
"tauri",
|
||||
"tempdir",
|
||||
"thiserror",
|
||||
"unlisten",
|
||||
"userauthcookie",
|
||||
|
12
Cargo.lock
generated
12
Cargo.lock
generated
@ -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",
|
||||
|
@ -1,3 +1,7 @@
|
||||
<p align="center">
|
||||
<img src="https://github.com/yuezk/GlobalProtect-openconnect/assets/3297602/9242df9c-217d-42ab-8c21-8f9f69cd4eb5">
|
||||
</p>
|
||||
|
||||
## Development
|
||||
|
||||
### 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-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,
|
||||
};
|
||||
|
BIN
screenshot-light.png
Normal file
BIN
screenshot-light.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 66 KiB |
Loading…
Reference in New Issue
Block a user