New Addon

Google BK
This commit is contained in:
2023-04-23 18:17:03 +07:00
parent 1c7fd2b476
commit 702d080a1e
248 changed files with 72745 additions and 0 deletions
@@ -0,0 +1,76 @@
<div id="about_modal" class="modal modal-fixed-footer">
<div class="modal-content">
<h4>About this project</h4>
<h5>Who did this?</h5>
<p>
I'm an individual who works on projects like this in my spare time.
If you like it and would like to support me, you can star my projects on
<a href="https://github.com/sabeechen" target="_blank" rel="noreferrer">Github</a>
or contribute financially usign any of the methods below, they all go into the same bucket used to support the add-on:
<div class="flex-wrap">
<a target="_blank" rel="noreferrer" href="https://www.buymeacoffee.com/sabeechen" class="donate-button">
<img src="static/{{ version }}/images/bmc-button.svg" alt="Support me">
</a>
<a href="https://www.paypal.com/paypalme/stephenbeechen?locale.x=en_US" target="_blank" rel="noreferrer" class="donate-button">
<img src="static/{{ version }}/images/paypal-button.svg" alt="Donate through PayPal" />
</a>
<a href="https://www.patreon.com/bePatron?u=4064183" target="_blank" rel="noreferrer" class="donate-button">
<img src="static/{{ version }}/images/patreon-button.svg"/>
</a>
<a href="https://github.com/sponsors/sabeechen" target="_blank" rel="noreferrer" class="donate-button">
<img src="static/{{ version }}/images/github-sponsors-button.svg"/>
</a>
<a href="#!" class="modal-trigger donate-button" data-target="monero_modal">
<img src="static/{{ version }}/images/monero-button.svg"/>
</a>
<a href="#!" class="modal-trigger donate-button" data-target="bitcoin_modal">
<img src="static/{{ version }}/images/bitcoin-button.svg"/>
</a>
<a href="#!" class="modal-trigger donate-button" data-target="ethereum_modal">
<img src="static/{{ version }}/images/ethereum-button.svg"/>
</a>
</div>
</p>
<h5>
Code Contributors <span id="contributors-count" class="default-hidden"></span>
</h5>
<div id="contributors-loading" class="progress" style="width: 30%; margin-top: 22px; margin-bottom: 22px;">
<div class="indeterminate"></div>
</div>
<div id="contributors" class="default-hidden">
<p id="contributors-extra" style="margin-bottom: 24px;">
</p>
<p id="contributors-simple" style="margin-bottom: 6px;">
</p>
<a href="https://github.com/sabeechen/hassio-google-drive-backup/graphs/contributors" target="_blank">
+ <span id="contributors-more-link-count"></span> more
</a>
</div>
<h5>Connect</h5>
<ul class="browser-default">
<li>
<a href="https://github.com/sabeechen/hassio-google-drive-backup" target="_blank" rel="noreferrer">
Project Github
</a>
</li>
<li>
<a href="https://github.com/sabeechen/hassio-google-drive-backup/issues" target="_blank" rel="noreferrer">
Issue Tracker
</a>
</li>
<li>
<a href="https://materializecss.com" target="_blank" rel="noreferrer">
Materialize
</a>
</li>
</ul>
</div>
<div class="modal-footer">
<div class="copyright left">
© 2022 Stephen Beechen
</div>
<a href="#!" class="modal-close btn-flat"><i class="material-icons">close</i>Close</a>
</div>
</div>
@@ -0,0 +1,106 @@
<div id="deletemodal" class="modal">
<script type="text/javascript">
function renderDeleteDialog(target) {
var backup = $(target).data('backup');
var slug = backup['slug'];
var sources = backup['sources'];
let list = $('#delete_sources');
list.html('');
for (let source of backup.sources) {
let template = `<li>
<label>
<input type="checkbox" name="{id}" id="{id}" data-sourcename='{id}' class="filled-in checkbox-ha" onchange="deleteOneChanged(this);" />
<span>{name}</span>
<br />
</label>
</li>`
template = template.replaceAll("{id}", source.key);
template = template.replaceAll("{name}", sourceToName(source.key));
list.append(template);
}
if (backup.sources.length > 1) {
$("#delete_multiple_text").show();
$("#delete_single_text").hide();
$("#delete_button").addClass("disabled");
$("#delete_dialog_everywhere").prop('checked', false);
$("#delete_everywhere_input").show();
list.show();
} else {
$("#delete_multiple_text").hide();
$("#delete_single_text").show();
$("#delete_button").removeClass("disabled");
$("input", $("#delete_sources")).prop('checked', true);
$("#delete_everywhere_input").hide();
list.hide();
}
$("#delete_button").attr("onClick", "doDeleteBackup('" + slug + "')");
}
function doDeleteBackup(slug) {
sources = []
$("input", $("#delete_sources")).each(function () {
let input = $(this);
if (input.prop('checked')) {
sources.push(input.data('sourcename'));
}
});
//console.log("Delete: " + slug + " Drive: " + drive + " HA: " + ha)
message = "Deleting backup from ";
if (sources.length == 1) {
message += sourceToName(sources[0])
} else if (sources.length > 1) {
message += " " + sources.length + " places"
} else {
message += "<i>...nowhere?</i>"
return;
}
// Send the delete request
data = {
'slug': slug,
'sources': sources,
};
postJson("deleteSnapshot", data, refreshstats, null, message);
}
function deleteEverywhereChanged(item) {
var checked = $(item).is(':checked');
$("input", $("#delete_sources")).prop('checked', checked);
deleteOneChanged(null);
}
function deleteOneChanged(item) {
let selected = false;
$("input", $("#delete_sources")).each(function () {
selected = selected || $(this).prop('checked');
});
if (selected) {
$("#delete_button").removeClass("disabled");
} else {
$("#delete_button").addClass("disabled");
}
}
</script>
<div class="modal-content">
<h4>Are you sure?</h4>
<p id="delete_multiple_text">
Choose below where you'd like to delete backup from, or check "Delete Everywhere" to delete the backup from everywhere. Be careful! There is no way to recover a backup once you've deleted it.
</p>
<p id="delete_single_text">
Be careful! There is no way to recover a backup once you've deleted it and it doesn't look like this backup is uploaded anywhere else.
</p>
<label id="delete_everywhere_input">
<input type="checkbox" name="delete_dialog_everywhere" id="delete_dialog_everywhere" class="filled-in checkbox-ha" onchange="deleteEverywhereChanged(this)" />
<span>Delete Everywhere</span>
<br />
</label>
<ul id="delete_sources" style="margin-left:20px">
</ul>
</div>
<div class="modal-footer">
<a href="#!" class="modal-close btn-flat danger-btn disabled" id="delete_button"><i class="material-icons prefix">delete</i>Delete</a>
<a href="#!" class="modal-close btn-flat" id="delete_cancel"><i class="material-icons">close</i>Cancel</a>
</div>
</div>
@@ -0,0 +1,610 @@
<div id="details_modal" class="modal modal-fixed-footer" style="max-width: 900px;">
<div id="backup-source-container" class="backup-source-container card default-hidden" data-slug='slug'
data-source='source' data-source-name='fake_source_name'>
<div class="card-content">
<span class="detail-badge">
<svg class="source-icon" viewBox="0 0 24 24">
<use xlink:href="#dummy" />
</svg>
<span class="detail-source-name" style="min-width: 110px; display: inline-block;">Source Name</span>
</span>
<a class="delete-initial btn-flat danger-btn" href="#!" onclick="deleteButtonClick(this);">
<i class="material-icons">delete</i>Delete
</a>
<a href="#!" class="never-delete btn-flat" onclick="neverDeleteButtonClick(this);">
<i class="material-icons">lock</i>Never Delete
</a>
<a href="#!" class="allow-delete btn-flat" onclick="allowDeleteButtonClick(this);">
<i class="material-icons">lock_open</i>Allow Deletion
</a>
<a class="delete-confirm btn-flat high-vis-danger-btn default-hidden" href="#!"
onclick="confirmDeleteButtonClick(this);">
<i class="material-icons">delete_forever</i>Confirm Delete
</a>
<a class="dont-ignore-button btn-flat" href="#!"
onclick="dontIgnoreButtonClick(this);">
<i class="material-icons">add</i>Don't Ignore
</a>
<a class="delete-cancel btn-flat default-hidden" href="#!" onclick="deleteButtonCancel(this);">
<i class="material-icons">close</i>Cancel
</a>
<div class="preloader-wrapper inline">
<div class="spinner-layer">
<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 class="retain-warning small sub-detail indent">
<i class="material-icons">lock</i>
<span>This backup will not be deleted or count against the total backups kept in <span
class="detail-source-name">Source Name</span> until you allow it manually.</span>
</div>
<div class="delete-warning small sub-detail indent">
<i class="material-icons">warning</i>
<span>This backup will be deleted from <span class="detail-source-name">Source Name</span> as soon as a new
backup is made.</span>
</div>
<div class="ignore-warning small sub-detail indent">
<i class="material-icons">warning</i>
<span>This backup is being ignored because you configured the addon to ignore some types of backups
in your addon settings. It won't count against the backups stored in Home Assistant and it will never be automatically uploaded or deleted. Click "Don't Ignore" to have the addon track, upload, and eventually delete this backup.</span>
</div>
</div>
</div>
<script type="text/javascript">
function deleteButtonClick(element) {
let container = $(element).closest('.backup-source-container');
container.data('processing', true);
$(".delete-initial", container).addClass('disabled');
$(".never-delete", container).addClass('default-hidden');
$(".delete-confirm", container).removeClass('default-hidden');
$(".delete-confirm", container).removeClass('disabled');
$(".delete-cancel", container).removeClass('default-hidden');
}
function deleteButtonCancel(element) {
let container = $(element).closest('.backup-source-container');
$(".delete-initial", container).removeClass('disabled');
$(".never-delete", container).removeClass('default-hidden');
$(".delete-confirm", container).addClass('default-hidden');
$(".delete-cancel", container).addClass('default-hidden');
container.data('processing', false);
}
function confirmDeleteButtonClick(element) {
let container = $(element).closest('.backup-source-container');
container.data('processing', true);
let source = container.data('source');
$(".preloader-wrapper", container).addClass("active");
$(".delete-confirm", container).addClass('disabled');
$(".delete-cancel", container).addClass('default-hidden');
// Send the delete request
data = {
'slug': source.slug,
'sources': [source.key],
};
postJson(
"deleteSnapshot",
data,
function () {
deleteSuccessful(container);
},
function () {
container.data('processing', false);
configureBackupSource(container);
},
"Deleting backup from " + sourceToName(source.key));
}
function neverDeleteButtonClick(element) {
let container = $(element).closest('.backup-source-container');
container.data('processing', true);
let source = container.data('source');
$(".never-delete", container).addClass('disabled');
$(".preloader-wrapper", container).addClass("active");
data = {
'slug': source.slug,
'sources': {}
};
data['sources'][source.key] = true;
let success = function (info) { neverDeleteCompleted(container, true); };
let fail = function (info) { neverDeleteCompleted(container, false); };
postJson("retain", data, success, fail, "Updating backup... ");
}
function dontIgnoreButtonClick(element) {
let container = $(element).closest('.backup-source-container');
container.data('processing', true);
let source = container.data('source');
$(".dont-ignore-button", container).addClass('disabled');
$(".preloader-wrapper", container).addClass("active");
data = {
'slug': source.slug,
'ignore': false,
};
let success = function (info) { ignoreCompleted(container, false); };
let fail = function (info) { ignoreCompleted(container, true); };
postJson("ignore", data, success, fail, "Updating backup... ");
}
function ignoreCompleted(container, ignored) {
let source = container.data('source');
container.data('processing', false);
source.ignored = ignored;
refreshstats();
// Get the updated source info
configureBackupSource(container);
}
function allowDeleteButtonClick(element) {
let container = $(element).closest('.backup-source-container');
container.data('processing', true);
let source = container.data('source');
$(".allow-delete", container).addClass('disabled');
$(".preloader-wrapper", container).addClass("active");
data = {
'slug': source.slug,
'sources': {}
};
data['sources'][source.key] = false;
let success = function (info) { neverDeleteCompleted(container, false); };
let fail = function (info) { neverDeleteCompleted(container, true); };
postJson("retain", data, success, fail, "Updating backup... ");
}
function neverDeleteCompleted(container, retained) {
let source = container.data('source');
container.data('processing', false);
source.retained = retained;
container.data('source', source);
container.data('source-name', source.key);
refreshstats();
// Get the updated source info
configureBackupSource(container);
}
function deleteSuccessful(element) {
let container = $(element);
container.data('processing', false);
let parent = container.closest(".detail-backup-sources");
container.remove();
if (parent.children().length == 0) {
M.Modal.getInstance(document.getElementById("details_modal")).close();
}
refreshstats();
}
function configureBackupSource(container) {
let source = container.data('source');
if (container.data('processing')) {
return;
}
$(".detail-source-name", container).html(sourceToName(source.key));
$("use", container).attr('xlink:href', "#" + last_data.sources[source.key].icon);
$(".delete-confirm", container).removeClass('disabled');
$(".allow-delete", container).removeClass('disabled');
$(".never-delete", container).removeClass('disabled');
$(".preloader-wrapper", container).removeClass("active");
$(".delete-initial", container).removeClass("disabled");
$(".dont-ignore-button").removeClass("disabled");
if (source.retained) {
$(".delete-initial", container).addClass('default-hidden');
$(".never-delete", container).addClass('default-hidden');
$(".delete-confirm", container).addClass('default-hidden');
$(".delete-cancel", container).addClass('default-hidden');
$(".allow-delete", container).removeClass('default-hidden');
$(".retain-warning", container).removeClass('default-hidden');
} else {
$(".delete-initial", container).removeClass('default-hidden');
$(".never-delete", container).removeClass('default-hidden');
$(".delete-confirm", container).addClass('default-hidden');
$(".delete-cancel", container).addClass('default-hidden');
$(".allow-delete", container).addClass('default-hidden');
$(".retain-warning", container).addClass('default-hidden');
}
if (source.delete_next) {
$(".delete-warning", container).removeClass('default-hidden');
} else {
$(".delete-warning", container).addClass('default-hidden');
}
if (source.ignored) {
$(".ignore-warning", container).removeClass('default-hidden');
$(".dont-ignore-button", container).removeClass('default-hidden');
$(".never-delete", container).addClass('default-hidden');
} else {
$(".ignore-warning", container).addClass('default-hidden');
$(".dont-ignore-button", container).addClass('default-hidden');
}
}
function setValuesForBackupUpdate(backup) {
let sources_container = $('.detail-backup-sources');
let rendered_sources = $(".backup-source-container", sources_container);
let modal = $('#details_modal');
$(".detail-comment").html(backup.note);
// Update values for any sources that have changed.
let existing_sources = {};
rendered_sources.each(function () {
existing_sources[$(this).data('source-name')] = $(this);
});
let new_sources = new Set();
for (let i = 0; i < backup.sources.length; i++) {
let source = backup.sources[i];
if (existing_sources[source.key] == undefined) {
// Add the source info to the list
let template = $('#backup-source-container').clone();
template.attr('id', 'detail-source-' + source.key);
template.data('source', source);
template.data('source-name', source.key);
template.data('processing', false);
template.removeClass('default-hidden');
configureBackupSource(template);
sources_container.append(template);
} else {
// Update with new info instead
let container = existing_sources[source.key];
container.data('source', source);
configureBackupSource(container);
}
new_sources.add(source.key);
}
rendered_sources.each(function () {
if (!new_sources.has($(this).data('source-name'))) {
$(this).remove();
}
});
// Render the upload card if needed
let upload_card = $('.detail-uploader-info');
if (backup.upload_info) {
$('.source-name', upload_card).html(backup.upload_info.name);
let progress = Math.round(backup.upload_info.progress);
let data = progress + "% (" + asSizeString(backup.upload_info.total) + ") - ";
$('.determinate', upload_card).css("width", progress + "%");
if (backup.upload_info.speed != undefined) {
data += asSizeString(backup.upload_info.speed) + "/second - ";
} else {
data += "(determining speed) - ";
}
data += "Started " + backup.upload_info.started;
$('.progress-info', upload_card).html(data);
upload_card.removeClass('default-hidden');
} else {
upload_card.addClass('default-hidden');
}
// Render the card the suggests uploadign to HA if necessary
if (backup.uploadable && backup.upload_info == null) {
$(".upload-reminder").show();
} else {
$(".upload-reminder").hide();
}
if (backup.restorable) {
$("#restore_link").removeClass('default-hidden');
} else {
$("#restore_link").addClass('default-hidden');
}
if ((backup.haVersion || backup.folders.length + backup.addons.length > 0) && !modal.data('details_shown')) {
let content = [];
let content_container = $(".details-contents")
content_container.html("");
if (backup.haVersion) {
content_container.append(genContentTemplate("HA Config", `v${backup.haVersion}`, "broken", "folder-home"));
}
for (var i = 0; i < backup.folders.length; i++) {
let slug = backup.folders[i];
let folder = "Unknown Folder";
let icon = "folder-outline";
let subtext = "&nbsp;";
if (slug == "share") {
folder = "Share Folder";
icon = "folder-account"
} else if (slug == "ssl") {
folder = "SSL Folder";
icon = "folder-key";
} else if (slug == "addons/local") {
folder = "Local Add-on Folder";
icon = "folder-pound";
} else if (slug == "media") {
folder = "Media Folder";
icon = "folder-music";
} else {
folder = slug;
icon = "folder-outline";
}
content_container.append(genContentTemplate(folder, subtext, "broken", icon));
}
for (var i = 0; i < backup.addons.length; i++) {
let addon = backup.addons[i];
let sub = `v${addon.version} - ${addon.size}`;
content_container.append(genContentTemplate(addon.name, sub, `logo/${addon.slug}`, 'puzzle-outline'));
}
modal.data('details_shown', true);
$(".detail-contents-card").removeClass('default-hidden');
}
}
function genContentTemplate(name, subtext, image, fallback_icon) {
return `
<div class='detail-badge col xl3 l4 m4 s6 content-badge' title="${name}">
<div class="left">
<svg class="content-icon left default-hidden" viewBox="0 0 24 24">
<use xlink:href="#${fallback_icon}" />
</svg>
<img class='left content-logo' src='${image}' onerror='addonImageError(this);''>
</div>
<div>
<span class='truncate detail-name'>${name}</span>
<span class='sub'>${subtext}</span>
</div>
</div>`;
}
function showDetails(target) {
var backup = $(target).data('backup');
if (backup.isPending) {
return;
}
let modal = $('#details_modal');
modal.data('slug', backup.slug);
modal.data('backup', backup);
modal.data('details_shown', false);
$(".detail-contents-card").addClass('default-hidden');
var details = backup.details;
$("#details_name").html(backup.name);
$("#details_name").attr("title", backup.name);
$("#details_date").html(backup.date);
gen_detail = "";
if (backup.status_detail) {
gen_detail = "Generation " + backup.status_detail[0];
}
configureDetailBadge('detail-type', backup.type == 'full' ? 'Full Backup' : 'Partial Backup', true);
if (backup.haVersion) {
$(".detail-ha-version").show();
configureDetailBadge('detail-ha-version', "HA v" + backup.haVersion, backup.haVersion);
} else {
$(".detail-ha-version").hide();
}
configureDetailBadge('detail-password', "Password Protected", backup.protected);
configureDetailBadge('detail-time', backup.date, true);
configureDetailBadge('detail-size', backup.size, true);
configureDetailBadge('detail-generational', gen_detail, backup.status_detail);
let sources_container = $('.detail-backup-sources');
sources_container.html("");
setValuesForBackupUpdate(backup);
cancelUploadHaClicked();
$("#download_link").data('backup', backup);
cancelEditCommentClick()
setInputValue("edit-comment-input", "");
M.Modal.getInstance(document.getElementById('details_modal')).open();
}
function editCommentClick() {
let modal = $("#details_modal");
let backup = modal.data("backup")
$(".detail-comment").addClass("default-hidden");
setInputValue("edit-comment-input", backup.note);
$(".edit-comment-button").addClass("default-hidden");
$(".cancel-comment-edit-button").removeClass("default-hidden");
$(".save-comment-edit-button").removeClass("default-hidden");
$(".detail-edit-comment").removeClass("default-hidden");
M.textareaAutoResize($('#edit-comment-input'));
M.updateTextFields();
$('#edit-comment-input').focus();
}
function cancelEditCommentClick() {
$(".detail-comment").removeClass("default-hidden");
$(".edit-comment-button").removeClass("default-hidden");
$(".cancel-comment-edit-button").addClass("default-hidden");
$(".save-comment-edit-button").addClass("default-hidden");
$(".detail-edit-comment").addClass("default-hidden");
setInputValue("edit-comment-input", "");
}
function saveEditCommentClick() {
let modal = $("#details_modal");
let backup = modal.data("backup")
let comment = $("#edit-comment-input").val();
data = {
'slug': backup.slug,
'note': comment
};
let success = function (info) { editCommentSaveSuccess(backup.slug, comment); };
let fail = function (info) { editCommentSaveFailure(); };
postJson("note", data, success, fail, "Updating backup... ");
}
function editCommentSaveSuccess(slug, comment) {
let modal = $("#details_modal");
let backup = modal.data("backup");
if (backup.slug == slug) {
cancelEditCommentClick();
backup.note = comment;
$(".detail-comment").html(comment);
}
}
function editCommentSaveFailure() {
}
function uploadHaClicked() {
let card = $(".upload-reminder");
$(".load-ha-button", card).addClass('disabled');
$(".load-ha-info", card).addClass('default-hidden');
$(".load-ha-confirm-warning", card).removeClass('default-hidden');
$(".cancel-load-ha-button", card).removeClass('default-hidden');
$(".confirm-load-ha-button", card).removeClass('default-hidden');
}
function cancelUploadHaClicked() {
let card = $(".upload-reminder");
$(".load-ha-button", card).removeClass('disabled');
$(".load-ha-info", card).removeClass('default-hidden');
$(".load-ha-confirm-warning", card).addClass('default-hidden');
$(".cancel-load-ha-button", card).addClass('default-hidden');
$(".confirm-load-ha-button", card).addClass('default-hidden');
$(".cancel-load-ha-button", card).removeClass('disabled');
$(".confirm-load-ha-button", card).removeClass('disabled');
}
function doUpload() {
let modal = $("#details_modal");
let backup = modal.data("backup")
message = "Uploading '" + backup.name + "'";
url = "upload?slug=" + encodeURIComponent(backup.slug);
$(".cancel-load-ha-button", modal).addClass('disabled');
$(".confirm-load-ha-button", modal).addClass('disabled');
postJson(url, {}, function () { refreshstats(); cancelUploadHaClicked(); }, cancelUploadHaClicked, message);
}
function addonImageError(element) {
let image = $(element);
image.addClass('default-hidden');
$(".content-icon", image.closest(".content-badge")).removeClass("default-hidden");
}
</script>
<div class="modal-content">
<h5 id="details_name" class="truncate-two-lines"></h5>
<div class="row">
<div class="detail-badge detail-type col s12 m6 l4" title="Backup Type">
<i class="material-icons">archive</i>
<span class="detail-name"></span>
</div>
<div class="detail-badge detail-ha-version col s12 m6 l4" title="Home Assistant Version">
<i class="material-icons">home</i>
<span class="detail-name"></span>
</div>
<div class="detail-badge detail-password col s12 m6 l4" title="The backup is password protected">
<i class="material-icons">vpn_key</i>
<span class="detail-name"></span>
</div>
<div class="detail-badge detail-time col s12 m6 l4" title="When this backup was created">
<i class="material-icons">access_time</i>
<span class="detail-name"></span>
</div>
<div class="detail-badge detail-size col s12 m6 l4" title="The size of the backup">
<i class="material-icons">sd_card</i>
<span class="detail-name"></span>
</div>
<div class="detail-badge detail-generational col s12 m6 l4"
title="This backup is kept because of your generational configuration">
<i class="material-icons">date_range</i>
<span class="detail-name"></span>
</div>
</div>
<div class="backup_presence_window">
This backup is present in:
<div class="detail-backup-sources">
</div>
<div class="card detail-uploader-info">
<div class="card-content">
Uploading to <span class="source-name">Google Drive</span>
<div class="progress">
<div class="determinate"></div>
</div>
<span class="progress-info"></span>
</div>
</div>
</div>
<div class='card upload-reminder'>
<div class="card-content">
<div class="load-ha-info">
<p>
To see more details about this backup or restore it, you'll need to
load it into Home Assistant. You can do that using the button below or by manually
copying it from Google Drive into your Home Assistant '/backup' folder using something like the
<a href="https://github.com/home-assistant/addons/blob/master/samba/DOCS.md" target="_blank">Samba
Addon</a>.
<blockquote>
Note: This won't restore the backup, it'll just copy the backup archive into a folder on your Home
Assistant machine and make it available for restoration.
</blockquote>
</p>
</div>
<div class="load-ha-confirm-warning">
<p>
Before loading this backup please note:
<blockquote>
<i class="material-icons left" style="font-size: 40px;">disc_full</i>
The backup takes up space, maybe a lot of space. Please make sure you have enough disk space in Home
Assistant to store the backup, as running out of disk space can make the machine dangerously unstable.
</blockquote>
<blockquote>
<i class="material-icons left" style="font-size: 40px;">lock</i>
The uploaded backup will be marked as "Never Delete" in Home Assistant. It won't count toward the limit
you've configured for backups kept in Home Assistant and it will never be deleted until you manually
tell the addon to do so.
</blockquote>
</p>
</div>
</div>
<div class="card-action">
<a class="btn-flat load-ha-button" onclick="uploadHaClicked();"><i class="material-icons">file_upload</i>Load
into Home Assistant</a>
<a class="btn-flat danger-btn confirm-load-ha-button" onclick="doUpload();"><i
class="material-icons">file_upload</i>Confirm load</a>
<a class="btn-flat cancel-load-ha-button" onclick="cancelUploadHaClicked();"><i
class="material-icons">close</i>Cancel</a>
</div>
</div>
<div class="row">
<span class="detail-comment" style="white-space: pre-wrap"></span>
<div class="input-field col s12 detail-edit-comment">
<i class="material-icons prefix">comment</i>
<textarea class="materialize-textarea edit-comment-input" id="edit-comment-input" name="edit-comment-input"></textarea>
<label for="edit-comment-input">Comment</label>
</div>
<a class="btn-flat edit-comment-button" onclick="editCommentClick();"><i
class="material-icons">edit</i>Edit Comment</a>
<a class="btn-flat cancel-comment-edit-button" onclick="cancelEditCommentClick();"><i
class="material-icons">close</i>Cancel</a>
<a class="btn-flat save-comment-edit-button" onclick="saveEditCommentClick();"><i
class="material-icons">save</i>Save</a>
</div>
<div class="detail-contents-card">
<h6>Contents</h6>
<div class="details-contents row">
</div>
</div>
</div>
<div class="modal-footer">
<a id="download_link" href="#!" onclick="downloadBackup(this);" class="btn-flat">
<i class="material-icons">file_download</i>Download
</a>
<a id="restore_link" href="#!" onclick="restoreClick(this)" class="modal-close btn-flat">
<i class="material-icons">input</i>Restore
</a>
<a id="save_cancel" href="#!" class="modal-close btn-flat"><i class="material-icons">close</i>Close</a>
</div>
</div>
@@ -0,0 +1,62 @@
<div id="backupmodal" class="modal">
<div class="modal-content">
<h4>New Backup</h4>
<p style="margin-bottom: 0;">
Create a new backup immediately, using the settings you've configured for the automatic backups
(eg full/partial, password, etc).
</p>
<form>
<div class="row" style="margin-bottom: 12px;">
<div class="input-field col s12">
<i class="material-icons prefix">label_outline</i>
<input type="text" id="backup_name_one_off" name="backup_name_one_off" class="validate"
onkeyup="backupNameOneOffExample()" />
<label for="backup_name_one_off">Backup Name</label>
<span class="helper-text">
The name to use for this backup, or leave it blank to use the default
naming scheme. It can't be changed later. Optionally you can use template variables.
<a target="_blank" rel="noreferrer"
href="https://github.com/sabeechen/hassio-google-drive-backup#can-i-give-backups-a-different-name">
See here
</a>
for a list of all available options.
<div style="margin-top: 10px;">
Your backup's name will look something like:
<br />
<b id="backup_name_example_one_off"></b>
</div>
</span>
</div>
<div class="input-field col s12">
<i class="material-icons prefix">comment</i>
<input type="text" id="backup_note_one_off" name="backup_note_one_off" />
<label for="backup_note_one_off">Comment</label>
<span class="helper-text">
Add a comment that appears alongside the backup in this web UI and in Google Drive. You can change this later.
</span>
</div>
</div>
<div class="row" style="margin-left: 8px; margin-bottom: 0;">
<div class="col s12">
<label>
<input type="checkbox" name="retain_drive_one_off" id="retain_drive_one_off" class="filled-in checkbox-ha" />
<span>Keep indefinitely in Google Drive</span>
</label>
<br />
<label>
<input type="checkbox" name="retain_ha_one_off" id="retain_ha_one_off" class="filled-in checkbox-ha" />
<span>Keep indefinitely in Home Assistant</span>
</label>
</div>
</div>
</form>
</div>
<div class="modal-footer">
<p class="left" style="margin-left: 18px; font-size: 12px;">
These options will only apply to this one-off backup.
</p>
<a href="#!" class="modal-close btn-flat" id="create_backup_button"
onclick="doNewBackup(); return false;"><i class="material-icons left">add</i>Create backup</a>
<a href="#!" class="modal-close btn-flat" id="cancel_backup"><i class="material-icons left">close</i>Cancel</a>
</div>
</div>
@@ -0,0 +1,16 @@
<div id="uploadmodal" class="modal">
<div class="modal-content">
<h4>Upload this backup to Home Assistant</h4>
<p>Are you sure you'd like to upload this backup back to Home Assistant? The backup will be downloaded from
Google Drive, and after that you'll be able to restore it.</p>
<p>
<b>Note:</b> The backup you upload from Google Drive will be kept indefinitely in Home Assistant until you
delete it manually. You can change this by selecting "Never
Delete" and unckecking "Keep indefinitely in Home Assistant" from the backup's action menu after its uploaded.
</p>
</div>
<div class="modal-footer">
<a href="#!" class="modal-close btn-flat" id="do_upload_button"><i class="material-icons">check</i>Upload Backup</a>
<a href="#!" class="modal-close btn-flat" id="upload_cancel"><i class="material-icons">close</i>Cancel</a>
</div>
</div>
@@ -0,0 +1,51 @@
<div id="bug_modal" class="modal modal-fixed-footer">
<div class="modal-content">
<h4><i class="material-icons"
style="font-size: 50px; padding-right: 10px; vertical-align: middle">bug_report</i><span>Report an Issue</span>
</h4>
<p class="detail-node">
Looks like you ran into a problem that you want to report to the developer(s). Getting a lot of detail about what
you ran into can really help them figure things out. To make
gathering that easy for you and the developer, follow these instructions:
</p>
<ul class="browser-default">
<li>
Click
<a target="_blank"
href="https://github.com/sabeechen/hassio-google-drive-backup/issues/new?title=Please%20add%20a%20title&body=Copy%20the%20info%20from%20the%20previous%20page%20here"><i
style="vertical-align: middle" class="material-icons tiny">open_in_new</i> Open Github</a>
to open a new issue on Github and paste the text you copied there. You can click "Preview" to make sure it looks
right.
</li>
<li>Before submitting the issue, add in any information you think might help. Consider including what you were
doing that caused the error, a screenshot, etc.</li>
<li>
Click "Submit new issue" on Github to post the issue. The developer will get back to you as fast as he can, but
keep in mind that he maintains this project in his free
time.
</li>
</ul>
<p>Here is a preview of the information you'll be providing:</p>
<ul class="tabs" id="bug_report_tabs">
<li class="tab col s3"><a class="active" href="#bug_preview">Preview</a></li>
<li class="tab col s3"><a href="#bug_markdown">Markdown</a></li>
</ul>
<div style="max-height: 200px;">
<div id="bug_preview" class="col s12">
<div id="bug_preview_display" class="highlight-border" style="overflow-y: scroll; overflow-x: scroll; max-height: 500px;"></div>
</div>
<div id="bug_markdown" class="col s12">
<div class="highlight-border">
<textarea id="bug_markdown_display" style="overflow-x: scroll; height: 500px; border: 0;"></textarea>
</div>
</div>
</div>
</div>
<div class="modal-footer">
<a href="#!" onclick="doBugDetailCopy()" class="btn-flat"><i class="material-icons" >content_copy</i>Copy Details</a>
<a target="_blank"
href="https://github.com/sabeechen/hassio-google-drive-backup/issues/new?title=Please%20add%20a%20title&body=Copy%20the%20info%20from%20the%20previous%20page%20here"
class="btn-flat"><i class="material-icons">open_in_new</i>Open Github</a>
<a href="#!" class="modal-close btn-flat"><i class="material-icons">close</i>Close</a>
</div>
</div>
@@ -0,0 +1,138 @@
<script type="text/javascript">
$(document).ready(function () {
M.Modal.init(document.querySelector("#monero_modal"), {
onOpenStart: () => loadMoneroInfo("8BtRhV9vUNkTDST7WEWuH7JumpvrFApVag5D2fVTLgfdJH8sjiy5LQo68WK4GaLBijTYb7XW6D6bChQGkDeDMmfTTTZGNTr"),
});
M.Modal.init(document.querySelector("#bitcoin_modal"), {
onOpenStart: () => loadBitcoinInfo("bc1qystpwsqqwusam38mhwafh8n4yln798zqprsl93"),
});
M.Modal.init(document.querySelector("#ethereum_modal"), {
onOpenStart: () => loadEthereumInfo("0xfa455Bdd245F67Dd1D549C2Ac67EA6BBf91402a7"),
});
});
async function loadMoneroInfo(address) {
$('#monero-qr').html('');
$('#monero-qr').qrcode(address);
let uri = "monero:" + address;
let uriElement = document.getElementById("monero-uri");
uriElement.innerHTML = address;
uriElement.href = uri;
document.getElementById("monero-uri-copy").onclick = function() {
navigator.clipboard.writeText(uri);
toast("Copied Monero URI to clipboard");
}
document.getElementById("monero-address-copy").onclick = function() {
navigator.clipboard.writeText(address);
toast("Copied Monero address to clipboard");
}
}
async function loadBitcoinInfo(address) {
$('#bitcoin-qr').html('');
$('#bitcoin-qr').qrcode(address);
let uri = "bitcoin:" + address;
let uriElement = document.getElementById("bitcoin-uri");
uriElement.innerHTML = address;
uriElement.href = uri;
document.getElementById("bitcoin-uri-copy").onclick = function() {
navigator.clipboard.writeText(uri);
toast("Copied Bitcoin URI to clipboard");
}
document.getElementById("bitcoin-address-copy").onclick = function() {
navigator.clipboard.writeText(address);
toast("Copied Bitcoin address to clipboard");
}
}
async function loadEthereumInfo(address) {
$('#ethereum-qr').html('');
$('#ethereum-qr').qrcode(address);
let uri = "ethereum:" + address;
let uriElement = document.getElementById("ethereum-uri");
uriElement.innerHTML = address;
uriElement.href = uri;
document.getElementById("ethereum-uri-copy").onclick = function() {
navigator.clipboard.writeText(uri);
toast("Copied Ethereum URI to clipboard");
}
document.getElementById("ethereum-address-copy").onclick = function() {
navigator.clipboard.writeText(address);
toast("Copied Ethereum address to clipboard");
}
}
</script>
<div id="monero_modal" class="modal modal-fixed-footer">
<div class="modal-content">
<h4><img class="title-logo" src="static/{{ version }}/images/monero-logo.svg"/>Contribute with Monero/XMR</h4>
<p>
Use the QR code or link below to get the wallet address used for contributions. Thank you for your generosity, and use cryptocurrency responsibly!
</p>
<p>
<a id="monero-uri"></a>
<br>
<br>
<a class="btn-flat" id="monero-uri-copy"><i class="material-icons">content_copy</i> Copy Monero URI</a>
<a class="btn-flat" id="monero-address-copy"><i class="material-icons">content_copy</i>Copy Monero Address</a>
</p>
<p>
<span> QR Code: </span>
<div class="qr-code" id="monero-qr"></div>
</p>
<p>Visit <a href="https://www.getmonero.org/" target="_blank" rel="noreferrer">https://www.getmonero.org/</a> to learn more.</p>
</div>
<div class="modal-footer">
<a href="#!" class="modal-close btn-flat"><i class="material-icons">close</i>Close</a>
</div>
</div>
<div id="bitcoin_modal" class="modal modal-fixed-footer">
<div class="modal-content">
<h4><img class="title-logo" src="static/{{ version }}/images/bitcoin-logo.svg"/>Contribute with Bitcoin/BTC</h4>
<p>
Use the QR code or link below to get the wallet address used for contributions. Thank you for your generosity, and use cryptocurrency responsibly!
</p>
<p>
<a id="bitcoin-uri"></a>
<br>
<br>
<a class="btn-flat" id="bitcoin-uri-copy"><i class="material-icons">content_copy</i> Copy Bitcoin URI</a>
<a class="btn-flat" id="bitcoin-address-copy"><i class="material-icons">content_copy</i>Copy Bitcoin Address</a>
</p>
<p>
<span> QR Code: </span>
<div class="qr-code" id="bitcoin-qr"></div>
</p>
<p>Visit <a href="https://bitcoin.org" target="_blank" rel="noreferrer">https://bitcoin.org</a> to learn more.</p>
</div>
<div class="modal-footer">
<a href="#!" class="modal-close btn-flat"><i class="material-icons">close</i>Close</a>
</div>
</div>
<div id="ethereum_modal" class="modal modal-fixed-footer">
<div class="modal-content">
<h4><img class="title-logo" src="static/{{ version }}/images/ethereum-logo.svg"/>Contribute with Ethereum/ETH</h4>
<p>
Use the QR code or link below to get the wallet address used for contributions. Thank you for your generosity, and use cryptocurrency responsibly!
</p>
<p>
<a id="ethereum-uri"></a>
<br>
<br>
<a class="btn-flat" id="ethereum-uri-copy"><i class="material-icons">content_copy</i> Copy Ethereum URI</a>
<a class="btn-flat" id="ethereum-address-copy"><i class="material-icons">content_copy</i>Copy Ethereum Address</a>
</p>
<p>
<span> QR Code: </span>
<div class="qr-code" id="ethereum-qr"></div>
</p>
<p>Visit <a href="https://ethereum.org" target="_blank" rel="noreferrer">https://ethereum.org</a> to learn more.</p>
</div>
<div class="modal-footer">
<a href="#!" class="modal-close btn-flat"><i class="material-icons">close</i>Close</a>
</div>
</div>
@@ -0,0 +1,26 @@
<div id="debug_modal" class="modal">
<script type="text/javascript">
$(document).ready(function () {
$('#debug_modal').modal();
document.onkeydown = function (e) {
// CTRL + ALT + Y
if (e.ctrlKey && e.altKey && e.which == 89) {
M.Modal.getInstance(document.getElementById('debug_modal')).open();
}
};
});
function debug_show_all_hidden_things() {
$(".error_card").show();
$(".error_card").removeClass('error_card');
}
</script>
<div class="modal-content">
<h4>Debug</h4>
<p>Ah, you've stumbled into the debug dialog. The developer uses this for testing things. You're welcome to play around here, but wise men know better.</p>
<a class="btn-flat" onclick="debug_show_all_hidden_things()">Reveal all elements</a>
</div>
<div class="modal-footer">
<a href="#!" class="modal-close btn-flat">Close</a>
</div>
</div>
@@ -0,0 +1,78 @@
<div id="help_modal" class="modal modal-fixed-footer">
<div class="modal-content">
<h4>Get Help</h4>
<p class="detail-node">Maybe this add-on isn't working or you just don't know what to do. You can resolve almost any
issue by following these steps:</p>
<ul class="browser-default">
<li>
<h6>Check the FAQ</h6>
First check out the <a href="https://github.com/sabeechen/hassio-google-drive-backup#faq" target="_blank"
rel="noreferrer">add-on FAQ</a>. Reading documentation is boring,
it's true, but the developer spent a lot of time trying to make them easy to read and follow. If you're lucky,
your question is already answered there.
</li>
<li>
<h6>Check the Forum</h6>
Next, try checking the
<a href="https://community.home-assistant.io/t/add-on-home-assistant-google-drive-backup/" target="_blank"
rel="noreferrer">add-on announcement forum post.</a>
Your question might already be there, and if not it's a good place to ask. If the developer can't answer your
question, it might be that another user can help.
</li>
<li>
<h6>Check the Logs</h6>
This add-on makes its <a target="_blank" href="log?format=view">debug logs</a>
available. They're pretty cryptic but might give you some context for what's going wrong. Sometimes, an error
only shows up in the Home Assistant supervisor logs, which you
can get to from within Home Assistant by clicking "Supervisor" > "System" > "System Logs" > "Refresh".
</li>
<li>
<h6>Restart Everything</h6>
Restart the add-on. Restart Home Assistant. Reinstall the add-on. Technology loves to be restarted, it's amazing
how many problems it can solve.
</li>
<li>
<h6>File an Issue</h6>
If you don't want to ask something in front of the angry mobs on the internet, you can file an issue on the
project GitHub <a onclick="bugReport()">Issue Tracker</a>, just
click "new issue". The developer gets an email if make one of those, which he promises to try and help with. No
question is stupid.
</li>
<li>
<h6>Reach out Directly</h6>
I put my name on this and I want it to work well for you. You can email me at
<a href="mailto:stephen@beechens.com">stephen@beechens.com</a>
</li>
</ul>
<p>Maybe what you're dealing with is of a more, <i>general</i> nature? Home Assistant has a lot of rough edges, but
really anything can be frustrating.</p>
<ul class="browser-default">
<li>
<h6>Take care of yourself</h6>
Make sure you've had enough water to drink today. If you love coffee, it might be time to put on a new pot. If
its late at night, now might be the time to hit the sack.
Everything is more difficult when you're not at your best.
</li>
<li>
<h6>Get a fresh perspective</h6>
Take a walk, seriously. Sometimes the mind gets so fixated on a problem that it can't think around it anymore.
<a href="https://en.wikipedia.org/wiki/Rubber_duck_debugging" target="_blank" rel="noreferrer">Explain your
problem to a duck</a>
and you might see the solution was obvious.
</li>
<li>
<h6>Be realistic</h6>
In a week you're unlikely to even remember this moment. Next year, you're going to be a different person, with a
different mind. If you have the spare time to get
frustrated at how your home automation system stores its backups, then whatever you've got going on in this
moment is probably small potatoes. You've always been the one to
decide how you react.
</li>
</ul>
</div>
<div class="modal-footer">
<a href="#!" class="modal-close btn-flat"><i class="material-icons">check</i>That Helps</a>
<a href="#!" class="modal-close btn-flat"><i class="material-icons">close</i>Never mind</a>
</div>
</div>
@@ -0,0 +1,632 @@
<div id="settings_modal" class="modal modal-fixed-footer">
<div class="modal-content">
<h4>Add-on Settings</h4>
<p>
Settings take effect right after saving them. If you enable or disable SSL, you may need to go to a different
address to access the UI after the settings are saved. See the
<a href="https://github.com/sabeechen/hassio-google-drive-backup#configuration" target="_blank"
rel="noreferrer">GitHub Readme</a>
for more help with settings.
</p>
<div class="row">
<form class="col s12" method="get" id="settings_form">
<div class="row">
<div class="input-field col m6 s12">
<i class="material-icons prefix">computer</i>
<input type="number" id="max_backups_in_ha" name="max_backups_in_ha" min="0" class="validate" />
<label for="max_backups_in_ha">Backups in Home Assistant</label>
<span class="helper-text">The maximum number of backups to keep locally in Home Assistant, or 0 to disable
clean-up of old backups entirely.</span>
</div>
<div class="input-field col m6 s12">
<i class="material-icons prefix">cloud_upload</i>
<input type="number" id="max_backups_in_google_drive" name="max_backups_in_google_drive" min="0"
class="validate" />
<label for="max_backups_in_google_drive">Backups in Google Drive</label>
<span class="helper-text">The maximum number of backups to keep remotely in Google Drive, or 0 to disable
clean-up of old backups entirely.</span>
</div>
</div>
<div class="row">
<div class="col s12">
<label>
<input type="checkbox" name="delete_after_upload" id="delete_after_upload"
class="filled-in checkbox-ha" />
<span>Delete Backups After Upload</span>
<br />
<span class="helper-text">Delete backups in Home Assistant immediately after uploading them to Google Drive.
This can be useful if your machine is low on space, but your backups will only be stored in the cloud.
Enabling this will cause "Backups in Home Assistant" to be ignored.</span>
</label>
</div>
</div>
<div class="row">
<div class="col s12">
<label>
<input type="checkbox" name="delete_before_new_backup" id="delete_before_new_backup"
class="filled-in checkbox-ha" />
<span>Delete Oldest Backup Before Making a New One</span>
<br />
<span class="helper-text">Makes the addon delete the oldest backup before a new one is created in Home Assistant or uploaded to Google Drive.
When turned off (the default) the oldest backup will only be deleted after a new one has been created or uploaded. Be warned that if a
backup fails to be created or uploaded to Google Drive, this can make you have one fewer than the number of backups you intended to keep there.</span>
</label>
</div>
</div>
<div class="row">
<div class="col s12">
<label>
<input type="checkbox" name="ignore_other_backups" id="ignore_other_backups"
class="filled-in checkbox-ha" onchange="updateIgnoredBackupOptions()"/>
<span>Ignore Other Backups</span>
<br />
<span class="helper-text">The addon will ignore any backups it didn't create.
Ignored backups will not count toward "Backups in Home Assistant", get deleted,
or get uploaded to Google Drive unless you manually tell the addon to do so.</span>
</label>
</div>
</div>
<div class="row">
<div class="col s12">
<label>
<input type="checkbox" name="ignore_upgrade_backups" id="ignore_upgrade_backups"
class="filled-in checkbox-ha" onchange="updateIgnoredBackupOptions()"/>
<span>Ignore "Upgrade" Backups</span>
<br />
<span class="helper-text">The addon will ignore backups with only one addon or folder inside them.
This is useful if you'd like the addon to ignore backups that Home Assistant automatically creates while updating.
Ignored backups will not count toward "Backups in Home Assistant", get deleted,
or get uploaded to Google Drive up unless you manually tell the addon to do so.</span>
</label>
</div>
</div>
<div class="col s11 offset-s1 row" id="ignored-backup-duration-block">
<div class="input-field col s11offset-s1">
<i class="material-icons prefix">delete_sweep</i>
<input type="text" id="delete_ignored_after_days" name="delete_ignored_after_days"
pattern="^([ ]*([0-9]*[.])?[0-9]+[ ]*(seconds|second|secs|sec|s|minutes|minute|mins|min|m|hours|hour|hr|h|days|day|d)?[ ,]*)*$"
class="validate" />
<label for="delete_ignored_after_days">Delete ignored backups after</label>
<span class="helper-text">
For any ignored backups, automatically delete them after this much time.
Leave blank (the default) to never delete them. This can be any duration of
time expressed as days, hours, minutes, and seconds. Be careful enabling this,
you can see if you have any ignored backups by scrolling to the bottom of the
list of backups under this dialog. Examples:
'7 days', '36 hours', '1 hour, 30 minutes, 10 seconds', etc.</span>
</div>
</div>
<div class="row">
<div class="input-field col m6 s12">
<i class="material-icons prefix">date_range</i>
<input type="number" id="days_between_backups" name="days_between_backups" min="0" class="validate" />
<label for="days_between_backups">Days between backups</label>
<span class="helper-text">The number of days between backup, or 0 to disable automatic backup
creation.</span>
</div>
<div class="input-field col m6 s12">
<i class="material-icons prefix">alarm_on</i>
<input type="text" id="backup_time_of_day" name="backup_time_of_day" pattern="^[0-2]\d:[0-5]\d$"
class="validate" />
<label for="backup_time_of_day">Backup time of day</label>
<span class="helper-text">The time of day when backups should be taken in the form 'HH:mm' (24 hour
clock).</span>
</div>
</div>
<div class="row">
<div class="input-field col m6 s12">
<i class="material-icons prefix">vpn_key</i>
<input type="password" autocomplete="new-password" id="backup_password" name="backup_password"
class="validate" onkeyup="checkForSecret()" />
<label for="backup_password">Password</label>
<span class="helper-text">When set, creates backups protected by a password. This password will be
required by Home Assistant when restoring the backup. Leave blank to require no
password. You can specify a password from your secrets.yaml by using the syntax "!secret
your_secret_key".</span>
</div>
<div class="input-field col m6 s12 default-hidden" id="password_renter_block">
<i class="material-icons prefix">vpn_key</i>
<input type="password" autocomplete="new-password" id="backup_password_reenter"
name="backup_password_reenter" settings_ignore="true" class="validate" onkeyup="checkForSecret()" />
<label for="backup_password_reenter">Re-enter password</label>
<span class="helper-text">Re-enter your new password to ensure it matches.</span>
</div>
</div>
<div class="row">
<div class="input-field col s12">
<i class="material-icons prefix">label_outline</i>
<input type="text" id="backup_name" name="backup_name" class="validate"
onkeyup="backupNameExample()" />
<label for="backup_name">Backup Name Template</label>
<span class="helper-text">The template to use for naming new backup.
<a target="_blank" rel="noreferrer"
href="https://github.com/sabeechen/hassio-google-drive-backup#can-i-give-backups-a-different-name">See
here</a>
for a list of all available options, your backup's name will look something like: <br /><span
id="backup_example"></span></span>
</div>
</div>
<div class="row">
<div class="col s12">
<label>
<input type="checkbox" name="specify_backup_folder" id="specify_backup_folder"
class="filled-in checkbox-ha" onchange="chooseFolderChanged();" />
<span>Manually specify the backup folder</span>
<br />
<span class="helper-text">Specify manually which folder backups should be uploaded into in Google Drive.
If disabled, the addon will automatically create a folder in your My Drive whenever its
needed.</span><span id="current_folder_span"><a id="current_folder_link" target="_blank"
rel="noreferrer"> Click here</a> to see the folder currently being used.</span>
</label>
<br />
<div id="choose_folder_controls">
<div class="input-field col s11 offset-s1">
<i class="material-icons prefix">folder_open</i>
<input type="text" settings_ignore="true" id="settings_specify_folder_id"
name="settings_specify_folder_id" class="validate" />
<label for="settings_specify_folder_id">Backup Folder ID</label>
<span class="helper-text hide-for-default-creds">The Google Drive ID of the folder you'd like to upload backups into. You
can get this id by navigating to the folder in
<a href="https://drive.google.com" target="_blank" rel="noreferrer">Google Drive</a>
and copying it out of the URL. It should be a long sequence of characters at the end of the URL. For
example if the URL for my folder is
<a target="_blank" rel="noreferrer"
href="https://drive.google.com/drive/folders/1el4ZWcE0xdkIu_Yt_2kxk36qE7K5O188">https://drive.google.com/drive/folders/1el4ZWcE0xdkIu_Yt_2kxk36qE7K5O188</a>,
then the Drive ID is 1el4ZWcE0xdkIu_Yt_2kxk36qE7K5O188</span>
<span class="helper-text hide-for-custom-creds">Use the "Choose Folder" button to open a new window where you can select the
folder you'd like to use and paste its folder ID here. Doing this gives the addon permission to store backups in that folder.</span>
<a href="#!" id="choose_folder_button" onclick="chooseBackupFolder()"
class="btn-flat hide-for-custom-creds"
style="margin-left: 20px; margin-top: 10px"><i class="material-icons">open_in_new</i>Choose Folder</a>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col m6 s12">
<label>
<input type="checkbox" name="notify_for_stale_backups" id="notify_for_stale_backups"
class="filled-in checkbox-ha" />
<span>Enable Staleness Notifications</span>
<br />
<span class="helper-text">When there is a problem with the backups, show a persistent notification in
the Home Assistant interface.</span>
</label>
</div>
<div class="col m6 s12">
<label>
<input type="checkbox" name="enable_backup_stale_sensor" id="enable_backup_stale_sensor"
class="filled-in checkbox-ha" />
<span>Enable Backup Staleness Sensor</span>
<br />
<span class="helper-text">Publishes a sensor, binary_sensor.backups_stale, indicating if there is a
problem uploading up or creating backups.</span>
</label>
</div>
</div>
<div class="row">
<div class="col m6 s12">
<label>
<input type="checkbox" name="enable_backup_state_sensor" id="enable_backup_state_sensor"
class="filled-in checkbox-ha" />
<span>Enable Backup State Sensor</span>
<br />
<span class="helper-text">Publishes a sensor, sensor.backup_state, with the status of existing
backups.</span>
</label>
</div>
<div class="col m6 s12">
<label>
<input type="checkbox" name="send_error_reports" id="send_error_reports" class="filled-in checkbox-ha" />
<span>Send Error Reports</span>
<br />
<span class="helper-text">Sends information about errors to a database maintained by developers. This helps
them identify problems with new releases and provide better help messages.</span>
</label>
</div>
</div>
<div class="row">
<div class="col m6 s12">
<label>
<input type="checkbox" name="confirm_multiple_deletes" id="confirm_multiple_deletes"
class="filled-in checkbox-ha" />
<span>Confirm before large deletes</span>
<br />
<span class="helper-text">When disabled, the add-on will NOT stop and ask for confirmation before deleting
more than one backup. Disabling this is dangerous because accidental
misconfiguration can cause you to lose your history of uploaded backups.</span>
</label>
</div>
<div class="col m6 s12">
<label>
<input type="checkbox" name="enable_drive_upload" id="enable_drive_upload"
class="filled-in checkbox-ha" />
<span>Enable Google Drive Upload</span>
<br />
<span class="helper-text">When disabled, new backups will still be created in Home Assistant but will
not be uploaded to Google Drive.</span>
</label>
</div>
</div>
<div class="row">
<div class="col s12">
<label>
<input type="checkbox" name="warn_for_low_space" id="warn_for_low_space" class="filled-in checkbox-ha" />
<span>Stop on low disk space</span>
<br />
<span class="helper-text">When enabled, the addon will stop creating backups and ask for you to take
action it thinks you don't have enough disk space. Disabling this is not recommended
because recovering from an exhausted disk can be very difficult.</span>
</label>
</div>
</div>
<div class="row">
<div class="col s12">
<label>
<input type="checkbox" name="expose_extra_server" id="expose_extra_server" class="filled-in checkbox-ha"
onchange="toggleSlide(this, 'settings_server_details');" />
<span id="expose_extra_server_label">Expose this web interface on an additional port</span>
<br />
<span class="helper-text" id="expose_extra_server_help">Expose this web interface on an additional port.
This isn't necessary unless you'd like to avoid accessing this web interface through
<a target="_blank" rel="noreferrer"
href="https://www.home-assistant.io/blog/2019/04/15/hassio-ingress/">ingress</a>.</span>
</label>
</div>
</div>
<div class="row" id="settings_server_details" style="display: none">
<div class="col s11 offset-s1">
<label>
<span class="helper-text">Use the options below to configure what kind of SSL and authentication you'd
like to use for this webpage. By default this server is exposed on port 1627, but
that can be changed from the add-on installation page.</span>
</label>
</div>
<div class="col s11 offset-s1">
<div class="row">
<div class="col s12">
<label>
<input type="checkbox" name="require_login" id="require_login" class="filled-in checkbox-ha" />
<span>Require Login</span>
<br />
<span class="helper-text">Makes you enter your Home Assistant credentials to login to this
webpage.</span>
</label>
</div>
</div>
<div class="row">
<div class="col s12">
<label>
<input type="checkbox" name="use_ssl" id="use_ssl" class="filled-in checkbox-ha"
onchange="toggleSlide(this, 'settings_ssl_details');" />
<span>Use SSL</span>
<br />
<span class="helper-text">Require SSL to access this web interface. You must already have SSL keys
configured to use this. Note that once this setting is changed, you'll need to
navigate to the new url to continue using this webpage.</span>
</label>
</div>
<div class="col s12 offset-s1 row" id="settings_ssl_details" style="display: none">
<div class="input-field col s12">
<i class="material-icons prefix">vpn_key</i>
<input type="text" id="certfile" name="certfile" class="validate" />
<label for="certfile">Certificate File</label>
<span class="helper-text">The path to your certificate file. If you use the default Let's Encrypt
settings, the default should be correct.</span>
</div>
<div class="input-field col s12">
<i class="material-icons prefix">vpn_key</i>
<input type="text" id="keyfile" name="keyfile" class="validate" />
<label for="keyfile">Key File</label>
<span class="helper-text">The path to your key file. If you use the default Let's Encrypt settings,
the default should be correct.</span>
</div>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col s12">
<label>
<input type="checkbox" settings_ignore="true" id="generational_enabled" name="generational_enabled"
class="filled-in checkbox-ha" onchange="toggleSlide(this, 'settings_gen_details');" />
<span>Keep Generational Backups</span>
<br />
<span class="helper-text">Keeps older backups longer on daily, weekly, monthly, and yearly rotations
instead of just deleting the oldest backups. When using this feature, its
recommended that you keep "Days Between Backups" at 1.
<a href="https://github.com/sabeechen/hassio-google-drive-backup/blob/master/hassio-google-drive-backup/GENERATIONAL_BACKUP.md"
target="_blank" rel="noreferrer">See here</a>
for a deeper explanation.</span>
</label>
</div>
<div class="row" id="settings_gen_details" style="display: none">
<div class="col s11 offset-s1">
<div class="row">
<div class="input-field col m6 s12">
<i class="material-icons prefix">date_range</i>
<input type="number" id="generational_days" name="generational_days" min="0" class="validate" />
<label for="generational_days">Days</label>
<span class="helper-text">The number of daily backups to keep.</span>
</div>
</div>
<div class="row">
<div class="input-field col m6 s12">
<i class="material-icons prefix">date_range</i>
<input type="number" id="generational_weeks" name="generational_weeks" min="0" class="validate" />
<label for="generational_weeks">Weeks</label>
<span class="helper-text">The number of weeks of backups to keep.</span>
</div>
<div class="input-field col m6 s12">
<select class="browser-default" name="generational_day_of_week" id="generational_day_of_week">
<option value="mon" selected>Monday</option>
<option value="tue">Tuesday</option>
<option value="wed">Wednesday</option>
<option value="thu">Thursday</option>
<option value="fri">Friday</option>
<option value="sat">Saturday</option>
<option value="sun">Sunday</option>
</select>
<label class="helper-text">The day of the week when weekly backups will be kept.</label>
</div>
</div>
<div class="row">
<div class="input-field col m6 s12">
<i class="material-icons prefix">date_range</i>
<input type="number" id="generational_months" name="generational_months" min="0" class="validate" />
<label for="generational_months">Months</label>
<span class="helper-text">The number of months of backups to keep.</span>
</div>
<div class="input-field col m6 s12">
<input type="number" id="generational_day_of_month" value="1" name="generational_day_of_month" min="1"
max="31" class="validate" />
<label for="generational_day_of_month">Day of Month</label>
<span class="helper-text">The day of the month to keep monthly backups, from 1 to 31</span>
</div>
</div>
<div class="row">
<div class="input-field col m6 s12">
<i class="material-icons prefix">date_range</i>
<input type="number" id="generational_years" name="generational_years" min="0" class="validate" />
<label for="generational_years">Years</label>
<span class="helper-text">The number of years of backups to keep.</span>
</div>
<div class="input-field col m6 s12">
<input type="number" id="generational_day_of_year" value="1" name="generational_day_of_year" min="1"
max="365" class="validate" />
<label for="generational_day_of_year">Day of Year</label>
<span class="helper-text">The day of the year to keep yearly backups, from 1 to 365</span>
</div>
</div>
<div class="row">
<div class="col s12">
<label>
<input type="checkbox" id="generational_delete_early" name="generational_delete_early"
class="filled-in checkbox-ha" />
<span>Purge Old Backups Early</span>
<br />
<span class="helper-text">When checked, old backups that aren't part of any daily, weekly,
monthly, or yearly cycle you've configured will be deleted even if you haven't reached the
configured maximum number of backups in Google Drive/Home Assistant.</span>
</label>
</div>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col s12">
<label>
<input type="checkbox" settings_ignore="true" name="partial_backups" id="partial_backups"
class="filled-in checkbox-ha" onchange="toggleSlide(this, 'settings_partial_details');backupNameExample();" />
<span>Partial Backups</span>
<br />
<span class="helper-text">Partial backups allow you to selectively backup portions of your Home
Assistant configuration, folders, and add-ons. Choose which parts of Home Assistant you'd
like included in backups below. New add-ons will be automatically included in backups unless you
update these settings to exclude them.</span>
</label>
</div>
</div>
<div class="row" id="settings_partial_details" style="display: none">
<div class="col s11 offset-s1">
<div class="row" style="margin-bottom: 0px">
<div class="col s12">
<span style="font-weight: bold">Folders</span>
<ul id="folder_selection_list">
</ul>
</div>
</div>
<div class="row">
<div class="col s12">
<span style="font-weight: bold">Addons</span>
<ul id="settings_addons"></ul>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col s12">
<label>
<input type="checkbox" settings_ignore="true" name="stop_addons" id="stop_addons"
class="filled-in checkbox-ha" onchange="toggleSlide(this, 'settings_stop_addons_details');" />
<span>Stop addons</span>
<br />
<span class="helper-text">
Some addons will not get backed up correctly while they are running. Any addons you select below will
be stopped before a backup is taken and then started after it finishes. Note that if this addon
is interrupted before a backup completes, you may have to restart the addon manually.</span>
</label>
</div>
</div>
<div class="row" id="settings_stop_addons_details" style="display: none">
<div class="col s11 offset-s1">
<div class="row">
<div class="col s12">
<ul id="stopped_addons"></ul>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col s12">
<label>
<span style="font-size: 15px">Look and Feel</span>
<br />
<span class="helper-text">Configure how this web UI looks</span>
</label>
</div>
</div>
<div class="row">
<div class="col s11 offset-s1 row">
<div class="row">
<div class="input-field col s6">
<i class="material-icons prefix">format_paint</i>
<a href="#!" class="btn-flat" style="margin-left: 45px; margin-top: 10px"
onclick="showPallette(this)" id="background_color"></a>
<label style="transform: translateY(-20px) scale(0.8)">Background Color</label>
<span class="helper-text" style="margin-top: 10px">The background color of this interface. <a href="#"
onclick="revertColors();">Click here</a> to revert back to the default.</span>
</div>
<div class="input-field col s6">
<i class="material-icons prefix">format_paint</i>
<a href="#!" class="btn-flat" style="margin-left: 45px; margin-top: 10px"
onclick="showPallette(this)" id="accent_color"></a>
<label style="transform: translateY(-20px) scale(0.8)">Accent Color</label>
<span class="helper-text" style="margin-top: 10px">The accent color of this interface (e.g. links,
buttons, and other elements you can interact with). <a href="#" onclick="revertColors();">Click
here</a> to revert
back to the default.</span>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col s12">
<label>
<span style="font-size: 15px">Uncommon Options</span>
<br />
<span class="helper-text">Most people don't need these options, but you might.</span>
</label>
</div>
</div>
<div class="row">
<div class="col s11 offset-s1 row">
<div class="input-field col m6 s12">
<i class="material-icons prefix">timelapse</i>
<input type="number" id="google_drive_timeout_seconds" name="google_drive_timeout_seconds" min="1"
class="validate" />
<label for="google_drive_timeout_seconds">Google Drive Timeout</label>
<span class="helper-text">The number of seconds the add-on should wait when connecting to or reading from
Google Drive's servers.</span>
</div>
<div class="input-field col m6 s12">
<i class="material-icons prefix">cloud</i>
<input type="text" id="alternate_dns_servers" name="alternate_dns_servers"
pattern="^([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3})(,[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3})*$"
class="validate" />
<label for="alternate_dns_servers">Alternate DNS Servers</label>
<span class="helper-text">When the add-on runs into trouble resolving Google Drive's IP Address, it will
use this comma-delimited list of DNS Servers as alternate sources for name
resolution. The servers must support TCP DNS lookups. The defaults are
<a href="https://developers.google.com/speed/public-dns/" target="_blank" rel="noreferrer">Google's
public DNS servers</a>.</span>
</div>
</div>
<div class="col s11 offset-s1 row">
<div class="col m12 s12">
<label>
<input type="checkbox" name="ignore_ipv6_addresses" id="ignore_ipv6_addresses"
class="filled-in checkbox-ha" />
<span>Ignore IPv6 Addresses</span>
<br />
<span class="helper-text">Ignore ipv6 Addresses for the purposes or DNS resolution. Some users have had
problems attempting to connect to Googles IPv6 servers because it is disabled on
their network.</span>
</label>
</div>
</div>
<div class="col s11 offset-s1 row">
<div class="input-field col s12 m12 s12">
<i class="material-icons prefix">cloud_queue</i>
<input type="text" id="drive_ipv4" name="drive_ipv4"
pattern="^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$" class="validate" />
<label for="drive_ipv4">Google Drive IP Address Override</label>
<span class="helper-text">If you're having problems with reaching Google Drive, you can use this to
specify Google Drive's IP address manually and circumvent DNS lookup entirely. You can
use a website like
<a target="_blank" rel="noreferrer"
href="https://www.xmyip.com/hostname-ip?hostname=www.googleapis.com">this one</a>
to lookup what address will work for www.googleapis.com.</span>
</div>
</div>
<div class="col s11 offset-s1 row">
<div class="input-field col s12 m12 s12">
<i class="material-icons prefix">refresh</i>
<input type="text" id="max_sync_interval_seconds" name="max_sync_interval_seconds"
pattern="^([ ]*([0-9]*[.])?[0-9]+[ ]*(seconds|second|secs|sec|s|minutes|minute|mins|min|m|hours|hour|hr|h|days|day|d)?[ ,]*)*$"
class="validate" />
<label for="max_sync_interval_seconds">Sync Interval</label>
<span class="helper-text">
How often the addon should check Home Assistant and Google Drive for backups. The addon will still
sync more frequently when it starts, creates a new backup, or
encounters errors. This can be any duration of time expressed as hours, minutes, and seconds. Examples:
'1 hour', '30 minutes', '1 hour, 30 minutes, 10 seconds',
etc.</span>
</div>
</div>
<div class="col s11 offset-s1 row">
<div class="input-field col s12 m12 s12">
<i class="material-icons prefix">sync</i>
<input type="text" id="ha_reporting_interval_seconds" name="ha_reporting_interval_seconds"
pattern="^([ ]*([0-9]*[.])?[0-9]+[ ]*(seconds|second|secs|sec|s|minutes|minute|mins|min|m|hours|hour|hr|h|days|day|d)?[ ,]*)*$"
class="validate" />
<label for="ha_reporting_interval_seconds">Home Assistant Reporting Interval</label>
<span class="helper-text">
How often the addon should update the "Backups Stale" sensor in Home Assistant. The addon will still update immediately when it encounters
errors. With longer values it may take longer after Home Assistant starts up before the "Backups Stale" sensor it available. This can be any duration of time expressed as hours, minutes, and seconds. Examples:
'10 seconds', '3 minute', '2 hours, 30 minutes', etc.
</span>
</div>
</div>
<div class="col s11 offset-s1 row">
<div class="input-field col s12 m12 s12">
<i class="material-icons prefix">refresh</i>
<input type="text" id="maximum_upload_chunk_bytes" name="maximum_upload_chunk_bytes"
pattern="^[ ]*([0-9,]*\.?[0-9]*)[ ]*(b|B|k|K|m|M|g|G|t|T|p|P|e|E|z|Z|y|Y)[a-zA-Z ]*[ ]*$"
class="validate" />
<label for="maximum_upload_chunk_bytes">Maximum Drive Upload Chunk Size</label>
<span class="helper-text">
Sets the maximum "chunk" size allowed for uploads to Google Drive. Larger sizes will upload faster but delay reports
of progress and may cause interruptions on some network hardware. Between 1MB and 20MB is recommended, minimum is 256 kB.
The value can be provided in any binary-prefix format, e.g. '256 Kb', '10 Mb', '3000Kb', etc.</span>
</div>
</div>
<div class="col s11 offset-s1">
<label>
<input type="checkbox" id="call_backup_snapshot" name="call_backup_snapshot"
class="filled-in checkbox-ha" />
<span>Use old sensor names/values with "snapshot" instead of "backup"</span>
<br />
<span class="helper-text">When checked, the addon's state and staleness sensors will be
published as binary_sensor.snapshots_stale and sensor.snapshot_backup and their
attributes will reference "snapshots" instead of "backups". <a target="_blank" rel="noreferrer"
href="https://github.com/sabeechen/hassio-google-drive-backup/blob/master/hassio-google-drive-backup/BACKUP_AND_SNAPSHOT.md">
See here</a> for a detailed explanation of why you might want this.</span>
</label>
</div>
</div>
</form>
</div>
</div>
<div class="modal-footer" style="min-height: 56px; height: auto">
<div class="left valign-wrapper red darken-4 white-text default-hidden" id="settings_error_div"
style="margin: 6px; padding-left: 16px; padding-right: 16px; min-height: 36px; display: flex">
<span id="settings_error">Some configuration is invalid, check for red errors up above.</span>
</div>
<a href="#!" class="btn-flat" id="save_settings" onClick="saveSettings()"><i class="material-icons">save</i>Save</a>
<a href="#!" class="btn-flat" onclick="handleCloseSettings()" id="save_cancel"><i class="material-icons">close</i>Cancel</a>
</div>
</div>