New Addon
Google BK
@@ -0,0 +1,73 @@
|
||||
{% import "layouts/macros.jinja2" as macros %}
|
||||
{% extends "layouts/base-server.jinja2" %}
|
||||
|
||||
{% block head %}
|
||||
{{ super() }}
|
||||
<script type="text/javascript">
|
||||
function redirect() {
|
||||
let searchParams = new URLSearchParams(window.location.search);
|
||||
if (searchParams.has('to')) {
|
||||
window.location.assign(searchParams.get('to'));
|
||||
}
|
||||
else {
|
||||
window.location.assign("{{ redirect_url }}");
|
||||
}
|
||||
}
|
||||
|
||||
function sleep (time) {
|
||||
return new Promise((resolve) => setTimeout(resolve, time));
|
||||
}
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{% call macros.header(version) %}{% endcall %}
|
||||
<main>
|
||||
<div class="section no-pad-bot" id="index-banner">
|
||||
<div class="container">
|
||||
<br><br>
|
||||
<div class="row center">
|
||||
<h6 class="header col s12 light">
|
||||
<div class="col s12 m8 offset-m2 l8 offset-l2" id="save_cred_message">
|
||||
<h5 class="header center">You're almost set.</h5>
|
||||
<br>
|
||||
<div class="center">
|
||||
You've authorized the addon to connect with Google Drive and created the authorization string shown below. Most of the time
|
||||
you can just click "Send Credentials" below to have them sent back to the addon, but depending on circumstances beyond the
|
||||
addon's control that might just return an error. If that happens, instead just copy the authorization string below
|
||||
and paste it into the addon where you clicked the "Authenticate With Google Drive" button that brought you here.
|
||||
</div>
|
||||
<br>
|
||||
<a class="center right btn-flat btn-high-vis" target="_blank" href="{{ redirect_url }}">
|
||||
<i class="material-icons">open_in_new</i>Send Credentials
|
||||
</a>
|
||||
</div>
|
||||
</h6>
|
||||
</div>
|
||||
<div class="row center">
|
||||
|
||||
</div>
|
||||
<div class="row center">
|
||||
<div class="col s12 m8 offset-m2 l4 offset-l4">
|
||||
<h6 class="left">Authorization String:</h6>
|
||||
<br>
|
||||
<textarea readonly style="height: 150px" id="credentials_serialized">
|
||||
{{ credentials_serialized }}
|
||||
</textarea>
|
||||
</div>
|
||||
<br>
|
||||
<div class="col s12 m8 offset-m2 l4 offset-l4">
|
||||
<button id="copy_button" onclick="copyFromInput('credentials_serialized')" class="right btn-flat btn-high-vis">
|
||||
<i class="material-icons">content_copy</i>Copy
|
||||
</button>
|
||||
</div>
|
||||
<br>
|
||||
<div class="col s12 m8 offset-m2 l4 offset-l4 light">
|
||||
<i>Note:</i> This string is like a password that lets the addon talk with Google Drive, so don't share it with anyone.
|
||||
</div>
|
||||
</div>
|
||||
<br><br>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,439 @@
|
||||
/*
|
||||
colpick Color Picker / colpick.com
|
||||
*/
|
||||
|
||||
/*Main container*/
|
||||
.colpick {
|
||||
position: absolute;
|
||||
box-sizing:content-box;
|
||||
width: 346px;
|
||||
height: 170px;
|
||||
overflow: hidden;
|
||||
display: none;
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
direction:ltr;
|
||||
background:#ebebeb;
|
||||
border: 1px solid #bbb;
|
||||
-webkit-border-radius: 5px;
|
||||
-moz-border-radius: 5px;
|
||||
border-radius: 5px;
|
||||
|
||||
/*Prevents selecting text when dragging the selectors*/
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
-o-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
/*Color selection box with gradients*/
|
||||
.colpick .colpick_color {
|
||||
position: absolute;
|
||||
left: 7px;
|
||||
top: 7px;
|
||||
width: 156px;
|
||||
height: 156px;
|
||||
overflow: hidden;
|
||||
outline: 1px solid #aaa;
|
||||
cursor: crosshair;
|
||||
}
|
||||
.colpick .colpick_color_overlay1 {
|
||||
position: absolute;
|
||||
left:0;
|
||||
top:0;
|
||||
width: 156px;
|
||||
height: 156px;
|
||||
-ms-filter: "progid:DXImageTransform.Microsoft.gradient(GradientType=1,startColorstr='#ffffff', endColorstr='#00ffffff')"; /* IE8 */
|
||||
background: -moz-linear-gradient(left, rgba(255,255,255,1) 0%, rgba(255,255,255,0) 100%); /* FF3.6+ */
|
||||
background: -webkit-gradient(linear, left top, right top, color-stop(0%,rgba(255,255,255,1)), color-stop(100%,rgba(255,255,255,0))); /* Chrome,Safari4+ */
|
||||
background: -webkit-linear-gradient(left, rgba(255,255,255,1) 0%,rgba(255,255,255,0) 100%); /* Chrome10+,Safari5.1+ */
|
||||
background: -o-linear-gradient(left, rgba(255,255,255,1) 0%,rgba(255,255,255,0) 100%); /* Opera 11.10+ */
|
||||
background: -ms-linear-gradient(left, rgba(255,255,255,1) 0%,rgba(255,255,255,0) 100%); /* IE10+ */
|
||||
background: linear-gradient(to right, rgba(255,255,255,1) 0%, rgba(255,255,255,0) 100%);
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(GradientType=1,startColorstr='#ffffff', endColorstr='#00ffffff'); /* IE6 & IE7 */
|
||||
}
|
||||
.colpick .colpick_color_overlay2 {
|
||||
position: absolute;
|
||||
left:0;
|
||||
top:0;
|
||||
width: 156px;
|
||||
height: 156px;
|
||||
-ms-filter: "progid:DXImageTransform.Microsoft.gradient(GradientType=0,startColorstr='#00000000', endColorstr='#000000')"; /* IE8 */
|
||||
background: -moz-linear-gradient(top, rgba(0,0,0,0) 0%, rgba(0,0,0,1) 100%); /* FF3.6+ */
|
||||
background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,rgba(0,0,0,0)), color-stop(100%,rgba(0,0,0,1))); /* Chrome,Safari4+ */
|
||||
background: -webkit-linear-gradient(top, rgba(0,0,0,0) 0%,rgba(0,0,0,1) 100%); /* Chrome10+,Safari5.1+ */
|
||||
background: -o-linear-gradient(top, rgba(0,0,0,0) 0%,rgba(0,0,0,1) 100%); /* Opera 11.10+ */
|
||||
background: -ms-linear-gradient(top, rgba(0,0,0,0) 0%,rgba(0,0,0,1) 100%); /* IE10+ */
|
||||
background: linear-gradient(to bottom, rgba(0,0,0,0) 0%,rgba(0,0,0,1) 100%); /* W3C */
|
||||
filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#00000000', endColorstr='#000000',GradientType=0 ); /* IE6-9 */
|
||||
}
|
||||
/*Circular color selector*/
|
||||
.colpick .colpick_selector_outer {
|
||||
background:none;
|
||||
position: absolute;
|
||||
width: 11px;
|
||||
height: 11px;
|
||||
margin: -6px 0 0 -6px;
|
||||
border: 1px solid black;
|
||||
border-radius: 50%;
|
||||
}
|
||||
.colpick .colpick_selector_inner{
|
||||
position: absolute;
|
||||
width: 9px;
|
||||
height: 9px;
|
||||
border: 1px solid white;
|
||||
border-radius: 50%;
|
||||
}
|
||||
/*Vertical hue bar*/
|
||||
.colpick .colpick_hue {
|
||||
position: absolute;
|
||||
top: 6px;
|
||||
left: 175px;
|
||||
width: 19px;
|
||||
height: 156px;
|
||||
border: 1px solid #aaa;
|
||||
cursor: n-resize;
|
||||
}
|
||||
/*Hue bar sliding indicator*/
|
||||
.colpick .colpick_hue_arrs {
|
||||
position: absolute;
|
||||
left: -8px;
|
||||
width: 35px;
|
||||
height: 7px;
|
||||
margin: -7px 0 0 0;
|
||||
}
|
||||
.colpick .colpick_hue_larr {
|
||||
position:absolute;
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-top: 6px solid transparent;
|
||||
border-bottom: 6px solid transparent;
|
||||
border-left: 7px solid #858585;
|
||||
}
|
||||
.colpick .colpick_hue_rarr {
|
||||
position:absolute;
|
||||
right:0;
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-top: 6px solid transparent;
|
||||
border-bottom: 6px solid transparent;
|
||||
border-right: 7px solid #858585;
|
||||
}
|
||||
/*New color box*/
|
||||
.colpick .colpick_new_color {
|
||||
position: absolute;
|
||||
left: 207px;
|
||||
top: 6px;
|
||||
width: 60px;
|
||||
height: 27px;
|
||||
background: #f00;
|
||||
border: 1px solid #8f8f8f;
|
||||
}
|
||||
/*Current color box*/
|
||||
.colpick .colpick_current_color {
|
||||
position: absolute;
|
||||
left: 277px;
|
||||
top: 6px;
|
||||
width: 60px;
|
||||
height: 27px;
|
||||
background: #f00;
|
||||
border: 1px solid #8f8f8f;
|
||||
}
|
||||
/*Input field containers*/
|
||||
.colpick .colpick_field, .colpick .colpick_hex_field {
|
||||
position: absolute;
|
||||
height: 20px;
|
||||
width: 60px;
|
||||
overflow:hidden;
|
||||
background:#f3f3f3;
|
||||
color:#b8b8b8;
|
||||
font-size:12px;
|
||||
border:1px solid #bdbdbd;
|
||||
-webkit-border-radius: 3px;
|
||||
-moz-border-radius: 3px;
|
||||
border-radius: 3px;
|
||||
}
|
||||
.colpick .colpick_rgb_r {
|
||||
top: 40px;
|
||||
left: 207px;
|
||||
}
|
||||
.colpick .colpick_rgb_g {
|
||||
top: 67px;
|
||||
left: 207px;
|
||||
}
|
||||
.colpick .colpick_rgb_b {
|
||||
top: 94px;
|
||||
left: 207px;
|
||||
}
|
||||
.colpick .colpick_hsb_h {
|
||||
top: 40px;
|
||||
left: 277px;
|
||||
}
|
||||
.colpick .colpick_hsb_s {
|
||||
top: 67px;
|
||||
left: 277px;
|
||||
}
|
||||
.colpick .colpick_hsb_b {
|
||||
top: 94px;
|
||||
left: 277px;
|
||||
}
|
||||
.colpick .colpick_hex_field {
|
||||
width: 68px;
|
||||
left: 207px;
|
||||
top: 121px;
|
||||
}
|
||||
/*Text field container on focus*/
|
||||
.colpick .colpick_focus {
|
||||
border-color: #999;
|
||||
}
|
||||
/*Field label container*/
|
||||
.colpick .colpick_field_letter {
|
||||
position: absolute;
|
||||
width: 12px;
|
||||
height: 20px;
|
||||
line-height: 20px;
|
||||
padding-left: 4px;
|
||||
background: #efefef;
|
||||
border-right: 1px solid #bdbdbd;
|
||||
font-weight: bold;
|
||||
color:#777;
|
||||
}
|
||||
/*Text inputs*/
|
||||
.colpick .colpick_field input, .colpick .colpick_hex_field input {
|
||||
position: absolute;
|
||||
right: 11px;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
height: 20px;
|
||||
line-height: 20px;
|
||||
background: transparent;
|
||||
border: none;
|
||||
font-size: 12px;
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
color: #555;
|
||||
text-align: right;
|
||||
outline: none;
|
||||
}
|
||||
.colpick .colpick_hex_field input {
|
||||
right: 4px;
|
||||
}
|
||||
/*Field up/down arrows*/
|
||||
.colpick .colpick_field_arrs {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
width: 9px;
|
||||
height: 21px;
|
||||
cursor: n-resize;
|
||||
}
|
||||
.colpick .colpick_field_uarr {
|
||||
position: absolute;
|
||||
top: 5px;
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-left: 4px solid transparent;
|
||||
border-right: 4px solid transparent;
|
||||
border-bottom: 4px solid #959595;
|
||||
}
|
||||
.colpick .colpick_field_darr {
|
||||
position: absolute;
|
||||
bottom:5px;
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-left: 4px solid transparent;
|
||||
border-right: 4px solid transparent;
|
||||
border-top: 4px solid #959595;
|
||||
}
|
||||
/*Submit/Select button*/
|
||||
.colpick .colpick_submit {
|
||||
position: absolute;
|
||||
left: 207px;
|
||||
top: 149px;
|
||||
width: 130px;
|
||||
height: 22px;
|
||||
line-height:22px;
|
||||
background: #efefef;
|
||||
text-align: center;
|
||||
color: #555;
|
||||
font-size: 12px;
|
||||
font-weight:bold;
|
||||
border: 1px solid #bdbdbd;
|
||||
-webkit-border-radius: 3px;
|
||||
-moz-border-radius: 3px;
|
||||
border-radius: 3px;
|
||||
}
|
||||
.colpick .colpick_submit:hover {
|
||||
background:#f3f3f3;
|
||||
border-color:#999;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/*full layout with no submit button*/
|
||||
.colpick.colpick_full_ns .colpick_submit,
|
||||
.colpick.colpick_full_ns .colpick_current_color{
|
||||
display:none;
|
||||
}
|
||||
.colpick.colpick_full_ns .colpick_new_color {
|
||||
width: 130px;
|
||||
height: 25px;
|
||||
}
|
||||
.colpick.colpick_full_ns .colpick_rgb_r,
|
||||
.colpick.colpick_full_ns .colpick_hsb_h {
|
||||
top: 42px;
|
||||
}
|
||||
.colpick.colpick_full_ns .colpick_rgb_g,
|
||||
.colpick.colpick_full_ns .colpick_hsb_s {
|
||||
top: 73px;
|
||||
}
|
||||
.colpick.colpick_full_ns .colpick_rgb_b,
|
||||
.colpick.colpick_full_ns .colpick_hsb_b {
|
||||
top: 104px;
|
||||
}
|
||||
.colpick.colpick_full_ns .colpick_hex_field {
|
||||
top: 135px;
|
||||
}
|
||||
|
||||
/*rgbhex layout*/
|
||||
.colpick.colpick_rgbhex .colpick_hsb_h,
|
||||
.colpick.colpick_rgbhex .colpick_hsb_s,
|
||||
.colpick.colpick_rgbhex .colpick_hsb_b {
|
||||
display:none;
|
||||
}
|
||||
.colpick.colpick_rgbhex {
|
||||
width:282px;
|
||||
}
|
||||
.colpick.colpick_rgbhex .colpick_field,
|
||||
.colpick.colpick_rgbhex .colpick_submit {
|
||||
width:68px;
|
||||
}
|
||||
.colpick.colpick_rgbhex .colpick_new_color {
|
||||
width:34px;
|
||||
border-right:none;
|
||||
}
|
||||
.colpick.colpick_rgbhex .colpick_current_color {
|
||||
width:34px;
|
||||
left:240px;
|
||||
border-left:none;
|
||||
}
|
||||
|
||||
/*rgbhex layout, no submit button*/
|
||||
.colpick.colpick_rgbhex_ns .colpick_submit,
|
||||
.colpick.colpick_rgbhex_ns .colpick_current_color{
|
||||
display:none;
|
||||
}
|
||||
.colpick.colpick_rgbhex_ns .colpick_new_color{
|
||||
width:68px;
|
||||
border: 1px solid #8f8f8f;
|
||||
}
|
||||
.colpick.colpick_rgbhex_ns .colpick_rgb_r {
|
||||
top: 42px;
|
||||
}
|
||||
.colpick.colpick_rgbhex_ns .colpick_rgb_g {
|
||||
top: 73px;
|
||||
}
|
||||
.colpick.colpick_rgbhex_ns .colpick_rgb_b {
|
||||
top: 104px;
|
||||
}
|
||||
.colpick.colpick_rgbhex_ns .colpick_hex_field {
|
||||
top: 135px;
|
||||
}
|
||||
|
||||
/*hex layout*/
|
||||
.colpick.colpick_hex .colpick_hsb_h,
|
||||
.colpick.colpick_hex .colpick_hsb_s,
|
||||
.colpick.colpick_hex .colpick_hsb_b,
|
||||
.colpick.colpick_hex .colpick_rgb_r,
|
||||
.colpick.colpick_hex .colpick_rgb_g,
|
||||
.colpick.colpick_hex .colpick_rgb_b {
|
||||
display:none;
|
||||
}
|
||||
.colpick.colpick_hex {
|
||||
width:206px;
|
||||
height:201px;
|
||||
}
|
||||
.colpick.colpick_hex .colpick_hex_field {
|
||||
width:72px;
|
||||
height:25px;
|
||||
top:168px;
|
||||
left:80px;
|
||||
}
|
||||
.colpick.colpick_hex .colpick_hex_field div,
|
||||
.colpick.colpick_hex .colpick_hex_field input {
|
||||
height: 25px;
|
||||
line-height: 25px;
|
||||
}
|
||||
.colpick.colpick_hex .colpick_new_color {
|
||||
left:9px;
|
||||
top:168px;
|
||||
width:30px;
|
||||
border-right:none;
|
||||
}
|
||||
.colpick.colpick_hex .colpick_current_color {
|
||||
left:39px;
|
||||
top:168px;
|
||||
width:30px;
|
||||
border-left:none;
|
||||
}
|
||||
.colpick.colpick_hex .colpick_submit {
|
||||
left:164px;
|
||||
top: 168px;
|
||||
width:30px;
|
||||
height:25px;
|
||||
line-height: 25px;
|
||||
}
|
||||
|
||||
/*hex layout, no submit button*/
|
||||
.colpick.colpick_hex_ns .colpick_submit,
|
||||
.colpick.colpick_hex_ns .colpick_current_color {
|
||||
display:none;
|
||||
}
|
||||
.colpick.colpick_hex_ns .colpick_hex_field {
|
||||
width:80px;
|
||||
}
|
||||
.colpick.colpick_hex_ns .colpick_new_color{
|
||||
width:60px;
|
||||
border: 1px solid #8f8f8f;
|
||||
}
|
||||
|
||||
/*Dark color scheme*/
|
||||
.colpick.colpick_dark {
|
||||
background: #161616;
|
||||
border-color: #2a2a2a;
|
||||
}
|
||||
.colpick.colpick_dark .colpick_color {
|
||||
outline-color: #333;
|
||||
}
|
||||
.colpick.colpick_dark .colpick_hue {
|
||||
border-color: #555;
|
||||
}
|
||||
.colpick.colpick_dark .colpick_field,
|
||||
.colpick.colpick_dark .colpick_hex_field {
|
||||
background: #101010;
|
||||
border-color: #2d2d2d;
|
||||
}
|
||||
.colpick.colpick_dark .colpick_field_letter {
|
||||
background: #131313;
|
||||
border-color: #2d2d2d;
|
||||
color: #696969;
|
||||
}
|
||||
.colpick.colpick_dark .colpick_field input,
|
||||
.colpick.colpick_dark .colpick_hex_field input {
|
||||
color: #7a7a7a;
|
||||
}
|
||||
.colpick.colpick_dark .colpick_field_uarr {
|
||||
border-bottom-color:#696969;
|
||||
}
|
||||
.colpick.colpick_dark .colpick_field_darr {
|
||||
border-top-color:#696969;
|
||||
}
|
||||
.colpick.colpick_dark .colpick_focus {
|
||||
border-color:#444;
|
||||
}
|
||||
.colpick.colpick_dark .colpick_submit {
|
||||
background: #131313;
|
||||
border-color:#2d2d2d;
|
||||
color:#7a7a7a;
|
||||
}
|
||||
.colpick.colpick_dark .colpick_submit:hover {
|
||||
background-color:#101010;
|
||||
border-color:#444;
|
||||
}
|
||||
|
After Width: | Height: | Size: 275 KiB |
@@ -0,0 +1,932 @@
|
||||
3d_rotation e84d
|
||||
ac_unit eb3b
|
||||
access_alarm e190
|
||||
access_alarms e191
|
||||
access_time e192
|
||||
accessibility e84e
|
||||
accessible e914
|
||||
account_balance e84f
|
||||
account_balance_wallet e850
|
||||
account_box e851
|
||||
account_circle e853
|
||||
adb e60e
|
||||
add e145
|
||||
add_a_photo e439
|
||||
add_alarm e193
|
||||
add_alert e003
|
||||
add_box e146
|
||||
add_circle e147
|
||||
add_circle_outline e148
|
||||
add_location e567
|
||||
add_shopping_cart e854
|
||||
add_to_photos e39d
|
||||
add_to_queue e05c
|
||||
adjust e39e
|
||||
airline_seat_flat e630
|
||||
airline_seat_flat_angled e631
|
||||
airline_seat_individual_suite e632
|
||||
airline_seat_legroom_extra e633
|
||||
airline_seat_legroom_normal e634
|
||||
airline_seat_legroom_reduced e635
|
||||
airline_seat_recline_extra e636
|
||||
airline_seat_recline_normal e637
|
||||
airplanemode_active e195
|
||||
airplanemode_inactive e194
|
||||
airplay e055
|
||||
airport_shuttle eb3c
|
||||
alarm e855
|
||||
alarm_add e856
|
||||
alarm_off e857
|
||||
alarm_on e858
|
||||
album e019
|
||||
all_inclusive eb3d
|
||||
all_out e90b
|
||||
android e859
|
||||
announcement e85a
|
||||
apps e5c3
|
||||
archive e149
|
||||
arrow_back e5c4
|
||||
arrow_downward e5db
|
||||
arrow_drop_down e5c5
|
||||
arrow_drop_down_circle e5c6
|
||||
arrow_drop_up e5c7
|
||||
arrow_forward e5c8
|
||||
arrow_upward e5d8
|
||||
art_track e060
|
||||
aspect_ratio e85b
|
||||
assessment e85c
|
||||
assignment e85d
|
||||
assignment_ind e85e
|
||||
assignment_late e85f
|
||||
assignment_return e860
|
||||
assignment_returned e861
|
||||
assignment_turned_in e862
|
||||
assistant e39f
|
||||
assistant_photo e3a0
|
||||
attach_file e226
|
||||
attach_money e227
|
||||
attachment e2bc
|
||||
audiotrack e3a1
|
||||
autorenew e863
|
||||
av_timer e01b
|
||||
backspace e14a
|
||||
backup e864
|
||||
battery_alert e19c
|
||||
battery_charging_full e1a3
|
||||
battery_full e1a4
|
||||
battery_std e1a5
|
||||
battery_unknown e1a6
|
||||
beach_access eb3e
|
||||
beenhere e52d
|
||||
block e14b
|
||||
bluetooth e1a7
|
||||
bluetooth_audio e60f
|
||||
bluetooth_connected e1a8
|
||||
bluetooth_disabled e1a9
|
||||
bluetooth_searching e1aa
|
||||
blur_circular e3a2
|
||||
blur_linear e3a3
|
||||
blur_off e3a4
|
||||
blur_on e3a5
|
||||
book e865
|
||||
bookmark e866
|
||||
bookmark_border e867
|
||||
border_all e228
|
||||
border_bottom e229
|
||||
border_clear e22a
|
||||
border_color e22b
|
||||
border_horizontal e22c
|
||||
border_inner e22d
|
||||
border_left e22e
|
||||
border_outer e22f
|
||||
border_right e230
|
||||
border_style e231
|
||||
border_top e232
|
||||
border_vertical e233
|
||||
branding_watermark e06b
|
||||
brightness_1 e3a6
|
||||
brightness_2 e3a7
|
||||
brightness_3 e3a8
|
||||
brightness_4 e3a9
|
||||
brightness_5 e3aa
|
||||
brightness_6 e3ab
|
||||
brightness_7 e3ac
|
||||
brightness_auto e1ab
|
||||
brightness_high e1ac
|
||||
brightness_low e1ad
|
||||
brightness_medium e1ae
|
||||
broken_image e3ad
|
||||
brush e3ae
|
||||
bubble_chart e6dd
|
||||
bug_report e868
|
||||
build e869
|
||||
burst_mode e43c
|
||||
business e0af
|
||||
business_center eb3f
|
||||
cached e86a
|
||||
cake e7e9
|
||||
call e0b0
|
||||
call_end e0b1
|
||||
call_made e0b2
|
||||
call_merge e0b3
|
||||
call_missed e0b4
|
||||
call_missed_outgoing e0e4
|
||||
call_received e0b5
|
||||
call_split e0b6
|
||||
call_to_action e06c
|
||||
camera e3af
|
||||
camera_alt e3b0
|
||||
camera_enhance e8fc
|
||||
camera_front e3b1
|
||||
camera_rear e3b2
|
||||
camera_roll e3b3
|
||||
cancel e5c9
|
||||
card_giftcard e8f6
|
||||
card_membership e8f7
|
||||
card_travel e8f8
|
||||
casino eb40
|
||||
cast e307
|
||||
cast_connected e308
|
||||
center_focus_strong e3b4
|
||||
center_focus_weak e3b5
|
||||
change_history e86b
|
||||
chat e0b7
|
||||
chat_bubble e0ca
|
||||
chat_bubble_outline e0cb
|
||||
check e5ca
|
||||
check_box e834
|
||||
check_box_outline_blank e835
|
||||
check_circle e86c
|
||||
chevron_left e5cb
|
||||
chevron_right e5cc
|
||||
child_care eb41
|
||||
child_friendly eb42
|
||||
chrome_reader_mode e86d
|
||||
class e86e
|
||||
clear e14c
|
||||
clear_all e0b8
|
||||
close e5cd
|
||||
closed_caption e01c
|
||||
cloud e2bd
|
||||
cloud_circle e2be
|
||||
cloud_done e2bf
|
||||
cloud_download e2c0
|
||||
cloud_off e2c1
|
||||
cloud_queue e2c2
|
||||
cloud_upload e2c3
|
||||
code e86f
|
||||
collections e3b6
|
||||
collections_bookmark e431
|
||||
color_lens e3b7
|
||||
colorize e3b8
|
||||
comment e0b9
|
||||
compare e3b9
|
||||
compare_arrows e915
|
||||
computer e30a
|
||||
confirmation_number e638
|
||||
contact_mail e0d0
|
||||
contact_phone e0cf
|
||||
contacts e0ba
|
||||
content_copy e14d
|
||||
content_cut e14e
|
||||
content_paste e14f
|
||||
control_point e3ba
|
||||
control_point_duplicate e3bb
|
||||
copyright e90c
|
||||
create e150
|
||||
create_new_folder e2cc
|
||||
credit_card e870
|
||||
crop e3be
|
||||
crop_16_9 e3bc
|
||||
crop_3_2 e3bd
|
||||
crop_5_4 e3bf
|
||||
crop_7_5 e3c0
|
||||
crop_din e3c1
|
||||
crop_free e3c2
|
||||
crop_landscape e3c3
|
||||
crop_original e3c4
|
||||
crop_portrait e3c5
|
||||
crop_rotate e437
|
||||
crop_square e3c6
|
||||
dashboard e871
|
||||
data_usage e1af
|
||||
date_range e916
|
||||
dehaze e3c7
|
||||
delete e872
|
||||
delete_forever e92b
|
||||
delete_sweep e16c
|
||||
description e873
|
||||
desktop_mac e30b
|
||||
desktop_windows e30c
|
||||
details e3c8
|
||||
developer_board e30d
|
||||
developer_mode e1b0
|
||||
device_hub e335
|
||||
devices e1b1
|
||||
devices_other e337
|
||||
dialer_sip e0bb
|
||||
dialpad e0bc
|
||||
directions e52e
|
||||
directions_bike e52f
|
||||
directions_boat e532
|
||||
directions_bus e530
|
||||
directions_car e531
|
||||
directions_railway e534
|
||||
directions_run e566
|
||||
directions_subway e533
|
||||
directions_transit e535
|
||||
directions_walk e536
|
||||
disc_full e610
|
||||
dns e875
|
||||
do_not_disturb e612
|
||||
do_not_disturb_alt e611
|
||||
do_not_disturb_off e643
|
||||
do_not_disturb_on e644
|
||||
dock e30e
|
||||
domain e7ee
|
||||
done e876
|
||||
done_all e877
|
||||
donut_large e917
|
||||
donut_small e918
|
||||
drafts e151
|
||||
drag_handle e25d
|
||||
drive_eta e613
|
||||
dvr e1b2
|
||||
edit e3c9
|
||||
edit_location e568
|
||||
eject e8fb
|
||||
email e0be
|
||||
enhanced_encryption e63f
|
||||
equalizer e01d
|
||||
error e000
|
||||
error_outline e001
|
||||
euro_symbol e926
|
||||
ev_station e56d
|
||||
event e878
|
||||
event_available e614
|
||||
event_busy e615
|
||||
event_note e616
|
||||
event_seat e903
|
||||
exit_to_app e879
|
||||
expand_less e5ce
|
||||
expand_more e5cf
|
||||
explicit e01e
|
||||
explore e87a
|
||||
exposure e3ca
|
||||
exposure_neg_1 e3cb
|
||||
exposure_neg_2 e3cc
|
||||
exposure_plus_1 e3cd
|
||||
exposure_plus_2 e3ce
|
||||
exposure_zero e3cf
|
||||
extension e87b
|
||||
face e87c
|
||||
fast_forward e01f
|
||||
fast_rewind e020
|
||||
favorite e87d
|
||||
favorite_border e87e
|
||||
featured_play_list e06d
|
||||
featured_video e06e
|
||||
feedback e87f
|
||||
fiber_dvr e05d
|
||||
fiber_manual_record e061
|
||||
fiber_new e05e
|
||||
fiber_pin e06a
|
||||
fiber_smart_record e062
|
||||
file_download e2c4
|
||||
file_upload e2c6
|
||||
filter e3d3
|
||||
filter_1 e3d0
|
||||
filter_2 e3d1
|
||||
filter_3 e3d2
|
||||
filter_4 e3d4
|
||||
filter_5 e3d5
|
||||
filter_6 e3d6
|
||||
filter_7 e3d7
|
||||
filter_8 e3d8
|
||||
filter_9 e3d9
|
||||
filter_9_plus e3da
|
||||
filter_b_and_w e3db
|
||||
filter_center_focus e3dc
|
||||
filter_drama e3dd
|
||||
filter_frames e3de
|
||||
filter_hdr e3df
|
||||
filter_list e152
|
||||
filter_none e3e0
|
||||
filter_tilt_shift e3e2
|
||||
filter_vintage e3e3
|
||||
find_in_page e880
|
||||
find_replace e881
|
||||
fingerprint e90d
|
||||
first_page e5dc
|
||||
fitness_center eb43
|
||||
flag e153
|
||||
flare e3e4
|
||||
flash_auto e3e5
|
||||
flash_off e3e6
|
||||
flash_on e3e7
|
||||
flight e539
|
||||
flight_land e904
|
||||
flight_takeoff e905
|
||||
flip e3e8
|
||||
flip_to_back e882
|
||||
flip_to_front e883
|
||||
folder e2c7
|
||||
folder_open e2c8
|
||||
folder_shared e2c9
|
||||
folder_special e617
|
||||
font_download e167
|
||||
format_align_center e234
|
||||
format_align_justify e235
|
||||
format_align_left e236
|
||||
format_align_right e237
|
||||
format_bold e238
|
||||
format_clear e239
|
||||
format_color_fill e23a
|
||||
format_color_reset e23b
|
||||
format_color_text e23c
|
||||
format_indent_decrease e23d
|
||||
format_indent_increase e23e
|
||||
format_italic e23f
|
||||
format_line_spacing e240
|
||||
format_list_bulleted e241
|
||||
format_list_numbered e242
|
||||
format_paint e243
|
||||
format_quote e244
|
||||
format_shapes e25e
|
||||
format_size e245
|
||||
format_strikethrough e246
|
||||
format_textdirection_l_to_r e247
|
||||
format_textdirection_r_to_l e248
|
||||
format_underlined e249
|
||||
forum e0bf
|
||||
forward e154
|
||||
forward_10 e056
|
||||
forward_30 e057
|
||||
forward_5 e058
|
||||
free_breakfast eb44
|
||||
fullscreen e5d0
|
||||
fullscreen_exit e5d1
|
||||
functions e24a
|
||||
g_translate e927
|
||||
gamepad e30f
|
||||
games e021
|
||||
gavel e90e
|
||||
gesture e155
|
||||
get_app e884
|
||||
gif e908
|
||||
golf_course eb45
|
||||
gps_fixed e1b3
|
||||
gps_not_fixed e1b4
|
||||
gps_off e1b5
|
||||
grade e885
|
||||
gradient e3e9
|
||||
grain e3ea
|
||||
graphic_eq e1b8
|
||||
grid_off e3eb
|
||||
grid_on e3ec
|
||||
group e7ef
|
||||
group_add e7f0
|
||||
group_work e886
|
||||
hd e052
|
||||
hdr_off e3ed
|
||||
hdr_on e3ee
|
||||
hdr_strong e3f1
|
||||
hdr_weak e3f2
|
||||
headset e310
|
||||
headset_mic e311
|
||||
healing e3f3
|
||||
hearing e023
|
||||
help e887
|
||||
help_outline e8fd
|
||||
high_quality e024
|
||||
highlight e25f
|
||||
highlight_off e888
|
||||
history e889
|
||||
home e88a
|
||||
hot_tub eb46
|
||||
hotel e53a
|
||||
hourglass_empty e88b
|
||||
hourglass_full e88c
|
||||
http e902
|
||||
https e88d
|
||||
image e3f4
|
||||
image_aspect_ratio e3f5
|
||||
import_contacts e0e0
|
||||
import_export e0c3
|
||||
important_devices e912
|
||||
inbox e156
|
||||
indeterminate_check_box e909
|
||||
info e88e
|
||||
info_outline e88f
|
||||
input e890
|
||||
insert_chart e24b
|
||||
insert_comment e24c
|
||||
insert_drive_file e24d
|
||||
insert_emoticon e24e
|
||||
insert_invitation e24f
|
||||
insert_link e250
|
||||
insert_photo e251
|
||||
invert_colors e891
|
||||
invert_colors_off e0c4
|
||||
iso e3f6
|
||||
keyboard e312
|
||||
keyboard_arrow_down e313
|
||||
keyboard_arrow_left e314
|
||||
keyboard_arrow_right e315
|
||||
keyboard_arrow_up e316
|
||||
keyboard_backspace e317
|
||||
keyboard_capslock e318
|
||||
keyboard_hide e31a
|
||||
keyboard_return e31b
|
||||
keyboard_tab e31c
|
||||
keyboard_voice e31d
|
||||
kitchen eb47
|
||||
label e892
|
||||
label_outline e893
|
||||
landscape e3f7
|
||||
language e894
|
||||
laptop e31e
|
||||
laptop_chromebook e31f
|
||||
laptop_mac e320
|
||||
laptop_windows e321
|
||||
last_page e5dd
|
||||
launch e895
|
||||
layers e53b
|
||||
layers_clear e53c
|
||||
leak_add e3f8
|
||||
leak_remove e3f9
|
||||
lens e3fa
|
||||
library_add e02e
|
||||
library_books e02f
|
||||
library_music e030
|
||||
lightbulb_outline e90f
|
||||
line_style e919
|
||||
line_weight e91a
|
||||
linear_scale e260
|
||||
link e157
|
||||
linked_camera e438
|
||||
list e896
|
||||
live_help e0c6
|
||||
live_tv e639
|
||||
local_activity e53f
|
||||
local_airport e53d
|
||||
local_atm e53e
|
||||
local_bar e540
|
||||
local_cafe e541
|
||||
local_car_wash e542
|
||||
local_convenience_store e543
|
||||
local_dining e556
|
||||
local_drink e544
|
||||
local_florist e545
|
||||
local_gas_station e546
|
||||
local_grocery_store e547
|
||||
local_hospital e548
|
||||
local_hotel e549
|
||||
local_laundry_service e54a
|
||||
local_library e54b
|
||||
local_mall e54c
|
||||
local_movies e54d
|
||||
local_offer e54e
|
||||
local_parking e54f
|
||||
local_pharmacy e550
|
||||
local_phone e551
|
||||
local_pizza e552
|
||||
local_play e553
|
||||
local_post_office e554
|
||||
local_printshop e555
|
||||
local_see e557
|
||||
local_shipping e558
|
||||
local_taxi e559
|
||||
location_city e7f1
|
||||
location_disabled e1b6
|
||||
location_off e0c7
|
||||
location_on e0c8
|
||||
location_searching e1b7
|
||||
lock e897
|
||||
lock_open e898
|
||||
lock_outline e899
|
||||
looks e3fc
|
||||
looks_3 e3fb
|
||||
looks_4 e3fd
|
||||
looks_5 e3fe
|
||||
looks_6 e3ff
|
||||
looks_one e400
|
||||
looks_two e401
|
||||
loop e028
|
||||
loupe e402
|
||||
low_priority e16d
|
||||
loyalty e89a
|
||||
mail e158
|
||||
mail_outline e0e1
|
||||
map e55b
|
||||
markunread e159
|
||||
markunread_mailbox e89b
|
||||
memory e322
|
||||
menu e5d2
|
||||
merge_type e252
|
||||
message e0c9
|
||||
mic e029
|
||||
mic_none e02a
|
||||
mic_off e02b
|
||||
mms e618
|
||||
mode_comment e253
|
||||
mode_edit e254
|
||||
monetization_on e263
|
||||
money_off e25c
|
||||
monochrome_photos e403
|
||||
mood e7f2
|
||||
mood_bad e7f3
|
||||
more e619
|
||||
more_horiz e5d3
|
||||
more_vert e5d4
|
||||
motorcycle e91b
|
||||
mouse e323
|
||||
move_to_inbox e168
|
||||
movie e02c
|
||||
movie_creation e404
|
||||
movie_filter e43a
|
||||
multiline_chart e6df
|
||||
music_note e405
|
||||
music_video e063
|
||||
my_location e55c
|
||||
nature e406
|
||||
nature_people e407
|
||||
navigate_before e408
|
||||
navigate_next e409
|
||||
navigation e55d
|
||||
near_me e569
|
||||
network_cell e1b9
|
||||
network_check e640
|
||||
network_locked e61a
|
||||
network_wifi e1ba
|
||||
new_releases e031
|
||||
next_week e16a
|
||||
nfc e1bb
|
||||
no_encryption e641
|
||||
no_sim e0cc
|
||||
not_interested e033
|
||||
note e06f
|
||||
note_add e89c
|
||||
notifications e7f4
|
||||
notifications_active e7f7
|
||||
notifications_none e7f5
|
||||
notifications_off e7f6
|
||||
notifications_paused e7f8
|
||||
offline_pin e90a
|
||||
ondemand_video e63a
|
||||
opacity e91c
|
||||
open_in_browser e89d
|
||||
open_in_new e89e
|
||||
open_with e89f
|
||||
pages e7f9
|
||||
pageview e8a0
|
||||
palette e40a
|
||||
pan_tool e925
|
||||
panorama e40b
|
||||
panorama_fish_eye e40c
|
||||
panorama_horizontal e40d
|
||||
panorama_vertical e40e
|
||||
panorama_wide_angle e40f
|
||||
party_mode e7fa
|
||||
pause e034
|
||||
pause_circle_filled e035
|
||||
pause_circle_outline e036
|
||||
payment e8a1
|
||||
people e7fb
|
||||
people_outline e7fc
|
||||
perm_camera_mic e8a2
|
||||
perm_contact_calendar e8a3
|
||||
perm_data_setting e8a4
|
||||
perm_device_information e8a5
|
||||
perm_identity e8a6
|
||||
perm_media e8a7
|
||||
perm_phone_msg e8a8
|
||||
perm_scan_wifi e8a9
|
||||
person e7fd
|
||||
person_add e7fe
|
||||
person_outline e7ff
|
||||
person_pin e55a
|
||||
person_pin_circle e56a
|
||||
personal_video e63b
|
||||
pets e91d
|
||||
phone e0cd
|
||||
phone_android e324
|
||||
phone_bluetooth_speaker e61b
|
||||
phone_forwarded e61c
|
||||
phone_in_talk e61d
|
||||
phone_iphone e325
|
||||
phone_locked e61e
|
||||
phone_missed e61f
|
||||
phone_paused e620
|
||||
phonelink e326
|
||||
phonelink_erase e0db
|
||||
phonelink_lock e0dc
|
||||
phonelink_off e327
|
||||
phonelink_ring e0dd
|
||||
phonelink_setup e0de
|
||||
photo e410
|
||||
photo_album e411
|
||||
photo_camera e412
|
||||
photo_filter e43b
|
||||
photo_library e413
|
||||
photo_size_select_actual e432
|
||||
photo_size_select_large e433
|
||||
photo_size_select_small e434
|
||||
picture_as_pdf e415
|
||||
picture_in_picture e8aa
|
||||
picture_in_picture_alt e911
|
||||
pie_chart e6c4
|
||||
pie_chart_outlined e6c5
|
||||
pin_drop e55e
|
||||
place e55f
|
||||
play_arrow e037
|
||||
play_circle_filled e038
|
||||
play_circle_outline e039
|
||||
play_for_work e906
|
||||
playlist_add e03b
|
||||
playlist_add_check e065
|
||||
playlist_play e05f
|
||||
plus_one e800
|
||||
poll e801
|
||||
polymer e8ab
|
||||
pool eb48
|
||||
portable_wifi_off e0ce
|
||||
portrait e416
|
||||
power e63c
|
||||
power_input e336
|
||||
power_settings_new e8ac
|
||||
pregnant_woman e91e
|
||||
present_to_all e0df
|
||||
print e8ad
|
||||
priority_high e645
|
||||
public e80b
|
||||
publish e255
|
||||
query_builder e8ae
|
||||
question_answer e8af
|
||||
queue e03c
|
||||
queue_music e03d
|
||||
queue_play_next e066
|
||||
radio e03e
|
||||
radio_button_checked e837
|
||||
radio_button_unchecked e836
|
||||
rate_review e560
|
||||
receipt e8b0
|
||||
recent_actors e03f
|
||||
record_voice_over e91f
|
||||
redeem e8b1
|
||||
redo e15a
|
||||
refresh e5d5
|
||||
remove e15b
|
||||
remove_circle e15c
|
||||
remove_circle_outline e15d
|
||||
remove_from_queue e067
|
||||
remove_red_eye e417
|
||||
remove_shopping_cart e928
|
||||
reorder e8fe
|
||||
repeat e040
|
||||
repeat_one e041
|
||||
replay e042
|
||||
replay_10 e059
|
||||
replay_30 e05a
|
||||
replay_5 e05b
|
||||
reply e15e
|
||||
reply_all e15f
|
||||
report e160
|
||||
report_problem e8b2
|
||||
restaurant e56c
|
||||
restaurant_menu e561
|
||||
restore e8b3
|
||||
restore_page e929
|
||||
ring_volume e0d1
|
||||
room e8b4
|
||||
room_service eb49
|
||||
rotate_90_degrees_ccw e418
|
||||
rotate_left e419
|
||||
rotate_right e41a
|
||||
rounded_corner e920
|
||||
router e328
|
||||
rowing e921
|
||||
rss_feed e0e5
|
||||
rv_hookup e642
|
||||
satellite e562
|
||||
save e161
|
||||
scanner e329
|
||||
schedule e8b5
|
||||
school e80c
|
||||
screen_lock_landscape e1be
|
||||
screen_lock_portrait e1bf
|
||||
screen_lock_rotation e1c0
|
||||
screen_rotation e1c1
|
||||
screen_share e0e2
|
||||
sd_card e623
|
||||
sd_storage e1c2
|
||||
search e8b6
|
||||
security e32a
|
||||
select_all e162
|
||||
send e163
|
||||
sentiment_dissatisfied e811
|
||||
sentiment_neutral e812
|
||||
sentiment_satisfied e813
|
||||
sentiment_very_dissatisfied e814
|
||||
sentiment_very_satisfied e815
|
||||
settings e8b8
|
||||
settings_applications e8b9
|
||||
settings_backup_restore e8ba
|
||||
settings_bluetooth e8bb
|
||||
settings_brightness e8bd
|
||||
settings_cell e8bc
|
||||
settings_ethernet e8be
|
||||
settings_input_antenna e8bf
|
||||
settings_input_component e8c0
|
||||
settings_input_composite e8c1
|
||||
settings_input_hdmi e8c2
|
||||
settings_input_svideo e8c3
|
||||
settings_overscan e8c4
|
||||
settings_phone e8c5
|
||||
settings_power e8c6
|
||||
settings_remote e8c7
|
||||
settings_system_daydream e1c3
|
||||
settings_voice e8c8
|
||||
share e80d
|
||||
shop e8c9
|
||||
shop_two e8ca
|
||||
shopping_basket e8cb
|
||||
shopping_cart e8cc
|
||||
short_text e261
|
||||
show_chart e6e1
|
||||
shuffle e043
|
||||
signal_cellular_4_bar e1c8
|
||||
signal_cellular_connected_no_internet_4_bar e1cd
|
||||
signal_cellular_no_sim e1ce
|
||||
signal_cellular_null e1cf
|
||||
signal_cellular_off e1d0
|
||||
signal_wifi_4_bar e1d8
|
||||
signal_wifi_4_bar_lock e1d9
|
||||
signal_wifi_off e1da
|
||||
sim_card e32b
|
||||
sim_card_alert e624
|
||||
skip_next e044
|
||||
skip_previous e045
|
||||
slideshow e41b
|
||||
slow_motion_video e068
|
||||
smartphone e32c
|
||||
smoke_free eb4a
|
||||
smoking_rooms eb4b
|
||||
sms e625
|
||||
sms_failed e626
|
||||
snooze e046
|
||||
sort e164
|
||||
sort_by_alpha e053
|
||||
spa eb4c
|
||||
space_bar e256
|
||||
speaker e32d
|
||||
speaker_group e32e
|
||||
speaker_notes e8cd
|
||||
speaker_notes_off e92a
|
||||
speaker_phone e0d2
|
||||
spellcheck e8ce
|
||||
star e838
|
||||
star_border e83a
|
||||
star_half e839
|
||||
stars e8d0
|
||||
stay_current_landscape e0d3
|
||||
stay_current_portrait e0d4
|
||||
stay_primary_landscape e0d5
|
||||
stay_primary_portrait e0d6
|
||||
stop e047
|
||||
stop_screen_share e0e3
|
||||
storage e1db
|
||||
store e8d1
|
||||
store_mall_directory e563
|
||||
straighten e41c
|
||||
streetview e56e
|
||||
strikethrough_s e257
|
||||
style e41d
|
||||
subdirectory_arrow_left e5d9
|
||||
subdirectory_arrow_right e5da
|
||||
subject e8d2
|
||||
subscriptions e064
|
||||
subtitles e048
|
||||
subway e56f
|
||||
supervisor_account e8d3
|
||||
surround_sound e049
|
||||
swap_calls e0d7
|
||||
swap_horiz e8d4
|
||||
swap_vert e8d5
|
||||
swap_vertical_circle e8d6
|
||||
switch_camera e41e
|
||||
switch_video e41f
|
||||
sync e627
|
||||
sync_disabled e628
|
||||
sync_problem e629
|
||||
system_update e62a
|
||||
system_update_alt e8d7
|
||||
tab e8d8
|
||||
tab_unselected e8d9
|
||||
tablet e32f
|
||||
tablet_android e330
|
||||
tablet_mac e331
|
||||
tag_faces e420
|
||||
tap_and_play e62b
|
||||
terrain e564
|
||||
text_fields e262
|
||||
text_format e165
|
||||
textsms e0d8
|
||||
texture e421
|
||||
theaters e8da
|
||||
thumb_down e8db
|
||||
thumb_up e8dc
|
||||
thumbs_up_down e8dd
|
||||
time_to_leave e62c
|
||||
timelapse e422
|
||||
timeline e922
|
||||
timer e425
|
||||
timer_10 e423
|
||||
timer_3 e424
|
||||
timer_off e426
|
||||
title e264
|
||||
toc e8de
|
||||
today e8df
|
||||
toll e8e0
|
||||
tonality e427
|
||||
touch_app e913
|
||||
toys e332
|
||||
track_changes e8e1
|
||||
traffic e565
|
||||
train e570
|
||||
tram e571
|
||||
transfer_within_a_station e572
|
||||
transform e428
|
||||
translate e8e2
|
||||
trending_down e8e3
|
||||
trending_flat e8e4
|
||||
trending_up e8e5
|
||||
tune e429
|
||||
turned_in e8e6
|
||||
turned_in_not e8e7
|
||||
tv e333
|
||||
unarchive e169
|
||||
undo e166
|
||||
unfold_less e5d6
|
||||
unfold_more e5d7
|
||||
update e923
|
||||
usb e1e0
|
||||
verified_user e8e8
|
||||
vertical_align_bottom e258
|
||||
vertical_align_center e259
|
||||
vertical_align_top e25a
|
||||
vibration e62d
|
||||
video_call e070
|
||||
video_label e071
|
||||
video_library e04a
|
||||
videocam e04b
|
||||
videocam_off e04c
|
||||
videogame_asset e338
|
||||
view_agenda e8e9
|
||||
view_array e8ea
|
||||
view_carousel e8eb
|
||||
view_column e8ec
|
||||
view_comfy e42a
|
||||
view_compact e42b
|
||||
view_day e8ed
|
||||
view_headline e8ee
|
||||
view_list e8ef
|
||||
view_module e8f0
|
||||
view_quilt e8f1
|
||||
view_stream e8f2
|
||||
view_week e8f3
|
||||
vignette e435
|
||||
visibility e8f4
|
||||
visibility_off e8f5
|
||||
voice_chat e62e
|
||||
voicemail e0d9
|
||||
volume_down e04d
|
||||
volume_mute e04e
|
||||
volume_off e04f
|
||||
volume_up e050
|
||||
vpn_key e0da
|
||||
vpn_lock e62f
|
||||
wallpaper e1bc
|
||||
warning e002
|
||||
watch e334
|
||||
watch_later e924
|
||||
wb_auto e42c
|
||||
wb_cloudy e42d
|
||||
wb_incandescent e42e
|
||||
wb_iridescent e436
|
||||
wb_sunny e430
|
||||
wc e63d
|
||||
web e051
|
||||
web_asset e069
|
||||
weekend e16b
|
||||
whatshot e80e
|
||||
widgets e1bd
|
||||
wifi e63e
|
||||
wifi_lock e1e1
|
||||
wifi_tethering e1e2
|
||||
work e8f9
|
||||
wrap_text e25b
|
||||
youtube_searched_for e8fa
|
||||
zoom_in e8ff
|
||||
zoom_out e900
|
||||
zoom_out_map e56b
|
||||
@@ -0,0 +1,36 @@
|
||||
@font-face {
|
||||
font-family: 'Material Icons';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
src: url(MaterialIcons-Regular.eot); /* For IE6-8 */
|
||||
src: local('Material Icons'),
|
||||
local('MaterialIcons-Regular'),
|
||||
url(MaterialIcons-Regular.woff2) format('woff2'),
|
||||
url(MaterialIcons-Regular.woff) format('woff'),
|
||||
url(MaterialIcons-Regular.ttf) format('truetype');
|
||||
}
|
||||
|
||||
.material-icons {
|
||||
font-family: 'Material Icons';
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
font-size: 24px; /* Preferred icon size */
|
||||
display: inline-block;
|
||||
line-height: 1;
|
||||
text-transform: none;
|
||||
letter-spacing: normal;
|
||||
word-wrap: normal;
|
||||
white-space: nowrap;
|
||||
direction: ltr;
|
||||
|
||||
/* Support for all WebKit browsers. */
|
||||
-webkit-font-smoothing: antialiased;
|
||||
/* Support for Safari and Chrome. */
|
||||
text-rendering: optimizeLegibility;
|
||||
|
||||
/* Support for Firefox. */
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
|
||||
/* Support for IE. */
|
||||
font-feature-settings: 'liga';
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
@font-face {
|
||||
font-family: 'Material Icons';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
src: url(iconfont/MaterialIcons-Regular.eot); /* For IE6-8 */
|
||||
src: local('Material Icons'),
|
||||
local('MaterialIcons-Regular'),
|
||||
url(iconfont/MaterialIcons-Regular.woff2) format('woff2'),
|
||||
url(iconfont/MaterialIcons-Regular.woff) format('woff'),
|
||||
url(iconfont/MaterialIcons-Regular.ttf) format('truetype');
|
||||
}
|
||||
|
||||
.material-icons {
|
||||
font-family: 'Material Icons';
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
font-size: 24px; /* Preferred icon size */
|
||||
display: inline-block;
|
||||
line-height: 1;
|
||||
text-transform: none;
|
||||
letter-spacing: normal;
|
||||
word-wrap: normal;
|
||||
white-space: nowrap;
|
||||
direction: ltr;
|
||||
|
||||
/* Support for all WebKit browsers. */
|
||||
-webkit-font-smoothing: antialiased;
|
||||
/* Support for Safari and Chrome. */
|
||||
text-rendering: optimizeLegibility;
|
||||
|
||||
/* Support for Firefox. */
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
|
||||
/* Support for IE. */
|
||||
font-feature-settings: 'liga';
|
||||
}
|
||||
@@ -0,0 +1,216 @@
|
||||
/* css-loading-spinners.css
|
||||
Copyright (c) Ian A. Cook
|
||||
MIT License
|
||||
https://github.com/nai888/css-loading-spinners
|
||||
*/
|
||||
|
||||
:root {
|
||||
--cls-color: #558B6E;
|
||||
--cls-sec-color: #EEEEEE;
|
||||
--cls-size: 5rem;
|
||||
--cls-margin: 1rem;
|
||||
--cls-speed: 2s;
|
||||
}
|
||||
|
||||
@-webkit-keyframes spin {
|
||||
0% {
|
||||
-webkit-transform: rotate(0deg);
|
||||
} 100% {
|
||||
-webkit-transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% {
|
||||
-webkit-transform: rotate(0deg);
|
||||
transform: rotate(0deg);
|
||||
} 100% {
|
||||
-webkit-transform: rotate(360deg);
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
@-webkit-keyframes flip-flop {
|
||||
0%, 100% {
|
||||
-webkit-transform: perspective(calc(var(--cls-size) / 5 * 8)) rotateY(0deg);
|
||||
} 50% {
|
||||
-webkit-transform: perspective(calc(var(--cls-size) / 5 * 8)) rotateY(180deg);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes flip-flop {
|
||||
0%, 100% {
|
||||
-webkit-transform: perspective(calc(var(--cls-size) / 5 * 8)) rotateY(0deg);
|
||||
transform: perspective(calc(var(--cls-size) / 5 * 8)) rotateY(0deg);
|
||||
} 50% {
|
||||
-webkit-transform: perspective(calc(var(--cls-size) / 5 * 8)) rotateY(180deg);
|
||||
transform: perspective(calc(var(--cls-size) / 5 * 8)) rotateY(180deg);
|
||||
}
|
||||
}
|
||||
|
||||
@-webkit-keyframes signal {
|
||||
0% {
|
||||
-webkit-transform: scale(0);
|
||||
-webkit-opacity: 0;
|
||||
} 50% {
|
||||
-webkit-opacity: 1;
|
||||
} 100% {
|
||||
-webkit-transform: scale(1);
|
||||
-webkit-opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes signal {
|
||||
0% {
|
||||
-webkit-transform: scale(0);
|
||||
transform: scale(0);
|
||||
-webkit-opacity: 0;
|
||||
opacity: 0;
|
||||
} 50% {
|
||||
-webkit-opacity: 1;
|
||||
opacity: 1;
|
||||
} 100% {
|
||||
-webkit-transform: scale(1);
|
||||
transform: scale(1);
|
||||
-webkit-opacity: 0;
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@-webkit-keyframes grow-shrink {
|
||||
0%, 100% {
|
||||
-webkit-transform: scale(0);
|
||||
} 50% {
|
||||
-webkit-transform: scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes grow-shrink {
|
||||
0%, 100% {
|
||||
-webkit-transform: scale(0);
|
||||
transform: scale(0);
|
||||
} 50% {
|
||||
-webkit-transform: scale(1);
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
[class^="cls-"] {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.cls-spinner {
|
||||
display: block;
|
||||
position: relative;
|
||||
width: var(--cls-size);
|
||||
height: var(--cls-size);
|
||||
margin: var(--cls-margin);
|
||||
}
|
||||
|
||||
.cls-flip-flop {
|
||||
-webkit-animation: flip-flop calc(var(--cls-speed) / 2) ease infinite;
|
||||
animation: flip-flop calc(var(--cls-speed) / 2) ease infinite;
|
||||
}
|
||||
|
||||
.cls-spin {
|
||||
-webkit-animation: spin var(--cls-speed) linear infinite;
|
||||
animation: spin var(--cls-speed) linear infinite;
|
||||
}
|
||||
|
||||
.cls-circle {
|
||||
border-width: calc(var(--cls-size) / 5 * 0.75);
|
||||
border-style: solid;
|
||||
border-color: var(--cls-sec-color);
|
||||
border-top-color: var(--cls-color);
|
||||
border-radius: 50%;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.cls-dual-circle {
|
||||
border-width: calc(var(--cls-size) / 5 * 0.75);
|
||||
border-style: solid;
|
||||
border-color: var(--cls-color) transparent;
|
||||
border-radius: 50%;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.cls-bowtie, .cls-bowtie-v {
|
||||
border-width: calc(var(--cls-size) / 2);
|
||||
border-style: solid;
|
||||
border-color: transparent var(--cls-color);
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.cls-bowtie-v {
|
||||
border-color: var(--cls-color) transparent;
|
||||
}
|
||||
|
||||
.cls-square {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: var(--cls-color);
|
||||
}
|
||||
|
||||
.cls-signal, .cls-triple-signal, .cls-triple-signal::before, .cls-triple-signal::after {
|
||||
border-width: calc(var(--cls-size) / 25);
|
||||
border-style: solid;
|
||||
border-color: var(--cls-color);
|
||||
border-radius: 50%;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
opacity: 0;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.cls-triple-signal::before, .cls-triple-signal::after, .cls-rings::before, .cls-rings::after {
|
||||
content: '';
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
margin: calc(var(--cls-size) / -2) 0 0 calc(var(--cls-size) / -2);
|
||||
}
|
||||
|
||||
.cls-signal, .cls-triple-signal, .cls-triple-signal::before, .cls-triple-signal::after {
|
||||
-webkit-animation: signal calc(var(--cls-speed) / 2) ease-out infinite;
|
||||
animation: signal calc(var(--cls-speed) / 2) ease-out infinite;
|
||||
}
|
||||
|
||||
.cls-triple-signal::before {
|
||||
-webkit-animation-delay: calc(var(--cls-speed) / 20);
|
||||
animation-delay: calc(var(--cls-speed) / 20);
|
||||
}
|
||||
|
||||
.cls-triple-signal::after {
|
||||
-webkit-animation-delay: calc(var(--cls-speed) * 3 / 20);
|
||||
animation-delay: calc(var(--cls-speed) * 3 / 20);
|
||||
}
|
||||
|
||||
.cls-ring, .cls-rings, .cls-rings::before, .cls-rings::after {
|
||||
border-width: calc(var(--cls-size) / 25);
|
||||
border-style: solid;
|
||||
border-color: var(--cls-color);
|
||||
border-radius: 50%;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.cls-rings, .cls-rings::before, .cls-rings::after {
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.cls-ring, .cls-rings, .cls-rings::before, .cls-rings::after {
|
||||
-webkit-animation: grow-shrink var(--cls-speed) ease-in-out infinite;
|
||||
animation: grow-shrink var(--cls-speed) ease-in-out infinite;
|
||||
}
|
||||
|
||||
.cls-rings::before {
|
||||
-webkit-animation-delay: calc(var(--cls-speed) / -4);
|
||||
animation-delay: calc(var(--cls-speed) / -4);
|
||||
}
|
||||
|
||||
.cls-rings::after {
|
||||
-webkit-animation-delay: calc(var(--cls-speed) / 4);
|
||||
animation-delay: calc(var(--cls-speed) / 4);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,590 @@
|
||||
body {
|
||||
display: flex;
|
||||
min-height: 100vh;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
main {
|
||||
flex: 1 0 auto;
|
||||
}
|
||||
|
||||
html {
|
||||
background-color: var(--background-primary-color);
|
||||
color: var(--text-primary-color);
|
||||
}
|
||||
|
||||
label {
|
||||
color: var(--text-primary-color);
|
||||
}
|
||||
|
||||
a {
|
||||
color: var(--accent-link-color);
|
||||
}
|
||||
|
||||
input {
|
||||
color: var(--text-primary-color);
|
||||
}
|
||||
|
||||
i.material-icons.active, input:focus + label {
|
||||
color: var(--accent-focus-color) !important;
|
||||
}
|
||||
|
||||
i.material-icons.active, textarea:focus + label {
|
||||
color: var(--accent-focus-color) !important;
|
||||
}
|
||||
|
||||
input:focus {
|
||||
border-bottom: 1px solid var(--accent-focus-color) !important;
|
||||
}
|
||||
|
||||
.helper-text {
|
||||
color: var(--helper-text-color) !important;
|
||||
}
|
||||
|
||||
.sidenav,
|
||||
.card,
|
||||
.dropdown-content {
|
||||
background-color: var(--background-color);
|
||||
}
|
||||
|
||||
.dropdown-content li:hover,
|
||||
.dropdown-content li.active {
|
||||
background-color: var(--background-hover);
|
||||
}
|
||||
|
||||
nav {
|
||||
background-color: var(--background-color);
|
||||
border-bottom: 1px solid var(--divider-color);
|
||||
}
|
||||
|
||||
nav .brand-logo {
|
||||
color: var(--text-secondary-color);
|
||||
}
|
||||
|
||||
nav ul a {
|
||||
color: var(--text-secondary-color);
|
||||
}
|
||||
|
||||
nav a i,
|
||||
nav ul a i,
|
||||
.sidenav li > a > i.material-icons {
|
||||
color: var(--icon-color);
|
||||
}
|
||||
|
||||
.modal,
|
||||
.modal .modal-footer {
|
||||
background-color: var(--background-modal-color);
|
||||
}
|
||||
|
||||
.modal {
|
||||
box-shadow: 0 24px 38px 3px var(--shadow-color), 0 9px 46px 8px var(--shadow-color), 0 11px 15px -7px var(--shadow-color);
|
||||
}
|
||||
|
||||
.card, .dropdown-content {
|
||||
box-shadow: 0 2px 2px 0 var(--shadow-color), 0 3px 1px -2px var(--shadow-color), 0 1px 5px 0 var(--shadow-color);
|
||||
|
||||
}
|
||||
|
||||
.dropdown-content li > a,
|
||||
.dropdown-content li > span,
|
||||
.sidenav li > a {
|
||||
color: var(--text-secondary-color);
|
||||
}
|
||||
|
||||
[type="checkbox"].filled-in:checked + span:not(.lever):after {
|
||||
border: 2px solid var(--accent-color);
|
||||
background-color: var(--accent-color);
|
||||
}
|
||||
|
||||
.source-icon {
|
||||
margin-right: 6px;
|
||||
fill: var(--accent-color);
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.icon-block .material-icons {
|
||||
font-size: inherit;
|
||||
}
|
||||
|
||||
.indented-li {
|
||||
padding-left: 15px;
|
||||
padding-top: 5px;
|
||||
padding-bottom: 5px;
|
||||
}
|
||||
|
||||
.important-inline {
|
||||
display: inline !important;
|
||||
}
|
||||
|
||||
.detail-node {
|
||||
margin-top: 0px;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.top-btn {
|
||||
box-shadow: none;
|
||||
-webkit-box-shadow: none;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.icon-warn-delete {
|
||||
color: var(--icon-warn);
|
||||
}
|
||||
|
||||
.input-field > label {
|
||||
color: var(--input-label);
|
||||
}
|
||||
|
||||
.default-hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.console-error {
|
||||
color: #b30000;
|
||||
}
|
||||
|
||||
.console-warning {
|
||||
color: #a55d00;
|
||||
}
|
||||
|
||||
.console-default {
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.console-debug {
|
||||
color: #3141d1;
|
||||
}
|
||||
|
||||
.console-trace {
|
||||
color: #282828;
|
||||
}
|
||||
|
||||
.console-critical {
|
||||
color: #810000;
|
||||
}
|
||||
|
||||
.colpick {
|
||||
z-index: 9999;
|
||||
}
|
||||
|
||||
#toast-container {
|
||||
top: auto !important;
|
||||
left: auto !important;
|
||||
bottom: 10%;
|
||||
right: 7%;
|
||||
}
|
||||
|
||||
nav {
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
nav .brand-logo {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
white-space: nowrap;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
nav .brand-logo img {
|
||||
margin-left: 1rem;
|
||||
margin-right: 1rem;
|
||||
}
|
||||
|
||||
.nav-wrapper {
|
||||
height: 56px;
|
||||
}
|
||||
|
||||
.sidenav-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
background-color: var(--background-sidenav-color);
|
||||
padding-top: 1rem;
|
||||
padding-bottom: 1rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.modal {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.choose-folder-container {
|
||||
padding: 5px 15px;
|
||||
margin: 10px 0;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.snapshot-card {
|
||||
padding-top: 24px;
|
||||
padding-right: 24px;
|
||||
padding-left: 24px;
|
||||
height: 145px;
|
||||
border-radius: 0 0 2px 2px;
|
||||
}
|
||||
|
||||
.copyright {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0 18px;
|
||||
}
|
||||
|
||||
.backup-ui .archive-icon {
|
||||
position: relative;
|
||||
margin-right: 18px;
|
||||
top: -10px;
|
||||
}
|
||||
|
||||
.tabs {
|
||||
background-color: transparent !important;
|
||||
}
|
||||
|
||||
.page-footer {
|
||||
padding: 10px !important;
|
||||
color: var(--text-secondary-color) !important;
|
||||
background-color: var(--background-color);
|
||||
border-top: 1px solid var(--divider-color);
|
||||
}
|
||||
|
||||
.bmc-button {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
height: 30px;
|
||||
text-decoration: none;
|
||||
color: var(--accent-text-color);
|
||||
background-color: var(--accent-color);
|
||||
border-radius: 5px;
|
||||
border: 1px solid transparent;
|
||||
padding: 5px;
|
||||
font-family: Cookie, cursive;
|
||||
font-size: 20px;
|
||||
min-width: 128px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.bmc-button svg {
|
||||
height: 22px;
|
||||
}
|
||||
|
||||
.bmc-cup {
|
||||
fill: var(--accent-dark)
|
||||
}
|
||||
|
||||
.bmc-outline {
|
||||
fill: var(--accent-color)
|
||||
}
|
||||
.btn-floating {
|
||||
background-color: var(--accent-color);
|
||||
color: var(--accent-text-color);
|
||||
}
|
||||
|
||||
.btn-floating:hover {
|
||||
background-color: var(--accent-color);
|
||||
}
|
||||
|
||||
#contributors-simple a {
|
||||
margin-right: 8px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
#contributors-extra .contributor-img-wrapper a {
|
||||
display: flex;
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
#contributors a img {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border-radius: 9999px;
|
||||
}
|
||||
|
||||
#contributors-count {
|
||||
font-size: 17px;
|
||||
background-color: var(--divider-color);
|
||||
padding: 2px 8px;
|
||||
border-radius: 9999px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.contributor-extra {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.truncate-two-lines {
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.divider {
|
||||
background-color: var(--divider-color);
|
||||
}
|
||||
|
||||
textarea {
|
||||
background-color: var(--background-primary-color);
|
||||
color: var(--text-primary-color);
|
||||
}
|
||||
|
||||
.card .card-action a:not(.btn):not(.btn-large):not(.btn-small):not(.btn-large):not(.btn-floating) {
|
||||
color: var(--accent-color);
|
||||
}
|
||||
|
||||
.card .card-action a:not(.btn):not(.btn-large):not(.btn-small):not(.btn-large):not(.btn-floating):hover {
|
||||
color: var(--accent-hover-color);
|
||||
}
|
||||
|
||||
.btn-flat {
|
||||
background-color: transparent;
|
||||
color: var(--accent-color);
|
||||
font-weight: 600;
|
||||
-webkit-transition: all .3s ease;
|
||||
transition: all .3s ease;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.btn-flat:hover {
|
||||
color: var(--accent-hover-color);
|
||||
background-color: var(--accent-bg-hover-color);
|
||||
}
|
||||
|
||||
.btn-flat:focus {
|
||||
color: var(--accent-hover-color);
|
||||
background-color: var(--accent-bg-hover-color);
|
||||
}
|
||||
|
||||
.btn-high-vis {
|
||||
color: var(--accent-hover-color);
|
||||
background-color: var(--accent-bg-hover-color);
|
||||
}
|
||||
|
||||
.btn-high-vis:hover {
|
||||
color: var(--accent-hover-color);
|
||||
background-color: var(--accent-bg-hover-color);
|
||||
}
|
||||
|
||||
.danger-btn {
|
||||
color: var(--danger-text);
|
||||
}
|
||||
|
||||
.danger-btn:hover {
|
||||
color: var(--danger-bg);
|
||||
background-color: var(--danger-text);
|
||||
}
|
||||
|
||||
.card .card-action .danger-btn a:not(.btn):not(.btn-large):not(.btn-small):not(.btn-large):not(.btn-floating) {
|
||||
color: var(--danger-text);
|
||||
}
|
||||
|
||||
.high-vis-danger-btn {
|
||||
color: var(--danger-bg);
|
||||
background-color: var(--danger-text);
|
||||
}
|
||||
|
||||
.high-vis-danger-btn:hover {
|
||||
color: var(--danger-bg);
|
||||
background-color: var(--danger-text);
|
||||
}
|
||||
|
||||
.danger-text {
|
||||
color: var(--danger-text);
|
||||
}
|
||||
|
||||
.blue-icon {
|
||||
color: var(--blue-icon);
|
||||
}
|
||||
|
||||
.card .card-action {
|
||||
padding: 8px 24px;
|
||||
}
|
||||
|
||||
.card-title > .material-icons {
|
||||
font-size: 50px;
|
||||
padding-right: 10px;
|
||||
}
|
||||
|
||||
.card-title > span {
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.inline-icon {
|
||||
display: inline;
|
||||
margin-right: 2px;
|
||||
vertical-align: middle;
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.btn-flat .material-icons {
|
||||
display: inline;
|
||||
margin-right: 5px;
|
||||
vertical-align: middle;
|
||||
font-size: 20px;
|
||||
padding-bottom: 3px;
|
||||
}
|
||||
|
||||
.detail-badge {
|
||||
margin: 8px 8px;
|
||||
white-space: nowrap;
|
||||
align-content: center;
|
||||
}
|
||||
|
||||
.detail-badge .material-icons {
|
||||
display: inline;
|
||||
margin-right: 2px;
|
||||
vertical-align: bottom;
|
||||
}
|
||||
|
||||
.spinner-layer {
|
||||
border-color: var(--accent-color);
|
||||
}
|
||||
|
||||
.preloader-wrapper.inline {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.sub-detail {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.sub-detail .material-icons {
|
||||
font-size: 16px;
|
||||
vertical-align: text-bottom;
|
||||
}
|
||||
|
||||
.progress .determinate {
|
||||
background-color: var(--accent-color);
|
||||
}
|
||||
|
||||
.progress .indeterminate {
|
||||
background-color: var(--accent-color);
|
||||
}
|
||||
|
||||
.progress {
|
||||
background: var(--progress-bg);
|
||||
}
|
||||
|
||||
.indent {
|
||||
margin-left: 36px;
|
||||
}
|
||||
|
||||
blockquote {
|
||||
border-left: 5px solid var(--accent-color);
|
||||
}
|
||||
|
||||
.content-logo {
|
||||
max-height: 2rem;
|
||||
margin-right: 9px;
|
||||
}
|
||||
|
||||
span.content-logo > i {
|
||||
font-size: 2rem;
|
||||
}
|
||||
|
||||
.sub {
|
||||
color: var(--input-label);
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.content-icon {
|
||||
margin-right: 9px;
|
||||
fill: var(--text-primary-color);
|
||||
width: 2rem;
|
||||
height: 2rem;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.detail-badge {
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.detail-name {
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.backup-card-icon {
|
||||
vertical-align: -2px;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.backup-help-icon {
|
||||
vertical-align: 3px;
|
||||
display: inline;
|
||||
font-size: 0.75rem;
|
||||
margin-left: 2px;
|
||||
}
|
||||
|
||||
input.valid:not([type]), input.valid:not([type]):focus, input[type="text"].valid:not(.browser-default), input[type="text"].valid:not(.browser-default):focus, input[type="password"].valid:not(.browser-default), input[type="password"].valid:not(.browser-default):focus, input[type="email"].valid:not(.browser-default), input[type="email"].valid:not(.browser-default):focus, input[type="url"].valid:not(.browser-default), input[type="url"].valid:not(.browser-default):focus, input[type="time"].valid:not(.browser-default), input[type="time"].valid:not(.browser-default):focus, input[type="date"].valid:not(.browser-default), input[type="date"].valid:not(.browser-default):focus, input[type="datetime"].valid:not(.browser-default), input[type="datetime"].valid:not(.browser-default):focus, input[type="datetime-local"].valid:not(.browser-default), input[type="datetime-local"].valid:not(.browser-default):focus, input[type="tel"].valid:not(.browser-default), input[type="tel"].valid:not(.browser-default):focus, input[type="number"].valid:not(.browser-default), input[type="number"].valid:not(.browser-default):focus, input[type="search"].valid:not(.browser-default), input[type="search"].valid:not(.browser-default):focus, textarea.materialize-textarea.valid, textarea.materialize-textarea.valid:focus, .select-wrapper.valid > input.select-dropdown {
|
||||
border-bottom: 1px solid var(--accent-color);
|
||||
-webkit-box-shadow: 0 1px 0 0 var(--accent-color);
|
||||
box-shadow: 0 1px 0 0 var(--accent-color);
|
||||
}
|
||||
|
||||
input:not([type]):focus:not([readonly]), input[type="text"]:not(.browser-default):focus:not([readonly]), input[type="password"]:not(.browser-default):focus:not([readonly]), input[type="email"]:not(.browser-default):focus:not([readonly]), input[type="url"]:not(.browser-default):focus:not([readonly]), input[type="time"]:not(.browser-default):focus:not([readonly]), input[type="date"]:not(.browser-default):focus:not([readonly]), input[type="datetime"]:not(.browser-default):focus:not([readonly]), input[type="datetime-local"]:not(.browser-default):focus:not([readonly]), input[type="tel"]:not(.browser-default):focus:not([readonly]), input[type="number"]:not(.browser-default):focus:not([readonly]), input[type="search"]:not(.browser-default):focus:not([readonly]), textarea.materialize-textarea:focus:not([readonly]) {
|
||||
border-bottom: 1px solid var(--accent-color);
|
||||
-webkit-box-shadow: 0 1px 0 0 var(--accent-color);
|
||||
box-shadow: 0 1px 0 0 var(--accent-color);
|
||||
}
|
||||
|
||||
.toast .toast-action {
|
||||
color: var(--accent-color);
|
||||
font-weight: 600;
|
||||
margin-right: 0px;
|
||||
}
|
||||
|
||||
.toast .toast-action:hover {
|
||||
color: var(--accent-hover-color);
|
||||
background-color: var(--accent-bg-hover-color);
|
||||
}
|
||||
|
||||
.device-code-box {
|
||||
font-size: 2em;
|
||||
width: auto;
|
||||
border-color: var(--accent-text-color);
|
||||
color: var(--accent-text-color);
|
||||
background: var(--accent-color);
|
||||
border-radius: 6px;
|
||||
padding: 6px;
|
||||
margin: 6px;
|
||||
}
|
||||
|
||||
.card .card-content .card-title.mini-title {
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.device-code-box:hover {
|
||||
background-color: var(--accent-bg-hover-color);
|
||||
}
|
||||
|
||||
.qr-code {
|
||||
padding: 5px;
|
||||
background-color: white;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.title-logo {
|
||||
position: relative;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
top: 8px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.flex-wrap {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.donate-button {
|
||||
margin: 7px;
|
||||
}
|
||||
|
||||
.donate-button img {
|
||||
width: 150px;
|
||||
height: 40px;
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<!-- Creator: CorelDRAW 2019 (64-Bit) -->
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" width="100%" height="100%" version="1.1" shape-rendering="geometricPrecision" text-rendering="geometricPrecision" image-rendering="optimizeQuality" fill-rule="evenodd" clip-rule="evenodd" viewBox="0 0 4257.46 1132" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:xodm="http://www.corel.com/coreldraw/odm/2003">
|
||||
<g id="Layer_x0020_1">
|
||||
<metadata id="CorelCorpID_0Corel-Layer"/>
|
||||
<path transform="scale(7.8, 7.4)" d="M0 24.48C0 10.9601 10.9601 0 24.48 0H520.2C533.72 0 544.68 10.9601 544.68 24.48V128.52C544.68 142.04 533.72 153 520.2 153H24.48C10.9601 153 0 142.04 0 128.52V24.48Z" fill="#eeeeee"/>
|
||||
<g id="_1421487920208" transform="translate(400, 200) scale(0.8, 0.8)">
|
||||
<path fill="#F7931A" fill-rule="nonzero" d="M875.92 551.95c-59.08,238.24 -300.11,383.47 -538.36,324.39 -238.24,-59.07 -383.47,-300.11 -324.39,-538.35 59.08,-238.25 300.11,-383.49 538.35,-324.41 0.4,0.1 0.81,0.2 1.21,0.31 237.61,59.63 382.17,300.28 323.19,538.06z"/>
|
||||
<path fill="white" fill-rule="nonzero" d="M545.37 380.28c-13.89,55.56 -98.61,27.08 -126.11,20.28l24.3 -97.22c27.08,6.67 115.7,19.44 101.39,76.94l0.42 0zm-15.14 157.1c-15,60.54 -116.94,27.78 -150,19.57l26.8 -107.36c33.06,8.33 138.89,24.58 123.2,87.79zm111.11 -156.26c8.75,-59.17 -36.25,-90.97 -97.22,-112.08l20 -80.14 -49.3 -12.22 -19.44 78.06c-12.78,-3.19 -25.97,-6.25 -39.03,-9.17l19.44 -78.89 -48.75 -12.22 -20 80.14 -31.11 -6.94 -67.36 -16.81 -12.92 52.09c0,0 36.11,8.33 35.42,8.89 13.79,1.67 23.85,13.92 22.78,27.78l-22.78 91.25c1.71,0.39 3.39,0.94 5,1.67l-5.14 -1.39 -31.81 127.92c-2.98,9.36 -12.98,14.51 -22.35,11.53l-0.01 0c0,0.69 -35.56,-8.75 -35.56,-8.75l-24.17 55.56 63.47 15.7 34.72 9.03 -20.14 80.97 48.75 12.22 20 -80.14c13.14,3.61 26.07,6.94 38.75,10l-19.72 80.42 48.75 12.08 20.14 -80.83c83.33,15.71 145.69,9.44 172.08,-65.83 21.25,-60.56 -1.11,-95.42 -44.86,-118.2 31.94,-7.36 55.56,-27.78 62.36,-71.67z"/>
|
||||
<path fill="#4D4D4D" fill-rule="nonzero" d="M1157.17 750c25.96,0.08 51.5,-6.53 74.17,-19.17 23.33,-12.44 43.85,-29.57 60.28,-50.28 17.12,-21.9 30.58,-46.43 39.86,-72.64 9.79,-27.08 14.76,-55.65 14.72,-84.44 1.56,-29.21 -4.68,-58.31 -18.06,-84.31 -11.94,-20.14 -34.17,-30.41 -66.39,-30.41 -14.04,0.48 -28,2.39 -41.67,5.69 -16.97,3.79 -32.67,11.93 -45.56,23.61l-73.61 307.78 11.81 2.22c3.51,0.86 7.1,1.42 10.69,1.67 4.61,0.57 9.25,0.8 13.89,0.69l19.86 -0.42zm146.25 -481.11c32.15,-0.74 64.07,5.82 93.33,19.17 25.4,12.11 47.72,29.82 65.28,51.81 17.98,22.74 31.2,48.85 38.89,76.8 8.42,30.82 12.57,62.64 12.36,94.59 -0.04,99.4 -38.57,194.91 -107.5,266.53 -33.42,34.28 -73.18,61.72 -117.08,80.83 -46.03,20.26 -95.82,30.59 -146.11,30.28l-35.56 0c-19.33,-0.63 -38.63,-2.29 -57.78,-5 -23.39,-3.39 -46.57,-8.02 -69.44,-13.89 -23.98,-5.69 -47.26,-13.98 -69.44,-24.72l195.14 -817.5 174.31 -27.78 -69.44 290.14c14.46,-6.54 29.47,-11.79 44.86,-15.69 15.86,-3.86 32.15,-5.78 48.47,-5.69l-0.28 0.14z"/>
|
||||
<path fill="#4D4D4D" fill-rule="nonzero" d="M1774.39 209.3c-22.72,0.22 -44.87,-7.06 -63.06,-20.69 -19.28,-15.25 -29.7,-39.1 -27.78,-63.61 -0.08,-15.29 3.18,-30.42 9.58,-44.31 6.13,-13.53 14.74,-25.78 25.42,-36.11 10.67,-10.14 23.03,-18.32 36.53,-24.17 14.04,-6.03 29.17,-9.1 44.44,-9.03 22.56,-0.07 44.5,7.25 62.5,20.83 19.24,15.28 29.65,39.11 27.78,63.61 0.12,15.33 -3.1,30.5 -9.44,44.44 -6.13,13.46 -14.7,25.65 -25.28,35.97 -10.64,10.18 -23,18.36 -36.53,24.17 -14.04,6.03 -29.17,9.06 -44.44,8.89l0.28 0zm-80.97 663.76l-166.67 0 140.83 -591.68 167.64 0 -141.81 591.68z"/>
|
||||
<path fill="#4D4D4D" fill-rule="nonzero" d="M1981.06 134.03l174.3 -26.94 -43.33 174.31 186.68 0 -33.63 137.22 -185.14 0 -49.44 206.39c-4.28,15.8 -6.93,32 -7.92,48.35 -1.02,13.22 0.94,26.5 5.69,38.87 4.65,11.1 13.26,20.07 24.17,25.14 15.7,6.94 32.84,10.1 50,9.17 17.58,0.06 35.12,-1.67 52.36,-5.14 17.35,-3.39 34.44,-8.03 51.11,-13.89l12.5 128.33c-23.93,8.6 -48.38,15.71 -73.2,21.25 -30.67,6.51 -61.98,9.54 -93.33,9.03 -51.81,0 -91.81,-7.78 -120.42,-23.06 -26.76,-13.61 -48.2,-35.81 -60.83,-63.06 -12.33,-28.91 -17.71,-60.31 -15.69,-91.67 1.87,-36.79 7.12,-73.33 15.69,-109.17l110.42 -465.69 0 0.56z"/>
|
||||
<path fill="#4D4D4D" fill-rule="nonzero" d="M2292.59 636.8c-0.35,-49.08 8.01,-97.86 24.72,-144.02 15.67,-43.65 39.74,-83.8 70.83,-118.19 31.35,-34.11 69.47,-61.26 111.94,-79.74 46.32,-20 96.35,-29.99 146.8,-29.29 30.46,-0.36 60.86,2.85 90.56,9.57 24.97,5.92 49.26,14.39 72.5,25.28l-57.91 130.14c-15,-6.11 -30.56,-11.37 -46.67,-16.25 -19.21,-5.35 -39.1,-7.83 -59.03,-7.36 -50.36,-1.82 -98.8,19.47 -131.53,57.78 -32.5,38.23 -48.81,89.62 -48.89,154.17 -1.53,32.74 7.11,65.14 24.72,92.79 16.47,23.61 46.86,35.4 91.11,35.4 21.21,0.02 42.35,-2.26 63.06,-6.8 18.47,-3.94 36.57,-9.56 54.03,-16.81l12.36 133.9c-22.72,8.6 -45.9,15.93 -69.44,21.93 -29.89,6.74 -60.47,9.92 -91.11,9.46 -40.67,1.18 -81.15,-5.67 -119.17,-20.15 -30.22,-12.18 -57.43,-30.8 -79.72,-54.57 -21.15,-23.04 -36.78,-50.6 -45.69,-80.57 -9.43,-31.54 -14.11,-64.31 -13.89,-97.22l0.42 0.56z"/>
|
||||
<path fill="#4D4D4D" fill-rule="nonzero" d="M3114.94 407.5c-23.5,-0.33 -46.47,7.14 -65.28,21.25 -19.07,14.76 -35.07,33.13 -47.08,54.03 -13.21,22.25 -23.13,46.31 -29.44,71.41 -6.11,24.06 -9.29,48.76 -9.44,73.59 -1.5,30.31 4.67,60.49 17.92,87.78 12.09,20.97 33.75,31.53 65.28,31.53 23.54,0.4 46.54,-7.13 65.28,-21.39 19.11,-14.76 35.14,-33.13 47.22,-54.01 13.02,-22.28 22.7,-46.33 28.76,-71.41 6.04,-24.11 9.22,-48.87 9.43,-73.75 1.56,-30.29 -4.63,-60.5 -17.92,-87.78 -12.08,-20.83 -33.89,-31.39 -65.28,-31.39l0.56 0.14zm-83.33 481.39c-35.39,0.82 -70.58,-5.32 -103.61,-18.06 -27.79,-10.96 -52.61,-28.26 -72.5,-50.56 -19.56,-22.46 -34.29,-48.69 -43.33,-77.08 -9.82,-31.83 -14.52,-65.03 -13.89,-98.33 0.11,-45.86 7.46,-91.43 21.82,-135 13.93,-43.85 35.68,-84.82 64.15,-120.97 28.6,-36.14 64.17,-66.18 104.58,-88.33 43.5,-23.39 92.28,-35.21 141.67,-34.31 35.18,-0.64 70.18,5.5 103.06,18.06 27.82,10.78 52.78,27.85 72.91,49.86 19.44,22.52 34.14,48.74 43.2,77.08 9.89,31.86 14.58,65.11 13.89,98.47 -0.14,45.82 -7.31,91.36 -21.25,135 -13.69,43.91 -35.04,85.04 -63.06,121.53 -28.18,36.29 -63.61,66.33 -104.03,88.2 -44.17,23.51 -93.58,35.37 -143.61,34.44z"/>
|
||||
<path fill="#4D4D4D" fill-rule="nonzero" d="M3626.75 209.3c-22.68,0.24 -44.81,-7.04 -62.91,-20.69 -19.29,-15.25 -29.71,-39.1 -27.78,-63.61 -0.08,-15.29 3.18,-30.42 9.57,-44.31 5.96,-13.46 14.33,-25.69 24.72,-36.11 10.74,-10.11 23.14,-18.29 36.67,-24.17 14,-6.02 29.08,-9.09 44.32,-9.03 22.72,-0.2 44.91,7.14 63.06,20.83 19.24,15.28 29.64,39.11 27.78,63.61 0.03,15.35 -3.29,30.52 -9.74,44.44 -6.02,13.47 -14.56,25.68 -25.13,35.97 -10.64,10.18 -23,18.36 -36.53,24.17 -13.92,5.97 -28.9,9 -44.04,8.89zm-80.82 663.76l-166.67 0 140.56 -591.68 167.78 0 -141.67 591.68z"/>
|
||||
<path fill="#4D4D4D" fill-rule="nonzero" d="M3807.59 308.33c12.64,-3.61 26.67,-8.06 41.67,-12.91 15,-4.86 32.5,-9.31 51.79,-13.89 21.17,-4.49 42.54,-7.87 64.04,-10.14 26.71,-2.82 53.56,-4.17 80.42,-4.03 87.78,0 148.33,25.5 181.67,76.53 19.96,30.56 30.06,67.84 30.3,111.85l0 3.17c-0.15,28.62 -4.42,60.05 -12.8,94.28l-76.53 319.44 -167.22 0 74.3 -312.79c4.44,-19.43 8.06,-38.32 10.7,-56.79 2.89,-15.99 2.89,-32.36 0,-48.33 -2.76,-13.35 -10.14,-25.29 -20.83,-33.75 -14.82,-9.72 -32.46,-14.26 -50.14,-12.92 -22.26,0.04 -44.46,2.32 -66.25,6.81l-109.17 458.33 -168.21 0 136.26 -564.86z"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 7.3 KiB |
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<!-- Creator: CorelDRAW 2019 (64-Bit) -->
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" width="100%" height="100%" version="1.1" shape-rendering="geometricPrecision" text-rendering="geometricPrecision" image-rendering="optimizeQuality" fill-rule="evenodd" clip-rule="evenodd" viewBox="0 0 4091.27 4091.73" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:xodm="http://www.corel.com/coreldraw/odm/2003">
|
||||
<g id="Layer_x0020_1">
|
||||
<metadata id="CorelCorpID_0Corel-Layer"/>
|
||||
<g id="_1421344023328">
|
||||
<path fill="#F7931A" fill-rule="nonzero" d="M4030.06 2540.77c-273.24,1096.01 -1383.32,1763.02 -2479.46,1489.71 -1095.68,-273.24 -1762.69,-1383.39 -1489.33,-2479.31 273.12,-1096.13 1383.2,-1763.19 2479,-1489.95 1096.06,273.24 1763.03,1383.51 1489.76,2479.57l0.02 -0.02z"/>
|
||||
<path fill="white" fill-rule="nonzero" d="M2947.77 1754.38c40.72,-272.26 -166.56,-418.61 -450,-516.24l91.95 -368.8 -224.5 -55.94 -89.51 359.09c-59.02,-14.72 -119.63,-28.59 -179.87,-42.34l90.16 -361.46 -224.36 -55.94 -92 368.68c-48.84,-11.12 -96.81,-22.11 -143.35,-33.69l0.26 -1.16 -309.59 -77.31 -59.72 239.78c0,0 166.56,38.18 163.05,40.53 90.91,22.69 107.35,82.87 104.62,130.57l-104.74 420.15c6.26,1.59 14.38,3.89 23.34,7.49 -7.49,-1.86 -15.46,-3.89 -23.73,-5.87l-146.81 588.57c-11.11,27.62 -39.31,69.07 -102.87,53.33 2.25,3.26 -163.17,-40.72 -163.17,-40.72l-111.46 256.98 292.15 72.83c54.35,13.63 107.61,27.89 160.06,41.3l-92.9 373.03 224.24 55.94 92 -369.07c61.26,16.63 120.71,31.97 178.91,46.43l-91.69 367.33 224.51 55.94 92.89 -372.33c382.82,72.45 670.67,43.24 791.83,-303.02 97.63,-278.78 -4.86,-439.58 -206.26,-544.44 146.69,-33.83 257.18,-130.31 286.64,-329.61l-0.07 -0.05zm-512.93 719.26c-69.38,278.78 -538.76,128.08 -690.94,90.29l123.28 -494.2c152.17,37.99 640.17,113.17 567.67,403.91zm69.43 -723.3c-63.29,253.58 -453.96,124.75 -580.69,93.16l111.77 -448.21c126.73,31.59 534.85,90.55 468.94,355.05l-0.02 0z"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.0 KiB |
|
After Width: | Height: | Size: 36 KiB |
@@ -0,0 +1,26 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<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 id="Fill-1" fill="#FF9100" 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 id="Fill-2" fill="#FFDD00" 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>
|
||||
|
After Width: | Height: | Size: 2.9 KiB |
|
After Width: | Height: | Size: 305 KiB |
@@ -0,0 +1,37 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1080 287.23">
|
||||
<title>ethereum-eth-logo-full-horizontal</title>
|
||||
<g id="Layer_2" data-name="Layer 2">
|
||||
<path transform="scale(1.98, 1.89)" d="M0 24.48C0 10.9601 10.9601 0 24.48 0H520.2C533.72 0 544.68 10.9601 544.68 24.48V128.52C544.68 142.04 533.72 153 520.2 153H24.48C10.9601 153 0 142.04 0 128.52V24.48Z" fill="#eeeeee"/>
|
||||
<g id="Layer_1-2" transform="translate(90, 30) scale(0.8, 0.8)" data-name="Layer 1">
|
||||
<path
|
||||
d="M306.2,155.7a3.56,3.56,0,0,1-3.6,3.5H236.3c1.7,16.4,14,31.4,31.4,31.4,11.9,0,20.7-4.5,27.3-14a3.58,3.58,0,0,1,2.9-1.7,3.21,3.21,0,0,1,3.3,3.3,3.1,3.1,0,0,1-.5,1.7c-6.7,11.6-20,17.3-33,17.3-22.3,0-38.3-20-38.3-41.3s15.9-41.3,38.3-41.3,38.4,19.8,38.5,41.1Zm-7.1-3.1c-1.4-16.4-14-31.4-31.4-31.4s-29.7,15-31.4,31.4Z"
|
||||
style="fill:#3b3b3b" />
|
||||
<path
|
||||
d="M386.8,116.2a3.4,3.4,0,0,1,3.3,3.3,3.21,3.21,0,0,1-3.3,3.3H369v69.9a3.33,3.33,0,0,1-3.3,3.3,3.4,3.4,0,0,1-3.3-3.3V122.8H345.3a3.21,3.21,0,0,1-3.3-3.3,3.33,3.33,0,0,1,3.3-3.3h17.1V90.7a3.55,3.55,0,0,1,3-3.5,3.27,3.27,0,0,1,3.7,3.3v25.7Z"
|
||||
style="fill:#3b3b3b" />
|
||||
<path
|
||||
d="M495.3,150v42.3a3.4,3.4,0,0,1-3.3,3.3,3.21,3.21,0,0,1-3.3-3.3V150c0-14.3-8.1-28.5-24-28.5-20.4,0-29.2,17.8-28,36.1,0,.5.2,2.6.2,2.9v31.7a3.55,3.55,0,0,1-3,3.5,3.27,3.27,0,0,1-3.7-3.3V53.3a3.33,3.33,0,0,1,3.3-3.3,3.4,3.4,0,0,1,3.3,3.3v78.6c5.7-10.2,15.9-17.1,27.8-17.1,19.6,0,30.7,17.1,30.7,35.2Z"
|
||||
style="fill:#3b3b3b" />
|
||||
<path
|
||||
d="M614.4,155.7a3.56,3.56,0,0,1-3.6,3.5H544.5c1.7,16.4,14,31.4,31.4,31.4,11.9,0,20.7-4.5,27.3-14a3.58,3.58,0,0,1,2.9-1.7,3.21,3.21,0,0,1,3.3,3.3,3.1,3.1,0,0,1-.5,1.7c-6.7,11.6-20,17.3-33,17.3-22.3,0-38.3-20-38.3-41.3s15.9-41.3,38.3-41.3c22.2,0,38.4,19.8,38.5,41.1Zm-7.2-3.1c-1.4-16.4-14-31.4-31.4-31.4s-29.7,15-31.4,31.4Z"
|
||||
style="fill:#3b3b3b" />
|
||||
<path
|
||||
d="M695.9,119.3a3.37,3.37,0,0,1-3.1,3.6c-19.5,2.9-28.3,18.8-28.3,37.3v31.7a3.55,3.55,0,0,1-3,3.5,3.27,3.27,0,0,1-3.7-3.3V119.8a3.55,3.55,0,0,1,3-3.5,3.27,3.27,0,0,1,3.7,3.3v14.7c5.5-9.3,16.4-18.1,27.8-18.1C694,116.2,695.9,117.4,695.9,119.3Z"
|
||||
style="fill:#3b3b3b" />
|
||||
<path
|
||||
d="M804.9,155.7a3.56,3.56,0,0,1-3.6,3.5H735c1.7,16.4,14,31.4,31.4,31.4,11.9,0,20.7-4.5,27.3-14a3.58,3.58,0,0,1,2.9-1.7,3.21,3.21,0,0,1,3.3,3.3,3.1,3.1,0,0,1-.5,1.7c-6.7,11.6-20,17.3-33,17.3-22.3,0-38.3-20-38.3-41.3s15.9-41.3,38.3-41.3,38.4,19.8,38.5,41.1Zm-7.1-3.1c-1.4-16.4-14-31.4-31.4-31.4s-29.7,15-31.4,31.4Z"
|
||||
style="fill:#3b3b3b" />
|
||||
<path
|
||||
d="M912.1,120.1v72.6a3.4,3.4,0,0,1-3.3,3.3,3.21,3.21,0,0,1-3.3-3.3V178.9c-5.5,10.9-15.2,18.8-27.6,18.8-19.7,0-30.6-17.1-30.6-35.2V120a3.33,3.33,0,0,1,3.3-3.3,3.4,3.4,0,0,1,3.3,3.3v42.5c0,14.3,8.1,28.5,24,28.5,22.3,0,27.6-20.9,27.6-44V119.9a3.35,3.35,0,0,1,4.5-3.1,3.63,3.63,0,0,1,2.1,3.3Z"
|
||||
style="fill:#3b3b3b" />
|
||||
<path
|
||||
d="M1080,149.7v42.5a3.4,3.4,0,0,1-3.3,3.3,3.21,3.21,0,0,1-3.3-3.3V149.7c0-14.3-8.1-28.3-24-28.3-20,0-27.6,21.4-27.6,38v32.8a3.4,3.4,0,0,1-3.3,3.3,3.21,3.21,0,0,1-3.3-3.3V149.7c0-14.3-8.1-28.3-24-28.3-20.2,0-28.5,15.9-27.8,37.1,0,.5.2,1.4,0,1.7v31.9a3.55,3.55,0,0,1-3,3.5,3.27,3.27,0,0,1-3.7-3.3V119.8a3.55,3.55,0,0,1,3-3.5,3.27,3.27,0,0,1,3.7,3.3v12.1c5.7-10.2,15.9-16.9,27.8-16.9,13.5,0,24,8.6,28.3,21.1,5.5-12.4,16.2-21.1,29.9-21.1,19.5,0,30.6,16.9,30.6,34.9Z"
|
||||
style="fill:#3b3b3b" />
|
||||
<path d="M83,100.1,0,137.8l83,49.1,83.1-49.1Z" style="opacity:0.6000000238418579;isolation:isolate" />
|
||||
<path d="M0,137.8l83,49.1V0Z" style="opacity:0.44999998807907104;isolation:isolate" />
|
||||
<path d="M83,0V186.9l83.1-49.1Z" style="opacity:0.800000011920929;isolation:isolate" />
|
||||
<path d="M0,153.6l83,117v-68Z" style="opacity:0.44999998807907104;isolation:isolate" />
|
||||
<path d="M83,202.6v68l83.1-117Z" style="opacity:0.800000011920929;isolation:isolate" />
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.0 KiB |
@@ -0,0 +1,18 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<!-- Creator: CorelDRAW 2019 (64-Bit) -->
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" width="100%" height="100%" version="1.1" shape-rendering="geometricPrecision" text-rendering="geometricPrecision" image-rendering="optimizeQuality" fill-rule="evenodd" clip-rule="evenodd" viewBox="0 0 784.37 1277.39" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:xodm="http://www.corel.com/coreldraw/odm/2003">
|
||||
<g id="Layer_x0020_1">
|
||||
<metadata id="CorelCorpID_0Corel-Layer"/>
|
||||
<g id="_1421394342400">
|
||||
<g>
|
||||
<polygon fill="#343434" fill-rule="nonzero" points="392.07,0 383.5,29.11 383.5,873.74 392.07,882.29 784.13,650.54 "/>
|
||||
<polygon fill="#8C8C8C" fill-rule="nonzero" points="392.07,0 -0,650.54 392.07,882.29 392.07,472.33 "/>
|
||||
<polygon fill="#3C3C3B" fill-rule="nonzero" points="392.07,956.52 387.24,962.41 387.24,1263.28 392.07,1277.38 784.37,724.89 "/>
|
||||
<polygon fill="#8C8C8C" fill-rule="nonzero" points="392.07,1277.38 392.07,956.52 -0,724.89 "/>
|
||||
<polygon fill="#141414" fill-rule="nonzero" points="392.07,882.29 784.13,650.54 392.07,472.33 "/>
|
||||
<polygon fill="#393939" fill-rule="nonzero" points="0,650.54 392.07,882.29 392.07,472.33 "/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 7.1 KiB |
|
After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 4.1 KiB |
|
After Width: | Height: | Size: 6.2 KiB |
|
After Width: | Height: | Size: 9.3 KiB |
@@ -0,0 +1,25 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
<svg xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:cc="http://creativecommons.org/ns#" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="282" height="75" id="Monero-Logo">
|
||||
<metadata id="metadata8">
|
||||
<rdf:RDF>
|
||||
<cc:Work rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"/>
|
||||
<dc:title/>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<defs id="defs6"/>
|
||||
<path transform="scale(0.5, 0.5)" d="M0 24.48C0 10.9601 10.9601 0 24.48 0H520.2C533.72 0 544.68 10.9601 544.68 24.48V128.52C544.68 142.04 533.72 153 520.2 153H24.48C10.9601 153 0 142.04 0 128.52V24.48Z" fill="#eeeeee"/>
|
||||
<g transform="translate(10, 5) scale(0.85, 0.85)">
|
||||
<path d="m 37.3,0.35329395 c -20.377,0 -36.903,16.524 -36.903,36.902 0,4.074 0.66,7.992 1.88,11.657 l 11.036,0 0,-31.049 23.987,23.987 23.987,-23.987 0,31.049 11.037,0 c 1.22,-3.665 1.88,-7.583 1.88,-11.657 0,-20.378 -16.526,-36.902 -36.904,-36.902" id="path22" style="fill:#ff6600"/>
|
||||
<path d="m 21.3164,36.895994 0,19.537 -15.55,0 c 6.478,10.628 18.178,17.726 31.533,17.726 13.355,0 25.056,-7.098 31.533,-17.726 l -15.549,0 0,-19.537 -15.984,15.984 z" id="path26" style="fill:#4c4c4c"/>
|
||||
<path d="m 272.7087,47.761494 c -1.951,2.009 -4.317,3.01 -7.099,3.01 -2.458,0 -4.631,-0.772 -6.533,-2.324 -2.445,-1.979 -3.666,-4.674 -3.666,-8.084 0,-3.053 0.972,-5.576 2.916,-7.556 1.937,-1.987 4.331,-2.974 7.184,-2.974 2.817,0 5.212,1.016 7.177,3.045 1.973,2.03 2.959,4.512 2.959,7.449 0,2.945 -0.978,5.418 -2.938,7.434 m 4.097,-18.937 c -3.132,-3.151 -6.877,-4.731 -11.238,-4.731 -2.874,0 -5.561,0.723 -8.048,2.166 -2.496,1.444 -4.455,3.402 -5.877,5.876 -1.423,2.473 -2.137,5.183 -2.137,8.127 0,4.397 1.529,8.192 4.59,11.389 3.058,3.202 6.898,4.796 11.514,4.796 4.411,0 8.165,-1.551 11.26,-4.668 3.095,-3.11 4.639,-6.919 4.639,-11.416 0,-4.533 -1.566,-8.378 -4.703,-11.539" id="path30" style="fill:#4c4c4c"/>
|
||||
<path d="m 238.3063,35.970494 c -0.743,0.518 -1.094,0.773 -3.06,0.773 l -7.736,0 0,-6.618 7.496,0 c 1.503,0 1.769,0.113 2.385,0.345 0.614,0.225 1.102,0.601 1.47,1.118 0.368,0.518 0.548,1.133 0.548,1.838 0,1.186 -0.368,2.034 -1.103,2.544 m 0.93,6.093 c 2.049,-0.736 3.587,-1.816 4.607,-3.241 1.021,-1.433 1.524,-3.205 1.524,-5.335 0,-2.019 -0.457,-3.775 -1.381,-5.253 -0.923,-1.478 -2.146,-2.536 -3.661,-3.174 -1.516,-0.638 -4.06,-0.96 -7.639,-0.96 l -11.329,0 0,32.34 6.153,0 0,-13.694 5.37,0 7.28,13.694 6.73,0 -7.654,-14.377 z" id="path34" style="fill:#4c4c4c"/>
|
||||
<path d="m 193.7751,24.093494 20.968,0 0,6.025 -14.852,0 0,6.619 14.852,0 0,5.92 -14.852,0 0,7.728 14.852,0 0,6.049 -20.968,0 0,-32.341 z" id="path38" style="fill:#4c4c4c"/>
|
||||
<path d="m 161.2868,24.093494 5.891,0 13.874,21.28 0,-21.28 6.153,0 0,32.34 -5.913,0 -13.852,-21.212 0,21.212 -6.153,0 0,-32.34 z" id="path42" style="fill:#4c4c4c"/>
|
||||
<use transform="translate(-125.0586,0)" id="use46" x="0" y="0" width="282" height="75" xlink:href="#path30"/>
|
||||
<path d="m 89.6882,24.092594 6.025,0 7.473,22.557 7.587,-22.557 5.935,0 5.449,32.341 -5.936,0 -3.474,-20.425 -6.881,20.425 -5.426,0 -6.79,-20.425 -3.542,20.425 -6.003,0 5.583,-32.341 z" id="path50" style="fill:#4c4c4c"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.4 KiB |
@@ -0,0 +1 @@
|
||||
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 3756.09 3756.49"><title>monero</title><path d="M4128,2249.81C4128,3287,3287.26,4127.86,2250,4127.86S372,3287,372,2249.81,1212.76,371.75,2250,371.75,4128,1212.54,4128,2249.81Z" transform="translate(-371.96 -371.75)" style="fill:#fff"/><path id="_149931032" data-name=" 149931032" d="M2250,371.75c-1036.89,0-1879.12,842.06-1877.8,1878,0.26,207.26,33.31,406.63,95.34,593.12h561.88V1263L2250,2483.57,3470.52,1263v1579.9h562c62.12-186.48,95-385.85,95.37-593.12C4129.66,1212.76,3287,372,2250,372Z" transform="translate(-371.96 -371.75)" style="fill:#f26822"/><path id="_149931160" data-name=" 149931160" d="M1969.3,2764.17l-532.67-532.7v994.14H1029.38l-384.29.07c329.63,540.8,925.35,902.56,1604.91,902.56S3525.31,3766.4,3855,3225.6H3063.25V2231.47l-532.7,532.7-280.61,280.61-280.62-280.61h0Z" transform="translate(-371.96 -371.75)" style="fill:#4d4d4d"/></svg>
|
||||
|
After Width: | Height: | Size: 940 B |
@@ -0,0 +1,15 @@
|
||||
<svg width="545" height="153" viewBox="0 0 545 153" xmlns="http://www.w3.org/2000/svg">
|
||||
<path transform="scale(1, 1)" d="M0 24.48C0 10.9601 10.9601 0 24.48 0H520.2C533.72 0 544.68 10.9601 544.68 24.48V128.52C544.68 142.04 533.72 153 520.2 153H24.48C10.9601 153 0 142.04 0 128.52V24.48Z" fill="#fff1eb"/>
|
||||
<g transform="translate(20, -50) scale(4.2, 4.2)">
|
||||
<g fill="#222c31" transform="matrix(.05770525 0 0 .05770525 14.044352 21.072947)">
|
||||
<path d="m1680 272c-10.3 0-20.6 0-31.7 0 0-7.7 0-15.3 0-22.9-.1-29.5 0-59-.3-88.5-.3-23.1-17.7-43.9-40.3-48.6-31-6.5-62.5 18.6-62.6 50.4-.1 34.5.1 69 .2 103.5v6.2c-10.5 0-20.4 0-30.8 0 0-60.9 0-121.8 0-183.2h30.5v10.1c10-8.9 20.6-14.4 32.6-17.4 47.2-11.9 96.6 22.8 101.4 71.2.1 1.4.6 2.9.9 4.3.1 38.2.1 76.6.1 114.9z"/>
|
||||
<path d="m481.8 251.5v91.4c-10.5 0-20.4 0-30.6 0-.1-1.3-.3-2.6-.3-3.9 0-53.2-.2-106.3.1-159.5.3-48.1 34-88.7 81-98.2 56.5-11.4 111.8 28.3 119.2 85.5 7.3 56.4-31.5 107-88.1 113.8-29.7 3.5-55.3-6.7-77.3-26.5-1-.9-1.5-2.5-2.3-3.8-.6.4-1.1.8-1.7 1.2zm69.9-1.1c38.7-.1 69.6-31.3 69.6-70.1-.1-38.7-31.4-70-69.9-69.8-38.6.2-69.6 31.4-69.5 70.2-.1 38.6 31.1 69.8 69.8 69.7z"/>
|
||||
<path d="m1127.7 221.3c14.8 22.6 46.1 33.8 73.8 26.4 16.9-4.5 30.4-14.2 40.1-28.7s13.3-30.7 11.2-48.4h30.6c5.5 38.7-17.6 89.2-69.4 105.7-49 15.6-101.9-8.4-122.6-55.5-20.8-47.3-2.5-102.8 42.6-128.5 46.7-26.6 99-9.2 123.4 19.9-43.2 36.3-86.3 72.6-129.7 109.1zm-13-29.5c31.1-26.1 61.3-51.5 91.8-77.2-23.3-9.7-53-3.2-71.9 15.7-16.6 16.7-22.9 37-19.9 61.5z"/>
|
||||
<path d="m864 272.1c-10.5 0-20.5 0-30.9 0 0-6.2 0-12.1 0-18.9-1.6 1.3-2.5 2-3.3 2.7-57.3 49.6-145.6 22-164.2-51.3-17.1-67.6 38.5-131.9 107.8-124.9 51 5.2 90 47.7 90.5 98.9.3 30 .1 60 .1 90zm-170.5-92.1c-.1 38.4 31 69.9 69.2 70.1 38.6.1 70.3-30.8 70.4-68.8.1-39.7-30.7-71.1-69.6-71.1-38.6-.2-69.9 31.1-70 69.8z"/>
|
||||
<path d="m1295.3 180c.1-55.6 45.3-100.8 100.7-100.8 55.6 0 101 45.7 100.7 101.3-.3 55.7-45.4 100.6-101 100.5-55.4-.1-100.5-45.4-100.4-101zm100.6 70c38.6 0 70-31.2 70-69.8.1-38.5-31.3-70.1-69.7-70.2-38.5-.1-70 31.3-70.1 69.8 0 38.7 31.3 70.2 69.8 70.2z"/>
|
||||
<path d="m873.8 88.3h17.7c0-23.7 0-47.1 0-70.8h31v70.8h46.2v30.9c-15.2 0-30.4 0-46.1 0v152.8c-10.4 0-20.3 0-30.8 0 0-2.2 0-4.1 0-6 0-47-.1-94 .1-141 0-4.5-1.1-6.3-5.8-5.8-3.9.4-7.9.1-12.2.1-.1-10.4-.1-20.3-.1-31z"/>
|
||||
<path d="m1015.5 115.1c14.7-13.2 31.5-22.3 50.9-25.7 6.8-1.2 13.8-1.3 21.2-2v28.4c0 3.5-2.6 2.9-4.6 2.9-15.4.2-29.2 5.1-41.1 14.6-17.3 13.7-26.7 31.7-27 53.9-.3 26.3-.1 52.7-.1 79v5.8c-10.3 0-20.2 0-30.5 0 0-61.1 0-122.1 0-183.3h29.9v25.8c.5.2.9.4 1.3.6z"/>
|
||||
</g>
|
||||
<path d="m23.075695 16.136992c.788702.142108 1.591615.23448 2.366108.426327 5.343282 1.321609 9.393376 5.854873 10.146551 11.326052 1.001867 7.275959-3.73035 13.926641-10.942359 15.369042-.518699.106583-1.051605.163427-1.577407.241587-2.415846 0-4.831691 0-7.247538 0 0-4.583002 0-9.166003.0072-13.741902 0-.305532.02841-.618169.07816-.916598.468959-3.076652 3.275603-5.400126 6.352254-5.279335 3.247181.135003 5.854875 2.643221 6.060931 5.840663.305534 4.746427-4.526157 8.078872-8.853364 6.103563-.09238-.04263-.18474-.07816-.319745-.127898 0 1.456613-.0072 2.863488.01421 4.270363 0 .08527.163425.213163.270006.241585 1.385561.397904 2.79954.476064 4.220625.248689 6.011193-.966338 9.890758-6.671998 8.590465-12.597925-1.200819-5.48539-6.68621-9.073631-12.150284-7.95808-4.945379.994761-8.398617 5.215385-8.412828 10.274451-.0072 4.426683 0 8.853363 0 13.280046 0 .120793.01421.234479.02841.355272-1.087131 0-2.181366 0-3.268497 0 0-4.902746 0-9.805491 0-14.708238.04264-.277112.09237-.554223.127898-.838442.753175-5.520918 4.781953-10.03997 10.189184-11.390001.767387-.191846 1.556089-.284216 2.330581-.419221h1.98952z" fill="#e6461a" stroke-width=".071054"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.7 KiB |
|
After Width: | Height: | Size: 3.3 KiB |
@@ -0,0 +1,15 @@
|
||||
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" width="545" height="153" viewBox="0 0 545 153">
|
||||
<defs>
|
||||
<style>
|
||||
.cls-1{fill:#009ee3;}.cls-1,.cls-2,.cls-3{fill-rule:evenodd;}.cls-2{fill:#113984;}.cls-3{fill:#172c70;}</style>
|
||||
</defs>
|
||||
<title>paypal-seeklogo.com</title>
|
||||
<path transform="scale(1, 1)" d="M0 24.48C0 10.9601 10.9601 0 24.48 0H520.2C533.72 0 544.68 10.9601 544.68 24.48V128.52C544.68 142.04 533.72 153 520.2 153H24.48C10.9601 153 0 142.04 0 128.52V24.48Z" fill="#ebf2ff"/>
|
||||
<g transform="scale(0.8, 0.8) translate(45, 25)">
|
||||
<path class="cls-1" d="M192.95,386.87h38.74c20.8,0,28.63,10.53,27.42,26-2,25.54-17.44,39.67-37.92,39.67H210.85c-2.81,0-4.7,1.86-5.46,6.9L201,488.74c-0.29,1.9-1.29,3-2.79,3.15H173.87c-2.29,0-3.1-1.75-2.5-5.54l14.84-93.93C186.79,388.66,188.85,386.87,192.95,386.87Z" transform="translate(-143.48 -354.54)"/>
|
||||
<path class="cls-2" d="M361.14,385.13c13.07,0,25.13,7.09,23.48,24.76-2,21-13.25,32.62-31,32.67H338.11c-2.23,0-3.31,1.82-3.89,5.55l-3,19.07c-0.45,2.88-1.93,4.3-4.11,4.3H312.68c-2.3,0-3.1-1.47-2.59-4.76L322,390.29c0.59-3.76,2-5.16,4.57-5.16h34.54Zm-23.5,40.92h11.75c7.35-.28,12.23-5.37,12.72-14.55,0.3-5.67-3.53-9.73-9.62-9.7l-11.06.05-3.79,24.2h0Zm86.21,39.58c1.32-1.2,2.66-1.82,2.47-.34l-0.47,3.54c-0.24,1.85.49,2.83,2.21,2.83h12.82c2.16,0,3.21-.87,3.74-4.21l7.9-49.58c0.4-2.49-.21-3.71-2.1-3.71H436.32c-1.27,0-1.89.71-2.22,2.65l-0.52,3.05c-0.27,1.59-1,1.87-1.68.27-2.39-5.66-8.49-8.2-17-8-19.77.41-33.1,15.42-34.53,34.66-1.1,14.88,9.56,26.57,23.62,26.57,10.2,0,14.76-3,19.9-7.7h0ZM413.11,458c-8.51,0-14.44-6.79-13.21-15.11s9.19-15.11,17.7-15.11,14.44,6.79,13.21,15.11S421.63,458,413.11,458h0Zm64.5-44h-13c-2.68,0-3.77,2-2.92,4.46l16.14,47.26L462,488.21c-1.33,1.88-.3,3.59,1.57,3.59h14.61a4.47,4.47,0,0,0,4.34-2.13l49.64-71.2c1.53-2.19.81-4.49-1.7-4.49H516.63c-2.37,0-3.32.94-4.68,2.91l-20.7,30L482,416.82C481.46,415,480.11,414,477.62,414Z" transform="translate(-143.48 -354.54)"/>
|
||||
<path class="cls-1" d="M583.8,385.13c13.07,0,25.13,7.09,23.48,24.76-2,21-13.25,32.62-31,32.67H560.78c-2.23,0-3.31,1.82-3.89,5.55l-3,19.07c-0.45,2.88-1.93,4.3-4.11,4.3H535.35c-2.3,0-3.1-1.47-2.59-4.76l11.93-76.45c0.59-3.76,2-5.16,4.57-5.16H583.8Zm-23.5,40.92h11.75c7.35-.28,12.23-5.37,12.72-14.55,0.3-5.67-3.53-9.73-9.62-9.7l-11.06.05-3.79,24.2h0Zm86.21,39.58c1.32-1.2,2.66-1.82,2.47-.34l-0.47,3.54c-0.24,1.85.49,2.83,2.21,2.83h12.82c2.16,0,3.21-.87,3.74-4.21l7.9-49.58c0.4-2.49-.21-3.71-2.1-3.71H659c-1.27,0-1.89.71-2.22,2.65l-0.52,3.05c-0.27,1.59-1,1.87-1.68.27-2.39-5.66-8.49-8.2-17-8-19.77.41-33.1,15.42-34.53,34.66-1.1,14.88,9.56,26.57,23.62,26.57,10.2,0,14.76-3,19.9-7.7h0ZM635.78,458c-8.51,0-14.44-6.79-13.21-15.11s9.19-15.11,17.7-15.11,14.44,6.79,13.21,15.11S644.29,458,635.78,458h0Zm59.13,13.74h-14.8a1.75,1.75,0,0,1-1.81-2l13-82.36a2.55,2.55,0,0,1,2.46-2h14.8a1.75,1.75,0,0,1,1.81,2l-13,82.36A2.55,2.55,0,0,1,694.91,471.76Z" transform="translate(-143.48 -354.54)"/>
|
||||
<path class="cls-2" d="M168.72,354.54h38.78c10.92,0,23.88.35,32.54,8,5.79,5.11,8.83,13.24,8.13,22-2.38,29.61-20.09,46.2-43.85,46.2H185.2c-3.26,0-5.41,2.16-6.33,8l-5.34,34c-0.35,2.2-1.3,3.5-3,3.66H146.6c-2.65,0-3.59-2-2.9-6.42L160.9,361C161.59,356.62,164,354.54,168.72,354.54Z" transform="translate(-143.48 -354.54)"/>
|
||||
<path class="cls-3" d="M179.43,435.29l6.77-42.87c0.59-3.76,2.65-5.56,6.75-5.56h38.74c6.41,0,11.6,1,15.66,2.85-3.89,26.36-20.94,41-43.26,41H185C182.44,430.72,180.56,432,179.43,435.29Z" transform="translate(-143.48 -354.54)"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.4 KiB |
|
After Width: | Height: | Size: 1.7 KiB |
@@ -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 %}
|
||||
@@ -0,0 +1,93 @@
|
||||
/*! URI.js v1.19.2 http://medialize.github.io/URI.js/ */
|
||||
/* build contains: IPv6.js, punycode.js, SecondLevelDomains.js, URI.js, URITemplate.js */
|
||||
(function(f,n){"object"===typeof module&&module.exports?module.exports=n():"function"===typeof define&&define.amd?define(n):f.IPv6=n(f)})(this,function(f){var n=f&&f.IPv6;return{best:function(h){h=h.toLowerCase().split(":");var k=h.length,b=8;""===h[0]&&""===h[1]&&""===h[2]?(h.shift(),h.shift()):""===h[0]&&""===h[1]?h.shift():""===h[k-1]&&""===h[k-2]&&h.pop();k=h.length;-1!==h[k-1].indexOf(".")&&(b=7);var q;for(q=0;q<k&&""!==h[q];q++);if(q<b)for(h.splice(q,1,"0000");h.length<b;)h.splice(q,0,"0000");
|
||||
for(q=0;q<b;q++){k=h[q].split("");for(var f=0;3>f;f++)if("0"===k[0]&&1<k.length)k.splice(0,1);else break;h[q]=k.join("")}k=-1;var n=f=0,g=-1,p=!1;for(q=0;q<b;q++)p?"0"===h[q]?n+=1:(p=!1,n>f&&(k=g,f=n)):"0"===h[q]&&(p=!0,g=q,n=1);n>f&&(k=g,f=n);1<f&&h.splice(k,f,"");k=h.length;b="";""===h[0]&&(b=":");for(q=0;q<k;q++){b+=h[q];if(q===k-1)break;b+=":"}""===h[k-1]&&(b+=":");return b},noConflict:function(){f.IPv6===this&&(f.IPv6=n);return this}}});
|
||||
(function(f){function n(b){throw new RangeError(w[b]);}function h(b,e){for(var g=b.length,h=[];g--;)h[g]=e(b[g]);return h}function k(b,e){var g=b.split("@"),f="";1<g.length&&(f=g[0]+"@",b=g[1]);b=b.replace(C,".");g=b.split(".");g=h(g,e).join(".");return f+g}function b(b){for(var e=[],g=0,h=b.length,f,a;g<h;)f=b.charCodeAt(g++),55296<=f&&56319>=f&&g<h?(a=b.charCodeAt(g++),56320==(a&64512)?e.push(((f&1023)<<10)+(a&1023)+65536):(e.push(f),g--)):e.push(f);return e}function q(b){return h(b,function(b){var e=
|
||||
"";65535<b&&(b-=65536,e+=t(b>>>10&1023|55296),b=56320|b&1023);return e+=t(b)}).join("")}function z(b,e){return b+22+75*(26>b)-((0!=e)<<5)}function u(b,g,h){var f=0;b=h?e(b/700):b>>1;for(b+=e(b/g);455<b;f+=36)b=e(b/35);return e(f+36*b/(b+38))}function g(b){var g=[],h=b.length,f=0,k=128,a=72,c,d;var l=b.lastIndexOf("-");0>l&&(l=0);for(c=0;c<l;++c)128<=b.charCodeAt(c)&&n("not-basic"),g.push(b.charCodeAt(c));for(l=0<l?l+1:0;l<h;){c=f;var m=1;for(d=36;;d+=36){l>=h&&n("invalid-input");var x=b.charCodeAt(l++);
|
||||
x=10>x-48?x-22:26>x-65?x-65:26>x-97?x-97:36;(36<=x||x>e((2147483647-f)/m))&&n("overflow");f+=x*m;var p=d<=a?1:d>=a+26?26:d-a;if(x<p)break;x=36-p;m>e(2147483647/x)&&n("overflow");m*=x}m=g.length+1;a=u(f-c,m,0==c);e(f/m)>2147483647-k&&n("overflow");k+=e(f/m);f%=m;g.splice(f++,0,k)}return q(g)}function p(g){var h,f,k,p=[];g=b(g);var a=g.length;var c=128;var d=0;var l=72;for(k=0;k<a;++k){var m=g[k];128>m&&p.push(t(m))}for((h=f=p.length)&&p.push("-");h<a;){var x=2147483647;for(k=0;k<a;++k)m=g[k],m>=c&&
|
||||
m<x&&(x=m);var q=h+1;x-c>e((2147483647-d)/q)&&n("overflow");d+=(x-c)*q;c=x;for(k=0;k<a;++k)if(m=g[k],m<c&&2147483647<++d&&n("overflow"),m==c){var r=d;for(x=36;;x+=36){m=x<=l?1:x>=l+26?26:x-l;if(r<m)break;var A=r-m;r=36-m;p.push(t(z(m+A%r,0)));r=e(A/r)}p.push(t(z(r,0)));l=u(d,q,h==f);d=0;++h}++d;++c}return p.join("")}var D="object"==typeof exports&&exports&&!exports.nodeType&&exports,A="object"==typeof module&&module&&!module.nodeType&&module,B="object"==typeof global&&global;if(B.global===B||B.window===
|
||||
B||B.self===B)f=B;var E=/^xn--/,r=/[^\x20-\x7E]/,C=/[\x2E\u3002\uFF0E\uFF61]/g,w={overflow:"Overflow: input needs wider integers to process","not-basic":"Illegal input >= 0x80 (not a basic code point)","invalid-input":"Invalid input"},e=Math.floor,t=String.fromCharCode,y;var v={version:"1.3.2",ucs2:{decode:b,encode:q},decode:g,encode:p,toASCII:function(b){return k(b,function(b){return r.test(b)?"xn--"+p(b):b})},toUnicode:function(b){return k(b,function(b){return E.test(b)?g(b.slice(4).toLowerCase()):
|
||||
b})}};if("function"==typeof define&&"object"==typeof define.amd&&define.amd)define("punycode",function(){return v});else if(D&&A)if(module.exports==D)A.exports=v;else for(y in v)v.hasOwnProperty(y)&&(D[y]=v[y]);else f.punycode=v})(this);
|
||||
(function(f,n){"object"===typeof module&&module.exports?module.exports=n():"function"===typeof define&&define.amd?define(n):f.SecondLevelDomains=n(f)})(this,function(f){var n=f&&f.SecondLevelDomains,h={list:{ac:" com gov mil net org ",ae:" ac co gov mil name net org pro sch ",af:" com edu gov net org ",al:" com edu gov mil net org ",ao:" co ed gv it og pb ",ar:" com edu gob gov int mil net org tur ",at:" ac co gv or ",au:" asn com csiro edu gov id net org ",ba:" co com edu gov mil net org rs unbi unmo unsa untz unze ",
|
||||
bb:" biz co com edu gov info net org store tv ",bh:" biz cc com edu gov info net org ",bn:" com edu gov net org ",bo:" com edu gob gov int mil net org tv ",br:" adm adv agr am arq art ato b bio blog bmd cim cng cnt com coop ecn edu eng esp etc eti far flog fm fnd fot fst g12 ggf gov imb ind inf jor jus lel mat med mil mus net nom not ntr odo org ppg pro psc psi qsl rec slg srv tmp trd tur tv vet vlog wiki zlg ",bs:" com edu gov net org ",bz:" du et om ov rg ",ca:" ab bc mb nb nf nl ns nt nu on pe qc sk yk ",
|
||||
ck:" biz co edu gen gov info net org ",cn:" ac ah bj com cq edu fj gd gov gs gx gz ha hb he hi hl hn jl js jx ln mil net nm nx org qh sc sd sh sn sx tj tw xj xz yn zj ",co:" com edu gov mil net nom org ",cr:" ac c co ed fi go or sa ",cy:" ac biz com ekloges gov ltd name net org parliament press pro tm ","do":" art com edu gob gov mil net org sld web ",dz:" art asso com edu gov net org pol ",ec:" com edu fin gov info med mil net org pro ",eg:" com edu eun gov mil name net org sci ",er:" com edu gov ind mil net org rochest w ",
|
||||
es:" com edu gob nom org ",et:" biz com edu gov info name net org ",fj:" ac biz com info mil name net org pro ",fk:" ac co gov net nom org ",fr:" asso com f gouv nom prd presse tm ",gg:" co net org ",gh:" com edu gov mil org ",gn:" ac com gov net org ",gr:" com edu gov mil net org ",gt:" com edu gob ind mil net org ",gu:" com edu gov net org ",hk:" com edu gov idv net org ",hu:" 2000 agrar bolt casino city co erotica erotika film forum games hotel info ingatlan jogasz konyvelo lakas media news org priv reklam sex shop sport suli szex tm tozsde utazas video ",
|
||||
id:" ac co go mil net or sch web ",il:" ac co gov idf k12 muni net org ","in":" ac co edu ernet firm gen gov i ind mil net nic org res ",iq:" com edu gov i mil net org ",ir:" ac co dnssec gov i id net org sch ",it:" edu gov ",je:" co net org ",jo:" com edu gov mil name net org sch ",jp:" ac ad co ed go gr lg ne or ",ke:" ac co go info me mobi ne or sc ",kh:" com edu gov mil net org per ",ki:" biz com de edu gov info mob net org tel ",km:" asso com coop edu gouv k medecin mil nom notaires pharmaciens presse tm veterinaire ",
|
||||
kn:" edu gov net org ",kr:" ac busan chungbuk chungnam co daegu daejeon es gangwon go gwangju gyeongbuk gyeonggi gyeongnam hs incheon jeju jeonbuk jeonnam k kg mil ms ne or pe re sc seoul ulsan ",kw:" com edu gov net org ",ky:" com edu gov net org ",kz:" com edu gov mil net org ",lb:" com edu gov net org ",lk:" assn com edu gov grp hotel int ltd net ngo org sch soc web ",lr:" com edu gov net org ",lv:" asn com conf edu gov id mil net org ",ly:" com edu gov id med net org plc sch ",ma:" ac co gov m net org press ",
|
||||
mc:" asso tm ",me:" ac co edu gov its net org priv ",mg:" com edu gov mil nom org prd tm ",mk:" com edu gov inf name net org pro ",ml:" com edu gov net org presse ",mn:" edu gov org ",mo:" com edu gov net org ",mt:" com edu gov net org ",mv:" aero biz com coop edu gov info int mil museum name net org pro ",mw:" ac co com coop edu gov int museum net org ",mx:" com edu gob net org ",my:" com edu gov mil name net org sch ",nf:" arts com firm info net other per rec store web ",ng:" biz com edu gov mil mobi name net org sch ",
|
||||
ni:" ac co com edu gob mil net nom org ",np:" com edu gov mil net org ",nr:" biz com edu gov info net org ",om:" ac biz co com edu gov med mil museum net org pro sch ",pe:" com edu gob mil net nom org sld ",ph:" com edu gov i mil net ngo org ",pk:" biz com edu fam gob gok gon gop gos gov net org web ",pl:" art bialystok biz com edu gda gdansk gorzow gov info katowice krakow lodz lublin mil net ngo olsztyn org poznan pwr radom slupsk szczecin torun warszawa waw wroc wroclaw zgora ",pr:" ac biz com edu est gov info isla name net org pro prof ",
|
||||
ps:" com edu gov net org plo sec ",pw:" belau co ed go ne or ",ro:" arts com firm info nom nt org rec store tm www ",rs:" ac co edu gov in org ",sb:" com edu gov net org ",sc:" com edu gov net org ",sh:" co com edu gov net nom org ",sl:" com edu gov net org ",st:" co com consulado edu embaixada gov mil net org principe saotome store ",sv:" com edu gob org red ",sz:" ac co org ",tr:" av bbs bel biz com dr edu gen gov info k12 name net org pol tel tsk tv web ",tt:" aero biz cat co com coop edu gov info int jobs mil mobi museum name net org pro tel travel ",
|
||||
tw:" club com ebiz edu game gov idv mil net org ",mu:" ac co com gov net or org ",mz:" ac co edu gov org ",na:" co com ",nz:" ac co cri geek gen govt health iwi maori mil net org parliament school ",pa:" abo ac com edu gob ing med net nom org sld ",pt:" com edu gov int net nome org publ ",py:" com edu gov mil net org ",qa:" com edu gov mil net org ",re:" asso com nom ",ru:" ac adygeya altai amur arkhangelsk astrakhan bashkiria belgorod bir bryansk buryatia cbg chel chelyabinsk chita chukotka chuvashia com dagestan e-burg edu gov grozny int irkutsk ivanovo izhevsk jar joshkar-ola kalmykia kaluga kamchatka karelia kazan kchr kemerovo khabarovsk khakassia khv kirov koenig komi kostroma kranoyarsk kuban kurgan kursk lipetsk magadan mari mari-el marine mil mordovia mosreg msk murmansk nalchik net nnov nov novosibirsk nsk omsk orenburg org oryol penza perm pp pskov ptz rnd ryazan sakhalin samara saratov simbirsk smolensk spb stavropol stv surgut tambov tatarstan tom tomsk tsaritsyn tsk tula tuva tver tyumen udm udmurtia ulan-ude vladikavkaz vladimir vladivostok volgograd vologda voronezh vrn vyatka yakutia yamal yekaterinburg yuzhno-sakhalinsk ",
|
||||
rw:" ac co com edu gouv gov int mil net ",sa:" com edu gov med net org pub sch ",sd:" com edu gov info med net org tv ",se:" a ac b bd c d e f g h i k l m n o org p parti pp press r s t tm u w x y z ",sg:" com edu gov idn net org per ",sn:" art com edu gouv org perso univ ",sy:" com edu gov mil net news org ",th:" ac co go in mi net or ",tj:" ac biz co com edu go gov info int mil name net nic org test web ",tn:" agrinet com defense edunet ens fin gov ind info intl mincom nat net org perso rnrt rns rnu tourism ",
|
||||
tz:" ac co go ne or ",ua:" biz cherkassy chernigov chernovtsy ck cn co com crimea cv dn dnepropetrovsk donetsk dp edu gov if in ivano-frankivsk kh kharkov kherson khmelnitskiy kiev kirovograd km kr ks kv lg lugansk lutsk lviv me mk net nikolaev od odessa org pl poltava pp rovno rv sebastopol sumy te ternopil uzhgorod vinnica vn zaporizhzhe zhitomir zp zt ",ug:" ac co go ne or org sc ",uk:" ac bl british-library co cym gov govt icnet jet lea ltd me mil mod national-library-scotland nel net nhs nic nls org orgn parliament plc police sch scot soc ",
|
||||
us:" dni fed isa kids nsn ",uy:" com edu gub mil net org ",ve:" co com edu gob info mil net org web ",vi:" co com k12 net org ",vn:" ac biz com edu gov health info int name net org pro ",ye:" co com gov ltd me net org plc ",yu:" ac co edu gov org ",za:" ac agric alt bourse city co cybernet db edu gov grondar iaccess imt inca landesign law mil net ngo nis nom olivetti org pix school tm web ",zm:" ac co com edu gov net org sch ",com:"ar br cn de eu gb gr hu jpn kr no qc ru sa se uk us uy za ",net:"gb jp se uk ",
|
||||
org:"ae",de:"com "},has:function(f){var b=f.lastIndexOf(".");if(0>=b||b>=f.length-1)return!1;var k=f.lastIndexOf(".",b-1);if(0>=k||k>=b-1)return!1;var n=h.list[f.slice(b+1)];return n?0<=n.indexOf(" "+f.slice(k+1,b)+" "):!1},is:function(f){var b=f.lastIndexOf(".");if(0>=b||b>=f.length-1||0<=f.lastIndexOf(".",b-1))return!1;var k=h.list[f.slice(b+1)];return k?0<=k.indexOf(" "+f.slice(0,b)+" "):!1},get:function(f){var b=f.lastIndexOf(".");if(0>=b||b>=f.length-1)return null;var k=f.lastIndexOf(".",b-1);
|
||||
if(0>=k||k>=b-1)return null;var n=h.list[f.slice(b+1)];return!n||0>n.indexOf(" "+f.slice(k+1,b)+" ")?null:f.slice(k+1)},noConflict:function(){f.SecondLevelDomains===this&&(f.SecondLevelDomains=n);return this}};return h});
|
||||
(function(f,n){"object"===typeof module&&module.exports?module.exports=n(require("./punycode"),require("./IPv6"),require("./SecondLevelDomains")):"function"===typeof define&&define.amd?define(["./punycode","./IPv6","./SecondLevelDomains"],n):f.URI=n(f.punycode,f.IPv6,f.SecondLevelDomains,f)})(this,function(f,n,h,k){function b(a,c){var d=1<=arguments.length,l=2<=arguments.length;if(!(this instanceof b))return d?l?new b(a,c):new b(a):new b;if(void 0===a){if(d)throw new TypeError("undefined is not a valid argument for URI");
|
||||
a="undefined"!==typeof location?location.href+"":""}if(null===a&&d)throw new TypeError("null is not a valid argument for URI");this.href(a);return void 0!==c?this.absoluteTo(c):this}function q(a){return a.replace(/([.*+?^=!:${}()|[\]\/\\])/g,"\\$1")}function z(a){return void 0===a?"Undefined":String(Object.prototype.toString.call(a)).slice(8,-1)}function u(a){return"Array"===z(a)}function g(a,c){var d={},b;if("RegExp"===z(c))d=null;else if(u(c)){var m=0;for(b=c.length;m<b;m++)d[c[m]]=!0}else d[c]=
|
||||
!0;m=0;for(b=a.length;m<b;m++)if(d&&void 0!==d[a[m]]||!d&&c.test(a[m]))a.splice(m,1),b--,m--;return a}function p(a,c){var d;if(u(c)){var b=0;for(d=c.length;b<d;b++)if(!p(a,c[b]))return!1;return!0}var m=z(c);b=0;for(d=a.length;b<d;b++)if("RegExp"===m){if("string"===typeof a[b]&&a[b].match(c))return!0}else if(a[b]===c)return!0;return!1}function D(a,c){if(!u(a)||!u(c)||a.length!==c.length)return!1;a.sort();c.sort();for(var d=0,b=a.length;d<b;d++)if(a[d]!==c[d])return!1;return!0}function A(a){return a.replace(/^\/+|\/+$/g,
|
||||
"")}function B(a){return escape(a)}function E(a){return encodeURIComponent(a).replace(/[!'()*]/g,B).replace(/\*/g,"%2A")}function r(a){return function(c,d){if(void 0===c)return this._parts[a]||"";this._parts[a]=c||null;this.build(!d);return this}}function C(a,c){return function(d,b){if(void 0===d)return this._parts[a]||"";null!==d&&(d+="",d.charAt(0)===c&&(d=d.substring(1)));this._parts[a]=d;this.build(!b);return this}}var w=k&&k.URI;b.version="1.19.2";var e=b.prototype,t=Object.prototype.hasOwnProperty;
|
||||
b._parts=function(){return{protocol:null,username:null,password:null,hostname:null,urn:null,port:null,path:null,query:null,fragment:null,preventInvalidHostname:b.preventInvalidHostname,duplicateQueryParameters:b.duplicateQueryParameters,escapeQuerySpace:b.escapeQuerySpace}};b.preventInvalidHostname=!1;b.duplicateQueryParameters=!1;b.escapeQuerySpace=!0;b.protocol_expression=/^[a-z][a-z0-9.+-]*$/i;b.idn_expression=/[^a-z0-9\._-]/i;b.punycode_expression=/(xn--)/i;b.ip4_expression=/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/;
|
||||
b.ip6_expression=/^\s*((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:)))(%.+)?\s*$/;
|
||||
b.find_uri_expression=/\b((?:[a-z][\w-]+:(?:\/{1,3}|[a-z0-9%])|www\d{0,3}[.]|[a-z0-9.\-]+[.][a-z]{2,4}\/)(?:[^\s()<>]+|\(([^\s()<>]+|(\([^\s()<>]+\)))*\))+(?:\(([^\s()<>]+|(\([^\s()<>]+\)))*\)|[^\s`!()\[\]{};:'".,<>?\u00ab\u00bb\u201c\u201d\u2018\u2019]))/ig;b.findUri={start:/\b(?:([a-z][a-z0-9.+-]*:\/\/)|www\.)/gi,end:/[\s\r\n]|$/,trim:/[`!()\[\]{};:'".,<>?\u00ab\u00bb\u201c\u201d\u201e\u2018\u2019]+$/,parens:/(\([^\)]*\)|\[[^\]]*\]|\{[^}]*\}|<[^>]*>)/g};b.defaultPorts={http:"80",https:"443",ftp:"21",
|
||||
gopher:"70",ws:"80",wss:"443"};b.hostProtocols=["http","https"];b.invalid_hostname_characters=/[^a-zA-Z0-9\.\-:_]/;b.domAttributes={a:"href",blockquote:"cite",link:"href",base:"href",script:"src",form:"action",img:"src",area:"href",iframe:"src",embed:"src",source:"src",track:"src",input:"src",audio:"src",video:"src"};b.getDomAttribute=function(a){if(a&&a.nodeName){var c=a.nodeName.toLowerCase();if("input"!==c||"image"===a.type)return b.domAttributes[c]}};b.encode=E;b.decode=decodeURIComponent;b.iso8859=
|
||||
function(){b.encode=escape;b.decode=unescape};b.unicode=function(){b.encode=E;b.decode=decodeURIComponent};b.characters={pathname:{encode:{expression:/%(24|26|2B|2C|3B|3D|3A|40)/ig,map:{"%24":"$","%26":"&","%2B":"+","%2C":",","%3B":";","%3D":"=","%3A":":","%40":"@"}},decode:{expression:/[\/\?#]/g,map:{"/":"%2F","?":"%3F","#":"%23"}}},reserved:{encode:{expression:/%(21|23|24|26|27|28|29|2A|2B|2C|2F|3A|3B|3D|3F|40|5B|5D)/ig,map:{"%3A":":","%2F":"/","%3F":"?","%23":"#","%5B":"[","%5D":"]","%40":"@",
|
||||
"%21":"!","%24":"$","%26":"&","%27":"'","%28":"(","%29":")","%2A":"*","%2B":"+","%2C":",","%3B":";","%3D":"="}}},urnpath:{encode:{expression:/%(21|24|27|28|29|2A|2B|2C|3B|3D|40)/ig,map:{"%21":"!","%24":"$","%27":"'","%28":"(","%29":")","%2A":"*","%2B":"+","%2C":",","%3B":";","%3D":"=","%40":"@"}},decode:{expression:/[\/\?#:]/g,map:{"/":"%2F","?":"%3F","#":"%23",":":"%3A"}}}};b.encodeQuery=function(a,c){var d=b.encode(a+"");void 0===c&&(c=b.escapeQuerySpace);return c?d.replace(/%20/g,"+"):d};b.decodeQuery=
|
||||
function(a,c){a+="";void 0===c&&(c=b.escapeQuerySpace);try{return b.decode(c?a.replace(/\+/g,"%20"):a)}catch(d){return a}};var y={encode:"encode",decode:"decode"},v,F=function(a,c){return function(d){try{return b[c](d+"").replace(b.characters[a][c].expression,function(d){return b.characters[a][c].map[d]})}catch(l){return d}}};for(v in y)b[v+"PathSegment"]=F("pathname",y[v]),b[v+"UrnPathSegment"]=F("urnpath",y[v]);y=function(a,c,d){return function(l){var m=d?function(a){return b[c](b[d](a))}:b[c];
|
||||
l=(l+"").split(a);for(var e=0,g=l.length;e<g;e++)l[e]=m(l[e]);return l.join(a)}};b.decodePath=y("/","decodePathSegment");b.decodeUrnPath=y(":","decodeUrnPathSegment");b.recodePath=y("/","encodePathSegment","decode");b.recodeUrnPath=y(":","encodeUrnPathSegment","decode");b.encodeReserved=F("reserved","encode");b.parse=function(a,c){c||(c={preventInvalidHostname:b.preventInvalidHostname});var d=a.indexOf("#");-1<d&&(c.fragment=a.substring(d+1)||null,a=a.substring(0,d));d=a.indexOf("?");-1<d&&(c.query=
|
||||
a.substring(d+1)||null,a=a.substring(0,d));"//"===a.substring(0,2)?(c.protocol=null,a=a.substring(2),a=b.parseAuthority(a,c)):(d=a.indexOf(":"),-1<d&&(c.protocol=a.substring(0,d)||null,c.protocol&&!c.protocol.match(b.protocol_expression)?c.protocol=void 0:"//"===a.substring(d+1,d+3)?(a=a.substring(d+3),a=b.parseAuthority(a,c)):(a=a.substring(d+1),c.urn=!0)));c.path=a;return c};b.parseHost=function(a,c){a||(a="");a=a.replace(/\\/g,"/");var d=a.indexOf("/");-1===d&&(d=a.length);if("["===a.charAt(0)){var l=
|
||||
a.indexOf("]");c.hostname=a.substring(1,l)||null;c.port=a.substring(l+2,d)||null;"/"===c.port&&(c.port=null)}else{var m=a.indexOf(":");l=a.indexOf("/");m=a.indexOf(":",m+1);-1!==m&&(-1===l||m<l)?(c.hostname=a.substring(0,d)||null,c.port=null):(l=a.substring(0,d).split(":"),c.hostname=l[0]||null,c.port=l[1]||null)}c.hostname&&"/"!==a.substring(d).charAt(0)&&(d++,a="/"+a);c.preventInvalidHostname&&b.ensureValidHostname(c.hostname,c.protocol);c.port&&b.ensureValidPort(c.port);return a.substring(d)||
|
||||
"/"};b.parseAuthority=function(a,c){a=b.parseUserinfo(a,c);return b.parseHost(a,c)};b.parseUserinfo=function(a,c){var d=a.indexOf("/"),l=a.lastIndexOf("@",-1<d?d:a.length-1);-1<l&&(-1===d||l<d)?(d=a.substring(0,l).split(":"),c.username=d[0]?b.decode(d[0]):null,d.shift(),c.password=d[0]?b.decode(d.join(":")):null,a=a.substring(l+1)):(c.username=null,c.password=null);return a};b.parseQuery=function(a,c){if(!a)return{};a=a.replace(/&+/g,"&").replace(/^\?*&*|&+$/g,"");if(!a)return{};for(var d={},l=a.split("&"),
|
||||
m=l.length,e,g,f=0;f<m;f++)if(e=l[f].split("="),g=b.decodeQuery(e.shift(),c),e=e.length?b.decodeQuery(e.join("="),c):null,t.call(d,g)){if("string"===typeof d[g]||null===d[g])d[g]=[d[g]];d[g].push(e)}else d[g]=e;return d};b.build=function(a){var c="",d=!1;a.protocol&&(c+=a.protocol+":");a.urn||!c&&!a.hostname||(c+="//",d=!0);c+=b.buildAuthority(a)||"";"string"===typeof a.path&&("/"!==a.path.charAt(0)&&d&&(c+="/"),c+=a.path);"string"===typeof a.query&&a.query&&(c+="?"+a.query);"string"===typeof a.fragment&&
|
||||
a.fragment&&(c+="#"+a.fragment);return c};b.buildHost=function(a){var c="";if(a.hostname)c=b.ip6_expression.test(a.hostname)?c+("["+a.hostname+"]"):c+a.hostname;else return"";a.port&&(c+=":"+a.port);return c};b.buildAuthority=function(a){return b.buildUserinfo(a)+b.buildHost(a)};b.buildUserinfo=function(a){var c="";a.username&&(c+=b.encode(a.username));a.password&&(c+=":"+b.encode(a.password));c&&(c+="@");return c};b.buildQuery=function(a,c,d){var l="",m,e;for(m in a)if(t.call(a,m))if(u(a[m])){var g=
|
||||
{};var f=0;for(e=a[m].length;f<e;f++)void 0!==a[m][f]&&void 0===g[a[m][f]+""]&&(l+="&"+b.buildQueryParameter(m,a[m][f],d),!0!==c&&(g[a[m][f]+""]=!0))}else void 0!==a[m]&&(l+="&"+b.buildQueryParameter(m,a[m],d));return l.substring(1)};b.buildQueryParameter=function(a,c,d){return b.encodeQuery(a,d)+(null!==c?"="+b.encodeQuery(c,d):"")};b.addQuery=function(a,c,d){if("object"===typeof c)for(var l in c)t.call(c,l)&&b.addQuery(a,l,c[l]);else if("string"===typeof c)void 0===a[c]?a[c]=d:("string"===typeof a[c]&&
|
||||
(a[c]=[a[c]]),u(d)||(d=[d]),a[c]=(a[c]||[]).concat(d));else throw new TypeError("URI.addQuery() accepts an object, string as the name parameter");};b.setQuery=function(a,c,d){if("object"===typeof c)for(var l in c)t.call(c,l)&&b.setQuery(a,l,c[l]);else if("string"===typeof c)a[c]=void 0===d?null:d;else throw new TypeError("URI.setQuery() accepts an object, string as the name parameter");};b.removeQuery=function(a,c,d){var l;if(u(c))for(d=0,l=c.length;d<l;d++)a[c[d]]=void 0;else if("RegExp"===z(c))for(l in a)c.test(l)&&
|
||||
(a[l]=void 0);else if("object"===typeof c)for(l in c)t.call(c,l)&&b.removeQuery(a,l,c[l]);else if("string"===typeof c)void 0!==d?"RegExp"===z(d)?!u(a[c])&&d.test(a[c])?a[c]=void 0:a[c]=g(a[c],d):a[c]!==String(d)||u(d)&&1!==d.length?u(a[c])&&(a[c]=g(a[c],d)):a[c]=void 0:a[c]=void 0;else throw new TypeError("URI.removeQuery() accepts an object, string, RegExp as the first parameter");};b.hasQuery=function(a,c,d,l){switch(z(c)){case "String":break;case "RegExp":for(var m in a)if(t.call(a,m)&&c.test(m)&&
|
||||
(void 0===d||b.hasQuery(a,m,d)))return!0;return!1;case "Object":for(var e in c)if(t.call(c,e)&&!b.hasQuery(a,e,c[e]))return!1;return!0;default:throw new TypeError("URI.hasQuery() accepts a string, regular expression or object as the name parameter");}switch(z(d)){case "Undefined":return c in a;case "Boolean":return a=!(u(a[c])?!a[c].length:!a[c]),d===a;case "Function":return!!d(a[c],c,a);case "Array":return u(a[c])?(l?p:D)(a[c],d):!1;case "RegExp":return u(a[c])?l?p(a[c],d):!1:!(!a[c]||!a[c].match(d));
|
||||
case "Number":d=String(d);case "String":return u(a[c])?l?p(a[c],d):!1:a[c]===d;default:throw new TypeError("URI.hasQuery() accepts undefined, boolean, string, number, RegExp, Function as the value parameter");}};b.joinPaths=function(){for(var a=[],c=[],d=0,l=0;l<arguments.length;l++){var m=new b(arguments[l]);a.push(m);m=m.segment();for(var e=0;e<m.length;e++)"string"===typeof m[e]&&c.push(m[e]),m[e]&&d++}if(!c.length||!d)return new b("");c=(new b("")).segment(c);""!==a[0].path()&&"/"!==a[0].path().slice(0,
|
||||
1)||c.path("/"+c.path());return c.normalize()};b.commonPath=function(a,c){var d=Math.min(a.length,c.length),b;for(b=0;b<d;b++)if(a.charAt(b)!==c.charAt(b)){b--;break}if(1>b)return a.charAt(0)===c.charAt(0)&&"/"===a.charAt(0)?"/":"";if("/"!==a.charAt(b)||"/"!==c.charAt(b))b=a.substring(0,b).lastIndexOf("/");return a.substring(0,b+1)};b.withinString=function(a,c,d){d||(d={});var l=d.start||b.findUri.start,e=d.end||b.findUri.end,g=d.trim||b.findUri.trim,f=d.parens||b.findUri.parens,h=/[a-z0-9-]=["']?$/i;
|
||||
for(l.lastIndex=0;;){var k=l.exec(a);if(!k)break;var p=k.index;if(d.ignoreHtml){var n=a.slice(Math.max(p-3,0),p);if(n&&h.test(n))continue}var r=p+a.slice(p).search(e);n=a.slice(p,r);for(r=-1;;){var q=f.exec(n);if(!q)break;r=Math.max(r,q.index+q[0].length)}n=-1<r?n.slice(0,r)+n.slice(r).replace(g,""):n.replace(g,"");n.length<=k[0].length||d.ignore&&d.ignore.test(n)||(r=p+n.length,k=c(n,p,r,a),void 0===k?l.lastIndex=r:(k=String(k),a=a.slice(0,p)+k+a.slice(r),l.lastIndex=p+k.length))}l.lastIndex=0;return a};
|
||||
b.ensureValidHostname=function(a,c){var d=!!a,l=!1;c&&(l=p(b.hostProtocols,c));if(l&&!d)throw new TypeError("Hostname cannot be empty, if protocol is "+c);if(a&&a.match(b.invalid_hostname_characters)){if(!f)throw new TypeError('Hostname "'+a+'" contains characters other than [A-Z0-9.-:_] and Punycode.js is not available');if(f.toASCII(a).match(b.invalid_hostname_characters))throw new TypeError('Hostname "'+a+'" contains characters other than [A-Z0-9.-:_]');}};b.ensureValidPort=function(a){if(a){var c=
|
||||
Number(a);if(!(/^[0-9]+$/.test(c)&&0<c&&65536>c))throw new TypeError('Port "'+a+'" is not a valid port');}};b.noConflict=function(a){if(a)return a={URI:this.noConflict()},k.URITemplate&&"function"===typeof k.URITemplate.noConflict&&(a.URITemplate=k.URITemplate.noConflict()),k.IPv6&&"function"===typeof k.IPv6.noConflict&&(a.IPv6=k.IPv6.noConflict()),k.SecondLevelDomains&&"function"===typeof k.SecondLevelDomains.noConflict&&(a.SecondLevelDomains=k.SecondLevelDomains.noConflict()),a;k.URI===this&&(k.URI=
|
||||
w);return this};e.build=function(a){if(!0===a)this._deferred_build=!0;else if(void 0===a||this._deferred_build)this._string=b.build(this._parts),this._deferred_build=!1;return this};e.clone=function(){return new b(this)};e.valueOf=e.toString=function(){return this.build(!1)._string};e.protocol=r("protocol");e.username=r("username");e.password=r("password");e.hostname=r("hostname");e.port=r("port");e.query=C("query","?");e.fragment=C("fragment","#");e.search=function(a,c){var b=this.query(a,c);return"string"===
|
||||
typeof b&&b.length?"?"+b:b};e.hash=function(a,c){var b=this.fragment(a,c);return"string"===typeof b&&b.length?"#"+b:b};e.pathname=function(a,c){if(void 0===a||!0===a){var d=this._parts.path||(this._parts.hostname?"/":"");return a?(this._parts.urn?b.decodeUrnPath:b.decodePath)(d):d}this._parts.path=this._parts.urn?a?b.recodeUrnPath(a):"":a?b.recodePath(a):"/";this.build(!c);return this};e.path=e.pathname;e.href=function(a,c){var d;if(void 0===a)return this.toString();this._string="";this._parts=b._parts();
|
||||
var l=a instanceof b,e="object"===typeof a&&(a.hostname||a.path||a.pathname);a.nodeName&&(e=b.getDomAttribute(a),a=a[e]||"",e=!1);!l&&e&&void 0!==a.pathname&&(a=a.toString());if("string"===typeof a||a instanceof String)this._parts=b.parse(String(a),this._parts);else if(l||e){l=l?a._parts:a;for(d in l)"query"!==d&&t.call(this._parts,d)&&(this._parts[d]=l[d]);l.query&&this.query(l.query,!1)}else throw new TypeError("invalid input");this.build(!c);return this};e.is=function(a){var c=!1,d=!1,e=!1,m=!1,
|
||||
g=!1,f=!1,k=!1,p=!this._parts.urn;this._parts.hostname&&(p=!1,d=b.ip4_expression.test(this._parts.hostname),e=b.ip6_expression.test(this._parts.hostname),c=d||e,g=(m=!c)&&h&&h.has(this._parts.hostname),f=m&&b.idn_expression.test(this._parts.hostname),k=m&&b.punycode_expression.test(this._parts.hostname));switch(a.toLowerCase()){case "relative":return p;case "absolute":return!p;case "domain":case "name":return m;case "sld":return g;case "ip":return c;case "ip4":case "ipv4":case "inet4":return d;case "ip6":case "ipv6":case "inet6":return e;
|
||||
case "idn":return f;case "url":return!this._parts.urn;case "urn":return!!this._parts.urn;case "punycode":return k}return null};var G=e.protocol,H=e.port,I=e.hostname;e.protocol=function(a,c){if(a&&(a=a.replace(/:(\/\/)?$/,""),!a.match(b.protocol_expression)))throw new TypeError('Protocol "'+a+"\" contains characters other than [A-Z0-9.+-] or doesn't start with [A-Z]");return G.call(this,a,c)};e.scheme=e.protocol;e.port=function(a,c){if(this._parts.urn)return void 0===a?"":this;void 0!==a&&(0===a&&
|
||||
(a=null),a&&(a+="",":"===a.charAt(0)&&(a=a.substring(1)),b.ensureValidPort(a)));return H.call(this,a,c)};e.hostname=function(a,c){if(this._parts.urn)return void 0===a?"":this;if(void 0!==a){var d={preventInvalidHostname:this._parts.preventInvalidHostname};if("/"!==b.parseHost(a,d))throw new TypeError('Hostname "'+a+'" contains characters other than [A-Z0-9.-]');a=d.hostname;this._parts.preventInvalidHostname&&b.ensureValidHostname(a,this._parts.protocol)}return I.call(this,a,c)};e.origin=function(a,
|
||||
c){if(this._parts.urn)return void 0===a?"":this;if(void 0===a){var d=this.protocol();return this.authority()?(d?d+"://":"")+this.authority():""}d=b(a);this.protocol(d.protocol()).authority(d.authority()).build(!c);return this};e.host=function(a,c){if(this._parts.urn)return void 0===a?"":this;if(void 0===a)return this._parts.hostname?b.buildHost(this._parts):"";if("/"!==b.parseHost(a,this._parts))throw new TypeError('Hostname "'+a+'" contains characters other than [A-Z0-9.-]');this.build(!c);return this};
|
||||
e.authority=function(a,c){if(this._parts.urn)return void 0===a?"":this;if(void 0===a)return this._parts.hostname?b.buildAuthority(this._parts):"";if("/"!==b.parseAuthority(a,this._parts))throw new TypeError('Hostname "'+a+'" contains characters other than [A-Z0-9.-]');this.build(!c);return this};e.userinfo=function(a,c){if(this._parts.urn)return void 0===a?"":this;if(void 0===a){var d=b.buildUserinfo(this._parts);return d?d.substring(0,d.length-1):d}"@"!==a[a.length-1]&&(a+="@");b.parseUserinfo(a,
|
||||
this._parts);this.build(!c);return this};e.resource=function(a,c){if(void 0===a)return this.path()+this.search()+this.hash();var d=b.parse(a);this._parts.path=d.path;this._parts.query=d.query;this._parts.fragment=d.fragment;this.build(!c);return this};e.subdomain=function(a,c){if(this._parts.urn)return void 0===a?"":this;if(void 0===a){if(!this._parts.hostname||this.is("IP"))return"";var d=this._parts.hostname.length-this.domain().length-1;return this._parts.hostname.substring(0,d)||""}d=this._parts.hostname.length-
|
||||
this.domain().length;d=this._parts.hostname.substring(0,d);d=new RegExp("^"+q(d));a&&"."!==a.charAt(a.length-1)&&(a+=".");if(-1!==a.indexOf(":"))throw new TypeError("Domains cannot contain colons");a&&b.ensureValidHostname(a,this._parts.protocol);this._parts.hostname=this._parts.hostname.replace(d,a);this.build(!c);return this};e.domain=function(a,c){if(this._parts.urn)return void 0===a?"":this;"boolean"===typeof a&&(c=a,a=void 0);if(void 0===a){if(!this._parts.hostname||this.is("IP"))return"";var d=
|
||||
this._parts.hostname.match(/\./g);if(d&&2>d.length)return this._parts.hostname;d=this._parts.hostname.length-this.tld(c).length-1;d=this._parts.hostname.lastIndexOf(".",d-1)+1;return this._parts.hostname.substring(d)||""}if(!a)throw new TypeError("cannot set domain empty");if(-1!==a.indexOf(":"))throw new TypeError("Domains cannot contain colons");b.ensureValidHostname(a,this._parts.protocol);!this._parts.hostname||this.is("IP")?this._parts.hostname=a:(d=new RegExp(q(this.domain())+"$"),this._parts.hostname=
|
||||
this._parts.hostname.replace(d,a));this.build(!c);return this};e.tld=function(a,c){if(this._parts.urn)return void 0===a?"":this;"boolean"===typeof a&&(c=a,a=void 0);if(void 0===a){if(!this._parts.hostname||this.is("IP"))return"";var b=this._parts.hostname.lastIndexOf(".");b=this._parts.hostname.substring(b+1);return!0!==c&&h&&h.list[b.toLowerCase()]?h.get(this._parts.hostname)||b:b}if(a)if(a.match(/[^a-zA-Z0-9-]/))if(h&&h.is(a))b=new RegExp(q(this.tld())+"$"),this._parts.hostname=this._parts.hostname.replace(b,
|
||||
a);else throw new TypeError('TLD "'+a+'" contains characters other than [A-Z0-9]');else{if(!this._parts.hostname||this.is("IP"))throw new ReferenceError("cannot set TLD on non-domain host");b=new RegExp(q(this.tld())+"$");this._parts.hostname=this._parts.hostname.replace(b,a)}else throw new TypeError("cannot set TLD empty");this.build(!c);return this};e.directory=function(a,c){if(this._parts.urn)return void 0===a?"":this;if(void 0===a||!0===a){if(!this._parts.path&&!this._parts.hostname)return"";
|
||||
if("/"===this._parts.path)return"/";var d=this._parts.path.length-this.filename().length-1;d=this._parts.path.substring(0,d)||(this._parts.hostname?"/":"");return a?b.decodePath(d):d}d=this._parts.path.length-this.filename().length;d=this._parts.path.substring(0,d);d=new RegExp("^"+q(d));this.is("relative")||(a||(a="/"),"/"!==a.charAt(0)&&(a="/"+a));a&&"/"!==a.charAt(a.length-1)&&(a+="/");a=b.recodePath(a);this._parts.path=this._parts.path.replace(d,a);this.build(!c);return this};e.filename=function(a,
|
||||
c){if(this._parts.urn)return void 0===a?"":this;if("string"!==typeof a){if(!this._parts.path||"/"===this._parts.path)return"";var d=this._parts.path.lastIndexOf("/");d=this._parts.path.substring(d+1);return a?b.decodePathSegment(d):d}d=!1;"/"===a.charAt(0)&&(a=a.substring(1));a.match(/\.?\//)&&(d=!0);var e=new RegExp(q(this.filename())+"$");a=b.recodePath(a);this._parts.path=this._parts.path.replace(e,a);d?this.normalizePath(c):this.build(!c);return this};e.suffix=function(a,c){if(this._parts.urn)return void 0===
|
||||
a?"":this;if(void 0===a||!0===a){if(!this._parts.path||"/"===this._parts.path)return"";var d=this.filename(),e=d.lastIndexOf(".");if(-1===e)return"";d=d.substring(e+1);d=/^[a-z0-9%]+$/i.test(d)?d:"";return a?b.decodePathSegment(d):d}"."===a.charAt(0)&&(a=a.substring(1));if(d=this.suffix())e=a?new RegExp(q(d)+"$"):new RegExp(q("."+d)+"$");else{if(!a)return this;this._parts.path+="."+b.recodePath(a)}e&&(a=b.recodePath(a),this._parts.path=this._parts.path.replace(e,a));this.build(!c);return this};e.segment=
|
||||
function(a,c,b){var d=this._parts.urn?":":"/",e=this.path(),g="/"===e.substring(0,1);e=e.split(d);void 0!==a&&"number"!==typeof a&&(b=c,c=a,a=void 0);if(void 0!==a&&"number"!==typeof a)throw Error('Bad segment "'+a+'", must be 0-based integer');g&&e.shift();0>a&&(a=Math.max(e.length+a,0));if(void 0===c)return void 0===a?e:e[a];if(null===a||void 0===e[a])if(u(c)){e=[];a=0;for(var f=c.length;a<f;a++)if(c[a].length||e.length&&e[e.length-1].length)e.length&&!e[e.length-1].length&&e.pop(),e.push(A(c[a]))}else{if(c||
|
||||
"string"===typeof c)c=A(c),""===e[e.length-1]?e[e.length-1]=c:e.push(c)}else c?e[a]=A(c):e.splice(a,1);g&&e.unshift("");return this.path(e.join(d),b)};e.segmentCoded=function(a,c,d){var e;"number"!==typeof a&&(d=c,c=a,a=void 0);if(void 0===c){a=this.segment(a,c,d);if(u(a)){var g=0;for(e=a.length;g<e;g++)a[g]=b.decode(a[g])}else a=void 0!==a?b.decode(a):void 0;return a}if(u(c))for(g=0,e=c.length;g<e;g++)c[g]=b.encode(c[g]);else c="string"===typeof c||c instanceof String?b.encode(c):c;return this.segment(a,
|
||||
c,d)};var J=e.query;e.query=function(a,c){if(!0===a)return b.parseQuery(this._parts.query,this._parts.escapeQuerySpace);if("function"===typeof a){var d=b.parseQuery(this._parts.query,this._parts.escapeQuerySpace),e=a.call(this,d);this._parts.query=b.buildQuery(e||d,this._parts.duplicateQueryParameters,this._parts.escapeQuerySpace);this.build(!c);return this}return void 0!==a&&"string"!==typeof a?(this._parts.query=b.buildQuery(a,this._parts.duplicateQueryParameters,this._parts.escapeQuerySpace),this.build(!c),
|
||||
this):J.call(this,a,c)};e.setQuery=function(a,c,d){var e=b.parseQuery(this._parts.query,this._parts.escapeQuerySpace);if("string"===typeof a||a instanceof String)e[a]=void 0!==c?c:null;else if("object"===typeof a)for(var g in a)t.call(a,g)&&(e[g]=a[g]);else throw new TypeError("URI.addQuery() accepts an object, string as the name parameter");this._parts.query=b.buildQuery(e,this._parts.duplicateQueryParameters,this._parts.escapeQuerySpace);"string"!==typeof a&&(d=c);this.build(!d);return this};e.addQuery=
|
||||
function(a,c,d){var e=b.parseQuery(this._parts.query,this._parts.escapeQuerySpace);b.addQuery(e,a,void 0===c?null:c);this._parts.query=b.buildQuery(e,this._parts.duplicateQueryParameters,this._parts.escapeQuerySpace);"string"!==typeof a&&(d=c);this.build(!d);return this};e.removeQuery=function(a,c,d){var e=b.parseQuery(this._parts.query,this._parts.escapeQuerySpace);b.removeQuery(e,a,c);this._parts.query=b.buildQuery(e,this._parts.duplicateQueryParameters,this._parts.escapeQuerySpace);"string"!==
|
||||
typeof a&&(d=c);this.build(!d);return this};e.hasQuery=function(a,c,d){var e=b.parseQuery(this._parts.query,this._parts.escapeQuerySpace);return b.hasQuery(e,a,c,d)};e.setSearch=e.setQuery;e.addSearch=e.addQuery;e.removeSearch=e.removeQuery;e.hasSearch=e.hasQuery;e.normalize=function(){return this._parts.urn?this.normalizeProtocol(!1).normalizePath(!1).normalizeQuery(!1).normalizeFragment(!1).build():this.normalizeProtocol(!1).normalizeHostname(!1).normalizePort(!1).normalizePath(!1).normalizeQuery(!1).normalizeFragment(!1).build()};
|
||||
e.normalizeProtocol=function(a){"string"===typeof this._parts.protocol&&(this._parts.protocol=this._parts.protocol.toLowerCase(),this.build(!a));return this};e.normalizeHostname=function(a){this._parts.hostname&&(this.is("IDN")&&f?this._parts.hostname=f.toASCII(this._parts.hostname):this.is("IPv6")&&n&&(this._parts.hostname=n.best(this._parts.hostname)),this._parts.hostname=this._parts.hostname.toLowerCase(),this.build(!a));return this};e.normalizePort=function(a){"string"===typeof this._parts.protocol&&
|
||||
this._parts.port===b.defaultPorts[this._parts.protocol]&&(this._parts.port=null,this.build(!a));return this};e.normalizePath=function(a){var c=this._parts.path;if(!c)return this;if(this._parts.urn)return this._parts.path=b.recodeUrnPath(this._parts.path),this.build(!a),this;if("/"===this._parts.path)return this;c=b.recodePath(c);var d="";if("/"!==c.charAt(0)){var e=!0;c="/"+c}if("/.."===c.slice(-3)||"/."===c.slice(-2))c+="/";c=c.replace(/(\/(\.\/)+)|(\/\.$)/g,"/").replace(/\/{2,}/g,"/");e&&(d=c.substring(1).match(/^(\.\.\/)+/)||
|
||||
"")&&(d=d[0]);for(;;){var g=c.search(/\/\.\.(\/|$)/);if(-1===g)break;else if(0===g){c=c.substring(3);continue}var f=c.substring(0,g).lastIndexOf("/");-1===f&&(f=g);c=c.substring(0,f)+c.substring(g+3)}e&&this.is("relative")&&(c=d+c.substring(1));this._parts.path=c;this.build(!a);return this};e.normalizePathname=e.normalizePath;e.normalizeQuery=function(a){"string"===typeof this._parts.query&&(this._parts.query.length?this.query(b.parseQuery(this._parts.query,this._parts.escapeQuerySpace)):this._parts.query=
|
||||
null,this.build(!a));return this};e.normalizeFragment=function(a){this._parts.fragment||(this._parts.fragment=null,this.build(!a));return this};e.normalizeSearch=e.normalizeQuery;e.normalizeHash=e.normalizeFragment;e.iso8859=function(){var a=b.encode,c=b.decode;b.encode=escape;b.decode=decodeURIComponent;try{this.normalize()}finally{b.encode=a,b.decode=c}return this};e.unicode=function(){var a=b.encode,c=b.decode;b.encode=E;b.decode=unescape;try{this.normalize()}finally{b.encode=a,b.decode=c}return this};
|
||||
e.readable=function(){var a=this.clone();a.username("").password("").normalize();var c="";a._parts.protocol&&(c+=a._parts.protocol+"://");a._parts.hostname&&(a.is("punycode")&&f?(c+=f.toUnicode(a._parts.hostname),a._parts.port&&(c+=":"+a._parts.port)):c+=a.host());a._parts.hostname&&a._parts.path&&"/"!==a._parts.path.charAt(0)&&(c+="/");c+=a.path(!0);if(a._parts.query){for(var d="",e=0,g=a._parts.query.split("&"),h=g.length;e<h;e++){var k=(g[e]||"").split("=");d+="&"+b.decodeQuery(k[0],this._parts.escapeQuerySpace).replace(/&/g,
|
||||
"%26");void 0!==k[1]&&(d+="="+b.decodeQuery(k[1],this._parts.escapeQuerySpace).replace(/&/g,"%26"))}c+="?"+d.substring(1)}return c+=b.decodeQuery(a.hash(),!0)};e.absoluteTo=function(a){var c=this.clone(),d=["protocol","username","password","hostname","port"],e,g;if(this._parts.urn)throw Error("URNs do not have any generally defined hierarchical components");a instanceof b||(a=new b(a));if(c._parts.protocol)return c;c._parts.protocol=a._parts.protocol;if(this._parts.hostname)return c;for(e=0;g=d[e];e++)c._parts[g]=
|
||||
a._parts[g];c._parts.path?(".."===c._parts.path.substring(-2)&&(c._parts.path+="/"),"/"!==c.path().charAt(0)&&(d=(d=a.directory())?d:0===a.path().indexOf("/")?"/":"",c._parts.path=(d?d+"/":"")+c._parts.path,c.normalizePath())):(c._parts.path=a._parts.path,c._parts.query||(c._parts.query=a._parts.query));c.build();return c};e.relativeTo=function(a){var c=this.clone().normalize();if(c._parts.urn)throw Error("URNs do not have any generally defined hierarchical components");a=(new b(a)).normalize();var d=
|
||||
c._parts;var e=a._parts;var g=c.path();a=a.path();if("/"!==g.charAt(0))throw Error("URI is already relative");if("/"!==a.charAt(0))throw Error("Cannot calculate a URI relative to another relative URI");d.protocol===e.protocol&&(d.protocol=null);if(d.username===e.username&&d.password===e.password&&null===d.protocol&&null===d.username&&null===d.password&&d.hostname===e.hostname&&d.port===e.port)d.hostname=null,d.port=null;else return c.build();if(g===a)return d.path="",c.build();g=b.commonPath(g,a);
|
||||
if(!g)return c.build();e=e.path.substring(g.length).replace(/[^\/]*$/,"").replace(/.*?\//g,"../");d.path=e+d.path.substring(g.length)||"./";return c.build()};e.equals=function(a){var c=this.clone(),d=new b(a);a={};var e;c.normalize();d.normalize();if(c.toString()===d.toString())return!0;var g=c.query();var f=d.query();c.query("");d.query("");if(c.toString()!==d.toString()||g.length!==f.length)return!1;c=b.parseQuery(g,this._parts.escapeQuerySpace);f=b.parseQuery(f,this._parts.escapeQuerySpace);for(e in c)if(t.call(c,
|
||||
e)){if(!u(c[e])){if(c[e]!==f[e])return!1}else if(!D(c[e],f[e]))return!1;a[e]=!0}for(e in f)if(t.call(f,e)&&!a[e])return!1;return!0};e.preventInvalidHostname=function(a){this._parts.preventInvalidHostname=!!a;return this};e.duplicateQueryParameters=function(a){this._parts.duplicateQueryParameters=!!a;return this};e.escapeQuerySpace=function(a){this._parts.escapeQuerySpace=!!a;return this};return b});
|
||||
(function(f,n){"object"===typeof module&&module.exports?module.exports=n(require("./URI")):"function"===typeof define&&define.amd?define(["./URI"],n):f.URITemplate=n(f.URI,f)})(this,function(f,n){function h(b){if(h._cache[b])return h._cache[b];if(!(this instanceof h))return new h(b);this.expression=b;h._cache[b]=this;return this}function k(b){this.data=b;this.cache={}}var b=n&&n.URITemplate,q=Object.prototype.hasOwnProperty,z=h.prototype,u={"":{prefix:"",separator:",",named:!1,empty_name_separator:!1,
|
||||
encode:"encode"},"+":{prefix:"",separator:",",named:!1,empty_name_separator:!1,encode:"encodeReserved"},"#":{prefix:"#",separator:",",named:!1,empty_name_separator:!1,encode:"encodeReserved"},".":{prefix:".",separator:".",named:!1,empty_name_separator:!1,encode:"encode"},"/":{prefix:"/",separator:"/",named:!1,empty_name_separator:!1,encode:"encode"},";":{prefix:";",separator:";",named:!0,empty_name_separator:!1,encode:"encode"},"?":{prefix:"?",separator:"&",named:!0,empty_name_separator:!0,encode:"encode"},
|
||||
"&":{prefix:"&",separator:"&",named:!0,empty_name_separator:!0,encode:"encode"}};h._cache={};h.EXPRESSION_PATTERN=/\{([^a-zA-Z0-9%_]?)([^\}]+)(\}|$)/g;h.VARIABLE_PATTERN=/^([^*:.](?:\.?[^*:.])*)((\*)|:(\d+))?$/;h.VARIABLE_NAME_PATTERN=/[^a-zA-Z0-9%_.]/;h.LITERAL_PATTERN=/[<>{}"`^| \\]/;h.expand=function(b,f,k){var g=u[b.operator],p=g.named?"Named":"Unnamed";b=b.variables;var n=[],r,q;for(q=0;r=b[q];q++){var w=f.get(r.name);if(0===w.type&&k&&k.strict)throw Error('Missing expansion value for variable "'+
|
||||
r.name+'"');if(w.val.length){if(1<w.type&&r.maxlength)throw Error('Invalid expression: Prefix modifier not applicable to variable "'+r.name+'"');n.push(h["expand"+p](w,g,r.explode,r.explode&&g.separator||",",r.maxlength,r.name))}else w.type&&n.push("")}return n.length?g.prefix+n.join(g.separator):""};h.expandNamed=function(b,h,k,n,q,u){var g="",p=h.encode;h=h.empty_name_separator;var A=!b[p].length,e=2===b.type?"":f[p](u),t;var y=0;for(t=b.val.length;y<t;y++){if(q){var v=f[p](b.val[y][1].substring(0,
|
||||
q));2===b.type&&(e=f[p](b.val[y][0].substring(0,q)))}else A?(v=f[p](b.val[y][1]),2===b.type?(e=f[p](b.val[y][0]),b[p].push([e,v])):b[p].push([void 0,v])):(v=b[p][y][1],2===b.type&&(e=b[p][y][0]));g&&(g+=n);k?g+=e+(h||v?"=":"")+v:(y||(g+=f[p](u)+(h||v?"=":"")),2===b.type&&(g+=e+","),g+=v)}return g};h.expandUnnamed=function(b,h,k,n,q){var g="",p=h.encode;h=h.empty_name_separator;var A=!b[p].length,w;var e=0;for(w=b.val.length;e<w;e++){if(q)var t=f[p](b.val[e][1].substring(0,q));else A?(t=f[p](b.val[e][1]),
|
||||
b[p].push([2===b.type?f[p](b.val[e][0]):void 0,t])):t=b[p][e][1];g&&(g+=n);if(2===b.type){var u=q?f[p](b.val[e][0].substring(0,q)):b[p][e][0];g+=u;g=k?g+(h||t?"=":""):g+","}g+=t}return g};h.noConflict=function(){n.URITemplate===h&&(n.URITemplate=b);return h};z.expand=function(b,f){var g="";this.parts&&this.parts.length||this.parse();b instanceof k||(b=new k(b));for(var p=0,n=this.parts.length;p<n;p++)g+="string"===typeof this.parts[p]?this.parts[p]:h.expand(this.parts[p],b,f);return g};z.parse=function(){var b=
|
||||
this.expression,f=h.EXPRESSION_PATTERN,k=h.VARIABLE_PATTERN,n=h.VARIABLE_NAME_PATTERN,q=h.LITERAL_PATTERN,z=[],r=0,C=function(b){if(b.match(q))throw Error('Invalid Literal "'+b+'"');return b};for(f.lastIndex=0;;){var w=f.exec(b);if(null===w){z.push(C(b.substring(r)));break}else z.push(C(b.substring(r,w.index))),r=w.index+w[0].length;if(!u[w[1]])throw Error('Unknown Operator "'+w[1]+'" in "'+w[0]+'"');if(!w[3])throw Error('Unclosed Expression "'+w[0]+'"');var e=w[2].split(",");for(var t=0,y=e.length;t<
|
||||
y;t++){var v=e[t].match(k);if(null===v)throw Error('Invalid Variable "'+e[t]+'" in "'+w[0]+'"');if(v[1].match(n))throw Error('Invalid Variable Name "'+v[1]+'" in "'+w[0]+'"');e[t]={name:v[1],explode:!!v[3],maxlength:v[4]&&parseInt(v[4],10)}}if(!e.length)throw Error('Expression Missing Variable(s) "'+w[0]+'"');z.push({expression:w[0],operator:w[1],variables:e})}z.length||z.push(C(b));this.parts=z;return this};k.prototype.get=function(b){var f=this.data,g={type:0,val:[],encode:[],encodeReserved:[]};
|
||||
if(void 0!==this.cache[b])return this.cache[b];this.cache[b]=g;f="[object Function]"===String(Object.prototype.toString.call(f))?f(b):"[object Function]"===String(Object.prototype.toString.call(f[b]))?f[b](b):f[b];if(void 0!==f&&null!==f)if("[object Array]"===String(Object.prototype.toString.call(f))){var h=0;for(b=f.length;h<b;h++)void 0!==f[h]&&null!==f[h]&&g.val.push([void 0,String(f[h])]);g.val.length&&(g.type=3)}else if("[object Object]"===String(Object.prototype.toString.call(f))){for(h in f)q.call(f,
|
||||
h)&&void 0!==f[h]&&null!==f[h]&&g.val.push([h,String(f[h])]);g.val.length&&(g.type=2)}else g.type=1,g.val.push([void 0,String(f)]);return g};f.expand=function(b,k){var g=(new h(b)).expand(k);return new f(g)};return h});
|
||||
@@ -0,0 +1,113 @@
|
||||
let contributorsLoaded = false;
|
||||
|
||||
const CONTRIBUTORS_API_URL = "https://api.github.com/repos/sabeechen/hassio-google-drive-backup/contributors";
|
||||
const CONTRIBUTOR_COMMIT_URL = "https://github.com/sabeechen/hassio-google-drive-backup/commits?author={AUTHOR}";
|
||||
const CONTRIBUTORS_COUNT_TO_SHOW = 14;
|
||||
|
||||
const EXTRA_CONTRIBUTORS = {
|
||||
"sabeechen": "Original author. Did most of this.",
|
||||
"ericmatte": "Redesigned the UI with divine HTML and CSS wizardry.",
|
||||
"jhampson-dbre": "Fixed a really nasty timing bug.",
|
||||
};
|
||||
|
||||
function createElementFromHTML(htmlString) {
|
||||
var div = document.createElement("div");
|
||||
div.innerHTML = htmlString.trim();
|
||||
return div.firstChild;
|
||||
}
|
||||
|
||||
function showElement(id) {
|
||||
document.getElementById(id).classList.remove("default-hidden");
|
||||
}
|
||||
|
||||
function hideElement(id) {
|
||||
document.getElementById(id).classList.add("default-hidden");
|
||||
}
|
||||
|
||||
async function preloadImage(url) {
|
||||
return new Promise((resolve) => {
|
||||
const img = new Image();
|
||||
img.src = url;
|
||||
img.onload = () => resolve(img);
|
||||
});
|
||||
}
|
||||
|
||||
const extraContributorTemplate = `
|
||||
<div class="contributor-extra">
|
||||
<div class="contributor-img-wrapper"></div>
|
||||
<div>
|
||||
<div>
|
||||
<a href="{hrefUsername}" target="_blank">{username}</a>
|
||||
-
|
||||
<a href="{hrefContributions}" target="_blank">{contributions}</a>
|
||||
</div>
|
||||
<div>{details}</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
async function loadContributor(contributor) {
|
||||
const img = await preloadImage(contributor.avatar_url);
|
||||
img.className = "contributor-img";
|
||||
img.title = `@${contributor.login}`;
|
||||
img.alt = img.title;
|
||||
|
||||
const a = document.createElement("a");
|
||||
a.href = contributor.html_url;
|
||||
a.target = "_blank";
|
||||
a.appendChild(img);
|
||||
|
||||
if (contributor.login in EXTRA_CONTRIBUTORS) {
|
||||
const div = createElementFromHTML(
|
||||
extraContributorTemplate
|
||||
.replace("{hrefUsername}", contributor.html_url)
|
||||
.replace("{username}", `@${contributor.login}`)
|
||||
.replace("{hrefContributions}", CONTRIBUTOR_COMMIT_URL.replace("{AUTHOR}", contributor.login))
|
||||
.replace("{contributions}", `${contributor.contributions} contributions`)
|
||||
.replace("{details}", EXTRA_CONTRIBUTORS[contributor.login]),
|
||||
);
|
||||
div.querySelector(".contributor-img-wrapper").appendChild(a);
|
||||
|
||||
return { element: div, isExtra: true };
|
||||
} else {
|
||||
return { element: a, isExtra: false };
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchContributors() {
|
||||
if (contributorsLoaded) return;
|
||||
|
||||
const data = await fetch(CONTRIBUTORS_API_URL);
|
||||
showElement("contributors-loading");
|
||||
hideElement("contributors-count");
|
||||
hideElement("contributors");
|
||||
|
||||
if (data.ok) {
|
||||
const contributors = await data.json();
|
||||
const containerSimple = document.getElementById("contributors-simple");
|
||||
const containerExtra = document.getElementById("contributors-extra");
|
||||
containerSimple.innerHTML = "";
|
||||
containerExtra.innerHTML = "";
|
||||
|
||||
const contributorsToShow = contributors.slice(0, CONTRIBUTORS_COUNT_TO_SHOW);
|
||||
const contributorItems = await Promise.all(contributorsToShow.map((contributor) => loadContributor(contributor)));
|
||||
|
||||
contributorItems.forEach((item) => {
|
||||
(item.isExtra ? containerExtra : containerSimple).appendChild(item.element);
|
||||
});
|
||||
|
||||
document.getElementById("contributors-more-link-count").innerText = contributors.length - CONTRIBUTORS_COUNT_TO_SHOW;
|
||||
document.getElementById("contributors-count").innerText = contributors.length;
|
||||
showElement("contributors-count");
|
||||
showElement("contributors");
|
||||
contributorsLoaded = true;
|
||||
}
|
||||
|
||||
hideElement("contributors-loading");
|
||||
}
|
||||
|
||||
$(document).ready(function () {
|
||||
M.Modal.init(document.querySelector("#about_modal"), {
|
||||
onOpenStart: () => fetchContributors(),
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,605 @@
|
||||
/*!
|
||||
* colpick Color Picker
|
||||
* https://github.com/mrgrain/colpick
|
||||
*
|
||||
* Copyright 2013, 2015 Moritz Kornher, Jose Vargas, Stefan Petre
|
||||
* Released under the MIT license and GPLv2 license
|
||||
* https://github.com/mrgrain/colpick/blob/master/LICENSE
|
||||
*/
|
||||
(function (factory) {
|
||||
if (typeof define === 'function' && define.amd) {
|
||||
// AMD. Register as an anonymous module.
|
||||
define(['jquery'], factory);
|
||||
} else if (typeof exports === 'object') {
|
||||
// Node/CommonJS
|
||||
factory(require('jquery'));
|
||||
} else {
|
||||
// Browser globals
|
||||
factory(jQuery);
|
||||
}
|
||||
}(function ($) {
|
||||
var colpick = function () {
|
||||
var
|
||||
tpl = '<div class="colpick"><div class="colpick_color"><div class="colpick_color_overlay1"><div class="colpick_color_overlay2"><div class="colpick_selector_outer"><div class="colpick_selector_inner"></div></div></div></div></div><div class="colpick_hue"><div class="colpick_hue_arrs"><div class="colpick_hue_larr"></div><div class="colpick_hue_rarr"></div></div></div><div class="colpick_new_color"></div><div class="colpick_current_color"></div><div class="colpick_hex_field"><div class="colpick_field_letter">#</div><input type="text" maxlength="6" size="6" /></div><div class="colpick_rgb_r colpick_field"><div class="colpick_field_letter">R</div><input type="text" maxlength="3" size="3" /><div class="colpick_field_arrs"><div class="colpick_field_uarr"></div><div class="colpick_field_darr"></div></div></div><div class="colpick_rgb_g colpick_field"><div class="colpick_field_letter">G</div><input type="text" maxlength="3" size="3" /><div class="colpick_field_arrs"><div class="colpick_field_uarr"></div><div class="colpick_field_darr"></div></div></div><div class="colpick_rgb_b colpick_field"><div class="colpick_field_letter">B</div><input type="text" maxlength="3" size="3" /><div class="colpick_field_arrs"><div class="colpick_field_uarr"></div><div class="colpick_field_darr"></div></div></div><div class="colpick_hsb_h colpick_field"><div class="colpick_field_letter">H</div><input type="text" maxlength="3" size="3" /><div class="colpick_field_arrs"><div class="colpick_field_uarr"></div><div class="colpick_field_darr"></div></div></div><div class="colpick_hsb_s colpick_field"><div class="colpick_field_letter">S</div><input type="text" maxlength="3" size="3" /><div class="colpick_field_arrs"><div class="colpick_field_uarr"></div><div class="colpick_field_darr"></div></div></div><div class="colpick_hsb_b colpick_field"><div class="colpick_field_letter">B</div><input type="text" maxlength="3" size="3" /><div class="colpick_field_arrs"><div class="colpick_field_uarr"></div><div class="colpick_field_darr"></div></div></div><div class="colpick_submit"></div></div>',
|
||||
defaults = {
|
||||
showEvent: 'click',
|
||||
onShow: function () {
|
||||
},
|
||||
onBeforeShow: function () {
|
||||
},
|
||||
onHide: function () {
|
||||
},
|
||||
onChange: function () {
|
||||
},
|
||||
onSubmit: function () {
|
||||
},
|
||||
colorScheme: 'light',
|
||||
color: 'auto',
|
||||
livePreview: true,
|
||||
flat: false,
|
||||
layout: 'full',
|
||||
submit: 1,
|
||||
submitText: 'OK',
|
||||
height: 156,
|
||||
polyfill: false,
|
||||
styles: false
|
||||
},
|
||||
//Fill the inputs of the plugin
|
||||
fillRGBFields = function (hsb, cal) {
|
||||
var rgb = hsbToRgb(hsb);
|
||||
$(cal).data('colpick').fields
|
||||
.eq(1).val(rgb.r).end()
|
||||
.eq(2).val(rgb.g).end()
|
||||
.eq(3).val(rgb.b).end();
|
||||
},
|
||||
fillHSBFields = function (hsb, cal) {
|
||||
$(cal).data('colpick').fields
|
||||
.eq(4).val(Math.round(hsb.h)).end()
|
||||
.eq(5).val(Math.round(hsb.s)).end()
|
||||
.eq(6).val(Math.round(hsb.b)).end();
|
||||
},
|
||||
fillHexFields = function (hsb, cal) {
|
||||
$(cal).data('colpick').fields.eq(0).val(hsbToHex(hsb));
|
||||
},
|
||||
//Set the round selector position
|
||||
setSelector = function (hsb, cal) {
|
||||
$(cal).data('colpick').selector.css('backgroundColor', '#' + hsbToHex({h: hsb.h, s: 100, b: 100}));
|
||||
$(cal).data('colpick').selectorIndic.css({
|
||||
left: parseInt($(cal).data('colpick').height * hsb.s / 100, 10),
|
||||
top: parseInt($(cal).data('colpick').height * (100 - hsb.b) / 100, 10)
|
||||
});
|
||||
},
|
||||
//Set the hue selector position
|
||||
setHue = function (hsb, cal) {
|
||||
$(cal).data('colpick').hue.css('top', parseInt($(cal).data('colpick').height - $(cal).data('colpick').height * hsb.h / 360, 10));
|
||||
},
|
||||
//Set current and new colors
|
||||
setCurrentColor = function (hsb, cal) {
|
||||
$(cal).data('colpick').currentColor.css('backgroundColor', '#' + hsbToHex(hsb));
|
||||
},
|
||||
setNewColor = function (hsb, cal) {
|
||||
$(cal).data('colpick').newColor.css('backgroundColor', '#' + hsbToHex(hsb));
|
||||
},
|
||||
//Called when the new color is changed
|
||||
change = function () {
|
||||
var cal = $(this).parent().parent(), col;
|
||||
if (this.parentNode.className.indexOf('_hex') > 0) {
|
||||
cal.data('colpick').color = col = hexToHsb(fixHex(this.value));
|
||||
fillRGBFields(col, cal.get(0));
|
||||
fillHSBFields(col, cal.get(0));
|
||||
} else if (this.parentNode.className.indexOf('_hsb') > 0) {
|
||||
cal.data('colpick').color = col = fixHSB({
|
||||
h: parseInt(cal.data('colpick').fields.eq(4).val(), 10),
|
||||
s: parseInt(cal.data('colpick').fields.eq(5).val(), 10),
|
||||
b: parseInt(cal.data('colpick').fields.eq(6).val(), 10)
|
||||
});
|
||||
fillRGBFields(col, cal.get(0));
|
||||
fillHexFields(col, cal.get(0));
|
||||
} else {
|
||||
cal.data('colpick').color = col = rgbToHsb(fixRGB({
|
||||
r: parseInt(cal.data('colpick').fields.eq(1).val(), 10),
|
||||
g: parseInt(cal.data('colpick').fields.eq(2).val(), 10),
|
||||
b: parseInt(cal.data('colpick').fields.eq(3).val(), 10)
|
||||
}));
|
||||
fillHexFields(col, cal.get(0));
|
||||
fillHSBFields(col, cal.get(0));
|
||||
}
|
||||
setSelector(col, cal.get(0));
|
||||
setHue(col, cal.get(0));
|
||||
setNewColor(col, cal.get(0));
|
||||
cal.data('colpick').onChange.apply(cal.parent(), [col, hsbToHex(col), hsbToRgb(col), cal.data('colpick').el, 0]);
|
||||
},
|
||||
//Change style on blur and on focus of inputs
|
||||
blur = function () {
|
||||
$(this).parent().removeClass('colpick_focus');
|
||||
},
|
||||
focus = function () {
|
||||
$(this).parent().parent().data('colpick').fields.parent().removeClass('colpick_focus');
|
||||
$(this).parent().addClass('colpick_focus');
|
||||
},
|
||||
//Increment/decrement arrows functions
|
||||
downIncrement = function (ev) {
|
||||
ev.preventDefault ? ev.preventDefault() : ev.returnValue = false;
|
||||
var field = $(this).parent().find('input').focus();
|
||||
var current = {
|
||||
el: $(this).parent().addClass('colpick_slider'),
|
||||
max: this.parentNode.className.indexOf('_hsb_h') > 0 ? 360 : (this.parentNode.className.indexOf('_hsb') > 0 ? 100 : 255),
|
||||
y: ev.pageY,
|
||||
field: field,
|
||||
val: parseInt(field.val(), 10),
|
||||
preview: $(this).parent().parent().data('colpick').livePreview
|
||||
};
|
||||
$(document).mouseup(current, upIncrement);
|
||||
$(document).mousemove(current, moveIncrement);
|
||||
},
|
||||
moveIncrement = function (ev) {
|
||||
ev.data.field.val(Math.max(0, Math.min(ev.data.max, parseInt(ev.data.val - ev.pageY + ev.data.y, 10))));
|
||||
if (ev.data.preview) {
|
||||
change.apply(ev.data.field.get(0), [true]);
|
||||
}
|
||||
return false;
|
||||
},
|
||||
upIncrement = function (ev) {
|
||||
change.apply(ev.data.field.get(0), [true]);
|
||||
ev.data.el.removeClass('colpick_slider').find('input').focus();
|
||||
$(document).off('mouseup', upIncrement);
|
||||
$(document).off('mousemove', moveIncrement);
|
||||
return false;
|
||||
},
|
||||
//Hue slider functions
|
||||
downHue = function (ev) {
|
||||
ev.preventDefault ? ev.preventDefault() : ev.returnValue = false;
|
||||
var current = {
|
||||
cal: $(this).parent(),
|
||||
y: $(this).offset().top
|
||||
};
|
||||
$(document).on('mouseup touchend', current, upHue);
|
||||
$(document).on('mousemove touchmove', current, moveHue);
|
||||
|
||||
var pageY = ((ev.type == 'touchstart') ? ev.originalEvent.changedTouches[0].pageY : ev.pageY );
|
||||
change.apply(
|
||||
current.cal.data('colpick')
|
||||
.fields.eq(4).val(parseInt(360 * (current.cal.data('colpick').height - (pageY - current.y)) / current.cal.data('colpick').height, 10))
|
||||
.get(0),
|
||||
[current.cal.data('colpick').livePreview]
|
||||
);
|
||||
return false;
|
||||
},
|
||||
moveHue = function (ev) {
|
||||
var pageY = ((ev.type == 'touchmove') ? ev.originalEvent.changedTouches[0].pageY : ev.pageY );
|
||||
change.apply(
|
||||
ev.data.cal.data('colpick')
|
||||
.fields.eq(4).val(parseInt(360 * (ev.data.cal.data('colpick').height - Math.max(0, Math.min(ev.data.cal.data('colpick').height, (pageY - ev.data.y)))) / ev.data.cal.data('colpick').height, 10))
|
||||
.get(0),
|
||||
[ev.data.preview]
|
||||
);
|
||||
return false;
|
||||
},
|
||||
upHue = function (ev) {
|
||||
fillRGBFields(ev.data.cal.data('colpick').color, ev.data.cal.get(0));
|
||||
fillHexFields(ev.data.cal.data('colpick').color, ev.data.cal.get(0));
|
||||
$(document).off('mouseup touchend', upHue);
|
||||
$(document).off('mousemove touchmove', moveHue);
|
||||
return false;
|
||||
},
|
||||
//Color selector functions
|
||||
downSelector = function (ev) {
|
||||
ev.preventDefault ? ev.preventDefault() : ev.returnValue = false;
|
||||
var current = {
|
||||
cal: $(this).parent(),
|
||||
pos: $(this).offset()
|
||||
};
|
||||
current.preview = current.cal.data('colpick').livePreview;
|
||||
|
||||
$(document).on('mouseup touchend', current, upSelector);
|
||||
$(document).on('mousemove touchmove', current, moveSelector);
|
||||
|
||||
var pageX, pageY;
|
||||
if (ev.type == 'touchstart') {
|
||||
pageX = ev.originalEvent.changedTouches[0].pageX;
|
||||
pageY = ev.originalEvent.changedTouches[0].pageY;
|
||||
} else {
|
||||
pageX = ev.pageX;
|
||||
pageY = ev.pageY;
|
||||
}
|
||||
|
||||
change.apply(
|
||||
current.cal.data('colpick').fields
|
||||
.eq(6).val(parseInt(100 * (current.cal.data('colpick').height - (pageY - current.pos.top)) / current.cal.data('colpick').height, 10)).end()
|
||||
.eq(5).val(parseInt(100 * (pageX - current.pos.left) / current.cal.data('colpick').height, 10))
|
||||
.get(0),
|
||||
[current.preview]
|
||||
);
|
||||
return false;
|
||||
},
|
||||
moveSelector = function (ev) {
|
||||
var pageX, pageY;
|
||||
if (ev.type == 'touchmove') {
|
||||
pageX = ev.originalEvent.changedTouches[0].pageX;
|
||||
pageY = ev.originalEvent.changedTouches[0].pageY;
|
||||
} else {
|
||||
pageX = ev.pageX;
|
||||
pageY = ev.pageY;
|
||||
}
|
||||
|
||||
change.apply(
|
||||
ev.data.cal.data('colpick').fields
|
||||
.eq(6).val(parseInt(100 * (ev.data.cal.data('colpick').height - Math.max(0, Math.min(ev.data.cal.data('colpick').height, (pageY - ev.data.pos.top)))) / ev.data.cal.data('colpick').height, 10)).end()
|
||||
.eq(5).val(parseInt(100 * (Math.max(0, Math.min(ev.data.cal.data('colpick').height, (pageX - ev.data.pos.left)))) / ev.data.cal.data('colpick').height, 10))
|
||||
.get(0),
|
||||
[ev.data.preview]
|
||||
);
|
||||
return false;
|
||||
},
|
||||
upSelector = function (ev) {
|
||||
fillRGBFields(ev.data.cal.data('colpick').color, ev.data.cal.get(0));
|
||||
fillHexFields(ev.data.cal.data('colpick').color, ev.data.cal.get(0));
|
||||
$(document).off('mouseup touchend', upSelector);
|
||||
$(document).off('mousemove touchmove', moveSelector);
|
||||
return false;
|
||||
},
|
||||
//Submit button
|
||||
clickSubmit = function () {
|
||||
var cal = $(this).parent();
|
||||
var col = cal.data('colpick').color;
|
||||
cal.data('colpick').origColor = col;
|
||||
setCurrentColor(col, cal.get(0));
|
||||
cal.data('colpick').onSubmit(col, hsbToHex(col), hsbToRgb(col), cal.data('colpick').el);
|
||||
},
|
||||
//Show/hide the color picker
|
||||
show = function (ev) {
|
||||
if (ev) {
|
||||
// Prevent the trigger of any direct parent
|
||||
ev.stopPropagation();
|
||||
}
|
||||
var cal = $('#' + $(this).data('colpickId'));
|
||||
if (ev && !cal.data('colpick').polyfill) {
|
||||
ev.preventDefault();
|
||||
}
|
||||
cal.data('colpick').onBeforeShow.apply(this, [cal.get(0)]);
|
||||
var pos = $(this).offset();
|
||||
var top = pos.top + this.offsetHeight;
|
||||
var left = pos.left;
|
||||
var viewPort = getViewport();
|
||||
var calW = cal.width();
|
||||
if (left + calW > viewPort.l + viewPort.w) {
|
||||
left -= calW;
|
||||
}
|
||||
cal.css({left: left + 'px', top: top + 'px'});
|
||||
if (cal.data('colpick').onShow.apply(this, [cal.get(0)]) != false) {
|
||||
cal.show();
|
||||
}
|
||||
//Hide when user clicks outside
|
||||
$('html').mousedown({cal: cal}, hide);
|
||||
cal.mousedown(function (ev) {
|
||||
ev.stopPropagation();
|
||||
})
|
||||
},
|
||||
hide = function (ev) {
|
||||
var cal = $('#' + $(this).data('colpickId'));
|
||||
if (ev) {
|
||||
cal = ev.data.cal;
|
||||
}
|
||||
if (cal.data('colpick').onHide.apply(this, [cal.get(0)]) != false) {
|
||||
cal.hide();
|
||||
}
|
||||
$('html').off('mousedown', hide);
|
||||
},
|
||||
getViewport = function () {
|
||||
var m = document.compatMode == 'CSS1Compat';
|
||||
return {
|
||||
l: window.pageXOffset || (m ? document.documentElement.scrollLeft : document.body.scrollLeft),
|
||||
w: window.innerWidth || (m ? document.documentElement.clientWidth : document.body.clientWidth)
|
||||
};
|
||||
},
|
||||
//Fix the values if the user enters a negative or high value
|
||||
fixHSB = function (hsb) {
|
||||
return {
|
||||
h: Math.min(360, Math.max(0, hsb.h)),
|
||||
s: Math.min(100, Math.max(0, hsb.s)),
|
||||
b: Math.min(100, Math.max(0, hsb.b))
|
||||
};
|
||||
},
|
||||
fixRGB = function (rgb) {
|
||||
return {
|
||||
r: Math.min(255, Math.max(0, rgb.r)),
|
||||
g: Math.min(255, Math.max(0, rgb.g)),
|
||||
b: Math.min(255, Math.max(0, rgb.b))
|
||||
};
|
||||
},
|
||||
fixHex = function (hex) {
|
||||
var len = 6 - hex.length;
|
||||
if (len == 3) {
|
||||
var e = [];
|
||||
for (var j = 0; j < len; j++) {
|
||||
e.push(hex[j]);
|
||||
e.push(hex[j]);
|
||||
}
|
||||
hex = e.join('');
|
||||
} else {
|
||||
if (len > 0) {
|
||||
var o = [];
|
||||
for (var i = 0; i < len; i++) {
|
||||
o.push('0');
|
||||
}
|
||||
o.push(hex);
|
||||
hex = o.join('');
|
||||
}
|
||||
}
|
||||
return hex;
|
||||
},
|
||||
restoreOriginal = function () {
|
||||
var cal = $(this).parent();
|
||||
var col = cal.data('colpick').origColor;
|
||||
cal.data('colpick').color = col;
|
||||
fillRGBFields(col, cal.get(0));
|
||||
fillHexFields(col, cal.get(0));
|
||||
fillHSBFields(col, cal.get(0));
|
||||
setSelector(col, cal.get(0));
|
||||
setHue(col, cal.get(0));
|
||||
setNewColor(col, cal.get(0));
|
||||
};
|
||||
return {
|
||||
init: function (opt) {
|
||||
opt = $.extend({}, defaults, opt || {});
|
||||
//Set color
|
||||
if (opt.color === 'auto') {
|
||||
} else if (typeof opt.color == 'string') {
|
||||
opt.color = hexToHsb(opt.color);
|
||||
} else if (opt.color.r != undefined && opt.color.g != undefined && opt.color.b != undefined) {
|
||||
opt.color = rgbToHsb(opt.color);
|
||||
} else if (opt.color.h != undefined && opt.color.s != undefined && opt.color.b != undefined) {
|
||||
opt.color = fixHSB(opt.color);
|
||||
} else {
|
||||
return this;
|
||||
}
|
||||
|
||||
//For each selected DOM element
|
||||
return this.each(function () {
|
||||
//If the element does not have an ID
|
||||
if (!$(this).data('colpickId')) {
|
||||
var options = $.extend({}, opt);
|
||||
//Color
|
||||
if (opt.color === 'auto') {
|
||||
options.color = $(this).val() ? hexToHsb($(this).val()) : {h: 0, s: 0, b: 0};
|
||||
}
|
||||
options.origColor = options.color;
|
||||
|
||||
//Polyfill
|
||||
if (typeof opt.polyfill == 'function') {
|
||||
options.polyfill = opt.polyfill(this);
|
||||
}
|
||||
if (options.polyfill && $(this).is('input') && this.type === "color") {
|
||||
return;
|
||||
}
|
||||
|
||||
//Generate and assign a random ID
|
||||
var id = 'collorpicker_' + parseInt(Math.random() * 1000);
|
||||
$(this).data('colpickId', id);
|
||||
//Set the tpl's ID and get the HTML
|
||||
var cal = $(tpl).attr('id', id);
|
||||
//Add class according to layout
|
||||
cal.addClass('colpick_' + options.layout + (options.submit ? '' : ' colpick_' + options.layout + '_ns'));
|
||||
//Add class if the color scheme is not default
|
||||
if (options.colorScheme != 'light') {
|
||||
cal.addClass('colpick_' + options.colorScheme);
|
||||
}
|
||||
//Setup submit button
|
||||
cal.find('div.colpick_submit').html(options.submitText).click(clickSubmit);
|
||||
//Setup input fields
|
||||
options.fields = cal.find('input').change(change).blur(blur).focus(focus);
|
||||
cal.find('div.colpick_field_arrs').mousedown(downIncrement).end().find('div.colpick_current_color').click(restoreOriginal);
|
||||
//Setup hue selector
|
||||
options.selector = cal.find('div.colpick_color').on('mousedown touchstart', downSelector);
|
||||
options.selectorIndic = options.selector.find('div.colpick_selector_outer');
|
||||
//Store parts of the plugin
|
||||
options.el = this;
|
||||
options.hue = cal.find('div.colpick_hue_arrs');
|
||||
var huebar = options.hue.parent();
|
||||
//Paint the hue bar
|
||||
var UA = navigator.userAgent.toLowerCase();
|
||||
var isIE = navigator.appName === 'Microsoft Internet Explorer';
|
||||
var IEver = isIE ? parseFloat(UA.match(/msie ([0-9]*[\.0-9]+)/)[1]) : 0;
|
||||
var ngIE = ( isIE && IEver < 10 );
|
||||
var stops = ['#ff0000', '#ff0080', '#ff00ff', '#8000ff', '#0000ff', '#0080ff', '#00ffff', '#00ff80', '#00ff00', '#80ff00', '#ffff00', '#ff8000', '#ff0000'];
|
||||
if (ngIE) {
|
||||
var i, div;
|
||||
for (i = 0; i <= 11; i++) {
|
||||
div = $('<div></div>').attr('style', 'height:8.333333%; filter:progid:DXImageTransform.Microsoft.gradient(GradientType=0,startColorstr=' + stops[i] + ', endColorstr=' + stops[i + 1] + '); -ms-filter: "progid:DXImageTransform.Microsoft.gradient(GradientType=0,startColorstr=' + stops[i] + ', endColorstr=' + stops[i + 1] + ')";');
|
||||
huebar.append(div);
|
||||
}
|
||||
} else {
|
||||
var stopList = stops.join(',');
|
||||
huebar.attr('style', 'background:-webkit-linear-gradient(top,' + stopList + '); background: -o-linear-gradient(top,' + stopList + '); background: -ms-linear-gradient(top,' + stopList + '); background:-moz-linear-gradient(top,' + stopList + '); -webkit-linear-gradient(top,' + stopList + '); background:linear-gradient(to bottom,' + stopList + '); ');
|
||||
}
|
||||
cal.find('div.colpick_hue').on('mousedown touchstart', downHue);
|
||||
options.newColor = cal.find('div.colpick_new_color');
|
||||
options.currentColor = cal.find('div.colpick_current_color');
|
||||
//Store options and fill with default color
|
||||
cal.data('colpick', options);
|
||||
fillRGBFields(options.color, cal.get(0));
|
||||
fillHSBFields(options.color, cal.get(0));
|
||||
fillHexFields(options.color, cal.get(0));
|
||||
setHue(options.color, cal.get(0));
|
||||
setSelector(options.color, cal.get(0));
|
||||
setCurrentColor(options.color, cal.get(0));
|
||||
setNewColor(options.color, cal.get(0));
|
||||
//Append to body if flat=false, else show in place
|
||||
if (options.flat) {
|
||||
cal.appendTo(options.appendTo || this).show();
|
||||
cal.css(options.styles || {
|
||||
position: 'relative',
|
||||
display: 'block'
|
||||
});
|
||||
} else {
|
||||
cal.appendTo(options.appendTo || document.body);
|
||||
$(this).on(options.showEvent, show);
|
||||
cal.css(options.styles || {
|
||||
position: 'absolute'
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
//Shows the picker
|
||||
showPicker: function () {
|
||||
return this.each(function () {
|
||||
if ($(this).data('colpickId')) {
|
||||
show.apply(this);
|
||||
}
|
||||
});
|
||||
},
|
||||
//Hides the picker
|
||||
hidePicker: function () {
|
||||
return this.each(function () {
|
||||
if ($(this).data('colpickId')) {
|
||||
hide.apply(this);
|
||||
}
|
||||
});
|
||||
},
|
||||
//Sets a color as new and current (default)
|
||||
setColor: function (col, setCurrent) {
|
||||
setCurrent = (typeof setCurrent === "undefined") ? 1 : setCurrent;
|
||||
if (typeof col == 'string') {
|
||||
col = hexToHsb(col);
|
||||
} else if (col.r != undefined && col.g != undefined && col.b != undefined) {
|
||||
col = rgbToHsb(col);
|
||||
} else if (col.h != undefined && col.s != undefined && col.b != undefined) {
|
||||
col = fixHSB(col);
|
||||
} else {
|
||||
return this;
|
||||
}
|
||||
return this.each(function () {
|
||||
if ($(this).data('colpickId')) {
|
||||
var cal = $('#' + $(this).data('colpickId'));
|
||||
cal.data('colpick').color = col;
|
||||
cal.data('colpick').origColor = col;
|
||||
fillRGBFields(col, cal.get(0));
|
||||
fillHSBFields(col, cal.get(0));
|
||||
fillHexFields(col, cal.get(0));
|
||||
setHue(col, cal.get(0));
|
||||
setSelector(col, cal.get(0));
|
||||
|
||||
setNewColor(col, cal.get(0));
|
||||
cal.data('colpick').onChange.apply(cal.parent(), [col, hsbToHex(col), hsbToRgb(col), cal.data('colpick').el, 1]);
|
||||
if (setCurrent) {
|
||||
setCurrentColor(col, cal.get(0));
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
destroy: function () {
|
||||
$('#' + $(this).data('colpickId')).remove();
|
||||
}
|
||||
};
|
||||
}();
|
||||
//Color space conversions
|
||||
var hexToRgb = function (hex) {
|
||||
hex = parseInt(((hex.indexOf('#') > -1) ? hex.substring(1) : hex), 16);
|
||||
return {r: hex >> 16, g: (hex & 0x00FF00) >> 8, b: (hex & 0x0000FF)};
|
||||
};
|
||||
var hexToHsb = function (hex) {
|
||||
return rgbToHsb(hexToRgb(hex));
|
||||
};
|
||||
var rgbToHsb = function (rgb) {
|
||||
var hsb = {h: 0, s: 0, b: 0};
|
||||
var min = Math.min(rgb.r, rgb.g, rgb.b);
|
||||
var max = Math.max(rgb.r, rgb.g, rgb.b);
|
||||
var delta = max - min;
|
||||
hsb.b = max;
|
||||
hsb.s = max != 0 ? 255 * delta / max : 0;
|
||||
if (hsb.s != 0) {
|
||||
if (rgb.r == max) hsb.h = (rgb.g - rgb.b) / delta;
|
||||
else if (rgb.g == max) hsb.h = 2 + (rgb.b - rgb.r) / delta;
|
||||
else hsb.h = 4 + (rgb.r - rgb.g) / delta;
|
||||
} else hsb.h = -1;
|
||||
hsb.h *= 60;
|
||||
if (hsb.h < 0) hsb.h += 360;
|
||||
hsb.s *= 100 / 255;
|
||||
hsb.b *= 100 / 255;
|
||||
return hsb;
|
||||
};
|
||||
var hsbToRgb = function (hsb) {
|
||||
var rgb = {};
|
||||
var h = hsb.h;
|
||||
var s = hsb.s * 255 / 100;
|
||||
var v = hsb.b * 255 / 100;
|
||||
if (s == 0) {
|
||||
rgb.r = rgb.g = rgb.b = v;
|
||||
} else {
|
||||
var t1 = v;
|
||||
var t2 = (255 - s) * v / 255;
|
||||
var t3 = (t1 - t2) * (h % 60) / 60;
|
||||
if (h == 360) h = 0;
|
||||
if (h < 60) {
|
||||
rgb.r = t1;
|
||||
rgb.b = t2;
|
||||
rgb.g = t2 + t3
|
||||
}
|
||||
else if (h < 120) {
|
||||
rgb.g = t1;
|
||||
rgb.b = t2;
|
||||
rgb.r = t1 - t3
|
||||
}
|
||||
else if (h < 180) {
|
||||
rgb.g = t1;
|
||||
rgb.r = t2;
|
||||
rgb.b = t2 + t3
|
||||
}
|
||||
else if (h < 240) {
|
||||
rgb.b = t1;
|
||||
rgb.r = t2;
|
||||
rgb.g = t1 - t3
|
||||
}
|
||||
else if (h < 300) {
|
||||
rgb.b = t1;
|
||||
rgb.g = t2;
|
||||
rgb.r = t2 + t3
|
||||
}
|
||||
else if (h < 360) {
|
||||
rgb.r = t1;
|
||||
rgb.g = t2;
|
||||
rgb.b = t1 - t3
|
||||
}
|
||||
else {
|
||||
rgb.r = 0;
|
||||
rgb.g = 0;
|
||||
rgb.b = 0
|
||||
}
|
||||
}
|
||||
return {r: Math.round(rgb.r), g: Math.round(rgb.g), b: Math.round(rgb.b)};
|
||||
};
|
||||
var rgbToHex = function (rgb) {
|
||||
var hex = [
|
||||
rgb.r.toString(16),
|
||||
rgb.g.toString(16),
|
||||
rgb.b.toString(16)
|
||||
];
|
||||
$.each(hex, function (nr, val) {
|
||||
if (val.length == 1) {
|
||||
hex[nr] = '0' + val;
|
||||
}
|
||||
});
|
||||
return hex.join('');
|
||||
};
|
||||
var hsbToHex = function (hsb) {
|
||||
return rgbToHex(hsbToRgb(hsb));
|
||||
};
|
||||
$.fn.extend({
|
||||
colpick: colpick.init,
|
||||
colpickHide: colpick.hidePicker,
|
||||
colpickShow: colpick.showPicker,
|
||||
colpickSetColor: colpick.setColor,
|
||||
colpickDestroy: colpick.destroy
|
||||
});
|
||||
$.extend({
|
||||
colpick: {
|
||||
rgbToHex: rgbToHex,
|
||||
rgbToHsb: rgbToHsb,
|
||||
hsbToHex: hsbToHex,
|
||||
hsbToRgb: hsbToRgb,
|
||||
hexToHsb: hexToHsb,
|
||||
hexToRgb: hexToRgb
|
||||
}
|
||||
});
|
||||
}));
|
||||
|
After Width: | Height: | Size: 275 KiB |
@@ -0,0 +1,9 @@
|
||||
The recommended way to use the Material Icons font is by linking to the web font hosted on Google Fonts:
|
||||
|
||||
```html
|
||||
<link href="https://fonts.googleapis.com/icon?family=Material+Icons"
|
||||
rel="stylesheet">
|
||||
```
|
||||
|
||||
Read more in our full usage guide:
|
||||
http://google.github.io/material-design-icons/#icon-font-for-the-web
|
||||
@@ -0,0 +1,932 @@
|
||||
3d_rotation e84d
|
||||
ac_unit eb3b
|
||||
access_alarm e190
|
||||
access_alarms e191
|
||||
access_time e192
|
||||
accessibility e84e
|
||||
accessible e914
|
||||
account_balance e84f
|
||||
account_balance_wallet e850
|
||||
account_box e851
|
||||
account_circle e853
|
||||
adb e60e
|
||||
add e145
|
||||
add_a_photo e439
|
||||
add_alarm e193
|
||||
add_alert e003
|
||||
add_box e146
|
||||
add_circle e147
|
||||
add_circle_outline e148
|
||||
add_location e567
|
||||
add_shopping_cart e854
|
||||
add_to_photos e39d
|
||||
add_to_queue e05c
|
||||
adjust e39e
|
||||
airline_seat_flat e630
|
||||
airline_seat_flat_angled e631
|
||||
airline_seat_individual_suite e632
|
||||
airline_seat_legroom_extra e633
|
||||
airline_seat_legroom_normal e634
|
||||
airline_seat_legroom_reduced e635
|
||||
airline_seat_recline_extra e636
|
||||
airline_seat_recline_normal e637
|
||||
airplanemode_active e195
|
||||
airplanemode_inactive e194
|
||||
airplay e055
|
||||
airport_shuttle eb3c
|
||||
alarm e855
|
||||
alarm_add e856
|
||||
alarm_off e857
|
||||
alarm_on e858
|
||||
album e019
|
||||
all_inclusive eb3d
|
||||
all_out e90b
|
||||
android e859
|
||||
announcement e85a
|
||||
apps e5c3
|
||||
archive e149
|
||||
arrow_back e5c4
|
||||
arrow_downward e5db
|
||||
arrow_drop_down e5c5
|
||||
arrow_drop_down_circle e5c6
|
||||
arrow_drop_up e5c7
|
||||
arrow_forward e5c8
|
||||
arrow_upward e5d8
|
||||
art_track e060
|
||||
aspect_ratio e85b
|
||||
assessment e85c
|
||||
assignment e85d
|
||||
assignment_ind e85e
|
||||
assignment_late e85f
|
||||
assignment_return e860
|
||||
assignment_returned e861
|
||||
assignment_turned_in e862
|
||||
assistant e39f
|
||||
assistant_photo e3a0
|
||||
attach_file e226
|
||||
attach_money e227
|
||||
attachment e2bc
|
||||
audiotrack e3a1
|
||||
autorenew e863
|
||||
av_timer e01b
|
||||
backspace e14a
|
||||
backup e864
|
||||
battery_alert e19c
|
||||
battery_charging_full e1a3
|
||||
battery_full e1a4
|
||||
battery_std e1a5
|
||||
battery_unknown e1a6
|
||||
beach_access eb3e
|
||||
beenhere e52d
|
||||
block e14b
|
||||
bluetooth e1a7
|
||||
bluetooth_audio e60f
|
||||
bluetooth_connected e1a8
|
||||
bluetooth_disabled e1a9
|
||||
bluetooth_searching e1aa
|
||||
blur_circular e3a2
|
||||
blur_linear e3a3
|
||||
blur_off e3a4
|
||||
blur_on e3a5
|
||||
book e865
|
||||
bookmark e866
|
||||
bookmark_border e867
|
||||
border_all e228
|
||||
border_bottom e229
|
||||
border_clear e22a
|
||||
border_color e22b
|
||||
border_horizontal e22c
|
||||
border_inner e22d
|
||||
border_left e22e
|
||||
border_outer e22f
|
||||
border_right e230
|
||||
border_style e231
|
||||
border_top e232
|
||||
border_vertical e233
|
||||
branding_watermark e06b
|
||||
brightness_1 e3a6
|
||||
brightness_2 e3a7
|
||||
brightness_3 e3a8
|
||||
brightness_4 e3a9
|
||||
brightness_5 e3aa
|
||||
brightness_6 e3ab
|
||||
brightness_7 e3ac
|
||||
brightness_auto e1ab
|
||||
brightness_high e1ac
|
||||
brightness_low e1ad
|
||||
brightness_medium e1ae
|
||||
broken_image e3ad
|
||||
brush e3ae
|
||||
bubble_chart e6dd
|
||||
bug_report e868
|
||||
build e869
|
||||
burst_mode e43c
|
||||
business e0af
|
||||
business_center eb3f
|
||||
cached e86a
|
||||
cake e7e9
|
||||
call e0b0
|
||||
call_end e0b1
|
||||
call_made e0b2
|
||||
call_merge e0b3
|
||||
call_missed e0b4
|
||||
call_missed_outgoing e0e4
|
||||
call_received e0b5
|
||||
call_split e0b6
|
||||
call_to_action e06c
|
||||
camera e3af
|
||||
camera_alt e3b0
|
||||
camera_enhance e8fc
|
||||
camera_front e3b1
|
||||
camera_rear e3b2
|
||||
camera_roll e3b3
|
||||
cancel e5c9
|
||||
card_giftcard e8f6
|
||||
card_membership e8f7
|
||||
card_travel e8f8
|
||||
casino eb40
|
||||
cast e307
|
||||
cast_connected e308
|
||||
center_focus_strong e3b4
|
||||
center_focus_weak e3b5
|
||||
change_history e86b
|
||||
chat e0b7
|
||||
chat_bubble e0ca
|
||||
chat_bubble_outline e0cb
|
||||
check e5ca
|
||||
check_box e834
|
||||
check_box_outline_blank e835
|
||||
check_circle e86c
|
||||
chevron_left e5cb
|
||||
chevron_right e5cc
|
||||
child_care eb41
|
||||
child_friendly eb42
|
||||
chrome_reader_mode e86d
|
||||
class e86e
|
||||
clear e14c
|
||||
clear_all e0b8
|
||||
close e5cd
|
||||
closed_caption e01c
|
||||
cloud e2bd
|
||||
cloud_circle e2be
|
||||
cloud_done e2bf
|
||||
cloud_download e2c0
|
||||
cloud_off e2c1
|
||||
cloud_queue e2c2
|
||||
cloud_upload e2c3
|
||||
code e86f
|
||||
collections e3b6
|
||||
collections_bookmark e431
|
||||
color_lens e3b7
|
||||
colorize e3b8
|
||||
comment e0b9
|
||||
compare e3b9
|
||||
compare_arrows e915
|
||||
computer e30a
|
||||
confirmation_number e638
|
||||
contact_mail e0d0
|
||||
contact_phone e0cf
|
||||
contacts e0ba
|
||||
content_copy e14d
|
||||
content_cut e14e
|
||||
content_paste e14f
|
||||
control_point e3ba
|
||||
control_point_duplicate e3bb
|
||||
copyright e90c
|
||||
create e150
|
||||
create_new_folder e2cc
|
||||
credit_card e870
|
||||
crop e3be
|
||||
crop_16_9 e3bc
|
||||
crop_3_2 e3bd
|
||||
crop_5_4 e3bf
|
||||
crop_7_5 e3c0
|
||||
crop_din e3c1
|
||||
crop_free e3c2
|
||||
crop_landscape e3c3
|
||||
crop_original e3c4
|
||||
crop_portrait e3c5
|
||||
crop_rotate e437
|
||||
crop_square e3c6
|
||||
dashboard e871
|
||||
data_usage e1af
|
||||
date_range e916
|
||||
dehaze e3c7
|
||||
delete e872
|
||||
delete_forever e92b
|
||||
delete_sweep e16c
|
||||
description e873
|
||||
desktop_mac e30b
|
||||
desktop_windows e30c
|
||||
details e3c8
|
||||
developer_board e30d
|
||||
developer_mode e1b0
|
||||
device_hub e335
|
||||
devices e1b1
|
||||
devices_other e337
|
||||
dialer_sip e0bb
|
||||
dialpad e0bc
|
||||
directions e52e
|
||||
directions_bike e52f
|
||||
directions_boat e532
|
||||
directions_bus e530
|
||||
directions_car e531
|
||||
directions_railway e534
|
||||
directions_run e566
|
||||
directions_subway e533
|
||||
directions_transit e535
|
||||
directions_walk e536
|
||||
disc_full e610
|
||||
dns e875
|
||||
do_not_disturb e612
|
||||
do_not_disturb_alt e611
|
||||
do_not_disturb_off e643
|
||||
do_not_disturb_on e644
|
||||
dock e30e
|
||||
domain e7ee
|
||||
done e876
|
||||
done_all e877
|
||||
donut_large e917
|
||||
donut_small e918
|
||||
drafts e151
|
||||
drag_handle e25d
|
||||
drive_eta e613
|
||||
dvr e1b2
|
||||
edit e3c9
|
||||
edit_location e568
|
||||
eject e8fb
|
||||
email e0be
|
||||
enhanced_encryption e63f
|
||||
equalizer e01d
|
||||
error e000
|
||||
error_outline e001
|
||||
euro_symbol e926
|
||||
ev_station e56d
|
||||
event e878
|
||||
event_available e614
|
||||
event_busy e615
|
||||
event_note e616
|
||||
event_seat e903
|
||||
exit_to_app e879
|
||||
expand_less e5ce
|
||||
expand_more e5cf
|
||||
explicit e01e
|
||||
explore e87a
|
||||
exposure e3ca
|
||||
exposure_neg_1 e3cb
|
||||
exposure_neg_2 e3cc
|
||||
exposure_plus_1 e3cd
|
||||
exposure_plus_2 e3ce
|
||||
exposure_zero e3cf
|
||||
extension e87b
|
||||
face e87c
|
||||
fast_forward e01f
|
||||
fast_rewind e020
|
||||
favorite e87d
|
||||
favorite_border e87e
|
||||
featured_play_list e06d
|
||||
featured_video e06e
|
||||
feedback e87f
|
||||
fiber_dvr e05d
|
||||
fiber_manual_record e061
|
||||
fiber_new e05e
|
||||
fiber_pin e06a
|
||||
fiber_smart_record e062
|
||||
file_download e2c4
|
||||
file_upload e2c6
|
||||
filter e3d3
|
||||
filter_1 e3d0
|
||||
filter_2 e3d1
|
||||
filter_3 e3d2
|
||||
filter_4 e3d4
|
||||
filter_5 e3d5
|
||||
filter_6 e3d6
|
||||
filter_7 e3d7
|
||||
filter_8 e3d8
|
||||
filter_9 e3d9
|
||||
filter_9_plus e3da
|
||||
filter_b_and_w e3db
|
||||
filter_center_focus e3dc
|
||||
filter_drama e3dd
|
||||
filter_frames e3de
|
||||
filter_hdr e3df
|
||||
filter_list e152
|
||||
filter_none e3e0
|
||||
filter_tilt_shift e3e2
|
||||
filter_vintage e3e3
|
||||
find_in_page e880
|
||||
find_replace e881
|
||||
fingerprint e90d
|
||||
first_page e5dc
|
||||
fitness_center eb43
|
||||
flag e153
|
||||
flare e3e4
|
||||
flash_auto e3e5
|
||||
flash_off e3e6
|
||||
flash_on e3e7
|
||||
flight e539
|
||||
flight_land e904
|
||||
flight_takeoff e905
|
||||
flip e3e8
|
||||
flip_to_back e882
|
||||
flip_to_front e883
|
||||
folder e2c7
|
||||
folder_open e2c8
|
||||
folder_shared e2c9
|
||||
folder_special e617
|
||||
font_download e167
|
||||
format_align_center e234
|
||||
format_align_justify e235
|
||||
format_align_left e236
|
||||
format_align_right e237
|
||||
format_bold e238
|
||||
format_clear e239
|
||||
format_color_fill e23a
|
||||
format_color_reset e23b
|
||||
format_color_text e23c
|
||||
format_indent_decrease e23d
|
||||
format_indent_increase e23e
|
||||
format_italic e23f
|
||||
format_line_spacing e240
|
||||
format_list_bulleted e241
|
||||
format_list_numbered e242
|
||||
format_paint e243
|
||||
format_quote e244
|
||||
format_shapes e25e
|
||||
format_size e245
|
||||
format_strikethrough e246
|
||||
format_textdirection_l_to_r e247
|
||||
format_textdirection_r_to_l e248
|
||||
format_underlined e249
|
||||
forum e0bf
|
||||
forward e154
|
||||
forward_10 e056
|
||||
forward_30 e057
|
||||
forward_5 e058
|
||||
free_breakfast eb44
|
||||
fullscreen e5d0
|
||||
fullscreen_exit e5d1
|
||||
functions e24a
|
||||
g_translate e927
|
||||
gamepad e30f
|
||||
games e021
|
||||
gavel e90e
|
||||
gesture e155
|
||||
get_app e884
|
||||
gif e908
|
||||
golf_course eb45
|
||||
gps_fixed e1b3
|
||||
gps_not_fixed e1b4
|
||||
gps_off e1b5
|
||||
grade e885
|
||||
gradient e3e9
|
||||
grain e3ea
|
||||
graphic_eq e1b8
|
||||
grid_off e3eb
|
||||
grid_on e3ec
|
||||
group e7ef
|
||||
group_add e7f0
|
||||
group_work e886
|
||||
hd e052
|
||||
hdr_off e3ed
|
||||
hdr_on e3ee
|
||||
hdr_strong e3f1
|
||||
hdr_weak e3f2
|
||||
headset e310
|
||||
headset_mic e311
|
||||
healing e3f3
|
||||
hearing e023
|
||||
help e887
|
||||
help_outline e8fd
|
||||
high_quality e024
|
||||
highlight e25f
|
||||
highlight_off e888
|
||||
history e889
|
||||
home e88a
|
||||
hot_tub eb46
|
||||
hotel e53a
|
||||
hourglass_empty e88b
|
||||
hourglass_full e88c
|
||||
http e902
|
||||
https e88d
|
||||
image e3f4
|
||||
image_aspect_ratio e3f5
|
||||
import_contacts e0e0
|
||||
import_export e0c3
|
||||
important_devices e912
|
||||
inbox e156
|
||||
indeterminate_check_box e909
|
||||
info e88e
|
||||
info_outline e88f
|
||||
input e890
|
||||
insert_chart e24b
|
||||
insert_comment e24c
|
||||
insert_drive_file e24d
|
||||
insert_emoticon e24e
|
||||
insert_invitation e24f
|
||||
insert_link e250
|
||||
insert_photo e251
|
||||
invert_colors e891
|
||||
invert_colors_off e0c4
|
||||
iso e3f6
|
||||
keyboard e312
|
||||
keyboard_arrow_down e313
|
||||
keyboard_arrow_left e314
|
||||
keyboard_arrow_right e315
|
||||
keyboard_arrow_up e316
|
||||
keyboard_backspace e317
|
||||
keyboard_capslock e318
|
||||
keyboard_hide e31a
|
||||
keyboard_return e31b
|
||||
keyboard_tab e31c
|
||||
keyboard_voice e31d
|
||||
kitchen eb47
|
||||
label e892
|
||||
label_outline e893
|
||||
landscape e3f7
|
||||
language e894
|
||||
laptop e31e
|
||||
laptop_chromebook e31f
|
||||
laptop_mac e320
|
||||
laptop_windows e321
|
||||
last_page e5dd
|
||||
launch e895
|
||||
layers e53b
|
||||
layers_clear e53c
|
||||
leak_add e3f8
|
||||
leak_remove e3f9
|
||||
lens e3fa
|
||||
library_add e02e
|
||||
library_books e02f
|
||||
library_music e030
|
||||
lightbulb_outline e90f
|
||||
line_style e919
|
||||
line_weight e91a
|
||||
linear_scale e260
|
||||
link e157
|
||||
linked_camera e438
|
||||
list e896
|
||||
live_help e0c6
|
||||
live_tv e639
|
||||
local_activity e53f
|
||||
local_airport e53d
|
||||
local_atm e53e
|
||||
local_bar e540
|
||||
local_cafe e541
|
||||
local_car_wash e542
|
||||
local_convenience_store e543
|
||||
local_dining e556
|
||||
local_drink e544
|
||||
local_florist e545
|
||||
local_gas_station e546
|
||||
local_grocery_store e547
|
||||
local_hospital e548
|
||||
local_hotel e549
|
||||
local_laundry_service e54a
|
||||
local_library e54b
|
||||
local_mall e54c
|
||||
local_movies e54d
|
||||
local_offer e54e
|
||||
local_parking e54f
|
||||
local_pharmacy e550
|
||||
local_phone e551
|
||||
local_pizza e552
|
||||
local_play e553
|
||||
local_post_office e554
|
||||
local_printshop e555
|
||||
local_see e557
|
||||
local_shipping e558
|
||||
local_taxi e559
|
||||
location_city e7f1
|
||||
location_disabled e1b6
|
||||
location_off e0c7
|
||||
location_on e0c8
|
||||
location_searching e1b7
|
||||
lock e897
|
||||
lock_open e898
|
||||
lock_outline e899
|
||||
looks e3fc
|
||||
looks_3 e3fb
|
||||
looks_4 e3fd
|
||||
looks_5 e3fe
|
||||
looks_6 e3ff
|
||||
looks_one e400
|
||||
looks_two e401
|
||||
loop e028
|
||||
loupe e402
|
||||
low_priority e16d
|
||||
loyalty e89a
|
||||
mail e158
|
||||
mail_outline e0e1
|
||||
map e55b
|
||||
markunread e159
|
||||
markunread_mailbox e89b
|
||||
memory e322
|
||||
menu e5d2
|
||||
merge_type e252
|
||||
message e0c9
|
||||
mic e029
|
||||
mic_none e02a
|
||||
mic_off e02b
|
||||
mms e618
|
||||
mode_comment e253
|
||||
mode_edit e254
|
||||
monetization_on e263
|
||||
money_off e25c
|
||||
monochrome_photos e403
|
||||
mood e7f2
|
||||
mood_bad e7f3
|
||||
more e619
|
||||
more_horiz e5d3
|
||||
more_vert e5d4
|
||||
motorcycle e91b
|
||||
mouse e323
|
||||
move_to_inbox e168
|
||||
movie e02c
|
||||
movie_creation e404
|
||||
movie_filter e43a
|
||||
multiline_chart e6df
|
||||
music_note e405
|
||||
music_video e063
|
||||
my_location e55c
|
||||
nature e406
|
||||
nature_people e407
|
||||
navigate_before e408
|
||||
navigate_next e409
|
||||
navigation e55d
|
||||
near_me e569
|
||||
network_cell e1b9
|
||||
network_check e640
|
||||
network_locked e61a
|
||||
network_wifi e1ba
|
||||
new_releases e031
|
||||
next_week e16a
|
||||
nfc e1bb
|
||||
no_encryption e641
|
||||
no_sim e0cc
|
||||
not_interested e033
|
||||
note e06f
|
||||
note_add e89c
|
||||
notifications e7f4
|
||||
notifications_active e7f7
|
||||
notifications_none e7f5
|
||||
notifications_off e7f6
|
||||
notifications_paused e7f8
|
||||
offline_pin e90a
|
||||
ondemand_video e63a
|
||||
opacity e91c
|
||||
open_in_browser e89d
|
||||
open_in_new e89e
|
||||
open_with e89f
|
||||
pages e7f9
|
||||
pageview e8a0
|
||||
palette e40a
|
||||
pan_tool e925
|
||||
panorama e40b
|
||||
panorama_fish_eye e40c
|
||||
panorama_horizontal e40d
|
||||
panorama_vertical e40e
|
||||
panorama_wide_angle e40f
|
||||
party_mode e7fa
|
||||
pause e034
|
||||
pause_circle_filled e035
|
||||
pause_circle_outline e036
|
||||
payment e8a1
|
||||
people e7fb
|
||||
people_outline e7fc
|
||||
perm_camera_mic e8a2
|
||||
perm_contact_calendar e8a3
|
||||
perm_data_setting e8a4
|
||||
perm_device_information e8a5
|
||||
perm_identity e8a6
|
||||
perm_media e8a7
|
||||
perm_phone_msg e8a8
|
||||
perm_scan_wifi e8a9
|
||||
person e7fd
|
||||
person_add e7fe
|
||||
person_outline e7ff
|
||||
person_pin e55a
|
||||
person_pin_circle e56a
|
||||
personal_video e63b
|
||||
pets e91d
|
||||
phone e0cd
|
||||
phone_android e324
|
||||
phone_bluetooth_speaker e61b
|
||||
phone_forwarded e61c
|
||||
phone_in_talk e61d
|
||||
phone_iphone e325
|
||||
phone_locked e61e
|
||||
phone_missed e61f
|
||||
phone_paused e620
|
||||
phonelink e326
|
||||
phonelink_erase e0db
|
||||
phonelink_lock e0dc
|
||||
phonelink_off e327
|
||||
phonelink_ring e0dd
|
||||
phonelink_setup e0de
|
||||
photo e410
|
||||
photo_album e411
|
||||
photo_camera e412
|
||||
photo_filter e43b
|
||||
photo_library e413
|
||||
photo_size_select_actual e432
|
||||
photo_size_select_large e433
|
||||
photo_size_select_small e434
|
||||
picture_as_pdf e415
|
||||
picture_in_picture e8aa
|
||||
picture_in_picture_alt e911
|
||||
pie_chart e6c4
|
||||
pie_chart_outlined e6c5
|
||||
pin_drop e55e
|
||||
place e55f
|
||||
play_arrow e037
|
||||
play_circle_filled e038
|
||||
play_circle_outline e039
|
||||
play_for_work e906
|
||||
playlist_add e03b
|
||||
playlist_add_check e065
|
||||
playlist_play e05f
|
||||
plus_one e800
|
||||
poll e801
|
||||
polymer e8ab
|
||||
pool eb48
|
||||
portable_wifi_off e0ce
|
||||
portrait e416
|
||||
power e63c
|
||||
power_input e336
|
||||
power_settings_new e8ac
|
||||
pregnant_woman e91e
|
||||
present_to_all e0df
|
||||
print e8ad
|
||||
priority_high e645
|
||||
public e80b
|
||||
publish e255
|
||||
query_builder e8ae
|
||||
question_answer e8af
|
||||
queue e03c
|
||||
queue_music e03d
|
||||
queue_play_next e066
|
||||
radio e03e
|
||||
radio_button_checked e837
|
||||
radio_button_unchecked e836
|
||||
rate_review e560
|
||||
receipt e8b0
|
||||
recent_actors e03f
|
||||
record_voice_over e91f
|
||||
redeem e8b1
|
||||
redo e15a
|
||||
refresh e5d5
|
||||
remove e15b
|
||||
remove_circle e15c
|
||||
remove_circle_outline e15d
|
||||
remove_from_queue e067
|
||||
remove_red_eye e417
|
||||
remove_shopping_cart e928
|
||||
reorder e8fe
|
||||
repeat e040
|
||||
repeat_one e041
|
||||
replay e042
|
||||
replay_10 e059
|
||||
replay_30 e05a
|
||||
replay_5 e05b
|
||||
reply e15e
|
||||
reply_all e15f
|
||||
report e160
|
||||
report_problem e8b2
|
||||
restaurant e56c
|
||||
restaurant_menu e561
|
||||
restore e8b3
|
||||
restore_page e929
|
||||
ring_volume e0d1
|
||||
room e8b4
|
||||
room_service eb49
|
||||
rotate_90_degrees_ccw e418
|
||||
rotate_left e419
|
||||
rotate_right e41a
|
||||
rounded_corner e920
|
||||
router e328
|
||||
rowing e921
|
||||
rss_feed e0e5
|
||||
rv_hookup e642
|
||||
satellite e562
|
||||
save e161
|
||||
scanner e329
|
||||
schedule e8b5
|
||||
school e80c
|
||||
screen_lock_landscape e1be
|
||||
screen_lock_portrait e1bf
|
||||
screen_lock_rotation e1c0
|
||||
screen_rotation e1c1
|
||||
screen_share e0e2
|
||||
sd_card e623
|
||||
sd_storage e1c2
|
||||
search e8b6
|
||||
security e32a
|
||||
select_all e162
|
||||
send e163
|
||||
sentiment_dissatisfied e811
|
||||
sentiment_neutral e812
|
||||
sentiment_satisfied e813
|
||||
sentiment_very_dissatisfied e814
|
||||
sentiment_very_satisfied e815
|
||||
settings e8b8
|
||||
settings_applications e8b9
|
||||
settings_backup_restore e8ba
|
||||
settings_bluetooth e8bb
|
||||
settings_brightness e8bd
|
||||
settings_cell e8bc
|
||||
settings_ethernet e8be
|
||||
settings_input_antenna e8bf
|
||||
settings_input_component e8c0
|
||||
settings_input_composite e8c1
|
||||
settings_input_hdmi e8c2
|
||||
settings_input_svideo e8c3
|
||||
settings_overscan e8c4
|
||||
settings_phone e8c5
|
||||
settings_power e8c6
|
||||
settings_remote e8c7
|
||||
settings_system_daydream e1c3
|
||||
settings_voice e8c8
|
||||
share e80d
|
||||
shop e8c9
|
||||
shop_two e8ca
|
||||
shopping_basket e8cb
|
||||
shopping_cart e8cc
|
||||
short_text e261
|
||||
show_chart e6e1
|
||||
shuffle e043
|
||||
signal_cellular_4_bar e1c8
|
||||
signal_cellular_connected_no_internet_4_bar e1cd
|
||||
signal_cellular_no_sim e1ce
|
||||
signal_cellular_null e1cf
|
||||
signal_cellular_off e1d0
|
||||
signal_wifi_4_bar e1d8
|
||||
signal_wifi_4_bar_lock e1d9
|
||||
signal_wifi_off e1da
|
||||
sim_card e32b
|
||||
sim_card_alert e624
|
||||
skip_next e044
|
||||
skip_previous e045
|
||||
slideshow e41b
|
||||
slow_motion_video e068
|
||||
smartphone e32c
|
||||
smoke_free eb4a
|
||||
smoking_rooms eb4b
|
||||
sms e625
|
||||
sms_failed e626
|
||||
snooze e046
|
||||
sort e164
|
||||
sort_by_alpha e053
|
||||
spa eb4c
|
||||
space_bar e256
|
||||
speaker e32d
|
||||
speaker_group e32e
|
||||
speaker_notes e8cd
|
||||
speaker_notes_off e92a
|
||||
speaker_phone e0d2
|
||||
spellcheck e8ce
|
||||
star e838
|
||||
star_border e83a
|
||||
star_half e839
|
||||
stars e8d0
|
||||
stay_current_landscape e0d3
|
||||
stay_current_portrait e0d4
|
||||
stay_primary_landscape e0d5
|
||||
stay_primary_portrait e0d6
|
||||
stop e047
|
||||
stop_screen_share e0e3
|
||||
storage e1db
|
||||
store e8d1
|
||||
store_mall_directory e563
|
||||
straighten e41c
|
||||
streetview e56e
|
||||
strikethrough_s e257
|
||||
style e41d
|
||||
subdirectory_arrow_left e5d9
|
||||
subdirectory_arrow_right e5da
|
||||
subject e8d2
|
||||
subscriptions e064
|
||||
subtitles e048
|
||||
subway e56f
|
||||
supervisor_account e8d3
|
||||
surround_sound e049
|
||||
swap_calls e0d7
|
||||
swap_horiz e8d4
|
||||
swap_vert e8d5
|
||||
swap_vertical_circle e8d6
|
||||
switch_camera e41e
|
||||
switch_video e41f
|
||||
sync e627
|
||||
sync_disabled e628
|
||||
sync_problem e629
|
||||
system_update e62a
|
||||
system_update_alt e8d7
|
||||
tab e8d8
|
||||
tab_unselected e8d9
|
||||
tablet e32f
|
||||
tablet_android e330
|
||||
tablet_mac e331
|
||||
tag_faces e420
|
||||
tap_and_play e62b
|
||||
terrain e564
|
||||
text_fields e262
|
||||
text_format e165
|
||||
textsms e0d8
|
||||
texture e421
|
||||
theaters e8da
|
||||
thumb_down e8db
|
||||
thumb_up e8dc
|
||||
thumbs_up_down e8dd
|
||||
time_to_leave e62c
|
||||
timelapse e422
|
||||
timeline e922
|
||||
timer e425
|
||||
timer_10 e423
|
||||
timer_3 e424
|
||||
timer_off e426
|
||||
title e264
|
||||
toc e8de
|
||||
today e8df
|
||||
toll e8e0
|
||||
tonality e427
|
||||
touch_app e913
|
||||
toys e332
|
||||
track_changes e8e1
|
||||
traffic e565
|
||||
train e570
|
||||
tram e571
|
||||
transfer_within_a_station e572
|
||||
transform e428
|
||||
translate e8e2
|
||||
trending_down e8e3
|
||||
trending_flat e8e4
|
||||
trending_up e8e5
|
||||
tune e429
|
||||
turned_in e8e6
|
||||
turned_in_not e8e7
|
||||
tv e333
|
||||
unarchive e169
|
||||
undo e166
|
||||
unfold_less e5d6
|
||||
unfold_more e5d7
|
||||
update e923
|
||||
usb e1e0
|
||||
verified_user e8e8
|
||||
vertical_align_bottom e258
|
||||
vertical_align_center e259
|
||||
vertical_align_top e25a
|
||||
vibration e62d
|
||||
video_call e070
|
||||
video_label e071
|
||||
video_library e04a
|
||||
videocam e04b
|
||||
videocam_off e04c
|
||||
videogame_asset e338
|
||||
view_agenda e8e9
|
||||
view_array e8ea
|
||||
view_carousel e8eb
|
||||
view_column e8ec
|
||||
view_comfy e42a
|
||||
view_compact e42b
|
||||
view_day e8ed
|
||||
view_headline e8ee
|
||||
view_list e8ef
|
||||
view_module e8f0
|
||||
view_quilt e8f1
|
||||
view_stream e8f2
|
||||
view_week e8f3
|
||||
vignette e435
|
||||
visibility e8f4
|
||||
visibility_off e8f5
|
||||
voice_chat e62e
|
||||
voicemail e0d9
|
||||
volume_down e04d
|
||||
volume_mute e04e
|
||||
volume_off e04f
|
||||
volume_up e050
|
||||
vpn_key e0da
|
||||
vpn_lock e62f
|
||||
wallpaper e1bc
|
||||
warning e002
|
||||
watch e334
|
||||
watch_later e924
|
||||
wb_auto e42c
|
||||
wb_cloudy e42d
|
||||
wb_incandescent e42e
|
||||
wb_iridescent e436
|
||||
wb_sunny e430
|
||||
wc e63d
|
||||
web e051
|
||||
web_asset e069
|
||||
weekend e16b
|
||||
whatshot e80e
|
||||
widgets e1bd
|
||||
wifi e63e
|
||||
wifi_lock e1e1
|
||||
wifi_tethering e1e2
|
||||
work e8f9
|
||||
wrap_text e25b
|
||||
youtube_searched_for e8fa
|
||||
zoom_in e8ff
|
||||
zoom_out e900
|
||||
zoom_out_map e56b
|
||||
@@ -0,0 +1,36 @@
|
||||
@font-face {
|
||||
font-family: 'Material Icons';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
src: url(MaterialIcons-Regular.eot); /* For IE6-8 */
|
||||
src: local('Material Icons'),
|
||||
local('MaterialIcons-Regular'),
|
||||
url(MaterialIcons-Regular.woff2) format('woff2'),
|
||||
url(MaterialIcons-Regular.woff) format('woff'),
|
||||
url(MaterialIcons-Regular.ttf) format('truetype');
|
||||
}
|
||||
|
||||
.material-icons {
|
||||
font-family: 'Material Icons';
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
font-size: 24px; /* Preferred icon size */
|
||||
display: inline-block;
|
||||
line-height: 1;
|
||||
text-transform: none;
|
||||
letter-spacing: normal;
|
||||
word-wrap: normal;
|
||||
white-space: nowrap;
|
||||
direction: ltr;
|
||||
|
||||
/* Support for all WebKit browsers. */
|
||||
-webkit-font-smoothing: antialiased;
|
||||
/* Support for Safari and Chrome. */
|
||||
text-rendering: optimizeLegibility;
|
||||
|
||||
/* Support for Firefox. */
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
|
||||
/* Support for IE. */
|
||||
font-feature-settings: 'liga';
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
@font-face {
|
||||
font-family: 'Material Icons';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
src: url(iconfont/MaterialIcons-Regular.eot); /* For IE6-8 */
|
||||
src: local('Material Icons'),
|
||||
local('MaterialIcons-Regular'),
|
||||
url(iconfont/MaterialIcons-Regular.woff2) format('woff2'),
|
||||
url(iconfont/MaterialIcons-Regular.woff) format('woff'),
|
||||
url(iconfont/MaterialIcons-Regular.ttf) format('truetype');
|
||||
}
|
||||
|
||||
.material-icons {
|
||||
font-family: 'Material Icons';
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
font-size: 24px; /* Preferred icon size */
|
||||
display: inline-block;
|
||||
line-height: 1;
|
||||
text-transform: none;
|
||||
letter-spacing: normal;
|
||||
word-wrap: normal;
|
||||
white-space: nowrap;
|
||||
direction: ltr;
|
||||
|
||||
/* Support for all WebKit browsers. */
|
||||
-webkit-font-smoothing: antialiased;
|
||||
/* Support for Safari and Chrome. */
|
||||
text-rendering: optimizeLegibility;
|
||||
|
||||
/* Support for Firefox. */
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
|
||||
/* Support for IE. */
|
||||
font-feature-settings: 'liga';
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
(function($){
|
||||
$(function(){
|
||||
|
||||
$('.sidenav').sidenav();
|
||||
|
||||
}); // end of document ready
|
||||
})(jQuery); // end of jQuery name space
|
||||
@@ -0,0 +1,28 @@
|
||||
(function(r){r.fn.qrcode=function(h){var s;function u(a){this.mode=s;this.data=a}function o(a,c){this.typeNumber=a;this.errorCorrectLevel=c;this.modules=null;this.moduleCount=0;this.dataCache=null;this.dataList=[]}function q(a,c){if(void 0==a.length)throw Error(a.length+"/"+c);for(var d=0;d<a.length&&0==a[d];)d++;this.num=Array(a.length-d+c);for(var b=0;b<a.length-d;b++)this.num[b]=a[b+d]}function p(a,c){this.totalCount=a;this.dataCount=c}function t(){this.buffer=[];this.length=0}u.prototype={getLength:function(){return this.data.length},
|
||||
write:function(a){for(var c=0;c<this.data.length;c++)a.put(this.data.charCodeAt(c),8)}};o.prototype={addData:function(a){this.dataList.push(new u(a));this.dataCache=null},isDark:function(a,c){if(0>a||this.moduleCount<=a||0>c||this.moduleCount<=c)throw Error(a+","+c);return this.modules[a][c]},getModuleCount:function(){return this.moduleCount},make:function(){if(1>this.typeNumber){for(var a=1,a=1;40>a;a++){for(var c=p.getRSBlocks(a,this.errorCorrectLevel),d=new t,b=0,e=0;e<c.length;e++)b+=c[e].dataCount;
|
||||
for(e=0;e<this.dataList.length;e++)c=this.dataList[e],d.put(c.mode,4),d.put(c.getLength(),j.getLengthInBits(c.mode,a)),c.write(d);if(d.getLengthInBits()<=8*b)break}this.typeNumber=a}this.makeImpl(!1,this.getBestMaskPattern())},makeImpl:function(a,c){this.moduleCount=4*this.typeNumber+17;this.modules=Array(this.moduleCount);for(var d=0;d<this.moduleCount;d++){this.modules[d]=Array(this.moduleCount);for(var b=0;b<this.moduleCount;b++)this.modules[d][b]=null}this.setupPositionProbePattern(0,0);this.setupPositionProbePattern(this.moduleCount-
|
||||
7,0);this.setupPositionProbePattern(0,this.moduleCount-7);this.setupPositionAdjustPattern();this.setupTimingPattern();this.setupTypeInfo(a,c);7<=this.typeNumber&&this.setupTypeNumber(a);null==this.dataCache&&(this.dataCache=o.createData(this.typeNumber,this.errorCorrectLevel,this.dataList));this.mapData(this.dataCache,c)},setupPositionProbePattern:function(a,c){for(var d=-1;7>=d;d++)if(!(-1>=a+d||this.moduleCount<=a+d))for(var b=-1;7>=b;b++)-1>=c+b||this.moduleCount<=c+b||(this.modules[a+d][c+b]=
|
||||
0<=d&&6>=d&&(0==b||6==b)||0<=b&&6>=b&&(0==d||6==d)||2<=d&&4>=d&&2<=b&&4>=b?!0:!1)},getBestMaskPattern:function(){for(var a=0,c=0,d=0;8>d;d++){this.makeImpl(!0,d);var b=j.getLostPoint(this);if(0==d||a>b)a=b,c=d}return c},createMovieClip:function(a,c,d){a=a.createEmptyMovieClip(c,d);this.make();for(c=0;c<this.modules.length;c++)for(var d=1*c,b=0;b<this.modules[c].length;b++){var e=1*b;this.modules[c][b]&&(a.beginFill(0,100),a.moveTo(e,d),a.lineTo(e+1,d),a.lineTo(e+1,d+1),a.lineTo(e,d+1),a.endFill())}return a},
|
||||
setupTimingPattern:function(){for(var a=8;a<this.moduleCount-8;a++)null==this.modules[a][6]&&(this.modules[a][6]=0==a%2);for(a=8;a<this.moduleCount-8;a++)null==this.modules[6][a]&&(this.modules[6][a]=0==a%2)},setupPositionAdjustPattern:function(){for(var a=j.getPatternPosition(this.typeNumber),c=0;c<a.length;c++)for(var d=0;d<a.length;d++){var b=a[c],e=a[d];if(null==this.modules[b][e])for(var f=-2;2>=f;f++)for(var i=-2;2>=i;i++)this.modules[b+f][e+i]=-2==f||2==f||-2==i||2==i||0==f&&0==i?!0:!1}},setupTypeNumber:function(a){for(var c=
|
||||
j.getBCHTypeNumber(this.typeNumber),d=0;18>d;d++){var b=!a&&1==(c>>d&1);this.modules[Math.floor(d/3)][d%3+this.moduleCount-8-3]=b}for(d=0;18>d;d++)b=!a&&1==(c>>d&1),this.modules[d%3+this.moduleCount-8-3][Math.floor(d/3)]=b},setupTypeInfo:function(a,c){for(var d=j.getBCHTypeInfo(this.errorCorrectLevel<<3|c),b=0;15>b;b++){var e=!a&&1==(d>>b&1);6>b?this.modules[b][8]=e:8>b?this.modules[b+1][8]=e:this.modules[this.moduleCount-15+b][8]=e}for(b=0;15>b;b++)e=!a&&1==(d>>b&1),8>b?this.modules[8][this.moduleCount-
|
||||
b-1]=e:9>b?this.modules[8][15-b-1+1]=e:this.modules[8][15-b-1]=e;this.modules[this.moduleCount-8][8]=!a},mapData:function(a,c){for(var d=-1,b=this.moduleCount-1,e=7,f=0,i=this.moduleCount-1;0<i;i-=2)for(6==i&&i--;;){for(var g=0;2>g;g++)if(null==this.modules[b][i-g]){var n=!1;f<a.length&&(n=1==(a[f]>>>e&1));j.getMask(c,b,i-g)&&(n=!n);this.modules[b][i-g]=n;e--; -1==e&&(f++,e=7)}b+=d;if(0>b||this.moduleCount<=b){b-=d;d=-d;break}}}};o.PAD0=236;o.PAD1=17;o.createData=function(a,c,d){for(var c=p.getRSBlocks(a,
|
||||
c),b=new t,e=0;e<d.length;e++){var f=d[e];b.put(f.mode,4);b.put(f.getLength(),j.getLengthInBits(f.mode,a));f.write(b)}for(e=a=0;e<c.length;e++)a+=c[e].dataCount;if(b.getLengthInBits()>8*a)throw Error("code length overflow. ("+b.getLengthInBits()+">"+8*a+")");for(b.getLengthInBits()+4<=8*a&&b.put(0,4);0!=b.getLengthInBits()%8;)b.putBit(!1);for(;!(b.getLengthInBits()>=8*a);){b.put(o.PAD0,8);if(b.getLengthInBits()>=8*a)break;b.put(o.PAD1,8)}return o.createBytes(b,c)};o.createBytes=function(a,c){for(var d=
|
||||
0,b=0,e=0,f=Array(c.length),i=Array(c.length),g=0;g<c.length;g++){var n=c[g].dataCount,h=c[g].totalCount-n,b=Math.max(b,n),e=Math.max(e,h);f[g]=Array(n);for(var k=0;k<f[g].length;k++)f[g][k]=255&a.buffer[k+d];d+=n;k=j.getErrorCorrectPolynomial(h);n=(new q(f[g],k.getLength()-1)).mod(k);i[g]=Array(k.getLength()-1);for(k=0;k<i[g].length;k++)h=k+n.getLength()-i[g].length,i[g][k]=0<=h?n.get(h):0}for(k=g=0;k<c.length;k++)g+=c[k].totalCount;d=Array(g);for(k=n=0;k<b;k++)for(g=0;g<c.length;g++)k<f[g].length&&
|
||||
(d[n++]=f[g][k]);for(k=0;k<e;k++)for(g=0;g<c.length;g++)k<i[g].length&&(d[n++]=i[g][k]);return d};s=4;for(var j={PATTERN_POSITION_TABLE:[[],[6,18],[6,22],[6,26],[6,30],[6,34],[6,22,38],[6,24,42],[6,26,46],[6,28,50],[6,30,54],[6,32,58],[6,34,62],[6,26,46,66],[6,26,48,70],[6,26,50,74],[6,30,54,78],[6,30,56,82],[6,30,58,86],[6,34,62,90],[6,28,50,72,94],[6,26,50,74,98],[6,30,54,78,102],[6,28,54,80,106],[6,32,58,84,110],[6,30,58,86,114],[6,34,62,90,118],[6,26,50,74,98,122],[6,30,54,78,102,126],[6,26,52,
|
||||
78,104,130],[6,30,56,82,108,134],[6,34,60,86,112,138],[6,30,58,86,114,142],[6,34,62,90,118,146],[6,30,54,78,102,126,150],[6,24,50,76,102,128,154],[6,28,54,80,106,132,158],[6,32,58,84,110,136,162],[6,26,54,82,110,138,166],[6,30,58,86,114,142,170]],G15:1335,G18:7973,G15_MASK:21522,getBCHTypeInfo:function(a){for(var c=a<<10;0<=j.getBCHDigit(c)-j.getBCHDigit(j.G15);)c^=j.G15<<j.getBCHDigit(c)-j.getBCHDigit(j.G15);return(a<<10|c)^j.G15_MASK},getBCHTypeNumber:function(a){for(var c=a<<12;0<=j.getBCHDigit(c)-
|
||||
j.getBCHDigit(j.G18);)c^=j.G18<<j.getBCHDigit(c)-j.getBCHDigit(j.G18);return a<<12|c},getBCHDigit:function(a){for(var c=0;0!=a;)c++,a>>>=1;return c},getPatternPosition:function(a){return j.PATTERN_POSITION_TABLE[a-1]},getMask:function(a,c,d){switch(a){case 0:return 0==(c+d)%2;case 1:return 0==c%2;case 2:return 0==d%3;case 3:return 0==(c+d)%3;case 4:return 0==(Math.floor(c/2)+Math.floor(d/3))%2;case 5:return 0==c*d%2+c*d%3;case 6:return 0==(c*d%2+c*d%3)%2;case 7:return 0==(c*d%3+(c+d)%2)%2;default:throw Error("bad maskPattern:"+
|
||||
a);}},getErrorCorrectPolynomial:function(a){for(var c=new q([1],0),d=0;d<a;d++)c=c.multiply(new q([1,l.gexp(d)],0));return c},getLengthInBits:function(a,c){if(1<=c&&10>c)switch(a){case 1:return 10;case 2:return 9;case s:return 8;case 8:return 8;default:throw Error("mode:"+a);}else if(27>c)switch(a){case 1:return 12;case 2:return 11;case s:return 16;case 8:return 10;default:throw Error("mode:"+a);}else if(41>c)switch(a){case 1:return 14;case 2:return 13;case s:return 16;case 8:return 12;default:throw Error("mode:"+
|
||||
a);}else throw Error("type:"+c);},getLostPoint:function(a){for(var c=a.getModuleCount(),d=0,b=0;b<c;b++)for(var e=0;e<c;e++){for(var f=0,i=a.isDark(b,e),g=-1;1>=g;g++)if(!(0>b+g||c<=b+g))for(var h=-1;1>=h;h++)0>e+h||c<=e+h||0==g&&0==h||i==a.isDark(b+g,e+h)&&f++;5<f&&(d+=3+f-5)}for(b=0;b<c-1;b++)for(e=0;e<c-1;e++)if(f=0,a.isDark(b,e)&&f++,a.isDark(b+1,e)&&f++,a.isDark(b,e+1)&&f++,a.isDark(b+1,e+1)&&f++,0==f||4==f)d+=3;for(b=0;b<c;b++)for(e=0;e<c-6;e++)a.isDark(b,e)&&!a.isDark(b,e+1)&&a.isDark(b,e+
|
||||
2)&&a.isDark(b,e+3)&&a.isDark(b,e+4)&&!a.isDark(b,e+5)&&a.isDark(b,e+6)&&(d+=40);for(e=0;e<c;e++)for(b=0;b<c-6;b++)a.isDark(b,e)&&!a.isDark(b+1,e)&&a.isDark(b+2,e)&&a.isDark(b+3,e)&&a.isDark(b+4,e)&&!a.isDark(b+5,e)&&a.isDark(b+6,e)&&(d+=40);for(e=f=0;e<c;e++)for(b=0;b<c;b++)a.isDark(b,e)&&f++;a=Math.abs(100*f/c/c-50)/5;return d+10*a}},l={glog:function(a){if(1>a)throw Error("glog("+a+")");return l.LOG_TABLE[a]},gexp:function(a){for(;0>a;)a+=255;for(;256<=a;)a-=255;return l.EXP_TABLE[a]},EXP_TABLE:Array(256),
|
||||
LOG_TABLE:Array(256)},m=0;8>m;m++)l.EXP_TABLE[m]=1<<m;for(m=8;256>m;m++)l.EXP_TABLE[m]=l.EXP_TABLE[m-4]^l.EXP_TABLE[m-5]^l.EXP_TABLE[m-6]^l.EXP_TABLE[m-8];for(m=0;255>m;m++)l.LOG_TABLE[l.EXP_TABLE[m]]=m;q.prototype={get:function(a){return this.num[a]},getLength:function(){return this.num.length},multiply:function(a){for(var c=Array(this.getLength()+a.getLength()-1),d=0;d<this.getLength();d++)for(var b=0;b<a.getLength();b++)c[d+b]^=l.gexp(l.glog(this.get(d))+l.glog(a.get(b)));return new q(c,0)},mod:function(a){if(0>
|
||||
this.getLength()-a.getLength())return this;for(var c=l.glog(this.get(0))-l.glog(a.get(0)),d=Array(this.getLength()),b=0;b<this.getLength();b++)d[b]=this.get(b);for(b=0;b<a.getLength();b++)d[b]^=l.gexp(l.glog(a.get(b))+c);return(new q(d,0)).mod(a)}};p.RS_BLOCK_TABLE=[[1,26,19],[1,26,16],[1,26,13],[1,26,9],[1,44,34],[1,44,28],[1,44,22],[1,44,16],[1,70,55],[1,70,44],[2,35,17],[2,35,13],[1,100,80],[2,50,32],[2,50,24],[4,25,9],[1,134,108],[2,67,43],[2,33,15,2,34,16],[2,33,11,2,34,12],[2,86,68],[4,43,27],
|
||||
[4,43,19],[4,43,15],[2,98,78],[4,49,31],[2,32,14,4,33,15],[4,39,13,1,40,14],[2,121,97],[2,60,38,2,61,39],[4,40,18,2,41,19],[4,40,14,2,41,15],[2,146,116],[3,58,36,2,59,37],[4,36,16,4,37,17],[4,36,12,4,37,13],[2,86,68,2,87,69],[4,69,43,1,70,44],[6,43,19,2,44,20],[6,43,15,2,44,16],[4,101,81],[1,80,50,4,81,51],[4,50,22,4,51,23],[3,36,12,8,37,13],[2,116,92,2,117,93],[6,58,36,2,59,37],[4,46,20,6,47,21],[7,42,14,4,43,15],[4,133,107],[8,59,37,1,60,38],[8,44,20,4,45,21],[12,33,11,4,34,12],[3,145,115,1,146,
|
||||
116],[4,64,40,5,65,41],[11,36,16,5,37,17],[11,36,12,5,37,13],[5,109,87,1,110,88],[5,65,41,5,66,42],[5,54,24,7,55,25],[11,36,12],[5,122,98,1,123,99],[7,73,45,3,74,46],[15,43,19,2,44,20],[3,45,15,13,46,16],[1,135,107,5,136,108],[10,74,46,1,75,47],[1,50,22,15,51,23],[2,42,14,17,43,15],[5,150,120,1,151,121],[9,69,43,4,70,44],[17,50,22,1,51,23],[2,42,14,19,43,15],[3,141,113,4,142,114],[3,70,44,11,71,45],[17,47,21,4,48,22],[9,39,13,16,40,14],[3,135,107,5,136,108],[3,67,41,13,68,42],[15,54,24,5,55,25],[15,
|
||||
43,15,10,44,16],[4,144,116,4,145,117],[17,68,42],[17,50,22,6,51,23],[19,46,16,6,47,17],[2,139,111,7,140,112],[17,74,46],[7,54,24,16,55,25],[34,37,13],[4,151,121,5,152,122],[4,75,47,14,76,48],[11,54,24,14,55,25],[16,45,15,14,46,16],[6,147,117,4,148,118],[6,73,45,14,74,46],[11,54,24,16,55,25],[30,46,16,2,47,17],[8,132,106,4,133,107],[8,75,47,13,76,48],[7,54,24,22,55,25],[22,45,15,13,46,16],[10,142,114,2,143,115],[19,74,46,4,75,47],[28,50,22,6,51,23],[33,46,16,4,47,17],[8,152,122,4,153,123],[22,73,45,
|
||||
3,74,46],[8,53,23,26,54,24],[12,45,15,28,46,16],[3,147,117,10,148,118],[3,73,45,23,74,46],[4,54,24,31,55,25],[11,45,15,31,46,16],[7,146,116,7,147,117],[21,73,45,7,74,46],[1,53,23,37,54,24],[19,45,15,26,46,16],[5,145,115,10,146,116],[19,75,47,10,76,48],[15,54,24,25,55,25],[23,45,15,25,46,16],[13,145,115,3,146,116],[2,74,46,29,75,47],[42,54,24,1,55,25],[23,45,15,28,46,16],[17,145,115],[10,74,46,23,75,47],[10,54,24,35,55,25],[19,45,15,35,46,16],[17,145,115,1,146,116],[14,74,46,21,75,47],[29,54,24,19,
|
||||
55,25],[11,45,15,46,46,16],[13,145,115,6,146,116],[14,74,46,23,75,47],[44,54,24,7,55,25],[59,46,16,1,47,17],[12,151,121,7,152,122],[12,75,47,26,76,48],[39,54,24,14,55,25],[22,45,15,41,46,16],[6,151,121,14,152,122],[6,75,47,34,76,48],[46,54,24,10,55,25],[2,45,15,64,46,16],[17,152,122,4,153,123],[29,74,46,14,75,47],[49,54,24,10,55,25],[24,45,15,46,46,16],[4,152,122,18,153,123],[13,74,46,32,75,47],[48,54,24,14,55,25],[42,45,15,32,46,16],[20,147,117,4,148,118],[40,75,47,7,76,48],[43,54,24,22,55,25],[10,
|
||||
45,15,67,46,16],[19,148,118,6,149,119],[18,75,47,31,76,48],[34,54,24,34,55,25],[20,45,15,61,46,16]];p.getRSBlocks=function(a,c){var d=p.getRsBlockTable(a,c);if(void 0==d)throw Error("bad rs block @ typeNumber:"+a+"/errorCorrectLevel:"+c);for(var b=d.length/3,e=[],f=0;f<b;f++)for(var h=d[3*f+0],g=d[3*f+1],j=d[3*f+2],l=0;l<h;l++)e.push(new p(g,j));return e};p.getRsBlockTable=function(a,c){switch(c){case 1:return p.RS_BLOCK_TABLE[4*(a-1)+0];case 0:return p.RS_BLOCK_TABLE[4*(a-1)+1];case 3:return p.RS_BLOCK_TABLE[4*
|
||||
(a-1)+2];case 2:return p.RS_BLOCK_TABLE[4*(a-1)+3]}};t.prototype={get:function(a){return 1==(this.buffer[Math.floor(a/8)]>>>7-a%8&1)},put:function(a,c){for(var d=0;d<c;d++)this.putBit(1==(a>>>c-d-1&1))},getLengthInBits:function(){return this.length},putBit:function(a){var c=Math.floor(this.length/8);this.buffer.length<=c&&this.buffer.push(0);a&&(this.buffer[c]|=128>>>this.length%8);this.length++}};"string"===typeof h&&(h={text:h});h=r.extend({},{render:"canvas",width:256,height:256,typeNumber:-1,
|
||||
correctLevel:2,background:"#ffffff",foreground:"#000000"},h);return this.each(function(){var a;if("canvas"==h.render){a=new o(h.typeNumber,h.correctLevel);a.addData(h.text);a.make();var c=document.createElement("canvas");c.width=h.width;c.height=h.height;for(var d=c.getContext("2d"),b=h.width/a.getModuleCount(),e=h.height/a.getModuleCount(),f=0;f<a.getModuleCount();f++)for(var i=0;i<a.getModuleCount();i++){d.fillStyle=a.isDark(f,i)?h.foreground:h.background;var g=Math.ceil((i+1)*b)-Math.floor(i*b),
|
||||
j=Math.ceil((f+1)*b)-Math.floor(f*b);d.fillRect(Math.round(i*b),Math.round(f*e),g,j)}}else{a=new o(h.typeNumber,h.correctLevel);a.addData(h.text);a.make();c=r("<table></table>").css("width",h.width+"px").css("height",h.height+"px").css("border","0px").css("border-collapse","collapse").css("background-color",h.background);d=h.width/a.getModuleCount();b=h.height/a.getModuleCount();for(e=0;e<a.getModuleCount();e++){f=r("<tr></tr>").css("height",b+"px").appendTo(c);for(i=0;i<a.getModuleCount();i++)r("<td></td>").css("width",
|
||||
d+"px").css("background-color",a.isDark(e,i)?h.foreground:h.background).appendTo(f)}}a=c;jQuery(a).appendTo(this)})}})(jQuery);
|
||||
@@ -0,0 +1,894 @@
|
||||
tooltipBackedUp = "This backup has been uploaded to Google Drive."
|
||||
tooltipDriveOnly = "This backup is only in Google Drive. Select \"Upload\" from the actions menu to Upload it to Home Assistant."
|
||||
tooltipHassio = "This backup is only in Home Assistant. Change the number of backups you keep in Drive to get it to upload."
|
||||
tooltipWaiting = "This backup is waiting to upload to Google Drive."
|
||||
tooltipLoading = "This backup is being downloaded from Google Drive to Home Assistant. Soon it will be available to restore."
|
||||
tooltipPending = "This backup is being created. If it takes a long time, see the addon's FAQ on GitHub"
|
||||
tooltipUploading = "This backup is being uploaded to Google Drive."
|
||||
|
||||
var github_bug_desc = `
|
||||
Please add some information about your configuration and the problem you ran into here.
|
||||
More info really helps speed up debugging, if you don't even read this and help me understand what happened, I probably won't help you.
|
||||
Remember that its just one guy back here doing all of this.
|
||||
If english isn't your first language, don't sweat it. Just try to be clear and I'll do the same for you. Some things you might consider including:
|
||||
* What were you doing when the problem happened?
|
||||
* A screenshot if its something visual.
|
||||
* What configuration options are you using with the add-on?
|
||||
* What logs is the add-on printing out? You can see the detailed logs by clicking "Logs" at the right of the web-UI.
|
||||
* Are there any problematic looking logs from the supervisor? You can get to them from the Home Assistant Interface from "Supervisor" > "System" > "System Log"
|
||||
\n\n`;
|
||||
var name_keys = {}
|
||||
|
||||
function toggleSlide(checkbox, target) {
|
||||
if ($(checkbox).is(':checked')) {
|
||||
$('#' + target).slideDown(400);
|
||||
} else {
|
||||
$('#' + target).slideUp(400);
|
||||
}
|
||||
}
|
||||
|
||||
function toggleLinkSlide(checkbox, target) {
|
||||
target = $('#' + target);
|
||||
if (target.is(":visible")) {
|
||||
target.slideUp(400);
|
||||
} else {
|
||||
target.slideDown(400);
|
||||
}
|
||||
}
|
||||
|
||||
function sourceToName(source) {
|
||||
if (source == "GoogleDrive") {
|
||||
return "Google Drive";
|
||||
} else if (source == "HomeAssistant") {
|
||||
return "Home Assistant";
|
||||
} else {
|
||||
return "Unknown";
|
||||
}
|
||||
}
|
||||
|
||||
function restoreClick(target) {
|
||||
$('#restore_help_card').fadeIn(500);
|
||||
//window.top.location.replace($(target).data('url'))
|
||||
}
|
||||
|
||||
function setInputValue(id, value) {
|
||||
if (value == null) {
|
||||
// Leave at default
|
||||
return;
|
||||
}
|
||||
if (typeof (value) == 'boolean') {
|
||||
$('#' + id).prop('checked', value);
|
||||
} else {
|
||||
$('#' + id).val(value);
|
||||
}
|
||||
}
|
||||
|
||||
function test(target) {
|
||||
console.log(target);
|
||||
}
|
||||
|
||||
function downloadBackup(target) {
|
||||
window.location.assign('download?slug=' + encodeURIComponent($(target).data('backup').slug));
|
||||
}
|
||||
|
||||
function uploadBackup(target) {
|
||||
var slug = $(target).data('backup').slug;
|
||||
var name = $(target).data('backup').name;
|
||||
$("#do_upload_button").attr("onClick", "doUpload('" + slug + "', '" + name + "')");
|
||||
}
|
||||
|
||||
function doUpload(slug, name) {
|
||||
message = "Uploading '" + name + "'";
|
||||
url = "upload?slug=" + encodeURIComponent(slug);
|
||||
postJson(url, {}, refreshstats, null, message);
|
||||
}
|
||||
|
||||
function configureDetailBadge(name, text, show) {
|
||||
let container = $('.' + name);
|
||||
let span = $('.detail-name', container);
|
||||
if (show) {
|
||||
span.html(text);
|
||||
container.show();
|
||||
} else {
|
||||
container.hide();
|
||||
}
|
||||
}
|
||||
var SIZE_SI = ["B", "kB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"]
|
||||
function asSizeString(size) {
|
||||
current = size * 1.0;
|
||||
for (let id in SIZE_SI) {
|
||||
if (current < 1024) {
|
||||
return (Math.round(current * 10) / 10) + " " + SIZE_SI[id];
|
||||
}
|
||||
current /= 1024
|
||||
}
|
||||
return "Beyond mortal comprehension"
|
||||
}
|
||||
|
||||
function errorReports(send) {
|
||||
var jqxhr = $.get("errorreports?send=" + send)
|
||||
$('#error_reports_card').fadeOut(500)
|
||||
}
|
||||
hideIngress = false;
|
||||
function exposeServer(expose) {
|
||||
var url = "exposeserver?expose=" + expose;
|
||||
postJson(url, {}, function (data) {
|
||||
$('#ingress_upgrade_card').fadeOut(500);
|
||||
if (expose == "true") {
|
||||
refreshstats();
|
||||
} else {
|
||||
if (data.hasOwnProperty("redirect")) {
|
||||
// Reqirect to the url
|
||||
window.location.assign(data.redirect.replace("{host}", window.location.hostname))
|
||||
}
|
||||
}
|
||||
}, null, "Saving setting...");
|
||||
}
|
||||
|
||||
function callBackupSnapshot(doSwitch) {
|
||||
var url = "callbackupsnapshot?switch=" + doSwitch;
|
||||
postJson(url, {}, function (data) {
|
||||
$('#backup_upgrade_card').fadeOut(500);
|
||||
refreshstats();
|
||||
}, null, "Saving...");
|
||||
}
|
||||
|
||||
function dismissIgnoredBackupWarning(doSwitch) {
|
||||
var url = "ignoredbackupswitch?switch=" + doSwitch;
|
||||
postJson(url, {}, function (data) {
|
||||
$('#warn_upgrade_backups_card').fadeOut(500);
|
||||
refreshstats();
|
||||
}, null, "Saving settings...");
|
||||
}
|
||||
|
||||
function ackCheckIgnoredBackups() {
|
||||
postJson("ackignorecheck", {}, function (data) {
|
||||
$('#ignore_helper_card').fadeOut(500);
|
||||
}, null, "Acknowledging...");
|
||||
|
||||
}
|
||||
|
||||
function aknowledgeooboauth() {
|
||||
var url = "aknowledgeooboauth";
|
||||
postJson(url, {}, function (data) {
|
||||
$('#warn_creds_deprecated_card').fadeOut(500);
|
||||
refreshstats();
|
||||
}, null, "Acknowledging...");
|
||||
}
|
||||
|
||||
function resolvefolder(use_existing) {
|
||||
var url = "resolvefolder?use_existing=" + use_existing;
|
||||
postJson(url, {}, refreshstats, null, null);
|
||||
setErrorWatermark();
|
||||
$('#existing_backup_folder').hide();
|
||||
refreshstats();
|
||||
}
|
||||
|
||||
function allowImmediateBackup(use_existing) {
|
||||
var url = "ignorestartupcooldown";
|
||||
postJson("ignorestartupcooldown", {}, refreshstats, null, "Ignoring delay...");
|
||||
$('#backups_boot_waiting_card').hide();
|
||||
refreshstats();
|
||||
}
|
||||
|
||||
function sync(dialog_class) {
|
||||
postJsonCloseErrorDialog("startSync", dialog_class);
|
||||
}
|
||||
|
||||
function postJsonCloseErrorDialog(url, dialog_class) {
|
||||
postJson(url, {},
|
||||
function (data) {
|
||||
if (dialog_class) {
|
||||
// Hide the error dialog
|
||||
$("." + dialog_class).hide();
|
||||
setErrorWatermark();
|
||||
}
|
||||
refreshstats(data);
|
||||
}, null)
|
||||
}
|
||||
|
||||
function toast(text) {
|
||||
M.toast({ html: text });
|
||||
}
|
||||
|
||||
function htmlEntities(str) {
|
||||
return String(str).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"').replace("'", "'");
|
||||
}
|
||||
|
||||
function postJson(path, json, onSuccess, onFail = null, toastWhile = null) {
|
||||
var notification = null
|
||||
var returned = false
|
||||
if (toastWhile) {
|
||||
setTimeout(function () {
|
||||
if (!returned) {
|
||||
notification = M.toast({ html: toastWhile, displayLength: 99999999 });
|
||||
}
|
||||
}, 1000);
|
||||
}
|
||||
$.post({
|
||||
url: path,
|
||||
data: JSON.stringify(json),
|
||||
success: function (data) {
|
||||
returned = true;
|
||||
if (notification != null) {
|
||||
notification.dismiss();
|
||||
}
|
||||
if (data.hasOwnProperty("message")) {
|
||||
toast(data.message);
|
||||
}
|
||||
if (data.hasOwnProperty("reload_page") && data.reload_page) {
|
||||
// Reload the page
|
||||
window.location.assign(getInnerHomeUri());
|
||||
}
|
||||
if (onSuccess) {
|
||||
onSuccess(data);
|
||||
}
|
||||
},
|
||||
dataType: "json",
|
||||
contentType: 'application/json'
|
||||
}).fail(
|
||||
function (e) {
|
||||
returned = true;
|
||||
if (notification != null) {
|
||||
notification.dismiss();
|
||||
}
|
||||
var info = parseErrorInfo(e);
|
||||
if (onFail) {
|
||||
onFail(info);
|
||||
}
|
||||
button_text = " <a class='btn-flat' target='_blank' onClick=\"$('#error_details_card').fadeIn(400);return false;\">Details</a>"
|
||||
$('#error_details_paragraph').text(info.details);
|
||||
|
||||
M.toast({ html: info.message + button_text, displayLength: 10000 });
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
function parseErrorInfo(e) {
|
||||
if (e.hasOwnProperty("message") && e.hasOwnProperty("details")) {
|
||||
return {
|
||||
message: e.message,
|
||||
details: e.details
|
||||
}
|
||||
}
|
||||
|
||||
if (e.hasOwnProperty("responseText")) {
|
||||
try {
|
||||
return JSON.parse(e.responseText)
|
||||
} catch (err) {
|
||||
// Try something else
|
||||
}
|
||||
}
|
||||
|
||||
if (e.hasOwnProperty("status") && e.hasOwnProperty("statusText") && e.hasOwnProperty("responseText")) {
|
||||
// Its an HTTP error, so format appropriately
|
||||
return {
|
||||
message: "Got unexpected HTTP error " + e.status + ": " + e.statusText,
|
||||
details: e.responseText
|
||||
}
|
||||
}
|
||||
return {
|
||||
message: "Got an unexpected error",
|
||||
details: JSON.stringify(error, undefined, 2)
|
||||
}
|
||||
}
|
||||
|
||||
function getInnerHomeUri() {
|
||||
return normalizeAddonUrl(URI(window.location.href));
|
||||
}
|
||||
|
||||
function getOutterHomeUri() {
|
||||
if (parent !== window) {
|
||||
return normalizeAddonUrl(URI(parent.location.href), false);
|
||||
} else {
|
||||
return getInnerHomeUri()
|
||||
}
|
||||
}
|
||||
|
||||
function getHomeAssistantUrl(path, alt_host) {
|
||||
if (parent !== window) {
|
||||
// Probably this is an ingress window
|
||||
var start = URI(normalizeAddonUrl(URI(parent.location.href), false));
|
||||
return start.pathname(path).toString();
|
||||
} else {
|
||||
// Probably this is the addon on a custom port
|
||||
var host = URI(window.location.href).hostname();
|
||||
return URI(alt_host.replace("{host}", host)).pathname(path).toString();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function normalizeAddonUrl(uri, add_trailing_slash = true) {
|
||||
path = uri.pathname();
|
||||
endings = [
|
||||
/\/reauthenticate$/g,
|
||||
/\/reauthenticate\/$/g,
|
||||
/\/index$/g,
|
||||
/\/index\/$/g,
|
||||
/\/index.html$/g,
|
||||
/\/index.html\/$/g
|
||||
]
|
||||
for (var i = 0; i < endings.length; i++) {
|
||||
path = path.replace(endings[i], "/");
|
||||
}
|
||||
if (add_trailing_slash) {
|
||||
path = path + "/";
|
||||
}
|
||||
path = path.replace("//", "/");
|
||||
return uri.pathname(path).search("").fragment("").hash("").toString();
|
||||
}
|
||||
|
||||
function cancelSync() {
|
||||
postJson("cancelSync", {}, refreshstats, null, "Canceling...")
|
||||
}
|
||||
|
||||
function setErrorWatermark() {
|
||||
error_minimum = last_data.last_error_count
|
||||
}
|
||||
|
||||
function bugReport() {
|
||||
postJson("makeanissue", {}, openBugDialog, null, "Generating Bug Info...")
|
||||
}
|
||||
|
||||
function openBugDialog(resp) {
|
||||
$("#bug_markdown_display").val(resp.markdown);
|
||||
renderMarkdown();
|
||||
tabs = M.Tabs.getInstance(document.querySelector("#bug_report_tabs"));
|
||||
M.Modal.getInstance(document.querySelector('#bug_modal')).open();
|
||||
tabs.select("bug_markdown");
|
||||
tabs.select("bug_preview");
|
||||
tabs.updateTabIndicator();
|
||||
}
|
||||
|
||||
function doBugDetailCopy() {
|
||||
tabs.select("bug_markdown");
|
||||
var copyText = document.getElementById("bug_markdown_display");
|
||||
copyText.select();
|
||||
document.execCommand("copy");
|
||||
M.toast({ html: "Copied! Now go to GitHub" });
|
||||
}
|
||||
|
||||
function renderMarkdown() {
|
||||
$("#bug_preview_display").html(window.markdownit().render($("#bug_markdown_display").val()));
|
||||
}
|
||||
|
||||
sync_toast = null;
|
||||
error_toast = null
|
||||
last_data = null;
|
||||
error_minimum = 0
|
||||
// Refreshes the display with stats from the server.
|
||||
function refreshstats() {
|
||||
var jqxhr = $.get("getstatus", processStatusUpdate, "json").fail(
|
||||
function (e) {
|
||||
console.log("Status update failed: ");
|
||||
console.log(e);
|
||||
$("#backups_loading").show();
|
||||
if (error_toast == null) {
|
||||
M.Toast.dismissAll();
|
||||
sync_toast = null;
|
||||
error_toast = M.toast({ html: 'Lost connection to add-on, will keep trying to connect...', displayLength: 9999999 })
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
function processSourcesUpdate(sources) {
|
||||
sources_div = $('#sources');
|
||||
for (var key in sources) {
|
||||
if (!sources.hasOwnProperty(key)) {
|
||||
continue;
|
||||
}
|
||||
let source = sources[key];
|
||||
|
||||
if (!source.enabled) {
|
||||
continue;
|
||||
}
|
||||
let template = $(".source_" + key);
|
||||
let isNew = false;
|
||||
if (template.length == 0) {
|
||||
isNew = true;
|
||||
template = $('#source-template').find(".source-ui").clone();
|
||||
template.addClass("source_" + key);
|
||||
template.addClass("active_source");
|
||||
template.data("source", key);
|
||||
}
|
||||
|
||||
$(".source_title", template).html("in " + source.title );
|
||||
$(".source-detail", template).html(source.detail);
|
||||
if (source.detail.length > 0) {
|
||||
$(".source-detail-label", template).show();
|
||||
} else {
|
||||
$(".source-detail-label", template).hide();
|
||||
}
|
||||
$("use", template).attr('xlink:href', "#" + source.icon);
|
||||
|
||||
if (source.retained > 0) {
|
||||
$(".source_retain_count", template).html(source.retained);
|
||||
$(".source_retain_label", template).show();
|
||||
} else {
|
||||
$(".source_retain_label", template).hide();
|
||||
}
|
||||
if (source.ignored > 0) {
|
||||
$(".source_ignored_count", template).html(source.ignored);
|
||||
$(".source_ignored_size", template).html(source.ignored_size);
|
||||
$(".source_ignored_label", template).show();
|
||||
} else {
|
||||
$(".source_ignored_label", template).hide();
|
||||
}
|
||||
$(".source_backup_count", template).html(source.backups + " (" + source.size + ")");
|
||||
|
||||
if (source.hasOwnProperty("free_space")) {
|
||||
$('.source_free_space_text', template).html(source.free_space + " remaining");
|
||||
$('.source_free_space_tooltip', template).attr("data-tooltip", "An estimate of the space available in " + source.title + ".");
|
||||
$('.source_free_space', template).show();
|
||||
} else {
|
||||
$('.source_free_space', template).hide();
|
||||
}
|
||||
|
||||
if (isNew) {
|
||||
sources_div.append(template);
|
||||
}
|
||||
}
|
||||
$(".active_source").each(function () {
|
||||
let source = $(this);
|
||||
let key = source.data('source');
|
||||
if (!(sources.hasOwnProperty(source.data('source')) && sources[key].enabled)) {
|
||||
source.remove();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function processBackupsUpdate(data) {
|
||||
let detail_modal = document.getElementById('details_modal');
|
||||
let detail_modal_slug = $("#details_modal").data('slug');
|
||||
detail_modal = M.Modal.getInstance(detail_modal);
|
||||
|
||||
let regular_backups = [];
|
||||
let ignored_backups = [];
|
||||
for (var key in data.backups) {
|
||||
if (data.backups.hasOwnProperty(key)) {
|
||||
backup = data.backups[key];
|
||||
if (backup.ignored) {
|
||||
ignored_backups.push(backup);
|
||||
} else {
|
||||
regular_backups.push(backup);
|
||||
}
|
||||
|
||||
// Update the detail modal is necessary
|
||||
if (detail_modal_slug == backup.slug && detail_modal && detail_modal.isOpen) {
|
||||
setValuesForBackupUpdate(backup);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let count_regular = populateBackupDiv($('#backups'), regular_backups, "archive");
|
||||
let count_ignored = populateBackupDiv($('#backups_ignored'), ignored_backups, "cloud_off");
|
||||
|
||||
if (count_ignored == 0) {
|
||||
$(".ignored_backup_slider").addClass("default-hidden");
|
||||
} else {
|
||||
$(".ignored_backup_slider").removeClass("default-hidden");
|
||||
}
|
||||
if (count_ignored > 1) {
|
||||
$(".ignored_backup_plural").removeClass("default-hidden");
|
||||
} else {
|
||||
$(".ignored_backup_plural").addClass("default-hidden");
|
||||
}
|
||||
|
||||
$(".ignored_backup_count").html(count_ignored);
|
||||
return count_regular + count_ignored;
|
||||
}
|
||||
|
||||
function populateBackupDiv(backup_div, backups, icon) {
|
||||
slugs = []
|
||||
count = 0;
|
||||
for (var key in backups) {
|
||||
if (backups.hasOwnProperty(key)) {
|
||||
count++;
|
||||
backup = backups[key];
|
||||
// try to find the item
|
||||
var template = $(".slug" + backup.slug, backup_div);
|
||||
slugs.push(backup.slug);
|
||||
var isNew = false;
|
||||
if (template.length == 0) {
|
||||
var template = $('#backup-template').find(".backup-ui").clone();
|
||||
template.addClass("slug" + backup.slug);
|
||||
template.addClass("active-backup");
|
||||
template.data("slug", backup.slug);
|
||||
template.data("timestamp", backup.timestamp);
|
||||
$("#backup_card", template).attr('id', "backup_card" + backup.slug);
|
||||
$("#loading", template).attr('id', "loading" + backup.slug);
|
||||
$(".backup_icon", template).html(icon);
|
||||
isNew = true;
|
||||
}
|
||||
|
||||
$("#size", template).html(backup['size']);
|
||||
if (backup['type'].toLowerCase() == 'full') {
|
||||
$("#type", template).html("Full backup");
|
||||
} else if (backup['type'].toLowerCase() == 'partial') {
|
||||
$("#type", template).html( "Partial backup");
|
||||
} else {
|
||||
$("#type", template).html('');
|
||||
}
|
||||
|
||||
$("#createdAt", template).html(backup['createdAt']);
|
||||
$("#name", template).html(backup['name']);
|
||||
$("#name", template).attr('title', backup['name']);
|
||||
$("#status", template).html(backup['status']);
|
||||
let note = $("#note", template);
|
||||
if (backup.note) {
|
||||
note.html(backup['note']);
|
||||
note.attr('data-tooltip', backup['note']);
|
||||
note.addClass('tooltipped');
|
||||
} else {
|
||||
note.html(" ");
|
||||
note.attr('data-tooltip', '');
|
||||
note.removeClass('tooltipped');
|
||||
}
|
||||
if (backup.status_detail) {
|
||||
$("#gen_detail", template).show();
|
||||
tooltip = "Kept generationally for " + backup.status_detail[0];
|
||||
$("#gen_detail", template).attr('data-tooltip', tooltip);
|
||||
} else {
|
||||
$("#gen_detail", template).hide();
|
||||
}
|
||||
|
||||
|
||||
if (backup.protected) {
|
||||
$(".icon-protected", template).show();
|
||||
} else {
|
||||
$(".icon-protected", template).hide();
|
||||
}
|
||||
|
||||
delete_next = [];
|
||||
retained = false;
|
||||
for (let source of backup.sources){
|
||||
if (source.delete_next) {
|
||||
delete_next.push(source);
|
||||
}
|
||||
if (source.retained) {
|
||||
retained = true;
|
||||
}
|
||||
}
|
||||
if (delete_next.length > 1) {
|
||||
$(".icon-warn-delete", template).show();
|
||||
$(".icon-warn-delete", template).attr("data-tooltip", "This backup will be deleted next from " + delete_next.length + " places when a new backup is created.");
|
||||
} else if (delete_next.length == 1) {
|
||||
$(".icon-warn-delete", template).show();
|
||||
$(".icon-warn-delete", template).attr("data-tooltip", "This backup will be deleted next from " + sourceToName(delete_next[0].key) + " when a new backup is created.");
|
||||
} else {
|
||||
$(".icon-warn-delete", template).hide();
|
||||
}
|
||||
|
||||
if (retained) {
|
||||
$(".icon-retain", template).show();
|
||||
} else {
|
||||
$(".icon-retain", template).hide();
|
||||
}
|
||||
|
||||
tip = "Help unavailable";
|
||||
|
||||
if (backup.status.includes("Drive")) {
|
||||
tip = tooltipDriveOnly;
|
||||
} else if (backup.status.includes("Backed Up")) {
|
||||
tip = tooltipBackedUp;
|
||||
} else if (backup.status.includes("Loading")) {
|
||||
tip = tooltipLoading;
|
||||
} else if (backup.status.includes("HA Only")) {
|
||||
tip = tooltipHassio;
|
||||
} else if (backup.status.includes("Pending")) {
|
||||
tip = tooltipPending;
|
||||
} else if (backup.status.includes("Upload")) {
|
||||
tip = tooltipUploading;
|
||||
} else if (backup.status.includes("aiting")) {
|
||||
tip = tooltipWaiting;
|
||||
}
|
||||
$("#status-help", template).attr("data-tooltip", tip);
|
||||
|
||||
if (isNew) {
|
||||
before = null;
|
||||
// Find where the backup should be inserted, which is almost always at the top.
|
||||
// This is an inefficient way of sorting but prevents juggling DOM entities around
|
||||
// and the "search" is almsot always O(1) in practice.
|
||||
$(".active-backup", backup_div).each(function () {
|
||||
if (template.data('timestamp') > $(this).data('timestamp')) {
|
||||
before = $(this);
|
||||
return false;
|
||||
}
|
||||
});
|
||||
if (before != null) {
|
||||
template.insertBefore(before);
|
||||
} else {
|
||||
backup_div.append(template);
|
||||
}
|
||||
}
|
||||
|
||||
if (backup.isPending) {
|
||||
$("#loading" + backup.slug).show();
|
||||
$("#backup_card" + backup.slug).css("cursor", "auto");
|
||||
} else {
|
||||
$("#loading" + backup.slug).hide();
|
||||
$("#backup_card" + backup.slug).css("cursor", "pointer");
|
||||
}
|
||||
|
||||
// Set up context
|
||||
$("#backup_card" + backup.slug).data('backup', backup);
|
||||
}
|
||||
}
|
||||
// Remove the backup card if the backup was deleted.
|
||||
$(".active-backup", backup_div).each(function () {
|
||||
var backup = $(this)
|
||||
if (!slugs.includes(backup.data('slug'))) {
|
||||
backup.remove();
|
||||
}
|
||||
});
|
||||
return count;
|
||||
}
|
||||
|
||||
function processStatusUpdate(data) {
|
||||
name_keys = data.backup_name_keys;
|
||||
$('#last_backup').empty().append(data.last_backup_text);
|
||||
$('#last_backup').attr("datetime", data.last_backup_machine);
|
||||
$('#last_backup').attr("title", data.last_backup_detail);
|
||||
|
||||
$('#next_backup').empty().append(data.next_backup_text);
|
||||
$('#next_backup').attr("datetime", data.next_backup_machine);
|
||||
$('#next_backup').attr("title", data.next_backup_detail);
|
||||
|
||||
if (data.sources.GoogleDrive.enabled && data.folder_id && data.folder_id.length > 0 ) {
|
||||
$('.open_drive_link').attr("href", "https://drive.google.com/drive/u/0/folders/" + data.folder_id);
|
||||
$('.open_drive_menu').show()
|
||||
} else {
|
||||
$('.open_drive_menu').hide()
|
||||
}
|
||||
|
||||
processSourcesUpdate(data.sources);
|
||||
count = processBackupsUpdate(data);
|
||||
|
||||
// Update the "syncing" toast message
|
||||
if (data.syncing) {
|
||||
if (sync_toast == null) {
|
||||
sync_toast = M.toast({ html: '<span>Syncing...</span><a class="btn-flat toast-action" onclick="cancelSync()">Cancel</button>', displayLength: 999999999 })
|
||||
}
|
||||
} else {
|
||||
// Make sure the toast isn't up
|
||||
if (sync_toast != null) {
|
||||
sync_toast.dismiss();
|
||||
sync_toast = null
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (count == 0) {
|
||||
if (!data.firstSync) {
|
||||
$("#no_backups_block").show();
|
||||
$("#backups_loading").hide();
|
||||
} else {
|
||||
$("#backups_loading").show();
|
||||
$("#no_backups_block").hide();
|
||||
}
|
||||
} else {
|
||||
$("#no_backups_block").hide();
|
||||
$("#backups_loading").hide();
|
||||
}
|
||||
|
||||
var found = false;
|
||||
var error = data.last_error;
|
||||
$('.error_card').each(function (i) {
|
||||
var item = $(this);
|
||||
let id = item.attr('id');
|
||||
if (id == "error_card" || id == "error_details_card") {
|
||||
// This card gets handled separately because it catches any other error.
|
||||
return;
|
||||
}
|
||||
if (data.last_error == null) {
|
||||
if (item.is(":visible")) {
|
||||
item.hide();
|
||||
}
|
||||
} else if (item.hasClass(error.error_type) && data.last_error_count != error_minimum && !data.ignore_errors_for_now && !data.ignore_sync_error) {
|
||||
found = true;
|
||||
if (data.hasOwnProperty('dns_info')) {
|
||||
var dns_div = $('.dns_info', item)
|
||||
if (dns_div.length > 0) {
|
||||
populateDnsInfo(dns_div, data.dns_info)
|
||||
}
|
||||
}
|
||||
if (error.data != null) {
|
||||
for (key in error.data) {
|
||||
if (!error.data.hasOwnProperty(key)) {
|
||||
continue;
|
||||
}
|
||||
var value = error.data[key];
|
||||
var index = key.lastIndexOf("#");
|
||||
if (index > 0) {
|
||||
var attr = key.slice(index + 1);
|
||||
key = key.slice(0, index);
|
||||
$("#data_" + key, item).attr(attr, value);
|
||||
} else {
|
||||
$("#data_" + key, item).html(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (item.is(":hidden")) {
|
||||
item.show()
|
||||
}
|
||||
} else {
|
||||
item.hide();
|
||||
}
|
||||
});
|
||||
|
||||
if (data.last_error != null && !found && data.last_error_count != error_minimum && !data.ignore_errors_for_now && !data.ignore_sync_error) {
|
||||
var card = $("#error_card")
|
||||
if (!card.is(":visible")) {
|
||||
card.fadeIn();
|
||||
}
|
||||
populateGitHubInfo(card, data.last_error);
|
||||
} else {
|
||||
$("#error_card").hide();
|
||||
}
|
||||
|
||||
// Only show one of the "question" cards at a TimeRanges, ir order to prevent the UI from blowing up
|
||||
let question_card = null;
|
||||
if (data.notify_check_ignored) {
|
||||
question_card = "ignore_helper_card";
|
||||
} else if (data.backup_cooldown_active) {
|
||||
question_card = "backups_boot_waiting_card";
|
||||
} else if(data.warn_ingress_upgrade && !hideIngress) {
|
||||
question_card = "ingress_upgrade_card";
|
||||
} else if (data.ask_error_reports && !found) {
|
||||
question_card = "error_reports_card";
|
||||
} else if (data.warn_backup_upgrade && !found) {
|
||||
question_card = "backup_upgrade_card";
|
||||
} else if (data.warn_upgrade_backups) {
|
||||
question_card = "warn_upgrade_backups_card";
|
||||
} else if (data.warn_oob_oauth) {
|
||||
question_card = "warn_creds_deprecated_card";
|
||||
}
|
||||
|
||||
$('.question-card').each(function (i) {
|
||||
let item = $(this);
|
||||
let id = item.attr('id');
|
||||
let visible = item.is(":visible");
|
||||
if (id == question_card && !visible) {
|
||||
item.fadeIn(500);
|
||||
item.slideDown(1000);
|
||||
} else if (id != question_card && visible) {
|
||||
item.slideUp(1000);
|
||||
item.fadeOut(500);
|
||||
}
|
||||
});
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
|
||||
$("#restore_hard_link").attr("href", getHomeAssistantUrl(data.restore_backup_path, data.ha_url_base));
|
||||
|
||||
last_data = data;
|
||||
|
||||
$('.tooltipped').tooltip({ "exitDelay": 1000 });
|
||||
if (error_toast != null) {
|
||||
error_toast.dismiss();
|
||||
error_toast = null;
|
||||
}
|
||||
}
|
||||
|
||||
function populateDnsInfo(target, data) {
|
||||
if (data == null) {
|
||||
target.html("No DNS info is available")
|
||||
} else if (data.hasOwnProperty('error')) {
|
||||
target.html(JSON.stringify(data.error))
|
||||
} else {
|
||||
var html = "";
|
||||
for (var host in data) {
|
||||
if (data.hasOwnProperty(host)) {
|
||||
html += "<div class='col s12 m6 row'> <h6>Host: " + host + "</h6>";
|
||||
var ips = data[host];
|
||||
for (var ip in ips) {
|
||||
if (ips.hasOwnProperty(ip)) {
|
||||
result = ips[ip];
|
||||
html += "<div class='col s7'>" + ip + "</div><div class='col s5'>" + result + "</div>";
|
||||
}
|
||||
}
|
||||
html += "</div>";
|
||||
}
|
||||
}
|
||||
target.html(html);
|
||||
}
|
||||
}
|
||||
|
||||
function populateGitHubInfo(target, error) {
|
||||
$('#generic_error_title', target).text(error.message);
|
||||
$('#generic_error_details', target).text(error.details);
|
||||
$('#error_github_search', target).attr("href", "https://github.com/sabeechen/hassio-google-drive-backup/issues?q=" + encodeURIComponent("\"" + error.message.replace("\"", "\\\"") + "\""));
|
||||
}
|
||||
|
||||
function simulateError() {
|
||||
$.get("simerror?error=This%20is%20a%20fake%20error.%20Select%20'Stop%20Simulated%20Error'%20from%20the%20menu%20to%20stop%20it.",
|
||||
function (data) {
|
||||
sync()
|
||||
})
|
||||
}
|
||||
|
||||
function stopSimulateError() {
|
||||
$.get("simerror?error=",
|
||||
function (data) {
|
||||
sync()
|
||||
})
|
||||
}
|
||||
|
||||
function newBackupClick() {
|
||||
setInputValue("retain_drive_one_off", false);
|
||||
setInputValue("retain_ha_one_off", false);
|
||||
setInputValue("backup_name_one_off", "");
|
||||
setInputValue("backup_note_one_off", "");
|
||||
backupNameOneOffExample();
|
||||
M.Modal.getInstance(document.querySelector('#backupmodal')).open();
|
||||
}
|
||||
|
||||
function doNewBackup() {
|
||||
var drive = $("#retain_drive_one_off").prop('checked');
|
||||
var ha = $("#retain_ha_one_off").prop('checked');
|
||||
var name = $("#backup_name_one_off").val()
|
||||
let note = $("#backup_note_one_off").val()
|
||||
var url = "backup?custom_name=" + encodeURIComponent(name) + "&retain_drive=" + drive + "&retain_ha=" + ha + "¬e=" + encodeURIComponent(note);
|
||||
postJson(url, {}, refreshstats, null, "Requesting backup (takes a few seconds)...");
|
||||
return false;
|
||||
}
|
||||
|
||||
function allowDeletion(always) {
|
||||
var url = "confirmdelete?always=" + always;
|
||||
postJson(url, {}, refreshstats, null, "Allowing deletion and syncing...");
|
||||
}
|
||||
|
||||
function chooseBackupFolder() {
|
||||
window.open(last_data.choose_folder_url);
|
||||
}
|
||||
|
||||
function skipLowSpaceWarning() {
|
||||
postJsonCloseErrorDialog("skipspacecheck", "low_space");
|
||||
}
|
||||
|
||||
|
||||
|
||||
$(document).ready(function () {
|
||||
if (window.top.location === window.location) {
|
||||
// We're in a standard webpage, show the full header
|
||||
$(".ingress-only").hide();
|
||||
$(".nav-wrapper .right").addClass("hide-on-med-and-down")
|
||||
} else {
|
||||
// We're in an ingress iframe.
|
||||
$(".non-ingress").hide();
|
||||
$(".nav-wrapper .brand-logo").addClass("hide-on-med-and-down")
|
||||
}
|
||||
M.Tabs.init(document.querySelector("#bug_report_tabs"), { "onShow": renderMarkdown });
|
||||
});
|
||||
|
||||
|
||||
function copyFromInput(id) {
|
||||
var copyText = document.getElementById(id);
|
||||
copyText.select();
|
||||
document.execCommand("copy");
|
||||
}
|
||||
|
||||
function saveFolder(id) {
|
||||
url = "changefolder?id=" + id
|
||||
postJson(url, {}, refreshstats, null, "Setting backup folder...");
|
||||
}
|
||||
|
||||
function exampleBackupName(backup_type, template) {
|
||||
name_keys["{type}"] = $("#partial_backups").is(':checked') ? "Partial" : "Full";
|
||||
if (template.length == 0) {
|
||||
template = last_data.backup_name_template;
|
||||
}
|
||||
for (key in name_keys) {
|
||||
template = template.replace(key, name_keys[key]);
|
||||
}
|
||||
return template;
|
||||
}
|
||||
@@ -0,0 +1,353 @@
|
||||
settingsChanged = false;
|
||||
|
||||
function showPallette(element) {
|
||||
let target = $(element);
|
||||
target.colpick({
|
||||
'layout': 'rgbhex',
|
||||
'color': target.html(),
|
||||
'onSubmit': colorSubmit,
|
||||
'onChange': function(hsb,hex,rgb,el,bySetColor){
|
||||
settingsChanged = true;
|
||||
let color = new Color(rgb['r'], rgb['g'], rgb['b']);
|
||||
updateColorSelector(target, color);
|
||||
setStyles();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function updateColorSelector(target, color) {
|
||||
target.html(color.toHex());
|
||||
target.css("background-color", color.toCss());
|
||||
target.css("color", color.textColor().toCss());
|
||||
target.css("border", "2px");
|
||||
target.css("border-color", color.textColor().toCss());
|
||||
target.css("border-style", 'solid');
|
||||
}
|
||||
|
||||
function colorSubmit(hsb,hex,rgb,el,bySetColor){
|
||||
setStyles();
|
||||
$(el).colpickHide();
|
||||
}
|
||||
|
||||
function setStyles() {
|
||||
background = Color.parse($("#background_color").html());
|
||||
accent = Color.parse($("#accent_color").html());
|
||||
window.setColors(background, accent);
|
||||
}
|
||||
|
||||
function revertColors() {
|
||||
background = Color.defaultBackground()
|
||||
updateColorSelector($("#background_color"), background);
|
||||
accent = Color.defaultAccent();
|
||||
updateColorSelector($("#accent_color"), accent);
|
||||
|
||||
window.setColors(background, accent);
|
||||
}
|
||||
|
||||
function backupNameExample() {
|
||||
$("#backup_example").html(exampleBackupName("Full", $("#backup_name").val()));
|
||||
}
|
||||
|
||||
function backupNameOneOffExample() {
|
||||
$("#backup_name_example_one_off").html(exampleBackupName("Full", $("#backup_name_one_off").val()));
|
||||
}
|
||||
|
||||
function checkForSecret() {
|
||||
var password = $("#backup_password");
|
||||
var password2 = $("#backup_password_reenter");
|
||||
var block = $("#password_renter_block");
|
||||
var new_password = password.val();
|
||||
var old_password = password.data('old_password');
|
||||
if (password.val().startsWith("!secret ")) {
|
||||
password.attr('type', 'text');
|
||||
block.fadeOut();
|
||||
return true;
|
||||
} else {
|
||||
password.attr('type', 'password');
|
||||
if (new_password.length > 0 && old_password != new_password) {
|
||||
block.fadeIn();
|
||||
if (new_password == password2.val()) {
|
||||
password2.removeClass("invalid");
|
||||
return true;
|
||||
} else {
|
||||
password2.addClass("invalid");
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
block.fadeOut();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$(document).ready(function () {
|
||||
// handle "escape" when settings dialog is presented
|
||||
$(document).keyup(function (e) {
|
||||
if (e.keyCode === 27) { // 27==escape
|
||||
if (M.Modal.getInstance(document.querySelector('#settings_modal')).isOpen) {
|
||||
handleCloseSettings();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
var settingsDialog = $("#settings_modal");
|
||||
$('#settings_modal :input').change(function () {
|
||||
settingsChanged = true;
|
||||
});
|
||||
});
|
||||
|
||||
function handleCloseSettings() {
|
||||
// determine is the settings hanve changed.
|
||||
if (settingsChanged) {
|
||||
if (confirm("Discard changes?")) {
|
||||
window.setColors(config.background_color, config.accent_color);
|
||||
M.Modal.getInstance(document.getElementById("settings_modal")).close();
|
||||
}
|
||||
} else {
|
||||
window.setColors(config.background_color, config.accent_color);
|
||||
M.Modal.getInstance(document.getElementById("settings_modal")).close();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function loadSettings() {
|
||||
postJson("getconfig", {}, handleSettingsDialog, null, "Loading settings...")
|
||||
}
|
||||
|
||||
function handleSettingsDialog(data) {
|
||||
config = data.config;
|
||||
addons = data.addons;
|
||||
defaults = data.defaults
|
||||
for (key in config) {
|
||||
if (config.hasOwnProperty(key)) {
|
||||
setInputValue(key, config[key]);
|
||||
}
|
||||
}
|
||||
|
||||
setInputValue("generational_enabled",
|
||||
config.generational_days > 0 || config.generational_weeks > 0 || config.generational_months > 0 || config.generational_years > 0);
|
||||
|
||||
var exclude_addons = [];
|
||||
var stop_addons = []
|
||||
if (config.hasOwnProperty('exclude_addons') && config.exclude_addons.length > 0) {
|
||||
exclude_addons = config.exclude_addons.split(",");
|
||||
}
|
||||
if (config.hasOwnProperty('stop_addons') && config.stop_addons.length > 0) {
|
||||
stop_addons = config.stop_addons.split(",");
|
||||
}
|
||||
|
||||
setInputValue("partial_backups", config.exclude_folders.length > 0 || exclude_addons.length > 0);
|
||||
setInputValue("stop_addons", stop_addons.length > 0);
|
||||
|
||||
// Set the state of excluded and stopped addons.
|
||||
$("#settings_addons").html("");
|
||||
$("#stopped_addons").html("");
|
||||
for (addon in addons) {
|
||||
addon = addons[addon];
|
||||
template = `<li class="indented-li">
|
||||
<label>
|
||||
<input class="filled-in {selector}" type="checkbox" name="{id}" id="{id}" data-slug="{slug}" settings_ignore='true' {checked} />
|
||||
<span>{name} <span class="helper-text">(v{version})</span></span>
|
||||
<br />
|
||||
<span class="helper-text">{description}</span>
|
||||
</label>
|
||||
</li>`;
|
||||
template = template
|
||||
.replace("{id}", addon.slug)
|
||||
.replace("{id}", addon.slug)
|
||||
.replace("{slug}", addon.slug)
|
||||
.replace("{description}", addon.description)
|
||||
.replace("{name}", addon.name)
|
||||
.replace("{version}", addon.version);
|
||||
|
||||
$("#settings_addons").append(template.replace("{checked}", exclude_addons.includes(addon.slug) ? "" : "checked").replace("{selector}", "settings_addon_checkbox"));
|
||||
$("#stopped_addons").append(template.replace("{checked}", stop_addons.includes(addon.slug) ? "checked" : "").replace("{selector}", "settings_stop_addon_checkbox"));
|
||||
}
|
||||
|
||||
$("#folder_selection_list").html("");
|
||||
for (folder of data.folders) {
|
||||
template = `<li class="indented-li">
|
||||
<label class="checkbox-label">
|
||||
<input class="filled-in settings_folder_checkbox" settings_ignore="true" type="checkbox" name="{id}" id="{id}" data-slug="{slug}" {checked} />
|
||||
<span class="checkbox-label">{name}</span>
|
||||
<br />
|
||||
<span class="helper-text">{description}</span>
|
||||
</label>
|
||||
</li>`;
|
||||
template = template
|
||||
.replace("{id}", folder.id)
|
||||
.replace("{id}", folder.id)
|
||||
.replace("{slug}", folder.slug)
|
||||
.replace("{description}", folder.description)
|
||||
.replace("{name}", folder.name)
|
||||
.replace("{name}", folder.slug)
|
||||
.replace("{checked}", config.exclude_folders.includes(folder.slug) ? "" : "checked");
|
||||
$("#folder_selection_list").append(template);
|
||||
}
|
||||
|
||||
$("#settings_error_div").hide();
|
||||
M.updateTextFields();
|
||||
$("#use_ssl").trigger("change");
|
||||
$("#generational_enabled").trigger("change");
|
||||
$("#partial_backups").trigger("change");
|
||||
$("#expose_extra_server").trigger("change");
|
||||
settingsChanged = false;
|
||||
backupNameExample();
|
||||
|
||||
// Configure the visibility/link of the "current backup folder" help text and button.
|
||||
if (data.backup_folder && data.backup_folder.length > 0) {
|
||||
$("#current_folder_span").show()
|
||||
$('#current_folder_link').attr("href", "https://drive.google.com/drive/u/0/folders/" + data.backup_folder);
|
||||
} else {
|
||||
$("#current_folder_span").hide();
|
||||
}
|
||||
|
||||
if (config.specify_backup_folder && last_data && last_data.sources.GoogleDrive.enabled) {
|
||||
$("#choose_folder_controls").show();
|
||||
} else {
|
||||
$("#choose_folder_controls").hide();
|
||||
}
|
||||
|
||||
setInputValue("settings_specify_folder_id", data.backup_folder);
|
||||
|
||||
if (config.hasOwnProperty("backup_password")) {
|
||||
$("#backup_password").data("old_password", config.backup_password);
|
||||
} else {
|
||||
$("#backup_password").data("old_password", "");
|
||||
}
|
||||
$("#backup_password_reenter").val("");
|
||||
|
||||
let background = config.background_color;
|
||||
if (!background || background.length == 0) {
|
||||
background = Color.defaultBackground();
|
||||
} else {
|
||||
background = Color.parse(config.background_color);
|
||||
}
|
||||
let accent = config.accent_color;
|
||||
if (!accent || accent.length == 0) {
|
||||
accent = Color.defaultAccent();
|
||||
} else {
|
||||
accent = Color.parse(config.accent_color);
|
||||
}
|
||||
|
||||
updateColorSelector($("#background_color"), background);
|
||||
updateColorSelector($("#accent_color"), accent);
|
||||
checkForSecret();
|
||||
M.Modal.getInstance(document.querySelector('#settings_modal')).open();
|
||||
showPallette($("#background_color"));
|
||||
showPallette($("#accent_color"));
|
||||
|
||||
toggleSlide(document.querySelector('#stop_addons'), 'settings_stop_addons_details');
|
||||
updateIgnoredBackupOptions();
|
||||
M.updateTextFields();
|
||||
}
|
||||
|
||||
function chooseFolderChanged() {
|
||||
if ($("#specify_backup_folder").is(':checked') && last_data && last_data.sources.GoogleDrive.enabled) {
|
||||
$("#choose_folder_controls").show();
|
||||
} else {
|
||||
$("#choose_folder_controls").hide();
|
||||
}
|
||||
M.updateTextFields();
|
||||
}
|
||||
|
||||
function updateIgnoredBackupOptions() {
|
||||
if ($("#ignore_upgrade_backups").prop('checked') || $("#ignore_other_backups").prop('checked')) {
|
||||
$("#ignored-backup-duration-block").fadeIn();
|
||||
} else {
|
||||
$("#ignored-backup-duration-block").fadeOut();
|
||||
}
|
||||
}
|
||||
|
||||
function saveSettings() {
|
||||
if (!document.getElementById('settings_form').checkValidity()) {
|
||||
showSettingError({message: "Some configuration is invalid, check for red errors up above."})
|
||||
return;
|
||||
}
|
||||
|
||||
if (!checkForSecret()) {
|
||||
showSettingError({message: "New backup passwords don't match"})
|
||||
return;
|
||||
}
|
||||
toast("Saving...")
|
||||
var config = {}
|
||||
$("select", $("#settings_form")).each(function () {
|
||||
var target = $(this)
|
||||
config[target.attr("id")] = target.val();
|
||||
});
|
||||
$("input", $("#settings_form")).each(function () {
|
||||
var target = $(this)
|
||||
if (target.attr("settings_ignore") == "true") {
|
||||
return;
|
||||
}
|
||||
var value = target.val()
|
||||
if (target.attr("type") == "checkbox") {
|
||||
value = target.prop('checked')
|
||||
} else {
|
||||
if (value.length == 0) {
|
||||
return;
|
||||
} else if (target.attr("type") == "number") {
|
||||
value = parseInt(value)
|
||||
}
|
||||
}
|
||||
config[target.attr("id")] = value;
|
||||
});
|
||||
excluded_addons = ""
|
||||
excluded_folders = ""
|
||||
stop_addons = ""
|
||||
if ($("#partial_backups").prop('checked')) {
|
||||
$(".settings_folder_checkbox").each(function () {
|
||||
if (!$(this).is(":checked")) {
|
||||
excluded_folders = excluded_folders + $(this).data("slug") + ",";
|
||||
}
|
||||
});
|
||||
$(".settings_addon_checkbox").each(function () {
|
||||
if (!$(this).is(":checked")) {
|
||||
excluded_addons = excluded_addons + $(this).data('slug') + ",";
|
||||
}
|
||||
});
|
||||
}
|
||||
if ($("#stop_addons").prop('checked')) {
|
||||
$(".settings_stop_addon_checkbox").each(function () {
|
||||
if ($(this).is(":checked")) {
|
||||
stop_addons = stop_addons + $(this).data('slug') + ",";
|
||||
}
|
||||
});
|
||||
}
|
||||
config.exclude_folders = excluded_folders.replace(/(^,)|(,$)/g, "");
|
||||
config.exclude_addons = excluded_addons.replace(/(^,)|(,$)/g, "");
|
||||
config.stop_addons = stop_addons.replace(/(^,)|(,$)/g, "");
|
||||
if (!$("#generational_enabled").prop('checked')) {
|
||||
generational_delete = ["generational_days", "generational_weeks", "generational_months", "generational_years"]
|
||||
for (prop in generational_delete) {
|
||||
if (config.hasOwnProperty(generational_delete[prop])) {
|
||||
delete config[generational_delete[prop]];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ( $("#background_color").html() == Color.defaultBackground().toHex()){
|
||||
config.background_color = "";
|
||||
} else {
|
||||
config.background_color = $("#background_color").html();
|
||||
}
|
||||
|
||||
if ( $("#accent_color").html() == Color.defaultAccent().toHex()){
|
||||
config.accent_color = "";
|
||||
} else {
|
||||
config.accent_color = $("#accent_color").html();
|
||||
}
|
||||
|
||||
modal = M.Modal.getInstance(document.getElementById("settings_modal"))
|
||||
postJson("saveconfig", {"config": config, "backup_folder": $("#settings_specify_folder_id").val()}, closeSettings, showSettingError);
|
||||
}
|
||||
|
||||
function closeSettings(){
|
||||
M.Modal.getInstance(document.getElementById("settings_modal")).close()
|
||||
}
|
||||
|
||||
function showSettingError(e){
|
||||
var element = $("#settings_error");
|
||||
element.html(e.message);
|
||||
$("#settings_error_div").fadeIn(400);
|
||||
}
|
||||
@@ -0,0 +1,247 @@
|
||||
const themeStyleContainer = document.getElementById("theme");
|
||||
|
||||
class Color {
|
||||
constructor(r = 0, g = 0, b = 0, a = 1) {
|
||||
this.r = this._clamp(r);
|
||||
this.g = this._clamp(g);
|
||||
this.b = this._clamp(b);
|
||||
this.a = this._clamp(a, 0, 1);
|
||||
}
|
||||
|
||||
static black() {
|
||||
return new Color(0, 0, 0);
|
||||
}
|
||||
|
||||
static white() {
|
||||
return new Color(255, 255, 255);
|
||||
}
|
||||
|
||||
static grey() {
|
||||
return new Color(128, 128, 128);
|
||||
}
|
||||
|
||||
static defaultAccent() {
|
||||
return new Color(3, 169, 260);
|
||||
}
|
||||
|
||||
static defaultBackgroundLight() {
|
||||
return Color.white();
|
||||
}
|
||||
|
||||
static defaultBackgroundDark() {
|
||||
return new Color(26, 26, 26);
|
||||
}
|
||||
|
||||
static defaultBackground() {
|
||||
if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) {
|
||||
return Color.defaultBackgroundDark();
|
||||
} else {
|
||||
return Color.defaultBackgroundLight();
|
||||
}
|
||||
}
|
||||
|
||||
static hexToRgb(hex) {
|
||||
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
|
||||
return result
|
||||
? {
|
||||
r: parseInt(result[1], 16),
|
||||
g: parseInt(result[2], 16),
|
||||
b: parseInt(result[3], 16),
|
||||
}
|
||||
: null;
|
||||
}
|
||||
|
||||
static parse(color) {
|
||||
const rgb = this.hexToRgb(color);
|
||||
return new Color(rgb.r, rgb.g, rgb.b);
|
||||
}
|
||||
|
||||
tint(to, amount) {
|
||||
amount = this._clamp(amount, 0, 1);
|
||||
|
||||
const r_new = this.r + (to.r - this.r) * amount;
|
||||
const g_new = this.g + (to.g - this.g) * amount;
|
||||
const b_new = this.b + (to.b - this.b) * amount;
|
||||
const a_new = this.a + (to.a - this.a) * amount;
|
||||
return new Color(r_new, g_new, b_new, a_new);
|
||||
}
|
||||
|
||||
darken(amount) {
|
||||
return this.tint(Color.black(), amount);
|
||||
}
|
||||
|
||||
lighten(amount) {
|
||||
return this.tint(Color.white(), amount);
|
||||
}
|
||||
|
||||
shift(amount) {
|
||||
return this.luminance() >= 0.5 ? this.darken(amount) : this.lighten(amount);
|
||||
}
|
||||
|
||||
saturate(change) {
|
||||
const Pr = 0.299;
|
||||
const Pg = 0.587;
|
||||
const Pb = 0.114;
|
||||
const P = Math.sqrt(this.r * this.r * Pr + this.g * this.g * Pg + this.b * this.b * Pb);
|
||||
|
||||
const R = this._clamp(P + (this.r - P) * change);
|
||||
const G = this._clamp(P + (this.g - P) * change);
|
||||
const B = this._clamp(P + (this.b - P) * change);
|
||||
return new Color(R, G, B, this.a);
|
||||
}
|
||||
|
||||
_clamp(value, min = 0, max = 255) {
|
||||
if (value > max) {
|
||||
return max;
|
||||
}
|
||||
if (value < min) {
|
||||
return min;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
toCss() {
|
||||
return "rgba(" + Math.floor(this.r) + "," + Math.floor(this.g) + "," + Math.floor(this.b) + "," + this.a + ")";
|
||||
}
|
||||
|
||||
toHex() {
|
||||
const componentToHex = (c) => {
|
||||
var hex = c.toString(16);
|
||||
return hex.length == 1 ? "0" + hex : hex;
|
||||
};
|
||||
|
||||
return (
|
||||
"#" + componentToHex(Math.floor(this.r)) + componentToHex(Math.floor(this.g)) + componentToHex(Math.floor(this.b))
|
||||
);
|
||||
}
|
||||
|
||||
textColor() {
|
||||
const luma = (0.299 * this.r + 0.587 * this.g + 0.114 * this.b) / 255;
|
||||
return luma > 0.53 ? Color.black() : Color.white();
|
||||
}
|
||||
|
||||
luminance() {
|
||||
const rg = Math.floor(this.r) <= 10 ? this.r / 3294.0 : Math.pow(this.r / 269.0 + 0.0513, 2.4);
|
||||
const gg = Math.floor(this.g) <= 10 ? this.g / 3294.0 : Math.pow(this.g / 269.0 + 0.0513, 2.4);
|
||||
const bg = Math.floor(this.b) <= 10 ? this.b / 3294.0 : Math.pow(this.b / 269.0 + 0.0513, 2.4);
|
||||
return 0.2126 * rg + 0.7152 * gg + 0.0722 * bg;
|
||||
}
|
||||
|
||||
contrast(other) {
|
||||
let big = this.luminance();
|
||||
let small = other.luminance();
|
||||
|
||||
if (big < small) {
|
||||
const temp = big;
|
||||
big = small;
|
||||
small = temp;
|
||||
}
|
||||
|
||||
return (big + 0.05) / (small + 0.05);
|
||||
}
|
||||
|
||||
withAlpha(alpha) {
|
||||
return new Color(this.r, this.g, this.b, alpha);
|
||||
}
|
||||
|
||||
// Return a color close to this but also legible against bg_color.
|
||||
// This is used in some places to make the UI minimally useable even
|
||||
// if the user picks stupid bg/accent colors.
|
||||
makeLegible(bg_color) {
|
||||
let ideal = bg_color.textColor();
|
||||
let contrast = bg_color.contrast(this);
|
||||
// This has a bunch of magic constants that were choosen experimentally.
|
||||
// Don't fuck with them.
|
||||
let contrast_threshold = 2.5;
|
||||
if (contrast > contrast_threshold) {
|
||||
return this;
|
||||
} else {
|
||||
const scale = 1 - (contrast - 1) / (contrast_threshold - 1);
|
||||
return this.tint(ideal, scale * 0.5);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function setColors(background, accent) {
|
||||
if (!background || (typeof(background) == 'string' && background.length == 0)) {
|
||||
background = Color.defaultBackground();
|
||||
} else if (typeof(background) == 'string') {
|
||||
background = Color.parse(background);
|
||||
}
|
||||
|
||||
if (!accent || (typeof(accent) == 'string' && accent.length == 0)) {
|
||||
accent = Color.defaultAccent();
|
||||
} else if (typeof(accent) == 'string') {
|
||||
accent = Color.parse(accent);
|
||||
}
|
||||
|
||||
themeStyleContainer.dataset.backgroundColor = background.toHex();
|
||||
themeStyleContainer.dataset.accentColor = accent.toHex();
|
||||
|
||||
let text = background.textColor();
|
||||
accent = accent.makeLegible(background);
|
||||
let linkAccent = accent.makeLegible(background);
|
||||
|
||||
let drop_shadow = Color.black();
|
||||
let bg_lum = background.luminance();
|
||||
if (bg_lum < 0.01) {
|
||||
drop_shadow = Color.white().tint(Color.black(), 0.9);
|
||||
}
|
||||
|
||||
let icon_warn = Color.parse("#f57f17").makeLegible(background);
|
||||
let input_label = Color.parse("#9e9e9e").makeLegible(background);
|
||||
let danger_text = Color.parse("#FF0000").tint(text, 0.25).makeLegible(background);
|
||||
let danger_bg = danger_text.textColor();
|
||||
let blue_icon = Color.parse("#0D47A1").makeLegible(background);
|
||||
|
||||
const styleSheet = {
|
||||
":root": {
|
||||
// Cls colors
|
||||
"--cls-color": text.toCss(),
|
||||
"--cls-sec-color": text.toCss(),
|
||||
"--cls-size": "2rem",
|
||||
"--cls-margin": "1rem",
|
||||
"--cls-speed": "4s",
|
||||
// Texts & icons colors
|
||||
"--helper-text-color": text.tint(background, 0.25).toCss(),
|
||||
"--text-primary-color": text.shift(0.13).toCss(),
|
||||
"--text-secondary-color": text.shift(0.26).toCss(),
|
||||
"--divider-color": text.withAlpha(0.12).toCss(),
|
||||
"--shadow-color": drop_shadow.withAlpha(0.14).toCss(),
|
||||
"--icon-color": text.shift(0.13).withAlpha(0.6).toCss(),
|
||||
"--icon-warn": icon_warn.toCss(),
|
||||
"--input-label": input_label.toCss(),
|
||||
"--danger-text": danger_text.toCss(),
|
||||
"--danger-bg": danger_text.textColor().toCss(),
|
||||
"--blue-icon": blue_icon.toCss(),
|
||||
// Accent colors
|
||||
"--accent-color": accent.toCss(),
|
||||
"--accent-dark": accent.darken(0.3).toCss(),
|
||||
"--accent-text-color": accent.textColor().toCss(),
|
||||
"--accent-focus-color": accent.saturate(1.2).toCss(),
|
||||
"--accent-hover-color": accent.textColor().toCss(),
|
||||
"--accent-bg-hover-color": accent.toCss(),
|
||||
"--accent-link-color": linkAccent.toCss(),
|
||||
"--progress-bg": accent.tint(background, 0.75).toCss(),
|
||||
"--accent-ripple": linkAccent.withAlpha(0.2).toCss(),
|
||||
// Background colors
|
||||
"--background-color": background.toCss(),
|
||||
"--background-sidenav-color": background.shift(0.03).toCss(),
|
||||
"--background-hover": background.shift(0.065),
|
||||
"--background-modal-color": background.tint(text, 0.02).toCss(),
|
||||
"--background-primary-color": background.shift(0.02).toCss(),
|
||||
},
|
||||
};
|
||||
|
||||
const properties = Object.keys(styleSheet).map((selector) => {
|
||||
const selectorProperties = Object.keys(styleSheet[selector]).map(
|
||||
(property) => ` ${property}: ${styleSheet[selector][property]};`,
|
||||
);
|
||||
|
||||
return `${selector} {\n${selectorProperties.join("\n")}\n}`;
|
||||
});
|
||||
|
||||
themeStyleContainer.innerHTML = properties.join("\n");
|
||||
}
|
||||
|
||||
setColors(themeStyleContainer.dataset.backgroundColor, themeStyleContainer.dataset.accentColor);
|
||||
@@ -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 = " ";
|
||||
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 %}
|
||||
@@ -0,0 +1,171 @@
|
||||
{% extends "layouts/base.jinja2" %}
|
||||
|
||||
{% block head %}
|
||||
{{ super() }}
|
||||
<style type="text/css">
|
||||
html,
|
||||
body {
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
#wrapper {
|
||||
min-height: 100%;
|
||||
}
|
||||
</style>
|
||||
<script type="text/javascript">
|
||||
toasted = false;
|
||||
running = false;
|
||||
|
||||
function closeHello() {
|
||||
$('#hello_card').hide();
|
||||
$('#hello_card').data('closed', true);
|
||||
}
|
||||
|
||||
function refreshLogs() {
|
||||
if ($('#pause').is(':checked')) {
|
||||
return;
|
||||
}
|
||||
var complete = false;
|
||||
if (running) {
|
||||
return;
|
||||
}
|
||||
running = true;
|
||||
$.ajax({
|
||||
url: 'log?format=colored&catchup=true',
|
||||
success: function (data) {
|
||||
console.log(data);
|
||||
if (data.length > 0) {
|
||||
$('#logwindow').append(data);
|
||||
$('#log_container').animate(
|
||||
{ scrollTop: $('#log_container').prop('scrollHeight') },
|
||||
250
|
||||
);
|
||||
}
|
||||
if (toasted) {
|
||||
M.Toast.dismissAll();
|
||||
toasted = false;
|
||||
}
|
||||
},
|
||||
error: function () {
|
||||
if (!toasted) {
|
||||
toasted = true;
|
||||
M.toast({
|
||||
html: 'Lost connection to add-on, will keep trying to connect...',
|
||||
displayLength: 9999999,
|
||||
});
|
||||
}
|
||||
},
|
||||
complete: function () {
|
||||
running = false;
|
||||
},
|
||||
timeout: 1000,
|
||||
});
|
||||
}
|
||||
|
||||
$(document).ready(function () {
|
||||
$('#hello_card').data('closed', false);
|
||||
|
||||
$.get('log?format=colored', function (data) {
|
||||
console.log('Loading logs');
|
||||
$('#logwindow').html(data);
|
||||
$('#log_container').animate(
|
||||
{ scrollTop: $('#log_container').prop('scrollHeight') },
|
||||
250
|
||||
);
|
||||
}).fail(function (e) {
|
||||
errorToast(e);
|
||||
});
|
||||
window.setInterval(refreshLogs, 2000);
|
||||
});
|
||||
|
||||
function errorToast(error) {
|
||||
message = '';
|
||||
details = '';
|
||||
var isError = false;
|
||||
if (
|
||||
error.hasOwnProperty('message') &&
|
||||
error.hasOwnProperty('error_details')
|
||||
) {
|
||||
// Its an error messag form the server
|
||||
message = error.message;
|
||||
details = error.error_details;
|
||||
isError = true;
|
||||
} else if (
|
||||
error.hasOwnProperty('status') &&
|
||||
error.hasOwnProperty('statusText') &&
|
||||
error.hasOwnProperty('responseText')
|
||||
) {
|
||||
// Its an HTTP error, so format appropriately
|
||||
message =
|
||||
'Got unexpected HTTP error ' + error.status + ': ' + error.statusText;
|
||||
details = error.responseText;
|
||||
isError = true;
|
||||
} else if (error.hasOwnProperty('error')) {
|
||||
message = error.error;
|
||||
details = JSON.stringify(error, undefined, 2);
|
||||
isError = true;
|
||||
} else if (error.hasOwnProperty('message')) {
|
||||
message = error.message;
|
||||
isError = false;
|
||||
} else {
|
||||
message = 'Got an unexpected error';
|
||||
details = JSON.stringify(error, undefined, 2);
|
||||
isError = true;
|
||||
}
|
||||
|
||||
button_text = '';
|
||||
if (details.length > 0) {
|
||||
button_text =
|
||||
" <a class='btn-flat' target='_blank' onClick=\"$('#error_details_card').fadeIn(400);return false;\">Details</a>";
|
||||
$('#error_details_paragraph').text(details);
|
||||
}
|
||||
|
||||
console.log(error);
|
||||
toast(message + button_text);
|
||||
return isError;
|
||||
}
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="wrapper black" style="height: 87%; display: flex; flex-direction: column">
|
||||
<div>
|
||||
<div class="row">
|
||||
<div class="col s12" style="max-width: 800px">
|
||||
<div id="hello_card" class="card blue-grey darken-3 white-text">
|
||||
<div class="card-content">
|
||||
<span class="card-title">A Message from the Developer</span>
|
||||
<p>Thanks for taking a look at the logs! I did my best to make this work well, but if you're
|
||||
here then maybe you've run into a problem. I'm only human after all. You can file an
|
||||
issue on the project <a
|
||||
href="https://github.com/sabeechen/hassio-google-drive-backup/issues" target="_blank" rel="noreferrer" class="light-blue-text">GitHub issue
|
||||
tracker</a>. I take bugs pretty seriously and I want this add-on to work well for
|
||||
you. You can download a copy of these logs to attach to an issue by clicking <a class="light-blue-text"
|
||||
href="log?format=download">here</a>.</p>
|
||||
<br>
|
||||
<p style="margin-left: 20px;">— Stephen <3</p>
|
||||
</div>
|
||||
<div class="card-action">
|
||||
<a href="#" class="btn-flat" onclick="closeHello();"><i class="material-icons">close</i>Dismiss</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col s12" style="display: flex; align-items: center;">
|
||||
<a class="btn-flat" href="log?format=download"><i class="material-icons left">file_download</i>Download logs</a>
|
||||
<label class="white-text" style="margin-top: 5px; margin-left: 20px;">
|
||||
<input type="checkbox" name="pause" id="pause" class="filled-in" />
|
||||
<span>Pause output</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="log_container" class="black white-text" style="overflow:scroll; width: 100%;">
|
||||
<pre id="logwindow" style="margin: 10px; line-height: 1;">
|
||||
Loading logs...
|
||||
</pre>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,178 @@
|
||||
{% import "layouts/macros.jinja2" as macros %}
|
||||
{% extends "layouts/base-server.jinja2" %}
|
||||
|
||||
{% block head %}
|
||||
{{ super() }}
|
||||
<script async defer src="https://apis.google.com/js/api.js" onload="onApiLoad()"></script>
|
||||
<script async defer src="https://accounts.google.com/gsi/client" onload="gisLoaded()"></script>
|
||||
<script type="text/javascript">
|
||||
var clientId = "{{ client_id }}";
|
||||
var appId = "{{ app_id }}";
|
||||
let scope = 'https://www.googleapis.com/auth/drive.file';
|
||||
var picker_redirect = {{ do_redirect }};
|
||||
var pickerApiLoaded = false;
|
||||
let tokenClient = null;
|
||||
let accessToken = null;
|
||||
|
||||
|
||||
function onApiLoad() {
|
||||
gapi.load('picker', () => {pickerInited = true;});
|
||||
}
|
||||
|
||||
function gisLoaded() {
|
||||
|
||||
tokenClient = google.accounts.oauth2.initTokenClient({
|
||||
client_id: clientId,
|
||||
scope: scope,
|
||||
callback: '', // defined later
|
||||
});
|
||||
gisInited = true;
|
||||
}
|
||||
|
||||
function loadPicker() {
|
||||
// Request an access token
|
||||
tokenClient.callback = async (response) => {
|
||||
if (response.error !== undefined) {
|
||||
throw (response);
|
||||
}
|
||||
accessToken = response.access_token;
|
||||
buildPicker();
|
||||
};
|
||||
|
||||
if (accessToken === null) {
|
||||
// Prompt the user to select a Google Account and ask for consent to share their data
|
||||
// when establishing a new session.
|
||||
tokenClient.requestAccessToken({prompt: 'consent'});
|
||||
} else {
|
||||
// Skip display of account chooser and consent dialog for an existing session.
|
||||
tokenClient.requestAccessToken({prompt: ''});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function handleAuthResult(authResult) {
|
||||
if (authResult && !authResult.error) {
|
||||
oauthToken = authResult.access_token;
|
||||
createPicker();
|
||||
}
|
||||
}
|
||||
|
||||
// Create and render a Picker object for searching images.
|
||||
function buildPicker(builder) {
|
||||
var mydrive = new google.picker.DocsView(google.picker.ViewId.DOCS)
|
||||
.setMode(google.picker.DocsViewMode.LIST)
|
||||
.setIncludeFolders(true)
|
||||
.setSelectFolderEnabled(true)
|
||||
.setParent('root')
|
||||
.setLabel("My Drive");
|
||||
var sharedWithMe = new google.picker.DocsView(google.picker.FOLDERS)
|
||||
.setMode(google.picker.DocsViewMode.LIST)
|
||||
//.setIncludeFolders(true)
|
||||
.setSelectFolderEnabled(true)
|
||||
.setOwnedByMe(true)
|
||||
.setQuery("*")
|
||||
.setLabel("Shared With Me");
|
||||
var sharedDrives = new google.picker.DocsView(google.picker.ViewId.DOCS)
|
||||
.setEnableDrives(true)
|
||||
.setMode(google.picker.DocsViewMode.LIST)
|
||||
.setIncludeFolders(true)
|
||||
.setSelectFolderEnabled(true);
|
||||
var recent = new google.picker.DocsView(google.picker.ViewId.RECENTLY_PICKED)
|
||||
.setMode(google.picker.DocsViewMode.LIST)
|
||||
.setIncludeFolders(true)
|
||||
.setSelectFolderEnabled(true);
|
||||
var picker = new google.picker.PickerBuilder()
|
||||
.disableFeature(google.picker.Feature.NAV_HIDDEN)
|
||||
.disableFeature(google.picker.Feature.MINE_ONLY)
|
||||
.enableFeature(google.picker.Feature.SUPPORT_DRIVES)
|
||||
.setAppId(appId)
|
||||
.setOAuthToken(accessToken)
|
||||
.addView(mydrive)
|
||||
//.addView(sharedWithMe)
|
||||
.addView(sharedDrives)
|
||||
.addView(recent)
|
||||
.setTitle("Choose a backup folder")
|
||||
.setCallback(pickerCallback)
|
||||
.build();
|
||||
picker.setVisible(true);
|
||||
}
|
||||
|
||||
function getQueryParams(params) {
|
||||
let href = window.location;
|
||||
//this expression is to get the query strings
|
||||
let reg = new RegExp('[?&]' + params + '=([^&#]*)', 'i');
|
||||
let queryString = reg.exec(href);
|
||||
return queryString ? queryString[1] : null;
|
||||
};
|
||||
|
||||
// A simple callback implementation.
|
||||
function pickerCallback(data) {
|
||||
if (data.action == google.picker.Action.PICKED) {
|
||||
var message = "";
|
||||
if (data.docs.length == 0) {
|
||||
message = "No document was selected. Please try selecting a folder again."
|
||||
} else if (data.docs[0].mimeType != "application/vnd.google-apps.folder") {
|
||||
// Has to be a folder. Doesn't make sense otherwise.
|
||||
message = "You can only upload backups to a folder. Please select a folder instead."
|
||||
}
|
||||
|
||||
if (message.length > 0) {
|
||||
alert(message);
|
||||
} else if(picker_redirect) {
|
||||
// Redirect back to the uer's home assistant with the now authorized folder id.
|
||||
window.location.href = decodeURIComponent(getQueryParams("returnto")) + "?id=" + data.docs[0].id
|
||||
} else {
|
||||
$("#drive_folder_name").html(data.docs[0].name);
|
||||
$("#drive_folder_id").val(data.docs[0].id);
|
||||
$("#copy_folder_card").show();
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
{% endblock %}
|
||||
{% block content %}
|
||||
{% call macros.header(version) %}{% endcall %}
|
||||
<main>
|
||||
<div class="container">
|
||||
<div class="section">
|
||||
<div class="row" id="hello_card">
|
||||
<div class="col s12 m9">
|
||||
<div class="card">
|
||||
<div class="card-content">
|
||||
<span class="card-title">Backup Folder Picker</span>
|
||||
<p>Use the button below to log into your google account and select a folder from your Google Drive.
|
||||
<ul class="browser-default">
|
||||
<li>The button opens a pop-up that creates a session with Google's services and then redirects back here. If your browser has extensions that prevent that kind of thing (eg aggressive pop-up blockers, cross-site cookie restriction, Firefox containers, etc) then you might need to disable them to make the authorization work. This is just how Google does authorization.</li>
|
||||
<li>If the folder you want to use doesn't exist yet, navigate to <a href="https://drive.google.com" target="_blank">Google Drive</a> and create it there first. The folder picker doesn't let you create new folders.</li>
|
||||
<li>The folder you select is where the addon will store backups from now on. This should be what you want if you're on this page.</li>
|
||||
<li>If you'd like to know why this page has to be hosted on an external domain, <a target="_blank" href="https://github.com/sabeechen/hassio-google-drive-backup/blob/master/hassio-google-drive-backup/AUTHENTICATION.md">click here to learn more</a>.</li>
|
||||
</ul>
|
||||
</p>
|
||||
<div class="card-action">
|
||||
<a target="_blank" class="btn-flat" onclick="loadPicker();">Choose Folder</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card" id="copy_folder_card" style="display: none;">
|
||||
<div class="card-content">
|
||||
<span class="card-title">Folder Chosen</span>
|
||||
<p>Below is the ID of the folder you chose, "<span id="drive_folder_name"></span>". Copy this ID back into the settings dialog that brought you here
|
||||
to start syncing backups into it. You can close this window once its saved.
|
||||
<br>
|
||||
<textarea readonly id="drive_folder_id">
|
||||
</textarea>
|
||||
</p>
|
||||
<div class="card-action">
|
||||
<a id="copy_button" onclick="copyFromInput('drive_folder_id')" class="btn-flat">
|
||||
<i class="material-icons">content_copy</i>Copy
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
{% endblock %}
|
||||
|
||||
@@ -0,0 +1,240 @@
|
||||
{% extends "layouts/base.jinja2" %}
|
||||
|
||||
{% block content %}
|
||||
<main>
|
||||
<div class="container">
|
||||
<h1>Privacy Policy</h1>
|
||||
|
||||
<p>Effective date: April 23, 2019</p>
|
||||
|
||||
<p>
|
||||
Home Assistant Google Drive Backup ("us", "we", or "our") operates the
|
||||
https://habackup.io/ website (hereinafter referred to as the "Service").
|
||||
</p>
|
||||
|
||||
<p>
|
||||
This page informs you of our policies regarding the collection, use, and
|
||||
disclosure of personal data when you use our Service and the choices you
|
||||
have associated with that data.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
We use your data to provide and improve the Service. By using the Service,
|
||||
you agree to the collection and use of information in accordance with this
|
||||
policy. Unless otherwise defined in this Privacy Policy, the terms used in
|
||||
this Privacy Policy have the same meanings as in our Terms and Conditions,
|
||||
accessible from https://habackup.io/
|
||||
</p>
|
||||
|
||||
<h3>Definitions</h3>
|
||||
<ul>
|
||||
<li>
|
||||
<p><strong>Service</strong></p>
|
||||
<p>
|
||||
Service is the https://habackup.io/ website operated by Home Assistant
|
||||
Google Drive Backup
|
||||
</p>
|
||||
</li>
|
||||
<li>
|
||||
<p><strong>Personal Data</strong></p>
|
||||
<p>
|
||||
Personal Data means data about a living individual who can be
|
||||
identified from those data (or from those and other information either
|
||||
in our possession or likely to come into our possession).
|
||||
</p>
|
||||
</li>
|
||||
<li>
|
||||
<p><strong>Usage Data</strong></p>
|
||||
<p>
|
||||
Usage Data is data collected automatically either generated by the use
|
||||
of the Service or from the Service infrastructure itself (for example,
|
||||
by authenticating the add-on with Google Drive).
|
||||
</p>
|
||||
</li>
|
||||
<li>
|
||||
<p><strong>The Add-on</strong></p>
|
||||
<p>
|
||||
The add-on is the component of this software that runs on your home
|
||||
Assistant instance. It is responsible for creating new backups and
|
||||
uploading them to your personal Google Drive account.
|
||||
</p>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<h3>Information Collection and Use</h3>
|
||||
<p>
|
||||
We collect several different types of information for the purposes of
|
||||
providing the Service to you.
|
||||
</p>
|
||||
|
||||
<h3>Types of Data Collected</h3>
|
||||
|
||||
<h4>Personal Data</h4>
|
||||
<p>
|
||||
When using the service, you may connect your Google Drive account. When
|
||||
doing so you may be required to provide your username, password, and other
|
||||
authentication information to Google's authentication servers. Google
|
||||
returns an opaque token to the service that can be used to access files
|
||||
created in Google Drive. This token is passed back to the add-on and not
|
||||
maintained by the Service, but it is briefly in the Service's possession.
|
||||
</p>
|
||||
|
||||
<h4>Usage Data</h4>
|
||||
<p>
|
||||
You may opt in to send error reports generated by the add-on to the
|
||||
Service. This is the full text of error messages generated by the add-on
|
||||
and version identifiers for the add-on
|
||||
</p>
|
||||
<p>
|
||||
Traffic to and from Google Drive generated by the add-on is aggregated
|
||||
among all users. Information about the total number of requests and
|
||||
information identifying the source of the requests (such as your IP
|
||||
Address) is maintained by Google and made available to the Service.
|
||||
</p>
|
||||
|
||||
<h3>Use of Data</h3>
|
||||
|
||||
<p>
|
||||
Home Assistant Google Drive Backup uses the collected data for various
|
||||
purposes:
|
||||
</p>
|
||||
<ul>
|
||||
<li>To provide and maintain the Service</li>
|
||||
<li>To provide customer care and support</li>
|
||||
<li>
|
||||
To provide analysis or valuable information so that we can improve the
|
||||
Service
|
||||
</li>
|
||||
<li>To monitor the usage of the Service</li>
|
||||
<li>To detect, prevent and address technical issues</li>
|
||||
</ul>
|
||||
|
||||
<h3>Transfer of Data</h3>
|
||||
<p>
|
||||
Your information, including Personal Data, may be transferred to - and
|
||||
maintained on - computers located outside of your state, province, country
|
||||
or other governmental jurisdiction where the data protection laws may
|
||||
differ than those from your jurisdiction.
|
||||
</p>
|
||||
<p>
|
||||
If you are located outside United States and choose to provide information
|
||||
to us, please note that we transfer the data, including Personal Data, to
|
||||
United States and process it there.
|
||||
</p>
|
||||
<p>
|
||||
Your consent to this Privacy Policy followed by your submission of such
|
||||
information represents your agreement to that transfer.
|
||||
</p>
|
||||
<p>
|
||||
Home Assistant Google Drive Backup will take all steps reasonably
|
||||
necessary to ensure that your data is treated securely and in accordance
|
||||
with this Privacy Policy and no transfer of your Personal Data will take
|
||||
place to an organization or a country unless there are adequate controls
|
||||
in place including the security of your data and other personal
|
||||
information.
|
||||
</p>
|
||||
|
||||
<h3>Disclosure of Data</h3>
|
||||
|
||||
<h3>Legal Requirements</h3>
|
||||
<p>
|
||||
Home Assistant Google Drive Backup may disclose your Personal Data in the
|
||||
good faith belief that such action is necessary to:
|
||||
</p>
|
||||
<ul>
|
||||
<li>To comply with a legal obligation</li>
|
||||
<li>
|
||||
To protect and defend the rights or property of Home Assistant Google
|
||||
Drive Backup
|
||||
</li>
|
||||
<li>
|
||||
To prevent or investigate possible wrongdoing in connection with the
|
||||
Service
|
||||
</li>
|
||||
<li>
|
||||
To protect the personal safety of users of the Service or the public
|
||||
</li>
|
||||
<li>To protect against legal liability</li>
|
||||
</ul>
|
||||
|
||||
<p>
|
||||
As a European citizen, under GDPR, you have certain individual rights.
|
||||
You can learn more about these guides in the
|
||||
<a
|
||||
href="https://termsfeed.com/blog/gdpr/#Individual_Rights_Under_the_GDPR"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>GDPR Guide</a
|
||||
>.
|
||||
</p>
|
||||
|
||||
<h3>Security of Data</h3>
|
||||
<p>
|
||||
The security of your data is important to us but remember that no method
|
||||
of transmission over the Internet or method of electronic storage is 100%
|
||||
secure. While we strive to use commercially acceptable means to protect
|
||||
your Personal Data, we cannot guarantee its absolute security.
|
||||
</p>
|
||||
|
||||
<h3>Service Providers</h3>
|
||||
<p>
|
||||
We may employ third-party companies and individuals to facilitate our
|
||||
Service ("Service Providers"), to provide the Service on our behalf, to
|
||||
perform Service-related services or to assist us in analyzing how our
|
||||
Service is used.
|
||||
</p>
|
||||
<p>
|
||||
These third parties have access to your Personal Data only to perform
|
||||
these tasks on our behalf and are obligated not to disclose or use it for
|
||||
any other purpose.
|
||||
</p>
|
||||
|
||||
<h3>Links to Other Sites</h3>
|
||||
<p>
|
||||
Our Service may contain links to other sites that are not operated by us.
|
||||
If you click a third-party link, you will be directed to that third
|
||||
party's site. We strongly advise you to review the Privacy Policy of every
|
||||
site you visit.
|
||||
</p>
|
||||
<p>
|
||||
We have no control over and assume no responsibility for the content,
|
||||
privacy policies or practices of any third-party sites or services.
|
||||
</p>
|
||||
|
||||
<h3>Children's Privacy</h3>
|
||||
<p>Our Service does not address anyone under the age of 18 ("Children").</p>
|
||||
<p>
|
||||
We do not knowingly collect personally identifiable information from
|
||||
anyone under the age of 18. If you are a parent or guardian and you are
|
||||
aware that your Child has provided us with Personal Data, please contact
|
||||
us. If we become aware that we have collected Personal Data from children
|
||||
without verification of parental consent, we take steps to remove that
|
||||
information from our servers.
|
||||
</p>
|
||||
|
||||
<h3>Changes to This Privacy Policy</h3>
|
||||
<p>
|
||||
We may update our Privacy Policy from time to time. We will notify you of
|
||||
any changes by posting the new Privacy Policy on this page.
|
||||
</p>
|
||||
<p>
|
||||
We will let you know via email and/or a prominent notice on our Service,
|
||||
prior to the change becoming effective and update the "effective date" at
|
||||
the top of this Privacy Policy.
|
||||
</p>
|
||||
<p>
|
||||
You are advised to review this Privacy Policy periodically for any
|
||||
changes. Changes to this Privacy Policy are effective when they are posted
|
||||
on this page.
|
||||
</p>
|
||||
|
||||
<h3>Contact Us</h3>
|
||||
<p>
|
||||
If you have any questions about this Privacy Policy, please contact us:
|
||||
</p>
|
||||
<ul>
|
||||
<li>By email: stephen@beechens.com</li>
|
||||
</ul>
|
||||
</div>
|
||||
</main>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,49 @@
|
||||
{% extends "layouts/base.jinja2" %}
|
||||
|
||||
{% block head %}
|
||||
{{ super() }}
|
||||
<script type="text/javascript">
|
||||
function redirect() {
|
||||
let searchParams = new URLSearchParams(window.location.search);
|
||||
if (searchParams.has('to')) {
|
||||
window.location.assign(searchParams.get('to'));
|
||||
}
|
||||
else {
|
||||
window.location.assign("{{ url }}");
|
||||
}
|
||||
}
|
||||
|
||||
function sleep (time) {
|
||||
return new Promise((resolve) => setTimeout(resolve, time));
|
||||
}
|
||||
|
||||
$(document).ready(function () {
|
||||
sleep(3000).then(() => {
|
||||
redirect();
|
||||
});
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
{% block navbar %}
|
||||
<!-- Remove navbar -->
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container">
|
||||
<div class="row" id="hello_card">
|
||||
<div class="col s12 m9">
|
||||
<div class="card">
|
||||
<div class="card-content">
|
||||
<span class="card-title">Redirecting...</span>
|
||||
<p>You should be redirected to <a href="{{ url }}">{{ url }}</a> in a moment. Click below to be redirected now.
|
||||
</p>
|
||||
<div class="card-action">
|
||||
<a target="_blank" class="btn-flat" onclick="redirect(); return false;">go now</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,52 @@
|
||||
{% import "layouts/macros.jinja2" as macros %}
|
||||
{% extends "layouts/base-server.jinja2" %}
|
||||
|
||||
{% block head %}
|
||||
{{ super() }}
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{% call macros.header(version) %}{% endcall %}
|
||||
<main>
|
||||
<div class="section no-pad-bot" id="index-banner">
|
||||
<div class="container">
|
||||
<br><br>
|
||||
<h2 class="header center">Simple Backup for Home Assistant</h1>
|
||||
<div class="row center">
|
||||
<h6 class="header col s12 light">A simple way to upload Home Assistant backups to
|
||||
<a href="https://drive.google.com/drive/my-drive">Google Drive™</a>. Keep your backups secured in Google's
|
||||
highly redundant and super reliable infrastructure. Stay safe! You don't want a random hard drive failure to keep you from turning on your lights.
|
||||
</h5>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div>
|
||||
<ul class="browser-default" style="display:table; margin:0 auto;">
|
||||
<li>Super easy setup and install</li>
|
||||
<li>UI targeted towards new users or anyone sick of writing YAML files</li>
|
||||
<li>Privacy-centric design philosophy</li>
|
||||
<li>Highly configurable</li>
|
||||
<li><a href="https://github.com/sabeechen/hassio-google-drive-backup#configuration">Thorough documentation</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row center">
|
||||
<div id="option1" class="col m12 l6 offset-l3 center center-align">
|
||||
<a target="_blank" id="authenticate-button" style="margin: 10px" href="https://github.com/sabeechen/hassio-google-drive-backup#detailed-install-instructions" class="btn-large btn-flat btn-high-vis">
|
||||
<i class="material-icons">assignment</i>
|
||||
Installation Instructions
|
||||
</a>
|
||||
|
||||
</div>
|
||||
<div id="option2" class="col s12 m6 offset-m3 default-hidden">
|
||||
Follow the <a target="_blank"
|
||||
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. This is a much more complicated and time
|
||||
consuming approach, but doesn't require you to authenticate yourself with an external domain.
|
||||
<br />
|
||||
</div>
|
||||
</div>
|
||||
<br><br>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,45 @@
|
||||
{% import "layouts/macros.jinja2" as macros %}
|
||||
{% extends "layouts/base-server.jinja2" %}
|
||||
|
||||
{% block head %}
|
||||
{{ super() }}
|
||||
<script type="text/javascript">
|
||||
function redirect() {
|
||||
let searchParams = new URLSearchParams(window.location.search);
|
||||
if (searchParams.has('to')) {
|
||||
window.location.assign(searchParams.get('to'));
|
||||
}
|
||||
else {
|
||||
window.location.assign("{{ redirect_url }}");
|
||||
}
|
||||
}
|
||||
|
||||
function sleep (time) {
|
||||
return new Promise((resolve) => setTimeout(resolve, time));
|
||||
}
|
||||
|
||||
$(document).ready(function () {
|
||||
sleep(1500).then(() => {
|
||||
redirect();
|
||||
});
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{% call macros.header(version) %}{% endcall %}
|
||||
<main>
|
||||
<div class="section no-pad-bot" id="index-banner">
|
||||
<div class="container">
|
||||
<br><br>
|
||||
<h5 class="header center">That should be it.</h5>
|
||||
<div class="row center">
|
||||
<h6 class="header col s12 light">
|
||||
Redirecting you back to your Home Assistant server at <a href="{{ redirect_url }}" >{{ redirect_text }}</a>
|
||||
</h6>
|
||||
</div>
|
||||
<br><br>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,86 @@
|
||||
{% extends "layouts/base.jinja2" %}
|
||||
|
||||
{% block content %}
|
||||
<main>
|
||||
<div class="container">
|
||||
<h1>Terms and Conditions</h1>
|
||||
|
||||
<p>Last updated: April 23, 2019</p>
|
||||
|
||||
<p>
|
||||
Please read these Terms and Conditions ("Terms", "Terms and Conditions")
|
||||
carefully before using the https://habackup.io/ website (the "Service")
|
||||
operated by Home Assistant Google Drive Backup ("us", "we", or "our").
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Your access to and use of the Service is conditioned on your acceptance of
|
||||
and compliance with these Terms. These Terms apply to all visitors, users
|
||||
and others who access or use the Service.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
By accessing or using the Service you agree to be bound by these Terms. If
|
||||
you disagree with any part of the terms then you may not access the
|
||||
Service.
|
||||
</p>
|
||||
|
||||
<h3>Links To Other Web Sites</h3>
|
||||
|
||||
<p>
|
||||
Our Service may contain links to third-party web sites or services that
|
||||
are not owned or controlled by Home Assistant Google Drive Backup.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Home Assistant Google Drive Backup has no control over, and assumes no
|
||||
responsibility for, the content, privacy policies, or practices of any
|
||||
third party web sites or services. You further acknowledge and agree that
|
||||
Home Assistant Google Drive Backup shall not be responsible or liable,
|
||||
directly or indirectly, for any damage or loss caused or alleged to be
|
||||
caused by or in connection with use of or reliance on any such content,
|
||||
goods or services available on or through any such web sites or services.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
We strongly advise you to read the terms and conditions and privacy
|
||||
policies of any third-party web sites or services that you visit.
|
||||
</p>
|
||||
|
||||
<h3>Governing Law</h3>
|
||||
|
||||
<p>
|
||||
These Terms shall be governed and construed in accordance with the laws of
|
||||
Colorado, United States, without regard to its conflict of law provisions.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Our failure to enforce any right or provision of these Terms will not be
|
||||
considered a waiver of those rights. If any provision of these Terms is
|
||||
held to be invalid or unenforceable by a court, the remaining provisions
|
||||
of these Terms will remain in effect. These Terms constitute the entire
|
||||
agreement between us regarding our Service, and supersede and replace any
|
||||
prior agreements we might have between us regarding the Service.
|
||||
</p>
|
||||
|
||||
<h3>Changes</h3>
|
||||
|
||||
<p>
|
||||
We reserve the right, at our sole discretion, to modify or replace these
|
||||
Terms at any time. If a revision is material we will try to provide at
|
||||
least 30 days notice prior to any new terms taking effect. What
|
||||
constitutes a material change will be determined at our sole discretion.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
By continuing to access or use our Service after those revisions become
|
||||
effective, you agree to be bound by the revised terms. If you do not agree
|
||||
to the new terms, please stop using the Service.
|
||||
</p>
|
||||
|
||||
<h3>Contact Us</h3>
|
||||
|
||||
<p>If you have any questions about these Terms, please contact us.</p>
|
||||
</div>
|
||||
</main>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,332 @@
|
||||
{% extends "layouts/base.jinja2" %}
|
||||
|
||||
{% block head %}
|
||||
{% include 'layouts/partials/icons.jinja2' %}
|
||||
{{ super() }}
|
||||
<script type="text/javascript">
|
||||
$(document).ready(function () {
|
||||
processStatusUpdate(bootstrap_update_data);
|
||||
// Every 5 seconds, refresh the metrics form the server.
|
||||
window.setInterval(refreshstats, 5000);
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div id="source-template" style="display: none;">
|
||||
<div class="source-ui">
|
||||
<h6 style="display: flex; align-items: center; margin-top: 0;">
|
||||
<svg class="source-icon">
|
||||
<use xlink:href="#dummy" />
|
||||
</svg>
|
||||
<span>
|
||||
<b class="source_backup_count"></b>
|
||||
<span class="source_title"></span>
|
||||
</span>
|
||||
</h6>
|
||||
<ul class="browser-default" style="margin-top: 5px; margin-bottom: 20px;">
|
||||
<li class="source_retain_label">
|
||||
<span class="source_retain_count"></span> saved indefinitely
|
||||
</li>
|
||||
<li class="source_free_space">
|
||||
<span class="source_free_space_text"></span>
|
||||
<i class="source_free_space_tooltip tooltipped material-icons backup-help-icon">info</i>
|
||||
</li>
|
||||
<li class="source_ignored_label">
|
||||
<span class="source_ignored_count"></span> ignored (<span class="source_ignored_size"></span>)
|
||||
</li>
|
||||
<li class="source-detail-label">
|
||||
<span class="source-detail"></span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div id="backup-template" style="display: none">
|
||||
<div class="backup-ui col s12 m12 l6 xl6">
|
||||
<div id="backup_card" onclick="showDetails(this);return false;" class="card">
|
||||
<div class="snapshot-card">
|
||||
<div style="display: flex; align-items: center;">
|
||||
<div class="archive-icon">
|
||||
<i class="large material-icons backup_icon" style="font-size: 30px;"></i>
|
||||
<div id="loading" class="progress default-hidden" style="position: absolute;">
|
||||
<div class="indeterminate"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div style="min-width: 0;">
|
||||
<h7 id="name" class="truncate" style="font-weight: 600;"></h7>
|
||||
<div>
|
||||
<i class="material-icons backup-card-icon icon-retain tooltipped danger-text" data-position="bottom"
|
||||
data-tooltip="This backup is kept indefinitely until you delete it.">lock</i>
|
||||
<i class="material-icons backup-card-icon icon-warn-delete tooltipped" data-position="bottom"
|
||||
|
||||
data-tooltip="">warning</i>
|
||||
<i class="material-icons backup-card-icon icon-protected tooltipped blue-icon" data-position="bottom"
|
||||
data-tooltip="This backup is password protected">vpn_key</i>
|
||||
<i class="material-icons backup-card-icon tooltipped" id="gen_detail" data-position="bottom"
|
||||
data-tooltip="Override">date_range</i>
|
||||
<span id="size"></span>
|
||||
-
|
||||
<span>
|
||||
<span id="status"></span>
|
||||
<i class="material-icons tooltipped backup-help-icon" id="status-help"
|
||||
data-position="bottom" data-tooltip="error">help</i>
|
||||
</span>
|
||||
</div>
|
||||
<div id="type"></div>
|
||||
<div id="createdAt"></div>
|
||||
<div id="note" style="font-style: italic;" class="truncate tooltipped" data-position="bottom" data-tooltip=""></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class='error' style='display:none' id="toast">You shouldn't see this</div>
|
||||
|
||||
<main style="margin: 0 1rem;">
|
||||
<div style="margin: 20px auto; max-width: 1400px;">
|
||||
<div class="row" style="margin-bottom: 0;">
|
||||
<div class="col s12 m5 l4 xl3">
|
||||
<h5>Backup Statistics</h5>
|
||||
<div style="margin-top: 20px;">
|
||||
<div id="sources">
|
||||
</div>
|
||||
<div class="divider"></div>
|
||||
<p style="display: flex; align-items: center;">
|
||||
<i class="material-icons" style="margin-right: 6px;">timer</i>
|
||||
<span>
|
||||
Last Backup <time datetime="" title="" id="last_backup" style="font-weight: 600;"></time>
|
||||
</span>
|
||||
</p>
|
||||
<p style="display: flex; align-items: center;">
|
||||
<i class="material-icons" style="margin-right: 6px;">update</i>
|
||||
<span>
|
||||
Next Backup in <time datetime="" title="" id="next_backup" style="font-weight: 600;"></time>
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col s12 m7 l8 xl9 row">
|
||||
{% include 'layouts/partials/error-messages.jinja2' %}
|
||||
|
||||
<div class="card default-hidden question-card s12" id="error_reports_card">
|
||||
<div class="card-content">
|
||||
<span class="card-title">
|
||||
<i class="material-icons">info_outline</i>
|
||||
<span>Share Error Reports?</span>
|
||||
</span>
|
||||
<p>If you select yes, the text of unexpected exceptions this add-on produces will be sent to a server
|
||||
maintained by the developer. This is completely optional, but helps give him a heads up for problems
|
||||
with new releases and aids in providing better help messages when things go wrong. If you're unsure,
|
||||
just select "No". You can always change this in the settings menu up on the top right of the page.</p>
|
||||
</div>
|
||||
<div class="card-action">
|
||||
<a target="_blank" class="btn-flat" onClick="errorReports('true');"><i class="material-icons">check</i>Yes</a>
|
||||
<a target="_blank" class="btn-flat" onClick="errorReports('false');"><i class="material-icons">close</i>No</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card default-hidden question-card s12" id="backup_upgrade_card">
|
||||
<div class="card-content">
|
||||
<span class="card-title">
|
||||
<i class="material-icons">info_outline</i>
|
||||
<span>'Snapshots' have been renamed to 'Backups'</span>
|
||||
</span>
|
||||
<p>In August 2021 <a href="https://www.home-assistant.io/blog/2021/08/24/supervisor-update/" target="_blank">the Home Assistant
|
||||
team announced</a> that 'snapshots' will be called 'backups' moving forward. This addon exposes a
|
||||
binary sensor to indicate if snapshots are stale and another sensor that publishes details about backups. Both of the sensors
|
||||
used 'snapshot' in their names and values, so they had to be changed to match the new language. In the spirit of "not breaking
|
||||
your stuff" the addon will only start using the new names and values if you tell it to, just in case you have automations or UI
|
||||
using the old sensor names/values.
|
||||
<ul class="browser-default">
|
||||
<li>If you don't have any automations based on these sensors, you can just click 'Use the new names and values'.</li>
|
||||
<li>If you do, you can either dismiss this message or update your automations/UI to use the new names and values.</li>
|
||||
<li>You can look at <a target="_blank" rel="noreferrer"
|
||||
href="https://github.com/sabeechen/hassio-google-drive-backup/blob/master/hassio-google-drive-backup/BACKUP_AND_SNAPSHOT.md">this document</a> to see exactly how the sensors have changed.</li>
|
||||
<li>You can change this later in the <a href="#!" onclick="loadSettings()"><i class="material-icons inline-icon">settings</i>add-on
|
||||
settings</a> if you choose. Look for it under "Uncommon Options"</li>
|
||||
</ul>
|
||||
</p>
|
||||
</div>
|
||||
<div class="card-action">
|
||||
<a target="_blank" class="btn-flat" onClick="callBackupSnapshot('true');"><i class="material-icons">check</i>Use the new names and values</a>
|
||||
<a target="_blank" class="btn-flat" onClick="callBackupSnapshot('false');"><i class="material-icons">close</i>Keep them as-is</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card s12 default-hidden question-card" id="warn_upgrade_backups_card">
|
||||
<div class="card-content">
|
||||
<span class="card-title">
|
||||
<i class="material-icons">info_outline</i>
|
||||
<span>Ignoring upgrade backups is recommended</span>
|
||||
</span>
|
||||
<p>With your current settings, this addon assumes any backup it finds should be uploaded to Google Drive. This can create undesirable behavior when you upgrade
|
||||
Home Assistant or an addon and an automatic partial backup is created by Home Assistant for the upgrade (this is Home Assistant's default behavior).
|
||||
<div class="card">
|
||||
<div class="card-content">
|
||||
<span class="card-title mini-title">For Example</span>
|
||||
If you save 4 full backups in Google Drive and then upgrade 4 add-ons, the 4 partial backups Home Assistant automatically creates would overwrite ALL of the full backups this add-on made previously leaving you with no
|
||||
full backups to restore from until the next one is made 24 hours or more later. Most people don't want that to happen, as it leaves a possible gap in your backup strategy.
|
||||
</div>
|
||||
</div>
|
||||
</p>
|
||||
<p>
|
||||
To prevent this most people enable the "Ignore upgrade backups" setting in this addon, which will cause it to completely ignore these partial upgrade backups.
|
||||
Moving forward this will be enabled by default for new installations, and it is recommended for everyone. Because you're upgrading from an
|
||||
older version of the addon that had different behavior, you have to choose. You can also enable some other features in the <a href="#!" onclick="loadSettings()"><i class="material-icons inline-icon">settings</i>add-on settings</a> that might be useful for this, such as:
|
||||
<ul class="browser-default">
|
||||
<li>The setting <b>'Delete ignored backups after: X'</b> can be used to automatically delete upgrade backups after some time period, which helps reclaim the space they might use.</li>
|
||||
<li>The <b>'Ignore Other Backups'</b> setting makes the addon ignore <i>any</i> backup it didn't create, including 'upgrade' backups.</li>
|
||||
</ul>
|
||||
If none of this makes any sense to you, just click "Start ignoring upgrade backups" below to make this notification go away.
|
||||
</p>
|
||||
</div>
|
||||
<div class="card-action">
|
||||
<a target="_blank" class="btn-flat" onClick="loadSettings();"><i class="material-icons">settings</i>Open Settings</a>
|
||||
<a target="_blank" class="btn-flat" onClick="dismissIgnoredBackupWarning('true');"><i class="material-icons">check</i>Start ignoring upgrade backups</a>
|
||||
<a target="_blank" class="btn-flat" onClick="dismissIgnoredBackupWarning('false');"><i class="material-icons">close</i>Keep current settings</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card s12 default-hidden question-card" id="warn_creds_deprecated_card">
|
||||
<div class="card-content">
|
||||
<span class="card-title">
|
||||
<i class="material-icons">info_outline</i>
|
||||
<span>Your credentials with Google may expire</span>
|
||||
</span>
|
||||
<p>Google <a href="https://developers.googleblog.com/2022/02/making-oauth-flows-safer.html" target="_blank" rel="noreferrer">recently announced</a> changes they're making
|
||||
to how their OAuth credentials are generated. You previously followed
|
||||
<a href="https://github.com/sabeechen/hassio-google-drive-backup/blob/master/LOCAL_AUTH.md" target="_blank" rel="noreferrer">these instructions</a> to set up credentials
|
||||
the add-on can use to upload to Google Drive. If you did that before <b>March 16 2022</b>, Google is going to make those credentials invalid in <b>October of 2022</b>.
|
||||
If you are affected this means that in October 2022 the add-on will be unable to authorize with Google Drive and your backups will not get uploaded.
|
||||
</p>
|
||||
<p>
|
||||
While annoying, this change is also somewhat expected. Using Google Cloud credentials for a local installation like this is not something Google 'intended' for us to do, it isn't an officially supported way of using their services. We can still make do though.
|
||||
</p>
|
||||
<p>
|
||||
If you are affected, you likely received an email from Google titled 'Migrate your OAuth out-of-band flow'. To make your credentials work, please follow the steps linked here to
|
||||
<a href="https://github.com/sabeechen/hassio-google-drive-backup/blob/master/LOCAL_AUTH.md" target="_blank" rel="noreferrer">make your credentials</a> and reauthorize the addon.
|
||||
You don't need to follow all the steps exactly; you just need to modify what you have to match up with the instructions. In particular please ensure that:
|
||||
<ul class="browser-default">
|
||||
<li>You can skip steps 0, 1, and 2 because you already did them</li>
|
||||
<li>In step 3, ensure your "OAuth Consent Screen" has a Publishing status of "In production". You don't need to create a new consent screen.</li>
|
||||
<li>In step 4, ensure your credentials use application type "TVs and Limited Input Devices". If not, you will need to create new credentials, which will create a new client ID and client secret you can use to reauthorize the addon.</li>
|
||||
</ul>
|
||||
|
||||
Once you do, please <a href="reauthenticate?private=1"></i>reauthorize</a> the add-on with Google Drive. You can also wait until your credentials stop working and regenerate them then. There is no rush.
|
||||
</p>
|
||||
</div>
|
||||
<div class="card-action">
|
||||
<a target="_blank" class="btn-flat" onClick="aknowledgeooboauth();"><i class="material-icons">check</i>Acknowledge (you won't see this message again)</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card default-hidden question-card" id="ingress_upgrade_card">
|
||||
<div class="card-content">
|
||||
<span class="card-title">Use Ingress instead?</span>
|
||||
<p>The latest version of this add-on supports <a target="_blank" rel="noreferrer"
|
||||
href="https://www.home-assistant.io/blog/2019/04/15/hassio-ingress/">ingress</a>. Ingress allows
|
||||
this web interface to be embedded within Home Assistant, which is usually more secure because it
|
||||
delegates authentication to the same mechanisms you've configured in Home Assistant (e.g. SSL and
|
||||
username/password). Currently this interface is being served on an additional port outside of
|
||||
Home Assistant which is less secure.</p>
|
||||
<br />
|
||||
<p><b>Select YES</b> to only use ingress to access this interface. This is recommended, as ingress is
|
||||
almost always more secure.
|
||||
In the future you can access this interface by following the "WEB UI" link from the add-on
|
||||
installation page or embedding it in
|
||||
your Home Assistant sidebar from the same place.</p>
|
||||
<br />
|
||||
<p><b>Select NO</b> to continue exposing a web interface on an open port with the SSL and authentication
|
||||
settings you choose. The port, which defaults to 1627, can be changed from the add-on page in Home
|
||||
Assistant.</p>
|
||||
<br />
|
||||
<p>You can change this option any time from the settings menu. No matter which you choose, the interface
|
||||
will be available through ingress.</p>
|
||||
<br />
|
||||
</div>
|
||||
<div class="card-action">
|
||||
<a target="_blank" class="btn-flat" onClick="exposeServer('false');"><i class="material-icons">check</i>Yes</a>
|
||||
<a target="_blank" class="btn-flat" onClick="exposeServer('true');"><i class="material-icons">close</i>No</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card default-hidden" id="restore_help_card">
|
||||
<div class="card-content">
|
||||
<span class="card-title">Restore a Backup</span>
|
||||
<p>Backups must be restored through the supervisor Backups page. You can restore a backup by
|
||||
navigating to
|
||||
"Settings" > "System" > "Backups" from the Home Assistant Web-UI,
|
||||
or by clicking the button below to navigate there by a direct link. Depending on how you access the Home
|
||||
Assistant Web-UI the direct link may not work, so you'll just have to navigate there.</p>
|
||||
</div>
|
||||
<div class="card-action">
|
||||
<a target="_blank" id="restore_hard_link" class="btn-flat"><i class="material-icons">open_in_new</i>Open Backups</a>
|
||||
<a target="_blank" class="btn-flat modal-close" onClick="$('#restore_help_card').fadeOut(500)"><i class="material-icons">close</i>Dismiss</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card default-hidden question-card" id="ignore_helper_card">
|
||||
<div class="card-content">
|
||||
<span class="card-title">
|
||||
<i class="material-icons">info_outline</i>
|
||||
<span>Double check your ignored backups</span>
|
||||
</span>
|
||||
<p>Because this addon gets a limited view of the world, it might not be able to accurately determine if all of your older backups were created by the addon.
|
||||
Its recommended that you check below to ensure that all of the backups the addon is ignoring now are the ones you intend to have ignored.
|
||||
If not, you can click on an ignored backups to see its details and click "Don't ignore" to have the addon include them in future backup, clean-up, etc.</p>
|
||||
</div>
|
||||
<div class="card-action">
|
||||
<a target="_blank" class="btn-flat" onClick="toggleLinkSlide(this, 'backups_ignored');"><i class="material-icons">remove_red_eye</i>Show/Hide Ignored Backups</a>
|
||||
<a target="_blank" class="btn-flat" onClick="ackCheckIgnoredBackups();"><i class="material-icons">check</i>Acknowledged</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card default-hidden question-card" id="backups_boot_waiting_card">
|
||||
<div class="card-content">
|
||||
<span class="card-title">
|
||||
<i class="material-icons">timer</i>
|
||||
<span>Taking a backup soon</span>
|
||||
</span>
|
||||
<p>Since the addon just started up a few minutes ago, it's going to wait a few minutes before taking a backup to make sure that's what you want. Look below to see exactly how long before the next backup.
|
||||
You can wait for that to happen, change your <a href="#!" onclick="loadSettings()"><i class="material-icons inline-icon">settings</i>add-on
|
||||
settings</a> to something different, or just click the button below to let the automatic backup happen immediately.</p>
|
||||
</div>
|
||||
<div class="card-action">
|
||||
<a target="_blank" class="btn-flat" onClick="allowImmediateBackup();"><i class="material-icons">alarm_on</i>Backup Now</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="default-hidden" id="no_backups_block">
|
||||
<div class="row">
|
||||
<div class='col s1'></div>
|
||||
<div class='col s2 valign-wrapper center'>
|
||||
<i class='material-icons' style="font-size: 90px">info_outline</i>
|
||||
</div>
|
||||
<div class='col s7 center valign-wrapper' style="margin-top: 10px;">
|
||||
<p>You don't have any backups in Google Drive or Home Assistant yet.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="divider"></div>
|
||||
</div>
|
||||
<div id="backups_loading">
|
||||
<div class="row">
|
||||
<div class='col s1'></div>
|
||||
<div class='col s2 valign-wrapper center'>
|
||||
<i class='material-icons' style="font-size: 90px">refresh</i>
|
||||
</div>
|
||||
<div class='col s7 center valign-wrapper' style="margin-top: 10px;">
|
||||
<p>Waiting for backups to load. If you don't see this go away soon, make sure the add-on is still
|
||||
running. Info on this page may be stale until then.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="divider"></div>
|
||||
</div>
|
||||
<div id="backups" class="s12 row"></div>
|
||||
<div class="indent ignored_backup_slider">
|
||||
<a class="btn-flat" href="#" onclick="toggleLinkSlide(this, 'backups_ignored');"><i class="material-icons">expand_more</i> and <span class="ignored_backup_count"></span> ignored backup<span class="ignored_backup_plural">s</span></a>
|
||||
</div>
|
||||
<div id="backups_ignored" class="s12 row" style="display: none;"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
{% endblock %}
|
||||