A previous `git pull --rebase origin main` dropped 177 local commits,
losing 3400+ files across admin-v2, backend, studio-v2, website,
klausur-service, and many other services. The partial restore attempt
(660295e2) only recovered some files.
This commit restores all missing files from pre-rebase ref 98933f5e
while preserving post-rebase additions (night-scheduler, night-mode UI,
NightModeWidget dashboard integration).
Restored features include:
- AI Module Sidebar (FAB), OCR Labeling, OCR Compare
- GPU Dashboard, RAG Pipeline, Magic Help
- Klausur-Korrektur (8 files), Abitur-Archiv (5+ files)
- Companion, Zeugnisse-Crawler, Screen Flow
- Full backend, studio-v2, website, klausur-service
- All compliance SDKs, agent-core, voice-service
- CI/CD configs, documentation, scripts
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
809 lines
21 KiB
Python
809 lines
21 KiB
Python
"""
|
|
BreakPilot Studio - Mac Mini Control Module
|
|
|
|
Funktionen:
|
|
- Fernsteuerung des Mac Mini Servers
|
|
- Status-Uebersicht (Ping, SSH, Docker, Ollama)
|
|
- Docker Container Management
|
|
- Ollama Modell-Downloads mit Fortschrittsanzeige
|
|
- Power Management (Wake-on-LAN, Restart, Shutdown)
|
|
"""
|
|
|
|
|
|
class MacMiniControlModule:
|
|
"""Modul fuer die Mac Mini Server-Steuerung."""
|
|
|
|
@staticmethod
|
|
def get_css() -> str:
|
|
"""CSS fuer das Mac Mini Control Modul."""
|
|
return """
|
|
/* =============================================
|
|
MAC MINI CONTROL MODULE
|
|
============================================= */
|
|
|
|
.panel-mac-mini {
|
|
display: flex;
|
|
flex-direction: column;
|
|
height: 100%;
|
|
background: var(--bp-bg);
|
|
overflow-y: auto;
|
|
}
|
|
|
|
.mac-mini-header {
|
|
padding: 32px 40px 24px;
|
|
background: var(--bp-surface);
|
|
border-bottom: 1px solid var(--bp-border);
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
}
|
|
|
|
.mac-mini-header h1 {
|
|
font-size: 28px;
|
|
font-weight: 700;
|
|
color: var(--bp-text);
|
|
margin: 0;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 12px;
|
|
}
|
|
|
|
.mac-mini-status-badge {
|
|
padding: 6px 16px;
|
|
border-radius: 20px;
|
|
font-size: 14px;
|
|
font-weight: 600;
|
|
}
|
|
|
|
.mac-mini-status-badge.online {
|
|
background: rgba(34, 197, 94, 0.2);
|
|
color: #22c55e;
|
|
border: 1px solid #22c55e;
|
|
}
|
|
|
|
.mac-mini-status-badge.offline {
|
|
background: rgba(239, 68, 68, 0.2);
|
|
color: #ef4444;
|
|
border: 1px solid #ef4444;
|
|
}
|
|
|
|
.mac-mini-status-badge.checking {
|
|
background: rgba(251, 191, 36, 0.2);
|
|
color: #fbbf24;
|
|
border: 1px solid #fbbf24;
|
|
}
|
|
|
|
.mac-mini-content {
|
|
padding: 32px 40px;
|
|
flex: 1;
|
|
}
|
|
|
|
.mac-mini-controls {
|
|
display: flex;
|
|
gap: 12px;
|
|
margin-bottom: 24px;
|
|
flex-wrap: wrap;
|
|
}
|
|
|
|
.mac-mini-controls .btn {
|
|
padding: 10px 20px;
|
|
border-radius: 8px;
|
|
font-weight: 600;
|
|
cursor: pointer;
|
|
border: none;
|
|
transition: all 0.2s;
|
|
}
|
|
|
|
.mac-mini-controls .btn:hover {
|
|
transform: translateY(-2px);
|
|
box-shadow: 0 4px 12px rgba(0,0,0,0.2);
|
|
}
|
|
|
|
.mac-mini-controls .btn-wake {
|
|
background: #22c55e;
|
|
color: white;
|
|
}
|
|
|
|
.mac-mini-controls .btn-restart {
|
|
background: #f59e0b;
|
|
color: white;
|
|
}
|
|
|
|
.mac-mini-controls .btn-shutdown {
|
|
background: #ef4444;
|
|
color: white;
|
|
}
|
|
|
|
.mac-mini-controls .btn-refresh {
|
|
background: var(--bp-surface-elevated);
|
|
color: var(--bp-text);
|
|
border: 1px solid var(--bp-border);
|
|
}
|
|
|
|
.mac-mini-grid {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
|
|
gap: 20px;
|
|
margin-bottom: 24px;
|
|
}
|
|
|
|
.mac-mini-card {
|
|
background: var(--bp-surface);
|
|
border: 1px solid var(--bp-border);
|
|
border-radius: 12px;
|
|
padding: 20px;
|
|
}
|
|
|
|
.mac-mini-card h3 {
|
|
color: var(--bp-text);
|
|
font-size: 16px;
|
|
margin: 0 0 16px 0;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
}
|
|
|
|
.mac-mini-card-row {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
padding: 10px 0;
|
|
border-bottom: 1px solid var(--bp-border);
|
|
}
|
|
|
|
.mac-mini-card-row:last-child {
|
|
border-bottom: none;
|
|
}
|
|
|
|
.mac-mini-card-label {
|
|
color: var(--bp-text-muted);
|
|
}
|
|
|
|
.mac-mini-card-value {
|
|
font-weight: 500;
|
|
}
|
|
|
|
.mac-mini-card-value.ok {
|
|
color: #22c55e;
|
|
}
|
|
|
|
.mac-mini-card-value.error {
|
|
color: #ef4444;
|
|
}
|
|
|
|
/* Docker Containers */
|
|
.mac-mini-container-list {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 8px;
|
|
}
|
|
|
|
.mac-mini-container-item {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
padding: 12px 16px;
|
|
background: var(--bp-surface-elevated);
|
|
border-radius: 8px;
|
|
border: 1px solid var(--bp-border);
|
|
}
|
|
|
|
.mac-mini-container-name {
|
|
color: var(--bp-text);
|
|
font-size: 14px;
|
|
font-family: monospace;
|
|
}
|
|
|
|
.mac-mini-container-status {
|
|
padding: 4px 12px;
|
|
border-radius: 12px;
|
|
font-size: 12px;
|
|
font-weight: 600;
|
|
}
|
|
|
|
.mac-mini-container-status.healthy {
|
|
background: rgba(34, 197, 94, 0.2);
|
|
color: #22c55e;
|
|
}
|
|
|
|
.mac-mini-container-status.running {
|
|
background: rgba(59, 130, 246, 0.2);
|
|
color: #3b82f6;
|
|
}
|
|
|
|
.mac-mini-container-status.stopped {
|
|
background: rgba(239, 68, 68, 0.2);
|
|
color: #ef4444;
|
|
}
|
|
|
|
/* Ollama Models */
|
|
.mac-mini-model-list {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 12px;
|
|
margin-bottom: 20px;
|
|
}
|
|
|
|
.mac-mini-model-item {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
padding: 16px;
|
|
background: var(--bp-surface-elevated);
|
|
border-radius: 8px;
|
|
border: 1px solid var(--bp-border);
|
|
}
|
|
|
|
.mac-mini-model-info {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 4px;
|
|
}
|
|
|
|
.mac-mini-model-name {
|
|
color: var(--bp-text);
|
|
font-size: 16px;
|
|
font-weight: 600;
|
|
}
|
|
|
|
.mac-mini-model-details {
|
|
color: var(--bp-text-muted);
|
|
font-size: 13px;
|
|
}
|
|
|
|
.mac-mini-model-size {
|
|
color: var(--bp-primary);
|
|
font-size: 14px;
|
|
font-weight: 600;
|
|
}
|
|
|
|
/* Download Progress */
|
|
.mac-mini-download {
|
|
margin-top: 20px;
|
|
padding-top: 20px;
|
|
border-top: 1px solid var(--bp-border);
|
|
}
|
|
|
|
.mac-mini-download h4 {
|
|
color: var(--bp-text);
|
|
margin: 0 0 12px 0;
|
|
}
|
|
|
|
.mac-mini-download-input {
|
|
display: flex;
|
|
gap: 12px;
|
|
margin-bottom: 16px;
|
|
}
|
|
|
|
.mac-mini-download-input input {
|
|
flex: 1;
|
|
padding: 12px 16px;
|
|
border-radius: 8px;
|
|
border: 1px solid var(--bp-border);
|
|
background: var(--bp-surface-elevated);
|
|
color: var(--bp-text);
|
|
font-size: 14px;
|
|
}
|
|
|
|
.mac-mini-download-input input:focus {
|
|
outline: none;
|
|
border-color: var(--bp-primary);
|
|
}
|
|
|
|
.mac-mini-progress {
|
|
display: none;
|
|
}
|
|
|
|
.mac-mini-progress.active {
|
|
display: block;
|
|
}
|
|
|
|
.mac-mini-progress-header {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
margin-bottom: 8px;
|
|
}
|
|
|
|
.mac-mini-progress-name {
|
|
color: var(--bp-text);
|
|
font-weight: 600;
|
|
}
|
|
|
|
.mac-mini-progress-stats {
|
|
color: var(--bp-text-muted);
|
|
font-size: 13px;
|
|
}
|
|
|
|
.mac-mini-progress-bar-container {
|
|
height: 24px;
|
|
background: var(--bp-surface-elevated);
|
|
border-radius: 12px;
|
|
overflow: hidden;
|
|
position: relative;
|
|
}
|
|
|
|
.mac-mini-progress-bar {
|
|
height: 100%;
|
|
background: linear-gradient(90deg, var(--bp-primary), #991b1b);
|
|
border-radius: 12px;
|
|
transition: width 0.3s ease;
|
|
width: 0%;
|
|
}
|
|
|
|
.mac-mini-progress-text {
|
|
position: absolute;
|
|
top: 50%;
|
|
left: 50%;
|
|
transform: translate(-50%, -50%);
|
|
color: white;
|
|
font-size: 12px;
|
|
font-weight: 600;
|
|
text-shadow: 0 1px 2px rgba(0,0,0,0.5);
|
|
}
|
|
|
|
.mac-mini-progress-log {
|
|
margin-top: 16px;
|
|
padding: 16px;
|
|
background: #0a0a0a;
|
|
border-radius: 8px;
|
|
font-family: monospace;
|
|
font-size: 12px;
|
|
color: #22c55e;
|
|
max-height: 200px;
|
|
overflow-y: auto;
|
|
white-space: pre-wrap;
|
|
}
|
|
|
|
/* Docker Controls */
|
|
.mac-mini-docker-controls {
|
|
margin-top: 16px;
|
|
display: flex;
|
|
gap: 8px;
|
|
}
|
|
|
|
.mac-mini-docker-controls .btn {
|
|
flex: 1;
|
|
padding: 10px 16px;
|
|
border-radius: 8px;
|
|
font-weight: 500;
|
|
cursor: pointer;
|
|
background: var(--bp-surface-elevated);
|
|
color: var(--bp-text);
|
|
border: 1px solid var(--bp-border);
|
|
transition: all 0.2s;
|
|
}
|
|
|
|
.mac-mini-docker-controls .btn:hover {
|
|
background: var(--bp-surface);
|
|
border-color: var(--bp-primary);
|
|
}
|
|
"""
|
|
|
|
@staticmethod
|
|
def get_html() -> str:
|
|
"""HTML fuer das Mac Mini Control Modul."""
|
|
return """
|
|
<!-- Mac Mini Control Panel -->
|
|
<div id="panel-mac-mini" class="panel-mac-mini" style="display: none;">
|
|
<!-- Header -->
|
|
<div class="mac-mini-header">
|
|
<h1>🖥️ Mac Mini Control</h1>
|
|
<span class="mac-mini-status-badge checking" id="mac-mini-status-badge">Prüfe...</span>
|
|
</div>
|
|
|
|
<!-- Content -->
|
|
<div class="mac-mini-content">
|
|
<!-- Power Controls -->
|
|
<div class="mac-mini-controls">
|
|
<button class="btn btn-wake" onclick="macMiniWake()" id="btn-mac-mini-wake">⚡ Wake on LAN</button>
|
|
<button class="btn btn-restart" onclick="macMiniRestart()" id="btn-mac-mini-restart">🔄 Neustart</button>
|
|
<button class="btn btn-shutdown" onclick="macMiniShutdown()" id="btn-mac-mini-shutdown">⏻ Herunterfahren</button>
|
|
<button class="btn btn-refresh" onclick="macMiniRefresh()">🔍 Status aktualisieren</button>
|
|
</div>
|
|
|
|
<!-- Status Grid -->
|
|
<div class="mac-mini-grid">
|
|
<!-- Connection -->
|
|
<div class="mac-mini-card">
|
|
<h3>🌐 Verbindung</h3>
|
|
<div class="mac-mini-card-row">
|
|
<span class="mac-mini-card-label">IP-Adresse</span>
|
|
<span class="mac-mini-card-value" id="mac-mini-ip">192.168.178.100</span>
|
|
</div>
|
|
<div class="mac-mini-card-row">
|
|
<span class="mac-mini-card-label">SSH</span>
|
|
<span class="mac-mini-card-value" id="mac-mini-ssh">--</span>
|
|
</div>
|
|
<div class="mac-mini-card-row">
|
|
<span class="mac-mini-card-label">Ping</span>
|
|
<span class="mac-mini-card-value" id="mac-mini-ping">--</span>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Services -->
|
|
<div class="mac-mini-card">
|
|
<h3>⚙️ Services</h3>
|
|
<div class="mac-mini-card-row">
|
|
<span class="mac-mini-card-label">Backend API</span>
|
|
<span class="mac-mini-card-value" id="mac-mini-backend">--</span>
|
|
</div>
|
|
<div class="mac-mini-card-row">
|
|
<span class="mac-mini-card-label">Ollama</span>
|
|
<span class="mac-mini-card-value" id="mac-mini-ollama">--</span>
|
|
</div>
|
|
<div class="mac-mini-card-row">
|
|
<span class="mac-mini-card-label">Docker</span>
|
|
<span class="mac-mini-card-value" id="mac-mini-docker">--</span>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- System -->
|
|
<div class="mac-mini-card">
|
|
<h3>💻 System</h3>
|
|
<div class="mac-mini-card-row">
|
|
<span class="mac-mini-card-label">Uptime</span>
|
|
<span class="mac-mini-card-value" id="mac-mini-uptime">--</span>
|
|
</div>
|
|
<div class="mac-mini-card-row">
|
|
<span class="mac-mini-card-label">CPU Load</span>
|
|
<span class="mac-mini-card-value" id="mac-mini-cpu">--</span>
|
|
</div>
|
|
<div class="mac-mini-card-row">
|
|
<span class="mac-mini-card-label">Memory</span>
|
|
<span class="mac-mini-card-value" id="mac-mini-memory">--</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Docker Containers -->
|
|
<div class="mac-mini-card" style="margin-bottom: 24px;">
|
|
<h3>🐳 Docker Container</h3>
|
|
<div class="mac-mini-container-list" id="mac-mini-containers">
|
|
<div style="color: var(--bp-text-muted); text-align: center; padding: 20px;">
|
|
Lade Container-Status...
|
|
</div>
|
|
</div>
|
|
<div class="mac-mini-docker-controls">
|
|
<button class="btn" onclick="macMiniDockerUp()">▶️ Container starten</button>
|
|
<button class="btn" onclick="macMiniDockerDown()">⏹️ Container stoppen</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Ollama Models -->
|
|
<div class="mac-mini-card">
|
|
<h3>🤖 Ollama LLM Modelle</h3>
|
|
<div class="mac-mini-model-list" id="mac-mini-models">
|
|
<div style="color: var(--bp-text-muted); text-align: center; padding: 20px;">
|
|
Lade Modelle...
|
|
</div>
|
|
</div>
|
|
|
|
<div class="mac-mini-download">
|
|
<h4>📥 Neues Modell herunterladen</h4>
|
|
<div class="mac-mini-download-input">
|
|
<input type="text" id="mac-mini-model-input" placeholder="Modellname (z.B. llama3.2-vision:11b, mistral, qwen2.5:7b)">
|
|
<button class="btn btn-refresh" onclick="macMiniPullModel()" id="btn-mac-mini-pull">Herunterladen</button>
|
|
</div>
|
|
|
|
<div class="mac-mini-progress" id="mac-mini-progress">
|
|
<div class="mac-mini-progress-header">
|
|
<span class="mac-mini-progress-name" id="mac-mini-progress-name">--</span>
|
|
<span class="mac-mini-progress-stats" id="mac-mini-progress-stats">-- / --</span>
|
|
</div>
|
|
<div class="mac-mini-progress-bar-container">
|
|
<div class="mac-mini-progress-bar" id="mac-mini-progress-bar"></div>
|
|
<span class="mac-mini-progress-text" id="mac-mini-progress-text">0%</span>
|
|
</div>
|
|
<div class="mac-mini-progress-log" id="mac-mini-progress-log">Starte Download...</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
"""
|
|
|
|
@staticmethod
|
|
def get_js() -> str:
|
|
"""JavaScript fuer das Mac Mini Control Modul."""
|
|
return """
|
|
// =============================================
|
|
// MAC MINI CONTROL MODULE
|
|
// =============================================
|
|
|
|
let macMiniModuleState = {
|
|
ip: '192.168.178.100',
|
|
isOnline: false,
|
|
downloadInProgress: false,
|
|
pollInterval: null
|
|
};
|
|
|
|
function loadMacMiniModule() {
|
|
console.log('Loading Mac Mini Control Module...');
|
|
macMiniRefresh();
|
|
startMacMiniPolling();
|
|
}
|
|
|
|
function unloadMacMiniModule() {
|
|
stopMacMiniPolling();
|
|
}
|
|
|
|
function startMacMiniPolling() {
|
|
stopMacMiniPolling();
|
|
macMiniModuleState.pollInterval = setInterval(macMiniRefresh, 30000);
|
|
}
|
|
|
|
function stopMacMiniPolling() {
|
|
if (macMiniModuleState.pollInterval) {
|
|
clearInterval(macMiniModuleState.pollInterval);
|
|
macMiniModuleState.pollInterval = null;
|
|
}
|
|
}
|
|
|
|
async function macMiniRefresh() {
|
|
const statusBadge = document.getElementById('mac-mini-status-badge');
|
|
if (!statusBadge) return;
|
|
|
|
statusBadge.className = 'mac-mini-status-badge checking';
|
|
statusBadge.textContent = 'Prüfe...';
|
|
|
|
try {
|
|
const response = await fetch('/api/mac-mini/status');
|
|
const data = await response.json();
|
|
|
|
macMiniModuleState.isOnline = data.online;
|
|
macMiniModuleState.ip = data.ip || macMiniModuleState.ip;
|
|
|
|
// Update status badge
|
|
if (data.online) {
|
|
statusBadge.className = 'mac-mini-status-badge online';
|
|
statusBadge.textContent = 'Online';
|
|
} else {
|
|
statusBadge.className = 'mac-mini-status-badge offline';
|
|
statusBadge.textContent = 'Offline';
|
|
}
|
|
|
|
// Update IP
|
|
setMacMiniValue('mac-mini-ip', macMiniModuleState.ip);
|
|
|
|
// Update connection
|
|
setMacMiniStatus('mac-mini-ssh', data.ssh ? 'Verbunden' : 'Nicht verfügbar', data.ssh);
|
|
setMacMiniStatus('mac-mini-ping', data.ping ? 'OK' : 'Timeout', data.ping);
|
|
|
|
// Update services
|
|
setMacMiniStatus('mac-mini-backend', data.backend ? 'Läuft' : 'Offline', data.backend);
|
|
setMacMiniStatus('mac-mini-ollama', data.ollama ? 'Läuft' : 'Offline', data.ollama);
|
|
setMacMiniStatus('mac-mini-docker', data.docker ? 'Läuft' : 'Offline', data.docker);
|
|
|
|
// Update system
|
|
setMacMiniValue('mac-mini-uptime', data.uptime || '--');
|
|
setMacMiniValue('mac-mini-cpu', data.cpu_load || '--');
|
|
setMacMiniValue('mac-mini-memory', data.memory || '--');
|
|
|
|
// Update containers
|
|
renderMacMiniContainers(data.containers || []);
|
|
|
|
// Update models
|
|
renderMacMiniModels(data.models || []);
|
|
|
|
// Enable/disable buttons
|
|
const btnWake = document.getElementById('btn-mac-mini-wake');
|
|
const btnRestart = document.getElementById('btn-mac-mini-restart');
|
|
const btnShutdown = document.getElementById('btn-mac-mini-shutdown');
|
|
if (btnWake) btnWake.disabled = data.online;
|
|
if (btnRestart) btnRestart.disabled = !data.online;
|
|
if (btnShutdown) btnShutdown.disabled = !data.online;
|
|
|
|
} catch (error) {
|
|
console.error('Mac Mini status error:', error);
|
|
statusBadge.className = 'mac-mini-status-badge offline';
|
|
statusBadge.textContent = 'Fehler';
|
|
}
|
|
}
|
|
|
|
function setMacMiniValue(id, value) {
|
|
const el = document.getElementById(id);
|
|
if (el) el.textContent = value;
|
|
}
|
|
|
|
function setMacMiniStatus(id, text, isOk) {
|
|
const el = document.getElementById(id);
|
|
if (el) {
|
|
el.textContent = text;
|
|
el.className = 'mac-mini-card-value ' + (isOk ? 'ok' : 'error');
|
|
}
|
|
}
|
|
|
|
function renderMacMiniContainers(containers) {
|
|
const list = document.getElementById('mac-mini-containers');
|
|
if (!list) return;
|
|
|
|
if (containers.length === 0) {
|
|
list.innerHTML = '<div style="color: var(--bp-text-muted); text-align: center; padding: 20px;">Keine Container gefunden</div>';
|
|
return;
|
|
}
|
|
|
|
list.innerHTML = containers.map(c => {
|
|
const isHealthy = c.status.includes('healthy');
|
|
const isRunning = c.status.includes('Up');
|
|
const statusClass = isHealthy ? 'healthy' : (isRunning ? 'running' : 'stopped');
|
|
return `
|
|
<div class="mac-mini-container-item">
|
|
<span class="mac-mini-container-name">${c.name}</span>
|
|
<span class="mac-mini-container-status ${statusClass}">${c.status}</span>
|
|
</div>
|
|
`;
|
|
}).join('');
|
|
}
|
|
|
|
function renderMacMiniModels(models) {
|
|
const list = document.getElementById('mac-mini-models');
|
|
if (!list) return;
|
|
|
|
if (models.length === 0) {
|
|
list.innerHTML = '<div style="color: var(--bp-text-muted); text-align: center; padding: 20px;">Keine Modelle installiert</div>';
|
|
return;
|
|
}
|
|
|
|
list.innerHTML = models.map(m => {
|
|
const sizeGB = (m.size / (1024 * 1024 * 1024)).toFixed(1);
|
|
const details = m.details || {};
|
|
return `
|
|
<div class="mac-mini-model-item">
|
|
<div class="mac-mini-model-info">
|
|
<span class="mac-mini-model-name">${m.name}</span>
|
|
<span class="mac-mini-model-details">${details.parameter_size || ''} | ${details.quantization_level || ''}</span>
|
|
</div>
|
|
<span class="mac-mini-model-size">${sizeGB} GB</span>
|
|
</div>
|
|
`;
|
|
}).join('');
|
|
}
|
|
|
|
// Power Controls
|
|
async function macMiniWake() {
|
|
if (!confirm('Mac Mini per Wake-on-LAN aufwecken?')) return;
|
|
try {
|
|
const response = await fetch('/api/mac-mini/wake', { method: 'POST' });
|
|
const data = await response.json();
|
|
alert(data.message || 'Wake-on-LAN Paket gesendet');
|
|
setTimeout(macMiniRefresh, 5000);
|
|
} catch (error) {
|
|
alert('Fehler: ' + error.message);
|
|
}
|
|
}
|
|
|
|
async function macMiniRestart() {
|
|
if (!confirm('Mac Mini wirklich neu starten?')) return;
|
|
try {
|
|
const response = await fetch('/api/mac-mini/restart', { method: 'POST' });
|
|
const data = await response.json();
|
|
alert(data.message || 'Neustart ausgelöst');
|
|
setTimeout(macMiniRefresh, 60000);
|
|
} catch (error) {
|
|
alert('Fehler: ' + error.message);
|
|
}
|
|
}
|
|
|
|
async function macMiniShutdown() {
|
|
if (!confirm('Mac Mini wirklich herunterfahren?')) return;
|
|
try {
|
|
const response = await fetch('/api/mac-mini/shutdown', { method: 'POST' });
|
|
const data = await response.json();
|
|
alert(data.message || 'Shutdown ausgelöst');
|
|
setTimeout(macMiniRefresh, 10000);
|
|
} catch (error) {
|
|
alert('Fehler: ' + error.message);
|
|
}
|
|
}
|
|
|
|
// Docker Controls
|
|
async function macMiniDockerUp() {
|
|
try {
|
|
const response = await fetch('/api/mac-mini/docker/up', { method: 'POST' });
|
|
const data = await response.json();
|
|
alert(data.message || 'Container werden gestartet...');
|
|
setTimeout(macMiniRefresh, 10000);
|
|
} catch (error) {
|
|
alert('Fehler: ' + error.message);
|
|
}
|
|
}
|
|
|
|
async function macMiniDockerDown() {
|
|
if (!confirm('Alle Container stoppen?')) return;
|
|
try {
|
|
const response = await fetch('/api/mac-mini/docker/down', { method: 'POST' });
|
|
const data = await response.json();
|
|
alert(data.message || 'Container werden gestoppt...');
|
|
setTimeout(macMiniRefresh, 5000);
|
|
} catch (error) {
|
|
alert('Fehler: ' + error.message);
|
|
}
|
|
}
|
|
|
|
// Ollama Model Download
|
|
async function macMiniPullModel() {
|
|
const input = document.getElementById('mac-mini-model-input');
|
|
const modelName = input ? input.value.trim() : '';
|
|
|
|
if (!modelName) {
|
|
alert('Bitte Modellnamen eingeben');
|
|
return;
|
|
}
|
|
|
|
if (macMiniModuleState.downloadInProgress) {
|
|
alert('Download läuft bereits');
|
|
return;
|
|
}
|
|
|
|
macMiniModuleState.downloadInProgress = true;
|
|
const btnPull = document.getElementById('btn-mac-mini-pull');
|
|
if (btnPull) btnPull.disabled = true;
|
|
|
|
const progressDiv = document.getElementById('mac-mini-progress');
|
|
const progressBar = document.getElementById('mac-mini-progress-bar');
|
|
const progressText = document.getElementById('mac-mini-progress-text');
|
|
const progressStats = document.getElementById('mac-mini-progress-stats');
|
|
const progressLog = document.getElementById('mac-mini-progress-log');
|
|
const progressName = document.getElementById('mac-mini-progress-name');
|
|
|
|
if (progressDiv) progressDiv.classList.add('active');
|
|
if (progressName) progressName.textContent = modelName;
|
|
if (progressLog) progressLog.textContent = 'Starte Download...\\n';
|
|
|
|
try {
|
|
const response = await fetch('/api/mac-mini/ollama/pull', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ model: modelName })
|
|
});
|
|
|
|
const reader = response.body.getReader();
|
|
const decoder = new TextDecoder();
|
|
|
|
while (true) {
|
|
const { done, value } = await reader.read();
|
|
if (done) break;
|
|
|
|
const text = decoder.decode(value);
|
|
const lines = text.split('\\n').filter(l => l.trim());
|
|
|
|
for (const line of lines) {
|
|
try {
|
|
const data = JSON.parse(line);
|
|
|
|
if (data.status && progressLog) {
|
|
progressLog.textContent += data.status + '\\n';
|
|
progressLog.scrollTop = progressLog.scrollHeight;
|
|
}
|
|
|
|
if (data.total && data.completed) {
|
|
const percent = Math.round((data.completed / data.total) * 100);
|
|
const completedMB = (data.completed / (1024 * 1024)).toFixed(1);
|
|
const totalMB = (data.total / (1024 * 1024)).toFixed(1);
|
|
|
|
if (progressBar) progressBar.style.width = percent + '%';
|
|
if (progressText) progressText.textContent = percent + '%';
|
|
if (progressStats) progressStats.textContent = completedMB + ' MB / ' + totalMB + ' MB';
|
|
}
|
|
|
|
if (data.status === 'success' && progressLog) {
|
|
progressLog.textContent += '\\n✅ Download abgeschlossen!\\n';
|
|
if (progressBar) progressBar.style.width = '100%';
|
|
if (progressText) progressText.textContent = '100%';
|
|
}
|
|
} catch (e) {
|
|
if (progressLog) progressLog.textContent += line + '\\n';
|
|
}
|
|
}
|
|
}
|
|
|
|
setTimeout(macMiniRefresh, 2000);
|
|
|
|
} catch (error) {
|
|
if (progressLog) progressLog.textContent += '\\n❌ Fehler: ' + error.message + '\\n';
|
|
} finally {
|
|
macMiniModuleState.downloadInProgress = false;
|
|
if (btnPull) btnPull.disabled = false;
|
|
}
|
|
}
|
|
"""
|