Hassio snapshot: download + design tweaks (#972)

* Hassio snapshot download, upload

* some improvements

* Remove event

* Remove upload stuff

* Feedback

* Make downloads as suggested

* More updates

* Add tooltips
This commit is contained in:
c727 2018-03-09 00:52:52 +01:00 committed by Paulus Schoutsen
parent 08d4e0af18
commit a1dbd18a9a
2 changed files with 160 additions and 120 deletions

View File

@ -1,81 +1,117 @@
<link rel='import' href='../../bower_components/polymer/polymer-element.html'>
<link rel='import' href='../../bower_components/paper-dialog/paper-dialog.html'>
<link rel='import' href='../../bower_components/paper-dialog-scrollable/paper-dialog-scrollable.html'>
<link rel='import' href='../../bower_components/app-layout/app-toolbar/app-toolbar.html'>
<link rel='import' href='../../bower_components/paper-icon-button/paper-icon-button.html'>
<link rel='import' href='../../bower_components/paper-checkbox/paper-checkbox.html'>
<link rel='import' href='../../bower_components/paper-button/paper-button.html'>
<link rel="import" href='../../bower_components/paper-input/paper-input.html'>
<link rel='import' href='../../src/resources/ha-style.html'>
<dom-module id='hassio-snapshot'>
<template>
<style include='ha-style'>
<style include='ha-style-dialog'>
paper-dialog {
min-width: 350px;
font-size: 14px;
border-radius: 2px;
}
app-toolbar {
margin: 0;
padding: 0 16px;
color: var(--primary-text-color);
background-color: var(--secondary-background-color);
}
app-toolbar [main-title] {
margin-left: 16px;
}
paper-dialog-scrollable {
margin: 0;
}
paper-checkbox {
display: block;
margin: 4px;
}
.snapshot-details {
color: var(--secondary-text-color);
margin-bottom: 16px;
@media all and (max-width: 450px), all and (max-height: 500px) {
paper-dialog {
max-height: 100%;
height: 100%;
}
app-toolbar {
color: var(--text-primary-color);
background-color: var(--primary-color);
}
}
.addon-version {
.details {
color: var(--secondary-text-color);
}
.download {
color: var(--primary-color);
}
.warning,
.error {
color: var(--google-red-500);
}
@media screen and (max-width: 600px) {
paper-dialog {
position: absolute !important;
bottom: 0; left: 0; right: 0;
border-bottom-left-radius: 0;
border-bottom-right-radius: 0;
margin: 0;
}
}
</style>
<paper-dialog id='dialog' with-backdrop on-iron-overlay-closed='dialogClosed'>
<h2>[[computeName(snapshot)]]</h2>
<div>
<div class='snapshot-details'>
[[computeType(snapshot.type)]] ([[computeSize(snapshot.size)]])<br/>
[[formatDatetime(snapshot.date)]]
</div>
<div>
Home Assistant:
<paper-checkbox checked='{{restoreHass}}'>
Home Assistant [[snapshot.homeassistant]]
<paper-dialog id='dialog' with-backdrop on-iron-overlay-closed='_dialogClosed'>
<app-toolbar>
<paper-icon-button
icon='mdi:close'
dialog-dismiss
></paper-icon-button>
<div main-title>[[_computeName(snapshot)]]</div>
</app-toolbar>
<div class='details'>
[[_computeType(snapshot.type)]] ([[_computeSize(snapshot.size)]])<br/>
[[_formatDatetime(snapshot.date)]]
</div>
<div>Home Assistant:</div>
<paper-checkbox checked='{{restoreHass}}'>
Home Assistant [[snapshot.homeassistant]]
</paper-checkbox>
<template is='dom-if' if='[[snapshot.addons.length]]'>
<div>Folders:</div>
<template is='dom-repeat' items='[[snapshot.folders]]'>
<paper-checkbox checked='{{item.checked}}'>
[[item.name]]
</paper-checkbox>
</div>
<div>
Folders:
<template is='dom-repeat' items='[[snapshot.folders]]'>
</template>
</template>
<template is='dom-if' if='[[snapshot.addons.length]]'>
<div>Add-ons:</div>
<paper-dialog-scrollable>
<template is='dom-repeat' items='[[snapshot.addons]]' sort='_sortAddons'>
<paper-checkbox checked='{{item.checked}}'>
[[item.name]]
<span class='details'>([[item.version]])</span>
</paper-checkbox>
</template>
</div>
<template is='dom-if' if='[[snapshot.addons.length]]'>
<div>
Add-ons:
<template is='dom-repeat' items='[[snapshot.addons]]' sort='sortAddons'>
<paper-checkbox checked='{{item.checked}}'>
[[item.name]]
<span class='addon-version'>([[item.version]])</span>
</paper-checkbox>
</template>
</div>
</template>
<template is='dom-if' if='[[snapshot.protected]]'>
<paper-input autofocus label='Password' type='password' value='{{snapshotPassword}}'></paper-input>
</template>
<template is='dom-if' if='[[error]]'>
<p class='error'>Error: [[error]]</p>
</template>
</div>
</paper-dialog-scrollable>
</template>
<template is='dom-if' if='[[snapshot.protected]]'>
<paper-input autofocus label='Password' type='password' value='{{snapshotPassword}}'></paper-input>
</template>
<template is='dom-if' if='[[error]]'>
<p class='error'>Error: [[error]]</p>
</template>
<div class='buttons'>
<paper-button class='warning' on-click='deleteTapped'>Delete</paper-button>
<paper-button on-click='partialRestoreTapped'>Partial restore</paper-button>
<template is='dom-if' if='[[isFullSnapshot(snapshot.type)]]'>
<paper-button on-click='fullRestoreTapped'>Full restore</paper-button>
<paper-icon-button
icon='mdi:delete'
on-click='_deleteClicked'
class='warning'
title='Delete snapshot'
></paper-icon-button>
<a href='[[_computeDownloadUrl(snapshotSlug)]]' download='[[_computeDownloadName(snapshot)]]'>
<paper-icon-button
icon='mdi:download'
class='download'
title='Download snapshot'
></paper-icon-button>
</a>
<paper-button on-click='_partialRestoreClicked'>Restore selected</paper-button>
<template is='dom-if' if='[[_isFullSnapshot(snapshot.type)]]'>
<paper-button on-click='_fullRestoreClicked'>Wipe &amp; restore</paper-button>
</template>
</div>
</paper-dialog>
@ -92,7 +128,7 @@ class HassioSnapshot extends Polymer.Element {
snapshotSlug: {
type: String,
notify: true,
observer: 'snapshotSlugChanged',
observer: '_snapshotSlugChanged',
},
snapshotDeleted: {
type: Boolean,
@ -108,12 +144,12 @@ class HassioSnapshot extends Polymer.Element {
};
}
snapshotSlugChanged(snapshotSlug) {
_snapshotSlugChanged(snapshotSlug) {
if (!snapshotSlug || snapshotSlug === 'update') return;
this.hass.callApi('get', `hassio/snapshots/${snapshotSlug}/info`)
.then((info) => {
info.data.folders = this.computeFolders(info.data.folders);
info.data.addons = this.computeAddons(info.data.addons);
info.data.folders = this._computeFolders(info.data.folders);
info.data.addons = this._computeAddons(info.data.addons);
this.snapshot = info.data;
this.$.dialog.open();
}, () => {
@ -121,7 +157,7 @@ class HassioSnapshot extends Polymer.Element {
});
}
computeFolders(folders) {
_computeFolders(folders) {
const list = [];
if (folders.includes('homeassistant')) list.push({ slug: 'homeassistant', name: 'Home Assistant configuration', checked: true });
if (folders.includes('ssl')) list.push({ slug: 'ssl', name: 'SSL', checked: true });
@ -130,16 +166,16 @@ class HassioSnapshot extends Polymer.Element {
return list;
}
computeAddons(addons) {
_computeAddons(addons) {
return addons.map(addon => (
{ slug: addon.slug, name: addon.name, version: addon.version, checked: true }));
}
isFullSnapshot(type) {
_isFullSnapshot(type) {
return type === 'full';
}
partialRestoreTapped() {
_partialRestoreClicked() {
if (!confirm('Are you sure you want to restore this snapshot?')) {
return;
}
@ -162,7 +198,7 @@ class HassioSnapshot extends Polymer.Element {
});
}
fullRestoreTapped() {
_fullRestoreClicked() {
if (!confirm('Are you sure you want to restore this snapshot?')) {
return;
}
@ -176,7 +212,7 @@ class HassioSnapshot extends Polymer.Element {
});
}
deleteTapped() {
_deleteClicked() {
if (!confirm('Are you sure you want to delete this snapshot?')) {
return;
}
@ -189,23 +225,33 @@ class HassioSnapshot extends Polymer.Element {
});
}
computeName(snapshot) {
_computeDownloadUrl(snapshotSlug) {
const password = encodeURI(this.hass.connection.options.authToken);
return `/api/hassio/snapshots/${snapshotSlug}/download?api_password=${password}`;
}
_computeDownloadName(snapshot) {
const name = this._computeName(snapshot).replace(/[^a-z0-9]+/gi, '_');
return `Hass_io_${name}.tar`;
}
_computeName(snapshot) {
return snapshot.name || snapshot.slug;
}
computeType(type) {
_computeType(type) {
return type === 'full' ? 'Full snapshot' : 'Partial snapshot';
}
computeSize(size) {
return Math.ceil(size) + ' MB';
_computeSize(size) {
return (Math.ceil(size * 10) / 10) + ' MB';
}
sortAddons(a, b) {
_sortAddons(a, b) {
return a.name < b.name ? -1 : 1;
}
formatDatetime(datetime) {
_formatDatetime(datetime) {
return new Date(datetime).toLocaleDateString(navigator.language, {
weekday: 'long',
year: 'numeric',
@ -216,7 +262,7 @@ class HassioSnapshot extends Polymer.Element {
});
}
dialogClosed() {
_dialogClosed() {
this.snapshotSlug = null;
}
}

View File

@ -13,7 +13,14 @@
<dom-module id="hassio-snapshots">
<template>
<style include="ha-style hassio-style">
<style include='ha-style hassio-style'>
paper-radio-group {
display: block;
}
paper-radio-button {
padding: 0 0 2px 2px;
}
paper-radio-button,
paper-checkbox,
paper-input[type="password"] {
display: block;
@ -22,14 +29,11 @@
.pointer {
cursor: pointer;
}
.hidden {
display: none;
}
</style>
<div class='content'>
<div class='card-group'>
<div class='title'>
New snapshot
Create snapshot
<div class='description'>
Snapshots allow you to easily backup and
restore all data of your Hass.io instance.
@ -38,8 +42,8 @@
<paper-card>
<div class='card-content'>
<paper-input autofocus label='Name' value='{{snapshotName}}'></paper-input>
<label id='lbltype'>Type:</label>
<paper-radio-group selected='{{snapshotType}}' aria-labelledby='lbltype' on-paper-radio-group-changed='typeChanged'>
Type:
<paper-radio-group selected='{{snapshotType}}'>
<paper-radio-button name='full'>
Full snapshot
</paper-radio-button>
@ -47,35 +51,31 @@
Partial snapshot
</paper-radio-button>
</paper-radio-group>
<div id='folders' class='hidden'>
<template is='dom-if' if='[[!_fullSelected(snapshotType)]]'>
Folders:
<template is='dom-repeat' items='[[folderList]]'>
<paper-checkbox checked='{{item.checked}}'>
[[item.name]]
</paper-checkbox>
</template>
</div>
<div id='addons' class='hidden'>
Add-ons:
<template is='dom-repeat' items='[[addonList]]' sort='sortAddons'>
<template is='dom-repeat' items='[[addonList]]' sort='_sortAddons'>
<paper-checkbox checked='{{item.checked}}'>
[[item.name]]
</paper-checkbox>
</template>
</div>
<div>
Password:
<paper-checkbox checked='{{snapshotHasPassword}}'>Password protected</paper-checkbox>
<template is='dom-if' if='[[snapshotHasPassword]]'>
<paper-input label='Password' type='password' value='{{snapshotPassword}}'></paper-input>
</template>
</div>
</template>
Security:
<paper-checkbox checked='{{snapshotHasPassword}}'>Password protection</paper-checkbox>
<template is='dom-if' if='[[snapshotHasPassword]]'>
<paper-input label='Password' type='password' value='{{snapshotPassword}}'></paper-input>
</template>
<template is='dom-if' if='[[error]]'>
<p class='error'>[[error]]</p>
</template>
</div>
<div class='card-actions'>
<paper-button disabled='[[creatingSnapshot]]' on-click='createSnapshot'>Create</paper-button>
<paper-button disabled='[[creatingSnapshot]]' on-click='_createSnapshot'>Create</paper-button>
</div>
</paper-card>
</div>
@ -87,14 +87,14 @@
<div class='card-content'>You don't have any snapshots yet.</div>
</paper-card>
</template>
<template is='dom-repeat' items='[[snapshots]]' as='snapshot' sort='sortSnapshots'>
<paper-card class='pointer' on-click='snapshotTapped'>
<template is='dom-repeat' items='[[snapshots]]' as='snapshot' sort='_sortSnapshots'>
<paper-card class='pointer' on-click='_snapshotClicked'>
<div class='card-content'>
<hassio-card-content
title='[[computeName(snapshot)]]'
description='[[computeDetails(snapshot)]]'
title='[[_computeName(snapshot)]]'
description='[[_computeDetails(snapshot)]]'
datetime='[[snapshot.date]]'
icon='[[computeIcon(snapshot.type)]]'
icon='[[_computeIcon(snapshot.type)]]'
icon-class='snapshot'
></hassio-card-content>
</div>
@ -131,7 +131,7 @@ class HassioSnapshots extends window.hassMixins.EventsMixin(Polymer.Element) {
},
installedAddons: {
type: Array,
observer: 'installedAddonsChanged',
observer: '_installedAddonsChanged',
},
addonList: Array,
folderList: {
@ -150,7 +150,7 @@ class HassioSnapshots extends window.hassMixins.EventsMixin(Polymer.Element) {
snapshotDeleted: {
type: Boolean,
notify: true,
observer: 'snapshotDeletedChanged',
observer: '_snapshotDeletedChanged',
},
creatingSnapshot: Boolean,
dialogOpened: Boolean,
@ -160,17 +160,17 @@ class HassioSnapshots extends window.hassMixins.EventsMixin(Polymer.Element) {
ready() {
super.ready();
this.addEventListener('hass-api-called', ev => this.apiCalled(ev));
this.updateSnapshots();
this.addEventListener('hass-api-called', ev => this._apiCalled(ev));
this._updateSnapshots();
}
apiCalled(ev) {
_apiCalled(ev) {
if (ev.detail.success) {
this.updateSnapshots();
this._updateSnapshots();
}
}
updateSnapshots() {
_updateSnapshots() {
this.hass.callApi('get', 'hassio/snapshots')
.then((result) => {
this.snapshots = result.data.snapshots;
@ -179,7 +179,7 @@ class HassioSnapshots extends window.hassMixins.EventsMixin(Polymer.Element) {
});
}
createSnapshot() {
_createSnapshot() {
this.error = '';
if (this.snapshotHasPassword && !this.snapshotPassword.length) {
this.error = 'Please enter a password.';
@ -220,48 +220,42 @@ class HassioSnapshots extends window.hassMixins.EventsMixin(Polymer.Element) {
});
}
installedAddonsChanged(addons) {
_installedAddonsChanged(addons) {
this.addonList = addons.map(addon => ({ slug: addon.slug, name: addon.name, checked: true }));
}
sortAddons(a, b) {
_sortAddons(a, b) {
return a.name < b.name ? -1 : 1;
}
sortSnapshots(a, b) {
_sortSnapshots(a, b) {
return a.date < b.date ? 1 : -1;
}
computeName(snapshot) {
_computeName(snapshot) {
return snapshot.name || snapshot.slug;
}
computeDetails(snapshot) {
_computeDetails(snapshot) {
const type = snapshot.type === 'full' ? 'Full snapshot' : 'Partial snapshot';
return snapshot.protected ? `${type}, password protected` : type;
}
computeIcon(type) {
_computeIcon(type) {
return type === 'full' ? 'mdi:package-variant-closed' : 'mdi:package-variant';
}
snapshotTapped(ev) {
_snapshotClicked(ev) {
this.snapshotSlug = ev.model.snapshot.slug;
}
typeChanged() {
if (this.snapshotType === 'full') {
this.$.folders.classList.add('hidden');
this.$.addons.classList.add('hidden');
} else {
this.$.folders.classList.remove('hidden');
this.$.addons.classList.remove('hidden');
}
_fullSelected(type) {
return type === 'full';
}
snapshotDeletedChanged(snapshotDeleted) {
_snapshotDeletedChanged(snapshotDeleted) {
if (snapshotDeleted) {
this.updateSnapshots();
this._updateSnapshots();
this.snapshotDeleted = false;
}
}
@ -269,7 +263,7 @@ class HassioSnapshots extends window.hassMixins.EventsMixin(Polymer.Element) {
refreshData() {
this.hass.callApi('post', 'hassio/snapshots/reload')
.then(() => {
this.updateSnapshots();
this._updateSnapshots();
});
}
}