mirror of
https://github.com/home-assistant/frontend.git
synced 2025-04-19 19:07:23 +00:00
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:
parent
08d4e0af18
commit
a1dbd18a9a
@ -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 & 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;
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user