Initial commit: Wake-on-LAN Manager mit Go und Web-Oberfläche

This commit is contained in:
2025-08-21 13:36:43 +02:00
commit f015e845a9
13 changed files with 1407 additions and 0 deletions

265
web/static/script.js Normal file
View File

@ -0,0 +1,265 @@
// JavaScript für Medi-WOL Web-Oberfläche
class PCManager {
constructor() {
this.init();
}
init() {
this.loadPCs();
this.setupEventListeners();
}
setupEventListeners() {
// Formular für neuen PC
document.getElementById('addPCForm').addEventListener('submit', (e) => {
e.preventDefault();
this.addPC();
});
// Edit PC Modal speichern Button
document.getElementById('saveEditBtn').addEventListener('click', () => {
this.saveEditPC();
});
// MAC-Adresse Formatierung für Edit-Formular
document.getElementById('editMACAddress')?.addEventListener('input', (e) => {
this.formatMACAddress(e.target);
});
}
async loadPCs() {
try {
const response = await fetch('/api/pcs');
const data = await response.json();
if (data.success) {
this.displayPCs(data.pcs);
} else {
this.showNotification('Fehler', data.message, 'danger');
}
} catch (error) {
this.showNotification('Fehler', 'Fehler beim Laden der PCs', 'danger');
}
}
displayPCs(pcs) {
const tableBody = document.getElementById('pcTableBody');
const noPCs = document.getElementById('noPCs');
const pcList = document.getElementById('pcList');
if (pcs.length === 0) {
tableBody.innerHTML = '';
noPCs.style.display = 'block';
pcList.style.display = 'none';
return;
}
noPCs.style.display = 'none';
pcList.style.display = 'block';
tableBody.innerHTML = pcs.map(pc => `
<tr>
<td><strong>${this.escapeHtml(pc.name)}</strong></td>
<td><code>${this.escapeHtml(pc.mac)}</code></td>
<td>${new Date(pc.created_at).toLocaleDateString('de-DE')}</td>
<td>
<div class="btn-group" role="group">
<button class="btn btn-success btn-sm" onclick="pcManager.wakePC(${pc.id})"
title="PC aufwecken">
<i class="fas fa-power-off"></i> Aufwecken
</button>
<button class="btn btn-warning btn-sm" onclick="pcManager.editPC(${pc.id}, '${this.escapeHtml(pc.name)}', '${this.escapeHtml(pc.mac)}')"
title="PC bearbeiten">
<i class="fas fa-edit"></i> Bearbeiten
</button>
<button class="btn btn-danger btn-sm" onclick="pcManager.deletePC(${pc.id})"
title="PC löschen">
<i class="fas fa-trash"></i> Löschen
</button>
</div>
</td>
</tr>
`).join('');
}
async addPC() {
const name = document.getElementById('pcName').value.trim();
const mac = document.getElementById('macAddress').value.trim();
if (!name || !mac) {
this.showNotification('Warnung', 'Bitte füllen Sie alle Felder aus', 'warning');
return;
}
try {
const response = await fetch('/api/pcs', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ name, mac })
});
const data = await response.json();
if (data.success) {
this.showNotification('Erfolg', 'PC erfolgreich hinzugefügt', 'success');
document.getElementById('addPCForm').reset();
this.loadPCs();
} else {
this.showNotification('Fehler', data.message, 'danger');
}
} catch (error) {
this.showNotification('Fehler', 'Fehler beim Hinzufügen des PCs', 'danger');
}
}
editPC(id, name, mac) {
// Modal mit PC-Daten füllen
document.getElementById('editPCId').value = id;
document.getElementById('editPCName').value = name;
document.getElementById('editMACAddress').value = mac;
// Modal öffnen
const editModal = new bootstrap.Modal(document.getElementById('editPCModal'));
editModal.show();
}
async saveEditPC() {
const id = document.getElementById('editPCId').value;
const name = document.getElementById('editPCName').value.trim();
const mac = document.getElementById('editMACAddress').value.trim();
if (!name || !mac) {
this.showNotification('Warnung', 'Bitte füllen Sie alle Felder aus', 'warning');
return;
}
try {
const response = await fetch(`/api/pcs/${id}`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ name, mac })
});
const data = await response.json();
if (data.success) {
this.showNotification('Erfolg', 'PC erfolgreich aktualisiert', 'success');
// Modal schließen
const editModal = bootstrap.Modal.getInstance(document.getElementById('editPCModal'));
editModal.hide();
// PC-Liste neu laden
this.loadPCs();
} else {
this.showNotification('Fehler', data.message, 'danger');
}
} catch (error) {
this.showNotification('Fehler', 'Fehler beim Aktualisieren des PCs', 'danger');
}
}
async deletePC(id) {
if (!confirm('Sind Sie sicher, dass Sie diesen PC löschen möchten?')) {
return;
}
try {
const response = await fetch(`/api/pcs/${id}`, {
method: 'DELETE'
});
const data = await response.json();
if (data.success) {
this.showNotification('Erfolg', 'PC erfolgreich gelöscht', 'success');
this.loadPCs();
} else {
this.showNotification('Fehler', data.message, 'danger');
}
} catch (error) {
this.showNotification('Fehler', 'Fehler beim Löschen des PCs', 'danger');
}
}
async wakePC(id) {
try {
const response = await fetch(`/api/pcs/${id}/wake`, {
method: 'POST'
});
const data = await response.json();
if (data.success) {
this.showNotification('Erfolg', data.message, 'success');
} else {
this.showNotification('Fehler', data.message, 'danger');
}
} catch (error) {
this.showNotification('Fehler', 'Fehler beim Senden des Wake-on-LAN Pakets', 'danger');
}
}
showNotification(title, message, type = 'info') {
const toast = document.getElementById('notificationToast');
const toastTitle = document.getElementById('toastTitle');
const toastMessage = document.getElementById('toastMessage');
// Toast-Typ setzen
toast.className = `toast ${type === 'success' ? 'bg-success' : type === 'danger' ? 'bg-danger' : type === 'warning' ? 'bg-warning' : 'bg-info'} text-white`;
toastTitle.textContent = title;
toastMessage.textContent = message;
// Toast anzeigen
const bsToast = new bootstrap.Toast(toast);
bsToast.show();
}
escapeHtml(text) {
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
formatMACAddress(input) {
let value = input.value.replace(/[^0-9A-Fa-f]/g, '');
if (value.length > 12) {
value = value.substring(0, 12);
}
// MAC-Adresse mit Doppelpunkten formatieren
if (value.length >= 2) {
value = value.match(/.{1,2}/g).join(':');
}
input.value = value.toUpperCase();
}
}
// PC Manager initialisieren, wenn die Seite geladen ist
document.addEventListener('DOMContentLoaded', () => {
window.pcManager = new PCManager();
});
// MAC-Adresse Formatierung für das Hauptformular
document.getElementById('macAddress')?.addEventListener('input', (e) => {
let value = e.target.value.replace(/[^0-9A-Fa-f]/g, '');
if (value.length > 12) {
value = value.substring(0, 12);
}
// MAC-Adresse mit Doppelpunkten formatieren
if (value.length >= 2) {
value = value.match(/.{1,2}/g).join(':');
}
e.target.value = value.toUpperCase();
});

