<script setup lang="ts"> import type { BucketOut, BucketPermissionOut } from "@/client"; 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, 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 type { FolderTree } from "@/types/PseudoFolder"; import { useNameStore } from "@/stores/names"; import { environment } from "@/environment"; import { useS3ObjectStore } from "@/stores/s3objects"; import { filesize } from "filesize"; import BootstrapToast from "@/components/BootstrapToast.vue"; import BucketLimitProgressBar from "@/components/object-storage/BucketLimitProgressBar.vue"; const props = defineProps<{ active: boolean; bucket: BucketOut; loading: boolean; deletable: boolean; }>(); const publicCheckbox = ref<HTMLInputElement | undefined>(undefined); const randomIDSuffix = Math.random().toString(16).substring(2, 8); const permissionRepository = useBucketStore(); const userRepository = useAuthStore(); const nameRepository = useNameStore(); 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], ); const subFolder = computed<FolderTree>(() => { const subFolders: Record<string, FolderTree> = {}; if (permission.value?.file_prefix != null) { subFolders[permission.value.file_prefix] = { subFolders: {}, files: [] }; } return { subFolders: subFolders, files: [] }; }); const bucketMeta = computed<[number, number]>(() => objectRepository.getMeta(props.bucket.name), ); const emit = defineEmits<{ (e: "delete-bucket", bucketName: string): void; }>(); function permissionDeleted() { router.push({ name: "buckets" }); } function toggleBucketPublicState() { requestState.loading = true; bucketRepository .updatePublicState(props.bucket.name, !props.bucket.public) .then(() => { successToast?.show(); }) .catch((err) => { requestState.error = err.toString(); if (publicCheckbox.value) { publicCheckbox.value.checked = props.bucket.public; } errorToast?.show(); }) .finally(() => { requestState.loading = false; }); } onMounted(() => { if (!props.loading) { new Tooltip("#tooltip-" + randomIDSuffix); new Tooltip(`#own-bucket-icon-${randomIDSuffix}`); new Tooltip(`#shared-bucket-icon-${randomIDSuffix}`); new Tooltip(`#public-bucket-icon-${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" :bucket-name="props.bucket.name" :sub-folders="subFolder" :edit-user-permission="permission" :readonly="true" :editable="false" :deletable="true" :back-modal-id="undefined" @permission-deleted="permissionDeleted" /> <bucket-detail-modal v-if="props.active" :modalId="`view-bucket-details-modal-${randomIDSuffix}`" :bucket="props.bucket" :edit-user-permission="permission" /> <div class="mt-2 mb-2"> <div v-if="loading" class="list-group-item list-group-item-action text-nowrap rounded" > <span class="placeholder w-75"></span> </div> <template v-else> <router-link class="list-group-item list-group-item-action d-flex justify-content-between align-items-center" :class="{ active: props.active, 'hover-shadow': !props.active, rounded: !props.active, 'rounded-top': props.active, }" :aria-current="props.active" :to="{ name: 'bucket', params: { bucketName: bucket.name, subFolders: [] }, }" > <span class="text-truncate flex-grow-3"> {{ bucket.name }} </span> <div class="text-nowrap"> <font-awesome-icon :hidden="!bucket.public" :id="`public-bucket-icon-${randomIDSuffix}`" icon="fa-solid fa-earth-americas" class="me-2" data-bs-toogle="tooltip" data-bs-title="Public Bucket" /> <font-awesome-icon :hidden="bucket.owner_id !== userRepository.currentUID" :id="`own-bucket-icon-${randomIDSuffix}`" icon="fa-solid fa-user" :class="{ 'me-2': props.active || permission || ( permissionRepository.bucketPermissionsMapping[bucket.name] ?? [] ).length > 0, }" data-bs-toogle="tooltip" data-bs-title="Own Bucket" /> <font-awesome-icon :hidden=" !permission && (permissionRepository.bucketPermissionsMapping[bucket.name] ?? []) .length === 0 " :id="`shared-bucket-icon-${randomIDSuffix}`" icon="fa-solid fa-users" :class="{ 'me-2': props.active }" data-bs-toogle="tooltip" data-bs-title="Shared Bucket" /> <font-awesome-icon v-if="props.active && !permission && props.deletable" icon="fa-solid fa-trash" class="delete-icon me-2" @click="emit('delete-bucket', bucket.name)" /> <font-awesome-icon class="info-icon" data-bs-toggle="modal" :data-bs-target="`#view-bucket-details-modal-${randomIDSuffix}`" v-if="props.active" icon="fa-solid fa-circle-info" /> </div> </router-link> <div :hidden="!props.active" class="px-2 rounded-bottom border shadow-sm border-3 border-top-0 border-primary" > <div v-if="permission" class="ms-1 pt-1 text-info">Foreign Bucket</div> <table class="table table-sm table-borderless mb-0 align-middle"> <tbody> <tr v-if="permission"> <th scope="row" class="fw-bold">Permission:</th> <td> <a href="#" data-bs-toggle="modal" :data-bs-target="'#view-permission-modal' + randomIDSuffix" >Show</a > </td> </tr> <tr v-if="permission"> <th scope="row" class="fw-bold">Owner:</th> <td class="text-truncate"> {{ nameRepository.getName(bucket.owner_id) }} </td> </tr> <tr v-if=" ( permissionRepository.bucketPermissionsMapping[bucket.name] ?? [] ).length > 0 " > <th scope="row" class="fw-bold">Permissions:</th> <td> <a href="#" data-bs-toggle="modal" data-bs-target="#permission-list-modal" >Show</a > </td> </tr> <tr> <th scope="row" class="fw-bold">Created:</th> <td> <span :id="'tooltip-' + randomIDSuffix" data-bs-toggle="tooltip" :data-bs-title=" dayjs.unix(bucket.created_at).format('DD.MM.YYYY HH:mm:ss') " > {{ dayjs.unix(bucket.created_at).fromNow() }} </span> </td> </tr> <tr> <th scope="row" class="fw-bold">Objects:</th> <td v-if="bucket.object_limit"> <bucket-limit-progress-bar :maximum="bucket.object_limit" :current-val="bucketMeta[0]" :label="bucketMeta[0] + '/' + bucket.object_limit" /> </td> <td v-else>{{ bucketMeta[0] }}</td> </tr> <tr> <th scope="row" class="fw-bold">Size:</th> <td v-if="bucket.size_limit"> <bucket-limit-progress-bar :maximum="1024 * bucket.size_limit" :current-val="bucketMeta[1]" :label=" filesize(bucketMeta[1]) + '/' + filesize(1024 * bucket.size_limit) " /> </td> <td v-else>{{ filesize(bucketMeta[1]) }}</td> </tr> <tr> <th scope="row"> <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> <a v-if="bucket.public" target="_blank" :href="environment.S3_URL + '/' + bucket.name" >Link</a > <span v-else>Disabled</span> </td> </tr> </tbody> </table> </div> </template> </div> </template> <style scoped> .delete-icon { color: white; } .delete-icon:hover { color: var(--bs-danger) !important; } .info-icon { color: white; } .info-icon:hover { color: var(--bs-info) !important; } </style>