diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index f13dc6a..7da67a8 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -6,8 +6,8 @@ on: workflow_dispatch: jobs: - build: - name: Build/Test + CLI: + name: Build/Test CLI runs-on: ubuntu-latest steps: @@ -31,20 +31,19 @@ jobs: - name: Install Docker and Docker Compose run: | sudo apt-get update - for pkg in docker.io docker-doc docker-compose docker-compose-v2 podman-docker containerd runc; do sudo apt-get remove $pkg; done + for pkg in docker.io docker-doc docker-compose docker-compose-v2 podman-docker containerd runc; do sudo apt-get remove -y $pkg; done sudo apt-get update - sudo apt-get install ca-certificates curl + sudo apt-get install -y ca-certificates curl sudo install -m 0755 -d /etc/apt/keyrings sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc sudo chmod a+r /etc/apt/keyrings/docker.asc - # Add the repository to Apt sources: echo \ "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu \ - $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \ + $(. /etc/os-release && echo \"$VERSION_CODENAME\") stable" | \ sudo tee /etc/apt/sources.list.d/docker.list > /dev/null sudo apt-get update - sudo apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin + sudo apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin - name: Set Up SSH Config for CI run: | @@ -63,19 +62,49 @@ jobs: run: docker compose -f docker-compose.yml up -d - name: Wait for Local Containers to Initialize - run: sleep 10 # Ensure services are fully started + run: sleep 10 - name: Run Installation Script - run: | - python3 install.py + run: python3 install.py - name: Verify Monitor Commands run: | - echo "🔄 Testing monitor --service" + echo "🔄 Testing monitor service" monitor service || { echo "❌ monitor service failed"; exit 1; } - echo "🔄 Testing monitor --state" + echo "🔄 Testing monitor state" monitor state || { echo "❌ monitor state failed"; exit 1; } - name: Stop and Clean Up Docker Containers run: docker compose down + + ElectronApp: + runs-on: ubuntu-latest + + steps: + - name: Checkout Repository + uses: actions/checkout@v3 + + - name: Set up Node + uses: actions/setup-node@v3 + with: + node-version: '16' + + - name: Install Dependencies + run: | + cd electron_app + npm install + + - name: Fix Electron sandbox + run: | + cd electron_app + sudo chown root:root node_modules/electron/dist/chrome-sandbox + sudo chmod 4755 node_modules/electron/dist/chrome-sandbox + + - name: Install xvfb + run: sudo apt-get update && sudo apt-get install -y xvfb + + - name: Run Electron Tests + run: | + cd electron_app + xvfb-run -a npm test diff --git a/.github/workflows/CICD.yml b/.github/workflows/CICD.yml index 95e3857..b80ae2e 100644 --- a/.github/workflows/CICD.yml +++ b/.github/workflows/CICD.yml @@ -90,7 +90,6 @@ jobs: name: Create GitHub Release needs: build runs-on: ubuntu-latest - # Release tylko przy pushu do main if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' }} steps: diff --git a/electron_app/index.html b/electron_app/index.html new file mode 100644 index 0000000..ab2dbcd --- /dev/null +++ b/electron_app/index.html @@ -0,0 +1,64 @@ + + + + + Electron Docker Monitor + + + +

Electron Docker Monitor

+ + +
No data yet
+ +
+ +

Remote Hosts

