Compare commits

..

2 Commits

Author SHA1 Message Date
d47ae5d5cc
🔨 Fix backup type chang 2023-02-05 18:59:23 +01:00
2eadfc9778
🔨 Add alerts 2023-02-05 18:58:58 +01:00
11 changed files with 190 additions and 38 deletions

View File

@ -22,7 +22,7 @@
"roboto-fontface": "*", "roboto-fontface": "*",
"uuid": "^9.0.0", "uuid": "^9.0.0",
"vue": "^3.2.45", "vue": "^3.2.45",
"vuetify": "3.1.1", "vuetify": "3.1.3",
"webfontloader": "^1.6.28" "webfontloader": "^1.6.28"
}, },
"devDependencies": { "devDependencies": {

View File

@ -27,7 +27,7 @@ specifiers:
vite-plugin-vuetify: ^1.0.1 vite-plugin-vuetify: ^1.0.1
vue: ^3.2.45 vue: ^3.2.45
vue-tsc: 1.0.7 vue-tsc: 1.0.7
vuetify: 3.1.1 vuetify: 3.1.3
webfontloader: ^1.6.28 webfontloader: ^1.6.28
dependencies: dependencies:
@ -42,7 +42,7 @@ dependencies:
roboto-fontface: 0.10.0 roboto-fontface: 0.10.0
uuid: 9.0.0 uuid: 9.0.0
vue: 3.2.45 vue: 3.2.45
vuetify: 3.1.1_ihnv4ujwy6q3qcyepcitbzrajy vuetify: 3.1.3_ihnv4ujwy6q3qcyepcitbzrajy
webfontloader: 1.6.28 webfontloader: 1.6.28
devDependencies: devDependencies:
@ -59,7 +59,7 @@ devDependencies:
prettier: 2.8.2 prettier: 2.8.2
typescript: 4.7.4 typescript: 4.7.4
vite: 3.2.5_@types+node@16.18.11 vite: 3.2.5_@types+node@16.18.11
vite-plugin-vuetify: 1.0.1_mc6ujyzexjrcgjeph6am73m5wu vite-plugin-vuetify: 1.0.1_bps52lyiebopdablgncd5xv5m4
vue-tsc: 1.0.7_typescript@4.7.4 vue-tsc: 1.0.7_typescript@4.7.4
packages: packages:
@ -497,7 +497,7 @@ packages:
'@types/node': 16.18.11 '@types/node': 16.18.11
dev: true dev: true
/@vuetify/loader-shared/1.7.0_vue@3.2.45+vuetify@3.1.1: /@vuetify/loader-shared/1.7.0_vue@3.2.45+vuetify@3.1.3:
resolution: {integrity: sha512-Db4K67wMhduDsbvdRBYkrYuomti+j0E/1vlz1lnDng5F9LYYBcXa60qypIazVGI6GX/CuY1vshN6XGtGQI4FKg==} resolution: {integrity: sha512-Db4K67wMhduDsbvdRBYkrYuomti+j0E/1vlz1lnDng5F9LYYBcXa60qypIazVGI6GX/CuY1vshN6XGtGQI4FKg==}
peerDependencies: peerDependencies:
vue: ^3.0.0 vue: ^3.0.0
@ -506,7 +506,7 @@ packages:
find-cache-dir: 3.3.2 find-cache-dir: 3.3.2
upath: 2.0.1 upath: 2.0.1
vue: 3.2.45 vue: 3.2.45
vuetify: 3.1.1_ihnv4ujwy6q3qcyepcitbzrajy vuetify: 3.1.3_ihnv4ujwy6q3qcyepcitbzrajy
/acorn-jsx/5.3.2_acorn@8.8.1: /acorn-jsx/5.3.2_acorn@8.8.1:
resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==}
@ -2260,18 +2260,18 @@ packages:
spdx-expression-parse: 3.0.1 spdx-expression-parse: 3.0.1
dev: true dev: true
/vite-plugin-vuetify/1.0.1_mc6ujyzexjrcgjeph6am73m5wu: /vite-plugin-vuetify/1.0.1_bps52lyiebopdablgncd5xv5m4:
resolution: {integrity: sha512-/xHsIDuHxq7f6fDqCBYxNascLhDi+X8dV3RzTwmo4mGPrSnGq9pHv8wJsXBIQIT3nY8s16V0lmd6sXMjm0F8wg==} resolution: {integrity: sha512-/xHsIDuHxq7f6fDqCBYxNascLhDi+X8dV3RzTwmo4mGPrSnGq9pHv8wJsXBIQIT3nY8s16V0lmd6sXMjm0F8wg==}
engines: {node: '>=12'} engines: {node: '>=12'}
peerDependencies: peerDependencies:
vite: ^2.7.0 || ^3.0.0 || ^4.0.0 vite: ^2.7.0 || ^3.0.0 || ^4.0.0
vuetify: ^3.0.0-beta.4 vuetify: ^3.0.0-beta.4
dependencies: dependencies:
'@vuetify/loader-shared': 1.7.0_vue@3.2.45+vuetify@3.1.1 '@vuetify/loader-shared': 1.7.0_vue@3.2.45+vuetify@3.1.3
debug: 4.3.4 debug: 4.3.4
upath: 2.0.1 upath: 2.0.1
vite: 3.2.5_@types+node@16.18.11 vite: 3.2.5_@types+node@16.18.11
vuetify: 3.1.1_ihnv4ujwy6q3qcyepcitbzrajy vuetify: 3.1.3_ihnv4ujwy6q3qcyepcitbzrajy
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
- vue - vue
@ -2369,8 +2369,8 @@ packages:
'@vue/server-renderer': 3.2.45_vue@3.2.45 '@vue/server-renderer': 3.2.45_vue@3.2.45
'@vue/shared': 3.2.45 '@vue/shared': 3.2.45
/vuetify/3.1.1_ihnv4ujwy6q3qcyepcitbzrajy: /vuetify/3.1.3_ihnv4ujwy6q3qcyepcitbzrajy:
resolution: {integrity: sha512-BkfRQZ406xQORpgrcUjuPaT/woO96ef/+2zHCfL3an+CrUhjG/sIAptEybHruq3xwFM0uJibDFqGiridsXc99w==} resolution: {integrity: sha512-QE/yXvHKDnlK9sjjMoZy9cAovNabpI97xg9RD+mAEDgSUaZNCX/jy7Qn0bP1DjJNeAsR2oAlcO0C2WhrsYe9kw==}
engines: {node: ^12.20 || >=14.13} engines: {node: ^12.20 || >=14.13}
peerDependencies: peerDependencies:
vite-plugin-vuetify: ^1.0.0-alpha.12 vite-plugin-vuetify: ^1.0.0-alpha.12
@ -2385,7 +2385,7 @@ packages:
webpack-plugin-vuetify: webpack-plugin-vuetify:
optional: true optional: true
dependencies: dependencies:
vite-plugin-vuetify: 1.0.1_mc6ujyzexjrcgjeph6am73m5wu vite-plugin-vuetify: 1.0.1_bps52lyiebopdablgncd5xv5m4
vue: 3.2.45 vue: 3.2.45
/webfontloader/1.6.28: /webfontloader/1.6.28:

View File

@ -3,7 +3,8 @@
<navbar-component></navbar-component> <navbar-component></navbar-component>
<message-bar></message-bar> <message-bar></message-bar>
<webdav-settings-menu></webdav-settings-menu> <webdav-settings-menu></webdav-settings-menu>
<BackupConfigMenu></BackupConfigMenu> <backup-config-menu></backup-config-menu>
<alert-manager></alert-manager>
<v-main class="mx-12"> <v-main class="mx-12">
<v-row> <v-row>
<v-col cols="6" offset="6"> <v-col cols="6" offset="6">
@ -15,11 +16,12 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import NavbarComponent from "./components/NavbarComponent.vue"; import AlertManager from "./components/AlertManager.vue";
import MessageBar from "./components/MessageBar.vue";
import WebdavSettingsMenu from "./components/settings/WebdavConfigMenu.vue";
import CloudList from "./components/cloud/CloudList.vue"; import CloudList from "./components/cloud/CloudList.vue";
import MessageBar from "./components/MessageBar.vue";
import NavbarComponent from "./components/NavbarComponent.vue";
import BackupConfigMenu from "./components/settings/BackupConfigMenu.vue"; import BackupConfigMenu from "./components/settings/BackupConfigMenu.vue";
import WebdavSettingsMenu from "./components/settings/WebdavConfigMenu.vue";
</script> </script>
<style scoped></style> <style scoped></style>

View File

@ -0,0 +1,56 @@
<template>
<v-fade-transition>
<div id="alertContainer" v-if="alertVisible">
<v-slide-x-transition group tag="div">
<v-alert
v-for="alert of alertList"
v-bind:key="alert.id"
elevation="24"
:type="alert.type"
border="start"
class="mb-2"
>
<v-row dense>
<v-col>
{{ alert.message }}
</v-col>
<v-col cols="2">
<v-btn
class="d-inline"
size="30"
variant="text"
rounded
icon="$close"
@click="alertStore.remove(alert.id)"
></v-btn>
</v-col>
</v-row>
<v-progress-linear
:max="alertStore.timeOutValue"
:model-value="alert.timeOut"
></v-progress-linear>
</v-alert>
</v-slide-x-transition>
</div>
</v-fade-transition>
</template>
<script lang="ts" setup>
import { useAlertStore } from "@/stores/alert";
import { storeToRefs } from "pinia";
import { computed } from "vue";
const alertStore = useAlertStore();
const { alertList } = storeToRefs(alertStore);
const alertVisible = computed(() => alertList.value.length > 0);
</script>
<style>
#alertContainer {
position: absolute;
top: 70px;
right: 20px;
z-index: 99999;
}
</style>

