rename benutzer to users
add role management to media page
This commit is contained in:
@@ -34,6 +34,7 @@
|
||||
"version": "2025.1.0-alpha.10",
|
||||
"date": "2025-10-15",
|
||||
"changes": [
|
||||
"🔐 Auth: Login und Benutzerverwaltung implementiert (rollenbasiert, persistente Sitzungen).",
|
||||
"✨ UI: Benutzer-Menü oben rechts – DropDownButton mit Benutzername/Rolle; Einträge: ‘Profil’ und ‘Abmelden’.",
|
||||
"🧩 Frontend: Syncfusion SplitButtons integriert (react-splitbuttons) und Vite-Konfiguration für Pre-Bundling ergänzt.",
|
||||
"🐛 Fix: Import-Fehler ‘@syncfusion/ej2-react-splitbuttons’ – Anleitung in README hinzugefügt (optimizeDeps + Volume-Reset)."
|
||||
|
||||
@@ -44,7 +44,7 @@ import Ressourcen from './ressourcen';
|
||||
import Infoscreens from './clients';
|
||||
import Infoscreen_groups from './infoscreen_groups';
|
||||
import Media from './media';
|
||||
import Benutzer from './benutzer';
|
||||
import Benutzer from './users';
|
||||
import Einstellungen from './settings';
|
||||
import SetupMode from './SetupMode';
|
||||
import Programminfo from './programminfo';
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import React, { useState } from 'react';
|
||||
import React, { useMemo, useState } from 'react';
|
||||
import { useAuth } from '../useAuth';
|
||||
import { DialogComponent } from '@syncfusion/ej2-react-popups';
|
||||
import {
|
||||
FileManagerComponent,
|
||||
@@ -19,6 +20,8 @@ type CustomSelectUploadEventModalProps = {
|
||||
|
||||
const CustomSelectUploadEventModal: React.FC<CustomSelectUploadEventModalProps> = props => {
|
||||
const { open, onClose, onSelect } = props;
|
||||
const { user } = useAuth();
|
||||
const isSuperadmin = useMemo(() => user?.role === 'superadmin', [user]);
|
||||
|
||||
const [selectedFile, setSelectedFile] = useState<{
|
||||
id: string;
|
||||
@@ -63,6 +66,23 @@ const CustomSelectUploadEventModal: React.FC<CustomSelectUploadEventModalProps>
|
||||
}
|
||||
};
|
||||
|
||||
type FileItem = { name: string; isFile: boolean };
|
||||
type ReadSuccessArgs = { action: string; result?: { files?: FileItem[] } };
|
||||
type FileOpenArgs = { fileDetails?: FileItem; cancel?: boolean };
|
||||
|
||||
const handleSuccess = (args: ReadSuccessArgs) => {
|
||||
if (isSuperadmin) return;
|
||||
if (args && args.action === 'read' && args.result && Array.isArray(args.result.files)) {
|
||||
args.result.files = args.result.files.filter((f: FileItem) => !(f.name === 'converted' && !f.isFile));
|
||||
}
|
||||
};
|
||||
|
||||
const handleFileOpen = (args: FileOpenArgs) => {
|
||||
if (!isSuperadmin && args && args.fileDetails && args.fileDetails.name === 'converted' && !args.fileDetails.isFile) {
|
||||
args.cancel = true;
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<DialogComponent
|
||||
target="#root"
|
||||
@@ -84,6 +104,9 @@ const CustomSelectUploadEventModal: React.FC<CustomSelectUploadEventModalProps>
|
||||
)}
|
||||
>
|
||||
<FileManagerComponent
|
||||
cssClass="e-bigger media-icons-xl"
|
||||
success={handleSuccess}
|
||||
fileOpen={handleFileOpen}
|
||||
ajaxSettings={{
|
||||
url: hostUrl + 'operations',
|
||||
getImageUrl: hostUrl + 'get-image',
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
/* Tailwind removed: base/components/utilities directives no longer used. */
|
||||
|
||||
/* Custom overrides moved to theme-overrides.css to load after Syncfusion styles */
|
||||
|
||||
/* :root {
|
||||
font-family: system-ui, Avenir, Helvetica, Arial, sans-serif;
|
||||
line-height: 1.5;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import React, { useState } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { useAuth } from './useAuth';
|
||||
|
||||
export default function Login() {
|
||||
@@ -7,14 +8,16 @@ export default function Login() {
|
||||
const [password, setPassword] = useState('');
|
||||
const [message, setMessage] = useState<string | null>(null);
|
||||
const isDev = import.meta.env.MODE !== 'production';
|
||||
const navigate = useNavigate();
|
||||
|
||||
const handleSubmit = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
setMessage(null);
|
||||
try {
|
||||
await login(username, password);
|
||||
// Browser will stay on /login; App's route gate will redirect to '/'
|
||||
setMessage('Login erfolgreich');
|
||||
// Redirect to dashboard after successful login
|
||||
navigate('/');
|
||||
} catch (err) {
|
||||
setMessage(err instanceof Error ? err.message : 'Login fehlgeschlagen');
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ import '@syncfusion/ej2-lists/styles/material3.css';
|
||||
import '@syncfusion/ej2-calendars/styles/material3.css';
|
||||
import '@syncfusion/ej2-splitbuttons/styles/material3.css';
|
||||
import '@syncfusion/ej2-icons/styles/material3.css';
|
||||
import './theme-overrides.css';
|
||||
|
||||
// Setze hier deinen Lizenzschlüssel ein
|
||||
registerLicense(
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useState, useRef } from 'react';
|
||||
import React, { useState, useRef, useMemo } from 'react';
|
||||
import CustomMediaInfoPanel from './components/CustomMediaInfoPanel';
|
||||
import {
|
||||
FileManagerComponent,
|
||||
@@ -7,10 +7,13 @@ import {
|
||||
DetailsView,
|
||||
Toolbar,
|
||||
} from '@syncfusion/ej2-react-filemanager';
|
||||
import { useAuth } from './useAuth';
|
||||
|
||||
const hostUrl = '/api/eventmedia/filemanager/'; // Dein Backend-Endpunkt für FileManager
|
||||
|
||||
const Media: React.FC = () => {
|
||||
const { user } = useAuth();
|
||||
const isSuperadmin = useMemo(() => user?.role === 'superadmin', [user]);
|
||||
// State für die angezeigten Dateidetails
|
||||
const [fileDetails] = useState<null | {
|
||||
name: string;
|
||||
@@ -43,6 +46,25 @@ const Media: React.FC = () => {
|
||||
}
|
||||
}, [viewMode]);
|
||||
|
||||
type FileItem = { name: string; isFile: boolean };
|
||||
type ReadSuccessArgs = { action: string; result?: { files?: FileItem[] } };
|
||||
type FileOpenArgs = { fileDetails?: FileItem; cancel?: boolean };
|
||||
|
||||
// Hide "converted" for non-superadmins after data load
|
||||
const handleSuccess = (args: ReadSuccessArgs) => {
|
||||
if (isSuperadmin) return;
|
||||
if (args && args.action === 'read' && args.result && Array.isArray(args.result.files)) {
|
||||
args.result.files = args.result.files.filter((f: FileItem) => !(f.name === 'converted' && !f.isFile));
|
||||
}
|
||||
};
|
||||
|
||||
// Prevent opening the "converted" folder for non-superadmins
|
||||
const handleFileOpen = (args: FileOpenArgs) => {
|
||||
if (!isSuperadmin && args && args.fileDetails && args.fileDetails.name === 'converted' && !args.fileDetails.isFile) {
|
||||
args.cancel = true;
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h2 className="text-xl font-bold mb-4">Medien</h2>
|
||||
@@ -65,6 +87,9 @@ const Media: React.FC = () => {
|
||||
{/* Debug-Ausgabe entfernt, da ReactNode erwartet wird */}
|
||||
<FileManagerComponent
|
||||
ref={fileManagerRef}
|
||||
cssClass="e-bigger media-icons-xl"
|
||||
success={handleSuccess}
|
||||
fileOpen={handleFileOpen}
|
||||
ajaxSettings={{
|
||||
url: hostUrl + 'operations',
|
||||
getImageUrl: hostUrl + 'get-image',
|
||||
|
||||
15
dashboard/src/theme-overrides.css
Normal file
15
dashboard/src/theme-overrides.css
Normal file
@@ -0,0 +1,15 @@
|
||||
/* FileManager icon size overrides (loaded after Syncfusion styles) */
|
||||
.e-filemanager.media-icons-xl .e-large-icons .e-list-icon {
|
||||
font-size: 40px; /* default ~24px */
|
||||
}
|
||||
|
||||
.e-filemanager.media-icons-xl .e-large-icons .e-fe-folder,
|
||||
.e-filemanager.media-icons-xl .e-large-icons .e-fe-file {
|
||||
font-size: 40px;
|
||||
}
|
||||
|
||||
/* Details (grid) view icons */
|
||||
.e-filemanager.media-icons-xl .e-fe-grid-icon .e-fe-folder,
|
||||
.e-filemanager.media-icons-xl .e-fe-grid-icon .e-fe-file {
|
||||
font-size: 24px;
|
||||
}
|
||||
@@ -38,7 +38,18 @@ def filemanager_operations():
|
||||
|
||||
print(action, path, name, new_name, target_path, full_path) # Debug-Ausgabe
|
||||
|
||||
# Superadmin-only protection for the converted folder
|
||||
from flask import session as flask_session
|
||||
user_role = flask_session.get('role')
|
||||
is_superadmin = user_role == 'superadmin'
|
||||
# Normalize path for checks
|
||||
norm_path = os.path.normpath('/' + path.lstrip('/'))
|
||||
under_converted = norm_path == '/converted' or norm_path.startswith('/converted/')
|
||||
|
||||
if action == 'read':
|
||||
# Block listing inside converted for non-superadmins
|
||||
if under_converted and not is_superadmin:
|
||||
return jsonify({'files': [], 'cwd': {'name': os.path.basename(full_path), 'path': path}})
|
||||
# List files and folders
|
||||
items = []
|
||||
session = Session()
|
||||
@@ -61,7 +72,9 @@ def filemanager_operations():
|
||||
item['dateModified'] = entry.stat().st_mtime
|
||||
else:
|
||||
item['dateModified'] = entry.stat().st_mtime
|
||||
items.append(item)
|
||||
# Hide the converted folder at root for non-superadmins
|
||||
if not (not is_superadmin and not entry.is_file() and entry.name == 'converted' and (norm_path == '/' or norm_path == '')):
|
||||
items.append(item)
|
||||
session.close()
|
||||
return jsonify({'files': items, 'cwd': {'name': os.path.basename(full_path), 'path': path}})
|
||||
|
||||
@@ -90,6 +103,8 @@ def filemanager_operations():
|
||||
session.close()
|
||||
return jsonify({'details': details})
|
||||
elif action == 'delete':
|
||||
if under_converted and not is_superadmin:
|
||||
return jsonify({'error': 'Insufficient permissions'}), 403
|
||||
for item in request.form.getlist('names[]'):
|
||||
item_path = os.path.join(full_path, item)
|
||||
if os.path.isdir(item_path):
|
||||
@@ -98,16 +113,23 @@ def filemanager_operations():
|
||||
os.remove(item_path)
|
||||
return jsonify({'success': True})
|
||||
elif action == 'rename':
|
||||
if under_converted and not is_superadmin:
|
||||
return jsonify({'error': 'Insufficient permissions'}), 403
|
||||
src = os.path.join(full_path, name)
|
||||
dst = os.path.join(full_path, new_name)
|
||||
os.rename(src, dst)
|
||||
return jsonify({'success': True})
|
||||
elif action == 'move':
|
||||
# Prevent moving into converted if not superadmin
|
||||
if (target_path and target_path.strip('/').split('/')[0] == 'converted') and not is_superadmin:
|
||||
return jsonify({'error': 'Insufficient permissions'}), 403
|
||||
src = os.path.join(full_path, name)
|
||||
dst = os.path.join(MEDIA_ROOT, target_path.lstrip('/'), name)
|
||||
os.rename(src, dst)
|
||||
return jsonify({'success': True})
|
||||
elif action == 'create':
|
||||
if under_converted and not is_superadmin:
|
||||
return jsonify({'error': 'Insufficient permissions'}), 403
|
||||
os.makedirs(os.path.join(full_path, name), exist_ok=True)
|
||||
return jsonify({'success': True})
|
||||
else:
|
||||
@@ -122,6 +144,12 @@ def filemanager_upload():
|
||||
session = Session()
|
||||
# Korrigiert: Erst aus request.form, dann aus request.args lesen
|
||||
path = request.form.get('path') or request.args.get('path', '/')
|
||||
from flask import session as flask_session
|
||||
user_role = flask_session.get('role')
|
||||
is_superadmin = user_role == 'superadmin'
|
||||
norm_path = os.path.normpath('/' + path.lstrip('/'))
|
||||
if (norm_path == '/converted' or norm_path.startswith('/converted/')) and not is_superadmin:
|
||||
return jsonify({'error': 'Insufficient permissions'}), 403
|
||||
upload_path = os.path.join(MEDIA_ROOT, path.lstrip('/'))
|
||||
os.makedirs(upload_path, exist_ok=True)
|
||||
for file in request.files.getlist('uploadFiles'):
|
||||
@@ -184,9 +212,16 @@ def filemanager_upload():
|
||||
@eventmedia_bp.route('/filemanager/download', methods=['GET'])
|
||||
def filemanager_download():
|
||||
path = request.args.get('path', '/')
|
||||
from flask import session as flask_session
|
||||
user_role = flask_session.get('role')
|
||||
is_superadmin = user_role == 'superadmin'
|
||||
norm_path = os.path.normpath('/' + path.lstrip('/'))
|
||||
names = request.args.getlist('names[]')
|
||||
# Nur Einzel-Download für Beispiel
|
||||
if names:
|
||||
# Block access to converted for non-superadmins
|
||||
if (norm_path == '/converted' or norm_path.startswith('/converted/')) and not is_superadmin:
|
||||
return jsonify({'error': 'Insufficient permissions'}), 403
|
||||
file_path = os.path.join(MEDIA_ROOT, path.lstrip('/'), names[0])
|
||||
return send_from_directory(os.path.dirname(file_path), os.path.basename(file_path), as_attachment=True)
|
||||
return jsonify({'error': 'No file specified'}), 400
|
||||
@@ -197,6 +232,12 @@ def filemanager_download():
|
||||
@eventmedia_bp.route('/filemanager/get-image', methods=['GET'])
|
||||
def filemanager_get_image():
|
||||
path = request.args.get('path', '/')
|
||||
from flask import session as flask_session
|
||||
user_role = flask_session.get('role')
|
||||
is_superadmin = user_role == 'superadmin'
|
||||
norm_path = os.path.normpath('/' + path.lstrip('/'))
|
||||
if (norm_path == '/converted' or norm_path.startswith('/converted/')) and not is_superadmin:
|
||||
return jsonify({'error': 'Insufficient permissions'}), 403
|
||||
file_path = os.path.join(MEDIA_ROOT, path.lstrip('/'))
|
||||
return send_from_directory(os.path.dirname(file_path), os.path.basename(file_path))
|
||||
|
||||
|
||||
Reference in New Issue
Block a user