Compare commits

...

12 Commits

Author SHA1 Message Date
0cf82495e9 update ss 2024-12-03 01:00:56 -05:00
0bfb29c6ff update name 2024-12-03 00:49:32 -05:00
faa225c077 update readme 2024-12-02 20:52:12 -05:00
ee9f65fef7 update 2024-12-02 20:39:17 -05:00
5e96160b7c update readme 2024-12-02 20:37:39 -05:00
526ef0ca84 add screenshots 2024-12-02 20:33:15 -05:00
35010022bc update 2024-12-02 06:17:23 -05:00
73bec336e3 update 2024-12-02 06:05:38 -05:00
605e30c368 update 2024-12-02 05:58:22 -05:00
3457eb0e46 fix up status indicator on deploy 2024-12-02 05:18:16 -05:00
5e8c085446 streamline 2024-12-02 05:05:43 -05:00
55d502c5f4 streamline stats 2024-12-02 05:05:42 -05:00
13 changed files with 360 additions and 216 deletions

BIN
.DS_Store vendored Normal file

Binary file not shown.

179
README.md
View File

@ -1,8 +1,8 @@
# Peartainer # peardock
## Overview ## Overview
Peartainer is a decentralized, peer-to-peer application designed to streamline Docker container management using Hyperswarm. The application connects multiple peers over a distributed hash table (DHT) network and provides full control over Docker containers, including starting, stopping, removing, duplicating, and monitoring real-time metrics. With its robust server key-based architecture, Peartainer ensures secure and persistent peer-to-peer communication. peardock is a decentralized, peer-to-peer application designed to streamline Docker container management using Hyperswarm. The application connects multiple peers over a distributed hash table (DHT) network and provides full control over Docker containers, including starting, stopping, removing, duplicating, viewing logs, deploying from templates, and monitoring real-time metrics. With its robust server key-based architecture, peardock ensures secure and persistent peer-to-peer communication.
The **server key** forms the foundation of the connection. It is automatically generated, saved, and reused unless explicitly refreshed, making it easy to maintain consistent access while allowing for manual key regeneration when needed. The **server key** forms the foundation of the connection. It is automatically generated, saved, and reused unless explicitly refreshed, making it easy to maintain consistent access while allowing for manual key regeneration when needed.
@ -25,15 +25,25 @@ pear run pear://7to8bzrk53ab5ufwauqcw57s1kxmuykc9b8cdnjicaqcgoefa4wo
- **Real-Time Docker Management**: - **Real-Time Docker Management**:
- List all containers across peers with statuses. - List all containers across peers with statuses.
- Start, stop, and remove containers remotely. - Start, stop, restart, and remove containers remotely.
- **Dynamic Terminal Sessions**: - **Dynamic Terminal Sessions**:
- Open and manage multiple terminals for running containers. - Open and manage multiple terminals for running containers.
- Real-time shell sessions streamed to connected peers. - Real-time shell sessions streamed to connected peers.
- **Docker CLI Terminal**:
- Access a Docker CLI terminal to run Docker commands on the remote peer.
- **Container Duplication**: - **Container Duplication**:
- Clone containers with custom configurations for CPUs, memory, network mode, and hostname. - Clone containers with custom configurations for CPUs, memory, network mode, and hostname.
- **Template Deployment**:
- Deploy containers using templates fetched from a remote repository.
- Customize deployment parameters such as ports, volumes, and environment variables.
- **Container Logs**:
- View real-time and historical logs of containers.
- **Live Statistics Streaming**: - **Live Statistics Streaming**:
- Broadcast CPU, memory, and network stats in real-time to connected peers. - Broadcast CPU, memory, and network stats in real-time to connected peers.
@ -47,11 +57,13 @@ pear run pear://7to8bzrk53ab5ufwauqcw57s1kxmuykc9b8cdnjicaqcgoefa4wo
- Modern, responsive UI built with **Bootstrap**. - Modern, responsive UI built with **Bootstrap**.
- Integrated terminal viewer powered by **Xterm.js**. - Integrated terminal viewer powered by **Xterm.js**.
- Real-time container stats displayed for each container. - Real-time container stats displayed for each container.
- View container logs directly from the UI.
- Deploy containers using templates with a user-friendly wizard.
- **Production Deployment**: - **Production Deployment**:
- Ready-to-use client app available via Pear runtime: - Ready-to-use client app available via Pear runtime:
```bash ```bash
pear run pear://7to8bzrk53ab5ufwauqcw57s1kxmuykc9b8cdnjicaqcgoefa4wo pear run pear://7to8bzrk53ab5ufwauqcw57s1kxmuykcgoefa4wo
``` ```
--- ---
@ -102,15 +114,15 @@ Peers connect to the server using the unique topic derived from the `SERVER_KEY`
The server interacts with Docker using **Dockerode**: The server interacts with Docker using **Dockerode**:
- List containers: - **List Containers**:
```javascript ```javascript
const containers = await docker.listContainers({ all: true }); const containers = await docker.listContainers({ all: true });
``` ```
- Start a container: - **Start a Container**:
```javascript ```javascript
await docker.getContainer(containerId).start(); await docker.getContainer(containerId).start();
``` ```
- Stream statistics: - **Stream Statistics**:
```javascript ```javascript
container.stats({ stream: true }, (err, stream) => { container.stats({ stream: true }, (err, stream) => {
stream.on('data', (data) => { stream.on('data', (data) => {
@ -119,6 +131,8 @@ The server interacts with Docker using **Dockerode**:
}); });
}); });
``` ```
- **Docker CLI Commands**:
- Execute Docker commands received from the client within controlled parameters to ensure security.
--- ---
@ -150,36 +164,37 @@ The server interacts with Docker using **Dockerode**:
### Server Setup ### Server Setup
1. Clone the repository: 1. **Clone the Repository**:
```bash ```bash
git clone https://git.ssh.surf/snxraven/peartainer.git git clone https://git.ssh.surf/snxraven/peardock.git
cd peartainer cd peardock
``` ```
2. Change to server Dir: 2. **Change to Server Directory**:
```bash ```bash
cd server cd server
``` ```
3. npm install: 3. **Install Dependencies**:
```bash ```bash
npm install hyperswarm dockerode hypercore-crypto stream dotenv npm install hyperswarm dockerode hypercore-crypto stream dotenv
``` ```
4. Run Server: 4. **Run the Server**:
```bash ```bash
node server.js node server.js
``` ```
--- ---
### Client Setup ### Client Setup
1. For development, run: 1. **For Development**, run:
```bash ```bash
pear run --dev . pear run --dev .
``` ```
2. For production, use the pre-deployed Pear app: 2. **For Production**, use the pre-deployed Pear app:
```bash ```bash
pear run pear://7to8bzrk53ab5ufwauqcw57s1kxmuykc9b8cdnjicaqcgoefa4wo pear run pear://7to8bzrk53ab5ufwauqcw57s1kxmuykc9b8cdnjicaqcgoefa4wo
``` ```
@ -198,20 +213,86 @@ The server interacts with Docker using **Dockerode**:
- **Listing Containers**: - **Listing Containers**:
- View all containers (running and stopped) with their statuses. - View all containers (running and stopped) with their statuses.
- **Starting/Stopping Containers**: - **Starting/Stopping/Restarting Containers**:
- Use the action buttons in the container list. - Use the action buttons (play, stop, restart icons) in the container list.
- **Removing Containers**: - **Removing Containers**:
- Click the trash icon to delete a container. - Click the trash icon to delete a container.
- **Viewing Container Logs**:
- Click the logs icon to view real-time and historical logs of a container.
- **Duplicating Containers**: - **Duplicating Containers**:
- Click the clone icon and customize the duplication form. - Click the clone icon and customize the duplication form.
### Terminal Access ### Terminal Access
- Open terminals for running containers. - **Container Terminal**:
- Open terminals for running containers by clicking the terminal icon.
- Switch between sessions using the tray at the bottom. - Switch between sessions using the tray at the bottom.
- **Docker CLI Terminal**:
- Access a Docker CLI terminal to execute Docker commands on the remote peer.
- Click the Docker terminal icon in the connection list.
### Template Deployment
- **Deploying from Templates**:
- Open the template deployment modal by clicking the deploy template icon.
- Search and select templates from the list.
- Customize deployment parameters such as container name, image, ports, volumes, and environment variables.
- Deploy the container with the specified settings.
---
## Screenshots
### Welcome Screen
![Welcome Screen](https://git.ssh.surf/snxraven/peardock/raw/branch/main/screenshots/screenshot-0.png)
*The initial welcome screen guiding users to add a connection.*
---
### Container List
![Container List](https://git.ssh.surf/snxraven/peardock/raw/branch/main/screenshots/screenshot-1.png)
*Displaying all Docker containers with real-time stats and action buttons.*
---
### Template Deployments
![Template Deployments](https://git.ssh.surf/snxraven/peardock/raw/branch/main/screenshots/screenshot-2.png)
*Browsing and selecting templates for deployment from a remote repository.*
---
### Final Deploy Modal
![Final Deploy Modal](https://git.ssh.surf/snxraven/peardock/raw/branch/main/screenshots/screenshot-3.png)
*Customizing deployment parameters before launching a new container.*
---
### Duplicate Container Form
![Duplicate Container Form](https://git.ssh.surf/snxraven/peardock/raw/branch/main/screenshots/screenshot-4.png)
*Duplicating an existing container with options to modify configurations.*
---
### Container Logs
![Container Logs](https://git.ssh.surf/snxraven/peardock/raw/branch/main/screenshots/screenshot-5.png)
*Viewing real-time logs of a container directly from the UI.*
--- ---
## Customization ## Customization
@ -232,7 +313,15 @@ The server interacts with Docker using **Dockerode**:
### Docker Commands ### Docker Commands
Add new commands in `server/server.js` under the `switch` statement for additional Docker functionalities. - Add new commands in `server/server.js` under the `switch` statement for additional Docker functionalities:
```javascript
switch (parsedData.command) {
case 'newCommand':
// Implement your command logic here
break;
// Existing cases...
}
```
--- ---
@ -240,6 +329,8 @@ Add new commands in `server/server.js` under the `switch` statement for addition
- The `SERVER_KEY` is sensitive and should be stored securely. - The `SERVER_KEY` is sensitive and should be stored securely.
- Refresh the key periodically to enhance security, especially in untrusted environments. - Refresh the key periodically to enhance security, especially in untrusted environments.
- peardock uses encrypted peer-to-peer connections, but it's recommended to run it within secure networks.
- Limit access to the server by controlling who has the `SERVER_KEY`.
--- ---
@ -250,16 +341,66 @@ Add new commands in `server/server.js` under the `switch` statement for addition
1. **Unable to Connect**: 1. **Unable to Connect**:
- Verify the `SERVER_KEY` matches on both server and client. - Verify the `SERVER_KEY` matches on both server and client.
- Ensure the server is running and accessible. - Ensure the server is running and accessible.
- Check network configurations and firewall settings.
2. **Docker Errors**: 2. **Docker Errors**:
- Ensure Docker is running and properly configured. - Ensure Docker is running and properly configured.
- Check permissions to manage Docker. - Check permissions to manage Docker.
- Verify that the user running the server has access to the Docker daemon.
3. **Terminal Issues**: 3. **Terminal Issues**:
- Verify the container has a valid shell (e.g., `/bin/bash`). - Verify the container has a valid shell (e.g., `/bin/bash`).
- Ensure that the container is running before opening a terminal.
- Check for network latency that might affect terminal responsiveness.
4. **Template Deployment Failures**:
- Ensure the Docker image specified in the template is valid and accessible.
- Check network connectivity if pulling images from remote repositories.
- Validate all required parameters in the deployment form.
--- ---
## Contributing ## Contributing
Contributions are welcome! Fork the repository, make your changes, and submit a pull request. Contributions are welcome! Fork the repository, make your changes, and submit a pull request.
1. **Fork the Repository**:
- Click the "Fork" button at the top of the repository page.
2. **Clone Your Fork**:
```bash
git clone https://github.com/your-username/peardock.git
```
3. **Create a Branch for Your Feature**:
```bash
git checkout -b feature/your-feature-name
```
4. **Make Changes and Commit**:
```bash
git add .
git commit -m "Add your feature"
```
5. **Push to Your Fork**:
```bash
git push origin feature/your-feature-name
```
6. **Submit a Pull Request**:
- Go to your fork on GitHub and click the "New pull request" button.
---
## Acknowledgments
- **Portainer**: For inspiring the creation of a powerful Docker management tool.
- **Hyperswarm**: Providing the peer-to-peer networking backbone.
- **Dockerode**: Facilitating Docker API interactions in Node.js.
---
## Contact
For questions, issues, or suggestions, please open an issue on the [GitHub repository](https://git.ssh.surf/snxraven/peardock).

53
app.js
View File

@ -64,7 +64,7 @@ function startStatsInterval() {
if (window.activePeer) { if (window.activePeer) {
const now = Date.now(); const now = Date.now();
if (now - lastStatsUpdate >= 500) { // Ensure at least 500ms between updates if (now - lastStatsUpdate >= 500) { // Ensure at least 500ms between updates
sendCommand('stats', {}); // Adjust command if necessary // sendCommand('allStats', {}); // Adjust command if necessary
lastStatsUpdate = now; lastStatsUpdate = now;
} }
} else { } else {
@ -266,31 +266,24 @@ function hideStatusIndicator() {
} }
} }
// Show Alert // Show Alert
// Show alert message
function showAlert(type, message) { function showAlert(type, message) {
const alertContainer = document.getElementById('alert-container'); const alertBox = document.createElement('div');
alertBox.className = `alert alert-${type}`;
alertBox.textContent = message;
// Create alert element const container = document.querySelector('#alert-container');
const alert = document.createElement('div'); if (container) {
alert.className = `alert ${type}`; container.appendChild(alertBox);
alert.innerHTML = `
<span>${message}</span>
<button class="close-btn" aria-label="Close">&times;</button>
`;
// Add close button functionality
const closeButton = alert.querySelector('.close-btn');
closeButton.addEventListener('click', () => {
alert.remove(); // Remove alert on close
});
// Append alert to container
alertContainer.appendChild(alert);
// Automatically remove alert after 5 seconds
setTimeout(() => { setTimeout(() => {
alert.remove(); container.removeChild(alertBox);
}, 5000); }, 5000);
} else {
console.warn('[WARN] Alert container not found.');
} }
}
// Collapse Sidebar Functionality // Collapse Sidebar Functionality
@ -314,10 +307,11 @@ function handlePeerData(data, topicId, peer) {
console.log(response.message) console.log(response.message)
if (response.success && response.message.includes && response.message.includes('deployed successfully')) { if (response.success && response.message.includes && response.message.includes('deployed successfully')) {
console.log(`[INFO] Template deployed successfully: ${response.message}`); console.log(`[INFO] Template deployed successfully: ${response.message}`);
closeAllModals(); // Close all modals after successful deployment
hideStatusIndicator(); hideStatusIndicator();
startStatsInterval(); // Restart stats polling startStatsInterval(); // Restart stats polling
showAlert('success', response.message); showAlert('success', response.message);
closeAllModals(); // Close all modals after successful deployment
hideStatusIndicator(); hideStatusIndicator();
} }
@ -334,12 +328,9 @@ function handlePeerData(data, topicId, peer) {
// Delegate handling based on the response type // Delegate handling based on the response type
switch (response.type) { switch (response.type) {
case 'stats': case 'allStats':
console.log('[INFO] Updating container stats...'); console.log('[INFO] Received aggregated stats for all containers.');
const stats = response.data; response.data.forEach((stats) => updateContainerStats(stats));
stats.ip = stats.ip || 'No IP Assigned'; // Add a fallback for missing IPs
console.log(`[DEBUG] Passing stats to updateContainerStats: ${JSON.stringify(stats, null, 2)}`);
updateContainerStats(stats);
break; break;
case 'containers': case 'containers':
@ -421,7 +412,9 @@ function addConnection(topicHex) {
<div class="connection-item row align-items-center px-2 py-1 border-bottom bg-dark text-light"> <div class="connection-item row align-items-center px-2 py-1 border-bottom bg-dark text-light">
<!-- Connection Info --> <!-- Connection Info -->
<div class="col-8 connection-info text-truncate"> <div class="col-8 connection-info text-truncate">
<span class="topic-id d-block text-primary fw-bold" title="${topicId}">${topicId}</span> <span>
<span class="connection-status ${connections[topicId].peer ? 'status-connected' : 'status-disconnected'}"></span>${topicId}
</span>
</div> </div>
<!-- Action Buttons --> <!-- Action Buttons -->
<div class="col-4 d-flex justify-content-end"> <div class="col-4 d-flex justify-content-end">
@ -650,7 +643,7 @@ function resetConnectionsView() {
connectionItem.dataset.topicId = topicId; connectionItem.dataset.topicId = topicId;
connectionItem.innerHTML = ` connectionItem.innerHTML = `
<span> <span>
<span class="connection-status ${connections[topicId].peer ? 'status-connected' : 'status-disconnected'}"></span>${topicId} <span class="connection-status ${connections[topicId].peer ? 'status-connected' : 'status-disconnected'}"></span>
</span> </span>
<button class="btn btn-sm btn-danger disconnect-btn">Disconnect</button> <button class="btn btn-sm btn-danger disconnect-btn">Disconnect</button>
`; `;
@ -1102,7 +1095,7 @@ function showWelcomePage() {
} }
if (connectionTitle) { if (connectionTitle) {
connectionTitle.textContent = '󠀠'; connectionTitle.textContent = '';
} else { } else {
console.warn('[WARN] Connection title element not found!'); console.warn('[WARN] Connection title element not found!');
} }

View File

@ -254,7 +254,8 @@
#alert-container { #alert-container {
position: fixed; position: fixed;
bottom: 20px; bottom: 20px;
right: 20px; left: 50%;
transform: translateX(-50%);
z-index: 1055; z-index: 1055;
/* Ensure it overlays important elements only */ /* Ensure it overlays important elements only */
display: flex; display: flex;
@ -266,11 +267,23 @@
/* Prevent container from blocking clicks */ /* Prevent container from blocking clicks */
} }
.alert { #alert-container {
position: fixed;
bottom: 20px;
left: 50%;
transform: translateX(-50%);
z-index: 1055;
display: flex; display: flex;
flex-direction: column-reverse;
gap: 10px;
pointer-events: none;
/* Prevent container from blocking clicks */
}
.alert {
display: inline-flex;
align-items: center; align-items: center;
justify-content: space-between; justify-content: space-between;
max-width: 80%;
padding: 12px 20px; padding: 12px 20px;
background-color: #2b2b2b; background-color: #2b2b2b;
color: #e0e0e0; color: #e0e0e0;
@ -453,7 +466,7 @@
<div id="content"> <div id="content">
<div id="welcome-page"> <div id="welcome-page">
<h1>Welcome to Peartainer</h1> <h1>Welcome to peardock</h1>
<p class="mt-3">Easily manage your Docker containers across peer-to-peer connections.</p> <p class="mt-3">Easily manage your Docker containers across peer-to-peer connections.</p>
<p>To get started, add a connection using the form in the sidebar.</p> <p>To get started, add a connection using the form in the sidebar.</p>
<!-- <img src="https://via.placeholder.com/500x300" alt="Welcome Graphic" class="img-fluid mt-4"> --> <!-- <img src="https://via.placeholder.com/500x300" alt="Welcome Graphic" class="img-fluid mt-4"> -->
@ -673,6 +686,7 @@
<div id="alert-container" class="position-fixed top-0 start-50 translate-middle-x mt-3" <div id="alert-container" class="position-fixed top-0 start-50 translate-middle-x mt-3"
style="z-index: 1051; max-width: 90%;"></div> style="z-index: 1051; max-width: 90%;"></div>
<!-- xterm.js --> <!-- xterm.js -->
<script src="https://cdn.jsdelivr.net/npm/xterm/lib/xterm.js"></script> <script src="https://cdn.jsdelivr.net/npm/xterm/lib/xterm.js"></script>

View File

@ -4,16 +4,14 @@ const templateSearchInput = document.getElementById('template-search-input');
const templateDeployModal = new bootstrap.Modal(document.getElementById('templateDeployModalUnique')); const templateDeployModal = new bootstrap.Modal(document.getElementById('templateDeployModalUnique'));
const deployForm = document.getElementById('deploy-form'); const deployForm = document.getElementById('deploy-form');
let templates = []; let templates = [];
// Function to close all modals
function closeAllModals() { function closeAllModals() {
const modals = document.querySelectorAll('.modal.show'); // Find and hide all open modals
const modals = document.querySelectorAll('.modal.show'); // Adjust selector if necessary
modals.forEach(modal => { modals.forEach(modal => {
const modalInstance = bootstrap.Modal.getInstance(modal); const modalInstance = bootstrap.Modal.getInstance(modal); // Get Bootstrap modal instance
if (modalInstance) modalInstance.hide(); modalInstance.hide(); // Close the modal
}); });
} }
// Show status indicator // Show status indicator
function showStatusIndicator(message = 'Processing...') { function showStatusIndicator(message = 'Processing...') {
const statusIndicator = document.createElement('div'); const statusIndicator = document.createElement('div');
@ -156,7 +154,6 @@ function openDeployModal(template) {
templateDeployModal.show(); templateDeployModal.show();
} }
// Deploy Docker container
// Deploy Docker container // Deploy Docker container
async function deployDockerContainer(payload) { async function deployDockerContainer(payload) {
const { containerName, imageName, ports = [], volumes = [], envVars = [] } = payload; const { containerName, imageName, ports = [], volumes = [], envVars = [] } = payload;
@ -178,6 +175,18 @@ async function deployDockerContainer(payload) {
}); });
console.log('[INFO] Sending deployment command to the server...'); console.log('[INFO] Sending deployment command to the server...');
return new Promise((resolve, reject) => {
window.handlePeerResponse = (response) => {
if (response.success && response.message.includes('deployed successfully')) {
console.log('[INFO] Deployment response received:', response.message);
resolve(response); // Resolve with success
} else if (response.error) {
reject(new Error(response.error)); // Reject on error
}
};
// Send the deployment command
sendCommand('deployContainer', { sendCommand('deployContainer', {
containerName, containerName,
image: imageName, image: imageName,
@ -185,26 +194,21 @@ async function deployDockerContainer(payload) {
volumes: validVolumes, volumes: validVolumes,
env: envVars.map(({ name, value }) => ({ name, value })), env: envVars.map(({ name, value }) => ({ name, value })),
}); });
// Fallback timeout to avoid hanging indefinitely
setTimeout(() => {
reject(new Error('Deployment timed out. No response from server.'));
}, 30000); // Adjust timeout duration as needed
});
} }
// Example of how to dispatch the event when the server response is received
function handleServerResponse(serverResponse) {
console.log('[DEBUG] Dispatching server response:', serverResponse);
const responseEvent = new CustomEvent('responseReceived', { detail: serverResponse });
window.dispatchEvent(responseEvent);
}
// Integration for server response handling
// Ensure this function is called whenever a server response is received
async function processServerMessage(response) {
if (response.type === 'deployResult') {
handleServerResponse(response);
}
}
// Handle form submission for deployment
// Handle form submission for deployment // Handle form submission for deployment
deployForm.addEventListener('submit', async (e) => { deployForm.addEventListener('submit', async (e) => {
closeAllModals();
e.preventDefault(); e.preventDefault();
const containerName = document.getElementById('deploy-container-name').value.trim(); const containerName = document.getElementById('deploy-container-name').value.trim();
@ -223,22 +227,18 @@ deployForm.addEventListener('submit', async (e) => {
try { try {
showStatusIndicator('Deploying container...'); showStatusIndicator('Deploying container...');
closeAllModals(); // Wait for deployment to complete
// Send the deployment request const successResponse = await deployDockerContainer(deployPayload);
await deployDockerContainer(deployPayload);
// Wait for a specific response
// Wait for the specific response
console.log('[INFO] Deployment success:', successResponse);
hideStatusIndicator(); hideStatusIndicator();
showAlert('success', successResponse.message); // showAlert('success', successResponse.message);
} catch (error) { } catch (error) {
console.error('[ERROR] Failed to deploy container:', error.message); console.error('[ERROR] Failed to deploy container:', error.message);
hideStatusIndicator(); hideStatusIndicator();
closeAllModals();
showAlert('danger', error.message); showAlert('danger', error.message);
} }
}); });
// Initialize templates on load // Initialize templates on load
document.addEventListener('DOMContentLoaded', fetchTemplates); document.addEventListener('DOMContentLoaded', fetchTemplates);

View File

@ -1,8 +1,8 @@
{ {
"name": "peartainer", "name": "peardock",
"main": "index.html", "main": "index.html",
"pear": { "pear": {
"name": "peartainer", "name": "peardock",
"type": "desktop", "type": "desktop",
"gui": { "gui": {
"backgroundColor": "#1F2430", "backgroundColor": "#1F2430",

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 120 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 158 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 161 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 148 KiB

View File

@ -206,7 +206,9 @@ swarm.on('connection', (peer) => {
await docker.getContainer(parsedData.args.id).start(); await docker.getContainer(parsedData.args.id).start();
response = { success: true, message: `Container ${parsedData.args.id} started` }; response = { success: true, message: `Container ${parsedData.args.id} started` };
break; break;
// case 'allStats':
// await handleallStatsRequest(peer);
// return; // No further response needed
case 'stopContainer': case 'stopContainer':
console.log(`[INFO] Handling 'stopContainer' command for container: ${parsedData.args.id}`); console.log(`[INFO] Handling 'stopContainer' command for container: ${parsedData.args.id}`);
await docker.getContainer(parsedData.args.id).stop(); await docker.getContainer(parsedData.args.id).stop();
@ -528,44 +530,6 @@ docker.listContainers({ all: true }, async (err, containers) => {
ipAddress = networks[0].IPAddress; // Use the first network's IP ipAddress = networks[0].IPAddress; // Use the first network's IP
} }
} }
// Start streaming container stats
container.stats({ stream: true }, (statsErr, stream) => {
if (statsErr) {
console.error(`[ERROR] Failed to get stats for container ${containerInfo.Id}: ${statsErr.message}`);
return;
}
stream.on('data', (data) => {
try {
const stats = JSON.parse(data.toString());
const cpuUsage = calculateCPUPercent(stats);
const memoryUsage = stats.memory_stats.usage || 0; // Default to 0 if undefined
const statsData = {
id: containerInfo.Id,
cpu: cpuUsage,
memory: memoryUsage,
ip: ipAddress, // Use the pre-inspected IP address
};
// Broadcast stats to all connected peers
for (const peer of connectedPeers) {
peer.write(JSON.stringify({ type: 'stats', data: statsData }));
}
} catch (parseErr) {
// console.error(`[ERROR] Failed to parse stats for container ${containerInfo.Id}: ${parseErr.message}`);
}
});
stream.on('error', (streamErr) => {
// console.error(`[ERROR] Stats stream error for container ${containerInfo.Id}: ${streamErr.message}`);
});
stream.on('close', () => {
// console.log(`[INFO] Stats stream closed for container ${containerInfo.Id}`);
});
});
}); });
}); });
}); });
@ -672,63 +636,95 @@ function handleKillTerminal(containerId, peer) {
} }
} }
function streamContainerStats(containerInfo) { async function collectContainerStats(containerStats) {
const currentContainers = await docker.listContainers({ all: true });
const currentIds = currentContainers.map((c) => c.Id);
// Collect stats for all containers, including newly added ones
for (const containerInfo of currentContainers) {
if (!containerStats[containerInfo.Id]) {
console.log(`[INFO] Found new container: ${containerInfo.Names[0]?.replace(/^\//, '')}`);
containerStats[containerInfo.Id] = await initializeContainerStats(containerInfo);
}
}
// Remove containers that no longer exist
Object.keys(containerStats).forEach((id) => {
if (!currentIds.includes(id)) {
console.log(`[INFO] Removing stats tracking for container: ${id}`);
delete containerStats[id];
}
});
return containerStats;
}
async function initializeContainerStats(containerInfo) {
const container = docker.getContainer(containerInfo.Id); const container = docker.getContainer(containerInfo.Id);
// Use the same logic as listContainers to get the IP address // Inspect container for IP address
container.inspect((inspectErr, details) => { let ipAddress = 'No IP Assigned';
let ipAddress = 'No IP Assigned'; // Default IP address fallback
if (inspectErr) {
console.error(`[ERROR] Failed to inspect container ${containerInfo.Id}: ${inspectErr.message}`);
} else if (details.NetworkSettings && details.NetworkSettings.Networks) {
const networks = Object.values(details.NetworkSettings.Networks);
if (networks.length > 0 && networks[0].IPAddress) {
ipAddress = networks[0].IPAddress; // Retrieve the first network's IP address
}
}
// Start streaming container stats
container.stats({ stream: true }, (statsErr, stream) => {
if (statsErr) {
console.error(`[ERROR] Failed to get stats for container ${containerInfo.Id}: ${statsErr.message}`);
return;
}
stream.on('data', (data) => {
try { try {
const stats = JSON.parse(data.toString()); const details = await container.inspect();
const cpuUsage = calculateCPUPercent(stats); const networks = details.NetworkSettings?.Networks || {};
const memoryUsage = stats.memory_stats.usage || 0; // Default to 0 if undefined ipAddress = Object.values(networks)[0]?.IPAddress || 'No IP Assigned';
} catch (err) {
console.error(`[ERROR] Failed to inspect container ${containerInfo.Id}: ${err.message}`);
}
// Use the extracted IP address in the stats data
const statsData = { const statsData = {
id: containerInfo.Id, id: containerInfo.Id,
cpu: cpuUsage, name: containerInfo.Names[0]?.replace(/^\//, '') || 'Unknown',
memory: memoryUsage, cpu: 0,
ip: ipAddress, // Use the IP address retrieved during inspection memory: 0,
ip: ipAddress,
}; };
// Broadcast stats to all connected peers // Start streaming stats for the container
for (const peer of connectedPeers) { try {
peer.write(JSON.stringify({ type: 'stats', data: statsData })); const statsStream = await container.stats({ stream: true });
} statsStream.on('data', (data) => {
} catch (parseErr) { try {
console.error(`[ERROR] Failed to parse stats for container ${containerInfo.Id}: ${parseErr.message}`); const stats = JSON.parse(data.toString());
statsData.cpu = calculateCPUPercent(stats);
statsData.memory = stats.memory_stats.usage || 0;
} catch (err) {
console.error(`[ERROR] Failed to parse stats for container ${containerInfo.Id}: ${err.message}`);
} }
}); });
stream.on('error', (streamErr) => { statsStream.on('error', (err) => {
console.error(`[ERROR] Stats stream error for container ${containerInfo.Id}: ${streamErr.message}`); console.error(`[ERROR] Stats stream error for container ${containerInfo.Id}: ${err.message}`);
}); });
stream.on('close', () => { statsStream.on('close', () => {
console.log(`[INFO] Stats stream closed for container ${containerInfo.Id}`); console.log(`[INFO] Stats stream closed for container ${containerInfo.Id}`);
}); });
}); } catch (err) {
}); console.error(`[ERROR] Failed to start stats stream for container ${containerInfo.Id}: ${err.message}`);
} }
return statsData;
}
async function handleStatsBroadcast() {
const containerStats = {};
// Periodically update stats and broadcast
setInterval(async () => {
await collectContainerStats(containerStats);
const aggregatedStats = Object.values(containerStats);
const response = { type: 'allStats', data: aggregatedStats };
for (const peer of connectedPeers) {
peer.write(JSON.stringify(response));
}
}, 1000); // Send stats every 500ms
}
// Start the stats broadcast
handleStatsBroadcast();
// Handle process termination // Handle process termination