resolve issues with new features
This commit is contained in:
@@ -63,6 +63,16 @@ const PERMISSION_ROLE_MIN: Record<string, AppRole> = {
|
|||||||
'bookings:delete': 'admin',
|
'bookings:delete': 'admin',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Derive an AppRole from Authentik JWT groups (highest matching role wins).
|
||||||
|
*/
|
||||||
|
function roleFromGroups(groups: string[]): AppRole {
|
||||||
|
if (groups.includes('dashboard_admin')) return 'admin';
|
||||||
|
if (groups.includes('dashboard_kommando')) return 'kommandant';
|
||||||
|
if (groups.includes('dashboard_fahrmeister') || groups.includes('dashboard_zeugmeister')) return 'gruppenfuehrer';
|
||||||
|
return 'mitglied';
|
||||||
|
}
|
||||||
|
|
||||||
function hasPermission(role: AppRole, permission: string): boolean {
|
function hasPermission(role: AppRole, permission: string): boolean {
|
||||||
const minRole = PERMISSION_ROLE_MIN[permission];
|
const minRole = PERMISSION_ROLE_MIN[permission];
|
||||||
if (!minRole) {
|
if (!minRole) {
|
||||||
@@ -116,24 +126,16 @@ export function requirePermission(permission: string) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const role = (req.user as any).role
|
const dbRole = (req.user as any).role
|
||||||
? (req.user as any).role as AppRole
|
? (req.user as any).role as AppRole
|
||||||
: await getUserRole(req.user.id);
|
: await getUserRole(req.user.id);
|
||||||
|
const groupRole = roleFromGroups(req.user?.groups ?? []);
|
||||||
|
const role = ROLE_HIERARCHY.indexOf(groupRole) > ROLE_HIERARCHY.indexOf(dbRole) ? groupRole : dbRole;
|
||||||
|
|
||||||
// Attach role to request for downstream use (e.g., bericht_text redaction)
|
// Attach role to request for downstream use (e.g., bericht_text redaction)
|
||||||
(req as Request & { userRole?: AppRole }).userRole = role;
|
(req as Request & { userRole?: AppRole }).userRole = role;
|
||||||
|
|
||||||
if (!hasPermission(role, permission)) {
|
if (!hasPermission(role, permission)) {
|
||||||
// Fallback: dashboard_admin group grants admin:access
|
|
||||||
if (permission === 'admin:access') {
|
|
||||||
const userGroups: string[] = req.user?.groups ?? [];
|
|
||||||
if (userGroups.includes('dashboard_admin')) {
|
|
||||||
(req as Request & { userRole?: AppRole }).userRole = 'admin';
|
|
||||||
next();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.warn('Permission denied', {
|
logger.warn('Permission denied', {
|
||||||
userId: req.user.id,
|
userId: req.user.id,
|
||||||
role,
|
role,
|
||||||
|
|||||||
@@ -49,16 +49,20 @@ const ChatPanelInner: React.FC = () => {
|
|||||||
// Mark unread rooms as read in Nextcloud whenever panel is open and rooms update
|
// Mark unread rooms as read in Nextcloud whenever panel is open and rooms update
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if (!chatPanelOpen) return;
|
if (!chatPanelOpen) return;
|
||||||
rooms.filter((r) => r.unreadMessages > 0).forEach((r) => {
|
const unread = rooms.filter((r) => r.unreadMessages > 0);
|
||||||
nextcloudApi.markAsRead(r.token).catch(() => {});
|
if (unread.length === 0) return;
|
||||||
|
Promise.allSettled(unread.map((r) => nextcloudApi.markAsRead(r.token))).then(() => {
|
||||||
|
queryClient.invalidateQueries({ queryKey: ['nextcloud', 'rooms'] });
|
||||||
});
|
});
|
||||||
}, [chatPanelOpen, rooms]);
|
}, [chatPanelOpen, rooms, queryClient]);
|
||||||
|
|
||||||
// Mark the selected room as read when a conversation is opened
|
// Mark the selected room as read when a conversation is opened
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if (!selectedRoomToken) return;
|
if (!selectedRoomToken) return;
|
||||||
nextcloudApi.markAsRead(selectedRoomToken).catch(() => {});
|
nextcloudApi.markAsRead(selectedRoomToken).then(() => {
|
||||||
}, [selectedRoomToken]);
|
queryClient.invalidateQueries({ queryKey: ['nextcloud', 'rooms'] });
|
||||||
|
}).catch(() => {});
|
||||||
|
}, [selectedRoomToken, queryClient]);
|
||||||
|
|
||||||
if (!chatPanelOpen) {
|
if (!chatPanelOpen) {
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { useState, useEffect } from 'react';
|
import { useState, useEffect } from 'react';
|
||||||
import { Box, Tabs, Tab, Typography } from '@mui/material';
|
import { Box, Tabs, Tab, Typography } from '@mui/material';
|
||||||
import { Navigate, useSearchParams } from 'react-router-dom';
|
import { Navigate, useNavigate, useSearchParams } from 'react-router-dom';
|
||||||
import DashboardLayout from '../components/dashboard/DashboardLayout';
|
import DashboardLayout from '../components/dashboard/DashboardLayout';
|
||||||
import ServiceManagerTab from '../components/admin/ServiceManagerTab';
|
import ServiceManagerTab from '../components/admin/ServiceManagerTab';
|
||||||
import SystemHealthTab from '../components/admin/SystemHealthTab';
|
import SystemHealthTab from '../components/admin/SystemHealthTab';
|
||||||
@@ -24,6 +24,7 @@ function TabPanel({ children, value, index }: TabPanelProps) {
|
|||||||
const ADMIN_TAB_COUNT = 6;
|
const ADMIN_TAB_COUNT = 6;
|
||||||
|
|
||||||
function AdminDashboard() {
|
function AdminDashboard() {
|
||||||
|
const navigate = useNavigate();
|
||||||
const [searchParams] = useSearchParams();
|
const [searchParams] = useSearchParams();
|
||||||
const [tab, setTab] = useState(() => {
|
const [tab, setTab] = useState(() => {
|
||||||
const t = Number(searchParams.get('tab'));
|
const t = Number(searchParams.get('tab'));
|
||||||
@@ -47,7 +48,7 @@ function AdminDashboard() {
|
|||||||
<Typography variant="h4" sx={{ mb: 3 }}>Administration</Typography>
|
<Typography variant="h4" sx={{ mb: 3 }}>Administration</Typography>
|
||||||
|
|
||||||
<Box sx={{ borderBottom: 1, borderColor: 'divider' }}>
|
<Box sx={{ borderBottom: 1, borderColor: 'divider' }}>
|
||||||
<Tabs value={tab} onChange={(_e, v) => setTab(v)}>
|
<Tabs value={tab} onChange={(_e, v) => { setTab(v); navigate(`/admin?tab=${v}`, { replace: true }); }}>
|
||||||
<Tab label="Services" />
|
<Tab label="Services" />
|
||||||
<Tab label="System" />
|
<Tab label="System" />
|
||||||
<Tab label="Benutzer" />
|
<Tab label="Benutzer" />
|
||||||
|
|||||||
@@ -159,8 +159,8 @@ const UebersichtTab: React.FC<UebersichtTabProps> = ({ equipment, onStatusUpdate
|
|||||||
await equipmentApi.updateStatus(equipment.id, payload);
|
await equipmentApi.updateStatus(equipment.id, payload);
|
||||||
setStatusDialogOpen(false);
|
setStatusDialogOpen(false);
|
||||||
onStatusUpdated();
|
onStatusUpdated();
|
||||||
} catch {
|
} catch (err: any) {
|
||||||
setSaveError('Status konnte nicht gespeichert werden.');
|
setSaveError(err?.response?.data?.message || 'Status konnte nicht gespeichert werden.');
|
||||||
} finally {
|
} finally {
|
||||||
setSaving(false);
|
setSaving(false);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -153,8 +153,8 @@ const UebersichtTab: React.FC<UebersichtTabProps> = ({ vehicle, onStatusUpdated,
|
|||||||
await vehiclesApi.updateStatus(vehicle.id, payload);
|
await vehiclesApi.updateStatus(vehicle.id, payload);
|
||||||
setStatusDialogOpen(false);
|
setStatusDialogOpen(false);
|
||||||
onStatusUpdated();
|
onStatusUpdated();
|
||||||
} catch {
|
} catch (err: any) {
|
||||||
setSaveError('Status konnte nicht gespeichert werden.');
|
setSaveError(err?.response?.data?.message || 'Status konnte nicht gespeichert werden.');
|
||||||
} finally {
|
} finally {
|
||||||
setSaving(false);
|
setSaving(false);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2158,7 +2158,7 @@ export default function Kalender() {
|
|||||||
{/* Tabs */}
|
{/* Tabs */}
|
||||||
<Tabs
|
<Tabs
|
||||||
value={activeTab}
|
value={activeTab}
|
||||||
onChange={(_, v) => setActiveTab(v)}
|
onChange={(_, v) => { setActiveTab(v); navigate(`/kalender?tab=${v}`, { replace: true }); }}
|
||||||
sx={{ mb: 2, borderBottom: 1, borderColor: 'divider' }}
|
sx={{ mb: 2, borderBottom: 1, borderColor: 'divider' }}
|
||||||
>
|
>
|
||||||
<Tab icon={<EventIcon />} iconPosition="start" label="Dienste & Veranstaltungen" />
|
<Tab icon={<EventIcon />} iconPosition="start" label="Dienste & Veranstaltungen" />
|
||||||
|
|||||||
Reference in New Issue
Block a user