mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-18 14:56:37 +00:00
Add Hardware Page to Configuration System Menu (#12405)
This commit is contained in:
parent
dee59486ba
commit
8e55c83996
@ -397,6 +397,10 @@ class HaPanelConfig extends HassRouterPage {
|
||||
tag: "ha-config-energy",
|
||||
load: () => import("./energy/ha-config-energy"),
|
||||
},
|
||||
hardware: {
|
||||
tag: "ha-config-hardware",
|
||||
load: () => import("./hardware/ha-config-hardware"),
|
||||
},
|
||||
integrations: {
|
||||
tag: "ha-config-integrations",
|
||||
load: () => import("./integrations/ha-config-integrations"),
|
||||
|
213
src/panels/config/hardware/dialog-hardware-available.ts
Normal file
213
src/panels/config/hardware/dialog-hardware-available.ts
Normal file
@ -0,0 +1,213 @@
|
||||
import { mdiClose } from "@mdi/js";
|
||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { fireEvent } from "../../../common/dom/fire_event";
|
||||
import { stringCompare } from "../../../common/string/compare";
|
||||
import "../../../components/ha-dialog";
|
||||
import "../../../components/ha-expansion-panel";
|
||||
import "../../../components/ha-icon-next";
|
||||
import "../../../components/search-input";
|
||||
import { extractApiErrorMessage } from "../../../data/hassio/common";
|
||||
import {
|
||||
fetchHassioHardwareInfo,
|
||||
HassioHardwareInfo,
|
||||
} from "../../../data/hassio/hardware";
|
||||
import { showAlertDialog } from "../../../dialogs/generic/show-dialog-box";
|
||||
import type { HassDialog } from "../../../dialogs/make-dialog-manager";
|
||||
import { dump } from "../../../resources/js-yaml-dump";
|
||||
import { haStyle, haStyleDialog } from "../../../resources/styles";
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
|
||||
const _filterDevices = memoizeOne(
|
||||
(showAdvanced: boolean, hardware: HassioHardwareInfo, filter: string) =>
|
||||
hardware.devices
|
||||
.filter(
|
||||
(device) =>
|
||||
(showAdvanced ||
|
||||
["tty", "gpio", "input"].includes(device.subsystem)) &&
|
||||
(device.by_id?.toLowerCase().includes(filter) ||
|
||||
device.name.toLowerCase().includes(filter) ||
|
||||
device.dev_path.toLocaleLowerCase().includes(filter) ||
|
||||
JSON.stringify(device.attributes)
|
||||
.toLocaleLowerCase()
|
||||
.includes(filter))
|
||||
)
|
||||
.sort((a, b) => stringCompare(a.name, b.name))
|
||||
);
|
||||
|
||||
@customElement("ha-dialog-hardware-available")
|
||||
class DialogHardwareAvailable extends LitElement implements HassDialog {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@state() private _hardware?: HassioHardwareInfo;
|
||||
|
||||
@state() private _filter?: string;
|
||||
|
||||
public async showDialog(): Promise<Promise<void>> {
|
||||
try {
|
||||
this._hardware = await fetchHassioHardwareInfo(this.hass);
|
||||
} catch (err: any) {
|
||||
await showAlertDialog(this, {
|
||||
title: this.hass.localize(
|
||||
"ui.panel.config.hardware.available_hardware.failed_to_get"
|
||||
),
|
||||
text: extractApiErrorMessage(err),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public closeDialog(): void {
|
||||
this._hardware = undefined;
|
||||
fireEvent(this, "dialog-closed", { dialog: this.localName });
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
if (!this._hardware) {
|
||||
return html``;
|
||||
}
|
||||
|
||||
const devices = _filterDevices(
|
||||
this.hass.userData?.showAdvanced || false,
|
||||
this._hardware,
|
||||
(this._filter || "").toLowerCase()
|
||||
);
|
||||
|
||||
return html`
|
||||
<ha-dialog
|
||||
open
|
||||
hideActions
|
||||
@closed=${this.closeDialog}
|
||||
.heading=${this.hass.localize(
|
||||
"ui.panel.config.hardware.available_hardware.title"
|
||||
)}
|
||||
>
|
||||
<div class="header" slot="heading">
|
||||
<h2>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.hardware.available_hardware.title"
|
||||
)}
|
||||
</h2>
|
||||
<ha-icon-button
|
||||
.label=${this.hass.localize("common.close")}
|
||||
.path=${mdiClose}
|
||||
dialogAction="close"
|
||||
></ha-icon-button>
|
||||
<search-input
|
||||
.hass=${this.hass}
|
||||
.filter=${this._filter}
|
||||
@value-changed=${this._handleSearchChange}
|
||||
.label=${this.hass.localize("common.search")}
|
||||
>
|
||||
</search-input>
|
||||
</div>
|
||||
${devices.map(
|
||||
(device) =>
|
||||
html`
|
||||
<ha-expansion-panel
|
||||
.header=${device.name}
|
||||
.secondary=${device.by_id || undefined}
|
||||
outlined
|
||||
>
|
||||
<div class="device-property">
|
||||
<span>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.hardware.available_hardware.subsystem"
|
||||
)}:
|
||||
</span>
|
||||
<span>${device.subsystem}</span>
|
||||
</div>
|
||||
<div class="device-property">
|
||||
<span>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.hardware.available_hardware.device_path"
|
||||
)}:
|
||||
</span>
|
||||
<code>${device.dev_path}</code>
|
||||
</div>
|
||||
${device.by_id
|
||||
? html`
|
||||
<div class="device-property">
|
||||
<span>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.hardware.available_hardware.id"
|
||||
)}:
|
||||
</span>
|
||||
<code>${device.by_id}</code>
|
||||
</div>
|
||||
`
|
||||
: ""}
|
||||
<div class="attributes">
|
||||
<span>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.hardware.available_hardware.attributes"
|
||||
)}:
|
||||
</span>
|
||||
<pre>${dump(device.attributes, { indent: 2 })}</pre>
|
||||
</div>
|
||||
</ha-expansion-panel>
|
||||
`
|
||||
)}
|
||||
</ha-dialog>
|
||||
`;
|
||||
}
|
||||
|
||||
private _handleSearchChange(ev: CustomEvent) {
|
||||
this._filter = ev.detail.value;
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return [
|
||||
haStyle,
|
||||
haStyleDialog,
|
||||
css`
|
||||
ha-icon-button {
|
||||
position: absolute;
|
||||
right: 16px;
|
||||
top: 10px;
|
||||
text-decoration: none;
|
||||
color: var(--primary-text-color);
|
||||
}
|
||||
h2 {
|
||||
margin: 18px 42px 0 18px;
|
||||
color: var(--primary-text-color);
|
||||
}
|
||||
ha-expansion-panel {
|
||||
margin: 4px 0;
|
||||
}
|
||||
pre,
|
||||
code {
|
||||
background-color: var(--markdown-code-background-color, none);
|
||||
border-radius: 3px;
|
||||
}
|
||||
pre {
|
||||
padding: 16px;
|
||||
overflow: auto;
|
||||
line-height: 1.45;
|
||||
font-family: var(--code-font-family, monospace);
|
||||
}
|
||||
code {
|
||||
font-size: 85%;
|
||||
padding: 0.2em 0.4em;
|
||||
}
|
||||
search-input {
|
||||
margin: 8px 16px 0;
|
||||
display: block;
|
||||
}
|
||||
.device-property {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
.attributes {
|
||||
margin-top: 12px;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-dialog-hardware-available": DialogHardwareAvailable;
|
||||
}
|
||||
}
|
257
src/panels/config/hardware/ha-config-hardware.ts
Normal file
257
src/panels/config/hardware/ha-config-hardware.ts
Normal file
@ -0,0 +1,257 @@
|
||||
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 { 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-settings-row";
|
||||
import {
|
||||
extractApiErrorMessage,
|
||||
ignoreSupervisorError,
|
||||
} from "../../../data/hassio/common";
|
||||
import {
|
||||
fetchHassioHassOsInfo,
|
||||
fetchHassioHostInfo,
|
||||
HassioHassOSInfo,
|
||||
HassioHostInfo,
|
||||
rebootHost,
|
||||
shutdownHost,
|
||||
} from "../../../data/hassio/host";
|
||||
import {
|
||||
showAlertDialog,
|
||||
showConfirmationDialog,
|
||||
} from "../../../dialogs/generic/show-dialog-box";
|
||||
import "../../../layouts/hass-subpage";
|
||||
import { haStyle } from "../../../resources/styles";
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
import { showhardwareAvailableDialog } from "./show-dialog-hardware-available";
|
||||
|
||||
@customElement("ha-config-hardware")
|
||||
class HaConfigHardware extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property({ type: Boolean }) public narrow!: boolean;
|
||||
|
||||
@state() private _error?: { code: string; message: string };
|
||||
|
||||
@state() private _OSData?: HassioHassOSInfo;
|
||||
|
||||
@state() private _hostData?: HassioHostInfo;
|
||||
|
||||
protected firstUpdated(changedProps: PropertyValues) {
|
||||
super.firstUpdated(changedProps);
|
||||
if (isComponentLoaded(this.hass, "hassio")) {
|
||||
this._load();
|
||||
}
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return html`
|
||||
<hass-subpage
|
||||
back-path="/config/system"
|
||||
.hass=${this.hass}
|
||||
.narrow=${this.narrow}
|
||||
.header=${this.hass.localize("ui.panel.config.hardware.caption")}
|
||||
>
|
||||
${this._error
|
||||
? html`
|
||||
<ha-alert alert-type="error"
|
||||
>${this._error.message || this._error.code}</ha-alert
|
||||
>
|
||||
`
|
||||
: ""}
|
||||
${this._OSData && this._hostData
|
||||
? html`
|
||||
<div class="content">
|
||||
<ha-card outlined>
|
||||
<div class="card-content">
|
||||
<ha-settings-row>
|
||||
<span slot="heading"
|
||||
>${this.hass.localize(
|
||||
"ui.panel.config.hardware.board"
|
||||
)}</span
|
||||
>
|
||||
<div slot="description">
|
||||
<span class="value">${this._OSData.board}</span>
|
||||
</div>
|
||||
</ha-settings-row>
|
||||
</div>
|
||||
<div class="card-actions">
|
||||
<div class="buttons">
|
||||
${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>
|
||||
<ha-button-menu corner="BOTTOM_START">
|
||||
<ha-icon-button
|
||||
.label=${this.hass.localize("common.menu")}
|
||||
.path=${mdiDotsVertical}
|
||||
slot="trigger"
|
||||
></ha-icon-button>
|
||||
<mwc-list-item
|
||||
.action=${"hardware"}
|
||||
@click=${this._openHardware}
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.hardware.available_hardware.title"
|
||||
)}
|
||||
</mwc-list-item>
|
||||
</ha-button-menu>
|
||||
</div>
|
||||
</ha-card>
|
||||
</div>
|
||||
`
|
||||
: ""}
|
||||
</hass-subpage>
|
||||
`;
|
||||
}
|
||||
|
||||
private async _load() {
|
||||
try {
|
||||
this._OSData = await fetchHassioHassOsInfo(this.hass);
|
||||
this._hostData = await fetchHassioHostInfo(this.hass);
|
||||
} catch (err: any) {
|
||||
this._error = err.message || err;
|
||||
}
|
||||
}
|
||||
|
||||
private async _openHardware() {
|
||||
showhardwareAvailableDialog(this);
|
||||
}
|
||||
|
||||
private async _hostReboot(ev: CustomEvent): Promise<void> {
|
||||
const button = ev.currentTarget as any;
|
||||
button.progress = true;
|
||||
|
||||
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"),
|
||||
confirmText: this.hass.localize("ui.panel.config.hardware.reboot_host"),
|
||||
dismissText: this.hass.localize("common.cancel"),
|
||||
});
|
||||
|
||||
if (!confirmed) {
|
||||
button.progress = false;
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await rebootHost(this.hass);
|
||||
} catch (err: any) {
|
||||
// Ignore connection errors, these are all expected
|
||||
if (this.hass.connection.connected && !ignoreSupervisorError(err)) {
|
||||
showAlertDialog(this, {
|
||||
title: this.hass.localize(
|
||||
"ui.panel.config.hardware.failed_to_reboot_host"
|
||||
),
|
||||
text: extractApiErrorMessage(err),
|
||||
});
|
||||
}
|
||||
}
|
||||
button.progress = false;
|
||||
}
|
||||
|
||||
private async _hostShutdown(ev: CustomEvent): Promise<void> {
|
||||
const button = ev.currentTarget as any;
|
||||
button.progress = true;
|
||||
|
||||
const confirmed = await showConfirmationDialog(this, {
|
||||
title: this.hass.localize("ui.panel.config.hardware.shutdown_host"),
|
||||
text: this.hass.localize(
|
||||
"ui.panel.config.hardware.shutdown_host_confirm"
|
||||
),
|
||||
confirmText: this.hass.localize("ui.panel.config.hardware.shutdown_host"),
|
||||
dismissText: this.hass.localize("common.cancel"),
|
||||
});
|
||||
|
||||
if (!confirmed) {
|
||||
button.progress = false;
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await shutdownHost(this.hass);
|
||||
} catch (err: any) {
|
||||
// Ignore connection errors, these are all expected
|
||||
if (this.hass.connection.connected && !ignoreSupervisorError(err)) {
|
||||
showAlertDialog(this, {
|
||||
title: this.hass.localize(
|
||||
"ui.panel.config.hardware.failed_to_shutdown_host"
|
||||
),
|
||||
text: extractApiErrorMessage(err),
|
||||
});
|
||||
}
|
||||
}
|
||||
button.progress = false;
|
||||
}
|
||||
|
||||
static styles = [
|
||||
haStyle,
|
||||
css`
|
||||
.content {
|
||||
padding: 28px 20px 0;
|
||||
max-width: 1040px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
ha-card {
|
||||
max-width: 500px;
|
||||
margin: 0 auto;
|
||||
height: 100%;
|
||||
justify-content: space-between;
|
||||
flex-direction: column;
|
||||
display: flex;
|
||||
}
|
||||
.card-content {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
flex-direction: column;
|
||||
padding: 16px 16px 0 16px;
|
||||
}
|
||||
ha-button-menu {
|
||||
color: var(--secondary-text-color);
|
||||
--mdc-menu-min-width: 200px;
|
||||
}
|
||||
.card-actions {
|
||||
height: 48px;
|
||||
border-top: none;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
.buttons {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-config-hardware": HaConfigHardware;
|
||||
}
|
||||
}
|
12
src/panels/config/hardware/show-dialog-hardware-available.ts
Normal file
12
src/panels/config/hardware/show-dialog-hardware-available.ts
Normal file
@ -0,0 +1,12 @@
|
||||
import { fireEvent } from "../../../common/dom/fire_event";
|
||||
|
||||
export const loadHardwareAvailableDialog = () =>
|
||||
import("./dialog-hardware-available");
|
||||
|
||||
export const showhardwareAvailableDialog = (element: HTMLElement): void => {
|
||||
fireEvent(element, "show-dialog", {
|
||||
dialogTag: "ha-dialog-hardware-available",
|
||||
dialogImport: loadHardwareAvailableDialog,
|
||||
dialogParams: {},
|
||||
});
|
||||
};
|
@ -10,7 +10,8 @@ import { stopPropagation } from "../../../common/dom/stop_propagation";
|
||||
import { currencies } from "../../../components/currency-datalist";
|
||||
import { createCloseHeading } from "../../../components/ha-dialog";
|
||||
import "../../../components/ha-formfield";
|
||||
import { HaRadio } from "../../../components/ha-radio";
|
||||
import "../../../components/ha-radio";
|
||||
import type { HaRadio } from "../../../components/ha-radio";
|
||||
import "../../../components/ha-select";
|
||||
import "../../../components/ha-textfield";
|
||||
import { ConfigUpdateValues, saveCoreConfig } from "../../../data/core";
|
||||
|
@ -1465,7 +1465,22 @@
|
||||
"internal_url_https_error_description": "You have configured an HTTPS certificate in Home Assistant. This means that your internal URL needs to be set to a domain covered by the certficate."
|
||||
},
|
||||
"hardware": {
|
||||
"caption": "Hardware"
|
||||
"caption": "Hardware",
|
||||
"available_hardware": {
|
||||
"failed_to_get": "Failed to get available hardware",
|
||||
"title": "Available hardware",
|
||||
"subsystem": "Subsystem",
|
||||
"device_path": "Device path",
|
||||
"id": "ID",
|
||||
"attributes": "Attributes"
|
||||
},
|
||||
"reboot_host": "Reboot 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",
|
||||
"shutdown_host_confirm": "Are you sure you want to shutdown your host?",
|
||||
"failed_to_shutdown_host": "Failed to shutdown host",
|
||||
"board": "Board"
|
||||
},
|
||||
"info": {
|
||||
"caption": "Info",
|
||||
|
Loading…
x
Reference in New Issue
Block a user