Merge branch 'bootstrap'

This commit is contained in:
Sebastien Clement 2021-01-09 00:28:02 +01:00
commit 84c3027d97
22 changed files with 11738 additions and 2748 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 75 KiB

After

Width:  |  Height:  |  Size: 108 KiB

View File

@ -13,7 +13,7 @@ Auto backup can be configure via the Home Assistant web interface.
### Features
- Auto Backup : Configure this add-on to automaticaly backup your HassIO instance.
- Auto Clean : You can specify the maximum number of local snapshots and (__ONLY__) auto backup snapshots.
- ~~Restore backups.~~ (Coming Soon)
- Upload backed-up snapshot to Home assistant.
> __Info:__
> Auto Clean is executed after every upload and every day at 00h30

View File

@ -13,7 +13,7 @@ Auto backup can be configured via the web interface.
### Features
- Auto Backup : Configure this add-on to automaticly backup your HassIO instance.
- Auto Clean : You can specify the maximum number of local snapshots and (__ONLY__) auto backed-up snapshots.
- ~~Restore backups.~~ (Coming Soon)
- Upload backed-up snapshot to Home assistant.
> __Info:__
> Auto Clean is executed after every upload and every day at 00h30

View File

@ -27,6 +27,28 @@ app.use(express.static(path.join(__dirname, "public")));
app.use("/", indexRouter);
app.use("/api", apiRouter);
/*
-----------------------------------------------------------
Library statics
----------------------------------------------------------
*/
// Boootstrap JS Files
app.use('/js/bootstrap.min.js', express.static(path.join(__dirname, '/node_modules/bootstrap/dist/js/bootstrap.min.js')))
// Fontawesome Files
app.use('/css/fa-all.min.css', express.static(path.join(__dirname, '/node_modules/@fortawesome/fontawesome-free/css/all.min.css')))
app.use('/webfonts/', express.static(path.join(__dirname, '/node_modules/@fortawesome/fontawesome-free/webfonts')))
// Jquery JS Files
app.use('/js/jquery.min.js', express.static(path.join(__dirname, '/node_modules/jquery/dist/jquery.min.js')))
/*
-----------------------------------------------------------
Error handler
----------------------------------------------------------
*/
// catch 404 and forward to error handler
app.use(function (req, res, next) {
next(createError(404));
@ -43,6 +65,12 @@ app.use(function (err, req, res, next) {
res.render("error");
});
/*
-----------------------------------------------------------
Init app
----------------------------------------------------------
*/
const fs = require("fs");
const newlog = require("./config/winston");
if (!fs.existsSync("/data")) fs.mkdirSync("/data");

File diff suppressed because it is too large Load Diff

View File

@ -6,7 +6,9 @@
"start": "node --inspect=0.0.0.0:9226 ./bin/www "
},
"dependencies": {
"@fortawesome/fontawesome-free": "^5.15.1",
"app-root-path": "^3.0.0",
"bootstrap": "^5.0.0-beta1",
"cookie-parser": "^1.4.5",
"cron": "^1.8.2",
"debug": "~2.6.9",
@ -15,16 +17,10 @@
"form-data": "^3.0.0",
"got": "^11.8.1",
"http-errors": "~1.6.3",
"jquery": "^3.5.1",
"moment": "^2.29.1",
"morgan": "~1.9.1",
"webdav": "^3.6.2",
"winston": "^3.3.3"
},
"devDependencies": {
"eslint": "^7.17.0",
"eslint-config-eslint": "^6.0.0",
"eslint-config-prettier": "^6.15.0",
"eslint-plugin-import": "^2.22.1",
"eslint-plugin-node": "^11.1.0"
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,15 @@
.navbar {
background-color: #0091ea;
}
#header-box {
min-height: 160px;
}
.toast-container {
position: fixed;
z-index: 1055;
margin: 5px;
top: 58px;
right: 0;
}

View File

@ -0,0 +1,448 @@
var last_status = "";
var last_local_snap = "";
var last_manu_back = "";
var last_auto_back = "";
const default_toast_timeout = 10000;
let loadingModal;
let nextcloud_setting_modal;
let backup_setting_modal;
document.addEventListener('DOMContentLoaded', function () {
$.ajaxSetup({ traditional: true });
updateLocalSnaps();
update_status();
loadingModal = new bootstrap.Modal(document.getElementById('loading-modal'), {
keyboard: false,
backdrop: 'static'
});
nextcloud_setting_modal = new bootstrap.Modal(document.getElementById('modal-settings-nextcloud'));
backup_setting_modal = new bootstrap.Modal(document.getElementById('modal-settings-backup'));
setInterval(update_status, 500);
setInterval(updateLocalSnaps, 5000);
listeners();
});
function updateDynamicListeners() {
$('.local-snap-listener').click(function () {
let id = this.getAttribute('data-id');
console.log(id);
});
let manual_back_list = $('.manual-back-list');
manual_back_list.unbind();
manual_back_list.click(function () {
let id = this.getAttribute('data-id');
let name = this.getAttribute('data-name');
manualBackup(id, name);
})
$('.restore').click(function () {
let to_restore = this.getAttribute('data-id');
console.log(to_restore)
restore(to_restore)
})
}
function updateLocalSnaps() {
let needUpdate = false;
$.get('./api/formated-local-snap', (data) => {
if (JSON.stringify(data) === last_local_snap)
return;
last_local_snap = JSON.stringify(data);
needUpdate = true;
let local_snaps = $('#local_snaps');
local_snaps.empty();
local_snaps.html(data);
}).always(() => {
updateManuBackup(needUpdate);
});
}
function updateManuBackup(prevUpdate) {
let needUpdate = false;
$.get('./api/formated-backup-manual', (data) => {
if (JSON.stringify(data) === last_manu_back)
return;
last_manu_back = JSON.stringify(data);
needUpdate = true;
let manual_backups = $('#manual_backups')
manual_backups.empty();
manual_backups.html(data);
}).always(() => {
updateAutoBackup(prevUpdate || needUpdate);
});
}
function updateAutoBackup(prevUpdate) {
let needUpdate = false;
$.get('./api/formated-backup-auto', (data) => {
if (JSON.stringify(data) === last_auto_back)
return;
needUpdate = true;
last_auto_back = JSON.stringify(data);
let auto_backups = $('#auto_backups')
auto_backups.empty();
auto_backups.html(data);
}).always(() => {
if (prevUpdate || needUpdate)
updateDynamicListeners();
});
}
function update_status() {
$.get('./api/status', (data) => {
if (JSON.stringify(data) !== last_status) {
last_status = JSON.stringify(data);
let buttons = $('#btn-backup-now, #btn-clean-now');
switch (data.status) {
case "error":
printStatus('Error', data.message);
buttons.removeClass("disabled");
break;
case "idle":
printStatus('Idle', "Waiting for next backup.");
buttons.removeClass("disabled");
break;
case "download":
printStatusWithBar('Downloading Snapshot', data.progress);
buttons.addClass("disabled");
break;
case "download-b":
printStatusWithBar('Downloading Backup', data.progress);
buttons.addClass("disabled");
break;
case "upload":
printStatusWithBar('Uploading Snapshot', data.progress);
buttons.addClass("disabled");
break;
case "upload-b":
printStatusWithBar('Uploading Snapshot', data.progress);
buttons.addClass("disabled");
break;
case "creating":
printStatusWithBar('Creating Snapshot', data.progress);
buttons.addClass("disabled");
break;
}
if (data.last_backup != null) {
let last_back_status = $('#last_back_status');
if (last_back_status.html() !== data.last_backup)
last_back_status.html(data.last_backup);
}
if (data.next_backup != null) {
let next_back_status = $('#next_back_status');
if (next_back_status.html() !== data.next_backup)
next_back_status.html(data.next_backup);
}
}
});
}
function printStatus(status, secondLine) {
let status_jq = $('#status');
status_jq.empty();
status_jq.html(status);
let status_s_l_jq = $('#status-second-line');
status_s_l_jq.empty();
status_s_l_jq.removeClass('text-center');
status_s_l_jq.html(secondLine);
$('#progress').addClass("invisible");
}
function printStatusWithBar(status, progress) {
let status_jq = $('#status')
status_jq.empty();
status_jq.html(status);
let secondLine = $('#status-second-line')
secondLine.empty();
secondLine.html(progress === -1 ? "Waiting..." : (Math.round(progress * 100) + "%"));
secondLine.addClass("text-center");
let progressDiv = $('#progress');
progressDiv.removeClass("invisible");
if (progress === -1) {
progressDiv.children().css('width', "100%");
progressDiv.children().addClass('progress-bar-striped progress-bar-animated');
} else {
progressDiv.children().removeClass('progress-bar-striped progress-bar-animated');
progressDiv.children().css('width', (progress * 100) + "%");
}
}
function listeners() {
$('#btn-backup-now').click(backupNow);
$('#btn-clean-now').click(cleanNow);
$('#trigger-backup-settings').click(getBackupSettings);
$('#cron-drop-settings').change(updateDropVisibility);
$('#save-backup-settings').click(sendBackupSettings);
$('#trigger-nextcloud-settings').click(getNextcloudSettings);
$('#save-nextcloud-settings').click(sendNextcloudSettings);
$('#ssl').change(function () {
let div = $('#self_signed').parent().parent();
if ($('#ssl').is(':checked'))
div.removeClass("invisible")
else
div.addClass("invisible");
});
}
function restore(id) {
loadingModal.show();
$.post('./api/restore', { path: id })
.done(() => {
console.log("Restore cmd send !");
create_toast("success", "Command send !", default_toast_timeout);
})
.fail((error) => {
console.log(error);
create_toast("error", "Can't send command !", default_toast_timeout);
})
.always(() => loadingModal.hide())
}
function sendNextcloudSettings() {
loadingModal.show();
nextcloud_setting_modal.hide();
let ssl = $('#ssl').is(':checked')
let self_signed = $('#self_signed').is(':checked')
let hostname = $('#hostname').val();
let username = $('#username').val();
let password = $('#password').val();
let back_dir = $('#back-dir').val();
$.post('./api/nextcloud-settings', {
ssl: ssl,
host: hostname,
username: username,
password: password,
back_dir: back_dir,
self_signed: self_signed
})
.done(() => {
console.log('Saved');
$('#nextcloud_settings_message').parent().addClass("d-none");
create_toast("success", "Nextcloud settings saved !", default_toast_timeout);
0
})
.fail((data) => {
let nextcloud_settings_message = $('#nextcloud_settings_message')
if (data.status === 406) {
console.log(data.responseJSON.message);
nextcloud_settings_message.html(data.responseJSON.message);
} else {
nextcloud_settings_message.html("Invalid Settings.");
}
nextcloud_settings_message.parent().removeClass("d-none");
nextcloud_setting_modal.show();
create_toast("error", "Invalid Nextcloud settings !", default_toast_timeout);
console.log('Fail');
}).always(() => {
loadingModal.hide();
})
}
function manualBackup(id, name) {
$.post(`./api/manual-backup?id=${id}&name=${name}`)
.done(() => {
console.log("manual bk cmd send !");
create_toast("success", "Command send !", default_toast_timeout);
})
.fail((error) => {
console.log(error);
create_toast("error", "Can't send command !", default_toast_timeout);
})
}
function getNextcloudSettings() {
loadingModal.show();
$.get('./api/nextcloud-settings', (data) => {
$('#ssl').prop("checked", data.ssl === "true");
let sef_signed_jq = $('#self_signed')
if (data.ssl === "true") {
let div = sef_signed_jq.parent().parent();
div.removeClass("invisible");
}
sef_signed_jq.prop('checked', data.self_signed === "true")
$('#hostname').val(data.host);
$('#username').val(data.username);
$('#password').val(data.password);
$('#back-dir').val(data.back_dir);
loadingModal.hide();
nextcloud_setting_modal.show();
});
}
function backupNow() {
loadingModal.show();
$.post('./api/new-backup')
.done(() => {
create_toast("success", "Command send !", default_toast_timeout);
})
.fail((error) => {
console.log(error);
create_toast("error", "Can't send command !", default_toast_timeout);
})
.always(() => {
loadingModal.hide();
})
}
function cleanNow() {
loadingModal.show();
$.post('./api/clean-now')
.done(() => {
create_toast("success", "Command send !", default_toast_timeout);
})
.fail((error) => {
console.log(error);
create_toast("error", "Can't send command !", default_toast_timeout);
})
.always(() => {
loadingModal.hide();
})
}
function getBackupSettings() {
loadingModal.show();
$.get('./api/backup-settings', (data) => {
if (JSON.stringify(data) === "{}") {
data = {
cron_base: "0",
cron_hour: "00:00",
cron_weekday: "0",
cron_month_day: "1",
auto_clean_local: false,
auto_clean_local_keep: 5,
auto_clean_backup: false,
auto_clean_backup_keep: 5,
}
}
$('#cron-drop-settings').val(data.settings.cron_base);
$('#name-template').val(data.settings.name_template);
$('#timepicker').val(data.settings.cron_hour);
$('#cron-drop-day-month-read').val(data.settings.cron_month_day);
$('#cron-drop-day-month').val(data.settings.cron_month_day);
$('#auto_clean_local').prop('checked', data.settings.auto_clean_local === "true");
$('#local-snap-keep').val(data.settings.auto_clean_local_keep);
$('#auto_clean_backup').prop('checked', data.settings.auto_clean_backup === "true");
$('#backup-snap-keep').val(data.settings.auto_clean_backup_keep);
$('#cron-drop-day').val(data.settings.cron_weekday);
let folder_html = ""
for (let thisFolder of data.folders) {
let exclude = data.settings.exclude_folder.includes(thisFolder.slug);
folder_html += `<li class="list-group-item"><div class="form-check"><input class="form-check-input folders-box" type="checkbox" id="${thisFolder.slug}" ${exclude ? "" : "checked"}><label class="form-label mb-0" for="${thisFolder.slug}">${thisFolder.name}</label></div></li>`
}
$("#folders-div").html(folder_html);
let addons_html = ""
for (let thisAddon of data.addonList) {
let exclude = data.settings.exclude_addon.includes(thisAddon.slug);
addons_html += `<li class="list-group-item"><div class="form-check"><input class="form-check-input addons-box" type="checkbox" id="${thisAddon.slug}" ${exclude ? "" : "checked"}><label class="form-label mb-0" for="${thisAddon.slug}">${thisAddon.name}</label></div></li>`
}
$("#addons-div").html(addons_html);
updateDropVisibility();
loadingModal.hide();
backup_setting_modal.show();
});
}
function updateDropVisibility() {
let cronBase = $("#cron-drop-settings").val();
let timepicker = $('#timepicker');
let cron_drop_day = $('#cron-drop-day');
let cron_drop_day_mount = $('#cron-drop-day-month');
switch (cronBase) {
case "3":
timepicker.parent().parent().removeClass("d-none");
cron_drop_day.parent().parent().addClass("d-none");
cron_drop_day_mount.parent().parent().removeClass("d-none");
break;
case "2":
timepicker.parent().parent().removeClass("d-none");
cron_drop_day.parent().parent().removeClass("d-none");
cron_drop_day_mount.parent().parent().addClass("d-none");
break;
case "1":
timepicker.parent().parent().removeClass("d-none");
cron_drop_day.parent().parent().addClass("d-none");
cron_drop_day_mount.parent().parent().addClass("d-none");
break;
case "0":
timepicker.parent().parent().addClass("d-none");
cron_drop_day.parent().parent().addClass("d-none");
cron_drop_day_mount.parent().parent().addClass("d-none");
break;
}
}
function sendBackupSettings() {
let cron_month_day = $('#cron-drop-day-month').val();
let cron_weekday = $('#cron-drop-day').val();
let cron_hour = $('#timepicker').val();
let cron_base = $('#cron-drop-settings').val();
let auto_clean_local = $("#auto_clean_local").is(':checked');
let auto_clean_backup = $("#auto_clean_backup").is(':checked');
let auto_clean_local_keep = $("#local-snap-keep").val();
let auto_clean_backup_keep = $("#backup-snap-keep").val();
let name_template = $('#name-template').val();
let excluded_folders_nodes = document.querySelectorAll('.folders-box:not(:checked)');
let exclude_folder = [""];
for (let i of excluded_folders_nodes) {
exclude_folder.push(i.id);
}
let excluded_addons_nodes = document.querySelectorAll('.addons-box:not(:checked)');
let exclude_addon = [""];
for (let i of excluded_addons_nodes) {
exclude_addon.push(i.id);
}
loadingModal.show();
backup_setting_modal.hide();
$.post('./api/backup-settings',
{
name_template: name_template,
cron_base: cron_base,
cron_hour: cron_hour,
cron_weekday: cron_weekday,
cron_month_day: cron_month_day,
auto_clean_local: auto_clean_local,
auto_clean_local_keep: auto_clean_local_keep,
auto_clean_backup: auto_clean_backup,
auto_clean_backup_keep: auto_clean_backup_keep,
exclude_addon: exclude_addon,
exclude_folder: exclude_folder
})
.done(() => {
create_toast("success", "Backup settings saved !", default_toast_timeout);
})
.fail(() => {
create_toast("error", "Can't save backup settings !", default_toast_timeout);
backup_setting_modal.show();
})
.always(() => {
loadingModal.hide();
});
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,30 @@
function create_toast(type, message, delay) {
let toast_class;
let icon_class;
switch (type) {
case 'error':
toast_class = 'bg-danger';
icon_class = 'fa-exclamation-triangle'
break;
default:
toast_class = `bg-${type}`
icon_class = 'fa-check-square'
}
let toast_id = Date.now().toString();
let toast_html = `<div id="${toast_id}" class="toast d-flex align-items-center text-white ${toast_class}" role="alert" aria-live="assertive" aria-atomic="true">`
toast_html += `<div class="toast-body h6 mb-0 align-middle"><i class="fas ${icon_class} me-2 h5 mb-0"></i><span>${message}</span></div>`
toast_html += `<button type="button" class="btn-close btn-close-white ms-auto me-2" data-bs-dismiss="toast" aria-label="Close"></button></div>`
$('#toast-container').prepend(toast_html);
let toast_dom = document.getElementById(toast_id)
let toast = new bootstrap.Toast(toast_dom, {
animation: true,
autohide: delay !== -1,
delay: delay
});
toast_dom.addEventListener('hidden.bs.toast', function () {
this.remove();
});
toast.show();
return toast;
}

View File

@ -30,6 +30,7 @@ router.get("/formated-local-snap", function (req, res, next) {
return -1;
}
});
res.render("localSnaps", { snaps: snaps, moment: moment });
},
(err) => {
@ -48,6 +49,10 @@ router.get("/formated-local-snap", function (req, res, next) {
if (moment(a.lastmod).isBefore(moment(b.lastmod))) return 1;
else return -1;
});
//TODO Remove this when bug is fixed, etag contain '&quot;' at start and end ?
for(let backup of contents){
backup.etag = backup.etag.replace(/&quot;/g, '');
}
res.render("backupSnaps", { backups: contents, moment: moment, humanFileSize: humanFileSize });
})
.catch(() => {
@ -64,6 +69,10 @@ router.get("/formated-local-snap", function (req, res, next) {
if (moment(a.lastmod).isBefore(moment(b.lastmod))) return 1;
else return -1;
});
//TODO Remove this when bug is fixed, etag contain '&quot;' at start and end ?
for(let backup of contents){
backup.etag = backup.etag.replace(/&quot;/g, '');
}
res.render("backupSnaps", { backups: contents, moment: moment, humanFileSize: humanFileSize });
})
.catch(() => {

View File

@ -0,0 +1,52 @@
$body-bg: #222222;
$dark: #292929;
$secondary: #343a40;
$accent: #b58e51;
$enable-shadows: true;
$btn-box-shadow: none;
$component-active-bg: $accent;
$input-color: $accent;
$input-bg: $secondary;
$input-border-color: $secondary;
$form-select-indicator-color: $accent;
$form-switch-color: $accent;
$form-switch-bg-image: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'><circle r='3' fill='#{$form-switch-color}'/></svg>");
$form-check-input-border: 1px solid $accent !default;
$list-group-action-color: $accent;
$list-group-bg: $secondary;
$list-group-hover-bg: $secondary;
$list-group-action-hover-color: #adb5bd;
$alert-bg-scale: 0%;
$alert-border-scale: -10%;
$alert-color-scale: -100%;
@import "../node_modules/bootstrap/scss/bootstrap";
.bg-accent {
background-color: $accent;
}
.text-accent {
color: $accent;
}
input[type="time"]::-webkit-calendar-picker-indicator {
filter: invert(63%) sepia(23%) saturate(819%) hue-rotate(358deg) brightness(88%) contrast(91%);
height: 30px;
margin: -12px;
width: 32px;
}
input[type="time"]::-webkit-calendar-picker-indicator:hover {
cursor: pointer;
}

View File

@ -1,54 +1,66 @@
<% if (locals.backups) { %>
<div class="collection">
<div class="list-group ">
<% for(const index in backups) { %>
<a class="collection-item modal-trigger" href="#modal-<%=backups[index].etag%>"
data-id="<%= backups[index].etag %>">
<div><%= backups[index].basename%><div class="secondary-content hide-on-small-and-down">
<%= moment(backups[index].lastmod).format('MMM D, YYYY HH:mm') %></div>
</div>
<a class="list-group-item list-group-item-action d-flex justify-content-between align-items-center"
href="#"
data-id="<%= backups[index].etag %>"
data-bs-toggle="modal"
data-bs-target="#modal-<%= backups[index].etag %>">
<%= backups[index].basename %>
<span class="badge bg-primary">
<%= moment(backups[index].lastmod).format('MMM D, YYYY HH:mm') %>
</span>
</a>
<div id="modal-<%=backups[index].etag%>" class="modal modal-fixed-footer blue-grey darken-4 white-text">
<div class="modal-content">
<div class="row">
<div class="col s12 center">
<h4>Backup Detail</h2>
<div id="modal-<%= backups[index].etag %>" class="modal fade">
<div class="modal-dialog modal-lg">
<div class="modal-content bg-dark">
<div class="modal-header border-secondary">
<h5 class="modal-title" id="exampleModalLabel"> Backup Detail</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"
aria-label="Close"></button>
</div>
</div>
<div class="row">
<div class="col s12 center divider">
</div>
</div>
<div class="row">
<div class="modal-body">
<div class="input-field col s12">
<input disabled type="text" id="name-<%=backups[index].etag%>" value="<%= backups[index].basename %>" />
<label for="name-<%=backups[index].etag%>" class="white-text active">Name</label>
<div class="mb-3">
<label for="name-<%= backups[index].etag %>" class="form-label">Name</label>
<input disabled type="text" class="form-control bg-secondary border-dark text-accent"
id="name-<%= backups[index].etag %>"
value="<%= backups[index].basename %>"/>
</div>
<div class="input-field col s12">
<input disabled type="text" id="date-<%=backups[index].etag%>"
<div class="mb-3">
<label for="date-<%= backups[index].etag %>" class="form-label">Date</label>
<input disabled type="text" class="form-control bg-secondary border-dark text-accent"
id="date-<%= backups[index].etag %>"
value="<%= moment(backups[index].lastmod).format('MMM D, YYYY HH:mm') %>"/>
<label for="date-<%=backups[index].etag%>" class="white-text active">Date</label>
</div>
<div class="input-field col s12">
<input disabled type="text" id="size-<%=backups[index].etag%>"
<label for="size-<%= backups[index].etag %>" class="form-label">Size</label>
<input disabled type="text" class="form-control bg-secondary border-dark text-accent"
id="size-<%= backups[index].etag %>"
value="<%= humanFileSize(backups[index].size, false) %>"/>
<label for="size-<%=backups[index].etag%>" class="white-text active">Size</label>
</div>
</div>
<div class="modal-footer border-secondary">
<button data-bs-dismiss="modal" class="btn btn-danger">Close</button>
<button class="btn btn-success restore"
data-id="<%= backups[index].filename %>"
data-name='<%= backups[index].basename ? backups[index].basename : backups[index].etag %>'
data-bs-dismiss="modal">
Upload to HA
</button>
</div>
<div class="modal-footer blue-grey darken-4">
<a href="#!" class="waves-effect waves-green btn green restore modal-close" data-id="<%=backups[index].filename%>" data-name='<%= backups[index].basename ? backups[index].basename : backups[index].etag %>'>Upload to HA</a>
<a href="#!" class="modal-close waves-effect waves-green btn red">Close</a>
</div>
</div>
</div>
<% } %>
</div>
<% } %>

View File

@ -8,144 +8,80 @@
<title>Nextcloud Backup</title>
<!-- <link rel='stylesheet' href='/stylesheets/style.css' /> -->
<link rel="stylesheet" href="./css/materialize.min.css">
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
<style>
.modal input[disabled] {
color: #757575 !important;
border-color: #616161 !important;
}
.modal .input-field span.helper-text {
color: #9e9e9e;
}
.modal div.row:last-child {
margin-bottom: 0;
}
.modal div.col:last-child {
margin-bottom: 0;
}
@media (min-width: 601px) {
.header-box {
height: 150px;
}
}
.header-box .col {
height: 100%;
}
.header-box .col .card {
height: 100%;
}
.header-box .card-content {
padding-top: 10px;
}
.header-box .card-content h5 {
margin-top: 10px;
}
ul.dropdown-content a:hover {
background-color: #101619 !important;
}
ul.dropdown-content li:hover {
background-color: #101619 !important;
}
/* change autocomplete color */
input:-webkit-autofill,
input:-webkit-autofill:hover,
input:-webkit-autofill:focus,
input:-webkit-autofill:active {
-webkit-box-shadow: 0 0 0 30px #263238 inset !important;
-webkit-text-fill-color: white !important;
}
.select-dropdown {
color: white;
}
</style>
<link rel='stylesheet' href='./css/style.css'/>
<link rel="stylesheet" href="./css/custom_bootstrap.css">
<link rel="stylesheet" href="./css/fa-all.min.css">
</head>
<body class="blue-grey darken-4">
<nav class=" light-blue accent-4">
<div class="nav-wrapper container">
<a href="#" class="brand-logo"><img src="./images/Nextcloud_Logo.svg" height="54"
style="margin: 5px">
<body class="">
<nav class="navbar navbar-dark navbar-expand">
<div class="container">
<a href="#" class="navbar-brand p-0">
<img src="./images/Nextcloud_Logo.svg" height="40">
<span class="align-middle d-none d-sm-inline">Nextcloud Backup</span>
</a>
<ul class="right">
<li id="setting-trigger">
<a class="dropdown-trigger" href="#" data-target="dropdown-settings">
<i class="material-icons">settings</i>
<ul class="navbar-nav">
<li class="nav-item dropdown" id="setting-trigger">
<a class="btn btn-outline-light bg-transparent nav-link px-2 text-white" href="#" id="dropdown-settings"
role="button"
data-bs-toggle="dropdown" aria-expanded="false">
<i class="fas fa-cogs"></i>
</a>
<ul class="dropdown-menu dropdown-menu-dark dropdown-menu-end" aria-labelledby="dropdown-settings">
<li><a href="#" id="trigger-nextcloud-settings"
class="modal-trigger dropdown-item">Nextcloud</a>
</li>
<li><a href="#" id="trigger-backup-settings" class="dropdown-item">Backup</a></li>
</ul>
</li>
</ul>
<div style="height: 64px; display: table; margin-left: 130px;" class="hide-on-med-and-down">
<h4 style="display: table-cell; vertical-align: middle;">Nextcloud Backup</h4>
</div>
</div>
</nav>
<ul id="dropdown-settings" class="dropdown-content blue-grey darken-4">
<li><a href="#modal-settings-nextcloud" id="trigger-nextcloud-settings" class="modal-trigger center">Nextcloud</a>
</li>
<li><a href="#" id="trigger-backup-settings" class="center">Backup</a></li>
</ul>
<div class="container">
<div class="row header-box">
<div class="col s12 m3">
<div class="card cyan darken-3">
<div class="card-content">
<span class="card-title white-text" style="font-weight: bold;">Status </span>
<div class="divider"></div>
<div class="row mt-3 mb-3" id="header-box">
<div class="col-12 col-md-3 mb-3 mb-md-0">
<div class="card text-white bg-dark h-100 shadow-sm border-secondary">
<div class="card-header fw-bold h5 border-secondary border-bottom">Status</div>
<div class="card-body">
<h6 id="status" class="white-text"></h6>
<div id="status-second-line" class="truncate tooltipped" data-position="bottom" data-tooltip=""></div>
<div class="progress hide" id="progress">
<div class="determinate light-green darken-2" style="width: 0%"></div>
<div id="status-second-line" class="truncate mb-2 text-accent"></div>
<div class="progress bg-secondary invisible" id="progress">
<div class="progress-bar bg-success" role="progressbar" style="width: 15%" aria-valuemin="0"
aria-valuemax="100"></div>
</div>
</div>
</div>
</div>
<div class="col s12 m3">
<div class="card cyan darken-3">
<div class="card-content">
<span class="card-title white-text" style="font-weight: bold;">Last Backup</span>
<div class="divider"></div>
<div class="col-12 col-md-3 mb-3 mb-md-0">
<div class="card text-white bg-dark h-100 shadow-sm border-secondary">
<div class="card-header fw-bold h5 border-secondary border-bottom">Last Backup</div>
<div class="card-body">
<h6 class="white-text" id="last_back_status"></h6>
</div>
</div>
</div>
<div class="col s12 m3">
<div class="card cyan darken-3 ">
<div class="card-content">
<span class="card-title white-text" style="font-weight: bold;">Next Backup </span>
<div class="divider"></div>
<div class="col-12 col-md-3 mb-3 mb-md-0">
<div class="card text-white bg-dark h-100 shadow-sm border-secondary">
<div class="card-header fw-bold h5 border-secondary border-bottom">Next Backup</div>
<div class="card-body">
<h6 class="white-text" id="next_back_status"></h6>
</div>
</div>
</div>
<div class="col s12 m3">
<div class="card cyan darken-3">
<div class="card-content">
<span class="card-title white-text" style="font-weight: bold;">Manual </span>
<div class="divider"></div>
<div style="width: 100%;" class="center">
<a class="btn green center waves-effect waves-light" id="btn-backup-now"
style="margin-top: 7px; display: block;">Backup
Now</a>
<a class="btn center teal darken-4 waves-effect waves-light" id="btn-clean-now"
style="margin-top: 7px; display: block;">Clean Now</a>
<div class="col-12 col-md-3">
<div class="card text-white bg-dark h-100 shadow-sm border-secondary">
<div class="card-header fw-bold h5 border-secondary border-bottom">Manual</div>
<div class="card-body">
<div class="w-100">
<a class="btn btn-success d-block mb-2" id="btn-backup-now">
Backup Now
</a>
<a class="btn btn-danger d-block" id="btn-clean-now">
Clean Now
</a>
</div>
</div>
@ -155,23 +91,22 @@
</div>
<div class="row">
<div class="col s12 m12 l6 ">
<div class="card cyan darken-3">
<div class="card-content">
<span class="card-title center white-text">Local Snapshots</span>
<div class="col-12 col-md-12 col-lg-6 mb-3 mb-lg-0">
<div class="card text-white bg-dark h-100 shadow-sm border-secondary">
<div class="card-header fw-bold h4 text-center border-secondary border-bottom">Local Snapshots</div>
<div class="card-body">
<div id="local_snaps"></div>
</div>
</div>
</div>
<div class="col s12 m12 l6 ">
<div class="card cyan darken-3">
<div class="card-content">
<span class="card-title center white-text">Snapshots in Nextcloud</span>
<span class="card-title center white-text">Auto</span>
<span class="card-title center white-text divider"></span>
<div class="col-12 col-md-12 col-lg-6 ">
<div class="card text-white bg-dark h-100 shadow-sm border-secondary">
<div class="card-header fw-bold h4 text-center border-secondary border-bottom">Snapshots in Nextcloud
</div>
<div class="card-body">
<h5 class="card-title text-center fw-bold border-bottom border-secondary pb-2">Auto</h5>
<div id="auto_backups"></div>
<span class="card-title center white-text">Manual</span>
<span class="card-title center white-text divider"></span>
<h5 class="card-title text-center fw-bold border-bottom border-secondary mt-3 pb-2">Manual</h5>
<div id="manual_backups"></div>
</div>
</div>
@ -182,561 +117,19 @@
<%- include('modals/nextcloud-settings-modal') %>
<%- include('modals/backup-settings-modal') %>
<!-- <%- include('modals/restore-modal.ejs') %> -->
<div id="modal-loading" class="modal blue-grey darken-4 white-text">
<div class="modal-content ">
<div class="row">
<div class="col s12 center">
<h4>Loading</h4>
</div>
</div>
<div class="row valign-wrapper" style="height: 150px;">
<div class="col s12 center">
<div class="preloader-wrapper big active">
<div class="spinner-layer spinner-blue">
<div class="circle-clipper left">
<div class="circle"></div>
</div>
<div class="gap-patch">
<div class="circle"></div>
</div>
<div class="circle-clipper right">
<div class="circle"></div>
</div>
</div>
<div class="spinner-layer spinner-red">
<div class="circle-clipper left">
<div class="circle"></div>
</div>
<div class="gap-patch">
<div class="circle"></div>
</div>
<div class="circle-clipper right">
<div class="circle"></div>
</div>
</div>
<div class="spinner-layer spinner-yellow">
<div class="circle-clipper left">
<div class="circle"></div>
</div>
<div class="gap-patch">
<div class="circle"></div>
</div>
<div class="circle-clipper right">
<div class="circle"></div>
</div>
</div>
<div class="spinner-layer spinner-green">
<div class="circle-clipper left">
<div class="circle"></div>
</div>
<div class="gap-patch">
<div class="circle"></div>
</div>
<div class="circle-clipper right">
<div class="circle"></div>
</div>
</div>
</div>
</div>
</div>
</div>
<%- include('modals/loading-modal') %>
<div id="toast-container" class="toast-container" aria-live="polite" aria-atomic="true">
</div>
</body>
<script src="./js/materialize.min.js"></script>
<script src="./js/jquery-3.4.1.min.js"></script>
<script src="./js/bootstrap.min.js"></script>
<script src="./js/jquery.min.js"></script>
<script src="./js/index.js"></script>
<script src="./js/toast.js"></script>
<script>
var last_status = "";
var last_local_snap = "";
var last_manu_back = "";
var last_auto_back = "";
var loadingModal = null;
document.addEventListener('DOMContentLoaded', function() {
$.ajaxSetup({traditional: true});
updateLocalSnaps();
update_status();
let tooltips = document.querySelectorAll('.tooltipped');
M.Tooltip.init(tooltips, {});
let drops = document.querySelectorAll('.dropdown-trigger');
M.Dropdown.init(drops, { constrainWidth: false, coverTrigger: false, alignment: 'right', onOpenStart: () => $('#setting-trigger').addClass('active'), onCloseEnd: () => $('#setting-trigger').removeClass('active') });
setInterval(update_status, 500);
setInterval(updateLocalSnaps, 5000);
listeners();
});
function updateDynamicListeners() {
var elems = document.querySelectorAll('.collapsible');
M.Collapsible.init(elems, { accordion: true });
var modals = document.querySelectorAll('.modal:not(#modal-loading)');
M.Modal.init(modals, { dismissible: true });
let loadingModals = document.querySelectorAll('#modal-loading');
M.Modal.init(loadingModals, { dismissible: false });
loadingModal = M.Modal.getInstance(document.querySelector('#modal-loading'));
$('.local-snap-listener').click(function() {
let id = this.getAttribute('data-id');
console.log(id);
});
$('.manual-back-list').unbind();
$('.manual-back-list').click(function() {
let id = this.getAttribute('data-id');
let name = this.getAttribute('data-name');
manualBackup(id, name);
})
$('.restore').click(function(){
let to_restore = this.getAttribute('data-id');
console.log(to_restore)
restore(to_restore)
})
}
function updateLocalSnaps() {
let needUpdate = false;
$.get('./api/formated-local-snap', (data) => {
if (JSON.stringify(data) === last_local_snap)
return;
last_local_snap = JSON.stringify(data);
needUpdate = true;
$('#local_snaps').empty();
$('#local_snaps').html(data);
}).always(() => {
updateManuBackup(needUpdate);
});
}
function updateManuBackup(prevUpdate) {
let needUpdate = false;
$.get('./api/formated-backup-manual', (data) => {
if (JSON.stringify(data) === last_manu_back)
return;
last_manu_back = JSON.stringify(data);
needUpdate = true;
$('#manual_backups').empty();
$('#manual_backups').html(data);
}).always(() => {
updateAutoBackup(prevUpdate || needUpdate);
});
}
function updateAutoBackup(prevUpdate) {
let needUpdate = false;
$.get('./api/formated-backup-auto', (data) => {
if (JSON.stringify(data) === last_auto_back)
return;
needUpdate = true;
last_auto_back = JSON.stringify(data);
$('#auto_backups').empty();
$('#auto_backups').html(data);
}).always(() => {
if (prevUpdate || needUpdate)
updateDynamicListeners();
});
}
function update_status() {
$.get('./api/status', (data) => {
if (JSON.stringify(data) !== last_status) {
last_status = JSON.stringify(data);
switch (data.status) {
case "error":
printStatus('Error', data.message);
$('#btn-backup-now, #btn-clean-now').removeClass("disabled");
break;
case "idle":
printStatus('Idle', "Waiting for next backup.");
$('#btn-backup-now, #btn-clean-now').removeClass("disabled");
break;
case "download":
printStatusWithBar('Downloading Snapshot', data.progress);
$('#btn-backup-now, #btn-clean-now').addClass("disabled");
break;
case "download-b":
printStatusWithBar('Downloading Backup', data.progress);
$('#btn-backup-now, #btn-clean-now').addClass("disabled");
break;
case "upload":
printStatusWithBar('Uploading Snapshot', data.progress);
$('#btn-backup-now, #btn-clean-now').addClass("disabled");
break;
case "upload-b":
printStatusWithBar('Uploading Snapshot', data.progress);
$('#btn-backup-now, #btn-clean-now').addClass("disabled");
break;
case "creating":
printStatusWithBar('Creating Snapshot', data.progress);
$('#btn-backup-now, #btn-clean-now').addClass("disabled");
break;
}
if (data.last_backup != null) {
if ($('#last_back_status').html() != data.last_backup)
$('#last_back_status').html(data.last_backup);
}
if (data.next_backup != null) {
if ($('#next_back_status').html() != data.next_backup)
$('#next_back_status').html(data.next_backup);
}
}
});
}
function printStatus(status, secondLine) {
$('#status').empty();
$('#status').html(status);
$('#status-second-line').empty();
$('#status-second-line').removeClass('center');
$('#status-second-line').html(secondLine);
$('#status-second-line').attr('data-tooltip', secondLine)
$('#progress').addClass("hide");
}
function printStatusWithBar(status, progress) {
$('#status').empty();
$('#status').html(status);
let secondLine = $('#status-second-line')
secondLine.empty();
secondLine.html(progress == -1 ? "Waiting..." : (Math.round(progress * 100) + "%"));
secondLine.addClass("center");
secondLine.attr('data-tooltip', Math.round(progress * 100) + "%");
let progressDiv = $('#progress');
progressDiv.removeClass("hide");
if (progress == -1) {
progressDiv.children().removeClass('determinate');
progressDiv.children().addClass('indeterminate');
}
else {
progressDiv.children().removeClass('indeterminate');
progressDiv.children().addClass('determinate');
progressDiv.children().css('width', (progress * 100) + "%");
}
}
function listeners() {
$('#save-nextcloud-settings').click(sendNextcloudSettings);
$('#trigger-nextcloud-settings').click(getNextcloudSettings);
$('#trigger-backup-settings').click(getBackupSettings);
$('#btn-backup-now').click(backupNow);
$('#btn-clean-now').click(cleanNow);
$('#save-backup-settings').click(sendBackupSettings);
$('#cron-drop-day-month').on('input', function() {
$('#cron-drop-day-month-read').val($(this).val());
});
$('#local-snap-keep').on('input', function() {
$('#local-snap-keep-read').val($(this).val());
});
$('#backup-snap-keep').on('input', function() {
$('#backup-snap-keep-read').val($(this).val());
});
$('#ssl').change(function(){
let div = $('#self_signed').parent().parent().parent();
if($('#ssl').is(':checked'))
div.removeClass("hide")
else
div.addClass("hide");
});
$('#confirm-restore').click(function(){
restore(to_restore);
});
}
function restore(id){
loadingModal.open();
$.post('./api/restore', {path: id})
.done((data)=>{
console.log("Restore cmd send !");
M.toast({ html: '<i class="material-icons" style="margin-right:10px">check_box</i> Command send !', classes: "green" });
}).fail((error) => {
console.log(error);
M.toast({ html: '<i class="material-icons" style="margin-right:10px">warning</i> Can\'t send command !', classes: "red" });
})
.always(()=> loadingModal.close())
}
function sendNextcloudSettings() {
loadingModal.open();
let ssl = $('#ssl').is(':checked')
let self_signed = $('#self_signed').is(':checked')
let hostname = $('#hostname').val();
let username = $('#username').val();
let password = $('#password').val();
let back_dir = $('#back-dir').val();
$.post('./api/nextcloud-settings', { ssl: ssl, host: hostname, username: username, password: password, back_dir: back_dir, self_signed: self_signed })
.done((data) => {
console.log('Saved');
$('#nextcloud_settings_message').parent().addClass("hide");
M.toast({ html: '<i class="material-icons" style="margin-right:10px">check_box</i> Nextcloud settings saved !', classes: "green" });
M.Modal.getInstance(document.querySelector('#modal-settings-nextcloud')).close();
}).fail((data) => {
debugger;
if (data.status == 406) {
console.log(data.responseJSON.message);
$('#nextcloud_settings_message').html(data.responseJSON.message);
}
else {
$('#nextcloud_settings_message').html("Invalid Settings.");
}
$('#nextcloud_settings_message').parent().removeClass("hide");
M.toast({ html: '<i class="material-icons" style="margin-right:10px">warning</i> Invalid Nextcloud settings !', classes: "red" });
console.log('Fail');
}).always(() => {
loadingModal.close();
})
}
function manualBackup(id, name) {
$.post('./api/manual-backup?id=' + id + '&name=' + name)
.done((data)=>{
console.log("manual bk cmd send !");
M.toast({ html: '<i class="material-icons" style="margin-right:10px">check_box</i> Command send !', classes: "green" });
}).fail((error) => {
console.log(error);
M.toast({ html: '<i class="material-icons" style="margin-right:10px">warning</i> Can\'t send command !', classes: "red" });
})
}
function getNextcloudSettings() {
loadingModal.open();
$.get('./api/nextcloud-settings', (data) => {
$('#ssl').prop("checked", data.ssl == "true");
if(data.ssl == "true"){
let div = $('#self_signed').parent().parent().parent();
div.removeClass("hide");
}
$('#self_signed').prop('checked', data.self_signed == "true")
$('#hostname').val(data.host);
$('#hostname + label').removeClass("active");
$('#hostname + label').addClass("active");
$('#username').val(data.username);
$('#username + label').removeClass("active");
$('#username + label').addClass("active");
$('#password').val(data.password);
$('#password + label').removeClass("active");
$('#password + label').addClass("active");
$('#back-dir').val(data.back_dir);
$('#back-dir + label').removeClass("active");
$('#back-dir + label').addClass("active");
loadingModal.close();
});
}
function backupNow() {
loadingModal.open();
$.post('./api/new-backup')
.done(() => {
M.toast({ html: '<i class="material-icons" style="margin-right:10px">check_box</i> Command send !', classes: "green" });
}).fail((error) => {
console.log(error);
M.toast({ html: '<i class="material-icons" style="margin-right:10px">warning</i> Can\'t send command !', classes: "red" });
})
.always(() => {
loadingModal.close();
})
}
function cleanNow() {
loadingModal.open();
$.post('./api/clean-now')
.done(() => {
M.toast({ html: '<i class="material-icons" style="margin-right:10px">check_box</i> Command send !', classes: "green" });
}).fail((error) => {
console.log(error);
M.toast({ html: '<i class="material-icons" style="margin-right:10px">warning</i> Can\'t send command !', classes: "red" });
})
.always(() => {
loadingModal.close();
})
}
function getBackupSettings() {
loadingModal.open();
$.get('./api/backup-settings', (data) => {
if (JSON.stringify(data) == "{}") {
data = {
cron_base: "0",
cron_hour: "00:00",
cron_weekday: "0",
cron_month_day: "1",
auto_clean_local: false,
auto_clean_local_keep: 5,
auto_clean_backup: false,
auto_clean_backup_keep: 5,
}
}
changeSelect('#cron-drop-settings', data.settings.cron_base);
$('#cron-drop-settings').change(updateDropVisibility);
$('#name-template').val(data.settings.name_template);
$('#name-template + label').removeClass("active");
$('#name-template + label').addClass("active");
let timepicker = document.querySelector('#timepicker');
$('#timepicker').val(data.settings.cron_hour);
$('#timepicker + label').removeClass("active");
$('#timepicker + label').addClass("active");
if (M.Timepicker.getInstance(timepicker) != null)
M.Timepicker.getInstance(timepicker).destroy();
M.Timepicker.init(timepicker, { defaultTime: data.settings.cron_hour, twelveHour: false, container: 'body' });
$('#cron-drop-day-month-read').val(data.settings.cron_month_day);
$('#cron-drop-day-month').val(data.settings.cron_month_day);
$('#cron-drop-day-month-read + label').removeClass("active");
$('#cron-drop-day-month-read + label').addClass("active");
$('#auto_clean_local').prop('checked', data.settings.auto_clean_local == "true");
$('#local-snap-keep').val(data.settings.auto_clean_local_keep);
$('#auto_clean_backup').prop('checked', data.settings.auto_clean_backup == "true");
$('#backup-snap-keep').val(data.settings.auto_clean_backup_keep);
$('#backup-snap-keep + label').removeClass("active");
$('#backup-snap-keep + label').addClass("active");
$('#local-snap-keep + label').removeClass("active");
$('#local-snap-keep + label').addClass("active");
changeSelect('#cron-drop-day', data.settings.cron_weekday);
let folder_html = ""
for(let index in data.folders){
let thisFolder = data.folders[index];
let exclude = data.settings.exclude_folder.includes(thisFolder.slug);
folder_html += `<li><label><input type="checkbox" class="folders-box" id="${thisFolder.slug}" ${exclude ? "" : "checked=checked"}/><span>${thisFolder.name}</span></label></li>`
}
$("#folders-div").html(folder_html);
let addons_html = ""
for(let index in data.addonList){
let thisAddon = data.addonList[index];
let exclude = data.settings.exclude_addon.includes(thisAddon.slug);
addons_html += `<li><label><input type="checkbox" class="addons-box" id="${thisAddon.slug}" ${exclude ? "" : "checked=checked"}/><span>${thisAddon.name}</span></label></li>`
}
$("#addons-div").html(addons_html);
updateDropVisibility();
loadingModal.close();
M.Modal.getInstance(document.querySelector("#modal-settings-backup")).open()
});
}
function updateDropVisibility() {
let cronBase = $("#cron-drop-settings").val();
switch (cronBase) {
case "3":
$('#timepicker').parent().parent().removeClass("hide");
$('#cron-drop-day').parent().parent().parent().addClass("hide");
$('#cron-drop-day-month').parent().parent().parent().removeClass("hide");
break;
case "2":
$('#timepicker').parent().parent().removeClass("hide");
$('#cron-drop-day').parent().parent().parent().removeClass("hide");
$('#cron-drop-day-month').parent().parent().parent().addClass("hide");
break;
case "1":
$('#timepicker').parent().parent().removeClass("hide");
$('#cron-drop-day').parent().parent().parent().addClass("hide");
$('#cron-drop-day-month').parent().parent().parent().addClass("hide");
break;
case "0":
$('#timepicker').parent().parent().addClass("hide");
$('#cron-drop-day').parent().parent().parent().addClass("hide");
$('#cron-drop-day-month').parent().parent().parent().addClass("hide");
break;
}
}
function sendBackupSettings() {
let cron_month_day = $('#cron-drop-day-month').val();
let cron_weekday = $('#cron-drop-day').val();
let cron_hour = $('#timepicker').val();
let cron_base = $('#cron-drop-settings').val();
let auto_clean_local = $("#auto_clean_local").is(':checked');
let auto_clean_backup = $("#auto_clean_backup").is(':checked');
let auto_clean_local_keep = $("#local-snap-keep").val();
let auto_clean_backup_keep = $("#backup-snap-keep").val();
let name_template = $('#name-template').val();
let excluded_folders_nodes = document.querySelectorAll('.folders-box:not(:checked)');
let exclude_folder = [""];
for(let i of excluded_folders_nodes){
exclude_folder.push(i.id);
}
let excluded_addons_nodes = document.querySelectorAll('.addons-box:not(:checked)');
let exclude_addon = [""];
for(let i of excluded_addons_nodes){
exclude_addon.push(i.id);
}
loadingModal.open();
$.post('./api/backup-settings',
{
name_template: name_template,
cron_base: cron_base,
cron_hour: cron_hour,
cron_weekday: cron_weekday,
cron_month_day: cron_month_day,
auto_clean_local: auto_clean_local,
auto_clean_local_keep: auto_clean_local_keep,
auto_clean_backup: auto_clean_backup,
auto_clean_backup_keep: auto_clean_backup_keep,
exclude_addon: exclude_addon,
exclude_folder: exclude_folder
})
.done(() => {
M.toast({ html: '<i class="material-icons" style="margin-right:10px">check_box</i> Backup settings saved !', classes: "green" });
M.Modal.getInstance(document.querySelector('#modal-settings-backup')).close();
}).fail(() => {
M.toast({ html: '<i class="material-icons" style="margin-right:10px">warning</i> Can\'t save backup settings !', classes: "red" });
}).always(() => {
loadingModal.close();
});
}
function changeSelect(selector, value) {
let selectBaseRaw = document.querySelector(selector);
if (M.FormSelect.getInstance(selectBaseRaw) != null)
M.FormSelect.getInstance(selectBaseRaw).destroy();
$(selector + ' option[selected]').removeAttr('selected');
$(selector + ' option[value=' + value + ']').attr('selected', "true");
M.FormSelect.init(selectBaseRaw, {});
}
</script>
</html>

View File

@ -1,61 +1,71 @@
<% if (locals.snaps) { %>
<div class="collection">
<div class="list-group">
<% for(const index in snaps) { %>
<a class="collection-item local-snap-listener modal-trigger" href="#modal-<%=snaps[index].slug%>"
data-id="<%= snaps[index].slug %>">
<div><%= snaps[index].name ? snaps[index].name : snaps[index].slug %><div class="secondary-content hide-on-small-and-down">
<%= moment(snaps[index].date).format('MMM D, YYYY HH:mm') %></div>
</div>
<a class="list-group-item list-group-item-action d-flex justify-content-between align-items-center"
href="#"
data-id="<%= snaps[index].slug %>"
data-bs-toggle="modal"
data-bs-target="#modal-<%= snaps[index].slug %>">
<%= snaps[index].name ? snaps[index].name : snaps[index].slug %>
<span class="badge bg-primary">
<%= moment(snaps[index].date).format('MMM D, YYYY HH:mm') %>
</span>
</a>
<div id="modal-<%=snaps[index].slug%>" class="modal modal-fixed-footer blue-grey darken-4 white-text">
<div class="modal-content">
<div class="row">
<div class="col s12 center">
<h4>Snapshot Detail</h2>
<div id="modal-<%= snaps[index].slug %>" class="modal fade">
<div class="modal-dialog modal-lg">
<div class="modal-content bg-dark">
<div class="modal-header border-secondary">
<h5 class="modal-title" id="exampleModalLabel"> Snapshot Detail</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"
aria-label="Close"></button>
</div>
</div>
<div class="row">
<div class="col s12 center divider">
</div>
</div>
<div class="row">
<div class="modal-body">
<div class="mb-3">
<label for="name-<%= snaps[index].slug %>" class="form-label">Name</label>
<input disabled type="text" class="form-control bg-secondary border-dark text-accent" id="name-<%= snaps[index].slug %>"
value="<%= snaps[index].name ? snaps[index].name : snaps[index].slug %>"/>
<div class="input-field col s12">
<input disabled type="text" id="name-<%=snaps[index].slug%>" value="<%= snaps[index].name ? snaps[index].name : snaps[index].slug %>" />
<label for="name-<%=snaps[index].slug%>" class="white-text active">Name</label>
</div>
<div class="input-field col s12">
<input disabled type="text" id="date-<%=snaps[index].slug%>"
<div class="mb-3">
<label for="date-<%= snaps[index].slug %>" class="form-label">Date</label>
<input disabled type="text" class="form-control bg-secondary border-dark text-accent" id="date-<%= snaps[index].slug %>"
value="<%= moment(snaps[index].date).format('MMM D, YYYY HH:mm') %>"/>
<label for="date-<%=snaps[index].slug%>" class="white-text active">Date</label>
</div>
<div class="input-field col s12">
<input disabled type="text" id="protected-<%=snaps[index].slug%>"
<div class="mb-3">
<label for="protected-<%= snaps[index].slug %>"
class="form-label">Protected</label>
<input disabled type="text" class="form-control bg-secondary border-dark text-accent" id="protected-<%= snaps[index].slug %>"
value="<%= snaps[index].protected %>"/>
<label for="protected-<%=snaps[index].slug%>" class="white-text active">Protected</label>
</div>
<div class="input-field col s12">
<input disabled type="text" id="type-<%=snaps[index].slug%>" value="<%=snaps[index].type%>" />
<label for="type-<%=snaps[index].slug%>" class="white-text active">Type</label>
<div >
<label for="type-<%= snaps[index].slug %>" class="form-label">Type</label>
<input disabled type="text" class="form-control bg-secondary border-dark text-accent" id="type-<%= snaps[index].slug %>"
value="<%= snaps[index].type %>"/>
</div>
</div>
</div>
<div class="modal-footer blue-grey darken-4">
<a href="#!" class="waves-effect waves-green btn green manual-back-list modal-close" data-id="<%=snaps[index].slug%>" data-name='<%= snaps[index].name ? snaps[index].name : snaps[index].slug %>'</a>Backup now</a>
<a href="#!" class="modal-close waves-effect waves-green btn red">Close</a>
<div class="modal-footer border-secondary">
<button data-bs-dismiss="modal" class="btn btn-danger">Close</button>
<button class="btn btn-success restore"
data-id="<%= snaps[index].slug %>"
data-name='<%= snaps[index].name ? snaps[index].name : snaps[index].slug %>'
data-bs-dismiss="modal">
Backup now
</button>
</div>
</div>
</div>
</div>
<% } %>
</div>
<% } %>

View File

@ -1,44 +1,49 @@
<div id="modal-settings-backup" class="modal modal-fixed-footer blue-grey darken-4 white-text">
<div class="modal-content">
<div class="row" style="margin-bottom: 5px;">
<div class="col s12 center" >
<h4>Backup Settings</h2>
</div>
<div id="modal-settings-backup" class="modal fade">
<div class="modal-dialog modal-lg">
<div class="modal-content bg-dark text-white">
<div class="modal-header border-secondary">
<h5 class="modal-title" id="exampleModalLabel">Backup Settings</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"
aria-label="Close"></button>
</div>
<div class="modal-body">
<div class="row">
<div class="col s12 center divider">
</div>
</div>
<div class="row">
<div class="input-field col offset-xl2 xl8 offset-l1 l10 m12 s12">
<input id="name-template" type="text" class="white-text">
<label for="name-template">Backup name</label>
<span class="helper-text">You can find all available variables <a target="_blank"
href="https://github.com/Sebclem/hassio-nextcloud-backup/blob/master/naming_template.md">here</a></span>
<div class="col-12 col-lg-10 offset-lg-1">
<label for="name-template" class="form-label">Backup name</label>
<input id="name-template" type="text" class="form-control" aria-describedby="help-template">
<span id="help-template" class="form-text">
You can find all available variables
<a target="_blank"
href="https://github.com/Sebclem/hassio-nextcloud-backup/blob/master/naming_template.md">
here
</a>
</span>
</div>
</div>
<div class="row">
<div class="input-field col offset-xl2 xl8 offset-l1 l10 m12 s12">
<select id="cron-drop-settings">
<div class="row mt-2">
<div class="col-12 col-lg-10 offset-lg-1">
<label for="cron-drop-settings" class="form-label">Auto Backup</label>
<select id="cron-drop-settings" class="form-select">
<option value="0">Disable</option>
<option value="1">Daily</option>
<option value="2">Weekly</option>
<option value="3">Monthly</option>
</select>
<label>Auto Backup</label>
</div>
</div>
<div class="row hide">
<div class="input-field col offset-xl2 xl8 offset-l1 l10 m12 s12">
<input type="text" class="timepicker white-text" readonly="true" id="timepicker">
<label>Hour </label>
<div class="row d-none mt-2">
<div class="col-12 col-lg-10 offset-lg-1">
<label for="timepicker" class="form-label">Hour </label>
<input type="time" class="form-control" id="timepicker">
</div>
</div>
<div class="row hide">
<div class="input-field col offset-xl2 xl8 offset-l1 l10 m12 s12">
<select id="cron-drop-day">
<div class="row d-none mt-2">
<div class="col-12 col-lg-10 offset-lg-1">
<label for="cron-drop-day" class="form-label">Day</label>
<select id="cron-drop-day" class="form-select">
<option value="1">Monday</option>
<option value="2">Tuesday</option>
<option value="3">Wednesday</option>
@ -47,101 +52,82 @@
<option value="6">Saturday</option>
<option value="0">Sunday</option>
</select>
<label>Day</label>
</div>
</div>
<div class="row hide">
<div class="input-field col s2">
<input type="text" class="white-text" disabled readonly="true" id="cron-drop-day-month-read">
<label class="white-text active">Day of month</label>
<div class="row d-none mt-2">
<div class="col-12 col-lg-10 offset-lg-1">
<label for="cron-drop-day-month" class="form-label">Day of month</label>
<input type="number" class="form-control" id="cron-drop-day-month" min="1" max="28">
</div>
<div class="input-field col s10">
<p class="range-field">
<input type="range" id="cron-drop-day-month" min="1" max="28" style="border: none;" />
</p>
</div>
</div>
<div class="row mt-3">
<div class="col-12 col-lg-6">
<div class="row">
<div class="col offset-xl1 xl5 l6 m12 s12">
<div class="row">
<div class="col s12 center">
<div class="col-12 text-center">
<h5>Folders</h5>
</div>
</div>
<div class="row">
<div class="col offset-s1 s11 xl10 offset-xl2" >
<ul id="folders-div">
<div class="col-12">
<ul id="folders-div" class="list-group">
</ul>
</div>
</div>
</div>
<div class="col xl5 l6 m12 s12" >
<div class="col-12 col-lg-6">
<div class="row">
<div class="col s12 center">
<div class="col-12 text-center">
<h5>Addons</h5>
</div>
</div>
<div class="row">
<div class="col offset-s1 s11 xl10 offset-xl2">
<ul id="addons-div">
<div class="col-12">
<ul id="addons-div" class="list-group">
</ul>
</div>
</div>
</div>
</div>
<div class="row" style="margin-bottom: 5px;">
<div class="col s12 center">
<h4>Auto Clean Settings</h2>
<div class="row mt-3">
<div class="col-12 text-center">
<h4>Auto Clean Settings</h4>
</div>
</div>
<div class="row mt-3">
<div class="col-12 col-md-5 offset-md-1">
<div class="row">
<div class="col s12 center divider">
<div class="col-12">
<div class="form-check form-switch">
<input class="form-check-input" id="auto_clean_local" type="checkbox">
<label class="form-check-label" for="auto_clean_local">Auto Clean Local Snapshots</label>
</div>
</div>
</div>
<div class="row mt-2">
<div class="col-12">
<label for="local-snap-keep" class="form-label">Local snapshot to keep</label>
<input type="number" class="form-control" id="local-snap-keep" min="0">
</div>
</div>
</div>
<div class="col-12 col-md-5">
<div class="row">
<div class="col offset-xl1 xl5 l6 m12 s12">
<div class="row" style="margin-bottom: 10px;">
<div class="col s12">
<div style="color: #9e9e9e; display: inline;">Auto Clean Local Snapshots</div>
<div class="switch" style="display: inline;">
<label>
<input id="auto_clean_local" type="checkbox">
<span class="lever"></span>
</label>
</div>
</div>
</div>
<div class="row">
<div class="input-field col s12">
<input type="number" class="white-text" id="local-snap-keep">
<label class="active">Local snapshot to keep</label>
<div class="col-12">
<div class="form-check form-switch">
<input class="form-check-input" id="auto_clean_backup" type="checkbox">
<label class="form-check-label" for="auto_clean_backup">Auto Clean Nextcloud Snapshots</label>
</div>
</div>
</div>
<div class="col xl5 l6 m12 s12" >
<div class="row" style="margin-bottom: 10px;">
<div class="col s12">
<div style="color: #9e9e9e; display: inline;">Auto Clean Nextcloud Snapshots</div>
<div class="switch" style="display: inline;">
<label>
<input id="auto_clean_backup" type="checkbox">
<span class="lever"></span>
</label>
</div>
</div>
</div>
<div class="row">
<div class="input-field col s12">
<input type="number" class="white-text" id="backup-snap-keep">
<label class="active">Nextcloud snapshot to keep</label>
<div class="row mt-2">
<div class="col-12">
<label for="backup-snap-keep" class="form-label">Nextcloud snapshot to keep</label>
<input type="number" class="form-control" id="backup-snap-keep" min="0">
</div>
</div>
@ -149,21 +135,12 @@
</div>
</div>
<div class="row" style="margin-bottom: 10px;">
</div>
<div class="row">
</div>
</div>
<div class="modal-footer blue-grey darken-4">
<a href="#" class="modal-close waves-effect btn red"><b>Cancel</b></a>
<a href="#" class="btn green waves-effect" style="margin-left: 5px;" id="save-backup-settings"><b>Save</b></a>
<div class="modal-footer border-secondary">
<button data-bs-dismiss="modal" class="btn btn-danger"><b>Cancel</b></button>
<button class="btn btn-success" id="save-backup-settings"><b>Save</b></button>
</div>
</div>
</div>
</div>

View File

@ -0,0 +1,17 @@
<div class="modal" id="loading-modal" tabindex="-1"
aria-hidden="true">
<div class="modal-dialog border-secondary">
<div class="modal-content bg-dark text-white">
<div class="modal-header border-secondary d-flex justify-content-center">
<h3 class="modal-title fw-bold">Loading</h4>
</div>
<div class="modal-body text-center">
<div class="spinner-border text-accent border-5" style="width: 5rem; height: 5rem" role="status">
<span class="visually-hidden">Loading...</span>
</div>
</div>
</div>
</div>
</div>
</div>

View File

@ -1,73 +1,73 @@
<div id="modal-settings-nextcloud" class="modal modal-fixed-footer blue-grey darken-4 white-text">
<div class="modal-content">
<div class="row">
<div class="col s12 center">
<h4>Nextcloud Settings</h4>
<div id="modal-settings-nextcloud" class="modal fade" xmlns="http://www.w3.org/1999/html">
<div class="modal-dialog modal-lg">
<div class="modal-content bg-dark text-white">
<div class="modal-header border-secondary">
<h5 class="modal-title" id="exampleModalLabel">Nextcloud Settings</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"
aria-label="Close"></button>
</div>
</div>
<div class="row">
<div class="col s12 center divider">
</div>
</div>
<div class="row hide">
<div class="col s12 center red-text" id="nextcloud_settings_message">
<div class="modal-body">
<div class="row d-none">
<div class="col-12 col-md-10 offset-md-1 text-center alert alert-danger" role="alert" id="nextcloud_settings_message">
</div>
</div>
<div class="row" style="margin-bottom: 10px;">
<div class="col m4 s12 " style="margin-bottom: 10px;">
<div style="color: #9e9e9e; display: inline;">SSL</div>
<div class="switch" style="display: inline;">
<label>
<input id="ssl" type="checkbox">
<span class="lever"></span>
</label>
</div>
</div>
<div class="col m8 s12 hide" style="margin-bottom: 10px;">
<div style="color: #9e9e9e; display: inline;">Accept Self-signed certificate</div>
<div class="switch" style="display: inline;">
<label>
<input id="self_signed" type="checkbox">
<span class="lever"></span>
</label>
</div>
</div>
</div>
<div class="row">
<div class="input-field col s12">
<input id="hostname" type="text" class="white-text">
<label for="hostname">Hostname</label>
<span class="helper-text">exemple.com:8080</span>
<div class="col-sm-12 col-md-4">
<div class="form-check form-switch">
<input class="form-check-input" id="ssl" type="checkbox">
<label class="form-check-label" for="ssl">SSL</label>
</div>
</div>
<div class="row">
<div class="input-field col m6 s12">
<input id="username" type="text" class="white-text">
<label for="username">Username</label>
</div>
<div class="input-field col m6 s12">
<input id="password" type="password" class="white-text">
<label for="password">Password</label>
<span class="helper-text">!!! Use App Password !!! See <a target="_blank"
href="https://github.com/Sebclem/hassio-nextcloud-backup#nextcloud-config">doc</a> for more
information.</span>
<div class="col-sm-12 col-md-8 invisible">
<div class="form-check form-switch">
<input class="form-check-input" id="self_signed" type="checkbox">
<label class="form-check-label" for="ssl">Accept Self-signed certificate</label>
</div>
</div>
<div class="row">
<div class="input-field col s12">
<input id="back-dir" type="text" class="white-text">
<label for="back-dir">Backup Directory</label>
<span class="helper-text">Default: /Hassio Backup/</span>
</div>
<div class="row mt-3">
<div class="col-12">
<label for="hostname" class="form-label">Hostname</label>
<input id="hostname" type="text" class="form-control" aria-describedby="hostname-help">
<div id="hostname-help" class="form-text">exemple.com:8080</span>
</div>
</div>
</div>
<div class="row mt-3">
<div class="col-12 col-md-6">
<label for="username" class="form-label">Username</label>
<input id="username" type="text" class="form-control">
</div>
<div class="col-12 col-md-6">
<label for="password" class="form-label">Password</label>
<input id="password" type="password" class="form-control" aria-describedby="password-help">
<div id="password-help" class="form-text">
!!! Use App Password !!! See
<a target="_blank"
href="https://github.com/Sebclem/hassio-nextcloud-backup#nextcloud-config">
doc
</a>
for more information.
</div>
</div>
</div>
<div class="row mt-3">
<div class="col-12">
<label for="back-dir" class="form-label">Backup Directory</label>
<input id="back-dir" type="text" class="form-control" aria-describedby="dir-help">
<div id="dir-help" class="form-text">Default: /Hassio Backup/</div>
</div>
</div>
</div>
<div class="modal-footer border-secondary">
<button data-bs-dismiss="modal" class="btn btn-danger"><b>Cancel</b></button>
<button class="btn btn-success" id="save-nextcloud-settings"><b>Save</b></button>
</div>
</div>
</div>
<div class="modal-footer blue-grey darken-4">
<a href="#" class="modal-close waves-effect btn red"><b>Cancel</b></a>
<a href="#" class="btn green waves-effect" style="margin-left: 5px;" id="save-nextcloud-settings"><b>Save</b></a>
</div>
</div>