Füge Backend-internes Frontend für API- und Druckertests hinzu
Implementiere eine Weboberfläche zur Verwaltung von Druckern, Druckaufträgen und Benutzern mit folgenden Funktionen: - Login/Registrierungsseiten - Dashboard mit Überblick - Drucker-Verwaltung (Hinzufügen, Bearbeiten, Löschen) - Auftrags-Verwaltung (Erstellen, Abbrechen, Verlängern) - Benutzer-Verwaltung (nur Admin) - Statistik-Dashboard 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
c3fa6455d0
commit
29730fa880
62
backend/app.py
Normal file → Executable file
62
backend/app.py
Normal file → Executable file
@ -1,4 +1,4 @@
|
||||
from flask import Flask, request, jsonify, g, redirect, url_for, session as flask_session
|
||||
from flask import Flask, request, jsonify, g, redirect, url_for, session as flask_session, render_template, flash
|
||||
from flask_cors import CORS
|
||||
from flask_sqlalchemy import SQLAlchemy
|
||||
from flask_migrate import Migrate
|
||||
@ -725,6 +725,66 @@ def server_error(error):
|
||||
app.logger.error(f'Serverfehler: {error}')
|
||||
return jsonify({'message': 'Interner Serverfehler!'}), 500
|
||||
|
||||
# Web UI Routen
|
||||
@app.route('/')
|
||||
def index():
|
||||
current_user = get_current_user()
|
||||
if current_user:
|
||||
return render_template('dashboard.html', current_user=current_user, active_page='home')
|
||||
return redirect(url_for('login_page'))
|
||||
|
||||
@app.route('/login')
|
||||
def login_page():
|
||||
return render_template('login.html', active_page='login')
|
||||
|
||||
@app.route('/register')
|
||||
def register_page():
|
||||
return render_template('register.html', active_page='register')
|
||||
|
||||
@app.route('/logout')
|
||||
def logout_page():
|
||||
session_id = flask_session.get('session_id')
|
||||
if session_id:
|
||||
session = Session.query.get(session_id)
|
||||
if session:
|
||||
db.session.delete(session)
|
||||
db.session.commit()
|
||||
|
||||
flask_session.pop('session_id', None)
|
||||
|
||||
flash('Sie wurden erfolgreich abgemeldet.', 'success')
|
||||
return redirect(url_for('login_page'))
|
||||
|
||||
@app.route('/admin/printers')
|
||||
def printers_page():
|
||||
current_user = get_current_user()
|
||||
if not current_user:
|
||||
return redirect(url_for('login_page'))
|
||||
return render_template('printers.html', current_user=current_user, active_page='printers')
|
||||
|
||||
@app.route('/admin/jobs')
|
||||
def jobs_page():
|
||||
current_user = get_current_user()
|
||||
if not current_user:
|
||||
return redirect(url_for('login_page'))
|
||||
return render_template('jobs.html', current_user=current_user, active_page='jobs')
|
||||
|
||||
@app.route('/admin/users')
|
||||
def users_page():
|
||||
current_user = get_current_user()
|
||||
if not current_user or current_user.role != 'admin':
|
||||
flash('Sie haben keine Berechtigung, diese Seite zu besuchen.', 'danger')
|
||||
return redirect(url_for('index'))
|
||||
return render_template('users.html', current_user=current_user, active_page='users')
|
||||
|
||||
@app.route('/admin/stats')
|
||||
def stats_page():
|
||||
current_user = get_current_user()
|
||||
if not current_user or current_user.role != 'admin':
|
||||
flash('Sie haben keine Berechtigung, diese Seite zu besuchen.', 'danger')
|
||||
return redirect(url_for('index'))
|
||||
return render_template('stats.html', current_user=current_user, active_page='stats')
|
||||
|
||||
# Server starten
|
||||
if __name__ == '__main__':
|
||||
app.run(debug=True, host='0.0.0.0')
|
169
backend/templates/base.html
Normal file
169
backend/templates/base.html
Normal file
@ -0,0 +1,169 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>{% block title %}MYP API Tester{% endblock %}</title>
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css">
|
||||
<style>
|
||||
.sidebar {
|
||||
min-height: calc(100vh - 56px);
|
||||
background-color: #f8f9fa;
|
||||
}
|
||||
.api-response {
|
||||
max-height: 300px;
|
||||
overflow-y: auto;
|
||||
font-family: monospace;
|
||||
background-color: #f5f5f5;
|
||||
padding: 10px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
.nav-link.active {
|
||||
background-color: #0d6efd;
|
||||
color: white !important;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
|
||||
<div class="container-fluid">
|
||||
<a class="navbar-brand" href="/">MYP API Tester</a>
|
||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
<div class="collapse navbar-collapse" id="navbarNav">
|
||||
<ul class="navbar-nav">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link {% if active_page == 'home' %}active{% endif %}" href="/">Home</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link {% if active_page == 'printers' %}active{% endif %}" href="/admin/printers">Drucker</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link {% if active_page == 'jobs' %}active{% endif %}" href="/admin/jobs">Druckaufträge</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link {% if active_page == 'users' %}active{% endif %}" href="/admin/users">Benutzer</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link {% if active_page == 'stats' %}active{% endif %}" href="/admin/stats">Statistiken</a>
|
||||
</li>
|
||||
</ul>
|
||||
<ul class="navbar-nav ms-auto">
|
||||
{% if current_user %}
|
||||
<li class="nav-item dropdown">
|
||||
<a class="nav-link dropdown-toggle" href="#" id="userDropdown" role="button" data-bs-toggle="dropdown">
|
||||
{{ current_user.username }}
|
||||
</a>
|
||||
<ul class="dropdown-menu dropdown-menu-end">
|
||||
<li><a class="dropdown-item" href="/logout">Abmelden</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
{% else %}
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/login">Anmelden</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<div class="container-fluid py-3">
|
||||
{% with messages = get_flashed_messages(with_categories=true) %}
|
||||
{% if messages %}
|
||||
{% for category, message in messages %}
|
||||
<div class="alert alert-{{ category }}" role="alert">
|
||||
{{ message }}
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
|
||||
{% block content %}{% endblock %}
|
||||
</div>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
|
||||
<script>
|
||||
function formatJson(jsonString) {
|
||||
try {
|
||||
const obj = JSON.parse(jsonString);
|
||||
return JSON.stringify(obj, null, 2);
|
||||
} catch (e) {
|
||||
return jsonString;
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// Format all response areas
|
||||
document.querySelectorAll('.api-response').forEach(function(el) {
|
||||
if (el.textContent) {
|
||||
el.textContent = formatJson(el.textContent);
|
||||
}
|
||||
});
|
||||
|
||||
// Add event listener to show response areas
|
||||
document.querySelectorAll('.api-form').forEach(function(form) {
|
||||
form.addEventListener('submit', async function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
const url = this.getAttribute('data-url');
|
||||
const method = this.getAttribute('data-method') || 'GET';
|
||||
const responseArea = document.getElementById(this.getAttribute('data-response'));
|
||||
const formData = new FormData(this);
|
||||
const data = {};
|
||||
|
||||
formData.forEach((value, key) => {
|
||||
if (value) {
|
||||
try {
|
||||
// Try to parse as JSON if it looks like JSON
|
||||
if (value.trim().startsWith('{') || value.trim().startsWith('[')) {
|
||||
data[key] = JSON.parse(value);
|
||||
} else {
|
||||
data[key] = value;
|
||||
}
|
||||
} catch (e) {
|
||||
data[key] = value;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const options = {
|
||||
method: method,
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
credentials: 'same-origin'
|
||||
};
|
||||
|
||||
if (method !== 'GET' && method !== 'HEAD') {
|
||||
options.body = JSON.stringify(data);
|
||||
}
|
||||
|
||||
try {
|
||||
responseArea.textContent = 'Sending request...';
|
||||
const response = await fetch(url, options);
|
||||
const responseText = await response.text();
|
||||
|
||||
try {
|
||||
const formatted = formatJson(responseText);
|
||||
responseArea.textContent = formatted;
|
||||
} catch (e) {
|
||||
responseArea.textContent = responseText;
|
||||
}
|
||||
|
||||
if (this.hasAttribute('data-reload') && response.ok) {
|
||||
setTimeout(() => {
|
||||
window.location.reload();
|
||||
}, 1000);
|
||||
}
|
||||
} catch (err) {
|
||||
responseArea.textContent = 'Error: ' + err.message;
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
{% block scripts %}{% endblock %}
|
||||
</body>
|
||||
</html>
|
68
backend/templates/dashboard.html
Normal file
68
backend/templates/dashboard.html
Normal file
@ -0,0 +1,68 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Dashboard - MYP API Tester{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="row">
|
||||
<div class="col-md-12 mb-4">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h4 class="mb-0">Willkommen, {{ current_user.display_name }}</h4>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<p>Benutzerdetails:</p>
|
||||
<ul>
|
||||
<li><strong>ID:</strong> {{ current_user.id }}</li>
|
||||
<li><strong>Benutzername:</strong> {{ current_user.username }}</li>
|
||||
<li><strong>E-Mail:</strong> {{ current_user.email or "Nicht angegeben" }}</li>
|
||||
<li><strong>Rolle:</strong> {{ current_user.role }}</li>
|
||||
</ul>
|
||||
<div class="mt-3">
|
||||
<a href="/admin/printers" class="btn btn-primary me-2">Drucker verwalten</a>
|
||||
<a href="/admin/jobs" class="btn btn-success me-2">Druckaufträge verwalten</a>
|
||||
{% if current_user.role == 'admin' %}
|
||||
<a href="/admin/users" class="btn btn-info me-2">Benutzer verwalten</a>
|
||||
<a href="/admin/stats" class="btn btn-secondary">Statistiken</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6 mb-4">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h5 class="mb-0">API-Test: GET /api/me</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<form class="api-form" data-url="/api/me" data-method="GET" data-response="meResponse">
|
||||
<button type="submit" class="btn btn-primary">API aufrufen</button>
|
||||
</form>
|
||||
<div class="mt-3">
|
||||
<h6>Antwort:</h6>
|
||||
<pre class="api-response" id="meResponse"></pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6 mb-4">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h5 class="mb-0">API-Test: GET /api/test</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<form class="api-form" data-url="/api/test" data-method="GET" data-response="testResponse">
|
||||
<button type="submit" class="btn btn-primary">API aufrufen</button>
|
||||
</form>
|
||||
<div class="mt-3">
|
||||
<h6>Antwort:</h6>
|
||||
<pre class="api-response" id="testResponse"></pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
346
backend/templates/jobs.html
Normal file
346
backend/templates/jobs.html
Normal file
@ -0,0 +1,346 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Druckaufträge - MYP API Tester{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="row">
|
||||
<div class="col-md-12 mb-4">
|
||||
<div class="card">
|
||||
<div class="card-header d-flex justify-content-between align-items-center">
|
||||
<h4 class="mb-0">Druckaufträge verwalten</h4>
|
||||
<button class="btn btn-primary" type="button" data-bs-toggle="collapse" data-bs-target="#newJobForm">
|
||||
Neuen Auftrag erstellen
|
||||
</button>
|
||||
</div>
|
||||
<div class="collapse" id="newJobForm">
|
||||
<div class="card-body border-bottom">
|
||||
<form class="api-form" data-url="/api/jobs" data-method="POST" data-response="createJobResponse" data-reload="true">
|
||||
<div class="mb-3">
|
||||
<label for="jobPrinterId" class="form-label">Drucker</label>
|
||||
<select class="form-control" id="jobPrinterId" name="printerId" required>
|
||||
<option value="">Drucker auswählen...</option>
|
||||
<!-- Wird dynamisch gefüllt -->
|
||||
</select>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="jobDuration" class="form-label">Dauer (Minuten)</label>
|
||||
<input type="number" class="form-control" id="jobDuration" name="durationInMinutes" min="1" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="jobComments" class="form-label">Kommentare</label>
|
||||
<textarea class="form-control" id="jobComments" name="comments" rows="3"></textarea>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-success">Auftrag erstellen</button>
|
||||
</form>
|
||||
<div class="mt-3">
|
||||
<h6>Antwort:</h6>
|
||||
<pre class="api-response" id="createJobResponse"></pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<form class="api-form mb-3" data-url="/api/jobs" data-method="GET" data-response="jobsResponse">
|
||||
<button type="submit" class="btn btn-primary">Aufträge aktualisieren</button>
|
||||
</form>
|
||||
|
||||
<div class="table-responsive">
|
||||
<table class="table table-striped table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th>Drucker</th>
|
||||
<th>Benutzer</th>
|
||||
<th>Start</th>
|
||||
<th>Dauer (Min)</th>
|
||||
<th>Verbleibend (Min)</th>
|
||||
<th>Status</th>
|
||||
<th>Kommentare</th>
|
||||
<th>Aktionen</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="jobsTableBody">
|
||||
<!-- Wird dynamisch gefüllt -->
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h6>API-Antwort:</h6>
|
||||
<pre class="api-response" id="jobsResponse"></pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Job abbrechen Modal -->
|
||||
<div class="modal fade" id="abortJobModal" tabindex="-1">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">Auftrag abbrechen</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p>Möchten Sie den Auftrag wirklich abbrechen?</p>
|
||||
<form id="abortJobForm" class="api-form" data-method="POST" data-response="abortJobResponse" data-reload="true">
|
||||
<input type="hidden" id="abortJobId" name="jobId">
|
||||
<div class="mb-3">
|
||||
<label for="abortReason" class="form-label">Abbruchgrund</label>
|
||||
<textarea class="form-control" id="abortReason" name="reason" rows="3"></textarea>
|
||||
</div>
|
||||
</form>
|
||||
<div class="mt-3">
|
||||
<h6>Antwort:</h6>
|
||||
<pre class="api-response" id="abortJobResponse"></pre>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Abbrechen</button>
|
||||
<button type="submit" form="abortJobForm" class="btn btn-danger">Auftrag abbrechen</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Job beenden Modal -->
|
||||
<div class="modal fade" id="finishJobModal" tabindex="-1">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">Auftrag beenden</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p>Möchten Sie den Auftrag als beendet markieren?</p>
|
||||
<form id="finishJobForm" class="api-form" data-method="POST" data-response="finishJobResponse" data-reload="true">
|
||||
<input type="hidden" id="finishJobId" name="jobId">
|
||||
</form>
|
||||
<div class="mt-3">
|
||||
<h6>Antwort:</h6>
|
||||
<pre class="api-response" id="finishJobResponse"></pre>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Abbrechen</button>
|
||||
<button type="submit" form="finishJobForm" class="btn btn-success">Auftrag beenden</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Job verlängern Modal -->
|
||||
<div class="modal fade" id="extendJobModal" tabindex="-1">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">Auftrag verlängern</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form id="extendJobForm" class="api-form" data-method="POST" data-response="extendJobResponse" data-reload="true">
|
||||
<input type="hidden" id="extendJobId" name="jobId">
|
||||
<div class="mb-3">
|
||||
<label for="extendHours" class="form-label">Stunden</label>
|
||||
<input type="number" class="form-control" id="extendHours" name="hours" min="0" value="0">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="extendMinutes" class="form-label">Minuten</label>
|
||||
<input type="number" class="form-control" id="extendMinutes" name="minutes" min="0" max="59" value="30">
|
||||
</div>
|
||||
</form>
|
||||
<div class="mt-3">
|
||||
<h6>Antwort:</h6>
|
||||
<pre class="api-response" id="extendJobResponse"></pre>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Abbrechen</button>
|
||||
<button type="submit" form="extendJobForm" class="btn btn-primary">Auftrag verlängern</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Job Kommentare bearbeiten Modal -->
|
||||
<div class="modal fade" id="editCommentsModal" tabindex="-1">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">Kommentare bearbeiten</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form id="editCommentsForm" class="api-form" data-method="PUT" data-response="editCommentsResponse" data-reload="true">
|
||||
<input type="hidden" id="editCommentsJobId" name="jobId">
|
||||
<div class="mb-3">
|
||||
<label for="editJobComments" class="form-label">Kommentare</label>
|
||||
<textarea class="form-control" id="editJobComments" name="comments" rows="3"></textarea>
|
||||
</div>
|
||||
</form>
|
||||
<div class="mt-3">
|
||||
<h6>Antwort:</h6>
|
||||
<pre class="api-response" id="editCommentsResponse"></pre>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Abbrechen</button>
|
||||
<button type="submit" form="editCommentsForm" class="btn btn-primary">Speichern</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// Drucker für Dropdown laden
|
||||
loadPrinters();
|
||||
|
||||
// Aufträge laden
|
||||
document.querySelector('form[data-url="/api/jobs"]').dispatchEvent(new Event('submit'));
|
||||
|
||||
// Tabelle aktualisieren, wenn Aufträge geladen werden
|
||||
const jobsResponse = document.getElementById('jobsResponse');
|
||||
const observer = new MutationObserver(function(mutations) {
|
||||
try {
|
||||
const jobs = JSON.parse(jobsResponse.textContent);
|
||||
updateJobsTable(jobs);
|
||||
} catch (e) {
|
||||
console.error('Fehler beim Parsen der Auftrags-Daten:', e);
|
||||
}
|
||||
});
|
||||
|
||||
observer.observe(jobsResponse, { childList: true, characterData: true, subtree: true });
|
||||
|
||||
// Abort-Modal vorbereiten
|
||||
document.getElementById('abortJobModal').addEventListener('show.bs.modal', function(event) {
|
||||
const button = event.relatedTarget;
|
||||
const jobId = button.getAttribute('data-job-id');
|
||||
|
||||
document.getElementById('abortJobId').value = jobId;
|
||||
document.getElementById('abortJobForm').setAttribute('data-url', `/api/jobs/${jobId}/abort`);
|
||||
});
|
||||
|
||||
// Finish-Modal vorbereiten
|
||||
document.getElementById('finishJobModal').addEventListener('show.bs.modal', function(event) {
|
||||
const button = event.relatedTarget;
|
||||
const jobId = button.getAttribute('data-job-id');
|
||||
|
||||
document.getElementById('finishJobId').value = jobId;
|
||||
document.getElementById('finishJobForm').setAttribute('data-url', `/api/jobs/${jobId}/finish`);
|
||||
});
|
||||
|
||||
// Extend-Modal vorbereiten
|
||||
document.getElementById('extendJobModal').addEventListener('show.bs.modal', function(event) {
|
||||
const button = event.relatedTarget;
|
||||
const jobId = button.getAttribute('data-job-id');
|
||||
|
||||
document.getElementById('extendJobId').value = jobId;
|
||||
document.getElementById('extendJobForm').setAttribute('data-url', `/api/jobs/${jobId}/extend`);
|
||||
});
|
||||
|
||||
// Edit-Comments-Modal vorbereiten
|
||||
document.getElementById('editCommentsModal').addEventListener('show.bs.modal', function(event) {
|
||||
const button = event.relatedTarget;
|
||||
const jobId = button.getAttribute('data-job-id');
|
||||
const comments = button.getAttribute('data-job-comments');
|
||||
|
||||
document.getElementById('editCommentsJobId').value = jobId;
|
||||
document.getElementById('editCommentsForm').setAttribute('data-url', `/api/jobs/${jobId}/comments`);
|
||||
document.getElementById('editJobComments').value = comments || '';
|
||||
});
|
||||
});
|
||||
|
||||
async function loadPrinters() {
|
||||
try {
|
||||
const response = await fetch('/api/printers');
|
||||
const printers = await response.json();
|
||||
|
||||
const selectElement = document.getElementById('jobPrinterId');
|
||||
selectElement.innerHTML = '<option value="">Drucker auswählen...</option>';
|
||||
|
||||
// Nur verfügbare Drucker anzeigen
|
||||
printers.filter(printer => printer.status === 0).forEach(printer => {
|
||||
const option = document.createElement('option');
|
||||
option.value = printer.id;
|
||||
option.textContent = `${printer.name} - ${printer.description}`;
|
||||
selectElement.appendChild(option);
|
||||
});
|
||||
} catch (e) {
|
||||
console.error('Fehler beim Laden der Drucker:', e);
|
||||
}
|
||||
}
|
||||
|
||||
function updateJobsTable(jobs) {
|
||||
const tableBody = document.getElementById('jobsTableBody');
|
||||
tableBody.innerHTML = '';
|
||||
|
||||
jobs.forEach(job => {
|
||||
const row = document.createElement('tr');
|
||||
|
||||
const startDate = new Date(job.startAt);
|
||||
const formattedStart = startDate.toLocaleString();
|
||||
|
||||
const isActive = !job.aborted && job.remainingMinutes > 0;
|
||||
|
||||
let statusText = '';
|
||||
let statusClass = '';
|
||||
|
||||
if (job.aborted) {
|
||||
statusText = 'Abgebrochen';
|
||||
statusClass = 'text-danger';
|
||||
} else if (job.remainingMinutes <= 0) {
|
||||
statusText = 'Abgeschlossen';
|
||||
statusClass = 'text-success';
|
||||
} else {
|
||||
statusText = 'Aktiv';
|
||||
statusClass = 'text-warning';
|
||||
}
|
||||
|
||||
row.innerHTML = `
|
||||
<td>${job.id}</td>
|
||||
<td>${job.printerId}</td>
|
||||
<td>${job.userId}</td>
|
||||
<td>${formattedStart}</td>
|
||||
<td>${job.durationInMinutes}</td>
|
||||
<td>${job.remainingMinutes}</td>
|
||||
<td><span class="${statusClass}">${statusText}</span></td>
|
||||
<td>${job.comments || '-'}</td>
|
||||
<td>
|
||||
${isActive ? `
|
||||
<button type="button" class="btn btn-sm btn-danger mb-1"
|
||||
data-bs-toggle="modal"
|
||||
data-bs-target="#abortJobModal"
|
||||
data-job-id="${job.id}">
|
||||
Abbrechen
|
||||
</button>
|
||||
<button type="button" class="btn btn-sm btn-success mb-1"
|
||||
data-bs-toggle="modal"
|
||||
data-bs-target="#finishJobModal"
|
||||
data-job-id="${job.id}">
|
||||
Beenden
|
||||
</button>
|
||||
<button type="button" class="btn btn-sm btn-primary mb-1"
|
||||
data-bs-toggle="modal"
|
||||
data-bs-target="#extendJobModal"
|
||||
data-job-id="${job.id}">
|
||||
Verlängern
|
||||
</button>
|
||||
` : ''}
|
||||
<button type="button" class="btn btn-sm btn-secondary mb-1"
|
||||
data-bs-toggle="modal"
|
||||
data-bs-target="#editCommentsModal"
|
||||
data-job-id="${job.id}"
|
||||
data-job-comments="${job.comments || ''}">
|
||||
Kommentare
|
||||
</button>
|
||||
</td>
|
||||
`;
|
||||
|
||||
tableBody.appendChild(row);
|
||||
});
|
||||
}
|
||||
</script>
|
||||
{% endblock %}
|
37
backend/templates/login.html
Normal file
37
backend/templates/login.html
Normal file
@ -0,0 +1,37 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Anmelden - MYP API Tester{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-6">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h4 class="mb-0">Anmelden</h4>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<form class="api-form" data-url="/auth/login" data-method="POST" data-response="loginResponse">
|
||||
<div class="mb-3">
|
||||
<label for="username" class="form-label">Benutzername</label>
|
||||
<input type="text" class="form-control" id="username" name="username" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="password" class="form-label">Passwort</label>
|
||||
<input type="password" class="form-control" id="password" name="password" required>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary">Anmelden</button>
|
||||
</form>
|
||||
|
||||
<div class="mt-3">
|
||||
<p>Noch kein Konto? <a href="/register">Registrieren</a></p>
|
||||
</div>
|
||||
|
||||
<div class="mt-3">
|
||||
<h5>Antwort:</h5>
|
||||
<pre class="api-response" id="loginResponse"></pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
248
backend/templates/printers.html
Normal file
248
backend/templates/printers.html
Normal file
@ -0,0 +1,248 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Drucker - MYP API Tester{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="row">
|
||||
<div class="col-md-12 mb-4">
|
||||
<div class="card">
|
||||
<div class="card-header d-flex justify-content-between align-items-center">
|
||||
<h4 class="mb-0">Drucker verwalten</h4>
|
||||
<button class="btn btn-primary" type="button" data-bs-toggle="collapse" data-bs-target="#newPrinterForm">
|
||||
Neuen Drucker hinzufügen
|
||||
</button>
|
||||
</div>
|
||||
<div class="collapse" id="newPrinterForm">
|
||||
<div class="card-body border-bottom">
|
||||
<form class="api-form" data-url="/api/printers" data-method="POST" data-response="createPrinterResponse" data-reload="true">
|
||||
<div class="mb-3">
|
||||
<label for="printerName" class="form-label">Name</label>
|
||||
<input type="text" class="form-control" id="printerName" name="name" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="printerDescription" class="form-label">Beschreibung</label>
|
||||
<textarea class="form-control" id="printerDescription" name="description" rows="3" required></textarea>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="printerStatus" class="form-label">Status</label>
|
||||
<select class="form-control" id="printerStatus" name="status">
|
||||
<option value="0">Verfügbar (0)</option>
|
||||
<option value="1">Besetzt (1)</option>
|
||||
<option value="2">Wartung (2)</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="printerIpAddress" class="form-label">IP-Adresse (Tapo Steckdose)</label>
|
||||
<input type="text" class="form-control" id="printerIpAddress" name="ipAddress" placeholder="z.B. 192.168.1.100">
|
||||
</div>
|
||||
<button type="submit" class="btn btn-success">Drucker erstellen</button>
|
||||
</form>
|
||||
<div class="mt-3">
|
||||
<h6>Antwort:</h6>
|
||||
<pre class="api-response" id="createPrinterResponse"></pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<form class="api-form mb-3" data-url="/api/printers" data-method="GET" data-response="printersResponse">
|
||||
<button type="submit" class="btn btn-primary">Drucker aktualisieren</button>
|
||||
</form>
|
||||
|
||||
<div class="table-responsive">
|
||||
<table class="table table-striped table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th>Name</th>
|
||||
<th>Beschreibung</th>
|
||||
<th>Status</th>
|
||||
<th>IP-Adresse</th>
|
||||
<th>Aktionen</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="printersTableBody">
|
||||
<!-- Wird dynamisch gefüllt -->
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h6>API-Antwort:</h6>
|
||||
<pre class="api-response" id="printersResponse"></pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Drucker bearbeiten Modal -->
|
||||
<div class="modal fade" id="editPrinterModal" tabindex="-1">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">Drucker bearbeiten</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form id="editPrinterForm" class="api-form" data-method="PUT" data-response="editPrinterResponse" data-reload="true">
|
||||
<input type="hidden" id="editPrinterId" name="printerId">
|
||||
<div class="mb-3">
|
||||
<label for="editPrinterName" class="form-label">Name</label>
|
||||
<input type="text" class="form-control" id="editPrinterName" name="name" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="editPrinterDescription" class="form-label">Beschreibung</label>
|
||||
<textarea class="form-control" id="editPrinterDescription" name="description" rows="3" required></textarea>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="editPrinterStatus" class="form-label">Status</label>
|
||||
<select class="form-control" id="editPrinterStatus" name="status">
|
||||
<option value="0">Verfügbar (0)</option>
|
||||
<option value="1">Besetzt (1)</option>
|
||||
<option value="2">Wartung (2)</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="editPrinterIpAddress" class="form-label">IP-Adresse (Tapo Steckdose)</label>
|
||||
<input type="text" class="form-control" id="editPrinterIpAddress" name="ipAddress" placeholder="z.B. 192.168.1.100">
|
||||
</div>
|
||||
</form>
|
||||
<div class="mt-3">
|
||||
<h6>Antwort:</h6>
|
||||
<pre class="api-response" id="editPrinterResponse"></pre>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Abbrechen</button>
|
||||
<button type="submit" form="editPrinterForm" class="btn btn-primary">Änderungen speichern</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Drucker löschen Modal -->
|
||||
<div class="modal fade" id="deletePrinterModal" tabindex="-1">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">Drucker löschen</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p>Möchten Sie den Drucker <span id="deletePrinterName"></span> wirklich löschen?</p>
|
||||
<form id="deletePrinterForm" class="api-form" data-method="DELETE" data-response="deletePrinterResponse" data-reload="true">
|
||||
<input type="hidden" id="deletePrinterId" name="printerId">
|
||||
</form>
|
||||
<div class="mt-3">
|
||||
<h6>Antwort:</h6>
|
||||
<pre class="api-response" id="deletePrinterResponse"></pre>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Abbrechen</button>
|
||||
<button type="submit" form="deletePrinterForm" class="btn btn-danger">Löschen</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// Drucker laden
|
||||
document.querySelector('form[data-url="/api/printers"]').dispatchEvent(new Event('submit'));
|
||||
|
||||
// Tabelle aktualisieren, wenn Drucker geladen werden
|
||||
const printersResponse = document.getElementById('printersResponse');
|
||||
const observer = new MutationObserver(function(mutations) {
|
||||
try {
|
||||
const printers = JSON.parse(printersResponse.textContent);
|
||||
updatePrintersTable(printers);
|
||||
} catch (e) {
|
||||
console.error('Fehler beim Parsen der Drucker-Daten:', e);
|
||||
}
|
||||
});
|
||||
|
||||
observer.observe(printersResponse, { childList: true, characterData: true, subtree: true });
|
||||
|
||||
// Edit-Modal vorbereiten
|
||||
document.getElementById('editPrinterModal').addEventListener('show.bs.modal', function(event) {
|
||||
const button = event.relatedTarget;
|
||||
const printerId = button.getAttribute('data-printer-id');
|
||||
const printerName = button.getAttribute('data-printer-name');
|
||||
const printerDescription = button.getAttribute('data-printer-description');
|
||||
const printerStatus = button.getAttribute('data-printer-status');
|
||||
const printerIpAddress = button.getAttribute('data-printer-ip');
|
||||
|
||||
document.getElementById('editPrinterId').value = printerId;
|
||||
document.getElementById('editPrinterForm').setAttribute('data-url', `/api/printers/${printerId}`);
|
||||
document.getElementById('editPrinterName').value = printerName;
|
||||
document.getElementById('editPrinterDescription').value = printerDescription;
|
||||
document.getElementById('editPrinterStatus').value = printerStatus;
|
||||
document.getElementById('editPrinterIpAddress').value = printerIpAddress || '';
|
||||
});
|
||||
|
||||
// Delete-Modal vorbereiten
|
||||
document.getElementById('deletePrinterModal').addEventListener('show.bs.modal', function(event) {
|
||||
const button = event.relatedTarget;
|
||||
const printerId = button.getAttribute('data-printer-id');
|
||||
const printerName = button.getAttribute('data-printer-name');
|
||||
|
||||
document.getElementById('deletePrinterId').value = printerId;
|
||||
document.getElementById('deletePrinterForm').setAttribute('data-url', `/api/printers/${printerId}`);
|
||||
document.getElementById('deletePrinterName').textContent = printerName;
|
||||
});
|
||||
});
|
||||
|
||||
function updatePrintersTable(printers) {
|
||||
const tableBody = document.getElementById('printersTableBody');
|
||||
tableBody.innerHTML = '';
|
||||
|
||||
printers.forEach(printer => {
|
||||
const row = document.createElement('tr');
|
||||
|
||||
const statusText = {
|
||||
0: 'Verfügbar',
|
||||
1: 'Besetzt',
|
||||
2: 'Wartung'
|
||||
}[printer.status] || 'Unbekannt';
|
||||
|
||||
const statusClass = {
|
||||
0: 'text-success',
|
||||
1: 'text-warning',
|
||||
2: 'text-danger'
|
||||
}[printer.status] || '';
|
||||
|
||||
row.innerHTML = `
|
||||
<td>${printer.id}</td>
|
||||
<td>${printer.name}</td>
|
||||
<td>${printer.description}</td>
|
||||
<td><span class="${statusClass}">${statusText} (${printer.status})</span></td>
|
||||
<td>${printer.ipAddress || '-'}</td>
|
||||
<td>
|
||||
<button type="button" class="btn btn-sm btn-primary"
|
||||
data-bs-toggle="modal"
|
||||
data-bs-target="#editPrinterModal"
|
||||
data-printer-id="${printer.id}"
|
||||
data-printer-name="${printer.name}"
|
||||
data-printer-description="${printer.description}"
|
||||
data-printer-status="${printer.status}"
|
||||
data-printer-ip="${printer.ipAddress || ''}">
|
||||
Bearbeiten
|
||||
</button>
|
||||
<button type="button" class="btn btn-sm btn-danger"
|
||||
data-bs-toggle="modal"
|
||||
data-bs-target="#deletePrinterModal"
|
||||
data-printer-id="${printer.id}"
|
||||
data-printer-name="${printer.name}">
|
||||
Löschen
|
||||
</button>
|
||||
</td>
|
||||
`;
|
||||
|
||||
tableBody.appendChild(row);
|
||||
});
|
||||
}
|
||||
</script>
|
||||
{% endblock %}
|
45
backend/templates/register.html
Normal file
45
backend/templates/register.html
Normal file
@ -0,0 +1,45 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Registrieren - MYP API Tester{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-6">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h4 class="mb-0">Registrieren</h4>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<form class="api-form" data-url="/auth/register" data-method="POST" data-response="registerResponse">
|
||||
<div class="mb-3">
|
||||
<label for="username" class="form-label">Benutzername</label>
|
||||
<input type="text" class="form-control" id="username" name="username" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="password" class="form-label">Passwort</label>
|
||||
<input type="password" class="form-control" id="password" name="password" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="displayName" class="form-label">Anzeigename</label>
|
||||
<input type="text" class="form-control" id="displayName" name="displayName">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="email" class="form-label">E-Mail</label>
|
||||
<input type="email" class="form-control" id="email" name="email">
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary">Registrieren</button>
|
||||
</form>
|
||||
|
||||
<div class="mt-3">
|
||||
<p>Bereits registriert? <a href="/login">Anmelden</a></p>
|
||||
</div>
|
||||
|
||||
<div class="mt-3">
|
||||
<h5>Antwort:</h5>
|
||||
<pre class="api-response" id="registerResponse"></pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
137
backend/templates/stats.html
Normal file
137
backend/templates/stats.html
Normal file
@ -0,0 +1,137 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Statistiken - MYP API Tester{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="row">
|
||||
<div class="col-md-12 mb-4">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h4 class="mb-0">Systemstatistiken</h4>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<form class="api-form mb-3" data-url="/api/stats" data-method="GET" data-response="statsResponse">
|
||||
<button type="submit" class="btn btn-primary">Statistiken aktualisieren</button>
|
||||
</form>
|
||||
|
||||
<div class="row" id="statsContainer">
|
||||
<!-- Wird dynamisch gefüllt -->
|
||||
</div>
|
||||
|
||||
<div class="mt-4">
|
||||
<h6>API-Antwort:</h6>
|
||||
<pre class="api-response" id="statsResponse"></pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// Statistiken laden
|
||||
document.querySelector('form[data-url="/api/stats"]').dispatchEvent(new Event('submit'));
|
||||
|
||||
// Statistiken aktualisieren, wenn API-Antwort geladen wird
|
||||
const statsResponse = document.getElementById('statsResponse');
|
||||
const observer = new MutationObserver(function(mutations) {
|
||||
try {
|
||||
const stats = JSON.parse(statsResponse.textContent);
|
||||
updateStatsDisplay(stats);
|
||||
} catch (e) {
|
||||
console.error('Fehler beim Parsen der Statistik-Daten:', e);
|
||||
}
|
||||
});
|
||||
|
||||
observer.observe(statsResponse, { childList: true, characterData: true, subtree: true });
|
||||
});
|
||||
|
||||
function updateStatsDisplay(stats) {
|
||||
const container = document.getElementById('statsContainer');
|
||||
container.innerHTML = '';
|
||||
|
||||
// Drucker-Statistiken
|
||||
const printerStats = document.createElement('div');
|
||||
printerStats.className = 'col-md-4 mb-3';
|
||||
printerStats.innerHTML = `
|
||||
<div class="card h-100">
|
||||
<div class="card-header bg-primary text-white">
|
||||
<h5 class="mb-0">Drucker</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="d-flex justify-content-between mb-2">
|
||||
<span>Gesamt:</span>
|
||||
<span>${stats.printers.total}</span>
|
||||
</div>
|
||||
<div class="d-flex justify-content-between mb-2">
|
||||
<span>Verfügbar:</span>
|
||||
<span>${stats.printers.available}</span>
|
||||
</div>
|
||||
<div class="d-flex justify-content-between mb-2">
|
||||
<span>Auslastung:</span>
|
||||
<span>${Math.round(stats.printers.utilization_rate * 100)}%</span>
|
||||
</div>
|
||||
<div class="progress mt-3">
|
||||
<div class="progress-bar" role="progressbar"
|
||||
style="width: ${Math.round(stats.printers.utilization_rate * 100)}%">
|
||||
${Math.round(stats.printers.utilization_rate * 100)}%
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
// Job-Statistiken
|
||||
const jobStats = document.createElement('div');
|
||||
jobStats.className = 'col-md-4 mb-3';
|
||||
jobStats.innerHTML = `
|
||||
<div class="card h-100">
|
||||
<div class="card-header bg-success text-white">
|
||||
<h5 class="mb-0">Druckaufträge</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="d-flex justify-content-between mb-2">
|
||||
<span>Gesamt:</span>
|
||||
<span>${stats.jobs.total}</span>
|
||||
</div>
|
||||
<div class="d-flex justify-content-between mb-2">
|
||||
<span>Aktiv:</span>
|
||||
<span>${stats.jobs.active}</span>
|
||||
</div>
|
||||
<div class="d-flex justify-content-between mb-2">
|
||||
<span>Abgeschlossen:</span>
|
||||
<span>${stats.jobs.completed}</span>
|
||||
</div>
|
||||
<div class="d-flex justify-content-between mb-2">
|
||||
<span>Durchschnittliche Dauer:</span>
|
||||
<span>${stats.jobs.avg_duration} Minuten</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
// Benutzer-Statistiken
|
||||
const userStats = document.createElement('div');
|
||||
userStats.className = 'col-md-4 mb-3';
|
||||
userStats.innerHTML = `
|
||||
<div class="card h-100">
|
||||
<div class="card-header bg-info text-white">
|
||||
<h5 class="mb-0">Benutzer</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="d-flex justify-content-between mb-2">
|
||||
<span>Gesamt:</span>
|
||||
<span>${stats.users.total}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
container.appendChild(printerStats);
|
||||
container.appendChild(jobStats);
|
||||
container.appendChild(userStats);
|
||||
}
|
||||
</script>
|
||||
{% endblock %}
|
238
backend/templates/users.html
Normal file
238
backend/templates/users.html
Normal file
@ -0,0 +1,238 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Benutzer - MYP API Tester{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="row">
|
||||
<div class="col-md-12 mb-4">
|
||||
<div class="card">
|
||||
<div class="card-header d-flex justify-content-between align-items-center">
|
||||
<h4 class="mb-0">Benutzer verwalten</h4>
|
||||
<button class="btn btn-primary" type="button" data-bs-toggle="collapse" data-bs-target="#newUserForm">
|
||||
Neuen Benutzer hinzufügen
|
||||
</button>
|
||||
</div>
|
||||
<div class="collapse" id="newUserForm">
|
||||
<div class="card-body border-bottom">
|
||||
<form class="api-form" data-url="/auth/register" data-method="POST" data-response="createUserResponse" data-reload="true">
|
||||
<div class="mb-3">
|
||||
<label for="userName" class="form-label">Benutzername</label>
|
||||
<input type="text" class="form-control" id="userName" name="username" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="userPassword" class="form-label">Passwort</label>
|
||||
<input type="password" class="form-control" id="userPassword" name="password" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="userDisplayName" class="form-label">Anzeigename</label>
|
||||
<input type="text" class="form-control" id="userDisplayName" name="displayName">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="userEmail" class="form-label">E-Mail</label>
|
||||
<input type="email" class="form-control" id="userEmail" name="email">
|
||||
</div>
|
||||
<button type="submit" class="btn btn-success">Benutzer erstellen</button>
|
||||
</form>
|
||||
<div class="mt-3">
|
||||
<h6>Antwort:</h6>
|
||||
<pre class="api-response" id="createUserResponse"></pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<form class="api-form mb-3" data-url="/api/users" data-method="GET" data-response="usersResponse">
|
||||
<button type="submit" class="btn btn-primary">Benutzer aktualisieren</button>
|
||||
</form>
|
||||
|
||||
<div class="table-responsive">
|
||||
<table class="table table-striped table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th>Benutzername</th>
|
||||
<th>Anzeigename</th>
|
||||
<th>E-Mail</th>
|
||||
<th>Rolle</th>
|
||||
<th>Aktionen</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="usersTableBody">
|
||||
<!-- Wird dynamisch gefüllt -->
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h6>API-Antwort:</h6>
|
||||
<pre class="api-response" id="usersResponse"></pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Benutzer bearbeiten Modal -->
|
||||
<div class="modal fade" id="editUserModal" tabindex="-1">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">Benutzer bearbeiten</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form id="editUserForm" class="api-form" data-method="PUT" data-response="editUserResponse" data-reload="true">
|
||||
<input type="hidden" id="editUserId" name="userId">
|
||||
<div class="mb-3">
|
||||
<label for="editUserName" class="form-label">Benutzername</label>
|
||||
<input type="text" class="form-control" id="editUserName" name="username" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="editUserDisplayName" class="form-label">Anzeigename</label>
|
||||
<input type="text" class="form-control" id="editUserDisplayName" name="displayName">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="editUserEmail" class="form-label">E-Mail</label>
|
||||
<input type="email" class="form-control" id="editUserEmail" name="email">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="editUserRole" class="form-label">Rolle</label>
|
||||
<select class="form-control" id="editUserRole" name="role">
|
||||
<option value="user">Benutzer</option>
|
||||
<option value="admin">Administrator</option>
|
||||
<option value="guest">Gast</option>
|
||||
</select>
|
||||
</div>
|
||||
</form>
|
||||
<div class="mt-3">
|
||||
<h6>Antwort:</h6>
|
||||
<pre class="api-response" id="editUserResponse"></pre>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Abbrechen</button>
|
||||
<button type="submit" form="editUserForm" class="btn btn-primary">Änderungen speichern</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Benutzer löschen Modal -->
|
||||
<div class="modal fade" id="deleteUserModal" tabindex="-1">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">Benutzer löschen</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p>Möchten Sie den Benutzer <span id="deleteUserName"></span> wirklich löschen?</p>
|
||||
<form id="deleteUserForm" class="api-form" data-method="DELETE" data-response="deleteUserResponse" data-reload="true">
|
||||
<input type="hidden" id="deleteUserId" name="userId">
|
||||
</form>
|
||||
<div class="mt-3">
|
||||
<h6>Antwort:</h6>
|
||||
<pre class="api-response" id="deleteUserResponse"></pre>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Abbrechen</button>
|
||||
<button type="submit" form="deleteUserForm" class="btn btn-danger">Löschen</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// Benutzer laden
|
||||
document.querySelector('form[data-url="/api/users"]').dispatchEvent(new Event('submit'));
|
||||
|
||||
// Tabelle aktualisieren, wenn Benutzer geladen werden
|
||||
const usersResponse = document.getElementById('usersResponse');
|
||||
const observer = new MutationObserver(function(mutations) {
|
||||
try {
|
||||
const users = JSON.parse(usersResponse.textContent);
|
||||
updateUsersTable(users);
|
||||
} catch (e) {
|
||||
console.error('Fehler beim Parsen der Benutzer-Daten:', e);
|
||||
}
|
||||
});
|
||||
|
||||
observer.observe(usersResponse, { childList: true, characterData: true, subtree: true });
|
||||
|
||||
// Edit-Modal vorbereiten
|
||||
document.getElementById('editUserModal').addEventListener('show.bs.modal', function(event) {
|
||||
const button = event.relatedTarget;
|
||||
const userId = button.getAttribute('data-user-id');
|
||||
const userName = button.getAttribute('data-user-name');
|
||||
const userDisplayName = button.getAttribute('data-user-displayname');
|
||||
const userEmail = button.getAttribute('data-user-email');
|
||||
const userRole = button.getAttribute('data-user-role');
|
||||
|
||||
document.getElementById('editUserId').value = userId;
|
||||
document.getElementById('editUserForm').setAttribute('data-url', `/api/users/${userId}`);
|
||||
document.getElementById('editUserName').value = userName;
|
||||
document.getElementById('editUserDisplayName').value = userDisplayName || '';
|
||||
document.getElementById('editUserEmail').value = userEmail || '';
|
||||
document.getElementById('editUserRole').value = userRole;
|
||||
});
|
||||
|
||||
// Delete-Modal vorbereiten
|
||||
document.getElementById('deleteUserModal').addEventListener('show.bs.modal', function(event) {
|
||||
const button = event.relatedTarget;
|
||||
const userId = button.getAttribute('data-user-id');
|
||||
const userName = button.getAttribute('data-user-name');
|
||||
|
||||
document.getElementById('deleteUserId').value = userId;
|
||||
document.getElementById('deleteUserForm').setAttribute('data-url', `/api/users/${userId}`);
|
||||
document.getElementById('deleteUserName').textContent = userName;
|
||||
});
|
||||
});
|
||||
|
||||
function updateUsersTable(users) {
|
||||
const tableBody = document.getElementById('usersTableBody');
|
||||
tableBody.innerHTML = '';
|
||||
|
||||
users.forEach(user => {
|
||||
const row = document.createElement('tr');
|
||||
|
||||
const roleClass = {
|
||||
'admin': 'text-danger',
|
||||
'user': 'text-primary',
|
||||
'guest': 'text-secondary'
|
||||
}[user.role] || '';
|
||||
|
||||
row.innerHTML = `
|
||||
<td>${user.id}</td>
|
||||
<td>${user.username}</td>
|
||||
<td>${user.displayName || user.username}</td>
|
||||
<td>${user.email || '-'}</td>
|
||||
<td><span class="${roleClass}">${user.role}</span></td>
|
||||
<td>
|
||||
<button type="button" class="btn btn-sm btn-primary"
|
||||
data-bs-toggle="modal"
|
||||
data-bs-target="#editUserModal"
|
||||
data-user-id="${user.id}"
|
||||
data-user-name="${user.username}"
|
||||
data-user-displayname="${user.displayName || ''}"
|
||||
data-user-email="${user.email || ''}"
|
||||
data-user-role="${user.role}">
|
||||
Bearbeiten
|
||||
</button>
|
||||
<button type="button" class="btn btn-sm btn-danger"
|
||||
data-bs-toggle="modal"
|
||||
data-bs-target="#deleteUserModal"
|
||||
data-user-id="${user.id}"
|
||||
data-user-name="${user.username}">
|
||||
Löschen
|
||||
</button>
|
||||
</td>
|
||||
`;
|
||||
|
||||
tableBody.appendChild(row);
|
||||
});
|
||||
}
|
||||
</script>
|
||||
{% endblock %}
|
Loading…
x
Reference in New Issue
Block a user