Compare commits

..

1 Commits

Author SHA1 Message Date
Ludeeus d1605ba196 Add move data disk 2021-09-16 15:15:57 +00:00
31 changed files with 268 additions and 874 deletions
@@ -1,191 +0,0 @@
import "@polymer/paper-dropdown-menu/paper-dropdown-menu";
import "@polymer/paper-item/paper-item";
import "@polymer/paper-listbox/paper-listbox";
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { customElement, property, state } from "lit/decorators";
import memoizeOne from "memoize-one";
import { fireEvent } from "../../../../src/common/dom/fire_event";
import "../../../../src/components/ha-circular-progress";
import "../../../../src/components/ha-markdown";
import {
extractApiErrorMessage,
ignoreSupervisorError,
} from "../../../../src/data/hassio/common";
import {
DatadiskList,
listDatadisks,
moveDatadisk,
} from "../../../../src/data/hassio/host";
import { Supervisor } from "../../../../src/data/supervisor/supervisor";
import { showAlertDialog } from "../../../../src/dialogs/generic/show-dialog-box";
import { haStyle, haStyleDialog } from "../../../../src/resources/styles";
import { HomeAssistant } from "../../../../src/types";
import { HassioDatatiskDialogParams } from "./show-dialog-hassio-datadisk";
const calculateMoveTime = memoizeOne((supervisor: Supervisor): number => {
const speed = supervisor.host.disk_life_time !== "" ? 30 : 10;
const moveTime = (supervisor.host.disk_used * 1000) / 60 / speed;
const rebootTime = (supervisor.host.startup_time * 4) / 60;
return Math.ceil((moveTime + rebootTime) / 10) * 10;
});
@customElement("dialog-hassio-datadisk")
class HassioDatadiskDialog extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@state() private dialogParams?: HassioDatatiskDialogParams;
@state() private selectedDevice?: string;
@state() private devices?: DatadiskList["devices"];
@state() private moving = false;
public showDialog(params: HassioDatatiskDialogParams) {
this.dialogParams = params;
listDatadisks(this.hass).then((data) => {
this.devices = data.devices;
});
}
public closeDialog(): void {
this.dialogParams = undefined;
this.selectedDevice = undefined;
this.devices = undefined;
this.moving = false;
fireEvent(this, "dialog-closed", { dialog: this.localName });
}
protected render(): TemplateResult {
if (!this.dialogParams) {
return html``;
}
return html`
<ha-dialog
open
scrimClickAction
escapeKeyAction
@closed=${this.closeDialog}
?hideActions=${this.moving}
>
${this.moving
? html`<slot name="heading">
<h2 id="title" class="header_title">
${this.dialogParams.supervisor.localize(
"dialog.datadisk_move.moving"
)}
</h2>
</slot>
<ha-circular-progress alt="Moving" size="large" active>
</ha-circular-progress>
<p class="progress-text">
${this.dialogParams.supervisor.localize(
"dialog.datadisk_move.moving_desc"
)}
</p>`
: html`<slot name="heading">
<h2 id="title" class="header_title">
${this.dialogParams.supervisor.localize(
"dialog.datadisk_move.title"
)}
</h2>
</slot>
${this.devices?.length
? html`
${this.dialogParams.supervisor.localize(
"dialog.datadisk_move.description",
{
current_path: this.dialogParams.supervisor.os.data_disk,
time: calculateMoveTime(this.dialogParams.supervisor),
}
)}
<br /><br />
<paper-dropdown-menu
.label=${this.dialogParams.supervisor.localize(
"dialog.datadisk_move.select_device"
)}
@value-changed=${this._select_device}
>
<paper-listbox slot="dropdown-content">
${this.devices.map(
(device) => html`<paper-item>${device}</paper-item>`
)}
</paper-listbox>
</paper-dropdown-menu>
`
: this.devices === undefined
? this.dialogParams.supervisor.localize(
"dialog.datadisk_move.loading_devices"
)
: this.dialogParams.supervisor.localize(
"dialog.datadisk_move.no_devices"
)}
<mwc-button slot="secondaryAction" @click=${this.closeDialog}>
${this.dialogParams.supervisor.localize(
"dialog.datadisk_move.cancel"
)}
</mwc-button>
<mwc-button
.disabled=${!this.selectedDevice}
slot="primaryAction"
@click=${this._moveDatadisk}
>
${this.dialogParams.supervisor.localize(
"dialog.datadisk_move.move"
)}
</mwc-button>`}
</ha-dialog>
`;
}
private _select_device(event) {
this.selectedDevice = event.detail.value;
}
private async _moveDatadisk() {
this.moving = true;
try {
await moveDatadisk(this.hass, this.selectedDevice!);
} catch (err) {
if (this.hass.connection.connected && !ignoreSupervisorError(err)) {
showAlertDialog(this, {
title: this.dialogParams!.supervisor.localize(
"system.host.failed_to_move"
),
text: extractApiErrorMessage(err),
});
this.closeDialog();
}
}
}
static get styles(): CSSResultGroup {
return [
haStyle,
haStyleDialog,
css`
paper-dropdown-menu {
width: 100%;
}
ha-circular-progress {
display: block;
margin: 32px;
text-align: center;
}
.progress-text {
text-align: center;
}
`,
];
}
}
declare global {
interface HTMLElementTagNameMap {
"dialog-hassio-datadisk": HassioDatadiskDialog;
}
}
@@ -1,17 +0,0 @@
import { fireEvent } from "../../../../src/common/dom/fire_event";
import { Supervisor } from "../../../../src/data/supervisor/supervisor";
export interface HassioDatatiskDialogParams {
supervisor: Supervisor;
}
export const showHassioDatadiskDialog = (
element: HTMLElement,
dialogParams: HassioDatatiskDialogParams
): void => {
fireEvent(element, "show-dialog", {
dialogTag: "dialog-hassio-datadisk",
dialogImport: () => import("./dialog-hassio-datadisk"),
dialogParams,
});
};
+43 -22
View File
@@ -18,6 +18,7 @@ import { fetchHassioHardwareInfo } from "../../../src/data/hassio/hardware";
import {
changeHostOptions,
configSyncOS,
dataDiskMove,
rebootHost,
shutdownHost,
updateOS,
@@ -39,9 +40,8 @@ import {
roundWithOneDecimal,
} from "../../../src/util/calculate";
import "../components/supervisor-metric";
import { showHassioDatadiskDialog } from "../dialogs/datadisk/show-dialog-hassio-datadisk";
import { showHassioHardwareDialog } from "../dialogs/hardware/show-dialog-hassio-hardware";
import { showNetworkDialog } from "../dialogs/network/show-dialog-network";
import { showHassioHardwareDialog } from "../dialogs/hardware/show-dialog-hassio-hardware";
import { hassioStyle } from "../resources/hassio-style";
@customElement("hassio-host-info")
@@ -189,18 +189,17 @@ class HassioHostInfo extends LitElement {
</mwc-list-item>
${this.supervisor.host.features.includes("haos")
? html`<mwc-list-item
@click=${() => this._handleMenuAction("import_from_usb")}
>
${this.supervisor.localize("system.host.import_from_usb")}
</mwc-list-item>
${this.supervisor.host.features.includes("os_agent") &&
atLeastVersion(this.supervisor.host.agent_version, 1, 2, 0)
? html`<mwc-list-item
@click=${() => this._handleMenuAction("move_datadisk")}
>
${this.supervisor.localize("system.host.move_datadisk")}
</mwc-list-item>`
: ""} `
@click=${() => this._handleMenuAction("import_from_usb")}
>
${this.supervisor.localize("system.host.import_from_usb")}
</mwc-list-item>`
: ""}
${this.supervisor.host.features.includes("agent")
? html`<mwc-list-item
@click=${() => this._handleMenuAction("data_disk_move")}
>
${this.supervisor.localize("system.host.data_disk_move")}
</mwc-list-item>`
: ""}
</ha-button-menu>
</div>
@@ -231,18 +230,12 @@ class HassioHostInfo extends LitElement {
case "import_from_usb":
await this._importFromUSB();
break;
case "move_datadisk":
await this._moveDatadisk();
case "data_disk_move":
await this._dataDiskMove();
break;
}
}
private _moveDatadisk(): void {
showHassioDatadiskDialog(this, {
supervisor: this.supervisor,
});
}
private async _showHardware(): Promise<void> {
let hardware;
try {
@@ -411,6 +404,34 @@ class HassioHostInfo extends LitElement {
}
}
private async _dataDiskMove(): Promise<void> {
const confirmed = await showConfirmationDialog(this, {
title: this.supervisor.localize("system.host.data_disk_move"),
text: html`${this.supervisor.localize(
"dialog.data_disk_move.description",
{ current_path: this.supervisor.os.data_disk }
)} <br /><br />${this.supervisor.localize(
"dialog.data_disk_move.confirm_text"
)}`,
confirmText: this.supervisor.localize("dialog.data_disk_move.move"),
dismissText: this.supervisor.localize("dialog.data_disk_move.cancel"),
});
if (!confirmed) {
return;
}
try {
await dataDiskMove(this.hass);
} catch (err) {
if (this.hass.connection.connected && !ignoreSupervisorError(err)) {
showAlertDialog(this, {
title: this.supervisor.localize("system.host.failed_to_move"),
text: extractApiErrorMessage(err),
});
}
}
}
private async _loadData(): Promise<void> {
if (atLeastVersion(this.hass.config.version, 2021, 2, 4)) {
fireEvent(this, "supervisor-collection-refresh", {
+1 -3
View File
@@ -34,18 +34,16 @@ import { hassioStyle } from "../resources/hassio-style";
const UNSUPPORTED_REASON_URL = {
apparmor: "/more-info/unsupported/apparmor",
container: "/more-info/unsupported/container",
content_trust: "/more-info/unsupported/content_trust",
dbus: "/more-info/unsupported/dbus",
docker_configuration: "/more-info/unsupported/docker_configuration",
docker_version: "/more-info/unsupported/docker_version",
job_conditions: "/more-info/unsupported/job_conditions",
lxc: "/more-info/unsupported/lxc",
network_manager: "/more-info/unsupported/network_manager",
os_agent: "/more-info/unsupported/content_trust",
os: "/more-info/unsupported/os",
privileged: "/more-info/unsupported/privileged",
source_mods: "/more-info/unsupported/source_mods",
systemd: "/more-info/unsupported/systemd",
content_trust: "/more-info/unsupported/content_trust",
};
const UNHEALTHY_REASON_URL = {
+1 -1
View File
@@ -2,7 +2,7 @@ from setuptools import setup, find_packages
setup(
name="home-assistant-frontend",
version="20210922.0",
version="20210911.0",
description="The Home Assistant frontend",
url="https://github.com/home-assistant/frontend",
author="The Home Assistant Authors",
-24
View File
@@ -1,24 +0,0 @@
/** Return an icon representing a alarm panel state. */
export const alarmPanelIcon = (state?: string) => {
switch (state) {
case "armed_away":
return "hass:shield-lock";
case "armed_vacation":
return "hass:shield-airplane";
case "armed_home":
return "hass:shield-home";
case "armed_night":
return "hass:shield-moon";
case "armed_custom_bypass":
return "hass:security";
case "pending":
return "hass:shield-outline";
case "triggered":
return "hass:bell-ring";
case "disarmed":
return "hass:shield-off";
default:
return "hass:shield";
}
};
+12 -2
View File
@@ -5,7 +5,6 @@ import { HassEntity } from "home-assistant-js-websocket";
* Optionally pass in a state to influence the domain icon.
*/
import { DEFAULT_DOMAIN_ICON, FIXED_DOMAIN_ICONS } from "../const";
import { alarmPanelIcon } from "./alarm_panel_icon";
import { binarySensorIcon } from "./binary_sensor_icon";
import { coverIcon } from "./cover_icon";
import { sensorIcon } from "./sensor_icon";
@@ -19,7 +18,18 @@ export const domainIcon = (
switch (domain) {
case "alarm_control_panel":
return alarmPanelIcon(compareState);
switch (compareState) {
case "armed_home":
return "hass:bell-plus";
case "armed_night":
return "hass:bell-sleep";
case "disarmed":
return "hass:bell-outline";
case "triggered":
return "hass:bell-ring";
default:
return "hass:bell";
}
case "binary_sensor":
return binarySensorIcon(compareState, stateObj);
+1 -1
View File
@@ -15,7 +15,7 @@ const haTabFixBehaviorImpl = {
},
};
// paper-dialog that uses the haTabFixBehaviorImpl behavior
// paper-dialog that uses the haTabFixBehaviorImpl behvaior
// export class HaPaperDialog extends paperDialogClass {}
// @ts-ignore
export class HaPaperDialog
@@ -13,6 +13,7 @@ import secondsToDuration from "../../common/datetime/seconds_to_duration";
import { computeStateDisplay } from "../../common/entity/compute_state_display";
import { computeStateDomain } from "../../common/entity/compute_state_domain";
import { computeStateName } from "../../common/entity/compute_state_name";
import { domainIcon } from "../../common/entity/domain_icon";
import { stateIcon } from "../../common/entity/state_icon";
import { timerTimeRemaining } from "../../data/timer";
import { formatNumber } from "../../common/string/format_number";
@@ -140,6 +141,26 @@ export class HaStateLabelBadge extends LitElement {
}
switch (domain) {
case "alarm_control_panel":
if (entityState.state === "pending") {
return "hass:clock-fast";
}
if (entityState.state === "armed_away") {
return "hass:nature";
}
if (entityState.state === "armed_home") {
return "hass:home-variant";
}
if (entityState.state === "armed_night") {
return "hass:weather-night";
}
if (entityState.state === "armed_custom_bypass") {
return "hass:shield-home";
}
if (entityState.state === "triggered") {
return "hass:alert-circle";
}
// state == 'disarmed'
return domainIcon(domain, entityState);
case "binary_sensor":
case "device_tracker":
case "updater":
@@ -82,7 +82,7 @@ export class HaTracePathDetails extends LitElement {
] as ChooseActionTraceStep[];
if (parentTraceInfo && parentTraceInfo[0]?.result?.choice === "default") {
return "The default action was executed because no options matched.";
return "The default node was executed because no choices matched.";
}
}
+3 -5
View File
@@ -325,15 +325,13 @@ class ActionRenderer {
if (defaultExecuted) {
this._renderEntry(choosePath, `${name}: Default action executed`);
} else if (chooseTrace.result) {
const choiceNumeric =
chooseTrace.result.choice !== "default"
? chooseTrace.result.choice + 1
: undefined;
const choiceConfig = this._getDataFromPath(
`${this.keys[index]}/choose/${chooseTrace.result.choice}`
) as ChooseActionChoice | undefined;
const choiceName = choiceConfig
? `${choiceConfig.alias || `Option ${choiceNumeric}`} executed`
? `${
choiceConfig.alias || `Choice ${chooseTrace.result.choice}`
} executed`
: `Error: ${chooseTrace.error}`;
this._renderEntry(choosePath, `${name}: ${choiceName}`);
} else {
+7 -96
View File
@@ -11,13 +11,7 @@ import { subscribeOne } from "../common/util/subscribe-one";
import { HomeAssistant } from "../types";
import { ConfigEntry, getConfigEntries } from "./config_entries";
import { subscribeEntityRegistry } from "./entity_registry";
import {
calculateStatisticsSumDecreaseGrowth,
calculateStatisticsSumGrowth,
calculateStatisticsSumIncreaseGrowth,
fetchStatistics,
Statistics,
} from "./history";
import { fetchStatistics, Statistics } from "./history";
const energyCollectionKeys: (string | undefined)[] = [];
@@ -44,7 +38,6 @@ export const emptyGridSourceEnergyPreference =
type: "grid",
flow_from: [],
flow_to: [],
flow_net: [],
cost_adjustment_day: 0,
});
@@ -85,12 +78,12 @@ export interface DeviceConsumptionEnergyPreference {
export interface FlowFromGridSourceEnergyPreference {
// kWh meter
stat_energy_from: string;
entity_energy_from: string | null;
// $ meter
stat_cost: string | null;
// Can be used to generate costs if stat_cost omitted
entity_energy_from: string | null;
entity_energy_price: string | null;
number_energy_price: number | null;
}
@@ -98,39 +91,21 @@ export interface FlowFromGridSourceEnergyPreference {
export interface FlowToGridSourceEnergyPreference {
// kWh meter
stat_energy_to: string;
entity_energy_to: string | null;
// $ meter
stat_compensation: string | null;
// Can be used to generate costs if stat_cost omitted
entity_energy_to: string | null;
entity_energy_price: string | null;
number_energy_price: number | null;
}
export interface FlowNetGridSourceEnergyPreference {
// kWh meter
stat_energy_net: string;
entity_energy_net: string | null;
// $ meter to
stat_cost: string | null;
// Can be used to generate to costs if stat_cost omitted
entity_energy_price_to: string | null;
number_energy_price_to: number | null;
// Can be used to generate from costs if stat_cost omitted
entity_energy_price_from: string | null;
number_energy_price_from: number | null;
}
export interface GridSourceTypeEnergyPreference {
type: "grid";
flow_from: FlowFromGridSourceEnergyPreference[];
flow_to: FlowToGridSourceEnergyPreference[];
flow_net?: FlowNetGridSourceEnergyPreference[];
cost_adjustment_day: number;
}
@@ -174,7 +149,7 @@ export interface EnergyPreferences {
}
export interface EnergyInfo {
cost_sensors: Record<string, Record<string, string>>;
cost_sensors: Record<string, string>;
solar_forecast_domains: string[];
}
@@ -288,7 +263,7 @@ const getEnergyData = async (
if (source.stat_cost) {
statIDs.push(source.stat_cost);
}
const costStatId = info.cost_sensors[source.stat_energy_from].none;
const costStatId = info.cost_sensors[source.stat_energy_from];
if (costStatId) {
statIDs.push(costStatId);
}
@@ -307,7 +282,7 @@ const getEnergyData = async (
if (flowFrom.stat_cost) {
statIDs.push(flowFrom.stat_cost);
}
const costStatId = info.cost_sensors[flowFrom.stat_energy_from].none;
const costStatId = info.cost_sensors[flowFrom.stat_energy_from];
if (costStatId) {
statIDs.push(costStatId);
}
@@ -317,29 +292,11 @@ const getEnergyData = async (
if (flowTo.stat_compensation) {
statIDs.push(flowTo.stat_compensation);
}
const costStatId = info.cost_sensors[flowTo.stat_energy_to].none;
const costStatId = info.cost_sensors[flowTo.stat_energy_to];
if (costStatId) {
statIDs.push(costStatId);
}
}
if (source.flow_net) {
for (const flowTo of source.flow_net) {
statIDs.push(flowTo.stat_energy_net);
if (flowTo.stat_cost) {
statIDs.push(flowTo.stat_cost);
}
const costStatIdInc =
info.cost_sensors[flowTo.stat_energy_net].increase;
if (costStatIdInc) {
statIDs.push(costStatIdInc);
}
const costStatIdDec =
info.cost_sensors[flowTo.stat_energy_net].decrease;
if (costStatIdDec) {
statIDs.push(costStatIdDec);
}
}
}
}
const stats = await fetchStatistics(hass!, addHours(start, -1), end, statIDs); // Subtract 1 hour from start to get starting point data
@@ -496,49 +453,3 @@ export const getEnergySolarForecasts = (hass: HomeAssistant) =>
hass.callWS<EnergySolarForecasts>({
type: "energy/solar_forecast",
});
export const getTotalGridConsumption = (
stats: Statistics,
gridSource: GridSourceTypeEnergyPreference
) => {
const consumedFromGrid = calculateStatisticsSumGrowth(
stats,
gridSource.flow_from.map((flow) => flow.stat_energy_from)
);
const consumedFromGridNetto = gridSource.flow_net
? calculateStatisticsSumIncreaseGrowth(
stats,
gridSource.flow_net.map((flow) => flow.stat_energy_net)
) ?? 0
: null;
if (consumedFromGrid === null && consumedFromGridNetto === null) {
return null;
}
return (consumedFromGrid || 0) + (consumedFromGridNetto || 0);
};
export const getTotalGridReturn = (
stats: Statistics,
gridSource: GridSourceTypeEnergyPreference
) => {
const returnedToGrid = calculateStatisticsSumGrowth(
stats,
gridSource.flow_to.map((flow) => flow.stat_energy_to)
);
const returnedToGridNetto = gridSource.flow_net
? calculateStatisticsSumDecreaseGrowth(
stats,
gridSource.flow_net.map((flow) => flow.stat_energy_net)
) ?? 0
: null;
if (returnedToGrid === null && returnedToGridNetto === null) {
return null;
}
return (returnedToGrid || 0) + (returnedToGridNetto || 0);
};
+13 -38
View File
@@ -3,7 +3,6 @@ import { HomeAssistant } from "../../types";
import { hassioApiResultExtractor, HassioResponse } from "./common";
export type HassioHostInfo = {
agent_version: string;
chassis: string;
cpe: string;
deployment: string;
@@ -15,8 +14,6 @@ export type HassioHostInfo = {
hostname: string;
kernel: string;
operating_system: string;
boot_timestamp: number;
startup_time: number;
};
export interface HassioHassOSInfo {
@@ -28,10 +25,6 @@ export interface HassioHassOSInfo {
data_disk: string;
}
export interface DatadiskList {
devices: string[];
}
export const fetchHassioHostInfo = async (
hass: HomeAssistant
): Promise<HassioHostInfo> => {
@@ -121,6 +114,19 @@ export const configSyncOS = async (hass: HomeAssistant) => {
return hass.callApi<HassioResponse<void>>("POST", "hassio/os/config/sync");
};
export const dataDiskMove = async (hass: HomeAssistant) => {
if (atLeastVersion(hass.config.version, 2021, 2, 4)) {
return hass.callWS({
type: "supervisor/api",
endpoint: "/os/datadisk/move",
method: "post",
timeout: null,
});
}
return hass.callApi<HassioResponse<void>>("POST", "hassio/os/datadisk/move");
};
export const changeHostOptions = async (hass: HomeAssistant, options: any) => {
if (atLeastVersion(hass.config.version, 2021, 2, 4)) {
return hass.callWS({
@@ -137,34 +143,3 @@ export const changeHostOptions = async (hass: HomeAssistant, options: any) => {
options
);
};
export const moveDatadisk = async (hass: HomeAssistant, device: string) => {
if (atLeastVersion(hass.config.version, 2021, 2, 4)) {
return hass.callWS({
type: "supervisor/api",
endpoint: "/os/datadisk/move",
method: "post",
timeout: null,
data: { device },
});
}
return hass.callApi<HassioResponse<void>>("POST", "hassio/os/datadisk/move");
};
export const listDatadisks = async (
hass: HomeAssistant
): Promise<DatadiskList> => {
if (atLeastVersion(hass.config.version, 2021, 2, 4)) {
return hass.callWS<DatadiskList>({
type: "supervisor/api",
endpoint: "/os/datadisk/list",
method: "get",
timeout: null,
});
}
return hassioApiResultExtractor(
await hass.callApi<HassioResponse<DatadiskList>>("GET", "/os/datadisk/list")
);
};
+7 -21
View File
@@ -68,8 +68,6 @@ export interface StatisticValue {
mean: number | null;
min: number | null;
sum: number | null;
sum_decrease?: number;
sum_increase?: number;
state: number | null;
}
@@ -308,18 +306,17 @@ export const fetchStatistics = (
});
export const calculateStatisticSumGrowth = (
values: StatisticValue[],
sumKey: "sum" | "sum_increase" | "sum_decrease" = "sum"
values: StatisticValue[]
): number | null => {
if (!values || values.length < 2) {
return null;
}
const endSum = values[values.length - 1][sumKey];
if (endSum === null || endSum === undefined) {
const endSum = values[values.length - 1].sum;
if (endSum === null) {
return null;
}
const startSum = values[0][sumKey];
if (startSum === null || startSum === undefined) {
const startSum = values[0].sum;
if (startSum === null) {
return endSum;
}
return endSum - startSum;
@@ -327,8 +324,7 @@ export const calculateStatisticSumGrowth = (
export const calculateStatisticsSumGrowth = (
data: Statistics,
stats: string[],
sumKey: "sum" | "sum_increase" | "sum_decrease" = "sum"
stats: string[]
): number | null => {
let totalGrowth: number | null = null;
@@ -336,7 +332,7 @@ export const calculateStatisticsSumGrowth = (
if (!(stat in data)) {
continue;
}
const statGrowth = calculateStatisticSumGrowth(data[stat], sumKey);
const statGrowth = calculateStatisticSumGrowth(data[stat]);
if (statGrowth === null) {
continue;
@@ -351,16 +347,6 @@ export const calculateStatisticsSumGrowth = (
return totalGrowth;
};
export const calculateStatisticsSumIncreaseGrowth = (
data: Statistics,
stats: string[]
): number | null => calculateStatisticsSumGrowth(data, stats, "sum_increase");
export const calculateStatisticsSumDecreaseGrowth = (
data: Statistics,
stats: string[]
): number | null => calculateStatisticsSumGrowth(data, stats, "sum_decrease");
export const statisticsHaveType = (
stats: StatisticValue[],
type: StatisticType
+1 -1
View File
@@ -29,7 +29,7 @@ import { HomeAssistant } from "../types";
import "./action-badge";
import "./integration-badge";
const HIDDEN_DOMAINS = new Set(["met", "rpi_power", "hassio"]);
const HIDDEN_DOMAINS = new Set(["met", "rpi_power"]);
@customElement("onboarding-integrations")
class OnboardingIntegrations extends LitElement {
@@ -3,7 +3,6 @@ import {
mdiDelete,
mdiHomeExportOutline,
mdiHomeImportOutline,
mdiHomePlusOutline,
mdiPencil,
mdiTransmissionTower,
} from "@mdi/js";
@@ -174,41 +173,6 @@ export class EnergyGridSettings extends LitElement {
<mwc-button @click=${this._addToSource}>Add return</mwc-button>
</div>
<p>
If your meter goes down when your return to the grid, add a net
source.
</p>
<h3>Combined consumption and return</h3>
${gridSource.flow_net?.map((flow) => {
const entityState = this.hass.states[flow.stat_energy_net];
return html`
<div class="row" .source=${flow}>
${entityState?.attributes.icon
? html`<ha-icon
.icon=${entityState.attributes.icon}
></ha-icon>`
: html`<ha-svg-icon
.path=${mdiHomePlusOutline}
></ha-svg-icon>`}
<span class="content"
>${entityState
? computeStateName(entityState)
: flow.stat_energy_net}</span
>
<mwc-icon-button @click=${this._editToSource}>
<ha-svg-icon .path=${mdiPencil}></ha-svg-icon>
</mwc-icon-button>
<mwc-icon-button @click=${this._deleteToSource}>
<ha-svg-icon .path=${mdiDelete}></ha-svg-icon>
</mwc-icon-button>
</div>
`;
})}
<div class="row border-bottom">
<ha-svg-icon .path=${mdiHomePlusOutline}></ha-svg-icon>
<mwc-button @click=${this._addToSource}>Add net</mwc-button>
</div>
<h3>Grid carbon footprint</h3>
${this._configEntries?.map(
(entry) => html`<div class="row" .entry=${entry}>
@@ -279,14 +279,9 @@ export class HaConfigLovelaceDashboards extends LitElement {
removeDashboard: async () => {
if (
!(await showConfirmationDialog(this, {
title: this.hass!.localize(
"ui.panel.config.lovelace.dashboards.confirm_delete_title",
{ dashboard_title: dashboard!.title }
),
text: this.hass!.localize(
"ui.panel.config.lovelace.dashboards.confirm_delete_text"
"ui.panel.config.lovelace.dashboards.confirm_delete"
),
confirmText: this.hass!.localize("ui.common.delete"),
}))
) {
return false;
@@ -46,8 +46,7 @@ export class EnergyStrategy {
const hasGrid = prefs.energy_sources.find(
(source) => source.type === "grid"
) as GridSourceTypeEnergyPreference;
const hasReturn =
hasGrid && (hasGrid.flow_to.length || hasGrid.flow_net?.length);
const hasReturn = hasGrid && hasGrid.flow_to.length;
const hasSolar = prefs.energy_sources.some(
(source) => source.type === "solar"
);
@@ -12,7 +12,6 @@ import {
EnergyData,
energySourcesByType,
getEnergyDataCollection,
getTotalGridConsumption,
} from "../../../../data/energy";
import {
calculateStatisticsSumGrowth,
@@ -78,9 +77,9 @@ class HuiEnergyCarbonGaugeCard
const prefs = this._data.prefs;
const types = energySourcesByType(prefs);
const totalGridConsumption = getTotalGridConsumption(
const totalGridConsumption = calculateStatisticsSumGrowth(
this._data.stats,
types.grid![0]
types.grid![0].flow_from.map((flow) => flow.stat_energy_from)
);
let value: number | undefined;
@@ -130,9 +129,9 @@ class HuiEnergyCarbonGaugeCard
<ha-svg-icon id="info" .path=${mdiInformation}></ha-svg-icon>
<paper-tooltip animation-delay="0" for="info" position="left">
<span>
This card indicates how much of the energy consumed by your
This card represents how much of the energy consumed by your
home was generated using non-fossil fuels like solar, wind and
nuclear. The higher, the better!
nuclear.
</span>
</paper-tooltip>
<ha-gauge
@@ -1,4 +1,3 @@
import "@material/mwc-button";
import {
mdiArrowDown,
mdiArrowLeft,
@@ -15,6 +14,7 @@ import { UnsubscribeFunc } from "home-assistant-js-websocket";
import { css, html, LitElement, svg } from "lit";
import { customElement, property, state } from "lit/decorators";
import { classMap } from "lit/directives/class-map";
import "@material/mwc-button";
import { formatNumber } from "../../../../common/string/format_number";
import "../../../../components/ha-card";
import "../../../../components/ha-svg-icon";
@@ -22,8 +22,6 @@ import {
EnergyData,
energySourcesByType,
getEnergyDataCollection,
getTotalGridConsumption,
getTotalGridReturn,
} from "../../../../data/energy";
import {
calculateStatisticsSumGrowth,
@@ -83,12 +81,13 @@ class HuiEnergyDistrubutionCard
const hasSolarProduction = types.solar !== undefined;
const hasBattery = types.battery !== undefined;
const hasGas = types.gas !== undefined;
const hasReturnToGrid =
hasConsumption &&
(types.grid![0].flow_to.length || types.grid![0].flow_net?.length);
const hasReturnToGrid = hasConsumption && types.grid![0].flow_to.length > 0;
const totalFromGrid =
getTotalGridConsumption(this._data.stats, types.grid![0]) ?? 0;
calculateStatisticsSumGrowth(
this._data.stats,
types.grid![0].flow_from.map((flow) => flow.stat_energy_from)
) ?? 0;
let gasUsage: number | null = null;
if (hasGas) {
@@ -129,7 +128,10 @@ class HuiEnergyDistrubutionCard
if (hasReturnToGrid) {
returnedToGrid =
getTotalGridReturn(this._data.stats, types.grid![0]) ?? 0;
calculateStatisticsSumGrowth(
this._data.stats,
types.grid![0].flow_to.map((flow) => flow.stat_energy_to)
) || 0;
}
let solarConsumption: number | null = null;
@@ -215,11 +217,6 @@ class HuiEnergyDistrubutionCard
.grid![0].flow_from.map(
(flow) => this._data!.stats[flow.stat_energy_from]
)
.concat(
types.grid![0].flow_net?.map(
(flow) => this._data!.stats[flow.stat_energy_net]
) || []
)
.filter(Boolean)
);
@@ -11,11 +11,9 @@ import type { LevelDefinition } from "../../../../components/ha-gauge";
import {
EnergyData,
getEnergyDataCollection,
getTotalGridConsumption,
getTotalGridReturn,
GridSourceTypeEnergyPreference,
} from "../../../../data/energy";
import { calculateStatisticsSumGrowth } from "../../../../data/history";
import { SubscribeMixin } from "../../../../mixins/subscribe-mixin";
import type { HomeAssistant } from "../../../../types";
import type { LovelaceCard } from "../../types";
@@ -77,12 +75,15 @@ class HuiEnergyGridGaugeCard
return html``;
}
const consumedFromGrid = getTotalGridConsumption(
const consumedFromGrid = calculateStatisticsSumGrowth(
this._data.stats,
gridSource
gridSource.flow_from.map((flow) => flow.stat_energy_from)
);
const returnedToGrid = getTotalGridReturn(this._data.stats, gridSource);
const returnedToGrid = calculateStatisticsSumGrowth(
this._data.stats,
gridSource.flow_to.map((flow) => flow.stat_energy_to)
);
if (consumedFromGrid !== null && returnedToGrid !== null) {
if (returnedToGrid > consumedFromGrid) {
@@ -11,7 +11,6 @@ import {
EnergyData,
energySourcesByType,
getEnergyDataCollection,
getTotalGridReturn,
} from "../../../../data/energy";
import { calculateStatisticsSumGrowth } from "../../../../data/history";
import { SubscribeMixin } from "../../../../mixins/subscribe-mixin";
@@ -70,19 +69,19 @@ class HuiEnergySolarGaugeCard
types.solar.map((source) => source.stat_energy_from)
);
const productionReturnedToGrid = getTotalGridReturn(
const productionReturnedToGrid = calculateStatisticsSumGrowth(
this._data.stats,
types.grid![0]
types.grid![0].flow_to.map((flow) => flow.stat_energy_to)
);
let value: number | undefined;
if (productionReturnedToGrid !== null && totalSolarProduction) {
const consumedSolar = Math.max(
const cosumedSolar = Math.max(
0,
totalSolarProduction - productionReturnedToGrid
);
value = (consumedSolar / totalSolarProduction) * 100;
value = (cosumedSolar / totalSolarProduction) * 100;
}
return html`
@@ -92,13 +91,12 @@ class HuiEnergySolarGaugeCard
<ha-svg-icon id="info" .path=${mdiInformation}></ha-svg-icon>
<paper-tooltip animation-delay="0" for="info" position="left">
<span>
This card indicates how much of the solar energy you produced
was used by your home instead of being returned to the grid.
This card represents how much of the solar energy was used by
your home and was not returned to the grid.
<br /><br />
If this number is typically very low, indicating excess solar
production, you might want to consider charging a home battery
or electric car from your solar panels at times of high solar
production.
If you frequently produce more than you consume, try to
conserve this energy by installing a battery or buying an
electric car to charge.
</span>
</paper-tooltip>
<ha-gauge
@@ -111,11 +109,11 @@ class HuiEnergySolarGaugeCard
"--gauge-color": this._computeSeverity(value),
})}
></ha-gauge>
<div class="name">Self-consumed solar energy</div>
<div class="name">Self consumed solar energy</div>
`
: totalSolarProduction === 0
? "You have not produced any solar energy"
: "Self-consumed solar energy couldn't be calculated"}
: "Self consumed solar energy couldn't be calculated"}
</ha-card>
`;
}
@@ -111,20 +111,12 @@ export class HuiEnergySourcesTableCard
flow.entity_energy_price ||
flow.number_energy_price
) ||
types.grid?.[0].flow_net?.some(
(flow) =>
flow.stat_cost ||
flow.entity_energy_price_from ||
flow.number_energy_price_from ||
flow.entity_energy_price_to ||
flow.number_energy_price_to
) ||
types.gas?.some(
(flow) =>
flow.stat_cost || flow.entity_energy_price || flow.number_energy_price
);
return html`<ha-card>
return html` <ha-card>
${this._config.title
? html`<h1 class="card-header">${this._config.title}</h1>`
: ""}
@@ -317,7 +309,7 @@ export class HuiEnergySourcesTableCard
totalGrid += energy;
const cost_stat =
flow.stat_cost ||
this._data!.info.cost_sensors[flow.stat_energy_from].none;
this._data!.info.cost_sensors[flow.stat_energy_from];
const cost = cost_stat
? calculateStatisticSumGrowth(
this._data!.stats[cost_stat]
@@ -377,7 +369,7 @@ export class HuiEnergySourcesTableCard
totalGrid += energy;
const cost_stat =
flow.stat_compensation ||
this._data!.info.cost_sensors[flow.stat_energy_to].none;
this._data!.info.cost_sensors[flow.stat_energy_to];
const cost = cost_stat
? (calculateStatisticSumGrowth(
this._data!.stats[cost_stat]
@@ -423,145 +415,6 @@ export class HuiEnergySourcesTableCard
</td>`
: ""}
</tr>`;
})}
${source.flow_net?.map((flow, idx) => {
const entity = this.hass.states[flow.stat_energy_net];
const energy_from =
calculateStatisticSumGrowth(
this._data!.stats[flow.stat_energy_net],
"sum_increase"
) || 0;
const energy_to =
(calculateStatisticSumGrowth(
this._data!.stats[flow.stat_energy_net],
"sum_decrease"
) || 0) * -1;
totalGrid += energy_from + energy_to;
let costIncrease: number | null = null;
let costDecrease: number | null = null;
if (flow.stat_cost) {
costIncrease =
calculateStatisticSumGrowth(
this._data!.stats[flow.stat_cost],
"sum_increase"
) || 0;
costDecrease =
(calculateStatisticSumGrowth(
this._data!.stats[flow.stat_cost],
"sum_decrease"
) || 0) * 1;
} else {
const cost_stat_increase =
this._data!.info.cost_sensors[flow.stat_energy_net]
.increase;
const cost_stat_decrease =
this._data!.info.cost_sensors[flow.stat_energy_net]
.decrease;
costIncrease = cost_stat_increase
? calculateStatisticSumGrowth(
this._data!.stats[cost_stat_increase]
) || 0
: null;
costDecrease = cost_stat_decrease
? (calculateStatisticSumGrowth(
this._data!.stats[cost_stat_decrease]
) || 0) * -1
: null;
}
if (costIncrease !== null) {
totalGridCost += costIncrease;
}
if (costDecrease !== null) {
totalGridCost += costDecrease;
}
const colorFrom =
idx > 0
? rgb2hex(
lab2rgb(
labDarken(
rgb2lab(hex2rgb(consumptionColor)),
source.flow_from.length + idx
)
)
)
: returnColor;
const colorTo =
idx > 0
? rgb2hex(
lab2rgb(
labDarken(
rgb2lab(hex2rgb(returnColor)),
source.flow_to.length + idx
)
)
)
: returnColor;
return html`<tr class="mdc-data-table__row">
<td class="mdc-data-table__cell cell-bullet">
<div
class="bullet"
style=${styleMap({
borderColor: colorFrom,
backgroundColor: colorFrom + "7F",
})}
></div>
</td>
<th class="mdc-data-table__cell" scope="row">
${entity
? computeStateName(entity)
: flow.stat_energy_net}
</th>
<td
class="mdc-data-table__cell mdc-data-table__cell--numeric"
>
${formatNumber(energy_from, this.hass.locale)} kWh
</td>
${showCosts
? html` <td
class="mdc-data-table__cell mdc-data-table__cell--numeric"
>
${costIncrease !== null
? formatNumber(costIncrease, this.hass.locale, {
style: "currency",
currency: this.hass.config.currency!,
})
: ""}
</td>`
: ""}
</tr>
<tr class="mdc-data-table__row">
<td class="mdc-data-table__cell cell-bullet">
<div
class="bullet"
style=${styleMap({
borderColor: colorTo,
backgroundColor: colorTo + "7F",
})}
></div>
</td>
<th class="mdc-data-table__cell" scope="row">
${entity
? computeStateName(entity)
: flow.stat_energy_net}
</th>
<td
class="mdc-data-table__cell mdc-data-table__cell--numeric"
>
${formatNumber(energy_to, this.hass.locale)} kWh
</td>
${showCosts
? html` <td
class="mdc-data-table__cell mdc-data-table__cell--numeric"
>
${costDecrease !== null
? formatNumber(costDecrease, this.hass.locale, {
style: "currency",
currency: this.hass.config.currency!,
})
: ""}
</td>`
: ""}
</tr>`;
})}`
)}
${types.grid
@@ -594,7 +447,7 @@ export class HuiEnergySourcesTableCard
totalGas += energy;
const cost_stat =
source.stat_cost ||
this._data!.info.cost_sensors[source.stat_energy_from].none;
this._data!.info.cost_sensors[source.stat_energy_from];
const cost = cost_stat
? calculateStatisticSumGrowth(this._data!.stats[cost_stat]) ||
0
@@ -231,7 +231,6 @@ export class HuiEnergyUsageGraphCard
const statistics: {
to_grid?: string[];
from_grid?: string[];
net_grid?: string[];
solar?: string[];
to_battery?: string[];
from_battery?: string[];
@@ -277,15 +276,6 @@ export class HuiEnergyUsageGraphCard
statistics.to_grid = [flowTo.stat_energy_to];
}
}
if (source.flow_net) {
for (const flowNet of source.flow_net) {
if (statistics.net_grid) {
statistics.net_grid.push(flowNet.stat_energy_net);
} else {
statistics.net_grid = [flowNet.stat_energy_net];
}
}
}
}
const dayDifference = differenceInDays(
@@ -335,8 +325,6 @@ export class HuiEnergyUsageGraphCard
to_battery?: { [start: string]: number };
from_battery?: { [start: string]: number };
solar?: { [start: string]: number };
net_grid_increased?: { [start: string]: number };
net_grid_decreased?: { [start: string]: number };
} = {};
const computedStyles = getComputedStyle(this);
@@ -378,15 +366,9 @@ export class HuiEnergyUsageGraphCard
"to_battery",
"from_battery",
].includes(key);
const add = !["solar", "from_battery", "net_grid"].includes(key);
const add = !["solar", "from_battery"].includes(key);
const totalStats: { [start: string]: number } = {};
const totalStatsInc: { [start: string]: number } = {};
const totalStatsDec: { [start: string]: number } = {};
const sets: { [statId: string]: { [start: string]: number } } = {};
const setsNet: {
setInc: { [statId: string]: { [start: string]: number } };
setDec: { [statId: string]: { [start: string]: number } };
} = { setInc: {}, setDec: {} };
statIds!.forEach((id) => {
const stats =
dayDifference > 35
@@ -398,111 +380,39 @@ export class HuiEnergyUsageGraphCard
return;
}
if (key === "net_grid") {
const setInc = {};
const setDec = {};
let prevValueInc: number;
let prevValueDec: number;
stats.forEach((stat) => {
if (
stat.sum_decrease === null ||
stat.sum_decrease === undefined ||
stat.sum_increase === null ||
stat.sum_increase === undefined
) {
return;
}
if (prevValueInc === undefined) {
prevValueInc = stat.sum_increase;
prevValueDec = stat.sum_decrease;
return;
}
const valIncrease = stat.sum_increase - prevValueInc;
const valDecrease = stat.sum_decrease - prevValueDec;
totalStatsInc[stat.start] =
stat.start in totalStatsInc
? totalStatsInc[stat.start] + valIncrease
: valIncrease;
totalStatsDec[stat.start] =
stat.start in totalStatsDec
? totalStatsDec[stat.start] + valDecrease
: valDecrease;
if (!(stat.start in setInc)) {
setInc[stat.start] = valIncrease;
setDec[stat.start] = valDecrease;
}
prevValueInc = stat.sum_increase;
prevValueDec = stat.sum_decrease;
});
setsNet.setInc[id] = setInc;
setsNet.setDec[id] = setDec;
} else {
const set = {};
let prevValue: number;
stats.forEach((stat) => {
if (stat.sum === null) {
return;
}
if (prevValue === undefined) {
prevValue = stat.sum;
return;
}
const val = stat.sum - prevValue;
if (sum) {
totalStats[stat.start] =
stat.start in totalStats ? totalStats[stat.start] + val : val;
}
if (add && !(stat.start in set)) {
set[stat.start] = val;
}
const set = {};
let prevValue: number;
stats.forEach((stat) => {
if (stat.sum === null) {
return;
}
if (prevValue === undefined) {
prevValue = stat.sum;
});
sets[id] = set;
}
return;
}
const val = stat.sum - prevValue;
// Get total of solar and to grid to calculate the solar energy used
if (sum) {
totalStats[stat.start] =
stat.start in totalStats ? totalStats[stat.start] + val : val;
}
if (add && !(stat.start in set)) {
set[stat.start] = val;
}
prevValue = stat.sum;
});
sets[id] = set;
});
if (key === "net_grid") {
combinedData.from_grid = {
...combinedData.from_grid,
...setsNet.setInc,
};
combinedData.to_grid = { ...combinedData.to_grid, ...setsNet.setDec };
summedData.net_grid_increased = totalStatsInc;
summedData.net_grid_decreased = totalStatsDec;
return;
}
if (sum) {
summedData[key] = totalStats;
}
if (add) {
combinedData[key] = { ...combinedData[key], ...sets };
combinedData[key] = sets;
}
});
if (summedData.net_grid_increased && summedData.net_grid_decreased) {
if (!summedData.to_grid && !summedData.from_grid) {
summedData.to_grid = summedData.net_grid_increased;
summedData.from_grid = summedData.net_grid_decreased;
} else {
if (!summedData.to_grid) {
summedData.to_grid = {};
}
if (!summedData.from_grid) {
summedData.from_grid = {};
}
for (const start of Object.keys(summedData.net_grid_increased)) {
summedData.to_grid[start] =
(summedData.to_grid[start] || 0) +
summedData.net_grid_increased[start];
summedData.from_grid[start] =
(summedData.from_grid[start] || 0) +
summedData.net_grid_decreased[start];
}
}
}
const grid_to_battery = {};
const battery_to_grid = {};
if ((summedData.to_grid || summedData.to_battery) && summedData.solar) {
const used_solar = {};
for (const start of Object.keys(summedData.solar)) {
@@ -12,7 +12,6 @@ import { customElement, property, state, query } from "lit/decorators";
import { classMap } from "lit/directives/class-map";
import { applyThemesOnElement } from "../../../common/dom/apply_themes_on_element";
import { fireEvent } from "../../../common/dom/fire_event";
import { alarmPanelIcon } from "../../../common/entity/alarm_panel_icon";
import "../../../components/ha-card";
import "../../../components/ha-label-badge";
import {
@@ -25,6 +24,17 @@ import { createEntityNotFoundWarning } from "../components/hui-warning";
import type { LovelaceCard } from "../types";
import { AlarmPanelCardConfig } from "./types";
const ICONS = {
armed_away: "hass:shield-lock",
armed_custom_bypass: "hass:security",
armed_home: "hass:shield-home",
armed_night: "hass:shield-moon",
armed_vacation: "hass:shield-lock",
disarmed: "hass:shield-check",
pending: "hass:shield-outline",
triggered: "hass:bell-ring",
};
const BUTTONS = ["1", "2", "3", "4", "5", "6", "7", "8", "9", "", "0", "clear"];
@customElement("hui-alarm-panel-card")
@@ -152,7 +162,7 @@ class HuiAlarmPanelCard extends LitElement implements LovelaceCard {
>
<ha-label-badge
class="${classMap({ [stateObj.state]: true })}"
.icon="${alarmPanelIcon(stateObj.state)}"
.icon="${ICONS[stateObj.state] || "hass:shield-outline"}"
.label="${this._stateIconLabel(stateObj.state)}"
@click=${this._handleMoreInfo}
></ha-label-badge>
@@ -55,7 +55,6 @@ export class HuiCreateDialogCard
public closeDialog(): boolean {
this._params = undefined;
this._currTabIndex = 0;
this._selectedEntities = [];
fireEvent(this, "dialog-closed", { dialog: this.localName });
return true;
}
@@ -1,9 +1,10 @@
import "@polymer/paper-dialog-scrollable/paper-dialog-scrollable";
import deepFreeze from "deep-freeze";
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { customElement, property, state } from "lit/decorators";
import { customElement, property, state, query } from "lit/decorators";
import { fireEvent } from "../../../../common/dom/fire_event";
import "../../../../components/dialog/ha-paper-dialog";
import type { HaPaperDialog } from "../../../../components/dialog/ha-paper-dialog";
import type { LovelaceCardConfig } from "../../../../data/lovelace";
import { haStyleDialog } from "../../../../resources/styles";
import type { HomeAssistant } from "../../../../types";
@@ -18,18 +19,16 @@ export class HuiDialogDeleteCard extends LitElement {
@state() private _cardConfig?: LovelaceCardConfig;
@query("ha-paper-dialog", true) private _dialog!: HaPaperDialog;
public async showDialog(params: DeleteCardDialogParams): Promise<void> {
this._params = params;
this._cardConfig = params.cardConfig;
if (!Object.isFrozen(this._cardConfig)) {
this._cardConfig = deepFreeze(this._cardConfig);
}
}
public closeDialog(): void {
this._params = undefined;
this._cardConfig = undefined;
fireEvent(this, "dialog-closed", { dialog: this.localName });
await this.updateComplete;
fireEvent(this._dialog as HTMLElement, "iron-resize");
}
protected render(): TemplateResult {
@@ -38,12 +37,9 @@ export class HuiDialogDeleteCard extends LitElement {
}
return html`
<ha-dialog
open
@closed=${this.closeDialog}
.heading=${this.hass.localize("ui.panel.lovelace.cards.confirm_delete")}
>
<div>
<ha-paper-dialog with-backdrop opened modal>
<h2>${this.hass.localize("ui.panel.lovelace.cards.confirm_delete")}</h2>
<paper-dialog-scrollable>
${this._cardConfig
? html`
<div class="element-preview">
@@ -54,18 +50,16 @@ export class HuiDialogDeleteCard extends LitElement {
</div>
`
: ""}
</paper-dialog-scrollable>
<div class="paper-dialog-buttons">
<mwc-button @click="${this._close}">
${this.hass!.localize("ui.common.cancel")}
</mwc-button>
<mwc-button class="warning" @click="${this._delete}">
${this.hass!.localize("ui.common.delete")}
</mwc-button>
</div>
<mwc-button slot="secondaryAction" @click="${this.closeDialog}">
${this.hass!.localize("ui.common.cancel")}
</mwc-button>
<mwc-button
slot="primaryAction"
class="warning"
@click="${this._delete}"
>
${this.hass!.localize("ui.common.delete")}
</mwc-button>
</ha-dialog>
</ha-paper-dialog>
`;
}
@@ -86,12 +80,17 @@ export class HuiDialogDeleteCard extends LitElement {
];
}
private _close(): void {
this._params = undefined;
this._cardConfig = undefined;
}
private _delete(): void {
if (!this._params?.deleteCard) {
return;
}
this._params.deleteCard();
this.closeDialog();
this._close();
}
}
@@ -1,8 +1,7 @@
import "@polymer/paper-dialog-scrollable/paper-dialog-scrollable";
import deepFreeze from "deep-freeze";
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { customElement, property, query, state } from "lit/decorators";
import { fireEvent } from "../../../../common/dom/fire_event";
import { customElement, property, state, query } from "lit/decorators";
import "../../../../components/dialog/ha-paper-dialog";
import "../../../../components/ha-yaml-editor";
import type { HaYamlEditor } from "../../../../components/ha-yaml-editor";
@@ -48,26 +47,16 @@ export class HuiDialogSuggestCard extends LitElement {
}
}
public closeDialog(): void {
this._params = undefined;
this._cardConfig = undefined;
fireEvent(this, "dialog-closed", { dialog: this.localName });
}
protected render(): TemplateResult {
if (!this._params) {
return html``;
}
return html`
<ha-dialog
open
scrimClickAction
@closed=${this.closeDialog}
.heading=${this.hass!.localize(
"ui.panel.lovelace.editor.suggest_card.header"
)}
>
<div>
<ha-paper-dialog with-backdrop opened>
<h2>
${this.hass!.localize("ui.panel.lovelace.editor.suggest_card.header")}
</h2>
<paper-dialog-scrollable>
${this._cardConfig
? html`
<div class="element-preview">
@@ -91,39 +80,37 @@ export class HuiDialogSuggestCard extends LitElement {
</div>
`
: ""}
</paper-dialog-scrollable>
<div class="paper-dialog-buttons">
<mwc-button @click="${this._close}">
${this._params.yaml
? this.hass!.localize("ui.common.close")
: this.hass!.localize("ui.common.cancel")}
</mwc-button>
${!this._params.yaml
? html`
<mwc-button @click="${this._pickCard}"
>${this.hass!.localize(
"ui.panel.lovelace.editor.suggest_card.create_own"
)}</mwc-button
>
<mwc-button ?disabled="${this._saving}" @click="${this._save}">
${this._saving
? html`
<ha-circular-progress
active
title="Saving"
size="small"
></ha-circular-progress>
`
: this.hass!.localize(
"ui.panel.lovelace.editor.suggest_card.add"
)}
</mwc-button>
`
: ""}
</div>
<mwc-button slot="secondaryAction" @click="${this.closeDialog}">
${this._params.yaml
? this.hass!.localize("ui.common.close")
: this.hass!.localize("ui.common.cancel")}
</mwc-button>
${!this._params.yaml
? html`
<mwc-button slot="primaryAction" @click="${this._pickCard}"
>${this.hass!.localize(
"ui.panel.lovelace.editor.suggest_card.create_own"
)}</mwc-button
>
<mwc-button
slot="primaryAction"
.disabled="${this._saving}"
@click="${this._save}"
>
${this._saving
? html`
<ha-circular-progress
active
title="Saving"
size="small"
></ha-circular-progress>
`
: this.hass!.localize(
"ui.panel.lovelace.editor.suggest_card.add"
)}
</mwc-button>
`
: ""}
</ha-dialog>
</ha-paper-dialog>
`;
}
@@ -133,17 +120,17 @@ export class HuiDialogSuggestCard extends LitElement {
css`
@media all and (max-width: 450px), all and (max-height: 500px) {
/* overrule the ha-style-dialog max-height on small screens */
ha-dialog {
ha-paper-dialog {
max-height: 100%;
height: 100%;
}
}
@media all and (min-width: 850px) {
ha-dialog {
ha-paper-dialog {
width: 845px;
}
}
ha-dialog {
ha-paper-dialog {
max-width: 845px;
--dialog-z-index: 5;
}
@@ -167,6 +154,11 @@ export class HuiDialogSuggestCard extends LitElement {
];
}
private _close(): void {
this._params = undefined;
this._cardConfig = undefined;
}
private _pickCard(): void {
if (
!this._params?.lovelaceConfig ||
@@ -182,7 +174,7 @@ export class HuiDialogSuggestCard extends LitElement {
path: this._params!.path,
entities: this._params!.entities,
});
this.closeDialog();
this._close();
}
private async _save(): Promise<void> {
@@ -204,7 +196,7 @@ export class HuiDialogSuggestCard extends LitElement {
);
this._saving = false;
showSaveSuccessToast(this, this.hass);
this.closeDialog();
this._close();
}
}
@@ -68,7 +68,9 @@ export class HuiEntityPickerTable extends LitElement {
<div @click=${this._handleEntityClicked} style="cursor: pointer;">
${name}
${narrow
? html` <div class="secondary">${entity.entity_id}</div> `
? html`
<div class="secondary">${entity.stateObj.entity_id}</div>
`
: ""}
</div>
`,
@@ -89,9 +89,9 @@ export class HuiUnusedEntities extends LitElement {
icon: "",
entity_id: entity,
stateObj,
name: stateObj ? computeStateName(stateObj) : "Unavailable",
name: computeStateName(stateObj),
domain: computeDomain(entity),
last_changed: stateObj?.last_changed,
last_changed: stateObj!.last_changed,
};
}) as DataTableRowData[]}
@selected-changed=${this._handleSelectedChanged}
+9 -21
View File
@@ -1117,13 +1117,9 @@
"title": "Unexpected unit of measurement",
"description": "The following entities do not have the expected units of measurement ''{currency}/kWh'' or ''{currency}/Wh'':"
},
"entity_unexpected_state_class": {
"entity_unexpected_state_class_total_increasing": {
"title": "Unexpected state class",
"description": "The following entities do not have the expected state class:"
},
"entity_state_class_measurement_no_last_reset": {
"title": "Last reset missing",
"description": "The following entities have state class 'measurement' but 'last_reset' is missing:"
"description": "The following entities do not have the expected state class 'total_increasing'"
}
}
}
@@ -1254,8 +1250,7 @@
"open": "Open",
"add_dashboard": "Add dashboard"
},
"confirm_delete_title": "Delete {dashboard_title}?",
"confirm_delete_text": "Your dashboard will be permanently deleted.",
"confirm_delete": "Are you sure you want to delete this dashboard?",
"cant_edit_yaml": "Dashboards defined in YAML cannot be edited from the UI. Change them in configuration.yaml.",
"cant_edit_default": "The standard Lovelace dashboard cannot be edited from the UI. You can hide it by setting another dashboard as default.",
"detail": {
@@ -4079,7 +4074,7 @@
"unsupported_reason": {
"apparmor": "AppArmor is not enabled on the host",
"container": "Containers known to cause issues",
"content_trust": "Content-trust validation is disabled",
"content-trust": "Content-trust validation is disabled",
"dbus": "DBUS",
"docker_configuration": "Docker Configuration",
"docker_version": "Docker Version",
@@ -4087,9 +4082,7 @@
"lxc": "LXC",
"network_manager": "Network Manager",
"os": "Operating System",
"os_agent": "OS Agent",
"privileged": "Supervisor is not privileged",
"source_mods": "Source modifications",
"systemd": "Systemd"
},
"unhealthy_reason": {
@@ -4106,7 +4099,7 @@
"failed_to_shutdown": "Failed to shutdown the host",
"failed_to_set_hostname": "Setting hostname failed",
"failed_to_import_from_usb": "Failed to import from USB",
"failed_to_move": "Failed to move datadisk",
"failed_to_move": "Failed to move data disk",
"used_space": "Used space",
"hostname": "Hostname",
"change_hostname": "Change Hostname",
@@ -4123,7 +4116,7 @@
"shutdown_host": "Shutdown host",
"hardware": "Hardware",
"import_from_usb": "Import from USB",
"move_datadisk": "Move datadisk"
"data_disk_move": "Move to data disk"
},
"core": {
"cpu_usage": "Core CPU Usage",
@@ -4211,14 +4204,9 @@
"attributes": "Attributes",
"device_path": "Device path"
},
"datadisk_move": {
"title": "[%key:supervisor::system::host::move_datadisk%]",
"description": "You are currently using ''{current_path}'' as datadisk. Moving data disks will reboot your device and it's estimated to take {time} minutes. Your Home Assistant installation will not be accessible during this period. Do not disconnect the power during the move!",
"select_device": "Select new datadisk",
"no_devices": "No suitable attached devices found",
"moving_desc": "Rebooting and moving datadisk. Please have patience",
"moving": "Moving datadisk",
"loading_devices": "Loading devices",
"data_disk_move": {
"description": "The current path to the data disk is ''{current_path}'', moving the disk will require a reboot of the host which will be done automatically.",
"confirm_text": "Do you want to move now?",
"cancel": "[%key:ui::common::cancel%]",
"move": "Move"
}