new features
This commit is contained in:
170
backend/src/database/migrations/045_add_permissions_batch.sql
Normal file
170
backend/src/database/migrations/045_add_permissions_batch.sql
Normal file
@@ -0,0 +1,170 @@
|
|||||||
|
-- Migration 045: Add new permissions + seed dependency config
|
||||||
|
-- 1. fahrzeuge:edit
|
||||||
|
-- 2. atemschutz:edit
|
||||||
|
-- 3. Per-tool admin permissions
|
||||||
|
-- 4. bestellungen:manage_orders
|
||||||
|
|
||||||
|
-- ═══════════════════════════════════════════════════════════════════════════
|
||||||
|
-- 1. New permissions
|
||||||
|
-- ═══════════════════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
|
-- fahrzeuge:edit (between create and change_status)
|
||||||
|
INSERT INTO permissions (id, feature_group_id, label, description, sort_order) VALUES
|
||||||
|
('fahrzeuge:edit', 'fahrzeuge', 'Bearbeiten', 'Fahrzeugdaten bearbeiten', 2)
|
||||||
|
ON CONFLICT (id) DO NOTHING;
|
||||||
|
|
||||||
|
-- atemschutz:edit (between view and create)
|
||||||
|
INSERT INTO permissions (id, feature_group_id, label, description, sort_order) VALUES
|
||||||
|
('atemschutz:edit', 'atemschutz', 'Bearbeiten', 'Atemschutz-Einträge bearbeiten', 2)
|
||||||
|
ON CONFLICT (id) DO NOTHING;
|
||||||
|
|
||||||
|
-- bestellungen:manage_orders (manage order status + received quantities)
|
||||||
|
INSERT INTO permissions (id, feature_group_id, label, description, sort_order) VALUES
|
||||||
|
('bestellungen:manage_orders', 'bestellungen', 'Bestellungen verwalten', 'Bestellstatus ändern und Wareneingänge verbuchen', 3)
|
||||||
|
ON CONFLICT (id) DO NOTHING;
|
||||||
|
|
||||||
|
-- Per-tool admin permissions
|
||||||
|
INSERT INTO permissions (id, feature_group_id, label, description, sort_order) VALUES
|
||||||
|
('admin:view_services', 'admin', 'Services ansehen', 'Service-Monitoring einsehen', 3),
|
||||||
|
('admin:edit_services', 'admin', 'Services bearbeiten', 'Überwachte Services verwalten', 4),
|
||||||
|
('admin:view_system', 'admin', 'System ansehen', 'Systeminformationen einsehen', 5),
|
||||||
|
('admin:view_users', 'admin', 'Benutzer ansehen', 'Benutzerliste einsehen', 6),
|
||||||
|
('admin:edit_users', 'admin', 'Benutzer bearbeiten', 'Benutzereinstellungen verwalten', 7),
|
||||||
|
('admin:edit_broadcast', 'admin', 'Broadcast senden', 'Push-Benachrichtigungen senden', 8),
|
||||||
|
('admin:view_banner', 'admin', 'Banner ansehen', 'Ankündigungs-Banner einsehen', 9),
|
||||||
|
('admin:edit_banner', 'admin', 'Banner bearbeiten', 'Ankündigungs-Banner verwalten', 10),
|
||||||
|
('admin:view_maintenance', 'admin', 'Wartung ansehen', 'Wartungsmodus-Status einsehen', 11),
|
||||||
|
('admin:edit_maintenance', 'admin', 'Wartung bearbeiten', 'Wartungsmodus umschalten', 12),
|
||||||
|
('admin:view_fdisk', 'admin', 'FDISK ansehen', 'FDISK-Synchronisation einsehen', 13),
|
||||||
|
('admin:edit_fdisk', 'admin', 'FDISK bearbeiten', 'FDISK-Synchronisation starten/verwalten', 14),
|
||||||
|
('admin:view_permissions', 'admin', 'Berechtigungen ansehen', 'Berechtigungsmatrix einsehen', 15),
|
||||||
|
('admin:edit_permissions', 'admin', 'Berechtigungen bearbeiten','Berechtigungen und Abhängigkeiten ändern',16),
|
||||||
|
('admin:view_order_settings', 'admin', 'Bestell-Admin ansehen', 'Bestellungs-Admin-Einstellungen einsehen',17),
|
||||||
|
('admin:edit_order_settings', 'admin', 'Bestell-Admin bearbeiten','Bestellungs-Admin-Einstellungen ändern', 18),
|
||||||
|
('admin:view_data', 'admin', 'Datenverwaltung ansehen', 'Datenverwaltung einsehen', 19),
|
||||||
|
('admin:edit_data', 'admin', 'Datenverwaltung bearbeiten','Daten importieren/exportieren/bereinigen',20),
|
||||||
|
('admin:view_debug', 'admin', 'Debug ansehen', 'Debug-Informationen einsehen', 21)
|
||||||
|
ON CONFLICT (id) DO NOTHING;
|
||||||
|
|
||||||
|
-- ═══════════════════════════════════════════════════════════════════════════
|
||||||
|
-- 2. Seed grants for existing groups
|
||||||
|
-- ═══════════════════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
|
-- Give fahrzeuge:edit to groups that already have fahrzeuge:create
|
||||||
|
INSERT INTO group_permissions (authentik_group, permission_id)
|
||||||
|
SELECT authentik_group, 'fahrzeuge:edit'
|
||||||
|
FROM group_permissions WHERE permission_id = 'fahrzeuge:create'
|
||||||
|
ON CONFLICT DO NOTHING;
|
||||||
|
|
||||||
|
-- Give atemschutz:edit to groups that already have atemschutz:create
|
||||||
|
INSERT INTO group_permissions (authentik_group, permission_id)
|
||||||
|
SELECT authentik_group, 'atemschutz:edit'
|
||||||
|
FROM group_permissions WHERE permission_id = 'atemschutz:create'
|
||||||
|
ON CONFLICT DO NOTHING;
|
||||||
|
|
||||||
|
-- Give bestellungen:manage_orders to groups that already have bestellungen:create
|
||||||
|
INSERT INTO group_permissions (authentik_group, permission_id)
|
||||||
|
SELECT authentik_group, 'bestellungen:manage_orders'
|
||||||
|
FROM group_permissions WHERE permission_id = 'bestellungen:create'
|
||||||
|
ON CONFLICT DO NOTHING;
|
||||||
|
|
||||||
|
-- Give all admin sub-permissions to groups that have admin:write
|
||||||
|
INSERT INTO group_permissions (authentik_group, permission_id)
|
||||||
|
SELECT gp.authentik_group, p.id
|
||||||
|
FROM group_permissions gp
|
||||||
|
CROSS JOIN permissions p
|
||||||
|
WHERE gp.permission_id = 'admin:write'
|
||||||
|
AND p.feature_group_id = 'admin'
|
||||||
|
AND p.id NOT IN ('admin:view', 'admin:write')
|
||||||
|
ON CONFLICT DO NOTHING;
|
||||||
|
|
||||||
|
-- Give all admin view sub-permissions to groups that have admin:view but not admin:write
|
||||||
|
INSERT INTO group_permissions (authentik_group, permission_id)
|
||||||
|
SELECT gp.authentik_group, p.id
|
||||||
|
FROM group_permissions gp
|
||||||
|
CROSS JOIN permissions p
|
||||||
|
WHERE gp.permission_id = 'admin:view'
|
||||||
|
AND p.feature_group_id = 'admin'
|
||||||
|
AND p.id LIKE 'admin:view_%'
|
||||||
|
ON CONFLICT DO NOTHING;
|
||||||
|
|
||||||
|
-- ═══════════════════════════════════════════════════════════════════════════
|
||||||
|
-- 3. Seed default permission dependency config
|
||||||
|
-- ═══════════════════════════════════════════════════════════════════════════
|
||||||
|
-- Only insert if no config exists yet (don't overwrite manual edits)
|
||||||
|
|
||||||
|
INSERT INTO app_settings (key, value)
|
||||||
|
SELECT 'permission_deps', '{
|
||||||
|
"kalender:create": ["kalender:view"],
|
||||||
|
"kalender:view_bookings": ["kalender:view"],
|
||||||
|
"kalender:manage_bookings": ["kalender:view_bookings", "kalender:view"],
|
||||||
|
|
||||||
|
"fahrzeuge:edit": ["fahrzeuge:view"],
|
||||||
|
"fahrzeuge:create": ["fahrzeuge:view"],
|
||||||
|
"fahrzeuge:change_status": ["fahrzeuge:view"],
|
||||||
|
"fahrzeuge:manage_maintenance": ["fahrzeuge:view"],
|
||||||
|
"fahrzeuge:delete": ["fahrzeuge:create", "fahrzeuge:view"],
|
||||||
|
"fahrzeuge:widget": ["fahrzeuge:view"],
|
||||||
|
|
||||||
|
"einsaetze:view_reports": ["einsaetze:view"],
|
||||||
|
"einsaetze:create": ["einsaetze:view"],
|
||||||
|
"einsaetze:delete": ["einsaetze:create", "einsaetze:view"],
|
||||||
|
"einsaetze:manage_personnel": ["einsaetze:view"],
|
||||||
|
|
||||||
|
"ausruestung:create": ["ausruestung:view"],
|
||||||
|
"ausruestung:manage_maintenance": ["ausruestung:view"],
|
||||||
|
"ausruestung:delete": ["ausruestung:create", "ausruestung:view"],
|
||||||
|
"ausruestung:manage_categories": ["ausruestung:view"],
|
||||||
|
"ausruestung:widget": ["ausruestung:view"],
|
||||||
|
|
||||||
|
"mitglieder:view_all": ["mitglieder:view_own"],
|
||||||
|
"mitglieder:edit": ["mitglieder:view_all", "mitglieder:view_own"],
|
||||||
|
"mitglieder:create_profile": ["mitglieder:edit", "mitglieder:view_all", "mitglieder:view_own"],
|
||||||
|
|
||||||
|
"atemschutz:edit": ["atemschutz:view"],
|
||||||
|
"atemschutz:create": ["atemschutz:view"],
|
||||||
|
"atemschutz:delete": ["atemschutz:create", "atemschutz:view"],
|
||||||
|
"atemschutz:widget": ["atemschutz:view"],
|
||||||
|
|
||||||
|
"bestellungen:create": ["bestellungen:view"],
|
||||||
|
"bestellungen:manage_orders": ["bestellungen:view"],
|
||||||
|
"bestellungen:delete": ["bestellungen:view"],
|
||||||
|
"bestellungen:manage_vendors": ["bestellungen:view"],
|
||||||
|
"bestellungen:export": ["bestellungen:view"],
|
||||||
|
"bestellungen:manage_reminders": ["bestellungen:view"],
|
||||||
|
"bestellungen:widget": ["bestellungen:view"],
|
||||||
|
|
||||||
|
"shop:create_request": ["shop:view"],
|
||||||
|
"shop:manage_catalog": ["shop:view"],
|
||||||
|
"shop:approve_requests": ["shop:view"],
|
||||||
|
"shop:link_orders": ["shop:approve_requests", "shop:view"],
|
||||||
|
"shop:view_overview": ["shop:view"],
|
||||||
|
"shop:order_for_user": ["shop:create_request", "shop:view"],
|
||||||
|
"shop:widget": ["shop:view"],
|
||||||
|
|
||||||
|
"issues:view_own": ["issues:create"],
|
||||||
|
"issues:view_all": ["issues:view_own"],
|
||||||
|
"issues:manage": ["issues:view_all", "issues:view_own"],
|
||||||
|
|
||||||
|
"admin:edit_services": ["admin:view_services", "admin:view"],
|
||||||
|
"admin:view_services": ["admin:view"],
|
||||||
|
"admin:view_system": ["admin:view"],
|
||||||
|
"admin:edit_users": ["admin:view_users", "admin:view"],
|
||||||
|
"admin:view_users": ["admin:view"],
|
||||||
|
"admin:edit_broadcast": ["admin:view"],
|
||||||
|
"admin:edit_banner": ["admin:view_banner", "admin:view"],
|
||||||
|
"admin:view_banner": ["admin:view"],
|
||||||
|
"admin:edit_maintenance": ["admin:view_maintenance", "admin:view"],
|
||||||
|
"admin:view_maintenance": ["admin:view"],
|
||||||
|
"admin:edit_fdisk": ["admin:view_fdisk", "admin:view"],
|
||||||
|
"admin:view_fdisk": ["admin:view"],
|
||||||
|
"admin:edit_permissions": ["admin:view_permissions", "admin:view"],
|
||||||
|
"admin:view_permissions": ["admin:view"],
|
||||||
|
"admin:edit_order_settings": ["admin:view_order_settings", "admin:view"],
|
||||||
|
"admin:view_order_settings": ["admin:view"],
|
||||||
|
"admin:edit_data": ["admin:view_data", "admin:view"],
|
||||||
|
"admin:view_data": ["admin:view"],
|
||||||
|
"admin:view_debug": ["admin:view"]
|
||||||
|
}'::jsonb
|
||||||
|
WHERE NOT EXISTS (SELECT 1 FROM app_settings WHERE key = 'permission_deps');
|
||||||
|
|
||||||
@@ -94,16 +94,30 @@ const PERMISSION_SUB_GROUPS: Record<string, Record<string, string[]>> = {
|
|||||||
'Buchungen': ['view_bookings', 'manage_bookings'],
|
'Buchungen': ['view_bookings', 'manage_bookings'],
|
||||||
},
|
},
|
||||||
bestellungen: {
|
bestellungen: {
|
||||||
'Bestellungen': ['view', 'create', 'delete', 'export'],
|
'Bestellungen': ['view', 'create', 'manage_orders', 'delete', 'export'],
|
||||||
'Lieferanten': ['manage_vendors'],
|
'Lieferanten': ['manage_vendors'],
|
||||||
'Erinnerungen': ['manage_reminders'],
|
'Erinnerungen': ['manage_reminders'],
|
||||||
'Widget': ['widget'],
|
'Widget': ['widget'],
|
||||||
},
|
},
|
||||||
shop: {
|
shop: {
|
||||||
'Katalog': ['view', 'manage_catalog'],
|
'Katalog': ['view', 'manage_catalog'],
|
||||||
'Anfragen': ['create_request', 'approve_requests', 'link_orders'],
|
'Anfragen': ['create_request', 'approve_requests', 'link_orders', 'view_overview', 'order_for_user'],
|
||||||
'Widget': ['widget'],
|
'Widget': ['widget'],
|
||||||
},
|
},
|
||||||
|
admin: {
|
||||||
|
'Allgemein': ['view', 'write'],
|
||||||
|
'Services': ['view_services', 'edit_services'],
|
||||||
|
'System': ['view_system'],
|
||||||
|
'Benutzer': ['view_users', 'edit_users'],
|
||||||
|
'Broadcast': ['edit_broadcast'],
|
||||||
|
'Banner': ['view_banner', 'edit_banner'],
|
||||||
|
'Wartung': ['view_maintenance', 'edit_maintenance'],
|
||||||
|
'FDISK': ['view_fdisk', 'edit_fdisk'],
|
||||||
|
'Berechtigungen': ['view_permissions', 'edit_permissions'],
|
||||||
|
'Bestell-Admin': ['view_order_settings', 'edit_order_settings'],
|
||||||
|
'Datenverwaltung': ['view_data', 'edit_data'],
|
||||||
|
'Debug': ['view_debug'],
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
function getSubGroupLabel(featureGroupId: string, permId: string): string | null {
|
function getSubGroupLabel(featureGroupId: string, permId: string): string | null {
|
||||||
@@ -327,6 +341,7 @@ function PermissionMatrixTab() {
|
|||||||
permissionDeps={permissionDeps}
|
permissionDeps={permissionDeps}
|
||||||
allGroups={nonAdminGroups}
|
allGroups={nonAdminGroups}
|
||||||
allPermissions={permissions}
|
allPermissions={permissions}
|
||||||
|
featureGroups={featureGroups}
|
||||||
onSave={(config) => depConfigMutation.mutate(config)}
|
onSave={(config) => depConfigMutation.mutate(config)}
|
||||||
isSaving={depConfigMutation.isPending}
|
isSaving={depConfigMutation.isPending}
|
||||||
/>
|
/>
|
||||||
@@ -512,14 +527,40 @@ interface DependencyEditorProps {
|
|||||||
permissionDeps: Record<string, string[]>;
|
permissionDeps: Record<string, string[]>;
|
||||||
allGroups: string[];
|
allGroups: string[];
|
||||||
allPermissions: Permission[];
|
allPermissions: Permission[];
|
||||||
|
featureGroups: FeatureGroup[];
|
||||||
onSave: (config: { groupHierarchy?: Record<string, string[]>; permissionDeps?: Record<string, string[]> }) => void;
|
onSave: (config: { groupHierarchy?: Record<string, string[]>; permissionDeps?: Record<string, string[]> }) => void;
|
||||||
isSaving: boolean;
|
isSaving: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
function DependencyEditor({ groupHierarchy, permissionDeps, allGroups, allPermissions, onSave, isSaving }: DependencyEditorProps) {
|
function DependencyEditor({ groupHierarchy, permissionDeps, allGroups, allPermissions, featureGroups, onSave, isSaving }: DependencyEditorProps) {
|
||||||
const groupSet = useMemo(() => new Set(allGroups), [allGroups]);
|
const groupSet = useMemo(() => new Set(allGroups), [allGroups]);
|
||||||
const permIdSet = useMemo(() => new Set(allPermissions.map(p => p.id)), [allPermissions]);
|
const permIdSet = useMemo(() => new Set(allPermissions.map(p => p.id)), [allPermissions]);
|
||||||
|
|
||||||
|
// Build lookup: permId → feature_group_id
|
||||||
|
const permToFeatureGroup = useMemo(() => {
|
||||||
|
const map: Record<string, string> = {};
|
||||||
|
for (const p of allPermissions) map[p.id] = p.feature_group_id;
|
||||||
|
return map;
|
||||||
|
}, [allPermissions]);
|
||||||
|
|
||||||
|
// Build lookup: feature_group_id → Permission[]
|
||||||
|
const permsByFeatureGroup = useMemo(() => {
|
||||||
|
const map: Record<string, Permission[]> = {};
|
||||||
|
for (const fg of featureGroups) map[fg.id] = [];
|
||||||
|
for (const p of allPermissions) {
|
||||||
|
if (!map[p.feature_group_id]) map[p.feature_group_id] = [];
|
||||||
|
map[p.feature_group_id].push(p);
|
||||||
|
}
|
||||||
|
return map;
|
||||||
|
}, [allPermissions, featureGroups]);
|
||||||
|
|
||||||
|
// Format label as "feature:action (Label)"
|
||||||
|
const formatPermLabel = useCallback((permId: string) => {
|
||||||
|
const perm = allPermissions.find(p => p.id === permId);
|
||||||
|
if (!perm) return permId;
|
||||||
|
return `${permId} (${perm.label})`;
|
||||||
|
}, [allPermissions]);
|
||||||
|
|
||||||
// Filter saved config to only include groups/permissions that actually exist
|
// Filter saved config to only include groups/permissions that actually exist
|
||||||
const [editHierarchy, setEditHierarchy] = useState<Record<string, string[]>>(() => {
|
const [editHierarchy, setEditHierarchy] = useState<Record<string, string[]>>(() => {
|
||||||
const filtered: Record<string, string[]> = {};
|
const filtered: Record<string, string[]> = {};
|
||||||
@@ -541,7 +582,6 @@ function DependencyEditor({ groupHierarchy, permissionDeps, allGroups, allPermis
|
|||||||
});
|
});
|
||||||
|
|
||||||
const nonAdminGroups = allGroups.filter(g => g !== 'dashboard_admin');
|
const nonAdminGroups = allGroups.filter(g => g !== 'dashboard_admin');
|
||||||
const permOptions = allPermissions.map(p => p.id);
|
|
||||||
|
|
||||||
const handleHierarchyChange = (group: string, inheritors: string[]) => {
|
const handleHierarchyChange = (group: string, inheritors: string[]) => {
|
||||||
setEditHierarchy(prev => {
|
setEditHierarchy(prev => {
|
||||||
@@ -577,6 +617,9 @@ function DependencyEditor({ groupHierarchy, permissionDeps, allGroups, allPermis
|
|||||||
|
|
||||||
const [newDepPerm, setNewDepPerm] = useState<string | null>(null);
|
const [newDepPerm, setNewDepPerm] = useState<string | null>(null);
|
||||||
|
|
||||||
|
// All permission options for "add new dependency" picker
|
||||||
|
const allPermOptions = allPermissions.map(p => p.id).filter(p => !editDeps[p]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 3, mt: 2 }}>
|
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 3, mt: 2 }}>
|
||||||
{/* Group Hierarchy Editor */}
|
{/* Group Hierarchy Editor */}
|
||||||
@@ -610,33 +653,45 @@ function DependencyEditor({ groupHierarchy, permissionDeps, allGroups, allPermis
|
|||||||
))}
|
))}
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
{/* Permission Dependency Editor */}
|
{/* Permission Dependency Editor — Grouped by Feature Group */}
|
||||||
<Box>
|
<Box>
|
||||||
<Typography variant="subtitle1" gutterBottom sx={{ fontWeight: 'bold' }}>
|
<Typography variant="subtitle1" gutterBottom sx={{ fontWeight: 'bold' }}>
|
||||||
Berechtigungsabhängigkeiten
|
Berechtigungsabhängigkeiten
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography variant="body2" color="text.secondary" sx={{ mb: 2 }}>
|
<Typography variant="body2" color="text.secondary" sx={{ mb: 2 }}>
|
||||||
Wenn eine Berechtigung gesetzt wird, werden die hier definierten Voraussetzungen automatisch mit aktiviert.
|
Wenn eine Berechtigung gesetzt wird, werden die hier definierten Voraussetzungen automatisch mit aktiviert.
|
||||||
|
Abhängigkeiten können nur innerhalb derselben Feature-Gruppe gesetzt werden.
|
||||||
</Typography>
|
</Typography>
|
||||||
{Object.entries(editDeps).map(([permId, deps]) => {
|
|
||||||
const perm = allPermissions.find(p => p.id === permId);
|
{featureGroups.map(fg => {
|
||||||
|
// Get dependencies that belong to this feature group
|
||||||
|
const fgDeps = Object.entries(editDeps).filter(([permId]) => permToFeatureGroup[permId] === fg.id);
|
||||||
|
if (fgDeps.length === 0) return null;
|
||||||
|
|
||||||
|
// Options for dependency targets: only perms within same feature group
|
||||||
|
const sameGroupPermOptions = (permsByFeatureGroup[fg.id] || []).map(p => p.id);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box key={permId} sx={{ display: 'flex', alignItems: 'center', gap: 1, mb: 1.5 }}>
|
<Box key={fg.id} sx={{ mb: 2 }}>
|
||||||
|
<Typography variant="body2" sx={{ fontWeight: 600, color: 'text.secondary', mb: 1, textTransform: 'uppercase', fontSize: '0.75rem', letterSpacing: 0.5 }}>
|
||||||
|
{fg.label}
|
||||||
|
</Typography>
|
||||||
|
{fgDeps.map(([permId, deps]) => (
|
||||||
|
<Box key={permId} sx={{ display: 'flex', alignItems: 'center', gap: 1, mb: 1.5, ml: 2 }}>
|
||||||
<Typography variant="body2" sx={{ minWidth: 220, fontWeight: 500 }}>
|
<Typography variant="body2" sx={{ minWidth: 220, fontWeight: 500 }}>
|
||||||
{perm?.label ?? permId}
|
{formatPermLabel(permId)}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography variant="body2" color="text.secondary" sx={{ mx: 1 }}>benötigt</Typography>
|
<Typography variant="body2" color="text.secondary" sx={{ mx: 1 }}>benötigt</Typography>
|
||||||
<Autocomplete
|
<Autocomplete
|
||||||
multiple size="small" sx={{ flex: 1 }}
|
multiple size="small" sx={{ flex: 1 }}
|
||||||
options={permOptions.filter(p => p !== permId)}
|
options={sameGroupPermOptions.filter(p => p !== permId)}
|
||||||
getOptionLabel={(p) => allPermissions.find(pp => pp.id === p)?.label ?? p}
|
getOptionLabel={formatPermLabel}
|
||||||
value={deps}
|
value={deps.filter(d => sameGroupPermOptions.includes(d))}
|
||||||
onChange={(_e, val) => handleDepChange(permId, val)}
|
onChange={(_e, val) => handleDepChange(permId, val)}
|
||||||
renderInput={(params) => <TextField {...params} placeholder="Voraussetzungen..." size="small" />}
|
renderInput={(params) => <TextField {...params} placeholder="Voraussetzungen..." size="small" />}
|
||||||
renderTags={(value, getTagProps) =>
|
renderTags={(value, getTagProps) =>
|
||||||
value.map((p, index) => (
|
value.map((p, index) => (
|
||||||
<Chip {...getTagProps({ index })} key={p}
|
<Chip {...getTagProps({ index })} key={p} label={formatPermLabel(p)} size="small" />
|
||||||
label={allPermissions.find(pp => pp.id === p)?.label ?? p} size="small" />
|
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
@@ -644,15 +699,21 @@ function DependencyEditor({ groupHierarchy, permissionDeps, allGroups, allPermis
|
|||||||
<DeleteIcon fontSize="small" />
|
<DeleteIcon fontSize="small" />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</Box>
|
</Box>
|
||||||
|
))}
|
||||||
|
</Box>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
|
||||||
{/* Add new dependency */}
|
{/* Add new dependency */}
|
||||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1, mt: 1 }}>
|
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1, mt: 1 }}>
|
||||||
<Autocomplete
|
<Autocomplete
|
||||||
size="small" sx={{ width: 300 }}
|
size="small" sx={{ width: 350 }}
|
||||||
options={permOptions.filter(p => !editDeps[p])}
|
options={allPermOptions}
|
||||||
getOptionLabel={(p) => allPermissions.find(pp => pp.id === p)?.label ?? p}
|
groupBy={(option) => {
|
||||||
|
const fg = featureGroups.find(f => f.id === permToFeatureGroup[option]);
|
||||||
|
return fg?.label ?? 'Sonstige';
|
||||||
|
}}
|
||||||
|
getOptionLabel={formatPermLabel}
|
||||||
value={newDepPerm}
|
value={newDepPerm}
|
||||||
onChange={(_e, val) => setNewDepPerm(val)}
|
onChange={(_e, val) => setNewDepPerm(val)}
|
||||||
renderInput={(params) => <TextField {...params} placeholder="Neue Abhängigkeit hinzufügen..." size="small" />}
|
renderInput={(params) => <TextField {...params} placeholder="Neue Abhängigkeit hinzufügen..." size="small" />}
|
||||||
|
|||||||
Reference in New Issue
Block a user