✨ 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
1011 lines
42 KiB
JavaScript
1011 lines
42 KiB
JavaScript
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);
|
||
}); |