Add restart dialog (#15337)

This commit is contained in:
Paul Bottein 2023-02-14 13:04:18 +01:00 committed by GitHub
parent ec66e8331e
commit e81f596d76
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 483 additions and 165 deletions

View File

@ -0,0 +1,382 @@
import "@material/mwc-list/mwc-list";
import { mdiAutoFix, mdiPower, mdiPowerCycle, mdiRefresh } from "@mdi/js";
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { customElement, property, state } from "lit/decorators";
import { isComponentLoaded } from "../../common/config/is_component_loaded";
import { fireEvent } from "../../common/dom/fire_event";
import { shouldHandleRequestSelectedEvent } from "../../common/mwc/handle-request-selected-event";
import "../../components/ha-circular-progress";
import { createCloseHeading } from "../../components/ha-dialog";
import "../../components/ha-list-item";
import {
extractApiErrorMessage,
ignoreSupervisorError,
} from "../../data/hassio/common";
import {
fetchHassioHostInfo,
HassioHostInfo,
rebootHost,
shutdownHost,
} from "../../data/hassio/host";
import { haStyle, haStyleDialog } from "../../resources/styles";
import { HomeAssistant } from "../../types";
import { showToast } from "../../util/toast";
import {
showAlertDialog,
showConfirmationDialog,
} from "../generic/show-dialog-box";
@customElement("dialog-restart")
class DialogRestart extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@state() private _open = false;
@state()
private _loadingHostInfo = false;
@state()
private _hostInfo?: HassioHostInfo;
public async showDialog(): Promise<void> {
const isHassioLoaded = isComponentLoaded(this.hass, "hassio");
this._open = true;
if (isHassioLoaded && !this._hostInfo) {
this._loadingHostInfo = true;
try {
this._hostInfo = await fetchHassioHostInfo(this.hass);
} catch (_err) {
// Do nothing
} finally {
this._loadingHostInfo = false;
}
}
const showReload = this.hass.userData?.showAdvanced;
const showRebootShutdown = !!this._hostInfo;
// Present restart core dialog if no host actions and not advanced mode as it's the only option
if (!showReload && !showRebootShutdown) {
this._open = false;
this._showRestartDialog().then(() => this.closeDialog());
return;
}
await this.updateComplete;
}
public closeDialog(): void {
this._open = false;
this._loadingHostInfo = false;
fireEvent(this, "dialog-closed", { dialog: this.localName });
}
protected render(): TemplateResult {
if (!this._open) {
return html``;
}
const showReload = this.hass.userData?.showAdvanced;
const showRebootShutdown = !!this._hostInfo;
return html`
<ha-dialog
open
@closed=${this.closeDialog}
hideActions
.heading=${!this._loadingHostInfo
? createCloseHeading(
this.hass,
this.hass.localize("ui.dialogs.restart.heading")
)
: undefined}
>
${this._loadingHostInfo
? html`
<div class="loader">
<ha-circular-progress active></ha-circular-progress>
</div>
`
: html`
<mwc-list dialogInitialFocus>
${showReload
? html`
<ha-list-item
graphic="avatar"
twoline
hasMeta
@request-selected=${this._reload}
>
<div slot="graphic" class="icon-background reload">
<ha-svg-icon .path=${mdiAutoFix}></ha-svg-icon>
</div>
<span>
${this.hass.localize(
"ui.dialogs.restart.reload.title"
)}
</span>
<span slot="secondary">
${this.hass.localize(
"ui.dialogs.restart.reload.description"
)}
</span>
</ha-list-item>
`
: null}
<ha-list-item
graphic="avatar"
twoline
hasMeta
@request-selected=${this._restart}
>
<div slot="graphic" class="icon-background restart">
<ha-svg-icon .path=${mdiRefresh}></ha-svg-icon>
</div>
<span>
${this.hass.localize("ui.dialogs.restart.restart.title")}
</span>
<span slot="secondary">
${this.hass.localize(
"ui.dialogs.restart.restart.description"
)}
</span>
</ha-list-item>
${showRebootShutdown
? html`
<div class="divider"></div>
<p class="section">
${this.hass.localize(
"ui.dialogs.restart.advanced_options"
)}
</p>
<ha-list-item
graphic="avatar"
twoline
hasMeta
@request-selected=${this._hostReboot}
>
<div slot="graphic" class="icon-background reboot">
<ha-svg-icon .path=${mdiPowerCycle}></ha-svg-icon>
</div>
<span>
${this.hass.localize(
"ui.dialogs.restart.reboot.title"
)}
</span>
<span slot="secondary">
${this.hass.localize(
"ui.dialogs.restart.reboot.description"
)}
</span>
</ha-list-item>
<ha-list-item
graphic="avatar"
twoline
hasMeta
@request-selected=${this._hostShutdown}
>
<div slot="graphic" class="icon-background shutdown">
<ha-svg-icon .path=${mdiPower}></ha-svg-icon>
</div>
<span>
${this.hass.localize(
"ui.dialogs.restart.shutdown.title"
)}
</span>
<span slot="secondary">
${this.hass.localize(
"ui.dialogs.restart.shutdown.description"
)}
</span>
</ha-list-item>
`
: null}
</mwc-list>
`}
</ha-dialog>
`;
}
private async _reload(ev) {
if (!shouldHandleRequestSelectedEvent(ev)) {
return;
}
this.closeDialog();
showToast(this, {
message: this.hass.localize("ui.dialogs.restart.reload.reloading"),
duration: 1000,
});
await this.hass.callService("homeassistant", "reload_all");
}
private async _restart(ev) {
if (!shouldHandleRequestSelectedEvent(ev)) {
return;
}
this._showRestartDialog();
}
private async _showRestartDialog() {
const confirmed = await showConfirmationDialog(this, {
title: this.hass.localize("ui.dialogs.restart.restart.confirm_title"),
text: this.hass.localize(
"ui.dialogs.restart.restart.confirm_description"
),
confirmText: this.hass.localize(
"ui.dialogs.restart.restart.confirm_action"
),
destructive: true,
});
if (!confirmed) {
return;
}
this.closeDialog();
try {
await this.hass.callService("homeassistant", "restart");
} catch (err: any) {
showAlertDialog(this, {
title: this.hass.localize("ui.dialogs.restart.restart.failed"),
text: err.message,
});
}
}
private async _hostReboot(ev): Promise<void> {
if (!shouldHandleRequestSelectedEvent(ev)) {
return;
}
const confirmed = await showConfirmationDialog(this, {
title: this.hass.localize("ui.dialogs.restart.reboot.confirm_title"),
text: this.hass.localize("ui.dialogs.restart.reboot.confirm_description"),
confirmText: this.hass.localize(
"ui.dialogs.restart.reboot.confirm_action"
),
destructive: true,
});
if (!confirmed) {
return;
}
this.closeDialog();
showToast(this, {
message: this.hass.localize("ui.dialogs.restart.reboot.rebooting"),
duration: 0,
});
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.dialogs.restart.reboot.failed"),
text: extractApiErrorMessage(err),
});
}
}
}
private async _hostShutdown(ev): Promise<void> {
if (!shouldHandleRequestSelectedEvent(ev)) {
return;
}
const confirmed = await showConfirmationDialog(this, {
title: this.hass.localize("ui.dialogs.restart.shutdown.confirm_title"),
text: this.hass.localize(
"ui.dialogs.restart.shutdown.confirm_description"
),
confirmText: this.hass.localize(
"ui.dialogs.restart.shutdown.confirm_action"
),
destructive: true,
});
if (!confirmed) {
return;
}
this.closeDialog();
showToast(this, {
message: this.hass.localize("ui.dialogs.restart.shutdown.shutting_down"),
duration: 0,
});
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.dialogs.restart.shutdown.failed"),
text: extractApiErrorMessage(err),
});
}
}
}
static get styles(): CSSResultGroup {
return [
haStyle,
haStyleDialog,
css`
ha-dialog {
--dialog-content-padding: 0;
}
.icon-background {
border-radius: 50%;
color: #fff;
}
.reload {
background-color: #5f8a49;
}
.restart {
background-color: #ffd500;
color: #665500;
}
.reboot {
background-color: #ba1b1b;
color: #fff;
}
.shutdown {
background-color: #0b1d29;
color: #fff;
}
.divider {
height: 1px;
background-color: var(--divider-color);
}
.section {
font-weight: 500;
font-size: 14px;
line-height: 20px;
margin: 8px 0 4px 0;
padding-left: var(--mdc-list-side-padding, 20px);
padding-right: var(--mdc-list-side-padding, 20px);
}
.loader {
display: flex;
align-items: center;
justify-content: center;
padding: 24px;
}
`,
];
}
}
declare global {
interface HTMLElementTagNameMap {
"dialog-restart": DialogRestart;
}
}

