feat: init
This commit is contained in:
20
backend/Dockerfile
Normal file
20
backend/Dockerfile
Normal file
@@ -0,0 +1,20 @@
|
||||
# Use an official Node.js runtime as a parent image
|
||||
FROM node:18-alpine
|
||||
|
||||
# Set the working directory in the container
|
||||
WORKDIR /usr/src/app
|
||||
|
||||
# Copy package.json and package-lock.json to the working directory
|
||||
COPY package*.json ./
|
||||
|
||||
# Install any needed packages
|
||||
RUN npm install
|
||||
|
||||
# Bundle app source
|
||||
COPY . .
|
||||
|
||||
# Make port 3001 available to the world outside this container
|
||||
EXPOSE 3001
|
||||
|
||||
# Define the command to run the app
|
||||
CMD [ "node", "index.js" ]
|
||||
114
backend/docker.js
Normal file
114
backend/docker.js
Normal file
@@ -0,0 +1,114 @@
|
||||
const Docker = require('dockerode');
|
||||
const os = require('os');
|
||||
const ip = require('ip');
|
||||
|
||||
const docker = new Docker({ socketPath: '/var/run/docker.sock' });
|
||||
|
||||
function getHostIpAddresses() {
|
||||
const interfaces = os.networkInterfaces();
|
||||
const ipAddresses = [];
|
||||
|
||||
for (const interfaceName in interfaces) {
|
||||
for (const iface of interfaces[interfaceName]) {
|
||||
// Only consider IPv4 and non-internal addresses
|
||||
if (iface.family === 'IPv4' && !iface.internal) {
|
||||
ipAddresses.push({
|
||||
address: iface.address,
|
||||
netmask: iface.netmask,
|
||||
cidr: iface.cidr,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
return ipAddresses;
|
||||
}
|
||||
|
||||
function isIpInSubnet(checkIp, subnetCidr) {
|
||||
try {
|
||||
return ip.cidrSubnet(subnetCidr).contains(checkIp);
|
||||
} catch (e) {
|
||||
console.error(`Error checking IP in subnet: ${e.message}`);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function getTraefikUrl(container) {
|
||||
const ruleLabel = Object.keys(container.Labels).find(label => label.endsWith('.rule'));
|
||||
if (ruleLabel) {
|
||||
const rule = container.Labels[ruleLabel];
|
||||
const match = rule.match(/Host\(`(.*?)`\)/);
|
||||
if (match) {
|
||||
return `http://${match[1]}`;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
async function listContainers() {
|
||||
const containers = await docker.listContainers();
|
||||
const hostIpAddresses = getHostIpAddresses();
|
||||
|
||||
return containers
|
||||
.filter(container => container.Ports.length > 0)
|
||||
.map(container => {
|
||||
const traefikUrl = getTraefikUrl(container);
|
||||
const containerName = container.Names[0].substring(1);
|
||||
|
||||
const accessibleUrls = [];
|
||||
let preferredLocalUrl = null;
|
||||
|
||||
// Get all container IPs
|
||||
const containerIps = [];
|
||||
for (const networkName in container.NetworkSettings.Networks) {
|
||||
const network = container.NetworkSettings.Networks[networkName];
|
||||
if (network.IPAddress) {
|
||||
containerIps.push(network.IPAddress);
|
||||
}
|
||||
}
|
||||
|
||||
// Determine accessible URLs and preferred local URL
|
||||
container.Ports.forEach(port => {
|
||||
const publicPort = port.PublicPort;
|
||||
const privatePort = port.PrivatePort;
|
||||
|
||||
// Add accessible URLs based on container IPs
|
||||
containerIps.forEach(containerIp => {
|
||||
const url = `http://${containerIp}:${privatePort}`;
|
||||
accessibleUrls.push({ type: 'container', url: url, description: `Container IP: ${containerIp}` });
|
||||
|
||||
// Check for preferred local URL
|
||||
if (!preferredLocalUrl) {
|
||||
for (const hostIp of hostIpAddresses) {
|
||||
if (isIpInSubnet(containerIp, hostIp.cidr)) {
|
||||
preferredLocalUrl = `http://${hostIp.address}:${publicPort || privatePort}`;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Add accessible URLs based on host exposed ports
|
||||
if (publicPort) {
|
||||
hostIpAddresses.forEach(hostIp => {
|
||||
const url = `http://${hostIp.address}:${publicPort}`;
|
||||
accessibleUrls.push({ type: 'host', url: url, description: `Host IP: ${hostIp.address}` });
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
id: container.Id,
|
||||
name: containerName,
|
||||
ports: container.Ports,
|
||||
traefikUrl: traefikUrl,
|
||||
preferredLocalUrl: preferredLocalUrl,
|
||||
accessibleUrls: accessibleUrls,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
docker,
|
||||
listContainers,
|
||||
getHostIpAddresses,
|
||||
};
|
||||
49
backend/index.js
Normal file
49
backend/index.js
Normal file
@@ -0,0 +1,49 @@
|
||||
const express = require('express');
|
||||
const cors = require('cors');
|
||||
const expressWs = require('express-ws');
|
||||
const { listContainers, docker } = require('./docker');
|
||||
|
||||
const app = express();
|
||||
const wsInstance = expressWs(app);
|
||||
|
||||
app.use(cors());
|
||||
|
||||
app.get('/api/services', async (req, res) => {
|
||||
const services = await listContainers();
|
||||
res.json(services);
|
||||
});
|
||||
|
||||
app.ws('/api/events', (ws, req) => {
|
||||
docker.getEvents((err, stream) => {
|
||||
if (err) {
|
||||
console.error(err);
|
||||
ws.close();
|
||||
return;
|
||||
}
|
||||
|
||||
stream.on('data', (chunk) => {
|
||||
const event = JSON.parse(chunk.toString());
|
||||
if (event.Type === 'container' && (event.Action === 'start' || event.Action === 'stop')) {
|
||||
ws.send(JSON.stringify({ type: 'container', action: event.Action }));
|
||||
}
|
||||
});
|
||||
|
||||
stream.on('end', () => {
|
||||
ws.close();
|
||||
});
|
||||
|
||||
ws.on('close', () => {
|
||||
stream.destroy();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
app.get('/', (req, res) => {
|
||||
res.send('Hello, World!');
|
||||
});
|
||||
|
||||
const port = process.env.PORT || 3001;
|
||||
|
||||
app.listen(port, () => {
|
||||
console.log(`Server listening on port ${port}`);
|
||||
});
|
||||
1569
backend/package-lock.json
generated
Normal file
1569
backend/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
19
backend/package.json
Normal file
19
backend/package.json
Normal file
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"name": "backend",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"dependencies": {
|
||||
"express": "*",
|
||||
"dockerode": "*",
|
||||
"cors": "*",
|
||||
"express-ws": "*",
|
||||
"ip": "*"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "ISC"
|
||||
}
|
||||
Reference in New Issue
Block a user