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,41 @@
<!DOCTYPE html>
<html lang="en">
<head>
{% block head %}
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1.0" />
<title>Home Assistant Google Drive Backup</title>
<link href="static/{{ version }}/css/icons.css?version={{ version }}" rel="stylesheet">
<link href="static/{{ version }}/css/spinner.css?version={{ version }}" rel="stylesheet">
<link href="static/{{ version }}/css/materialize.css?version={{ version }}" type="text/css" rel="stylesheet" media="screen,projection" />
<link href="static/{{ version }}/css/colpick.css?version={{ version }}" type="text/css" rel="stylesheet" media="screen,projection" />
<link href="static/{{ version }}/css/static.css?version={{ version }}" type="text/css" rel="stylesheet" media="screen,projection" />
<link rel="icon" type="image/png" href="static/{{ version }}/images/favicon.png?version={{ version }}">
<link href="https://fonts.googleapis.com/css?family=Cookie" rel="stylesheet">
<script src="static/{{ version }}/js/URI.js?version={{ version }}"></script>
<script src="static/{{ version }}/js/jquery.js?version={{ version }}"></script>
<script src="static/{{ version }}/js/materialize.js?version={{ version }}"></script>
<script src="static/{{ version }}/js/colpick.js?version={{ version }}"></script>
<script src="static/{{ version }}/js/init.js?version={{ version }}"></script>
<script src="static/{{ version }}/js/scripts.js?version={{ version }}"></script>
<script src="static/{{ version }}/js/settings.js?version={{ version }}"></script>
<script src="static/{{ version }}/js/about.js?version={{ version }}"></script>
<script src="static/{{ version }}/js/jquery.qrcode.min.js?version={{ version }}"></script>
<style id="theme" type="text/css" data-background-color="{{ backgroundColor }}" data-accent-color="{{ accentColor }}">
/* Dynamically created from javascript */
</style>
<script src="static/{{ version }}/js/theme.js?version={{ version }}"></script>
{% endblock %}
</head>
<body>
{% block content %}{% endblock %}
{% include 'layouts/partials/footer.jinja2' %}
{% block scripts %}{% endblock %}
</body>
</html>
@@ -0,0 +1,51 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta content="text/html;charset=utf-8" http-equiv="Content-Type">
<meta content="utf-8" http-equiv="encoding">
{% block head %}
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1.0" />
<title>Home Assistant Google Drive Backup</title>
<link href="static/{{ version }}/css/icons.css?version={{ version }}" rel="stylesheet">
<link href="static/{{ version }}/css/spinner.css?version={{ version }}" rel="stylesheet">
<link href="static/{{ version }}/css/materialize.css?version={{ version }}" type="text/css" rel="stylesheet" media="screen,projection" />
<link href="static/{{ version }}/css/colpick.css?version={{ version }}" type="text/css" rel="stylesheet" media="screen,projection" />
<link href="static/{{ version }}/css/static.css?version={{ version }}" type="text/css" rel="stylesheet" media="screen,projection" />
<link href="https://fonts.googleapis.com/css?family=Cookie" rel="stylesheet">
<link rel="icon" type="image/png" href="static/{{ version }}/images/favicon.png?version={{ version }}">
<style id="theme" type="text/css" data-background-color="{{ backgroundColor }}" data-accent-color="{{ accentColor }}">
/* Dynamically created from javascript */
</style>
<script src="static/{{ version }}/js/theme.js?version={{ version }}"></script>
<script src="static/{{ version }}/js/URI.js?version={{ version }}"></script>
<script src="static/{{ version }}/js/jquery.js?version={{ version }}"></script>
<script src="static/{{ version }}/js/markdown-it.min.js?version={{ version }}"></script>
<script src="static/{{ version }}/js/materialize.js?version={{ version }}"></script>
<script src="static/{{ version }}/js/colpick.js?version={{ version }}"></script>
<script src="static/{{ version }}/js/init.js?version={{ version }}"></script>
<script src="static/{{ version }}/js/scripts.js?version={{ version }}"></script>
<script src="static/{{ version }}/js/settings.js?version={{ version }}"></script>
<script src="static/{{ version }}/js/about.js?version={{ version }}"></script>
<script src="static/{{ version }}/js/moment.js?version={{ version }}"></script>
<script src="static/{{ version }}/js/jquery.qrcode.min.js?version={{ version }}"></script>
<script src="bootstrap?version={{ version }}"></script>
{% endblock %}
</head>
<body>
{% block navbar %}
{% include 'layouts/partials/navbar.jinja2' %}
{% endblock %}
{% block content %}{% endblock %}
{% include 'layouts/partials/footer.jinja2' %}
{% block scripts %}{% endblock %}
</body>
</html>
@@ -0,0 +1,36 @@
{% macro header(version, title) -%}
<header>
<nav role="navigation">
<div class="nav-wrapper">
<a class="brand-logo" href="">
<img src="static/{{ version }}/images/logo.png" width=40 height=40 />
<span class="hide-on-small-only">
{% if title %}
{{ title }}
{% else %}
<span class="hide-on-med-and-down">Home Assistant</span> Google Drive Backup
{% endif %}
</span>
</a>
{{ caller() }}
</div>
</nav>
</header>
{%- endmacro %}
{% macro errorMessage(title, id, icon, actions) -%}
<div class="card default-hidden error_card {{ id }}" id="{{ id }}">
<div class="card-content">
<span class="card-title danger-text">
<i class="material-icons" style="font-size: 50px; padding-right: 10px;">{{ icon }}</i>
<b style="vertical-align: top;">{{ title }}</b>
</span>
{{ caller() }}
</div>
{% if actions %}
<div class="card-action">
{{actions}}
</div>
{% endif %}
</div>
{%- endmacro %}
@@ -0,0 +1,693 @@
{% import "layouts/macros.jinja2" as macros %}
{# Reusable macros #}
{% macro chooseFolder(id) %}
<div class="choose-folder-container z-depth-1">
<div class="input-field find_parent_target">
<i class="material-icons prefix">folder_open</i>
<input type="text" id="{{ id }}" />
<label for="{{ id }}">Enter your folder ID here, then click "Save"</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. Doing this gives the addon permission to store backups in that folder.</span>
<a href="#!" id="choose_folder_button" style="margin-left: 50px" onclick="chooseBackupFolder()"
class="btn-flat hide-for-custom-creds">
<i class="material-icons left">open_in_new</i>
Choose Folder
</a>
<a href="#!" id="choose_folder_button" onclick="saveFolder($('#{{ id }}', $(this).parent('.find_parent_target')).val())"
class="btn-flat">
<i class="material-icons left">save</i>
Save
</a>
</div>
</div>
{% endmacro %}
{# Error messages #}
{% set error_card_actions %}
<a class="btn-flat" id="error_github_search" href="#" target="_blank"><i class="material-icons">search</i>Find similar issues</a>
<a class="btn-flat" id="error_github_link" onclick="bugReport()"><i class="material-icons">bug_report</i>File this issue on Github</a>
<a class="btn-flat" id="download_logs_link" href="log?format=download" target="_blank"><i class="material-icons">file_download</i>Download logs</a>
{% endset %}
{% call macros.errorMessage("Error while backing up", "error_card", "sync_problem", error_card_actions) %}
<p>
The add-on got the error below while trying to sync your backups. Some transient errors are expected
(e.g. an upload could fail if the internet is down) but if this persist for more than a few minutes your
backups won't get uploaded and the information you see on this page may be stale. If you file an
issue on GitHub with the link below, it could be helpful to provide the logs downloaded from the
"Download Logs" link below
</p>
<br />
<p id="generic_error_title" style="white-space: pre-wrap;font-family: monospace;"></p>
<div>
<a onclick="toggleLinkSlide(this, 'generic_error_details');" href="#!"><i class="material-icons"
style="padding-right: 10px; vertical-align: bottom;">arrow_drop_down</i> View Error
Details</a>
<div id="generic_error_details" class="default-hidden" style="white-space: pre-wrap; padding: 10px; font-family: monospace;">
</div>
</div>
{% endcall %}
{% set low_space_actions %}
<a class="btn-flat" href="#" onclick="sync('low_space'); return false;"><i class="material-icons">refresh</i>Try Syncing Again</a>
<a class="btn-flat" href="#" onclick="skipLowSpaceWarning(); return false;"><i class="material-icons">add</i>Create backup anyway</a>
{% endset %}
{% call macros.errorMessage("Low Disk Space", "low_space", "disc_full", low_space_actions) %}
<p>
It looks like you're low on disk space. The addon won't attempt to create a backup until you take
action or more space becomes available. It looks like your backup folder in Home Assistant is
<b id="data_pct_used"></b> full and only has <b id="data_space_remaining"></b> left so creating
another backup runs the risk of exhausting the space available. When Home Assistant runs out of
disk space, it can stop responding and become very difficult to fix. This addon can only estimate
how big the next backup will be based on your most recent one, it can't know how big the backup
will be until its actually created.
</p>
<p>To resolve this you can:</p>
<ul class="browser-default">
<li>
Free up space in Home Assistant. Addons like the <a target="_blank" rel="noreferrer" href="https://www.home-assistant.io/addons/configurator/">Configurator</a> can help you see what files might be taking up space.
</li>
<li>
Make your backups smaller (see <a target="_blank" rel="noreferrer" href="https://github.com/sabeechen/hassio-google-drive-backup#will-this-fill-up-my-google-drive-why-are-my-backups-so-big">this tip</a>).
You can also click the little <i class="material-icons" style="display: inline; font-size: 15px">info</i>
next to backups below to see which parts of them are taking up the most space.
</li>
<li>
Modify your <a href="#!" onclick="loadSettings()"><i class="material-icons" style="display: inline; font-size: 15px">settings</i> add-on settings</a>
to store fewer backups in Home Assistant.
</li>
<li>
Delete existing backups from Home Assistant by clicking <b>"Action"</b> -> <b>"Delete"</b> on backups below. Make sure either you don't need them anymore or they're uploaded to Google Drive.
</li>
<li>
Modify your <a href="#!" onclick="loadSettings()"><i class="material-icons" style="display: inline; font-size: 15px">settings</i> add-on settings</a>
and disable "Stop on low disk space". This will make the addon stop checking disk usage, do so at your own peril.
</li>
<li>
Click "Create backup anyway" to bypass this warning just this one time. This is useful if you're confident that the backup actually will fit in the available space. Again, do so at your own peril.
</li>
</ul>
{% endcall %}
{% set drive_full_actions %}
<a class="btn-flat" href="#" onclick="sync('drive_full'); return false;"><i class="material-icons">refresh</i>Try Syncing Again</a>
{% endset %}
{% call macros.errorMessage("Google Drive is Full", "drive_full", "disc_full", drive_full_actions) %}
<p>
Google Drive is full, backups can't continue until space is available. You can go to Google Drive's
<a href="https://drive.google.com/drive/quota" target="_blank" rel="noreferrer">quota page</a>
to see what's using up space in your Google Drive, but because that space is shared between several Google products (e.g. Gmail, Google Photos)
one of those products might be using the space instead. You can see your usage across all those products on
Google's <a href="https://drive.google.com/settings/storage" target="_blank" rel="noreferrer">storage settings</a> page.
</p>
<p>To resolve this you can:</p>
<ul class="browser-default">
<li>
Free up space in Google Drive, Gmail, or Google Photos by deleting things.
</li>
<li>
Modify your <a href="#!" onclick="loadSettings()"><i class="material-icons" style="display: inline; font-size: 15px">settings</i> add-on settings</a>
to store fewer backups in Google Drive
</li>
<li>
<a href="https://drive.google.com/settings/storage" target="_blank" rel="noreferrer">Buy more storage</a> from Google
</li>
<li>
Make your backups smaller (see <a href="https://github.com/sabeechen/hassio-google-drive-backup#will-this-fill-up-my-google-drive-why-are-my-backups-so-big" target="_blank" rel="noreferrer">this tip</a>)
</li>
{% endcall %}
{% call macros.errorMessage("Couldn't Log in with Google", "creds_bad", "error") %}
<p>Google Drive rejected your credentials. Your backups will not be uploaded until this is resolved.</p>
<p>This can happen if:</p>
<ul class="browser-default">
<li>You revoked the add-on's access in your Google account settings.</li>
<li>You recently changed your Google account password.</li>
<li>You haven't used your Google account for over 6 months.</li>
<li>The add-on's credentials changed, or Google changed them.</li>
</ul>
<p>To resolve this, you'll need to <a href="reauthenticate">authenticate</a> this add-on with Google again. If that still doesn't help, you may need to update the add-on</p>
{% endcall %}
{% set delete_error_actions %}
<a class="btn-flat" href="#" onclick="sync('delete_error'); return false;"><i class="material-icons">refresh</i>Try Syncing Again</a>
{% endset %}
{% call macros.errorMessage("Couldn't delete old backups in Home Assistant", "delete_error", "delete_forever", delete_error_actions) %}
<p>
The add-on ran into an error (HTTP 400) trying to delete your oldest backup. This can happen
sometimes (especially if you just installed the add-on) when Home Assistant's cache of your existing backups
is out of sync. The add-on can't continue until this is resolved.
</p>
<p>To resolve this, you can try:</p>
<ul class="browser-default">
<li>
Refreshing your backups by going to the "Backups" page in Home Assistant and clicking the refresh icon in the top right.
</li>
<li>Restarting the machine Home Assistant runs on.</li>
</ul>
<p>
The add-on will retry periodically to check if the issue is resolved. To retry now, click "Try
Syncing Again" below. If the above doesn't help, please file an issue on <a onclick="bugReport()">GitHub</a>
to get more help.
</p>
{% endcall %}
{% set google_dns_actions %}
<a class="btn-flat" href="#" onclick="sync('google_dns'); return false;"><i class="material-icons">refresh</i>Try Syncing Again</a>
{% endset %}
{% call macros.errorMessage("Couldn't Reach Google Drive (DNS error)", "google_dns", "cloud_off", google_dns_actions) %}
<p>
The add-on couldn't resolve Google Drive's server address. Backups can't be uploaded
without being able to reach Google Drive.
</p>
<p>Please ensure:</p>
<ul class="browser-default">
<li>The internet is working.</li>
<li>
The server www.googleapis.com is reachable from your Home Assistant
machine. Some ad-blocking and firewall software (e.g. Ad Guard, Pi-hole, etc) can block this
addresses.
</li>
</ul>
<div style="margin-top: -10px; margin-bottom: 15px;">
<a onclick="toggleLinkSlide(this, 'cant_reach_google_dns');" href="#!"><i class="material-icons"
style="padding-right: 10px; vertical-align: bottom;">arrow_drop_down</i> Show DNS info</a>
<div id="cant_reach_google_dns" class="default-hidden" style="padding: 10px; font-family: monospace;">
<p>
Below is the list of names and IP Addresses Google's API servers resolve to. Next to each is
the result from an attempt the add-on has made to ping the address. This may help in
determining if something in your network is blocking access to one of Google's API servers,
or if Google's API servers are having problems.
</p>
<div class="dns_info row"></div>
<div class="divider"></div>
</div>
</div>
<p>You can also try restarting this add-on, the supervisor, or Home Assistant.</p>
{% endcall %}
{% set google_timeout_actions %}
<a class="btn-flat" href="#" onclick="sync('google_timeout'); return false;"><i class="material-icons">refresh</i>Try Syncing Again</a>
{% endset %}
{% call macros.errorMessage("Google Drive Connection Timeout", "google_timeout", "cloud_off", google_timeout_actions) %}
<p>
The connection timed out or was closed unexpectedly while trying to communicate with Google Drive.
Backups can't be uploaded without being able to reach Google Drive reliably. This can happen transiently from time to
time and the addon will continue to attempt to sync your backups. This problem almost always resolves itself after some time.
</p>
<p>Please ensure:</p>
<ul class="browser-default">
<li>The internet is working.</li>
<li>
The server www.googleapis.com are reachable from your Home Assistant
machine. Some ad-blocking and
firewall software (e.g. Ad Guard, Pi-hole, etc) can block or throttle traffic to this address.
</li>
</ul>
<div style="margin-top: -10px; margin-bottom: 15px;">
<a onclick="toggleLinkSlide(this, 'cant_reach_google_dns_timeout');" href="#!">
<i class="material-icons" style="padding-right: 10px; vertical-align: bottom;">arrow_drop_down</i>
Show DNS info
</a>
<div id="cant_reach_google_dns_timeout" class="default-hidden" style="padding: 10px; font-family: monospace;">
<p>
Below is the list of names and IP Addresses Google's API servers resolve to. Next to each is
the result from an attempt the add-on has made to ping the address. This may help in
determining if something in your network is blocking access to one of Google's API servers,
or if Google's API servers are having problems.
</p>
<div class="dns_info row"></div>
<div class="divider"></div>
</div>
</div>
<p>You can also try syncing again or restarting this add-on, the supervisor, or Home Assistant.</p>
{% endcall %}
{% set google_cant_connect_actions %}
<a class="btn-flat" href="#" onclick="sync('google_cant_connect'); return false;"><i class="material-icons">refresh</i>Try Syncing Again</a>
{% endset %}
{% call macros.errorMessage("Can't Connect to Google Drive", "google_cant_connect", "cloud_off", google_cant_connect_actions) %}
<p>
Google Drive refused an attempt to connect with it. Backups can't be uploaded without being able
to reach Google Drive.
</p>
<p>Please ensure:</p>
<ul class="browser-default">
<li>The internet is working.</li>
<li>
The server www.googleapis.com are reachable from your Home Assistant
machine. Some ad-blocking and
firewall software (eg Ad Guard, pi-hole, etc) can block or throttle traffic to this address.
</li>
</ul>
<div style="margin-top: -10px; margin-bottom: 15px;">
<a onclick="toggleLinkSlide(this, 'cant_connect_google_dns');" href="#!">
<i class="material-icons" style="padding-right: 10px; vertical-align: bottom;">arrow_drop_down</i>
Show DNS info
</a>
<div id="cant_connect_google_dns" class="default-hidden" style="padding: 10px; font-family: monospace;">
<p>
Below is the list of names and IP Addresses Google's API servers resolve to. Next to each is
the result from an attempt the add-on has made to ping the address. This may help in
determining if something in your network is blocking access to one of Google's API servers,
or if Google's API servers are having problems.
</p>
<div class="dns_info row"></div>
<div class="divider"></div>
</div>
</div>
<p>You can also try syncing again or restarting this add-on, the supervisor, or Home Assistant.</p>
{% endcall %}
{% set password_key_invalid_actions %}
<a class="btn-flat" href="#" onclick="sync('password_key_invalid'); return false;"><i class="material-icons">refresh</i>Try Syncing Again</a>
{% endset %}
{% call macros.errorMessage("Invalid Backup Password", "password_key_invalid", "vpn_key", password_key_invalid_actions) %}
<p>
The add-on couldn't find your backup password in your secrets.yaml file. Backups can't be created
until you either configure a valid password or remove it.
</p>
<p>This can happen if:</p>
<ul class="browser-default">
<li>Your secrets.yaml file isn't present in your Home Assistant configuration directory</li>
<li>The password key isn't present in your secrets.yaml file.</li>
</ul>
<p>
Please review your <a href="#!" onclick="loadSettings()"><i class="material-icons"
style="display: inline; font-size: 15px">settings</i> add-on settings</a>
to ensure you've set the backup password you intended, then sync again.
</p>
{% endcall %}
{% set backup_in_progress_actions %}
<a class="btn-flat" href="#" onclick="sync('backup_in_progress'); return false;"><i class="material-icons">refresh</i>Try Syncing Again</a>
{% endset %}
{% call macros.errorMessage("Backup Already in Progress", "backup_in_progress", "settings_backup_restore", backup_in_progress_actions) %}
<p>The add-on couldn't create a backup on schedule because one is already in progress. Home Assistant can
only create one backup at a time. This can happen if:</p>
<ul class="browser-default">
<li>The add-on was started while a backup was already in progress</li>
<li>A backup was started outside of the add-on, eg manually from the supervisor Backups page.</li>
</ul>
<p>Typically you don't need to do anything to resolve this, as the add-on will pick up again once the
backup in progress finishes.
If you are certain no backup is in progress already or this error sticks around, you can try
restarting the addon or Home Assistant's host machine.</p>
{% endcall %}
{% set multiple_deletes_actions %}
<a class="btn-flat" href="#" onclick="allowDeletion('false'); return false;"><i class="material-icons">delete</i>Allow Deletion</a>
<a class="btn-flat" href="#" onclick="allowDeletion('true'); return false;"><i class="material-icons">delete_forever</i>Never Ask again</a>
{% endset %}
{% call macros.errorMessage("Attempting to Delete Multiple Backups", "multiple_deletes", "delete_forever", multiple_deletes_actions) %}
<p>The add-on attempted to delete <span id="data_GoogleDrive">(?)</span> backup(s) from Google
Drive and <span id="data_HomeAssistant">(?)</span> backup(s) from Home Assistant.
Under normal circumstance, the add-on will only delete one backup at a time (the oldest).
This warning is produced to prevent the add-on from accidentally deleting your history of backups.
This is normal if you recently lowered the number of backups to keep in Google Drive or Home
Assistant.</p>
<p>To resolve this you can:</p>
<ul class="browser-default">
<li>
Review the <a href="#!" onclick="loadSettings()"><i class="material-icons" style="display: inline; font-size: 15px">settings</i> add-on settings</a>
and increase the number of backups you keep.
</li>
<li>Click "Allow Deletion" below to permit these deletions to happen this one time.</li>
<li>Click "Never ask again" below to permit the deletes this time and in the future (not recommended)
</li>
</ul>
{% endcall %}
{% set google_server_error_actions %}
<a class="btn-flat" href="#" onclick="sync('google_server_error'); return false;"><i class="material-icons">refresh</i>Try Syncing Again</a>
{% endset %}
{% call macros.errorMessage("Google Server Error", "google_server_error", "cloud_off", google_server_error_actions) %}
<p>Google returned a server error (HTTP code 500) while trying to backup your backups.</p>
<p>This happens sometimes for reasons beyond anyone's control and almost always the problem goes away on
its own. The add-on will attempt uploading again soon, but you can also click "Try Syncing Again"
below to make it retry now.</p>
<p>If this error keeps sticking around, you can try:</p>
<ul class="browser-default">
<li>Making sure the internet is working fine.</li>
<li>Making sure you're able to log into your <a href="https://drive.google.com" target="_blank" rel="noreferrer">Google Drive
account</a> and upload files without problems.</li>
<li>Re-authenticating the add-on with Google Drive (click <b>"ACTIONS"</b> -> <b>"Reauthorize Google Drive"</b> up top)
</li>
<li>Restarting the add-on, Home Assistant, or the supervisor.</li>
<li>Reinstalling the add-on.</li>
</ul>
{% endcall %}
{% set google_session_expired_actions %}
<a class="btn-flat" href="#" onclick="sync('google_session_expired'); return false;"><i class="material-icons">refresh</i>Try Syncing Again</a>
{% endset %}
{% call macros.errorMessage("Google Session Expired", "google_session_expired", "cloud_off", google_session_expired_actions) %}
<p>Google returned a server error (HTTP code 404) while trying to upload your backups.</p>
<p>In most cases, this indicates that your upload took too long to upload. According to Google's
documentation if an upload takes longer than a week then it will be forcibly cancelled and return this
error. This can happen if your internet is very slow or your backups are very large, however the
developer of this addon suspects this can happen in more common circumstances. If the below options don't
help, please file an issue on <a onclick="bugReport()">GitHub</a> with as much
detail as you can stomach to help him look into it.
</p>
<p>If this error keeps sticking around, you can try:</p>
<ul class="browser-default">
<li>Syncing again (if it hasn't already started) by clicking "Try Syncing Again" below.</li>
<li>Making sure you're able to log into your <a target="_blank" rel="noreferrer" href="https://drive.google.com">Google
Drive
account</a> and upload files without problems.</li>
<li>Ensuring your internet connection is reasonably fast by doing a <a target="_blank" rel="noreferrer"
href="https://www.google.com/search?q=speed+test">speed test</a></li>
<li>Making your backups smaller. Usually backups are large because of a huge sensor database. You
can keep a smaller history by setting up the <a target="_blank" rel="noreferrer"
href="https://www.home-assistant.io/components/recorder/">recorder component</a> purge settings in
Home Assistant</li>
<li>Restarting the add-on, Home Assistant, or the supervisor.</li>
<li>Reinstalling the add-on.</li>
</ul>
{% endcall %}
{% set existing_backup_folder_actions %}
<a class="btn-flat" href="#" onclick="resolvefolder('true')"><i class="material-icons">folder</i>Use the folder</a>
<a class="btn-flat" href="#" onclick="chooseBackupFolder()"><i class="material-icons">open_in_new</i>Choose a different folder</a>
<a class="btn-flat" href="#" onclick="resolvefolder('false')"><i class="material-icons">create_new_folder</i>Create a new folder</a>
{% endset %}
{% call macros.errorMessage("Existing Backup Folder Found", "existing_backup_folder", "folder_special", existing_backup_folder_actions) %}
<p>The add-on found a backup folder named "<b><span id="data_existing_name"></span></b>" in Google
Drive that might already have backups
in it. This is normal if you've used this addon before, but you'll need to decide where backups
should go before this will start backing up again. <a id="data_existing_url" target="_blank">Click
here</a> to see the folder contents in Google Drive.</p>
<p>To resolve this you can:</p>
<ul class="browser-default">
<li>"Use the folder" and merge in backups from this Home Assistant instance. If this
instance has newer backups, then some of the backups in Google Drive might get deleted to make
room for these newer ones (this depends on your settings). You probably want this option if you just
reinstalled the addon.</li>
<li>"Choose a different folder" to store backups in, ignoring the existing folder. You probably want
his option if you have multiple Home Assistant instances that should all back up to their
own folder.</li>
<li>Or "create a new folder" to automatically create a folder and ignore the existing one. The new folder will be created
at the root of your My Drive.</li>
</ul>
<p>Note: If you run multiple instance of Home Assistant with this addon using the same Google
Drive account,
its recommended that you use a different folder for each instance. Otherwise they'll delete
each other's
backups!</p>
{% endcall %}
{% set backup_folder_missing_actions %}
<a class="btn-flat" href="#" onclick="loadSettings()"><i class="material-icons">settings</i>Open settings</a>
{% endset %}
{% call macros.errorMessage("Please choose a backup folder", "backup_folder_missing", "folder_special", backup_folder_missing_actions) %}
<p class="hide-for-custom-creds">You've chosen to upload your backups to a folder of your choosing.
Click the link below to open a new window and select the folder in Google Drive you'd like to store
your backups in.</p>
<p class="hide-for-default-creds">You've chosen to upload your backups to a folder of your choosing.
Please navigate to the addon settings to specify a folder to store backups in.</p>
{{ chooseFolder(id="choose_folder_folder_missing") }}
<ul class="browser-default">
<li>
<p>If you'd rather not specify the folder for uploads, please return to your <a href="#!"
onclick="loadSettings()"><i class="material-icons"
style="display: inline; font-size: 15px">settings</i> add-on
settings</a> and change your preference.</p>
</li>
<li>
Your backups in Home Assistant will be re-uploaded to the new folder. If you already have them in
an existing folder in Google Drive and you'd like to avoid this, please move them in manually before
selecting the folder.
</li>
<li>
<p>If you run multiple instances of
Home Assistant with this addon using the same Google Drive account, its recommended that you use a
different folder
for each instance. Otherwise they'll delete each other's backups!</p>
</li>
</ul>
<p class="hide-for-custom-creds"><b>Note:</b> Google's authentication mechanisms require you to
select your folder through an external domain, which is where the "Choose Folder" link will take you.
<a href="https://github.com/sabeechen/hassio-google-drive-backup/blob/master/hassio-google-drive-backup/AUTHENTICATION.md"
target="_blank" rel="noreferrer">See here</a> for details if you'd like to know more about why.</p>
{% endcall %}
{% set backup_folder_inaccessible_actions %}
<a class="btn-flat" href="#" onclick="sync('backup_folder_inaccessible')"><i class="material-icons">refresh</i>Try syncing again</a>
{% endset %}
{% call macros.errorMessage("Couldn't use your backup folder", "backup_folder_inaccessible", "sync_problem", backup_folder_inaccessible_actions) %}
<p>The addon couldn't use the folder you chose for storing backups. The addon can't backup your
backups until you resolve this. This can happen if:
<ul class="browser-default">
<li>The folder was deleted</li>
<li>You lost permissions on the folder</li>
<li>You gave the addon an invalid folder ID</li>
<li>You signed in with drive using different credentials</li>
<li>Something else made the folder unreadable</li>
</ul>
</p>
<p>
To resolve this, you can:
</p>
<ul class="browser-default">
<li class="hide-for-custom-creds">
<p>Use the link below to choose a new folder to store backups in.
{{ chooseFolder(id="choose_folder_folder_inaccessible") }}
</p>
</li>
<li class="hide-for-default-creds">
<p>Open your <a href="#!" onclick="loadSettings()"><i class="material-icons"
style="display: inline; font-size: 15px">settings</i> add-on
settings</a> and, enter a valid "Backup Folder Id".</p>
</li>
<li>
<p>Open your <a href="#!" onclick="loadSettings()"><i class="material-icons"
style="display: inline; font-size: 15px">settings</i> add-on
settings</a> and deselect "Manually specify the backup folder". The addon will automatically
create a new folder for you.</p>
</li>
<li>
<p>If the folder still exists, you can restore access to it by the addon by ensuring that your
google account has access to it. The folder should be accessible from <a id="data_existing_url"
target="_blank" rel="noreferrer">this link</a>. After doing so, click "sync now" below to try syncing again.</p>
</li>
</ul>
<p class="hide-for-custom-creds"><b>Note:</b> Google's authentication mechanisms require you to select
your folder through an
external domain, which is where the "choose folder" link below will take you.
<a href="https://github.com/sabeechen/hassio-google-drive-backup/blob/master/hassio-google-drive-backup/AUTHENTICATION.md"
target="_blank" rel="noreferrer">See here</a> for details if you'd like to know more about why.</p>
{% endcall %}
{% set cancelled_actions %}
<a class="btn-flat" href="#" onclick="sync('cancelled')"><i class="material-icons">play_arrow</i>Resume</a>
{% endset %}
{% call macros.errorMessage("Backup is Paused", "cancelled", "pause", cancelled_actions) %}
<p>Sync was cancelled because you requested it. The addon will not resume syncing for another hour unless you take action.</p>
<br />
<p>
Please note:
</p>
<ul class="browser-default">
<li>
<p>A pending backup can't be stopped once its started, because its creation is managed by Home Assistant. Being "Paused" only stops the addon from creating new backups and syncing them with Google Drive.</p>
</li>
<li>
<p>You can resume by clicking the link below or just waiting.</p>
</li>
</ul>
{% endcall %}
{% set token_refresh_google_error_actions %}
<a class="btn-flat" href="#" onclick="sync('token_refresh_google_error')"><i class="material-icons">refresh</i>try syncing again</a>
{% endset %}
{% call macros.errorMessage("Couldn't Refresh Google Drive Credentials", "token_refresh_google_error", "error", token_refresh_google_error_actions) %}
<p>
The addon was unable to refresh your credentials with Google Drive. When it tried, Google said:
</p>
<pre>
<span id="data_from_google">(?)</span>
</pre>
<p>This can happen if:</p>
<ul class="browser-default">
<li>The domain <a target="_blank" href="https://habackup.io">https://habackup.io</a>, which grants your credentials from Google Drive (<a target="_blank" href="https://github.com/sabeechen/hassio-google-drive-backup/blob/master/hassio-google-drive-backup/AUTHENTICATION.md">click here</a> to see why), is having trouble communicating with Google Drive.</li>
<li>Google Drive is experiencing a temporary outage.</li>
<li>Something else has gone wrong with your Google Drive credentials.</li>
</ul>
<p>Usually this problem will resolve on its own, but you can try:</p>
<ul class="browser-default">
<li>Syncing again with the button below. The addon will retry on its own periodically.</li>
<li><a href="reauthenticate">Re-authenticating</a> this add-on with Google
again. Its unlikely this will resolve the problem, but it also won't cause you to lose any of your data.</li>
<li>If the problem persists, <a onclick="bugReport()">file an issue on GitHub</a> to let the developer of this addon know about it.</li>
</ul>
{% endcall %}
{% set token_refresh_my_error_actions %}
<a class="btn-flat" href="#" onclick="sync('token_refresh_my_error')"><i class="material-icons">refresh</i>try syncing again</a>
{% endset %}
{% call macros.errorMessage("Couldn't Refresh Google Drive Credentials", "token_refresh_my_error", "error", token_refresh_my_error_actions) %}
<p>
The addon was unable to refresh your credentials with Google Drive. When it tried, https://habackup.io said:
</p>
<pre>
<span id="data_reason">(?)</span>
</pre>
<p>This can happen if:</p>
<ul class="browser-default">
<li>The domain <a target="_blank" href="https://habackup.io">https://habackup.io</a>, which grants your credentials from Google Drive (<a target="_blank" href="https://github.com/sabeechen/hassio-google-drive-backup/blob/master/hassio-google-drive-backup/AUTHENTICATION.md">click here</a> to see why), is having some unexpected problems.</li>
<li>Something else has gone wrong with your Google Drive credentials.</li>
</ul>
<p>Usually this problem will resolve on its own, but you can try:</p>
<ul class="browser-default">
<li>Syncing again with the button below. The addon will retry on its own periodically.</li>
<li><a href="reauthenticate">Re-authenticating</a> this add-on with Google
again. Its unlikely this will resolve the problem, but it also won't cause you to lose any of your data.</li>
<li>If the problem persists, <a onclick="bugReport()">file an issue on GitHub</a> to let the developer of this addon know about it.</li>
</ul>
{% endcall %}
{% set supervisor_connection_actions %}
<a class="btn-flat" href="#" onclick="sync('supervisor_connection')"><i class="material-icons">refresh</i>try syncing again</a>
{% endset %}
{% call macros.errorMessage("Unable to Reach Home Assistant Supervisor", "supervisor_connection", "error", supervisor_connection_actions) %}
<p>
The addon was unable to connect to the Home Assistant Supervisor. This addon requires the supervisor to be running to create
backups, load configuration, etc. Without the supervisor the addon will not be able to do anything useful. Typically this
should never happen, and indicates that something is wrong with your Home Assistant installation.
</p>
<p>To resolve this you can try:</p>
<ul class="browser-default">
<li>Syncing again with the button below. The addon will retry on its own periodically.</li>
<li>Restarting the machine that Home Assistant runs on. Often this resolves transient problems the Home Assistant Supervisor might have.</li>
<li>If the problem persists, <a href="#!" onclick="bugReport()">file an issue on GitHub</a> to let the developer of this addon know about it.</li>
</ul>
{% endcall %}
{% set supervisor_permission_actions %}
<a class="btn-flat" href="#" onclick="sync('supervisor_connection')"><i class="material-icons">refresh</i>try syncing again</a>
{% endset %}
{% call macros.errorMessage("Unable to Talk to the Home Assistant Supervisor", "supervisor_permission", "error", supervisor_permission_actions) %}
<p>
The addon was unable to communicate with the Supervisor because it said the addon is unauthorized. This addon
must communicate with the supervisor to create backups, load configuration, etc. Without the supervisor the
addon will not be able to do anything useful. Typically this should never happen, and indicates that something
is wrong with your Home Assistant installation.
</p>
<ul class="browser-default">
<li>Syncing again with the button below. The addon will also retry on its own periodically.</li>
<li>Restarting the addon, the supervisor, or the whole machine Home Assistant is running on. Sometimes the supervisor
can get into a bad state and this works around it.</li>
<li>Checking the Supervisor's logs ("Supervisor" > "System" > "Logs") to see if any errors might indicate what the problem is.</li>
<li>If the problem persists, <a onclick="bugReport()">file an issue on GitHub</a> to let the developer of this addon know about it. He might be able to help.</li>
</ul>
{% endcall %}
{% set supervisor_timeout_actions %}
<a class="btn-flat" href="#" onclick="sync('supervisor_timeout')"><i class="material-icons">refresh</i>try syncing again</a>
{% endset %}
{% call macros.errorMessage("A Request to the Supervisor Timed Out", "supervisor_timeout", "error", supervisor_timeout_actions) %}
<p>
The addon was unable to communicate with the Supervisor because it timed out. This addon
must communicate with the supervisor to create backups, load configuration, etc. Without the supervisor the
addon will not be able to do anything useful. This usually resolves itself after the addon retries, but if
you see this error stick around for a while you can try
</p>
<ul class="browser-default">
<li>Syncing again with the button below. The addon will also retry on its own periodically.</li>
<li>Restarting the addon, the supervisor, or the whole machine Home Assistant is running on. Sometimes the supervisor
can get into a bad state and this works around it.</li>
<li>If the problem persists, <a onclick="bugReport()">file an issue on GitHub</a> to let the developer of this addon know about it. He might be able to help.</li>
</ul>
{% endcall %}
{% set supervisor_unexpected_actions %}
<a class="btn-flat" href="#" onclick="sync('supervisor_unexpected')"><i class="material-icons">refresh</i>try syncing again</a>
{% endset %}
{% call macros.errorMessage("The Supervisor Returned an Unexpected Response", "supervisor_unexpected", "error", supervisor_unexpected_actions) %}
<p>
The addon was unable to communicate with the Supervisor because it gave back an unreadable response.
This addon must communicate with the supervisor to create backups, load configuration, etc. Without
the supervisor the addon will not be able to do anything useful. This usually
resolves itself after the addon retries, but if you see this error stick around for a while you can try:
</p>
<ul class="browser-default">
<li>Syncing again with the button below. The addon will also retry on its own periodically.</li>
<li>Restarting the addon, the supervisor, or the whole machine Home Assistant is running on. Sometimes the supervisor
can get into a bad state and this works around it.</li>
<li>If the problem persists, <a onclick="bugReport()">file an issue on GitHub</a> to let the developer of this addon know about it. He might be able to help.</li>
</ul>
{% endcall %}
{% set supervisor_fs_error_actions %}
<a class="btn-flat" href="#" onclick="sync('supervisor_unexpected')"><i class="material-icons">refresh</i>try syncing again</a>
{% endset %}
{% call macros.errorMessage("Unable to write to the file system", "supervisor_fs_error", "error", supervisor_fs_error_actions) %}
<p>
The addon was unable to write to the file system on this machine. Normally this indicates there is a problem
with the Home Assistant supervisor, which is responsible for creating the file system this addon needs to function. Here are some reasons this might happen:
</p>
<ul class="browser-default">
<li>You recently upgraded Home Assistant. Sometimes during an upgrade, the docker environment Home Assistant creates for the addon can get messed up.
If this is the problem, then restarting the host machine where Home assistant is running will usually resolve it.</li>
<li>Your machine may have run out of space. Home Assistant will sometimes make the file system read-only to protect itself if it is close to running out of space, causing this error. You
should be able to see an estimate of how much space is remaining down below on this page or by going to "Supervisor" > "System" in the Home Assistant UI.</li>
<li>If none of the above helps and the problem persists, <a onclick="bugReport()">file an issue on GitHub</a> to let the developer of this addon know about it. He might be able to help.</li>
</ul>
{% endcall %}
{% set google_unexpected_actions %}
<a class="btn-flat" href="#" onclick="sync('supervisor_unexpected')"><i class="material-icons">refresh</i>try syncing again</a>
{% endset %}
{% call macros.errorMessage("Google drive Returned an Unexpected Response", "google_unexpected", "error", google_unexpected_actions) %}
<p>
The addon was unable to communicate with Google Drive because it gave back an unreadable response.
Without being able to communicate with Google Drive, the addon will not be able to transfer backups to the cloud.
This usually resolves itself after the addon retries, but if you see this error stick around for a while you can try:
</p>
<ul class="browser-default">
<li>Syncing again with the button below. The addon will also retry on its own periodically.</li>
<li>Restarting the addon, the supervisor, or the whole machine Home Assistant is running on.</li>
<li>If the problem persists, <a onclick="bugReport()">file an issue on GitHub</a> to let the developer of this addon know about it. He might be able to help.</li>
</ul>
{% endcall %}
{% set log_in_to_drive_actions %}
<a class="btn-flat" href="#" onclick="sync('backup_folder_inaccessible')"><i class="material-icons">refresh</i>try syncing again</a>
{% endset %}
{% call macros.errorMessage("Please log in to Google Drive Manually", "log_in_to_drive", "account_circle", log_in_to_drive_actions) %}
<p>The addon couldn't backup your backups because it looks like it you've never logged in
to the <a href="https://drive.google.com" target="_blank">Google Drive website</a> before. Doing this at
least once is required for applications like this to use Google Drive.
</p>
<p>
To resolve this, you can:
</p>
<ul class="browser-default">
<li>
<p>Visit <a href="https://drive.google.com" target="_blank">https://drive.google.com</a> and log-in with
the account you used to connect this addon to Google Drive, then click "SYNC NOW" below to start syncing.</p>
</li>
<li>
<p><a href="reauthenticate">Reauthorize</a> the addon with Google Drive using a different account.</p>
</li>
</ul>
{% endcall %}
{% set error_details_card_actions %}
<a class="btn-flat" target="_blank" onClick="$('#error_details_card').fadeOut(500)"><i class="material-icons">close</i>Close</a>
{% endset %}
{% call macros.errorMessage("Error details", "error_details_card", "error", error_details_card_actions) %}
<p id="error_details_paragraph" style="white-space: pre-wrap;font-family: monospace;"></p>
{% endcall %}
@@ -0,0 +1,40 @@
<footer class="page-footer">
<div class="container valign-wrapper">
<span style="margin-right: 5px;">
<b>© 2022 Stephen Beechen</b>
<span class="hide-on-small-only"> <span class="hide-on-med-only">with love from <a href="https://www.openstreetmap.org/search?query=Longmont%2C Colorado" target="_blank">Colorado</a></span> - </span>
<br class="hide-on-med-and-up">
If you like this project, keep it going and
</span>
<a class="bmc-button" target="_blank" rel="noreferrer" href="https://www.buymeacoffee.com/sabeechen" alt="Support me">
<svg width="24px" height="36px" viewBox="0 0 24 36" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="Group-2" transform="translate(-17.000000, -7.000000)">
<g id="Logo">
<g id="Group-4" transform="translate(17.000000, 8.000000)">
<g id="Group">
<g id="Logo" transform="translate(0.559947, 0.000000)">
<polygon class="bmc-outline" id="Fill-1" points="11.2752139 6.65517263 2.53594776 6.60250871 6.82755165 33.7158335 7.76390159 33.7158335 16.3471094 33.7158335 17.2834593 33.7158335 21.5750632 6.60250871"></polygon>
<polygon class="bmc-cup" id="Fill-2" points="11.2752139 6.65517263 2.53594776 6.60250871 6.82755165 33.7158335 7.76390159 33.7158335 14.1622929 33.7158335 15.0986428 33.7158335 19.3902467 6.60250871"></polygon>
<polygon id="Fill-3" fill="#FFFFFF" points="0.0390145809 6.60252433 22.5894423 6.60252433 22.5894423 4.10216009 0.0390145809 4.10216009"></polygon>
<polygon id="Stroke-4" stroke="#000000" stroke-width="1.17043743" points="0.0390145809 6.60252433 22.5894423 6.60252433 22.5894423 4.10216009 0.0390145809 4.10216009"></polygon>
<polygon id="Fill-6" fill="#FFFFFF" points="18.2198093 0.0390681913 12.8357971 0.0390681913 9.63660147 0.0390681913 4.25258931 0.0390681913 2.61397692 3.78961456 9.63660147 3.78961456 12.8357971 3.78961456 19.8584217 3.78961456"></polygon>
<g id="Group-11" transform="translate(0.936350, 0.000000)" stroke-width="1.17043743">
<polygon id="Stroke-7" stroke="#050505" points="17.2834593 0.0390681913 11.8994472 0.0390681913 8.70025153 0.0390681913 3.31623937 0.0390681913 1.67762698 3.78961456 8.70025153 3.78961456 11.8994472 3.78961456 18.9220717 3.78961456"></polygon>
<polygon id="Stroke-9" stroke="#000000" points="10.3388639 6.65517263 0.0390145809 6.60250871 4.33061848 33.7158335 5.26696842 33.7158335 15.4107594 33.7158335 16.3471094 33.7158335 20.6387133 6.60250871"></polygon>
</g>
<polygon id="Fill-12" fill="#FFFFFF" points="21.8871799 14.2598898 11.6059795 14.2598898 10.8664191 14.2598898 0.585218713 14.2598898 2.50832543 25.0427106 11.2361993 24.9487126 19.9640731 25.0427106"></polygon>
<polygon id="Stroke-13" stroke="#000000" stroke-width="1.17043743" points="21.8871799 14.2598898 11.6059795 14.2598898 10.8664191 14.2598898 0.585218713 14.2598898 2.50832543 25.0427106 11.2361993 24.9487126 19.9640731 25.0427106"></polygon>
</g>
</g>
</g>
</g>
</g>
</g>
</svg>
<span>Buy me a coffee</span>
</a>
<a href="#!" class="modal-trigger" data-target="about_modal" style="margin-left: 5px"> + other ways</a>
</div>
</footer>
@@ -0,0 +1,80 @@
<div class="default-hidden">
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="24"
height="24" viewBox="0 0 24 24">
<symbol id="home-assistant" viewBox="0 0 24 24">
<path
d="M21.8,13H20V21H13V17.67L15.79,14.88L16.5,15C17.66,15 18.6,14.06 18.6,12.9C18.6,11.74 17.66,10.8 16.5,10.8A2.1,2.1 0 0,0 14.4,12.9L14.5,13.61L13,15.13V9.65C13.66,9.29 14.1,8.6 14.1,7.8A2.1,2.1 0 0,0 12,5.7A2.1,2.1 0 0,0 9.9,7.8C9.9,8.6 10.34,9.29 11,9.65V15.13L9.5,13.61L9.6,12.9A2.1,2.1 0 0,0 7.5,10.8A2.1,2.1 0 0,0 5.4,12.9A2.1,2.1 0 0,0 7.5,15L8.21,14.88L11,17.67V21H4V13H2.25C1.83,13 1.42,13 1.42,12.79C1.43,12.57 1.85,12.15 2.28,11.72L11,3C11.33,2.67 11.67,2.33 12,2.33C12.33,2.33 12.67,2.67 13,3L17,7V6H19V9L21.78,11.78C22.18,12.18 22.59,12.59 22.6,12.8C22.6,13 22.2,13 21.8,13M7.5,12A0.9,0.9 0 0,1 8.4,12.9A0.9,0.9 0 0,1 7.5,13.8A0.9,0.9 0 0,1 6.6,12.9A0.9,0.9 0 0,1 7.5,12M16.5,12C17,12 17.4,12.4 17.4,12.9C17.4,13.4 17,13.8 16.5,13.8A0.9,0.9 0 0,1 15.6,12.9A0.9,0.9 0 0,1 16.5,12M12,6.9C12.5,6.9 12.9,7.3 12.9,7.8C12.9,8.3 12.5,8.7 12,8.7C11.5,8.7 11.1,8.3 11.1,7.8C11.1,7.3 11.5,6.9 12,6.9Z" />
</symbol>
</svg>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="24"
height="24" viewBox="0 0 24 24">
<symbol id="google-drive" viewBox="0 0 24 24">
<path
d="M7.71,3.5L1.15,15L4.58,21L11.13,9.5M9.73,15L6.3,21H19.42L22.85,15M22.28,14L15.42,2H8.58L8.57,2L15.43,14H22.28Z" />
</symbol>
</svg>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="24"
height="24" viewBox="0 0 24 24">
<symbol id="folder-home">
<path
d="M20 6H12L10 4H4A2 2 0 0 0 2 6V18A2 2 0 0 0 4 20H20A2 2 0 0 0 22 18V8A2 2 0 0 0 20 6M20 18H4V8H20M13 17V14H15V17H17V13H19L14 9L9 13H11V17Z" />
</symbol>
</svg>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="24"
height="24" viewBox="0 0 24 24">
<symbol id="folder-key">
<path
d="M20 18H4V8H20M20 6H12L10 4H4C2.89 4 2 4.89 2 6V18C2 19.1 2.9 20 4 20H20C21.1 20 22 19.1 22 18V8C22 6.89 21.1 6 20 6M13.83 12C13.42 10.84 12.31 10 11 10C9.34 10 8 11.34 8 13S9.34 16 11 16C12.31 16 13.42 15.17 13.83 14H16V16H18V14H19V12H13.83M11 14C10.45 14 10 13.56 10 13C10 12.45 10.45 12 11 12S12 12.45 12 13 11.55 14 11 14Z" />
</symbol>
</svg>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="24"
height="24" viewBox="0 0 24 24">
<symbol id="folder-music">
<path
d="M20 18V8H4V18H20M20 6C21.1 6 22 6.89 22 8V18C22 19.1 21.1 20 20 20H4C2.89 20 2 19.1 2 18L2 6C2 4.89 2.89 4 4 4H10L12 6H20M18 9V11H16V15C16 16.11 15.11 17 14 17C12.9 17 12 16.11 12 15C12 13.9 12.9 13 14 13C14.17 13 14.34 13 14.5 13.06V9H18Z" />
</symbol>
</svg>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="24"
height="24" viewBox="0 0 24 24">
<symbol id="folder-plus">
<path
d="M12 12H14V10H16V12H18V14H16V16H14V14H12V12M22 8V18C22 19.11 21.11 20 20 20H4C2.89 20 2 19.11 2 18V6C2 4.89 2.89 4 4 4H10L12 6H20C21.11 6 22 6.89 22 8M20 8H4V18H20V8Z" />
</symbol>
</svg>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="24"
height="24" viewBox="0 0 24 24">
<symbol id="folder-pound">
<path
d="M20,18H4V8H20M20,6H12L10,4H4C2.89,4 2,4.89 2,6V18A2,2 0 0,0 4,20H20A2,2 0 0,0 22,18V8C22,6.89 21.1,6 20,6M19,12V11H17.5L18,9H17L16.5,11H14.5L15,9H14L13.5,11H12V12H13.25L12.75,14H11V15H12.5L12,17H13L13.5,15H15.5L15,17H16L16.5,15H18V14H16.75L17.25,12H19M15.75,14H13.75L14.25,12H16.25L15.75,14Z" />
</symbol>
</svg>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="24"
height="24" viewBox="0 0 24 24">
<symbol id="folder-text">
<path
d="M20,18H4V8H20M20,6H12L10,4H4A2,2 0 0,0 2,6V18A2,2 0 0,0 4,20H20A2,2 0 0,0 22,18V8A2,2 0 0,0 20,6M15,16H6V14H15V16M18,12H6V10H18V12Z" />
</symbol>
</svg>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="24"
height="24" viewBox="0 0 24 24">
<symbol id="folder-outline">
<path
d="M20,18H4V8H20M20,6H12L10,4H4C2.89,4 2,4.89 2,6V18A2,2 0 0,0 4,20H20A2,2 0 0,0 22,18V8C22,6.89 21.1,6 20,6Z" />
</symbol>
</svg>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="24"
height="24" viewBox="0 0 24 24">
<symbol id="folder-account">
<path
d="M15 14C16.33 14 19 14.67 19 16V17H11V16C11 14.67 13.67 14 15 14M15 13C16.11 13 17 12.11 17 11S16.11 9 15 9C13.9 9 13 9.89 13 11C13 12.11 13.9 13 15 13M22 8V18C22 19.11 21.11 20 20 20H4C2.9 20 2 19.11 2 18V6C2 4.89 2.9 4 4 4H10L12 6H20C21.11 6 22 6.9 22 8M20 8H4V18H20V8Z" />
</symbol>
</svg>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="24"
height="24" viewBox="0 0 24 24">
<symbol id="puzzle-outline">
<path
d="M22,13.5C22,15.26 20.7,16.72 19,16.96V20A2,2 0 0,1 17,22H13.2V21.7A2.7,2.7 0 0,0 10.5,19C9,19 7.8,20.21 7.8,21.7V22H4A2,2 0 0,1 2,20V16.2H2.3C3.79,16.2 5,15 5,13.5C5,12 3.79,10.8 2.3,10.8H2V7A2,2 0 0,1 4,5H7.04C7.28,3.3 8.74,2 10.5,2C12.26,2 13.72,3.3 13.96,5H17A2,2 0 0,1 19,7V10.04C20.7,10.28 22,11.74 22,13.5M17,15H18.5A1.5,1.5 0 0,0 20,13.5A1.5,1.5 0 0,0 18.5,12H17V7H12V5.5A1.5,1.5 0 0,0 10.5,4A1.5,1.5 0 0,0 9,5.5V7H4V9.12C5.76,9.8 7,11.5 7,13.5C7,15.5 5.75,17.2 4,17.88V20H6.12C6.8,18.25 8.5,17 10.5,17C12.5,17 14.2,18.25 14.88,20H17V15Z" />
</symbol>
</svg>
</div>
@@ -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>
@@ -0,0 +1,129 @@
{% import "layouts/macros.jinja2" as macros %}
{% if coordEnabled %}
{% include 'layouts/partials/modals/backup/delete.jinja2' %}
{% include 'layouts/partials/modals/backup/new.jinja2' %}
{% include 'layouts/partials/modals/backup/upload.jinja2' %}
{% include 'layouts/partials/modals/backup/details.jinja2' %}
{% endif %}
{% include 'layouts/partials/modals/settings.jinja2' %}
{% include 'layouts/partials/modals/help.jinja2' %}
{% include 'layouts/partials/modals/bug.jinja2' %}
{% include 'layouts/partials/modals/about.jinja2' %}
{% include 'layouts/partials/modals/about.jinja2' %}
{% include 'layouts/partials/modals/crypto_donate.jinja2' %}
{% if coordEnabled %}
<script type="text/javascript">
$(document).ready(function () {
$('#deletemodal').modal();
$('#backupmodal').modal();
$('#uploadmodal').modal();
$('#details_modal').modal();
});
</script>
{% endif %}
<script type="text/javascript">
$(document).ready(function () {
$('#settings_modal').modal({
dismissible: false
});
$('#help_modal').modal();
$('#bug_modal').modal();
$('#details_modal').modal();
$('.tooltipped').tooltip();
$('.collapsible').collapsible();
$('.sidenav').sidenav();
$('.fixed-action-btn').floatingActionButton();
const dropdownTriggers = document.querySelectorAll('.dropdown-trigger');
const instances = M.Dropdown.init(dropdownTriggers, { 'constrainWidth': false });
});
</script>
{% macro actionMenu(id) %}
<ul id="{{ id }}" class="dropdown-content">
{% if coordEnabled %}
{% if showOpenDriveLink %}
<li>
<a onclick="newBackupClick(); return false;" style="cursor: pointer">
<i class="material-icons left">add</i>Backup Now
</a>
</li>
<li>
<a onclick="sync('error_card')" style="cursor: pointer"><i class="material-icons left">autorenew</i>Sync Now</a>
</li>
<li class="open_drive_menu default-hidden" ><a class="open_drive_link" target="_blank"><i class="material-icons">cloud</i>Open Drive Backups</a></li>
{% endif %}
<li><a href="reauthenticate"><i class="material-icons">vpn_key</i>Reauthorize Google Drive</a></li>
{% endif %}
<li><a onclick="bugReport()" style="cursor: pointer"><i class="material-icons">bug_report</i>Report a bug</a></li>
<li><a href="#!" class="modal-trigger" data-target="about_modal"><i class="material-icons">info</i>About</a></li>
</ul>
{% endmacro %}
{% call macros.header(version, navBarTitle) %}
<a href="#" data-target="mobile-menu" class="sidenav-trigger non-ingress">
<i class="material-icons">menu</i>
</a>
<ul class="right">
<li>
<a href="#!" onClick="loadSettings()">
<i class="material-icons" style="display: inline; margin-right: 5px; vertical-align: bottom">settings</i>
Settings
</a>
</li>
<li>
<a href="log?format=view" target="_blank" rel="noreferrer">
<i class="material-icons" style="display: inline; margin-right: 5px; vertical-align: bottom">code</i>
Logs
</a>
</li>
<li>
<a href="#!" class="modal-trigger" data-target="help_modal">
<i class="material-icons" style="display: inline; margin-right: 5px; vertical-align: bottom">help</i>
Get Help
</a>
</li>
<li>
{{ actionMenu(id="main_menu_dropdown") }}
<a class="dropdown-trigger" href="#!" data-target="main_menu_dropdown">
Actions <i class="material-icons right">arrow_drop_down</i>
</a>
</li>
</ul>
{% endcall %}
<ul class="sidenav non-ingress" id="mobile-menu">
<div class="sidenav-header">
<img src="static/{{ version }}/images/logo.png" width="120" />
</div>
<li><a href="#!" onClick="loadSettings()"><i class="material-icons">settings</i>Settings</a></li>
<li><a href="log?format=view" target="_blank" rel="noreferrer"><i class="material-icons">code</i>Logs</a></li>
{% if coordEnabled %}
{% if showOpenDriveLink %}
<li>
<a onclick="newBackupClick(); return false;" style="cursor: pointer">
<i class="material-icons left">add</i>Backup Now</a>
</li>
<li><a onclick="sync()" style="cursor: pointer"><i class="material-icons left">autorenew</i>Sync Now</a></li>
<li class="open_drive_menu"><a class="open_drive_link" target="_blank"><i class="material-icons">cloud</i>Open Drive Backups</a></li>
{% endif %}
<li><a href="reauthenticate"><i class="material-icons">vpn_key</i>Reauthorize Google Drive</a></li>
{% endif %}
<li><a href="#!" data-target="help_modal" class="modal-trigger"><i class="material-icons">help</i>Get Help</a></li>
<li><a onclick="bugReport()" style="cursor: pointer"><i class="material-icons">bug_report</i>Report a bug</a></li>
<li><a href="#!" class="modal-trigger" data-target="about_modal"><i class="material-icons">info</i>About</a></li>
</ul>
{% if coordEnabled %}
<div class="fixed-action-btn">
<a onclick="newBackupClick(); return false;" class="btn-floating btn-large tooltipped" data-position="left" data-tooltip="Backup Now">
<i class="large material-icons">add</i>
</a>
</div>
{% endif %}