New Addon
Google BK
This commit is contained in:
@@ -0,0 +1,390 @@
|
||||
{% extends "layouts/base.jinja2" %}
|
||||
|
||||
{% block head %}
|
||||
{{ super() }}
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<main>
|
||||
<div class="section no-pad-bot" id="index-banner">
|
||||
<div class="container">
|
||||
<br><br>
|
||||
<h1 class="header center">Get Started</h1>
|
||||
<div class="row center">
|
||||
<h5 class="header col s12 light">To begin, this add-on will need access to your Google Drive™.
|
||||
<span id="flavor_auto_folder">On your first backup it will create a new folder at the root of your <a
|
||||
href="https://drive.google.com/drive/my-drive" target="_blank" rel="noreferrer">My Drive</a> where future backups will be stored. Once
|
||||
its created, you can move the folder wherever you want.</span>
|
||||
<span id="flavor_specific_folder">After connecting with Google Drive, you'll be prompted to select your
|
||||
backup folder.</span>
|
||||
</h5>
|
||||
</div>
|
||||
<div>
|
||||
|
||||
</div>
|
||||
<div class="row center">
|
||||
<div id="option1" class="col m12 l6 offset-l3 center center-align">
|
||||
<div id="option1-step1">
|
||||
<div class="row center">
|
||||
<div class="col s12">
|
||||
<h6 id="what_do_next_now_please" style="display: none;" class="header light">
|
||||
<span id="what_do_next_now_please_text"></span>
|
||||
Change your <a href="#!" onclick="loadSettings()"><i class="material-icons"
|
||||
style="display: inline; margin-right: 2px; vertical-align: middle; font-size: 15px">settings</i>settings</a>
|
||||
to alter the add-on's behavior if that isn't what you want.
|
||||
</h6>
|
||||
</div>
|
||||
</div>
|
||||
<a target="_blank" id="authenticate-button" style="margin: 10px"
|
||||
class="btn-large btn-flat btn-high-vis">
|
||||
Authenticate with Google Drive
|
||||
</a>
|
||||
<div class="row center">
|
||||
<div class="col s12">
|
||||
<ul class="browser-default" style="text-align: left;">
|
||||
<li>
|
||||
Clicking this link authenticates you with an external domain maintained by the developer of this add-on (<a
|
||||
href="https://habackup.io">habackup.io</a>).
|
||||
</li>
|
||||
<li>You can <a href="https://github.com/sabeechen/hassio-google-drive-backup/blob/master/hassio-google-drive-backup/AUTHENTICATION.md" target="_blank" rel="noreferrer">read this</a> to understand what this means and why it's necessary.
|
||||
</li>
|
||||
<li>
|
||||
By doing this you must agree to the <a href="pp">Privacy Policy</a> and <a href="tos">Terms of Service</a></h6>
|
||||
</li>
|
||||
<li>
|
||||
The addon will only have access to the files and folders it creates. It can't see any of the other files you've added to Google Drive.</h6>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row center">
|
||||
<div class="col s12">
|
||||
|
||||
After you get the authorization code from the link above, paste the result here:
|
||||
<br/>
|
||||
<textarea id="google_credentials_box" style="height: 5rem;"></textarea>
|
||||
<br>
|
||||
<div class="left" style="display: none;" id="save_cred_message">
|
||||
<div class="cls-spinner left" id="save_cred_working">
|
||||
<div class="cls-signal"></div>
|
||||
</div>
|
||||
<div class="left center" style="display: flex;align-items: center;height: 52px;" id="save_cred_message"></div>
|
||||
</div>
|
||||
|
||||
<a target="_blank" class="right btn-large btn-flat btn-high-vis disabled"
|
||||
id="send_creds_button" onclick="send_creds($('#google_credentials_box').val())">Save</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row center">
|
||||
To generate your own private credentials with Google instead that doesn't use a third-party domain, <a href="#" onclick="doOption2();">click here</a>. Fair warning: that way is much more complicated.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="option2" class="col s12 m6 offset-m3 default-hidden">
|
||||
<div id="option2-step1">
|
||||
Follow the <a target="_blank" rel="noreferrer"
|
||||
href="https://github.com/sabeechen/hassio-google-drive-backup/blob/master/LOCAL_AUTH.md">instructions
|
||||
here</a> to get your own client ID and client secret from Google's Cloud Console. This is a much more complicated and time
|
||||
consuming approach but doesn't require you to authenticate yourself with an external domain.
|
||||
<div class="row">
|
||||
<form class="col s10" action="manualauth" method="get">
|
||||
<div class="row">
|
||||
<div class="input-field col s12">
|
||||
<i class="material-icons prefix">vpn_key</i>
|
||||
<input id="client_id" name="client_id" type="text" class="validate" />
|
||||
<label for="client_id">Client ID</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="input-field col s12">
|
||||
<i class="material-icons prefix">vpn_key</i>
|
||||
<input id="client_secret" name="client_secret" type="text" class="validate" />
|
||||
<label for="client_secret">Client Secret</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class=row>
|
||||
<div class="input-field col s12">
|
||||
<a target="_blank" class="right btn-large btn-flat btn-high-vis disabled right-align" style="position: absolute; right: 0px;"
|
||||
id="secret_button" onclick="getAuthUrl()">Next</a>
|
||||
<a target="_blank" class="right btn-large btn-flat btn-high-vis left-align" style="position: absolute; left: 30px;"
|
||||
id="secret_button" onclick="backOption2Step1()"><i class="material-icons">arrow_back</i>back</a>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row" style="display: none" id="option2-step2">
|
||||
<div class="col s12">
|
||||
<div class="row">
|
||||
<div class="col s12">
|
||||
Please navigate to <a id="custom-auth-link" target="_blank" rel="noreferrer"></a> and enter the code shown below to authorize the addon using your Google account. This page will automatically update once you do so.
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col s12">
|
||||
<span id="code-box" class="device-code-box"></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col s12">
|
||||
<div>
|
||||
Expires: <span id="device-code-valid-time-left"></span>
|
||||
</div>
|
||||
<div id="device-code-loading" class="progress">
|
||||
<div class="indeterminate"></div>
|
||||
</div>
|
||||
<div id="device-code-status-text">
|
||||
Loading...
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col s12">
|
||||
<a target="_blank" class="right btn-large btn-flat btn-high-vis left-align"
|
||||
id="secret_button" onclick="backOption2Step2()">Back</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<br><br>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
<script type="text/javascript">
|
||||
|
||||
function doOption2() {
|
||||
$('#option2').show();
|
||||
$('#option1').hide();
|
||||
}
|
||||
|
||||
function backOption2Step1() {
|
||||
$('#option2').hide();
|
||||
$('#option1').show();
|
||||
}
|
||||
function backOption2Step2() {
|
||||
$("#option2-step1").show();
|
||||
$("#option2-step2").hide();
|
||||
}
|
||||
|
||||
function submitCode() {
|
||||
var code = $("#code").val();
|
||||
if (!code || code.length == 0) {
|
||||
M.toast({ html: "Please enter the code you got form Google by clicking \"connect\" above" })
|
||||
}
|
||||
var url = "manualauth?code=" + code;
|
||||
$.get(url,
|
||||
function (data) {
|
||||
M.toast({ html: "Submitting authorization code..." })
|
||||
if (data.hasOwnProperty("auth_url")) {
|
||||
console.log("Redirecting to " + data.auth_url)
|
||||
window.location = data.auth_url
|
||||
} else if (data.hasOwnProperty("error")) {
|
||||
M.toast({ html: data.error })
|
||||
}
|
||||
}, "json")
|
||||
}
|
||||
|
||||
function updateHref() {
|
||||
var client_id = $("#client_id").val();
|
||||
var client_secret = $("#client_secret").val();
|
||||
if (client_id && client_id.length > 0 && client_secret && client_secret.length > 0) {
|
||||
$("#secret_button").removeClass('disabled');
|
||||
$("#help_text").fadeIn(400)
|
||||
$("#code_form").fadeIn(400)
|
||||
} else {
|
||||
$("#secret_button").addClass('disabled');
|
||||
}
|
||||
}
|
||||
|
||||
function updateCredsButton() {
|
||||
var text = $('#google_credentials_box').val()
|
||||
if (text.length > 0){
|
||||
$("#send_creds_button").removeClass('disabled');
|
||||
} else {
|
||||
$("#send_creds_button").addClass('disabled');
|
||||
}
|
||||
}
|
||||
|
||||
function send_creds(value) {
|
||||
path = URI("{{save_drive_creds_path}}").query({
|
||||
'creds': value,
|
||||
'host': getOutterHomeUri()
|
||||
});
|
||||
postJson(path.toString(), {}, function(data){
|
||||
window.location.assign(getInnerHomeUri());
|
||||
},
|
||||
function(data){
|
||||
M.toast({ html: "Couldn't save these credentials, did you copy the whole string?" })
|
||||
}, "Saving credentials")
|
||||
}
|
||||
|
||||
last_cred_version = -1;
|
||||
function reloadForNewCreds() {
|
||||
var jqxhr = $.get("getstatus", function (data) {
|
||||
last_data = data;
|
||||
if (data.is_custom_creds) {
|
||||
$(".hide-for-custom-creds").hide();
|
||||
$(".hide-for-default-creds").show();
|
||||
} else {
|
||||
$(".hide-for-custom-creds").show();
|
||||
$(".hide-for-default-creds").hide();
|
||||
}
|
||||
|
||||
if (data.is_specify_folder) {
|
||||
$("#flavor_auto_folder").hide();
|
||||
$("#flavor_specific_folder").show();
|
||||
} else {
|
||||
$("#flavor_auto_folder").show();
|
||||
$("#flavor_specific_folder").hide();
|
||||
}
|
||||
|
||||
if (data.hasOwnProperty("cred_version")) {
|
||||
if (last_cred_version == -1) {
|
||||
last_cred_version = data.cred_version;
|
||||
} else if (last_cred_version != data.cred_version) {
|
||||
//reload
|
||||
window.location.assign(getInnerHomeUri())
|
||||
} else {
|
||||
last_cred_version = data.cred_version;
|
||||
}
|
||||
}
|
||||
token_url = URI(getInnerHomeUri() + "{{ save_drive_creds_path }}");
|
||||
return_url = getOutterHomeUri()
|
||||
auth_url = URI(data.authenticate_url).query({
|
||||
'redirectbacktoken': encodeURIComponent(token_url.toString()),
|
||||
'version': encodeURIComponent("{{ version }}"),
|
||||
'return': encodeURIComponent(return_url),
|
||||
'bg': themeStyleContainer.dataset.backgroundColor,
|
||||
"ac": themeStyleContainer.dataset.accentColor,
|
||||
});
|
||||
document.getElementById("authenticate-button").href = auth_url.toString();
|
||||
|
||||
if (!data.firstSync) {
|
||||
backups = data.sources.HomeAssistant.backups;
|
||||
hasBackups = backups > 0;
|
||||
maxConfigured = data.sources.HomeAssistant.max;
|
||||
toDelete = Math.max(0, hasBackups - data.sources.HomeAssistant.max);
|
||||
if (data.sources.HomeAssistant.max == 0) {
|
||||
toDelete = 0;
|
||||
}
|
||||
willBackup = data.next_backup_text != "Disabled";
|
||||
willUpload = data.sources.GoogleDrive.max > 0;
|
||||
toUpload = Math.min(data.sources.GoogleDrive.max, backups - toDelete);
|
||||
text = "";
|
||||
if (!hasBackups && !willBackup) {
|
||||
text = "You have no backups in Home Assistant and you've configured this add-on not to create any."
|
||||
} else if (hasBackups && toDelete > 0) {
|
||||
text = "You have <b>" + backups + " backup(s)</b> in Home Assistant already. Once you authenticate with Google Drive the <b>" + toDelete + " oldest backup(s) will be deleted</b>";
|
||||
if (toUpload > 0) {
|
||||
text += " and the <b>" + toUpload + " newest backup(s) will get uploaded</b>."
|
||||
} else {
|
||||
text += "."
|
||||
}
|
||||
} else if (hasBackups && toUpload > 0) {
|
||||
text = "You have <b>" + backups + " backup(s)</b> in Home Assistant already. Once you authenticate with Google Drive the <b>" + toUpload + " newest backup(s) will get uploaded</b>."
|
||||
} else if (!hasBackups) {
|
||||
text = "You have <b>no backups in Home Assistant</b>";
|
||||
if (willUpload) {
|
||||
text += ", authenticate with Google Drive to start automatically creating and backing them up."
|
||||
} else {
|
||||
text += ", authenticate with Google Drive to start automatically creating them."
|
||||
}
|
||||
} else if (willBackup) {
|
||||
text = "You have <b>" + backups + " backup(s)</b> in Home Assistant already, authenticate with Google Drive to start creating new ones on a schedule."
|
||||
} else {
|
||||
text = "You have <b>" + backups + " backup(s)</b> in Home Assistant already but you haven't configured this add-on to do anything with them or create new ones."
|
||||
}
|
||||
$("#what_do_next_now_please_text").html(text);
|
||||
$("#what_do_next_now_please").show();
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
$(document).ready(function () {
|
||||
// Needed to make modal dialogs work.
|
||||
$('.modal').modal();
|
||||
$("#client_id").change(updateHref);
|
||||
$("#client_secret").change(updateHref);
|
||||
$("#client_secret").keyup(updateHref);
|
||||
$("#client_id").keyup(updateHref);
|
||||
$("#client_secret").mouseup(updateHref);
|
||||
$("#client_id").mouseup(updateHref);
|
||||
$("#google_credentials_box").change(updateCredsButton)
|
||||
$("#google_credentials_box").keyup(updateCredsButton);
|
||||
|
||||
window.setInterval(reloadForNewCreds, 5000);
|
||||
reloadForNewCreds();
|
||||
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
if (params.get("private")) {
|
||||
doOption2();
|
||||
}
|
||||
});
|
||||
|
||||
function sleep(ms) {
|
||||
return new Promise(resolve => setTimeout(resolve, ms));
|
||||
}
|
||||
|
||||
async function getAuthUrl() {
|
||||
var client_id = $("#client_id").val();
|
||||
var client_secret = $("#client_secret").val();
|
||||
if (!(client_id && client_id.length > 0 && client_secret && client_secret.length > 0)) {
|
||||
M.toast({ html: "Please enter a Client ID and Client Secret first" })
|
||||
}
|
||||
let toast = M.toast({ html: "Talking with Google..."})
|
||||
url = URI("manualauth").query({
|
||||
'client_id': client_id,
|
||||
'client_secret': client_secret});
|
||||
try {
|
||||
const resp = await fetch(url.toString());
|
||||
const data = await resp.json();
|
||||
if (resp.ok) {
|
||||
$("#custom-auth-link").attr("href", data.auth_url);
|
||||
$("#custom-auth-link").html(data.auth_url);
|
||||
$("#code-box").html(data.code);
|
||||
$("#code-hidden").val(data.code);
|
||||
$("#option2-step2").show();
|
||||
$("#option2-step1").hide();
|
||||
} else {
|
||||
toast.dismiss();
|
||||
M.toast({ html: data.message })
|
||||
return;
|
||||
}
|
||||
} catch (e) {
|
||||
toast.dismiss();
|
||||
M.toast({ html: "Encountered and unexpected error. Please make sure the add-on is still running."})
|
||||
return;
|
||||
}
|
||||
toast.dismiss();
|
||||
|
||||
// Continually check for status
|
||||
let status = $("#device-code-status-text");
|
||||
let progress = $("#device-code-loading");
|
||||
progress.show();
|
||||
while(true) {
|
||||
try {
|
||||
const resp = await fetch(URI("checkManualAuth").toString());
|
||||
const data = await resp.json();
|
||||
status.html(data.message);
|
||||
if(resp.ok) {
|
||||
$("#device-code-valid-time-left").html(data.expires)
|
||||
await sleep(1000);
|
||||
} else {
|
||||
progress.hide();
|
||||
break;
|
||||
}
|
||||
} catch (e) {
|
||||
progress.hode();
|
||||
status.html("Unexpected error: " + e);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
{% endblock %}
|
||||
Reference in New Issue
Block a user