Skip to content
Snippets Groups Projects
Select Git revision
  • 4ee1911890f537408b4df6e6e050643dfeb25d65
  • main default protected
  • Test/xxxx-shaclForm
  • dev protected
  • Issue/3240-changeMetadataUpdateButton
  • Issue/3242-UserDeletionOnProfile
  • Issue/3245-maintenanceAPIUpdate
  • Issue/3212-AddEmailforexpiredToken
  • Hotfix/xxxx-podmanComposeFix
  • Issue/3135-newUiUnitTests
  • Issue/3187-VersionStorage
  • Issue/3179-sortDataPublicationServiceList
  • Issue/3193-processingOfPersonalDataConsent
  • Hotfix/2486-ImprovedGitLabTokenHandling
  • Issue/2486-ImprovedGitLabTokenHandling
  • Issue/2511-moveResourceInfoToModalView
  • Issue/2450-AdminPage
  • Issue/3222-FairDOFeedbackChanges
  • Issue/1560-VisibilityAndOrder
  • Issue/3082-visualizeFDOs
  • Issue/3203-brokenCoscineSurfacePage
  • v3.28.0
  • v3.27.0
  • v3.26.1
  • v3.26.0
  • v3.25.0
  • v3.24.0
  • v3.23.0
  • v3.22.0
  • v3.21.0
  • v3.20.0
  • v3.19.2
  • v3.19.1
  • v3.19.0
  • v3.18.0
  • v3.17.2
  • v3.17.1
  • v3.17.0
  • v3.16.1
  • v3.16.0
  • v3.15.6
41 results

QuotaTable.vue

