diff --git a/.eslintrc.js b/.eslintrc.js
index ba46a82a308290fb6bcaae957b77f0f22cb63498..5fea712a1c54b99da56a1ef7e496477520919bc2 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -24,7 +24,8 @@ module.exports = {
"error",
{ "allowWholeFile": true }
],
- "@typescript-eslint/no-empty-interface": 1, // empty Interfaces will be only warnings for now.
+ // ToDo: REMOVE ONCE error AND pid MODULE'S STORE STATES ARE IMPLEMENTED
+ "@typescript-eslint/no-empty-interface": 0, // Empty Interfaces error/warning will be ignored for now.
"@typescript-eslint/no-unused-vars": ["warn", { "argsIgnorePattern": "^_" }], // will only ignore variables that start with an underscore _
"vue/multi-word-component-names": "off"
},
diff --git a/src/App.vue b/src/App.vue
index 6d591d9f8587e3f0194b984206d94ecf2cd86cb2..318fd7e27dbe536a452f4597cef7da30a40cc05e 100644
--- a/src/App.vue
+++ b/src/App.vue
@@ -75,4 +75,12 @@ export default defineComponent({
});
</script>
-<style scoped></style>
+<style>
+.h-divider {
+ margin-top: 5px;
+ margin-bottom: 10px;
+ height: 1px;
+ width: 100%;
+ border-top: 1px solid #bebbbb;
+}
+</style>
diff --git a/src/modules/admin/AdminModule.vue b/src/modules/admin/AdminModule.vue
index b58932377881d2930f291f7748ee316f5cd7565b..9ac2177af6e705852d0418d2a2ae7dcdcd7a3c4f 100644
--- a/src/modules/admin/AdminModule.vue
+++ b/src/modules/admin/AdminModule.vue
@@ -1,6 +1,6 @@
<template>
<div>
- <router-view v-if="moduleIsReady" />
+ <router-view v-if="true" />
</div>
</template>
@@ -22,7 +22,7 @@ export default defineComponent({
computed: {
moduleIsReady(): boolean {
- return true;
+ return this.adminStore.project !== null;
},
},
@@ -32,8 +32,9 @@ export default defineComponent({
methods: {
async initialize() {
- // do initialization stuff (e.g. API calls, element loading, etc.)
- // ...
+ // await Promise.all([
+ // //this.adminStore.retrieveProjectQuotas(this.adminStore.project.projectGuid),
+ // ])
},
},
});
diff --git a/src/modules/admin/components/QuotaTable.vue b/src/modules/admin/components/QuotaTable.vue
new file mode 100644
index 0000000000000000000000000000000000000000..d5bc6e84b88b0f42c7494f8a98bc051891356e75
--- /dev/null
+++ b/src/modules/admin/components/QuotaTable.vue
@@ -0,0 +1,271 @@
+<template>
+ <div id="quotatable">
+ <CoscineHeadline :headline="$t('page.admin.projectQuotaHeadline')" />
+
+ <!-- Filter Hidden Resources -->
+ <b-form-checkbox
+ v-if="project"
+ v-model="showEnabledResources"
+ 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="false"
+ :show-empty="true"
+ :empty-text="$t('page.admin.projectNotSelected')"
+ fixed
+ sticky-header="100%"
+ no-border-collapse
+ sort-icon-right
+ striped
+ bordered
+ outlined
+ hover
+ head-variant="dark"
+ >
+ <!-- Quota Table - Resource Type Column -->
+ <template #cell(resourceType)="row">
+ {{
+ row.item.iDisplayName ? row.item.iDisplayName : row.item.resourceType
+ }}
+ </template>
+
+ <!-- Quota Table - Reserved Quota Column -->
+ <template #cell(currentQuota)="row">
+ {{
+ row.item.isQuotaAvailable === false
+ ? $t("default.none")
+ : $t("page.admin.gb", { number: row.item.quota })
+ }}
+ </template>
+
+ <!-- Quota Table - Resource Type Column -->
+ <template #cell(allocatedQuota)="row">
+ {{
+ row.item.isQuotaAvailable === false
+ ? $t("default.none")
+ : $t("page.admin.gb", { number: row.item.allocated })
+ }}
+ </template>
+
+ <!-- Quota Table - New Quota Column -->
+ <template #cell(newQuota)="row">
+ <b-form-input
+ v-if="row.item.isQuotaAvailable !== false"
+ v-model.number="newQuotas[row.item.quotaId]"
+ type="number"
+ aria-describedby="projectHelp"
+ :placeholder="$t('page.admin.newQuotaInputPlaceHolder')"
+ >
+ </b-form-input>
+ </template>
+
+ <!-- Quota Table - Action Column -->
+ <template #cell(action)="row">
+ <div class="text-center">
+ <b-button
+ v-if="row.item.isQuotaAvailable !== false"
+ :id="`action${row.index}`"
+ variant="primary"
+ :disabled="
+ !newQuotas[row.item.quotaId] || newQuotas[row.item.quotaId] < 0
+ "
+ @click.stop.prevent="
+ saveNewQuota(row.item.quotaId, row.item.iDisplayName)
+ "
+ >
+ {{ $t("buttons.save") }}
+ </b-button>
+ </div>
+ </template>
+
+ <!-- Quota Table - Header Row -->
+ <template #head()="header">
+ <span :id="header.label">
+ {{ header.label }}
+ <b-icon v-if="header.field.hint" icon="info-circle" />
+ </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-demi";
+// import the store for current module
+import useAdminStore from "../store";
+// import the main store
+
+import type { ProjectObject } from "@coscine/api-client/dist/types/Coscine.Api.Admin";
+import type { UpdateQuotaParameterObject } from "@coscine/api-client/dist/types/Coscine.Api.Admin";
+import useResourceStore from "@/modules/resource/store";
+import type { ExtendedProjectQuotaObject } from "../types";
+import { isNumber } from "lodash";
+import useNotificationStore from "@/store/notification";
+
+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,
+ showEnabledResources: true,
+ };
+ },
+
+ computed: {
+ project(): ProjectObject | null {
+ return this.adminStore.project;
+ },
+ projectQuotas(): ExtendedProjectQuotaObject[] | null {
+ if (this.project) {
+ // Retrieve all project quotas (incl. for hidden resources)
+ const projectQuotas = this.project.quotas;
+ // Get only enabled resource types
+ const enabledResourceTypes = this.resourceStore.resourceTypes;
+ if (projectQuotas && enabledResourceTypes) {
+ let projectQuotasExtended = [] as ExtendedProjectQuotaObject[];
+ projectQuotas.forEach((entry) => {
+ // Find corresponding entry from enabled resource types
+ const enabledResource = enabledResourceTypes.find(
+ (q) => q.displayName === entry.resourceType
+ );
+ let extended = { iDisplayName: "" } as ExtendedProjectQuotaObject;
+ Object.assign(extended, entry);
+ // Replace name with pretty resource name from i18n
+ extended.iDisplayName = this.$t(
+ `resourceTypes.${entry.resourceType}.displayName`
+ ).toString();
+ extended.isEnabled = enabledResource
+ ? enabledResource.isEnabled
+ : undefined;
+ extended.isQuotaAvailable = enabledResource
+ ? enabledResource.isQuotaAvailable
+ : undefined;
+ // Push to the newly filtered array
+ projectQuotasExtended.push(extended);
+ });
+ return projectQuotasExtended;
+ } else {
+ return projectQuotas as ExtendedProjectQuotaObject[];
+ }
+ } else {
+ return null;
+ }
+ },
+ filteredProjectQuotas(): ExtendedProjectQuotaObject[] | null {
+ if (this.showEnabledResources && this.projectQuotas) {
+ return this.projectQuotas.filter((entry) => entry.isEnabled);
+ } else {
+ return this.projectQuotas;
+ }
+ },
+ headers() {
+ // Define as computed property to have table
+ // header text react on language changes.
+ return [
+ {
+ label: this.$t("page.admin.headers.resourceType"),
+ key: "resourceType",
+ sortable: true,
+ },
+ {
+ label: this.$t("page.admin.headers.currentQuota"),
+ key: "currentQuota",
+ sortable: true,
+ hint: this.$t("page.admin.headers.currentQuotaHint"),
+ },
+ {
+ label: this.$t("page.admin.headers.allocatedQuota"),
+ key: "allocatedQuota",
+ sortable: true,
+ hint: this.$t("page.admin.headers.allocatedQuotaHint"),
+ },
+ {
+ 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 Enabled Resource Types if not present
+ if (this.resourceStore.resourceTypes === null) {
+ this.resourceStore.retrieveResourceTypes();
+ }
+ },
+
+ methods: {
+ async saveNewQuota(quotaId: string, resourceDisplayName: string) {
+ const updatedQuota: UpdateQuotaParameterObject = {
+ quotaId: quotaId,
+ quota: this.newQuotas[quotaId],
+ };
+ if (isNumber(updatedQuota.quota) && updatedQuota.quota >= 0) {
+ this.isWaitingForResponse = true;
+ const success = await this.adminStore.updateProjectQuota(updatedQuota);
+ if (success) {
+ // On Success
+ delete this.newQuotas[quotaId];
+ if (this.project && this.project.guid) {
+ // Refresh the quota values
+ await this.adminStore.retrieveProjectQuotas(this.project.guid);
+ }
+ this.notificationStore.postNotification({
+ title: this.$t("page.admin.toast.success.title").toString(),
+ body: this.$t("page.admin.toast.success.body", {
+ resourceType: resourceDisplayName,
+ projectName: this.project?.name,
+ newQuota: updatedQuota.quota,
+ }).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;
+ }
+ },
+ },
+});
+</script>
diff --git a/src/modules/admin/i18n/de.ts b/src/modules/admin/i18n/de.ts
index 3b562529948ccd64874130f0624ea6c24903ce35..036e6a1003a60d6329b82de83b93aeec6718b04e 100644
--- a/src/modules/admin/i18n/de.ts
+++ b/src/modules/admin/i18n/de.ts
@@ -1,15 +1,58 @@
import VueI18n from "vue-i18n";
export default {
- /*
- --------------------------------------------------------------------------------------
- GERMAN STRINGS
- --------------------------------------------------------------------------------------
- */
page: {
admin: {
- title: "Adminseite",
- description: "Das ist die @:page.admin.title des Coscine UIv2 Apps",
+ headline: "Adminseite",
+
+ projectInputPlaceholder: "Projekt-GUID oder -Slug eingeben",
+
+ projectFound: "Projekt gefunden",
+ projectNotSelected: "Kein Projekt ausgewählt",
+
+ form: {
+ labelSymbol: ":",
+
+ projectName: "Projektname",
+ projectNameLabel:
+ "@:(page.admin.form.projectName)@:(page.admin.form.labelSymbol)",
+
+ projectShortName: "Anzeigename",
+ projectShortNameLabel:
+ "@:(page.admin.form.projectShortName)@:(page.admin.form.labelSymbol)",
+
+ projectGuid: "GUID",
+ projectGuidLabel:
+ "@:(page.admin.form.projectGuid)@:(page.admin.form.labelSymbol)",
+ },
+ projectQuotaHeadline: "Quota",
+ displayHiddenResources: "Nur aktivierte Ressourcentypen anzeigen",
+
+ headers: {
+ resourceType: "Ressourcentyp",
+ currentQuota: "Aktuelles Projekt Reserviertes Quota",
+ currentQuotaHint:
+ "Dieser Wert gibt die Speicherobergrenze an, die für den ausgewählten Ressourcentyp im Projekt verfügbar ist.",
+ allocatedQuota: "Reservierte Gesamtquota für Ressourcen",
+ allocatedQuotaHint:
+ "Dieser Wert gibt den aktuellen Speicherplatz an, der von allen vorhandenen Ressourcen desselben Ressourcentyps reserviert wird.",
+ newQuota: "Neue Projektquota",
+ newQuotaHint:
+ 'Mit diesem Wert wird "@:(page.admin.headers.currentQuota)" angepasst',
+ action: "Aktion",
+ },
+ newQuotaInputPlaceHolder: "Quota in GB angeben",
+ gb: "{number} GB",
+ toast: {
+ success: {
+ title: "Quota erfolgreich geändert",
+ body: "{resourceType} Quota für Projekt {projectName} gesetzt auf {newQuota} GB",
+ },
+ fail: {
+ title: "Aktualisierung fehlgeschlagen",
+ body: "Aktualisierung der Quota fehlgeschlagen.",
+ },
+ },
},
},
} as VueI18n.LocaleMessageObject;
diff --git a/src/modules/admin/i18n/en.ts b/src/modules/admin/i18n/en.ts
index 55dc51af3220b6d6a3b28a8c29226fe6ad33fd74..c43f68768933308975a952f22a259e106f05513d 100644
--- a/src/modules/admin/i18n/en.ts
+++ b/src/modules/admin/i18n/en.ts
@@ -1,15 +1,58 @@
import VueI18n from "vue-i18n";
export default {
- /*
- --------------------------------------------------------------------------------------
- ENGLISH STRINGS
- --------------------------------------------------------------------------------------
- */
page: {
admin: {
- title: "Admin Page",
- description: "This is the @:page.admin.title for the Coscine UIv2 App",
+ headline: "Quota Admin Panel",
+
+ projectInputPlaceholder: "Enter a project's Slug or GUID",
+
+ projectFound: "Project found",
+ projectNotSelected: "No project selected",
+
+ form: {
+ labelSymbol: ":",
+
+ projectName: "Project Name",
+ projectNameLabel:
+ "@:(page.admin.form.projectName)@:(page.admin.form.labelSymbol)",
+
+ projectShortName: "Display Name",
+ projectShortNameLabel:
+ "@:(page.admin.form.projectShortName)@:(page.admin.form.labelSymbol)",
+
+ projectGuid: "GUID",
+ projectGuidLabel:
+ "@:(page.admin.form.projectGuid)@:(page.admin.form.labelSymbol)",
+ },
+ projectQuotaHeadline: "Quotas",
+ displayHiddenResources: "Display enabled Resource Types only",
+
+ headers: {
+ resourceType: "Resource Type",
+ currentQuota: "Current Project Reserved Quota",
+ currentQuotaHint:
+ "This value indicates the storage upper limit, that is available for the selected resource type in the project.",
+ allocatedQuota: "Total Resource Reserved Quota",
+ allocatedQuotaHint:
+ "This value indicates the current storage reserved by all existing resources of the same resource type.",
+ newQuota: "New Project Quota",
+ newQuotaHint:
+ 'This value will change the "@:(page.admin.headers.currentQuota)"',
+ action: "Action",
+ },
+ newQuotaInputPlaceHolder: "Enter Quota in GB",
+ gb: "{number} GB",
+ toast: {
+ success: {
+ title: "Quota successfully changed",
+ body: "{resourceType} quota for project {projectName} set to {newQuota} GB",
+ },
+ fail: {
+ title: "Quota update failed",
+ body: "Updating quota for the selected resource type failed.",
+ },
+ },
},
},
} as VueI18n.LocaleMessageObject;
diff --git a/src/modules/admin/pages/Admin.vue b/src/modules/admin/pages/Admin.vue
index 37f4a8604764916be343e65fae3959832935a40d..dbc73a84659720a9f48484aa822d7318b5f2dfa7 100644
--- a/src/modules/admin/pages/Admin.vue
+++ b/src/modules/admin/pages/Admin.vue
@@ -1,16 +1,75 @@
<template>
- <div>
- <section
- class="container flex flex-col items-center px-5 py-12 mx-auto text-gray-600 body-font md:flex-row"
+ <div id="admin">
+ <CoscineHeadline :headline="$t('page.admin.headline')" />
+ <!-- Project input Tooltip -->
+ <b-form id="project_request_form" @submit.stop.prevent="queryProject()">
+ <b-input-group class="mt-3">
+ <b-form-input
+ v-model="projectString"
+ :placeholder="$t('page.admin.projectInputPlaceholder')"
+ >
+ </b-form-input>
+ <!-- Select Button -->
+ <b-input-group-append>
+ <b-button variant="primary" @click="queryProject()">
+ {{ $t("buttons.submit") }}
+ </b-button>
+ </b-input-group-append>
+ </b-input-group>
+ </b-form>
+ <!-- Project Query Status -->
+ <span class="d-block h6 my-3">
+ {{
+ project && project.guid
+ ? $t("page.admin.projectFound")
+ : $t("page.admin.projectNotSelected")
+ }}
+ </span>
+ <!-- Project Container -->
+ <!-- Project Name -->
+ <CoscineFormGroup
+ label-for="ProjectName"
+ :label="$t('page.admin.form.projectNameLabel')"
+ label-cols-sm="2"
+ label-align-sm="left"
>
- <div>
- <CoscineHeadline :headline="$t('page.admin.title')" />
- <p class="mb-8 leading-relaxed dark:text-white">
- {{ $t("page.admin.description") }}
- </p>
- <img alt="From Coscine Old" src="@/assets/images/Admin.png" />
- </div>
- </section>
+ <b-form-input
+ id="project_name"
+ :value="project ? project.name : ''"
+ readonly
+ />
+ </CoscineFormGroup>
+ <!-- Project Short Name -->
+ <CoscineFormGroup
+ label-for="ProjectShortName"
+ :label="$t('page.admin.form.projectShortNameLabel')"
+ label-cols-sm="2"
+ label-align-sm="left"
+ >
+ <b-form-input
+ id="project_name"
+ :value="project ? project.name : ''"
+ readonly
+ />
+ </CoscineFormGroup>
+ <!-- Project Guid -->
+ <CoscineFormGroup
+ label-for="ProjectGuid"
+ :label="$t('page.admin.form.projectGuidLabel')"
+ label-cols-sm="2"
+ label-align-sm="left"
+ >
+ <b-form-input
+ id="project_guid"
+ :value="project ? project.guid : ''"
+ readonly
+ />
+ </CoscineFormGroup>
+
+ <div class="h-divider" />
+
+ <!-- Quota Table -->
+ <QuotaTable />
</div>
</template>
@@ -22,16 +81,42 @@ import CoscineHeadline from "@/components/coscine/CoscineHeadline.vue";
import useAdminStore from "../store";
// import the main store
import useMainStore from "@/store/index";
+import type { ProjectObject } from "@coscine/api-client/dist/types/Coscine.Api.Admin";
+import QuotaTable from "../components/QuotaTable.vue";
export default defineComponent({
components: {
CoscineHeadline,
+ QuotaTable,
},
+
setup() {
const mainStore = useMainStore();
const adminStore = useAdminStore();
return { mainStore, adminStore };
},
+
+ data() {
+ return {
+ projectString: "",
+ };
+ },
+
+ computed: {
+ project(): ProjectObject | null {
+ return this.adminStore.project;
+ },
+ },
+
+ methods: {
+ async queryProject() {
+ if (!this.projectString.trim()) {
+ this.adminStore.project = null;
+ } else {
+ await this.adminStore.retrieveProjectQuotas(this.projectString);
+ }
+ },
+ },
});
</script>
diff --git a/src/modules/admin/routes.ts b/src/modules/admin/routes.ts
index 5c4893ea379a2188de13dfd6f63fc5dc4fe6207f..cf880d24af2bd8db63acda199d18168f0cdaa8cc 100644
--- a/src/modules/admin/routes.ts
+++ b/src/modules/admin/routes.ts
@@ -11,7 +11,6 @@ export const AdminRoutes: RouteConfig[] = [
component: AdminModule,
// only authenticated users can access admin
meta: {
- breadCrumb: "admin",
requiresAdmin: true,
requiresAuth: true,
i18n: AdminI18nMessages,
@@ -21,6 +20,9 @@ export const AdminRoutes: RouteConfig[] = [
path: "/",
name: "admin",
component: Admin,
+ meta: {
+ breadCrumb: "admin",
+ },
},
],
},
diff --git a/src/modules/admin/store.ts b/src/modules/admin/store.ts
index 63c995bba484d081e0b541d20e94424b738d25ee..c07f3ccfaa98ca511253ddf38e281999f9d166c9 100644
--- a/src/modules/admin/store.ts
+++ b/src/modules/admin/store.ts
@@ -1,5 +1,10 @@
import { defineStore } from "pinia";
import { AdminState } from "./types";
+import { AdminApi } from "@coscine/api-client";
+import type { UpdateQuotaParameterObject } from "@coscine/api-client/dist/types/Coscine.Api.Admin";
+
+import useNotificationStore from "@/store/notification";
+import type { AxiosError } from "axios";
/*
Store variable name is "this.<id>Store"
@@ -13,29 +18,45 @@ export const useAdminStore = defineStore({
STATES
--------------------------------------------------------------------------------------
*/
- state: (): AdminState => ({}),
-
+ state: (): AdminState => ({
+ project: null,
+ }),
/*
--------------------------------------------------------------------------------------
GETTERS
--------------------------------------------------------------------------------------
Synchronous code only.
-
+
In a component use as e.g.:
- :label = "this.adminStore.<getter_name>;
+ :label = "this.projectStore.<getter_name>;"
*/
getters: {},
- /*
- --------------------------------------------------------------------------------------
- ACTIONS
- --------------------------------------------------------------------------------------
- Asynchronous & Synchronous code comes here (e.g. API calls and VueX mutations).
- To change a state use an action.
- In a component use as e.g.:
- @click = "this.adminStore.<action_name>();
- */
- actions: {},
+ actions: {
+ async retrieveProjectQuotas(projectString: string) {
+ const notificationStore = useNotificationStore();
+ try {
+ const apiResponse = await AdminApi.adminGetProject(projectString);
+ this.project = apiResponse.data;
+ } catch (error) {
+ // Handle other Status Codes
+ notificationStore.postApiErrorNotification(error as AxiosError);
+ }
+ },
+ async updateProjectQuota(
+ quota: UpdateQuotaParameterObject
+ ): Promise<boolean> {
+ const notificationStore = useNotificationStore();
+ try {
+ await AdminApi.adminUpdateQuota(quota);
+ return true;
+ } catch (error) {
+ // Handle other Status Codes
+ notificationStore.postApiErrorNotification(error as AxiosError);
+ return false;
+ }
+ },
+ },
});
export default useAdminStore;
diff --git a/src/modules/admin/types.ts b/src/modules/admin/types.ts
index 7d6b0eaf0584c97fbfd557228abbc61d6ebad86d..9dc68e1da0d914fd1a93f0ebf859359917105cd1 100644
--- a/src/modules/admin/types.ts
+++ b/src/modules/admin/types.ts
@@ -1,7 +1,14 @@
+import type {
+ ProjectObject,
+ ProjectQuotaObject,
+} from "@coscine/api-client/dist/types/Coscine.Api.Admin";
+
+export interface ExtendedProjectQuotaObject extends ProjectQuotaObject {
+ iDisplayName?: string;
+ isQuotaAvailable?: boolean;
+ isEnabled?: boolean;
+}
+
export interface AdminState {
- /*
- --------------------------------------------------------------------------------------
- STATE TYPE DEFINITION
- --------------------------------------------------------------------------------------
- */
+ project: ProjectObject | null;
}
diff --git a/src/modules/project/ProjectModule.vue b/src/modules/project/ProjectModule.vue
index fa56c2859ac0b828a45ec672bb173717fcbc551c..1faf7ff58ef07370fbf0633c0f44a067241e7e03 100644
--- a/src/modules/project/ProjectModule.vue
+++ b/src/modules/project/ProjectModule.vue
@@ -90,9 +90,11 @@ export default defineComponent({
if (this.projectStore.currentQuotas === null) {
this.projectStore.retrieveQuotas(this.project);
}
+ // Load list of Enabled Resource Types if not present
if (this.resourceStore.resourceTypes === null) {
this.resourceStore.retrieveResourceTypes();
}
+ // Load list of all Project Role Types if not present
if (this.projectStore.roles === null) {
this.projectStore.retrieveRoles();
}
diff --git a/src/modules/project/i18n/de.ts b/src/modules/project/i18n/de.ts
index 56ee0ebf5c1e85573b8ad0900a3ccd5b668c00a7..130615a221000c53f620d92367e81e476d54f93b 100644
--- a/src/modules/project/i18n/de.ts
+++ b/src/modules/project/i18n/de.ts
@@ -54,23 +54,21 @@ export default {
pleaseTypeSomething:
"Bitte geben Sie einen Namen oder eine E-Mail Adresse ein",
removeSelectedInvitation:
- "Sind Sie sicher, dass Sie die Einladung von Benutzer <strong>{user}</strong> zum Projekt <strong>{projectName}</strong> zurückziehen möchten?",
+ "Sind Sie sicher, dass Sie die Einladung von Benutzer {user} zum Projekt {projectName} zurückziehen möchten?",
removeSelectedUser:
- "Sind Sie sicher, dass Sie den Benutzer <strong>{user}</strong> aus dem Projekt <strong>{projectName}</strong> entfernen möchten?",
+ "Sind Sie sicher, dass Sie den Benutzer {user} aus dem Projekt {projectName} entfernen möchten?",
deleteInvitationTitle: "Einladung Zurückziehen",
deleteUserTitle: "Benutzer entfernen",
inviteUser: "Einladung abschicken",
reInviteUser: "Erneut senden",
inviteUserText:
- "Sind Sie sicher, dass Sie den Benutzer <strong>{email}</strong> mit einer Rolle als <strong>{role}</strong> zum Projekt <strong>{projectName}</strong> einladen wollen?",
+ "Sind Sie sicher, dass Sie den Benutzer {email} mit einer Rolle als {role} zum Projekt {projectName} einladen wollen?",
inviteUserTitle: "Benutzer einladen",
inviteUserCaption: "Wählen Sie {displayName} aus",
invitedUserText:
"Eine Einladung wurde an {email} mit einer Rolle als {role} für das Projekt {projectName} verschickt.",
- invitationPendingTextTop:
- "Es wurde bereits eine Einladung an <strong>{email}</strong> gesendet.",
- invitationPendingTextBottom:
- "Wenn Sie eine neue Einladungsemail an diesen Benutzer schicken möchten, müssen Sie die vorherige Einladung über den Tab Eingeladene Benutzer löschen.",
+ invitationPendingText:
+ "Es wurde bereits eine Einladung an {email} gesendet. {br}Wenn Sie eine neue Einladungsemail an diesen Benutzer schicken möchten, müssen Sie die vorherige Einladung über den Tab Eingeladene Benutzer löschen.",
invitedUserError:
"Ein Fehler ist beim Einladen von {email} aufgetreten. Eingaben sind fehlerhaft oder der Benutzer wurde bereits eingeladen.",
deleteExternalUserError:
@@ -88,7 +86,7 @@ export default {
removeUser: "Entfernen",
searchProjectPlaceholder: "Wählen Sie ein Projekt aus...",
existingEmailInvitation:
- "Es wurde bereits eine Einladung an <strong>{email}</strong> versendet. Möchten Sie die Einladung erneut versenden?",
+ "Es wurde bereits eine Einladung an {email} versendet. Möchten Sie die Einladung erneut versenden?",
},
// ProjectPage.vue
diff --git a/src/modules/project/i18n/en.ts b/src/modules/project/i18n/en.ts
index 007f58402d301a7d8a53392c1a344f60e3717f08..91c1a032f0d2bfb126859a2bc685c409f425a696 100644
--- a/src/modules/project/i18n/en.ts
+++ b/src/modules/project/i18n/en.ts
@@ -50,23 +50,21 @@ export default {
userManagement: "@:(page.members.title)",
pleaseTypeSomething: "Please enter a name or an email address",
removeSelectedInvitation:
- "Are you sure you want to revoke the invitation for <strong>{user}</strong> to the project <strong>{projectName}</strong>?",
+ "Are you sure you want to revoke the invitation for {user} to the project {projectName}?",
removeSelectedUser:
- "Are you sure you want to remove <strong>{user}</strong> from the project <strong>{projectName}</strong>?",
+ "Are you sure you want to remove {user} from the project {projectName}?",
deleteInvitationTitle: "Revoke Invitation",
deleteUserTitle: "Remove User",
inviteUser: "Send Invitation",
reInviteUser: "Resend Invitation",
inviteUserText:
- "Are you sure you want to invite <strong>{email}</strong> with a role as <strong>{role}</strong> to the project <strong>{projectName}</strong>?",
+ "Are you sure you want to invite {email} with a role as {role} to the project {projectName}?",
inviteUserTitle: "Invite User",
inviteUserCaption: "Choose to invite {displayName}",
invitedUserText:
"An invitation has been sent to {email} with a role as {role} for project {projectName}.",
- invitationPendingTextTop:
- "An invitation has already been sent to <strong>{email}</strong>.",
- invitationPendingTextBottom:
- "If you would like to resend an invitation email to this user, you have to cancel the previous invitation via the Invited Users tab.",
+ invitationPendingText:
+ "An invitation has already been sent to {email}. {br}If you would like to resend an invitation email to this user, you have to cancel the previous invitation via the Invited Users tab.",
invitedUserError:
"An error ocurred while trying to invite {email}. Invalid input was provided or the user has already been invited.",
deleteExternalUserError:
@@ -83,7 +81,7 @@ export default {
removeUser: "Remove",
searchProjectPlaceholder: "Select a project...",
existingEmailInvitation:
- "An invitation has already been sent to <strong>{email}</strong>. Do you want to send the invitation again?",
+ "An invitation has already been sent to {email}. Do you want to send the invitation again?",
},
// ProjectPage.vue
diff --git a/src/modules/project/pages/CreateProject.vue b/src/modules/project/pages/CreateProject.vue
index 9b7f302c2bf1b245f5f50527104f507d87ef3c6c..51f6b2a7324c36f47c34a6c098cd79962d526059 100644
--- a/src/modules/project/pages/CreateProject.vue
+++ b/src/modules/project/pages/CreateProject.vue
@@ -195,12 +195,4 @@ export default defineComponent({
});
</script>
-<style scoped>
-.h-divider {
- margin-top: 5px;
- margin-bottom: 10px;
- height: 1px;
- width: 100%;
- border-top: 1px solid #bebbbb;
-}
-</style>
+<style scoped></style>
diff --git a/src/modules/project/pages/Quota.vue b/src/modules/project/pages/Quota.vue
index 990276430c31a8ef4a35177a7bf8675e7f905f7b..aaad654cd749e5f0b8f608ba3bd04518efefe287 100644
--- a/src/modules/project/pages/Quota.vue
+++ b/src/modules/project/pages/Quota.vue
@@ -439,13 +439,6 @@ input[type="range"] {
width: calc(100% - 8em);
padding-top: 0.6rem;
}
-.h-divider {
- margin-top: 5px;
- margin-bottom: 10px;
- height: 1px;
- width: 100%;
- border-top: 1px solid #bebbbb;
-}
#quotaManagement >>> .b-table-empty-row {
background-color: #f5f5f5;
}
diff --git a/src/modules/project/pages/Settings.vue b/src/modules/project/pages/Settings.vue
index a6923f79f8987f63f595e18e663f88c8dea12788..7d67d117d7d792c275d32415eb935a9b4c86e1bf 100644
--- a/src/modules/project/pages/Settings.vue
+++ b/src/modules/project/pages/Settings.vue
@@ -259,12 +259,4 @@ export default defineComponent({
});
</script>
-<style scoped>
-.h-divider {
- margin-top: 5px;
- margin-bottom: 10px;
- height: 1px;
- width: 100%;
- border-top: 1px solid #bebbbb;
-}
-</style>
+<style scoped></style>
diff --git a/src/modules/project/pages/components/modals/DeleteModal.vue b/src/modules/project/pages/components/modals/DeleteModal.vue
index 75518c90d7c26595c40809850bc864d74080dbf5..6657aafecdfe0c23b5e0242eb795fe0082963d22 100644
--- a/src/modules/project/pages/components/modals/DeleteModal.vue
+++ b/src/modules/project/pages/components/modals/DeleteModal.vue
@@ -1,25 +1,23 @@
<template>
- <div>
- <b-modal
- :visible="visible"
- :title="$t(titleKey)"
- ok-variant="danger"
- :ok-title="$t('buttons.delete')"
- :cancel-title="$t('buttons.cancel')"
- @hidden="$emit('close', $event.target.value)"
- @ok="$emit('ok', $event.target.value)"
- @cancel="$emit('close', $event.target.value)"
- >
- <div
- v-html="
- $t(descriptionKey, {
- user: selectedUser,
- projectName: selectedProject,
- })
- "
- />
- </b-modal>
- </div>
+ <b-modal
+ :visible="visible"
+ :title="$t(titleKey)"
+ ok-variant="danger"
+ :ok-title="$t('buttons.delete')"
+ :cancel-title="$t('buttons.cancel')"
+ @hidden="$emit('close', $event.target.value)"
+ @ok="$emit('ok', $event.target.value)"
+ @cancel="$emit('close', $event.target.value)"
+ >
+ <i18n :path="descriptionKey" tag="span">
+ <template #user>
+ <b>{{ selectedUser }}</b>
+ </template>
+ <template #projectName>
+ <b>{{ selectedProject }}</b>
+ </template>
+ </i18n>
+ </b-modal>
</template>
<script lang="ts">
diff --git a/src/modules/project/pages/components/modals/InvitationPendingModal.vue b/src/modules/project/pages/components/modals/InvitationPendingModal.vue
index 74493cec37b047e37e4905a75885b31cc6c068c7..ecdf31bdd9bb92d26a665e2c46ea51677d61dce8 100644
--- a/src/modules/project/pages/components/modals/InvitationPendingModal.vue
+++ b/src/modules/project/pages/components/modals/InvitationPendingModal.vue
@@ -4,22 +4,18 @@
:title="$t('page.members.inviteUserTitle')"
:hide-footer="true"
>
- <ul>
- <li
- v-html="
- $t('page.members.invitationPendingTextTop', {
- email: candidateForInvitation.email,
- })
- "
- ></li>
- <li>
- {{ $t("page.members.invitationPendingTextBottom") }}
- </li>
- </ul>
+ <i18n path="page.members.invitationPendingText" tag="span">
+ <template #email>
+ <b>{{ candidateForInvitation.email }}</b>
+ </template>
+ <template #br>
+ <br />
+ </template>
+ </i18n>
<br />
- <b-button @click="$bvModal.hide('invitationPendingModal')">{{
- $t("buttons.cancel")
- }}</b-button>
+ <b-button class="mt-3" @click="$bvModal.hide('invitationPendingModal')">
+ {{ $t("buttons.cancel") }}
+ </b-button>
</b-modal>
</template>
diff --git a/src/modules/project/pages/components/modals/InviteUserModal.vue b/src/modules/project/pages/components/modals/InviteUserModal.vue
index 6368dac6f28037be25ff6c76f701a60a465526a7..d0552576eba87d7cc6a9244698fb55563e72aabb 100644
--- a/src/modules/project/pages/components/modals/InviteUserModal.vue
+++ b/src/modules/project/pages/components/modals/InviteUserModal.vue
@@ -5,30 +5,35 @@
:hide-footer="true"
>
<!-- Body Text - New Invitation -->
- <div
+ <i18n
v-if="candidateForInvitation && !candidateForInvitation.invited"
- v-html="
- $t('page.members.inviteUserText', {
- email: candidateForInvitation.email,
- role: getRoleNameFromId(candidateForInvitation.role),
- projectName: projectName,
- })
- "
- />
+ path="page.members.inviteUserText"
+ tag="span"
+ >
+ <template #email>
+ <b>{{ candidateForInvitation.email }}</b>
+ </template>
+ <template #role>
+ <b>{{ getRoleNameFromId(candidateForInvitation.role) }}</b>
+ </template>
+ <template #projectName>
+ <b>{{ projectName }}</b>
+ </template>
+ </i18n>
<!-- Body Text - Existing Invitation -->
- <div
+ <i18n
v-else-if="candidateForInvitation && candidateForInvitation.invited"
- v-html="
- $t('page.members.existingEmailInvitation', {
- email: candidateForInvitation.email,
- })
- "
- />
- <br />
+ path="page.members.existingEmailInvitation"
+ tag="span"
+ >
+ <template #email>
+ <b>{{ candidateForInvitation.email }}</b>
+ </template>
+ </i18n>
<!-- Buttons -->
- <div>
+ <div class="mt-3">
<!-- Invite -->
<b-button
class="inviteModalRightBtn"
diff --git a/src/modules/user/pages/UserProfile.vue b/src/modules/user/pages/UserProfile.vue
index 53d07a4e2e329d4ec8094dc39e973c991545863c..f12aa1c9d4d27697290e5de41cb03136818631c3 100644
--- a/src/modules/user/pages/UserProfile.vue
+++ b/src/modules/user/pages/UserProfile.vue
@@ -242,7 +242,7 @@
<AccessToken />
<!-- User Preferences -->
- <div class="h-divider"></div>
+ <div class="h-divider" />
<CoscineHeadline
:headline="$t('page.userprofile.form.userPreferences.header')"
/>
diff --git a/src/modules/user/pages/components/AccessToken.vue b/src/modules/user/pages/components/AccessToken.vue
index 4d4f77d5cdbbce2c4ba7ead21426fca82ef4c590..f935e6bb80403e06224869d7d4b95eef500298de 100644
--- a/src/modules/user/pages/components/AccessToken.vue
+++ b/src/modules/user/pages/components/AccessToken.vue
@@ -355,13 +355,6 @@ export default defineComponent({
#accesstoken .table .b-table-empty-row {
background-color: #f5f5f5;
}
-#accesstoken .h-divider {
- margin-top: 5px;
- margin-bottom: 10px;
- height: 1px;
- width: 100%;
- border-top: 1px solid #bebbbb;
-}
#accesstoken .btn-danger:focus {
outline: none;
box-shadow: none;
diff --git a/yarn.lock-workspace b/yarn.lock-workspace
index 6fbaf58a66a3a03b334f0073de96216e73df84ec..7f49708d04965efa9320dfbddb90f01de0444349 100644
--- a/yarn.lock-workspace
+++ b/yarn.lock-workspace
@@ -3093,7 +3093,7 @@ __metadata:
languageName: node
linkType: hard
-"cacache@npm:*, cacache@npm:^16.0.0, cacache@npm:^16.0.2, cacache@npm:^16.0.6":
+"cacache@npm:*, cacache@npm:^16.0.0, cacache@npm:^16.0.2, cacache@npm:^16.0.6, cacache@npm:^16.0.7":
version: 16.0.7
resolution: "cacache@npm:16.0.7"
dependencies:
@@ -8648,8 +8648,8 @@ __metadata:
linkType: hard
"npm@npm:^8.3.0":
- version: 8.8.0
- resolution: "npm@npm:8.8.0"
+ version: 8.9.0
+ resolution: "npm@npm:8.9.0"
dependencies:
"@isaacs/string-locale-compare": ^1.1.0
"@npmcli/arborist": ^5.0.4
@@ -8661,7 +8661,7 @@ __metadata:
"@npmcli/run-script": ^3.0.1
abbrev: ~1.1.1
archy: ~1.0.0
- cacache: ^16.0.6
+ cacache: ^16.0.7
chalk: ^4.1.2
chownr: ^2.0.0
cli-columns: ^4.0.0
@@ -8703,7 +8703,7 @@ __metadata:
npm-user-validate: ^1.0.1
npmlog: ^6.0.2
opener: ^1.5.2
- pacote: ^13.1.1
+ pacote: ^13.3.0
parse-conflict-json: ^2.0.2
proc-log: ^2.0.1
qrcode-terminal: ^0.12.0
@@ -8724,7 +8724,7 @@ __metadata:
bin:
npm: bin/npm-cli.js
npx: bin/npx-cli.js
- checksum: ece9941f5e3fe1cdeeb80030bd17512ee3f6ea3fd4c9efd7cc682e0170356cd39350de814cb0a6ba4dd3ed993b1e51c3edbcf16a440b3719994e510c3eba654b
+ checksum: ab555b93cfe6070fc5bcdbdfae87185f38e9b30909f0121b6330cbba1ee0c22ba71629431163f37e079d0502b8cc88ef2c58508073c9addb356089fca1706e79
languageName: node
linkType: hard
@@ -9052,7 +9052,7 @@ __metadata:
languageName: node
linkType: hard
-"pacote@npm:*, pacote@npm:^13.0.3, pacote@npm:^13.0.5, pacote@npm:^13.1.1":
+"pacote@npm:*, pacote@npm:^13.0.3, pacote@npm:^13.0.5":
version: 13.2.0
resolution: "pacote@npm:13.2.0"
dependencies:
@@ -9083,6 +9083,37 @@ __metadata:
languageName: node
linkType: hard
+"pacote@npm:^13.3.0":
+ version: 13.3.0
+ resolution: "pacote@npm:13.3.0"
+ dependencies:
+ "@npmcli/git": ^3.0.0
+ "@npmcli/installed-package-contents": ^1.0.7
+ "@npmcli/promise-spawn": ^3.0.0
+ "@npmcli/run-script": ^3.0.1
+ cacache: ^16.0.0
+ chownr: ^2.0.0
+ fs-minipass: ^2.1.0
+ infer-owner: ^1.0.4
+ minipass: ^3.1.6
+ mkdirp: ^1.0.4
+ npm-package-arg: ^9.0.0
+ npm-packlist: ^5.0.0
+ npm-pick-manifest: ^7.0.0
+ npm-registry-fetch: ^13.0.1
+ proc-log: ^2.0.0
+ promise-retry: ^2.0.1
+ read-package-json: ^5.0.0
+ read-package-json-fast: ^2.0.3
+ rimraf: ^3.0.2
+ ssri: ^9.0.0
+ tar: ^6.1.11
+ bin:
+ pacote: lib/bin.js
+ checksum: 49badbafac64e7cd9c87aa14342424ee90946e0ac729faeffb489a94ed6cdab815bd7ffec7673b4863e20e113ee678b8038ace95ee2fc1f58206bc4482ff0bf5
+ languageName: node
+ linkType: hard
+
"pako@npm:~1.0.5":
version: 1.0.11
resolution: "pako@npm:1.0.11"
@@ -10346,8 +10377,8 @@ __metadata:
linkType: hard
"rollup@npm:^2.58.0, rollup@npm:^2.59.0":
- version: 2.71.1
- resolution: "rollup@npm:2.71.1"
+ version: 2.72.0
+ resolution: "rollup@npm:2.72.0"
dependencies:
fsevents: ~2.3.2
dependenciesMeta:
@@ -10355,7 +10386,7 @@ __metadata:
optional: true
bin:
rollup: dist/bin/rollup
- checksum: fe2b2fda7bf53c86e970f3b026b784c00e2237089b802755b3e43725db88f5d1869c1f81f8c5257e9b68b0fd1840dcbd3897d2f19768cce97a37c70e1a563dce
+ checksum: d4c213d4250a0455e0ee40664db3339a9e3ca5affa09c348019b4895e7100cdbedeba881a239a7a80c407b4055dca4e97d10a740d99c09b17651efbfedd61a4b
languageName: node
linkType: hard
@@ -12047,8 +12078,8 @@ __metadata:
linkType: hard
"vite@npm:^2.7.10, vite@npm:^2.8.6":
- version: 2.9.7
- resolution: "vite@npm:2.9.7"
+ version: 2.9.8
+ resolution: "vite@npm:2.9.8"
dependencies:
esbuild: ^0.14.27
fsevents: ~2.3.2
@@ -12071,7 +12102,7 @@ __metadata:
optional: true
bin:
vite: bin/vite.js
- checksum: d3d2a86855709e037d244c3066e785d7b700e41bf59ceb776818ea06ee2ed579055c10596ca883dd56de46b3654ec82da53369062013a012a1a60819dbb7c0ee
+ checksum: 7de3450bec4caa06f4540d1d252563ef0b7e1bdd167d04abf03db72cfd8c9a93879e18861283bc7d075e1d094b78b71770ca36f9b965bdf28c66665eafdc29dc
languageName: node
linkType: hard