mirror of
https://github.com/Sebclem/hassio-nextcloud-backup.git
synced 2024-11-28 20:04:52 +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
|
||||
.getBackups()
|
||||
.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) => {
|
||||
res.status(500);
|
||||
@ -29,6 +41,6 @@ homeAssistantRouter.get("/addons", (req, res, next) => {
|
||||
|
||||
homeAssistantRouter.get("/folders", (req, res, next) => {
|
||||
res.json(haOsService.getFolderList());
|
||||
})
|
||||
});
|
||||
|
||||
export default homeAssistantRouter;
|
||||
|
@ -185,7 +185,7 @@ function delSnap(id: string) {
|
||||
);
|
||||
}
|
||||
|
||||
function checkSnap(id: string) {
|
||||
function getBackupInfo(id: string) {
|
||||
const option: OptionsOfJSONResponseBody = {
|
||||
headers: { authorization: `Bearer ${token}` },
|
||||
responseType: "json",
|
||||
@ -269,7 +269,7 @@ function clean(backups: BackupModel[]) {
|
||||
return;
|
||||
}
|
||||
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);
|
||||
for (const i of toDel) {
|
||||
@ -511,4 +511,5 @@ export {
|
||||
startAddons,
|
||||
clean,
|
||||
publish_state,
|
||||
getBackupInfo
|
||||
};
|
||||
|
@ -51,7 +51,7 @@ export interface BackupModel {
|
||||
slug: string;
|
||||
date: string;
|
||||
name: string;
|
||||
type: string;
|
||||
type: "full" | "partial";
|
||||
protected: boolean;
|
||||
content: BackupContent;
|
||||
compressed: boolean;
|
||||
@ -65,7 +65,7 @@ export interface BackupContent {
|
||||
|
||||
export interface BackupDetailModel {
|
||||
slug: string;
|
||||
type: string;
|
||||
type: "full" | "partial";
|
||||
name: string;
|
||||
date: string;
|
||||
size: string;
|
||||
@ -76,7 +76,7 @@ export interface BackupDetailModel {
|
||||
name: string;
|
||||
version: string;
|
||||
size: number;
|
||||
};
|
||||
}[];
|
||||
repositories: string[];
|
||||
folders: string[];
|
||||
}
|
||||
|
@ -7,7 +7,10 @@
|
||||
<alert-manager></alert-manager>
|
||||
<v-main class="mx-12">
|
||||
<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>
|
||||
</v-col>
|
||||
</v-row>
|
||||
@ -18,6 +21,7 @@
|
||||
<script setup lang="ts">
|
||||
import AlertManager from "./components/AlertManager.vue";
|
||||
import CloudList from "./components/cloud/CloudList.vue";
|
||||
import HaList from "./components/homeAssistant/HaList.vue";
|
||||
import MessageBar from "./components/MessageBar.vue";
|
||||
import NavbarComponent from "./components/NavbarComponent.vue";
|
||||
import BackupConfigMenu from "./components/settings/BackupConfigMenu.vue";
|
||||
|
@ -3,7 +3,7 @@
|
||||
<v-dialog
|
||||
v-model="dialog"
|
||||
:width="width"
|
||||
:fullscreen="xs"
|
||||
:fullscreen="isFullScreen"
|
||||
:persistent="loading"
|
||||
>
|
||||
<v-card>
|
||||
@ -30,25 +30,17 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useMenuSize } from "@/composable/menuSize";
|
||||
import { deleteWebdabBackup } from "@/services/webdavService";
|
||||
import type { WebdavBackup } from "@/types/webdav";
|
||||
import { computed, ref } from "vue";
|
||||
import { useDisplay } from "vuetify/dist/vuetify";
|
||||
|
||||
const { xs, mdAndDown } = useDisplay();
|
||||
const dialog = ref(false);
|
||||
const loading = ref(false);
|
||||
const item = ref<WebdavBackup | null>(null);
|
||||
|
||||
const width = computed(() => {
|
||||
if (xs.value) {
|
||||
return undefined;
|
||||
} else if (mdAndDown.value) {
|
||||
return "80%";
|
||||
} else {
|
||||
return "50%";
|
||||
}
|
||||
});
|
||||
const { width, isFullScreen } = useMenuSize();
|
||||
|
||||
function confirm() {
|
||||
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";
|
||||
|
||||
export function getFolders() {
|
||||
@ -8,3 +13,11 @@ export function getFolders() {
|
||||
export function getAddons() {
|
||||
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;
|
||||
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