Blame
  • Code owners
    Assign users and groups as approvers for specific file changes. Learn more.
    QuotaTable.vue 12.52 KiB
    <template>
      <div id="quotatable">
        <CoscineHeadline :headline="$t('page.admin.projectQuotaHeadline')" />
    
        <!-- Filter Hidden Resources -->
        <b-form-checkbox
          v-if="project"
          v-model="showOnlyEnabledResources"
          class="mt-3 mb-1"
          switch
        >
          {{ $t("page.admin.displayHiddenResources") }}
        </b-form-checkbox>
    
        <!-- QuotaTable -->
        <b-table
          id="project_quota_table"
          :fields="headers"
          :items="filteredProjectQuotas"
          :busy="!project || isWaitingForResponse"
          :locale="$i18n.locale"
          :sort-by.sync="sortBy"
          :sort-desc="true"
          :show-empty="true"
          :empty-text="$t('page.admin.projectNotSelected')"
          sticky-header="100%"
          no-border-collapse
          sort-icon-right
          striped
          bordered
          outlined
          hover
          head-variant="dark"
        >
          <template #thead-top>
            <b-tr>
              <b-th colspan="1" />
              <b-th colspan="3">{{ $t("page.admin.headers.projectQuota") }}</b-th>
              <b-th colspan="2">{{ $t("page.admin.headers.resourceQuota") }}</b-th>
              <b-th colspan="2" />
            </b-tr>
          </template>
    
          <!-- Quota Table - Resource Type Column -->
          <template #cell(iDisplayName)="row">
            <b :class="row.item.isEnabled ? '' : 'text-secondary'">
              {{
                row.item.iDisplayName
                  ? row.item.iDisplayName
                  : row.item.resourceType
              }}
            </b>
          </template>
    
          <!-- Quota Table - Maximum Quota Column -->
          <template #cell(maximum)="row">
            <strong>
              {{
                !row.item.isQuotaAvailable
                  ? $t("default.none")
                  : $t("default.gb", { number: toGiB(row.item.maximum) })
              }}
            </strong>
          </template>
    
          <!-- Quota Table - Allocated Quota Column -->
          <template #cell(allocated)="row">
            <span class="text-secondary">
              {{
                !row.item.isQuotaAvailable
                  ? $t("default.none").toString()
                  : $t("default.gb", {
                      number: toGiB(row.item.allocated).toString(),
                    })
              }}
            </span>
          </template>
    
          <!-- Quota Table - Free Quota Column -->
          <template #cell(free)="row">
            {{
              !row.item.isQuotaAvailable
                ? $t("default.none")
                : $t("default.gb", {
                    number: row.item.free.value,
                  })
            }}
          </template>
    
          <!-- Quota Table - Total Used Resource Column -->
          <template #cell(totalUsed)="row">
            {{
              !row.item.isQuotaAvailable
                ? $t("default.none")
                : formatUsed(row.item.totalUsed)
            }}
          </template>
    
          <!-- Quota Table - Total Reserved Resource Column -->
          <template #cell(totalReserved)="row">
            {{
              !row.item.isQuotaAvailable
                ? $t("default.none")
                : $t("default.gb", { number: toGiB(row.item.totalReserved) })
            }}
          </template>
    
          <!-- Quota Table - New Quota Column -->
          <template #cell(newQuota)="row">
            <b-form-input
              v-if="row.item.isQuotaAvailable === true"
              id="quotaInputField"
              v-model.number="newQuotas[row.item.resourceType.id]"
              type="number"
              aria-describedby="projectHelp"
              :placeholder="$t('page.admin.newQuotaInputPlaceHolder')"
              @keypress.enter.stop="saveNewQuota(project, row.item)"
            >
            </b-form-input>
          </template>
    
          <!-- Quota Table - Action Column -->
          <template #cell(action)="row">
            <div class="text-center">
              <b-button
                v-if="row.item.isQuotaAvailable === true"
                :id="`action${row.index}`"
                variant="primary"
                :disabled="!isQuotaValueValid(row.item)"
                @click.stop.prevent="saveNewQuota(project, row.item)"
              >
                {{ $t("buttons.save") }}
              </b-button>
            </div>
          </template>
    
          <!-- Quota Table - Header Row -->
          <template #head()="header">
            <span
              :id="header.label"
              :class="header.field.hint ? 'hasInfoPopover' : ''"
            >
              {{ header.label }}
            </span>
            <!-- Hint Tooltip -->
            <b-tooltip
              v-if="header.field.hint"
              :target="header.label"
              triggers="hover"
              boundary="viewport"
              placement="bottom"
              variant="light"
            >
              {{ header.field.hint }}
            </b-tooltip>
          </template>
        </b-table>
      </div>
    </template>
    
    <script lang="ts">
    import { defineComponent } from "vue";
    // import the store for current module
    import useAdminStore from "../store";
    // import the main store
    
    import useResourceStore from "@/modules/resource/store";
    import type { ExtendedProjectQuotaDto } from "../types";
    import { isNumber } from "lodash";
    import useNotificationStore from "@/store/notification";
    import { FileUtil } from "@/modules/resource/utils/FileUtil";
    import type {
      ProjectDto,
      QuotaDto,
      ResourceTypeInformationDto,
    } from "@coscine/api-client/dist/types/Coscine.Api/api";
    import { QuotaUnit } from "@/modules/resource/types";
    
    export default defineComponent({
      setup() {
        const adminStore = useAdminStore();
        const notificationStore = useNotificationStore();
        const resourceStore = useResourceStore();
    
        return { adminStore, notificationStore, resourceStore };
      },
      data() {
        return {
          newQuotas: {} as { [id: string]: number },
          sortBy: "iDisplayName",
          isWaitingForResponse: false,
          showOnlyEnabledResources: true,
          showQuotaInputHint: true,
        };
      },
    
      computed: {
        project(): ProjectDto | null | undefined {
          return this.adminStore.project;
        },
        projectQuotas(): ExtendedProjectQuotaDto[] | null {
          if (!this.project) {
            return null; // In case project can't be resolved return null
          }
          // Retrieve all project quotas (incl. for hidden resources)
          const projectQuotas = this.adminStore.projectQuotas;
          // Get all resource types
          const resourceTypes: ResourceTypeInformationDto[] | null | undefined =
            this.resourceStore.resourceTypes;
    
          if (!projectQuotas || !resourceTypes) {
            return null; // In case resource types can't be resolved return null
          }
    
          return projectQuotas.map((entry) => {
            // Find corresponding entry
            const resourceType = this.resourceTypes?.find(
              (q) => q.specificType === entry.resourceType?.specificType,
            );
    
            // Populate the extended project quota object
            const extended = {
              ...entry,
              iDisplayName: this.$t(
                `resourceTypes.${entry.resourceType?.specificType}.displayName`,
              ).toString(),
              isEnabled: resourceType?.isEnabled,
              isQuotaAvailable: resourceType?.isQuotaAvailable,
              free:
                entry.maximum && entry.allocated
                  ? {
                      value:
                        this.toGiB(entry.maximum) - this.toGiB(entry.allocated),
                      unit: QuotaUnit.GibiByte,
                    }
                  : undefined,
            } as ExtendedProjectQuotaDto;
    
            // For every resource type that has no quota available, set the value to be undefined
            if (!extended.isQuotaAvailable) {
              extended.allocated = undefined;
              extended.free = undefined;
              extended.maximum = undefined;
              extended.totalReserved = undefined;
              extended.totalUsed = undefined;
            }
    
            // Push to the newly filtered array
            return extended;
          });
        },
        filteredProjectQuotas(): ExtendedProjectQuotaDto[] | null {
          if (this.showOnlyEnabledResources && this.projectQuotas) {
            return this.projectQuotas.filter((entry) => entry.isEnabled);
          } else {
            return this.projectQuotas;
          }
        },
        resourceTypes(): ResourceTypeInformationDto[] | null | undefined {
          return this.resourceStore.resourceTypes;
        },
        headers() {
          // Define as computed property to have table
          // header text react on language changes.
          return [
            {
              label: this.$t("page.admin.headers.resourceType"),
              key: "iDisplayName",
              sortable: true,
            },
            {
              label: this.$t("page.admin.headers.maximumQuota"),
              key: "maximum",
              sortable: true,
              hint: this.$t("page.admin.headers.maximumQuotaHint"),
            },
            {
              label: this.$t("page.admin.headers.allocatedQuota"),
              key: "allocated",
              sortable: true,
              hint: this.$t("page.admin.headers.allocatedQuotaHint"),
            },
            {
              label: this.$t("page.admin.headers.freeQuota"),
              key: "free",
              sortable: true,
              hint: this.$t("page.admin.headers.freeQuotaHint"),
            },
            {
              label: this.$t("page.admin.headers.totalUsedQuota"),
              key: "totalUsed",
              sortable: true,
              hint: this.$t("page.admin.headers.totalUsedQuotaHint"),
            },
            {
              label: this.$t("page.admin.headers.totalReservedQuota"),
              key: "totalReserved",
              sortable: true,
              hint: this.$t("page.admin.headers.totalReservedQuotaHint"),
            },
            {
              label: this.$t("page.admin.headers.newQuota"),
              key: "newQuota",
              sortable: false,
              hint: this.$t("page.admin.headers.newQuotaHint"),
            },
            {
              label: this.$t("page.admin.headers.action"),
              key: "action",
              sortable: false,
            },
          ];
        },
      },
    
      created() {
        // Load list of all Resource Types if not present
        if (this.resourceStore.resourceTypes === null) {
          this.resourceStore.retrieveAllResourceTypesInformation();
        }
      },
    
      methods: {
        async saveNewQuota(
          project: ProjectDto | null | undefined,
          resourceQuota: ExtendedProjectQuotaDto,
        ) {
          if (
            project &&
            project.id &&
            resourceQuota.resourceType?.id &&
            this.isQuotaValueValid(resourceQuota)
          ) {
            const quota: QuotaDto = {
              value: this.newQuotas[resourceQuota.resourceType?.id],
              unit: QuotaUnit.GibiByte,
            };
            if (isNumber(quota.value) && quota.value >= 0) {
              this.isWaitingForResponse = true;
              const success = await this.adminStore.updateProjectQuota(
                project.id,
                resourceQuota.resourceType.id,
                quota,
              );
              if (success) {
                // On Success
                delete this.newQuotas[resourceQuota.resourceType.id];
                if (this.project?.id) {
                  // Refresh the quota values
                  await this.adminStore.retrieveProjectAndQuotas(this.project.id);
                }
                this.notificationStore.postNotification({
                  title: this.$t("page.admin.toast.success.title").toString(),
                  body: this.$t("page.admin.toast.success.body", {
                    resourceType: resourceQuota.iDisplayName,
                    projectName: this.project?.displayName,
                    newQuota: quota.value,
                  }).toString(),
                });
              } else {
                // On Failure
                this.notificationStore.postNotification({
                  title: this.$t("page.admin.toast.fail.title").toString(),
                  body: this.$t("page.admin.toast.fail.body").toString(),
                  variant: "warning",
                });
              }
              this.isWaitingForResponse = false;
            }
          }
        },
        isQuotaValueValid(projectQuotaObject: ExtendedProjectQuotaDto): boolean {
          if (
            projectQuotaObject.resourceType?.id &&
            projectQuotaObject.totalReserved
          ) {
            return (
              // Value must be a positive number, that is greater
              // than the total reserved resource quota.
              isNumber(this.newQuotas[projectQuotaObject.resourceType.id]) &&
              this.newQuotas[projectQuotaObject.resourceType.id] >= 0 &&
              this.newQuotas[projectQuotaObject.resourceType.id] >=
                this.toGiB(projectQuotaObject.totalReserved)
            );
          } else return false;
        },
        /**
         * Converts a quota value to GiB (Gibibytes).
         * @param quota - The quota to be converted.
         * @returns {number} - The quota value in GiB.
         */
        toGiB(quota: QuotaDto): number {
          // Almost all displayed values inside the html must be shown in GiB, making this method necessary to ensure that's always the case.
          return FileUtil.convertCapacityUnits(quota, QuotaUnit.GibiByte);
        },
        formatUsed(quota: QuotaDto): string {
          return FileUtil.formatBytes(
            FileUtil.convertCapacityUnits(quota, QuotaUnit.Byte),
          );
        },
      },
    });
    </script>