0.1.0-alpha
This commit is contained in:
commit
43e27eb713
|
|
@ -0,0 +1,3 @@
|
|||
node_modules
|
||||
yarn.lock
|
||||
.env
|
||||
|
|
@ -0,0 +1,148 @@
|
|||
# Bitstream (Docker Management Server)
|
||||
|
||||
This project provides a Node.js backend server for managing Docker containers and images, with real-time stats via WebSocket. It is built using Express, Dockerode, and supports CORS for frontend integration.
|
||||
|
||||
## Features
|
||||
|
||||
- List all Docker containers (running and stopped)
|
||||
- Start, stop, restart, and delete containers
|
||||
- Create and run new containers with custom options
|
||||
- View container stats (CPU, memory, network, block I/O)
|
||||
- View container logs
|
||||
- List available Docker images
|
||||
- Pull new images from Docker Hub
|
||||
- Get Docker system info
|
||||
- Real-time container stats updates via WebSocket
|
||||
|
||||
## API Endpoints
|
||||
|
||||
### Containers
|
||||
- `GET /api/containers` — List all containers
|
||||
- `GET /api/containers/:id/stats` — Get stats for a container
|
||||
- `POST /api/containers/:id/start` — Start a container
|
||||
- `POST /api/containers/:id/stop` — Stop a container
|
||||
- `POST /api/containers/:id/restart` — Restart a container
|
||||
- `POST /api/containers/create` — Create and start a new container
|
||||
- `DELETE /api/containers/:id` — Delete a container
|
||||
- `GET /api/containers/:id/logs` — Get logs for a container
|
||||
|
||||
### Images
|
||||
- `GET /api/images` — List Docker images
|
||||
- `POST /api/images/pull` — Pull a Docker image (body: `{ image: "<image-name>" }`)
|
||||
|
||||
### System
|
||||
- `GET /api/system/info` — Get Docker system info
|
||||
|
||||
### WebSocket
|
||||
- Connect to the server via WebSocket (same port) to receive real-time container stats updates.
|
||||
|
||||
## Getting Started
|
||||
|
||||
### Prerequisites
|
||||
- [Node.js](https://nodejs.org/) (v22 recommended)
|
||||
- [Docker](https://www.docker.com/) running locally
|
||||
- On Windows: Docker must be configured to expose the API on TCP (see below)
|
||||
|
||||
### Installation
|
||||
|
||||
1. Clone the repository:
|
||||
```sh
|
||||
git clone https://git.netbyte.host/netbyte/bitstream.git
|
||||
cd bitstream
|
||||
```
|
||||
2. Install dependencies:
|
||||
```sh
|
||||
yarn
|
||||
# or
|
||||
npm install
|
||||
```
|
||||
|
||||
### Configuration
|
||||
|
||||
- **Linux/Mac:** Uses Docker socket at `/var/run/docker.sock` by default.
|
||||
- **Windows:**
|
||||
- Edit `server.js` and set Docker connection to `{ host: '127.0.0.1', port: 2375 }`.
|
||||
- Ensure Docker is configured to expose the API on TCP port 2375 (insecure).
|
||||
|
||||
### Running the Server
|
||||
|
||||
```sh
|
||||
node server.js
|
||||
```
|
||||
|
||||
The server will start on port `3001` by default (or set `PORT` environment variable).
|
||||
|
||||
### API Authentication (Secret Key)
|
||||
|
||||
Bitstream requires a secret API key for all requests. This prevents unauthorized access (like Pterodactyl's Wings).
|
||||
|
||||
#### Generating a Key
|
||||
|
||||
Run the following command to generate a new API secret:
|
||||
|
||||
```sh
|
||||
npm run keygen
|
||||
```
|
||||
|
||||
Copy the printed key and store it in a `.env` file at the root of your project:
|
||||
|
||||
```
|
||||
BITSTREAM_API_SECRET=your-generated-key-here
|
||||
```
|
||||
|
||||
You can use `.env.example` as a template.
|
||||
|
||||
**All API requests must include the header:**
|
||||
|
||||
```
|
||||
x-api-key: your-generated-key-here
|
||||
```
|
||||
|
||||
If the key is missing or incorrect, the server will return `401 Unauthorized`.
|
||||
|
||||
## Example: Creating a Container
|
||||
|
||||
POST `/api/containers/create`
|
||||
```json
|
||||
{
|
||||
"name": "my-nginx",
|
||||
"image": "nginx:latest",
|
||||
"ports": { "80/tcp": [{ "HostPort": "8080" }] },
|
||||
"environment": ["ENV_VAR=value"],
|
||||
"volumes": ["/host/path:/container/path"]
|
||||
}
|
||||
```
|
||||
|
||||
## Real-Time Stats
|
||||
|
||||
Connect to the WebSocket endpoint (same port) to receive periodic stats updates for all running containers.
|
||||
|
||||
## Running as a systemd Service
|
||||
|
||||
A sample systemd unit file is provided in `systemd/bitstream.service`. To use:
|
||||
|
||||
1. Copy the file to `/etc/systemd/system/bitstream.service` (edit paths as needed).
|
||||
2. Set `WorkingDirectory` and `EnvironmentFile` to your Bitstream install location.
|
||||
3. Create a dedicated user (e.g., `bitstream`) for security. (optional)
|
||||
4. Reload systemd and start the service:
|
||||
```sh
|
||||
sudo systemctl daemon-reload
|
||||
sudo systemctl enable --now bitstream
|
||||
sudo systemctl status bitstream
|
||||
```
|
||||
|
||||
## Debug Mode
|
||||
|
||||
You can run Bitstream in debug mode to log all API requests and see more detailed output:
|
||||
|
||||
```sh
|
||||
node server.js --debug
|
||||
# or
|
||||
yarn dev --debug
|
||||
```
|
||||
|
||||
When debug mode is enabled, all API interactions (method, path, status, duration) are logged to the console.
|
||||
|
||||
## License
|
||||
|
||||
MIT
|
||||
|
|
@ -0,0 +1,101 @@
|
|||
// containerService.js
|
||||
const docker = require('./dockerClient');
|
||||
const calculateCPUPercent = require('../utils/calculateCPU');
|
||||
const chalk = require('chalk').default;
|
||||
|
||||
async function listContainers() {
|
||||
const containers = await docker.listContainers({ all: true });
|
||||
return Promise.all(containers.map(async (containerInfo) => {
|
||||
const container = docker.getContainer(containerInfo.Id);
|
||||
const inspect = await container.inspect();
|
||||
return {
|
||||
id: containerInfo.Id,
|
||||
name: containerInfo.Names[0].replace('/', ''),
|
||||
image: containerInfo.Image,
|
||||
status: containerInfo.State,
|
||||
state: containerInfo.Status,
|
||||
ports: containerInfo.Ports,
|
||||
created: containerInfo.Created,
|
||||
labels: containerInfo.Labels || {},
|
||||
inspect: {
|
||||
config: inspect.Config,
|
||||
hostConfig: inspect.HostConfig,
|
||||
networkSettings: inspect.NetworkSettings
|
||||
}
|
||||
};
|
||||
}));
|
||||
}
|
||||
|
||||
async function getContainerStats(id) {
|
||||
const container = docker.getContainer(id);
|
||||
const stats = await container.stats({ stream: false });
|
||||
const cpuPercent = calculateCPUPercent(stats);
|
||||
const memoryUsage = stats.memory_stats.usage || 0;
|
||||
const memoryLimit = stats.memory_stats.limit || 0;
|
||||
const memoryPercent = memoryLimit > 0 ? (memoryUsage / memoryLimit) * 100 : 0;
|
||||
const networks = stats.networks || {};
|
||||
const networkIO = Object.keys(networks).reduce((acc, key) => ({
|
||||
rx_bytes: acc.rx_bytes + (networks[key].rx_bytes || 0),
|
||||
tx_bytes: acc.tx_bytes + (networks[key].tx_bytes || 0)
|
||||
}), { rx_bytes: 0, tx_bytes: 0 });
|
||||
return {
|
||||
cpu_percent: cpuPercent,
|
||||
memory: { usage: memoryUsage, limit: memoryLimit, percent: memoryPercent },
|
||||
network: networkIO,
|
||||
block_io: stats.blkio_stats
|
||||
};
|
||||
}
|
||||
|
||||
async function startContainer(id) {
|
||||
const container = docker.getContainer(id);
|
||||
await container.start();
|
||||
console.log(chalk.yellow('Container started:'), chalk.cyan(id));
|
||||
}
|
||||
|
||||
async function stopContainer(id) {
|
||||
const container = docker.getContainer(id);
|
||||
await container.stop();
|
||||
console.log(chalk.yellow('Container stopped:'), chalk.cyan(id));
|
||||
}
|
||||
|
||||
async function restartContainer(id) {
|
||||
const container = docker.getContainer(id);
|
||||
await container.restart();
|
||||
console.log(chalk.yellow('Container restarted:'), chalk.cyan(id));
|
||||
}
|
||||
|
||||
async function createContainer(options) {
|
||||
const container = await docker.createContainer(options);
|
||||
await container.start();
|
||||
console.log(chalk.green('Container created and started:'), chalk.cyan(container.id));
|
||||
return container.id;
|
||||
}
|
||||
|
||||
async function removeContainer(id) {
|
||||
const container = docker.getContainer(id);
|
||||
try { await container.stop(); } catch (e) {}
|
||||
await container.remove();
|
||||
console.log(chalk.red('Container deleted:'), chalk.cyan(id));
|
||||
}
|
||||
|
||||
async function getContainerLogs(id) {
|
||||
const container = docker.getContainer(id);
|
||||
const logs = await container.logs({
|
||||
stdout: true,
|
||||
stderr: true,
|
||||
tail: 100,
|
||||
timestamps: true
|
||||
});
|
||||
return logs.toString();
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
listContainers,
|
||||
getContainerStats,
|
||||
startContainer,
|
||||
stopContainer,
|
||||
restartContainer,
|
||||
createContainer,
|
||||
removeContainer,
|
||||
getContainerLogs
|
||||
};
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
// dockerClient.js
|
||||
const Docker = require('dockerode');
|
||||
|
||||
// For Linux/Mac: socketPath, for Windows: host/port
|
||||
const docker = new Docker({
|
||||
socketPath: '/var/run/docker.sock'
|
||||
// For Windows: { host: '127.0.0.1', port: 2375 }
|
||||
});
|
||||
|
||||
module.exports = docker;
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
// imageService.js
|
||||
|
||||
const docker = require('./dockerClient');
|
||||
const chalk = require('chalk').default;
|
||||
|
||||
async function listImages() {
|
||||
return docker.listImages();
|
||||
}
|
||||
|
||||
async function pullImage(image) {
|
||||
const stream = await docker.pull(image);
|
||||
return new Promise((resolve, reject) => {
|
||||
docker.modem.followProgress(stream, (err, result) => {
|
||||
if (err) {
|
||||
console.error(chalk.red('Error pulling image:'), chalk.cyan(image), err.message);
|
||||
return reject(err);
|
||||
}
|
||||
console.log(chalk.green('Image pulled successfully:'), chalk.cyan(image));
|
||||
resolve(result);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
listImages,
|
||||
pullImage
|
||||
};
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
// middleware/auth.js
|
||||
module.exports = function (req, res, next) {
|
||||
const apiKey = req.headers['x-api-key'];
|
||||
if (apiKey && apiKey === process.env.BITSTREAM_API_SECRET) {
|
||||
return next();
|
||||
}
|
||||
return res.status(401).json({ error: 'Unauthorized' });
|
||||
};
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
// middleware/logApi.js
|
||||
const chalk = require('chalk').default;
|
||||
|
||||
module.exports = function (req, res, next) {
|
||||
if (global.DEBUG_MODE) {
|
||||
const start = Date.now();
|
||||
res.on('finish', () => {
|
||||
const duration = Date.now() - start;
|
||||
console.log(
|
||||
chalk.magenta('[API]'),
|
||||
chalk.cyan(req.method),
|
||||
chalk.white(req.originalUrl),
|
||||
chalk.yellow(res.statusCode),
|
||||
chalk.gray(`${duration}ms`),
|
||||
req.headers['x-api-key'] ? chalk.green('[x-api-key]') : ''
|
||||
);
|
||||
});
|
||||
}
|
||||
next();
|
||||
};
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
{
|
||||
"name": "bitstream",
|
||||
"version": "0.1.0-alpha",
|
||||
"description": "Docker API built for Navigator",
|
||||
"main": "server.js",
|
||||
"scripts": {
|
||||
"debug": "node server.js --debug",
|
||||
"dev": "node server.js",
|
||||
"keygen": "node scripts/keygen.js"
|
||||
},
|
||||
"repository": "https://git.netbyte.host/netbyte/bitstream",
|
||||
"author": "Andreas Claesson <andreas@netbyte.host>",
|
||||
"license": "Apache2.0",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"chalk": "^5.6.2",
|
||||
"cors": "^2.8.5",
|
||||
"dockerode": "^4.0.7",
|
||||
"express": "^5.1.0",
|
||||
"ws": "^8.18.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"dotenv": "^17.2.2"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,101 @@
|
|||
// containers.js
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const containerService = require('../docker/containerService');
|
||||
|
||||
// List all containers
|
||||
router.get('/', async (req, res) => {
|
||||
try {
|
||||
const containers = await containerService.listContainers();
|
||||
res.json(containers);
|
||||
} catch (error) {
|
||||
res.status(500).json({ error: 'Failed to fetch containers' });
|
||||
}
|
||||
});
|
||||
|
||||
// Get container stats
|
||||
router.get('/:id/stats', async (req, res) => {
|
||||
try {
|
||||
const stats = await containerService.getContainerStats(req.params.id);
|
||||
res.json(stats);
|
||||
} catch (error) {
|
||||
res.status(500).json({ error: 'Failed to fetch container stats' });
|
||||
}
|
||||
});
|
||||
|
||||
// Start container
|
||||
router.post('/:id/start', async (req, res) => {
|
||||
try {
|
||||
await containerService.startContainer(req.params.id);
|
||||
res.json({ message: 'Container started successfully' });
|
||||
} catch (error) {
|
||||
res.status(500).json({ error: 'Failed to start container' });
|
||||
}
|
||||
});
|
||||
|
||||
// Stop container
|
||||
router.post('/:id/stop', async (req, res) => {
|
||||
try {
|
||||
await containerService.stopContainer(req.params.id);
|
||||
res.json({ message: 'Container stopped successfully' });
|
||||
} catch (error) {
|
||||
res.status(500).json({ error: 'Failed to stop container' });
|
||||
}
|
||||
});
|
||||
|
||||
// Restart container
|
||||
router.post('/:id/restart', async (req, res) => {
|
||||
try {
|
||||
await containerService.restartContainer(req.params.id);
|
||||
res.json({ message: 'Container restarted successfully' });
|
||||
} catch (error) {
|
||||
res.status(500).json({ error: 'Failed to restart container' });
|
||||
}
|
||||
});
|
||||
|
||||
// Create and run new container
|
||||
router.post('/create', async (req, res) => {
|
||||
try {
|
||||
const { name, image, ports = {}, environment = [], volumes = [], restart = 'unless-stopped' } = req.body;
|
||||
const createOptions = {
|
||||
Image: image,
|
||||
name: name,
|
||||
Env: environment,
|
||||
HostConfig: {
|
||||
PortBindings: ports,
|
||||
Binds: volumes,
|
||||
RestartPolicy: { Name: restart }
|
||||
},
|
||||
ExposedPorts: Object.keys(ports).reduce((acc, port) => {
|
||||
acc[port] = {};
|
||||
return acc;
|
||||
}, {})
|
||||
};
|
||||
const id = await containerService.createContainer(createOptions);
|
||||
res.json({ id, message: 'Container created and started successfully' });
|
||||
} catch (error) {
|
||||
res.status(500).json({ error: 'Failed to create container' });
|
||||
}
|
||||
});
|
||||
|
||||
// Delete container
|
||||
router.delete('/:id', async (req, res) => {
|
||||
try {
|
||||
await containerService.removeContainer(req.params.id);
|
||||
res.json({ message: 'Container deleted successfully' });
|
||||
} catch (error) {
|
||||
res.status(500).json({ error: 'Failed to delete container' });
|
||||
}
|
||||
});
|
||||
|
||||
// Get container logs
|
||||
router.get('/:id/logs', async (req, res) => {
|
||||
try {
|
||||
const logs = await containerService.getContainerLogs(req.params.id);
|
||||
res.json({ logs });
|
||||
} catch (error) {
|
||||
res.status(500).json({ error: 'Failed to fetch logs' });
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
// images.js
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const imageService = require('../docker/imageService');
|
||||
|
||||
// List images
|
||||
router.get('/', async (req, res) => {
|
||||
try {
|
||||
const images = await imageService.listImages();
|
||||
res.json(images);
|
||||
} catch (error) {
|
||||
res.status(500).json({ error: 'Failed to fetch images' });
|
||||
}
|
||||
});
|
||||
|
||||
// Pull image
|
||||
router.post('/pull', async (req, res) => {
|
||||
try {
|
||||
const { image } = req.body;
|
||||
await imageService.pullImage(image);
|
||||
res.json({ message: 'Image pull started' });
|
||||
} catch (error) {
|
||||
res.status(500).json({ error: 'Failed to pull image' });
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
// system.js
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const docker = require('../docker/dockerClient');
|
||||
|
||||
// Get Docker system info
|
||||
router.get('/info', async (req, res) => {
|
||||
try {
|
||||
const info = await docker.info();
|
||||
res.json(info);
|
||||
} catch (error) {
|
||||
res.status(500).json({ error: 'Failed to fetch system info' });
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
// scripts/keygen.js
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const crypto = require('crypto');
|
||||
|
||||
const envPath = path.join(__dirname, '../.env');
|
||||
const force = process.argv.includes('--force');
|
||||
|
||||
let envContent = '';
|
||||
let exists = false;
|
||||
let alreadySet = false;
|
||||
|
||||
if (fs.existsSync(envPath)) {
|
||||
envContent = fs.readFileSync(envPath, 'utf8');
|
||||
exists = true;
|
||||
alreadySet = /^BITSTREAM_API_SECRET=.+/m.test(envContent);
|
||||
}
|
||||
|
||||
if (alreadySet && !force) {
|
||||
console.warn('BITSTREAM_API_SECRET already exists in .env!');
|
||||
console.warn('If you want to overwrite it, run: npm run keygen --force / yarn keygen --force');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const key = crypto.randomBytes(32).toString('hex');
|
||||
|
||||
let newEnvContent = '';
|
||||
if (exists) {
|
||||
// Remove any existing BITSTREAM_API_SECRET line
|
||||
newEnvContent = envContent.replace(/^BITSTREAM_API_SECRET=.*$/m, '').trim();
|
||||
if (newEnvContent.length > 0 && !newEnvContent.endsWith('\n')) newEnvContent += '\n';
|
||||
newEnvContent += `BITSTREAM_API_SECRET=${key}\n`;
|
||||
} else {
|
||||
newEnvContent = `# Bitstream API Secret\nBITSTREAM_API_SECRET=${key}\n`;
|
||||
}
|
||||
|
||||
fs.writeFileSync(envPath, newEnvContent, 'utf8');
|
||||
console.log('Your new Bitstream API secret key:');
|
||||
console.log(key);
|
||||
console.log('\nThe key has been written to .env as BITSTREAM_API_SECRET.');
|
||||
if (alreadySet && force) {
|
||||
console.log('Previous BITSTREAM_API_SECRET was overwritten.');
|
||||
}
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
// server.js - Main backend server
|
||||
require('dotenv').config({ quiet: true });
|
||||
const express = require('express');
|
||||
const chalk = require('chalk').default;
|
||||
const auth = require('./middleware/auth');
|
||||
const logApi = require('./middleware/logApi');
|
||||
const cors = require('cors');
|
||||
const http = require('http');
|
||||
const containersRouter = require('./routes/containers');
|
||||
const imagesRouter = require('./routes/images');
|
||||
const systemRouter = require('./routes/system');
|
||||
const setupWebSocket = require('./ws/statsSocket');
|
||||
|
||||
// Debug mode flag
|
||||
const DEBUG_MODE = process.argv.includes('--debug');
|
||||
global.DEBUG_MODE = DEBUG_MODE;
|
||||
|
||||
const app = express();
|
||||
|
||||
const server = http.createServer(app);
|
||||
|
||||
app.use(cors());
|
||||
app.use(express.json());
|
||||
|
||||
|
||||
// Setup WebSocket for real-time stats
|
||||
setupWebSocket(server);
|
||||
|
||||
|
||||
// Log all API requests if debug mode is enabled
|
||||
app.use('/api', logApi);
|
||||
// Protect all /api routes with API key auth
|
||||
app.use('/api', auth);
|
||||
app.use('/api/containers', containersRouter);
|
||||
app.use('/api/images', imagesRouter);
|
||||
app.use('/api/system', systemRouter);
|
||||
|
||||
const PORT = process.env.PORT || 6560;
|
||||
|
||||
server.listen(PORT, () => {
|
||||
console.log(chalk.green.bold('Bitstream server running on port'), chalk.cyan.bold(PORT));
|
||||
if (DEBUG_MODE) {
|
||||
console.log(chalk.yellow.bold('[DEBUG MODE ENABLED] All API requests will be logged.'));
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = app;
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
[Unit]
|
||||
Description=Bitstream Docker Management API
|
||||
After=network.target docker.service
|
||||
Requires=docker.service
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
WorkingDirectory=/path/to/bitstream
|
||||
ExecStart=/usr/bin/node server.js
|
||||
Restart=on-failure
|
||||
EnvironmentFile=/path/to/bitstream/.env
|
||||
User=bitstream
|
||||
Group=bitstream
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
// calculateCPU.js
|
||||
function calculateCPUPercent(stats) {
|
||||
const cpuStats = stats.cpu_stats;
|
||||
const preCpuStats = stats.precpu_stats;
|
||||
const cpuDelta = cpuStats.cpu_usage.total_usage - preCpuStats.cpu_usage.total_usage;
|
||||
const systemDelta = cpuStats.system_cpu_usage - preCpuStats.system_cpu_usage;
|
||||
if (systemDelta > 0 && cpuDelta > 0) {
|
||||
const cpuPercent = (cpuDelta / systemDelta) * cpuStats.online_cpus * 100;
|
||||
return Math.round(cpuPercent * 100) / 100;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
module.exports = calculateCPUPercent;
|
||||
|
|
@ -0,0 +1,56 @@
|
|||
// statsSocket.js
|
||||
const WebSocket = require('ws');
|
||||
const docker = require('../docker/dockerClient');
|
||||
const calculateCPUPercent = require('../utils/calculateCPU');
|
||||
|
||||
let clients = new Set();
|
||||
|
||||
function setupWebSocket(server) {
|
||||
const wss = new WebSocket.Server({ server });
|
||||
wss.on('connection', (ws) => {
|
||||
clients.add(ws);
|
||||
ws.on('close', () => clients.delete(ws));
|
||||
});
|
||||
startStatsMonitoring();
|
||||
}
|
||||
|
||||
function broadcastStats(data) {
|
||||
clients.forEach(client => {
|
||||
if (client.readyState === WebSocket.OPEN) {
|
||||
client.send(JSON.stringify(data));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function startStatsMonitoring() {
|
||||
setInterval(async () => {
|
||||
try {
|
||||
const containers = await docker.listContainers();
|
||||
const statsPromises = containers.map(async (containerInfo) => {
|
||||
try {
|
||||
const container = docker.getContainer(containerInfo.Id);
|
||||
const stats = await container.stats({ stream: false });
|
||||
return {
|
||||
id: containerInfo.Id,
|
||||
name: containerInfo.Names[0].replace('/', ''),
|
||||
stats: {
|
||||
cpu_percent: calculateCPUPercent(stats),
|
||||
memory: {
|
||||
usage: stats.memory_stats.usage || 0,
|
||||
limit: stats.memory_stats.limit || 0,
|
||||
percent: stats.memory_stats.limit > 0 ? (stats.memory_stats.usage / stats.memory_stats.limit) * 100 : 0
|
||||
}
|
||||
}
|
||||
};
|
||||
} catch (error) {
|
||||
return null;
|
||||
}
|
||||
});
|
||||
const allStats = await Promise.all(statsPromises);
|
||||
const validStats = allStats.filter(stat => stat !== null);
|
||||
broadcastStats({ type: 'stats_update', data: validStats });
|
||||
} catch (error) {}
|
||||
}, 5000);
|
||||
}
|
||||
|
||||
module.exports = setupWebSocket;
|
||||
Loading…
Reference in New Issue