mirror of
https://github.com/Sebclem/hassio-nextcloud-backup.git
synced 2024-11-30 12:54:52 +01:00
Compare commits
5 Commits
2c03791db5
...
56cfb4ee78
Author | SHA1 | Date | |
---|---|---|---|
56cfb4ee78 | |||
630f048c13 | |||
02a2e73c29 | |||
e94f767e93 | |||
c4df879434 |
@ -13,8 +13,7 @@ actionRouter.post("/backup", (req, res) => {
|
|||||||
.catch(() => {
|
.catch(() => {
|
||||||
logger.error("Something wrong !");
|
logger.error("Something wrong !");
|
||||||
});
|
});
|
||||||
res.statusCode = 200;
|
res.sendStatus(202);
|
||||||
res.send();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export default actionRouter;
|
export default actionRouter;
|
||||||
|
@ -24,12 +24,10 @@ configRouter.put("/backup", (req, res) => {
|
|||||||
validateBackupConfig(req.body as BackupConfig)
|
validateBackupConfig(req.body as BackupConfig)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
saveBackupConfig(req.body as BackupConfig);
|
saveBackupConfig(req.body as BackupConfig);
|
||||||
res.status(204);
|
res.status(204).send();
|
||||||
res.send();
|
|
||||||
})
|
})
|
||||||
.catch((error: ValidationError) => {
|
.catch((error: ValidationError) => {
|
||||||
res.status(400);
|
res.status(400).json(error.details);
|
||||||
res.json(error.details);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -44,8 +42,7 @@ configRouter.put("/webdav", (req, res) => {
|
|||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
saveWebdavConfig(req.body as WebdavConfig);
|
saveWebdavConfig(req.body as WebdavConfig);
|
||||||
res.status(204);
|
res.status(204).send();
|
||||||
res.send();
|
|
||||||
})
|
})
|
||||||
.catch((error: ValidationError) => {
|
.catch((error: ValidationError) => {
|
||||||
res.status(400);
|
res.status(400);
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
import express from "express";
|
import express from "express";
|
||||||
import * as haOsService from "../services/homeAssistantService.js";
|
import * as haOsService from "../services/homeAssistantService.js";
|
||||||
|
import { uploadToCloud } from "../services/orchestrator.js";
|
||||||
|
import logger from "../config/winston.js";
|
||||||
|
|
||||||
const homeAssistantRouter = express.Router();
|
const homeAssistantRouter = express.Router();
|
||||||
|
|
||||||
@ -14,8 +16,7 @@ homeAssistantRouter.get("/backups/", (req, res) => {
|
|||||||
);
|
);
|
||||||
})
|
})
|
||||||
.catch((reason) => {
|
.catch((reason) => {
|
||||||
res.status(500);
|
res.status(500).json(reason);
|
||||||
res.json(reason);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -26,11 +27,32 @@ homeAssistantRouter.get("/backup/:slug", (req, res) => {
|
|||||||
res.json(value.body.data);
|
res.json(value.body.data);
|
||||||
})
|
})
|
||||||
.catch((reason) => {
|
.catch((reason) => {
|
||||||
res.status(500);
|
res.status(500).json(reason);
|
||||||
res.json(reason);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
homeAssistantRouter.delete("/backup/:slug", (req, res) => {
|
||||||
|
haOsService
|
||||||
|
.delSnap(req.params.slug)
|
||||||
|
.then((value) => {
|
||||||
|
res.json(value.body);
|
||||||
|
})
|
||||||
|
.catch((reason) => {
|
||||||
|
res.status(500).json(reason);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
homeAssistantRouter.post("/backup/:slug/upload", (req, res) => {
|
||||||
|
uploadToCloud(req.params.slug)
|
||||||
|
.then(() => {
|
||||||
|
logger.info("All good !");
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
logger.error("Something wrong !");
|
||||||
|
});
|
||||||
|
res.sendStatus(202);
|
||||||
|
});
|
||||||
|
|
||||||
homeAssistantRouter.get("/addons", (req, res) => {
|
homeAssistantRouter.get("/addons", (req, res) => {
|
||||||
haOsService
|
haOsService
|
||||||
.getAddonList()
|
.getAddonList()
|
||||||
@ -38,8 +60,7 @@ homeAssistantRouter.get("/addons", (req, res) => {
|
|||||||
res.json(value.body.data);
|
res.json(value.body.data);
|
||||||
})
|
})
|
||||||
.catch((reason) => {
|
.catch((reason) => {
|
||||||
res.status(500);
|
res.status(500).json(reason);
|
||||||
res.json(reason);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -11,7 +11,7 @@ messageRouter.patch("/:messageId/readed", (req, res) => {
|
|||||||
if (messageManager.markReaded(req.params.messageId)) {
|
if (messageManager.markReaded(req.params.messageId)) {
|
||||||
res.json(messageManager.get());
|
res.json(messageManager.get());
|
||||||
} else {
|
} else {
|
||||||
res.status(404).send();
|
res.sendStatus(404);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -7,8 +7,11 @@ import {
|
|||||||
} from "../services/webdavConfigService.js";
|
} from "../services/webdavConfigService.js";
|
||||||
import * as webdavService from "../services/webdavService.js";
|
import * as webdavService from "../services/webdavService.js";
|
||||||
import * as pathTools from "../tools/pathTools.js";
|
import * as pathTools from "../tools/pathTools.js";
|
||||||
import type { WebdavDelete } from "../types/services/webdav.js";
|
import type { WebdavGenericPath } from "../types/services/webdav.js";
|
||||||
import { WebdavDeleteValidation } from "../types/services/webdavValidation.js";
|
import { WebdavDeleteValidation } from "../types/services/webdavValidation.js";
|
||||||
|
import { restoreToHA } from "../services/orchestrator.js";
|
||||||
|
import path from "path";
|
||||||
|
import logger from "../config/winston.js";
|
||||||
|
|
||||||
const webdavRouter = express.Router();
|
const webdavRouter = express.Router();
|
||||||
|
|
||||||
@ -55,7 +58,7 @@ webdavRouter.get("/backup/manual", (req, res) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
webdavRouter.delete("/", (req, res) => {
|
webdavRouter.delete("/", (req, res) => {
|
||||||
const body = req.body as WebdavDelete;
|
const body = req.body as WebdavGenericPath;
|
||||||
const validator = Joi.object(WebdavDeleteValidation);
|
const validator = Joi.object(WebdavDeleteValidation);
|
||||||
const config = getWebdavConfig();
|
const config = getWebdavConfig();
|
||||||
validateWebdavConfig(config)
|
validateWebdavConfig(config)
|
||||||
@ -80,4 +83,21 @@ webdavRouter.delete("/", (req, res) => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
webdavRouter.post("/restore", (req, res) => {
|
||||||
|
const body = req.body as WebdavGenericPath;
|
||||||
|
const validator = Joi.object(WebdavDeleteValidation);
|
||||||
|
validator
|
||||||
|
.validateAsync(body)
|
||||||
|
.then(() => {
|
||||||
|
return restoreToHA(body.path, path.basename(body.path));
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
logger.info("All good !");
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
logger.error("Something wrong !");
|
||||||
|
});
|
||||||
|
res.sendStatus(202);
|
||||||
|
});
|
||||||
|
|
||||||
export default webdavRouter;
|
export default webdavRouter;
|
||||||
|
@ -4,7 +4,6 @@ import FormData from "form-data";
|
|||||||
import got, {
|
import got, {
|
||||||
RequestError,
|
RequestError,
|
||||||
type OptionsOfJSONResponseBody,
|
type OptionsOfJSONResponseBody,
|
||||||
type PlainResponse,
|
|
||||||
type Progress,
|
type Progress,
|
||||||
type Response,
|
type Response,
|
||||||
} from "got";
|
} from "got";
|
||||||
@ -162,6 +161,7 @@ function downloadSnapshot(id: string): Promise<string> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function delSnap(id: string) {
|
function delSnap(id: string) {
|
||||||
|
logger.info(`Deleting Home Assistant backup ${id}`);
|
||||||
const option = {
|
const option = {
|
||||||
headers: { authorization: `Bearer ${token}` },
|
headers: { authorization: `Bearer ${token}` },
|
||||||
};
|
};
|
||||||
@ -295,54 +295,42 @@ function clean(backups: BackupModel[], numberToKeep: number) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function uploadSnapshot(path: string) {
|
function uploadSnapshot(path: string) {
|
||||||
return new Promise((resolve, reject) => {
|
const status = statusTools.getStatus();
|
||||||
const status = statusTools.getStatus();
|
status.status = States.BKUP_UPLOAD_HA;
|
||||||
status.status = States.BKUP_UPLOAD_HA;
|
status.progress = 0;
|
||||||
status.progress = 0;
|
statusTools.setStatus(status);
|
||||||
statusTools.setStatus(status);
|
logger.info("Uploading backup...");
|
||||||
logger.info("Uploading backup...");
|
const stream = fs.createReadStream(path);
|
||||||
const stream = fs.createReadStream(path);
|
const form = new FormData();
|
||||||
const form = new FormData();
|
form.append("file", stream);
|
||||||
form.append("file", stream);
|
|
||||||
|
|
||||||
const options = {
|
const options = {
|
||||||
body: form,
|
body: form,
|
||||||
headers: { authorization: `Bearer ${token}` },
|
headers: { authorization: `Bearer ${token}` },
|
||||||
};
|
};
|
||||||
got.stream
|
return got
|
||||||
.post(`http://hassio/backups/new/upload`, options)
|
.post(`http://hassio/backups/new/upload`, options)
|
||||||
.on("uploadProgress", (e: Progress) => {
|
.on("uploadProgress", (e: Progress) => {
|
||||||
const percent = e.percent;
|
const percent = e.percent;
|
||||||
if (status.progress !== percent) {
|
if (status.progress !== percent) {
|
||||||
status.progress = percent;
|
status.progress = percent;
|
||||||
statusTools.setStatus(status);
|
statusTools.setStatus(status);
|
||||||
}
|
}
|
||||||
if (percent >= 1) {
|
if (percent >= 1) {
|
||||||
logger.info("Upload done...");
|
logger.info("Upload done...");
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.on("response", (res: PlainResponse) => {
|
.then(
|
||||||
if (res.statusCode !== 200) {
|
(res) => {
|
||||||
messageManager.error(
|
logger.info(`...Upload finish ! (status: ${res.statusCode})`);
|
||||||
"Fail to upload backup to Home Assistant",
|
const status = statusTools.getStatus();
|
||||||
`Code: ${res.statusCode} Body: ${res.body as string}`
|
status.status = States.IDLE;
|
||||||
);
|
status.progress = undefined;
|
||||||
logger.error("Fail to upload backup to Home Assistant");
|
statusTools.setStatus(status);
|
||||||
logger.error(`Code: ${res.statusCode}`);
|
fs.unlinkSync(path);
|
||||||
logger.error(`Body: ${res.body as string}`);
|
return res;
|
||||||
fs.unlinkSync(path);
|
},
|
||||||
reject(new Error(res.statusCode.toString()));
|
(err: RequestError) => {
|
||||||
} else {
|
|
||||||
logger.info(`...Upload finish ! (status: ${res.statusCode})`);
|
|
||||||
const status = statusTools.getStatus();
|
|
||||||
status.status = States.IDLE;
|
|
||||||
status.progress = undefined;
|
|
||||||
statusTools.setStatus(status);
|
|
||||||
fs.unlinkSync(path);
|
|
||||||
resolve(res);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.on("error", (err: RequestError) => {
|
|
||||||
const status = statusTools.getStatus();
|
const status = statusTools.getStatus();
|
||||||
status.status = States.IDLE;
|
status.status = States.IDLE;
|
||||||
status.progress = undefined;
|
status.progress = undefined;
|
||||||
@ -354,9 +342,9 @@ function uploadSnapshot(path: string) {
|
|||||||
);
|
);
|
||||||
logger.error("Fail to upload backup to Home Assistant");
|
logger.error("Fail to upload backup to Home Assistant");
|
||||||
logger.error(err);
|
logger.error(err);
|
||||||
reject(err);
|
logger.error(`Body: ${err.response?.body as string}`);
|
||||||
});
|
}
|
||||||
});
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function stopAddons(addonSlugs: string[]) {
|
function stopAddons(addonSlugs: string[]) {
|
||||||
@ -531,6 +519,7 @@ function publish_state() {
|
|||||||
export {
|
export {
|
||||||
clean,
|
clean,
|
||||||
createNewBackup,
|
createNewBackup,
|
||||||
|
delSnap,
|
||||||
downloadSnapshot,
|
downloadSnapshot,
|
||||||
getAddonList,
|
getAddonList,
|
||||||
getBackupInfo,
|
getBackupInfo,
|
||||||
|
@ -4,7 +4,12 @@ import logger from "../config/winston.js";
|
|||||||
import messageManager from "../tools/messageManager.js";
|
import messageManager from "../tools/messageManager.js";
|
||||||
import * as statusTools from "../tools/status.js";
|
import * as statusTools from "../tools/status.js";
|
||||||
import { BackupType } from "../types/services/backupConfig.js";
|
import { BackupType } from "../types/services/backupConfig.js";
|
||||||
import type { AddonModel } from "../types/services/ha_os_response.js";
|
import type {
|
||||||
|
AddonModel,
|
||||||
|
BackupDetailModel,
|
||||||
|
BackupUpload,
|
||||||
|
SupervisorResponse,
|
||||||
|
} from "../types/services/ha_os_response.js";
|
||||||
import { WorkflowType } from "../types/services/orchecstrator.js";
|
import { WorkflowType } from "../types/services/orchecstrator.js";
|
||||||
import * as backupConfigService from "./backupConfigService.js";
|
import * as backupConfigService from "./backupConfigService.js";
|
||||||
import * as homeAssistantService from "./homeAssistantService.js";
|
import * as homeAssistantService from "./homeAssistantService.js";
|
||||||
@ -89,7 +94,10 @@ export function doBackupWorkflow(type: WorkflowType) {
|
|||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
logger.info("Backup workflow finished successfully !");
|
logger.info("Backup workflow finished successfully !");
|
||||||
messageManager.info("Backup workflow finished successfully !");
|
messageManager.info(
|
||||||
|
"Backup workflow finished successfully !",
|
||||||
|
`name: ${name}`
|
||||||
|
);
|
||||||
const status = statusTools.getStatus();
|
const status = statusTools.getStatus();
|
||||||
status.last_backup.success = true;
|
status.last_backup.success = true;
|
||||||
status.last_backup.last_try = DateTime.now();
|
status.last_backup.last_try = DateTime.now();
|
||||||
@ -105,6 +113,55 @@ export function doBackupWorkflow(type: WorkflowType) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function uploadToCloud(slug: string) {
|
||||||
|
const webdavConfig = getWebdavConfig();
|
||||||
|
let tmpBackupFile = "";
|
||||||
|
let backupInfo = {} as BackupDetailModel;
|
||||||
|
|
||||||
|
return webDavService
|
||||||
|
.checkWebdavLogin(webdavConfig)
|
||||||
|
.then(() => {
|
||||||
|
return homeAssistantService.getBackupInfo(slug);
|
||||||
|
})
|
||||||
|
.then((response) => {
|
||||||
|
backupInfo = response.body.data;
|
||||||
|
return homeAssistantService.downloadSnapshot(slug);
|
||||||
|
})
|
||||||
|
.then((tmpFile) => {
|
||||||
|
tmpBackupFile = tmpFile;
|
||||||
|
if (webdavConfig.chunckedUpload) {
|
||||||
|
return webDavService.chunkedUpload(
|
||||||
|
tmpFile,
|
||||||
|
getBackupFolder(WorkflowType.MANUAL, webdavConfig) +
|
||||||
|
backupInfo.name +
|
||||||
|
".tar",
|
||||||
|
webdavConfig
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return webDavService.webdavUploadFile(
|
||||||
|
tmpFile,
|
||||||
|
getBackupFolder(WorkflowType.MANUAL, webdavConfig) +
|
||||||
|
backupInfo.name +
|
||||||
|
".tar",
|
||||||
|
webdavConfig
|
||||||
|
);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
logger.info(`Successfully uploaded ${backupInfo.name} to cloud.`);
|
||||||
|
messageManager.info(
|
||||||
|
"Successfully uploaded backup to cloud.",
|
||||||
|
`Name: ${backupInfo.name}`
|
||||||
|
);
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
if (tmpBackupFile != "") {
|
||||||
|
unlinkSync(tmpBackupFile);
|
||||||
|
}
|
||||||
|
return Promise.reject(new Error());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// This methods remove addon that are no installed in HA from the conf array
|
// This methods remove addon that are no installed in HA from the conf array
|
||||||
function sanitizeAddonList(addonInConf: string[], addonInHA: AddonModel[]) {
|
function sanitizeAddonList(addonInConf: string[], addonInHA: AddonModel[]) {
|
||||||
return addonInConf.filter((value) => addonInHA.some((v) => v.slug == value));
|
return addonInConf.filter((value) => addonInHA.some((v) => v.slug == value));
|
||||||
@ -140,3 +197,26 @@ function backupFail() {
|
|||||||
statusTools.setStatus(status);
|
statusTools.setStatus(status);
|
||||||
messageManager.error("Last backup as failed !");
|
messageManager.error("Last backup as failed !");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function restoreToHA(webdavPath: string, filename: string) {
|
||||||
|
const webdavConfig = getWebdavConfig();
|
||||||
|
return webDavService
|
||||||
|
.checkWebdavLogin(webdavConfig)
|
||||||
|
.then(() => {
|
||||||
|
return webDavService.downloadFile(webdavPath, filename, webdavConfig);
|
||||||
|
})
|
||||||
|
.then((tmpFile) => {
|
||||||
|
return homeAssistantService.uploadSnapshot(tmpFile);
|
||||||
|
})
|
||||||
|
.then((res) => {
|
||||||
|
if (res) {
|
||||||
|
const body = JSON.parse(res.body) as SupervisorResponse<BackupUpload>;
|
||||||
|
logger.info(`Successfully uploaded ${filename} to Home Assistant.`);
|
||||||
|
messageManager.info(
|
||||||
|
"Successfully uploaded backup to Home Assistant.",
|
||||||
|
`Name: ${filename} slug: ${body.data.slug}
|
||||||
|
`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
@ -21,6 +21,8 @@ import type { WebdavConfig } from "../types/services/webdavConfig.js";
|
|||||||
import { States } from "../types/status.js";
|
import { States } from "../types/status.js";
|
||||||
import { templateToRegexp } from "./backupConfigService.js";
|
import { templateToRegexp } from "./backupConfigService.js";
|
||||||
import { getChunkEndpoint, getEndpoint } from "./webdavConfigService.js";
|
import { getChunkEndpoint, getEndpoint } from "./webdavConfigService.js";
|
||||||
|
import { pipeline } from "stream/promises";
|
||||||
|
import { humanFileSize } from "../tools/toolbox.js";
|
||||||
|
|
||||||
const CHUNK_SIZE = 5 * 1024 * 1024; // 5MiB Same as desktop client
|
const CHUNK_SIZE = 5 * 1024 * 1024; // 5MiB Same as desktop client
|
||||||
const CHUNK_NUMBER_SIZE = 5; // To add landing "0"
|
const CHUNK_NUMBER_SIZE = 5; // To add landing "0"
|
||||||
@ -452,7 +454,7 @@ export async function chunkedUpload(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function uploadChunk(
|
function uploadChunk(
|
||||||
url: string,
|
url: string,
|
||||||
finalDestination: string,
|
finalDestination: string,
|
||||||
body: fs.ReadStream,
|
body: fs.ReadStream,
|
||||||
@ -497,7 +499,7 @@ export function uploadChunk(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function initChunkedUpload(
|
function initChunkedUpload(
|
||||||
url: string,
|
url: string,
|
||||||
finalDestination: string,
|
finalDestination: string,
|
||||||
config: WebdavConfig
|
config: WebdavConfig
|
||||||
@ -517,7 +519,7 @@ export function initChunkedUpload(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function assembleChunkedUpload(
|
function assembleChunkedUpload(
|
||||||
url: string,
|
url: string,
|
||||||
finalDestination: string,
|
finalDestination: string,
|
||||||
totalLength: number,
|
totalLength: number,
|
||||||
@ -539,436 +541,63 @@ export function assembleChunkedUpload(
|
|||||||
https: { rejectUnauthorized: !config.allowSelfSignedCerts },
|
https: { rejectUnauthorized: !config.allowSelfSignedCerts },
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
// import fs from "fs";
|
|
||||||
// import got from "got";
|
|
||||||
// import https from "https";
|
|
||||||
// import path from "path";
|
|
||||||
// import stream from "stream";
|
|
||||||
// import { promisify } from "util";
|
|
||||||
// import { createClient, WebDAVClient } from "webdav";
|
|
||||||
|
|
||||||
// import { DateTime } from "luxon";
|
export function downloadFile(
|
||||||
// import logger from "../config/winston.js";
|
webdavPath: string,
|
||||||
// import { WebdavSettings } from "../types/settings.js";
|
filename: string,
|
||||||
// import * as pathTools from "../tools/pathTools.js";
|
config: WebdavConfig
|
||||||
// import * as settingsTools from "../tools/settingsTools.js";
|
) {
|
||||||
// import * as statusTools from "../tools/status.js";
|
logger.info(`Downloading ${webdavPath} from webdav...`);
|
||||||
|
if (!fs.existsSync("./temp/")) {
|
||||||
// const endpoint = "/remote.php/webdav";
|
fs.mkdirSync("./temp/");
|
||||||
// const configPath = "/data/webdav_conf.json";
|
}
|
||||||
|
const tmp_file = `./temp/${filename}`;
|
||||||
// const pipeline = promisify(stream.pipeline);
|
const stream = fs.createWriteStream(tmp_file);
|
||||||
|
const options = {
|
||||||
// class WebdavTools {
|
headers: {
|
||||||
// host: string | undefined;
|
authorization:
|
||||||
// client: WebDAVClient | undefined;
|
"Basic " +
|
||||||
// baseUrl: string | undefined;
|
Buffer.from(config.username + ":" + config.password).toString("base64"),
|
||||||
// username: string | undefined;
|
},
|
||||||
// password: string | undefined;
|
https: { rejectUnauthorized: !config.allowSelfSignedCerts },
|
||||||
|
};
|
||||||
// init(
|
const url = config.url + getEndpoint(config) + webdavPath;
|
||||||
// ssl: boolean,
|
logger.debug(`...URI: ${encodeURI(url)}`);
|
||||||
// host: string,
|
logger.debug(`...rejectUnauthorized: ${options.https?.rejectUnauthorized}`);
|
||||||
// username: string,
|
const status = statusTools.getStatus();
|
||||||
// password: string,
|
status.status = States.BKUP_DOWNLOAD_CLOUD;
|
||||||
// accept_selfsigned_cert: boolean
|
status.progress = 0;
|
||||||
// ) {
|
statusTools.setStatus(status);
|
||||||
// return new Promise((resolve, reject) => {
|
return pipeline(
|
||||||
// this.host = host;
|
got.stream.get(encodeURI(url), options).on("downloadProgress", (e) => {
|
||||||
// const status = statusTools.getStatus();
|
const percent = Math.round(e.percent * 100) / 100;
|
||||||
// logger.info("Initializing and checking webdav client...");
|
if (status.progress !== percent) {
|
||||||
// this.baseUrl = (ssl ? "https" : "http") + "://" + host + endpoint;
|
status.progress = percent;
|
||||||
// this.username = username;
|
statusTools.setStatus(status);
|
||||||
// this.password = password;
|
}
|
||||||
// const agent_option = ssl
|
}),
|
||||||
// ? { rejectUnauthorized: !accept_selfsigned_cert }
|
stream
|
||||||
// : {};
|
).then(
|
||||||
// try {
|
() => {
|
||||||
// this.client = createClient(this.baseUrl, {
|
logger.info("Download success !");
|
||||||
// username: username,
|
status.progress = 1;
|
||||||
// password: password,
|
statusTools.setStatus(status);
|
||||||
// httpsAgent: new https.Agent(agent_option),
|
logger.debug(
|
||||||
// });
|
`Backup dl size: ${humanFileSize(fs.statSync(tmp_file).size)}`
|
||||||
|
);
|
||||||
// this.client
|
return tmp_file;
|
||||||
// .getDirectoryContents("/")
|
},
|
||||||
// .then(() => {
|
(reason: RequestError) => {
|
||||||
// if (status.error_code === 3) {
|
if (fs.existsSync(tmp_file)) fs.unlinkSync(tmp_file);
|
||||||
// status.status = "idle";
|
messageManager.error("Fail to download Cloud backup", reason.message);
|
||||||
// status.message = undefined;
|
const status = statusTools.getStatus();
|
||||||
// status.error_code = undefined;
|
status.status = States.IDLE;
|
||||||
// statusTools.setStatus(status);
|
status.progress = undefined;
|
||||||
// }
|
statusTools.setStatus(status);
|
||||||
// logger.debug("Nextcloud connection: \x1b[32mSuccess !\x1b[0m");
|
return Promise.reject(reason);
|
||||||
// this.initFolder().then(() => {
|
}
|
||||||
// resolve(undefined);
|
);
|
||||||
// });
|
}
|
||||||
// })
|
|
||||||
// .catch((error) => {
|
|
||||||
// this.__cant_connect_status(error);
|
|
||||||
// this.client = undefined;
|
|
||||||
// reject("Can't connect to Nextcloud (" + error + ") !");
|
|
||||||
// });
|
|
||||||
// } catch (err) {
|
|
||||||
// this.__cant_connect_status(err);
|
|
||||||
// this.client = undefined;
|
|
||||||
// reject("Can't connect to Nextcloud (" + err + ") !");
|
|
||||||
// }
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
// __cant_connect_status(err: any) {
|
|
||||||
// statusTools.setError(`Can't connect to Nextcloud (${err})`, 3);
|
|
||||||
// }
|
|
||||||
|
|
||||||
// async __createRoot() {
|
|
||||||
// const conf = this.getConf();
|
|
||||||
// if (this.client && conf) {
|
|
||||||
// const root_splited = conf.back_dir.split("/").splice(1);
|
|
||||||
// let path = "/";
|
|
||||||
// for (const elem of root_splited) {
|
|
||||||
// if (elem !== "") {
|
|
||||||
// path = path + elem + "/";
|
|
||||||
// try {
|
|
||||||
// await this.client.createDirectory(path);
|
|
||||||
// logger.debug(`Path ${path} created.`);
|
|
||||||
// } catch (error) {
|
|
||||||
// if ((error as any).status === 405)
|
|
||||||
// logger.debug(`Path ${path} already exist.`);
|
|
||||||
// else logger.error(error);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// initFolder() {
|
|
||||||
// return new Promise((resolve, reject) => {
|
|
||||||
// this.__createRoot()
|
|
||||||
// .catch((err) => {
|
|
||||||
// logger.error(err);
|
|
||||||
// })
|
|
||||||
// .then(() => {
|
|
||||||
// if (!this.client) {
|
|
||||||
// return;
|
|
||||||
// }
|
|
||||||
// this.client
|
|
||||||
// .createDirectory(this.getConf()?.back_dir + pathTools.auto)
|
|
||||||
// .catch(() => {
|
|
||||||
// // Ignore
|
|
||||||
// })
|
|
||||||
// .then(() => {
|
|
||||||
// if (!this.client) {
|
|
||||||
// return;
|
|
||||||
// }
|
|
||||||
// this.client
|
|
||||||
// .createDirectory(this.getConf()?.back_dir + pathTools.manual)
|
|
||||||
// .catch(() => {
|
|
||||||
// // Ignore
|
|
||||||
// })
|
|
||||||
// .then(() => {
|
|
||||||
// resolve(undefined);
|
|
||||||
// });
|
|
||||||
// });
|
|
||||||
// });
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
|
|
||||||
// /**
|
|
||||||
// * Check if theh webdav config is valid, if yes, start init of webdav client
|
|
||||||
// */
|
|
||||||
// confIsValid() {
|
|
||||||
// return new Promise((resolve, reject) => {
|
|
||||||
// const status = statusTools.getStatus();
|
|
||||||
// const conf = this.getConf();
|
|
||||||
// if (conf != undefined) {
|
|
||||||
// if (
|
|
||||||
// conf.ssl != undefined &&
|
|
||||||
// conf.host != undefined &&
|
|
||||||
// conf.username != undefined &&
|
|
||||||
// conf.password != undefined
|
|
||||||
// ) {
|
|
||||||
// if (status.error_code === 2) {
|
|
||||||
// status.status = "idle";
|
|
||||||
// status.message = undefined;
|
|
||||||
// status.error_code = undefined;
|
|
||||||
// statusTools.setStatus(status);
|
|
||||||
// }
|
|
||||||
// // Check if self_signed option exist
|
|
||||||
// if (conf.self_signed == undefined) {
|
|
||||||
// conf.self_signed = false;
|
|
||||||
// this.setConf(conf);
|
|
||||||
// }
|
|
||||||
// this.init(
|
|
||||||
// conf.ssl,
|
|
||||||
// conf.host,
|
|
||||||
// conf.username,
|
|
||||||
// conf.password,
|
|
||||||
// conf.self_signed
|
|
||||||
// )
|
|
||||||
// .then(() => {
|
|
||||||
// resolve(undefined);
|
|
||||||
// })
|
|
||||||
// .catch((err) => {
|
|
||||||
// reject(err);
|
|
||||||
// });
|
|
||||||
// } else {
|
|
||||||
// status.status = "error";
|
|
||||||
// status.error_code = 2;
|
|
||||||
// status.message = "Nextcloud config invalid !";
|
|
||||||
// statusTools.setStatus(status);
|
|
||||||
// logger.error(status.message);
|
|
||||||
// reject("Nextcloud config invalid !");
|
|
||||||
// }
|
|
||||||
|
|
||||||
// if (conf.back_dir == null || conf.back_dir === "") {
|
|
||||||
// logger.info("Backup dir is null, initializing it.");
|
|
||||||
// conf.back_dir = pathTools.default_root;
|
|
||||||
// this.setConf(conf);
|
|
||||||
// } else {
|
|
||||||
// if (!conf.back_dir.startsWith("/")) {
|
|
||||||
// logger.warn("Backup dir not starting with '/', fixing this...");
|
|
||||||
// conf.back_dir = `/${conf.back_dir}`;
|
|
||||||
// this.setConf(conf);
|
|
||||||
// }
|
|
||||||
// if (!conf.back_dir.endsWith("/")) {
|
|
||||||
// logger.warn("Backup dir not ending with '/', fixing this...");
|
|
||||||
// conf.back_dir = `${conf.back_dir}/`;
|
|
||||||
// this.setConf(conf);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// } else {
|
|
||||||
// status.status = "error";
|
|
||||||
// status.error_code = 2;
|
|
||||||
// status.message = "Nextcloud config not found !";
|
|
||||||
// statusTools.setStatus(status);
|
|
||||||
// logger.error(status.message);
|
|
||||||
// reject("Nextcloud config not found !");
|
|
||||||
// }
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
|
|
||||||
// getConf(): WebdavSettings | undefined {
|
|
||||||
// if (fs.existsSync(configPath)) {
|
|
||||||
// return JSON.parse(fs.readFileSync(configPath).toString());
|
|
||||||
// } else return undefined;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// setConf(conf: WebdavSettings) {
|
|
||||||
// fs.writeFileSync(configPath, JSON.stringify(conf));
|
|
||||||
// }
|
|
||||||
|
|
||||||
// uploadFile(id: string, path: string) {
|
|
||||||
// return new Promise((resolve, reject) => {
|
|
||||||
// if (this.client == null) {
|
|
||||||
// this.confIsValid()
|
|
||||||
// .then(() => {
|
|
||||||
// this._startUpload(id, path)
|
|
||||||
// .then(() => resolve(undefined))
|
|
||||||
// .catch((err) => reject(err));
|
|
||||||
// })
|
|
||||||
// .catch((err) => {
|
|
||||||
// reject(err);
|
|
||||||
// });
|
|
||||||
// } else
|
|
||||||
// this._startUpload(id, path)
|
|
||||||
// .then(() => resolve(undefined))
|
|
||||||
// .catch((err) => reject(err));
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
|
|
||||||
// _startUpload(id: string, path: string) {
|
|
||||||
// return new Promise((resolve, reject) => {
|
|
||||||
// const status = statusTools.getStatus();
|
|
||||||
// status.status = "upload";
|
|
||||||
// status.progress = 0;
|
|
||||||
// status.message = undefined;
|
|
||||||
// status.error_code = undefined;
|
|
||||||
// statusTools.setStatus(status);
|
|
||||||
// logger.info("Uploading snap...");
|
|
||||||
// const tmpFile = `./temp/${id}.tar`;
|
|
||||||
// const stats = fs.statSync(tmpFile);
|
|
||||||
// const stream = fs.createReadStream(tmpFile);
|
|
||||||
// const conf = this.getConf();
|
|
||||||
// const options = {
|
|
||||||
// body: stream,
|
|
||||||
// headers: {
|
|
||||||
// authorization:
|
|
||||||
// "Basic " +
|
|
||||||
// Buffer.from(this.username + ":" + this.password).toString("base64"),
|
|
||||||
// "content-length": String(stats.size),
|
|
||||||
// },
|
|
||||||
// https: undefined as any | undefined,
|
|
||||||
// };
|
|
||||||
// if (conf?.ssl) {
|
|
||||||
// options["https"] = { rejectUnauthorized: !conf.self_signed };
|
|
||||||
// }
|
|
||||||
// logger.debug(
|
|
||||||
// `...URI: ${encodeURI(
|
|
||||||
// this.baseUrl?.replace(this.host as string, "host.hiden") + path
|
|
||||||
// )}`
|
|
||||||
// );
|
|
||||||
// if (conf?.ssl)
|
|
||||||
// logger.debug(
|
|
||||||
// `...rejectUnauthorized: ${options.https?.rejectUnauthorized}`
|
|
||||||
// );
|
|
||||||
|
|
||||||
// got.stream
|
|
||||||
// .put(encodeURI(this.baseUrl + path), options)
|
|
||||||
// .on("uploadProgress", (e) => {
|
|
||||||
// const percent = e.percent;
|
|
||||||
// if (status.progress !== percent) {
|
|
||||||
// status.progress = percent;
|
|
||||||
// statusTools.setStatus(status);
|
|
||||||
// }
|
|
||||||
// if (percent >= 1) {
|
|
||||||
// logger.info("Upload done...");
|
|
||||||
// }
|
|
||||||
// })
|
|
||||||
// .on("response", (res) => {
|
|
||||||
// if (res.statusCode !== 201 && res.statusCode !== 204) {
|
|
||||||
// status.status = "error";
|
|
||||||
// status.error_code = 4;
|
|
||||||
// status.message = `Fail to upload snapshot to nextcloud (Status code: ${res.statusCode})!`;
|
|
||||||
// statusTools.setStatus(status);
|
|
||||||
// logger.error(status.message);
|
|
||||||
// fs.unlinkSync(tmpFile);
|
|
||||||
// reject(status.message);
|
|
||||||
// } else {
|
|
||||||
// logger.info(`...Upload finish ! (status: ${res.statusCode})`);
|
|
||||||
// status.status = "idle";
|
|
||||||
// status.progress = -1;
|
|
||||||
// status.message = undefined;
|
|
||||||
// status.error_code = undefined;
|
|
||||||
// status.last_backup = DateTime.now().toFormat("dd MMM yyyy, HH:mm");
|
|
||||||
// statusTools.setStatus(status);
|
|
||||||
// cleanTempFolder();
|
|
||||||
// const autoCleanCloud =
|
|
||||||
// settingsTools.getSettings().auto_clean_backup;
|
|
||||||
// if (autoCleanCloud != null && autoCleanCloud === "true") {
|
|
||||||
// this.clean().catch();
|
|
||||||
// }
|
|
||||||
// const autoCleanlocal = settingsTools.getSettings().auto_clean_local;
|
|
||||||
// if (autoCleanlocal != null && autoCleanlocal === "true") {
|
|
||||||
// hassioApiTools.clean().catch();
|
|
||||||
// }
|
|
||||||
// resolve(undefined);
|
|
||||||
// }
|
|
||||||
// })
|
|
||||||
// .on("error", (err) => {
|
|
||||||
// fs.unlinkSync(tmpFile);
|
|
||||||
// status.status = "error";
|
|
||||||
// status.error_code = 4;
|
|
||||||
// status.message = `Fail to upload snapshot to nextcloud (${err}) !`;
|
|
||||||
// statusTools.setStatus(status);
|
|
||||||
// logger.error(status.message);
|
|
||||||
// logger.error(err.stack);
|
|
||||||
// reject(status.message);
|
|
||||||
// });
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
|
|
||||||
// downloadFile(path: string) {
|
|
||||||
// return new Promise<string>((resolve, reject) => {
|
|
||||||
// if (this.client == null) {
|
|
||||||
// this.confIsValid()
|
|
||||||
// .then(() => {
|
|
||||||
// this._startDownload(path)
|
|
||||||
// .then((path) => resolve(path))
|
|
||||||
// .catch(() => reject());
|
|
||||||
// })
|
|
||||||
// .catch((err) => {
|
|
||||||
// reject(err);
|
|
||||||
// });
|
|
||||||
// } else
|
|
||||||
// this._startDownload(path)
|
|
||||||
// .then((path) => resolve(path))
|
|
||||||
// .catch(() => reject());
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
|
|
||||||
// _startDownload(path: string) {
|
|
||||||
// return new Promise<string>((resolve, reject) => {
|
|
||||||
// const status = statusTools.getStatus();
|
|
||||||
// status.status = "download-b";
|
|
||||||
// status.progress = 0;
|
|
||||||
// status.message = undefined;
|
|
||||||
// status.error_code = undefined;
|
|
||||||
// statusTools.setStatus(status);
|
|
||||||
|
|
||||||
// logger.info("Downloading backup...");
|
|
||||||
// if (!fs.existsSync("./temp/")) fs.mkdirSync("./temp/");
|
|
||||||
// const tmpFile = `./temp/restore_${DateTime.now().toFormat(
|
|
||||||
// "MMM-dd-yyyy_HH_mm"
|
|
||||||
// )}.tar`;
|
|
||||||
// const stream = fs.createWriteStream(tmpFile);
|
|
||||||
// const conf = this.getConf();
|
|
||||||
// const options = {
|
|
||||||
// headers: {
|
|
||||||
// authorization:
|
|
||||||
// "Basic " +
|
|
||||||
// Buffer.from(this.username + ":" + this.password).toString("base64"),
|
|
||||||
// },
|
|
||||||
// https: undefined as any | undefined,
|
|
||||||
// };
|
|
||||||
// if (conf?.ssl) {
|
|
||||||
// options["https"] = { rejectUnauthorized: !conf?.self_signed };
|
|
||||||
// }
|
|
||||||
// logger.debug(
|
|
||||||
// `...URI: ${encodeURI(
|
|
||||||
// this.baseUrl?.replace(this.host as string, "host.hiden") + path
|
|
||||||
// )}`
|
|
||||||
// );
|
|
||||||
// if (conf?.ssl)
|
|
||||||
// logger.debug(
|
|
||||||
// `...rejectUnauthorized: ${options.https?.rejectUnauthorized}`
|
|
||||||
// );
|
|
||||||
// pipeline(
|
|
||||||
// got.stream
|
|
||||||
// .get(encodeURI(this.baseUrl + path), options)
|
|
||||||
// .on("downloadProgress", (e) => {
|
|
||||||
// const percent = Math.round(e.percent * 100) / 100;
|
|
||||||
// if (status.progress !== percent) {
|
|
||||||
// status.progress = percent;
|
|
||||||
// statusTools.setStatus(status);
|
|
||||||
// }
|
|
||||||
// }),
|
|
||||||
// stream
|
|
||||||
// )
|
|
||||||
// .then((res) => {
|
|
||||||
// logger.info("Download success !");
|
|
||||||
// status.progress = 1;
|
|
||||||
// statusTools.setStatus(status);
|
|
||||||
// logger.debug(
|
|
||||||
// "Backup dl size : " + fs.statSync(tmpFile).size / 1024 / 1024
|
|
||||||
// );
|
|
||||||
// resolve(tmpFile);
|
|
||||||
// })
|
|
||||||
// .catch((err) => {
|
|
||||||
// if (fs.existsSync(tmpFile)) fs.unlinkSync(tmpFile);
|
|
||||||
// status.status = "error";
|
|
||||||
// status.message =
|
|
||||||
// "Fail to download Hassio snapshot (" + err.message + ")";
|
|
||||||
// status.error_code = 7;
|
|
||||||
// statusTools.setStatus(status);
|
|
||||||
// logger.error(status.message);
|
|
||||||
// logger.error(err.stack);
|
|
||||||
// reject(err.message);
|
|
||||||
// });
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
|
|
||||||
// getFolderContent(path: string) {
|
|
||||||
// return new Promise((resolve, reject) => {
|
|
||||||
// if (this.client == null) {
|
|
||||||
// reject();
|
|
||||||
// return;
|
|
||||||
// }
|
|
||||||
// this.client
|
|
||||||
// .getDirectoryContents(path)
|
|
||||||
// .then((contents) => resolve(contents))
|
|
||||||
// .catch((error) => reject(error));
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
|
|
||||||
// clean() {
|
// clean() {
|
||||||
// let limit = settingsTools.getSettings().auto_clean_backup_keep;
|
// let limit = settingsTools.getSettings().auto_clean_backup_keep;
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
|
|
||||||
export interface SupervisorResponse<T> {
|
export interface SupervisorResponse<T> {
|
||||||
result: string;
|
result: string;
|
||||||
data: T;
|
data: T;
|
||||||
@ -25,7 +24,6 @@ export interface AddonData {
|
|||||||
addons: AddonModel[];
|
addons: AddonModel[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export interface AddonModel {
|
export interface AddonModel {
|
||||||
name: string;
|
name: string;
|
||||||
slug: string;
|
slug: string;
|
||||||
@ -43,10 +41,9 @@ export interface AddonModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface BackupData {
|
export interface BackupData {
|
||||||
backups: BackupModel[]
|
backups: BackupModel[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export interface BackupModel {
|
export interface BackupModel {
|
||||||
slug: string;
|
slug: string;
|
||||||
date: string;
|
date: string;
|
||||||
@ -80,3 +77,8 @@ export interface BackupDetailModel {
|
|||||||
repositories: string[];
|
repositories: string[];
|
||||||
folders: string[];
|
folders: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface BackupUpload {
|
||||||
|
slug: string;
|
||||||
|
job_id: string;
|
||||||
|
}
|
||||||
|
@ -10,7 +10,6 @@ export interface WebdavBackup {
|
|||||||
creationDate?: DateTime;
|
creationDate?: DateTime;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface WebdavGenericPath {
|
||||||
export interface WebdavDelete {
|
|
||||||
path: string;
|
path: string;
|
||||||
}
|
}
|
1
nextcloud_backup/frontend/components.d.ts
vendored
1
nextcloud_backup/frontend/components.d.ts
vendored
@ -22,6 +22,7 @@ declare module 'vue' {
|
|||||||
CloudList: typeof import('./src/components/cloud/CloudList.vue')['default']
|
CloudList: typeof import('./src/components/cloud/CloudList.vue')['default']
|
||||||
CloudListItem: typeof import('./src/components/cloud/CloudListItem.vue')['default']
|
CloudListItem: typeof import('./src/components/cloud/CloudListItem.vue')['default']
|
||||||
ConnectionStatus: typeof import('./src/components/statusBar/ConnectionStatus.vue')['default']
|
ConnectionStatus: typeof import('./src/components/statusBar/ConnectionStatus.vue')['default']
|
||||||
|
HaDeleteDialog: typeof import('./src/components/homeAssistant/HaDeleteDialog.vue')['default']
|
||||||
HaList: typeof import('./src/components/homeAssistant/HaList.vue')['default']
|
HaList: typeof import('./src/components/homeAssistant/HaList.vue')['default']
|
||||||
HaListItem: typeof import('./src/components/homeAssistant/HaListItem.vue')['default']
|
HaListItem: typeof import('./src/components/homeAssistant/HaListItem.vue')['default']
|
||||||
HaListItemContent: typeof import('./src/components/homeAssistant/HaListItemContent.vue')['default']
|
HaListItemContent: typeof import('./src/components/homeAssistant/HaListItemContent.vue')['default']
|
||||||
|
@ -53,8 +53,10 @@ const alertVisible = computed(() => alertList.value.length > 0);
|
|||||||
right: 20px;
|
right: 20px;
|
||||||
z-index: 99999;
|
z-index: 99999;
|
||||||
height: 100vh;
|
height: 100vh;
|
||||||
|
pointer-events: none;
|
||||||
#alertContainer {
|
#alertContainer {
|
||||||
position: sticky;
|
position: sticky;
|
||||||
|
pointer-events: all;
|
||||||
top: 80px;
|
top: 80px;
|
||||||
right: 20px;
|
right: 20px;
|
||||||
}
|
}
|
||||||
|
@ -39,6 +39,7 @@
|
|||||||
:item="item"
|
:item="item"
|
||||||
:index="index"
|
:index="index"
|
||||||
@delete="deleteBackup"
|
@delete="deleteBackup"
|
||||||
|
@upload="restore"
|
||||||
>
|
>
|
||||||
</cloud-list-item>
|
</cloud-list-item>
|
||||||
</v-list>
|
</v-list>
|
||||||
@ -67,6 +68,7 @@
|
|||||||
:item="item"
|
:item="item"
|
||||||
:index="index"
|
:index="index"
|
||||||
@delete="deleteBackup"
|
@delete="deleteBackup"
|
||||||
|
@upload="restore"
|
||||||
>
|
>
|
||||||
</cloud-list-item>
|
</cloud-list-item>
|
||||||
</v-list>
|
</v-list>
|
||||||
@ -89,9 +91,11 @@ import type { WebdavBackup } from "@/types/webdav";
|
|||||||
import {
|
import {
|
||||||
getAutoBackupList,
|
getAutoBackupList,
|
||||||
getManualBackupList,
|
getManualBackupList,
|
||||||
|
restoreWebdavBackup,
|
||||||
} from "@/services/webdavService";
|
} from "@/services/webdavService";
|
||||||
import CloudDeleteDialog from "./CloudDeleteDialog.vue";
|
import CloudDeleteDialog from "./CloudDeleteDialog.vue";
|
||||||
import CloudListItem from "./CloudListItem.vue";
|
import CloudListItem from "./CloudListItem.vue";
|
||||||
|
import { useAlertStore } from "@/store/alert";
|
||||||
|
|
||||||
const deleteDialog = ref<InstanceType<typeof CloudDeleteDialog> | null>(null);
|
const deleteDialog = ref<InstanceType<typeof CloudDeleteDialog> | null>(null);
|
||||||
const deleteItem = ref<WebdavBackup | null>(null);
|
const deleteItem = ref<WebdavBackup | null>(null);
|
||||||
@ -99,7 +103,7 @@ const autoBackups = ref<WebdavBackup[]>([]);
|
|||||||
const manualBackups = ref<WebdavBackup[]>([]);
|
const manualBackups = ref<WebdavBackup[]>([]);
|
||||||
|
|
||||||
const loading = ref<boolean>(true);
|
const loading = ref<boolean>(true);
|
||||||
|
const alertStore = useAlertStore();
|
||||||
function refreshBackup() {
|
function refreshBackup() {
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
getAutoBackupList()
|
getAutoBackupList()
|
||||||
@ -120,6 +124,16 @@ function deleteBackup(item: WebdavBackup) {
|
|||||||
deleteItem.value = item;
|
deleteItem.value = item;
|
||||||
deleteDialog.value?.open(item);
|
deleteDialog.value?.open(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function restore(item: WebdavBackup) {
|
||||||
|
restoreWebdavBackup(item.path)
|
||||||
|
.then(() => {
|
||||||
|
alertStore.add("success", "Backup upload as started.");
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
alertStore.add("error", "Fail to start backup upload !");
|
||||||
|
});
|
||||||
|
}
|
||||||
refreshBackup();
|
refreshBackup();
|
||||||
defineExpose({ refreshBackup });
|
defineExpose({ refreshBackup });
|
||||||
</script>
|
</script>
|
||||||
|
@ -94,7 +94,12 @@
|
|||||||
<v-card-actions class="justify-center">
|
<v-card-actions class="justify-center">
|
||||||
<v-tooltip text="Upload to Home Assitant" location="bottom">
|
<v-tooltip text="Upload to Home Assitant" location="bottom">
|
||||||
<template v-slot:activator="{ props }">
|
<template v-slot:activator="{ props }">
|
||||||
<v-btn variant="outlined" color="success" v-bind="props">
|
<v-btn
|
||||||
|
variant="outlined"
|
||||||
|
color="success"
|
||||||
|
v-bind="props"
|
||||||
|
@click="emits('upload', item)"
|
||||||
|
>
|
||||||
<v-icon>mdi-upload</v-icon>
|
<v-icon>mdi-upload</v-icon>
|
||||||
</v-btn>
|
</v-btn>
|
||||||
</template>
|
</template>
|
||||||
@ -108,18 +113,20 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { restoreWebdavBackup } from "@/services/webdavService";
|
||||||
import type { WebdavBackup } from "@/types/webdav";
|
import type { WebdavBackup } from "@/types/webdav";
|
||||||
import { DateTime } from "luxon";
|
import { DateTime } from "luxon";
|
||||||
import prettyBytes from "pretty-bytes";
|
import prettyBytes from "pretty-bytes";
|
||||||
import { ref } from "vue";
|
import { ref } from "vue";
|
||||||
|
|
||||||
const detail = ref(false);
|
const detail = ref(false);
|
||||||
defineProps<{
|
const props = defineProps<{
|
||||||
item: WebdavBackup;
|
item: WebdavBackup;
|
||||||
index: number;
|
index: number;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const emits = defineEmits<{
|
const emits = defineEmits<{
|
||||||
(e: "delete", item: WebdavBackup): void;
|
(e: "delete", item: WebdavBackup): void;
|
||||||
|
(e: "upload", item: WebdavBackup): void;
|
||||||
}>();
|
}>();
|
||||||
</script>
|
</script>
|
||||||
|
@ -0,0 +1,74 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<v-dialog
|
||||||
|
v-model="dialog"
|
||||||
|
:width="width"
|
||||||
|
:fullscreen="isFullScreen"
|
||||||
|
:persistent="loading"
|
||||||
|
>
|
||||||
|
<v-card>
|
||||||
|
<v-card-title class="d-flex align-center">
|
||||||
|
<v-icon color="red" class="mr-2">mdi-trash-can</v-icon> Delete Home
|
||||||
|
Assistant backup
|
||||||
|
</v-card-title>
|
||||||
|
<v-divider></v-divider>
|
||||||
|
<v-card-text>
|
||||||
|
Delete <v-code tag="code">{{ item?.name }}</v-code> backup in Home
|
||||||
|
Assistant ?
|
||||||
|
</v-card-text>
|
||||||
|
<v-divider></v-divider>
|
||||||
|
<v-card-actions class="justify-end">
|
||||||
|
<v-btn color="success" @click="dialog = false" :disabled="loading">
|
||||||
|
Close
|
||||||
|
</v-btn>
|
||||||
|
<v-btn color="red" @click="confirm()" :loading="loading">
|
||||||
|
<v-icon>mdi-trash-can</v-icon> Delete
|
||||||
|
</v-btn>
|
||||||
|
</v-card-actions>
|
||||||
|
</v-card>
|
||||||
|
</v-dialog>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { useMenuSize } from "@/composable/menuSize";
|
||||||
|
import { deleteHomeAssistantBackup } from "@/services/homeAssistantService";
|
||||||
|
import { deleteWebdabBackup } from "@/services/webdavService";
|
||||||
|
import { useAlertStore } from "@/store/alert";
|
||||||
|
import { BackupModel } from "@/types/homeAssistant";
|
||||||
|
import { ref } from "vue";
|
||||||
|
|
||||||
|
const dialog = ref(false);
|
||||||
|
const loading = ref(false);
|
||||||
|
const item = ref<BackupModel | null>(null);
|
||||||
|
|
||||||
|
const { width, isFullScreen } = useMenuSize();
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(e: "deleted"): void;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const alertStore = useAlertStore();
|
||||||
|
function confirm() {
|
||||||
|
loading.value = true;
|
||||||
|
if (item.value) {
|
||||||
|
deleteHomeAssistantBackup(item.value.slug)
|
||||||
|
.then(() => {
|
||||||
|
loading.value = false;
|
||||||
|
dialog.value = false;
|
||||||
|
alertStore.add("success", "Backup deleted from Home Assistant");
|
||||||
|
emit("deleted");
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
loading.value = false;
|
||||||
|
alertStore.add("error", "Fail to deleted backup from Home Assistant");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function open(value: BackupModel) {
|
||||||
|
item.value = value;
|
||||||
|
dialog.value = true;
|
||||||
|
}
|
||||||
|
defineExpose({ open });
|
||||||
|
</script>
|
@ -37,6 +37,8 @@
|
|||||||
:key="item.slug"
|
:key="item.slug"
|
||||||
:item="item"
|
:item="item"
|
||||||
:index="index"
|
:index="index"
|
||||||
|
@upload="upload"
|
||||||
|
@delete="deleteBackup"
|
||||||
>
|
>
|
||||||
</ha-list-item>
|
</ha-list-item>
|
||||||
</v-list>
|
</v-list>
|
||||||
@ -47,17 +49,32 @@
|
|||||||
</v-card-text>
|
</v-card-text>
|
||||||
</v-card>
|
</v-card>
|
||||||
</div>
|
</div>
|
||||||
|
<ha-delete-dialog
|
||||||
|
ref="deleteDialog"
|
||||||
|
@deleted="refreshBackup"
|
||||||
|
></ha-delete-dialog>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import type { BackupModel } from "@/types/homeAssistant";
|
import type { BackupModel } from "@/types/homeAssistant";
|
||||||
import { ref, onBeforeUnmount } from "vue";
|
import { ref, onBeforeUnmount } from "vue";
|
||||||
import { getBackups } from "@/services/homeAssistantService";
|
import {
|
||||||
|
getBackups,
|
||||||
|
uploadHomeAssistantBackup,
|
||||||
|
} from "@/services/homeAssistantService";
|
||||||
import HaListItem from "./HaListItem.vue";
|
import HaListItem from "./HaListItem.vue";
|
||||||
|
import { useAlertStore } from "@/store/alert";
|
||||||
|
import HaDeleteDialog from "./HaDeleteDialog.vue";
|
||||||
|
|
||||||
|
const deleteDialog = ref<InstanceType<typeof HaDeleteDialog> | null>(null);
|
||||||
const backups = ref<BackupModel[]>([]);
|
const backups = ref<BackupModel[]>([]);
|
||||||
|
|
||||||
|
const deleteItem = ref<BackupModel | null>(null);
|
||||||
|
|
||||||
const loading = ref<boolean>(true);
|
const loading = ref<boolean>(true);
|
||||||
|
|
||||||
|
const alertStore = useAlertStore();
|
||||||
|
|
||||||
function refreshBackup() {
|
function refreshBackup() {
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
getBackups()
|
getBackups()
|
||||||
@ -70,9 +87,22 @@ function refreshBackup() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function upload(item: BackupModel) {
|
||||||
|
uploadHomeAssistantBackup(item.slug)
|
||||||
|
.then(() => {
|
||||||
|
alertStore.add("success", "Backup upload as started.");
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
alertStore.add("error", "Fail to start backup upload !");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function deleteBackup(item: BackupModel) {
|
||||||
|
deleteItem.value = item;
|
||||||
|
deleteDialog.value?.open(item);
|
||||||
|
}
|
||||||
|
|
||||||
refreshBackup();
|
refreshBackup();
|
||||||
|
|
||||||
defineExpose({ refreshBackup });
|
defineExpose({ refreshBackup });
|
||||||
|
|
||||||
// TODO Manage delete
|
|
||||||
</script>
|
</script>
|
||||||
|
@ -156,7 +156,12 @@
|
|||||||
<v-card-actions class="justify-center">
|
<v-card-actions class="justify-center">
|
||||||
<v-tooltip text="Upload to Cloud" location="bottom">
|
<v-tooltip text="Upload to Cloud" location="bottom">
|
||||||
<template v-slot:activator="{ props }">
|
<template v-slot:activator="{ props }">
|
||||||
<v-btn variant="outlined" color="success" v-bind="props">
|
<v-btn
|
||||||
|
variant="outlined"
|
||||||
|
color="success"
|
||||||
|
v-bind="props"
|
||||||
|
@click="emits('upload', item)"
|
||||||
|
>
|
||||||
<v-icon>mdi-cloud-upload</v-icon>
|
<v-icon>mdi-cloud-upload</v-icon>
|
||||||
</v-btn>
|
</v-btn>
|
||||||
</template>
|
</template>
|
||||||
@ -193,5 +198,6 @@ watch(detail, (value) => {
|
|||||||
|
|
||||||
const emits = defineEmits<{
|
const emits = defineEmits<{
|
||||||
(e: "delete", item: BackupModel): void;
|
(e: "delete", item: BackupModel): void;
|
||||||
|
(e: "upload", item: BackupModel): void;
|
||||||
}>();
|
}>();
|
||||||
</script>
|
</script>
|
||||||
|
@ -129,4 +129,3 @@ const { save, loading } = useConfigForm(
|
|||||||
);
|
);
|
||||||
defineExpose({ save });
|
defineExpose({ save });
|
||||||
</script>
|
</script>
|
||||||
@/store/backupConfig
|
|
||||||
|
@ -21,3 +21,11 @@ export function getBackups() {
|
|||||||
export function getBackupDetail(slug: string) {
|
export function getBackupDetail(slug: string) {
|
||||||
return kyClient.get(`homeAssistant/backup/${slug}`).json<BackupDetailModel>();
|
return kyClient.get(`homeAssistant/backup/${slug}`).json<BackupDetailModel>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function uploadHomeAssistantBackup(slug: string) {
|
||||||
|
return kyClient.post(`homeAssistant/backup/${slug}/upload`);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function deleteHomeAssistantBackup(slug: string) {
|
||||||
|
return kyClient.delete(`homeAssistant/backup/${slug}`);
|
||||||
|
}
|
||||||
|
@ -18,3 +18,13 @@ export function deleteWebdabBackup(path: string) {
|
|||||||
})
|
})
|
||||||
.text();
|
.text();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function restoreWebdavBackup(path: string) {
|
||||||
|
return kyClient
|
||||||
|
.post("webdav/restore", {
|
||||||
|
json: {
|
||||||
|
path: path,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.text();
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user