diff --git a/src/client/index.ts b/src/client/index.ts index 9f41f3e7ede88b28944d1b09ce2271ac74f81662..39654c3a51fa08ded67ea208344e53329322644c 100644 --- a/src/client/index.ts +++ b/src/client/index.ts @@ -8,7 +8,8 @@ export type { OpenAPIConfig } from './core/OpenAPI'; export type { BucketIn } from './models/BucketIn'; export type { BucketOut } from './models/BucketOut'; -export type { BucketPermission } from './models/BucketPermission'; +export type { BucketPermissionIn } from './models/BucketPermissionIn'; +export type { BucketPermissionOut } from './models/BucketPermissionOut'; export type { BucketPermissionParameters } from './models/BucketPermissionParameters'; export type { ErrorDetail } from './models/ErrorDetail'; export type { HTTPValidationError } from './models/HTTPValidationError'; diff --git a/src/client/models/BucketPermission.ts b/src/client/models/BucketPermissionIn.ts similarity index 86% rename from src/client/models/BucketPermission.ts rename to src/client/models/BucketPermissionIn.ts index 32d7d000b828a75b2d788852c027ce5e81ba704b..eeedae9e518ba46543d75e50456a95ba2393c158 100644 --- a/src/client/models/BucketPermission.ts +++ b/src/client/models/BucketPermissionIn.ts @@ -5,9 +5,9 @@ import type { PermissionEnum } from './PermissionEnum'; /** - * Schema for the bucket permissions. + * Schema for the parameters of a bucket permission. */ -export type BucketPermission = { +export type BucketPermissionIn = { /** * Start date of permission */ diff --git a/src/client/models/BucketPermissionOut.ts b/src/client/models/BucketPermissionOut.ts new file mode 100644 index 0000000000000000000000000000000000000000..484f22fba902b94c6323fa90ca6db10e5dec31da --- /dev/null +++ b/src/client/models/BucketPermissionOut.ts @@ -0,0 +1,40 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +import type { PermissionEnum } from './PermissionEnum'; + +/** + * Schema for the bucket permissions. + */ +export type BucketPermissionOut = { + /** + * Start date of permission + */ + from_timestamp?: string; + /** + * End date of permission + */ + to_timestamp?: string; + /** + * Prefix of subfolder + */ + file_prefix?: string; + /** + * Permission + */ + permission?: (PermissionEnum | string); + /** + * UID of the grantee + */ + uid: string; + /** + * Name of Bucket + */ + bucket_name: string; + /** + * Display Name of the grantee + */ + grantee_display_name: string; +}; + diff --git a/src/client/services/BucketPermissionsService.ts b/src/client/services/BucketPermissionsService.ts index 5fbf147d725a4d4d68d19dd5bc8fed0c2b3c4e43..1450d0ece96e706f5fc3a7d21b39e69a6152a24d 100644 --- a/src/client/services/BucketPermissionsService.ts +++ b/src/client/services/BucketPermissionsService.ts @@ -1,7 +1,8 @@ /* istanbul ignore file */ /* tslint:disable */ /* eslint-disable */ -import type { BucketPermission } from '../models/BucketPermission'; +import type { BucketPermissionIn } from '../models/BucketPermissionIn'; +import type { BucketPermissionOut } from '../models/BucketPermissionOut'; import type { BucketPermissionParameters } from '../models/BucketPermissionParameters'; import type { CancelablePromise } from '../core/CancelablePromise'; @@ -18,13 +19,13 @@ export class BucketPermissionsService { * * @param bucketName Name of bucket * @param uid UID of a user - * @returns BucketPermission Successful Response + * @returns BucketPermissionOut Successful Response * @throws ApiError */ public static bucketPermissionsGetPermissionForBucket( bucketName: string, uid: string, - ): CancelablePromise<BucketPermission> { + ): CancelablePromise<BucketPermissionOut> { return __request(OpenAPI, { method: 'GET', url: '/permissions/bucket/{bucket_name}/user/{uid}', @@ -48,14 +49,14 @@ export class BucketPermissionsService { * @param uid UID of a user * @param bucketName Name of bucket * @param requestBody - * @returns BucketPermission Successful Response + * @returns BucketPermissionOut Successful Response * @throws ApiError */ public static bucketPermissionsUpdatePermission( uid: string, bucketName: string, requestBody: BucketPermissionParameters, - ): CancelablePromise<BucketPermission> { + ): CancelablePromise<BucketPermissionOut> { return __request(OpenAPI, { method: 'PUT', url: '/permissions/bucket/{bucket_name}/user/{uid}', @@ -110,12 +111,12 @@ export class BucketPermissionsService { * List all the bucket permissions for the given bucket. * * @param bucketName Name of bucket - * @returns BucketPermission Successful Response + * @returns BucketPermissionOut Successful Response * @throws ApiError */ public static bucketPermissionsListPermissionsPerBucket( bucketName: string, - ): CancelablePromise<Array<BucketPermission>> { + ): CancelablePromise<Array<BucketPermissionOut>> { return __request(OpenAPI, { method: 'GET', url: '/permissions/bucket/{bucket_name}', @@ -136,12 +137,12 @@ export class BucketPermissionsService { * List all the bucket permissions for the given user. * * @param uid UID of a user - * @returns BucketPermission Successful Response + * @returns BucketPermissionOut Successful Response * @throws ApiError */ public static bucketPermissionsListPermissionsPerUser( uid: string, - ): CancelablePromise<Array<BucketPermission>> { + ): CancelablePromise<Array<BucketPermissionOut>> { return __request(OpenAPI, { method: 'GET', url: '/permissions/user/{uid}', @@ -162,12 +163,12 @@ export class BucketPermissionsService { * Create a permission for a bucket and user. * * @param requestBody - * @returns BucketPermission Successful Response + * @returns BucketPermissionOut Successful Response * @throws ApiError */ public static bucketPermissionsCreatePermission( - requestBody: BucketPermission, - ): CancelablePromise<BucketPermission> { + requestBody: BucketPermissionIn, + ): CancelablePromise<BucketPermissionOut> { return __request(OpenAPI, { method: 'POST', url: '/permissions/', diff --git a/src/client/services/UserService.ts b/src/client/services/UserService.ts index 8a1467587f2d38417615706cfcfbe9039695900b..a30dde25b66c58ddfce2fbe002865411a38ac594 100644 --- a/src/client/services/UserService.ts +++ b/src/client/services/UserService.ts @@ -29,6 +29,32 @@ export class UserService { }); } + /** + * Search for users by their name + * Return the users that have a specific substring in their name. + * + * @param nameLike + * @returns User Successful Response + * @throws ApiError + */ + public static userSearchUsers( + nameLike: string, + ): CancelablePromise<Array<User>> { + return __request(OpenAPI, { + method: 'GET', + url: '/users/', + query: { + 'name_like': nameLike, + }, + errors: { + 400: `Error decoding JWT Token`, + 403: `Not authenticated`, + 404: `Entity not Found`, + 422: `Validation Error`, + }, + }); + } + /** * Get a user by its uid * Return the user with the specific uid. A user can only view himself. diff --git a/src/components/BucketListItem.vue b/src/components/BucketListItem.vue index 0cd7cf6874949dfe1034f992344d6a9c719daa2a..805b1cabdcbcc8c563536d0397e8770351a4c8dc 100644 --- a/src/components/BucketListItem.vue +++ b/src/components/BucketListItem.vue @@ -1,5 +1,5 @@ <script setup lang="ts"> -import type { BucketOut, BucketPermission } from "@/client"; +import type { BucketOut, BucketPermissionOut } from "@/client"; import BootstrapIcon from "@/components/BootstrapIcon.vue"; import PermissionModal from "@/components/Modals/PermissionModal.vue"; import dayjs from "dayjs"; @@ -11,7 +11,7 @@ const props = defineProps<{ active: boolean; bucket: BucketOut; loading: boolean; - permission: BucketPermission | undefined; + permission: BucketPermissionOut | undefined; }>(); const randomIDSuffix = Math.random().toString(16).substr(2, 8); @@ -32,7 +32,6 @@ onMounted(() => { <permission-modal v-if="props.permission != null && props.active" :modalID="'view-permission-modal' + randomIDSuffix" - modal-label="view-permission-modal-label" :bucket-name="props.bucket.name" :sub-folders="{ subFolders: {}, files: [] }" :edit-user-permission="props.permission" diff --git a/src/components/BucketView.vue b/src/components/BucketView.vue index 69aeaa30b7d6f5c5de725638cb97eb271cc6522a..0e1813ad07450c65e0d29fd37c4f0ace516ee61a 100644 --- a/src/components/BucketView.vue +++ b/src/components/BucketView.vue @@ -3,7 +3,7 @@ import { onMounted, reactive, watch, computed } from "vue"; import type { ComputedRef } from "vue"; import type { S3ObjectMetaInformation, - BucketPermission, + BucketPermissionOut, BucketOut, } from "@/client"; import { ObjectService } from "@/client"; @@ -86,7 +86,7 @@ authStore.$onAction(({ name, args }) => { const props = defineProps<{ bucketName: string; subFolders: string[] | string; - permission: BucketPermission | undefined; + permission: BucketPermissionOut | undefined; writableBuckets: BucketOut[]; }>(); const randomIDSuffix = Math.random().toString(16).substr(2, 8); @@ -153,7 +153,7 @@ const objectState = reactive({ filterString: string; bucketNotFoundError: boolean; bucketPermissionError: boolean; - createdPermission: undefined | BucketPermission; + createdPermission: undefined | BucketPermissionOut; editObjectKey: string; copyObject: S3ObjectMetaInformation; viewDetailObject: S3ObjectMetaInformation; @@ -541,7 +541,6 @@ watch( </div> <DeleteModal modalID="delete-object-modal" - modal-label="some-label" :object-name-delete="deleteObjectsState.potentialObjectToDelete" :back-modal-id="undefined" @confirm-delete=" @@ -620,7 +619,6 @@ watch( :bucket-name="props.bucketName" :s3-client="client" modalID="upload-object-modal" - modal-label="some-label" :key-prefix="currentSubFolders.join('/')" :edit-object-file-name="undefined" @object-created="objectUploaded" @@ -642,7 +640,6 @@ watch( :bucket-name="props.bucketName" :s3-client="client" modalID="create-folder-modal" - modal-label="some-label" :key-prefix="currentSubFolders.join('/')" @folder-created="objectUploaded" /> @@ -665,7 +662,6 @@ watch( </button> <permission-modal modalID="create-permission-modal" - modal-label="create-permission-modal-label" :bucket-name="props.bucketName" :sub-folders="folderStructure" :edit-user-permission="undefined" @@ -897,7 +893,6 @@ watch( :bucket-name="props.bucketName" :s3-client="client" modalID="edit-object-modal" - modal-label="some-label" :key-prefix="currentSubFolders.join('/')" :edit-object-file-name="getObjectFileName(objectState.editObjectKey)" @object-created="objectUploaded" @@ -906,14 +901,12 @@ watch( :source-object="objectState.copyObject" :s3-client="client" modalID="copy-object-modal" - modal-label="some-label" :available-buckets="props.writableBuckets" @object-copied="objectCopied" /> <object-detail-modal :s3-object="objectState.viewDetailObject" modalID="detail-object-modal" - modal-label="some-label" /> </div> </div> diff --git a/src/components/Modals/CopyObjectModal.vue b/src/components/Modals/CopyObjectModal.vue index f239951573e4132034b6c2409b37fb355dc20bdf..b4c72a29e865a259515a5b2584cc6189217d4f55 100644 --- a/src/components/Modals/CopyObjectModal.vue +++ b/src/components/Modals/CopyObjectModal.vue @@ -10,7 +10,6 @@ import dayjs from "dayjs"; const props = defineProps<{ modalID: string; - modalLabel: string; sourceObject: S3ObjectMetaInformation; s3Client: S3Client; availableBuckets: BucketOut[]; @@ -137,7 +136,7 @@ onMounted(() => { <bootstrap-modal :modalID="modalID" :static-backdrop="true" - :modal-label="modalLabel" + modal-label="Copy Object Modal" v-on="{ 'hidden.bs.modal': modalClosed }" > <template v-slot:header> diff --git a/src/components/Modals/CreateBucketModal.vue b/src/components/Modals/CreateBucketModal.vue index 1a7dee6f5cdd6cc890d07f442521ed1210e962c4..e173a4b39e6b9ac43c2dabc368c4aaf14c9270c0 100644 --- a/src/components/Modals/CreateBucketModal.vue +++ b/src/components/Modals/CreateBucketModal.vue @@ -21,7 +21,6 @@ const formState = reactive({ const props = defineProps<{ modalID: string; - modalLabel: string; }>(); let createBucketModal: Modal | null = null; @@ -74,7 +73,7 @@ function modalClosed() { <bootstrap-modal :modalID="modalID" :static-backdrop="true" - :modal-label="modalLabel" + modal-label="Create Bucket Modal" v-on="{ 'hidden.bs.modal': modalClosed }" > <template v-slot:header> Create new Bucket </template> diff --git a/src/components/Modals/CreateFolderModal.vue b/src/components/Modals/CreateFolderModal.vue index bc9b2f8e8f459c7690b1e86c7fddd7c0be81d5fe..bf473259f5f3865b27ddc3064852a71e09e6f8eb 100644 --- a/src/components/Modals/CreateFolderModal.vue +++ b/src/components/Modals/CreateFolderModal.vue @@ -10,7 +10,6 @@ import { Modal, Toast } from "bootstrap"; const props = defineProps<{ modalID: string; - modalLabel: string; bucketName: string; keyPrefix: string; s3Client: S3Client; @@ -126,7 +125,7 @@ onMounted(() => { <bootstrap-modal :modalID="modalID" :static-backdrop="true" - :modal-label="modalLabel" + modal-label="Create Folder Modal" > <template v-slot:header> <h4>Create folder in</h4> diff --git a/src/components/Modals/DeleteModal.vue b/src/components/Modals/DeleteModal.vue index 108993d4bf31346d29101e953c26fbbd38ac01ad..00dbff6fa32c8691fd18371dc532e61479b3793c 100644 --- a/src/components/Modals/DeleteModal.vue +++ b/src/components/Modals/DeleteModal.vue @@ -6,7 +6,6 @@ import BootstrapModal from "@/components/Modals/BootstrapModal.vue"; const props = defineProps<{ modalID: string; - modalLabel: string; objectNameDelete: string; backModalId: string | undefined; }>(); @@ -38,7 +37,7 @@ onMounted(() => { <bootstrap-modal :modalID="props.modalID" :static-backdrop="true" - :modal-label="props.modalLabel" + modal-label="Confirm Delete Modal" v-on="{ 'hidden.bs.modal': modalClosed }" > <template v-slot:header> Delete {{ props.objectNameDelete }}</template> diff --git a/src/components/Modals/ObjectDetailModal.vue b/src/components/Modals/ObjectDetailModal.vue index 86699619802ed5382fb2bd4392449c221c90573a..733a4653972a9dbacfbace89601d301860ad0e45 100644 --- a/src/components/Modals/ObjectDetailModal.vue +++ b/src/components/Modals/ObjectDetailModal.vue @@ -6,7 +6,6 @@ import { filesize } from "filesize"; const props = defineProps<{ modalID: string; - modalLabel: string; s3Object: S3ObjectMetaInformation; }>(); </script> @@ -15,7 +14,7 @@ const props = defineProps<{ <bootstrap-modal :modalID="modalID" :static-backdrop="false" - :modal-label="modalLabel" + modal-label="Object Detail Modal" > <template v-slot:header> <h4>Object Details</h4> diff --git a/src/components/Modals/PermissionListModal.vue b/src/components/Modals/PermissionListModal.vue index 97eb195897e57bd0c39fa96845f4def7e2efec85..42bdcd9b58a2e79ee9f76024ce9c11f995912473 100644 --- a/src/components/Modals/PermissionListModal.vue +++ b/src/components/Modals/PermissionListModal.vue @@ -1,5 +1,9 @@ <script setup lang="ts"> -import type { BucketPermission, S3ObjectMetaInformation } from "@/client"; +import type { + BucketPermissionIn, + BucketPermissionOut, + S3ObjectMetaInformation, +} from "@/client"; import { reactive } from "vue"; import { BucketPermissionsService } from "@/client"; import { onBeforeMount, watch } from "vue"; @@ -23,7 +27,7 @@ const props = defineProps<{ bucketName: string; subFolders: FolderTree; modalID: string; - addPermission: undefined | BucketPermission; + addPermission: undefined | BucketPermissionOut; }>(); // Reactive State @@ -35,11 +39,12 @@ const state = reactive({ bucket_name: "bucketname", uid: "uid", permission: "READ", + grantee_display_name: "display_name", }, } as { - permissions: BucketPermission[]; + permissions: BucketPermissionOut[]; loading: boolean; - currentPermission: BucketPermission; + currentPermission: BucketPermissionOut; }); const randomIDSuffix = Math.random().toString(16).substr(2, 8); @@ -78,17 +83,17 @@ function updateBucketPermissions(bucketName: string) { }); } -function permissionDeleted(bucketPermission: BucketPermission) { +function permissionDeleted(bucketPermission: BucketPermissionIn) { state.permissions = state.permissions.filter( (perm) => perm.uid != bucketPermission.uid ); } -function permissionCreated(bucketPermission: BucketPermission) { +function permissionCreated(bucketPermission: BucketPermissionOut) { state.permissions.push(bucketPermission); } -function permissionEdited(bucketPermission: BucketPermission) { +function permissionEdited(bucketPermission: BucketPermissionOut) { const index = state.permissions.findIndex( (perm) => perm.uid == bucketPermission.uid ); @@ -112,7 +117,6 @@ onBeforeMount(() => { :sub-folders="props.subFolders" :back-modal-id="props.modalID" :modalID="'permission-list-edit-modal' + randomIDSuffix" - modal-label="permission-list-edit" @permission-deleted="permissionDeleted" @permission-created="permissionCreated" @permission-edited="permissionEdited" @@ -120,7 +124,7 @@ onBeforeMount(() => { <bootstrap-modal :modalID="props.modalID" :static-backdrop="true" - modal-label="permission-list" + modal-label="Permission List Modal" > <template v-slot:header> Bucket Permissions </template> <template v-slot:body> @@ -145,8 +149,10 @@ onBeforeMount(() => { data-bs-toggle="modal" :data-bs-target="'#permission-list-edit-modal' + randomIDSuffix" > - <span class="text-info">{{ permission.permission }}</span> - {{ permission.uid }} + <div class="row"> + <span class="text-info col-2">{{ permission.permission }}</span> + <span class="col-9"> {{ permission.grantee_display_name }}</span> + </div> </button> </div> <div v-else> diff --git a/src/components/Modals/PermissionModal.vue b/src/components/Modals/PermissionModal.vue index a76048e39ef8cbd45e3e62820fbc53a41b474482..305469ec506076b84eeb1c759bf6ad66706be8ea 100644 --- a/src/components/Modals/PermissionModal.vue +++ b/src/components/Modals/PermissionModal.vue @@ -2,12 +2,15 @@ import { onMounted, reactive, watch, ref, computed } from "vue"; import BootstrapModal from "@/components/Modals/BootstrapModal.vue"; import DeleteModal from "@/components/Modals/DeleteModal.vue"; +import SearchUserModal from "@/components/Modals/SearchUserModal.vue"; import { Modal } from "bootstrap"; import dayjs from "dayjs"; import type { - BucketPermission, + BucketPermissionOut, + BucketPermissionIn, S3ObjectMetaInformation, BucketPermissionParameters, + User, } from "@/client"; import type { ComputedRef, Ref } from "vue"; import { PermissionEnum, BucketPermissionsService } from "@/client"; @@ -30,10 +33,9 @@ type FolderTree = { // ----------------------------------------------------------------------------- const props = defineProps<{ modalID: string; - modalLabel: string; bucketName: string; subFolders: FolderTree; - editUserPermission: BucketPermission | undefined; + editUserPermission: BucketPermissionOut | undefined; readonly: boolean; editable: boolean; deletable: boolean; @@ -50,10 +52,12 @@ let successToast: Toast | null = null; // ----------------------------------------------------------------------------- const formState = reactive({ loading: false, + grantee_name: "", error: false, readonly: props.readonly, } as { loading: boolean; + grantee_name: string; error: boolean; readonly: boolean; }); @@ -65,7 +69,7 @@ const permission = reactive({ permission: undefined, uid: "", bucket_name: props.bucketName, -} as BucketPermission); +} as BucketPermissionIn); const permissionDeleted: Ref<boolean> = ref(false); @@ -101,9 +105,9 @@ watch( // Events // ----------------------------------------------------------------------------- const emit = defineEmits<{ - (e: "permission-deleted", permission: BucketPermission): void; - (e: "permission-created", permission: BucketPermission): void; - (e: "permission-edited", permission: BucketPermission): void; + (e: "permission-deleted", permission: BucketPermissionIn): void; + (e: "permission-created", permission: BucketPermissionOut): void; + (e: "permission-edited", permission: BucketPermissionOut): void; }>(); // Functions @@ -142,6 +146,7 @@ function updatePermission() { permission.bucket_name = props.editUserPermission.bucket_name; permission.file_prefix = props.editUserPermission.file_prefix; permission.uid = props.editUserPermission.uid; + formState.grantee_name = props.editUserPermission.grantee_display_name; permission.from_timestamp = props.editUserPermission.from_timestamp != null ? dayjs(props.editUserPermission.from_timestamp).format("YYYY-MM-DD") @@ -157,6 +162,7 @@ function updatePermission() { permission.from_timestamp = undefined; permission.to_timestamp = undefined; permission.permission = undefined; + formState.grantee_name = ""; } } @@ -196,7 +202,7 @@ function formSubmit() { "permissionCreateEditForm" + randomIDSuffix )! as HTMLFormElement; if (form.checkValidity()) { - const tempPermission: BucketPermission = permission; + const tempPermission: BucketPermissionIn = permission; if (permission.from_timestamp != null) { tempPermission.from_timestamp = permission.from_timestamp.length > 0 @@ -271,6 +277,10 @@ function confirmedDeletePermission(bucketName: string, uid: string) { } } +function updateUser(user: User) { + permission.uid = user.uid; + formState.grantee_name = user.display_name; +} // Lifecycle Hooks // ----------------------------------------------------------------------------- onMounted(() => { @@ -283,13 +293,17 @@ onMounted(() => { <template> <DeleteModal :modalID="'delete-permission-modal' + randomIDSuffix" - modal-label="some-label" object-name-delete="permission" :back-modal-id="modalID" @confirm-delete=" confirmedDeletePermission(permission.bucket_name, permission.uid) " /> + <SearchUserModal + :modalID="'search-user-modal' + randomIDSuffix" + :back-modal-id="modalID" + @user-found="updateUser" + /> <div class="toast-container position-fixed top-toast end-0 p-3"> <div role="alert" @@ -320,7 +334,7 @@ onMounted(() => { <bootstrap-modal :modalID="modalID" :static-backdrop="true" - :modal-label="modalLabel" + modal-label="Permission Modal" v-on="{ 'hidden.bs.modal': modalClosed }" > <template v-slot:header v-if="formState.readonly" @@ -372,26 +386,41 @@ onMounted(() => { /> </div> </div> - <div class="mb-3 row"> + <div class="mb-3 row align-items-center d-flex"> <label for="permissionGranteeInput" class="col-2 col-form-label"> User<span v-if="!formState.readonly">*</span> </label> - <div class="col-10"> + <div + :class="{ + 'col-10': permissionUserReadonly, + 'col-9': !permissionUserReadonly, + }" + > <input type="text" - :class="{ - 'form-control-plaintext': permissionUserReadonly, - 'form-control': !permissionUserReadonly, - }" + class="form-control" id="permissionGranteeInput" required - minlength="3" - maxlength="64" - placeholder="Grantee of the permission" - v-model.trim="permission.uid" - :readonly="permissionUserReadonly" + placeholder="Search for a user" + v-model.trim="formState.grantee_name" + readonly /> </div> + <div v-if="!permissionUserReadonly" class="col-1"> + <button + type="button" + class="btn btn-secondary btn-sm float-end" + data-bs-toggle="modal" + :data-bs-target="'#search-user-modal' + randomIDSuffix" + > + <bootstrap-icon + icon="search" + :height="13" + :width="13" + fill="white" + /> + </button> + </div> </div> <div class="mb-3 row"> <label for="permissionTypeInput" class="col-2 col-form-label"> @@ -455,7 +484,7 @@ onMounted(() => { </div> </div> <div - class="mb-3 row" + class="mb-3 row align-items-center d-flex" v-if=" inputVisible(permission.file_prefix) && possibleSubFolders.length > 0 @@ -464,7 +493,12 @@ onMounted(() => { <label for="permissionSubFolderInput" class="col-2 col-form-label"> Subfolder </label> - <div class="col-10"> + <div + :class="{ + 'col-10': formState.readonly, + 'col-9': !formState.readonly, + }" + > <select class="form-select" id="permissionSubFolderInput" @@ -481,6 +515,21 @@ onMounted(() => { </option> </select> </div> + <div class="col-1" v-if="!formState.readonly"> + <button + type="button" + class="btn btn-outline-danger btn-sm float-end" + @click="permission.file_prefix = undefined" + :disabled="permission.file_prefix === undefined" + > + <bootstrap-icon + icon="x-lg" + :height="14" + :width="14" + fill="currentColor" + /> + </button> + </div> </div> </form> <span class="text-danger" v-if="formState.error" diff --git a/src/components/Modals/SearchUserModal.vue b/src/components/Modals/SearchUserModal.vue new file mode 100644 index 0000000000000000000000000000000000000000..14b5db0b4f62d17e985a32d145e13c1d8e6069b6 --- /dev/null +++ b/src/components/Modals/SearchUserModal.vue @@ -0,0 +1,162 @@ +<script setup lang="ts"> +import { reactive, watch } from "vue"; +import BootstrapModal from "@/components/Modals/BootstrapModal.vue"; +import BootstrapIcon from "@/components/BootstrapIcon.vue"; +import { UserService } from "@/client"; +import type { User } from "@/client"; +import { useAuthStore } from "@/stores/auth"; + +const props = defineProps<{ + modalID: string; + backModalId: string | undefined; +}>(); +const randomIDSuffix = Math.random().toString(16).substr(2, 8); + +const store = useAuthStore(); + +const formState = reactive({ + searchString: "", + potentialUsers: [], + lastSearchTimerId: null, + error: false, + loading: false, +} as { + searchString: string; + potentialUsers: User[]; + lastSearchTimerId: ReturnType<typeof setTimeout> | null; + error: boolean; + loading: boolean; +}); + +watch( + () => formState.searchString, + (newName) => { + if (formState.lastSearchTimerId !== null) { + clearTimeout(formState.lastSearchTimerId); + } + if (newName.length > 2) { + formState.loading = true; + formState.lastSearchTimerId = setTimeout(searchUser, 500, newName); + } else { + formState.loading = false; + formState.potentialUsers = []; + } + } +); + +const emit = defineEmits<{ + (e: "user-found", user: User): void; +}>(); + +function modalClosed() { + formState.searchString = ""; + formState.potentialUsers = []; +} + +function searchUser(name: string) { + formState.error = false; + UserService.userSearchUsers(name) + .then((userSuggestions) => { + formState.potentialUsers = userSuggestions.filter( + (user) => store.user?.uid != user.uid + ); + }) + .catch((err) => { + formState.error = true; + console.error(err); + }) + .finally(() => { + formState.loading = false; + }); +} +</script> + +<template> + <bootstrap-modal + :modalID="props.modalID" + :static-backdrop="true" + modal-label="Search User Modal" + v-on="{ 'hidden.bs.modal': modalClosed }" + > + <template v-slot:header>Search User</template> + <template v-slot:body> + <div class="input-group mt-2 mb-4"> + <span class="input-group-text" id="objects-search-wrapping" + ><bootstrap-icon icon="search" :width="16" :height="16" + /></span> + <input + class="form-control" + :id="'searchUserInput' + randomIDSuffix" + placeholder="Search for a user" + v-model.trim="formState.searchString" + /> + </div> + <div v-if="formState.loading" class="text-center"> + <div class="spinner-border"> + <span class="visually-hidden">Loading...</span> + </div> + </div> + <div v-else-if="formState.error" class="text-center fs-2"> + <bootstrap-icon + icon="x-lg" + class="mb-2" + :width="56" + :height="56" + style="color: var(--bs-danger)" + fill="currentColor" + /><br /> + <span class="text-danger" + >There seems to be an error<br />Try again later</span + > + </div> + <div class="list-group" v-else-if="formState.potentialUsers.length > 0"> + <button + v-for="user in formState.potentialUsers" + :key="user.uid" + type="button" + class="list-group-item list-group-item-action" + :data-bs-target="'#' + props.backModalId" + data-bs-toggle="modal" + @click="emit('user-found', user)" + > + {{ user.display_name }} + </button> + </div> + <div v-else class="text-center fs-2"> + <bootstrap-icon + icon="search" + class="mb-2" + :width="56" + :height="56" + style="color: var(--bs-secondary)" + fill="currentColor" + /><br /> + <span v-if="formState.searchString.length > 2" + >Could not find any Users</span + > + <span v-else>Search for a user</span> + </div> + </template> + <template v-slot:footer> + <button + v-if="backModalId !== undefined" + type="button" + class="btn btn-secondary" + :data-bs-target="'#' + props.backModalId" + data-bs-toggle="modal" + > + Back + </button> + <button + v-else + type="button" + class="btn btn-secondary" + data-bs-dismiss="modal" + > + Close + </button> + </template> + </bootstrap-modal> +</template> + +<style scoped></style> diff --git a/src/components/Modals/UploadObjectModal.vue b/src/components/Modals/UploadObjectModal.vue index a1d5beaf04b3b9de2efaa480f2bb53015cfca8cd..c8af82e3e1937924dcd947c8c32de133a6ff7054 100644 --- a/src/components/Modals/UploadObjectModal.vue +++ b/src/components/Modals/UploadObjectModal.vue @@ -11,7 +11,6 @@ import { Modal, Toast } from "bootstrap"; const props = defineProps<{ modalID: string; - modalLabel: string; bucketName: string; keyPrefix: string; s3Client: S3Client; @@ -171,7 +170,7 @@ onMounted(() => { <bootstrap-modal :modalID="modalID" :static-backdrop="true" - :modal-label="modalLabel" + modal-label="Upload Object Modal" > <template v-slot:header> <h4>Upload file to</h4> diff --git a/src/components/S3KeyView.vue b/src/components/S3KeyView.vue index 481e1e4bdb0b981c423ef92ec441d50c73b70028..9d42d5d0c3e2194fb16fe075210456cbc8bc43c3 100644 --- a/src/components/S3KeyView.vue +++ b/src/components/S3KeyView.vue @@ -34,7 +34,6 @@ function deleteKeyTrigger() { <template> <DeleteModal modalID="delete-key-modal" - modal-label="Delete S3 Key" :object-name-delete="props.s3key.access_key" :back-modal-id="undefined" @confirm-delete="deleteKeyTrigger" diff --git a/src/views/object-storage/BucketsView.vue b/src/views/object-storage/BucketsView.vue index 9d8ce59e0af209d7b7ab219bafb1a48d6c0a1105..93d000e5becfa021b9fa7d1e25071ddcf3366932 100644 --- a/src/views/object-storage/BucketsView.vue +++ b/src/views/object-storage/BucketsView.vue @@ -1,7 +1,7 @@ <script setup lang="ts"> import type { ComputedRef } from "vue"; import { computed, onMounted, reactive } from "vue"; -import type { BucketOut, BucketPermission } from "@/client"; +import type { BucketOut, BucketPermissionOut } from "@/client"; import { BucketPermissionsService, BucketService } from "@/client"; import { useRoute, useRouter } from "vue-router"; import BootstrapIcon from "@/components/BootstrapIcon.vue"; @@ -25,7 +25,7 @@ const bucketsState = reactive({ buckets: BucketOut[]; loading: boolean; filterString: string; - permissions: Record<string, BucketPermission>; + permissions: Record<string, BucketPermissionOut>; potentialDeleteBucketName: string; }); let deleteModal: Modal | null = null; @@ -42,7 +42,7 @@ function fetchBuckets() { BucketPermissionsService.bucketPermissionsListPermissionsPerUser( authStore.user.uid ).then((permissions) => { - const new_permissions: Record<string, BucketPermission> = {}; + const new_permissions: Record<string, BucketPermissionOut> = {}; for (const perm of permissions) { new_permissions[perm.bucket_name] = perm; } @@ -59,11 +59,10 @@ const writableBuckets: ComputedRef<BucketOut[]> = computed(() => { ); }); -const currentPermission: ComputedRef<BucketPermission | undefined> = computed( - () => { +const currentPermission: ComputedRef<BucketPermissionOut | undefined> = + computed(() => { return bucketsState.permissions[route.params.bucketName as string]; - } -); + }); const filteredBuckets: ComputedRef<BucketOut[]> = computed(() => { return bucketsState.filterString.length > 0 @@ -106,7 +105,6 @@ onMounted(() => { <template> <DeleteModal modalID="delete-bucket-modal" - modal-label="some-label" :object-name-delete="bucketsState.potentialDeleteBucketName" :back-modal-id="undefined" @confirm-delete=" @@ -140,7 +138,6 @@ onMounted(() => { </div> <create-bucket-modal modalID="create-bucket-modal" - modal-label="create-bucket-modal-label" @bucket-created="addBucket" /> <div class="input-group flex-nowrap mt-2">