+ + +
+ + + + diff --git a/electron_app/main.js b/electron_app/main.js new file mode 100644 index 0000000..91127b3 --- /dev/null +++ b/electron_app/main.js @@ -0,0 +1,58 @@ +// Main process +const { app, BrowserWindow, ipcMain } = require('electron'); +const path = require('path'); +const Docker = require('dockerode'); +const { getSSHHosts } = require('./sshUtils'); + +let docker = new Docker(); + +function createWindow() { + const win = new BrowserWindow({ + width: 900, + height: 600, + webPreferences: { + contextIsolation: true, + preload: path.join(__dirname, 'preload.js') + } + }); + win.loadFile('index.html'); +} + +app.whenReady().then(() => { + createWindow(); + app.on('activate', () => { + if (BrowserWindow.getAllWindows().length === 0) { + createWindow(); + } + }); +}); + +app.on('window-all-closed', () => { + if (process.platform !== 'darwin') app.quit(); +}); + +ipcMain.handle('start-poll', async () => { + return fetchContainers(); +}); + +// SSH host list +ipcMain.handle('get-ssh-hosts', async () => { + return getSSHHosts(); +}); + +ipcMain.handle('connect-remote', async (event, hostAlias) => { + return `Connected to remote host: ${hostAlias}`; +}); + +async function fetchContainers() { + try { + const containers = await docker.listContainers({ all: true }); + return containers.map((c) => ({ + name: c.Names[0], + status: c.Status, + ports: c.Ports + })); + } catch (err) { + return []; + } +} diff --git a/electron_app/package.json b/electron_app/package.json new file mode 100644 index 0000000..b411f94 --- /dev/null +++ b/electron_app/package.json @@ -0,0 +1,19 @@ +{ + "name": "monitor-electron", + "version": "1.0.0", + "main": "main.js", + "scripts": { + "start": "electron .", + "test": "mocha test/e2e.spec.js" + }, + "dependencies": { + "electron": "^34.2.0", + "dockerode": "^4.0.4", + "ssh-config": "^5.0.3" + }, + "devDependencies": { + "spectron": "^15.0.0", + "mocha": "^10.2.0", + "chai": "^4.3.7" + } +} diff --git a/electron_app/preload.js b/electron_app/preload.js new file mode 100644 index 0000000..88c313a --- /dev/null +++ b/electron_app/preload.js @@ -0,0 +1,14 @@ +// Expose API to renderer +const { contextBridge, ipcRenderer } = require('electron'); + +contextBridge.exposeInMainWorld('electronAPI', { + startPoll: async () => { + return await ipcRenderer.invoke('start-poll'); + }, + getSSHHosts: async () => { + return await ipcRenderer.invoke('get-ssh-hosts'); + }, + connectRemote: async (hostAlias) => { + return await ipcRenderer.invoke('connect-remote', hostAlias); + } +}); diff --git a/electron_app/sshUtils.js b/electron_app/sshUtils.js new file mode 100644 index 0000000..b354372 --- /dev/null +++ b/electron_app/sshUtils.js @@ -0,0 +1,19 @@ +const fs = require('fs'); +const path = require('path'); +const SSHConfig = require('ssh-config'); + +function getSSHHosts() { + try { + const configPath = path.join(process.env.HOME || process.env.USERPROFILE, '.ssh', 'config'); + const data = fs.readFileSync(configPath, 'utf8'); + const parsed = SSHConfig.parse(data); + return parsed + .filter((item) => item.type === SSHConfig.DIRECTIVE && item.param === 'Host') + .map((item) => item.value) + .filter((host) => host !== '*'); + } catch (err) { + return []; + } +} + +module.exports = { getSSHHosts }; diff --git a/electron_app/test/e2e.spec.js b/electron_app/test/e2e.spec.js new file mode 100644 index 0000000..c8720a2 --- /dev/null +++ b/electron_app/test/e2e.spec.js @@ -0,0 +1,46 @@ +// Spectron E2E test +const { Application } = require('spectron'); +const assert = require('chai').assert; +const path = require('path'); + +describe('Electron App Tests', function() { + this.timeout(10000); // Increase if needed + let app; + + before(async () => { + // Path to local electron bin + const electronPath = path.join(__dirname, '..', 'node_modules', '.bin', 'electron'); + // App root + const appPath = path.join(__dirname, '..'); + + app = new Application({ + path: electronPath, + args: [appPath] + }); + + await app.start(); + }); + + after(async () => { + if (app && app.isRunning()) { + await app.stop(); + } + }); + + it('shows the main window', async () => { + const count = await app.client.getWindowCount(); + assert.equal(count, 1, 'Main window not found'); + }); + + it('has correct title', async () => { + const title = await app.client.getTitle(); + assert.equal(title, 'Electron Docker Monitor', 'Title mismatch'); + }); + + it('renders UI elements', async () => { + // Example: check if a button or text is present + const button = await app.client.$('#btnStart'); + const exists = await button.isExisting(); + assert.isTrue(exists, 'btnStart not found'); + }); +});