Compare commits

..

14 Commits

Author SHA1 Message Date
Zack
6e49d79538 Logs are default 2022-05-03 11:32:41 -05:00
Zack
454f852531 Add new system menu discriptions 2022-05-03 11:29:21 -05:00
Bram Kragten
b53645ce92 Add disabled support to trace timeline and step details (#12555) 2022-05-03 09:50:33 -05:00
Bram Kragten
de34a5a597 Fix searching in hassio logs (#12560) 2022-05-03 07:30:01 -05:00
Joakim Sørensen
bd8e15bdd1 Add supervisor redirects to quickbar (#12557)
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2022-05-03 11:57:09 +00:00
Bram Kragten
45c7e0eeeb Use outline for cards on config pages (#12558) 2022-05-03 06:44:55 -05:00
Zack Barett
a35a380ec7 Update Quickbar Section Logic to include all (#12553) 2022-05-03 13:25:46 +02:00
Bram Kragten
02e67d1146 Use ha-tip for yaml move tip (#12559) 2022-05-03 11:22:48 +00:00
Zack Barett
a5411f7ac4 Search in Overflow on Mobile (#12552) 2022-05-03 13:17:47 +02:00
Zack Barett
e8da203fe1 Fix Webhook Overflow (#12551) 2022-05-03 13:17:02 +02:00
Joakim Sørensen
10aa0a8829 Add add-on logs to log selector (#12556) 2022-05-03 13:13:20 +02:00
Paulus Schoutsen
85a37e2d2f Bumped version to 20220502.0 2022-05-02 15:08:01 -07:00
Bram Kragten
ba8621fa2c Indicate things are disabled in trace graph (#12550)
* Indicate things are disabled in trace graph

* Update hat-script-graph.ts
2022-05-02 15:07:36 -07:00
Bram Kragten
43e80f1a2e Add parallel action to trace timeline (#12549) 2022-05-02 15:07:01 -07:00
66 changed files with 616 additions and 183 deletions

View File

@@ -68,6 +68,7 @@ class HassioAddonRepositoryEl extends LitElement {
${addons.map(
(addon) => html`
<ha-card
outlined
.addon=${addon}
class=${addon.available ? "" : "not_available"}
@click=${this._addonTapped}

View File

@@ -50,6 +50,7 @@ class HassioAddonAudio extends LitElement {
protected render(): TemplateResult {
return html`
<ha-card
outlined
.header=${this.supervisor.localize("addon.configuration.audio.header")}
>
<div class="card-content">

View File

@@ -162,7 +162,7 @@ class HassioAddonConfig extends LitElement {
);
return html`
<h1>${this.addon.name}</h1>
<ha-card>
<ha-card outlined>
<div class="header">
<h2>
${this.supervisor.localize("addon.configuration.options.header")}

View File

@@ -58,6 +58,7 @@ class HassioAddonNetwork extends LitElement {
return html`
<ha-card
outlined
.header=${this.supervisor.localize(
"addon.configuration.network.header"
)}

View File

@@ -38,7 +38,7 @@ class HassioAddonDocumentationDashboard extends LitElement {
}
return html`
<div class="content">
<ha-card>
<ha-card outlined>
${this._error
? html`<ha-alert alert-type="error">${this._error}</ha-alert>`
: ""}

View File

@@ -166,7 +166,7 @@ class HassioAddonInfo extends LitElement {
`
: ""}
<ha-card>
<ha-card outlined>
<div class="card-content">
<div class="addon-header">
${!this.narrow ? this.addon.name : ""}
@@ -649,7 +649,7 @@ class HassioAddonInfo extends LitElement {
${this.addon.long_description
? html`
<ha-card>
<ha-card outlined>
<div class="card-content">
<ha-markdown
.content=${this.addon.long_description}

View File

@@ -34,7 +34,7 @@ class HassioAddonLogs extends LitElement {
protected render(): TemplateResult {
return html`
<h1>${this.addon.name}</h1>
<ha-card>
<ha-card outlined>
${this._error
? html`<ha-alert alert-type="error">${this._error}</ha-alert>`
: ""}

View File

@@ -26,7 +26,7 @@ class HassioAddons extends LitElement {
<div class="card-group">
${!this.supervisor.supervisor.addons?.length
? html`
<ha-card>
<ha-card outlined>
<div class="card-content">
<button class="link" @click=${this._openStore}>
${this.supervisor.localize("dashboard.no_addons")}
@@ -38,7 +38,11 @@ class HassioAddons extends LitElement {
.sort((a, b) => caseInsensitiveStringCompare(a.name, b.name))
.map(
(addon) => html`
<ha-card .addon=${addon} @click=${this._addonTapped}>
<ha-card
outlined
.addon=${addon}
@click=${this._addonTapped}
>
<div class="card-content">
<hassio-card-content
.hass=${this.hass}

View File

@@ -1,6 +1,6 @@
[metadata]
name = home-assistant-frontend
version = 20220429.0
version = 20220502.0
author = The Home Assistant Authors
author_email = hello@home-assistant.io
license = Apache-2.0

View File

@@ -10,6 +10,8 @@ export class HaTimeline extends LitElement {
@property({ type: Boolean, reflect: true }) public raised = false;
@property({ reflect: true, type: Boolean }) notEnabled = false;
@property({ type: Boolean }) public lastItem = false;
@property({ type: String }) public icon?: string;
@@ -76,6 +78,9 @@ export class HaTimeline extends LitElement {
margin-right: 8px;
width: 24px;
}
:host([notEnabled]) ha-svg-icon {
opacity: 0.5;
}
ha-svg-icon {
color: var(
--timeline-ball-color,

View File

@@ -114,6 +114,11 @@ export class HaTracePathDetails extends LitElement {
const { path, timestamp, result, error, changed_variables, ...rest } =
trace as any;
if (result?.enabled === false) {
return html`This node was disabled and skipped during execution so
no further trace information is available.`;
}
return html`
${curPath === this.selected.path
? ""

View File

@@ -19,6 +19,8 @@ export class HatGraphNode extends LitElement {
@property({ reflect: true, type: Boolean }) disabled?: boolean;
@property({ reflect: true, type: Boolean }) notEnabled = false;
@property({ reflect: true, type: Boolean }) graphStart?: boolean;
@property({ type: Boolean, attribute: "nofocus" }) noFocus = false;
@@ -114,8 +116,14 @@ export class HatGraphNode extends LitElement {
--stroke-clr: var(--hover-clr);
--icon-clr: var(--default-icon-clr);
}
:host([disabled]) circle {
stroke: var(--disabled-clr);
:host([notEnabled]) circle {
--stroke-clr: var(--disabled-clr);
}
:host([notEnabled][active]) circle {
--stroke-clr: var(--disabled-active-clr);
}
:host([notEnabled]:hover) circle {
--stroke-clr: var(--disabled-hover-clr);
}
svg {
width: 100%;

View File

@@ -96,6 +96,7 @@ export class HatScriptGraph extends LitElement {
@focus=${this.selectNode(config, path)}
?active=${this.selected === path}
.iconPath=${mdiAsterisk}
.notEnabled=${config.enabled === false}
tabindex=${track ? "0" : "-1"}
></hat-graph-node>
`;
@@ -130,20 +131,31 @@ export class HatScriptGraph extends LitElement {
other: this.render_other_node,
};
private render_action_node(node: Action, path: string, graphStart = false) {
private render_action_node(
node: Action,
path: string,
graphStart = false,
disabled = false
) {
const type =
Object.keys(this.typeRenderers).find((key) => key in node) || "other";
this.renderedNodes[path] = { config: node, path };
if (this.trace && path in this.trace.trace) {
this.trackedNodes[path] = this.renderedNodes[path];
}
return this.typeRenderers[type].bind(this)(node, path, graphStart);
return this.typeRenderers[type].bind(this)(
node,
path,
graphStart,
disabled
);
}
private render_choose_node(
config: ChooseAction,
path: string,
graphStart = false
graphStart = false,
disabled = false
) {
const trace = this.trace.trace[path] as ChooseActionTraceStep[] | undefined;
const trace_path = trace
@@ -160,12 +172,14 @@ export class HatScriptGraph extends LitElement {
@focus=${this.selectNode(config, path)}
?track=${trace !== undefined}
?active=${this.selected === path}
.notEnabled=${disabled || config.enabled === false}
>
<hat-graph-node
.graphStart=${graphStart}
.iconPath=${mdiArrowDecision}
?track=${trace !== undefined}
?active=${this.selected === path}
.notEnabled=${disabled || config.enabled === false}
slot="head"
nofocus
></hat-graph-node>
@@ -188,12 +202,15 @@ export class HatScriptGraph extends LitElement {
@focus=${this.selectNode(config, branch_path)}
?track=${track_this}
?active=${this.selected === branch_path}
.notEnabled=${disabled || config.enabled === false}
></hat-graph-node>
${branch.sequence !== null
? ensureArray(branch.sequence).map((action, j) =>
this.render_action_node(
action,
`${branch_path}/sequence/${j}`
`${branch_path}/sequence/${j}`,
false,
disabled || config.enabled === false
)
)
: ""}
@@ -205,7 +222,12 @@ export class HatScriptGraph extends LitElement {
<hat-graph-spacer ?track=${track_default}></hat-graph-spacer>
${config.default !== null
? ensureArray(config.default)?.map((action, i) =>
this.render_action_node(action, `${path}/default/${i}`)
this.render_action_node(
action,
`${path}/default/${i}`,
false,
disabled || config.enabled === false
)
)
: ""}
</div>
@@ -213,7 +235,12 @@ export class HatScriptGraph extends LitElement {
`;
}
private render_if_node(config: IfAction, path: string, graphStart = false) {
private render_if_node(
config: IfAction,
path: string,
graphStart = false,
disabled = false
) {
const trace = this.trace.trace[path] as IfActionTraceStep[] | undefined;
let trackThen = false;
let trackElse = false;
@@ -234,12 +261,14 @@ export class HatScriptGraph extends LitElement {
@focus=${this.selectNode(config, path)}
?track=${trace !== undefined}
?active=${this.selected === path}
.notEnabled=${disabled || config.enabled === false}
>
<hat-graph-node
.graphStart=${graphStart}
.iconPath=${mdiCallSplit}
?track=${trace !== undefined}
?active=${this.selected === path}
.notEnabled=${disabled || config.enabled === false}
slot="head"
nofocus
></hat-graph-node>
@@ -249,10 +278,16 @@ export class HatScriptGraph extends LitElement {
.iconPath=${mdiCallMissed}
?track=${trackElse}
?active=${this.selected === path}
.notEnabled=${disabled || config.enabled === false}
nofocus
></hat-graph-node
>${ensureArray(config.else).map((action, j) =>
this.render_action_node(action, `${path}/else/${j}`)
this.render_action_node(
action,
`${path}/else/${j}`,
false,
disabled || config.enabled === false
)
)}
</div>`
: html`<hat-graph-spacer ?track=${trackElse}></hat-graph-spacer>`}
@@ -261,10 +296,16 @@ export class HatScriptGraph extends LitElement {
.iconPath=${mdiCallReceived}
?track=${trackThen}
?active=${this.selected === path}
.notEnabled=${disabled || config.enabled === false}
nofocus
></hat-graph-node>
${ensureArray(config.then).map((action, j) =>
this.render_action_node(action, `${path}/then/${j}`)
this.render_action_node(
action,
`${path}/then/${j}`,
false,
disabled || config.enabled === false
)
)}
</div>
</hat-graph-branch>
@@ -274,7 +315,8 @@ export class HatScriptGraph extends LitElement {
private render_condition_node(
node: Condition,
path: string,
graphStart = false
graphStart = false,
disabled = false
) {
const trace = this.trace.trace[path] as ConditionTraceStep[] | undefined;
let track = false;
@@ -300,6 +342,7 @@ export class HatScriptGraph extends LitElement {
@focus=${this.selectNode(node, path)}
?track=${track}
?active=${this.selected === path}
.notEnabled=${disabled || node.enabled === false}
tabindex=${trace === undefined ? "-1" : "0"}
short
>
@@ -308,6 +351,7 @@ export class HatScriptGraph extends LitElement {
slot="head"
?track=${track}
?active=${this.selected === path}
.notEnabled=${disabled || node.enabled === false}
.iconPath=${mdiAbTesting}
nofocus
></hat-graph-node>
@@ -322,6 +366,7 @@ export class HatScriptGraph extends LitElement {
nofocus
?track=${trackFailed}
?active=${this.selected === path}
.notEnabled=${disabled || node.enabled === false}
></hat-graph-node>
</hat-graph-branch>
`;
@@ -330,7 +375,8 @@ export class HatScriptGraph extends LitElement {
private render_delay_node(
node: DelayAction,
path: string,
graphStart = false
graphStart = false,
disabled = false
) {
return html`
<hat-graph-node
@@ -339,6 +385,7 @@ export class HatScriptGraph extends LitElement {
@focus=${this.selectNode(node, path)}
?track=${path in this.trace.trace}
?active=${this.selected === path}
.notEnabled=${disabled || node.enabled === false}
tabindex=${this.trace && path in this.trace.trace ? "0" : "-1"}
></hat-graph-node>
`;
@@ -347,7 +394,8 @@ export class HatScriptGraph extends LitElement {
private render_device_node(
node: DeviceAction,
path: string,
graphStart = false
graphStart = false,
disabled = false
) {
return html`
<hat-graph-node
@@ -356,6 +404,7 @@ export class HatScriptGraph extends LitElement {
@focus=${this.selectNode(node, path)}
?track=${path in this.trace.trace}
?active=${this.selected === path}
.notEnabled=${disabled || node.enabled === false}
tabindex=${this.trace && path in this.trace.trace ? "0" : "-1"}
></hat-graph-node>
`;
@@ -364,7 +413,8 @@ export class HatScriptGraph extends LitElement {
private render_event_node(
node: EventAction,
path: string,
graphStart = false
graphStart = false,
disabled = false
) {
return html`
<hat-graph-node
@@ -373,6 +423,7 @@ export class HatScriptGraph extends LitElement {
@focus=${this.selectNode(node, path)}
?track=${path in this.trace.trace}
?active=${this.selected === path}
.notEnabled=${disabled || node.enabled === false}
tabindex=${this.trace && path in this.trace.trace ? "0" : "-1"}
></hat-graph-node>
`;
@@ -381,7 +432,8 @@ export class HatScriptGraph extends LitElement {
private render_repeat_node(
node: RepeatAction,
path: string,
graphStart = false
graphStart = false,
disabled = false
) {
const trace: any = this.trace.trace[path];
const repeats = this.trace?.trace[`${path}/repeat/sequence/0`]?.length;
@@ -391,12 +443,14 @@ export class HatScriptGraph extends LitElement {
@focus=${this.selectNode(node, path)}
?track=${path in this.trace.trace}
?active=${this.selected === path}
.notEnabled=${disabled || node.enabled === false}
>
<hat-graph-node
.graphStart=${graphStart}
.iconPath=${mdiRefresh}
?track=${path in this.trace.trace}
?active=${this.selected === path}
.notEnabled=${disabled || node.enabled === false}
slot="head"
nofocus
></hat-graph-node>
@@ -404,12 +458,18 @@ export class HatScriptGraph extends LitElement {
.iconPath=${mdiArrowUp}
?track=${repeats > 1}
?active=${this.selected === path}
.notEnabled=${disabled || node.enabled === false}
nofocus
.badge=${repeats > 1 ? repeats : undefined}
></hat-graph-node>
<div ?track=${trace}>
${ensureArray(node.repeat.sequence).map((action, i) =>
this.render_action_node(action, `${path}/repeat/sequence/${i}`)
this.render_action_node(
action,
`${path}/repeat/sequence/${i}`,
false,
disabled || node.enabled === false
)
)}
</div>
</hat-graph-branch>
@@ -419,7 +479,8 @@ export class HatScriptGraph extends LitElement {
private render_scene_node(
node: SceneAction,
path: string,
graphStart = false
graphStart = false,
disabled = false
) {
return html`
<hat-graph-node
@@ -428,6 +489,7 @@ export class HatScriptGraph extends LitElement {
@focus=${this.selectNode(node, path)}
?track=${path in this.trace.trace}
?active=${this.selected === path}
.notEnabled=${disabled || node.enabled === false}
tabindex=${this.trace && path in this.trace.trace ? "0" : "-1"}
></hat-graph-node>
`;
@@ -436,7 +498,8 @@ export class HatScriptGraph extends LitElement {
private render_service_node(
node: ServiceAction,
path: string,
graphStart = false
graphStart = false,
disabled = false
) {
return html`
<hat-graph-node
@@ -445,6 +508,7 @@ export class HatScriptGraph extends LitElement {
@focus=${this.selectNode(node, path)}
?track=${path in this.trace.trace}
?active=${this.selected === path}
.notEnabled=${disabled || node.enabled === false}
tabindex=${this.trace && path in this.trace.trace ? "0" : "-1"}
></hat-graph-node>
`;
@@ -453,7 +517,8 @@ export class HatScriptGraph extends LitElement {
private render_wait_node(
node: WaitAction | WaitForTriggerAction,
path: string,
graphStart = false
graphStart = false,
disabled = false
) {
return html`
<hat-graph-node
@@ -462,6 +527,7 @@ export class HatScriptGraph extends LitElement {
@focus=${this.selectNode(node, path)}
?track=${path in this.trace.trace}
?active=${this.selected === path}
.notEnabled=${disabled || node.enabled === false}
tabindex=${this.trace && path in this.trace.trace ? "0" : "-1"}
></hat-graph-node>
`;
@@ -470,7 +536,8 @@ export class HatScriptGraph extends LitElement {
private render_parallel_node(
node: ParallelAction,
path: string,
graphStart = false
graphStart = false,
disabled = false
) {
const trace: any = this.trace.trace[path];
return html`
@@ -479,12 +546,14 @@ export class HatScriptGraph extends LitElement {
@focus=${this.selectNode(node, path)}
?track=${path in this.trace.trace}
?active=${this.selected === path}
.notEnabled=${disabled || node.enabled === false}
>
<hat-graph-node
.graphStart=${graphStart}
.iconPath=${mdiShuffleDisabled}
?track=${path in this.trace.trace}
?active=${this.selected === path}
.notEnabled=${disabled || node.enabled === false}
slot="head"
nofocus
></hat-graph-node>
@@ -495,20 +564,29 @@ export class HatScriptGraph extends LitElement {
(sAction, j) =>
this.render_action_node(
sAction,
`${path}/parallel/${i}/sequence/${j}`
`${path}/parallel/${i}/sequence/${j}`,
false,
disabled || node.enabled === false
)
)}
</div>`
: this.render_action_node(
action,
`${path}/parallel/${i}/sequence/0`
`${path}/parallel/${i}/sequence/0`,
false,
disabled || node.enabled === false
)
)}
</hat-graph-branch>
`;
}
private render_stop_node(node: Action, path: string, graphStart = false) {
private render_stop_node(
node: Action,
path: string,
graphStart = false,
disabled = false
) {
const trace = this.trace.trace[path] as StopActionTraceStep[] | undefined;
return html`
<hat-graph-node
@@ -519,11 +597,17 @@ export class HatScriptGraph extends LitElement {
@focus=${this.selectNode(node, path)}
?track=${path in this.trace.trace}
?active=${this.selected === path}
.notEnabled=${disabled || node.enabled === false}
></hat-graph-node>
`;
}
private render_other_node(node: Action, path: string, graphStart = false) {
private render_other_node(
node: Action,
path: string,
graphStart = false,
disabled = false
) {
return html`
<hat-graph-node
.graphStart=${graphStart}
@@ -531,6 +615,7 @@ export class HatScriptGraph extends LitElement {
@focus=${this.selectNode(node, path)}
?track=${path in this.trace.trace}
?active=${this.selected === path}
.notEnabled=${disabled || node.enabled === false}
></hat-graph-node>
`;
}
@@ -669,6 +754,8 @@ export class HatScriptGraph extends LitElement {
--track-clr: var(--track-color, var(--accent-color));
--hover-clr: var(--hover-color, var(--primary-color));
--disabled-clr: var(--disabled-color, var(--disabled-text-color));
--disabled-active-clr: rgba(var(--rgb-primary-color), 0.5);
--disabled-hover-clr: rgba(var(--rgb-primary-color), 0.7);
--default-trigger-color: 3, 169, 244;
--rgb-trigger-color: var(--trigger-color, var(--default-trigger-color));
--background-clr: var(--background-color, white);

View File

@@ -296,7 +296,12 @@ class ActionRenderer {
return this._handleParallel(index);
}
this._renderEntry(path, describeAction(this.hass, data, actionType));
this._renderEntry(
path,
describeAction(this.hass, data, actionType),
undefined,
data.enabled === false
);
let i = index + 1;
@@ -349,10 +354,16 @@ class ActionRenderer {
const chooseConfig = this._getDataFromPath(
this.keys[index]
) as ChooseAction;
const disabled = chooseConfig.enabled === false;
const name = chooseConfig.alias || "Choose";
if (defaultExecuted) {
this._renderEntry(choosePath, `${name}: Default action executed`);
this._renderEntry(
choosePath,
`${name}: Default action executed`,
undefined,
disabled
);
} else if (chooseTrace.result) {
const choiceNumeric =
chooseTrace.result.choice !== "default"
@@ -364,9 +375,19 @@ class ActionRenderer {
const choiceName = choiceConfig
? `${choiceConfig.alias || `Option ${choiceNumeric}`} executed`
: `Error: ${chooseTrace.error}`;
this._renderEntry(choosePath, `${name}: ${choiceName}`);
this._renderEntry(
choosePath,
`${name}: ${choiceName}`,
undefined,
disabled
);
} else {
this._renderEntry(choosePath, `${name}: No action taken`);
this._renderEntry(
choosePath,
`${name}: No action taken`,
undefined,
disabled
);
}
let i;
@@ -414,9 +435,11 @@ class ActionRenderer {
const repeatConfig = this._getDataFromPath(
this.keys[index]
) as RepeatAction;
const disabled = repeatConfig.enabled === false;
const name = repeatConfig.alias || describeAction(this.hass, repeatConfig);
this._renderEntry(repeatPath, name);
this._renderEntry(repeatPath, name, undefined, disabled);
let i;
@@ -441,18 +464,24 @@ class ActionRenderer {
const ifTrace = this._getItem(index)[0] as IfActionTraceStep;
const ifConfig = this._getDataFromPath(this.keys[index]) as IfAction;
const disabled = ifConfig.enabled === false;
const name = ifConfig.alias || "If";
if (ifTrace.result) {
if (ifTrace.result?.choice) {
const choiceConfig = this._getDataFromPath(
`${this.keys[index]}/${ifTrace.result.choice}/`
) as any;
const choiceName = choiceConfig
? `${choiceConfig.alias || `${ifTrace.result.choice} action executed`}`
: `Error: ${ifTrace.error}`;
this._renderEntry(ifPath, `${name}: ${choiceName}`);
this._renderEntry(ifPath, `${name}: ${choiceName}`, undefined, disabled);
} else {
this._renderEntry(ifPath, `${name}: No action taken`);
this._renderEntry(
ifPath,
`${name}: No action taken`,
undefined,
disabled
);
}
let i;
@@ -489,9 +518,11 @@ class ActionRenderer {
this.keys[index]
) as ParallelAction;
const disabled = parallelConfig.enabled === false;
const name = parallelConfig.alias || "Execute in parallel";
this._renderEntry(parallelPath, name);
this._renderEntry(parallelPath, name, undefined, disabled);
let i;
@@ -513,11 +544,14 @@ class ActionRenderer {
private _renderEntry(
path: string,
description: string,
icon = mdiRecordCircleOutline
icon = mdiRecordCircleOutline,
disabled = false
) {
this.entries.push(html`
<ha-timeline .icon=${icon} data-path=${path}>
${description}
<ha-timeline .icon=${icon} data-path=${path} .notEnabled=${disabled}>
${description}${disabled
? html`<span class="disabled"> (disabled)</span>`
: ""}
</ha-timeline>
`);
}

View File

@@ -179,7 +179,10 @@ export const fetchHassioInfo = async (
};
export const fetchHassioLogs = async (hass: HomeAssistant, provider: string) =>
hass.callApi<string>("GET", `hassio/${provider}/logs`);
hass.callApi<string>(
"GET",
`hassio/${provider.includes("_") ? `addons/${provider}` : provider}/logs`
);
export const setSupervisorOption = async (
hass: HomeAssistant,

View File

@@ -17,6 +17,7 @@ import { styleMap } from "lit/directives/style-map";
import memoizeOne from "memoize-one";
import { canShowPage } from "../../common/config/can_show_page";
import { componentsWithService } from "../../common/config/components_with_service";
import { isComponentLoaded } from "../../common/config/is_component_loaded";
import { fireEvent } from "../../common/dom/fire_event";
import { computeDomain } from "../../common/entity/compute_domain";
import { computeStateName } from "../../common/entity/compute_state_name";
@@ -33,6 +34,7 @@ import "../../components/ha-circular-progress";
import "../../components/ha-header-bar";
import "../../components/ha-icon-button";
import "../../components/ha-textfield";
import { fetchHassioSupervisorInfo } from "../../data/hassio/supervisor";
import { domainToName } from "../../data/integration";
import { getPanelNameTranslationKey } from "../../data/panel";
import { PageNavigation } from "../../layouts/hass-tabs-subpage";
@@ -245,9 +247,10 @@ export class QuickBar extends LitElement {
`;
}
private _initializeItemsIfNeeded() {
private async _initializeItemsIfNeeded() {
if (this._commandMode) {
this._commandItems = this._commandItems || this._generateCommandItems();
this._commandItems =
this._commandItems || (await this._generateCommandItems());
} else {
this._entityItems = this._entityItems || this._generateEntityItems();
}
@@ -485,11 +488,11 @@ export class QuickBar extends LitElement {
);
}
private _generateCommandItems(): CommandItem[] {
private async _generateCommandItems(): Promise<CommandItem[]> {
return [
...this._generateReloadCommands(),
...this._generateServerControlCommands(),
...this._generateNavigationCommands(),
...(await this._generateNavigationCommands()),
].sort((a, b) =>
caseInsensitiveStringCompare(a.strings.join(" "), b.strings.join(" "))
);
@@ -578,11 +581,40 @@ export class QuickBar extends LitElement {
});
}
private _generateNavigationCommands(): CommandItem[] {
private async _generateNavigationCommands(): Promise<CommandItem[]> {
const panelItems = this._generateNavigationPanelCommands();
const sectionItems = this._generateNavigationConfigSectionCommands();
const supervisorItems: BaseNavigationCommand[] = [];
if (isComponentLoaded(this.hass, "hassio")) {
const supervisorInfo = await fetchHassioSupervisorInfo(this.hass);
supervisorItems.push({
path: "/hassio/store",
primaryText: this.hass.localize(
"ui.dialogs.quick-bar.commands.navigation.addon_store"
),
});
supervisorItems.push({
path: "/hassio/dashboard",
primaryText: this.hass.localize(
"ui.dialogs.quick-bar.commands.navigation.addon_dashboard"
),
});
for (const addon of supervisorInfo.addons) {
supervisorItems.push({
path: `/hassio/addon/${addon.slug}`,
primaryText: this.hass.localize(
"ui.dialogs.quick-bar.commands.navigation.addon_info",
{ addon: addon.name }
),
});
}
}
return this._finalizeNavigationCommands([...panelItems, ...sectionItems]);
return this._finalizeNavigationCommands([
...panelItems,
...sectionItems,
...supervisorItems,
]);
}
private _generateNavigationPanelCommands(): BaseNavigationCommand[] {
@@ -610,20 +642,14 @@ export class QuickBar extends LitElement {
if (!canShowPage(this.hass, page)) {
continue;
}
if (!page.component) {
continue;
}
const info = this._getNavigationInfoFromConfig(page);
if (!info) {
continue;
}
// Add to list, but only if we do not already have an entry for the same path and component
if (
items.some(
(e) => e.path === info.path && e.component === info.component
)
) {
if (items.some((e) => e.path === info.path)) {
continue;
}
@@ -637,14 +663,19 @@ export class QuickBar extends LitElement {
private _getNavigationInfoFromConfig(
page: PageNavigation
): NavigationInfo | undefined {
if (!page.component) {
return undefined;
}
const caption = this.hass.localize(
`ui.dialogs.quick-bar.commands.navigation.${page.component}`
);
const path = page.path.substring(1);
if (page.translationKey && caption) {
let name = path.substring(path.indexOf("/") + 1);
name = name.indexOf("/") > -1 ? name.substring(0, name.indexOf("/")) : name;
const caption =
(name &&
this.hass.localize(
`ui.dialogs.quick-bar.commands.navigation.${name}`
)) ||
(page.translationKey && this.hass.localize(page.translationKey));
if (caption) {
return { ...page, primaryText: caption };
}

View File

@@ -259,6 +259,7 @@ class HaConfigAreaPage extends LitElement {
<ha-svg-icon .path=${mdiImagePlus} slot="icon"></ha-svg-icon>
</mwc-button>`}
<ha-card
outlined
.header=${this.hass.localize("ui.panel.config.devices.caption")}
>${devices.length
? devices.map(
@@ -281,6 +282,7 @@ class HaConfigAreaPage extends LitElement {
`}
</ha-card>
<ha-card
outlined
.header=${this.hass.localize(
"ui.panel.config.areas.editor.linked_entities_caption"
)}
@@ -314,6 +316,7 @@ class HaConfigAreaPage extends LitElement {
${isComponentLoaded(this.hass, "automation")
? html`
<ha-card
outlined
.header=${this.hass.localize(
"ui.panel.config.devices.automation.automations_heading"
)}
@@ -361,6 +364,7 @@ class HaConfigAreaPage extends LitElement {
${isComponentLoaded(this.hass, "scene")
? html`
<ha-card
outlined
.header=${this.hass.localize(
"ui.panel.config.devices.scene.scenes_heading"
)}
@@ -400,6 +404,7 @@ class HaConfigAreaPage extends LitElement {
${isComponentLoaded(this.hass, "script")
? html`
<ha-card
outlined
.header=${this.hass.localize(
"ui.panel.config.devices.script.scripts_heading"
)}

View File

@@ -164,7 +164,7 @@ export default class HaAutomationActionRow extends LitElement {
const yamlMode = this._yamlMode;
return html`
<ha-card>
<ha-card outlined>
${this.action.enabled === false
? html`<div class="disabled-bar">
${this.hass.localize(

View File

@@ -32,7 +32,7 @@ export default class HaAutomationAction extends LitElement {
></ha-automation-action-row>
`
)}
<ha-card>
<ha-card outlined>
<div class="card-actions add-card">
<mwc-button @click=${this._addAction}>
${this.hass.localize(

View File

@@ -69,7 +69,7 @@ export class HaChooseAction extends LitElement implements ActionElement {
</div>
</ha-card>`
)}
<ha-card>
<ha-card outlined>
<div class="card-actions add-card">
<mwc-button @click=${this._addOption}>
${this.hass.localize(

View File

@@ -75,7 +75,7 @@ export class HaBlueprintAutomationEditor extends LitElement {
"ui.panel.config.automation.editor.introduction"
)}
</span>
<ha-card>
<ha-card outlined>
<div class="card-content">
<ha-textfield
.label=${this.hass.localize(
@@ -145,6 +145,7 @@ export class HaBlueprintAutomationEditor extends LitElement {
</ha-config-section>
<ha-card
outlined
class="blueprint"
.header=${this.hass.localize(
"ui.panel.config.automation.editor.blueprint.header"

View File

@@ -5,7 +5,6 @@ import { dynamicElement } from "../../../../common/dom/dynamic-element-directive
import { fireEvent } from "../../../../common/dom/fire_event";
import { stringCompare } from "../../../../common/string/compare";
import type { LocalizeFunc } from "../../../../common/translations/localize";
import "../../../../components/ha-card";
import "../../../../components/ha-select";
import type { HaSelect } from "../../../../components/ha-select";
import "../../../../components/ha-yaml-editor";

View File

@@ -67,7 +67,7 @@ export default class HaAutomationConditionRow extends LitElement {
return html``;
}
return html`
<ha-card>
<ha-card outlined>
${this.condition.enabled === false
? html`<div class="disabled-bar">
${this.hass.localize(

View File

@@ -56,7 +56,7 @@ export default class HaAutomationCondition extends LitElement {
></ha-automation-condition-row>
`
)}
<ha-card>
<ha-card outlined>
<div class="card-actions add-card">
<mwc-button @click=${this._addCondition}>
${this.hass.localize(

View File

@@ -3,7 +3,6 @@ import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { customElement, property, state } from "lit/decorators";
import { fireEvent } from "../../../common/dom/fire_event";
import "../../../components/ha-blueprint-picker";
import "../../../components/ha-card";
import "../../../components/ha-circular-progress";
import { createCloseHeading } from "../../../components/ha-dialog";
import { showAutomationEditor } from "../../../data/automation";

View File

@@ -239,8 +239,8 @@ export class HaAutomationEditor extends KeyboardShortcutMixin(LitElement) {
? html`
${!this.narrow
? html`
<ha-card
><div class="card-header">
<ha-card outlined>
<div class="card-header">
${this._config.alias}
</div>
${stateObj
@@ -275,8 +275,8 @@ export class HaAutomationEditor extends KeyboardShortcutMixin(LitElement) {
.defaultValue=${this._preprocessYaml()}
@value-changed=${this._yamlChanged}
></ha-yaml-editor>
<ha-card
><div class="card-actions">
<ha-card outlined>
<div class="card-actions">
<mwc-button @click=${this._copyYaml}>
${this.hass.localize(
"ui.panel.config.automation.editor.copy_to_clipboard"

View File

@@ -47,7 +47,7 @@ export class HaManualAutomationEditor extends LitElement {
"ui.panel.config.automation.editor.introduction"
)}
</span>
<ha-card>
<ha-card outlined>
<div class="card-content">
<ha-textfield
.label=${this.hass.localize(

View File

@@ -127,7 +127,7 @@ export default class HaAutomationTriggerRow extends LitElement {
const showId = "id" in this.trigger || this._requestShowId;
return html`
<ha-card>
<ha-card outlined>
${this.trigger.enabled === false
? html`<div class="disabled-bar">
${this.hass.localize(

View File

@@ -27,7 +27,7 @@ export default class HaAutomationTrigger extends LitElement {
></ha-automation-trigger-row>
`
)}
<ha-card>
<ha-card outlined>
<div class="card-actions add-card">
<mwc-button @click=${this._addTrigger}>
${this.hass.localize(

View File

@@ -1,26 +1,28 @@
import "@material/mwc-button";
import "@material/mwc-list/mwc-list-item";
import type { ActionDetail } from "@material/mwc-list";
import "@polymer/paper-item/paper-item-body";
import "@material/mwc-list/mwc-list-item";
import { mdiDotsVertical } from "@mdi/js";
import { LitElement, css, html, PropertyValues } from "lit";
import "@polymer/paper-item/paper-item-body";
import { css, html, LitElement, PropertyValues } from "lit";
import { customElement, property, state } from "lit/decorators";
import { formatDateTime } from "../../../../common/datetime/format_date_time";
import { fireEvent } from "../../../../common/dom/fire_event";
import { computeRTLDirection } from "../../../../common/util/compute_rtl";
import { debounce } from "../../../../common/util/debounce";
import "../../../../components/buttons/ha-call-api-button";
import "../../../../components/ha-card";
import "../../../../components/ha-alert";
import "../../../../components/ha-button-menu";
import "../../../../components/ha-card";
import "../../../../components/ha-icon-button";
import { debounce } from "../../../../common/util/debounce";
import {
cloudLogout,
CloudStatusLoggedIn,
fetchCloudSubscriptionInfo,
SubscriptionInfo,
} from "../../../../data/cloud";
import { showConfirmationDialog } from "../../../../dialogs/generic/show-dialog-box";
import "../../../../layouts/hass-subpage";
import { SubscribeMixin } from "../../../../mixins/subscribe-mixin";
import { HomeAssistant } from "../../../../types";
import "../../ha-config-section";
import "./cloud-alexa-pref";
@@ -28,8 +30,6 @@ import "./cloud-google-pref";
import "./cloud-remote-pref";
import "./cloud-tts-pref";
import "./cloud-webhooks";
import { SubscribeMixin } from "../../../../mixins/subscribe-mixin";
import { showConfirmationDialog } from "../../../../dialogs/generic/show-dialog-box";
@customElement("cloud-account")
export class CloudAccount extends SubscribeMixin(LitElement) {
@@ -81,6 +81,7 @@ export class CloudAccount extends SubscribeMixin(LitElement) {
</div>
<ha-card
outlined
.header=${this.hass.localize(
"ui.panel.config.cloud.account.nabu_casa_account"
)}
@@ -210,6 +211,7 @@ export class CloudAccount extends SubscribeMixin(LitElement) {
<cloud-webhooks
.hass=${this.hass}
.narrow=${this.narrow}
.cloudStatus=${this.cloudStatus}
dir=${this._rtlDirection}
></cloud-webhooks>

View File

@@ -26,6 +26,7 @@ export class CloudAlexaPref extends LitElement {
return html`
<ha-card
outlined
header=${this.hass!.localize(
"ui.panel.config.cloud.account.alexa.title"
)}

View File

@@ -31,6 +31,7 @@ export class CloudGooglePref extends LitElement {
return html`
<ha-card
outlined
header=${this.hass.localize(
"ui.panel.config.cloud.account.google.title"
)}

View File

@@ -34,6 +34,7 @@ export class CloudRemotePref extends LitElement {
if (!remote_certificate) {
return html`
<ha-card
outlined
header=${this.hass.localize(
"ui.panel.config.cloud.account.remote.title"
)}
@@ -49,6 +50,7 @@ export class CloudRemotePref extends LitElement {
return html`
<ha-card
outlined
header=${this.hass.localize(
"ui.panel.config.cloud.account.remote.title"
)}

View File

@@ -44,6 +44,7 @@ export class CloudTTSPref extends LitElement {
return html`
<ha-card
outlined
header=${this.hass.localize("ui.panel.config.cloud.account.tts.title")}
>
<div class="card-content">

View File

@@ -40,6 +40,7 @@ export class CloudWebhooks extends LitElement {
protected render() {
return html`
<ha-card
outlined
header=${this.hass!.localize(
"ui.panel.config.cloud.account.webhooks.title"
)}

View File

@@ -153,7 +153,7 @@ class CloudAlexa extends SubscribeMixin(LitElement) {
></ha-icon-button>`;
target.push(html`
<ha-card>
<ha-card outlined>
<div class="card-content">
<div class="top-line">
<state-info

View File

@@ -36,6 +36,7 @@ export class CloudForgotPassword extends LitElement {
>
<div class="content">
<ha-card
outlined
.header=${this.hass.localize(
"ui.panel.config.cloud.forgot_password.subtitle"
)}

View File

@@ -159,7 +159,7 @@ class CloudGoogleAssistant extends SubscribeMixin(LitElement) {
></ha-icon-button>`;
target.push(html`
<ha-card>
<ha-card outlined>
<div class="card-content">
<div class="top-line">
<state-info

View File

@@ -99,6 +99,7 @@ export class CloudLogin extends LitElement {
: ""}
<ha-card
outlined
.header=${this.hass.localize(
"ui.panel.config.cloud.login.sign_in"
)}
@@ -157,7 +158,7 @@ export class CloudLogin extends LitElement {
</div>
</ha-card>
<ha-card>
<ha-card outlined>
<paper-item @click=${this._handleRegister}>
<paper-item-body two-line>
${this.hass.localize(

View File

@@ -121,6 +121,7 @@ export class CloudRegister extends LitElement {
</ul>
</div>
<ha-card
outlined
.header=${this.hass.localize(
"ui.panel.config.cloud.register.create_account"
)}

View File

@@ -196,7 +196,7 @@ class HaConfigSectionUpdates extends LitElement {
display: flex;
justify-content: space-between;
flex-direction: column;
padding: 16px;
padding: 0;
}
`;
}

View File

@@ -1,9 +1,21 @@
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { customElement, property } from "lit/decorators";
import { customElement, property, state } from "lit/decorators";
import { canShowPage } from "../../../common/config/can_show_page";
import { isComponentLoaded } from "../../../common/config/is_component_loaded";
import { relativeTime } from "../../../common/datetime/relative_time";
import "../../../components/ha-card";
import "../../../components/ha-navigation-list";
import { CloudStatus } from "../../../data/cloud";
import "../../../components/ha-tip";
import { BackupContent, fetchBackupInfo } from "../../../data/backup";
import { CloudStatus, fetchCloudStatus } from "../../../data/cloud";
import { BOARD_NAMES } from "../../../data/hardware";
import { fetchHassioBackups, HassioBackup } from "../../../data/hassio/backup";
import {
fetchHassioHassOsInfo,
fetchHassioHostInfo,
HassioHassOSInfo,
HassioHostInfo,
} from "../../../data/hassio/host";
import {
showAlertDialog,
showConfirmationDialog,
@@ -27,15 +39,80 @@ class HaConfigSystemNavigation extends LitElement {
@property({ type: Boolean }) public showAdvanced!: boolean;
@state() private _latestBackupDate?: string;
@state() private _boardName?: string;
@state() private _storageInfo?: { used: number; free: number; total: number };
@state() private _externalAccess = false;
protected render(): TemplateResult {
const pages = configSections.general
.filter((page) => canShowPage(this.hass, page))
.map((page) => ({
...page,
name: page.translationKey
? this.hass.localize(page.translationKey)
: page.name,
}));
.map((page) => {
let description = "";
switch (page.translationKey) {
case "backup":
description = this._latestBackupDate
? this.hass.localize(
"ui.panel.config.backup.description",
"relative_time",
relativeTime(
new Date(this._latestBackupDate),
this.hass.locale
)
)
: this.hass.localize(
"ui.panel.config.backup.description_no_backup"
);
break;
case "network":
description = this.hass.localize(
"ui.panel.config.network.description",
"state",
this._externalAccess
? this.hass.localize("ui.panel.config.network.enabled")
: this.hass.localize("ui.panel.config.network.disabled")
);
break;
case "storage":
description = this._storageInfo
? this.hass.localize(
"ui.panel.config.storage.description",
"percent_used",
`${Math.round(
(this._storageInfo.used / this._storageInfo.total) * 100
)}%`,
"free_space",
`${this._storageInfo.free} GB`
)
: "";
break;
case "hardware":
description =
this._boardName ||
this.hass.localize("ui.panel.config.hardware.description");
break;
default:
description = this.hass.localize(
`ui.panel.config.${page.translationKey}.description`
);
break;
}
return {
...page,
name: page.translationKey
? this.hass.localize(
`ui.panel.config.${page.translationKey}.caption`
)
: page.name,
description,
};
});
return html`
<hass-subpage
@@ -59,14 +136,32 @@ class HaConfigSystemNavigation extends LitElement {
.hass=${this.hass}
.narrow=${this.narrow}
.pages=${pages}
hasSecondary
></ha-navigation-list>
</ha-card>
<div class="yaml-config">Looking for YAML Configuration? It has moved to <a href="/developer-tools/yaml">Developer Tools</a></a></div>
${this.hass.userData?.showAdvanced
? html`<ha-tip>
Looking for YAML Configuration? It has moved to
<a href="/developer-tools/yaml">Developer Tools</a>
</ha-tip>`
: ""}
</ha-config-section>
</hass-subpage>
`;
}
protected firstUpdated(_changedProperties): void {
super.firstUpdated(_changedProperties);
this._fetchNetworkStatus();
const isHassioLoaded = isComponentLoaded(this.hass, "hassio");
this._fetchBackupInfo(isHassioLoaded);
if (isHassioLoaded) {
this._fetchHardwareInfo();
this._fetchStorageInfo();
}
}
private _restart() {
showConfirmationDialog(this, {
title: this.hass.localize(
@@ -91,6 +186,48 @@ class HaConfigSystemNavigation extends LitElement {
});
}
private async _fetchBackupInfo(isHassioLoaded: boolean) {
const backups: BackupContent[] | HassioBackup[] = isHassioLoaded
? await fetchHassioBackups(this.hass)
: await fetchBackupInfo(this.hass).then(
(backupData) => backupData.backups
);
if (backups.length > 0) {
this._latestBackupDate = (backups as any[]).reduce((a, b) =>
a.date > b.date ? a : b
).date;
}
}
private async _fetchHardwareInfo() {
const osData: HassioHassOSInfo = await fetchHassioHassOsInfo(this.hass);
if (osData.board) {
this._boardName = BOARD_NAMES[osData.board];
}
}
private async _fetchStorageInfo() {
const hostInfo: HassioHostInfo = await fetchHassioHostInfo(this.hass);
this._storageInfo = {
used: hostInfo.disk_used,
free: hostInfo.disk_free,
total: hostInfo.disk_total,
};
}
private async _fetchNetworkStatus() {
if (isComponentLoaded(this.hass, "cloud")) {
fetchCloudStatus(this.hass).then((cloudStatus) => {
if (cloudStatus.logged_in) {
this._externalAccess = true;
}
});
} else {
this._externalAccess = this.hass.config.external_url !== null;
}
}
static get styles(): CSSResultGroup {
return [
haStyle,
@@ -135,12 +272,9 @@ class HaConfigSystemNavigation extends LitElement {
ha-navigation-list {
--navigation-list-item-title-font-size: 16px;
--navigation-list-item-padding: 4px;
}
.yaml-config {
margin-bottom: max(env(safe-area-inset-bottom), 24px);
text-align: center;
font-style: italic;
ha-tip {
margin-bottom: max(env(safe-area-inset-bottom), 8px);
}
`,
];

View File

@@ -6,7 +6,7 @@ import { canShowPage } from "../../../common/config/can_show_page";
import "../../../components/ha-card";
import "../../../components/ha-icon-next";
import "../../../components/ha-navigation-list";
import type { CloudStatus, CloudStatusLoggedIn } from "../../../data/cloud";
import type { CloudStatus } from "../../../data/cloud";
import type { PageNavigation } from "../../../layouts/hass-tabs-subpage";
import type { HomeAssistant } from "../../../types";
@@ -37,9 +37,7 @@ class HaConfigNavigation extends LitElement {
? page.info.logged_in
? `
${this.hass.localize(
"ui.panel.config.cloud.description_login",
"email",
(page.info as CloudStatusLoggedIn).email
"ui.panel.config.cloud.description_login"
)}
`
: `

View File

@@ -1,5 +1,4 @@
import { customElement } from "lit/decorators";
import "../../../../components/ha-card";
import {
DeviceAction,
localizeDeviceAutomationAction,

View File

@@ -1,7 +1,6 @@
import { css, html, LitElement, TemplateResult } from "lit";
import { property, state } from "lit/decorators";
import { fireEvent } from "../../../../common/dom/fire_event";
import "../../../../components/ha-card";
import "../../../../components/ha-chip";
import "../../../../components/ha-chip-set";
import { showAutomationEditor } from "../../../../data/automation";

View File

@@ -1,5 +1,4 @@
import { customElement } from "lit/decorators";
import "../../../../components/ha-card";
import {
DeviceCondition,
localizeDeviceAutomationCondition,

View File

@@ -62,7 +62,7 @@ export class HaDeviceEntitiesCard extends LitElement {
protected render(): TemplateResult {
if (!this.entities.length) {
return html`
<ha-card .header=${this.header}>
<ha-card outlined .header=${this.header}>
<div class="empty card-content">
${this.hass.localize("ui.panel.config.devices.entities.none")}
</div>
@@ -89,7 +89,7 @@ export class HaDeviceEntitiesCard extends LitElement {
});
return html`
<ha-card .header=${this.header}>
<ha-card outlined .header=${this.header}>
<div id="entities" @hass-more-info=${this._overrideMoreInfo}>
${shownEntities.map((entry) =>
this.hass.states[entry.entity_id]

View File

@@ -1,5 +1,6 @@
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { customElement, property } from "lit/decorators";
import "../../../../components/ha-card";
import { AreaRegistryEntry } from "../../../../data/area_registry";
import {
computeDeviceName,
@@ -24,6 +25,7 @@ export class HaDeviceCard extends LitElement {
protected render(): TemplateResult {
return html`
<ha-card
outlined
.header=${this.hass.localize(
"ui.panel.config.devices.device_info",
"type",
@@ -145,3 +147,9 @@ export class HaDeviceCard extends LitElement {
];
}
}
declare global {
interface HTMLElementTagNameMap {
"ha-device-info-card": HaDeviceCard;
}
}

View File

@@ -579,7 +579,7 @@ export class HaConfigDevicePage extends LitElement {
${
isComponentLoaded(this.hass, "automation")
? html`
<ha-card>
<ha-card outlined>
<h1 class="card-header">
${this.hass.localize(
"ui.panel.config.devices.automation.automations_heading"
@@ -673,7 +673,7 @@ export class HaConfigDevicePage extends LitElement {
${
isComponentLoaded(this.hass, "scene") && entities.length
? html`
<ha-card>
<ha-card outlined>
<h1 class="card-header">
${this.hass.localize(
"ui.panel.config.devices.scene.scenes_heading"
@@ -771,7 +771,7 @@ export class HaConfigDevicePage extends LitElement {
${
isComponentLoaded(this.hass, "script")
? html`
<ha-card>
<ha-card outlined>
<h1 class="card-header">
${this.hass.localize(
"ui.panel.config.devices.script.scripts_heading"

View File

@@ -51,7 +51,7 @@ export class EnergyBatterySettings extends LitElement {
});
return html`
<ha-card>
<ha-card outlined>
<h1 class="card-header">
<ha-svg-icon .path=${mdiBatteryHigh}></ha-svg-icon>
${this.hass.localize("ui.panel.config.energy.battery.title")}

View File

@@ -36,7 +36,7 @@ export class EnergyDeviceSettings extends LitElement {
protected render(): TemplateResult {
return html`
<ha-card>
<ha-card outlined>
<h1 class="card-header">
<ha-svg-icon .path=${mdiDevices}></ha-svg-icon>
${this.hass.localize(

View File

@@ -51,7 +51,7 @@ export class EnergyGasSettings extends LitElement {
});
return html`
<ha-card>
<ha-card outlined>
<h1 class="card-header">
<ha-svg-icon .path=${mdiFire}></ha-svg-icon>
${this.hass.localize("ui.panel.config.energy.gas.title")}

View File

@@ -80,7 +80,7 @@ export class EnergyGridSettings extends LitElement {
}
return html`
<ha-card>
<ha-card outlined>
<h1 class="card-header">
<ha-svg-icon .path=${mdiTransmissionTower}></ha-svg-icon>
${this.hass.localize("ui.panel.config.energy.grid.title")}

View File

@@ -54,7 +54,7 @@ export class EnergySolarSettings extends LitElement {
});
return html`
<ha-card>
<ha-card outlined>
<h1 class="card-header">
<ha-svg-icon .path=${mdiSolarPower}></ha-svg-icon>
${this.hass.localize("ui.panel.config.energy.solar.title")}

View File

@@ -256,68 +256,68 @@ export const configSections: { [name: string]: PageNavigation[] } = {
general: [
{
path: "/config/general",
translationKey: "ui.panel.config.core.caption",
translationKey: "core",
iconPath: mdiCog,
iconColor: "#653249",
core: true,
},
{
path: "/config/updates",
translationKey: "ui.panel.config.updates.caption",
translationKey: "updates",
iconPath: mdiUpdate,
iconColor: "#3B808E",
},
{
component: "logs",
path: "/config/logs",
translationKey: "ui.panel.config.logs.caption",
translationKey: "logs",
iconPath: mdiMathLog,
iconColor: "#C65326",
core: true,
},
{
path: "/config/backup",
translationKey: "ui.panel.config.backup.caption",
translationKey: "backup",
iconPath: mdiBackupRestore,
iconColor: "#0D47A1",
component: "backup",
},
{
path: "/hassio/backups",
translationKey: "ui.panel.config.backup.caption",
translationKey: "backup",
iconPath: mdiBackupRestore,
iconColor: "#0D47A1",
component: "hassio",
},
{
path: "/config/analytics",
translationKey: "ui.panel.config.analytics.caption",
translationKey: "analytics",
iconPath: mdiShape,
iconColor: "#f1c447",
},
{
path: "/config/network",
translationKey: "ui.panel.config.network.caption",
translationKey: "network",
iconPath: mdiNetwork,
iconColor: "#B1345C",
},
{
path: "/config/storage",
translationKey: "ui.panel.config.storage.caption",
translationKey: "storage",
iconPath: mdiDatabase,
iconColor: "#518C43",
component: "hassio",
},
{
path: "/config/hardware",
translationKey: "ui.panel.config.hardware.caption",
translationKey: "hardware",
iconPath: mdiMemory,
iconColor: "#301A8E",
component: "hassio",
},
{
path: "/config/system_health",
translationKey: "ui.panel.config.system_health.caption",
translationKey: "system_health",
iconPath: mdiHeart,
iconColor: "#507FfE",
components: ["system_health", "hassio"],

View File

@@ -21,6 +21,7 @@ import { fetchErrorLog } from "../../../data/error_log";
import { extractApiErrorMessage } from "../../../data/hassio/common";
import { fetchHassioLogs } from "../../../data/hassio/supervisor";
import { HomeAssistant } from "../../../types";
import { debounce } from "../../../common/util/debounce";
@customElement("error-log-card")
class ErrorLogCard extends LitElement {
@@ -76,6 +77,12 @@ class ErrorLogCard extends LitElement {
`;
}
private _debounceSearch = debounce(
() => (this._isLogLoaded ? this._refreshLogs() : this._debounceSearch()),
150,
false
);
protected firstUpdated(changedProps: PropertyValues) {
super.firstUpdated(changedProps);
@@ -93,11 +100,15 @@ class ErrorLogCard extends LitElement {
}
if (
(changedProps.has("filter") && this._isLogLoaded) ||
(changedProps.has("show") && this.show) ||
(changedProps.has("provider") && this.show)
) {
this._refreshLogs();
return;
}
if (changedProps.has("filter")) {
this._debounceSearch();
}
}
@@ -116,6 +127,18 @@ class ErrorLogCard extends LitElement {
if (isComponentLoaded(this.hass, "hassio")) {
try {
log = await fetchHassioLogs(this.hass, this.provider);
if (this.filter) {
log = log
.split("\n")
.filter((entry) =>
entry.toLowerCase().includes(this.filter.toLowerCase())
)
.join("\n");
}
if (!log) {
this._logHTML = this.hass.localize("ui.panel.config.logs.no_errors");
return;
}
this._logHTML = html`<ha-ansi-to-html .content=${log}>
</ha-ansi-to-html>`;
this._isLogLoaded = true;
@@ -136,31 +159,33 @@ class ErrorLogCard extends LitElement {
this._isLogLoaded = true;
this._logHTML = log
? log
.split("\n")
.filter((entry) => {
if (this.filter) {
return entry.toLowerCase().includes(this.filter.toLowerCase());
}
return entry;
})
.map((entry) => {
if (entry.includes("INFO"))
return html`<div class="info">${entry}</div>`;
const split = log && log.split("\n");
if (entry.includes("WARNING"))
return html`<div class="warning">${entry}</div>`;
this._logHTML = split
? (this.filter
? split.filter((entry) => {
if (this.filter) {
return entry.toLowerCase().includes(this.filter.toLowerCase());
}
return entry;
})
: split
).map((entry) => {
if (entry.includes("INFO"))
return html`<div class="info">${entry}</div>`;
if (
entry.includes("ERROR") ||
entry.includes("FATAL") ||
entry.includes("CRITICAL")
)
return html`<div class="error">${entry}</div>`;
if (entry.includes("WARNING"))
return html`<div class="warning">${entry}</div>`;
return html`<div>${entry}</div>`;
})
if (
entry.includes("ERROR") ||
entry.includes("FATAL") ||
entry.includes("CRITICAL")
)
return html`<div class="error">${entry}</div>`;
return html`<div>${entry}</div>`;
})
: this.hass.localize("ui.panel.config.logs.no_errors");
}

View File

@@ -6,6 +6,7 @@ import { extractSearchParam } from "../../../common/url/search-params";
import "../../../components/ha-button-menu";
import "../../../components/search-input";
import { LogProvider } from "../../../data/error_log";
import { fetchHassioSupervisorInfo } from "../../../data/hassio/supervisor";
import "../../../layouts/hass-subpage";
import "../../../layouts/hass-tabs-subpage";
import { haStyle } from "../../../resources/styles";
@@ -59,6 +60,8 @@ export class HaConfigLogs extends LitElement {
@state() private _selectedLogProvider = "core";
@state() private _logProviders = logProviders;
public connectedCallback() {
super.connectedCallback();
if (this.systemLog && this.systemLog.loaded) {
@@ -66,6 +69,13 @@ export class HaConfigLogs extends LitElement {
}
}
protected firstUpdated(changedProps): void {
super.firstUpdated(changedProps);
if (isComponentLoaded(this.hass, "hassio")) {
this._getInstalledAddons();
}
}
private async _filterChanged(ev) {
this._filter = ev.detail.value;
}
@@ -107,7 +117,7 @@ export class HaConfigLogs extends LitElement {
<ha-button-menu corner="BOTTOM_START" slot="toolbar-icon">
<mwc-button
slot="trigger"
.label=${logProviders.find(
.label=${this._logProviders.find(
(p) => p.key === this._selectedLogProvider
)!.name}
>
@@ -116,7 +126,7 @@ export class HaConfigLogs extends LitElement {
.path=${mdiChevronDown}
></ha-svg-icon>
</mwc-button>
${logProviders.map(
${this._logProviders.map(
(provider) => html`
<mwc-list-item
?selected=${provider.key === this._selectedLogProvider}
@@ -155,6 +165,21 @@ export class HaConfigLogs extends LitElement {
this._selectedLogProvider = (ev.currentTarget as any).provider;
}
private async _getInstalledAddons() {
try {
const supervisorInfo = await fetchHassioSupervisorInfo(this.hass);
this._logProviders = [
...this._logProviders,
...supervisorInfo.addons.map((addon) => ({
key: addon.slug,
name: addon.name,
})),
];
} catch (err) {
// Ignore, nothing the user can do anyway
}
}
static get styles(): CSSResultGroup {
return [
haStyle,

View File

@@ -88,7 +88,7 @@ class HaConfigPerson extends LitElement {
</a>
</span>
<ha-card class="storage">
<ha-card outlined class="storage">
${this._storageItems.map(
(entry) => html`
<paper-icon-item @click=${this._openEditEntry} .entry=${entry}>
@@ -117,7 +117,7 @@ class HaConfigPerson extends LitElement {
</ha-card>
${this._configItems.length > 0
? html`
<ha-card header="Configuration.yaml persons">
<ha-card outlined header="Configuration.yaml persons">
${this._configItems.map(
(entry) => html`
<paper-icon-item>

View File

@@ -287,7 +287,7 @@ export class HaSceneEditor extends SubscribeMixin(
"ui.panel.config.scene.editor.introduction"
)}
</div>
<ha-card>
<ha-card outlined>
<div class="card-content">
<ha-textfield
.value=${this._config.name}
@@ -335,7 +335,7 @@ export class HaSceneEditor extends SubscribeMixin(
${devices.map(
(device) =>
html`
<ha-card>
<ha-card outlined>
<h1 class="card-header">
${device.name}
<ha-icon-button
@@ -373,6 +373,7 @@ export class HaSceneEditor extends SubscribeMixin(
)}
<ha-card
outlined
.header=${this.hass.localize(
"ui.panel.config.scene.editor.devices.add"
)}
@@ -405,6 +406,7 @@ export class HaSceneEditor extends SubscribeMixin(
${entities.length
? html`
<ha-card
outlined
class="entities"
.header=${this.hass.localize(
"ui.panel.config.scene.editor.entities.without_device"
@@ -445,6 +447,7 @@ export class HaSceneEditor extends SubscribeMixin(
: ""}
<ha-card
outlined
header=${this.hass.localize(
"ui.panel.config.scene.editor.entities.add"
)}

View File

@@ -51,7 +51,7 @@ export class HaBlueprintScriptEditor extends LitElement {
"ui.panel.config.automation.editor.blueprint.header"
)}</span
>
<ha-card>
<ha-card outlined>
<div class="blueprint-picker-container">
${this._blueprints
? Object.keys(this._blueprints).length

View File

@@ -290,7 +290,7 @@ export class HaScriptEditor extends KeyboardShortcutMixin(LitElement) {
"ui.panel.config.script.editor.introduction"
)}
</span>
<ha-card>
<ha-card outlined>
<div class="card-content">
<ha-form
.schema=${schema}
@@ -387,8 +387,8 @@ export class HaScriptEditor extends KeyboardShortcutMixin(LitElement) {
? html`
${!this.narrow
? html`
<ha-card
><div class="card-header">${this._config?.alias}</div>
<ha-card outlined>
<div class="card-header">${this._config?.alias}</div>
<div
class="card-actions layout horizontal justified center"
>
@@ -412,8 +412,8 @@ export class HaScriptEditor extends KeyboardShortcutMixin(LitElement) {
.defaultValue=${this._preprocessYaml()}
@value-changed=${this._yamlChanged}
></ha-yaml-editor>
<ha-card
><div class="card-actions">
<ha-card outlined>
<div class="card-actions">
<mwc-button @click=${this._copyYaml}>
${this.hass.localize(
"ui.panel.config.automation.editor.copy_to_clipboard"

View File

@@ -3,6 +3,7 @@ import { css, html, LitElement, PropertyValues, TemplateResult } from "lit";
import { customElement, property, state } from "lit/decorators";
import { isComponentLoaded } from "../../../common/config/is_component_loaded";
import "../../../components/ha-alert";
import "../../../components/ha-button-menu";
import "../../../components/ha-metric";
import { fetchHassioHostInfo, HassioHostInfo } from "../../../data/hassio/host";
import "../../../layouts/hass-subpage";

View File

@@ -9,7 +9,6 @@ import { html, LitElement, PropertyValues } from "lit";
import { customElement, property, state } from "lit/decorators";
import memoizeOne from "memoize-one";
import { DataTableColumnContainer } from "../../../components/data-table/ha-data-table";
import "../../../components/ha-card";
import "../../../components/ha-fab";
import "../../../components/ha-icon-button";
import "../../../components/ha-relative-time";

View File

@@ -228,7 +228,7 @@ export class HaConfigZone extends SubscribeMixin(LitElement) {
<span slot="introduction">
${hass.localize("ui.panel.config.zone.introduction")}
</span>
<ha-card>${listBox}</ha-card>
<ha-card outlined>${listBox}</ha-card>
</ha-config-section>
`
: ""}
@@ -471,7 +471,6 @@ export class HaConfigZone extends SubscribeMixin(LitElement) {
color: var(--primary-color);
}
ha-card {
max-width: 600px;
margin: 16px auto;
overflow: hidden;
}

View File

@@ -266,10 +266,14 @@ class HUIRoot extends LitElement {
</ha-tabs>
`
: html`<div main-title>${this.config.title}</div>`}
<ha-icon-button
.path=${mdiMagnify}
@click=${this._showQuickBar}
></ha-icon-button>
${!this.narrow
? html`
<ha-icon-button
.path=${mdiMagnify}
@click=${this._showQuickBar}
></ha-icon-button>
`
: ""}
${!this.narrow &&
this._conversation(this.hass.config.components)
? html`
@@ -292,6 +296,28 @@ class HUIRoot extends LitElement {
)}
.path=${mdiDotsVertical}
></ha-icon-button>
${this.narrow
? html`
<mwc-list-item
.label=${this.hass!.localize(
"ui.panel.lovelace.menu.search"
)}
graphic="icon"
@request-selected=${this._showQuickBar}
>
<span
>${this.hass!.localize(
"ui.panel.lovelace.menu.search"
)}</span
>
<ha-svg-icon
slot="graphic"
.path=${mdiMagnify}
></ha-svg-icon>
</mwc-list-item>
`
: ""}
${this.narrow &&
this._conversation(this.hass.config.components)
? html`

View File

@@ -676,18 +676,30 @@
"areas": "[%key:ui::panel::config::areas::caption%]",
"scene": "[%key:ui::panel::config::scene::caption%]",
"helpers": "[%key:ui::panel::config::helpers::caption%]",
"tag": "[%key:ui::panel::config::tag::caption%]",
"tags": "[%key:ui::panel::config::tag::caption%]",
"person": "[%key:ui::panel::config::person::caption%]",
"devices": "[%key:ui::panel::config::devices::caption%]",
"entities": "[%key:ui::panel::config::entities::caption%]",
"energy": "Energy Configuration",
"lovelace": "[%key:ui::panel::config::lovelace::caption%]",
"core": "[%key:ui::panel::config::core::caption%]",
"zone": "[%key:ui::panel::config::zone::caption%]",
"users": "[%key:ui::panel::config::users::caption%]",
"info": "[%key:ui::panel::config::info::caption%]",
"network": "[%key:ui::panel::config::network::caption%]",
"updates": "[%key:ui::panel::config::updates::caption%]",
"hardware": "[%key:ui::panel::config::hardware::caption%]",
"storage": "[%key:ui::panel::config::storage::caption%]",
"general": "[%key:ui::panel::config::core::caption%]",
"backups": "[%key:ui::panel::config::backup::caption%]",
"backup": "[%key:ui::panel::config::backup::caption%]",
"analytics": "[%key:ui::panel::config::analytics::caption%]",
"system_health": "[%key:ui::panel::config::system_health::caption%]",
"blueprint": "[%key:ui::panel::config::blueprint::caption%]",
"server_control": "[%key:ui::panel::developer-tools::tabs::yaml::title%]"
"server_control": "[%key:ui::panel::developer-tools::tabs::yaml::title%]",
"system": "[%key:ui::panel::config::dashboard::system::main%]",
"addon_dashboard": "Add-on Dashboard",
"addon_store": "Add-on Store",
"addon_info": "{addon} Info"
}
},
"filter_placeholder": "Entity Filter",
@@ -1132,7 +1144,7 @@
},
"tags": {
"main": "Tags",
"secondary": "Manage NFC tags and QR codes"
"secondary": "Setup NFC tags and QR codes"
},
"people": {
"main": "People",
@@ -1163,6 +1175,7 @@
},
"updates": {
"caption": "Updates",
"description": "Manage updates of Home Assistant, Add-ons and devices",
"no_updates": "No updates available",
"no_update_entities": {
"title": "Unable to check for updates",
@@ -1221,6 +1234,8 @@
},
"backup": {
"caption": "Backups",
"description": "Last backup {relative_time}",
"description_no_backup": "Manage backups and restore Home Assistant to a previous state",
"create_backup": "[%key:supervisor::backup::create_backup%]",
"creating_backup": "Backup is currently being created",
"download_backup": "[%key:supervisor::backup::download_backup%]",
@@ -1475,7 +1490,7 @@
},
"core": {
"caption": "General",
"description": "Location, network and analytics",
"description": "Name, Timezone and locale settings",
"section": {
"core": {
"header": "General Configuration",
@@ -1517,6 +1532,7 @@
},
"hardware": {
"caption": "Hardware",
"description": "Configure your hub and connected hardware",
"available_hardware": {
"failed_to_get": "Failed to get available hardware",
"title": "All Hardware",
@@ -1561,7 +1577,7 @@
},
"logs": {
"caption": "Logs",
"description": "View the Home Assistant logs",
"description": "View and search logs to diagnose issues",
"details": "Log Details ({level})",
"search": "Search logs",
"failed_get_logs": "Failed to get {provider} logs, {error}",
@@ -2230,7 +2246,7 @@
}
},
"cloud": {
"description_login": "Logged in as {email}",
"description_login": "Logged in and connected",
"description_not_login": "Not logged in",
"description_features": "Control home when away and integrate with Alexa and Google Assistant",
"login": {
@@ -3122,10 +3138,14 @@
"join": "Join the community on our {forums}, {twitter}, {discord}, {blog} or {newsletter}"
},
"analytics": {
"caption": "Analytics"
"caption": "Analytics",
"description": "Learn how to share data to better the Open Home"
},
"network": {
"caption": "Network",
"description": "External access {state}",
"enabled": "enabled",
"disabled": "disabled",
"supervisor": {
"title": "Configure network interfaces",
"connected_to": "Connected to {ssid}",
@@ -3146,6 +3166,7 @@
},
"storage": {
"caption": "Storage",
"description": "{percent_used} used - {free_space} free",
"used_space": "Used Space",
"emmc_lifetime_used": "eMMC Lifetime Used",
"datadisk": {
@@ -3163,6 +3184,7 @@
},
"system_health": {
"caption": "System Health",
"description": "Status, Stats and Integration startup time",
"cpu_usage": "Processor Usage",
"ram_usage": "Memory Usage",
"core_stats": "Core Stats",
@@ -3314,6 +3336,7 @@
"menu": {
"configure_ui": "Edit Dashboard",
"help": "Help",
"search": "Search",
"start_conversation": "Start conversation",
"reload_resources": "Reload resources",
"exit_edit_mode": "Done",