View File

@ -0,0 +1,14 @@
import { fireEvent } from "../../common/dom/fire_event";
// eslint-disable-next-line @typescript-eslint/no-empty-interface
export interface RestartDialogParams {}
export const loadRestartDialog = () => import("./dialog-restart");
export const showRestartDialog = (element: HTMLElement): void => {
fireEvent(element, "show-dialog", {
dialogTag: "dialog-restart",
dialogImport: loadRestartDialog,
dialogParams: {},
});
};

View File

@ -1,3 +1,4 @@
import { mdiPower } from "@mdi/js";
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { customElement, property, state } from "lit/decorators";
import { canShowPage } from "../../../common/config/can_show_page";
@ -5,6 +6,7 @@ import { isComponentLoaded } from "../../../common/config/is_component_loaded";
import { relativeTime } from "../../../common/datetime/relative_time";
import { blankBeforePercent } from "../../../common/translations/blank_before_percent";
import "../../../components/ha-card";
import "../../../components/ha-icon-button";
import "../../../components/ha-navigation-list";
import "../../../components/ha-tip";
import { BackupContent, fetchBackupInfo } from "../../../data/backup";
@ -17,10 +19,7 @@ import {
HassioHassOSInfo,
HassioHostInfo,
} from "../../../data/hassio/host";
import {
showAlertDialog,
showConfirmationDialog,
} from "../../../dialogs/generic/show-dialog-box";
import { showRestartDialog } from "../../../dialogs/restart/show-dialog-restart";
import "../../../layouts/hass-subpage";
import { haStyle } from "../../../resources/styles";
import type { HomeAssistant } from "../../../types";
@ -121,13 +120,14 @@ class HaConfigSystemNavigation extends LitElement {
back-path="/config"
.header=${this.hass.localize("ui.panel.config.dashboard.system.main")}
>
<mwc-button
<ha-icon-button
slot="toolbar-icon"
.path=${mdiPower}
.label=${this.hass.localize(
"ui.panel.config.system_dashboard.restart_homeassistant_short"
"ui.panel.config.system_dashboard.restart_homeassistant"
)}
@click=${this._restart}
></mwc-button>
@click=${this._showRestartDialog}
></ha-icon-button>
<ha-config-section
.narrow=${this.narrow}
.isWide=${this.isWide}
@ -161,31 +161,6 @@ class HaConfigSystemNavigation extends LitElement {
}
}
private _restart() {
showConfirmationDialog(this, {
title: this.hass.localize(
"ui.panel.config.system_dashboard.confirm_restart_title"
),
text: this.hass.localize(
"ui.panel.config.system_dashboard.confirm_restart_text"
),
confirmText: this.hass.localize(
"ui.panel.config.system_dashboard.restart_homeassistant_short"
),
confirm: () => {
this.hass.callService("homeassistant", "restart").catch((reason) => {
showAlertDialog(this, {
title: this.hass.localize(
"ui.panel.config.system_dashboard.restart_error"
),
text: reason.message,
});
});
},
destructive: true,
});
}
private async _fetchBackupInfo(isHassioLoaded: boolean) {
const backups: BackupContent[] | HassioBackup[] = isHassioLoaded
? await fetchHassioBackups(this.hass)
@ -238,6 +213,10 @@ class HaConfigSystemNavigation extends LitElement {
this._externalAccess = this.hass.config.external_url !== null;
}
private async _showRestartDialog() {
showRestartDialog(this);
}
static get styles(): CSSResultGroup {
return [
haStyle,
@ -269,6 +248,14 @@ class HaConfigSystemNavigation extends LitElement {
padding-bottom: 0;
}
.restart-section {
display: flex;
align-items: center;
flex-direction: column;
justify-content: center;
margin-bottom: 24px;
}
@media all and (max-width: 600px) {
ha-card {
border-width: 1px 0;

View File

@ -1,6 +1,6 @@
import "@material/mwc-list/mwc-list";
import "@material/mwc-list/mwc-list-item";
import { mdiDotsVertical } from "@mdi/js";
import { mdiPower } from "@mdi/js";
import type { ChartOptions } from "chart.js";
import { UnsubscribeFunc } from "home-assistant-js-websocket";
import { css, html, LitElement, PropertyValues, TemplateResult } from "lit";
@ -13,9 +13,9 @@ import { blankBeforePercent } from "../../../common/translations/blank_before_pe
import "../../../components/buttons/ha-progress-button";
import "../../../components/chart/ha-chart-base";
import "../../../components/ha-alert";
import "../../../components/ha-button-menu";
import "../../../components/ha-card";
import "../../../components/ha-clickable-list-item";
import "../../../components/ha-icon-button";
import "../../../components/ha-icon-next";
import "../../../components/ha-settings-row";
import {
@ -27,31 +27,19 @@ import {
HardwareInfo,
SystemStatusStreamMessage,
} from "../../../data/hardware";
import {
extractApiErrorMessage,
ignoreSupervisorError,
} from "../../../data/hassio/common";
import {
fetchHassioHassOsInfo,
fetchHassioHostInfo,
HassioHassOSInfo,
HassioHostInfo,
rebootHost,
shutdownHost,
} from "../../../data/hassio/host";
import { scanUSBDevices } from "../../../data/usb";
import { showOptionsFlowDialog } from "../../../dialogs/config-flow/show-dialog-options-flow";
import {
showAlertDialog,
showConfirmationDialog,
} from "../../../dialogs/generic/show-dialog-box";
import { showRestartDialog } from "../../../dialogs/restart/show-dialog-restart";
import "../../../layouts/hass-subpage";
import { SubscribeMixin } from "../../../mixins/subscribe-mixin";
import { DEFAULT_PRIMARY_COLOR } from "../../../resources/ha-style";
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";
const DATASAMPLES = 60;
@ -75,8 +63,6 @@ class HaConfigHardware extends SubscribeMixin(LitElement) {
@state() private _OSData?: HassioHassOSInfo;
@state() private _hostData?: HassioHostInfo;
@state() private _hardwareInfo?: HardwareInfo;
@state() private _chartOptions?: ChartOptions;
@ -273,32 +259,16 @@ class HaConfigHardware extends SubscribeMixin(LitElement) {
.header=${this.hass.localize("ui.panel.config.hardware.caption")}
>
${isComponentLoaded(this.hass, "hassio")
? html`<ha-button-menu corner="BOTTOM_START" slot="toolbar-icon">
? html`
<ha-icon-button
.label=${this.hass.localize("ui.common.menu")}
.path=${mdiDotsVertical}
slot="trigger"
slot="toolbar-icon"
.path=${mdiPower}
.label=${this.hass.localize(
"ui.panel.config.hardware.restart_homeassistant"
)}
@click=${this._showRestartDialog}
></ha-icon-button>
<mwc-list-item @click=${this._openHardware}
>${this.hass.localize(
"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`
@ -367,6 +337,15 @@ class HaConfigHardware extends SubscribeMixin(LitElement) {
"ui.panel.config.hardware.configure"
)}
</mwc-button>
${isComponentLoaded(this.hass, "hassio")
? html`
<mwc-button @click=${this._openHardware}>
${this.hass.localize(
"ui.panel.config.hardware.available_hardware.title"
)}
</mwc-button>
`
: null}
</div>`
: ""}
</ha-card>
@ -475,10 +454,6 @@ class HaConfigHardware extends SubscribeMixin(LitElement) {
if (isHassioLoaded && !this._hardwareInfo?.hardware.length) {
this._OSData = await fetchHassioHassOsInfo(this.hass);
}
if (isHassioLoaded) {
this._hostData = await fetchHassioHostInfo(this.hass);
}
} catch (err: any) {
this._error = err.message || err;
}
@ -496,72 +471,8 @@ class HaConfigHardware extends SubscribeMixin(LitElement) {
showhardwareAvailableDialog(this);
}
private async _hostReboot(): Promise<void> {
const confirmed = await showConfirmationDialog(this, {
title: this.hass.localize("ui.panel.config.hardware.reboot_host_title"),
text: this.hass.localize("ui.panel.config.hardware.reboot_host_text"),
confirmText: this.hass.localize("ui.panel.config.hardware.reboot"),
dismissText: this.hass.localize("ui.common.cancel"),
destructive: true,
});
if (!confirmed) {
return;
}
showToast(this, {
message: this.hass.localize("ui.panel.config.hardware.rebooting_host"),
duration: 0,
});
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),
});
}
}
}
private async _hostShutdown(): Promise<void> {
const confirmed = await showConfirmationDialog(this, {
title: this.hass.localize("ui.panel.config.hardware.shutdown_host_title"),
text: this.hass.localize("ui.panel.config.hardware.shutdown_host_text"),
confirmText: this.hass.localize("ui.panel.config.hardware.shutdown"),
dismissText: this.hass.localize("ui.common.cancel"),
destructive: true,
});
if (!confirmed) {
return;
}
showToast(this, {
message: this.hass.localize(
"ui.panel.config.hardware.host_shutting_down"
),
duration: 0,
});
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),
});
}
}
private async _showRestartDialog() {
showRestartDialog(this);
}
static styles = [
@ -587,10 +498,6 @@ class HaConfigHardware extends SubscribeMixin(LitElement) {
flex-direction: column;
padding: 16px;
}
ha-button-menu {
color: var(--secondary-text-color);
--mdc-menu-min-width: 200px;
}
.primary-text {
font-size: 16px;
@ -620,6 +527,10 @@ class HaConfigHardware extends SubscribeMixin(LitElement) {
height: 48px;
padding: 8px 16px;
}
.card-actions {
display: flex;
justify-content: space-between;
}
`,
];
}

View File

@ -1008,6 +1008,41 @@
"aliases_description": "Aliases are alternative names used in voice assistants to refer to this entity."
}
},
"restart": {
"heading": "Restart Home Assistant",
"advanced_options": "Advanced options",
"reload": {
"title": "Quick reload",
"description": "Load new YAML configurations without a restart.",
"reloading": "Reloading configuration"
},
"restart": {
"title": "Restart Home Assistant",
"description": "Interrupts all running automations and scripts.",
"confirm_title": "Restart?",
"confirm_description": "This will interrupt all running automations and scripts.",
"confirm_action": "Restart Home Assistant",
"failed": "Failed to restart Home Assistant"
},
"reboot": {
"title": "Reboot system",
"description": "Restart the system running Home Assistant and all Add-ons.",
"confirm_title": "Reboot system?",
"confirm_description": "This will reboot the complete system which includes Home Assistant and all the Add-ons.",
"confirm_action": "Reboot",
"rebooting": "Rebooting system",
"failed": "Failed to reboot system"
},
"shutdown": {
"title": "Shutdown system",
"description": "Shutdown the system running Home Assistant and all Add-ons.",
"confirm_title": "Shutdown system?",
"confirm_description": "This will shutdown the complete system whitch includes Home Assistant and all Add-ons.",
"confirm_action": "Shutdown",
"shutting_down": "Shutting down system",
"failed": "Failed to shutdown system"
}
},
"aliases": {
"heading": "{name} aliases",
"remove_alias": "Remove alias {number}",
@ -1742,24 +1777,16 @@
"id": "ID",
"attributes": "Attributes"
},
"reboot_moved_title": "Reboot and shutdown moved",
"reboot_moved_description": "Reboot and shutdown actions has been moved to system page.",
"reboot_moved_link": "Go to system page.",
"processor": "Processor",
"memory": "Memory",
"rebooting_host": "Rebooting system",
"reboot": "Reboot",
"reboot_host": "Reboot system",
"reboot_host_title": "Reboot system?",
"reboot_host_text": "This will reboot the complete system which includes the Core and all Add-ons.",
"failed_to_reboot_host": "Failed to reboot system",
"host_shutting_down": "system shutting down",
"shutdown": "Shutdown",
"shutdown_host": "Shutdown system",
"shutdown_host_title": "Shutdown system?",
"shutdown_host_text": "This will shutdown the complete system which includes the Core and all Add-ons.",
"failed_to_shutdown_host": "Failed to shutdown system",
"board": "Board",
"documentation": "Documentation",
"configure": "Configure",
"documentation_description": "Find extra information about your device"
"documentation_description": "Find extra information about your device",
"restart_homeassistant": "[%key:ui::panel::config::system_dashboard::restart_homeassistant%]"
},
"info": {
"caption": "About",
@ -3734,10 +3761,7 @@
"integration_start_time": "Integration Startup Time"
},
"system_dashboard": {
"confirm_restart_text": "This will stop all your active dashboards, automations and scripts.",
"confirm_restart_title": "Restart Home Assistant?",
"restart_homeassistant_short": "Restart",
"restart_error": "Failed to restart Home Assistant"
"restart_homeassistant": "Restart Home Assistant"
}
},
"lovelace": {