148
web/static/style.css Normal file
View File

@ -0,0 +1,148 @@
/* Custom Styles für Medi-WOL */
body {
background-color: #f8f9fa;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
}
.card {
border: none;
border-radius: 15px;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
transition: transform 0.2s ease-in-out;
}
.card:hover {
transform: translateY(-2px);
}
.card-header {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
border-radius: 15px 15px 0 0 !important;
border: none;
}
.btn {
border-radius: 25px;
padding: 8px 20px;
font-weight: 500;
transition: all 0.3s ease;
}
.btn:hover {
transform: translateY(-1px);
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
}
.btn-primary {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border: none;
}
.btn-success {
background: linear-gradient(135deg, #56ab2f 0%, #a8e6cf 100%);
border: none;
}
.btn-danger {
background: linear-gradient(135deg, #ff416c 0%, #ff4b2b 100%);
border: none;
}
.btn-warning {
background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
border: none;
}
.table {
border-radius: 10px;
overflow: hidden;
}
.table thead th {
background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
color: white;
border: none;
font-weight: 600;
}
.table tbody tr {
transition: background-color 0.2s ease;
}
.table tbody tr:hover {
background-color: #f8f9fa;
}
.form-control {
border-radius: 10px;
border: 2px solid #e9ecef;
transition: border-color 0.3s ease;
}
.form-control:focus {
border-color: #667eea;
box-shadow: 0 0 0 0.2rem rgba(102, 126, 234, 0.25);
}
.toast {
border-radius: 15px;
border: none;
box-shadow: 0 8px 16px rgba(0, 0, 0, 0.2);
}
.toast-header {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
border-radius: 15px 15px 0 0;
}
.btn-close {
filter: invert(1);
}
/* Animationen */
@keyframes fadeIn {
from { opacity: 0; transform: translateY(20px); }
to { opacity: 1; transform: translateY(0); }
}
.card {
animation: fadeIn 0.5s ease-out;
}
/* Responsive Design */
@media (max-width: 768px) {
.container {
padding: 0 15px;
}
.btn {
width: 100%;
margin-bottom: 10px;
}
.table-responsive {
font-size: 0.9rem;
}
}
/* Custom Scrollbar */
::-webkit-scrollbar {
width: 8px;
}
::-webkit-scrollbar-track {
background: #f1f1f1;
border-radius: 10px;
}
::-webkit-scrollbar-thumb {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border-radius: 10px;
}
::-webkit-scrollbar-thumb:hover {
background: linear-gradient(135deg, #5a6fd8 0%, #6a4190 100%);
}

141
web/templates/index.html Normal file
View File

@ -0,0 +1,141 @@
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{.title}}</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" rel="stylesheet">
<link href="/static/style.css" rel="stylesheet">
</head>
<body>
<div class="container mt-4">
<div class="row">
<div class="col-12">
<h1 class="text-center mb-4">
<i class="fas fa-power-off text-primary"></i>
{{.title}}
</h1>
</div>
</div>
<!-- Neuen PC hinzufügen -->
<div class="row mb-4">
<div class="col-12">
<div class="card">
<div class="card-header">
<h5 class="mb-0">
<i class="fas fa-plus-circle"></i> Neuen PC hinzufügen
</h5>
</div>
<div class="card-body">
<form id="addPCForm">
<div class="row">
<div class="col-md-6">
<div class="mb-3">
<label for="pcName" class="form-label">PC-Name</label>
<input type="text" class="form-control" id="pcName" required>
</div>
</div>
<div class="col-md-6">
<div class="mb-3">
<label for="macAddress" class="form-label">MAC-Adresse</label>
<input type="text" class="form-control" id="macAddress"
placeholder="XX:XX:XX:XX:XX:XX" required>
</div>
</div>
</div>
<button type="submit" class="btn btn-primary">
<i class="fas fa-save"></i> PC hinzufügen
</button>
</form>
</div>
</div>
</div>
</div>
<!-- PC-Liste -->
<div class="row">
<div class="col-12">
<div class="card">
<div class="card-header">
<h5 class="mb-0">
<i class="fas fa-desktop"></i> Gespeicherte PCs
</h5>
</div>
<div class="card-body">
<div id="pcList" class="table-responsive">
<table class="table table-hover">
<thead>
<tr>
<th>Name</th>
<th>MAC-Adresse</th>
<th>Erstellt am</th>
<th>Aktionen</th>
</tr>
</thead>
<tbody id="pcTableBody">
<!-- PCs werden hier dynamisch eingefügt -->
</tbody>
</table>
</div>
<div id="noPCs" class="text-center text-muted" style="display: none;">
<i class="fas fa-info-circle fa-2x mb-2"></i>
<p>Noch keine PCs hinzugefügt.</p>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Edit PC Modal -->
<div class="modal fade" id="editPCModal" tabindex="-1" aria-labelledby="editPCModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="editPCModalLabel">
<i class="fas fa-edit"></i> PC bearbeiten
</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<form id="editPCForm">
<input type="hidden" id="editPCId">
<div class="mb-3">
<label for="editPCName" class="form-label">PC-Name</label>
<input type="text" class="form-control" id="editPCName" required>
</div>
<div class="mb-3">
<label for="editMACAddress" class="form-label">MAC-Adresse</label>
<input type="text" class="form-control" id="editMACAddress"
placeholder="XX:XX:XX:XX:XX:XX" required>
</div>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Abbrechen</button>
<button type="button" class="btn btn-primary" id="saveEditBtn">
<i class="fas fa-save"></i> Speichern
</button>
</div>
</div>
</div>
</div>
<!-- Toast für Benachrichtigungen -->
<div class="toast-container position-fixed bottom-0 end-0 p-3">
<div id="notificationToast" class="toast" role="alert">
<div class="toast-header">
<strong class="me-auto" id="toastTitle">Benachrichtigung</strong>
<button type="button" class="btn-close" data-bs-dismiss="toast"></button>
</div>
<div class="toast-body" id="toastMessage">
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
<script src="/static/script.js"></script>
</body>
</html>