From 104a73b48265461f9f98920261cdd2b56914253d Mon Sep 17 00:00:00 2001
From: xxnuo <54252779+xxnuo@users.noreply.github.com>
Date: Sat, 10 Jan 2026 00:26:59 +0800
Subject: [PATCH] feat(desktop): simplify
---
desktop/app.js | 121 +++++++---------------
desktop/tray.js | 21 ++--
src/config/index.ts | 78 +++++++++++---
src/server/index.ts | 115 +++++++++++++++++++-
ui/src/components/DesktopSettingsPage.tsx | 73 ++++++-------
ui/src/lib/desktop.ts | 28 +++++
6 files changed, 281 insertions(+), 155 deletions(-)
diff --git a/desktop/app.js b/desktop/app.js
index 2eac8d4..9fd7da3 100644
--- a/desktop/app.js
+++ b/desktop/app.js
@@ -1,4 +1,4 @@
-import { app, dialog, nativeImage, shell, Menu } from 'electron';
+import { app, dialog, nativeImage, shell, Menu, screen } from 'electron';
import fs from 'fs/promises';
import fsSync from 'fs';
import path from 'path';
@@ -7,13 +7,6 @@ import { loadDesktopConfig, saveDesktopConfig, resetDesktopConfig, getDefaultDes
import { resolveLocale, getMessages } from './i18n.js';
import { getFreePort, isPortAvailable } from './ports.js';
import { getServerStatus, startServerWithConfig, stopServerInstance } from './server.js';
-import {
- createMainWindow,
- createSettingsWindow,
- showMainWindow,
- showSettingsWindow,
- updateWindowUrls
-} from './windows.js';
import { createTray, updateTrayMenu } from './tray.js';
import { registerIpcHandlers } from './ipc.js';
@@ -83,11 +76,10 @@ function updateTray() {
messages,
statusLabel: getStatusLabel(),
versionLabel: app.getVersion(),
- onOpenMain: showMainWindow,
onOpenBrowserUi: () => shell.openExternal(getUiUrl(desktopConfig.server)),
onOpenBrowserDocs: () => shell.openExternal(getDocsUrl(desktopConfig.server)),
onOpenRepo: () => shell.openExternal(repoUrl),
- onOpenSettings: showSettingsWindow,
+ onOpenSettings: () => shell.openExternal(getSettingsUrl(desktopConfig.server)),
onRestart: restartServer,
onOpenModels: () => shell.openPath(desktopConfig.server.modelDir),
onOpenConfig: () => shell.openPath(desktopConfig.server.configDir),
@@ -167,10 +159,6 @@ async function restartServer() {
await ensureWritableDirs();
const ok = await startServer();
if (!ok) return false;
- updateWindowUrls({
- mainUrl: getUiUrl(desktopConfig.server),
- settingsUrl: getSettingsUrl(desktopConfig.server)
- });
updateTray();
return true;
} catch {
@@ -232,40 +220,16 @@ async function ensureWritableDirs() {
}
}
-function getLoadingUrl() {
- const html = `
-
-
-
-
-
- ${messages.appName}
-
-
-
-
-
-
${messages.appName}
-
${messages.trayServiceStatus}: ${messages.trayServiceRunning} ...
-
-
-
-
- `;
- return `data:text/html;charset=utf-8,${encodeURIComponent(html)}`;
+function buildDesktopResponse() {
+ return { config: desktopConfig, status: getServerStatus(), version: app.getVersion() };
}
export async function startDesktop() {
app.isQuitting = false;
Menu.setApplicationMenu(null);
+ if (app.dock?.hide) {
+ app.dock.hide();
+ }
desktopConfig = await loadDesktopConfig();
updateLocale(desktopConfig.locale);
desktopLogPath = path.join(desktopConfig.server.configDir, 'desktop.log');
@@ -273,12 +237,6 @@ export async function startDesktop() {
await logDesktop('desktop starting');
await ensureWritableDirs();
- const preloadPath = path.join(__dirname, 'preload.cjs');
- const loadingUrl = getLoadingUrl();
- const mainWindow = createMainWindow({ url: loadingUrl, preload: preloadPath });
- createSettingsWindow({ url: loadingUrl, preload: preloadPath, parent: mainWindow });
- mainWindow.once('ready-to-show', () => mainWindow.show());
-
const trayIcon = getTrayIcon();
tray = createTray({
icon: trayIcon,
@@ -286,11 +244,10 @@ export async function startDesktop() {
messages,
statusLabel: getStatusLabel(),
versionLabel: app.getVersion(),
- onOpenMain: showMainWindow,
onOpenBrowserUi: () => shell.openExternal(getUiUrl(desktopConfig.server)),
onOpenBrowserDocs: () => shell.openExternal(getDocsUrl(desktopConfig.server)),
onOpenRepo: () => shell.openExternal(repoUrl),
- onOpenSettings: showSettingsWindow,
+ onOpenSettings: () => shell.openExternal(getSettingsUrl(desktopConfig.server)),
onRestart: restartServer,
onOpenModels: () => shell.openPath(desktopConfig.server.modelDir),
onOpenConfig: () => shell.openPath(desktopConfig.server.configDir),
@@ -303,52 +260,52 @@ export async function startDesktop() {
return;
}
- updateWindowUrls({
- mainUrl: getUiUrl(desktopConfig.server),
- settingsUrl: getSettingsUrl(desktopConfig.server)
- });
+ const getConfigResponse = async () => buildDesktopResponse();
+ const applyConfig = async (config) => {
+ desktopConfig = await saveDesktopConfig(config);
+ updateLocale(desktopConfig.locale);
+ const ok = await restartServer();
+ if (!ok) return buildDesktopResponse();
+ return buildDesktopResponse();
+ };
+ const resetConfig = async () => {
+ desktopConfig = await resetDesktopConfig();
+ updateLocale(desktopConfig.locale);
+ const ok = await restartServer();
+ if (!ok) return buildDesktopResponse();
+ return buildDesktopResponse();
+ };
+ const restartAndRespond = async () => {
+ const ok = await restartServer();
+ if (!ok) return buildDesktopResponse();
+ return buildDesktopResponse();
+ };
+
+ globalThis.mtranDesktopControl = {
+ getConfig: getConfigResponse,
+ applyConfig,
+ resetConfig,
+ restartServer: restartAndRespond
+ };
registerIpcHandlers({
- getConfig: async () => ({
- config: desktopConfig,
- status: getServerStatus(),
- version: app.getVersion()
- }),
- applyConfig: async (config) => {
- desktopConfig = await saveDesktopConfig(config);
- updateLocale(desktopConfig.locale);
- const ok = await restartServer();
- if (!ok) return { config: desktopConfig, status: getServerStatus(), version: app.getVersion() };
- return { config: desktopConfig, status: getServerStatus(), version: app.getVersion() };
- },
- resetConfig: async () => {
- desktopConfig = await resetDesktopConfig();
- updateLocale(desktopConfig.locale);
- const ok = await restartServer();
- if (!ok) return { config: desktopConfig, status: getServerStatus(), version: app.getVersion() };
- return { config: desktopConfig, status: getServerStatus(), version: app.getVersion() };
- },
- restartServer: async () => {
- const ok = await restartServer();
- if (!ok) return { config: desktopConfig, status: getServerStatus(), version: app.getVersion() };
- return { config: desktopConfig, status: getServerStatus(), version: app.getVersion() };
- },
+ getConfig: getConfigResponse,
+ applyConfig,
+ resetConfig,
+ restartServer: restartAndRespond,
getStatus: async () => ({ status: getServerStatus() }),
openExternal: async (url) => shell.openExternal(url),
openPath: async (targetPath) => shell.openPath(targetPath)
});
- app.on('activate', () => showMainWindow());
app.on('before-quit', (event) => {
if (app.isQuitting) return;
event.preventDefault();
quitApp();
});
- app.on('window-all-closed', (event) => event.preventDefault());
updateTray();
}
export function focusMainWindow() {
- showMainWindow();
}
diff --git a/desktop/tray.js b/desktop/tray.js
index d9fea4c..ebe4d68 100644
--- a/desktop/tray.js
+++ b/desktop/tray.js
@@ -8,7 +8,6 @@ export function createTray({
messages,
statusLabel,
versionLabel,
- onOpenMain,
onOpenBrowserUi,
onOpenBrowserDocs,
onOpenRepo,
@@ -21,12 +20,11 @@ export function createTray({
if (tray) return tray;
tray = new Tray(icon);
tray.setToolTip(tooltip);
- tray.on('click', () => onOpenMain());
+ tray.on('click', () => tray.popUpContextMenu());
updateTrayMenu({
messages,
statusLabel,
versionLabel,
- onOpenMain,
onOpenBrowserUi,
onOpenBrowserDocs,
onOpenRepo,
@@ -43,7 +41,6 @@ export function updateTrayMenu({
messages,
statusLabel,
versionLabel,
- onOpenMain,
onOpenBrowserUi,
onOpenBrowserDocs,
onOpenRepo,
@@ -56,16 +53,16 @@ export function updateTrayMenu({
if (!tray) return;
const contextMenu = Menu.buildFromTemplate([
{
- label: messages.trayOpenMain,
- click: onOpenMain
+ label: messages.trayOpenUi,
+ click: onOpenBrowserUi
},
{
- label: messages.trayOpenBrowser,
- submenu: [
- { label: messages.trayOpenUi, click: onOpenBrowserUi },
- { label: messages.trayOpenDocs, click: onOpenBrowserDocs },
- { label: messages.trayOpenRepo, click: onOpenRepo }
- ]
+ label: messages.trayOpenDocs,
+ click: onOpenBrowserDocs
+ },
+ {
+ label: messages.trayOpenRepo,
+ click: onOpenRepo
},
{ type: 'separator' },
{
diff --git a/src/config/index.ts b/src/config/index.ts
index 203be7a..db16c7b 100644
--- a/src/config/index.ts
+++ b/src/config/index.ts
@@ -24,6 +24,27 @@ export interface Config {
}
let globalConfig: Config | null = null;
+let fileConfigCache: Partial | null = null;
+
+function getConfigFilePath(homeDir: string) {
+ return path.join(homeDir, 'server.json');
+}
+
+function readConfigFile(homeDir: string): Partial {
+ if (fileConfigCache) return fileConfigCache;
+ const configPath = getConfigFilePath(homeDir);
+ try {
+ const raw = fs.readFileSync(configPath, 'utf8');
+ const parsed = JSON.parse(raw);
+ if (parsed && typeof parsed === 'object') {
+ fileConfigCache = parsed as Partial;
+ return fileConfigCache;
+ }
+ } catch {
+ return {};
+ }
+ return {};
+}
function getArgValue(flag: string): string | null {
const index = process.argv.indexOf(flag);
@@ -88,12 +109,13 @@ export function getConfig(): Config {
}
const homeDir = path.join(os.homedir(), '.config', 'mtran');
+ const fileConfig = readConfigFile(homeDir);
- const configDir = getString('--config-dir', 'MT_CONFIG_DIR', path.join(homeDir, 'server'));
+ const configDir = getString('--config-dir', 'MT_CONFIG_DIR', fileConfig.configDir || path.join(homeDir, 'server'));
const localModelsDir = path.join(process.cwd(), 'models');
const defaultModelDir = fs.existsSync(localModelsDir) ? localModelsDir : path.join(homeDir, 'models');
- const modelDir = getString('--model-dir', 'MT_MODEL_DIR', defaultModelDir);
- const logDir = getString('--log-dir', 'MT_LOG_DIR', path.join(homeDir, 'logs'));
+ const modelDir = getString('--model-dir', 'MT_MODEL_DIR', fileConfig.modelDir || defaultModelDir);
+ const logDir = getString('--log-dir', 'MT_LOG_DIR', fileConfig.logDir || path.join(homeDir, 'logs'));
globalConfig = {
homeDir,
@@ -101,26 +123,26 @@ export function getConfig(): Config {
modelDir,
logDir,
- logLevel: getString('--log-level', 'MT_LOG_LEVEL', 'warn'),
- host: getString('--host', 'MT_HOST', '0.0.0.0'),
- port: getString('--port', 'MT_PORT', '8989'),
+ logLevel: getString('--log-level', 'MT_LOG_LEVEL', fileConfig.logLevel || 'warn'),
+ host: getString('--host', 'MT_HOST', fileConfig.host || '0.0.0.0'),
+ port: getString('--port', 'MT_PORT', fileConfig.port || '8989'),
- enableWebUI: getBool('--ui', 'MT_ENABLE_UI', true),
- enableOfflineMode: getBool('--offline', 'MT_OFFLINE', false),
+ enableWebUI: getBool('--ui', 'MT_ENABLE_UI', fileConfig.enableWebUI ?? true),
+ enableOfflineMode: getBool('--offline', 'MT_OFFLINE', fileConfig.enableOfflineMode ?? false),
- workerIdleTimeout: getInt('--worker-idle-timeout', 'MT_WORKER_IDLE_TIMEOUT', 60),
- workersPerLanguage: getInt('--workers-per-language', 'MT_WORKERS_PER_LANGUAGE', 1),
- maxLengthBreak: getInt('--max-length-break', 'MT_MAX_LENGTH_BREAK', 128),
+ workerIdleTimeout: getInt('--worker-idle-timeout', 'MT_WORKER_IDLE_TIMEOUT', fileConfig.workerIdleTimeout ?? 60),
+ workersPerLanguage: getInt('--workers-per-language', 'MT_WORKERS_PER_LANGUAGE', fileConfig.workersPerLanguage ?? 1),
+ maxLengthBreak: getInt('--max-length-break', 'MT_MAX_LENGTH_BREAK', fileConfig.maxLengthBreak ?? 128),
- apiToken: getString('--api-token', 'MT_API_TOKEN', ''),
+ apiToken: getString('--api-token', 'MT_API_TOKEN', fileConfig.apiToken || ''),
- logToFile: getBool('--log-to-file', 'MT_LOG_TO_FILE', false),
- logConsole: getBool('--log-console', 'MT_LOG_CONSOLE', true),
- logRequests: getBool('--log-requests', 'MT_LOG_REQUESTS', false),
+ logToFile: getBool('--log-to-file', 'MT_LOG_TO_FILE', fileConfig.logToFile ?? false),
+ logConsole: getBool('--log-console', 'MT_LOG_CONSOLE', fileConfig.logConsole ?? true),
+ logRequests: getBool('--log-requests', 'MT_LOG_REQUESTS', fileConfig.logRequests ?? false),
- checkUpdate: getBool('--check-update', 'MT_CHECK_UPDATE', true),
+ checkUpdate: getBool('--check-update', 'MT_CHECK_UPDATE', fileConfig.checkUpdate ?? true),
- cacheSize: getInt('--cache-size', 'MT_CACHE_SIZE', 1000),
+ cacheSize: getInt('--cache-size', 'MT_CACHE_SIZE', fileConfig.cacheSize ?? 1000),
};
return globalConfig;
@@ -133,4 +155,26 @@ export function setConfig(config: Partial) {
export function resetConfig() {
globalConfig = null;
+ fileConfigCache = null;
+}
+
+export function saveConfigFile(config: Partial) {
+ const current = getConfig();
+ const next = { ...current, ...config };
+ const configPath = getConfigFilePath(current.homeDir);
+ fs.mkdirSync(current.homeDir, { recursive: true });
+ fs.writeFileSync(configPath, JSON.stringify(next, null, 2), 'utf8');
+ fileConfigCache = next;
+}
+
+export function clearConfigFile() {
+ const homeDir = path.join(os.homedir(), '.config', 'mtran');
+ const configPath = getConfigFilePath(homeDir);
+ try {
+ if (fs.existsSync(configPath)) {
+ fs.unlinkSync(configPath);
+ }
+ } catch {
+ return;
+ }
}
diff --git a/src/server/index.ts b/src/server/index.ts
index 5f83be8..348b135 100644
--- a/src/server/index.ts
+++ b/src/server/index.ts
@@ -1,7 +1,7 @@
import express, { Request, Response, NextFunction } from 'express';
import fs from 'fs/promises';
import swaggerUi from 'swagger-ui-express';
-import { getConfig } from '@/config/index.js';
+import { getConfig, setConfig, resetConfig, saveConfigFile, clearConfigFile } from '@/config/index.js';
import * as logger from '@/logger/index.js';
import { initRecords } from '@/models/index.js';
import { cleanupAllEngines } from '@/services/index.js';
@@ -36,6 +36,119 @@ export async function startServer({ handleSignals = true } = {}) {
app.use(requestLogger());
}
+ const getDesktopControl = () => (globalThis as any).mtranDesktopControl;
+ const getSettingsPayload = () => {
+ const current = getConfig();
+ return {
+ config: {
+ locale: 'system',
+ server: {
+ host: current.host,
+ port: Number(current.port),
+ logLevel: current.logLevel,
+ enableWebUI: current.enableWebUI,
+ enableOfflineMode: current.enableOfflineMode,
+ workerIdleTimeout: current.workerIdleTimeout,
+ workersPerLanguage: current.workersPerLanguage,
+ apiToken: current.apiToken,
+ logDir: current.logDir,
+ logToFile: current.logToFile,
+ logConsole: current.logConsole,
+ logRequests: current.logRequests,
+ maxLengthBreak: current.maxLengthBreak,
+ checkUpdate: current.checkUpdate,
+ cacheSize: current.cacheSize,
+ modelDir: current.modelDir,
+ configDir: current.configDir
+ }
+ },
+ status: 'running',
+ version: VERSION
+ };
+ };
+ const toNumber = (value: unknown, fallback: number) => {
+ if (typeof value === 'number' && Number.isFinite(value)) return value;
+ const parsed = Number(value);
+ return Number.isFinite(parsed) ? parsed : fallback;
+ };
+ const toBool = (value: unknown, fallback: boolean) => {
+ if (typeof value === 'boolean') return value;
+ return fallback;
+ };
+ const toString = (value: unknown, fallback: string) => {
+ if (typeof value === 'string') return value;
+ return fallback;
+ };
+ const applyServerConfig = (input: any) => {
+ const current = getConfig();
+ const next = {
+ host: toString(input.host, current.host),
+ port: String(toNumber(input.port, Number(current.port))),
+ logLevel: toString(input.logLevel, current.logLevel),
+ enableWebUI: toBool(input.enableWebUI, current.enableWebUI),
+ enableOfflineMode: toBool(input.enableOfflineMode, current.enableOfflineMode),
+ workerIdleTimeout: toNumber(input.workerIdleTimeout, current.workerIdleTimeout),
+ workersPerLanguage: toNumber(input.workersPerLanguage, current.workersPerLanguage),
+ apiToken: toString(input.apiToken, current.apiToken),
+ logDir: toString(input.logDir, current.logDir),
+ logToFile: toBool(input.logToFile, current.logToFile),
+ logConsole: toBool(input.logConsole, current.logConsole),
+ logRequests: toBool(input.logRequests, current.logRequests),
+ maxLengthBreak: toNumber(input.maxLengthBreak, current.maxLengthBreak),
+ checkUpdate: toBool(input.checkUpdate, current.checkUpdate),
+ cacheSize: toNumber(input.cacheSize, current.cacheSize),
+ modelDir: toString(input.modelDir, current.modelDir),
+ configDir: toString(input.configDir, current.configDir)
+ };
+ setConfig(next);
+ saveConfigFile(next);
+ logger.setLogLevel(next.logLevel as any);
+ };
+
+ app.get('/ui/api/settings', async (_, res) => {
+ const control = getDesktopControl();
+ if (control?.getConfig) {
+ const payload = await control.getConfig();
+ res.json(payload);
+ return;
+ }
+ res.json(getSettingsPayload());
+ });
+
+ app.post('/ui/api/settings/apply', async (req, res) => {
+ const control = getDesktopControl();
+ if (control?.applyConfig) {
+ const payload = await control.applyConfig(req.body?.config || req.body);
+ res.json(payload);
+ return;
+ }
+ const input = req.body?.config?.server || req.body?.server || {};
+ applyServerConfig(input);
+ res.json(getSettingsPayload());
+ });
+
+ app.post('/ui/api/settings/reset', async (_, res) => {
+ const control = getDesktopControl();
+ if (control?.resetConfig) {
+ const payload = await control.resetConfig();
+ res.json(payload);
+ return;
+ }
+ clearConfigFile();
+ resetConfig();
+ res.json(getSettingsPayload());
+ });
+
+ app.post('/ui/api/settings/restart', async (_, res) => {
+ const control = getDesktopControl();
+ if (control?.restartServer) {
+ const payload = await control.restartServer();
+ res.json(payload);
+ return;
+ }
+ res.json(getSettingsPayload());
+ });
+
RegisterRoutes(app);
app.use('/ui', (req: Request, res: Response, next: NextFunction) => {
diff --git a/ui/src/components/DesktopSettingsPage.tsx b/ui/src/components/DesktopSettingsPage.tsx
index 5ed8f25..bf39c3b 100644
--- a/ui/src/components/DesktopSettingsPage.tsx
+++ b/ui/src/components/DesktopSettingsPage.tsx
@@ -6,14 +6,17 @@ import { Switch } from '@/components/ui/switch'
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'
import { Separator } from '@/components/ui/separator'
-import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert'
import { toast } from 'sonner'
import {
applyDesktopConfig,
getDesktopConfig,
isDesktop,
resetDesktopConfig,
- restartDesktopServer
+ restartDesktopServer,
+ applyWebConfig,
+ getWebConfig,
+ resetWebConfig,
+ restartWebServer
} from '@/lib/desktop'
import type { DesktopConfig } from '@/lib/desktop'
@@ -73,6 +76,10 @@ function toNumber(value: string, fallback: number) {
export function DesktopSettingsPage() {
const { t } = useTranslation()
const desktopAvailable = isDesktop()
+ const getConfigData = desktopAvailable ? getDesktopConfig : getWebConfig
+ const applyConfig = desktopAvailable ? applyDesktopConfig : applyWebConfig
+ const resetConfig = desktopAvailable ? resetDesktopConfig : resetWebConfig
+ const restartServer = desktopAvailable ? restartDesktopServer : restartWebServer
const [config, setConfig] = useState(null)
const [form, setForm] = useState(null)
const [status, setStatus] = useState('')
@@ -81,11 +88,7 @@ export function DesktopSettingsPage() {
const [saving, setSaving] = useState(false)
useEffect(() => {
- if (!desktopAvailable) {
- setLoading(false)
- return
- }
- getDesktopConfig()
+ getConfigData()
.then((response) => {
if (!response) return
setConfig(response.config)
@@ -97,7 +100,7 @@ export function DesktopSettingsPage() {
toast.error(t('loadingConfigFailed'))
})
.finally(() => setLoading(false))
- }, [desktopAvailable, t])
+ }, [getConfigData, t])
const serverStatusLabel = useMemo(() => {
if (!status) return ''
@@ -131,7 +134,7 @@ export function DesktopSettingsPage() {
}
}
try {
- const response = await applyDesktopConfig(nextConfig)
+ const response = await applyConfig(nextConfig)
if (response) {
setConfig(response.config)
setForm(toForm(response.config))
@@ -147,10 +150,9 @@ export function DesktopSettingsPage() {
}
const handleRestart = async () => {
- if (!desktopAvailable) return
setSaving(true)
try {
- const response = await restartDesktopServer()
+ const response = await restartServer()
if (response) {
setConfig(response.config)
setForm(toForm(response.config))
@@ -166,10 +168,9 @@ export function DesktopSettingsPage() {
}
const handleReset = async () => {
- if (!desktopAvailable) return
setSaving(true)
try {
- const response = await resetDesktopConfig()
+ const response = await resetConfig()
if (response) {
setConfig(response.config)
setForm(toForm(response.config))
@@ -184,22 +185,6 @@ export function DesktopSettingsPage() {
}
}
- if (!desktopAvailable) {
- return (
-
-
-
- {t('desktopOnly')}
- {t('desktopOnlyDesc')}
-
-
-
-
-
-
- )
- }
-
if (loading || !form || !config) {
return (
@@ -250,20 +235,22 @@ export function DesktopSettingsPage() {
-
-
-
-
+ {desktopAvailable && (
+
+
+
+
+ )}
diff --git a/ui/src/lib/desktop.ts b/ui/src/lib/desktop.ts
index 42f3b8f..a02fd59 100644
--- a/ui/src/lib/desktop.ts
+++ b/ui/src/lib/desktop.ts
@@ -50,3 +50,31 @@ export async function restartDesktopServer() {
if (!window.mtranDesktop) return null
return window.mtranDesktop.restartServer()
}
+
+async function fetchSettings(path: string, body?: unknown) {
+ const res = await fetch(`/ui/api/settings${path}`, {
+ method: body ? 'POST' : 'GET',
+ headers: body ? { 'Content-Type': 'application/json' } : undefined,
+ body: body ? JSON.stringify(body) : undefined
+ })
+ if (!res.ok) {
+ throw new Error(`Request failed: ${res.status}`)
+ }
+ return res.json()
+}
+
+export async function getWebConfig() {
+ return fetchSettings('')
+}
+
+export async function applyWebConfig(config: DesktopConfig) {
+ return fetchSettings('/apply', { config })
+}
+
+export async function resetWebConfig() {
+ return fetchSettings('/reset', {})
+}
+
+export async function restartWebServer() {
+ return fetchSettings('/restart', {})
+}