diff --git a/src/components/object-storage/BucketListItem.vue b/src/components/object-storage/BucketListItem.vue index d12d12e8d724822be8b5b7b07b1cec9d489a9250..4a5bdcd89d924fefcdec0521a483936d07aa5888 100644 --- a/src/components/object-storage/BucketListItem.vue +++ b/src/components/object-storage/BucketListItem.vue @@ -5,8 +5,8 @@ import FontAwesomeIcon from "@/components/FontAwesomeIcon.vue"; import PermissionModal from "@/components/object-storage/modals/PermissionModal.vue"; import BucketDetailModal from "@/components/object-storage/modals/BucketDetailModal.vue"; import dayjs from "dayjs"; -import { computed, onMounted, ref } from "vue"; -import { Tooltip } from "bootstrap"; +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"; @@ -15,6 +15,7 @@ import { useNameStore } from "@/stores/names"; import { environment } from "@/environment"; import { useS3ObjectStore } from "@/stores/s3objects"; import { filesize } from "filesize"; +import BootstrapToast from "@/components/BootstrapToast.vue"; const props = defineProps<{ active: boolean; @@ -32,6 +33,17 @@ const bucketRepository = useBucketStore(); const objectRepository = useS3ObjectStore(); const router = useRouter(); +let successToast: Toast | null; +let errorToast: Toast | null; + +const requestState = reactive<{ + error: string; + loading: boolean; +}>({ + error: "", + loading: false, +}); + const permission = computed<BucketPermissionOut | undefined>( () => permissionRepository.ownPermissions[props.bucket.name], ); @@ -56,12 +68,21 @@ function permissionDeleted() { } function toggleBucketPublicState() { + requestState.loading = true; bucketRepository .togglePublicState(props.bucket.name, !props.bucket.public) - .catch(() => { + .then(() => { + successToast?.show(); + }) + .catch((err) => { + requestState.error = err.toString(); if (publicCheckbox.value) { publicCheckbox.value.checked = props.bucket.public; } + errorToast?.show(); + }) + .finally(() => { + requestState.loading = false; }); } @@ -70,11 +91,28 @@ onMounted(() => { new Tooltip("#tooltip-" + randomIDSuffix); new Tooltip("#ownBucketIcon-" + randomIDSuffix); new Tooltip("#sharedBucketIcon-" + randomIDSuffix); + successToast = new Toast("#success-public-bucket-" + randomIDSuffix); + errorToast = new Toast("#error-public-bucket-" + randomIDSuffix); } }); </script> <template> + <bootstrap-toast + :toast-id="'success-public-bucket-' + randomIDSuffix" + v-if="!loading" + > + Bucket {{ bucket.name }} is now {{ bucket.public ? "public" : "private" }} + </bootstrap-toast> + <bootstrap-toast + :toast-id="'error-public-bucket-' + randomIDSuffix" + color-class="danger" + v-if="!loading" + > + Error making the bucket {{ bucket.name }} + {{ !bucket.public ? "public" : "private" }}:<br /> + {{ requestState.error }} + </bootstrap-toast> <permission-modal v-if="permission != undefined && props.active" :modalId="'view-permission-modal' + randomIDSuffix" @@ -236,30 +274,35 @@ onMounted(() => { </tr> <tr v-if="bucket.owner_constraint == undefined"> <th scope="row"> - <label - :for="'public-checkbox-' + randomIDSuffix" - class="fw-bold" - > - Public</label + <div + :class="{ 'form-check': !loading && permission == undefined }" > + <input + v-if="!loading && permission == undefined" + ref="publicCheckbox" + class="form-check-input" + type="checkbox" + :disabled="requestState.loading" + :checked="bucket.public" + :id="'public-checkbox-' + randomIDSuffix" + @change="toggleBucketPublicState" + /> + <label + :for="'public-checkbox-' + randomIDSuffix" + class="fw-bold" + > + Public</label + > + </div> </th> <td> - <input - ref="publicCheckbox" - class="form-check-input" - :disabled="loading || permission != undefined" - type="checkbox" - :checked="bucket.public" - :id="'public-checkbox-' + randomIDSuffix" - @change="toggleBucketPublicState" - /> <a - class="ms-4" v-if="bucket.public" target="_blank" :href="environment.S3_URL + '/' + bucket.name" - >Show</a + >Link</a > + <span v-else>Disabled</span> </td> </tr> </tbody> diff --git a/src/components/resources/modals/ResourceVersionInfoModal.vue b/src/components/resources/modals/ResourceVersionInfoModal.vue index 6dc9b1b3fe30aefb59109e5833c3ffa1fa540c05..13ace3a48316db4917febec9ac2b0739b528a1e5 100644 --- a/src/components/resources/modals/ResourceVersionInfoModal.vue +++ b/src/components/resources/modals/ResourceVersionInfoModal.vue @@ -34,11 +34,11 @@ const resourceVersion = computed<ResourceVersionOut | undefined>( <template #body> <h5>Resource</h5> <div class="mb-3 row"> - <div class="col-8"> + <div class="col-7"> <label for="resource-version-info-modal-resource-id" class="form-label" - >ID</label + >Resource ID</label > <div class="input-group"> <input @@ -54,7 +54,7 @@ const resourceVersion = computed<ResourceVersionOut | undefined>( /></span> </div> </div> - <div class="col-4"> + <div class="col-5"> <label for="resource-version-info-modal-resource-name" class="form-label" @@ -72,7 +72,7 @@ const resourceVersion = computed<ResourceVersionOut | undefined>( </div> </div> <div class="mb-3 row"> - <div class="col-8"> + <div class="col-7"> <label for="resource-version-info-modal-maintainer-id" class="form-label" @@ -92,7 +92,7 @@ const resourceVersion = computed<ResourceVersionOut | undefined>( /></span> </div> </div> - <div class="col-4"> + <div class="col-5"> <label for="resource-version-info-modal-maintainer-name" class="form-label" @@ -143,7 +143,7 @@ const resourceVersion = computed<ResourceVersionOut | undefined>( </div> <h5>Resource Version</h5> <div class="mb-3 row"> - <div class="col-8"> + <div class="col-7"> <label for="resource-version-info-modal-resource-version-id" class="form-label" @@ -163,7 +163,7 @@ const resourceVersion = computed<ResourceVersionOut | undefined>( /></span> </div> </div> - <div class="col-4"> + <div class="col-5"> <label for="resource-version-info-modal-resource-version-release" class="form-label" diff --git a/src/components/workflows/WorkflowCard.vue b/src/components/workflows/WorkflowCard.vue index c0de6f6821004902b839d11ba4fedb516b366e6b..9284e74222fcae9eb55c117bf2c605954f373e4e 100644 --- a/src/components/workflows/WorkflowCard.vue +++ b/src/components/workflows/WorkflowCard.vue @@ -2,7 +2,7 @@ import type { WorkflowOut, WorkflowVersion } from "@/client/workflow"; import FontAwesomeIcon from "@/components/FontAwesomeIcon.vue"; import dayjs from "dayjs"; -import { onMounted, ref, computed } from "vue"; +import { onMounted, computed } from "vue"; import { Tooltip } from "bootstrap"; import { latestVersion as calculateLatestVersion } from "@/utils/Workflow"; @@ -12,7 +12,6 @@ const props = defineProps<{ }>(); const randomIDSuffix: string = Math.random().toString(16).substring(2, 8); -const truncateDescription = ref<boolean>(true); const latestVersion = computed<WorkflowVersion | undefined>(() => calculateLatestVersion(props.workflow.versions), ); @@ -55,18 +54,11 @@ onMounted(() => { class="img-fluid float-end icon" /> </div> - <p class="card-text" :class="{ 'text-truncate': truncateDescription }"> + <p class="card-text"> <span v-if="props.loading" class="placeholder-glow" ><span class="placeholder col-12"></span ></span> - <span - v-else - @click="truncateDescription = false" - :class="{ - 'cursor-pointer': truncateDescription, - }" - >{{ props.workflow.short_description }}</span - > + <span v-else>{{ props.workflow.short_description }}</span> </p> <div class="d-flex justify-content-between mb-0"> <div v-if="props.loading" class="placeholder-glow w-50"> diff --git a/src/stores/resources.ts b/src/stores/resources.ts index 61fb1e9f27fb68fc05f87587b2736712fe3a58cc..0c9a78176fa4531e24ba19585cbd540d966aca5e 100644 --- a/src/stores/resources.ts +++ b/src/stores/resources.ts @@ -115,6 +115,10 @@ export const useResourceStore = defineStore({ return ResourceService.resourceListSyncRequests() .then((requests) => { this.__syncRequestsFetched = true; + const userStore = useAuthStore(); + userStore.fetchUsernames( + requests.map((request) => request.requester_id), + ); const newMapping: Record<string, UserSynchronizationRequestOut> = {}; for (const request of requests) { newMapping[request.resource_version_id] = request; @@ -136,6 +140,10 @@ export const useResourceStore = defineStore({ searchString, _public, ).then((resources) => { + const userStore = useAuthStore(); + userStore.fetchUsernames( + resources.map((resource) => resource.maintainer_id), + ); const nameStore = useNameStore(); for (const resource of resources) { nameStore.addNameToMapping(resource.resource_id, resource.name); diff --git a/src/views/admin/AdminUsersView.vue b/src/views/admin/AdminUsersView.vue index 58eeccc300fc1d69a492e5886059aa84ea0a55dc..34df43a3486840b63b056c42d8f1c196f4889e5b 100644 --- a/src/views/admin/AdminUsersView.vue +++ b/src/views/admin/AdminUsersView.vue @@ -92,12 +92,22 @@ function searchUsers() { Search </button> </form> - <table class="table table-striped align-middle" v-if="userState.users"> + <table + class="table table-striped align-middle caption-top" + v-if="userState.users" + > + <caption> + Displaying + {{ + userState.users.length + }} + Users + </caption> <thead> <tr> <th scope="col"><b>Name</b></th> <th scope="col">UID</th> - <th scope="col" class="text-center">Normal User</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> diff --git a/src/views/workflows/ListWorkflowsView.vue b/src/views/workflows/ListWorkflowsView.vue index baf7d7aeb28e8435a0f632306107261065ecfb1c..3316af5eeae702dc950b8af8d879e1deabafa9d1 100644 --- a/src/views/workflows/ListWorkflowsView.vue +++ b/src/views/workflows/ListWorkflowsView.vue @@ -13,12 +13,12 @@ const workflowRepository = useWorkflowStore(); const workflowsState = reactive<{ loading: boolean; filterString: string; - sortByAttribute: string; + sortByAttribute: "name" | "release"; sortDesc: boolean; }>({ loading: true, filterString: "", - sortByAttribute: "name", + sortByAttribute: "release", sortDesc: true, });