diff --git a/package-lock.json b/package-lock.json index 0277daf57b61496f7ece3967f89f5f788ece1252..fb83a8e79e068dc4f2275f77836236db8248d4b6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -46,7 +46,7 @@ "@vue/eslint-config-prettier": "~9.0.0", "@vue/eslint-config-typescript": "~13.0.0", "@vue/tsconfig": "~0.5.0", - "axios": "~1.6.0", + "axios": "~1.7.0", "eslint": "~8.57.0", "eslint-plugin-vue": "~9.26.0", "highlight.js": "^11.9.0", @@ -3080,9 +3080,9 @@ } }, "node_modules/axios": { - "version": "1.6.8", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.8.tgz", - "integrity": "sha512-v/ZHtJDU39mDpyBoFVkETcd/uNdxrWRrg3bKpOKzXFA6Bvqopts6ALSMU3y6ijYxbw2B+wPrIv46egTzJXCLGQ==", + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.2.tgz", + "integrity": "sha512-2A8QhOMrbomlDuiLeK9XibIBzuHeRcqqNOHp0Cyp5EoJ1IFDh+XZH3A6BkXtv0K4gFGCI0Y4BM7B1wOEi0Rmgw==", "dev": true, "dependencies": { "follow-redirects": "^1.15.6", diff --git a/package.json b/package.json index c52435aa8bb0bccccfc2697e3f78f025f89055e1..f4119d1f2a6d0131d82ebd32f067cc729c53cd34 100644 --- a/package.json +++ b/package.json @@ -50,7 +50,7 @@ "@vue/eslint-config-prettier": "~9.0.0", "@vue/eslint-config-typescript": "~13.0.0", "@vue/tsconfig": "~0.5.0", - "axios": "~1.6.0", + "axios": "~1.7.0", "eslint": "~8.57.0", "eslint-plugin-vue": "~9.26.0", "highlight.js": "^11.9.0", diff --git a/src/App.vue b/src/App.vue index 55e8ce1bb8db5cab8526d8bb4687fc636e93fa70..190e60f004f0fb2b041d6f2e3e47e93ae22c441e 100644 --- a/src/App.vue +++ b/src/App.vue @@ -1,7 +1,7 @@ <script setup lang="ts"> import { onBeforeMount, onMounted } from "vue"; import { useCookies } from "vue3-cookies"; -import { useAuthStore } from "@/stores/users"; +import { useUserStore } from "@/stores/users"; import { useRoute, useRouter } from "vue-router"; import { OpenAPI } from "@/client"; import { environment } from "@/environment"; @@ -17,7 +17,7 @@ import { useS3KeyStore } from "@/stores/s3keys"; const { cookies } = useCookies(); const router = useRouter(); const route = useRoute(); -const userRepository = useAuthStore(); +const userRepository = useUserStore(); const nameRepository = useNameStore(); const resourceRepository = useResourceStore(); const workflowRepository = useWorkflowStore(); diff --git a/src/components/AppHeader.vue b/src/components/AppHeader.vue index ff4585b608b7198191646544114c6ccf3ae4faec..78d7f011ce78bf54ed14c3d0c7e32b4aa60d2fc2 100644 --- a/src/components/AppHeader.vue +++ b/src/components/AppHeader.vue @@ -1,6 +1,6 @@ <script setup lang="ts"> import FontAwesomeIcon from "@/components/FontAwesomeIcon.vue"; -import { useAuthStore } from "@/stores/users"; +import { useUserStore } from "@/stores/users"; import { useRoute } from "vue-router"; import { useCookies } from "vue3-cookies"; import { watch, ref, computed } from "vue"; @@ -9,12 +9,12 @@ import { OpenAPI } from "@/client"; import CopyToClipboardIcon from "@/components/CopyToClipboardIcon.vue"; import dayjs from "dayjs"; -const store = useAuthStore(); +const userRepository = useUserStore(); const { cookies } = useCookies(); const route = useRoute(); function logout() { - store.logout(); + userRepository.logout(); cookies.remove("bearer"); } @@ -69,7 +69,7 @@ watch( CloWM </router-link> - <div class="d-flex flex-grow-1" v-if="store.authenticated"> + <div class="d-flex flex-grow-1" v-if="userRepository.authenticated"> <ul class="navbar-nav"> <li class="nav-item dropdown"> <a @@ -136,17 +136,23 @@ watch( >My Workflow Executions </router-link> </li> - <li v-if="store.workflowDev || store.rewiewer || store.admin"> + <li + v-if=" + userRepository.workflowDev || + userRepository.rewiewer || + userRepository.admin + " + > <hr class="dropdown-divider" /> </li> - <li v-if="store.workflowDev || store.admin"> + <li v-if="userRepository.workflowDev || userRepository.admin"> <router-link class="dropdown-item" :to="{ name: 'workflows-developer' }" >My Workflows </router-link> </li> - <li v-if="store.rewiewer || store.admin"> + <li v-if="userRepository.rewiewer || userRepository.admin"> <router-link class="dropdown-item" :to="{ name: 'workflows-reviewer' }" @@ -178,18 +184,24 @@ watch( </router-link> </li> <li - v-if="store.resourceMaintainer || store.rewiewer || store.admin" + v-if=" + userRepository.resourceMaintainer || + userRepository.rewiewer || + userRepository.admin + " > <hr class="dropdown-divider" /> </li> - <li v-if="store.resourceMaintainer || store.admin"> + <li + v-if="userRepository.resourceMaintainer || userRepository.admin" + > <router-link class="dropdown-item" :to="{ name: 'resource-maintainer' }" >My Resources </router-link> </li> - <li v-if="store.rewiewer || store.admin"> + <li v-if="userRepository.rewiewer || userRepository.admin"> <router-link class="dropdown-item" :to="{ name: 'resource-review' }" @@ -198,7 +210,7 @@ watch( </li> </ul> </li> - <li v-if="store.admin"> + <li v-if="userRepository.admin"> <a class="nav-link dropdown-toggle" :class="{ 'text-black': adminActive }" @@ -245,7 +257,10 @@ watch( </li> </ul> </div> - <div class="dropdown" v-if="store.authenticated && store.user != null"> + <div + class="dropdown" + v-if="userRepository.authenticated && userRepository.user != null" + > <a href="#" class="d-flex align-items-center text-white text-decoration-none dropdown-toggle-split" @@ -253,7 +268,7 @@ watch( data-bs-toggle="dropdown" aria-expanded="false" > - <strong class="me-2">{{ store.user?.display_name }}</strong> + <strong class="me-2">{{ userRepository.user?.display_name }}</strong> <font-awesome-icon icon="fa-solid fa-circle-user" class="fs-5" /> </a> <ul @@ -274,7 +289,7 @@ watch( </li> <li><h6 class="dropdown-header">Roles</h6></li> <li - v-for="role in store.roles" + v-for="role in userRepository.roles" :key="role" class="dropdown-item text-capitalize disabled" > @@ -299,7 +314,7 @@ watch( <bootstrap-modal modal-id="advancedUsageModal" modal-label="Advanced Usage Modal" - v-if="store.authenticated" + v-if="userRepository.authenticated" size-modifier-modal="lg" > <template v-slot:header> @@ -326,7 +341,9 @@ watch( <label for="clowm-jwt" class="form-label" >JWT for Services (expires at {{ - dayjs.unix(store.decodedToken!.exp).format("DD.MM.YYYY HH:mm") + dayjs + .unix(userRepository.decodedToken!.exp) + .format("DD.MM.YYYY HH:mm") }})</label > <div class="input-group"> @@ -335,11 +352,11 @@ watch( readonly class="form-control text-truncate" id="clowm-jwt" - :value="store.token" + :value="userRepository.token" aria-describedby="clowm-jwt-copy" /> <span class="input-group-text" id="clowm-jwt-copy" - ><copy-to-clipboard-icon :text="store.token ?? ''" + ><copy-to-clipboard-icon :text="userRepository.token ?? ''" /></span> </div> </div> diff --git a/src/components/admin/UserRoleMark.vue b/src/components/admin/UserRoleMark.vue deleted file mode 100644 index f87b5a6f6768b7c917cacf8f2a83c7abad9c6701..0000000000000000000000000000000000000000 --- a/src/components/admin/UserRoleMark.vue +++ /dev/null @@ -1,20 +0,0 @@ -<script setup lang="ts"> -import { RoleEnum, type UserOutExtended } from "@/client"; -import FontAwesomeIcon from "@/components/FontAwesomeIcon.vue"; - -const props = defineProps<{ - role: RoleEnum; - user: UserOutExtended; -}>(); -</script> - -<template> - <font-awesome-icon - v-if="props.user.roles?.includes(props.role)" - icon="fa-solid fa-check" - class="text-success fs-5" - /> - <font-awesome-icon v-else icon="fa-solid fa-xmark" class="text-danger fs-5" /> -</template> - -<style scoped></style> diff --git a/src/components/modals/SearchUserModal.vue b/src/components/modals/SearchUserModal.vue index c6b5b26093a6a9d217a92b1e3751c79c3819054c..9bb99400ff390b85e0901f33039ac3514e2e46f4 100644 --- a/src/components/modals/SearchUserModal.vue +++ b/src/components/modals/SearchUserModal.vue @@ -3,7 +3,7 @@ import { reactive, ref, watch } from "vue"; import BootstrapModal from "@/components/modals/BootstrapModal.vue"; import FontAwesomeIcon from "@/components/FontAwesomeIcon.vue"; import type { UserOut } from "@/client"; -import { useAuthStore } from "@/stores/users"; +import { useUserStore } from "@/stores/users"; const props = defineProps<{ modalId: string; @@ -12,7 +12,7 @@ const props = defineProps<{ }>(); const randomIDSuffix = Math.random().toString(16).substring(2, 8); -const store = useAuthStore(); +const store = useUserStore(); const textInputElement = ref<HTMLInputElement | undefined>(undefined); const formState = reactive<{ diff --git a/src/components/object-storage/BucketListItem.vue b/src/components/object-storage/BucketListItem.vue index 672b09c18b2c63cc5a0260121f9100ca75ad4aa7..aba2225d1400b256027bdd7d3f238473eda29f1c 100644 --- a/src/components/object-storage/BucketListItem.vue +++ b/src/components/object-storage/BucketListItem.vue @@ -8,7 +8,7 @@ import { computed, onMounted, reactive, ref } from "vue"; import { Toast, Tooltip } from "bootstrap"; import { useBucketStore } from "@/stores/buckets"; import { useRouter } from "vue-router"; -import { useAuthStore } from "@/stores/users"; +import { useUserStore } from "@/stores/users"; import type { FolderTree } from "@/types/PseudoFolder"; import { useNameStore } from "@/stores/names"; import { environment } from "@/environment"; @@ -27,7 +27,7 @@ const props = defineProps<{ const publicCheckbox = ref<HTMLInputElement | undefined>(undefined); const randomIDSuffix = Math.random().toString(16).substring(2, 8); const permissionRepository = useBucketStore(); -const userRepository = useAuthStore(); +const userRepository = useUserStore(); const nameRepository = useNameStore(); const bucketRepository = useBucketStore(); const objectRepository = useS3ObjectStore(); diff --git a/src/components/object-storage/modals/PermissionListModal.vue b/src/components/object-storage/modals/PermissionListModal.vue index 3eefe9fb64c0895281d5fa78024e5d1cd596d6a0..8566788e2acd55f48c6bf9334aab24e07dc40c64 100644 --- a/src/components/object-storage/modals/PermissionListModal.vue +++ b/src/components/object-storage/modals/PermissionListModal.vue @@ -7,11 +7,11 @@ import BootstrapModal from "@/components/modals/BootstrapModal.vue"; import PermissionModal from "@/components/object-storage/modals/PermissionModal.vue"; import { useBucketStore } from "@/stores/buckets"; import { useNameStore } from "@/stores/names"; -import { useAuthStore } from "@/stores/users"; +import { useUserStore } from "@/stores/users"; const bucketRepository = useBucketStore(); const nameRepository = useNameStore(); -const userRepository = useAuthStore(); +const userRepository = useUserStore(); // Props // ----------------------------------------------------------------------------- diff --git a/src/components/resources/ResourceCard.vue b/src/components/resources/ResourceCard.vue index d2c21e8db37fe0ef4c43d3e8f906773aecc0330d..7c148c9c854a71f733cb7906fa9126eda44bb5ea 100644 --- a/src/components/resources/ResourceCard.vue +++ b/src/components/resources/ResourceCard.vue @@ -14,7 +14,7 @@ import { useResourceStore } from "@/stores/resources"; import { Tooltip } from "bootstrap"; import { useNameStore } from "@/stores/names"; import RequestReviewButton from "@/components/resources/RequestReviewButton.vue"; -import { useAuthStore } from "@/stores/users"; +import { useUserStore } from "@/stores/users"; import { createDownloadUrl } from "@/utils/DownloadJson"; import { filesize } from "filesize"; @@ -22,7 +22,7 @@ const randomIDSuffix: string = Math.random().toString(16).substring(2, 8); const objectRepository = useS3ObjectStore(); const resourceRepository = useResourceStore(); const nameRepository = useNameStore(); -const userRepository = useAuthStore(); +const userRepository = useUserStore(); const props = defineProps<{ resource: ResourceOut; diff --git a/src/stores/buckets.ts b/src/stores/buckets.ts index 5c5d702f686a994ce2f7405e3cc5d8674ca44b6c..ac198a698ddbe1f7b39227a4c411b5acd82c2292 100644 --- a/src/stores/buckets.ts +++ b/src/stores/buckets.ts @@ -7,7 +7,7 @@ import type { BucketPermissionIn, BucketPermissionParameters, } from "@/client"; -import { useAuthStore } from "@/stores/users"; +import { useUserStore } from "@/stores/users"; export const useBucketStore = defineStore({ id: "buckets", @@ -30,7 +30,7 @@ export const useBucketStore = defineStore({ return tempList; }, ownBuckets(): BucketOut[] { - const authStore = useAuthStore(); + const authStore = useUserStore(); return this.buckets.filter( (bucket) => bucket.owner_id === authStore.currentUID, ); @@ -39,7 +39,7 @@ export const useBucketStore = defineStore({ return (bucketName) => this.bucketPermissionsMapping[bucketName] ?? []; }, permissionFeatureAllowed(): (bucketName: string) => boolean { - const authStore = useAuthStore(); + const authStore = useUserStore(); return (bucketName) => { // If a permission for the bucket exist, then false if (this.ownPermissions[bucketName] != undefined) { @@ -50,7 +50,7 @@ export const useBucketStore = defineStore({ }; }, writableBucket(): (bucketName: string) => boolean { - const authStore = useAuthStore(); + const authStore = useUserStore(); return (bucketName) => { // If this is a foreign bucket, check that the user has write permission if (this.ownPermissions[bucketName] != undefined) { @@ -60,7 +60,7 @@ export const useBucketStore = defineStore({ }; }, readableBucket(): (bucketName: string) => boolean { - const authStore = useAuthStore(); + const authStore = useUserStore(); return (bucketName) => { // If this is a foreign bucket, check that the user has read permission if (this.ownPermissions[bucketName] != undefined) { @@ -77,7 +77,7 @@ export const useBucketStore = defineStore({ ): Promise<BucketOut[]> { return BucketService.bucketListBuckets(ownerId, bucketType).then( (buckets) => { - const userRepository = useAuthStore(); + const userRepository = useUserStore(); userRepository.fetchUsernames( buckets .map((bucket) => bucket.owner_id) @@ -90,7 +90,7 @@ export const useBucketStore = defineStore({ fetchOwnPermissions( onFinally?: () => void, ): Promise<BucketPermissionOut[]> { - const authStore = useAuthStore(); + const authStore = useUserStore(); if (Object.keys(this.ownPermissions).length > 0) { onFinally?.(); } @@ -115,7 +115,7 @@ export const useBucketStore = defineStore({ if (this.ownBuckets.length > 0) { onFinally?.(); } - const userStore = useAuthStore(); + const userStore = useUserStore(); return this.fetchBuckets(userStore.currentUID) .then((buckets) => { for (const bucket of buckets) { @@ -154,7 +154,7 @@ export const useBucketStore = defineStore({ bucketName: string, onFinally?: () => void, ): Promise<BucketPermissionOut[]> { - const authStore = useAuthStore(); + const authStore = useUserStore(); if (authStore.foreignUser) { return Promise.resolve([]).finally(onFinally); } @@ -175,7 +175,7 @@ export const useBucketStore = defineStore({ bucketName, uid, ).then(() => { - const userRepository = useAuthStore(); + const userRepository = useUserStore(); if (uid == userRepository.currentUID) { this.deleteOwnPermission(bucketName); } diff --git a/src/stores/resources.ts b/src/stores/resources.ts index f43fe0a04e9165aeb94657055eafedde3fb9c4be..c84dc20db3531158293dc348d6247ba0f7c69907 100644 --- a/src/stores/resources.ts +++ b/src/stores/resources.ts @@ -14,7 +14,7 @@ import { ResourceVersionService, ResourceVersionStatus, } from "@/client"; -import { useAuthStore } from "@/stores/users"; +import { useUserStore } from "@/stores/users"; import { useNameStore } from "@/stores/names"; function parseFileTree(parent: string, tree?: FileTree | null): string[] { @@ -115,7 +115,7 @@ export const useResourceStore = defineStore({ return ResourceService.resourceListSyncRequests() .then((requests) => { this.__syncRequestsFetched = true; - const userStore = useAuthStore(); + const userStore = useUserStore(); userStore.fetchUsernames( requests.map((request) => request.requester_id), ); @@ -140,7 +140,7 @@ export const useResourceStore = defineStore({ searchString, _public, ).then((resources) => { - const userStore = useAuthStore(); + const userStore = useUserStore(); userStore.fetchUsernames( resources .map((resource) => resource.maintainer_id ?? "") @@ -241,7 +241,7 @@ export const useResourceStore = defineStore({ .finally(onFinally); }, fetchOwnResources(onFinally?: () => void): Promise<ResourceOut[]> { - const authStore = useAuthStore(); + const authStore = useUserStore(); if (this.ownResources.length > 0) { onFinally?.(); } diff --git a/src/stores/s3keys.ts b/src/stores/s3keys.ts index bb95b17c721d9b997fda0cdd0f25423e68186957..22f3c2b1f898f97fc6e5c47f5426e58cd9ab0c4e 100644 --- a/src/stores/s3keys.ts +++ b/src/stores/s3keys.ts @@ -1,5 +1,5 @@ import { defineStore } from "pinia"; -import { useAuthStore } from "@/stores/users"; +import { useUserStore } from "@/stores/users"; import type { S3Key } from "@/client"; import { S3KeyService } from "@/client"; import { useS3ObjectStore } from "@/stores/s3objects"; @@ -26,7 +26,7 @@ export const useS3KeyStore = defineStore({ if (this.keys.length > 0) { onFinally?.(); } - return S3KeyService.s3KeyGetUserKeys(useAuthStore().currentUID) + return S3KeyService.s3KeyGetUserKeys(useUserStore().currentUID) .then((keys) => { const s3ObjectRepository = useS3ObjectStore(); s3ObjectRepository.updateS3Client(keys[0]); @@ -40,7 +40,7 @@ export const useS3KeyStore = defineStore({ .finally(onFinally); }, deleteS3Key(access_id: string): Promise<void> { - const userRepository = useAuthStore(); + const userRepository = useUserStore(); return S3KeyService.s3KeyDeleteUserKey( access_id, userRepository.currentUID, @@ -51,7 +51,7 @@ export const useS3KeyStore = defineStore({ }); }, createS3Key(): Promise<S3Key> { - const userRepository = useAuthStore(); + const userRepository = useUserStore(); return S3KeyService.s3KeyCreateUserKey(userRepository.currentUID).then( (key) => { this.keyMapping[key.access_key] = key; diff --git a/src/stores/s3objects.ts b/src/stores/s3objects.ts index 8f2b67e5cd934f3459deb955014707650c003088..f92f016b1e30e15e573cbc413f7c78300ab4e816 100644 --- a/src/stores/s3objects.ts +++ b/src/stores/s3objects.ts @@ -26,7 +26,7 @@ import dayjs from "dayjs"; import { Upload } from "@aws-sdk/lib-storage"; import type { Progress } from "@aws-sdk/lib-storage"; import type { AbortController } from "@smithy/types"; -import { useAuthStore } from "@/stores/users"; +import { useUserStore } from "@/stores/users"; export const useS3ObjectStore = defineStore({ id: "s3objects", @@ -114,7 +114,7 @@ export const useS3ObjectStore = defineStore({ .then((response) => response.Uploads ?? []) .then((uploads) => { this.multiPartUploadsMapping[bucketName] = uploads; - const userRepository = useAuthStore(); + const userRepository = useUserStore(); userRepository.fetchUsernames( uploads .map((upload) => upload.Initiator?.ID ?? "") diff --git a/src/stores/users.ts b/src/stores/users.ts index 9b22015a3650476c41dc6be9588dc3b638bac303..8c4ca1116dba353bc7576a83a122d7e587034e51 100644 --- a/src/stores/users.ts +++ b/src/stores/users.ts @@ -31,8 +31,8 @@ function parseJwt(token: string): DecodedToken { return JSON.parse(jsonPayload) as DecodedToken; } -export const useAuthStore = defineStore({ - id: "auth", +export const useUserStore = defineStore({ + id: "user", state: () => ({ token: null, @@ -108,6 +108,11 @@ export const useAuthStore = defineStore({ }, ); }, + updateUserRoles(uid: string, roles: RoleEnum[]): Promise<UserOutExtended> { + return UserService.userUpdateRoles(uid, { + roles: roles, + }); + }, searchUser(searchString: string): Promise<UserOut[]> { return UserService.userSearchUsers(searchString).then((users) => { const nameStore = useNameStore(); diff --git a/src/stores/workflowExecutions.ts b/src/stores/workflowExecutions.ts index 5eab671aba325ba2db854e43be4464451c3a96e2..19204773832ae69d105468255e957381ff2d8666 100644 --- a/src/stores/workflowExecutions.ts +++ b/src/stores/workflowExecutions.ts @@ -10,7 +10,7 @@ import { WorkflowExecutionStatus, WorkflowService, } from "@/client"; -import { useAuthStore } from "@/stores/users"; +import { useUserStore } from "@/stores/users"; import dayjs from "dayjs"; import { set, get } from "idb-keyval"; import type { @@ -64,7 +64,7 @@ export const useWorkflowExecutionStore = defineStore({ if (this.anonymizedExecutions.length > 0) { onFinally?.(); } - const userStore = useAuthStore(); + const userStore = useUserStore(); return WorkflowService.workflowGetDeveloperWorkflowStatistics( userStore.currentUID, ) @@ -78,7 +78,7 @@ export const useWorkflowExecutionStore = defineStore({ if (Object.keys(this.executionMapping).length > 0) { onFinally?.(); } - const userStore = useAuthStore(); + const userStore = useUserStore(); return WorkflowExecutionService.workflowExecutionListWorkflowExecutions( userStore.currentUID, ) diff --git a/src/stores/workflows.ts b/src/stores/workflows.ts index 5643a6f9f8fa69086aaf8f958f776ddff2387aba..0ec7842b69f0406cceb9435491ddf4b0a02da62e 100644 --- a/src/stores/workflows.ts +++ b/src/stores/workflows.ts @@ -18,7 +18,7 @@ import { WorkflowService, WorkflowVersionService, } from "@/client"; -import { useAuthStore } from "@/stores/users"; +import { useUserStore } from "@/stores/users"; import { get, set } from "idb-keyval"; import { useNameStore } from "@/stores/names"; @@ -42,7 +42,7 @@ export const useWorkflowStore = defineStore({ return Object.values(this.workflowMapping); }, ownWorkflows(): WorkflowOut[] { - const authStore = useAuthStore(); + const authStore = useUserStore(); return Object.values(this.comprehensiveWorkflowMapping).filter( (workflow) => workflow.developer_id === authStore.currentUID, ); @@ -118,7 +118,7 @@ export const useWorkflowStore = defineStore({ .finally(onFinally); }, fetchOwnWorkflows(onFinally?: () => void): Promise<WorkflowOut[]> { - const authStore = useAuthStore(); + const authStore = useUserStore(); if (this.ownWorkflows.length > 0) { onFinally?.(); } diff --git a/src/views/LoginView.vue b/src/views/LoginView.vue index b571a83af8863e50193f798dac04f933a819dcf9..d8fff7e2c5388fac61202a07b026f88c7163eb78 100644 --- a/src/views/LoginView.vue +++ b/src/views/LoginView.vue @@ -1,6 +1,6 @@ <script setup lang="ts"> import { onBeforeMount, onMounted, computed } from "vue"; -import { useAuthStore } from "@/stores/users"; +import { useUserStore } from "@/stores/users"; import { useRouter } from "vue-router"; import { OpenAPI } from "@/client"; import { Toast } from "bootstrap"; @@ -13,7 +13,7 @@ const props = defineProps<{ loginError?: string; }>(); -const store = useAuthStore(); +const store = useUserStore(); let errorToast: Toast | null = null; diff --git a/src/views/admin/AdminSyncRequestsView.vue b/src/views/admin/AdminSyncRequestsView.vue index 9981f2d9316533a67a09fd328d0ec31f9d3611c4..68a3d8a48a6de3e5520cacc36cd9e3a9c3a669b5 100644 --- a/src/views/admin/AdminSyncRequestsView.vue +++ b/src/views/admin/AdminSyncRequestsView.vue @@ -11,7 +11,7 @@ import { } from "@/client"; import { useResourceStore } from "@/stores/resources"; import { useNameStore } from "@/stores/names"; -import { useAuthStore } from "@/stores/users"; +import { useUserStore } from "@/stores/users"; import { Modal, Toast } from "bootstrap"; import FontAwesomeIcon from "@/components/FontAwesomeIcon.vue"; @@ -33,7 +33,7 @@ const resourceState = reactive<{ const resourceRepository = useResourceStore(); const nameRepository = useNameStore(); -const userRepository = useAuthStore(); +const userRepository = useUserStore(); let rejectReasonModal: Modal | null = null; let successToast: Toast | null = null; diff --git a/src/views/admin/AdminUsersView.vue b/src/views/admin/AdminUsersView.vue index 8dbe2dccf280c14a0884e000ccfa0f1b105380b9..9dde3ff037049c527b73556e94cc0a3b9dfef5f1 100644 --- a/src/views/admin/AdminUsersView.vue +++ b/src/views/admin/AdminUsersView.vue @@ -1,15 +1,23 @@ <script setup lang="ts"> -import { useAuthStore } from "@/stores/users"; -import { reactive } from "vue"; +import { useUserStore } from "@/stores/users"; +import { onMounted, reactive } from "vue"; import { RoleEnum, type UserOutExtended } from "@/client"; import FontAwesomeIcon from "@/components/FontAwesomeIcon.vue"; -import UserRoleMark from "@/components/admin/UserRoleMark.vue"; +import BootstrapToast from "@/components/BootstrapToast.vue"; +import { Toast } from "bootstrap"; -const userRepository = useAuthStore(); +const userRepository = useUserStore(); +type RoleList = RoleEnum[]; +let successToast: Toast | undefined; +let errorToast: Toast | undefined; const userState = reactive<{ loading: boolean; users: UserOutExtended[]; + newUserRoles: RoleList[]; + editUserRoles: boolean[]; + editedUser?: UserOutExtended; + errorMessage: string; searchString: string; userRoles: RoleEnum[]; inspectUser?: UserOutExtended; @@ -17,6 +25,10 @@ const userState = reactive<{ }>({ loading: false, users: [], + newUserRoles: [], + editUserRoles: [], + editedUser: undefined, + errorMessage: "", searchString: "", userRoles: [], inspectUser: undefined, @@ -32,15 +44,58 @@ function searchUsers() { ) .then((users) => { userState.users = users; + userState.newUserRoles = users.map((user) => user.roles); + userState.editUserRoles = users.map(() => false); }) .finally(() => { userState.loading = false; userState.searched = true; }); } + +function saveUserRoles(index: number) { + userState.loading = true; + userState.editedUser = userState.users[index]; + userRepository + .updateUserRoles(userState.users[index].uid, userState.newUserRoles[index]) + .then((user) => { + userState.users[index] = user; + userState.editUserRoles[index] = false; + successToast?.show(); + }) + .catch((err) => { + userState.errorMessage = err.body?.detail; + errorToast?.show(); + }) + .finally(() => { + userState.loading = false; + }); +} + +function resetUserRoles(index: number) { + userState.editUserRoles[index] = false; + userState.newUserRoles[index] = JSON.parse( + JSON.stringify(userState.users[index].roles), + ); +} + +onMounted(() => { + successToast = new Toast("#change-role-success-toast"); + errorToast = new Toast("#change-role-error-toast"); +}); </script> <template> + <bootstrap-toast toast-id="change-role-success-toast"> + Successfully change roles of user {{ userState.editedUser?.display_name }} + </bootstrap-toast> + <bootstrap-toast toast-id="change-role-error-toast" color-class="danger"> + <template #default + >Couldn't change the roles of + {{ userState.editedUser?.display_name }} + </template> + <template #body>Error: {{ userState.errorMessage }}</template> + </bootstrap-toast> <div class="row border-bottom mb-4 justify-content-between align-items-center" > @@ -106,16 +161,20 @@ function searchUsers() { <tr> <th scope="col"><b>Name</b></th> <th scope="col">UID</th> - <th scope="col" class="text-center">Approved User</th> - <th scope="col" class="text-center">Developer</th> - <th scope="col" class="text-center">Resource Maintainer</th> - <th scope="col" class="text-center">Reviewer</th> - <th scope="col" class="text-center">Admin</th> + <th + v-for="role in Object.values(RoleEnum)" + :key="role" + scope="col" + class="text-center" + > + {{ role.toUpperCase() }} + </th> + <th scope="col"></th> </tr> </thead> <tbody v-if="userState.users.length === 0"> <tr> - <td colspan="7" class="text-center fst-italic fw-light"> + <td colspan="8" class="text-center fst-italic fw-light"> <template v-if="userState.searched" >No Users found with specified filters </template> @@ -124,23 +183,54 @@ function searchUsers() { </tr> </tbody> <tbody v-else> - <tr v-for="user in userState.users" :key="user.uid"> + <tr v-for="(user, index) in userState.users" :key="user.uid"> <th scope="row">{{ user.display_name }}</th> <td>{{ user.uid }}</td> - <td class="text-center"> - <user-role-mark :role="RoleEnum.USER" :user="user" /> - </td> - <td class="text-center"> - <user-role-mark :role="RoleEnum.DEVELOPER" :user="user" /> - </td> - <td class="text-center"> - <user-role-mark :role="RoleEnum.DB_MAINTAINER" :user="user" /> + <td + v-for="role in Object.values(RoleEnum)" + :key="role" + class="text-center" + > + <input + type="checkbox" + class="form-check-input" + :value="role" + :disabled="!userState.editUserRoles[index]" + v-model="userState.newUserRoles[index]" + /> </td> - <td class="text-center"> - <user-role-mark :role="RoleEnum.REVIEWER" :user="user" /> + <td class="text-end" v-if="userState.editUserRoles[index]"> + <div + class="btn-group btn-group-sm" + role="group" + aria-label="Basic mixed styles example" + > + <button + type="button" + class="btn btn-success" + :disabled="userState.loading" + @click="saveUserRoles(index)" + > + Save + </button> + <button + type="button" + class="btn btn-danger" + :disabled="userState.loading" + @click="resetUserRoles(index)" + > + Cancel + </button> + </div> </td> - <td class="text-center"> - <user-role-mark :role="RoleEnum.ADMINISTRATOR" :user="user" /> + <td class="text-end" v-else> + <button + type="button" + class="btn btn-sm btn-secondary" + @click="userState.editUserRoles[index] = true" + > + Edit Roles + </button> </td> </tr> </tbody> diff --git a/src/views/object-storage/BucketView.vue b/src/views/object-storage/BucketView.vue index 73f9c56745f54ce5c71623f202f21fd4a185808f..90ff2a8474faed56adda2b0d91d6b88f2ae3570d 100644 --- a/src/views/object-storage/BucketView.vue +++ b/src/views/object-storage/BucketView.vue @@ -17,13 +17,13 @@ import ObjectDetailModal from "@/components/object-storage/modals/ObjectDetailMo import CreateFolderModal from "@/components/object-storage/modals/CreateFolderModal.vue"; import DeleteModal from "@/components/modals/DeleteModal.vue"; import type { _Object as S3Object } from "@aws-sdk/client-s3"; -import { useAuthStore } from "@/stores/users"; +import { useUserStore } from "@/stores/users"; import { useBucketStore } from "@/stores/buckets"; import { useS3ObjectStore } from "@/stores/s3objects"; import { useS3KeyStore } from "@/stores/s3keys"; import BootstrapToast from "@/components/BootstrapToast.vue"; -const authStore = useAuthStore(); +const authStore = useUserStore(); const bucketRepository = useBucketStore(); const objectRepository = useS3ObjectStore(); const s3KeyRepository = useS3KeyStore(); diff --git a/src/views/object-storage/BucketsView.vue b/src/views/object-storage/BucketsView.vue index 69ea504715bf281b43b1d662ee3eb9a64ca42a06..f31d46061a8f3d3af8de82762e2408e0f4598c61 100644 --- a/src/views/object-storage/BucketsView.vue +++ b/src/views/object-storage/BucketsView.vue @@ -8,11 +8,11 @@ import DeleteModal from "@/components/modals/DeleteModal.vue"; import BucketListItem from "@/components/object-storage/BucketListItem.vue"; import { useBucketStore } from "@/stores/buckets"; import { Modal, Tooltip } from "bootstrap"; -import { useAuthStore } from "@/stores/users"; +import { useUserStore } from "@/stores/users"; const router = useRouter(); const bucketRepository = useBucketStore(); -const authStore = useAuthStore(); +const authStore = useUserStore(); const props = defineProps<{ bucketName?: string; diff --git a/src/views/object-storage/MultiPartUploadsView.vue b/src/views/object-storage/MultiPartUploadsView.vue index 083557a983aef916cbacebcc41f8b706e7dedd00..60fcb86a99750348a1dacbdce9494f0a9b101d8c 100644 --- a/src/views/object-storage/MultiPartUploadsView.vue +++ b/src/views/object-storage/MultiPartUploadsView.vue @@ -4,7 +4,7 @@ import { useS3ObjectStore } from "@/stores/s3objects"; import { useBucketStore } from "@/stores/buckets"; import { useNameStore } from "@/stores/names"; import dayjs from "dayjs"; -import { useAuthStore } from "@/stores/users"; +import { useUserStore } from "@/stores/users"; import FontAwesomeIcon from "@/components/FontAwesomeIcon.vue"; import { Tooltip } from "bootstrap"; import { useS3KeyStore } from "@/stores/s3keys"; @@ -12,7 +12,7 @@ import { useS3KeyStore } from "@/stores/s3keys"; const objectRepository = useS3ObjectStore(); const bucketRepository = useBucketStore(); const nameRepository = useNameStore(); -const userRepository = useAuthStore(); +const userRepository = useUserStore(); const s3keyRepository = useS3KeyStore(); let refreshTimeout: NodeJS.Timeout | undefined = undefined; diff --git a/src/views/object-storage/S3KeysView.vue b/src/views/object-storage/S3KeysView.vue index 018415a4a0cf215d59d294eeca391ba4f91a8367..dda0a528ddfa4970ab334c9a3ca415ae087b087e 100644 --- a/src/views/object-storage/S3KeysView.vue +++ b/src/views/object-storage/S3KeysView.vue @@ -2,12 +2,12 @@ import S3KeyView from "@/views/object-storage/S3KeyView.vue"; import FontAwesomeIcon from "@/components/FontAwesomeIcon.vue"; import { reactive, onMounted, computed } from "vue"; -import { useAuthStore } from "@/stores/users"; +import { useUserStore } from "@/stores/users"; import { Toast, Tooltip } from "bootstrap"; import { useS3KeyStore } from "@/stores/s3keys"; import BootstrapToast from "@/components/BootstrapToast.vue"; -const authStore = useAuthStore(); +const authStore = useUserStore(); const keyRepository = useS3KeyStore(); let successToast: Toast | null = null; diff --git a/src/views/resources/ListResourcesView.vue b/src/views/resources/ListResourcesView.vue index 094ebfd84c4db9a477edfb672fbcf859ff441b87..036bf1c41166ec8e6420b2e2fc681fadac152e58 100644 --- a/src/views/resources/ListResourcesView.vue +++ b/src/views/resources/ListResourcesView.vue @@ -3,7 +3,7 @@ import { computed, onMounted, reactive } from "vue"; import { useResourceStore } from "@/stores/resources"; import ResourceCard from "@/components/resources/ResourceCard.vue"; import CardTransitionGroup from "@/components/transitions/CardTransitionGroup.vue"; -import { useAuthStore } from "@/stores/users"; +import { useUserStore } from "@/stores/users"; import FontAwesomeIcon from "@/components/FontAwesomeIcon.vue"; import type { ResourceOut, ResourceVersionOut } from "@/client"; import ReasonModal from "@/components/modals/ReasonModal.vue"; @@ -11,7 +11,7 @@ import { Modal, Toast } from "bootstrap"; import BootstrapToast from "@/components/BootstrapToast.vue"; const resourceRepository = useResourceStore(); -const userRepository = useAuthStore(); +const userRepository = useUserStore(); let requestReasonModal: Modal | null = null; let syncRequestSuccessToast: Toast | null = null; diff --git a/src/views/workflows/ReviewWorkflowsView.vue b/src/views/workflows/ReviewWorkflowsView.vue index 063873af59de91e7db5081f2adc227821346c21f..6ef699efd046ab502de711bf15e6c8a1b2cbf0f9 100644 --- a/src/views/workflows/ReviewWorkflowsView.vue +++ b/src/views/workflows/ReviewWorkflowsView.vue @@ -5,14 +5,14 @@ import FontAwesomeIcon from "@/components/FontAwesomeIcon.vue"; import { determineGitIcon } from "@/utils/GitRepository"; import { sortedVersions } from "@/utils/Workflow"; import { useWorkflowStore } from "@/stores/workflows"; -import { useAuthStore } from "@/stores/users"; +import { useUserStore } from "@/stores/users"; import { useNameStore } from "@/stores/names"; import dayjs from "dayjs"; import { Tooltip } from "bootstrap"; const workflowRepository = useWorkflowStore(); const nameRepository = useNameStore(); -const userRepository = useAuthStore(); +const userRepository = useUserStore(); const workflowsState = reactive<{ loading: boolean; diff --git a/src/views/workflows/WorkflowView.vue b/src/views/workflows/WorkflowView.vue index a60bd37c387d7ab6ea014b79bcc1d9dd1c8afb38..f99d1d2d2741ca39d5bda18b3e8b4c6fded9e789 100644 --- a/src/views/workflows/WorkflowView.vue +++ b/src/views/workflows/WorkflowView.vue @@ -10,12 +10,12 @@ import { sortedVersions, } from "@/utils/Workflow"; import { determineGitIcon } from "@/utils/GitRepository"; -import { useAuthStore } from "@/stores/users"; +import { useUserStore } from "@/stores/users"; import { useWorkflowStore } from "@/stores/workflows"; import { useNameStore } from "@/stores/names"; const workflowRepository = useWorkflowStore(); -const userRepository = useAuthStore(); +const userRepository = useUserStore(); const nameRepository = useNameStore(); // Props