OfficeServerJs/management/management-server.js
OfficeServer dgsoft 88932bfbf4 🎛️ Web-GUI Management System mit Custom Tags
 Neue Features:
- Vollständiges Management Dashboard (Port 3000)
- Custom Tags System mit REST-API Integration
- Mehrere Tags erstellbar und konfigurierbar
- Externe Tag-Aktivierung per REST-API
- Server-Fernsteuerung (Start/Stop/Restart)
- SSL-Zertifikat Management
- Echtzeit-Überwachung mit Socket.IO

🏷️ Custom Tags Features:
- Dynamische Tag-Erstellung über GUI
- Tag-Aktivierung/Deaktivierung per Toggle
- REST-APIs für externe Tag-Kontrolle
- Integration in Template-Verarbeitung
- Konfigurierbare Positionen und Typen

📁 Neue Dateien:
- management/ - Komplettes Management-System
- API-TAGS-DOCUMENTATION.md - API Dokumentation
- start-management.sh - Startup Script

🔧 Verbesserte Template-Verarbeitung:
- Automatisches Laden aktivierter Custom Tags
- Priorität: Custom-Daten → Custom Tags → Auto-Generierung
- Erweiterte Logging und Status-Meldungen

🌐 REST-APIs:
- GET /api/public/tags - Alle Tags auflisten
- POST /api/public/tags/{TAG_NAME}/activate - Tag aktivieren
- POST /api/public/tags/{TAG_NAME}/deactivate - Tag deaktivieren
- Management APIs für vollständige CRUD-Operationen
2025-10-05 20:51:30 +02:00

1011 lines
42 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

