Compare commits

..

1 Commits

Author SHA1 Message Date
Ludeeus
a0b11eb357 Move partial backup logic to backend 2021-12-16 12:36:02 +00:00
23 changed files with 115 additions and 189 deletions

View File

@@ -133,7 +133,6 @@ class HassioAddonInfo extends LitElement {
.narrow=${this.narrow}
.supervisor=${this.supervisor}
.addonSlug=${this.addon.slug}
@update-complete=${this._updateComplete}
></update-available-card>
`
: ""}
@@ -866,15 +865,6 @@ class HassioAddonInfo extends LitElement {
}
}
private _updateComplete() {
const eventdata = {
success: true,
response: undefined,
path: "install",
};
fireEvent(this, "hass-api-called", eventdata);
}
private async _installClicked(ev: CustomEvent): Promise<void> {
const button = ev.currentTarget as any;
button.progress = true;

View File

@@ -350,7 +350,9 @@ export class SupervisorBackupContent extends LitElement {
if (folders?.length) {
data.folders = folders;
}
data.homeassistant = this.homeAssistant;
if (this.homeAssistant) {
data.homeassistant = this.homeAssistant;
}
return data;
}

View File

@@ -1,6 +1,5 @@
import "@polymer/paper-tooltip/paper-tooltip";
import "@material/mwc-button/mwc-button";
import { mdiDelete, mdiDeleteOff } from "@mdi/js";
import { mdiDelete } from "@mdi/js";
import "@polymer/paper-input/paper-input";
import type { PaperInputElement } from "@polymer/paper-input/paper-input";
import "@polymer/paper-item/paper-item";
@@ -16,7 +15,6 @@ import { createCloseHeading } from "../../../../src/components/ha-dialog";
import "../../../../src/components/ha-icon-button";
import {
fetchHassioAddonsInfo,
HassioAddonInfo,
HassioAddonRepository,
} from "../../../../src/data/hassio/addon";
import { extractApiErrorMessage } from "../../../../src/data/hassio/common";
@@ -62,24 +60,11 @@ class HassioRepositoriesDialog extends LitElement {
.sort((a, b) => caseInsensitiveStringCompare(a.name, b.name))
);
private _filteredUsedRepositories = memoizeOne(
(repos: HassioAddonRepository[], addons: HassioAddonInfo[]) =>
repos
.filter((repo) =>
addons.some((addon) => addon.repository === repo.slug)
)
.map((repo) => repo.slug)
);
protected render(): TemplateResult {
if (!this._dialogParams?.supervisor || this._repositories === undefined) {
return html``;
}
const repositories = this._filteredRepositories(this._repositories);
const usedRepositories = this._filteredUsedRepositories(
repositories,
this._dialogParams.supervisor.supervisor.addons
);
return html`
<ha-dialog
.open=${this._opened}
@@ -104,32 +89,18 @@ class HassioRepositoriesDialog extends LitElement {
<div secondary>${repo.maintainer}</div>
<div secondary>${repo.url}</div>
</paper-item-body>
<div class="delete">
<ha-icon-button
.disabled=${usedRepositories.includes(repo.slug)}
.slug=${repo.slug}
.path=${usedRepositories.includes(repo.slug)
? mdiDeleteOff
: mdiDelete}
@click=${this._removeRepository}
>
</ha-icon-button>
<paper-tooltip
animation-delay="0"
position="bottom"
offset="1"
>
${this._dialogParams!.supervisor.localize(
usedRepositories.includes(repo.slug)
? "dialog.repositories.used"
: "dialog.repositories.remove"
)}
</paper-tooltip>
</div>
<ha-icon-button
.slug=${repo.slug}
.label=${this._dialogParams!.supervisor.localize(
"dialog.repositories.remove"
)}
.path=${mdiDelete}
@click=${this._removeRepository}
></ha-icon-button>
</paper-item>
`
)
: html`<paper-item> No repositories </paper-item>`}
: html` <paper-item> No repositories </paper-item> `}
<div class="layout horizontal bottom">
<paper-input
class="flex-auto"
@@ -186,9 +157,6 @@ class HassioRepositoriesDialog extends LitElement {
margin: 32px;
text-align: center;
}
div.delete ha-icon-button {
color: var(--error-color);
}
`,
];
}

View File

