Skip to content
Snippets Groups Projects
Commit 68faa55f authored by Petar Hristov's avatar Petar Hristov :speech_balloon: Committed by Benedikt Heinrichs
Browse files

New: Migrated Support Admin page to UIv2

parent e57fdd83
No related tags found
3 merge requests!54Chore: 1.7.0,!51Release: Sprint/2022 08 :robot:,!50New: Migrated Support Admin page to UIv2
Showing
with 603 additions and 147 deletions
...@@ -24,7 +24,8 @@ module.exports = { ...@@ -24,7 +24,8 @@ module.exports = {
"error", "error",
{ "allowWholeFile": true } { "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 _ "@typescript-eslint/no-unused-vars": ["warn", { "argsIgnorePattern": "^_" }], // will only ignore variables that start with an underscore _
"vue/multi-word-component-names": "off" "vue/multi-word-component-names": "off"
}, },
......
...@@ -75,4 +75,12 @@ export default defineComponent({ ...@@ -75,4 +75,12 @@ export default defineComponent({
}); });
</script> </script>
<style scoped></style> <style>
.h-divider {
margin-top: 5px;
margin-bottom: 10px;
height: 1px;
width: 100%;
border-top: 1px solid #bebbbb;
}
</style>
<template> <template>
<div> <div>
<router-view v-if="moduleIsReady" /> <router-view v-if="true" />
</div> </div>
</template> </template>
...@@ -22,7 +22,7 @@ export default defineComponent({ ...@@ -22,7 +22,7 @@ export default defineComponent({
computed: { computed: {
moduleIsReady(): boolean { moduleIsReady(): boolean {
return true; return this.adminStore.project !== null;
}, },
}, },
...@@ -32,8 +32,9 @@ export default defineComponent({ ...@@ -32,8 +32,9 @@ export default defineComponent({
methods: { methods: {
async initialize() { async initialize() {
// do initialization stuff (e.g. API calls, element loading, etc.) // await Promise.all([
// ... // //this.adminStore.retrieveProjectQuotas(this.adminStore.project.projectGuid),
// ])
}, },
}, },
}); });
......
<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>
import VueI18n from "vue-i18n"; import VueI18n from "vue-i18n";
export default { export default {
/*
--------------------------------------------------------------------------------------
GERMAN STRINGS
--------------------------------------------------------------------------------------
*/
page: { page: {
admin: { admin: {
title: "Adminseite", headline: "Adminseite",
description: "Das ist die @:page.admin.title des Coscine UIv2 Apps",
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; } as VueI18n.LocaleMessageObject;
import VueI18n from "vue-i18n"; import VueI18n from "vue-i18n";
export default { export default {
/*
--------------------------------------------------------------------------------------
ENGLISH STRINGS
--------------------------------------------------------------------------------------
*/
page: { page: {
admin: { admin: {
title: "Admin Page", headline: "Quota Admin Panel",
description: "This is the @:page.admin.title for the Coscine UIv2 App",
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; } as VueI18n.LocaleMessageObject;
<template> <template>
<div> <div id="admin">
<section <CoscineHeadline :headline="$t('page.admin.headline')" />
class="container flex flex-col items-center px-5 py-12 mx-auto text-gray-600 body-font md:flex-row" <!-- 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')"
> >
<div> </b-form-input>
<CoscineHeadline :headline="$t('page.admin.title')" /> <!-- Select Button -->
<p class="mb-8 leading-relaxed dark:text-white"> <b-input-group-append>
{{ $t("page.admin.description") }} <b-button variant="primary" @click="queryProject()">
</p> {{ $t("buttons.submit") }}
<img alt="From Coscine Old" src="@/assets/images/Admin.png" /> </b-button>
</div> </b-input-group-append>
</section> </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"
>
<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> </div>
</template> </template>
...@@ -22,16 +81,42 @@ import CoscineHeadline from "@/components/coscine/CoscineHeadline.vue"; ...@@ -22,16 +81,42 @@ import CoscineHeadline from "@/components/coscine/CoscineHeadline.vue";
import useAdminStore from "../store"; import useAdminStore from "../store";
// import the main store // import the main store
import useMainStore from "@/store/index"; 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({ export default defineComponent({
components: { components: {
CoscineHeadline, CoscineHeadline,
QuotaTable,
}, },
setup() { setup() {
const mainStore = useMainStore(); const mainStore = useMainStore();
const adminStore = useAdminStore(); const adminStore = useAdminStore();
return { mainStore, adminStore }; 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> </script>
...@@ -11,7 +11,6 @@ export const AdminRoutes: RouteConfig[] = [ ...@@ -11,7 +11,6 @@ export const AdminRoutes: RouteConfig[] = [
component: AdminModule, component: AdminModule,
// only authenticated users can access admin // only authenticated users can access admin
meta: { meta: {
breadCrumb: "admin",
requiresAdmin: true, requiresAdmin: true,
requiresAuth: true, requiresAuth: true,
i18n: AdminI18nMessages, i18n: AdminI18nMessages,
...@@ -21,6 +20,9 @@ export const AdminRoutes: RouteConfig[] = [ ...@@ -21,6 +20,9 @@ export const AdminRoutes: RouteConfig[] = [
path: "/", path: "/",
name: "admin", name: "admin",
component: Admin, component: Admin,
meta: {
breadCrumb: "admin",
},
}, },
], ],
}, },
......
import { defineStore } from "pinia"; import { defineStore } from "pinia";
import { AdminState } from "./types"; 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" Store variable name is "this.<id>Store"
...@@ -13,8 +18,9 @@ export const useAdminStore = defineStore({ ...@@ -13,8 +18,9 @@ export const useAdminStore = defineStore({
STATES STATES
-------------------------------------------------------------------------------------- --------------------------------------------------------------------------------------
*/ */
state: (): AdminState => ({}), state: (): AdminState => ({
project: null,
}),
/* /*
-------------------------------------------------------------------------------------- --------------------------------------------------------------------------------------
GETTERS GETTERS
...@@ -22,20 +28,35 @@ export const useAdminStore = defineStore({ ...@@ -22,20 +28,35 @@ export const useAdminStore = defineStore({
Synchronous code only. Synchronous code only.
In a component use as e.g.: In a component use as e.g.:
:label = "this.adminStore.<getter_name>; :label = "this.projectStore.<getter_name>;"
*/ */
getters: {}, 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.: actions: {
@click = "this.adminStore.<action_name>(); async retrieveProjectQuotas(projectString: string) {
*/ const notificationStore = useNotificationStore();
actions: {}, 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; export default useAdminStore;
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 { export interface AdminState {
/* project: ProjectObject | null;
--------------------------------------------------------------------------------------
STATE TYPE DEFINITION
--------------------------------------------------------------------------------------
*/
} }
...@@ -90,9 +90,11 @@ export default defineComponent({ ...@@ -90,9 +90,11 @@ export default defineComponent({
if (this.projectStore.currentQuotas === null) { if (this.projectStore.currentQuotas === null) {
this.projectStore.retrieveQuotas(this.project); this.projectStore.retrieveQuotas(this.project);
} }
// Load list of Enabled Resource Types if not present
if (this.resourceStore.resourceTypes === null) { if (this.resourceStore.resourceTypes === null) {
this.resourceStore.retrieveResourceTypes(); this.resourceStore.retrieveResourceTypes();
} }
// Load list of all Project Role Types if not present
if (this.projectStore.roles === null) { if (this.projectStore.roles === null) {
this.projectStore.retrieveRoles(); this.projectStore.retrieveRoles();
} }
......
...@@ -54,23 +54,21 @@ export default { ...@@ -54,23 +54,21 @@ export default {
pleaseTypeSomething: pleaseTypeSomething:
"Bitte geben Sie einen Namen oder eine E-Mail Adresse ein", "Bitte geben Sie einen Namen oder eine E-Mail Adresse ein",
removeSelectedInvitation: 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: 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", deleteInvitationTitle: "Einladung Zurückziehen",
deleteUserTitle: "Benutzer entfernen", deleteUserTitle: "Benutzer entfernen",
inviteUser: "Einladung abschicken", inviteUser: "Einladung abschicken",
reInviteUser: "Erneut senden", reInviteUser: "Erneut senden",
inviteUserText: 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", inviteUserTitle: "Benutzer einladen",
inviteUserCaption: "Wählen Sie {displayName} aus", inviteUserCaption: "Wählen Sie {displayName} aus",
invitedUserText: invitedUserText:
"Eine Einladung wurde an {email} mit einer Rolle als {role} für das Projekt {projectName} verschickt.", "Eine Einladung wurde an {email} mit einer Rolle als {role} für das Projekt {projectName} verschickt.",
invitationPendingTextTop: invitationPendingText:
"Es wurde bereits eine Einladung an <strong>{email}</strong> gesendet.", "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.",
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.",
invitedUserError: invitedUserError:
"Ein Fehler ist beim Einladen von {email} aufgetreten. Eingaben sind fehlerhaft oder der Benutzer wurde bereits eingeladen.", "Ein Fehler ist beim Einladen von {email} aufgetreten. Eingaben sind fehlerhaft oder der Benutzer wurde bereits eingeladen.",
deleteExternalUserError: deleteExternalUserError:
...@@ -88,7 +86,7 @@ export default { ...@@ -88,7 +86,7 @@ export default {
removeUser: "Entfernen", removeUser: "Entfernen",
searchProjectPlaceholder: "Wählen Sie ein Projekt aus...", searchProjectPlaceholder: "Wählen Sie ein Projekt aus...",
existingEmailInvitation: 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 // ProjectPage.vue
......
...@@ -50,23 +50,21 @@ export default { ...@@ -50,23 +50,21 @@ export default {
userManagement: "@:(page.members.title)", userManagement: "@:(page.members.title)",
pleaseTypeSomething: "Please enter a name or an email address", pleaseTypeSomething: "Please enter a name or an email address",
removeSelectedInvitation: 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: 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", deleteInvitationTitle: "Revoke Invitation",
deleteUserTitle: "Remove User", deleteUserTitle: "Remove User",
inviteUser: "Send Invitation", inviteUser: "Send Invitation",
reInviteUser: "Resend Invitation", reInviteUser: "Resend Invitation",
inviteUserText: 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", inviteUserTitle: "Invite User",
inviteUserCaption: "Choose to invite {displayName}", inviteUserCaption: "Choose to invite {displayName}",
invitedUserText: invitedUserText:
"An invitation has been sent to {email} with a role as {role} for project {projectName}.", "An invitation has been sent to {email} with a role as {role} for project {projectName}.",
invitationPendingTextTop: invitationPendingText:
"An invitation has already been sent to <strong>{email}</strong>.", "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.",
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.",
invitedUserError: invitedUserError:
"An error ocurred while trying to invite {email}. Invalid input was provided or the user has already been invited.", "An error ocurred while trying to invite {email}. Invalid input was provided or the user has already been invited.",
deleteExternalUserError: deleteExternalUserError:
...@@ -83,7 +81,7 @@ export default { ...@@ -83,7 +81,7 @@ export default {
removeUser: "Remove", removeUser: "Remove",
searchProjectPlaceholder: "Select a project...", searchProjectPlaceholder: "Select a project...",
existingEmailInvitation: 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 // ProjectPage.vue
......
...@@ -195,12 +195,4 @@ export default defineComponent({ ...@@ -195,12 +195,4 @@ export default defineComponent({
}); });
</script> </script>
<style scoped> <style scoped></style>
.h-divider {
margin-top: 5px;
margin-bottom: 10px;
height: 1px;
width: 100%;
border-top: 1px solid #bebbbb;
}
</style>
...@@ -439,13 +439,6 @@ input[type="range"] { ...@@ -439,13 +439,6 @@ input[type="range"] {
width: calc(100% - 8em); width: calc(100% - 8em);
padding-top: 0.6rem; 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 { #quotaManagement >>> .b-table-empty-row {
background-color: #f5f5f5; background-color: #f5f5f5;
} }
......
...@@ -259,12 +259,4 @@ export default defineComponent({ ...@@ -259,12 +259,4 @@ export default defineComponent({
}); });
</script> </script>
<style scoped> <style scoped></style>
.h-divider {
margin-top: 5px;
margin-bottom: 10px;
height: 1px;
width: 100%;
border-top: 1px solid #bebbbb;
}
</style>
<template> <template>
<div>
<b-modal <b-modal
:visible="visible" :visible="visible"
:title="$t(titleKey)" :title="$t(titleKey)"
...@@ -10,16 +9,15 @@ ...@@ -10,16 +9,15 @@
@ok="$emit('ok', $event.target.value)" @ok="$emit('ok', $event.target.value)"
@cancel="$emit('close', $event.target.value)" @cancel="$emit('close', $event.target.value)"
> >
<div <i18n :path="descriptionKey" tag="span">
v-html=" <template #user>
$t(descriptionKey, { <b>{{ selectedUser }}</b>
user: selectedUser, </template>
projectName: selectedProject, <template #projectName>
}) <b>{{ selectedProject }}</b>
" </template>
/> </i18n>
</b-modal> </b-modal>
</div>
</template> </template>
<script lang="ts"> <script lang="ts">
......
...@@ -4,22 +4,18 @@ ...@@ -4,22 +4,18 @@
:title="$t('page.members.inviteUserTitle')" :title="$t('page.members.inviteUserTitle')"
:hide-footer="true" :hide-footer="true"
> >
<ul> <i18n path="page.members.invitationPendingText" tag="span">
<li <template #email>
v-html=" <b>{{ candidateForInvitation.email }}</b>
$t('page.members.invitationPendingTextTop', { </template>
email: candidateForInvitation.email, <template #br>
}) <br />
" </template>
></li> </i18n>
<li>
{{ $t("page.members.invitationPendingTextBottom") }}
</li>
</ul>
<br /> <br />
<b-button @click="$bvModal.hide('invitationPendingModal')">{{ <b-button class="mt-3" @click="$bvModal.hide('invitationPendingModal')">
$t("buttons.cancel") {{ $t("buttons.cancel") }}
}}</b-button> </b-button>
</b-modal> </b-modal>
</template> </template>
......
...@@ -5,30 +5,35 @@ ...@@ -5,30 +5,35 @@
:hide-footer="true" :hide-footer="true"
> >
<!-- Body Text - New Invitation --> <!-- Body Text - New Invitation -->
<div <i18n
v-if="candidateForInvitation && !candidateForInvitation.invited" v-if="candidateForInvitation && !candidateForInvitation.invited"
v-html=" path="page.members.inviteUserText"
$t('page.members.inviteUserText', { tag="span"
email: candidateForInvitation.email, >
role: getRoleNameFromId(candidateForInvitation.role), <template #email>
projectName: projectName, <b>{{ candidateForInvitation.email }}</b>
}) </template>
" <template #role>
/> <b>{{ getRoleNameFromId(candidateForInvitation.role) }}</b>
</template>
<template #projectName>
<b>{{ projectName }}</b>
</template>
</i18n>
<!-- Body Text - Existing Invitation --> <!-- Body Text - Existing Invitation -->
<div <i18n
v-else-if="candidateForInvitation && candidateForInvitation.invited" v-else-if="candidateForInvitation && candidateForInvitation.invited"
v-html=" path="page.members.existingEmailInvitation"
$t('page.members.existingEmailInvitation', { tag="span"
email: candidateForInvitation.email, >
}) <template #email>
" <b>{{ candidateForInvitation.email }}</b>
/> </template>
<br /> </i18n>
<!-- Buttons --> <!-- Buttons -->
<div> <div class="mt-3">
<!-- Invite --> <!-- Invite -->
<b-button <b-button
class="inviteModalRightBtn" class="inviteModalRightBtn"
......
...@@ -242,7 +242,7 @@ ...@@ -242,7 +242,7 @@
<AccessToken /> <AccessToken />
<!-- User Preferences --> <!-- User Preferences -->
<div class="h-divider"></div> <div class="h-divider" />
<CoscineHeadline <CoscineHeadline
:headline="$t('page.userprofile.form.userPreferences.header')" :headline="$t('page.userprofile.form.userPreferences.header')"
/> />
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment