mirror of
https://github.com/immich-app/immich.git
synced 2025-07-31 15:08:44 -04:00
feat(web): api key permission search (#20248)
This commit is contained in:
parent
da80b69062
commit
153bb70f6e
@ -1,9 +1,9 @@
|
||||
<script lang="ts">
|
||||
import type { SearchOptions } from '$lib/utils/dipatch';
|
||||
import { mdiClose, mdiMagnify } from '@mdi/js';
|
||||
import LoadingSpinner from '../shared-components/loading-spinner.svelte';
|
||||
import { t } from 'svelte-i18n';
|
||||
import { IconButton } from '@immich/ui';
|
||||
import { mdiClose, mdiMagnify } from '@mdi/js';
|
||||
import { t } from 'svelte-i18n';
|
||||
import LoadingSpinner from '../shared-components/loading-spinner.svelte';
|
||||
|
||||
interface Props {
|
||||
name: string;
|
||||
|
@ -31,7 +31,7 @@
|
||||
};
|
||||
</script>
|
||||
|
||||
<div class="mx-4 my-2 border bg-subtle dark:bg-black/30 dark:border-black p-4 rounded-2xl">
|
||||
<div class="border bg-subtle dark:bg-black/30 dark:border-black p-4 rounded-2xl">
|
||||
<div class="flex items-center gap-2">
|
||||
<Checkbox
|
||||
id="permission-{title}"
|
||||
|
@ -5,11 +5,17 @@
|
||||
} from '$lib/components/shared-components/notification/notification';
|
||||
import ApiKeyGrid from '$lib/components/user-settings-page/user-api-key-grid.svelte';
|
||||
import { Permission } from '@immich/sdk';
|
||||
import { Button, Checkbox, HStack, Label, Modal, ModalBody, ModalFooter } from '@immich/ui';
|
||||
import { mdiKeyVariant } from '@mdi/js';
|
||||
import { Button, Checkbox, Field, HStack, IconButton, Input, Label, Modal, ModalBody, ModalFooter } from '@immich/ui';
|
||||
import { mdiClose, mdiKeyVariant } from '@mdi/js';
|
||||
import { onMount } from 'svelte';
|
||||
import { t } from 'svelte-i18n';
|
||||
import { SvelteMap } from 'svelte/reactivity';
|
||||
|
||||
const matches = (value: string) => {
|
||||
value = value.toLowerCase();
|
||||
return ([title, items]: [string, Permission[]]) => {
|
||||
return title.toLowerCase().includes(value) || items.some((item) => item.toLowerCase().includes(value));
|
||||
};
|
||||
};
|
||||
|
||||
interface Props {
|
||||
apiKey: { name: string; permissions: Permission[] };
|
||||
@ -20,137 +26,26 @@
|
||||
}
|
||||
|
||||
let { apiKey = $bindable(), title, cancelText = $t('cancel'), submitText = $t('save'), onClose }: Props = $props();
|
||||
let name = $derived(apiKey.name);
|
||||
|
||||
let selectedItems: Permission[] = $state(apiKey.permissions);
|
||||
let selectAllItems = $derived(selectedItems.length === Object.keys(Permission).length - 1);
|
||||
|
||||
const permissions: Map<string, Permission[]> = new SvelteMap();
|
||||
const permissions: Record<string, Permission[]> = {};
|
||||
for (const permission of Object.values(Permission)) {
|
||||
if (permission === Permission.All) {
|
||||
continue;
|
||||
}
|
||||
|
||||
permissions.set('activity', [
|
||||
Permission.ActivityCreate,
|
||||
Permission.ActivityRead,
|
||||
Permission.ActivityUpdate,
|
||||
Permission.ActivityDelete,
|
||||
Permission.ActivityStatistics,
|
||||
]);
|
||||
const [group] = permission.split('.');
|
||||
if (!permissions[group]) {
|
||||
permissions[group] = [];
|
||||
}
|
||||
permissions[group].push(permission);
|
||||
}
|
||||
|
||||
permissions.set('api_key', [
|
||||
Permission.ApiKeyCreate,
|
||||
Permission.ApiKeyRead,
|
||||
Permission.ApiKeyUpdate,
|
||||
Permission.ApiKeyDelete,
|
||||
]);
|
||||
|
||||
permissions.set('asset', [
|
||||
Permission.AssetRead,
|
||||
Permission.AssetUpdate,
|
||||
Permission.AssetDelete,
|
||||
Permission.AssetShare,
|
||||
Permission.AssetView,
|
||||
Permission.AssetDownload,
|
||||
Permission.AssetUpload,
|
||||
]);
|
||||
|
||||
permissions.set('album', [
|
||||
Permission.AlbumCreate,
|
||||
Permission.AlbumRead,
|
||||
Permission.AlbumUpdate,
|
||||
Permission.AlbumDelete,
|
||||
Permission.AlbumStatistics,
|
||||
|
||||
Permission.AlbumAddAsset,
|
||||
Permission.AlbumRemoveAsset,
|
||||
Permission.AlbumShare,
|
||||
Permission.AlbumDownload,
|
||||
]);
|
||||
|
||||
permissions.set('auth_device', [Permission.AuthDeviceDelete]);
|
||||
|
||||
permissions.set('archive', [Permission.ArchiveRead]);
|
||||
|
||||
permissions.set('face', [Permission.FaceCreate, Permission.FaceRead, Permission.FaceUpdate, Permission.FaceDelete]);
|
||||
|
||||
permissions.set('library', [
|
||||
Permission.LibraryCreate,
|
||||
Permission.LibraryRead,
|
||||
Permission.LibraryUpdate,
|
||||
Permission.LibraryDelete,
|
||||
Permission.LibraryStatistics,
|
||||
]);
|
||||
|
||||
permissions.set('timeline', [Permission.TimelineRead, Permission.TimelineDownload]);
|
||||
|
||||
permissions.set('memory', [
|
||||
Permission.MemoryCreate,
|
||||
Permission.MemoryRead,
|
||||
Permission.MemoryUpdate,
|
||||
Permission.MemoryDelete,
|
||||
]);
|
||||
|
||||
permissions.set('notification', [
|
||||
Permission.NotificationCreate,
|
||||
Permission.NotificationRead,
|
||||
Permission.NotificationUpdate,
|
||||
Permission.NotificationDelete,
|
||||
]);
|
||||
|
||||
permissions.set('partner', [
|
||||
Permission.PartnerCreate,
|
||||
Permission.PartnerRead,
|
||||
Permission.PartnerUpdate,
|
||||
Permission.PartnerDelete,
|
||||
]);
|
||||
|
||||
permissions.set('person', [
|
||||
Permission.PersonCreate,
|
||||
Permission.PersonRead,
|
||||
Permission.PersonUpdate,
|
||||
Permission.PersonDelete,
|
||||
Permission.PersonStatistics,
|
||||
Permission.PersonMerge,
|
||||
Permission.PersonReassign,
|
||||
]);
|
||||
|
||||
permissions.set('session', [
|
||||
Permission.SessionCreate,
|
||||
Permission.SessionRead,
|
||||
Permission.SessionUpdate,
|
||||
Permission.SessionDelete,
|
||||
Permission.SessionLock,
|
||||
]);
|
||||
|
||||
permissions.set('sharedLink', [
|
||||
Permission.SharedLinkCreate,
|
||||
Permission.SharedLinkRead,
|
||||
Permission.SharedLinkUpdate,
|
||||
Permission.SharedLinkDelete,
|
||||
]);
|
||||
|
||||
permissions.set('stack', [
|
||||
Permission.StackCreate,
|
||||
Permission.StackRead,
|
||||
Permission.StackUpdate,
|
||||
Permission.StackDelete,
|
||||
]);
|
||||
|
||||
permissions.set('systemConfig', [Permission.SystemConfigRead, Permission.SystemConfigUpdate]);
|
||||
|
||||
permissions.set('systemMetadata', [Permission.SystemMetadataRead, Permission.SystemMetadataUpdate]);
|
||||
|
||||
permissions.set('tag', [
|
||||
Permission.TagCreate,
|
||||
Permission.TagRead,
|
||||
Permission.TagUpdate,
|
||||
Permission.TagDelete,
|
||||
Permission.TagAsset,
|
||||
]);
|
||||
|
||||
permissions.set('adminUser', [
|
||||
Permission.AdminUserCreate,
|
||||
Permission.AdminUserRead,
|
||||
Permission.AdminUserUpdate,
|
||||
Permission.AdminUserDelete,
|
||||
]);
|
||||
let searchValue = $state('');
|
||||
let filteredResults = $derived(Object.entries(permissions).filter(matches(searchValue)));
|
||||
|
||||
const handleSelectItems = (permissions: Permission[]) => {
|
||||
selectedItems = Array.from(new Set([...selectedItems, ...permissions]));
|
||||
@ -177,9 +72,9 @@
|
||||
});
|
||||
} else {
|
||||
if (selectAllItems) {
|
||||
onClose({ name: apiKey.name, permissions: [Permission.All] });
|
||||
onClose({ name, permissions: [Permission.All] });
|
||||
} else {
|
||||
onClose({ name: apiKey.name, permissions: selectedItems });
|
||||
onClose({ name, permissions: selectedItems });
|
||||
}
|
||||
}
|
||||
};
|
||||
@ -200,22 +95,37 @@
|
||||
<ModalBody>
|
||||
<form {onsubmit} autocomplete="off" id="api-key-form">
|
||||
<div class="mb-4 flex flex-col gap-2">
|
||||
<label class="immich-form-label" for="name">{$t('name')}</label>
|
||||
<input class="immich-form-input" id="name" name="name" type="text" bind:value={apiKey.name} />
|
||||
<Field label={$t('name')}>
|
||||
<Input bind:value={name} />
|
||||
</Field>
|
||||
</div>
|
||||
<label class="immich-form-label" for="permission">{$t('permission')}</label>
|
||||
<div class="flex items-center gap-2 m-4" id="permission">
|
||||
<Checkbox
|
||||
id="select-all-permissions"
|
||||
size="tiny"
|
||||
checked={selectAllItems}
|
||||
onCheckedChange={handleSelectAllItems}
|
||||
/>
|
||||
<Label label={$t('select_all')} for="select-all-permissions" />
|
||||
<Label label={$t('permission')} for="permission-container" />
|
||||
<div class="flex items-center gap-2 m-4" id="permission-container">
|
||||
<Checkbox id="input-select-all" size="tiny" checked={selectAllItems} onCheckedChange={handleSelectAllItems} />
|
||||
<Label label={$t('select_all')} for="input-select-all" />
|
||||
</div>
|
||||
|
||||
<div class="ms-4 flex flex-col gap-2">
|
||||
<Input bind:value={searchValue} placeholder={$t('search')}>
|
||||
{#snippet trailingIcon()}
|
||||
{#if searchValue}
|
||||
<IconButton
|
||||
icon={mdiClose}
|
||||
size="small"
|
||||
variant="ghost"
|
||||
shape="round"
|
||||
color="secondary"
|
||||
class="me-1"
|
||||
onclick={() => (searchValue = '')}
|
||||
aria-label={$t('clear')}
|
||||
/>
|
||||
{/if}
|
||||
{/snippet}
|
||||
</Input>
|
||||
{#each filteredResults as [title, subItems] (title)}
|
||||
<ApiKeyGrid {title} {subItems} {selectedItems} {handleSelectItems} {handleDeselectItems} />
|
||||
{/each}
|
||||
</div>
|
||||
{#each permissions as [title, subItems] (title)}
|
||||
<ApiKeyGrid {title} {subItems} {selectedItems} {handleSelectItems} {handleDeselectItems} />
|
||||
{/each}
|
||||
</form>
|
||||
</ModalBody>
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user