New Addon

Google BK
This commit is contained in:
2023-04-23 18:17:03 +07:00
parent 1c7fd2b476
commit 702d080a1e
248 changed files with 72745 additions and 0 deletions
@@ -0,0 +1,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;
}
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large Load Diff

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';
}
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
@@ -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

File diff suppressed because one or more lines are too long

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

Binary file not shown.

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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&trade;.
<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
}
});
}));
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large Load Diff

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