View File

@ -32,17 +32,21 @@ import { watch } from "vue";
defineProps<{ loading: boolean }>(); defineProps<{ loading: boolean }>();
const backupConfigStore = useBackupConfigStore(); const backupConfigStore = useBackupConfigStore();
const { data, addons, invertedAddons } = storeToRefs(backupConfigStore); const { data, addons, invertedAddons } = storeToRefs(backupConfigStore);
watch(invertedAddons, () => { watch(invertedAddons, manageInverted);
manageInverted();
function manageInverted() {
if (!data.value.exclude) { if (!data.value.exclude) {
data.value.exclude = { addon: [], folder: [] }; backupConfigStore.initExcludes();
} }
data.value.exclude.addon = []; data.value.exclude!.addon = [];
for (const addon of addons.value) { for (const addon of addons.value) {
if (!invertedAddons.value.includes(addon.slug)) { if (!invertedAddons.value.includes(addon.slug)) {
data.value.exclude.addon.push(addon.slug); data.value.exclude!.addon.push(addon.slug);
} }
} }
}); }
</script> </script>
<style scoped></style> <style scoped></style>

View File

@ -33,14 +33,21 @@ defineProps<{ loading: boolean }>();
const backupConfigStore = useBackupConfigStore(); const backupConfigStore = useBackupConfigStore();
const { data, folders, invertedFolders } = storeToRefs(backupConfigStore); const { data, folders, invertedFolders } = storeToRefs(backupConfigStore);
watch(invertedFolders, () => { watch(invertedFolders, manageInverted);
data.value.exclude.folder = [];
manageInverted();
function manageInverted() {
if (!data.value.exclude) {
backupConfigStore.initExcludes();
}
data.value.exclude!.folder = [];
for (const folder of folders.value) { for (const folder of folders.value) {
if (!invertedFolders.value.includes(folder.slug)) { if (!invertedFolders.value.includes(folder.slug)) {
data.value.exclude.folder.push(folder.slug); data.value.exclude!.folder.push(folder.slug);
} }
} }
}); }
</script> </script>
<style scoped></style> <style scoped></style>

