diff --git a/bun.lockb b/bun.lockb index 22d04f0fe..3d12e8bfb 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/electron/main.ts b/electron/main.ts index b9dd15441..bf0685c89 100644 --- a/electron/main.ts +++ b/electron/main.ts @@ -1,9 +1,16 @@ import { app, BrowserWindow, protocol, screen } from 'electron' +import logger from 'electron-log' import { join } from 'path' +import { setupAutoUpdater } from './services/auto-update' import store from './services/config-store' import { setupNetworkService } from './services/network' +// If the app is packaged, push logs to the system instead of the console +if (app.isPackaged) { + Object.assign(console, logger.functions) +} + export const ROOT_PATH = { dist: join(__dirname, '..'), } @@ -33,11 +40,6 @@ function createWindow(): void { store.set('windowBounds', { x, y, width, height }) }) - // Test active push message to Renderer-process. - mainWindow.webContents.on('did-finish-load', () => { - mainWindow?.webContents.send('main-process-message', new Date().toLocaleString()) - }) - if (process.env.VITE_DEV_SERVER_URL) { mainWindow.loadURL(process.env.VITE_DEV_SERVER_URL) } else { @@ -71,7 +73,17 @@ protocol.registerSchemesAsPrivileged([ setupNetworkService() -app.whenReady().then(createWindow) +app.whenReady().then(async () => { + console.log('Electron app is ready.') + console.log(`Cockpit version: ${app.getVersion()}`) + + console.log('Creating window...') + createWindow() + + setTimeout(() => { + setupAutoUpdater(mainWindow as BrowserWindow) + }, 5000) +}) app.on('before-quit', () => { // @ts-ignore: import.meta.env does not exist in the types diff --git a/electron/preload.ts b/electron/preload.ts index 4312004c1..08977a09b 100644 --- a/electron/preload.ts +++ b/electron/preload.ts @@ -2,4 +2,16 @@ import { contextBridge, ipcRenderer } from 'electron' contextBridge.exposeInMainWorld('electronAPI', { getInfoOnSubnets: () => ipcRenderer.invoke('get-info-on-subnets'), + onUpdateAvailable: (callback: (info: any) => void) => + ipcRenderer.on('update-available', (_event, info) => callback(info)), + onUpdateDownloaded: (callback: (info: any) => void) => + ipcRenderer.on('update-downloaded', (_event, info) => callback(info)), + onCheckingForUpdate: (callback: () => void) => ipcRenderer.on('checking-for-update', () => callback()), + onUpdateNotAvailable: (callback: (info: any) => void) => + ipcRenderer.on('update-not-available', (_event, info) => callback(info)), + onDownloadProgress: (callback: (info: any) => void) => + ipcRenderer.on('download-progress', (_event, info) => callback(info)), + downloadUpdate: () => ipcRenderer.send('download-update'), + installUpdate: () => ipcRenderer.send('install-update'), + cancelUpdate: () => ipcRenderer.send('cancel-update'), }) diff --git a/electron/services/auto-update.ts b/electron/services/auto-update.ts new file mode 100644 index 000000000..077d4bf64 --- /dev/null +++ b/electron/services/auto-update.ts @@ -0,0 +1,51 @@ +import { BrowserWindow, ipcMain } from 'electron' +import electronUpdater, { type AppUpdater } from 'electron-updater' + +/** + * Setup auto updater + * @param {BrowserWindow} mainWindow - The main Electron window + */ +export const setupAutoUpdater = (mainWindow: BrowserWindow): void => { + const autoUpdater: AppUpdater = electronUpdater.autoUpdater + autoUpdater.logger = console + autoUpdater.autoDownload = false // Prevent automatic downloads + + autoUpdater + .checkForUpdates() + .then((e) => console.info(e)) + .catch((e) => console.error(e)) + + autoUpdater.on('checking-for-update', () => { + mainWindow.webContents.send('checking-for-update') + }) + + autoUpdater.on('update-available', (info) => { + mainWindow.webContents.send('update-available', info) + }) + + autoUpdater.on('update-not-available', (info) => { + mainWindow.webContents.send('update-not-available', info) + }) + + autoUpdater.on('download-progress', (progressInfo) => { + mainWindow.webContents.send('download-progress', progressInfo) + }) + + autoUpdater.on('update-downloaded', (info) => { + mainWindow.webContents.send('update-downloaded', info) + }) + + // Add handlers for update control + ipcMain.on('download-update', () => { + autoUpdater.downloadUpdate() + }) + + ipcMain.on('install-update', () => { + autoUpdater.quitAndInstall() + }) + + ipcMain.on('cancel-update', () => { + autoUpdater.removeAllListeners('update-downloaded') + autoUpdater.removeAllListeners('download-progress') + }) +} diff --git a/package.json b/package.json index 26c50b13a..f8674b64a 100644 --- a/package.json +++ b/package.json @@ -39,7 +39,9 @@ "browser-or-node": "^2.0.0", "colord": "^2.9.3", "date-fns": "^2.29.3", + "electron-log": "^5.2.0", "electron-store": "^10.0.0", + "electron-updater": "^6.3.9", "file-saver": "^2.0.5", "floating-vue": "^5.0.3", "flowbite": "^2.2.1", diff --git a/src/App.vue b/src/App.vue index 94af043d2..f50106e6c 100644 --- a/src/App.vue +++ b/src/App.vue @@ -316,6 +316,7 @@ + diff --git a/src/libs/cosmos.ts b/src/libs/cosmos.ts index 1d875983f..810bef604 100644 --- a/src/libs/cosmos.ts +++ b/src/libs/cosmos.ts @@ -82,26 +82,104 @@ declare global { sum(): number } - /* eslint-disable jsdoc/require-jsdoc */ + /** + * Extended Window interface with custom dedicated dedicated APIs. + */ interface Window { + /** + * Exposed Cockpit APIs + * E.g. data-lake, cockpit actions, etc. + */ cockpit: { // Data lake: + + /** + * The object that holds the data-lake variables data + */ dataLakeVariableData: typeof dataLakeVariableData + /** + * Get data from an specific data lake variable + * @param id - The id of the data to retrieve + * @returns The data or undefined if not available + */ getDataLakeVariableData: typeof getDataLakeVariableData + /** + * Listen to data changes on a specific data lake variable + * @param id - The id of the data to listen to + * @param listener - The listener callback + */ listenDataLakeVariable: typeof listenDataLakeVariable + /** + * Stop listening to data changes on a specific data lake variable + * @param id - The id of the data to stop listening to + */ unlistenDataLakeVariable: typeof unlistenDataLakeVariable + /** + * Get info about all variables in the data lake + * @returns Data lake data + */ getAllDataLakeVariablesInfo: typeof getAllDataLakeVariablesInfo + /** + * Get info about a specific variable in the data lake + * @param id - The id of the data to retrieve + * @returns The data info or undefined if not available + */ getDataLakeVariableInfo: typeof getDataLakeVariableInfo + /** + * Set the value of an specific data lake variable + * @param id - The id of the data to set + * @param value - The value to set + */ setDataLakeVariableData: typeof setDataLakeVariableData + /** + * Create a new variable in the data lake + * @param variable - The variable to create + * @param initialValue - The initial value for the variable + */ createDataLakeVariable: typeof createDataLakeVariable + /** + * Update information about an specific data lake variable + * @param variable - The variable to update + */ updateDataLakeVariableInfo: typeof updateDataLakeVariableInfo + /** + * Delete a variable from the data lake + * @param id - The id of the variable to delete + */ deleteDataLakeVariable: typeof deleteDataLakeVariable + // Cockpit actions: + + /** + * Get all available cockpit actions + * @returns Available cockpit actions + */ availableCockpitActions: typeof availableCockpitActions + /** + * Register a new cockpit action + * @param action - The action to register + */ registerNewAction: typeof registerNewAction + /** + * Delete a cockpit action + * @param id - The id of the action to delete + */ deleteAction: typeof deleteAction + /** + * Register a callback for a cockpit action + * @param action - The action to register the callback for + * @param callback - The callback to register + */ registerActionCallback: typeof registerActionCallback + /** + * Unregister a callback for a cockpit action + * @param id - The id of the action to unregister the callback for + */ unregisterActionCallback: typeof unregisterActionCallback + /** + * Execute the callback for a cockpit action + * @param id - The id of the action to execute the callback for + */ executeActionCallback: typeof executeActionCallback } /** @@ -113,9 +191,40 @@ declare global { * @returns Promise containing subnet information */ getInfoOnSubnets: () => Promise + /** + * Register callback for update available event + */ + onUpdateAvailable: (callback: (info: any) => void) => void + /** + * Register callback for update downloaded event + */ + onUpdateDownloaded: (callback: (info: any) => void) => void + /** + * Trigger update download + */ + downloadUpdate: () => void + /** + * Trigger update installation + */ + installUpdate: () => void + /** + * Cancel ongoing update + */ + cancelUpdate: () => void + /** + * Register callback for checking for update event + */ + onCheckingForUpdate: (callback: () => void) => void + /** + * Register callback for update not available event + */ + onUpdateNotAvailable: (callback: (info: any) => void) => void + /** + * Register callback for download progress event + */ + onDownloadProgress: (callback: (info: any) => void) => void } } - /* eslint-enable jsdoc/require-jsdoc */ } // Use global as window when running for browsers