Compare commits
3 commits
main
...
anton/expo
Author | SHA1 | Date | |
---|---|---|---|
903c2f7560 | |||
c657b36292 | |||
|
115b2937e5 |
5 changed files with 181 additions and 2 deletions
|
@ -286,6 +286,20 @@ const router = createRouter({
|
|||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: "database",
|
||||
name: "admin-settings-database",
|
||||
component: () => import("@/views/RouterView.vue"),
|
||||
meta: { type: "read", section: "settings", module: "database" },
|
||||
beforeEnter: [abilityAndNavUpdate],
|
||||
children: [
|
||||
{
|
||||
path: "",
|
||||
name: "admin-settings-database-status",
|
||||
component: () => import("@/views/admin/settings/Database.vue"),
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
|
|
55
src/stores/admin/database.ts
Normal file
55
src/stores/admin/database.ts
Normal file
|
@ -0,0 +1,55 @@
|
|||
import { defineStore } from "pinia";
|
||||
import { http } from "@/serverCom";
|
||||
import type { AxiosResponse } from "axios";
|
||||
|
||||
export const useDatabaseStore = defineStore("database", {
|
||||
state: () => {
|
||||
return {
|
||||
data: {} as any,
|
||||
status: "idle" as "idle" | "working" | "success" | "failed",
|
||||
failureReason: '' as string,
|
||||
ex: {} as Error,
|
||||
};
|
||||
},
|
||||
actions: {
|
||||
async import(noEncryption: boolean, secret: string, requestData: any) {
|
||||
try {
|
||||
this.data = {};
|
||||
this.status = "working";
|
||||
this.failureReason = '';
|
||||
this.ex = {};
|
||||
const requestHeaders = {headers: {'Content-Type': 'application/json', 'x-decrypt-no' : noEncryption}};
|
||||
if (!noEncryption) {
|
||||
requestHeaders.headers['x-decrypt-with'] = secret;
|
||||
}
|
||||
await http.post("/admin/database", requestData, requestHeaders);
|
||||
this.status = "success";
|
||||
} catch (ex) {
|
||||
this.status = "failed";
|
||||
this.failureReason = (ex as Error).message;
|
||||
this.ex = (ex as Error);
|
||||
throw ex;
|
||||
}
|
||||
},
|
||||
async export(noEncryption: boolean, secret: string) {
|
||||
try {
|
||||
this.data = {};
|
||||
this.status = "working";
|
||||
this.failureReason = '';
|
||||
this.ex = {};
|
||||
const requestHeaders = {headers: {'Content-Type': 'application/json', 'x-encrypt-no' : noEncryption}};
|
||||
if (!noEncryption) {
|
||||
requestHeaders.headers['x-encrypt-with'] = secret;
|
||||
}
|
||||
const response: AxiosResponse = await http.get("/admin/database", requestHeaders);
|
||||
this.data = response.data;
|
||||
this.status = "success";
|
||||
} catch (ex) {
|
||||
this.status = "failed";
|
||||
this.failureReason = (ex as Error).message;
|
||||
this.ex = (ex as Error);
|
||||
throw ex;
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
|
@ -108,6 +108,9 @@ export const useNavigationStore = defineStore("navigation", {
|
|||
...(abilityStore.can("read", "settings", "membership_status")
|
||||
? [{ key: "membership_status", title: "Mitgliedsstatus" }]
|
||||
: []),
|
||||
...(abilityStore.can("read", "settings", "database")
|
||||
? [{ key: "database", title: "Datenbank" }]
|
||||
: []),
|
||||
],
|
||||
},
|
||||
user: {
|
||||
|
|
|
@ -11,7 +11,8 @@ export type PermissionModule =
|
|||
| "communication"
|
||||
| "membership_status"
|
||||
| "user"
|
||||
| "role";
|
||||
| "role"
|
||||
| "database";
|
||||
|
||||
export type PermissionType = "read" | "create" | "update" | "delete";
|
||||
|
||||
|
@ -47,10 +48,11 @@ export const permissionModules: Array<PermissionModule> = [
|
|||
"membership_status",
|
||||
"user",
|
||||
"role",
|
||||
"database",
|
||||
];
|
||||
export const permissionTypes: Array<PermissionType> = ["read", "create", "update", "delete"];
|
||||
export const sectionsAndModules: SectionsAndModulesObject = {
|
||||
club: ["member", "calendar", "newsletter", "protocoll"],
|
||||
settings: ["qualification", "award", "executive_position", "communication", "membership_status"],
|
||||
settings: ["qualification", "award", "executive_position", "communication", "membership_status", "database"],
|
||||
user: ["user", "role"],
|
||||
};
|
||||
|
|
105
src/views/admin/settings/Database.vue
Normal file
105
src/views/admin/settings/Database.vue
Normal file
|
@ -0,0 +1,105 @@
|
|||
<template>
|
||||
<MainTemplate>
|
||||
<template #topBar>
|
||||
<div class="flex flex-row items-center justify-between pt-5 pb-3 px-7">
|
||||
<h1 class="font-bold text-xl h-8">Datenbank</h1>
|
||||
</div>
|
||||
</template>
|
||||
<template #main>
|
||||
<div class="flex flex-row justify-start items-center gap-2">
|
||||
<label for="file" class="w-2/6">Zum Importieren Datei auswählen</label>
|
||||
<input type="file" class="" id="file" @change="addFile"/>
|
||||
</div>
|
||||
<div class="flex flex-row justify-start items-center gap-2">
|
||||
<label for="noEnc" class="w-2/6">Ohne Verschlüsselung</label>
|
||||
<input type="checkbox" class="w-fit" id="noEnc" required v-model="noEncryption" />
|
||||
</div>
|
||||
<div class="flex flex-row justify-start items-center gap-2">
|
||||
<label for="encKey" class="w-2/6">Encryption Key</label>
|
||||
<input :disabled="noEncryption" type="password" id="encKey" required v-model="secret" />
|
||||
</div>
|
||||
<div class="flex flex-row justify-end gap-2">
|
||||
<span v-if="status == 'failed'">Fehler</span>
|
||||
<Spinner v-if="status == 'working'" class="my-auto" />
|
||||
<SuccessCheckmark v-else-if="status == 'success'" />
|
||||
<FailureXMark v-else-if="status == 'failed'" />
|
||||
<button primary :disabled="uploadDisabled" class="!w-fit" @click="importDatabase">Import</button>
|
||||
<button :disabled="disabled" primary class="!w-fit" @click="exportDatabase">Exportieren</button>
|
||||
</div>
|
||||
<div class="flex flex-row justify-end gap-2">
|
||||
<a class="download-link" v-if="dataUrl !== ''" :href="dataUrl" :download="downloadFilename">Hier für Download ({{dataUrl.length}} Bytes) klicken</a>
|
||||
</div>
|
||||
</template>
|
||||
</MainTemplate>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import MainTemplate from "@/templates/Main.vue";
|
||||
import { defineComponent, defineAsyncComponent } from "vue";
|
||||
import { mapState, mapActions } from "pinia";
|
||||
import { useDatabaseStore } from "@/stores/admin/database";
|
||||
import FailureXMark from "@/components/FailureXMark.vue";
|
||||
import Spinner from "@/components/Spinner.vue";
|
||||
import SuccessCheckmark from "@/components/SuccessCheckmark.vue";
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
|
||||
export default defineComponent({
|
||||
data() {
|
||||
return {
|
||||
dataUrl: '' as string,
|
||||
noEncryption: false as boolean,
|
||||
file: null,
|
||||
downloadFilename: '' as string,
|
||||
secret: '' as string,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapState(useDatabaseStore, ["data", "status", "failureReason"]),
|
||||
disabled() : boolean {
|
||||
return this.status === 'working' || (this.secret === '' && !this.noEncryption);
|
||||
},
|
||||
uploadDisabled() : boolean {
|
||||
return this.status === 'working' || (this.secret === '' && !this.noEncryption) || this.file === null;
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.secret = '' as string;
|
||||
this.noEncryption = false;
|
||||
},
|
||||
methods: {
|
||||
...mapActions(useDatabaseStore, ["export", "import"]),
|
||||
addFile(e) {
|
||||
this.dataUrl = '';
|
||||
|
||||
if (e.target.files?.length !== 1) {
|
||||
return;
|
||||
}
|
||||
this.file = e.target.files[0];
|
||||
},
|
||||
async exportDatabase() {
|
||||
try {
|
||||
if (this.dataUrl) {
|
||||
this.dataUrl = '';
|
||||
}
|
||||
await this.export(this.noEncryption, this.secret);
|
||||
this.dataUrl = 'data:application/octet-stream,' + encodeURIComponent(JSON.stringify(this.data));
|
||||
this.downloadFilename = `${new Date().toISOString().replace(/:/g, '-')}_database_export.${this.noEncryption ? 'json' : 'enc'}`;
|
||||
}
|
||||
catch(ex) {
|
||||
console.log(ex);
|
||||
}
|
||||
},
|
||||
async importDatabase() {
|
||||
try {
|
||||
const fileContent = await this.file.text();
|
||||
await this.import(this.noEncryption, this.secret, fileContent);
|
||||
}
|
||||
catch(ex) {
|
||||
console.log(ex);
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
</script>
|
Loading…
Reference in a new issue