Compare commits

..
Author SHA1 Message Date
J. Nick Koston dd00b51f21 Adjust WebSocket ping timeout to 15 seconds
5 seconds was too low to prevent the UI from reloading
when connecting the WebSocket during startup or on
a high latancy connection

This problem presented as the UI reloading over
and over again because it could never respond
to the ping in time on high latancy connections.

At startup it usually only did this once so it
went unnoticed in most cases.

This ping was added in #18934
2025-02-20 11:46:59 -06:00
J. Nick Koston 64b886eea0 Reduce size of address column on Bluetooth Advertisement monitor 2025-01-29 12:51:56 -06:00
19 changed files with 118 additions and 417 deletions
+1 -1
View File
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "home-assistant-frontend"
version = "20250130.0"
version = "20250129.0"
license = {text = "Apache-2.0"}
description = "The Home Assistant frontend"
readme = "README.md"
-10
View File
@@ -1,4 +1,3 @@
import memoizeOne from "memoize-one";
import { theme2hex } from "./convert-color";
export const COLORS = [
@@ -75,12 +74,3 @@ export function getGraphColorByIndex(
getColorByIndex(index);
return theme2hex(themeColor);
}
export const getAllGraphColors = memoizeOne(
(style: CSSStyleDeclaration) =>
COLORS.map((_color, index) => getGraphColorByIndex(index, style)),
(newArgs: [CSSStyleDeclaration], lastArgs: [CSSStyleDeclaration]) =>
// this is not ideal, but we need to memoize the colors
newArgs[0].getPropertyValue("--graph-color-1") ===
lastArgs[0].getPropertyValue("--graph-color-1")
);
+15 -180
View File
@@ -20,7 +20,6 @@ import type { ECOption } from "../../resources/echarts";
import { listenMediaQuery } from "../../common/dom/media_query";
import type { Themes } from "../../data/ws-themes";
import { themesContext } from "../../data/context";
import { getAllGraphColors } from "../../common/color/colors";
export const MIN_TIME_BETWEEN_UPDATES = 60 * 5 * 1000;
@@ -184,9 +183,10 @@ export class HaChartBase extends LitElement {
}
const echarts = (await import("../../resources/echarts")).default;
echarts.registerTheme("custom", this._createTheme());
this.chart = echarts.init(container, "custom");
this.chart = echarts.init(
container,
this._themes.darkMode ? "dark" : "light"
);
this.chart.on("legendselectchanged", (params: any) => {
if (this.externalHidden) {
const isSelected = params.selected[params.name];
@@ -237,14 +237,24 @@ export class HaChartBase extends LitElement {
}
private _createOptions(): ECOption {
const darkMode = this._themes.darkMode ?? false;
const options = {
backgroundColor: "transparent",
animation: !this._reducedMotion,
darkMode: this._themes.darkMode ?? false,
darkMode,
aria: {
show: true,
},
dataZoom: this._getDataZoomConfig(),
...this.options,
legend: this.options?.legend
? {
// we should create our own theme but this is a quick fix for now
inactiveColor: darkMode ? "#444" : "#ccc",
...this.options.legend,
}
: undefined,
};
const isMobile = window.matchMedia(
@@ -264,181 +274,6 @@ export class HaChartBase extends LitElement {
return options;
}
private _createTheme() {
const style = getComputedStyle(this);
return {
color: getAllGraphColors(style),
backgroundColor: "transparent",
textStyle: {
color: style.getPropertyValue("--primary-text-color"),
},
title: {
textStyle: {
color: style.getPropertyValue("--primary-text-color"),
},
subtextStyle: {
color: style.getPropertyValue("--secondary-text-color"),
},
},
line: {
lineStyle: {
width: 1.5,
},
symbolSize: 1,
symbol: "circle",
smooth: false,
},
bar: {
itemStyle: {
barBorderWidth: 1.5,
},
},
categoryAxis: {
axisLine: {
show: false,
},
axisTick: {
show: false,
},
axisLabel: {
show: true,
color: style.getPropertyValue("--primary-text-color"),
},
splitLine: {
show: false,
lineStyle: {
color: style.getPropertyValue("--divider-color"),
},
},
splitArea: {
show: false,
areaStyle: {
color: [
style.getPropertyValue("--divider-color") + "3F",
style.getPropertyValue("--divider-color") + "7F",
],
},
},
},
valueAxis: {
axisLine: {
show: true,
lineStyle: {
color: style.getPropertyValue("--divider-color"),
},
},
axisTick: {
show: true,
lineStyle: {
color: style.getPropertyValue("--divider-color"),
},
},
axisLabel: {
show: true,
color: style.getPropertyValue("--primary-text-color"),
},
splitLine: {
show: true,
lineStyle: {
color: style.getPropertyValue("--divider-color"),
},
},
splitArea: {
show: false,
areaStyle: {
color: [
style.getPropertyValue("--divider-color") + "3F",
style.getPropertyValue("--divider-color") + "7F",
],
},
},
},
logAxis: {
axisLine: {
show: true,
lineStyle: {
color: style.getPropertyValue("--divider-color"),
},
},
axisTick: {
show: true,
lineStyle: {
color: style.getPropertyValue("--divider-color"),
},
},
axisLabel: {
show: true,
color: style.getPropertyValue("--primary-text-color"),
},
splitLine: {
show: true,
lineStyle: {
color: style.getPropertyValue("--divider-color"),
},
},
splitArea: {
show: false,
areaStyle: {
color: [
style.getPropertyValue("--divider-color") + "3F",
style.getPropertyValue("--divider-color") + "7F",
],
},
},
},
timeAxis: {
axisLine: {
show: true,
lineStyle: {
color: style.getPropertyValue("--divider-color"),
},
},
axisTick: {
show: true,
lineStyle: {
color: style.getPropertyValue("--divider-color"),
},
},
axisLabel: {
show: true,
color: style.getPropertyValue("--primary-text-color"),
},
splitLine: {
show: true,
lineStyle: {
color: style.getPropertyValue("--divider-color"),
},
},
splitArea: {
show: false,
areaStyle: {
color: [
style.getPropertyValue("--divider-color") + "3F",
style.getPropertyValue("--divider-color") + "7F",
],
},
},
},
legend: {
textStyle: {
color: style.getPropertyValue("--primary-text-color"),
},
inactiveColor: style.getPropertyValue("--disabled-text-color"),
},
tooltip: {
axisPointer: {
lineStyle: {
color: style.getPropertyValue("--divider-color"),
},
crossStyle: {
color: style.getPropertyValue("--divider-color"),
},
},
},
timeline: {},
};
}
private _getDefaultHeight() {
return Math.max(this.clientWidth / 2, 400);
}
@@ -158,6 +158,9 @@ export class StateHistoryChartLine extends LitElement {
) {
const dayDifference = differenceInDays(this.endTime, this.startTime);
const rtl = computeRTL(this.hass);
const splitLineStyle = this.hass.themes?.darkMode
? { opacity: 0.15 }
: {};
this._chartOptions = {
xAxis: {
type: "time",
@@ -173,6 +176,7 @@ export class StateHistoryChartLine extends LitElement {
},
splitLine: {
show: true,
lineStyle: splitLineStyle,
},
minInterval:
dayDifference >= 89 // quarter
@@ -192,8 +196,9 @@ export class StateHistoryChartLine extends LitElement {
nameTextStyle: {
align: "left",
},
axisLine: {
show: false,
splitLine: {
show: true,
lineStyle: splitLineStyle,
},
axisLabel: {
margin: 5,
@@ -197,12 +197,9 @@ export class StateHistoryChartTimeline extends LitElement {
max: this.endTime,
axisTick: {
show: true,
},
splitLine: {
show: false,
},
axisLine: {
show: false,
lineStyle: {
opacity: 0.4,
},
},
axisLabel: getTimeAxisLabelConfig(
this.hass.locale,
+1 -4
View File
@@ -135,7 +135,7 @@ export class StateHistoryCharts extends LitElement {
return html``;
}
if (!Array.isArray(item)) {
return html`<div class="entry-container line">
return html`<div class="entry-container">
<state-history-chart-line
.hass=${this.hass}
.unit=${item.unit}
@@ -299,9 +299,6 @@ export class StateHistoryCharts extends LitElement {
.entry-container {
width: 100%;
}
.entry-container.line {
flex: 1;
}
@@ -106,7 +106,6 @@ export class HaConfigApplicationCredentials extends LitElement {
},
actions: {
title: "",
label: localize("ui.panel.config.generic.headers.actions"),
type: "overflow-menu",
showNarrow: true,
hideable: false,
@@ -378,9 +378,8 @@ class HaBackupConfigData extends LitElement {
}
@media all and (max-width: 450px) {
ha-md-select {
min-width: 140px;
width: 140px;
--md-filled-field-content-space: 0;
min-width: 160px;
width: 160px;
}
}
`;
@@ -403,11 +403,11 @@ class HaBackupConfigSchedule extends LitElement {
backup_create: html`<a
href=${documentationUrl(
this.hass,
"/integrations/backup/#action-backupcreate_automatic"
"/integrations/backup#example-backing-up-every-night-at-300-am"
)}
target="_blank"
rel="noopener noreferrer"
>backup.create_automatic</a
>backup.create</a
>`,
})}</ha-tip
>
@@ -545,12 +545,6 @@ class HaBackupConfigSchedule extends LitElement {
ha-md-select,
ha-time-input {
min-width: 160px;
width: 160px;
--md-filled-field-content-space: 0;
}
ha-time-input {
min-width: 120px;
width: 120px;
}
}
ha-md-textfield#value {
@@ -559,16 +553,6 @@ class HaBackupConfigSchedule extends LitElement {
ha-md-select#type {
min-width: 100px;
}
@media all and (max-width: 450px) {
ha-md-textfield#value {
min-width: 60px;
margin: 0 -8px;
}
ha-md-select#type {
min-width: 120px;
width: 120px;
}
}
ha-expansion-panel {
--expansion-panel-summary-padding: 0 16px;
--expansion-panel-content-padding: 0 16px;
@@ -141,10 +141,7 @@ class HaConfigBackupBackups extends SubscribeMixin(LitElement) {
};
private _columns = memoizeOne(
(
localize: LocalizeFunc,
maxDisplayedAgents: number
): DataTableColumnContainer<BackupRow> => ({
(localize: LocalizeFunc): DataTableColumnContainer<BackupRow> => ({
name: {
title: localize("ui.panel.config.backup.name"),
main: true,
@@ -175,75 +172,54 @@ class HaConfigBackupBackups extends SubscribeMixin(LitElement) {
locations: {
title: localize("ui.panel.config.backup.locations"),
showNarrow: true,
// 24 icon size, 4 gap, 16 left and right padding
minWidth: `${maxDisplayedAgents * 24 + (maxDisplayedAgents - 1) * 4 + 32}px`,
template: (backup) => {
const agentIds = backup.agent_ids;
const displayedAgentIds =
agentIds.length > maxDisplayedAgents
? [...agentIds].splice(0, maxDisplayedAgents - 1)
: agentIds;
const agentsMore = Math.max(
agentIds.length - displayedAgentIds.length,
0
);
return html`
<div style="display: flex; gap: 4px;">
${displayedAgentIds.map((agentId) => {
const name = computeBackupAgentName(
this.hass.localize,
agentId,
this.agents
);
if (isLocalAgent(agentId)) {
return html`
<ha-svg-icon
.path=${mdiHarddisk}
title=${name}
style="flex-shrink: 0;"
></ha-svg-icon>
`;
}
if (isNetworkMountAgent(agentId)) {
return html`
<ha-svg-icon
.path=${mdiNas}
title=${name}
style="flex-shrink: 0;"
></ha-svg-icon>
`;
}
const domain = computeDomain(agentId);
minWidth: "60px",
template: (backup) => html`
<div style="display: flex; gap: 4px;">
${(backup.agent_ids || []).map((agentId) => {
const name = computeBackupAgentName(
this.hass.localize,
agentId,
this.agents
);
if (isLocalAgent(agentId)) {
return html`
<img
<ha-svg-icon
.path=${mdiHarddisk}
title=${name}
.src=${brandsUrl({
domain,
type: "icon",
useFallback: true,
darkOptimized: this.hass.themes?.darkMode,
})}
height="24"
crossorigin="anonymous"
referrerpolicy="no-referrer"
alt=${name}
slot="graphic"
style="flex-shrink: 0;"
/>
></ha-svg-icon>
`;
})}
${agentsMore
? html`
<span
style="display: flex; align-items: center; font-size: 14px;"
>
+${agentsMore}
</span>
`
: nothing}
</div>
`;
},
}
if (isNetworkMountAgent(agentId)) {
return html`
<ha-svg-icon
.path=${mdiNas}
title=${name}
style="flex-shrink: 0;"
></ha-svg-icon>
`;
}
const domain = computeDomain(agentId);
return html`
<img
title=${name}
.src=${brandsUrl({
domain,
type: "icon",
useFallback: true,
darkOptimized: this.hass.themes?.darkMode,
})}
height="24"
crossorigin="anonymous"
referrerpolicy="no-referrer"
alt=${name}
slot="graphic"
style="flex-shrink: 0;"
/>
`;
})}
</div>
`,
},
actions: {
title: "",
@@ -317,31 +293,20 @@ class HaConfigBackupBackups extends SubscribeMixin(LitElement) {
}
return filteredBackups.map((backup) => {
const type = backup.with_automatic_settings ? "automatic" : "manual";
const agentIds = Object.keys(backup.agents);
return {
...backup,
size: computeBackupSize(backup),
agent_ids: agentIds.sort(compareAgents),
agent_ids: Object.keys(backup.agents).sort(compareAgents),
formatted_type: localize(`ui.panel.config.backup.type.${type}`),
};
});
}
);
private _maxAgents = memoizeOne((data: BackupRow[]): number =>
Math.max(...data.map((row) => row.agent_ids.length))
);
protected render(): TemplateResult {
const backupInProgress =
"state" in this.manager && this.manager.state === "in_progress";
const data = this._data(this.backups, this._filters, this.hass.localize);
const maxDisplayedAgents = Math.min(
this._maxAgents(data),
this.narrow ? 3 : 5
);
return html`
<hass-tabs-subpage-data-table
has-fab
@@ -378,8 +343,8 @@ class HaConfigBackupBackups extends SubscribeMixin(LitElement) {
@selection-changed=${this._handleSelectionChanged}
.route=${this.route}
@row-click=${this._showBackupDetails}
.columns=${this._columns(this.hass.localize, maxDisplayedAgents)}
.data=${data}
.columns=${this._columns(this.hass.localize)}
.data=${this._data(this.backups, this._filters, this.hass.localize)}
.noDataText=${this.hass.localize("ui.panel.config.backup.no_backups")}
.searchLabel=${this.hass.localize(
"ui.panel.config.backup.picker.search"
@@ -50,11 +50,9 @@ class HaConfigBackupSettings extends LitElement {
}
}
public connectedCallback(): void {
super.connectedCallback();
protected firstUpdated(_changedProperties: PropertyValues): void {
super.firstUpdated(_changedProperties);
this._scrollToSection();
// Update config the page is displayed (e.g. when coming back from a location detail page)
this._config = this.config;
}
private async _scrollToSection() {
@@ -119,7 +119,6 @@ class HaConfigBackup extends SubscribeMixin(HassRouterPage) {
settings: {
tag: "ha-config-backup-settings",
load: () => import("./ha-config-backup-settings"),
cache: true,
},
location: {
tag: "ha-config-backup-location",
@@ -1,9 +1,8 @@
import type { UnsubscribeFunc } from "home-assistant-js-websocket";
import type { CSSResultGroup, TemplateResult } from "lit";
import { html, LitElement } from "lit";
import { customElement, property, state } from "lit/decorators";
import memoizeOne from "memoize-one";
import { storage } from "../../../../../common/decorators/storage";
import type { UnsubscribeFunc } from "home-assistant-js-websocket";
import type { HASSDomEvent } from "../../../../../common/dom/fire_event";
import type { LocalizeFunc } from "../../../../../common/translations/localize";
import type {
@@ -12,6 +11,9 @@ import type {
} from "../../../../../components/data-table/ha-data-table";
import "../../../../../components/ha-fab";
import "../../../../../components/ha-icon-button";
import "../../../../../layouts/hass-tabs-subpage-data-table";
import { haStyle } from "../../../../../resources/styles";
import type { HomeAssistant, Route } from "../../../../../types";
import type {
BluetoothDeviceData,
BluetoothScannersDetails,
@@ -20,10 +22,6 @@ import {
subscribeBluetoothAdvertisements,
subscribeBluetoothScannersDetails,
} from "../../../../../data/bluetooth";
import type { DeviceRegistryEntry } from "../../../../../data/device_registry";
import "../../../../../layouts/hass-tabs-subpage-data-table";
import { haStyle } from "../../../../../resources/styles";
import type { HomeAssistant, Route } from "../../../../../types";
import { showBluetoothDeviceInfoDialog } from "./show-dialog-bluetooth-device-info";
@customElement("bluetooth-advertisement-monitor")
@@ -40,22 +38,6 @@ export class BluetoothAdvertisementMonitorPanel extends LitElement {
@state() private _scanners: BluetoothScannersDetails = {};
@state() private _sourceDevices: Record<string, DeviceRegistryEntry> = {};
@storage({
key: "bluetooth-advertisement-table-grouping",
state: false,
subscribe: false,
})
private _activeGrouping?: string = "source";
@storage({
key: "bluetooth-advertisement-table-collapsed",
state: false,
subscribe: false,
})
private _activeCollapsed: string[] = [];
private _unsub_advertisements?: UnsubscribeFunc;
private _unsub_scanners?: UnsubscribeFunc;
@@ -75,19 +57,6 @@ export class BluetoothAdvertisementMonitorPanel extends LitElement {
this._scanners = scanners;
}
);
const devices = Object.values(this.hass.devices);
const bluetoothDevices = devices.filter((device) =>
device.connections.find((connection) => connection[0] === "bluetooth")
);
this._sourceDevices = Object.fromEntries(
bluetoothDevices.map((device) => {
const connection = device.connections.find(
(c) => c[0] === "bluetooth"
)!;
return [connection[1], device];
})
);
}
}
@@ -122,28 +91,14 @@ export class BluetoothAdvertisementMonitorPanel extends LitElement {
filterable: true,
sortable: true,
},
device: {
title: localize("ui.panel.config.bluetooth.device"),
filterable: true,
sortable: true,
template: (data) => html`${data.device || "-"}`,
},
source: {
title: localize("ui.panel.config.bluetooth.source"),
filterable: true,
sortable: true,
groupable: true,
},
source_address: {
title: localize("ui.panel.config.bluetooth.source_address"),
filterable: true,
sortable: true,
defaultHidden: true,
},
rssi: {
title: localize("ui.panel.config.bluetooth.rssi"),
type: "numeric",
maxWidth: "60px",
sortable: true,
},
};
@@ -153,22 +108,11 @@ export class BluetoothAdvertisementMonitorPanel extends LitElement {
);
private _dataWithNamedSourceAndIds = memoizeOne((data) =>
data.map((row) => {
const device = this._sourceDevices[row.address];
const scannerDevice = this._sourceDevices[row.source];
const scanner = this._scanners[row.source];
return {
...row,
id: row.address,
source_address: row.source,
source:
scannerDevice?.name_by_user ||
scannerDevice?.name ||
scanner?.name ||
row.source,
device: device?.name_by_user || device?.name || undefined,
};
})
data.map((row) => ({
...row,
id: row.address,
source: this._scanners[row.source]?.name || row.source,
}))
);
protected render(): TemplateResult {
@@ -180,23 +124,11 @@ export class BluetoothAdvertisementMonitorPanel extends LitElement {
.columns=${this._columns(this.hass.localize)}
.data=${this._dataWithNamedSourceAndIds(this._data)}
@row-click=${this._handleRowClicked}
.initialGroupColumn=${this._activeGrouping}
.initialCollapsedGroups=${this._activeCollapsed}
@grouping-changed=${this._handleGroupingChanged}
@collapsed-changed=${this._handleCollapseChanged}
clickable
></hass-tabs-subpage-data-table>
`;
}
private _handleGroupingChanged(ev: CustomEvent) {
this._activeGrouping = ev.detail.value;
}
private _handleCollapseChanged(ev: CustomEvent) {
this._activeCollapsed = ev.detail.value;
}
private _handleRowClicked(ev: HASSDomEvent<RowClickedEvent>) {
const entry = this._data.find((ent) => ent.address === ev.detail.id);
showBluetoothDeviceInfoDialog(this, {
@@ -53,6 +53,8 @@ class DialogBluetoothDeviceInfo extends LitElement implements HassDialog {
return html`
<ha-dialog
open
scrimClickAction
escapeKeyAction
@closed=${this.closeDialog}
.heading=${createCloseHeading(
this.hass,
@@ -314,9 +314,8 @@ export class HuiEnergyDevicesDetailGraphCard
processedData.forEach((device) => {
device.data.forEach((datapoint) => {
totalDeviceConsumption[datapoint[compare ? 2 : 0]] =
(totalDeviceConsumption[datapoint[compare ? 2 : 0]] || 0) +
datapoint[1];
totalDeviceConsumption[datapoint[0]] =
(totalDeviceConsumption[datapoint[0]] || 0) + datapoint[1];
});
});
const compareOffset = compare
@@ -334,12 +333,10 @@ export class HuiEnergyDevicesDetailGraphCard
}
untrackedConsumption.push(dataPoint);
});
// random id to always add untracked at the end
const order = Date.now();
const dataset: BarSeriesOption = {
type: "bar",
cursor: "default",
id: compare ? `compare-untracked-${order}` : `untracked-${order}`,
id: compare ? "compare-untracked" : "untracked",
name: this.hass.localize(
"ui.panel.lovelace.cards.energy.energy_devices_detail_graph.untracked_consumption"
),
@@ -422,10 +419,9 @@ export class HuiEnergyDevicesDetailGraphCard
data.push({
type: "bar",
cursor: "default",
// add order to id, otherwise echarts refuses to reorder them
id: compare
? `compare-${source.stat_consumption}-${order}`
: `${source.stat_consumption}-${order}`,
? `compare-${source.stat_consumption}`
: source.stat_consumption,
name:
source.name ||
getStatisticLabel(
@@ -442,9 +438,7 @@ export class HuiEnergyDevicesDetailGraphCard
stack: compare ? "devicesCompare" : "devices",
});
});
return sorted_devices.map(
(device) => data.find((d) => (d.id as string).includes(device))!
);
return data;
}
static styles = css`
@@ -88,7 +88,7 @@ export class HuiEnergyDevicesGraphCard
<ha-chart-base
.hass=${this.hass}
.data=${this._chartData}
.options=${this._createOptions(this._chartData)}
.options=${this._createOptions(this.hass.themes?.darkMode)}
.height=${`${(this._chartData[0]?.data?.length || 0) * 28 + 50}px`}
@chart-click=${this._handleChartClick}
></ha-chart-base>
@@ -110,17 +110,18 @@ export class HuiEnergyDevicesGraphCard
}
private _createOptions = memoizeOne(
(data: BarSeriesOption[]): ECOption => ({
(darkMode: boolean): ECOption => ({
xAxis: {
type: "value",
name: "kWh",
splitLine: {
lineStyle: darkMode ? { opacity: 0.15 } : {},
},
},
yAxis: {
type: "category",
inverse: true,
triggerEvent: true,
// take order from data
data: data[0]?.data?.map((d: any) => d.value[1]),
axisLabel: {
formatter: this._getDeviceName.bind(this),
overflow: "truncate",
@@ -201,7 +201,9 @@ export class HuiGenericEntityRow extends LitElement {
padding-inline-end: 8px;
flex: 1 1 30%;
min-height: 40px;
line-height: 40px;
display: flex;
flex-direction: column;
justify-content: center;
}
.info,
.info > * {
@@ -236,7 +238,8 @@ export class HuiGenericEntityRow extends LitElement {
.value {
direction: ltr;
min-height: 40px;
line-height: 40px;
display: flex;
align-items: center;
}
`;
}
+5 -2
View File
@@ -286,7 +286,10 @@ export const connectionMixin = <T extends Constructor<HassBaseEl>>(
clearInterval(this.__backendPingInterval);
this.__backendPingInterval = setInterval(() => {
if (this.hass?.connected) {
promiseTimeout(5000, this.hass?.connection.ping()).catch(() => {
// If the backend if busy, or the connection is latent,
// it can take more than 10 seconds for the ping to return.
// We give it a 15 second timeout to be safe.
promiseTimeout(15000, this.hass?.connection.ping()).catch(() => {
if (!this.hass?.connected) {
return;
}
@@ -296,7 +299,7 @@ export const connectionMixin = <T extends Constructor<HassBaseEl>>(
this.hass?.connection.reconnect(true);
});
}
}, 10000);
}, 30000);
}
protected hassReconnected() {
+5 -7
View File
@@ -2678,19 +2678,19 @@
"encryption": {
"title": "Encryption",
"description": "All your backups are encrypted by default to keep your data private and secure.",
"location_encrypted": "Backups made to this location will be encrypted",
"location_unencrypted": "Backups made to this location will be unencrypted",
"location_encrypted_description": "Your data is private and secure by encrypting backups with your encryption key.",
"location_encrypted": "This location is encrypted",
"location_unencrypted": "This location is unencrypted",
"location_encrypted_description": "Your data private and secure by securing it with your encryption key.",
"location_encrypted_cloud_description": "Home Assistant Cloud is the privacy-focused cloud. This is why it will only accept encrypted backups and why we dont store your encryption key.",
"location_encrypted_cloud_learn_more": "Learn more",
"location_unencrypted_description": "Please keep your backups private and secure.",
"encryption_turn_on": "Turn on",
"encryption_turn_off": "Turn off",
"encryption_turn_off_confirm_title": "Turn encryption off?",
"encryption_turn_off_confirm_text": "After confirming, backups created will be unencrypted for this location. Please ensure your backups remain private and secure.",
"encryption_turn_off_confirm_text": "All your next backups will not be encrypted for this location. Please keep your backups private and secure.",
"encryption_turn_off_confirm_action": "Turn encryption off",
"warning_encryption_turn_off": "Encryption turned off",
"warning_encryption_turn_off_description": "Backups will be unencrypted."
"warning_encryption_turn_off_description": "All your next backups will not be encrypted."
}
}
},
@@ -5330,8 +5330,6 @@
"name": "Name",
"source": "Source",
"rssi": "RSSI",
"source_address": "Source address",
"device": "Device",
"device_information": "Device information",
"advertisement_data": "Advertisement data",
"manufacturer_data": "Manufacturer data",