diff --git a/hassio/src/components/supervisor-metric.ts b/hassio/src/components/supervisor-metric.ts new file mode 100644 index 0000000000..b0af0fd9a2 --- /dev/null +++ b/hassio/src/components/supervisor-metric.ts @@ -0,0 +1,87 @@ +import { + css, + CSSResult, + customElement, + html, + LitElement, + property, + TemplateResult, +} from "lit-element"; +import { classMap } from "lit-html/directives/class-map"; +import "../../../src/components/ha-bar"; +import "../../../src/components/ha-settings-row"; +import { roundWithOneDecimal } from "../../../src/util/calculate"; + +@customElement("supervisor-metric") +class SupervisorMetric extends LitElement { + @property({ type: Number }) public value!: number; + + @property({ type: String }) public description!: string; + + @property({ type: String }) public tooltip?: string; + + protected render(): TemplateResult { + const roundedValue = roundWithOneDecimal(this.value); + return html` + + ${this.description} + +
+ + ${roundedValue}% + + 50, + "target-critical": roundedValue > 85, + })}" + .value=${this.value} + > +
+
`; + } + + static get styles(): CSSResult { + return css` + ha-settings-row { + padding: 0; + height: 54px; + width: 100%; + } + ha-settings-row > div[slot="description"] { + white-space: normal; + color: var(--secondary-text-color); + display: flex; + justify-content: space-between; + } + ha-bar { + --ha-bar-primary-color: var( + --hassio-bar-ok-color, + var(--success-color) + ); + } + .target-warning { + --ha-bar-primary-color: var( + --hassio-bar-warning-color, + var(--warning-color) + ); + } + .target-critical { + --ha-bar-primary-color: var( + --hassio-bar-critical-color, + var(--error-color) + ); + } + .value { + width: 42px; + padding-right: 4px; + } + `; + } +} + +declare global { + interface HTMLElementTagNameMap { + "supervisor-metric": SupervisorMetric; + } +} diff --git a/hassio/src/system/hassio-core-info.ts b/hassio/src/system/hassio-core-info.ts new file mode 100644 index 0000000000..96c3aad65f --- /dev/null +++ b/hassio/src/system/hassio-core-info.ts @@ -0,0 +1,246 @@ +import "@material/mwc-button"; +import "@material/mwc-list/mwc-list-item"; +import { + css, + CSSResult, + customElement, + html, + internalProperty, + LitElement, + property, + TemplateResult, +} from "lit-element"; +import "../../../src/components/buttons/ha-progress-button"; +import "../../../src/components/ha-button-menu"; +import "../../../src/components/ha-card"; +import "../../../src/components/ha-settings-row"; +import { + extractApiErrorMessage, + fetchHassioStats, + HassioStats, +} from "../../../src/data/hassio/common"; +import { restartCore, updateCore } from "../../../src/data/supervisor/core"; +import { Supervisor } from "../../../src/data/supervisor/supervisor"; +import { + showAlertDialog, + showConfirmationDialog, +} from "../../../src/dialogs/generic/show-dialog-box"; +import { haStyle } from "../../../src/resources/styles"; +import { HomeAssistant } from "../../../src/types"; +import { bytesToString } from "../../../src/util/bytes-to-string"; +import "../components/supervisor-metric"; +import { hassioStyle } from "../resources/hassio-style"; + +@customElement("hassio-core-info") +class HassioCoreInfo extends LitElement { + @property({ attribute: false }) public hass!: HomeAssistant; + + @property({ attribute: false }) public supervisor!: Supervisor; + + @internalProperty() private _metrics?: HassioStats; + + protected render(): TemplateResult | void { + const metrics = [ + { + description: "Core CPU Usage", + value: this._metrics?.cpu_percent, + }, + { + description: "Core RAM Usage", + value: this._metrics?.memory_percent, + tooltip: `${bytesToString(this._metrics?.memory_usage)}/${bytesToString( + this._metrics?.memory_limit + )}`, + }, + ]; + + return html` + +
+
+ + + Version + + + core-${this.supervisor.core.version} + + + + + Newest Version + + + core-${this.supervisor.core.version_latest} + + ${this.supervisor.core.update_available + ? html` + + Update + + ` + : ""} + +
+
+ ${metrics.map( + (metric) => + html` + + ` + )} +
+
+
+ + Restart Core + +
+
+ `; + } + + protected firstUpdated(): void { + this._loadData(); + } + + private async _loadData(): Promise { + this._metrics = await fetchHassioStats(this.hass, "core"); + } + + private async _coreRestart(ev: CustomEvent): Promise { + const button = ev.currentTarget as any; + button.progress = true; + + const confirmed = await showConfirmationDialog(this, { + title: "Restart Home Assistant Core", + text: "Are you sure you want to restart Home Assistant Core", + confirmText: "restart", + dismissText: "cancel", + }); + + if (!confirmed) { + button.progress = false; + return; + } + + try { + await restartCore(this.hass); + } catch (err) { + showAlertDialog(this, { + title: "Failed to restart Home Assistant Core", + text: extractApiErrorMessage(err), + }); + } finally { + button.progress = false; + } + } + + private async _coreUpdate(ev: CustomEvent): Promise { + const button = ev.currentTarget as any; + button.progress = true; + + const confirmed = await showConfirmationDialog(this, { + title: "Update Home Assistant Core", + text: `Are you sure you want to update Home Assistant Core to version ${this.supervisor.core.version_latest}?`, + confirmText: "update", + dismissText: "cancel", + }); + + if (!confirmed) { + button.progress = false; + return; + } + + try { + await updateCore(this.hass); + } catch (err) { + showAlertDialog(this, { + title: "Failed to update Home Assistant Core", + text: extractApiErrorMessage(err), + }); + } finally { + button.progress = false; + } + } + + static get styles(): CSSResult[] { + return [ + haStyle, + hassioStyle, + css` + ha-card { + height: 100%; + justify-content: space-between; + flex-direction: column; + display: flex; + } + .card-actions { + height: 48px; + border-top: none; + display: flex; + justify-content: flex-end; + align-items: center; + } + .card-content { + display: flex; + flex-direction: column; + height: calc(100% - 124px); + justify-content: space-between; + } + ha-settings-row { + padding: 0; + height: 54px; + width: 100%; + } + ha-settings-row[three-line] { + height: 74px; + } + ha-settings-row > span[slot="description"] { + white-space: normal; + color: var(--secondary-text-color); + } + + .warning { + --mdc-theme-primary: var(--error-color); + } + + ha-button-menu { + color: var(--secondary-text-color); + --mdc-menu-min-width: 200px; + } + @media (min-width: 563px) { + paper-listbox { + max-height: 150px; + overflow: auto; + } + } + paper-item { + cursor: pointer; + min-height: 35px; + } + mwc-list-item ha-svg-icon { + color: var(--secondary-text-color); + } + `, + ]; + } +} + +declare global { + interface HTMLElementTagNameMap { + "hassio-core-info": HassioCoreInfo; + } +} diff --git a/hassio/src/system/hassio-host-info.ts b/hassio/src/system/hassio-host-info.ts index 42b066c214..773fcc8bab 100644 --- a/hassio/src/system/hassio-host-info.ts +++ b/hassio/src/system/hassio-host-info.ts @@ -43,6 +43,11 @@ import { } from "../../../src/dialogs/generic/show-dialog-box"; import { haStyle } from "../../../src/resources/styles"; import { HomeAssistant } from "../../../src/types"; +import { + getValueInPercentage, + roundWithOneDecimal, +} from "../../../src/util/calculate"; +import "../components/supervisor-metric"; import { showHassioMarkdownDialog } from "../dialogs/markdown/show-dialog-hassio-markdown"; import { showNetworkDialog } from "../dialogs/network/show-dialog-network"; import { hassioStyle } from "../resources/hassio-style"; @@ -57,80 +62,105 @@ class HassioHostInfo extends LitElement { const primaryIpAddress = this.supervisor.host.features.includes("network") ? this._primaryIpAddress(this.supervisor.network!) : ""; - return html` - -
- ${this.supervisor.host.features.includes("hostname") - ? html` - - Hostname - - - ${this.supervisor.host.hostname} - - - - ` - : ""} - ${this.supervisor.host.features.includes("network") - ? html` - - IP Address - - - ${primaryIpAddress} - - - - ` - : ""} - - - Operating System - - - ${this.supervisor.host.operating_system} - - ${this.supervisor.os.update_available - ? html` - +
+
+ ${this.supervisor.host.features.includes("hostname") + ? html` + + Hostname + + + ${this.supervisor.host.hostname} + + - Update - - ` + + ` : ""} - - ${!this.supervisor.host.features.includes("hassos") - ? html` - - Docker version - - - ${this.supervisor.info.docker} - - ` - : ""} - ${this.supervisor.host.deployment - ? html` - - Deployment - - - ${this.supervisor.host.deployment} - - ` - : ""} + ${this.supervisor.host.features.includes("network") + ? html` + + IP Address + + + ${primaryIpAddress} + + + + ` + : ""} + + + + Operating System + + + ${this.supervisor.host.operating_system} + + ${this.supervisor.os.update_available + ? html` + + Update + + ` + : ""} + + ${!this.supervisor.host.features.includes("hassos") + ? html` + + Docker version + + + ${this.supervisor.info.docker} + + ` + : ""} + ${this.supervisor.host.deployment + ? html` + + Deployment + + + ${this.supervisor.host.deployment} + + ` + : ""} +
+
+ ${metrics.map( + (metric) => + html` + + ` + )} +
${this.supervisor.host.features.includes("reboot") @@ -140,7 +170,7 @@ class HassioHostInfo extends LitElement { class="warning" @click=${this._hostReboot} > - Reboot + Reboot Host ` : ""} @@ -151,7 +181,7 @@ class HassioHostInfo extends LitElement { class="warning" @click=${this._hostShutdown} > - Shutdown + Shutdown Host ` : ""} @@ -183,6 +213,10 @@ class HassioHostInfo extends LitElement { this._loadData(); } + private _getUsedSpace = memoizeOne((used: number, total: number) => + roundWithOneDecimal(getValueInPercentage(used, 0, total)) + ); + private _primaryIpAddress = memoizeOne((network_info: NetworkInfo) => { if (!network_info || !network_info.interfaces) { return ""; @@ -369,6 +403,12 @@ class HassioHostInfo extends LitElement { justify-content: space-between; align-items: center; } + .card-content { + display: flex; + flex-direction: column; + height: calc(100% - 124px); + justify-content: space-between; + } ha-settings-row { padding: 0; height: 54px; diff --git a/hassio/src/system/hassio-supervisor-info.ts b/hassio/src/system/hassio-supervisor-info.ts index ce966a86a8..b29252d2ad 100644 --- a/hassio/src/system/hassio-supervisor-info.ts +++ b/hassio/src/system/hassio-supervisor-info.ts @@ -3,6 +3,7 @@ import { CSSResult, customElement, html, + internalProperty, LitElement, property, TemplateResult, @@ -12,7 +13,11 @@ import "../../../src/components/buttons/ha-progress-button"; import "../../../src/components/ha-card"; import "../../../src/components/ha-settings-row"; import "../../../src/components/ha-switch"; -import { extractApiErrorMessage } from "../../../src/data/hassio/common"; +import { + extractApiErrorMessage, + fetchHassioStats, + HassioStats, +} from "../../../src/data/hassio/common"; import { fetchHassioSupervisorInfo, reloadSupervisor, @@ -28,7 +33,9 @@ import { } from "../../../src/dialogs/generic/show-dialog-box"; import { haStyle } from "../../../src/resources/styles"; import { HomeAssistant } from "../../../src/types"; +import { bytesToString } from "../../../src/util/bytes-to-string"; import { documentationUrl } from "../../../src/util/documentation-url"; +import "../components/supervisor-metric"; import { hassioStyle } from "../resources/hassio-style"; const UNSUPPORTED_REASON = { @@ -87,127 +94,164 @@ class HassioSupervisorInfo extends LitElement { @property({ attribute: false }) public supervisor!: Supervisor; + @internalProperty() private _metrics?: HassioStats; + protected render(): TemplateResult | void { + const metrics = [ + { + description: "Supervisor CPU Usage", + value: this._metrics?.cpu_percent, + }, + { + description: "Supervisor RAM Usage", + value: this._metrics?.memory_percent, + tooltip: `${bytesToString(this._metrics?.memory_usage)}/${bytesToString( + this._metrics?.memory_limit + )}`, + }, + ]; return html`
- - - Version - - - supervisor-${this.supervisor.supervisor.version} - - - - - Newest Version - - - supervisor-${this.supervisor.supervisor.version_latest} - - ${this.supervisor.supervisor.update_available - ? html` - - Update - - ` - : ""} - - - - Channel - - - ${this.supervisor.supervisor.channel} - - ${this.supervisor.supervisor.channel === "beta" - ? html` - - Leave beta channel - - ` - : this.supervisor.supervisor.channel === "stable" - ? html` - - Join beta channel - - ` - : ""} - +
+ + + Version + + + supervisor-${this.supervisor.supervisor.version} + + + + + Newest Version + + + supervisor-${this.supervisor.supervisor.version_latest} + + ${this.supervisor.supervisor.update_available + ? html` + + Update + + ` + : ""} + + + + Channel + + + ${this.supervisor.supervisor.channel} + + ${this.supervisor.supervisor.channel === "beta" + ? html` + + Leave beta channel + + ` + : this.supervisor.supervisor.channel === "stable" + ? html` + + Join beta channel + + ` + : ""} + - ${this.supervisor.supervisor.supported - ? html` - - Share Diagnostics - -
- Share crash reports and diagnostic information. + ${this.supervisor.supervisor.supported + ? html` + + Share Diagnostics + +
+ Share crash reports and diagnostic information. + +
+ +
` + : html`
+ You are running an unsupported installation. -
- - ` - : html`
- You are running an unsupported installation. - -
`} - ${!this.supervisor.supervisor.healthy - ? html`
- Your installation is running in an unhealthy state. - -
` - : ""} +
`} + ${!this.supervisor.supervisor.healthy + ? html`
+ Your installation is running in an unhealthy state. + +
` + : ""} +
+
+ ${metrics.map( + (metric) => + html` + + ` + )} +
- Reload + Reload Supervisor - Restart + Restart Supervisor
`; } + protected firstUpdated(): void { + this._loadData(); + } + + private async _loadData(): Promise { + this._metrics = await fetchHassioStats(this.hass, "supervisor"); + } + private async _toggleBeta(ev: CustomEvent): Promise { const button = ev.currentTarget as any; button.progress = true; @@ -282,6 +326,18 @@ class HassioSupervisorInfo extends LitElement { const button = ev.currentTarget as any; button.progress = true; + const confirmed = await showConfirmationDialog(this, { + title: "Restart the Supervisor", + text: "Are you sure you want to restart the Supervisor", + confirmText: "restart", + dismissText: "cancel", + }); + + if (!confirmed) { + button.progress = false; + return; + } + try { await restartSupervisor(this.hass); } catch (err) { @@ -426,6 +482,15 @@ class HassioSupervisorInfo extends LitElement { justify-content: space-between; align-items: center; } + .card-content { + display: flex; + flex-direction: column; + height: calc(100% - 124px); + justify-content: space-between; + } + .metrics-block { + margin-top: 16px; + } button.link { color: var(--primary-color); } diff --git a/hassio/src/system/hassio-system-metrics.ts b/hassio/src/system/hassio-system-metrics.ts deleted file mode 100644 index 8cd4450e90..0000000000 --- a/hassio/src/system/hassio-system-metrics.ts +++ /dev/null @@ -1,185 +0,0 @@ -import "@material/mwc-button"; -import "@material/mwc-list/mwc-list-item"; -import { - css, - CSSResult, - customElement, - html, - internalProperty, - LitElement, - property, - TemplateResult, -} from "lit-element"; -import { classMap } from "lit-html/directives/class-map"; -import memoizeOne from "memoize-one"; -import "../../../src/components/buttons/ha-progress-button"; -import "../../../src/components/ha-bar"; -import "../../../src/components/ha-button-menu"; -import "../../../src/components/ha-card"; -import "../../../src/components/ha-settings-row"; -import { fetchHassioStats, HassioStats } from "../../../src/data/hassio/common"; -import { HassioHostInfo } from "../../../src/data/hassio/host"; -import { Supervisor } from "../../../src/data/supervisor/supervisor"; -import { haStyle } from "../../../src/resources/styles"; -import { HomeAssistant } from "../../../src/types"; -import { bytesToString } from "../../../src/util/bytes-to-string"; -import { - getValueInPercentage, - roundWithOneDecimal, -} from "../../../src/util/calculate"; -import { hassioStyle } from "../resources/hassio-style"; - -@customElement("hassio-system-metrics") -class HassioSystemMetrics extends LitElement { - @property({ attribute: false }) public hass!: HomeAssistant; - - @property({ attribute: false }) public supervisor!: Supervisor; - - @internalProperty() private _supervisorMetrics?: HassioStats; - - @internalProperty() private _coreMetrics?: HassioStats; - - protected render(): TemplateResult | void { - const metrics = [ - { - description: "Core CPU Usage", - value: this._coreMetrics?.cpu_percent, - }, - { - description: "Core RAM Usage", - value: this._coreMetrics?.memory_percent, - tooltip: `${bytesToString( - this._coreMetrics?.memory_usage - )}/${bytesToString(this._coreMetrics?.memory_limit)}`, - }, - { - description: "Supervisor CPU Usage", - value: this._supervisorMetrics?.cpu_percent, - }, - { - description: "Supervisor RAM Usage", - value: this._supervisorMetrics?.memory_percent, - tooltip: `${bytesToString( - this._supervisorMetrics?.memory_usage - )}/${bytesToString(this._supervisorMetrics?.memory_limit)}`, - }, - { - description: "Used Space", - value: this._getUsedSpace(this.supervisor.host), - tooltip: `${this.supervisor.host.disk_used} GB/${this.supervisor.host.disk_total} GB`, - }, - ]; - - return html` - -
- ${metrics.map((metric) => - this._renderMetric( - metric.description, - metric.value ?? 0, - metric.tooltip - ) - )} -
-
- `; - } - - protected firstUpdated(): void { - this._loadData(); - } - - private _renderMetric( - description: string, - value: number, - tooltip?: string - ): TemplateResult { - const roundedValue = roundWithOneDecimal(value); - return html` - - ${description} - -
- - ${roundedValue}% - - 50, - "target-critical": roundedValue > 85, - })}" - .value=${value} - > -
-
`; - } - - private _getUsedSpace = memoizeOne((hostInfo: HassioHostInfo) => - roundWithOneDecimal( - getValueInPercentage(hostInfo.disk_used, 0, hostInfo.disk_total) - ) - ); - - private async _loadData(): Promise { - const [supervisor, core] = await Promise.all([ - fetchHassioStats(this.hass, "supervisor"), - fetchHassioStats(this.hass, "core"), - ]); - this._supervisorMetrics = supervisor; - this._coreMetrics = core; - } - - static get styles(): CSSResult[] { - return [ - haStyle, - hassioStyle, - css` - ha-card { - height: 100%; - justify-content: space-between; - flex-direction: column; - display: flex; - } - ha-settings-row { - padding: 0; - height: 54px; - width: 100%; - } - ha-settings-row > div[slot="description"] { - white-space: normal; - color: var(--secondary-text-color); - display: flex; - justify-content: space-between; - } - ha-bar { - --ha-bar-primary-color: var( - --hassio-bar-ok-color, - var(--success-color) - ); - } - .target-warning { - --ha-bar-primary-color: var( - --hassio-bar-warning-color, - var(--warning-color) - ); - } - .target-critical { - --ha-bar-primary-color: var( - --hassio-bar-critical-color, - var(--error-color) - ); - } - .value { - width: 42px; - padding-right: 4px; - } - `, - ]; - } -} - -declare global { - interface HTMLElementTagNameMap { - "hassio-system-metrics": HassioSystemMetrics; - } -} diff --git a/hassio/src/system/hassio-system.ts b/hassio/src/system/hassio-system.ts index 9c44de311b..fa9999485e 100644 --- a/hassio/src/system/hassio-system.ts +++ b/hassio/src/system/hassio-system.ts @@ -13,10 +13,10 @@ import { haStyle } from "../../../src/resources/styles"; import { HomeAssistant, Route } from "../../../src/types"; import { supervisorTabs } from "../hassio-tabs"; import { hassioStyle } from "../resources/hassio-style"; +import "./hassio-core-info"; import "./hassio-host-info"; import "./hassio-supervisor-info"; import "./hassio-supervisor-log"; -import "./hassio-system-metrics"; @customElement("hassio-system") class HassioSystem extends LitElement { @@ -41,6 +41,10 @@ class HassioSystem extends LitElement { System
+ -
diff --git a/src/data/supervisor/core.ts b/src/data/supervisor/core.ts new file mode 100644 index 0000000000..611fbabd36 --- /dev/null +++ b/src/data/supervisor/core.ts @@ -0,0 +1,10 @@ +import { HomeAssistant } from "../../types"; +import { HassioResponse } from "../hassio/common"; + +export const restartCore = async (hass: HomeAssistant) => { + await hass.callService("homeassistant", "restart"); +}; + +export const updateCore = async (hass: HomeAssistant) => { + await hass.callApi>("POST", `hassio/core/update`); +};