View File

@ -12,7 +12,7 @@
<v-card-text> <v-card-text>
<backup-config-form <backup-config-form
ref="form" ref="form"
@fail="saving = false" @fail="fail"
@success="saved" @success="saved"
@loaded="loading = false" @loaded="loading = false"
@loading="loading = true" @loading="loading = true"
@ -39,6 +39,9 @@ import { useDialogStatusStore } from "@/stores/dialogStatus";
import { computed, ref } from "vue"; import { computed, ref } from "vue";
import { useMenuSize } from "@/composable/menuSize"; import { useMenuSize } from "@/composable/menuSize";
import BackupConfigForm from "./BackupConfigForm.vue"; import BackupConfigForm from "./BackupConfigForm.vue";
import { useAlertStore } from "@/stores/alert";
const alertStore = useAlertStore();
const dialogStatusStore = useDialogStatusStore(); const dialogStatusStore = useDialogStatusStore();
const form = ref<InstanceType<typeof BackupConfigForm> | null>(null); const form = ref<InstanceType<typeof BackupConfigForm> | null>(null);
@ -55,8 +58,14 @@ function save() {
form.value?.save(); form.value?.save();
} }
function saved() { function fail() {
dialogStatusStore.webdav = false;
saving.value = false; saving.value = false;
alertStore.add("error", "Fail to save backup settings !");
}
function saved() {
dialogStatusStore.backup = false;
saving.value = false;
alertStore.add("success", "Backup settings saved !");
} }
</script> </script>

View File

@ -12,7 +12,7 @@
<v-card-text> <v-card-text>
<webdav-settings-form <webdav-settings-form
ref="form" ref="form"
@fail="saving = false" @fail="fail"
@success="saved" @success="saved"
@loaded="loading = false" @loaded="loading = false"
@loading="loading = true" @loading="loading = true"
@ -36,10 +36,13 @@
<script setup lang="ts"> <script setup lang="ts">
import { useMenuSize } from "@/composable/menuSize"; import { useMenuSize } from "@/composable/menuSize";
import { useAlertStore } from "@/stores/alert";
import { useDialogStatusStore } from "@/stores/dialogStatus"; import { useDialogStatusStore } from "@/stores/dialogStatus";
import { computed, ref } from "vue"; import { computed, ref } from "vue";
import WebdavSettingsForm from "./WebdavConfigForm.vue"; import WebdavSettingsForm from "./WebdavConfigForm.vue";
const alertStore = useAlertStore();
const dialogStatusStore = useDialogStatusStore(); const dialogStatusStore = useDialogStatusStore();
const form = ref<InstanceType<typeof WebdavSettingsForm> | null>(null); const form = ref<InstanceType<typeof WebdavSettingsForm> | null>(null);
const { width, isFullScreen } = useMenuSize(); const { width, isFullScreen } = useMenuSize();
@ -55,8 +58,14 @@ function save() {
form.value?.save(); form.value?.save();
} }
function fail() {
saving.value = false;
alertStore.add("error", "Fail to save cloud settings !");
}
function saved() { function saved() {
dialogStatusStore.webdav = false; dialogStatusStore.webdav = false;
saving.value = false; saving.value = false;
alertStore.add("success", "Cloud settings saved !");
} }
</script> </script>

