diff --git a/backend/api/views/docker_view.py b/backend/api/views/docker_view.py index 1e2da9d7..6fad8423 100644 --- a/backend/api/views/docker_view.py +++ b/backend/api/views/docker_view.py @@ -61,6 +61,14 @@ def patch_public(self, request: Request, **_) -> Response: return Response(serializer.data) + @action(detail=False, methods=['DELETE'], permission_classes=[IsAdminUser]) + def delete(self, request: Request, **_) -> Response: + response = self.queryset.filter(id__in=request.data['ids']).delete() + + return Response(response) + + # TODO: Maybe not necessary + # https://www.django-rest-framework.org/api-guide/permissions/#overview-of-access-restriction-methods def list(self, request: Request) -> Response: images: BaseManager[DockerImage] = DockerImage.objects.all() if not request.user.is_staff: diff --git a/backend/api/views/user_view.py b/backend/api/views/user_view.py index 3a65fa37..9d8b33a2 100644 --- a/backend/api/views/user_view.py +++ b/backend/api/views/user_view.py @@ -57,7 +57,7 @@ def search(self, request: Request) -> Response: role_filters = Q() for role in roles: - role_filters |= Q(**{f"{role}__isnull": False, f"{role}__is_active": True}) + role_filters &= Q(**{f"{role}__isnull": False, f"{role}__is_active": True}) queryset = queryset.filter( role_filters, diff --git a/frontend/src/assets/lang/app/en.json b/frontend/src/assets/lang/app/en.json index bafc4d00..9e86d683 100644 --- a/frontend/src/assets/lang/app/en.json +++ b/frontend/src/assets/lang/app/en.json @@ -245,6 +245,14 @@ "login": { "success": "You have successfully logged in.", "error": "An error occurred while logging in." + }, + "admin": { + "save": { + "error": { + "title": "Invalid save operation", + "detail": "You are trying to save an item without selecting it" + } + } } } }, @@ -287,6 +295,7 @@ "edit": "Edit", "cancel": "Cancel", "save": "Save", + "delete": "Delete", "users": { "title": "Users", "username": "Username", @@ -316,7 +325,9 @@ "private": "Private" }, "none_found": "No matching data.", - "loading": "Loading data. Please wait." + "loading": "Loading data. Please wait.", + "safeGuard": "Are you sure?", + "no_file": "No file found." }, "primevue": { "startsWith": "Starts with", @@ -416,6 +427,7 @@ "emptySelectionMessage": "No selected item", "emptySearchMessage": "No results found", "emptyMessage": "No available options", + "emptyFileSelect": "No file selected", "aria": { "trueLabel": "True", "falseLabel": "False", diff --git a/frontend/src/assets/lang/app/nl.json b/frontend/src/assets/lang/app/nl.json index 7864c171..884cb246 100644 --- a/frontend/src/assets/lang/app/nl.json +++ b/frontend/src/assets/lang/app/nl.json @@ -242,6 +242,14 @@ "login": { "success": "Je bent succesvol ingelogd.", "error": "Er is een fout opgetreden tijdens het inloggen." + }, + "admin": { + "save": { + "error": { + "title": "Ongeldige opsla operatie", + "detail": "U probeert een item op te slaan zonder dit te selecteren" + } + } } } }, @@ -283,6 +291,7 @@ "edit": "Bewerken", "cancel": "Annuleer", "save": "Sla op", + "delete": "Verwijder", "users": { "title": "Gebruikers", "username": "Gebruikersnaam", @@ -312,7 +321,8 @@ "private": "Privaat" }, "none_found": "Geen overeenkomende data gevonden.", - "loading": "Aan het laden. Wacht een momentje aub." + "loading": "Aan het laden. Wacht even aub.", + "safeGuard": "Bent u het zeker?" }, "primevue": { "accept": "Ja", @@ -364,6 +374,7 @@ "emptyMessage": "Geen resultaten gevonden", "emptySearchMessage": "Geen resultaten gevonden", "emptySelectionMessage": "Geen optie geselecteerd", + "emptyFileSelect": "Geen bestand geselecteerd", "endsWith": "Eindigt met", "equals": "Is gelijk aan", "fileSizeTypes": [ diff --git a/frontend/src/components/admin/LazyDataTable.vue b/frontend/src/components/admin/LazyDataTable.vue index 9a1e2a32..dcdfe499 100644 --- a/frontend/src/components/admin/LazyDataTable.vue +++ b/frontend/src/components/admin/LazyDataTable.vue @@ -8,14 +8,27 @@ import { type Filter } from '@/types/filter/Filter.ts'; import Column from 'primevue/column'; /* Properties */ -const props = defineProps<{ - pagination: PaginatorResponse | null; - entities: any[] | null; // list containing all the entities displayed by data table after executing get method - get: () => Promise; // get method for backend - search: (filters: Filter, page: number, pageSize: number) => Promise; - filter: Filter; - onFilter: (callback: () => Promise, debounce?: number | undefined, immediate?: boolean | undefined) => void; -}>(); +const props = withDefaults( + defineProps<{ + pagination: PaginatorResponse | null; + entities: any[] | null; // list containing all the entities displayed by data table after executing get method + get: () => Promise; // get method for backend + search: (filters: Filter, page: number, pageSize: number) => Promise; + filter: Filter; + onFilter: ( + callback: () => Promise, + debounce?: number | undefined, + immediate?: boolean | undefined, + ) => void; + select?: boolean; + }>(), + { + select: false, + }, +); + +/* Emits */ +const emit = defineEmits(['select']); /* Injections */ const { t } = useI18n(); @@ -59,17 +72,21 @@ const onSelectAllChange = (event: DataTableSelectAllChangeEvent): void => { props.get().then(() => { selectAll.value = true; selected.value = props.entities; + emit('select', selected.value); }); } else { selectAll.value = false; selected.value = []; + emit('select', selected.value); } }; const onRowSelect = (): void => { selectAll.value = selected.value?.length === (props.pagination?.count ?? 0); + emit('select', selected.value); }; const onRowUnselect = (): void => { selectAll.value = false; + emit('select', selected.value); }; defineExpose({ fetch }); @@ -109,7 +126,7 @@ defineExpose({ fetch }); {{ t('admin.loading') }} - + diff --git a/frontend/src/components/layout/admin/AdminHeader.vue b/frontend/src/components/layout/admin/AdminHeader.vue index 731accb5..06e38cc3 100644 --- a/frontend/src/components/layout/admin/AdminHeader.vue +++ b/frontend/src/components/layout/admin/AdminHeader.vue @@ -53,7 +53,7 @@ const items = computed(() => [