✨ Add comprehensive admin panel with user management
Features added: - Admin authentication system with is_admin field - Complete admin dashboard with user statistics - User management (create, edit, delete, toggle admin) - Protected admin routes with @admin_required decorator - Security features (prevent self-deletion, last admin protection) - Responsive admin UI with Bootstrap integration - Database migration script for admin field - Admin navigation link for authorized users Technical improvements: - Enhanced 3-column dashboard layout (tables | editor | saved queries) - Removed plus button and made right sidebar more compact - Admin user (admin/admin123) automatically created with admin privileges - Full CRUD operations for user management - Flash messages for user feedback - Form validation and error handling
This commit is contained in:
parent
44b8667f31
commit
f176560c02
18
.env.example
Normal file
18
.env.example
Normal file
@ -0,0 +1,18 @@
|
||||
# Flask Konfiguration
|
||||
SECRET_KEY=your-secret-key-here
|
||||
FLASK_ENV=development
|
||||
FLASK_DEBUG=True
|
||||
|
||||
# Oracle Datenbank Konfiguration
|
||||
ORACLE_HOST=your-oracle-host.com
|
||||
ORACLE_PORT=1521
|
||||
ORACLE_SERVICE_NAME=ORCL
|
||||
ORACLE_USERNAME=your_username
|
||||
ORACLE_PASSWORD=your_password
|
||||
|
||||
# PostgreSQL Datenbank Konfiguration (optional)
|
||||
POSTGRES_HOST=your-postgres-host.com
|
||||
POSTGRES_PORT=5432
|
||||
POSTGRES_DATABASE=your_database
|
||||
POSTGRES_USERNAME=your_username
|
||||
POSTGRES_PASSWORD=your_password
|
||||
54
.github/copilot-instructions.md
vendored
Normal file
54
.github/copilot-instructions.md
vendored
Normal file
@ -0,0 +1,54 @@
|
||||
<!-- Use this file to provide workspace-specific custom instructions to Copilot. For more details, visit https://code.visualstudio.com/docs/copilot/copilot-customization#_use-a-githubcopilotinstructionsmd-file -->
|
||||
- [x] Verify that the copilot-instructions.md file in the .github directory is created. ✓ Completed
|
||||
|
||||
- [x] Clarify Project Requirements - Python Flask web server with authentication, SQL query builder interface with table list on left, query input and results on right, ability to save and retrieve queries via API as JSON/CSV
|
||||
<!-- Ask for project type, language, and frameworks if not specified. Skip if already provided. -->
|
||||
|
||||
- [x] Scaffold the Project - ✓ Flask project structure created with authentication, database models, routes, templates, and static files
|
||||
<!--
|
||||
Ensure that the previous step has been marked as completed.
|
||||
Call project setup tool with projectType parameter.
|
||||
Run scaffolding command to create project files and folders.
|
||||
Use '.' as the working directory.
|
||||
If no appropriate projectType is available, search documentation using available tools.
|
||||
Otherwise, create the project structure manually using available file creation tools.
|
||||
-->
|
||||
|
||||
- [x] Customize the Project - ✓ Completed with full query builder functionality, authentication, and API endpoints
|
||||
<!--
|
||||
Verify that all previous steps have been completed successfully and you have marked the step as completed.
|
||||
Develop a plan to modify codebase according to user requirements.
|
||||
Apply modifications using appropriate tools and user-provided references.
|
||||
Skip this step for "Hello World" projects.
|
||||
-->
|
||||
|
||||
- [x] Install Required Extensions - ✓ No additional extensions needed
|
||||
<!-- ONLY install extensions provided mentioned in the get_project_setup_info. Skip this step otherwise and mark as completed. -->
|
||||
|
||||
- [x] Compile the Project - ✓ Completed - Python environment configured and all dependencies installed successfully
|
||||
<!--
|
||||
Verify that all previous steps have been completed.
|
||||
Install any missing dependencies.
|
||||
Run diagnostics and resolve any issues.
|
||||
Check for markdown files in project folder for relevant instructions on how to do this.
|
||||
-->
|
||||
|
||||
- [ ] Create and Run Task
|
||||
<!--
|
||||
Verify that all previous steps have been completed.
|
||||
Check https://code.visualstudio.com/docs/debugtest/tasks to determine if the project needs a task. If so, use the create_and_run_task to create and launch a task based on package.json, README.md, and project structure.
|
||||
Skip this step otherwise.
|
||||
-->
|
||||
|
||||
- [ ] Launch the Project
|
||||
<!--
|
||||
Verify that all previous steps have been completed.
|
||||
Prompt user for debug mode, launch only if confirmed.
|
||||
-->
|
||||
|
||||
- [ ] Ensure Documentation is Complete
|
||||
<!--
|
||||
Verify that all previous steps have been completed.
|
||||
Verify that README.md and the copilot-instructions.md file in the .github directory exists and contains current project information.
|
||||
Clean up the copilot-instructions.md file in the .github directory by removing all HTML comments.
|
||||
-->
|
||||
63
.gitignore
vendored
Normal file
63
.gitignore
vendored
Normal file
@ -0,0 +1,63 @@
|
||||
# Python
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
*.so
|
||||
.Python
|
||||
build/
|
||||
develop-eggs/
|
||||
dist/
|
||||
downloads/
|
||||
eggs/
|
||||
.eggs/
|
||||
lib/
|
||||
lib64/
|
||||
parts/
|
||||
sdist/
|
||||
var/
|
||||
wheels/
|
||||
*.egg-info/
|
||||
.installed.cfg
|
||||
*.egg
|
||||
MANIFEST
|
||||
|
||||
# Flask
|
||||
instance/
|
||||
.webassets-cache
|
||||
|
||||
# Environment variables
|
||||
.env
|
||||
|
||||
# IDE
|
||||
.vscode/
|
||||
.idea/
|
||||
*.swp
|
||||
*.swo
|
||||
|
||||
# OS
|
||||
.DS_Store
|
||||
.DS_Store?
|
||||
._*
|
||||
.Spotlight-V100
|
||||
.Trashes
|
||||
ehthumbs.db
|
||||
Thumbs.db
|
||||
|
||||
# Database
|
||||
*.db
|
||||
*.sqlite
|
||||
*.sqlite3
|
||||
|
||||
# Logs
|
||||
*.log
|
||||
|
||||
# Virtual Environment
|
||||
.venv/
|
||||
venv/
|
||||
ENV/
|
||||
env/
|
||||
.env
|
||||
|
||||
# Temporary files
|
||||
*.tmp
|
||||
*.temp
|
||||
@ -46,7 +46,7 @@ def create_app():
|
||||
from app.models import User
|
||||
admin = User.query.filter_by(username='admin').first()
|
||||
if not admin:
|
||||
admin = User(username='admin', email='admin@example.com')
|
||||
admin = User(username='admin', email='admin@example.com', is_admin=True)
|
||||
admin.set_password('admin123')
|
||||
db.session.add(admin)
|
||||
db.session.commit()
|
||||
|
||||
Binary file not shown.
88
app/templates/admin/create_user.html
Normal file
88
app/templates/admin/create_user.html
Normal file
@ -0,0 +1,88 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Neuen Benutzer erstellen{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-6">
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<h2><i class="fas fa-user-plus"></i> Neuen Benutzer erstellen</h2>
|
||||
<a href="{{ url_for('admin.users') }}" class="btn btn-outline-secondary">
|
||||
<i class="fas fa-arrow-left"></i> Zurück
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<form method="POST">
|
||||
<div class="mb-3">
|
||||
<label for="username" class="form-label">
|
||||
<i class="fas fa-user"></i> Benutzername *
|
||||
</label>
|
||||
<input type="text" class="form-control" id="username" name="username"
|
||||
required maxlength="80" placeholder="z.B. john.doe">
|
||||
<div class="form-text">Der Benutzername muss eindeutig sein</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="email" class="form-label">
|
||||
<i class="fas fa-envelope"></i> E-Mail-Adresse *
|
||||
</label>
|
||||
<input type="email" class="form-control" id="email" name="email"
|
||||
required maxlength="120" placeholder="john.doe@example.com">
|
||||
<div class="form-text">Die E-Mail-Adresse muss eindeutig sein</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="password" class="form-label">
|
||||
<i class="fas fa-lock"></i> Passwort *
|
||||
</label>
|
||||
<input type="password" class="form-control" id="password" name="password"
|
||||
required minlength="6" placeholder="Mindestens 6 Zeichen">
|
||||
<div class="form-text">Mindestens 6 Zeichen erforderlich</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-4">
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" id="is_admin" name="is_admin">
|
||||
<label class="form-check-label" for="is_admin">
|
||||
<i class="fas fa-user-shield text-danger"></i> Administrator-Rechte gewähren
|
||||
</label>
|
||||
<div class="form-text">
|
||||
Administratoren können andere Benutzer verwalten und haben Vollzugriff
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="d-grid gap-2">
|
||||
<button type="submit" class="btn btn-success">
|
||||
<i class="fas fa-user-plus"></i> Benutzer erstellen
|
||||
</button>
|
||||
<a href="{{ url_for('admin.users') }}" class="btn btn-outline-secondary">
|
||||
<i class="fas fa-times"></i> Abbrechen
|
||||
</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Hinweise -->
|
||||
<div class="card mt-4">
|
||||
<div class="card-header">
|
||||
<h6><i class="fas fa-info-circle"></i> Wichtige Hinweise</h6>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<ul class="mb-0">
|
||||
<li>Alle mit * markierten Felder sind Pflichtfelder</li>
|
||||
<li>Der Benutzername und die E-Mail-Adresse müssen eindeutig sein</li>
|
||||
<li>Das Passwort sollte sicher gewählt werden</li>
|
||||
<li>Administrator-Rechte gewähren Vollzugriff auf alle Funktionen</li>
|
||||
<li>Neue Benutzer können sich sofort mit ihren Anmeldedaten einloggen</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
173
app/templates/admin/dashboard.html
Normal file
173
app/templates/admin/dashboard.html
Normal file
@ -0,0 +1,173 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Admin Dashboard{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<h2><i class="fas fa-users-cog"></i> Admin Dashboard</h2>
|
||||
<a href="{{ url_for('main.dashboard') }}" class="btn btn-outline-secondary">
|
||||
<i class="fas fa-arrow-left"></i> Zurück zum Dashboard
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<!-- Statistiken -->
|
||||
<div class="row mb-4">
|
||||
<div class="col-md-3">
|
||||
<div class="card bg-primary text-white">
|
||||
<div class="card-body">
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<div>
|
||||
<h4>{{ users|length }}</h4>
|
||||
<small>Benutzer gesamt</small>
|
||||
</div>
|
||||
<i class="fas fa-users fa-2x opacity-75"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="card bg-success text-white">
|
||||
<div class="card-body">
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<div>
|
||||
<h4>{{ users|selectattr('is_admin')|list|length }}</h4>
|
||||
<small>Administratoren</small>
|
||||
</div>
|
||||
<i class="fas fa-user-shield fa-2x opacity-75"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="card bg-info text-white">
|
||||
<div class="card-body">
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<div>
|
||||
<h4>{{ users|rejectattr('is_admin')|list|length }}</h4>
|
||||
<small>Standard-Benutzer</small>
|
||||
</div>
|
||||
<i class="fas fa-user fa-2x opacity-75"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="card bg-warning text-white">
|
||||
<div class="card-body">
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<div>
|
||||
<h4>{{ (users|selectattr('created_at')|map(attribute='created_at')|list|length) }}</h4>
|
||||
<small>Aktive Benutzer</small>
|
||||
</div>
|
||||
<i class="fas fa-chart-line fa-2x opacity-75"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Schnellzugriff -->
|
||||
<div class="row mb-4">
|
||||
<div class="col-12">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h5><i class="fas fa-tachometer-alt"></i> Schnellzugriff</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="row">
|
||||
<div class="col-md-6 mb-3">
|
||||
<a href="{{ url_for('admin.users') }}" class="btn btn-outline-primary btn-lg w-100">
|
||||
<i class="fas fa-users"></i>
|
||||
<div class="mt-2">
|
||||
<strong>Benutzerverwaltung</strong>
|
||||
<br><small class="text-muted">Benutzer anzeigen, bearbeiten und erstellen</small>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
<div class="col-md-6 mb-3">
|
||||
<a href="{{ url_for('admin.create_user') }}" class="btn btn-outline-success btn-lg w-100">
|
||||
<i class="fas fa-user-plus"></i>
|
||||
<div class="mt-2">
|
||||
<strong>Neuen Benutzer erstellen</strong>
|
||||
<br><small class="text-muted">Schnell einen neuen Benutzer hinzufügen</small>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Letzte Benutzer -->
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h5><i class="fas fa-clock"></i> Letzte Benutzer</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
{% if users %}
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Benutzername</th>
|
||||
<th>E-Mail</th>
|
||||
<th>Rolle</th>
|
||||
<th>Erstellt am</th>
|
||||
<th>Aktionen</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for user in users[:5] %}
|
||||
<tr>
|
||||
<td>
|
||||
<strong>{{ user.username }}</strong>
|
||||
{% if user.id == current_user.id %}
|
||||
<span class="badge bg-primary ms-1">Sie</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>{{ user.email }}</td>
|
||||
<td>
|
||||
{% if user.is_admin %}
|
||||
<span class="badge bg-danger"><i class="fas fa-user-shield"></i> Administrator</span>
|
||||
{% else %}
|
||||
<span class="badge bg-secondary"><i class="fas fa-user"></i> Benutzer</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>{{ user.created_at.strftime('%d.%m.%Y %H:%M') }}</td>
|
||||
<td>
|
||||
<a href="{{ url_for('admin.edit_user', user_id=user.id) }}"
|
||||
class="btn btn-sm btn-outline-primary" title="Bearbeiten">
|
||||
<i class="fas fa-edit"></i>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="text-center mt-3">
|
||||
<a href="{{ url_for('admin.users') }}" class="btn btn-primary">
|
||||
Alle Benutzer anzeigen <i class="fas fa-arrow-right"></i>
|
||||
</a>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="text-center text-muted py-4">
|
||||
<i class="fas fa-users fa-3x mb-3"></i>
|
||||
<h5>Keine Benutzer gefunden</h5>
|
||||
<p>Erstellen Sie den ersten Benutzer über den Button oben.</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
145
app/templates/admin/edit_user.html
Normal file
145
app/templates/admin/edit_user.html
Normal file
@ -0,0 +1,145 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Benutzer bearbeiten{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-6">
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<h2><i class="fas fa-user-edit"></i> Benutzer bearbeiten</h2>
|
||||
<a href="{{ url_for('admin.users') }}" class="btn btn-outline-secondary">
|
||||
<i class="fas fa-arrow-left"></i> Zurück
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h5>
|
||||
{{ user.username }}
|
||||
{% if user.id == current_user.id %}
|
||||
<span class="badge bg-primary">Sie</span>
|
||||
{% endif %}
|
||||
{% if user.is_admin %}
|
||||
<span class="badge bg-danger">Administrator</span>
|
||||
{% endif %}
|
||||
</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<form method="POST">
|
||||
<div class="mb-3">
|
||||
<label for="username" class="form-label">
|
||||
<i class="fas fa-user"></i> Benutzername *
|
||||
</label>
|
||||
<input type="text" class="form-control" id="username" name="username"
|
||||
value="{{ user.username }}" required maxlength="80"
|
||||
placeholder="z.B. john.doe">
|
||||
<div class="form-text">Der Benutzername muss eindeutig sein</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="email" class="form-label">
|
||||
<i class="fas fa-envelope"></i> E-Mail-Adresse *
|
||||
</label>
|
||||
<input type="email" class="form-control" id="email" name="email"
|
||||
value="{{ user.email }}" required maxlength="120"
|
||||
placeholder="john.doe@example.com">
|
||||
<div class="form-text">Die E-Mail-Adresse muss eindeutig sein</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="password" class="form-label">
|
||||
<i class="fas fa-lock"></i> Neues Passwort
|
||||
</label>
|
||||
<input type="password" class="form-control" id="password" name="password"
|
||||
minlength="6" placeholder="Leer lassen um Passwort beizubehalten">
|
||||
<div class="form-text">Nur ausfüllen wenn Sie das Passwort ändern möchten (mindestens 6 Zeichen)</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-4">
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" id="is_admin" name="is_admin"
|
||||
{% if user.is_admin %}checked{% endif %}
|
||||
{% if user.id == current_user.id %}disabled{% endif %}>
|
||||
<label class="form-check-label" for="is_admin">
|
||||
<i class="fas fa-user-shield text-danger"></i> Administrator-Rechte
|
||||
</label>
|
||||
{% if user.id == current_user.id %}
|
||||
<div class="form-text text-warning">
|
||||
<i class="fas fa-exclamation-triangle"></i> Sie können Ihre eigenen Admin-Rechte nicht ändern
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="form-text">
|
||||
Administratoren können andere Benutzer verwalten und haben Vollzugriff
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="d-grid gap-2">
|
||||
<button type="submit" class="btn btn-primary">
|
||||
<i class="fas fa-save"></i> Änderungen speichern
|
||||
</button>
|
||||
<a href="{{ url_for('admin.users') }}" class="btn btn-outline-secondary">
|
||||
<i class="fas fa-times"></i> Abbrechen
|
||||
</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Benutzer-Informationen -->
|
||||
<div class="card mt-4">
|
||||
<div class="card-header">
|
||||
<h6><i class="fas fa-info-circle"></i> Benutzer-Informationen</h6>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="row">
|
||||
<div class="col-sm-4"><strong>Benutzer-ID:</strong></div>
|
||||
<div class="col-sm-8">{{ user.id }}</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-4"><strong>Erstellt am:</strong></div>
|
||||
<div class="col-sm-8">{{ user.created_at.strftime('%d.%m.%Y %H:%M:%S') }}</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-4"><strong>Rolle:</strong></div>
|
||||
<div class="col-sm-8">
|
||||
{% if user.is_admin %}
|
||||
<span class="badge bg-danger">Administrator</span>
|
||||
{% else %}
|
||||
<span class="badge bg-secondary">Standard-Benutzer</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-4"><strong>Anzahl Queries:</strong></div>
|
||||
<div class="col-sm-8">{{ user.saved_queries|length }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Lösch-Option -->
|
||||
{% if user.id != current_user.id %}
|
||||
<div class="card mt-4 border-danger">
|
||||
<div class="card-header bg-danger text-white">
|
||||
<h6><i class="fas fa-exclamation-triangle"></i> Gefahrenbereich</h6>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<p class="text-danger">
|
||||
<strong>Achtung:</strong> Das Löschen eines Benutzers kann nicht rückgängig gemacht werden.
|
||||
Alle gespeicherten Queries dieses Benutzers gehen verloren.
|
||||
</p>
|
||||
<form method="POST" action="{{ url_for('admin.delete_user', user_id=user.id) }}"
|
||||
onsubmit="return confirm('Benutzer {{ user.username }} wirklich unwiderruflich löschen?\\n\\nAlle gespeicherten Queries gehen verloren!')">
|
||||
<button type="submit" class="btn btn-danger">
|
||||
<i class="fas fa-trash"></i> Benutzer löschen
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
154
app/templates/admin/users.html
Normal file
154
app/templates/admin/users.html
Normal file
@ -0,0 +1,154 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Benutzerverwaltung{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<h2><i class="fas fa-users"></i> Benutzerverwaltung</h2>
|
||||
<div>
|
||||
<a href="{{ url_for('admin.create_user') }}" class="btn btn-success">
|
||||
<i class="fas fa-user-plus"></i> Neuen Benutzer erstellen
|
||||
</a>
|
||||
<a href="{{ url_for('admin.admin_dashboard') }}" class="btn btn-outline-secondary">
|
||||
<i class="fas fa-arrow-left"></i> Admin Dashboard
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if users %}
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th>Benutzername</th>
|
||||
<th>E-Mail</th>
|
||||
<th>Rolle</th>
|
||||
<th>Erstellt am</th>
|
||||
<th>Aktionen</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for user in users %}
|
||||
<tr id="user-row-{{ user.id }}">
|
||||
<td>{{ user.id }}</td>
|
||||
<td>
|
||||
<strong>{{ user.username }}</strong>
|
||||
{% if user.id == current_user.id %}
|
||||
<span class="badge bg-primary ms-1">Sie</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>{{ user.email }}</td>
|
||||
<td>
|
||||
<div class="d-flex align-items-center">
|
||||
{% if user.is_admin %}
|
||||
<span class="badge bg-danger me-2">
|
||||
<i class="fas fa-user-shield"></i> Administrator
|
||||
</span>
|
||||
{% else %}
|
||||
<span class="badge bg-secondary me-2">
|
||||
<i class="fas fa-user"></i> Benutzer
|
||||
</span>
|
||||
{% endif %}
|
||||
|
||||
{% if user.id != current_user.id %}
|
||||
<button class="btn btn-xs btn-outline-info"
|
||||
onclick="toggleAdmin({{ user.id }})"
|
||||
title="Rolle wechseln">
|
||||
<i class="fas fa-exchange-alt"></i>
|
||||
</button>
|
||||
{% endif %}
|
||||
</div>
|
||||
</td>
|
||||
<td>{{ user.created_at.strftime('%d.%m.%Y %H:%M') }}</td>
|
||||
<td>
|
||||
<div class="btn-group btn-group-sm">
|
||||
<a href="{{ url_for('admin.edit_user', user_id=user.id) }}"
|
||||
class="btn btn-outline-primary" title="Bearbeiten">
|
||||
<i class="fas fa-edit"></i>
|
||||
</a>
|
||||
|
||||
{% if user.id != current_user.id %}
|
||||
<form method="POST" action="{{ url_for('admin.delete_user', user_id=user.id) }}"
|
||||
class="d-inline" onsubmit="return confirm('Benutzer {{ user.username }} wirklich löschen?')">
|
||||
<button type="submit" class="btn btn-outline-danger" title="Löschen">
|
||||
<i class="fas fa-trash"></i>
|
||||
</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="card">
|
||||
<div class="card-body text-center py-5">
|
||||
<i class="fas fa-users fa-3x text-muted mb-3"></i>
|
||||
<h4>Keine Benutzer gefunden</h4>
|
||||
<p class="text-muted">Erstellen Sie den ersten Benutzer.</p>
|
||||
<a href="{{ url_for('admin.create_user') }}" class="btn btn-success">
|
||||
<i class="fas fa-user-plus"></i> Ersten Benutzer erstellen
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
async function toggleAdmin(userId) {
|
||||
try {
|
||||
const response = await fetch(`/admin/users/${userId}/toggle_admin`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
}
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (data.success) {
|
||||
showAlert(data.message, 'success');
|
||||
// Seite neu laden um Änderungen anzuzeigen
|
||||
setTimeout(() => window.location.reload(), 1000);
|
||||
} else {
|
||||
showAlert(data.error, 'error');
|
||||
}
|
||||
} catch (error) {
|
||||
showAlert(`Fehler: ${error.message}`, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
function showAlert(message, type = 'info') {
|
||||
const alertClass = type === 'error' ? 'alert-danger' :
|
||||
type === 'success' ? 'alert-success' : 'alert-info';
|
||||
|
||||
const alertHtml = `
|
||||
<div class="alert ${alertClass} alert-dismissible fade show" role="alert">
|
||||
${message}
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
|
||||
</div>
|
||||
`;
|
||||
|
||||
// Alert am Anfang der Seite einfügen
|
||||
const container = document.querySelector('.container-fluid');
|
||||
const existingAlert = container.querySelector('.alert');
|
||||
if (existingAlert) {
|
||||
existingAlert.remove();
|
||||
}
|
||||
|
||||
container.insertAdjacentHTML('afterbegin', alertHtml);
|
||||
}
|
||||
</script>
|
||||
{% endblock %}
|
||||
@ -23,8 +23,16 @@
|
||||
|
||||
{% if current_user.is_authenticated %}
|
||||
<div class="navbar-nav ms-auto">
|
||||
{% if current_user.is_admin %}
|
||||
<a class="nav-link me-3" href="{{ url_for('admin.admin_dashboard') }}">
|
||||
<i class="fas fa-users-cog"></i> Admin
|
||||
</a>
|
||||
{% endif %}
|
||||
<span class="navbar-text me-3">
|
||||
<i class="fas fa-user"></i> {{ current_user.username }}
|
||||
{% if current_user.is_admin %}
|
||||
<span class="badge bg-danger ms-1">Admin</span>
|
||||
{% endif %}
|
||||
</span>
|
||||
<a class="nav-link" href="{{ url_for('auth.logout') }}">
|
||||
<i class="fas fa-sign-out-alt"></i> Abmelden
|
||||
|
||||
Binary file not shown.
84
migrate_admin.py
Normal file
84
migrate_admin.py
Normal file
@ -0,0 +1,84 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Migrations-Skript um das is_admin Feld zur User-Tabelle hinzuzufügen
|
||||
"""
|
||||
import sqlite3
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
def migrate_database():
|
||||
"""Fügt das is_admin Feld zur User-Tabelle hinzu"""
|
||||
|
||||
# Finde die Datenbankdatei
|
||||
possible_paths = [
|
||||
'querybuilder.db',
|
||||
'instance/querybuilder.db',
|
||||
Path(__file__).parent / 'querybuilder.db',
|
||||
Path(__file__).parent / 'instance' / 'querybuilder.db'
|
||||
]
|
||||
|
||||
db_path = None
|
||||
for path in possible_paths:
|
||||
if os.path.exists(path):
|
||||
db_path = path
|
||||
break
|
||||
|
||||
if not db_path:
|
||||
print("❌ Keine Datenbankdatei gefunden!")
|
||||
return False
|
||||
|
||||
print(f"📁 Verwende Datenbank: {db_path}")
|
||||
|
||||
try:
|
||||
conn = sqlite3.connect(db_path)
|
||||
cursor = conn.cursor()
|
||||
|
||||
# Prüfe ob Spalte bereits existiert
|
||||
cursor.execute("PRAGMA table_info(user)")
|
||||
columns = [row[1] for row in cursor.fetchall()]
|
||||
|
||||
if 'is_admin' in columns:
|
||||
print("✅ Spalte 'is_admin' existiert bereits!")
|
||||
|
||||
# Setze den ersten User (admin) als Administrator
|
||||
cursor.execute("UPDATE user SET is_admin = 1 WHERE username = 'admin'")
|
||||
if cursor.rowcount > 0:
|
||||
print("✅ Admin-User wurde als Administrator markiert!")
|
||||
|
||||
conn.commit()
|
||||
conn.close()
|
||||
return True
|
||||
|
||||
# Füge is_admin Spalte hinzu
|
||||
print("➕ Füge 'is_admin' Spalte hinzu...")
|
||||
cursor.execute("ALTER TABLE user ADD COLUMN is_admin BOOLEAN NOT NULL DEFAULT 0")
|
||||
|
||||
# Setze den ersten User (admin) als Administrator
|
||||
cursor.execute("UPDATE user SET is_admin = 1 WHERE username = 'admin'")
|
||||
admin_updated = cursor.rowcount > 0
|
||||
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
print("✅ Migration erfolgreich abgeschlossen!")
|
||||
if admin_updated:
|
||||
print("✅ Admin-User wurde als Administrator markiert!")
|
||||
else:
|
||||
print("⚠️ Kein Admin-User gefunden - wird beim nächsten Start erstellt")
|
||||
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ Fehler bei der Migration: {e}")
|
||||
return False
|
||||
|
||||
if __name__ == "__main__":
|
||||
print("🔄 Starte Datenbank-Migration...")
|
||||
success = migrate_database()
|
||||
|
||||
if success:
|
||||
print("\n🎉 Migration abgeschlossen!")
|
||||
print("Der Admin-User kann jetzt auf das Admin-Panel zugreifen.")
|
||||
else:
|
||||
print("\n💥 Migration fehlgeschlagen!")
|
||||
print("Bitte überprüfen Sie die Fehlermeldungen oben.")
|
||||
Loading…
x
Reference in New Issue
Block a user