@@ -29,10 +29,6 @@ import {
HassioAddonDetails,
updateHassioAddon,
} from "../../../src/data/hassio/addon";
import {
createHassioPartialBackup,
HassioPartialBackupCreateParams,
} from "../../../src/data/hassio/backup";
import {
extractApiErrorMessage,
ignoreSupervisorError,
@@ -103,7 +99,7 @@ class UpdateAvailableCard extends LitElement {
@state() private _addonInfo?: HassioAddonDetails;
@state() private _action: "backup" | "update" | null = null;
@state() private _updating = false;
@state() private _error?: string;
@@ -138,7 +134,7 @@ class UpdateAvailableCard extends LitElement {
name: this._name,
})}
</p>`
: this._action === null
: !this._updating
? html`
${this._changelogContent
? html`
@@ -172,18 +168,13 @@ class UpdateAvailableCard extends LitElement {
: html`<ha-circular-progress alt="Updating" size="large" active>
</ha-circular-progress>
<p class="progress-text">
${this._action === "update"
? this.supervisor.localize("update_available.updating", {
name: this._name,
version: this._version_latest,
})
: this.supervisor.localize(
"update_available.creating_backup",
{ name: this._name }
)}
${this.supervisor.localize("update_available.updating", {
name: this._name,
version: this._version_latest,
})}
</p>`}
</div>
${this._version !== this._version_latest && this._action === null
${this._version !== this._version_latest && !this._updating
? html`
<div class="card-actions">
${changelog
@@ -319,37 +310,16 @@ class UpdateAvailableCard extends LitElement {
private async _update() {
this._error = undefined;
if (this._shouldCreateBackup) {
let backupArgs: HassioPartialBackupCreateParams;
if (this._updateType === "addon") {
backupArgs = {
name: `addon_${this.addonSlug}_${this._version}`,
addons: [this.addonSlug!],
homeassistant: false,
};
} else {
backupArgs = {
name: `${this._updateType}_${this._version}`,
folders: ["homeassistant"],
homeassistant: true,
};
}
this._action = "backup";
try {
await createHassioPartialBackup(this.hass, backupArgs);
} catch (err: any) {
this._error = extractApiErrorMessage(err);
this._action = null;
return;
}
}
this._action = "update";
this._updating = true;
try {
if (this._updateType === "addon") {
await updateHassioAddon(this.hass, this.addonSlug!);
await updateHassioAddon(
this.hass,
this.addonSlug!,
this._shouldCreateBackup
);
} else if (this._updateType === "core") {
await updateCore(this.hass);
await updateCore(this.hass, this._shouldCreateBackup);
} else if (this._updateType === "os") {
await updateOS(this.hass);
} else if (this._updateType === "supervisor") {
@@ -358,7 +328,7 @@ class UpdateAvailableCard extends LitElement {
} catch (err: any) {
if (this.hass.connection.connected && !ignoreSupervisorError(err)) {
this._error = extractApiErrorMessage(err);
this._action = null;
this._updating = false;
return;
}
}

View File

@@ -46,7 +46,6 @@ class UpdateAvailableDashboard extends LitElement {
update-available-card {
margin: auto;
margin-top: 16px;
margin-bottom: 24px;
max-width: 600px;
}
`;

View File

@@ -102,7 +102,7 @@
"fuse.js": "^6.0.0",
"google-timezones-json": "^1.0.2",
"hls.js": "^1.0.11",
"home-assistant-js-websocket": "^6.0.0",
"home-assistant-js-websocket": "^5.11.3",
"idb-keyval": "^5.1.3",
"intl-messageformat": "^9.9.1",
"js-yaml": "^4.1.0",

View File

@@ -2,7 +2,7 @@ from setuptools import setup, find_packages
setup(
name="home-assistant-frontend",
version="20211227.0",
version="20211215.0",
description="The Home Assistant frontend",
url="https://github.com/home-assistant/frontend",
author="The Home Assistant Authors",

View File

@@ -199,16 +199,13 @@ export const DOMAINS_HIDE_DEFAULT_MORE_INFO = [
"select",
];
/** Domains that render an input element instead of a text value when displayed in a row.
/** Domains that render an input element instead of a text value when rendered in a row.
* Those rows should then not show a cursor pointer when hovered (which would normally
* be the default) unless the element itself enforces it (e.g. a button). Also those elements
* should not act as a click target to open the more info dialog (the row name and state icon
* still do of course) as the click should instead e.g. activate the input field or toggle
* the button that this row shows.
* still do of course) as the click might instead e.g. activate the input field that this row shows.
*/
export const DOMAINS_INPUT_ROW = [
"automation",
"button",
"cover",
"fan",
"group",
@@ -226,7 +223,6 @@ export const DOMAINS_INPUT_ROW = [
"script",
"select",
"switch",
"vacuum",
];
/** Domains that should have the history hidden in the more info dialog. */

View File

@@ -8,10 +8,6 @@ import { FrontendLocaleData } from "../data/translation";
import { getValueInPercentage, normalize } from "../util/calculate";
import { isSafari } from "../util/is_safari";
// Safari version 15.2 and up behaves differently than other Safari versions.
// https://github.com/home-assistant/frontend/issues/10766
const isSafari152 = isSafari && /Version\/15\.[^0-1]/.test(navigator.userAgent);
const getAngle = (value: number, min: number, max: number) => {
const percentage = getValueInPercentage(normalize(value, min, max), min, max);
return (percentage * 180) / 100;
@@ -117,9 +113,7 @@ export class Gauge extends LitElement {
: undefined
)}
transform=${ifDefined(
isSafari
? `rotate(${this._angle}${isSafari152 ? "" : " 50 50"})`
: undefined
isSafari ? `rotate(${this._angle} 50 50)` : undefined
)}
>
`
@@ -132,9 +126,7 @@ export class Gauge extends LitElement {
: undefined
)}
transform=${ifDefined(
isSafari
? `rotate(${this._angle}${isSafari152 ? "" : " 50 50"})`
: undefined
isSafari ? `rotate(${this._angle} 50 50)` : undefined
)}
>`
}

View File

@@ -302,7 +302,8 @@ export const installHassioAddon = async (
export const updateHassioAddon = async (
hass: HomeAssistant,
slug: string
slug: string,
backup: boolean
): Promise<void> => {
if (atLeastVersion(hass.config.version, 2021, 2, 4)) {
await hass.callWS({
@@ -310,11 +311,13 @@ export const updateHassioAddon = async (
endpoint: `/store/addons/${slug}/update`,
method: "post",
timeout: null,
data: { backup: backup },
});
} else {
await hass.callApi<HassioResponse<void>>(
"POST",
`hassio/addons/${slug}/update`
`hassio/addons/${slug}/update`,
{ backup: backup }
);
}
};

View File

@@ -6,15 +6,18 @@ export const restartCore = async (hass: HomeAssistant) => {
await hass.callService("homeassistant", "restart");
};
export const updateCore = async (hass: HomeAssistant) => {
export const updateCore = async (hass: HomeAssistant, backup: boolean) => {
if (atLeastVersion(hass.config.version, 2021, 2, 4)) {
await hass.callWS({
type: "supervisor/api",
endpoint: "/core/update",
method: "post",
timeout: null,
data: { backup: backup },
});
} else {
await hass.callApi<HassioResponse<void>>("POST", `hassio/core/update`);
await hass.callApi<HassioResponse<void>>("POST", `hassio/core/update`, {
backup: backup,
});
}
};

View File

@@ -110,7 +110,7 @@ export class ExternalMessaging {
},
});
} else if (msg.command === "restart") {
this.connection.reconnect(true);
this.connection.socket.close();
this.fireMessage({
id: msg.id,
type: "result",

View File

@@ -271,10 +271,7 @@ export const provideHass = (
updateStates,
updateTranslations,
addTranslations,
loadFragmentTranslation: async (fragment: string) => {
await updateTranslations(fragment);
return hass().localize;
},
loadFragmentTranslation: async (_fragment: string) => hass().localize,
addEntities,
mockWS(type, callback) {
wsCommands[type] = callback;

View File

@@ -34,6 +34,8 @@ const panelUrl = (path: string) => {
export class HomeAssistantAppEl extends QuickBarMixin(HassElement) {
@state() private _route: Route;
@state() private _error = false;
private _panelUrl: string;
private _haVersion?: string;
@@ -42,6 +44,8 @@ export class HomeAssistantAppEl extends QuickBarMixin(HassElement) {
private _visiblePromiseResolve?: () => void;
private _visibleLaunchScreen = true;
constructor() {
super();
const path = curPath();
@@ -58,22 +62,27 @@ export class HomeAssistantAppEl extends QuickBarMixin(HassElement) {
this._panelUrl = panelUrl(path);
}
protected renderHass() {
return html`
<home-assistant-main
.hass=${this.hass}
.route=${this._route}
></home-assistant-main>
`;
protected render() {
if (this._isHassComplete() && this.hass) {
return html`
<home-assistant-main
.hass=${this.hass}
.route=${this._route}
></home-assistant-main>
`;
}
return "";
}
update(changedProps) {
if (this.hass?.states && this.hass.config && this.hass.services) {
this.render = this.renderHass;
this.update = super.update;
super.update(changedProps);
// Remove launch screen if main gui is loaded
if (this._isHassComplete() && this._visibleLaunchScreen) {
this._visibleLaunchScreen = false;
removeLaunchScreen();
}
super.update(changedProps);
}
protected firstUpdated(changedProps) {
@@ -120,9 +129,10 @@ export class HomeAssistantAppEl extends QuickBarMixin(HassElement) {
});
// Render launch screen info box (loading data / error message)
// if Home Assistant is not loaded yet.
if (this.render !== this.renderHass) {
this._renderInitInfo(false);
if (!this._isHassComplete() && this._visibleLaunchScreen) {
renderLaunchScreenInfoBox(
html`<ha-init-page .error=${this._error}></ha-init-page>`
);
}
}
@@ -178,7 +188,7 @@ export class HomeAssistantAppEl extends QuickBarMixin(HassElement) {
if (window.hassConnection) {
result = await window.hassConnection;
} else {
// In the edge case that core.ts loads before app.ts
// In the edge case that
result = await new Promise((resolve) => {
window.hassConnectionReady = resolve;
});
@@ -188,7 +198,7 @@ export class HomeAssistantAppEl extends QuickBarMixin(HassElement) {
this._haVersion = conn.haVersion;
this.initializeHass(auth, conn);
} catch (err: any) {
this._renderInitInfo(true);
this._error = true;
}
}
@@ -245,10 +255,12 @@ export class HomeAssistantAppEl extends QuickBarMixin(HassElement) {
}
}
private _renderInitInfo(error: boolean) {
renderLaunchScreenInfoBox(
html`<ha-init-page .error=${error}></ha-init-page>`
);
private _isHassComplete(): boolean {
if (this.hass?.states && this.hass.config && this.hass.services) {
return true;
}
return false;
}
}

View File

@@ -118,9 +118,9 @@ export class HaDeviceCard extends LitElement {
word-wrap: break-word;
}
.manuf,
.entity-id,
.model {
color: var(--secondary-text-color);
word-wrap: break-word;
}
`;
}

View File

@@ -218,11 +218,9 @@ class ZHADeviceCard extends SubscribeMixin(LitElement) {
.device .manuf {
color: var(--secondary-text-color);
margin-bottom: 20px;
word-wrap: break-word;
}
.extra-info {
margin-top: 8px;
word-wrap: break-word;
}
state-badge {
cursor: pointer;

View File

@@ -5,6 +5,7 @@ import "@polymer/paper-input/paper-input";
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { customElement, property, state } from "lit/decorators";
import { componentsWithService } from "../../../common/config/components_with_service";
import { isComponentLoaded } from "../../../common/config/is_component_loaded";
import "../../../components/buttons/ha-call-service-button";
import "../../../components/ha-card";
import { checkCoreConfig } from "../../../data/core";
@@ -157,6 +158,20 @@ export class HaConfigServerControl extends LitElement {
"ui.panel.config.server_control.section.server_management.restart"
)}
</ha-call-service-button>
${!isComponentLoaded(this.hass, "hassio")
? html`<ha-call-service-button
class="warning"
.hass=${this.hass}
domain="homeassistant"
service="stop"
confirmation=${this.hass.localize(
"ui.panel.config.server_control.section.server_management.confirm_stop"
)}
>${this.hass.localize(
"ui.panel.config.server_control.section.server_management.stop"
)}
</ha-call-service-button>`
: ""}
</div>
</ha-card>

View File

@@ -163,16 +163,16 @@ class HuiEntitiesCard extends LitElement implements LovelaceCard {
if (!this._config || !this._hass) {
return;
}
const oldHass = changedProps.get("_hass") as HomeAssistant | undefined;
const oldHass = changedProps.get("hass") as HomeAssistant | undefined;
const oldConfig = changedProps.get("_config") as
| EntitiesCardConfig
| undefined;
if (
(changedProps.has("_hass") &&
(!oldHass || oldHass.themes !== this._hass.themes)) ||
(changedProps.has("_config") &&
(!oldConfig || oldConfig.theme !== this._config.theme))
!oldHass ||
!oldConfig ||
oldHass.themes !== this.hass.themes ||
oldConfig.theme !== this._config.theme
) {
applyThemesOnElement(this, this._hass.themes, this._config.theme);
}

View File

@@ -33,13 +33,6 @@ class HuiGenericEntityRow extends LitElement {
@property({ type: Boolean }) public hideName = false;
// Allows to control if this row should capture the user interaction, e.g. with its
// toggle switch, button or input field. Some domains dynamically decide what to show
// => static determination will not work => the caller has to pass the desired value in.
// Same applies for custom components that want to override the default behavior.
// Default behavior is controlled by DOMAINS_INPUT_ROW.
@property({ type: Boolean }) public catchInteraction?;
protected render(): TemplateResult {
if (!this.hass || !this.config) {
return html``;
@@ -57,11 +50,8 @@ class HuiGenericEntityRow extends LitElement {
}
const domain = computeDomain(this.config.entity);
// By default, we always show a pointer, since if there is no explicit configuration provided,
// the frontend always assumes "more-info" in the action handler. We only need to hide the pointer
// if the tap action is explicitly set to "none".
const pointer = !(
this.config.tap_action && this.config.tap_action.action === "none"
this.config.tap_action && this.config.tap_action.action !== "none"
);
const hasSecondary = this.secondaryText || this.config.secondary_info;
@@ -154,7 +144,7 @@ class HuiGenericEntityRow extends LitElement {
: ""}
</div>`
: html``}
${this.catchInteraction ?? !DOMAINS_INPUT_ROW.includes(domain)
${!DOMAINS_INPUT_ROW.includes(domain)
? html` <div
class="text-content ${classMap({
pointer,

View File

@@ -41,18 +41,11 @@ class HuiToggleEntityRow extends LitElement implements LovelaceRow {
`;
}
const showToggle =
stateObj.state === "on" ||
stateObj.state === "off" ||
UNAVAILABLE_STATES.includes(stateObj.state);
return html`
<hui-generic-entity-row
.hass=${this.hass}
.config=${this._config}
.catchInteraction=${!showToggle}
>
${showToggle
<hui-generic-entity-row .hass=${this.hass} .config=${this._config}>
${stateObj.state === "on" ||
stateObj.state === "off" ||
UNAVAILABLE_STATES.includes(stateObj.state)
? html`
<ha-entity-toggle
.hass=${this.hass}

View File

@@ -1419,7 +1419,7 @@
},
"server_control": {
"caption": "Server Controls",
"description": "Validate and restart the Home Assistant server",
"description": "Restart and stop the Home Assistant server",
"section": {
"validation": {
"heading": "Configuration validation",
@@ -1461,8 +1461,7 @@
"telegram": "Telegram notify services",
"smtp": "SMTP notify services",
"mqtt": "Manually configured MQTT entities",
"rpi_gpio": "Raspberry Pi GPIO entities",
"timer": "Timers"
"rpi_gpio": "Raspberry Pi GPIO entities"
},
"server_management": {
"heading": "Server management",
@@ -4467,8 +4466,7 @@
"repositories": {
"title": "Manage add-on repositories",
"add": "Add",
"remove": "Remove",
"used": "Repository is in use for installed add-ons and can't be removed."
"remove": "Remove"
},
"restart_addon": {
"confirm_text": "Restart add-on",

View File

@@ -7,9 +7,9 @@ export const removeLaunchScreen = () => {
}
};
export const renderLaunchScreenInfoBox = (content: TemplateResult) => {
export const renderLaunchScreenInfoBox = (element: TemplateResult) => {
const infoBoxElement = document.getElementById("ha-launch-screen-info-box");
if (infoBoxElement) {
render(content, infoBoxElement);
render(element, infoBoxElement);
}
};

View File

@@ -9113,7 +9113,7 @@ fsevents@^1.2.7:
gulp-rename: ^2.0.0
gulp-zopfli-green: ^3.0.1
hls.js: ^1.0.11
home-assistant-js-websocket: ^6.0.0
home-assistant-js-websocket: ^5.11.3
html-minifier: ^4.0.0
husky: ^1.3.1
idb-keyval: ^5.1.3
@@ -9184,10 +9184,10 @@ fsevents@^1.2.7:
languageName: unknown
linkType: soft
"home-assistant-js-websocket@npm:^6.0.0":
version: 6.0.0
resolution: "home-assistant-js-websocket@npm:6.0.0"
checksum: fef904210f66e180457ac2ae003fd29a0613179d3f8e5bb3f37aaaf0d5708a86ed071feb0d3bbf7268fdf454acec49539c8e9c0100ef159a711fcc98fd78cb14
"home-assistant-js-websocket@npm:^5.11.3":
version: 5.11.3
resolution: "home-assistant-js-websocket@npm:5.11.3"
checksum: 3ab90e5105c5f379d77fb23ab53eaec2789be7bf1fd507a7520d9cf329d36942b8e978a591b822cff96100630d43bd036a4e25e2f49c40d0c56a111808fb90a5
languageName: node
linkType: hard