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