mirror of
https://github.com/Sebclem/hassio-nextcloud-backup.git
synced 2024-11-13 04:52:58 +01:00
Compare commits
No commits in common. "ace8fe7caaef835d998b2532e2c7bb439ae9e1bc" and "8287bdeedc29fbc5ae612ce87ae240978aafe48f" have entirely different histories.
ace8fe7caa
...
8287bdeedc
6
nextcloud_backup/backend/.vscode/launch.json
vendored
6
nextcloud_backup/backend/.vscode/launch.json
vendored
@ -12,9 +12,11 @@
|
|||||||
"request": "launch",
|
"request": "launch",
|
||||||
"restart": true,
|
"restart": true,
|
||||||
"runtimeExecutable": "${workspaceFolder}/node_modules/.bin/nodemon",
|
"runtimeExecutable": "${workspaceFolder}/node_modules/.bin/nodemon",
|
||||||
"skipFiles": ["<node_internals>/**"],
|
"skipFiles": [
|
||||||
|
"<node_internals>/**"
|
||||||
|
],
|
||||||
"type": "node",
|
"type": "node",
|
||||||
"preLaunchTask": "npm: build:watch"
|
"preLaunchTask": "npm: build:watch"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
26
nextcloud_backup/backend/.vscode/tasks.json
vendored
26
nextcloud_backup/backend/.vscode/tasks.json
vendored
@ -1,14 +1,14 @@
|
|||||||
{
|
{
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"tasks": [
|
"tasks": [
|
||||||
{
|
{
|
||||||
"type": "npm",
|
"type": "npm",
|
||||||
"script": "build:watch",
|
"script": "build:watch",
|
||||||
"group": "build",
|
"group": "build",
|
||||||
"label": "npm: build:watch",
|
"label": "npm: build:watch",
|
||||||
"detail": "tsc -w",
|
"detail": "tsc -w",
|
||||||
"isBackground": true,
|
"isBackground": true,
|
||||||
"problemMatcher": "$tsc-watch"
|
"problemMatcher": "$tsc-watch"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
@ -21,9 +21,9 @@ app.use(
|
|||||||
|
|
||||||
app.set("port", process.env.PORT || 3000);
|
app.set("port", process.env.PORT || 3000);
|
||||||
|
|
||||||
// app.use(
|
app.use(
|
||||||
// morgan("dev", { stream: { write: (message) => logger.debug(message) } })
|
morgan("dev", { stream: { write: (message) => logger.debug(message) } })
|
||||||
// );
|
);
|
||||||
app.use(express.json());
|
app.use(express.json());
|
||||||
app.use(express.urlencoded({ extended: false }));
|
app.use(express.urlencoded({ extended: false }));
|
||||||
app.use(cookieParser());
|
app.use(cookieParser());
|
||||||
@ -35,14 +35,13 @@ app.use("/v2/api/", apiV2Router);
|
|||||||
Error handler
|
Error handler
|
||||||
----------------------------------------------------------
|
----------------------------------------------------------
|
||||||
*/
|
*/
|
||||||
|
// catch 404 and forward to error handler
|
||||||
|
app.use((req, res, next) => {
|
||||||
|
next(createError(404));
|
||||||
|
});
|
||||||
|
|
||||||
// error handler
|
// error handler
|
||||||
if (app.get("env") == "development") {
|
if (process.env.NODE_ENV === "development") {
|
||||||
// catch 404 and forward to error handler
|
|
||||||
app.use((req, res, next) => {
|
|
||||||
next(createError(404));
|
|
||||||
});
|
|
||||||
|
|
||||||
// only use in development
|
// only use in development
|
||||||
app.use(errorHandler());
|
app.use(errorHandler());
|
||||||
}
|
}
|
||||||
|
279
nextcloud_backup/backend/src/routes/api.ts
Normal file
279
nextcloud_backup/backend/src/routes/api.ts
Normal file
@ -0,0 +1,279 @@
|
|||||||
|
// import express from "express";
|
||||||
|
// import * as statusTools from "../tools/status.js";
|
||||||
|
// import webdav from "../services/webdavService.js";
|
||||||
|
// import * as settingsTools from "../tools/settingsTools.js";
|
||||||
|
// import * as pathTools from "../tools/pathTools.js";
|
||||||
|
// import * as hassioApiTools from "../tools/hassioApiTools.js";
|
||||||
|
// import { humanFileSize } from "../tools/toolbox.js";
|
||||||
|
// import cronTools from "../tools/cronTools.js";
|
||||||
|
// import logger from "../config/winston.js";
|
||||||
|
// import { DateTime } from "luxon";
|
||||||
|
|
||||||
|
// const router = express.Router();
|
||||||
|
|
||||||
|
// router.get("/status", (req, res, next) => {
|
||||||
|
// cronTools.updateNextDate();
|
||||||
|
// const status = statusTools.getStatus();
|
||||||
|
// res.json(status);
|
||||||
|
// });
|
||||||
|
|
||||||
|
// router.get("/formated-local-snap", function (req, res, next) {
|
||||||
|
// hassioApiTools
|
||||||
|
// .getSnapshots()
|
||||||
|
// .then((snaps) => {
|
||||||
|
// snaps.sort((a, b) => {
|
||||||
|
// return a.date < b.date ? 1 : -1;
|
||||||
|
// });
|
||||||
|
|
||||||
|
// res.render("localSnaps", { snaps: snaps, DateTime: DateTime });
|
||||||
|
// })
|
||||||
|
// .catch((err) => {
|
||||||
|
// logger.error(err);
|
||||||
|
// res.status(500);
|
||||||
|
// res.send("");
|
||||||
|
// });
|
||||||
|
// });
|
||||||
|
|
||||||
|
// router.get("/formated-backup-manual", function (req, res, next) {
|
||||||
|
// if (webdav.getConf() == null) {
|
||||||
|
// res.send("");
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
// webdav
|
||||||
|
// .getFolderContent(webdav.getConf()?.back_dir + pathTools.manual)
|
||||||
|
// .then((contents: any) => {
|
||||||
|
// contents.sort((a: any, b: any) => {
|
||||||
|
// return a.date < b.date ? 1 : -1;
|
||||||
|
// });
|
||||||
|
// //TODO Remove this when bug is fixed, etag contain '"' at start and end ?
|
||||||
|
// for (const backup of contents) {
|
||||||
|
// backup.etag = backup.etag.replace(/"/g, "");
|
||||||
|
// }
|
||||||
|
// res.render("backupSnaps", {
|
||||||
|
// backups: contents,
|
||||||
|
// DateTime: DateTime,
|
||||||
|
// humanFileSize: humanFileSize,
|
||||||
|
// });
|
||||||
|
// })
|
||||||
|
// .catch((err) => {
|
||||||
|
// res.status(500);
|
||||||
|
// res.send(err);
|
||||||
|
// });
|
||||||
|
// });
|
||||||
|
|
||||||
|
// router.get("/formated-backup-auto", function (req, res, next) {
|
||||||
|
// if (webdav.getConf() == null) {
|
||||||
|
// res.send("");
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
// const url = webdav.getConf()?.back_dir + pathTools.auto;
|
||||||
|
// webdav
|
||||||
|
// .getFolderContent(url)
|
||||||
|
// .then((contents: any) => {
|
||||||
|
// contents.sort((a: any, b: any) => {
|
||||||
|
// return a.date < b.date ? 1 : -1;
|
||||||
|
// });
|
||||||
|
// //TODO Remove this when bug is fixed, etag contain '"' at start and end ?
|
||||||
|
// for (const backup of contents) {
|
||||||
|
// backup.etag = backup.etag.replace(/"/g, "");
|
||||||
|
// }
|
||||||
|
// res.render("backupSnaps", {
|
||||||
|
// backups: contents,
|
||||||
|
// DateTime: DateTime,
|
||||||
|
// humanFileSize: humanFileSize,
|
||||||
|
// });
|
||||||
|
// })
|
||||||
|
// .catch((err) => {
|
||||||
|
// res.status(500);
|
||||||
|
// res.send(err);
|
||||||
|
// });
|
||||||
|
// });
|
||||||
|
|
||||||
|
// router.post("/nextcloud-settings", function (req, res, next) {
|
||||||
|
// const settings = req.body;
|
||||||
|
// if (
|
||||||
|
// settings.ssl != null &&
|
||||||
|
// settings.host != null &&
|
||||||
|
// settings.host !== "" &&
|
||||||
|
// settings.username != null &&
|
||||||
|
// settings.password != null
|
||||||
|
// ) {
|
||||||
|
// webdav.setConf(settings);
|
||||||
|
// webdav
|
||||||
|
// .confIsValid()
|
||||||
|
// .then(() => {
|
||||||
|
// res.status(201);
|
||||||
|
// res.send();
|
||||||
|
// })
|
||||||
|
// .catch((err) => {
|
||||||
|
// res.status(406);
|
||||||
|
// res.json({ message: err });
|
||||||
|
// });
|
||||||
|
// } else {
|
||||||
|
// res.status(400);
|
||||||
|
// res.send();
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
|
||||||
|
// router.get("/nextcloud-settings", function (req, res, next) {
|
||||||
|
// const conf = webdav.getConf();
|
||||||
|
// if (conf == null) {
|
||||||
|
// res.status(404);
|
||||||
|
// res.send();
|
||||||
|
// } else {
|
||||||
|
// res.json(conf);
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
|
||||||
|
// router.post("/manual-backup", function (req, res, next) {
|
||||||
|
// const id = req.query.id;
|
||||||
|
// const name = req.query.name;
|
||||||
|
// const status = statusTools.getStatus();
|
||||||
|
// if (
|
||||||
|
// status.status == "creating" ||
|
||||||
|
// status.status == "upload" ||
|
||||||
|
// status.status == "download"
|
||||||
|
// ) {
|
||||||
|
// res.status(503);
|
||||||
|
// res.send();
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// hassioApiTools
|
||||||
|
// .downloadSnapshot(id as string)
|
||||||
|
// .then(() => {
|
||||||
|
// webdav
|
||||||
|
// .uploadFile(
|
||||||
|
// id as string,
|
||||||
|
// webdav.getConf()?.back_dir + pathTools.manual + name + ".tar"
|
||||||
|
// )
|
||||||
|
// .then(() => {
|
||||||
|
// res.status(201);
|
||||||
|
// res.send();
|
||||||
|
// })
|
||||||
|
// .catch(() => {
|
||||||
|
// res.status(500);
|
||||||
|
// res.send();
|
||||||
|
// });
|
||||||
|
// })
|
||||||
|
// .catch(() => {
|
||||||
|
// res.status(500);
|
||||||
|
// res.send();
|
||||||
|
// });
|
||||||
|
// });
|
||||||
|
|
||||||
|
// router.post("/new-backup", function (req, res, next) {
|
||||||
|
// const status = statusTools.getStatus();
|
||||||
|
// if (
|
||||||
|
// status.status == "creating" ||
|
||||||
|
// status.status == "upload" ||
|
||||||
|
// status.status == "download" ||
|
||||||
|
// status.status == "stopping" ||
|
||||||
|
// status.status == "starting"
|
||||||
|
// ) {
|
||||||
|
// res.status(503);
|
||||||
|
// res.send();
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
// hassioApiTools
|
||||||
|
// .stopAddons()
|
||||||
|
// .then(() => {
|
||||||
|
// hassioApiTools
|
||||||
|
// .getVersion()
|
||||||
|
// .then((version) => {
|
||||||
|
// const name = settingsTools.getFormatedName(true, version);
|
||||||
|
// hassioApiTools
|
||||||
|
// .createNewBackup(name)
|
||||||
|
// .then((id) => {
|
||||||
|
// hassioApiTools
|
||||||
|
// .downloadSnapshot(id)
|
||||||
|
// .then(() => {
|
||||||
|
// webdav
|
||||||
|
// .uploadFile(
|
||||||
|
// id,
|
||||||
|
// webdav.getConf()?.back_dir +
|
||||||
|
// pathTools.manual +
|
||||||
|
// name +
|
||||||
|
// ".tar"
|
||||||
|
// )
|
||||||
|
// .then(() => {
|
||||||
|
// hassioApiTools.startAddons().catch(() => {
|
||||||
|
// // Skip
|
||||||
|
// });
|
||||||
|
// })
|
||||||
|
// .catch(() => {
|
||||||
|
// // Skip
|
||||||
|
// });
|
||||||
|
// })
|
||||||
|
// .catch(() => {
|
||||||
|
// // Skip
|
||||||
|
// });
|
||||||
|
// })
|
||||||
|
// .catch(() => {
|
||||||
|
// // Skip
|
||||||
|
// });
|
||||||
|
// })
|
||||||
|
// .catch(() => {
|
||||||
|
// // Skip
|
||||||
|
// });
|
||||||
|
// })
|
||||||
|
// .catch(() => {
|
||||||
|
// hassioApiTools.startAddons().catch(() => {
|
||||||
|
// // Skip
|
||||||
|
// });
|
||||||
|
// });
|
||||||
|
|
||||||
|
// res.status(201);
|
||||||
|
// res.send();
|
||||||
|
// });
|
||||||
|
|
||||||
|
// router.get("/backup-settings", function (req, res, next) {
|
||||||
|
// hassioApiTools.getAddonList().then((addonList) => {
|
||||||
|
// const data = {
|
||||||
|
// folder: hassioApiTools.getFolderList(),
|
||||||
|
// addonList: addonList,
|
||||||
|
// settings: settingsTools.getSettings()
|
||||||
|
// };
|
||||||
|
// res.send(data);
|
||||||
|
// });
|
||||||
|
// });
|
||||||
|
|
||||||
|
// router.post("/backup-settings", function (req, res, next) {
|
||||||
|
// const [result, message] = settingsTools.check(req.body);
|
||||||
|
// if (result) {
|
||||||
|
// settingsTools.setSettings(req.body);
|
||||||
|
// cronTools.init();
|
||||||
|
// res.send();
|
||||||
|
// } else {
|
||||||
|
// res.status(400);
|
||||||
|
// res.send(message);
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
|
||||||
|
// router.post("/clean-now", function (req, res, next) {
|
||||||
|
// webdav
|
||||||
|
// .clean()
|
||||||
|
// .then(() => {
|
||||||
|
// hassioApiTools.clean().catch();
|
||||||
|
// })
|
||||||
|
// .catch(() => {
|
||||||
|
// hassioApiTools.clean().catch();
|
||||||
|
// });
|
||||||
|
// res.status(201);
|
||||||
|
// res.send();
|
||||||
|
// });
|
||||||
|
|
||||||
|
// router.post("/restore", function (req, res, next) {
|
||||||
|
// if (req.body["path"] != null) {
|
||||||
|
// webdav.downloadFile(req.body["path"]).then((path) => {
|
||||||
|
// hassioApiTools.uploadSnapshot(path).catch();
|
||||||
|
// });
|
||||||
|
// res.status(200);
|
||||||
|
// res.send();
|
||||||
|
// } else {
|
||||||
|
// res.status(400);
|
||||||
|
// res.send();
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
|
||||||
|
// export default router;
|
@ -73,20 +73,17 @@ export function doBackupWorkflow(type: WorkflowType) {
|
|||||||
if (webdavConfig.chunckedUpload) {
|
if (webdavConfig.chunckedUpload) {
|
||||||
return webDavService.chunkedUpload(
|
return webDavService.chunkedUpload(
|
||||||
tmpFile,
|
tmpFile,
|
||||||
getBackupFolder(type, webdavConfig) + name + ".tar",
|
getBackupFolder(type, webdavConfig) + name,
|
||||||
webdavConfig
|
webdavConfig
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
return webDavService.webdavUploadFile(
|
return webDavService.webdavUploadFile(
|
||||||
tmpFile,
|
tmpFile,
|
||||||
getBackupFolder(type, webdavConfig) + name + ".tar",
|
getBackupFolder(type, webdavConfig) + name,
|
||||||
webdavConfig
|
webdavConfig
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.then(() => {
|
|
||||||
return homeAssistantService.startAddons(addonsToStartStop);
|
|
||||||
})
|
|
||||||
.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 !");
|
||||||
@ -107,7 +104,8 @@ export function doBackupWorkflow(type: WorkflowType) {
|
|||||||
|
|
||||||
// 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));
|
addonInConf.filter((value) => addonInHA.some((v) => v.slug == value));
|
||||||
|
return addonInConf;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getAddonToBackup(excludedAddon: string[], addonInHA: AddonModel[]) {
|
function getAddonToBackup(excludedAddon: string[], addonInHA: AddonModel[]) {
|
||||||
|
@ -414,12 +414,6 @@ export async function chunkedUpload(
|
|||||||
logger.debug("Chunked upload funished, assembling chunks.");
|
logger.debug("Chunked upload funished, assembling chunks.");
|
||||||
try {
|
try {
|
||||||
await assembleChunkedUpload(chunkedUrl, finalDestination, fileSize, config);
|
await assembleChunkedUpload(chunkedUrl, finalDestination, fileSize, config);
|
||||||
const status = statusTools.getStatus();
|
|
||||||
status.status = States.IDLE;
|
|
||||||
status.progress = undefined;
|
|
||||||
statusTools.setStatus(status);
|
|
||||||
logger.info(`...Upload finish !`);
|
|
||||||
fs.unlinkSync(localPath);
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (err instanceof RequestError) {
|
if (err instanceof RequestError) {
|
||||||
messageManager.error(
|
messageManager.error(
|
||||||
@ -488,7 +482,7 @@ export function initChunkedUpload(
|
|||||||
finalDestination: string,
|
finalDestination: string,
|
||||||
config: WebdavConfig
|
config: WebdavConfig
|
||||||
) {
|
) {
|
||||||
logger.info(`Init chuncked upload.`);
|
logger.debug(`Init chuncked upload.`);
|
||||||
logger.debug(`...URI: ${encodeURI(url)}`);
|
logger.debug(`...URI: ${encodeURI(url)}`);
|
||||||
logger.debug(`...Final destination: ${encodeURI(finalDestination)}`);
|
logger.debug(`...Final destination: ${encodeURI(finalDestination)}`);
|
||||||
return got(encodeURI(url), {
|
return got(encodeURI(url), {
|
||||||
@ -510,7 +504,7 @@ export function assembleChunkedUpload(
|
|||||||
config: WebdavConfig
|
config: WebdavConfig
|
||||||
) {
|
) {
|
||||||
const chunckFile = `${url}/.file`;
|
const chunckFile = `${url}/.file`;
|
||||||
logger.info(`Assemble chuncked upload.`);
|
logger.debug(`Assemble chuncked upload.`);
|
||||||
logger.debug(`...URI: ${encodeURI(chunckFile)}`);
|
logger.debug(`...URI: ${encodeURI(chunckFile)}`);
|
||||||
logger.debug(`...Final destination: ${encodeURI(finalDestination)}`);
|
logger.debug(`...Final destination: ${encodeURI(finalDestination)}`);
|
||||||
return got(encodeURI(chunckFile), {
|
return got(encodeURI(chunckFile), {
|
||||||
|
@ -32,49 +32,26 @@
|
|||||||
</v-select>
|
</v-select>
|
||||||
</v-col>
|
</v-col>
|
||||||
</v-row>
|
</v-row>
|
||||||
<div v-if="data.webdavEndpoint.type == WebdavEndpointType.CUSTOM">
|
<v-row v-if="data.webdavEndpoint.type == WebdavEndpointType.CUSTOM">
|
||||||
<v-row>
|
<v-col>
|
||||||
<v-col>
|
<div class="text-subtitle-1 text-medium-emphasis">Custom Endpoint</div>
|
||||||
<div class="text-subtitle-1 text-medium-emphasis">
|
<v-text-field
|
||||||
Custom endpoint
|
placeholder="/remote.php/dav/files/$username"
|
||||||
</div>
|
hint="You can use the $username variable"
|
||||||
<v-text-field
|
variant="outlined"
|
||||||
placeholder="/remote.php/dav/files/$username"
|
density="compact"
|
||||||
hint="You can use the $username variable"
|
hide-details="auto"
|
||||||
variant="outlined"
|
v-model="data.webdavEndpoint.customEndpoint"
|
||||||
density="compact"
|
:error-messages="errors.customEndpoint"
|
||||||
hide-details="auto"
|
:loading="loading"
|
||||||
v-model="data.webdavEndpoint.customEndpoint"
|
color="orange"
|
||||||
:error-messages="errors.customEndpoint"
|
></v-text-field>
|
||||||
:loading="loading"
|
</v-col>
|
||||||
color="orange"
|
</v-row>
|
||||||
></v-text-field>
|
|
||||||
</v-col>
|
|
||||||
</v-row>
|
|
||||||
<v-row>
|
|
||||||
<v-col>
|
|
||||||
<div class="text-subtitle-1 text-medium-emphasis">
|
|
||||||
Custom chunk endpoint
|
|
||||||
</div>
|
|
||||||
<v-text-field
|
|
||||||
placeholder="/remote.php/dav/uploads/$username"
|
|
||||||
hint="You can use the $username variable"
|
|
||||||
variant="outlined"
|
|
||||||
density="compact"
|
|
||||||
hide-details="auto"
|
|
||||||
v-model="data.webdavEndpoint.customChunkEndpoint"
|
|
||||||
:error-messages="errors.customChunkEndpoint"
|
|
||||||
:loading="loading"
|
|
||||||
color="orange"
|
|
||||||
></v-text-field>
|
|
||||||
</v-col>
|
|
||||||
</v-row>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<v-row class="mt-0">
|
<v-row class="mt-0">
|
||||||
<v-col class="d-flex align-content-end">
|
<v-col class="d-flex align-content-end">
|
||||||
<v-switch
|
<v-switch
|
||||||
label="Allow self signed certificate"
|
label="Allow Self Signed Certificate"
|
||||||
v-model="data.allowSelfSignedCerts"
|
v-model="data.allowSelfSignedCerts"
|
||||||
hide-details="auto"
|
hide-details="auto"
|
||||||
density="compact"
|
density="compact"
|
||||||
@ -88,7 +65,7 @@
|
|||||||
<v-row class="mt-0">
|
<v-row class="mt-0">
|
||||||
<v-col class="d-flex align-content-end">
|
<v-col class="d-flex align-content-end">
|
||||||
<v-switch
|
<v-switch
|
||||||
label="Chunked upload (Beta)"
|
label="Chunked Upload (Beta)"
|
||||||
v-model="data.chunckedUpload"
|
v-model="data.chunckedUpload"
|
||||||
hide-details="auto"
|
hide-details="auto"
|
||||||
density="compact"
|
density="compact"
|
||||||
@ -194,7 +171,6 @@ const errors = ref({
|
|||||||
allowSelfSignedCerts: [],
|
allowSelfSignedCerts: [],
|
||||||
type: [],
|
type: [],
|
||||||
customEndpoint: [],
|
customEndpoint: [],
|
||||||
customChunkEndpoint: [],
|
|
||||||
chunckedUpload: [],
|
chunckedUpload: [],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -13,6 +13,5 @@ export interface WebdavConfig {
|
|||||||
webdavEndpoint: {
|
webdavEndpoint: {
|
||||||
type: WebdavEndpointType;
|
type: WebdavEndpointType;
|
||||||
customEndpoint?: string;
|
customEndpoint?: string;
|
||||||
customChunkEndpoint?: string;
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user