1052 lines
40 KiB
HTML
1052 lines
40 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="de">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>Lizenzgenerator</title>
|
||
<style>
|
||
* {
|
||
margin: 0;
|
||
padding: 0;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
body {
|
||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||
min-height: 100vh;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
padding: 20px;
|
||
}
|
||
|
||
.container {
|
||
background: rgba(255, 255, 255, 0.95);
|
||
backdrop-filter: blur(10px);
|
||
border-radius: 20px;
|
||
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1);
|
||
padding: 40px;
|
||
max-width: 700px;
|
||
width: 100%;
|
||
position: relative;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.container::before {
|
||
content: '';
|
||
position: absolute;
|
||
top: 0;
|
||
left: 0;
|
||
right: 0;
|
||
height: 4px;
|
||
background: linear-gradient(90deg, #667eea, #764ba2, #f093fb, #f5576c);
|
||
background-size: 300% 100%;
|
||
animation: gradientShift 3s ease infinite;
|
||
}
|
||
|
||
@keyframes gradientShift {
|
||
0% { background-position: 0% 50%; }
|
||
50% { background-position: 100% 50%; }
|
||
100% { background-position: 0% 50%; }
|
||
}
|
||
|
||
h1 {
|
||
text-align: center;
|
||
color: #333;
|
||
margin-bottom: 30px;
|
||
font-size: 2.2em;
|
||
font-weight: 300;
|
||
letter-spacing: -1px;
|
||
}
|
||
|
||
.form-group {
|
||
margin-bottom: 25px;
|
||
position: relative;
|
||
}
|
||
|
||
label {
|
||
display: block;
|
||
margin-bottom: 8px;
|
||
color: #555;
|
||
font-weight: 500;
|
||
font-size: 0.95em;
|
||
}
|
||
|
||
input, textarea {
|
||
width: 100%;
|
||
padding: 15px 20px;
|
||
border: 2px solid #e0e0e0;
|
||
border-radius: 12px;
|
||
font-size: 1em;
|
||
transition: all 0.3s ease;
|
||
background: #fafafa;
|
||
font-family: inherit;
|
||
}
|
||
|
||
textarea {
|
||
resize: vertical;
|
||
min-height: 80px;
|
||
}
|
||
|
||
input:focus, textarea:focus {
|
||
outline: none;
|
||
border-color: #667eea;
|
||
background: white;
|
||
box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
|
||
transform: translateY(-2px);
|
||
}
|
||
|
||
input:hover, textarea:hover {
|
||
border-color: #ccc;
|
||
}
|
||
|
||
.db-config {
|
||
background: linear-gradient(135deg, #f8f9ff 0%, #e8f2ff 100%);
|
||
border: 2px solid #e3f2fd;
|
||
border-radius: 12px;
|
||
padding: 20px;
|
||
margin-bottom: 25px;
|
||
opacity: 0;
|
||
transform: translateY(-20px);
|
||
transition: all 0.4s ease;
|
||
max-height: 0;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.db-config.show {
|
||
opacity: 1;
|
||
transform: translateY(0);
|
||
max-height: 1000px;
|
||
}
|
||
|
||
.db-config h3 {
|
||
color: #1565c0;
|
||
margin-bottom: 15px;
|
||
font-size: 1.1em;
|
||
}
|
||
|
||
.tier-notice {
|
||
background: linear-gradient(135deg, #fff3e0 0%, #ffe0b2 100%);
|
||
border: 2px solid #ffcc02;
|
||
border-radius: 8px;
|
||
padding: 12px;
|
||
margin-top: 10px;
|
||
font-size: 0.9em;
|
||
color: #f57c00;
|
||
text-align: center;
|
||
}
|
||
|
||
.generate-btn {
|
||
width: 100%;
|
||
padding: 18px;
|
||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||
color: white;
|
||
border: none;
|
||
border-radius: 12px;
|
||
font-size: 1.1em;
|
||
font-weight: 600;
|
||
cursor: pointer;
|
||
transition: all 0.3s ease;
|
||
margin-top: 10px;
|
||
position: relative;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.generate-btn:hover {
|
||
transform: translateY(-2px);
|
||
box-shadow: 0 10px 25px rgba(102, 126, 234, 0.3);
|
||
}
|
||
|
||
.generate-btn:active {
|
||
transform: translateY(0);
|
||
}
|
||
|
||
.result-section {
|
||
margin-top: 30px;
|
||
opacity: 0;
|
||
transform: translateY(20px);
|
||
transition: all 0.4s ease;
|
||
}
|
||
|
||
.result-section.show {
|
||
opacity: 1;
|
||
transform: translateY(0);
|
||
}
|
||
|
||
.license-output {
|
||
background: linear-gradient(135deg, #f8f9ff 0%, #e8f2ff 100%);
|
||
border: 2px solid #e3f2fd;
|
||
border-radius: 12px;
|
||
padding: 20px;
|
||
font-family: 'Courier New', monospace;
|
||
font-size: 0.9em;
|
||
word-break: break-all;
|
||
color: #1565c0;
|
||
position: relative;
|
||
}
|
||
|
||
.license-label {
|
||
font-family: 'Segoe UI', sans-serif;
|
||
font-size: 0.85em;
|
||
color: #666;
|
||
margin-bottom: 8px;
|
||
font-weight: 500;
|
||
}
|
||
|
||
.copy-btn {
|
||
width: 100%;
|
||
padding: 12px;
|
||
background: #4caf50;
|
||
color: white;
|
||
border: none;
|
||
border-radius: 8px;
|
||
font-size: 0.95em;
|
||
font-weight: 500;
|
||
cursor: pointer;
|
||
margin-top: 15px;
|
||
transition: all 0.3s ease;
|
||
}
|
||
|
||
.copy-btn:hover {
|
||
background: #45a049;
|
||
transform: translateY(-1px);
|
||
}
|
||
|
||
.copy-btn.copied {
|
||
background: #2196f3;
|
||
animation: pulse 0.6s;
|
||
}
|
||
|
||
@keyframes pulse {
|
||
0% { transform: scale(1); }
|
||
50% { transform: scale(1.05); }
|
||
100% { transform: scale(1); }
|
||
}
|
||
|
||
.success {
|
||
background: #e8f5e8;
|
||
color: #2e7d32;
|
||
padding: 15px;
|
||
border-radius: 8px;
|
||
margin-top: 15px;
|
||
border-left: 4px solid #4caf50;
|
||
font-size: 0.9em;
|
||
opacity: 0;
|
||
transform: translateY(-10px);
|
||
transition: all 0.3s ease;
|
||
}
|
||
|
||
.success.show {
|
||
opacity: 1;
|
||
transform: translateY(0);
|
||
}
|
||
|
||
.error {
|
||
background: #ffebee;
|
||
color: #c62828;
|
||
padding: 15px;
|
||
border-radius: 8px;
|
||
margin-top: 15px;
|
||
border-left: 4px solid #f44336;
|
||
font-size: 0.9em;
|
||
opacity: 0;
|
||
transform: translateY(-10px);
|
||
transition: all 0.3s ease;
|
||
}
|
||
|
||
.error.show {
|
||
opacity: 1;
|
||
transform: translateY(0);
|
||
}
|
||
|
||
.info-text {
|
||
text-align: center;
|
||
color: #666;
|
||
font-size: 0.85em;
|
||
margin-top: 20px;
|
||
line-height: 1.5;
|
||
}
|
||
|
||
@media (max-width: 480px) {
|
||
.container {
|
||
padding: 30px 20px;
|
||
margin: 10px;
|
||
}
|
||
|
||
h1 {
|
||
font-size: 1.8em;
|
||
}
|
||
}
|
||
|
||
.loading {
|
||
display: inline-block;
|
||
width: 20px;
|
||
height: 20px;
|
||
border: 3px solid rgba(255,255,255,.3);
|
||
border-radius: 50%;
|
||
border-top-color: #fff;
|
||
animation: spin 1s ease-in-out infinite;
|
||
margin-right: 10px;
|
||
}
|
||
|
||
@keyframes spin {
|
||
to { transform: rotate(360deg); }
|
||
}
|
||
|
||
/* Standortsuche Styles */
|
||
.coordinates-display {
|
||
animation: slideDown 0.4s ease;
|
||
}
|
||
|
||
.map-container {
|
||
animation: slideDown 0.4s ease;
|
||
}
|
||
|
||
@keyframes slideDown {
|
||
from {
|
||
opacity: 0;
|
||
transform: translateY(-10px);
|
||
}
|
||
to {
|
||
opacity: 1;
|
||
transform: translateY(0);
|
||
}
|
||
}
|
||
|
||
#mapFrame iframe {
|
||
border-radius: 10px;
|
||
}
|
||
|
||
.coordinates-display h4 {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
}
|
||
|
||
.coordinates-display strong {
|
||
color: #2e7d32;
|
||
}
|
||
|
||
/* Verbesserte Standortsuche Layouts */
|
||
.location-search-container {
|
||
display: flex;
|
||
gap: 10px;
|
||
align-items: stretch;
|
||
}
|
||
|
||
.location-search-container input {
|
||
flex: 1;
|
||
min-width: 0;
|
||
}
|
||
|
||
.location-search-container button {
|
||
white-space: nowrap;
|
||
min-width: 120px;
|
||
}
|
||
|
||
/* Responsive Design für Standortsuche */
|
||
@media (max-width: 600px) {
|
||
.location-search-container {
|
||
flex-direction: column;
|
||
gap: 15px;
|
||
}
|
||
|
||
.location-search-container button {
|
||
min-width: auto;
|
||
width: 100%;
|
||
}
|
||
|
||
.coordinates-display .flex-container {
|
||
flex-direction: column;
|
||
gap: 10px;
|
||
}
|
||
}
|
||
|
||
/* Interaktive Karte Styles */
|
||
#interactiveMap {
|
||
position: relative;
|
||
}
|
||
|
||
#map {
|
||
border-radius: 10px;
|
||
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
||
}
|
||
|
||
.leaflet-container {
|
||
border-radius: 10px;
|
||
}
|
||
|
||
.leaflet-control-zoom {
|
||
border: none;
|
||
box-shadow: 0 2px 8px rgba(0,0,0,0.2);
|
||
}
|
||
|
||
.leaflet-control-zoom a {
|
||
background: white;
|
||
color: #333;
|
||
border: 1px solid #ddd;
|
||
}
|
||
|
||
.leaflet-control-zoom a:hover {
|
||
background: #f8f9fa;
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div class="container">
|
||
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px;">
|
||
<h1 style="margin: 0;">🔐 Lizenzgenerator</h1>
|
||
<button onclick="logout()" style="padding: 10px 20px; background: #f44336; color: white; border: none; border-radius: 8px; cursor: pointer; font-weight: 500; transition: all 0.3s ease;">🚪 Abmelden</button>
|
||
</div>
|
||
|
||
<div class="form-group">
|
||
<label for="mac">Geräte-ID (MAC-Adresse)</label>
|
||
<input type="text" id="mac" placeholder="00:1A:2B:3C:4D:5E" required>
|
||
</div>
|
||
|
||
<div class="form-group">
|
||
<label for="tier">Lizenzstufe (1–4)</label>
|
||
<input type="number" id="tier" min="1" max="4" placeholder="z.B. 2" required onchange="toggleTokenFields()">
|
||
<div class="tier-notice">
|
||
📝 Bei Lizenzstufe 3+ wird der Schlüssel automatisch als API-Token gespeichert
|
||
</div>
|
||
</div>
|
||
|
||
<div id="dbConfig" class="db-config">
|
||
<h3>🗄️ Token-Informationen (für Stufe 3+)</h3>
|
||
<div class="form-group">
|
||
<label for="description">Token Beschreibung</label>
|
||
<textarea id="description" placeholder="z.B. API-Zugang für Standort München"></textarea>
|
||
</div>
|
||
<div class="form-group">
|
||
<label for="standorte">Standorte</label>
|
||
<input type="text" id="standorte" placeholder="z.B. München, Berlin">
|
||
</div>
|
||
|
||
<!-- Neue Standortsuche-Sektion -->
|
||
<div class="form-group">
|
||
<label for="locationSearch">Standort suchen & auf Karte anzeigen</label>
|
||
<div class="location-search-container">
|
||
<input type="text" id="locationSearch" placeholder="z.B. München, Marienplatz">
|
||
<button onclick="searchLocation(this)" style="padding: 15px 20px; background: #4caf50; color: white; border: none; border-radius: 12px; cursor: pointer; font-weight: 500; transition: all 0.3s ease;">🔍 Suchen</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Koordinaten-Anzeige -->
|
||
<div id="coordinates" class="coordinates-display" style="display: none;">
|
||
<div style="background: #f0f8ff; border: 2px solid #4caf50; border-radius: 8px; padding: 15px; margin-top: 15px;">
|
||
<h4 style="margin: 0 0 10px 0; color: #2e7d32;">📍 Gefundene Koordinaten:</h4>
|
||
<div class="flex-container" style="display: flex; gap: 20px; flex-wrap: wrap;">
|
||
<div>
|
||
<strong>Breitengrad (LAT):</strong>
|
||
<span id="latitude" style="font-family: monospace; color: #1565c0;"></span>
|
||
</div>
|
||
<div>
|
||
<strong>Längengrad (LON):</strong>
|
||
<span id="longitude" style="font-family: monospace; color: #1565c0;"></span>
|
||
</div>
|
||
</div>
|
||
<div style="margin-top: 15px; text-align: center;">
|
||
<div style="font-size: 0.85em; color: #666; margin-bottom: 10px;">
|
||
💡 Der Standort wird automatisch beim Generieren der Lizenz gespeichert
|
||
</div>
|
||
<button id="saveLocationBtn" onclick="saveLocationToDatabase()" style="padding: 12px 24px; background: #2196f3; color: white; border: none; border-radius: 8px; cursor: pointer; font-weight: 500; transition: all 0.3s ease;">
|
||
💾 Standort manuell speichern
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Karten-Container -->
|
||
<div id="mapContainer" class="map-container" style="display: none; margin-top: 20px;">
|
||
<h4 style="margin: 0 0 15px 0; color: #333;">🗺️ Standort auf der Karte:</h4>
|
||
<div id="mapFrame" style="width: 100%; height: 300px; border: 2px solid #ddd; border-radius: 12px; overflow: hidden;"></div>
|
||
<div style="margin-top: 10px; font-size: 0.85em; color: #666; text-align: center;">
|
||
💡 <strong>Interaktiv:</strong> Klicken Sie auf die Karte, um den Pin zu verschieben
|
||
</div>
|
||
</div>
|
||
<div class="info-text" style="margin-top: 10px; font-size: 0.8em;">
|
||
📝 Standorte werden in der lokalen PostgreSQL-Datenbank gespeichert
|
||
</div>
|
||
</div>
|
||
|
||
<button class="generate-btn" onclick="generateLicense()">
|
||
<span id="btn-text">Lizenz generieren</span>
|
||
</button>
|
||
|
||
<div id="result" class="result-section">
|
||
<div class="license-label">Generierter Lizenzschlüssel:</div>
|
||
<div id="license-output" class="license-output"></div>
|
||
<button id="copyButton" class="copy-btn" onclick="copyToClipboard()">
|
||
📋 In Zwischenablage kopieren
|
||
</button>
|
||
</div>
|
||
|
||
<div id="success" class="success"></div>
|
||
<div id="error" class="error"></div>
|
||
|
||
<div class="info-text">
|
||
Geben Sie eine gültige MAC-Adresse und Lizenzstufe ein, um einen sicheren Lizenzschlüssel zu generieren.
|
||
</div>
|
||
</div>
|
||
|
||
<script>
|
||
// Toggle Token-Felder basierend auf Lizenzstufe
|
||
function toggleTokenFields() {
|
||
const tierInput = document.getElementById("tier");
|
||
const dbConfig = document.getElementById("dbConfig");
|
||
const tier = parseInt(tierInput.value);
|
||
|
||
if (tier >= 3 && !isNaN(tier)) {
|
||
dbConfig.innerHTML = `
|
||
<h3>🗄️ Token-Informationen (für Stufe 3+)</h3>
|
||
<div class="form-group">
|
||
<label for="description">Token Beschreibung</label>
|
||
<textarea id="description" placeholder="z.B. API-Zugang für Standort München"></textarea>
|
||
</div>
|
||
<div class="form-group">
|
||
<label for="standorte">Standorte</label>
|
||
<input type="text" id="standorte" placeholder="z.B. München, Berlin">
|
||
</div>
|
||
|
||
<!-- Neue Standortsuche-Sektion -->
|
||
<div class="form-group">
|
||
<label for="locationSearch">Standort suchen & auf Karte anzeigen</label>
|
||
<div class="location-search-container">
|
||
<input type="text" id="locationSearch" placeholder="z.B. München, Marienplatz">
|
||
<button onclick="searchLocation(this)" style="padding: 15px 20px; background: #4caf50; color: white; border: none; border-radius: 12px; cursor: pointer; font-weight: 500; transition: all 0.3s ease;">🔍 Suchen</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Koordinaten-Anzeige -->
|
||
<div id="coordinates" class="coordinates-display" style="display: none;">
|
||
<div style="background: #f0f8ff; border: 2px solid #4caf50; border-radius: 8px; padding: 15px; margin-top: 15px;">
|
||
<h4 style="margin: 0 0 10px 0; color: #2e7d32;">📍 Gefundene Koordinaten:</h4>
|
||
<div style="display: flex; gap: 20px; flex-wrap: wrap;">
|
||
<div>
|
||
<strong>Breitengrad (LAT):</strong>
|
||
<span id="latitude" style="font-family: monospace; color: #1565c0;"></span>
|
||
</div>
|
||
<div>
|
||
<strong>Längengrad (LON):</strong>
|
||
<span id="longitude" style="font-family: monospace; color: #1565c0;"></span>
|
||
</div>
|
||
</div>
|
||
<div style="margin-top: 15px; text-align: center;">
|
||
<div style="font-size: 0.85em; color: #666; margin-bottom: 10px;">
|
||
💡 Der Standort wird automatisch beim Generieren der Lizenz gespeichert
|
||
</div>
|
||
<button id="saveLocationBtn" onclick="saveLocationToDatabase()" style="padding: 12px 24px; background: #2196f3; color: white; border: none; border-radius: 8px; cursor: pointer; font-weight: 500; transition: all 0.3s ease;">
|
||
💾 Standort manuell speichern
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Karten-Container -->
|
||
<div id="mapContainer" class="map-container" style="display: none; margin-top: 20px;">
|
||
<h4 style="margin: 0 0 15px 0; color: #333;">🗺️ Standort auf der Karte:</h4>
|
||
<div id="mapFrame" style="width: 100%; height: 300px; border: 2px solid #ddd; border-radius: 12px; overflow: hidden;"></div>
|
||
</div>
|
||
<div class="info-text" style="margin-top: 10px; font-size: 0.8em;">
|
||
📝 Standorte werden in der lokalen PostgreSQL-Datenbank gespeichert
|
||
</div>
|
||
`;
|
||
dbConfig.classList.add("show");
|
||
} else {
|
||
dbConfig.classList.remove("show");
|
||
setTimeout(() => {
|
||
if (!dbConfig.classList.contains("show")) {
|
||
dbConfig.innerHTML = "";
|
||
}
|
||
}, 400);
|
||
}
|
||
}
|
||
|
||
const secret = "542ff224606c61fb3024e22f76ef9ac8";
|
||
|
||
function isValidMac(mac) {
|
||
const pattern = /^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})$|^[0-9A-Fa-f]{12}$/;
|
||
return pattern.test(mac);
|
||
}
|
||
|
||
function showMessage(elementId, message, isError = false) {
|
||
const messageDiv = document.getElementById(elementId);
|
||
messageDiv.textContent = message;
|
||
messageDiv.classList.add("show");
|
||
setTimeout(() => {
|
||
messageDiv.classList.remove("show");
|
||
}, 4000);
|
||
}
|
||
|
||
function showError(message) {
|
||
showMessage("error", message, true);
|
||
}
|
||
|
||
function showSuccess(message) {
|
||
showMessage("success", message, false);
|
||
}
|
||
|
||
function setLoading(isLoading) {
|
||
const btnText = document.getElementById("btn-text");
|
||
const btn = document.querySelector(".generate-btn");
|
||
|
||
if (isLoading) {
|
||
btnText.innerHTML = '<span class="loading"></span>Generiere...';
|
||
btn.disabled = true;
|
||
btn.style.opacity = '0.7';
|
||
} else {
|
||
btnText.textContent = 'Lizenz generieren';
|
||
btn.disabled = false;
|
||
btn.style.opacity = '1';
|
||
}
|
||
}
|
||
|
||
async function saveToDatabase(token, tier) {
|
||
const description = document.getElementById("description").value.trim();
|
||
const standorte = document.getElementById("standorte").value.trim();
|
||
|
||
try {
|
||
const response = await fetch('/api/web/save-token', {
|
||
method: 'POST',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
},
|
||
body: JSON.stringify({
|
||
token: token,
|
||
description: description || `API-Token Stufe ${tier}`,
|
||
standorte: standorte
|
||
})
|
||
});
|
||
|
||
if (!response.ok) {
|
||
const errorData = await response.json();
|
||
throw new Error(errorData.message || 'Fehler beim Speichern in der Datenbank');
|
||
}
|
||
|
||
const result = await response.json();
|
||
return result;
|
||
} catch (error) {
|
||
// Fallback: Zeige dem Benutzer den SQL-Befehl an, den er manuell ausführen kann
|
||
const sql = `INSERT INTO api_tokens (token, description, standorte) VALUES ('${token}', '${description || `API-Token Stufe ${tier}`}', '${standorte}');`;
|
||
|
||
throw new Error(`Automatisches Speichern fehlgeschlagen. Server nicht erreichbar.\n\nFühren Sie folgenden SQL-Befehl manuell aus:\n${sql}`);
|
||
}
|
||
}
|
||
|
||
async function generateLicense() {
|
||
const macInput = document.getElementById("mac").value.trim();
|
||
const tierInput = document.getElementById("tier").value.trim();
|
||
const resultDiv = document.getElementById("result");
|
||
const licenseOutput = document.getElementById("license-output");
|
||
const errorDiv = document.getElementById("error");
|
||
const successDiv = document.getElementById("success");
|
||
|
||
// Reset states
|
||
resultDiv.classList.remove("show");
|
||
errorDiv.classList.remove("show");
|
||
successDiv.classList.remove("show");
|
||
setLoading(true);
|
||
|
||
// Simulate slight delay for better UX
|
||
await new Promise(resolve => setTimeout(resolve, 500));
|
||
|
||
try {
|
||
if (!isValidMac(macInput)) {
|
||
throw new Error("Ungültige MAC-Adresse. Bitte verwenden Sie das Format 00:1A:2B:3C:4D:5E");
|
||
}
|
||
|
||
const mac = macInput.replace(/[:-]/g, "").toUpperCase();
|
||
const tier = parseInt(tierInput);
|
||
|
||
if (isNaN(tier) || tier < 1 || tier > 4) {
|
||
throw new Error("Lizenzstufe muss eine Zahl zwischen 1 und 4 sein.");
|
||
}
|
||
|
||
// Standort automatisch speichern, falls vorhanden
|
||
let locationSaved = false;
|
||
const locationName = document.getElementById('locationSearch')?.value?.trim();
|
||
const latitude = document.getElementById('latitude')?.textContent;
|
||
const longitude = document.getElementById('longitude')?.textContent;
|
||
|
||
if (locationName && latitude && longitude && tier >= 3) {
|
||
try {
|
||
await saveLocationToDatabase();
|
||
locationSaved = true;
|
||
} catch (locationError) {
|
||
console.warn('Standort konnte nicht gespeichert werden:', locationError);
|
||
// Fahre trotzdem mit der Lizenzgenerierung fort
|
||
}
|
||
}
|
||
|
||
const data = `${mac}:${tier}`;
|
||
const enc = new TextEncoder();
|
||
const key = await crypto.subtle.importKey(
|
||
"raw",
|
||
enc.encode(secret),
|
||
{ name: "HMAC", hash: "SHA-256" },
|
||
false,
|
||
["sign"]
|
||
);
|
||
const signature = await crypto.subtle.sign("HMAC", key, enc.encode(data));
|
||
const hex = Array.from(new Uint8Array(signature))
|
||
.map(b => b.toString(16).padStart(2, "0"))
|
||
.join("")
|
||
.toUpperCase();
|
||
|
||
licenseOutput.textContent = hex;
|
||
resultDiv.classList.add("show");
|
||
|
||
// Reset copy button
|
||
const copyBtn = document.getElementById("copyButton");
|
||
copyBtn.textContent = "📋 In Zwischenablage kopieren";
|
||
copyBtn.classList.remove("copied");
|
||
|
||
// Bei Stufe 3+ in Datenbank speichern
|
||
if (tier >= 3) {
|
||
try {
|
||
await saveToDatabase(hex, tier);
|
||
let successMessage = `✅ Lizenzschlüssel generiert und als API-Token gespeichert!`;
|
||
if (locationSaved) {
|
||
successMessage += ` Standort wurde ebenfalls gespeichert.`;
|
||
}
|
||
showSuccess(successMessage);
|
||
} catch (dbError) {
|
||
showError(`⚠️ Lizenz generiert, aber Datenbank-Fehler: ${dbError.message}`);
|
||
}
|
||
} else {
|
||
let successMessage = `✅ Lizenzschlüssel erfolgreich generiert!`;
|
||
if (locationSaved) {
|
||
successMessage += ` Standort wurde in der Datenbank gespeichert.`;
|
||
}
|
||
showSuccess(successMessage);
|
||
}
|
||
|
||
} catch (error) {
|
||
showError(error.message);
|
||
} finally {
|
||
setLoading(false);
|
||
}
|
||
}
|
||
|
||
async function copyToClipboard() {
|
||
const licenseOutput = document.getElementById("license-output");
|
||
const copyBtn = document.getElementById("copyButton");
|
||
|
||
try {
|
||
await navigator.clipboard.writeText(licenseOutput.textContent);
|
||
copyBtn.textContent = "✅ Kopiert!";
|
||
copyBtn.classList.add("copied");
|
||
|
||
setTimeout(() => {
|
||
copyBtn.textContent = "📋 In Zwischenablage kopieren";
|
||
copyBtn.classList.remove("copied");
|
||
}, 2000);
|
||
} catch (err) {
|
||
// Fallback for older browsers
|
||
const textArea = document.createElement("textarea");
|
||
textArea.value = licenseOutput.textContent;
|
||
document.body.appendChild(textArea);
|
||
textArea.select();
|
||
document.execCommand('copy');
|
||
document.body.removeChild(textArea);
|
||
|
||
copyBtn.textContent = "✅ Kopiert!";
|
||
copyBtn.classList.add("copied");
|
||
|
||
setTimeout(() => {
|
||
copyBtn.textContent = "📋 In Zwischenablage kopieren";
|
||
copyBtn.classList.remove("copied");
|
||
}, 2000);
|
||
}
|
||
}
|
||
|
||
// Enter key support
|
||
document.addEventListener('keypress', function(e) {
|
||
if (e.key === 'Enter') {
|
||
generateLicense();
|
||
}
|
||
});
|
||
|
||
// Input formatting for MAC address
|
||
document.getElementById('mac').addEventListener('input', function(e) {
|
||
let value = e.target.value.replace(/[^0-9A-Fa-f]/g, '');
|
||
if (value.length > 12) value = value.substr(0, 12);
|
||
|
||
// Add colons every 2 characters
|
||
value = value.replace(/(.{2})/g, '$1:').replace(/:$/, '');
|
||
e.target.value = value;
|
||
});
|
||
|
||
// Input event listener für Lizenzstufe
|
||
document.getElementById('tier').addEventListener('input', toggleTokenFields);
|
||
|
||
// Standortsuche-Funktionalität
|
||
async function searchLocation(buttonElement) {
|
||
const locationInput = document.getElementById('locationSearch').value.trim();
|
||
const coordinatesDiv = document.getElementById('coordinates');
|
||
const mapContainer = document.getElementById('mapContainer');
|
||
const latitudeSpan = document.getElementById('latitude');
|
||
const longitudeSpan = document.getElementById('longitude');
|
||
const mapFrame = document.getElementById('mapFrame');
|
||
|
||
if (!locationInput) {
|
||
showError('Bitte geben Sie einen Standort ein.');
|
||
return;
|
||
}
|
||
|
||
let originalText = '';
|
||
let searchBtn = null;
|
||
|
||
try {
|
||
// Zeige Ladeanimation
|
||
searchBtn = buttonElement || document.querySelector('button[onclick*="searchLocation"]');
|
||
if (searchBtn) {
|
||
originalText = searchBtn.innerHTML;
|
||
searchBtn.innerHTML = '<span class="loading"></span>Suche...';
|
||
searchBtn.disabled = true;
|
||
}
|
||
|
||
// API-Abfrage an Nominatim (OpenStreetMap)
|
||
const response = await fetch(`https://nominatim.openstreetmap.org/search?format=json&q=${encodeURIComponent(locationInput)}&limit=1`);
|
||
|
||
if (!response.ok) {
|
||
throw new Error('Fehler bei der API-Abfrage');
|
||
}
|
||
|
||
const data = await response.json();
|
||
|
||
if (data.length === 0) {
|
||
throw new Error('Standort nicht gefunden. Bitte versuchen Sie eine andere Beschreibung.');
|
||
}
|
||
|
||
const location = data[0];
|
||
const lat = parseFloat(location.lat);
|
||
const lon = parseFloat(location.lon);
|
||
|
||
// Koordinaten anzeigen
|
||
updateCoordinates(lat, lon);
|
||
coordinatesDiv.style.display = 'block';
|
||
|
||
// Interaktive Karte erstellen
|
||
createInteractiveMap(lat, lon);
|
||
mapContainer.style.display = 'block';
|
||
|
||
// Erfolgsmeldung
|
||
showSuccess(`✅ Standort "${locationInput}" erfolgreich gefunden! Klicken Sie auf die Karte, um den Pin zu verschieben.`);
|
||
|
||
} catch (error) {
|
||
showError(`Fehler bei der Standortsuche: ${error.message}`);
|
||
coordinatesDiv.style.display = 'none';
|
||
mapContainer.style.display = 'none';
|
||
} finally {
|
||
// Button zurücksetzen
|
||
if (searchBtn && originalText) {
|
||
searchBtn.innerHTML = originalText;
|
||
searchBtn.disabled = false;
|
||
}
|
||
}
|
||
}
|
||
|
||
// Koordinaten aktualisieren
|
||
function updateCoordinates(lat, lon) {
|
||
const latitudeSpan = document.getElementById('latitude');
|
||
const longitudeSpan = document.getElementById('longitude');
|
||
|
||
if (latitudeSpan && longitudeSpan) {
|
||
latitudeSpan.textContent = lat.toFixed(6);
|
||
longitudeSpan.textContent = lon.toFixed(6);
|
||
}
|
||
}
|
||
|
||
// Interaktive Karte erstellen
|
||
function createInteractiveMap(initialLat, initialLon) {
|
||
const mapFrame = document.getElementById('mapFrame');
|
||
|
||
// Verwende Leaflet.js für interaktive Karte
|
||
const mapHtml = `
|
||
<div id="interactiveMap" style="width: 100%; height: 100%; position: relative;">
|
||
<div id="map" style="width: 100%; height: 100%; border-radius: 10px;"></div>
|
||
<div style="position: absolute; top: 10px; right: 10px; background: white; padding: 8px; border-radius: 6px; box-shadow: 0 2px 8px rgba(0,0,0,0.2); font-size: 12px; color: #666;">
|
||
📍 Klicken Sie auf die Karte, um den Pin zu verschieben
|
||
</div>
|
||
</div>
|
||
`;
|
||
|
||
mapFrame.innerHTML = mapHtml;
|
||
|
||
// Leaflet.js laden und Karte initialisieren
|
||
loadLeafletAndCreateMap(initialLat, initialLon);
|
||
}
|
||
|
||
// Leaflet.js laden und Karte erstellen
|
||
function loadLeafletAndCreateMap(initialLat, initialLon) {
|
||
// Prüfe ob Leaflet bereits geladen ist
|
||
if (typeof L !== 'undefined') {
|
||
createMap(initialLat, initialLon);
|
||
return;
|
||
}
|
||
|
||
// Leaflet CSS laden
|
||
const leafletCSS = document.createElement('link');
|
||
leafletCSS.rel = 'stylesheet';
|
||
leafletCSS.href = 'https://unpkg.com/leaflet@1.9.4/dist/leaflet.css';
|
||
document.head.appendChild(leafletCSS);
|
||
|
||
// Leaflet JavaScript laden
|
||
const leafletScript = document.createElement('script');
|
||
leafletScript.src = 'https://unpkg.com/leaflet@1.9.4/dist/leaflet.js';
|
||
leafletScript.onload = () => createMap(initialLat, initialLon);
|
||
document.head.appendChild(leafletScript);
|
||
}
|
||
|
||
// Karte mit Leaflet erstellen
|
||
function createMap(initialLat, initialLon) {
|
||
try {
|
||
const map = L.map('map').setView([initialLat, initialLon], 15);
|
||
|
||
// OpenStreetMap Tile Layer
|
||
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
|
||
attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
|
||
}).addTo(map);
|
||
|
||
// Marker erstellen
|
||
const marker = L.marker([initialLat, initialLon], {
|
||
draggable: true,
|
||
title: 'Standort'
|
||
}).addTo(map);
|
||
|
||
// Marker-Drag Event
|
||
marker.on('dragend', function(event) {
|
||
const newLat = event.target.getLatLng().lat;
|
||
const newLon = event.target.getLatLng().lng;
|
||
updateCoordinates(newLat, newLon);
|
||
showSuccess(`📍 Pin auf neue Position verschoben: ${newLat.toFixed(6)}, ${newLon.toFixed(6)}`);
|
||
});
|
||
|
||
// Klick-Event auf die Karte
|
||
map.on('click', function(event) {
|
||
const newLat = event.latlng.lat;
|
||
const newLon = event.latlng.lng;
|
||
|
||
// Marker auf neue Position setzen
|
||
marker.setLatLng([newLat, newLon]);
|
||
|
||
// Koordinaten aktualisieren
|
||
updateCoordinates(newLat, newLon);
|
||
|
||
// Erfolgsmeldung
|
||
showSuccess(`📍 Pin auf neue Position gesetzt: ${newLat.toFixed(6)}, ${newLon.toFixed(6)}`);
|
||
});
|
||
|
||
// Zoom-Controls hinzufügen
|
||
map.zoomControl.setPosition('bottomright');
|
||
|
||
} catch (error) {
|
||
console.error('Fehler beim Erstellen der Karte:', error);
|
||
// Fallback zu iframe
|
||
const mapFrame = document.getElementById('mapFrame');
|
||
const mapUrl = `https://www.openstreetmap.org/export/embed.html?bbox=${initialLon-0.01},${initialLat-0.01},${initialLon+0.01},${initialLat+0.01}&layer=mapnik&marker=${initialLat},${initialLon}`;
|
||
mapFrame.innerHTML = `<iframe src="${mapUrl}" width="100%" height="100%" frameborder="0" scrolling="no" marginheight="0" marginwidth="0" title="Standort auf der Karte"></iframe>`;
|
||
}
|
||
}
|
||
|
||
// Standort in Datenbank speichern
|
||
async function saveLocationToDatabase() {
|
||
const locationName = document.getElementById('locationSearch').value.trim();
|
||
const latitude = document.getElementById('latitude').textContent;
|
||
const longitude = document.getElementById('longitude').textContent;
|
||
const saveBtn = document.getElementById('saveLocationBtn');
|
||
|
||
if (!locationName || !latitude || !longitude) {
|
||
showError('Bitte suchen Sie zuerst einen Standort.');
|
||
return;
|
||
}
|
||
|
||
try {
|
||
// Button-Status ändern
|
||
const originalText = saveBtn.innerHTML;
|
||
saveBtn.innerHTML = '<span class="loading"></span>Speichere...';
|
||
saveBtn.disabled = true;
|
||
|
||
// Web-authenticated API für Standortverwaltung aufrufen
|
||
const response = await fetch('/api/web/create-location', {
|
||
method: 'POST',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
},
|
||
body: JSON.stringify({
|
||
name: locationName,
|
||
lat: parseFloat(latitude),
|
||
lon: parseFloat(longitude)
|
||
})
|
||
});
|
||
|
||
const result = await response.json();
|
||
|
||
if (result.success) {
|
||
showSuccess(`✅ Standort "${locationName}" erfolgreich in der Datenbank gespeichert!`);
|
||
saveBtn.innerHTML = '✅ Gespeichert!';
|
||
saveBtn.style.background = '#4caf50';
|
||
|
||
// Button nach 3 Sekunden zurücksetzen
|
||
setTimeout(() => {
|
||
saveBtn.innerHTML = originalText;
|
||
saveBtn.disabled = false;
|
||
saveBtn.style.background = '#2196f3';
|
||
}, 3000);
|
||
} else {
|
||
throw new Error(result.message || 'Unbekannter Fehler beim Speichern');
|
||
}
|
||
|
||
} catch (error) {
|
||
console.error('Fehler beim Speichern:', error);
|
||
showError(`Fehler beim Speichern: ${error.message}`);
|
||
|
||
// Button zurücksetzen
|
||
saveBtn.innerHTML = '💾 Standort in Datenbank speichern';
|
||
saveBtn.disabled = false;
|
||
}
|
||
}
|
||
|
||
// Logout-Funktion
|
||
async function logout() {
|
||
try {
|
||
const response = await fetch('/api/logout', {
|
||
method: 'POST',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
}
|
||
});
|
||
|
||
const result = await response.json();
|
||
|
||
if (result.success) {
|
||
window.location.href = '/login';
|
||
} else {
|
||
console.error('Fehler beim Abmelden:', result.message);
|
||
// Trotzdem zur Login-Seite weiterleiten
|
||
window.location.href = '/login';
|
||
}
|
||
} catch (error) {
|
||
console.error('Fehler beim Abmelden:', error);
|
||
// Bei Fehler trotzdem zur Login-Seite weiterleiten
|
||
window.location.href = '/login';
|
||
}
|
||
}
|
||
|
||
// Enter-Taste für Standortsuche
|
||
document.addEventListener('DOMContentLoaded', function() {
|
||
const locationSearch = document.getElementById('locationSearch');
|
||
if (locationSearch) {
|
||
locationSearch.addEventListener('keypress', function(e) {
|
||
if (e.key === 'Enter') {
|
||
searchLocation();
|
||
}
|
||
});
|
||
}
|
||
});
|
||
</script>
|
||
</body>
|
||
</html> |