View File

@ -0,0 +1,42 @@
import type { Alert } from "@/types/alert";
import { defineStore } from "pinia";
import { ref, type Ref } from "vue";
import { v4 as uuidv4 } from "uuid";
export const useAlertStore = defineStore("alert", () => {
const alertList = ref([]) as Ref<Alert[]>;
const timeOutValue = ref(100);
function add(
type: "error" | "success" | "warning" | "info",
message: string
) {
const alert: Alert = {
id: uuidv4(),
timeOut: ref(timeOutValue.value),
interval: setInterval(() => {
timeout(alert);
}, 50),
type: type,
message: message,
};
alertList.value.push(alert);
}
function timeout(alert: Alert) {
if (alert.timeOut.value <= 0) {
remove(alert.id!);
} else {
alert.timeOut.value--;
}
}
function remove(id: string) {
const alertToRemove = alertList.value.find((value) => value.id == id);
if (alertToRemove) {
clearInterval(alertToRemove.interval);
}
alertList.value = alertList.value.filter((value) => value.id != id);
}
return { alertList, timeOutValue, add, remove };
});

View File

@ -1,6 +1,6 @@
import { getBackupConfig } from "@/services/configService"; import { getBackupConfig } from "@/services/configService";
import { getAddons, getFolders } from "@/services/homeAssistantService"; import { getAddons, getFolders } from "@/services/homeAssistantService";
import { CronMode, type BackupConfig } from "@/types/backupConfig"; import { BackupType, CronMode, type BackupConfig } from "@/types/backupConfig";
import type { Folder, AddonModel } from "@/types/homeAssistant"; import type { Folder, AddonModel } from "@/types/homeAssistant";
import { defineStore } from "pinia"; import { defineStore } from "pinia";
import { ref } from "vue"; import { ref } from "vue";
@ -20,6 +20,7 @@ export const useBackupConfigStore = defineStore("backupConfig", () => {
const foldersProm = getFolders(); const foldersProm = getFolders();
const addonsProm = getAddons(); const addonsProm = getAddons();
return Promise.all([conf, foldersProm, addonsProm]).then((value) => { return Promise.all([conf, foldersProm, addonsProm]).then((value) => {
if (value[0].backupType == BackupType.Partial && value[0].exclude) {
for (const folder of value[1]) { for (const folder of value[1]) {
if (!value[0].exclude.folder.includes(folder.slug)) { if (!value[0].exclude.folder.includes(folder.slug)) {
invertedFolders.value.push(folder.slug); invertedFolders.value.push(folder.slug);
@ -30,6 +31,8 @@ export const useBackupConfigStore = defineStore("backupConfig", () => {
invertedAddons.value.push(addon.slug); invertedAddons.value.push(addon.slug);
} }
} }
}
data.value = value[0]; data.value = value[0];
folders.value = value[1]; folders.value = value[1];
addons.value = value[2].addons; addons.value = value[2].addons;
@ -43,6 +46,16 @@ export const useBackupConfigStore = defineStore("backupConfig", () => {
}); });
} }
function initExcludes() {
data.value.exclude = { addon: [], folder: [] };
addons.value.forEach((value) => {
invertedAddons.value.push(value.slug);
});
folders.value.forEach((value) => {
invertedFolders.value.push(value.slug);
});
}
function removeCron(id: string) { function removeCron(id: string) {
data.value.cron = data.value.cron.filter((value) => value.id != id); data.value.cron = data.value.cron.filter((value) => value.id != id);
} }
@ -56,5 +69,6 @@ export const useBackupConfigStore = defineStore("backupConfig", () => {
loadAll, loadAll,
addEmptyCron, addEmptyCron,
removeCron, removeCron,
initExcludes,
}; };
}); });

View File

@ -0,0 +1,9 @@
import type { Ref } from "vue";
export interface Alert {
id: string;
type: "error" | "success" | "warning" | "info";
message: string;
timeOut: Ref<number>;
interval: number;
}