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.
This commit is contained in:
137
dashboard/js/pages/logs.js
Normal file
137
dashboard/js/pages/logs.js
Normal file
@@ -0,0 +1,137 @@
|
||||
import { deploys as deploysApi, createLogSocket } from '../api.js';
|
||||
import { icons } from '../icons.js';
|
||||
|
||||
export function renderLogs(container) {
|
||||
let deploysList = [];
|
||||
let selectedDeploy = null;
|
||||
let logWs = null;
|
||||
|
||||
container.innerHTML = `
|
||||
<div class="page-header"><div><h2>Log Deploy</h2><p>Storico di tutti i deploy con i relativi log</p></div></div>
|
||||
<div style="display:grid;grid-template-columns:350px 1fr;gap:16px;min-height:500px">
|
||||
<div class="card" style="overflow:auto;max-height:70vh" id="deploy-list">
|
||||
<h3 class="card-title mb-4">${icons.scrollText(16)} Deploy Recenti</h3>
|
||||
<p class="text-muted">Caricamento...</p>
|
||||
</div>
|
||||
<div class="card" id="log-panel">
|
||||
<div class="empty-state">${icons.scrollText(32)}<h3 class="mt-4">Seleziona un deploy</h3><p>Clicca su un deploy nella lista per visualizzarne i log</p></div>
|
||||
</div>
|
||||
</div>`;
|
||||
|
||||
async function load() {
|
||||
try {
|
||||
deploysList = await deploysApi.list();
|
||||
renderList();
|
||||
} catch (err) {
|
||||
document.getElementById('deploy-list').innerHTML = `<p class="text-muted text-sm">Errore: ${err.message}</p>`;
|
||||
}
|
||||
}
|
||||
|
||||
function renderList() {
|
||||
const el = document.getElementById('deploy-list');
|
||||
el.innerHTML = `<h3 class="card-title mb-4">${icons.scrollText(16)} Deploy Recenti</h3>` +
|
||||
(deploysList.length === 0 ? '<p class="text-muted text-sm">Nessun deploy trovato</p>' :
|
||||
`<div class="flex flex-col gap-2">${deploysList.map(d => `
|
||||
<div class="card deploy-item" data-id="${d.id}" style="cursor:pointer;padding:12px 14px;background:${selectedDeploy?.id === d.id ? 'var(--accent-muted)' : 'var(--bg-glass)'};border-color:${selectedDeploy?.id === d.id ? 'rgba(99,102,241,0.3)' : 'var(--border-primary)'}">
|
||||
<div class="flex items-center justify-between mb-2">
|
||||
<span class="font-bold text-sm">${d.service_name}</span>
|
||||
<span class="status-badge ${d.status}">${d.status}</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-3 text-xs text-muted">
|
||||
${d.commit_sha ? `<span class="text-mono">${icons.gitCommit(10)} ${d.commit_sha}</span>` : ''}
|
||||
<span>${icons.clock(10)} ${new Date(d.created_at).toLocaleString('it-IT')}</span>
|
||||
</div>
|
||||
${d.commit_message ? `<p class="text-xs text-muted mt-1 truncate">${esc(d.commit_message)}</p>` : ''}
|
||||
</div>`).join('')}</div>`);
|
||||
|
||||
el.querySelectorAll('.deploy-item').forEach(item => {
|
||||
item.onclick = () => {
|
||||
const did = parseInt(item.dataset.id);
|
||||
selectedDeploy = deploysList.find(d => d.id === did);
|
||||
renderList();
|
||||
renderLogPanel();
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
function renderLogPanel() {
|
||||
const panel = document.getElementById('log-panel');
|
||||
if (!selectedDeploy) {
|
||||
panel.innerHTML = `<div class="empty-state">${icons.scrollText(32)}<h3 class="mt-4">Seleziona un deploy</h3><p>Clicca su un deploy nella lista per visualizzarne i log</p></div>`;
|
||||
return;
|
||||
}
|
||||
|
||||
if (logWs) { logWs.close(); logWs = null; }
|
||||
|
||||
const d = selectedDeploy;
|
||||
panel.innerHTML = `
|
||||
<div class="card-header">
|
||||
<h3 class="card-title">Log: ${d.service_name}</h3>
|
||||
<span class="status-badge ${d.status}">${d.status}</span>
|
||||
</div>
|
||||
<div class="flex gap-4 text-xs text-muted mb-4">
|
||||
<span>${icons.user(10)} ${d.commit_author || d.trigger}</span>
|
||||
<span>${icons.gitCommit(10)} ${d.commit_sha || '–'}</span>
|
||||
${d.duration_ms > 0 ? `<span>${icons.clock(10)} ${(d.duration_ms / 1000).toFixed(1)}s</span>` : ''}
|
||||
</div>
|
||||
<div class="flex items-center justify-between mb-2">
|
||||
<span class="text-xs text-muted" id="lv-count">0 righe</span>
|
||||
<div class="flex gap-2">
|
||||
<button class="btn btn-secondary btn-sm" id="lv-pause">${icons.pause(12)} Pause</button>
|
||||
<button class="btn btn-secondary btn-sm" id="lv-download">${icons.download(12)} Download</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="log-viewer" id="lv-container"><div class="log-line info">In attesa di log...</div></div>`;
|
||||
|
||||
let lines = [];
|
||||
let paused = false;
|
||||
const lv = panel.querySelector('#lv-container');
|
||||
|
||||
logWs = createLogSocket(`deploy:${d.id}`);
|
||||
logWs.onmessage = (event) => {
|
||||
try {
|
||||
const msg = JSON.parse(event.data);
|
||||
if (msg.type === 'log' || msg.type === 'info' || msg.type === 'error') {
|
||||
lines.push(msg);
|
||||
if (lines.length > 2000) lines = lines.slice(-1500);
|
||||
if (lv.firstChild?.classList?.contains('info') && lines.length === 1) lv.innerHTML = '';
|
||||
const div = document.createElement('div');
|
||||
div.className = `log-line ${msg.type}`;
|
||||
div.textContent = msg.data;
|
||||
lv.appendChild(div);
|
||||
panel.querySelector('#lv-count').textContent = `${lines.length} righe`;
|
||||
if (!paused) lv.scrollTop = lv.scrollHeight;
|
||||
}
|
||||
} catch {}
|
||||
};
|
||||
logWs.onerror = () => appendLine('error', 'WebSocket connection error');
|
||||
logWs.onclose = () => appendLine('info', '— Stream ended —');
|
||||
|
||||
function appendLine(type, text) {
|
||||
lines.push({ type, data: text });
|
||||
const div = document.createElement('div');
|
||||
div.className = `log-line ${type}`;
|
||||
div.textContent = text;
|
||||
lv.appendChild(div);
|
||||
if (!paused) lv.scrollTop = lv.scrollHeight;
|
||||
}
|
||||
|
||||
panel.querySelector('#lv-pause').onclick = () => {
|
||||
paused = !paused;
|
||||
panel.querySelector('#lv-pause').innerHTML = paused ? `${icons.play(12)} Resume` : `${icons.pause(12)} Pause`;
|
||||
};
|
||||
panel.querySelector('#lv-download').onclick = () => {
|
||||
const blob = new Blob([lines.map(l => l.data).join('\n')], { type: 'text/plain' });
|
||||
const a = document.createElement('a');
|
||||
a.href = URL.createObjectURL(blob);
|
||||
a.download = `logs-deploy-${d.id}-${Date.now()}.txt`;
|
||||
a.click();
|
||||
URL.revokeObjectURL(a.href);
|
||||
};
|
||||
}
|
||||
|
||||
load();
|
||||
return () => { if (logWs) logWs.close(); };
|
||||
}
|
||||
|
||||
function esc(s) { return String(s).replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>'); }
|
||||
Reference in New Issue
Block a user