Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions src/data/hardware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,20 @@ export const BOARD_NAMES: Record<string, string> = {
"intel-nuc": "Intel NUC",
yellow: "Home Assistant Yellow",
};

export interface HardwareInfo {
hardware: HardwareInfoEntry[];
}

export interface HardwareInfoEntry {
board: HardwareInfoBoardInfo;
name: string;
url?: string;
}

export interface HardwareInfoBoardInfo {
manufacturer: string;
model?: string;
revision?: string;
hassio_board_id?: string;
}
193 changes: 122 additions & 71 deletions src/panels/config/hardware/ha-config-hardware.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
import "@material/mwc-list/mwc-list";
import "@material/mwc-list/mwc-list-item";
import { mdiDotsVertical } from "@mdi/js";
import { css, html, LitElement, PropertyValues, TemplateResult } from "lit";
import { customElement, property, state } from "lit/decorators";
import { ifDefined } from "lit/directives/if-defined";
import { isComponentLoaded } from "../../../common/config/is_component_loaded";
import "../../../components/buttons/ha-progress-button";
import "../../../components/ha-alert";
import "../../../components/ha-button-menu";
import "../../../components/ha-card";
import "../../../components/ha-clickable-list-item";
import "../../../components/ha-icon-next";
import "../../../components/ha-settings-row";
import { BOARD_NAMES } from "../../../data/hardware";
import { BOARD_NAMES, HardwareInfo } from "../../../data/hardware";
import {
extractApiErrorMessage,
ignoreSupervisorError,
Expand All @@ -28,6 +32,8 @@ import {
import "../../../layouts/hass-subpage";
import { haStyle } from "../../../resources/styles";
import type { HomeAssistant } from "../../../types";
import { hardwareBrandsUrl } from "../../../util/brands-url";
import { showToast } from "../../../util/toast";
import { showhardwareAvailableDialog } from "./show-dialog-hardware-available";

@customElement("ha-config-hardware")
Expand All @@ -42,14 +48,36 @@ class HaConfigHardware extends LitElement {

@state() private _hostData?: HassioHostInfo;

@state() private _hardwareInfo?: HardwareInfo;

protected firstUpdated(changedProps: PropertyValues) {
super.firstUpdated(changedProps);
if (isComponentLoaded(this.hass, "hassio")) {
this._load();
}
this._load();
}

protected render(): TemplateResult {
let boardId: string | undefined;
let boardName: string | undefined;
let imageURL: string | undefined;
let documentationURL: string | undefined;

if (this._hardwareInfo?.hardware.length) {
const boardData = this._hardwareInfo!.hardware[0];

boardId = boardData.board.hassio_board_id;
boardName = boardData.name;
documentationURL = boardData.url;
imageURL = hardwareBrandsUrl({
category: "boards",
manufacturer: boardData.board.manufacturer,
model: boardData.board.model,
darkOptimized: this.hass.themes?.darkMode,
});
} else if (this._OSData?.board) {
boardId = this._OSData.board;
boardName = BOARD_NAMES[this._OSData.board];
}

return html`
<hass-subpage
back-path="/config/system"
Expand All @@ -68,6 +96,20 @@ class HaConfigHardware extends LitElement {
"ui.panel.config.hardware.available_hardware.title"
)}</mwc-list-item
>
${this._hostData
? html`
<mwc-list-item class="warning" @click=${this._hostReboot}
>${this.hass.localize(
"ui.panel.config.hardware.reboot_host"
)}</mwc-list-item
>
<mwc-list-item class="warning" @click=${this._hostShutdown}
>${this.hass.localize(
"ui.panel.config.hardware.shutdown_host"
)}</mwc-list-item
>
`
: ""}
</ha-button-menu>
${this._error
? html`
Expand All @@ -76,57 +118,55 @@ class HaConfigHardware extends LitElement {
>
`
: ""}
${this._OSData || this._hostData
${boardName
? html`
<div class="content">
<ha-card outlined>
${this._OSData?.board
? html`
<div class="card-content">
<ha-settings-row>
<span slot="heading"
>${BOARD_NAMES[this._OSData.board] ||
this.hass.localize(
"ui.panel.config.hardware.board"
)}</span
<div class="card-content">
<mwc-list>
<mwc-list-item
graphic=${ifDefined(imageURL ? "medium" : undefined)}
.twoline=${Boolean(boardId)}
>
${imageURL
? html`<img slot="graphic" src=${imageURL} />`
: ""}
<span class="primary-text">
${boardName ||
this.hass.localize("ui.panel.config.hardware.board")}
</span>
${boardId
? html`
<span class="secondary-text" slot="secondary"
>${boardId}</span
>
`
: ""}
</mwc-list-item>
${documentationURL
? html`
<ha-clickable-list-item
.href=${documentationURL}
openNewTab
twoline
hasMeta
>
<div slot="description">
<span class="value">${this._OSData.board}</span>
</div>
</ha-settings-row>
</div>
`
: ""}
${this._hostData
? html`
<div class="card-actions">
${this._hostData.features.includes("reboot")
? html`
<ha-progress-button
class="warning"
@click=${this._hostReboot}
>
${this.hass.localize(
"ui.panel.config.hardware.reboot_host"
)}
</ha-progress-button>
`
: ""}
${this._hostData.features.includes("shutdown")
? html`
<ha-progress-button
class="warning"
@click=${this._hostShutdown}
>
${this.hass.localize(
"ui.panel.config.hardware.shutdown_host"
)}
</ha-progress-button>
`
: ""}
</div>
`
: ""}
<span
>${this.hass.localize(
"ui.panel.config.hardware.documentation"
)}</span
>
<span slot="secondary"
>${this.hass.localize(
"ui.panel.config.hardware.documentation_description"
)}</span
>
<ha-icon-next slot="meta"></ha-icon-next>
</ha-clickable-list-item>
`
: ""}
</mwc-list>
</div>
</ha-card>
</div>
`
Expand All @@ -136,9 +176,17 @@ class HaConfigHardware extends LitElement {
}

private async _load() {
const isHassioLoaded = isComponentLoaded(this.hass, "hassio");
try {
this._OSData = await fetchHassioHassOsInfo(this.hass);
this._hostData = await fetchHassioHostInfo(this.hass);
if (isComponentLoaded(this.hass, "hardware")) {
this._hardwareInfo = await this.hass.callWS({ type: "hardware/info" });
} else if (isHassioLoaded) {
this._OSData = await fetchHassioHassOsInfo(this.hass);
}

if (isHassioLoaded) {
this._hostData = await fetchHassioHostInfo(this.hass);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

now host data is not loaded when the hardware integration is loaded?

}
} catch (err: any) {
this._error = err.message || err;
}
Expand All @@ -148,10 +196,7 @@ class HaConfigHardware extends LitElement {
showhardwareAvailableDialog(this);
}

private async _hostReboot(ev: CustomEvent): Promise<void> {
const button = ev.currentTarget as any;
button.progress = true;

private async _hostReboot(): Promise<void> {
const confirmed = await showConfirmationDialog(this, {
title: this.hass.localize("ui.panel.config.hardware.reboot_host"),
text: this.hass.localize("ui.panel.config.hardware.reboot_host_confirm"),
Expand All @@ -160,10 +205,14 @@ class HaConfigHardware extends LitElement {
});

if (!confirmed) {
button.progress = false;
return;
}

showToast(this, {
message: this.hass.localize("ui.panel.config.hardware.rebooting_host"),
duration: 0,
});

try {
await rebootHost(this.hass);
} catch (err: any) {
Expand All @@ -177,13 +226,9 @@ class HaConfigHardware extends LitElement {
});
}
}
button.progress = false;
}

private async _hostShutdown(ev: CustomEvent): Promise<void> {
const button = ev.currentTarget as any;
button.progress = true;

private async _hostShutdown(): Promise<void> {
const confirmed = await showConfirmationDialog(this, {
title: this.hass.localize("ui.panel.config.hardware.shutdown_host"),
text: this.hass.localize(
Expand All @@ -194,10 +239,16 @@ class HaConfigHardware extends LitElement {
});

if (!confirmed) {
button.progress = false;
return;
}

showToast(this, {
message: this.hass.localize(
"ui.panel.config.hardware.host_shutting_down"
),
duration: 0,
});

try {
await shutdownHost(this.hass);
} catch (err: any) {
Expand All @@ -211,7 +262,6 @@ class HaConfigHardware extends LitElement {
});
}
}
button.progress = false;
}

static styles = [
Expand All @@ -234,17 +284,18 @@ class HaConfigHardware extends LitElement {
display: flex;
justify-content: space-between;
flex-direction: column;
padding: 16px 16px 0 16px;
padding: 16px;
}
ha-button-menu {
color: var(--secondary-text-color);
--mdc-menu-min-width: 200px;
}
.card-actions {
height: 48px;
display: flex;
justify-content: space-between;
align-items: center;

.primary-text {
font-size: 16px;
}
.secondary-text {
font-size: 14px;
}
`,
];
Expand Down
6 changes: 5 additions & 1 deletion src/translations/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -1569,12 +1569,16 @@
"attributes": "Attributes"
},
"reboot_host": "Reboot host",
"rebooting_host": "Rebooting host",
"reboot_host_confirm": "Are you sure you want to reboot your host?",
"failed_to_reboot_host": "Failed to reboot host",
"shutdown_host": "Shutdown host",
"host_shutting_down": "Host shutting down",
"shutdown_host_confirm": "Are you sure you want to shutdown your host?",
"failed_to_shutdown_host": "Failed to shutdown host",
"board": "Board"
"board": "Board",
"documentation": "Documentation",
"documentation_description": "Find extra information about your device"
},
"info": {
"caption": "About",
Expand Down
12 changes: 12 additions & 0 deletions src/util/brands-url.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,21 @@ export interface BrandsOptions {
darkOptimized?: boolean;
}

export interface HardwareBrandsOptions {
category: string;
model?: string;
manufacturer: string;
darkOptimized?: boolean;
}

export const brandsUrl = (options: BrandsOptions): string =>
`https://brands.home-assistant.io/${options.useFallback ? "_/" : ""}${
options.domain
}/${options.darkOptimized ? "dark_" : ""}${options.type}.png`;

export const hardwareBrandsUrl = (options: HardwareBrandsOptions): string =>
`https://brands.home-assistant.io/hardware/${options.category}/${
options.darkOptimized ? "dark_" : ""
}${options.manufacturer}${options.model ? `_${options.model}` : ""}.png`;

export const extractDomainFromBrandUrl = (url: string) => url.split("/")[4];