This commit is contained in:
SebClem 2024-08-09 17:08:20 +02:00
parent 1f9d79d2ca
commit 5f3c59c572
Signed by: sebclem
GPG Key ID: 5A4308F6A359EA50
8 changed files with 202 additions and 48 deletions

View File

@ -15,7 +15,7 @@
"app-root-path": "3.1.0", "app-root-path": "3.1.0",
"cookie-parser": "1.4.6", "cookie-parser": "1.4.6",
"cors": "^2.8.5", "cors": "^2.8.5",
"cron": "3.1.6", "cron": "3.1.7",
"debug": "4.3.4", "debug": "4.3.4",
"errorhandler": "^1.5.1", "errorhandler": "^1.5.1",
"express": "4.18.2", "express": "4.18.2",

View File

@ -15,8 +15,8 @@ dependencies:
specifier: ^2.8.5 specifier: ^2.8.5
version: 2.8.5 version: 2.8.5
cron: cron:
specifier: 3.1.6 specifier: 3.1.7
version: 3.1.6 version: 3.1.7
debug: debug:
specifier: 4.3.4 specifier: 4.3.4
version: 4.3.4(supports-color@5.5.0) version: 4.3.4(supports-color@5.5.0)
@ -400,13 +400,8 @@ packages:
resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==}
dev: true dev: true
/@types/luxon@3.3.8:
resolution: {integrity: sha512-jYvz8UMLDgy3a5SkGJne8H7VA7zPV2Lwohjx0V8V31+SqAjNmurWMkk9cQhfvlcnXWudBpK9xPM1n4rljOcHYQ==}
dev: false
/@types/luxon@3.4.2: /@types/luxon@3.4.2:
resolution: {integrity: sha512-TifLZlFudklWlMBfhubvgqTXRzLDI5pCbGa4P8a3wPyUQSW+1xQ5eDsreP9DWHX3tjq1ke96uYG/nwundroWcA==} resolution: {integrity: sha512-TifLZlFudklWlMBfhubvgqTXRzLDI5pCbGa4P8a3wPyUQSW+1xQ5eDsreP9DWHX3tjq1ke96uYG/nwundroWcA==}
dev: true
/@types/mime@1.3.5: /@types/mime@1.3.5:
resolution: {integrity: sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==} resolution: {integrity: sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==}
@ -928,10 +923,10 @@ packages:
resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==} resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==}
dev: true dev: true
/cron@3.1.6: /cron@3.1.7:
resolution: {integrity: sha512-cvFiQCeVzsA+QPM6fhjBtlKGij7tLLISnTSvFxVdnFGLdz+ZdXN37kNe0i2gefmdD17XuZA6n2uPVwzl4FxW/w==} resolution: {integrity: sha512-tlBg7ARsAMQLzgwqVxy8AZl/qlTc5nibqYwtNGoCrd+cV+ugI+tvZC1oT/8dFH8W455YrywGykx/KMmAqOr7Jw==}
dependencies: dependencies:
'@types/luxon': 3.3.8 '@types/luxon': 3.4.2
luxon: 3.4.4 luxon: 3.4.4
dev: false dev: false

View File

@ -12,6 +12,8 @@ import {
validateWebdavConfig, validateWebdavConfig,
} from "./services/webdavConfigService.js"; } from "./services/webdavConfigService.js";
import messageManager from "./tools/messageManager.js"; import messageManager from "./tools/messageManager.js";
import { initCron } from "./services/cronService.js";
import { getBackupConfig } from "./services/backupConfigService.js";
function postInit() { function postInit() {
logger.info(`Log level: ${process.env.LOG_LEVEL}`); logger.info(`Log level: ${process.env.LOG_LEVEL}`);
@ -39,34 +41,46 @@ function postInit() {
); );
const webdavConf = getWebdavConfig(); const webdavConf = getWebdavConfig();
validateWebdavConfig(webdavConf).then( validateWebdavConfig(webdavConf)
() => { .then(
logger.info("Webdav config: " + kleur.green().bold("Go !")); () => {
checkWebdavLogin(webdavConf).then( logger.info("Webdav config: " + kleur.green().bold("Go !"));
() => { return checkWebdavLogin(webdavConf);
logger.info("Webdav : " + kleur.green().bold("Go !")); },
createBackupFolder(webdavConf).then( (reason: Error) => {
() => { logger.error("Webdav config: " + kleur.red().bold("FAIL !"));
logger.info("Webdav fodlers: " + kleur.green().bold("Go !")); logger.error(reason);
}, messageManager.error("Invalid webdav config", reason.message);
(reason) => { }
logger.error("Webdav folders: " + kleur.red().bold("FAIL !")); )
logger.error(reason); .then(
} () => {
); logger.info("Webdav : " + kleur.green().bold("Go !"));
}, return createBackupFolder(webdavConf);
(reason) => { },
logger.error("Webdav : " + kleur.red().bold("FAIL !")); (reason) => {
logger.error(reason); logger.error("Webdav : " + kleur.red().bold("FAIL !"));
} logger.error(reason);
); }
}, )
(reason: Error) => { .then(
logger.error("Webdav config: " + kleur.red().bold("FAIL !")); () => {
logger.error(reason); logger.info("Webdav fodlers: " + kleur.green().bold("Go !"));
messageManager.error("Invalid webdav config", reason.message); return initCron(getBackupConfig());
} },
); (reason) => {
logger.error("Webdav folders: " + kleur.red().bold("FAIL !"));
logger.error(reason);
}
)
.then(
() => {
logger.info("Cron: " + kleur.green().bold("Go !"));
},
(reason) => {
logger.info("Cron: " + kleur.red().bold("FAIL !"));
}
);
// settingsTools.check(settingsTools.getSettings(), true); // settingsTools.check(settingsTools.getSettings(), true);
// cronTools.init(); // cronTools.init();

View File

@ -22,13 +22,24 @@ configRouter.get("/backup", (req, res) => {
configRouter.put("/backup", (req, res) => { configRouter.put("/backup", (req, res) => {
validateBackupConfig(req.body as BackupConfig) validateBackupConfig(req.body as BackupConfig)
.then(() => { .then(
saveBackupConfig(req.body as BackupConfig); () => {
res.status(204).send(); return saveBackupConfig(req.body as BackupConfig);
}) },
.catch((error: ValidationError) => { (error: ValidationError) => {
res.status(400).json(error.details); res.status(400).json(error.details);
}); }
)
.then(
() => {
res.status(204).send();
},
() => {
res.status(400).json({
message: "Fail to init cron, please check cron config",
});
}
);
}); });
configRouter.get("/webdav", (req, res) => { configRouter.get("/webdav", (req, res) => {

View File

@ -8,6 +8,7 @@ import {
import backupConfigValidation from "../types/services/backupConfigValidation.js"; import backupConfigValidation from "../types/services/backupConfigValidation.js";
import { DateTime } from "luxon"; import { DateTime } from "luxon";
import { WorkflowType } from "../types/services/orchecstrator.js"; import { WorkflowType } from "../types/services/orchecstrator.js";
import { initCron } from "./cronService.js";
const backupConfigPath = "/data/backupConfigV2.json"; const backupConfigPath = "/data/backupConfigV2.json";
@ -20,13 +21,14 @@ export function validateBackupConfig(config: BackupConfig) {
export function saveBackupConfig(config: BackupConfig) { export function saveBackupConfig(config: BackupConfig) {
fs.writeFileSync(backupConfigPath, JSON.stringify(config, undefined, 2)); fs.writeFileSync(backupConfigPath, JSON.stringify(config, undefined, 2));
return initCron(config);
} }
export function getBackupConfig(): BackupConfig { export function getBackupConfig(): BackupConfig {
if (!fs.existsSync(backupConfigPath)) { if (!fs.existsSync(backupConfigPath)) {
logger.warn("Config file not found, creating default one !"); logger.warn("Config file not found, creating default one !");
const defaultConfig = getBackupDefaultConfig(); const defaultConfig = getBackupDefaultConfig();
saveBackupConfig(defaultConfig); saveBackupConfig(defaultConfig).catch(() => {});
return defaultConfig; return defaultConfig;
} else { } else {
return JSON.parse( return JSON.parse(

View File

@ -0,0 +1,119 @@
import { CronJob } from "cron";
import {
CronMode,
type BackupConfig,
type CronConfig,
} from "../types/services/backupConfig.js";
import { WorkflowType } from "../types/services/orchecstrator.js";
import { doBackupWorkflow } from "./orchestrator.js";
import { DateTime } from "luxon";
import { getStatus, setStatus } from "../tools/status.js";
import logger from "../config/winston.js";
let cronList: Map<string, CronJob>;
export function initCron(backupConfig: BackupConfig) {
return new Promise((res, rej) => {
const fn = doBackupWorkflow;
if (cronList) {
stopAllCron(cronList);
}
cronList = new Map();
for (const cronItem of backupConfig.cron) {
try {
if (cronItem.mode == CronMode.DAILY) {
cronList.set(cronItem.id, getDailyCron(cronItem, fn));
} else if (cronItem.mode == CronMode.WEEKLY) {
cronList.set(cronItem.id, getWeeklyCron(cronItem, fn));
} else if (cronItem.mode == CronMode.MONTHLY) {
cronList.set(cronItem.id, getMonthlyCron(cronItem, fn));
} else if (cronItem.mode == CronMode.CUSTOM) {
cronList.set(cronItem.id, getCustomCron(cronItem, fn));
}
} catch {
logger.error(`Fail to init CRON ${cronItem.id} (${cronItem.mode})`);
stopAllCron(cronList);
rej(Error(cronItem.id));
}
}
const nextDate = getNextDate(cronList);
const status = getStatus();
status.next_backup = nextDate;
setStatus(status);
res(null);
});
}
function getNextDate(cronList: Map<string, CronJob>) {
let nextDate: DateTime | undefined = undefined;
for (const item of cronList) {
const thisDate = item[1].nextDate();
if (!nextDate) {
nextDate = thisDate;
}
if (nextDate > thisDate) {
nextDate = thisDate;
}
}
return nextDate;
}
function stopAllCron(cronList: Map<string, CronJob>) {
for (const item of cronList) {
item[1].stop();
}
}
function getDailyCron(
config: CronConfig,
fn: (type: WorkflowType) => Promise<void>
) {
const splited = (config.hour as string).split(":");
return new CronJob(
`${splited[1]} ${splited[0]} * * *`,
() => fn(WorkflowType.AUTO),
null,
true,
Intl.DateTimeFormat().resolvedOptions().timeZone
);
}
function getWeeklyCron(
config: CronConfig,
fn: (type: WorkflowType) => Promise<void>
) {
const splited = (config.hour as string).split(":");
return new CronJob(
`${splited[1]} ${splited[0]} * * ${config.weekday}`,
() => fn(WorkflowType.AUTO),
null,
true,
Intl.DateTimeFormat().resolvedOptions().timeZone
);
}
function getMonthlyCron(
config: CronConfig,
fn: (type: WorkflowType) => Promise<void>
) {
const splited = (config.hour as string).split(":");
return new CronJob(
`${splited[1]} ${splited[0]} ${config.monthDay} * *`,
() => fn(WorkflowType.AUTO),
null,
true,
Intl.DateTimeFormat().resolvedOptions().timeZone
);
}
function getCustomCron(
config: CronConfig,
fn: (type: WorkflowType) => Promise<void>
) {
return new CronJob(
config.custom as string,
() => fn(WorkflowType.AUTO),
null,
true,
Intl.DateTimeFormat().resolvedOptions().timeZone
);
}

View File

@ -21,7 +21,7 @@ export interface Status {
last_success?: DateTime; last_success?: DateTime;
last_try?: DateTime; last_try?: DateTime;
}; };
next_backup?: string; next_backup?: DateTime;
webdav: { webdav: {
logged_in: boolean; logged_in: boolean;
folder_created: boolean; folder_created: boolean;

View File

@ -47,6 +47,19 @@
<v-col xl="6" lg="12" sm="6" cols="12"> <v-col xl="6" lg="12" sm="6" cols="12">
<div class="h-100 d-flex align-center"> <div class="h-100 d-flex align-center">
<span class="me-auto">Next</span> <span class="me-auto">Next</span>
<v-chip
variant="elevated"
color="success"
prepend-icon="mdi-update"
>
{{
status?.next_backup
? DateTime.fromISO(status?.next_backup).toLocaleString(
DateTime.DATETIME_MED
)
: "Unknown"
}}
</v-chip>
</div> </div>
</v-col> </v-col>
</v-row> </v-row>