- 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.
138 lines
6.0 KiB
JavaScript
138 lines
6.0 KiB
JavaScript
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,'>'); }
|