This repository has been archived on 2026-05-11. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
autodeployer-old-version/dashboard/js/pages/dashboard.js
Giuseppe Raffa 87d698bc5c feat: add Docker and Gitea services, monitoring, queue, and Telegram notification functionalities
- Implemented Docker operations including image building, container management, and resource stats.
- Added Gitea API client for repository management and webhook handling.
- Introduced monitoring service to collect and store container metrics in InfluxDB.
- Created a queue system using BullMQ for managing deployment jobs with real-time log streaming.
- Developed Telegram notification service for deployment status updates.
- Added Traefik label generation for dynamic reverse proxy configuration.
- Implemented WebSocket endpoints for log streaming and terminal access to containers.
- Created an updater sidecar for self-updating the AutoDeployer container.
2026-04-13 23:23:18 +02:00

145 lines
7.2 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { services as api } from '../api.js';
import { icons } from '../icons.js';
import { navigate } from '../router.js';
function formatTime(dateStr) {
if (!dateStr) return '';
const d = new Date(dateStr), diff = Math.floor((Date.now() - d) / 60000);
if (diff < 1) return 'ora';
if (diff < 60) return `${diff}m fa`;
const h = Math.floor(diff / 60);
if (h < 24) return `${h}h fa`;
return `${Math.floor(h / 24)}g fa`;
}
function serviceCardHTML(s) {
const status = s.container?.status || s.status || 'stopped';
const repo = s.gitea_repo_url?.replace(/https?:\/\/[^/]+\//, '').replace('.git', '') || '';
const ld = s.last_deploy;
return `
<div class="card service-card" data-id="${s.id}">
<div class="service-card-header">
<div>
<div class="service-card-name">${s.name} <span class="status-badge ${status}">${status}</span></div>
${s.description ? `<p class="text-xs text-muted mt-2" style="max-width:260px">${s.description}</p>` : ''}
</div>
</div>
<div class="service-card-repo">${icons.gitBranch(12)} <span class="truncate">${repo}</span> <span style="color:var(--accent)">:${s.gitea_branch}</span></div>
${s.traefik_domain ? `<div class="service-card-repo mt-2">${icons.globe(12)} <span>${s.traefik_domain}</span></div>` : ''}
<div class="service-card-meta">
<div class="service-card-meta-item">${icons.clock(12)} ${ld ? `${ld.status === 'success' ? '✅' : ld.status === 'failed' ? '❌' : '⏳'} ${formatTime(ld.created_at)}` : 'Mai deployato'}</div>
${ld?.commit_sha ? `<div class="service-card-meta-item text-mono">${ld.commit_sha}</div>` : ''}
</div>
<div class="service-card-actions">
<button class="btn btn-primary btn-sm btn-deploy" data-id="${s.id}" ${status === 'building' ? 'disabled' : ''}>${icons.play(14)} Deploy</button>
${status === 'running' || status === 'error' ? `<button class="btn btn-danger btn-sm btn-stop" data-id="${s.id}">${icons.square(14)} Stop</button>` : ''}
</div>
</div>`;
}
export function renderDashboard(container) {
let interval;
container.innerHTML = `
<div class="page-header">
<div><h2>Dashboard</h2><p>Gestisci i tuoi servizi</p></div>
<div class="flex gap-2">
<button class="btn btn-secondary" id="refresh-btn">${icons.refreshCw(16)} Refresh</button>
<button class="btn btn-primary" id="create-btn">${icons.plus(16)} Nuovo Servizio</button>
</div>
</div>
<div id="services-grid" class="card-grid"></div>
<div id="create-modal"></div>`;
async function load() {
const grid = document.getElementById('services-grid');
try {
const list = await api.list();
if (list.length === 0) {
grid.innerHTML = `<div class="empty-state"><h3>Nessun servizio configurato</h3><p>Crea il tuo primo servizio per iniziare</p></div>`;
return;
}
grid.innerHTML = list.map(serviceCardHTML).join('');
// Events
grid.querySelectorAll('.service-card').forEach(el => {
el.onclick = (e) => { if (!e.target.closest('button')) navigate(`/services/${el.dataset.id}`); };
});
grid.querySelectorAll('.btn-deploy').forEach(b => {
b.onclick = async (e) => { e.stopPropagation(); await api.deploy(b.dataset.id); load(); };
});
grid.querySelectorAll('.btn-stop').forEach(b => {
b.onclick = async (e) => { e.stopPropagation(); await api.stop(b.dataset.id); load(); };
});
} catch (err) { grid.innerHTML = `<div class="empty-state"><p>Errore: ${err.message}</p></div>`; }
}
document.getElementById('refresh-btn').onclick = load;
document.getElementById('create-btn').onclick = () => showCreateModal(load);
load();
interval = setInterval(load, 10000);
return () => clearInterval(interval);
}
function showCreateModal(onCreated) {
const modal = document.getElementById('create-modal');
modal.innerHTML = `
<div class="modal-overlay" id="modal-overlay">
<div class="modal" style="max-width:700px" onclick="event.stopPropagation()">
<div class="modal-header"><h3 class="modal-title">Nuovo Servizio</h3><button class="btn btn-secondary btn-sm" id="modal-close">✕</button></div>
<div id="modal-error" class="login-error hidden"></div>
<form id="create-form">
<div class="form-row">
<div class="form-group"><label class="form-label">Nome Servizio</label><input class="form-input" id="cf-name" placeholder="api-service" required></div>
<div class="form-group"><label class="form-label">Container Name</label><input class="form-input mono" id="cf-container" placeholder="api-service" required></div>
</div>
<div class="form-group"><label class="form-label">Descrizione</label><input class="form-input" id="cf-desc" placeholder="Opzionale"></div>
<div class="form-row">
<div class="form-group"><label class="form-label">Gitea Repository URL</label><input class="form-input mono" id="cf-repo" value="http://gitea:3000/" required></div>
<div class="form-group"><label class="form-label">Branch</label><input class="form-input mono" id="cf-branch" value="main"></div>
</div>
<div class="form-row">
<div class="form-group"><label class="form-label">Container Port</label><input type="number" class="form-input" id="cf-port" value="3000"></div>
<div class="form-group"><label class="form-label">Dominio Traefik</label><input class="form-input mono" id="cf-domain" placeholder="api.mebboat.it"></div>
</div>
<div class="flex gap-2 mt-4" style="justify-content:flex-end">
<button type="button" class="btn btn-secondary" id="modal-cancel">Annulla</button>
<button type="submit" class="btn btn-primary" id="modal-submit">Crea Servizio</button>
</div>
</form>
</div>
</div>`;
const close = () => { modal.innerHTML = ''; };
document.getElementById('modal-overlay').onclick = close;
document.getElementById('modal-close').onclick = close;
document.getElementById('modal-cancel').onclick = close;
document.getElementById('cf-name').oninput = (e) => {
document.getElementById('cf-container').value = e.target.value.toLowerCase().replace(/[^a-z0-9-]/g, '-');
};
document.getElementById('create-form').onsubmit = async (e) => {
e.preventDefault();
const errEl = document.getElementById('modal-error');
errEl.classList.add('hidden');
try {
const s = await api.create({
name: document.getElementById('cf-name').value,
container_name: document.getElementById('cf-container').value,
description: document.getElementById('cf-desc').value,
gitea_repo_url: document.getElementById('cf-repo').value,
gitea_branch: document.getElementById('cf-branch').value,
container_port: parseInt(document.getElementById('cf-port').value),
traefik_domain: document.getElementById('cf-domain').value,
traefik_enabled: true, traefik_tls_resolver: 'cloudflare',
traefik_network: 'meb-public', networks: ['meb-public'],
});
close();
onCreated();
navigate(`/services/${s.id}`);
} catch (err) {
errEl.textContent = err.message;
errEl.classList.remove('hidden');
}
};
}