mirror of
https://github.com/Sebclem/hassio-nextcloud-backup.git
synced 2024-11-29 04:14:53 +01:00
🔨 Add home assistant backup list
This commit is contained in:
parent
d47ae5d5cc
commit
8f51d5d837
@ -7,7 +7,19 @@ homeAssistantRouter.get("/backups/", (req, res, next) => {
|
|||||||
haOsService
|
haOsService
|
||||||
.getBackups()
|
.getBackups()
|
||||||
.then((value) => {
|
.then((value) => {
|
||||||
res.json(value.body.data.backups);
|
res.json(value.body.data.backups.sort((a, b)=> Date.parse(b.date) - Date.parse(a.date)));
|
||||||
|
})
|
||||||
|
.catch((reason) => {
|
||||||
|
res.status(500);
|
||||||
|
res.json(reason);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
homeAssistantRouter.get("/backup/:slug", (req, res, next) => {
|
||||||
|
haOsService
|
||||||
|
.getBackupInfo(req.params.slug)
|
||||||
|
.then((value) => {
|
||||||
|
res.json(value.body.data);
|
||||||
})
|
})
|
||||||
.catch((reason) => {
|
.catch((reason) => {
|
||||||
res.status(500);
|
res.status(500);
|
||||||
@ -29,6 +41,6 @@ homeAssistantRouter.get("/addons", (req, res, next) => {
|
|||||||
|
|
||||||
homeAssistantRouter.get("/folders", (req, res, next) => {
|
homeAssistantRouter.get("/folders", (req, res, next) => {
|
||||||
res.json(haOsService.getFolderList());
|
res.json(haOsService.getFolderList());
|
||||||
})
|
});
|
||||||
|
|
||||||
export default homeAssistantRouter;
|
export default homeAssistantRouter;
|
||||||
|
@ -185,7 +185,7 @@ function delSnap(id: string) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function checkSnap(id: string) {
|
function getBackupInfo(id: string) {
|
||||||
const option: OptionsOfJSONResponseBody = {
|
const option: OptionsOfJSONResponseBody = {
|
||||||
headers: { authorization: `Bearer ${token}` },
|
headers: { authorization: `Bearer ${token}` },
|
||||||
responseType: "json",
|
responseType: "json",
|
||||||
@ -269,7 +269,7 @@ function clean(backups: BackupModel[]) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
backups.sort((a, b) => {
|
backups.sort((a, b) => {
|
||||||
return a.date < b.date ? 1 : -1;
|
return Date.parse(b.date) - Date.parse(a.date);
|
||||||
});
|
});
|
||||||
const toDel = backups.slice(limit);
|
const toDel = backups.slice(limit);
|
||||||
for (const i of toDel) {
|
for (const i of toDel) {
|
||||||
@ -511,4 +511,5 @@ export {
|
|||||||
startAddons,
|
startAddons,
|
||||||
clean,
|
clean,
|
||||||
publish_state,
|
publish_state,
|
||||||
|
getBackupInfo
|
||||||
};
|
};
|
||||||
|
@ -51,7 +51,7 @@ export interface BackupModel {
|
|||||||
slug: string;
|
slug: string;
|
||||||
date: string;
|
date: string;
|
||||||
name: string;
|
name: string;
|
||||||
type: string;
|
type: "full" | "partial";
|
||||||
protected: boolean;
|
protected: boolean;
|
||||||
content: BackupContent;
|
content: BackupContent;
|
||||||
compressed: boolean;
|
compressed: boolean;
|
||||||
@ -65,7 +65,7 @@ export interface BackupContent {
|
|||||||
|
|
||||||
export interface BackupDetailModel {
|
export interface BackupDetailModel {
|
||||||
slug: string;
|
slug: string;
|
||||||
type: string;
|
type: "full" | "partial";
|
||||||
name: string;
|
name: string;
|
||||||
date: string;
|
date: string;
|
||||||
size: string;
|
size: string;
|
||||||
@ -76,7 +76,7 @@ export interface BackupDetailModel {
|
|||||||
name: string;
|
name: string;
|
||||||
version: string;
|
version: string;
|
||||||
size: number;
|
size: number;
|
||||||
};
|
}[];
|
||||||
repositories: string[];
|
repositories: string[];
|
||||||
folders: string[];
|
folders: string[];
|
||||||
}
|
}
|
||||||
|
@ -7,7 +7,10 @@
|
|||||||
<alert-manager></alert-manager>
|
<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">
|
||||||
|
<ha-list></ha-list>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="6">
|
||||||
<cloud-list></cloud-list>
|
<cloud-list></cloud-list>
|
||||||
</v-col>
|
</v-col>
|
||||||
</v-row>
|
</v-row>
|
||||||
@ -18,6 +21,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import AlertManager from "./components/AlertManager.vue";
|
import AlertManager from "./components/AlertManager.vue";
|
||||||
import CloudList from "./components/cloud/CloudList.vue";
|
import CloudList from "./components/cloud/CloudList.vue";
|
||||||
|
import HaList from "./components/homeAssistant/HaList.vue";
|
||||||
import MessageBar from "./components/MessageBar.vue";
|
import MessageBar from "./components/MessageBar.vue";
|
||||||
import NavbarComponent from "./components/NavbarComponent.vue";
|
import NavbarComponent from "./components/NavbarComponent.vue";
|
||||||
import BackupConfigMenu from "./components/settings/BackupConfigMenu.vue";
|
import BackupConfigMenu from "./components/settings/BackupConfigMenu.vue";
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
<v-dialog
|
<v-dialog
|
||||||
v-model="dialog"
|
v-model="dialog"
|
||||||
:width="width"
|
:width="width"
|
||||||
:fullscreen="xs"
|
:fullscreen="isFullScreen"
|
||||||
:persistent="loading"
|
:persistent="loading"
|
||||||
>
|
>
|
||||||
<v-card>
|
<v-card>
|
||||||
@ -30,25 +30,17 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { useMenuSize } from "@/composable/menuSize";
|
||||||
import { deleteWebdabBackup } from "@/services/webdavService";
|
import { deleteWebdabBackup } from "@/services/webdavService";
|
||||||
import type { WebdavBackup } from "@/types/webdav";
|
import type { WebdavBackup } from "@/types/webdav";
|
||||||
import { computed, ref } from "vue";
|
import { computed, ref } from "vue";
|
||||||
import { useDisplay } from "vuetify/dist/vuetify";
|
import { useDisplay } from "vuetify/dist/vuetify";
|
||||||
|
|
||||||
const { xs, mdAndDown } = useDisplay();
|
|
||||||
const dialog = ref(false);
|
const dialog = ref(false);
|
||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
const item = ref<WebdavBackup | null>(null);
|
const item = ref<WebdavBackup | null>(null);
|
||||||
|
|
||||||
const width = computed(() => {
|
const { width, isFullScreen } = useMenuSize();
|
||||||
if (xs.value) {
|
|
||||||
return undefined;
|
|
||||||
} else if (mdAndDown.value) {
|
|
||||||
return "80%";
|
|
||||||
} else {
|
|
||||||
return "50%";
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
function confirm() {
|
function confirm() {
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
|
@ -0,0 +1,61 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<v-card elevation="10" class="mt-10" border>
|
||||||
|
<v-card-title class="text-center"> Home Assistant </v-card-title>
|
||||||
|
<v-divider></v-divider>
|
||||||
|
<v-card-text>
|
||||||
|
<v-row>
|
||||||
|
<v-col>
|
||||||
|
<v-card>
|
||||||
|
<v-card-title
|
||||||
|
class="text-center text-white bg-light-blue-darken-4"
|
||||||
|
>Backups</v-card-title
|
||||||
|
>
|
||||||
|
<v-divider></v-divider>
|
||||||
|
<v-card-text class="pa-0">
|
||||||
|
<v-list class="pa-0">
|
||||||
|
<v-list-item
|
||||||
|
v-if="backups.length == 0"
|
||||||
|
class="text-center text-subtitle-2 text-disabled"
|
||||||
|
>No backup in Home Assistant</v-list-item
|
||||||
|
>
|
||||||
|
<ha-list-item
|
||||||
|
v-for="(item, index) in backups"
|
||||||
|
:key="item.slug"
|
||||||
|
:item="item"
|
||||||
|
:index="index"
|
||||||
|
>
|
||||||
|
</ha-list-item>
|
||||||
|
</v-list>
|
||||||
|
</v-card-text>
|
||||||
|
</v-card>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
</v-card-text>
|
||||||
|
</v-card>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import type { BackupModel } from "@/types/homeAssistant";
|
||||||
|
import { ref, onBeforeUnmount } from "vue";
|
||||||
|
import { getBackups } from "@/services/homeAssistantService";
|
||||||
|
import HaListItem from "./HaListItem.vue";
|
||||||
|
|
||||||
|
const backups = ref<BackupModel[]>([]);
|
||||||
|
|
||||||
|
function refreshBackup() {
|
||||||
|
getBackups().then((value) => {
|
||||||
|
backups.value = value;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
refreshBackup();
|
||||||
|
const interval = setInterval(refreshBackup, 2000);
|
||||||
|
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
clearInterval(interval);
|
||||||
|
});
|
||||||
|
|
||||||
|
// TODO Manage delete
|
||||||
|
</script>
|
@ -0,0 +1,197 @@
|
|||||||
|
<template>
|
||||||
|
<v-divider v-if="index != 0" color="grey-darken-3"></v-divider>
|
||||||
|
<v-list-item class="bg-grey-darken-3">
|
||||||
|
<v-list-item-title>{{ item.name }}</v-list-item-title>
|
||||||
|
<template v-slot:append>
|
||||||
|
<v-scroll-x-transition>
|
||||||
|
<div v-if="!detail">
|
||||||
|
<v-chip color="primary" variant="flat" size="small" class="mr-2">
|
||||||
|
{{ item.type == "partial" ? "P" : "F" }}
|
||||||
|
</v-chip>
|
||||||
|
<v-chip color="primary" variant="flat" size="small" class="mr-1">
|
||||||
|
{{
|
||||||
|
DateTime.fromISO(item.date).toLocaleString(DateTime.DATETIME_MED)
|
||||||
|
}}
|
||||||
|
</v-chip>
|
||||||
|
</div>
|
||||||
|
</v-scroll-x-transition>
|
||||||
|
<v-btn variant="text" icon color="success" @click="detail = !detail">
|
||||||
|
<v-icon>{{ detail ? "mdi-chevron-up" : "mdi-information" }}</v-icon>
|
||||||
|
</v-btn>
|
||||||
|
</template>
|
||||||
|
</v-list-item>
|
||||||
|
<v-expand-transition>
|
||||||
|
<v-card v-show="detail" variant="tonal" color="secondary" rounded="0">
|
||||||
|
<v-card-text>
|
||||||
|
<v-row v-if="!detailData">
|
||||||
|
<v-col class="text-center">
|
||||||
|
<v-progress-circular indeterminate></v-progress-circular>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
<template v-if="detailData">
|
||||||
|
<v-row>
|
||||||
|
<v-col class="d-flex justify-center">
|
||||||
|
<v-tooltip text="Creation" location="top">
|
||||||
|
<template v-slot:activator="{ props }">
|
||||||
|
<v-chip
|
||||||
|
color="primary"
|
||||||
|
variant="flat"
|
||||||
|
class="mr-2"
|
||||||
|
label
|
||||||
|
v-bind="props"
|
||||||
|
>
|
||||||
|
<v-icon start icon="mdi-folder-plus"></v-icon>
|
||||||
|
{{
|
||||||
|
DateTime.fromISO(detailData.date).toLocaleString(
|
||||||
|
DateTime.DATETIME_MED
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
</v-chip>
|
||||||
|
</template>
|
||||||
|
</v-tooltip>
|
||||||
|
<v-tooltip text="Home Assistant Version" location="top">
|
||||||
|
<template v-slot:activator="{ props }">
|
||||||
|
<v-chip color="success" variant="flat" label v-bind="props">
|
||||||
|
<v-icon start icon="mdi-home-assistant"></v-icon>
|
||||||
|
{{ detailData.homeassistant }}
|
||||||
|
</v-chip>
|
||||||
|
</template>
|
||||||
|
</v-tooltip>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
<v-row dense>
|
||||||
|
<v-col class="d-flex justify-center">
|
||||||
|
<v-tooltip text="Password protection" location="top">
|
||||||
|
<template v-slot:activator="{ props }">
|
||||||
|
<v-chip
|
||||||
|
:color="detailData.protected ? 'success' : 'primary'"
|
||||||
|
variant="flat"
|
||||||
|
class="mr-2"
|
||||||
|
label
|
||||||
|
v-bind="props"
|
||||||
|
>
|
||||||
|
<v-icon
|
||||||
|
start
|
||||||
|
:icon="
|
||||||
|
detailData.protected ? 'mdi-lock' : 'mdi-lock-open'
|
||||||
|
"
|
||||||
|
></v-icon>
|
||||||
|
{{ detailData.protected ? "Protected" : "Unprotected" }}
|
||||||
|
</v-chip>
|
||||||
|
</template>
|
||||||
|
</v-tooltip>
|
||||||
|
<v-tooltip text="Size" location="top">
|
||||||
|
<template v-slot:activator="{ props }">
|
||||||
|
<v-chip
|
||||||
|
color="success"
|
||||||
|
variant="flat"
|
||||||
|
class="mr-2"
|
||||||
|
label
|
||||||
|
v-bind="props"
|
||||||
|
>
|
||||||
|
<v-icon start icon="mdi-database"></v-icon>
|
||||||
|
{{ detailData.size }} MB
|
||||||
|
</v-chip>
|
||||||
|
</template>
|
||||||
|
</v-tooltip>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
<v-row dense>
|
||||||
|
<v-col class="d-flex justify-center">
|
||||||
|
<v-tooltip text="Backup Type" location="top">
|
||||||
|
<template v-slot:activator="{ props }">
|
||||||
|
<v-chip color="success" variant="flat" label v-bind="props">
|
||||||
|
<v-icon
|
||||||
|
start
|
||||||
|
:icon="
|
||||||
|
detailData.type == 'full'
|
||||||
|
? 'mdi-alpha-f-box'
|
||||||
|
: 'mdi-alpha-p-box'
|
||||||
|
"
|
||||||
|
></v-icon>
|
||||||
|
{{ detailData.type }}
|
||||||
|
</v-chip>
|
||||||
|
</template>
|
||||||
|
</v-tooltip>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
<v-row>
|
||||||
|
<v-col class="d-flex justify-center text-white">
|
||||||
|
<h3>Content</h3>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
<v-divider class="mt-2"></v-divider>
|
||||||
|
<v-row>
|
||||||
|
<v-col cols="12" lg="6">
|
||||||
|
<div class="text-center text-white mt-2">
|
||||||
|
<h3>Folders</h3>
|
||||||
|
</div>
|
||||||
|
<v-list density="compact" variant="tonal">
|
||||||
|
<ha-list-item-content
|
||||||
|
v-for="item of detailData.folders.sort()"
|
||||||
|
:name="item"
|
||||||
|
v-bind:key="item"
|
||||||
|
></ha-list-item-content>
|
||||||
|
</v-list>
|
||||||
|
</v-col>
|
||||||
|
<v-col>
|
||||||
|
<div class="text-center text-white mt-2">
|
||||||
|
<h3>Addons</h3>
|
||||||
|
</div>
|
||||||
|
<v-list density="compact" variant="tonal">
|
||||||
|
<ha-list-item-content
|
||||||
|
v-for="item of detailData.addons.sort((a, b) =>
|
||||||
|
a.name.localeCompare(b.name)
|
||||||
|
)"
|
||||||
|
:name="item.name"
|
||||||
|
v-bind:key="item.slug"
|
||||||
|
:version="item.version"
|
||||||
|
></ha-list-item-content>
|
||||||
|
</v-list>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
</template>
|
||||||
|
</v-card-text>
|
||||||
|
<v-divider class="mx-4"></v-divider>
|
||||||
|
<v-card-actions class="justify-center">
|
||||||
|
<v-tooltip text="Upload to Cloud" location="bottom">
|
||||||
|
<template v-slot:activator="{ props }">
|
||||||
|
<v-btn variant="outlined" color="success" v-bind="props">
|
||||||
|
<v-icon>mdi-cloud-upload</v-icon>
|
||||||
|
</v-btn>
|
||||||
|
</template>
|
||||||
|
</v-tooltip>
|
||||||
|
<v-btn variant="outlined" color="red" @click="emits('delete', item)">
|
||||||
|
<v-icon>mdi-trash-can</v-icon>
|
||||||
|
</v-btn>
|
||||||
|
</v-card-actions>
|
||||||
|
</v-card>
|
||||||
|
</v-expand-transition>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { getBackupDetail } from "@/services/homeAssistantService";
|
||||||
|
import type { BackupDetailModel, BackupModel } from "@/types/homeAssistant";
|
||||||
|
import { DateTime } from "luxon";
|
||||||
|
import { ref, watch } from "vue";
|
||||||
|
import HaListItemContent from "./HaListItemContent.vue";
|
||||||
|
|
||||||
|
const detail = ref(false);
|
||||||
|
const detailData = ref<BackupDetailModel | null>(null);
|
||||||
|
const props = defineProps<{
|
||||||
|
item: BackupModel;
|
||||||
|
index: number;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
watch(detail, (value) => {
|
||||||
|
if (value) {
|
||||||
|
getBackupDetail(props.item.slug).then((response) => {
|
||||||
|
detailData.value = response;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const emits = defineEmits<{
|
||||||
|
(e: "delete", item: BackupModel): void;
|
||||||
|
}>();
|
||||||
|
</script>
|
@ -0,0 +1,16 @@
|
|||||||
|
<template>
|
||||||
|
<v-list-item :title="name" rounded>
|
||||||
|
<template v-slot:append v-if="version">
|
||||||
|
<div class="text-secondary">{{ version }}</div>
|
||||||
|
</template>
|
||||||
|
</v-list-item>
|
||||||
|
<v-divider></v-divider>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
name: string;
|
||||||
|
version?: string;
|
||||||
|
}>();
|
||||||
|
</script>
|
@ -1,4 +1,9 @@
|
|||||||
import type { AddonData, Folder } from "@/types/homeAssistant";
|
import type {
|
||||||
|
BackupDetailModel,
|
||||||
|
AddonData,
|
||||||
|
BackupModel,
|
||||||
|
Folder,
|
||||||
|
} from "@/types/homeAssistant";
|
||||||
import kyClient from "./kyClient";
|
import kyClient from "./kyClient";
|
||||||
|
|
||||||
export function getFolders() {
|
export function getFolders() {
|
||||||
@ -8,3 +13,11 @@ export function getFolders() {
|
|||||||
export function getAddons() {
|
export function getAddons() {
|
||||||
return kyClient.get("homeAssistant/addons").json<AddonData>();
|
return kyClient.get("homeAssistant/addons").json<AddonData>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getBackups() {
|
||||||
|
return kyClient.get("homeAssistant/backups").json<BackupModel[]>();
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getBackupDetail(slug: string) {
|
||||||
|
return kyClient.get(`homeAssistant/backup/${slug}`).json<BackupDetailModel>();
|
||||||
|
}
|
||||||
|
@ -22,3 +22,37 @@ export interface AddonModel {
|
|||||||
logo: boolean;
|
logo: boolean;
|
||||||
state: string;
|
state: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface BackupModel {
|
||||||
|
slug: string;
|
||||||
|
date: string;
|
||||||
|
name: string;
|
||||||
|
type: "full" | "partial";
|
||||||
|
protected: boolean;
|
||||||
|
content: BackupContent;
|
||||||
|
compressed: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface BackupContent {
|
||||||
|
homeassistant: boolean;
|
||||||
|
addons: string[];
|
||||||
|
folders: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface BackupDetailModel {
|
||||||
|
slug: string;
|
||||||
|
type: "full" | "partial";
|
||||||
|
name: string;
|
||||||
|
date: string;
|
||||||
|
size: string;
|
||||||
|
protected: boolean;
|
||||||
|
homeassistant: string;
|
||||||
|
addons: {
|
||||||
|
slug: string;
|
||||||
|
name: string;
|
||||||
|
version: string;
|
||||||
|
size: number;
|
||||||
|
}[];
|
||||||
|
repositories: string[];
|
||||||
|
folders: string[];
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user