Move System Information to Repairs (#13281)

Co-authored-by: Bram Kragten <mail@bramkragten.nl>
This commit is contained in:
Zack Barett 2022-07-27 04:23:55 -05:00 committed by GitHub
parent f7090583ac
commit c73677f15d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 431 additions and 347 deletions

View File

@ -26,7 +26,7 @@ import {
import { import {
UNHEALTHY_REASON_URL, UNHEALTHY_REASON_URL,
UNSUPPORTED_REASON_URL, UNSUPPORTED_REASON_URL,
} from "../../../src/panels/config/system-health/ha-config-system-health"; } from "../../../src/panels/config/repairs/dialog-system-information";
import { haStyle } from "../../../src/resources/styles"; import { haStyle } from "../../../src/resources/styles";
import { HomeAssistant } from "../../../src/types"; import { HomeAssistant } from "../../../src/types";
import { bytesToString } from "../../../src/util/bytes-to-string"; import { bytesToString } from "../../../src/util/bytes-to-string";

View File

@ -6,7 +6,6 @@ import {
mdiCog, mdiCog,
mdiDatabase, mdiDatabase,
mdiDevices, mdiDevices,
mdiHeart,
mdiInformation, mdiInformation,
mdiInformationOutline, mdiInformationOutline,
mdiLifebuoy, mdiLifebuoy,
@ -322,13 +321,6 @@ export const configSections: { [name: string]: PageNavigation[] } = {
iconColor: "#301A8E", iconColor: "#301A8E",
component: "hassio", component: "hassio",
}, },
{
path: "/config/system_health",
translationKey: "system_health",
iconPath: mdiHeart,
iconColor: "#507FfE",
components: ["system_health", "hassio"],
},
], ],
about: [ about: [
{ {
@ -447,10 +439,6 @@ class HaPanelConfig extends HassRouterPage {
tag: "ha-config-section-storage", tag: "ha-config-section-storage",
load: () => import("./storage/ha-config-section-storage"), load: () => import("./storage/ha-config-section-storage"),
}, },
system_health: {
tag: "ha-config-system-health",
load: () => import("./system-health/ha-config-system-health"),
},
updates: { updates: {
tag: "ha-config-section-updates", tag: "ha-config-section-updates",
load: () => import("./core/ha-config-section-updates"), load: () => import("./core/ha-config-section-updates"),

View File

@ -0,0 +1,57 @@
import "@material/mwc-button/mwc-button";
import { CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { customElement, property, state } from "lit/decorators";
import { fireEvent } from "../../../common/dom/fire_event";
import "../../../components/ha-card";
import { createCloseHeading } from "../../../components/ha-dialog";
import { haStyleDialog } from "../../../resources/styles";
import type { HomeAssistant } from "../../../types";
import "./integrations-startup-time";
@customElement("dialog-integration-startup")
class DialogIntegrationStartup extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@state() private _opened = false;
public showDialog(): void {
this._opened = true;
}
public closeDialog() {
this._opened = false;
fireEvent(this, "dialog-closed", { dialog: this.localName });
}
protected render(): TemplateResult {
if (!this._opened) {
return html``;
}
return html`
<ha-dialog
open
@closed=${this.closeDialog}
scrimClickAction
escapeKeyAction
.heading=${createCloseHeading(
this.hass,
this.hass.localize("ui.panel.config.repairs.integration_startup_time")
)}
>
<integrations-startup-time
.hass=${this.hass}
narrow
></integrations-startup-time>
</ha-dialog>
`;
}
static styles: CSSResultGroup = haStyleDialog;
}
declare global {
interface HTMLElementTagNameMap {
"dialog-integration-startup": DialogIntegrationStartup;
}
}

View File

@ -1,17 +1,15 @@
import { ActionDetail } from "@material/mwc-list"; import "@material/mwc-button/mwc-button";
import "@material/mwc-list/mwc-list-item"; import { UnsubscribeFunc } from "home-assistant-js-websocket";
import { mdiContentCopy } from "@mdi/js";
import { UnsubscribeFunc } from "home-assistant-js-websocket/dist/types";
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { customElement, property, state } from "lit/decorators"; import { customElement, property, state } from "lit/decorators";
import { isComponentLoaded } from "../../../common/config/is_component_loaded"; import { isComponentLoaded } from "../../../common/config/is_component_loaded";
import { formatDateTime } from "../../../common/datetime/format_date_time"; import { formatDateTime } from "../../../common/datetime/format_date_time";
import { fireEvent } from "../../../common/dom/fire_event";
import { copyToClipboard } from "../../../common/util/copy-clipboard"; import { copyToClipboard } from "../../../common/util/copy-clipboard";
import { subscribePollingCollection } from "../../../common/util/subscribe-polling"; import { subscribePollingCollection } from "../../../common/util/subscribe-polling";
import "../../../components/ha-alert"; import "../../../components/ha-alert";
import "../../../components/ha-button-menu";
import "../../../components/ha-card"; import "../../../components/ha-card";
import "../../../components/ha-circular-progress"; import { createCloseHeading } from "../../../components/ha-dialog";
import "../../../components/ha-metric"; import "../../../components/ha-metric";
import { fetchHassioStats, HassioStats } from "../../../data/hassio/common"; import { fetchHassioStats, HassioStats } from "../../../data/hassio/common";
import { import {
@ -25,12 +23,11 @@ import {
SystemHealthInfo, SystemHealthInfo,
} from "../../../data/system_health"; } from "../../../data/system_health";
import { showAlertDialog } from "../../../dialogs/generic/show-dialog-box"; import { showAlertDialog } from "../../../dialogs/generic/show-dialog-box";
import "../../../layouts/hass-subpage"; import { haStyleDialog } from "../../../resources/styles";
import { SubscribeMixin } from "../../../mixins/subscribe-mixin";
import type { HomeAssistant } from "../../../types"; import type { HomeAssistant } from "../../../types";
import { documentationUrl } from "../../../util/documentation-url"; import { documentationUrl } from "../../../util/documentation-url";
import { showToast } from "../../../util/toast"; import { showToast } from "../../../util/toast";
import "./integrations-card"; import "../../../components/ha-circular-progress";
const sortKeys = (a: string, b: string) => { const sortKeys = (a: string, b: string) => {
if (a === "homeassistant") { if (a === "homeassistant") {
@ -53,28 +50,40 @@ export const UNHEALTHY_REASON_URL = {
privileged: "/more-info/unsupported/privileged", privileged: "/more-info/unsupported/privileged",
}; };
@customElement("ha-config-system-health") @customElement("dialog-system-information")
class HaConfigSystemHealth extends SubscribeMixin(LitElement) { class DialogSystemInformation extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant; @property({ attribute: false }) public hass!: HomeAssistant;
@property({ type: Boolean }) public narrow!: boolean; @state() private _systemInfo?: SystemHealthInfo;
@state() private _info?: SystemHealthInfo;
@state() private _supervisorStats?: HassioStats;
@state() private _resolutionInfo?: HassioResolution; @state() private _resolutionInfo?: HassioResolution;
@state() private _supervisorStats?: HassioStats;
@state() private _coreStats?: HassioStats; @state() private _coreStats?: HassioStats;
@state() private _error?: { code: string; message: string }; @state() private _opened = false;
public hassSubscribe(): Array<UnsubscribeFunc | Promise<UnsubscribeFunc>> { private _subscriptions?: Array<UnsubscribeFunc | Promise<UnsubscribeFunc>>;
public showDialog(): void {
this._opened = true;
this.hass!.loadBackendTranslation("system_health");
this._subscribe();
}
public closeDialog() {
this._opened = false;
this._unsubscribe();
fireEvent(this, "dialog-closed", { dialog: this.localName });
}
private _subscribe(): void {
const subs: Array<UnsubscribeFunc | Promise<UnsubscribeFunc>> = []; const subs: Array<UnsubscribeFunc | Promise<UnsubscribeFunc>> = [];
if (isComponentLoaded(this.hass, "system_health")) { if (isComponentLoaded(this.hass, "system_health")) {
subs.push( subs.push(
subscribeSystemHealthInfo(this.hass!, (info) => { subscribeSystemHealthInfo(this.hass!, (info) => {
this._info = info; this._systemInfo = info;
}) })
); );
} }
@ -93,149 +102,51 @@ class HaConfigSystemHealth extends SubscribeMixin(LitElement) {
10000 10000
) )
); );
fetchHassioResolution(this.hass).then((data) => { fetchHassioResolution(this.hass).then((data) => {
this._resolutionInfo = data; this._resolutionInfo = data;
}); });
} }
return subs; this._subscriptions = subs;
} }
protected firstUpdated(changedProps) { private _unsubscribe() {
super.firstUpdated(changedProps); while (this._subscriptions?.length) {
const unsub = this._subscriptions.pop()!;
if (unsub instanceof Promise) {
unsub.then((unsubFunc) => unsubFunc());
} else {
unsub();
}
}
this._subscriptions = undefined;
this.hass!.loadBackendTranslation("system_health"); this._systemInfo = undefined;
this._resolutionInfo = undefined;
this._coreStats = undefined;
this._supervisorStats = undefined;
} }
protected render(): TemplateResult { protected render(): TemplateResult {
const sections: TemplateResult[] = []; if (!this._opened) {
return html``;
if (!this._info) {
sections.push(
html`
<div class="loading-container">
<ha-circular-progress active></ha-circular-progress>
</div>
`
);
} else {
const domains = Object.keys(this._info).sort(sortKeys);
for (const domain of domains) {
const domainInfo = this._info[domain];
const keys: TemplateResult[] = [];
for (const key of Object.keys(domainInfo.info)) {
let value: unknown;
if (
domainInfo.info[key] &&
typeof domainInfo.info[key] === "object"
) {
const info = domainInfo.info[key] as SystemCheckValueObject;
if (info.type === "pending") {
value = html`
<ha-circular-progress active size="tiny"></ha-circular-progress>
`;
} else if (info.type === "failed") {
value = html`
<span class="error">${info.error}</span>${!info.more_info
? ""
: html`
<a
href=${info.more_info}
target="_blank"
rel="noreferrer noopener"
>
${this.hass.localize(
"ui.panel.config.info.system_health.more_info"
)}
</a>
`}
`;
} else if (info.type === "date") {
value = formatDateTime(new Date(info.value), this.hass.locale);
}
} else {
value = domainInfo.info[key];
}
keys.push(html`
<tr>
<td>
${this.hass.localize(
`component.${domain}.system_health.info.${key}`
) || key}
</td>
<td>${value}</td>
</tr>
`);
}
if (domain !== "homeassistant") {
sections.push(
html`
<div class="card-header">
<h3>${domainToName(this.hass.localize, domain)}</h3>
${!domainInfo.manage_url
? ""
: html`
<a class="manage" href=${domainInfo.manage_url}>
<mwc-button>
${this.hass.localize(
"ui.panel.config.info.system_health.manage"
)}
</mwc-button>
</a>
`}
</div>
`
);
}
sections.push(html`
<table>
${keys}
</table>
`);
}
} }
const sections = this._getSections();
return html` return html`
<hass-subpage <ha-dialog
.hass=${this.hass} open
.narrow=${this.narrow} @closed=${this.closeDialog}
back-path="/config/system" scrimClickAction
.header=${this.hass.localize("ui.panel.config.system_health.caption")} escapeKeyAction
.heading=${createCloseHeading(
this.hass,
this.hass.localize("ui.panel.config.repairs.system_information")
)}
> >
${this._error <div>
? html`
<ha-alert alert-type="error"
>${this._error.message || this._error.code}</ha-alert
>
`
: ""}
${this._info
? html`
<ha-button-menu
corner="BOTTOM_START"
slot="toolbar-icon"
@action=${this._copyInfo}
>
<ha-icon-button
slot="trigger"
.label=${this.hass.localize("ui.panel.config.info.copy_menu")}
.path=${mdiContentCopy}
></ha-icon-button>
<mwc-list-item>
${this.hass.localize("ui.panel.config.info.copy_raw")}
</mwc-list-item>
<mwc-list-item>
${this.hass.localize("ui.panel.config.info.copy_github")}
</mwc-list-item>
</ha-button-menu>
`
: ""}
<div class="content">
${this._resolutionInfo ${this._resolutionInfo
? html`${this._resolutionInfo.unhealthy.length ? html`${this._resolutionInfo.unhealthy.length
? html`<ha-alert alert-type="error"> ? html`<ha-alert alert-type="error">
@ -265,66 +176,63 @@ class HaConfigSystemHealth extends SubscribeMixin(LitElement) {
: ""} ` : ""} `
: ""} : ""}
<ha-card outlined> <div>${sections}</div>
<div class="card-content">${sections}</div>
</ha-card>
${!this._coreStats && !this._supervisorStats ${!this._coreStats && !this._supervisorStats
? "" ? ""
: html` : html`
<ha-card outlined> <div>
<div class="card-content"> ${this._coreStats
${this._coreStats ? html`
? html` <h3>
<h3> ${this.hass.localize(
${this.hass.localize( "ui.panel.config.system_health.core_stats"
"ui.panel.config.system_health.core_stats" )}
)} </h3>
</h3> <ha-metric
<ha-metric .heading=${this.hass.localize(
.heading=${this.hass.localize( "ui.panel.config.system_health.cpu_usage"
"ui.panel.config.system_health.cpu_usage" )}
)} .value=${this._coreStats.cpu_percent}
.value=${this._coreStats.cpu_percent} ></ha-metric>
></ha-metric> <ha-metric
<ha-metric .heading=${this.hass.localize(
.heading=${this.hass.localize( "ui.panel.config.system_health.ram_usage"
"ui.panel.config.system_health.ram_usage" )}
)} .value=${this._coreStats.memory_percent}
.value=${this._coreStats.memory_percent} ></ha-metric>
></ha-metric> `
` : ""}
: ""} ${this._supervisorStats
${this._supervisorStats ? html`
? html` <h3>
<h3> ${this.hass.localize(
${this.hass.localize( "ui.panel.config.system_health.supervisor_stats"
"ui.panel.config.system_health.supervisor_stats" )}
)} </h3>
</h3> <ha-metric
<ha-metric .heading=${this.hass.localize(
.heading=${this.hass.localize( "ui.panel.config.system_health.cpu_usage"
"ui.panel.config.system_health.cpu_usage" )}
)} .value=${this._supervisorStats.cpu_percent}
.value=${this._supervisorStats.cpu_percent} ></ha-metric>
></ha-metric> <ha-metric
<ha-metric .heading=${this.hass.localize(
.heading=${this.hass.localize( "ui.panel.config.system_health.ram_usage"
"ui.panel.config.system_health.ram_usage" )}
)} .value=${this._supervisorStats.memory_percent}
.value=${this._supervisorStats.memory_percent} ></ha-metric>
></ha-metric> `
` : ""}
: ""} </div>
</div>
</ha-card>
`} `}
<integrations-card
.hass=${this.hass}
.narrow=${this.narrow}
></integrations-card>
</div> </div>
</hass-subpage> <mwc-button
slot="primaryAction"
.label=${this.hass.localize("ui.panel.config.repairs.copy")}
@click=${this._copyInfo}
></mwc-button>
</ha-dialog>
`; `;
} }
@ -386,17 +294,111 @@ class HaConfigSystemHealth extends SubscribeMixin(LitElement) {
}); });
} }
private async _copyInfo(ev: CustomEvent<ActionDetail>): Promise<void> { private _getSections(): TemplateResult[] {
const github = ev.detail.index === 1; const sections: TemplateResult[] = [];
if (!this._systemInfo) {
sections.push(
html`
<div class="loading-container">
<ha-circular-progress active></ha-circular-progress>
</div>
`
);
} else {
const domains = Object.keys(this._systemInfo).sort(sortKeys);
for (const domain of domains) {
const domainInfo = this._systemInfo[domain];
const keys: TemplateResult[] = [];
for (const key of Object.keys(domainInfo.info)) {
let value: unknown;
if (
domainInfo.info[key] &&
typeof domainInfo.info[key] === "object"
) {
const info = domainInfo.info[key] as SystemCheckValueObject;
if (info.type === "pending") {
value = html`
<ha-circular-progress active size="tiny"></ha-circular-progress>
`;
} else if (info.type === "failed") {
value = html`
<span class="error">${info.error}</span>${!info.more_info
? ""
: html`
<a
href=${info.more_info}
target="_blank"
rel="noreferrer noopener"
>
${this.hass.localize(
"ui.panel.config.info.system_health.more_systemInfo"
)}
</a>
`}
`;
} else if (info.type === "date") {
value = formatDateTime(new Date(info.value), this.hass.locale);
}
} else {
value = domainInfo.info[key];
}
keys.push(html`
<tr>
<td>
${this.hass.localize(
`component.${domain}.system_health.info.${key}`
) || key}
</td>
<td>${value}</td>
</tr>
`);
}
if (domain !== "homeassistant") {
sections.push(
html`
<div class="card-header">
<h3>${domainToName(this.hass.localize, domain)}</h3>
${!domainInfo.manage_url
? ""
: html`
<a class="manage" href=${domainInfo.manage_url}>
<mwc-button>
${this.hass.localize(
"ui.panel.config.info.system_health.manage"
)}
</mwc-button>
</a>
`}
</div>
`
);
}
sections.push(html`
<table>
${keys}
</table>
`);
}
}
return sections;
}
private async _copyInfo(): Promise<void> {
let haContent: string | undefined; let haContent: string | undefined;
const domainParts: string[] = []; const domainParts: string[] = [];
for (const domain of Object.keys(this._info!).sort(sortKeys)) { for (const domain of Object.keys(this._systemInfo!).sort(sortKeys)) {
const domainInfo = this._info![domain]; const domainInfo = this._systemInfo![domain];
let first = true; let first = true;
const parts = [ const parts = [
`${ `${
github && domain !== "homeassistant" domain !== "homeassistant"
? `<details><summary>${domainToName( ? `<details><summary>${domainToName(
this.hass.localize, this.hass.localize,
domain domain
@ -408,7 +410,7 @@ class HaConfigSystemHealth extends SubscribeMixin(LitElement) {
for (const key of Object.keys(domainInfo.info)) { for (const key of Object.keys(domainInfo.info)) {
let value: unknown; let value: unknown;
if (typeof domainInfo.info[key] === "object") { if (domainInfo.info[key] && typeof domainInfo.info[key] === "object") {
const info = domainInfo.info[key] as SystemCheckValueObject; const info = domainInfo.info[key] as SystemCheckValueObject;
if (info.type === "pending") { if (info.type === "pending") {
@ -421,11 +423,11 @@ class HaConfigSystemHealth extends SubscribeMixin(LitElement) {
} else { } else {
value = domainInfo.info[key]; value = domainInfo.info[key];
} }
if (github && first) { if (first) {
parts.push(`${key} | ${value}\n-- | --`); parts.push(`${key} | ${value}\n-- | --`);
first = false; first = false;
} else { } else {
parts.push(`${key}${github ? " | " : ": "}${value}`); parts.push(`${key} | ${value}`);
} }
} }
@ -433,16 +435,14 @@ class HaConfigSystemHealth extends SubscribeMixin(LitElement) {
haContent = parts.join("\n"); haContent = parts.join("\n");
} else { } else {
domainParts.push(parts.join("\n")); domainParts.push(parts.join("\n"));
if (github && domain !== "homeassistant") { if (domain !== "homeassistant") {
domainParts.push("</details>"); domainParts.push("</details>");
} }
} }
} }
await copyToClipboard( await copyToClipboard(
`${github ? "## " : ""}System Health\n${haContent}\n\n${domainParts.join( `${"## "}System Information\n${haContent}\n\n${domainParts.join("\n\n")}`
"\n\n"
)}`
); );
showToast(this, { showToast(this, {
@ -450,73 +450,50 @@ class HaConfigSystemHealth extends SubscribeMixin(LitElement) {
}); });
} }
static styles: CSSResultGroup = css` static styles: CSSResultGroup = [
.content { haStyleDialog,
padding: 28px 20px 0; css`
max-width: 1040px; ha-alert {
margin: 0 auto; margin-bottom: 16px;
} display: block;
integrations-card { }
max-width: 600px; table {
display: block; width: 100%;
max-width: 600px; }
margin: 0 auto;
margin-bottom: 24px;
margin-bottom: max(24px, env(safe-area-inset-bottom));
}
ha-card {
display: block;
max-width: 600px;
margin: 0 auto;
padding-bottom: 16px;
margin-bottom: 24px;
}
ha-alert {
display: block;
max-width: 500px;
margin: 0 auto;
margin-bottom: max(24px, env(safe-area-inset-bottom));
}
table {
width: 100%;
}
td:first-child { td:first-child {
width: 45%; width: 45%;
} }
td:last-child { td:last-child {
direction: ltr; direction: ltr;
} }
.loading-container { .loading-container {
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
} }
.card-header { .card-header {
justify-content: space-between; justify-content: space-between;
display: flex; display: flex;
align-items: center; align-items: center;
} }
.error { .error {
color: var(--error-color); color: var(--error-color);
} }
a { a.manage {
color: var(--primary-color); text-decoration: none;
} }
`,
a.manage { ];
text-decoration: none;
}
`;
} }
declare global { declare global {
interface HTMLElementTagNameMap { interface HTMLElementTagNameMap {
"ha-config-system-health": HaConfigSystemHealth; "dialog-system-information": DialogSystemInformation;
} }
} }

View File

@ -1,8 +1,10 @@
import type { ActionDetail } from "@material/mwc-list"; import { RequestSelectedDetail } from "@material/mwc-list/mwc-list-item-base";
import { mdiDotsVertical } from "@mdi/js"; import { mdiDotsVertical } from "@mdi/js";
import { css, html, LitElement, PropertyValues, TemplateResult } from "lit"; import { css, html, LitElement, PropertyValues, TemplateResult } from "lit";
import { customElement, property, state } from "lit/decorators"; import { customElement, property, state } from "lit/decorators";
import memoizeOne from "memoize-one"; import memoizeOne from "memoize-one";
import { isComponentLoaded } from "../../../common/config/is_component_loaded";
import { shouldHandleRequestSelectedEvent } from "../../../common/mwc/handle-request-selected-event";
import "../../../components/ha-card"; import "../../../components/ha-card";
import { import {
fetchRepairsIssues, fetchRepairsIssues,
@ -10,11 +12,14 @@ import {
severitySort, severitySort,
} from "../../../data/repairs"; } from "../../../data/repairs";
import "../../../layouts/hass-subpage"; import "../../../layouts/hass-subpage";
import { SubscribeMixin } from "../../../mixins/subscribe-mixin";
import type { HomeAssistant } from "../../../types"; import type { HomeAssistant } from "../../../types";
import "./ha-config-repairs"; import "./ha-config-repairs";
import { showIntegrationStartupDialog } from "./show-integration-startup-dialog";
import { showSystemInformationDialog } from "./show-system-information-dialog";
@customElement("ha-config-repairs-dashboard") @customElement("ha-config-repairs-dashboard")
class HaConfigRepairsDashboard extends LitElement { class HaConfigRepairsDashboard extends SubscribeMixin(LitElement) {
@property({ attribute: false }) public hass!: HomeAssistant; @property({ attribute: false }) public hass!: HomeAssistant;
@property({ type: Boolean }) public narrow!: boolean; @property({ type: Boolean }) public narrow!: boolean;
@ -40,6 +45,7 @@ class HaConfigRepairsDashboard extends LitElement {
this._showIgnored, this._showIgnored,
this._repairsIssues this._repairsIssues
); );
return html` return html`
<hass-subpage <hass-subpage
back-path="/config/system" back-path="/config/system"
@ -48,13 +54,32 @@ class HaConfigRepairsDashboard extends LitElement {
.header=${this.hass.localize("ui.panel.config.repairs.caption")} .header=${this.hass.localize("ui.panel.config.repairs.caption")}
> >
<div slot="toolbar-icon"> <div slot="toolbar-icon">
<ha-button-menu corner="BOTTOM_START" @action=${this._handleAction}> <ha-button-menu corner="BOTTOM_START">
<ha-icon-button <ha-icon-button
slot="trigger" slot="trigger"
.label=${this.hass.localize("ui.common.menu")} .label=${this.hass.localize("ui.common.menu")}
.path=${mdiDotsVertical} .path=${mdiDotsVertical}
></ha-icon-button> ></ha-icon-button>
<mwc-list-item id="skipped"> ${isComponentLoaded(this.hass, "system_health") ||
isComponentLoaded(this.hass, "hassio")
? html`
<mwc-list-item
@request-selected=${this._showSystemInformationDialog}
>
${this.hass.localize(
"ui.panel.config.repairs.system_information"
)}
</mwc-list-item>
`
: ""}
<mwc-list-item
@request-selected=${this._showIntegrationStartupDialog}
>
${this.hass.localize(
"ui.panel.config.repairs.integration_startup_time"
)}
</mwc-list-item>
<mwc-list-item @request-selected=${this._toggleIgnored}>
${this._showIgnored ${this._showIgnored
? this.hass.localize("ui.panel.config.repairs.hide_ignored") ? this.hass.localize("ui.panel.config.repairs.hide_ignored")
: this.hass.localize("ui.panel.config.repairs.show_ignored")} : this.hass.localize("ui.panel.config.repairs.show_ignored")}
@ -98,12 +123,32 @@ class HaConfigRepairsDashboard extends LitElement {
this.hass.loadBackendTranslation("issues", [...integrations]); this.hass.loadBackendTranslation("issues", [...integrations]);
} }
private _handleAction(ev: CustomEvent<ActionDetail>) { private _showSystemInformationDialog(
switch (ev.detail.index) { ev: CustomEvent<RequestSelectedDetail>
case 0: ): void {
this._showIgnored = !this._showIgnored; if (!shouldHandleRequestSelectedEvent(ev)) {
break; return;
} }
showSystemInformationDialog(this);
}
private _showIntegrationStartupDialog(
ev: CustomEvent<RequestSelectedDetail>
): void {
if (!shouldHandleRequestSelectedEvent(ev)) {
return;
}
showIntegrationStartupDialog(this);
}
private _toggleIgnored(ev: CustomEvent<RequestSelectedDetail>): void {
if (!shouldHandleRequestSelectedEvent(ev)) {
return;
}
this._showIgnored = !this._showIgnored;
} }
static styles = css` static styles = css`

View File

@ -21,8 +21,8 @@ import type { HomeAssistant } from "../../../types";
import { brandsUrl } from "../../../util/brands-url"; import { brandsUrl } from "../../../util/brands-url";
import { documentationUrl } from "../../../util/documentation-url"; import { documentationUrl } from "../../../util/documentation-url";
@customElement("integrations-card") @customElement("integrations-startup-time")
class IntegrationsCard extends LitElement { class IntegrationsStartupTime extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant; @property({ attribute: false }) public hass!: HomeAssistant;
@property({ type: Boolean }) public narrow = false; @property({ type: Boolean }) public narrow = false;
@ -45,57 +45,47 @@ class IntegrationsCard extends LitElement {
} }
return html` return html`
<ha-card <mwc-list>
outlined ${this._setups?.map((setup) => {
.header=${this.hass.localize( const manifest = this._manifests && this._manifests[setup.domain];
"ui.panel.config.system_health.integration_start_time" const docLink = manifest
)} ? manifest.is_built_in
> ? documentationUrl(this.hass, `/integrations/${manifest.domain}`)
<mwc-list> : manifest.documentation
${this._setups?.map((setup) => { : "";
const manifest = this._manifests && this._manifests[setup.domain];
const docLink = manifest
? manifest.is_built_in
? documentationUrl(
this.hass,
`/integrations/${manifest.domain}`
)
: manifest.documentation
: "";
const setupSeconds = setup.seconds?.toFixed(2); const setupSeconds = setup.seconds?.toFixed(2);
return html` return html`
<ha-clickable-list-item <ha-clickable-list-item
graphic="avatar" graphic="avatar"
twoline twoline
hasMeta hasMeta
openNewTab openNewTab
@click=${this._entryClicked} @click=${this._entryClicked}
href=${docLink} href=${docLink}
> >
<img <img
loading="lazy" loading="lazy"
src=${brandsUrl({ src=${brandsUrl({
domain: setup.domain, domain: setup.domain,
type: "icon", type: "icon",
useFallback: true, useFallback: true,
darkOptimized: this.hass.themes?.darkMode, darkOptimized: this.hass.themes?.darkMode,
})} })}
referrerpolicy="no-referrer" referrerpolicy="no-referrer"
slot="graphic" slot="graphic"
/> />
<span> <span>
${domainToName(this.hass.localize, setup.domain, manifest)} ${domainToName(this.hass.localize, setup.domain, manifest)}
</span> </span>
<span slot="secondary">${setup.domain}</span> <span slot="secondary">${setup.domain}</span>
<div slot="meta"> <div slot="meta">
${setupSeconds ? html`${setupSeconds} s` : ""} ${setupSeconds ? html`${setupSeconds} s` : ""}
</div> </div>
</ha-clickable-list-item> </ha-clickable-list-item>
`; `;
})} })}
</mwc-list> </mwc-list>
</ha-card>
`; `;
} }
@ -149,6 +139,6 @@ class IntegrationsCard extends LitElement {
declare global { declare global {
interface HTMLElementTagNameMap { interface HTMLElementTagNameMap {
"integrations-card": IntegrationsCard; "integrations-startup-time": IntegrationsStartupTime;
} }
} }

View File

@ -0,0 +1,12 @@
import { fireEvent } from "../../../common/dom/fire_event";
export const loadIntegrationStartupDialog = () =>
import("./dialog-integration-startup");
export const showIntegrationStartupDialog = (element: HTMLElement): void => {
fireEvent(element, "show-dialog", {
dialogTag: "dialog-integration-startup",
dialogImport: loadIntegrationStartupDialog,
dialogParams: {},
});
};

View File

@ -0,0 +1,12 @@
import { fireEvent } from "../../../common/dom/fire_event";
export const loadSystemInformationDialog = () =>
import("./dialog-system-information");
export const showSystemInformationDialog = (element: HTMLElement): void => {
fireEvent(element, "show-dialog", {
dialogTag: "dialog-system-information",
dialogImport: loadSystemInformationDialog,
dialogParams: undefined,
});
};

View File

@ -1237,6 +1237,9 @@
"critical": "Critical", "critical": "Critical",
"error": "Error", "error": "Error",
"warning": "Warning", "warning": "Warning",
"system_information": "System information",
"integration_startup_time": "Integration startup time",
"copy": "Copy",
"dialog": { "dialog": {
"title": "Repair", "title": "Repair",
"fix": "Repair", "fix": "Repair",