const express = require('express');
const cors = require('cors');
const helmet = require('helmet');
const multer = require('multer');
const fs = require('fs');
const path = require('path');
const axios = require('axios');
const { spawn, exec } = require('child_process');
const chokidar = require('chokidar');
const http = require('http');
const socketIo = require('socket.io');
const app = express();
const server = http.createServer(app);
const io = socketIo(server, {
cors: {
origin: "*",
methods: ["GET", "POST"]
}
});
const MANAGEMENT_PORT = 3000;
const MAIN_SERVER_DIR = path.join(__dirname, '..');
const CONFIG_FILE = path.join(__dirname, 'config.json');
// Middleware
app.use(helmet({
contentSecurityPolicy: false, // Für WebSocket-Verbindungen
}));
app.use(cors());
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.use(express.static(path.join(__dirname, 'public')));
// Multer für File Uploads
const upload = multer({ dest: 'uploads/' });
// Default-Konfiguration
const defaultConfig = {
server: {
httpPort: 80,
httpsPort: 443,
httpsEnabled: true,
publicAccess: false,
domain: 'localhost'
},
ssl: {
certPath: '203_cert.pem',
keyPath: '203_key.pem',
autoRenew: false
},
dataSources: [],
customTags: [
{
id: 'header',
name: 'Header',
tagName: 'HEADER_CONTENT',
enabled: false,
content: '',
position: 'top',
type: 'text'
},
{
id: 'footer',
name: 'Footer',
tagName: 'FOOTER_CONTENT',
enabled: false,
content: '',
position: 'bottom',
type: 'text'
},
{
id: 'signature',
name: 'Unterschrift',
tagName: 'SIGNATURE',
enabled: false,
content: '',
position: 'end',
type: 'text'
},
{
id: 'company_logo',
name: 'Firmen-Logo',
tagName: 'COMPANY_LOGO',
enabled: false,
content: '',
position: 'header',
type: 'image'
},
{
id: 'watermark',
name: 'Wasserzeichen',
tagName: 'WATERMARK',
enabled: false,
content: '',
position: 'background',
type: 'image'
}
],
monitoring: {
logLevel: 'info',
enableMetrics: true,
retentionDays: 30
}
};
// Konfiguration laden oder erstellen
function loadConfig() {
try {
if (fs.existsSync(CONFIG_FILE)) {
const config = JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf8'));
return { ...defaultConfig, ...config };
}
} catch (error) {
console.error('❌ Fehler beim Laden der Konfiguration:', error);
}
return defaultConfig;
}
// Konfiguration speichern
function saveConfig(config) {
try {
fs.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2));
console.log('✅ Konfiguration gespeichert');
return true;
} catch (error) {
console.error('❌ Fehler beim Speichern der Konfiguration:', error);
return false;
}
}
let currentConfig = loadConfig();
// Server-Status-Tracking
let serverStatus = {
running: false,
pid: null,
startTime: null,
lastRestart: null,
requests: 0,
errors: 0
};
// WebSocket für Live-Updates
io.on('connection', (socket) => {
console.log('🔌 Management-Client verbunden');
// Aktuellen Status senden
socket.emit('server-status', serverStatus);
socket.emit('config-update', currentConfig);
socket.on('disconnect', () => {
console.log('🔌 Management-Client getrennt');
});
});
// Log-Watcher für Live-Updates
function watchServerLogs() {
const logFile = path.join(MAIN_SERVER_DIR, 'server.log');
if (fs.existsSync(logFile)) {
const watcher = chokidar.watch(logFile);
watcher.on('change', () => {
try {
const logs = fs.readFileSync(logFile, 'utf8').split('\n').slice(-10);
io.emit('log-update', logs);
} catch (error) {
console.error('❌ Fehler beim Lesen der Logs:', error);
}
});
}
}
// Server-Status überprüfen
function checkServerStatus() {
exec('ps aux | grep "node server.js" | grep -v grep', (error, stdout) => {
const wasRunning = serverStatus.running;
serverStatus.running = !error && stdout.trim().length > 0;
if (serverStatus.running && !wasRunning) {
const match = stdout.match(/^\S+\s+(\d+)/);
serverStatus.pid = match ? parseInt(match[1]) : null;
serverStatus.startTime = new Date();
} else if (!serverStatus.running && wasRunning) {
serverStatus.pid = null;
serverStatus.startTime = null;
}
io.emit('server-status', serverStatus);
});
}
// Regelmäßige Status-Updates
setInterval(checkServerStatus, 5000);
watchServerLogs();
// Routes
// Hauptseite - Management Dashboard
app.get('/', (req, res) => {
res.send(`
<!DOCTYPE html>
<html>
<head>
<title>DOCX Template Server - Management</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<script src="/socket.io/socket.io.js"></script>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; background: #f5f7fa; }
.container { max-width: 1200px; margin: 0 auto; padding: 20px; }
.header { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; padding: 30px; border-radius: 10px; margin-bottom: 30px; }
.header h1 { font-size: 2.5em; margin-bottom: 10px; }
.header p { opacity: 0.9; font-size: 1.1em; }
.grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: 20px; margin-bottom: 30px; }
.card { background: white; border-radius: 10px; padding: 25px; box-shadow: 0 4px 15px rgba(0,0,0,0.1); transition: transform 0.2s; }
.card:hover { transform: translateY(-2px); }
.card h3 { color: #333; margin-bottom: 15px; font-size: 1.3em; }
.status { display: flex; align-items: center; gap: 10px; margin-bottom: 15px; }
.status-dot { width: 12px; height: 12px; border-radius: 50%; }
.status-running { background: #4CAF50; }
.status-stopped { background: #f44336; }
.btn { background: #667eea; color: white; border: none; padding: 12px 24px; border-radius: 6px; cursor: pointer; font-size: 14px; transition: all 0.2s; margin: 5px; }
.btn:hover { background: #5a6fd8; transform: translateY(-1px); }
.btn-danger { background: #f44336; }
.btn-danger:hover { background: #d32f2f; }
.btn-success { background: #4CAF50; }
.btn-success:hover { background: #45a049; }
.config-section { background: white; border-radius: 10px; padding: 25px; margin-bottom: 20px; box-shadow: 0 4px 15px rgba(0,0,0,0.1); }
.form-group { margin-bottom: 20px; }
.form-group label { display: block; margin-bottom: 5px; font-weight: 600; color: #333; }
.form-group input, .form-group select, .form-group textarea { width: 100%; padding: 10px; border: 1px solid #ddd; border-radius: 5px; font-size: 14px; }
.form-group textarea { resize: vertical; min-height: 100px; }
.toggle { position: relative; display: inline-block; width: 60px; height: 34px; }
.toggle input { opacity: 0; width: 0; height: 0; }
.slider { position: absolute; cursor: pointer; top: 0; left: 0; right: 0; bottom: 0; background-color: #ccc; transition: .4s; border-radius: 34px; }
.slider:before { position: absolute; content: ""; height: 26px; width: 26px; left: 4px; bottom: 4px; background-color: white; transition: .4s; border-radius: 50%; }
input:checked + .slider { background-color: #667eea; }
input:checked + .slider:before { transform: translateX(26px); }
.log-container { background: #1e1e1e; color: #f5f5f5; padding: 20px; border-radius: 5px; font-family: 'Courier New', monospace; font-size: 13px; max-height: 300px; overflow-y: auto; }
.metrics { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 15px; }
.metric { text-align: center; }
.metric-value { font-size: 2em; font-weight: bold; color: #667eea; }
.metric-label { color: #666; text-transform: uppercase; font-size: 0.8em; letter-spacing: 1px; }
.tabs { display: flex; border-bottom: 2px solid #eee; margin-bottom: 20px; }
.tab { padding: 15px 25px; background: none; border: none; cursor: pointer; font-size: 16px; transition: all 0.2s; }
.tab.active { border-bottom: 3px solid #667eea; color: #667eea; font-weight: 600; }
.tab-content { display: none; }
.tab-content.active { display: block; }
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>🚀 DOCX Template Server</h1>
<p>Management Dashboard & Configuration</p>
</div>
<div class="grid">
<div class="card">
<h3>📊 Server Status</h3>
<div class="status">
<div class="status-dot" id="status-dot"></div>
<span id="status-text">Prüfe Status...</span>
</div>
<div class="metrics">
<div class="metric">
<div class="metric-value" id="uptime">--</div>
<div class="metric-label">Uptime</div>
</div>
<div class="metric">
<div class="metric-value" id="requests">--</div>
<div class="metric-label">Requests</div>
</div>
<div class="metric">
<div class="metric-value" id="pid">--</div>
<div class="metric-label">PID</div>
</div>
</div>
<div style="margin-top: 20px;">
<button class="btn btn-success" onclick="startServer()">Start</button>
<button class="btn btn-danger" onclick="stopServer()">Stop</button>
<button class="btn" onclick="restartServer()">Restart</button>
</div>
</div>
<div class="card">
<h3>📝 Live Logs</h3>
<div class="log-container" id="logs">
Warte auf Logs...
</div>
</div>
</div>
<div class="tabs">
<button class="tab active" onclick="showTab('config')">Konfiguration</button>
<button class="tab" onclick="showTab('datasources')">Datenquellen</button>
<button class="tab" onclick="showTab('tags')">Custom Tags</button>
<button class="tab" onclick="showTab('ssl')">SSL/TLS</button>
</div>
<div id="config" class="tab-content active">
<div class="config-section">
<h3>Server-Konfiguration</h3>
<div class="grid">
<div class="form-group">
<label>HTTP Port</label>
<input type="number" id="httpPort" value="80">
</div>
<div class="form-group">
<label>HTTPS Port</label>
<input type="number" id="httpsPort" value="443">
</div>
<div class="form-group">
<label>Domain</label>
<input type="text" id="domain" value="localhost" placeholder="beispiel.de">
</div>
<div class="form-group">
<label>
<span>Öffentlicher Zugriff</span>
<label class="toggle">
<input type="checkbox" id="publicAccess">
<span class="slider"></span>
</label>
</label>
</div>
</div>
<button class="btn" onclick="saveServerConfig()">Speichern</button>
</div>
</div>
<div id="datasources" class="tab-content">
<div class="config-section">
<h3>🔌 Externe Datenquellen</h3>
<div id="datasources-list"></div>
<button class="btn" onclick="addDataSource()"> Datenquelle hinzufügen</button>
</div>
</div>
<div id="tags" class="tab-content">
<div class="config-section">
<h3>Custom Tags</h3>
<div style="margin-bottom: 20px;">
<button class="btn btn-success" onclick="addNewTag()">Neuen Tag hinzufügen</button>
<button class="btn" onclick="refreshTags()">Tags aktualisieren</button>
</div>
<div id="tagsContainer">
<!-- Tags werden hier dynamisch geladen -->
</div>
</div>
</div>
<div id="ssl" class="tab-content">
<div class="config-section">
<h3>SSL/TLS Zertifikate</h3>
<div class="grid">
<div class="form-group">
<label>Zertifikat-Datei (.pem)</label>
<input type="file" id="certFile" accept=".pem,.crt,.cer">
</div>
<div class="form-group">
<label>Private Key (.pem)</label>
<input type="file" id="keyFile" accept=".pem,.key">
</div>
</div>
<button class="btn" onclick="uploadSSL()">Zertifikate hochladen</button>
<button class="btn" onclick="generateSelfSigned()">Self-Signed erstellen</button>
</div>
</div>
</div>
<script>
const socket = io();
let currentConfig = {};
socket.on('server-status', (status) => {
updateServerStatus(status);
});
socket.on('config-update', (config) => {
currentConfig = config;
updateConfigUI(config);
});
socket.on('log-update', (logs) => {
document.getElementById('logs').innerHTML = logs.join('\\n');
});
function updateServerStatus(status) {
const dot = document.getElementById('status-dot');
const text = document.getElementById('status-text');
const uptimeEl = document.getElementById('uptime');
const requestsEl = document.getElementById('requests');
const pidEl = document.getElementById('pid');
if (status.running) {
dot.className = 'status-dot status-running';
text.textContent = '🟢 Server läuft';
pidEl.textContent = status.pid || '--';
if (status.startTime) {
const uptime = Math.floor((Date.now() - new Date(status.startTime)) / 1000);
uptimeEl.textContent = formatTime(uptime);
}
} else {
dot.className = 'status-dot status-stopped';
text.textContent = '🔴 Server gestoppt';
uptimeEl.textContent = '--';
pidEl.textContent = '--';
}
requestsEl.textContent = status.requests || '--';
}
function updateConfigUI(config) {
document.getElementById('httpPort').value = config.server.httpPort;
document.getElementById('httpsPort').value = config.server.httpsPort;
document.getElementById('domain').value = config.server.domain;
document.getElementById('publicAccess').checked = config.server.publicAccess;
document.getElementById('headerEnabled').checked = config.customTags.header.enabled;
document.getElementById('headerContent').value = config.customTags.header.content;
document.getElementById('footerEnabled').checked = config.customTags.footer.enabled;
document.getElementById('footerContent').value = config.customTags.footer.content;
}
function formatTime(seconds) {
const hours = Math.floor(seconds / 3600);
const minutes = Math.floor((seconds % 3600) / 60);
const secs = seconds % 60;
return hours > 0 ? \`\${hours}h \${minutes}m\` : \`\${minutes}m \${secs}s\`;
}
function showTab(tabName) {
document.querySelectorAll('.tab').forEach(t => t.classList.remove('active'));
document.querySelectorAll('.tab-content').forEach(c => c.classList.remove('active'));
event.target.classList.add('active');
document.getElementById(tabName).classList.add('active');
// Tags laden wenn Tags-Tab geöffnet wird
if (tabName === 'tags') {
refreshTags();
}
}
// Beim Laden der Seite
window.onload = function() {
// Initialer Load der verfügbaren Tags
refreshTags();
};
async function startServer() {
const response = await fetch('/api/server/start', { method: 'POST' });
const result = await response.json();
alert(result.message);
}
async function stopServer() {
const response = await fetch('/api/server/stop', { method: 'POST' });
const result = await response.json();
alert(result.message);
}
async function restartServer() {
const response = await fetch('/api/server/restart', { method: 'POST' });
const result = await response.json();
alert(result.message);
}
async function saveServerConfig() {
const config = {
server: {
httpPort: parseInt(document.getElementById('httpPort').value),
httpsPort: parseInt(document.getElementById('httpsPort').value),
domain: document.getElementById('domain').value,
publicAccess: document.getElementById('publicAccess').checked
}
};
const response = await fetch('/api/config', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(config)
});
const result = await response.json();
alert(result.message);
}
async function refreshTags() {
const response = await fetch('/api/tags');
const tags = await response.json();
renderTags(tags);
}
function renderTags(tags) {
const container = document.getElementById('tagsContainer');
container.innerHTML = '';
tags.forEach(tag => {
const tagElement = document.createElement('div');
tagElement.className = 'card';
tagElement.style.marginBottom = '15px';
const header = document.createElement('div');
header.style.cssText = 'display: flex; justify-content: space-between; align-items: center; margin-bottom: 15px;';
const title = document.createElement('h4');
title.textContent = tag.name + ' (' + tag.tagName + ')';
const controls = document.createElement('div');
const toggle = document.createElement('label');
toggle.className = 'toggle';
const checkbox = document.createElement('input');
checkbox.type = 'checkbox';
checkbox.checked = tag.enabled;
checkbox.onchange = () => toggleTag(tag.id, checkbox.checked);
const slider = document.createElement('span');
slider.className = 'slider';
toggle.appendChild(checkbox);
toggle.appendChild(slider);
const deleteBtn = document.createElement('button');
deleteBtn.className = 'btn btn-danger';
deleteBtn.textContent = 'Löschen';
deleteBtn.style.marginLeft = '10px';
deleteBtn.onclick = () => deleteTag(tag.id);
controls.appendChild(toggle);
controls.appendChild(deleteBtn);
header.appendChild(title);
header.appendChild(controls);
// Form-Felder
const formGrid = document.createElement('div');
formGrid.className = 'grid';
// Tag-Name Feld
const tagNameGroup = document.createElement('div');
tagNameGroup.className = 'form-group';
tagNameGroup.innerHTML = '<label>Tag-Name im Template</label>';
const tagNameInput = document.createElement('input');
tagNameInput.type = 'text';
tagNameInput.value = tag.tagName;
tagNameInput.onchange = () => updateTagProperty(tag.id, 'tagName', tagNameInput.value);
tagNameGroup.appendChild(tagNameInput);
// Position Feld
const positionGroup = document.createElement('div');
positionGroup.className = 'form-group';
positionGroup.innerHTML = '<label>Position</label>';
const positionSelect = document.createElement('select');
const positions = [
{value: 'top', text: 'Oben'},
{value: 'bottom', text: 'Unten'},
{value: 'header', text: 'Header'},
{value: 'footer', text: 'Footer'},
{value: 'background', text: 'Hintergrund'},
{value: 'end', text: 'Ende'}
];
positions.forEach(pos => {
const option = document.createElement('option');
option.value = pos.value;
option.text = pos.text;
option.selected = tag.position === pos.value;
positionSelect.appendChild(option);
});
positionSelect.onchange = () => updateTagProperty(tag.id, 'position', positionSelect.value);
positionGroup.appendChild(positionSelect);
// Typ Feld
const typeGroup = document.createElement('div');
typeGroup.className = 'form-group';
typeGroup.innerHTML = '<label>Typ</label>';
const typeSelect = document.createElement('select');
const types = [
{value: 'text', text: 'Text'},
{value: 'image', text: 'Bild'},
{value: 'html', text: 'HTML'}
];
types.forEach(type => {
const option = document.createElement('option');
option.value = type.value;
option.text = type.text;
option.selected = tag.type === type.value;
typeSelect.appendChild(option);
});
typeSelect.onchange = () => updateTagProperty(tag.id, 'type', typeSelect.value);
typeGroup.appendChild(typeSelect);
formGrid.appendChild(tagNameGroup);
formGrid.appendChild(positionGroup);
formGrid.appendChild(typeGroup);
// Content Feld
const contentGroup = document.createElement('div');
contentGroup.className = 'form-group';
contentGroup.innerHTML = '<label>Inhalt</label>';
const contentTextarea = document.createElement('textarea');
contentTextarea.value = tag.content;
contentTextarea.placeholder = 'Tag-Inhalt...';
contentTextarea.onchange = () => updateTagProperty(tag.id, 'content', contentTextarea.value);
contentGroup.appendChild(contentTextarea);
tagElement.appendChild(header);
tagElement.appendChild(formGrid);
tagElement.appendChild(contentGroup);
container.appendChild(tagElement);
});
}
async function addNewTag() {
const name = prompt('Name für den neuen Tag:');
if (!name) return;
const tagName = prompt('Tag-Name im Template (z.B. CUSTOM_TAG):');
if (!tagName) return;
const newTag = {
id: Date.now().toString(),
name: name,
tagName: tagName.toUpperCase(),
enabled: false,
content: '',
position: 'top',
type: 'text'
};
const response = await fetch('/api/tags', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(newTag)
});
const result = await response.json();
if (result.success) {
refreshTags();
} else {
alert('Fehler beim Erstellen des Tags: ' + result.message);
}
}
async function toggleTag(tagId, enabled) {
const response = await fetch('/api/tags/' + tagId + '/toggle', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ enabled })
});
const result = await response.json();
if (!result.success) {
alert('Fehler beim Aktivieren/Deaktivieren: ' + result.message);
}
}
async function updateTagProperty(tagId, property, value) {
const response = await fetch('/api/tags/' + tagId, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ [property]: value })
});
const result = await response.json();
if (!result.success) {
alert('Fehler beim Aktualisieren: ' + result.message);
}
}
async function deleteTag(tagId) {
if (!confirm('Tag wirklich löschen?')) return;
const response = await fetch('/api/tags/' + tagId, {
method: 'DELETE'
});
const result = await response.json();
if (result.success) {
refreshTags();
} else {
alert('Fehler beim Löschen: ' + result.message);
}
}
async function saveCustomTags() {
// Diese Funktion ist jetzt durch die einzelnen Tag-Updates ersetzt
await refreshTags();
}
</script>
</body>
</html>
`);
});
// API Routes
// Server-Steuerung
app.post('/api/server/start', (req, res) => {
exec(`cd ${MAIN_SERVER_DIR} && nohup node server.js > server.log 2>&1 &`, (error) => {
if (error) {
console.error('❌ Fehler beim Starten:', error);
return res.json({ success: false, message: 'Fehler beim Starten des Servers' });
}
serverStatus.lastRestart = new Date();
setTimeout(checkServerStatus, 2000);
res.json({ success: true, message: '🚀 Server wird gestartet...' });
});
});
app.post('/api/server/stop', (req, res) => {
exec('pkill -f "node server.js"', (error) => {
serverStatus.running = false;
serverStatus.pid = null;
setTimeout(checkServerStatus, 2000);
res.json({ success: true, message: '⏹️ Server gestoppt' });
});
});
app.post('/api/server/restart', (req, res) => {
exec('pkill -f "node server.js"', () => {
setTimeout(() => {
exec(`cd ${MAIN_SERVER_DIR} && nohup node server.js > server.log 2>&1 &`, (error) => {
serverStatus.lastRestart = new Date();
setTimeout(checkServerStatus, 2000);
res.json({ success: true, message: 'Server neu gestartet' });
});
}, 1000);
});
});
// Tag-Management APIs
app.get('/api/tags', (req, res) => {
const config = loadConfig();
res.json(config.customTags || []);
});
app.post('/api/tags', (req, res) => {
try {
const config = loadConfig();
const newTag = req.body;
// Validierung
if (!newTag.name || !newTag.tagName) {
return res.json({ success: false, message: 'Name und Tag-Name sind erforderlich' });
}
// Prüfen ob Tag-Name bereits existiert
const exists = config.customTags.find(tag => tag.tagName === newTag.tagName);
if (exists) {
return res.json({ success: false, message: 'Tag-Name bereits vorhanden' });
}
if (!config.customTags) config.customTags = [];
config.customTags.push(newTag);
if (saveConfig(config)) {
res.json({ success: true, message: 'Tag erfolgreich erstellt' });
} else {
res.json({ success: false, message: 'Fehler beim Speichern' });
}
} catch (error) {
res.json({ success: false, message: 'Server-Fehler: ' + error.message });
}
});
app.put('/api/tags/:id', (req, res) => {
try {
const config = loadConfig();
const tagId = req.params.id;
const updates = req.body;
const tagIndex = config.customTags.findIndex(tag => tag.id === tagId);
if (tagIndex === -1) {
return res.json({ success: false, message: 'Tag nicht gefunden' });
}
// Tag aktualisieren
config.customTags[tagIndex] = { ...config.customTags[tagIndex], ...updates };
if (saveConfig(config)) {
res.json({ success: true, message: 'Tag aktualisiert' });
} else {
res.json({ success: false, message: 'Fehler beim Speichern' });
}
} catch (error) {
res.json({ success: false, message: 'Server-Fehler: ' + error.message });
}
});
app.post('/api/tags/:id/toggle', (req, res) => {
try {
const config = loadConfig();
const tagId = req.params.id;
const { enabled } = req.body;
const tagIndex = config.customTags.findIndex(tag => tag.id === tagId);
if (tagIndex === -1) {
return res.json({ success: false, message: 'Tag nicht gefunden' });
}
config.customTags[tagIndex].enabled = enabled;
if (saveConfig(config)) {
res.json({
success: true,
message: enabled ? 'Tag aktiviert' : 'Tag deaktiviert'
});
} else {
res.json({ success: false, message: 'Fehler beim Speichern' });
}
} catch (error) {
res.json({ success: false, message: 'Server-Fehler: ' + error.message });
}
});
app.delete('/api/tags/:id', (req, res) => {
try {
const config = loadConfig();
const tagId = req.params.id;
const tagIndex = config.customTags.findIndex(tag => tag.id === tagId);
if (tagIndex === -1) {
return res.json({ success: false, message: 'Tag nicht gefunden' });
}
config.customTags.splice(tagIndex, 1);
if (saveConfig(config)) {
res.json({ success: true, message: 'Tag gelöscht' });
} else {
res.json({ success: false, message: 'Fehler beim Speichern' });
}
} catch (error) {
res.json({ success: false, message: 'Server-Fehler: ' + error.message });
}
});
// Public API für externe Tag-Aktivierung
app.post('/api/public/tags/:tagName/activate', (req, res) => {
try {
const config = loadConfig();
const tagName = req.params.tagName.toUpperCase();
const tagIndex = config.customTags.findIndex(tag => tag.tagName === tagName);
if (tagIndex === -1) {
return res.json({ success: false, message: 'Tag nicht gefunden' });
}
config.customTags[tagIndex].enabled = true;
if (saveConfig(config)) {
res.json({
success: true,
message: `Tag ${tagName} aktiviert`,
tag: config.customTags[tagIndex]
});
} else {
res.json({ success: false, message: 'Fehler beim Speichern' });
}
} catch (error) {
res.json({ success: false, message: 'Server-Fehler: ' + error.message });
}
});
app.post('/api/public/tags/:tagName/deactivate', (req, res) => {
try {
const config = loadConfig();
const tagName = req.params.tagName.toUpperCase();
const tagIndex = config.customTags.findIndex(tag => tag.tagName === tagName);
if (tagIndex === -1) {
return res.json({ success: false, message: 'Tag nicht gefunden' });
}
config.customTags[tagIndex].enabled = false;
if (saveConfig(config)) {
res.json({
success: true,
message: `Tag ${tagName} deaktiviert`,
tag: config.customTags[tagIndex]
});
} else {
res.json({ success: false, message: 'Fehler beim Speichern' });
}
} catch (error) {
res.json({ success: false, message: 'Server-Fehler: ' + error.message });
}
});
// Liste der verfügbaren Tags für externe Systeme
app.get('/api/public/tags', (req, res) => {
try {
const config = loadConfig();
const publicTags = config.customTags.map(tag => ({
tagName: tag.tagName,
name: tag.name,
enabled: tag.enabled,
type: tag.type,
position: tag.position
}));
res.json(publicTags);
} catch (error) {
res.json({ success: false, message: 'Server-Fehler: ' + error.message });
}
});
// Konfiguration
app.get('/api/config', (req, res) => {
res.json(currentConfig);
});
app.post('/api/config', (req, res) => {
try {
currentConfig = { ...currentConfig, ...req.body };
if (saveConfig(currentConfig)) {
io.emit('config-update', currentConfig);
res.json({ success: true, message: '✅ Konfiguration gespeichert' });
} else {
res.json({ success: false, message: '❌ Fehler beim Speichern' });
}
} catch (error) {
res.status(500).json({ success: false, message: error.message });
}
});
// SSL-Upload
app.post('/api/ssl/upload', upload.fields([
{ name: 'cert', maxCount: 1 },
{ name: 'key', maxCount: 1 }
]), (req, res) => {
try {
if (req.files.cert && req.files.key) {
const certPath = path.join(MAIN_SERVER_DIR, '203_cert.pem');
const keyPath = path.join(MAIN_SERVER_DIR, '203_key.pem');
fs.copyFileSync(req.files.cert[0].path, certPath);
fs.copyFileSync(req.files.key[0].path, keyPath);
// Temp-Dateien löschen
fs.unlinkSync(req.files.cert[0].path);
fs.unlinkSync(req.files.key[0].path);
res.json({ success: true, message: '🔒 SSL-Zertifikate hochgeladen' });
} else {
res.json({ success: false, message: 'Beide Dateien erforderlich' });
}
} catch (error) {
res.status(500).json({ success: false, message: error.message });
}
});
// Self-Signed Zertifikat generieren
app.post('/api/ssl/generate', (req, res) => {
const domain = req.body.domain || 'localhost';
const certPath = path.join(MAIN_SERVER_DIR, '203_cert.pem');
const keyPath = path.join(MAIN_SERVER_DIR, '203_key.pem');
const command = `openssl req -x509 -newkey rsa:4096 -keyout "${keyPath}" -out "${certPath}" -days 365 -nodes -subj "/C=DE/ST=State/L=City/O=Organization/CN=${domain}"`;
exec(command, (error) => {
if (error) {
return res.json({ success: false, message: 'OpenSSL nicht verfügbar oder Fehler' });
}
res.json({ success: true, message: `🔧 Self-Signed Zertifikat für ${domain} erstellt` });
});
});
// Datenquellen-Management
app.get('/api/datasources', (req, res) => {
res.json(currentConfig.dataSources || []);
});
app.post('/api/datasources', (req, res) => {
const { name, type, url, headers, mapping } = req.body;
const dataSource = {
id: Date.now().toString(),
name,
type,
url,
headers: headers || {},
mapping: mapping || {},
enabled: true,
created: new Date()
};
currentConfig.dataSources = currentConfig.dataSources || [];
currentConfig.dataSources.push(dataSource);
if (saveConfig(currentConfig)) {
res.json({ success: true, message: 'Datenquelle hinzugefügt', dataSource });
} else {
res.status(500).json({ success: false, message: 'Fehler beim Speichern' });
}
});
// Server starten
server.listen(MANAGEMENT_PORT, () => {
console.log('\n🎛 Management-Server gestartet!');
console.log(`📍 Dashboard: http://localhost:${MANAGEMENT_PORT}`);
console.log(`🔧 Verwalte deinen DOCX Template Server`);
console.log('─'.repeat(50));
// Initialer Status-Check
setTimeout(checkServerStatus, 1000);
});