Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 41 additions & 12 deletions .github/workflows/CI.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ on:
workflow_dispatch:

jobs:
build:
name: Build/Test
CLI:
name: Build/Test CLI
runs-on: ubuntu-latest

steps:
Expand All @@ -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: |
Expand All @@ -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
1 change: 0 additions & 1 deletion .github/workflows/CICD.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
64 changes: 64 additions & 0 deletions electron_app/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>Electron Docker Monitor</title>
<style>
body { font-family: sans-serif; margin: 1rem; }
#containerList { white-space: pre; background: #f1f1f1; padding: 1rem; }
</style>
</head>
<body>
<h1>Electron Docker Monitor</h1>

<button id="btnStart">Start Polling</button>
<div id="containerList">No data yet</div>

<hr/>

<h2>Remote Hosts</h2>
<select id="sshSelect"></select>
<button id="btnConnect">Connect Remote</button>
<div id="remoteStatus"></div>

<script>
// UI references
const btnStart = document.getElementById('btnStart');
const containerList = document.getElementById('containerList');
const sshSelect = document.getElementById('sshSelect');
const btnConnect = document.getElementById('btnConnect');
const remoteStatus = document.getElementById('remoteStatus');

// Populate SSH hosts on load
window.electronAPI.getSSHHosts().then((hosts) => {
sshSelect.innerHTML = '';
hosts.forEach((h) => {
const opt = document.createElement('option');
opt.value = h;
opt.textContent = h;
sshSelect.appendChild(opt);
});
});

// Continuous local container poll
let pollInterval = null;
btnStart.addEventListener('click', async () => {
// Clear old poll if any
if (pollInterval) clearInterval(pollInterval);
// Start new poll
pollInterval = setInterval(async () => {
const data = await window.electronAPI.startPoll();
containerList.textContent = JSON.stringify(data, null, 2);
}, 3000);
});

// Connect remote
btnConnect.addEventListener('click', async () => {
const selected = sshSelect.value;
if (!selected) return;
const res = await window.electronAPI.connectRemote(selected);
remoteStatus.textContent = res;
});
</script>
</body>
</html>
58 changes: 58 additions & 0 deletions electron_app/main.js
Original file line number Diff line number Diff line change
@@ -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 [];
}
}
19 changes: 19 additions & 0 deletions electron_app/package.json
Original file line number Diff line number Diff line change
@@ -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"
}
}
14 changes: 14 additions & 0 deletions electron_app/preload.js
Original file line number Diff line number Diff line change
@@ -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);
}
});
19 changes: 19 additions & 0 deletions electron_app/sshUtils.js
Original file line number Diff line number Diff line change
@@ -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 };
46 changes: 46 additions & 0 deletions electron_app/test/e2e.spec.js
Original file line number Diff line number Diff line change
@@ -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');
});
});