diff --git a/index.html b/index.html
index d8acfda..1c00fad 100644
--- a/index.html
+++ b/index.html
@@ -3,7 +3,7 @@
Open WebUI
-
+
diff --git a/src/main.ts b/src/main.ts
index 6026c62..d85bf81 100644
--- a/src/main.ts
+++ b/src/main.ts
@@ -9,7 +9,8 @@ import {
BrowserWindow,
globalShortcut,
Notification,
- ipcMain
+ ipcMain,
+ ipcRenderer
} from 'electron';
import path from 'path';
import started from 'electron-squirrel-startup';
@@ -17,6 +18,7 @@ import started from 'electron-squirrel-startup';
import {
installPackage,
removePackage,
+ logEmitter,
startServer,
stopAllServers,
validateInstallation
@@ -56,6 +58,11 @@ if (!gotTheLock) {
let tray: Tray | null = null;
let SERVER_URL = null;
+ let SERVER_STATUS = 'stopped';
+
+ logEmitter.on('log', (message) => {
+ mainWindow?.webContents.send('main:log', message);
+ });
const loadDefaultView = () => {
// Load index.html or dev server URL
@@ -66,6 +73,34 @@ if (!gotTheLock) {
}
};
+ const startServerHandler = async () => {
+ SERVER_STATUS = 'starting';
+ mainWindow.webContents.send('main:data', {
+ type: 'server:status',
+ data: SERVER_STATUS
+ });
+
+ try {
+ SERVER_URL = await startServer();
+ SERVER_STATUS = 'started';
+ mainWindow.webContents.send('main:data', {
+ type: 'server:status',
+ data: SERVER_STATUS
+ });
+
+ mainWindow.loadURL(SERVER_URL);
+ } catch (error) {
+ console.error('Failed to start server:', error);
+ SERVER_STATUS = 'failed';
+ mainWindow.webContents.send('main:data', {
+ type: 'server:status',
+ data: SERVER_STATUS
+ });
+
+ mainWindow.webContents.send('main:log', `Failed to start server: ${error}`);
+ }
+ };
+
const onReady = async () => {
console.log(process.resourcesPath);
console.log(app.getName());
@@ -113,12 +148,7 @@ if (!gotTheLock) {
data: true
});
- try {
- SERVER_URL = await startServer();
- mainWindow.loadURL(SERVER_URL);
- } catch (error) {
- console.error('Failed to start server:', error);
- }
+ await startServerHandler();
} else {
mainWindow.webContents.send('main:data', {
type: 'install:status',
@@ -204,16 +234,25 @@ if (!gotTheLock) {
removePackage();
});
+ ipcMain.handle('server:status', async (event) => {
+ return SERVER_STATUS;
+ });
+
ipcMain.handle('server:start', async (event) => {
console.log('Starting server...');
- startServer();
+ await startServerHandler();
});
ipcMain.handle('server:stop', async (event) => {
console.log('Stopping server...');
- stopAllServers();
+ await stopAllServers();
+ SERVER_STATUS = 'stopped';
+ mainWindow.webContents.send('main:data', {
+ type: 'server:status',
+ data: SERVER_STATUS
+ });
});
ipcMain.handle('server:url', async (event) => {
diff --git a/src/preload.ts b/src/preload.ts
index 9e05e8c..cfde1b8 100644
--- a/src/preload.ts
+++ b/src/preload.ts
@@ -28,9 +28,12 @@ window.addEventListener('DOMContentLoaded', () => {
});
contextBridge.exposeInMainWorld('electronAPI', {
- sendPing: async () => {
- console.log('Sending PING to main process...');
- await ipcRenderer.invoke('send-ping'); // Send the ping back to the main process
+ onLog: (callback: (message: string) => void) => {
+ if (!isLocalSource()) {
+ throw new Error('Access restricted: This operation is only allowed in a local environment.');
+ }
+
+ ipcRenderer.on('main:log', (_, message: string) => callback(message));
},
installPackage: async () => {
@@ -53,6 +56,14 @@ contextBridge.exposeInMainWorld('electronAPI', {
await ipcRenderer.invoke('remove');
},
+ getServerStatus: async () => {
+ if (!isLocalSource()) {
+ throw new Error('Access restricted: This operation is only allowed in a local environment.');
+ }
+
+ return await ipcRenderer.invoke('server:status');
+ },
+
startServer: async () => {
if (!isLocalSource()) {
throw new Error('Access restricted: This operation is only allowed in a local environment.');
diff --git a/src/render/App.svelte b/src/render/App.svelte
index 83ff5af..b365b7b 100644
--- a/src/render/App.svelte
+++ b/src/render/App.svelte
@@ -1,6 +1,6 @@
diff --git a/src/render/lib/components/Main.svelte b/src/render/lib/components/Main.svelte
index 15a50c5..0bb3174 100644
--- a/src/render/lib/components/Main.svelte
+++ b/src/render/lib/components/Main.svelte
@@ -1,13 +1,16 @@
diff --git a/src/render/lib/stores.ts b/src/render/lib/stores.ts
index 0db9a8c..4df373c 100644
--- a/src/render/lib/stores.ts
+++ b/src/render/lib/stores.ts
@@ -1,3 +1,4 @@
import { writable } from 'svelte/store';
export const installStatus = writable(null);
+export const serverStatus = writable(null);
diff --git a/src/utils/index.ts b/src/utils/index.ts
index 12f20e5..7da07e5 100644
--- a/src/utils/index.ts
+++ b/src/utils/index.ts
@@ -1,6 +1,9 @@
import * as fs from 'fs';
import * as os from 'os';
import * as path from 'path';
+import net from 'net';
+import crypto from 'crypto';
+
import {
exec,
execFile,
@@ -10,13 +13,16 @@ import {
spawn,
ChildProcess
} from 'child_process';
-import net from 'net';
+import { EventEmitter } from 'events';
import * as tar from 'tar';
import log from 'electron-log';
import { app } from 'electron';
+// Create and export a global event emitter specifically for logs
+export const logEmitter = new EventEmitter();
+
////////////////////////////////////////////////
//
// General Utils
@@ -60,6 +66,18 @@ export function getOpenWebUIDataPath(): string {
return openWebUIDataDir;
}
+export function getSecretKey(keyPath?: string, key?: string): string {
+ keyPath = keyPath || path.join(getOpenWebUIDataPath(), '.key');
+
+ if (fs.existsSync(keyPath)) {
+ return fs.readFileSync(keyPath, 'utf-8');
+ }
+
+ key = key || crypto.randomBytes(64).toString('hex');
+ fs.writeFileSync(keyPath, key);
+ return key;
+}
+
export async function portInUse(port: number, host: string = '127.0.0.1'): Promise {
return new Promise((resolve) => {
const client = new net.Socket();
@@ -182,22 +200,21 @@ export async function installOpenWebUI(installationPath: string) {
// unpackCommand = `${createAdHocSignCommand(installationPath)}\n${unpackCommand}`;
// }
- console.log(unpackCommand);
-
const commandProcess = exec(unpackCommand, {
shell: process.platform === 'win32' ? 'cmd.exe' : '/bin/bash'
});
- commandProcess.stdout?.on('data', (data) => {
+ const onLog = (data) => {
console.log(data);
- });
+ logEmitter.emit('log', data);
+ };
- commandProcess.stderr?.on('data', (data) => {
- console.error(data);
- });
+ commandProcess.stdout?.on('data', onLog);
+ commandProcess.stderr?.on('data', onLog);
commandProcess.on('exit', (code) => {
console.log(`Child exited with code ${code}`);
+ logEmitter.emit('log', `Child exited with code ${code}`);
});
}
@@ -209,6 +226,7 @@ export async function installBundledPython(installationPath?: string) {
console.log(installationPath, pythonTarPath);
if (!fs.existsSync(pythonTarPath)) {
log.error('Python tarball not found');
+ logEmitter.emit('log', 'Python tarball not found'); // Emit log
return;
}
@@ -220,6 +238,7 @@ export async function installBundledPython(installationPath?: string) {
});
} catch (error) {
log.error(error);
+ logEmitter.emit('log', error); // Emit log
}
// Get the path to the installed Python binary
@@ -227,6 +246,7 @@ export async function installBundledPython(installationPath?: string) {
if (!fs.existsSync(bundledPythonPath)) {
log.error('Python binary not found in install path');
+ logEmitter.emit('log', 'Python binary not found in install path'); // Emit log
return;
}
@@ -236,6 +256,7 @@ export async function installBundledPython(installationPath?: string) {
encoding: 'utf-8'
});
console.log('Installed Python Version:', pythonVersion.trim());
+ logEmitter.emit('log', `Installed Python Version: ${pythonVersion.trim()}`); // Emit log
} catch (error) {
log.error('Failed to execute Python binary', error);
}
@@ -319,6 +340,7 @@ export async function startServer(installationPath?: string, port?: number): Pro
if (!(await validateInstallation(installationPath))) {
console.error('Failed to validate installation');
+ logEmitter.emit('log', 'Failed to validate installation'); // Emit log
return;
}
@@ -327,11 +349,11 @@ export async function startServer(installationPath?: string, port?: number): Pro
? `${installationPath}\\Scripts\\activate.bat && set DATA_DIR="${path.join(
app.getPath('userData'),
'data'
- )}" && open-webui serve`
+ )}" && set WEBUI_SECRET_KEY=${getSecretKey()} && open-webui serve`
: `source "${installationPath}/bin/activate" && export DATA_DIR="${path.join(
app.getPath('userData'),
'data'
- )}" && open-webui serve`;
+ )}" && export WEBUI_SECRET_KEY=${getSecretKey()} && open-webui serve`;
port = port || 8080;
while (await portInUse(port)) {
@@ -341,6 +363,8 @@ export async function startServer(installationPath?: string, port?: number): Pro
startCommand += ` --port ${port}`;
console.log('Starting Open-WebUI server...');
+ logEmitter.emit('log', 'Starting Open-WebUI server...'); // Emit log
+
const childProcess = spawn(startCommand, {
shell: true,
detached: true,
@@ -356,6 +380,7 @@ export async function startServer(installationPath?: string, port?: number): Pro
const handleLog = (data: Buffer) => {
const logLine = data.toString().trim();
console.log(`[Open-WebUI Log]: ${logLine}`);
+ logEmitter.emit('log', logLine);
// Look for "Uvicorn running on http://:"
const match = logLine.match(
@@ -386,6 +411,7 @@ export async function startServer(installationPath?: string, port?: number): Pro
if (childProcess.pid) {
serverPIDs.add(childProcess.pid);
console.log(`Server started with PID: ${childProcess.pid}`);
+ logEmitter.emit('log', `Server started with PID: ${childProcess.pid}`); // Emit PID log
} else {
throw new Error('Failed to start server: No PID available');
}
@@ -405,6 +431,7 @@ export async function startServer(installationPath?: string, port?: number): Pro
}
console.log(`Server is now running at ${detectedURL}`);
+ logEmitter.emit('log', `Server is now running at ${detectedURL}`); // Emit server URL log
return detectedURL; // Return the detected URL
}