297 lines
12 KiB
JavaScript
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 • ${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 →</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();
|
|
});
|