Merge branch 'develop' into milestone/ff-admin-unit
# Conflicts: # package-lock.json # package.json
This commit is contained in:
commit
0b16599d2a
13 changed files with 198 additions and 107 deletions
|
@ -6,7 +6,7 @@
|
|||
<div class="w-full flex flex-row gap-2 h-full align-middle">
|
||||
<TopLevelLink
|
||||
v-if="routeName == 'admin' || routeName.includes('admin-')"
|
||||
v-for="item in topLevel"
|
||||
v-for="item in topLevelObject"
|
||||
:key="item.key"
|
||||
:link="item"
|
||||
:disableSubLink="true"
|
||||
|
@ -34,7 +34,7 @@ import TopLevelLink from "./admin/TopLevelLink.vue";
|
|||
export default defineComponent({
|
||||
computed: {
|
||||
...mapState(useAuthStore, ["authCheck"]),
|
||||
...mapState(useNavigationStore, ["topLevel"]),
|
||||
...mapState(useNavigationStore, ["topLevelObject"]),
|
||||
routeName() {
|
||||
return typeof this.$route.name == "string" ? this.$route.name : "";
|
||||
},
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
<div v-if="authCheck" class="hidden md:flex flex-row gap-2 h-full align-middle">
|
||||
<TopLevelLink
|
||||
v-if="routeName == 'admin' || routeName.includes('admin-')"
|
||||
v-for="item in topLevel"
|
||||
v-for="item in topLevelObject"
|
||||
:key="item.key"
|
||||
:link="item"
|
||||
/>
|
||||
|
@ -46,7 +46,7 @@ import { useConfigurationStore } from "@/stores/configuration";
|
|||
export default defineComponent({
|
||||
computed: {
|
||||
...mapState(useAuthStore, ["authCheck"]),
|
||||
...mapState(useNavigationStore, ["topLevel"]),
|
||||
...mapState(useNavigationStore, ["topLevelObject"]),
|
||||
...mapState(useConfigurationStore, ["clubName"]),
|
||||
routeName() {
|
||||
return typeof this.$route.name == "string" ? this.$route.name : "";
|
||||
|
|
|
@ -19,11 +19,16 @@ export async function abilityAndNavUpdate(to: any, from: any, next: any) {
|
|||
navigation.updateNavigation();
|
||||
NProgress.done();
|
||||
next();
|
||||
} else if ((admin && ability.isAdmin()) || ability.can(type, section, module)) {
|
||||
} else if (module && ((admin && ability.isAdmin()) || ability.can(type, section, module))) {
|
||||
NProgress.done();
|
||||
navigation.activeNavigation = to.name.split("-")[1];
|
||||
navigation.activeLink = to.name.split("-")[2];
|
||||
next();
|
||||
} else if (!module && ((admin && ability.isAdmin()) || ability.canSection(type, section))) {
|
||||
NProgress.done();
|
||||
navigation.activeNavigation = to.name.split("-")[1];
|
||||
navigation.activeLink = null;
|
||||
next();
|
||||
} else {
|
||||
NProgress.done();
|
||||
next({ name: "admin-default" });
|
||||
|
|
|
@ -33,7 +33,8 @@ export const useAbilityStore = defineStore("ability", {
|
|||
if (type == "admin") return permissions?.admin ?? permissions?.adminByOwner ?? false;
|
||||
if (permissions?.admin || permissions?.adminByOwner) return true;
|
||||
if (
|
||||
(permissions[section]?.all == "*" || permissions[section]?.all?.includes(type)) &&
|
||||
permissions[section]?.all == "*" ||
|
||||
permissions[section]?.all?.includes(type) ||
|
||||
permissions[section] != undefined
|
||||
)
|
||||
return true;
|
||||
|
|
|
@ -49,7 +49,8 @@ export const useMembershipStore = defineStore("membership", {
|
|||
http
|
||||
.get(`/admin/member/${memberId}/memberships/totalstatistics`)
|
||||
.then((result) => {
|
||||
this.totalMembershipStatistics = result.data;
|
||||
if (result.status == 200) this.totalMembershipStatistics = result.data;
|
||||
else this.totalMembershipStatistics = undefined;
|
||||
})
|
||||
.catch((err) => {});
|
||||
},
|
||||
|
|
|
@ -37,6 +37,11 @@ export const useNavigationStore = defineStore("navigation", {
|
|||
};
|
||||
},
|
||||
getters: {
|
||||
topLevelObject: (state) =>
|
||||
state.topLevel.map((tl) => ({
|
||||
...tl,
|
||||
levelDefault: state.navigation[tl.key].main.filter((m) => !m.key.includes("divider"))[0]?.key ?? "",
|
||||
})),
|
||||
activeNavigationObject: (state) => (state.navigation[state.activeNavigation] ?? {}) as navigationSplitModel,
|
||||
activeTopLevelObject: (state) =>
|
||||
(state.topLevel.find((elem) => elem.key == state.activeNavigation) ?? {}) as topLevelNavigationModel,
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<div class="flex flex-col items-center">
|
||||
<div class="flex flex-col gap-2 items-center">
|
||||
<br />
|
||||
<h1 class="w-full p-4 text-center font-bold text-3xl">Kein Zugriff</h1>
|
||||
<br />
|
||||
|
|
|
@ -24,7 +24,7 @@
|
|||
type="text"
|
||||
name="title"
|
||||
id="title"
|
||||
placeholder="Entscheidung"
|
||||
placeholder="Beschluss"
|
||||
autocomplete="off"
|
||||
v-model="item.topic"
|
||||
@keyup.prevent
|
||||
|
@ -57,7 +57,7 @@
|
|||
<QuillEditor
|
||||
id="top"
|
||||
theme="snow"
|
||||
placeholder="Entscheidung Inhalt..."
|
||||
placeholder="Beschluss Inhalt..."
|
||||
style="height: 250px; max-height: 250px; min-height: 250px"
|
||||
contentType="html"
|
||||
:toolbar="toolbarOptions"
|
||||
|
|
|
@ -57,8 +57,8 @@
|
|||
<QuillEditor
|
||||
id="top"
|
||||
theme="snow"
|
||||
placeholder="Entscheidung Inhalt..."
|
||||
style="height: 100px; max-height: 100px; min-height: 100px"
|
||||
placeholder="Abstimmung Inhalt..."
|
||||
style="height: 150px; max-height: 150px; min-height: 150px"
|
||||
contentType="html"
|
||||
:toolbar="toolbarOptions"
|
||||
v-model:content="item.context"
|
||||
|
|
|
@ -157,6 +157,8 @@ export default defineComponent({
|
|||
this.inviteStatus = "success";
|
||||
localStorage.setItem("accessToken", result.data.accessToken);
|
||||
localStorage.setItem("refreshToken", result.data.refreshToken);
|
||||
localStorage.setItem("routine", this.tab);
|
||||
localStorage.setItem("username", this.username);
|
||||
setTimeout(() => {
|
||||
this.$router.push(`/admin`);
|
||||
}, 1000);
|
||||
|
|
|
@ -15,15 +15,61 @@
|
|||
<RouterLink to="/setup" class="text-primary">Zum Einrichtungsstart</RouterLink>
|
||||
</div>
|
||||
<form v-else class="flex flex-col gap-2" @submit.prevent="setup">
|
||||
<img :src="image" alt="totp" class="w-56 h-56 self-center" />
|
||||
<div class="w-full flex flex-row gap-2 justify-center">
|
||||
<p
|
||||
class="w-1/2 p-0.5 pl-0 rounded-lg py-2.5 text-sm text-center font-medium leading-5 outline-hidden cursor-pointer"
|
||||
:class="
|
||||
tab == 'totp' ? 'bg-red-200 shadow-sm border-b-2 border-primary rounded-b-none' : ' hover:bg-red-200'
|
||||
"
|
||||
@click="tab = 'totp'"
|
||||
>
|
||||
TOTP
|
||||
</p>
|
||||
<p
|
||||
class="w-1/2 p-0.5 rounded-lg py-2.5 text-sm text-center font-medium leading-5 outline-hidden cursor-pointer"
|
||||
:class="
|
||||
tab == 'password' ? 'bg-red-200 shadow-sm border-b-2 border-primary rounded-b-none' : 'hover:bg-red-200'
|
||||
"
|
||||
@click="tab = 'password'"
|
||||
>
|
||||
Passwort
|
||||
</p>
|
||||
</div>
|
||||
<p class="text-center">Dein Nutzername: {{ username }}</p>
|
||||
<div v-if="tab == 'totp'" class="flex flex-col gap-2">
|
||||
<img :src="image" alt="totp" class="w-56 h-56 self-center" />
|
||||
|
||||
<TextCopy :copyText="otp" />
|
||||
<TextCopy :copyText="otp" />
|
||||
|
||||
<div class="-space-y-px">
|
||||
<div>
|
||||
<input id="totp" name="totp" type="text" required placeholder="TOTP" />
|
||||
<div class="-space-y-px">
|
||||
<div>
|
||||
<input id="totp" name="totp" type="text" required placeholder="TOTP" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else>
|
||||
<input
|
||||
id="password"
|
||||
name="password"
|
||||
type="password"
|
||||
required
|
||||
placeholder="Passwort"
|
||||
class="rounded-b-none!"
|
||||
autocomplete="new-password"
|
||||
:class="notMatching ? 'border-red-600!' : ''"
|
||||
/>
|
||||
<input
|
||||
id="password_rep"
|
||||
name="password_rep"
|
||||
type="password"
|
||||
required
|
||||
placeholder="Passwort wiederholen"
|
||||
class="rounded-t-none!"
|
||||
autocomplete="new-password"
|
||||
:class="notMatching ? 'border-red-600!' : ''"
|
||||
/>
|
||||
<p v-if="notMatching">Passwörter stimmen nicht überein</p>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-row gap-2">
|
||||
<button type="submit" primary :disabled="setupStatus == 'loading' || setupStatus == 'success'">
|
||||
|
@ -51,6 +97,7 @@ import { RouterLink } from "vue-router";
|
|||
import FormBottomBar from "@/components/FormBottomBar.vue";
|
||||
import TextCopy from "@/components/TextCopy.vue";
|
||||
import AppLogo from "@/components/AppLogo.vue";
|
||||
import { hashString } from "@/helpers/crypto";
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
|
@ -61,11 +108,14 @@ export default defineComponent({
|
|||
},
|
||||
data() {
|
||||
return {
|
||||
tab: "totp",
|
||||
verification: "loading" as "success" | "loading" | "failed",
|
||||
image: undefined as undefined | string,
|
||||
otp: undefined as undefined | string,
|
||||
username: "" as string,
|
||||
setupStatus: undefined as undefined | "loading" | "success" | "failed",
|
||||
setupError: "" as string,
|
||||
notMatching: false as boolean,
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
|
@ -79,6 +129,7 @@ export default defineComponent({
|
|||
this.verification = "success";
|
||||
this.image = result.data.dataUrl;
|
||||
this.otp = result.data.otp;
|
||||
this.username = result.data.username;
|
||||
}, 1000);
|
||||
})
|
||||
.catch((err) => {
|
||||
|
@ -88,20 +139,28 @@ export default defineComponent({
|
|||
});
|
||||
},
|
||||
methods: {
|
||||
setup(e: any) {
|
||||
let formData = e.target.elements;
|
||||
async setup(e: any) {
|
||||
let secret = "";
|
||||
if (this.tab == "totp") secret = this.totp(e);
|
||||
else secret = await this.password(e);
|
||||
|
||||
if (secret == "") return;
|
||||
|
||||
this.setupStatus = "loading";
|
||||
this.setupError = "";
|
||||
this.$http
|
||||
.post(`/setup/finish`, {
|
||||
token: this.token,
|
||||
mail: this.mail,
|
||||
totp: formData.totp.value,
|
||||
secret: secret,
|
||||
routine: this.tab,
|
||||
})
|
||||
.then((result) => {
|
||||
this.setupStatus = "success";
|
||||
localStorage.setItem("accessToken", result.data.accessToken);
|
||||
localStorage.setItem("refreshToken", result.data.refreshToken);
|
||||
localStorage.setItem("routine", this.tab);
|
||||
localStorage.setItem("username", this.username);
|
||||
setTimeout(() => {
|
||||
this.$router.push(`/admin`);
|
||||
}, 1000);
|
||||
|
@ -111,6 +170,24 @@ export default defineComponent({
|
|||
this.setupError = err.response.data;
|
||||
});
|
||||
},
|
||||
|
||||
totp(e: any) {
|
||||
let formData = e.target.elements;
|
||||
return formData.totp.value;
|
||||
},
|
||||
async password(e: any) {
|
||||
let formData = e.target.elements;
|
||||
|
||||
let new_pw = await hashString(formData.password.value);
|
||||
let new_rep = await hashString(formData.password_rep.value);
|
||||
if (new_pw != new_rep) {
|
||||
this.notMatching = true;
|
||||
return "";
|
||||
}
|
||||
this.notMatching = false;
|
||||
|
||||
return await hashString(formData.password.value);
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue