mirror of
https://github.com/BreizhHardware/Jellystat.git
synced 2026-01-18 16:27:20 +01:00
Added optiosn to toggle which tables to backup
Fixed logging not displaying in backup task
This commit is contained in:
@@ -1,11 +1,13 @@
|
||||
const { Pool } = require("pg");
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
const configClass = require("./config");
|
||||
|
||||
const moment = require("moment");
|
||||
const Logging = require("./logging");
|
||||
|
||||
const taskstate = require("../logging/taskstate");
|
||||
const { tables } = require("../global/backup_tables");
|
||||
|
||||
// Database connection parameters
|
||||
const postgresUser = process.env.POSTGRES_USER;
|
||||
@@ -15,18 +17,6 @@ const postgresPort = process.env.POSTGRES_PORT;
|
||||
const postgresDatabase = process.env.POSTGRES_DB || "jfstat";
|
||||
const backupfolder = "backup-data";
|
||||
|
||||
// Tables to back up
|
||||
const tables = [
|
||||
"jf_libraries",
|
||||
"jf_library_items",
|
||||
"jf_library_seasons",
|
||||
"jf_library_episodes",
|
||||
"jf_users",
|
||||
"jf_playback_activity",
|
||||
"jf_playback_reporting_plugin_data",
|
||||
"jf_item_info",
|
||||
];
|
||||
|
||||
function checkFolderWritePermission(folderPath) {
|
||||
try {
|
||||
const testFile = `${folderPath}/.writableTest`;
|
||||
@@ -39,6 +29,15 @@ function checkFolderWritePermission(folderPath) {
|
||||
}
|
||||
// Backup function
|
||||
async function backup(refLog) {
|
||||
const config = await new configClass().getConfig();
|
||||
|
||||
if (config.error) {
|
||||
refLog.logData.push({ color: "red", Message: "Backup Failed: Failed to get config" });
|
||||
refLog.logData.push({ color: "red", Message: "Backup Failed with errors" });
|
||||
Logging.updateLog(refLog.uuid, refLog.logData, taskstate.FAILED);
|
||||
return;
|
||||
}
|
||||
|
||||
refLog.logData.push({ color: "lawngreen", Message: "Starting Backup" });
|
||||
const pool = new Pool({
|
||||
user: postgresUser,
|
||||
@@ -62,30 +61,41 @@ async function backup(refLog) {
|
||||
console.error("No write permissions for the folder:", backuppath);
|
||||
refLog.logData.push({ color: "red", Message: "Backup Failed: No write permissions for the folder: " + backuppath });
|
||||
refLog.logData.push({ color: "red", Message: "Backup Failed with errors" });
|
||||
Logging.updateLog(refLog.uuid, refLog.loggedData, taskstate.FAILED);
|
||||
Logging.updateLog(refLog.uuid, refLog.logData, taskstate.FAILED);
|
||||
await pool.end();
|
||||
return;
|
||||
}
|
||||
|
||||
const ExcludedTables = config.settings?.ExcludedTables || [];
|
||||
|
||||
let filteredTables = tables.filter((table) => !ExcludedTables.includes(table.value));
|
||||
|
||||
if (filteredTables.length === 0) {
|
||||
refLog.logData.push({ color: "red", Message: "Backup Failed: No tables to backup" });
|
||||
refLog.logData.push({ color: "red", Message: "Backup Failed with errors" });
|
||||
Logging.updateLog(refLog.uuid, refLog.logData, taskstate.FAILED);
|
||||
await pool.end();
|
||||
return;
|
||||
}
|
||||
|
||||
// const backupPath = `../backup-data/backup_${now.format('yyyy-MM-DD HH-mm-ss')}.json`;
|
||||
const directoryPath = path.join(__dirname, "..", backupfolder, `backup_${now.format("yyyy-MM-DD HH-mm-ss")}.json`);
|
||||
|
||||
refLog.logData.push({ color: "yellow", Message: "Begin Backup " + directoryPath });
|
||||
const stream = fs.createWriteStream(directoryPath, { flags: "a" });
|
||||
stream.on("error", (error) => {
|
||||
refLog.logData.push({ color: "red", Message: "Backup Failed: " + error });
|
||||
Logging.updateLog(refLog.uuid, refLog.loggedData, taskstate.FAILED);
|
||||
Logging.updateLog(refLog.uuid, refLog.logData, taskstate.FAILED);
|
||||
return;
|
||||
});
|
||||
const backup_data = [];
|
||||
|
||||
refLog.logData.push({ color: "yellow", Message: "Begin Backup " + directoryPath });
|
||||
for (let table of tables) {
|
||||
const query = `SELECT * FROM ${table}`;
|
||||
for (let table of filteredTables) {
|
||||
const query = `SELECT * FROM ${table.value}`;
|
||||
|
||||
const { rows } = await pool.query(query);
|
||||
refLog.logData.push({ color: "dodgerblue", Message: `Saving ${rows.length} rows for table ${table}` });
|
||||
refLog.logData.push({ color: "dodgerblue", Message: `Saving ${rows.length} rows for table ${table.value}` });
|
||||
|
||||
backup_data.push({ [table]: rows });
|
||||
backup_data.push({ [table.value]: rows });
|
||||
}
|
||||
|
||||
await stream.write(JSON.stringify(backup_data));
|
||||
@@ -142,7 +152,7 @@ async function backup(refLog) {
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
refLog.logData.push({ color: "red", Message: "Backup Failed: " + error });
|
||||
Logging.updateLog(refLog.uuid, refLog.loggedData, taskstate.FAILED);
|
||||
Logging.updateLog(refLog.uuid, refLog.logData, taskstate.FAILED);
|
||||
}
|
||||
|
||||
await pool.end();
|
||||
|
||||
12
backend/global/backup_tables.js
Normal file
12
backend/global/backup_tables.js
Normal file
@@ -0,0 +1,12 @@
|
||||
const tables = [
|
||||
{ value: "jf_libraries", name: "Libraries" },
|
||||
{ value: "jf_library_items", name: "Library Items" },
|
||||
{ value: "jf_library_seasons", name: "Seasons" },
|
||||
{ value: "jf_library_episodes", name: "Episodes" },
|
||||
{ value: "jf_users", name: "Users" },
|
||||
{ value: "jf_playback_activity", name: "Activity" },
|
||||
{ value: "jf_playback_reporting_plugin_data", name: "Playback Reporting Plugin Data" },
|
||||
{ value: "jf_item_info", name: "Item Info" },
|
||||
];
|
||||
|
||||
module.exports = { tables };
|
||||
@@ -11,6 +11,7 @@ const { checkForUpdates } = require("../version-control");
|
||||
const API = require("../classes/api-loader");
|
||||
const { sendUpdate } = require("../ws");
|
||||
const moment = require("moment");
|
||||
const { tables } = require("../global/backup_tables");
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
@@ -965,6 +966,65 @@ router.delete("/libraryItems/purge", async (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
router.get("/getBackupTables", async (req, res) => {
|
||||
try {
|
||||
const config = await new configClass().getConfig();
|
||||
const excluded_tables = config.settings.ExcludedTables || [];
|
||||
|
||||
let backupTables = tables.map((table) => {
|
||||
return {
|
||||
...table,
|
||||
Excluded: excluded_tables.includes(table.value),
|
||||
};
|
||||
});
|
||||
|
||||
res.send(backupTables);
|
||||
return;
|
||||
} catch (error) {
|
||||
res.status(503);
|
||||
res.send(error);
|
||||
}
|
||||
});
|
||||
|
||||
router.post("/setExcludedBackupTable", async (req, res) => {
|
||||
const { table } = req.body;
|
||||
if (table === undefined || tables.map((item) => item.value).indexOf(table) === -1) {
|
||||
res.status(400);
|
||||
res.send("Invalid table provided");
|
||||
return;
|
||||
}
|
||||
|
||||
const settingsjson = await db.query('SELECT settings FROM app_config where "ID"=1').then((res) => res.rows);
|
||||
|
||||
if (settingsjson.length > 0) {
|
||||
const settings = settingsjson[0].settings || {};
|
||||
|
||||
let excludedTables = settings.ExcludedTables || [];
|
||||
if (excludedTables.includes(table)) {
|
||||
excludedTables = excludedTables.filter((item) => item !== table);
|
||||
} else {
|
||||
excludedTables.push(table);
|
||||
}
|
||||
settings.ExcludedTables = excludedTables;
|
||||
|
||||
let query = 'UPDATE app_config SET settings=$1 where "ID"=1';
|
||||
|
||||
await db.query(query, [settings]);
|
||||
|
||||
let backupTables = tables.map((table) => {
|
||||
return {
|
||||
...table,
|
||||
Excluded: settings.ExcludedTables.includes(table.value),
|
||||
};
|
||||
});
|
||||
|
||||
res.send(backupTables);
|
||||
} else {
|
||||
res.status(404);
|
||||
res.send("Settings not found");
|
||||
}
|
||||
});
|
||||
|
||||
//DB Queries - History
|
||||
router.get("/getHistory", async (req, res) => {
|
||||
try {
|
||||
|
||||
@@ -122,7 +122,7 @@ router.get("/beginBackup", async (req, res) => {
|
||||
|
||||
const uuid = randomUUID();
|
||||
let refLog = { logData: [], uuid: uuid };
|
||||
Logging.insertLog(uuid, triggertype.Manual, taskName.backup);
|
||||
await Logging.insertLog(uuid, triggertype.Manual, taskName.backup);
|
||||
await backup(refLog);
|
||||
Logging.updateLog(uuid, refLog.logData, taskstate.SUCCESS);
|
||||
res.send("Backup completed successfully");
|
||||
|
||||
12
src/pages/components/settings/backup_page.jsx
Normal file
12
src/pages/components/settings/backup_page.jsx
Normal file
@@ -0,0 +1,12 @@
|
||||
import { Col } from "react-bootstrap";
|
||||
import BackupTables from "./backup_tables";
|
||||
import BackupFiles from "./backupfiles";
|
||||
|
||||
export default function BackupPage() {
|
||||
return (
|
||||
<Col>
|
||||
<BackupTables />
|
||||
<BackupFiles />
|
||||
</Col>
|
||||
);
|
||||
}
|
||||
78
src/pages/components/settings/backup_tables.jsx
Normal file
78
src/pages/components/settings/backup_tables.jsx
Normal file
@@ -0,0 +1,78 @@
|
||||
import { useState, useEffect } from "react";
|
||||
import axios from "axios";
|
||||
|
||||
import "../../css/settings/backups.css";
|
||||
import { Trans } from "react-i18next";
|
||||
import { Button } from "react-bootstrap";
|
||||
|
||||
const token = localStorage.getItem("token");
|
||||
|
||||
export default function BackupTables() {
|
||||
const [tables, setTables] = useState([]);
|
||||
|
||||
const setTableExclusion = async (table) => {
|
||||
const tableData = await axios.post(
|
||||
`/api/setExcludedBackupTable`,
|
||||
{
|
||||
table: table,
|
||||
},
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`,
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
}
|
||||
);
|
||||
if (tableData.data) {
|
||||
setTables(tableData.data ?? []);
|
||||
}
|
||||
return;
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const fetchData = async () => {
|
||||
try {
|
||||
const backupTables = await axios.get(`/api/getBackupTables`, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`,
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
});
|
||||
setTables(backupTables.data);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
};
|
||||
|
||||
fetchData();
|
||||
|
||||
const intervalId = setInterval(fetchData, 60000 * 5);
|
||||
return () => clearInterval(intervalId);
|
||||
}, []);
|
||||
|
||||
function toggleTable(table) {
|
||||
setTableExclusion(table);
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h1 className="my-2">
|
||||
<Trans i18nKey={"TAB_CONTROLS.OPTIONS"} />
|
||||
</h1>
|
||||
<div>
|
||||
{tables.length > 0 &&
|
||||
tables.map((table, index) => (
|
||||
<Button
|
||||
key={index}
|
||||
table={table.name}
|
||||
variant={table.Excluded ? "danger" : "primary"}
|
||||
onClick={() => toggleTable(table.value)}
|
||||
className="me-2"
|
||||
>
|
||||
{table.name}
|
||||
</Button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -2,7 +2,6 @@ import {Tabs, Tab } from 'react-bootstrap';
|
||||
|
||||
import SettingsConfig from "./components/settings/settingsConfig";
|
||||
import Tasks from "./components/settings/Tasks";
|
||||
import BackupFiles from "./components/settings/backupfiles";
|
||||
import SecuritySettings from "./components/settings/security";
|
||||
import ApiKeys from "./components/settings/apiKeys";
|
||||
import LibrarySelector from "./library_selector";
|
||||
@@ -14,6 +13,7 @@ import Logs from "./components/settings/logs";
|
||||
|
||||
import "./css/settings/settings.css";
|
||||
import { Trans } from "react-i18next";
|
||||
import BackupPage from './components/settings/backup_page';
|
||||
|
||||
export default function Settings() {
|
||||
|
||||
@@ -37,7 +37,7 @@ export default function Settings() {
|
||||
</Tab>
|
||||
|
||||
<Tab eventKey="tabBackup" className='bg-transparent my-2' title={<Trans i18nKey={"SETTINGS_PAGE.BACKUP"}/>} style={{minHeight:'500px'}}>
|
||||
<BackupFiles/>
|
||||
<BackupPage/>
|
||||
</Tab>
|
||||
|
||||
<Tab eventKey="tabLogs" className='bg-transparent my-2' title={<Trans i18nKey={"SETTINGS_PAGE.LOGS"}/>} style={{minHeight:'500px'}}>
|
||||
|
||||
Reference in New Issue
Block a user