Merge branch 'dev' into allthebackupchanges

This commit is contained in:
Paul Bottein 2024-11-20 09:30:54 +01:00
commit 7b4536564e
No known key found for this signature in database
15 changed files with 416 additions and 48 deletions

View File

@ -1,6 +1,6 @@
export default {
"*.?(c|m){js,ts}": [
"eslint --cache --cache-strategy=content --cache-location=node_modules/.cache/eslint/.eslintcache --fix",
"eslint --flag unstable_config_lookup_from_file --cache --cache-strategy=content --cache-location=node_modules/.cache/eslint/.eslintcache --fix",
"prettier --cache --write",
"lit-analyzer --quiet",
],

View File

@ -72,6 +72,12 @@ export class StatisticsChart extends LitElement {
@property() public chartType: ChartType = "line";
@property({ type: Number }) public minYAxis?: number;
@property({ type: Number }) public maxYAxis?: number;
@property({ type: Boolean }) public fitYData = false;
@property({ type: Boolean }) public hideLegend = false;
@property({ type: Boolean }) public logarithmicScale = false;
@ -113,6 +119,9 @@ export class StatisticsChart extends LitElement {
changedProps.has("unit") ||
changedProps.has("period") ||
changedProps.has("chartType") ||
changedProps.has("minYAxis") ||
changedProps.has("maxYAxis") ||
changedProps.has("fitYData") ||
changedProps.has("logarithmicScale") ||
changedProps.has("hideLegend")
) {
@ -232,6 +241,8 @@ export class StatisticsChart extends LitElement {
text: unit || this.unit,
},
type: this.logarithmicScale ? "logarithmic" : "linear",
min: this.fitYData ? null : this.minYAxis,
max: this.fitYData ? null : this.maxYAxis,
},
},
plugins: {

View File

@ -20,6 +20,8 @@ class HaHLSPlayer extends LitElement {
@property() public entityid?: string;
@property() public url?: string;
@property({ attribute: "poster-url" }) public posterUrl?: string;
@property({ type: Boolean, attribute: "controls" })
@ -94,14 +96,19 @@ class HaHLSPlayer extends LitElement {
super.updated(changedProps);
const entityChanged = changedProps.has("entityid");
const urlChanged = changedProps.has("url");
if (!entityChanged) {
return;
if (entityChanged) {
this._getStreamUrlFromEntityId();
} else if (urlChanged && this.url) {
this._cleanUp();
this._resetError();
this._url = this.url;
this._startHls();
}
this._getStreamUrl();
}
private async _getStreamUrl(): Promise<void> {
private async _getStreamUrlFromEntityId(): Promise<void> {
this._cleanUp();
this._resetError();

View File

@ -23,6 +23,7 @@ export const STATE_ATTRIBUTES = [
"state_class",
"supported_features",
"unit_of_measurement",
"available_tones",
];
export const TEMPERATURE_ATTRIBUTES = new Set([

7
src/data/siren.ts Normal file
View File

@ -0,0 +1,7 @@
export const SirenEntityFeature = {
TURN_ON: 1,
TURN_OFF: 2,
TONES: 4,
VOLUME_SET: 8,
DURATION: 16,
};

View File

@ -0,0 +1,224 @@
import type { CSSResultGroup } from "lit";
import { css, html, LitElement, nothing } from "lit";
import { customElement, property, query, state } from "lit/decorators";
import type { HassEntity } from "home-assistant-js-websocket";
import { mdiClose, mdiPlay, mdiStop } from "@mdi/js";
import type { HomeAssistant } from "../../../../types";
import { stopPropagation } from "../../../../common/dom/stop_propagation";
import {
getMobileCloseToBottomAnimation,
getMobileOpenFromBottomAnimation,
} from "../../../../components/ha-md-dialog";
import "../../../../components/ha-dialog-header";
import "../../../../components/ha-icon-button";
import "../../../../components/ha-button";
import "../../../../components/ha-textfield";
import "../../../../components/ha-control-button";
import "../../../../components/ha-select";
import "../../../../components/ha-list-item";
import type { HaMdDialog } from "../../../../components/ha-md-dialog";
import { fireEvent } from "../../../../common/dom/fire_event";
import { supportsFeature } from "../../../../common/entity/supports-feature";
import { SirenEntityFeature } from "../../../../data/siren";
import { haStyle } from "../../../../resources/styles";
@customElement("ha-more-info-siren-advanced-controls")
class MoreInfoSirenAdvancedControls extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@state() _stateObj?: HassEntity;
@state() _tone?: string;
@state() _volume?: number;
@state() _duration?: number;
@query("ha-md-dialog") private _dialog?: HaMdDialog;
public showDialog({ stateObj }: { stateObj: HassEntity }) {
this._stateObj = stateObj;
}
public closeDialog(): void {
this._dialog?.close();
}
private _dialogClosed(): void {
this._stateObj = undefined;
fireEvent(this, "dialog-closed", { dialog: this.localName });
}
render() {
if (!this._stateObj) {
return nothing;
}
const supportsTones =
supportsFeature(this._stateObj, SirenEntityFeature.TONES) &&
this._stateObj.attributes.available_tones;
const supportsVolume = supportsFeature(
this._stateObj,
SirenEntityFeature.VOLUME_SET
);
const supportsDuration = supportsFeature(
this._stateObj,
SirenEntityFeature.DURATION
);
return html`
<ha-md-dialog
open
@closed=${this._dialogClosed}
aria-labelledby="dialog-light-color-favorite-title"
.getOpenAnimation=${getMobileOpenFromBottomAnimation}
.getCloseAnimation=${getMobileCloseToBottomAnimation}
>
<ha-dialog-header slot="headline">
<ha-icon-button
slot="navigationIcon"
@click=${this.closeDialog}
.label=${this.hass.localize("ui.common.close")}
.path=${mdiClose}
></ha-icon-button>
<span slot="title" id="dialog-light-color-favorite-title"
>${this.hass.localize(
"ui.components.siren.advanced_controls"
)}</span
>
</ha-dialog-header>
<div slot="content">
<div class="options">
${supportsTones
? html`
<ha-select
.label=${this.hass.localize("ui.components.siren.tone")}
@closed=${stopPropagation}
@change=${this._handleToneChange}
.value=${this._tone}
>
${Object.entries(
this._stateObj.attributes.available_tones
).map(
([toneId, toneName]) => html`
<ha-list-item .value=${toneId}
>${toneName}</ha-list-item
>
`
)}
</ha-select>
`
: nothing}
${supportsVolume
? html`
<ha-textfield
type="number"
.label=${this.hass.localize("ui.components.siren.volume")}
.suffix=${"%"}
.value=${this._volume ? this._volume * 100 : undefined}
@change=${this._handleVolumeChange}
.min=${0}
.max=${100}
.step=${1}
></ha-textfield>
`
: nothing}
${supportsDuration
? html`
<ha-textfield
type="number"
.label=${this.hass.localize("ui.components.siren.duration")}
.value=${this._duration}
suffix="s"
@change=${this._handleDurationChange}
></ha-textfield>
`
: nothing}
</div>
<div class="controls">
<ha-control-button
.label=${this.hass.localize("ui.card.common.turn_on")}
@click=${this._turnOn}
>
<ha-svg-icon .path=${mdiPlay}></ha-svg-icon>
</ha-control-button>
<ha-control-button
.label=${this.hass.localize("ui.card.common.turn_off")}
@click=${this._turnOff}
>
<ha-svg-icon .path=${mdiStop}></ha-svg-icon>
</ha-control-button>
</div>
</div>
<div slot="actions">
<ha-button @click=${this.closeDialog}>
${this.hass.localize("ui.common.close")}
</ha-button>
</div>
</ha-md-dialog>
`;
}
private _handleToneChange(ev) {
this._tone = ev.target.value;
}
private _handleVolumeChange(ev) {
this._volume = parseFloat(ev.target.value) / 100;
if (isNaN(this._volume)) {
this._volume = undefined;
}
}
private _handleDurationChange(ev) {
this._duration = parseInt(ev.target.value);
if (isNaN(this._duration)) {
this._duration = undefined;
}
}
private async _turnOn() {
await this.hass.callService("siren", "turn_on", {
entity_id: this._stateObj!.entity_id,
tone: this._tone,
volume: this._volume,
duration: this._duration,
});
}
private async _turnOff() {
await this.hass.callService("siren", "turn_off", {
entity_id: this._stateObj!.entity_id,
});
}
static get styles(): CSSResultGroup {
return [
haStyle,
css`
.options {
display: flex;
flex-direction: column;
gap: 16px;
}
.controls {
display: flex;
flex-direction: row;
justify-content: center;
gap: 16px;
margin-top: 16px;
}
ha-control-button {
--control-button-border-radius: 16px;
--mdc-icon-size: 24px;
width: 64px;
height: 64px;
}
`,
];
}
}
declare global {
interface HTMLElementTagNameMap {
"ha-more-info-siren-advanced-controls": MoreInfoSirenAdvancedControls;
}
}

View File

@ -0,0 +1,18 @@
import type { HassEntity } from "home-assistant-js-websocket";
import { fireEvent } from "../../../../common/dom/fire_event";
export const loadSirenAdvancedControlsView = () =>
import("./ha-more-info-siren-advanced-controls");
export const showSirenAdvancedControlsView = (
element: HTMLElement,
stateObj: HassEntity
): void => {
fireEvent(element, "show-dialog", {
dialogTag: "ha-more-info-siren-advanced-controls",
dialogImport: loadSirenAdvancedControlsView,
dialogParams: {
stateObj,
},
});
};

View File

@ -5,9 +5,13 @@ import { LitElement, html, nothing } from "lit";
import { customElement, property } from "lit/decorators";
import "../../../components/ha-attributes";
import "../../../state-control/ha-state-control-toggle";
import "../../../components/ha-button";
import type { HomeAssistant } from "../../../types";
import "../components/ha-more-info-state-header";
import { moreInfoControlStyle } from "../components/more-info-control-style";
import { supportsFeature } from "../../../common/entity/supports-feature";
import { SirenEntityFeature } from "../../../data/siren";
import { showSirenAdvancedControlsView } from "../components/siren/show-dialog-siren-advanced-controls";
@customElement("more-info-siren")
class MoreInfoSiren extends LitElement {
@ -20,6 +24,20 @@ class MoreInfoSiren extends LitElement {
return nothing;
}
const supportsTones =
supportsFeature(this.stateObj, SirenEntityFeature.TONES) &&
this.stateObj.attributes.available_tones;
const supportsVolume = supportsFeature(
this.stateObj,
SirenEntityFeature.VOLUME_SET
);
const supportsDuration = supportsFeature(
this.stateObj,
SirenEntityFeature.DURATION
);
// show advanced controls dialog if extra features are supported
const allowAdvanced = supportsTones || supportsVolume || supportsDuration;
return html`
<ha-more-info-state-header
.hass=${this.hass}
@ -32,6 +50,11 @@ class MoreInfoSiren extends LitElement {
.iconPathOn=${mdiVolumeHigh}
.iconPathOff=${mdiVolumeOff}
></ha-state-control-toggle>
${allowAdvanced
? html`<ha-button @click=${this._showAdvancedControlsDialog}>
${this.hass.localize("ui.components.siren.advanced_controls")}
</ha-button>`
: nothing}
</div>
<ha-attributes
.hass=${this.hass}
@ -40,6 +63,10 @@ class MoreInfoSiren extends LitElement {
`;
}
private _showAdvancedControlsDialog() {
showSirenAdvancedControlsView(this, this.stateObj!);
}
static get styles(): CSSResultGroup {
return moreInfoControlStyle;
}

View File

@ -1,5 +1,5 @@
import type { CSSResultGroup, PropertyValues } from "lit";
import { css, html, LitElement, nothing } from "lit";
import { css, html, LitElement } from "lit";
import { customElement, property, state } from "lit/decorators";
import memoizeOne from "memoize-one";
import type { LocalizeKeys } from "../../../../common/translations/localize";
@ -8,7 +8,6 @@ import type { AssistPipeline } from "../../../../data/assist_pipeline";
import type { HomeAssistant } from "../../../../types";
import type { WakeWord } from "../../../../data/wake_word";
import { fetchWakeWordInfo } from "../../../../data/wake_word";
import { documentationUrl } from "../../../../util/documentation-url";
import { fireEvent } from "../../../../common/dom/fire_event";
@customElement("assist-pipeline-detail-wakeword")
@ -79,12 +78,7 @@ export class AssistPipelineDetailWakeWord extends LitElement {
}
}
private _hasWakeWorkEntities = memoizeOne((states: HomeAssistant["states"]) =>
Object.keys(states).some((entityId) => entityId.startsWith("wake_word."))
);
protected render() {
const hasWakeWorkEntities = this._hasWakeWorkEntities(this.hass.states);
return html`
<div class="section">
<div class="content">
@ -99,29 +93,17 @@ export class AssistPipelineDetailWakeWord extends LitElement {
`ui.panel.config.voice_assistants.assistants.pipeline.detail.steps.wakeword.description`
)}
</p>
<ha-alert alert-type="info">
${this.hass.localize(
`ui.panel.config.voice_assistants.assistants.pipeline.detail.steps.wakeword.note`
)}
</ha-alert>
</div>
${!hasWakeWorkEntities
? html`${this.hass.localize(
`ui.panel.config.voice_assistants.assistants.pipeline.detail.steps.wakeword.no_wake_words`
)}
<a
href=${documentationUrl(
this.hass,
"/voice_control/install_wake_word_add_on/"
)}
target="_blank"
rel="noreferrer noopener"
>${this.hass.localize(
`ui.panel.config.voice_assistants.assistants.pipeline.detail.steps.wakeword.no_wake_words_link`
)}</a
>`
: nothing}
<ha-form
.schema=${this._schema(this._wakeWords)}
.data=${this.data}
.hass=${this.hass}
.computeLabel=${this._computeLabel}
.disabled=${!hasWakeWorkEntities}
></ha-form>
</div>
</div>

View File

@ -1,7 +1,8 @@
import { mdiClose } from "@mdi/js";
import { mdiClose, mdiDotsVertical } from "@mdi/js";
import type { CSSResultGroup } from "lit";
import { css, html, LitElement, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import memoizeOne from "memoize-one";
import { fireEvent } from "../../../common/dom/fire_event";
import "../../../components/ha-button";
import "../../../components/ha-dialog-header";
@ -21,6 +22,7 @@ import "./assist-pipeline-detail/assist-pipeline-detail-wakeword";
import "./debug/assist-render-pipeline-events";
import type { VoiceAssistantPipelineDetailsDialogParams } from "./show-dialog-voice-assistant-pipeline-detail";
import { computeDomain } from "../../../common/entity/compute_domain";
import { stopPropagation } from "../../../common/dom/stop_propagation";
@customElement("dialog-voice-assistant-pipeline-detail")
export class DialogVoiceAssistantPipelineDetail extends LitElement {
@ -30,6 +32,8 @@ export class DialogVoiceAssistantPipelineDetail extends LitElement {
@state() private _data?: Partial<AssistPipeline>;
@state() private _hideWakeWord = false;
@state() private _cloudActive?: boolean;
@state() private _error?: Record<string, string>;
@ -42,11 +46,17 @@ export class DialogVoiceAssistantPipelineDetail extends LitElement {
this._params = params;
this._error = undefined;
this._cloudActive = this._params.cloudActiveSubscription;
if (this._params.pipeline) {
this._data = this._params.pipeline;
this._hideWakeWord =
this._params.hideWakeWord || !this._data.wake_word_entity;
return;
}
this._hideWakeWord = true;
let sstDefault: string | undefined;
let ttsDefault: string | undefined;
if (this._cloudActive) {
@ -79,6 +89,7 @@ export class DialogVoiceAssistantPipelineDetail extends LitElement {
public closeDialog(): void {
this._params = undefined;
this._data = undefined;
this._hideWakeWord = false;
fireEvent(this, "dialog-closed", { dialog: this.localName });
}
@ -91,6 +102,10 @@ export class DialogVoiceAssistantPipelineDetail extends LitElement {
this._supportedLanguages = languages;
}
private _hasWakeWorkEntities = memoizeOne((states: HomeAssistant["states"]) =>
Object.keys(states).some((entityId) => entityId.startsWith("wake_word."))
);
protected render() {
if (!this._params || !this._data) {
return nothing;
@ -118,6 +133,27 @@ export class DialogVoiceAssistantPipelineDetail extends LitElement {
.path=${mdiClose}
></ha-icon-button>
<span slot="title" .title=${title}>${title}</span>
${!this._hideWakeWord ||
this._params.hideWakeWord ||
!this._hasWakeWorkEntities(this.hass.states)
? nothing
: html`<ha-button-menu
slot="actionItems"
@action=${this._handleShowWakeWord}
@closed=${stopPropagation}
menuCorner="END"
corner="BOTTOM_END"
>
<ha-icon-button
.path=${mdiDotsVertical}
slot="trigger"
></ha-icon-button>
<mwc-list-item>
${this.hass.localize(
"ui.panel.config.voice_assistants.assistants.pipeline.detail.add_streaming_wake_word"
)}
</mwc-list-item></ha-button-menu
>`}
</ha-dialog-header>
<div class="content">
${this._error
@ -171,7 +207,7 @@ export class DialogVoiceAssistantPipelineDetail extends LitElement {
keys="tts_engine,tts_language,tts_voice"
@value-changed=${this._valueChanged}
></assist-pipeline-detail-tts>
${this._params.hideWakeWord
${this._hideWakeWord
? nothing
: html`<assist-pipeline-detail-wakeword
.hass=${this.hass}
@ -198,6 +234,10 @@ export class DialogVoiceAssistantPipelineDetail extends LitElement {
`;
}
private _handleShowWakeWord() {
this._hideWakeWord = false;
}
private _valueChanged(ev: CustomEvent) {
this._error = undefined;
const value = {};

View File

@ -315,7 +315,6 @@ export class HaPanelLogbook extends LitElement {
.filters {
display: flex;
align-items: flex-end;
padding: 8px 16px 0;
}

View File

@ -195,6 +195,9 @@ export class HuiStatisticsGraphCard extends LitElement implements LovelaceCard {
.statTypes=${this._statTypes!}
.names=${this._names}
.unit=${this._unit}
.minYAxis=${this._config.min_y_axis}
.maxYAxis=${this._config.max_y_axis}
.fitYData=${this._config.fit_y_data || false}
.hideLegend=${this._config.hide_legend || false}
.logarithmicScale=${this._config.logarithmic_scale || false}
></statistics-chart>

View File

@ -356,6 +356,9 @@ export interface StatisticsGraphCardConfig extends LovelaceCardConfig {
period?: "5minute" | "hour" | "day" | "month";
stat_types?: StatisticType | StatisticType[];
chart_type?: "line" | "bar";
min_y_axis?: number;
max_y_axis?: number;
fit_y_data?: boolean;
hide_legend?: boolean;
logarithmic_scale?: boolean;
}

View File

@ -69,6 +69,9 @@ const cardConfigStruct = assign(
unit: optional(string()),
hide_legend: optional(boolean()),
logarithmic_scale: optional(boolean()),
min_y_axis: optional(number()),
max_y_axis: optional(number()),
fit_y_data: optional(boolean()),
})
);
@ -126,7 +129,8 @@ export class HuiStatisticsGraphCardEditor
(
localize: LocalizeFunc,
statisticIds: string[] | undefined,
metaDatas: StatisticsMetaData[] | undefined
metaDatas: StatisticsMetaData[] | undefined,
showFitOption: boolean
) => {
const units = new Set<string>();
metaDatas?.forEach((metaData) => {
@ -213,6 +217,33 @@ export class HuiStatisticsGraphCardEditor
],
],
},
{
name: "",
type: "grid",
schema: [
{
name: "min_y_axis",
required: false,
selector: { number: { mode: "box", step: "any" } },
},
{
name: "max_y_axis",
required: false,
selector: { number: { mode: "box", step: "any" } },
},
],
},
...(showFitOption
? [
{
name: "fit_y_data",
required: false,
selector: { boolean: {} },
},
]
: []),
{
name: "hide_legend",
required: false,
@ -254,7 +285,9 @@ export class HuiStatisticsGraphCardEditor
const schema = this._schema(
this.hass.localize,
this._configEntities,
this._metaDatas
this._metaDatas,
this._config!.min_y_axis !== undefined ||
this._config!.max_y_axis !== undefined
);
const configured_stat_types = this._config!.stat_types
? ensureArray(this._config.stat_types)
@ -359,6 +392,9 @@ export class HuiStatisticsGraphCardEditor
case "unit":
case "hide_legend":
case "logarithmic_scale":
case "min_y_axis":
case "max_y_axis":
case "fit_y_data":
return this.hass!.localize(
`ui.panel.lovelace.editor.card.statistics-graph.${schema.name}`
);

View File

@ -367,7 +367,8 @@
"copied": "Copied",
"copied_clipboard": "Copied to clipboard",
"name": "Name",
"optional": "optional"
"optional": "optional",
"default": "Default"
},
"components": {
"selectors": {
@ -881,6 +882,12 @@
"restore": "Restore defaults"
}
},
"siren": {
"advanced_controls": "Advanced controls",
"tone": "Tone",
"duration": "Duration",
"volume": "Volume"
},
"media-browser": {
"tts": {
"message": "Message",
@ -2179,7 +2186,7 @@
"create_backup": "[%key:supervisor::backup::create_backup%]",
"creating_backup": "Backup is currently being created",
"download_backup": "[%key:supervisor::backup::download_backup%]",
"remove_backup": "[%key:supervisor::backup::delete_backup_title%]",
"remove_backup": "Delete backup",
"name": "[%key:supervisor::backup::name%]",
"path": "Path",
"size": "[%key:supervisor::backup::size%]",
@ -2224,7 +2231,7 @@
"name": "Name",
"description": "Description",
"tag_id": "Tag ID",
"tag_id_placeholder": "Autogenerated when left empty",
"tag_id_placeholder": "Autogenerated if left empty",
"delete": "Delete",
"update": "Update",
"create": "Create",
@ -2712,6 +2719,7 @@
"try_tts": "Try voice",
"debug": "Debug",
"set_as_preferred": "Set as preferred",
"add_streaming_wake_word": "Add streaming wake word",
"form": {
"name": "[%key:ui::common::name%]",
"conversation_engine": "Conversation agent",
@ -2743,10 +2751,9 @@
"description": "When you are controlling your assistant with voice, the text-to-speech engine turns the conversation text responses into audio."
},
"wakeword": {
"title": "Wake word",
"description": "If a device supports wake words, you can activate Assist by saying this word.",
"no_wake_words": "It looks like you don't have a wake word engine setup yet.",
"no_wake_words_link": "Find out more about wake words."
"title": "Streaming wake word engine",
"description": " If a device supports streaming wake word engines, you can activate Assist by saying this word.",
"note": "Most recent devices support on-device wake word engines and are configured on their device page."
}
},
"no_cloud_message": "You should have an active cloud subscription to use cloud speech services.",
@ -3275,9 +3282,9 @@
"value_template": "[%key:ui::panel::config::automation::editor::triggers::type::numeric_state::value_template%]",
"description": {
"picker": "If the numeric value of an entity''s state (or attribute''s value) is above or below a given threshold.",
"above": "When {attribute, select, \n undefined {} \n other {{attribute} from }\n }{entity} {numberOfEntities, plural,\n one {is}\n other {are}\n} above {above}",
"below": "When {attribute, select, \n undefined {} \n other {{attribute} from }\n }{entity} {numberOfEntities, plural,\n one {is}\n other {are}\n} below {below}",
"above-below": "When {attribute, select, \n undefined {} \n other {{attribute} from }\n }{entity} {numberOfEntities, plural,\n one {is}\n other {are}\n} above {above} and below {below}"
"above": "If {attribute, select, \n undefined {} \n other {{attribute} from }\n }{entity} {numberOfEntities, plural,\n one {is}\n other {are}\n} above {above}",
"below": "If {attribute, select, \n undefined {} \n other {{attribute} from }\n }{entity} {numberOfEntities, plural,\n one {is}\n other {are}\n} below {below}",
"above-below": "If {attribute, select, \n undefined {} \n other {{attribute} from }\n }{entity} {numberOfEntities, plural,\n one {is}\n other {are}\n} above {above} and below {below}"
}
},
"or": {
@ -3347,7 +3354,7 @@
"id": "Trigger",
"description": {
"picker": "If the automation has been triggered by a specific trigger.",
"full": "When triggered by {id}"
"full": "If triggered by {id}"
}
},
"zone": {
@ -3597,7 +3604,7 @@
"set_conversation_response": {
"label": "Set conversation response",
"description": {
"picker": "Set response of conversation when automation was triggered by conversation trigger.",
"picker": "Set response of conversation if automation was triggered by conversation trigger.",
"full": "Set response of conversation to {response}"
}
},
@ -6155,7 +6162,10 @@
"pick_statistic": "Add a statistic",
"picked_statistic": "Statistic",
"hide_legend": "Hide legend",
"logarithmic_scale": "Logarithmic scale"
"logarithmic_scale": "Logarithmic scale",
"min_y_axis": "Y axis minimum",
"max_y_axis": "Y axis maximum",
"fit_y_data": "Extend Y axis limits to fit data"
},
"statistic": {
"name": "Statistic",
@ -7857,7 +7867,7 @@
"create_blocked_not_running": "Creating a backup is not possible right now because the system is in \"{state}\" state.",
"restore_blocked_not_running": "Restoring a backup is not possible right now because the system is in \"{state}\" state.",
"delete_selected": "Delete selected backups",
"delete_backup_title": "Delete backup",
"delete_backup_title": "Delete backups?",
"delete_backup_text": "Do you want to delete {number} {number, plural,\n one {backup}\n other {backups}\n}?",
"delete_backup_confirm": "delete",
"selected": "{number} selected",