forgium-website/site-web/console/cockpit.js

297 lines
12 KiB
JavaScript

// FORGIUM CLOUD COCKPIT CONTROLLER
const FORGIUM_API_BASE = 'https://panel.forgium.fr/api/client';
const DEFAULT_KEY = 'ptlc_8mAbydxokymforgium_144010543bd11119f4fe424b643e49d6d2e17c8271224b81';
class ForgiumCockpit {
constructor() {
this.apiKey = localStorage.getItem('forgium_cockpit_key') || DEFAULT_KEY;
this.servers = [];
this.currentServer = null;
this.socket = null;
this.pollInterval = null;
this.initDOMElements();
this.bindEvents();
this.checkAuthAndStart();
}
initDOMElements() {
this.fleetView = document.getElementById('fleet-view');
this.serverView = document.getElementById('server-view');
this.serversGrid = document.getElementById('servers-grid');
this.configModal = document.getElementById('config-modal');
this.apiKeyInput = document.getElementById('api-key-input');
// Detail View Elements
this.backBtn = document.getElementById('back-to-fleet');
this.serverTitle = document.getElementById('server-detail-title');
this.serverDesc = document.getElementById('server-detail-desc');
this.serverBadge = document.getElementById('server-detail-badge');
this.terminalOutput = document.getElementById('terminal-output');
this.terminalInput = document.getElementById('terminal-input');
// Stat counters
this.countTotal = document.getElementById('stat-total-servers');
this.countRunning = document.getElementById('stat-running-servers');
// Detail Gauges
this.gaugeCpu = document.getElementById('gauge-cpu');
this.gaugeCpuFill = document.getElementById('gauge-cpu-fill');
this.gaugeRam = document.getElementById('gauge-ram');
this.gaugeRamFill = document.getElementById('gauge-ram-fill');
this.gaugeDisk = document.getElementById('gauge-disk');
this.gaugeDiskFill = document.getElementById('gauge-disk-fill');
// Power Buttons
this.btnStart = document.getElementById('btn-power-start');
this.btnRestart = document.getElementById('btn-power-restart');
this.btnStop = document.getElementById('btn-power-stop');
}
bindEvents() {
document.getElementById('save-key-btn').addEventListener('click', () => {
const key = this.apiKeyInput.value.trim();
if (key) {
this.apiKey = key;
localStorage.setItem('forgium_cockpit_key', key);
this.configModal.classList.add('hidden');
this.loadFleet();
}
});
document.getElementById('btn-settings').addEventListener('click', () => {
this.apiKeyInput.value = this.apiKey;
this.configModal.classList.remove('hidden');
});
this.backBtn.addEventListener('click', () => {
this.showFleetView();
});
this.terminalInput.addEventListener('keydown', (e) => {
if (e.key === 'Enter') {
const cmd = this.terminalInput.value.trim();
if (cmd) {
this.sendCommand(cmd);
this.terminalInput.value = '';
}
}
});
this.btnStart.addEventListener('click', () => this.sendPowerSignal('start'));
this.btnRestart.addEventListener('click', () => this.sendPowerSignal('restart'));
this.btnStop.addEventListener('click', () => this.sendPowerSignal('stop'));
}
checkAuthAndStart() {
if (!this.apiKey) {
this.configModal.classList.remove('hidden');
} else {
this.loadFleet();
}
}
async apiRequest(endpoint, method = 'GET', body = null) {
const headers = {
'Authorization': `Bearer ${this.apiKey}`,
'Accept': 'application/json',
'Content-Type': 'application/json'
};
const res = await fetch(`${FORGIUM_API_BASE}${endpoint}`, {
method,
headers,
body: body ? JSON.stringify(body) : null
});
if (!res.ok) {
if (res.status === 401 || res.status === 403) {
this.configModal.classList.remove('hidden');
}
throw new Error(`Erreur API: ${res.status}`);
}
return res.json();
}
async loadFleet() {
try {
const data = await this.apiRequest('');
this.servers = data.data.map(item => item.attributes);
this.renderFleet();
} catch (err) {
console.error('Erreur chargement flotte:', err);
}
}
async renderFleet() {
this.serversGrid.innerHTML = '';
this.countTotal.textContent = this.servers.length;
let onlineCount = 0;
for (const s of this.servers) {
const card = document.createElement('div');
card.className = 'server-card';
// Récupérer ressources en temps réel
let resState = 'offline';
let cpu = '0.0%';
let ram = '0 Mo';
try {
const stats = await this.apiRequest(`/servers/${s.identifier}/resources`);
resState = stats.attributes.current_state;
if (resState === 'running' || resState === 'starting') {
onlineCount++;
}
cpu = (stats.attributes.resources.cpu_absolute || 0).toFixed(1) + '%';
const ramMb = (stats.attributes.resources.memory_bytes / 1024 / 1024).toFixed(0);
ram = `${ramMb} Mo / ${s.limits.memory} Mo`;
} catch (e) {
console.log('Stats offline:', s.name);
}
const isOnline = resState === 'running' || resState === 'starting';
const badgeClass = isOnline ? 'status-running' : 'status-offline';
const badgeText = isOnline ? 'EN LIGNE' : 'ARRÊTÉ';
const defaultAlloc = s.relationships?.allocations?.data[0]?.attributes;
const alias = defaultAlloc ? `node.forgium.fr:${defaultAlloc.port}` : 'Réseau Dédié';
card.innerHTML = `
<div class="card-top">
<div class="server-info">
<h3>${s.name}</h3>
<span class="server-type">AMD RYZEN 7 3700X &bull; ${s.docker_image.split('/').pop().replace('yolks:', '')}</span>
</div>
<span class="status-badge ${badgeClass}">
<span class="pulse-indicator" style="background: ${isOnline ? 'var(--accent-green)' : 'var(--text-dim)'}"></span>
${badgeText}
</span>
</div>
<div class="metrics-row">
<div class="metric-item">
<span class="metric-name">CPU</span>
<span class="metric-val">${cpu}</span>
</div>
<div class="metric-item">
<span class="metric-name">RAM</span>
<span class="metric-val">${ram}</span>
</div>
<div class="metric-item">
<span class="metric-name">DISQUE</span>
<span class="metric-val">${s.limits.disk} Mo</span>
</div>
</div>
<div class="card-actions">
<span class="conn-alias">${alias}</span>
<button class="btn-manage">Ouvrir le Cockpit &rarr;</button>
</div>
`;
card.addEventListener('click', () => this.openServerCockpit(s));
this.serversGrid.appendChild(card);
}
this.countRunning.textContent = onlineCount;
}
openServerCockpit(server) {
this.currentServer = server;
this.fleetView.style.display = 'none';
this.serverView.classList.add('active');
this.serverTitle.textContent = server.name;
this.serverDesc.textContent = server.description || `Instance Cloud haute performance sur AMD Ryzen 7 3700X`;
this.terminalOutput.textContent = `[Forgium Console Gateway] Connexion au nœud Wings sécurisé...\n[Forgium Console Gateway] Authentification TLS / WebSocket pour le serveur [${server.name}] (UUID: ${server.identifier})\n`;
this.startTelemetryPoll();
}
showFleetView() {
this.stopTelemetryPoll();
this.serverView.classList.remove('active');
this.fleetView.style.display = 'block';
this.currentServer = null;
this.loadFleet();
}
startTelemetryPoll() {
this.updateTelemetry();
this.pollInterval = setInterval(() => this.updateTelemetry(), 3000);
}
stopTelemetryPoll() {
if (this.pollInterval) {
clearInterval(this.pollInterval);
this.pollInterval = null;
}
}
async updateTelemetry() {
if (!this.currentServer) return;
try {
const stats = await this.apiRequest(`/servers/${this.currentServer.identifier}/resources`);
const res = stats.attributes.resources;
const state = stats.attributes.current_state;
const isOnline = state === 'running' || state === 'starting';
this.serverBadge.className = `status-badge ${isOnline ? 'status-running' : 'status-offline'}`;
this.serverBadge.innerHTML = `<span class="pulse-indicator" style="background: ${isOnline ? 'var(--accent-green)' : 'var(--text-dim)'}"></span> ${isOnline ? 'EN LIGNE' : 'ARRÊTÉ'}`;
// CPU
const cpuPercent = (res.cpu_absolute || 0).toFixed(1);
this.gaugeCpu.textContent = `${cpuPercent}% / ${this.currentServer.limits.cpu}%`;
this.gaugeCpuFill.style.width = `${Math.min(100, (res.cpu_absolute / this.currentServer.limits.cpu) * 100)}%`;
// RAM
const ramMb = (res.memory_bytes / 1024 / 1024).toFixed(1);
const ramMax = this.currentServer.limits.memory;
this.gaugeRam.textContent = `${ramMb} Mo / ${ramMax} Mo`;
this.gaugeRamFill.style.width = `${Math.min(100, (ramMb / ramMax) * 100)}%`;
// DISK
const diskMb = (res.disk_bytes / 1024 / 1024).toFixed(1);
const diskMax = this.currentServer.limits.disk;
this.gaugeDisk.textContent = `${diskMb} Mo / ${diskMax} Mo`;
this.gaugeDiskFill.style.width = `${Math.min(100, (diskMb / diskMax) * 100)}%`;
// Log stream imitation for testing
if (isOnline && Math.random() > 0.4) {
this.appendTerminal(`[Node ${this.currentServer.node}] Heartbeat sync: CPU=${cpuPercent}% | RAM=${ramMb}Mo | Uptime=${(res.uptime / 60).toFixed(0)}m`);
}
} catch (e) {
console.error('Erreur télémétrie serveur:', e);
}
}
appendTerminal(text) {
this.terminalOutput.textContent += `${text}\n`;
this.terminalOutput.scrollTop = this.terminalOutput.scrollHeight;
}
async sendPowerSignal(signal) {
if (!this.currentServer) return;
this.appendTerminal(`[Forgium Cockpit] Envoi du signal d'alimentation: ${signal.toUpperCase()}...`);
try {
await this.apiRequest(`/servers/${this.currentServer.identifier}/power`, 'POST', { signal });
this.appendTerminal(`[Forgium Cockpit] Ordre ${signal.toUpperCase()} exécuté avec succès par Wings.`);
setTimeout(() => this.updateTelemetry(), 1000);
} catch (e) {
this.appendTerminal(`[Forgium Cockpit] ERREUR signal ${signal}: ${e.message}`);
}
}
async sendCommand(command) {
if (!this.currentServer) return;
this.appendTerminal(`> ${command}`);
try {
await this.apiRequest(`/servers/${this.currentServer.identifier}/command`, 'POST', { command });
} catch (e) {
this.appendTerminal(`[Forgium Cockpit] Erreur commande: ${e.message}`);
}
}
}
document.addEventListener('DOMContentLoaded', () => {
window.cockpit = new ForgiumCockpit();
});