mirror of
https://github.com/home-assistant/frontend.git
synced 2026-04-22 10:32:57 +00:00
Compare commits
1 Commits
20220727.0
...
fix-breaki
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
43dcd75be6 |
@@ -26,7 +26,7 @@ import {
|
||||
import {
|
||||
UNHEALTHY_REASON_URL,
|
||||
UNSUPPORTED_REASON_URL,
|
||||
} from "../../../src/panels/config/repairs/dialog-system-information";
|
||||
} from "../../../src/panels/config/system-health/ha-config-system-health";
|
||||
import { haStyle } from "../../../src/resources/styles";
|
||||
import { HomeAssistant } from "../../../src/types";
|
||||
import { bytesToString } from "../../../src/util/bytes-to-string";
|
||||
|
||||
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "home-assistant-frontend"
|
||||
version = "20220727.0"
|
||||
version = "20220707.0"
|
||||
license = {text = "Apache-2.0"}
|
||||
description = "The Home Assistant frontend"
|
||||
readme = "README.md"
|
||||
|
||||
@@ -37,6 +37,7 @@ import {
|
||||
mdiLightningBolt,
|
||||
mdiMailbox,
|
||||
mdiMapMarkerRadius,
|
||||
mdiMicrophone,
|
||||
mdiMolecule,
|
||||
mdiMoleculeCo,
|
||||
mdiMoleculeCo2,
|
||||
@@ -47,7 +48,6 @@ import {
|
||||
mdiRobotVacuum,
|
||||
mdiScriptText,
|
||||
mdiSineWave,
|
||||
mdiMicrophoneMessage,
|
||||
mdiThermometer,
|
||||
mdiThermostat,
|
||||
mdiTimerOutline,
|
||||
@@ -74,7 +74,7 @@ export const FIXED_DOMAIN_ICONS = {
|
||||
camera: mdiVideo,
|
||||
climate: mdiThermostat,
|
||||
configurator: mdiCog,
|
||||
conversation: mdiMicrophoneMessage,
|
||||
conversation: mdiMicrophone,
|
||||
counter: mdiCounter,
|
||||
demo: mdiHomeAssistant,
|
||||
fan: mdiFan,
|
||||
|
||||
@@ -1,88 +0,0 @@
|
||||
import { html } from "lit";
|
||||
import { getConfigEntries } from "../../data/config_entries";
|
||||
import { showConfirmationDialog } from "../../dialogs/generic/show-dialog-box";
|
||||
import { showZWaveJSAddNodeDialog } from "../../panels/config/integrations/integration-panels/zwave_js/show-dialog-zwave_js-add-node";
|
||||
import type { HomeAssistant } from "../../types";
|
||||
import { documentationUrl } from "../../util/documentation-url";
|
||||
import { isComponentLoaded } from "../config/is_component_loaded";
|
||||
import { fireEvent } from "../dom/fire_event";
|
||||
import { navigate } from "../navigate";
|
||||
|
||||
export const protocolIntegrationPicked = async (
|
||||
element: HTMLElement,
|
||||
hass: HomeAssistant,
|
||||
slug: string
|
||||
) => {
|
||||
if (slug === "zwave_js") {
|
||||
const entries = await getConfigEntries(hass, {
|
||||
domain: "zwave_js",
|
||||
});
|
||||
|
||||
if (!entries.length) {
|
||||
// If the component isn't loaded, ask them to load the integration first
|
||||
showConfirmationDialog(element, {
|
||||
text: hass.localize(
|
||||
"ui.panel.config.integrations.config_flow.missing_zwave_zigbee",
|
||||
{
|
||||
integration: "Z-Wave",
|
||||
supported_hardware_link: html`<a
|
||||
href=${documentationUrl(hass, "/docs/z-wave/controllers")}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>${hass.localize(
|
||||
"ui.panel.config.integrations.config_flow.supported_hardware"
|
||||
)}</a
|
||||
>`,
|
||||
}
|
||||
),
|
||||
confirmText: hass.localize(
|
||||
"ui.panel.config.integrations.config_flow.proceed"
|
||||
),
|
||||
confirm: () => {
|
||||
fireEvent(element, "handler-picked", {
|
||||
handler: "zwave_js",
|
||||
});
|
||||
},
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
showZWaveJSAddNodeDialog(element, {
|
||||
entry_id: entries[0].entry_id,
|
||||
});
|
||||
} else if (slug === "zha") {
|
||||
// If the component isn't loaded, ask them to load the integration first
|
||||
if (!isComponentLoaded(hass, "zha")) {
|
||||
showConfirmationDialog(element, {
|
||||
text: hass.localize(
|
||||
"ui.panel.config.integrations.config_flow.missing_zwave_zigbee",
|
||||
{
|
||||
integration: "Zigbee",
|
||||
supported_hardware_link: html`<a
|
||||
href=${documentationUrl(
|
||||
hass,
|
||||
"/integrations/zha/#known-working-zigbee-radio-modules"
|
||||
)}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>${hass.localize(
|
||||
"ui.panel.config.integrations.config_flow.supported_hardware"
|
||||
)}</a
|
||||
>`,
|
||||
}
|
||||
),
|
||||
confirmText: hass.localize(
|
||||
"ui.panel.config.integrations.config_flow.proceed"
|
||||
),
|
||||
confirm: () => {
|
||||
fireEvent(element, "handler-picked", {
|
||||
handler: "zha",
|
||||
});
|
||||
},
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
navigate("/config/zha/add");
|
||||
}
|
||||
};
|
||||
@@ -15,6 +15,8 @@ type LocalizeKeyExceptions =
|
||||
| `state.${string}`
|
||||
| `state_attributes.${string}`
|
||||
| `state_badge.${string}`
|
||||
| `groups.${string}`
|
||||
| `config_entry.${string}`
|
||||
| `ui.${string}`
|
||||
| `${keyof TranslationDict["supervisor"]}.${string}`
|
||||
| `component.${string}`;
|
||||
|
||||
@@ -52,11 +52,6 @@ export class HaSelect extends SelectBase {
|
||||
inset-inline-end: initial;
|
||||
direction: var(--direction);
|
||||
}
|
||||
.mdc-select--filled.mdc-select--with-leading-icon .mdc-floating-label {
|
||||
inset-inline-start: 48px;
|
||||
inset-inline-end: initial;
|
||||
direction: var(--direction);
|
||||
}
|
||||
.mdc-select .mdc-select__anchor {
|
||||
padding-inline-start: 12px;
|
||||
padding-inline-end: 0px;
|
||||
|
||||
@@ -21,7 +21,6 @@ import "@polymer/paper-item/paper-icon-item";
|
||||
import type { PaperIconItemElement } from "@polymer/paper-item/paper-icon-item";
|
||||
import "@polymer/paper-item/paper-item";
|
||||
import "@polymer/paper-listbox/paper-listbox";
|
||||
import { UnsubscribeFunc } from "home-assistant-js-websocket";
|
||||
import {
|
||||
css,
|
||||
CSSResult,
|
||||
@@ -45,9 +44,7 @@ import {
|
||||
PersistentNotification,
|
||||
subscribeNotifications,
|
||||
} from "../data/persistent_notification";
|
||||
import { subscribeRepairsIssueRegistry } from "../data/repairs";
|
||||
import { updateCanInstall, UpdateEntity } from "../data/update";
|
||||
import { SubscribeMixin } from "../mixins/subscribe-mixin";
|
||||
import { actionHandler } from "../panels/lovelace/common/directives/action-handler-directive";
|
||||
import { haStyleScrollbar } from "../resources/styles";
|
||||
import type { HomeAssistant, PanelInfo, Route } from "../types";
|
||||
@@ -180,7 +177,7 @@ const computePanels = memoizeOne(
|
||||
let Sortable;
|
||||
|
||||
@customElement("ha-sidebar")
|
||||
class HaSidebar extends SubscribeMixin(LitElement) {
|
||||
class HaSidebar extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property({ type: Boolean, reflect: true }) public narrow!: boolean;
|
||||
@@ -195,8 +192,6 @@ class HaSidebar extends SubscribeMixin(LitElement) {
|
||||
|
||||
@state() private _updatesCount = 0;
|
||||
|
||||
@state() private _issuesCount = 0;
|
||||
|
||||
@state() private _renderEmptySortable = false;
|
||||
|
||||
private _mouseLeaveTimeout?: number;
|
||||
@@ -219,16 +214,6 @@ class HaSidebar extends SubscribeMixin(LitElement) {
|
||||
|
||||
private _sortable?;
|
||||
|
||||
public hassSubscribe(): UnsubscribeFunc[] {
|
||||
return [
|
||||
subscribeRepairsIssueRegistry(this.hass.connection!, (repairs) => {
|
||||
this._issuesCount = repairs.issues.filter(
|
||||
(issue) => !issue.ignored
|
||||
).length;
|
||||
}),
|
||||
];
|
||||
}
|
||||
|
||||
protected render() {
|
||||
if (!this.hass) {
|
||||
return html``;
|
||||
@@ -253,7 +238,6 @@ class HaSidebar extends SubscribeMixin(LitElement) {
|
||||
changedProps.has("alwaysExpand") ||
|
||||
changedProps.has("_externalConfig") ||
|
||||
changedProps.has("_updatesCount") ||
|
||||
changedProps.has("_issuesCount") ||
|
||||
changedProps.has("_notifications") ||
|
||||
changedProps.has("editMode") ||
|
||||
changedProps.has("_renderEmptySortable") ||
|
||||
@@ -516,7 +500,7 @@ class HaSidebar extends SubscribeMixin(LitElement) {
|
||||
}
|
||||
|
||||
private _renderConfiguration(title: string | null) {
|
||||
return html`<a
|
||||
return html` <a
|
||||
class="configuration-container"
|
||||
role="option"
|
||||
href="/config"
|
||||
@@ -527,20 +511,17 @@ class HaSidebar extends SubscribeMixin(LitElement) {
|
||||
>
|
||||
<paper-icon-item class="configuration" role="option">
|
||||
<ha-svg-icon slot="item-icon" .path=${mdiCog}></ha-svg-icon>
|
||||
${!this.alwaysExpand &&
|
||||
(this._updatesCount > 0 || this._issuesCount > 0)
|
||||
${!this.alwaysExpand && this._updatesCount > 0
|
||||
? html`
|
||||
<span class="configuration-badge" slot="item-icon">
|
||||
${this._updatesCount + this._issuesCount}
|
||||
${this._updatesCount}
|
||||
</span>
|
||||
`
|
||||
: ""}
|
||||
<span class="item-text">${title}</span>
|
||||
${this.alwaysExpand && (this._updatesCount > 0 || this._issuesCount > 0)
|
||||
${this.alwaysExpand && this._updatesCount > 0
|
||||
? html`
|
||||
<span class="configuration-badge"
|
||||
>${this._updatesCount + this._issuesCount}</span
|
||||
>
|
||||
<span class="configuration-badge">${this._updatesCount}</span>
|
||||
`
|
||||
: ""}
|
||||
</paper-icon-item>
|
||||
|
||||
@@ -83,7 +83,7 @@ export class HaTextField extends TextFieldBase {
|
||||
}
|
||||
|
||||
input {
|
||||
text-align: var(--text-field-text-align, start);
|
||||
text-align: var(--text-field-text-align);
|
||||
}
|
||||
|
||||
/* Chrome, Safari, Edge, Opera */
|
||||
|
||||
@@ -13,8 +13,8 @@ export interface EntityRegistryEntry {
|
||||
config_entry_id: string | null;
|
||||
device_id: string | null;
|
||||
area_id: string | null;
|
||||
disabled_by: "user" | "device" | "integration" | "config_entry" | null;
|
||||
hidden_by: Exclude<EntityRegistryEntry["disabled_by"], "config_entry">;
|
||||
disabled_by: string | null;
|
||||
hidden_by: string | null;
|
||||
entity_category: "config" | "diagnostic" | null;
|
||||
has_entity_name: boolean;
|
||||
original_name?: string;
|
||||
|
||||
@@ -1,9 +1,5 @@
|
||||
import type { Connection } from "home-assistant-js-websocket";
|
||||
import { createCollection } from "home-assistant-js-websocket";
|
||||
import type { Store } from "home-assistant-js-websocket/dist/store";
|
||||
import { debounce } from "../common/util/debounce";
|
||||
import type { HomeAssistant } from "../types";
|
||||
import type { DataEntryFlowStep } from "./data_entry_flow";
|
||||
import { DataEntryFlowStep } from "./data_entry_flow";
|
||||
|
||||
export interface RepairsIssue {
|
||||
domain: string;
|
||||
@@ -26,21 +22,19 @@ export const severitySort = {
|
||||
warning: 3,
|
||||
};
|
||||
|
||||
export const fetchRepairsIssues = (conn: Connection) =>
|
||||
conn.sendMessagePromise<{ issues: RepairsIssue[] }>({
|
||||
export const fetchRepairsIssues = async (hass: HomeAssistant) =>
|
||||
hass.callWS<{ issues: RepairsIssue[] }>({
|
||||
type: "repairs/list_issues",
|
||||
});
|
||||
|
||||
export const ignoreRepairsIssue = async (
|
||||
export const dismissRepairsIssue = async (
|
||||
hass: HomeAssistant,
|
||||
issue: RepairsIssue,
|
||||
ignore: boolean
|
||||
issue: RepairsIssue
|
||||
) =>
|
||||
hass.callWS<string>({
|
||||
type: "repairs/ignore_issue",
|
||||
type: "repairs/dismiss_issue",
|
||||
issue_id: issue.issue_id,
|
||||
domain: issue.domain,
|
||||
ignore,
|
||||
});
|
||||
|
||||
export const createRepairsFlow = (
|
||||
@@ -65,31 +59,3 @@ export const handleRepairsFlowStep = (
|
||||
|
||||
export const deleteRepairsFlow = (hass: HomeAssistant, flowId: string) =>
|
||||
hass.callApi("DELETE", `repairs/issues/fix/${flowId}`);
|
||||
|
||||
const subscribeRepairsIssueUpdates = (
|
||||
conn: Connection,
|
||||
store: Store<{ issues: RepairsIssue[] }>
|
||||
) =>
|
||||
conn.subscribeEvents(
|
||||
debounce(
|
||||
() =>
|
||||
fetchRepairsIssues(conn).then((repairs) =>
|
||||
store.setState(repairs, true)
|
||||
),
|
||||
500,
|
||||
true
|
||||
),
|
||||
"repairs_issue_registry_updated"
|
||||
);
|
||||
|
||||
export const subscribeRepairsIssueRegistry = (
|
||||
conn: Connection,
|
||||
onChange: (repairs: { issues: RepairsIssue[] }) => void
|
||||
) =>
|
||||
createCollection<{ issues: RepairsIssue[] }>(
|
||||
"_repairsIssueRegistry",
|
||||
fetchRepairsIssues,
|
||||
subscribeRepairsIssueUpdates,
|
||||
conn,
|
||||
onChange
|
||||
);
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { SupportedBrandObj } from "../dialogs/config-flow/step-flow-pick-handler";
|
||||
import type { HomeAssistant } from "../types";
|
||||
|
||||
export type SupportedBrandHandler = Record<string, string>;
|
||||
@@ -7,21 +6,3 @@ export const getSupportedBrands = (hass: HomeAssistant) =>
|
||||
hass.callWS<Record<string, SupportedBrandHandler>>({
|
||||
type: "supported_brands",
|
||||
});
|
||||
|
||||
export const getSupportedBrandsLookup = (
|
||||
supportedBrands: Record<string, SupportedBrandHandler>
|
||||
): Record<string, Partial<SupportedBrandObj>> => {
|
||||
const supportedBrandsIntegrations: Record<
|
||||
string,
|
||||
Partial<SupportedBrandObj>
|
||||
> = {};
|
||||
for (const [d, domainBrands] of Object.entries(supportedBrands)) {
|
||||
for (const [slug, name] of Object.entries(domainBrands)) {
|
||||
supportedBrandsIntegrations[slug] = {
|
||||
name,
|
||||
supported_flows: [d],
|
||||
};
|
||||
}
|
||||
}
|
||||
return supportedBrandsIntegrations;
|
||||
};
|
||||
|
||||
@@ -4,7 +4,7 @@ import {
|
||||
mdiHomeCircleOutline,
|
||||
mdiCancel,
|
||||
} from "@mdi/js";
|
||||
import { HomeAssistant, TranslationDict } from "../types";
|
||||
import { HomeAssistant } from "../types";
|
||||
import { Credential } from "./auth";
|
||||
|
||||
export const SYSTEM_GROUP_ID_ADMIN = "system-admin";
|
||||
@@ -21,7 +21,7 @@ export interface User {
|
||||
is_active: boolean;
|
||||
local_only: boolean;
|
||||
system_generated: boolean;
|
||||
group_ids: (keyof TranslationDict["groups"])[];
|
||||
group_ids: string[];
|
||||
credentials: Credential[];
|
||||
}
|
||||
|
||||
@@ -95,12 +95,7 @@ export const computeUserBadges = (
|
||||
includeSystem: boolean
|
||||
) => {
|
||||
const labels: [string, string][] = [];
|
||||
const translate = (
|
||||
key: Extract<
|
||||
keyof TranslationDict["ui"]["panel"]["config"]["users"],
|
||||
`is_${string}`
|
||||
>
|
||||
) => hass.localize(`ui.panel.config.users.${key}`);
|
||||
const translate = (key) => hass.localize(`ui.panel.config.users.${key}`);
|
||||
|
||||
if (user.is_owner) {
|
||||
labels.push([OWNER_ICON, translate("is_owner")]);
|
||||
|
||||
@@ -14,13 +14,14 @@ import { styleMap } from "lit/directives/style-map";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { isComponentLoaded } from "../../common/config/is_component_loaded";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
import { protocolIntegrationPicked } from "../../common/integrations/protocolIntegrationPicked";
|
||||
import { navigate } from "../../common/navigate";
|
||||
import { caseInsensitiveStringCompare } from "../../common/string/compare";
|
||||
import { LocalizeFunc } from "../../common/translations/localize";
|
||||
import "../../components/ha-icon-next";
|
||||
import "../../components/search-input";
|
||||
import { getConfigEntries } from "../../data/config_entries";
|
||||
import { domainToName } from "../../data/integration";
|
||||
import { showZWaveJSAddNodeDialog } from "../../panels/config/integrations/integration-panels/zwave_js/show-dialog-zwave_js-add-node";
|
||||
import { HomeAssistant } from "../../types";
|
||||
import { brandsUrl } from "../../util/brands-url";
|
||||
import { documentationUrl } from "../../util/documentation-url";
|
||||
@@ -35,7 +36,7 @@ interface HandlerObj {
|
||||
is_helper?: boolean;
|
||||
}
|
||||
|
||||
export interface SupportedBrandObj extends HandlerObj {
|
||||
interface SupportedBrandObj extends HandlerObj {
|
||||
supported_flows: string[];
|
||||
}
|
||||
|
||||
@@ -299,7 +300,79 @@ class StepFlowPickHandler extends LitElement {
|
||||
}
|
||||
|
||||
private async _handleAddPicked(slug: string): Promise<void> {
|
||||
await protocolIntegrationPicked(this, this.hass, slug);
|
||||
if (slug === "zwave_js") {
|
||||
const entries = await getConfigEntries(this.hass, {
|
||||
domain: "zwave_js",
|
||||
});
|
||||
|
||||
if (!entries.length) {
|
||||
// If the component isn't loaded, ask them to load the integration first
|
||||
showConfirmationDialog(this, {
|
||||
text: this.hass.localize(
|
||||
"ui.panel.config.integrations.config_flow.missing_zwave_zigbee",
|
||||
{
|
||||
integration: "Z-Wave",
|
||||
supported_hardware_link: html`<a
|
||||
href=${documentationUrl(this.hass, "/docs/z-wave/controllers")}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>${this.hass.localize(
|
||||
"ui.panel.config.integrations.config_flow.supported_hardware"
|
||||
)}</a
|
||||
>`,
|
||||
}
|
||||
),
|
||||
confirmText: this.hass.localize(
|
||||
"ui.panel.config.integrations.config_flow.proceed"
|
||||
),
|
||||
confirm: () => {
|
||||
fireEvent(this, "handler-picked", {
|
||||
handler: "zwave_js",
|
||||
});
|
||||
},
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
showZWaveJSAddNodeDialog(this, {
|
||||
entry_id: entries[0].entry_id,
|
||||
});
|
||||
} else if (slug === "zha") {
|
||||
// If the component isn't loaded, ask them to load the integration first
|
||||
if (!isComponentLoaded(this.hass, "zha")) {
|
||||
showConfirmationDialog(this, {
|
||||
text: this.hass.localize(
|
||||
"ui.panel.config.integrations.config_flow.missing_zwave_zigbee",
|
||||
{
|
||||
integration: "Zigbee",
|
||||
supported_hardware_link: html`<a
|
||||
href=${documentationUrl(
|
||||
this.hass,
|
||||
"/integrations/zha/#known-working-zigbee-radio-modules"
|
||||
)}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>${this.hass.localize(
|
||||
"ui.panel.config.integrations.config_flow.supported_hardware"
|
||||
)}</a
|
||||
>`,
|
||||
}
|
||||
),
|
||||
confirmText: this.hass.localize(
|
||||
"ui.panel.config.integrations.config_flow.proceed"
|
||||
),
|
||||
confirm: () => {
|
||||
fireEvent(this, "handler-picked", {
|
||||
handler: "zha",
|
||||
});
|
||||
},
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
navigate("/config/zha/add");
|
||||
}
|
||||
|
||||
// This closes dialog.
|
||||
fireEvent(this, "flow-update");
|
||||
}
|
||||
|
||||
@@ -206,7 +206,6 @@ class MoreInfoMediaPlayer extends LitElement {
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
--mdc-theme-primary: currentColor;
|
||||
direction: ltr;
|
||||
}
|
||||
|
||||
.basic-controls {
|
||||
@@ -214,15 +213,6 @@ class MoreInfoMediaPlayer extends LitElement {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.volume {
|
||||
direction: ltr;
|
||||
}
|
||||
|
||||
.source-input,
|
||||
.sound-input {
|
||||
direction: var(--direction);
|
||||
}
|
||||
|
||||
.volume,
|
||||
.source-input,
|
||||
.sound-input {
|
||||
@@ -235,9 +225,6 @@ class MoreInfoMediaPlayer extends LitElement {
|
||||
.sound-input ha-select {
|
||||
margin-left: 10px;
|
||||
flex-grow: 1;
|
||||
margin-inline-start: 10px;
|
||||
margin-inline-end: initial;
|
||||
direction: var(--direction);
|
||||
}
|
||||
|
||||
.tts {
|
||||
|
||||
@@ -329,9 +329,6 @@ export class HaVoiceCommandDialog extends LitElement {
|
||||
ha-icon-button {
|
||||
color: var(--secondary-text-color);
|
||||
margin-right: -24px;
|
||||
margin-inline-end: -24px;
|
||||
margin-inline-start: initial;
|
||||
direction: var(--direction);
|
||||
}
|
||||
|
||||
ha-icon-button[active] {
|
||||
@@ -376,25 +373,19 @@ export class HaVoiceCommandDialog extends LitElement {
|
||||
|
||||
.message.user {
|
||||
margin-left: 24px;
|
||||
margin-inline-start: 24px;
|
||||
margin-inline-end: initial;
|
||||
float: var(--float-end);
|
||||
float: right;
|
||||
text-align: right;
|
||||
border-bottom-right-radius: 0px;
|
||||
background-color: var(--light-primary-color);
|
||||
color: var(--text-light-primary-color, var(--primary-text-color));
|
||||
direction: var(--direction);
|
||||
}
|
||||
|
||||
.message.hass {
|
||||
margin-right: 24px;
|
||||
margin-inline-end: 24px;
|
||||
margin-inline-start: initial;
|
||||
float: var(--float-start);
|
||||
float: left;
|
||||
border-bottom-left-radius: 0px;
|
||||
background-color: var(--primary-color);
|
||||
color: var(--text-primary-color);
|
||||
direction: var(--direction);
|
||||
}
|
||||
|
||||
.message a {
|
||||
|
||||
@@ -3,7 +3,7 @@ import "@material/mwc-list/mwc-list-item";
|
||||
import { mdiCloudLock, mdiDotsVertical, mdiMagnify } from "@mdi/js";
|
||||
import "@polymer/app-layout/app-header/app-header";
|
||||
import "@polymer/app-layout/app-toolbar/app-toolbar";
|
||||
import { HassEntities, UnsubscribeFunc } from "home-assistant-js-websocket";
|
||||
import { HassEntities } from "home-assistant-js-websocket";
|
||||
import {
|
||||
css,
|
||||
CSSResultGroup,
|
||||
@@ -24,9 +24,9 @@ import "../../../components/ha-svg-icon";
|
||||
import "../../../components/ha-tip";
|
||||
import { CloudStatus } from "../../../data/cloud";
|
||||
import {
|
||||
fetchRepairsIssues,
|
||||
RepairsIssue,
|
||||
severitySort,
|
||||
subscribeRepairsIssueRegistry,
|
||||
} from "../../../data/repairs";
|
||||
import {
|
||||
checkForEntityUpdates,
|
||||
@@ -36,7 +36,6 @@ import {
|
||||
import { showQuickBar } from "../../../dialogs/quick-bar/show-dialog-quick-bar";
|
||||
import "../../../layouts/ha-app-layout";
|
||||
import { PageNavigation } from "../../../layouts/hass-tabs-subpage";
|
||||
import { SubscribeMixin } from "../../../mixins/subscribe-mixin";
|
||||
import { haStyle } from "../../../resources/styles";
|
||||
import { HomeAssistant } from "../../../types";
|
||||
import { documentationUrl } from "../../../util/documentation-url";
|
||||
@@ -111,7 +110,7 @@ const randomTip = (hass: HomeAssistant, narrow: boolean) => {
|
||||
};
|
||||
|
||||
@customElement("ha-config-dashboard")
|
||||
class HaConfigDashboard extends SubscribeMixin(LitElement) {
|
||||
class HaConfigDashboard extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property({ type: Boolean, reflect: true })
|
||||
@@ -145,27 +144,6 @@ class HaConfigDashboard extends SubscribeMixin(LitElement) {
|
||||
return [...pages, ...configSections.dashboard];
|
||||
});
|
||||
|
||||
public hassSubscribe(): UnsubscribeFunc[] {
|
||||
return [
|
||||
subscribeRepairsIssueRegistry(this.hass.connection!, (repairs) => {
|
||||
const repairsIssues = repairs.issues.filter((issue) => !issue.ignored);
|
||||
|
||||
this._repairsIssues = {
|
||||
issues: repairsIssues
|
||||
.sort((a, b) => severitySort[a.severity] - severitySort[b.severity])
|
||||
.slice(0, repairsIssues.length === 3 ? repairsIssues.length : 2),
|
||||
total: repairsIssues.length,
|
||||
};
|
||||
|
||||
const integrations: Set<string> = new Set();
|
||||
for (const issue of this._repairsIssues.issues) {
|
||||
integrations.add(issue.domain);
|
||||
}
|
||||
this.hass.loadBackendTranslation("issues", [...integrations]);
|
||||
}),
|
||||
];
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
const { updates: canInstallUpdates, total: totalUpdates } =
|
||||
this._filterUpdateEntitiesWithInstall(this.hass.states);
|
||||
@@ -210,58 +188,53 @@ class HaConfigDashboard extends SubscribeMixin(LitElement) {
|
||||
.isWide=${this.isWide}
|
||||
full-width
|
||||
>
|
||||
${repairsIssues.length || canInstallUpdates.length
|
||||
? html`<ha-card outlined>
|
||||
${repairsIssues.length
|
||||
? html`
|
||||
<ha-config-repairs
|
||||
.hass=${this.hass}
|
||||
.narrow=${this.narrow}
|
||||
.total=${totalRepairIssues}
|
||||
.repairsIssues=${repairsIssues}
|
||||
></ha-config-repairs>
|
||||
${totalRepairIssues > repairsIssues.length
|
||||
? html`
|
||||
<a class="button" href="/config/repairs">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.repairs.more_repairs",
|
||||
{
|
||||
count:
|
||||
totalRepairIssues - repairsIssues.length,
|
||||
}
|
||||
)}
|
||||
</a>
|
||||
`
|
||||
: ""}
|
||||
`
|
||||
: ""}
|
||||
${repairsIssues.length && canInstallUpdates.length
|
||||
? html`<hr />`
|
||||
: ""}
|
||||
${canInstallUpdates.length
|
||||
? html`
|
||||
<ha-config-updates
|
||||
.hass=${this.hass}
|
||||
.narrow=${this.narrow}
|
||||
.total=${totalUpdates}
|
||||
.updateEntities=${canInstallUpdates}
|
||||
></ha-config-updates>
|
||||
${totalUpdates > canInstallUpdates.length
|
||||
? html`
|
||||
<a class="button" href="/config/updates">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.updates.more_updates",
|
||||
{
|
||||
count:
|
||||
totalUpdates - canInstallUpdates.length,
|
||||
}
|
||||
)}
|
||||
</a>
|
||||
`
|
||||
: ""}
|
||||
`
|
||||
: ""}
|
||||
</ha-card>`
|
||||
${repairsIssues.length
|
||||
? html`
|
||||
<ha-card outlined>
|
||||
<ha-config-repairs
|
||||
.hass=${this.hass}
|
||||
.narrow=${this.narrow}
|
||||
.total=${totalRepairIssues}
|
||||
.repairsIssues=${repairsIssues}
|
||||
></ha-config-repairs>
|
||||
${totalRepairIssues > repairsIssues.length
|
||||
? html`
|
||||
<a class="button" href="/config/repairs">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.repairs.more_repairs",
|
||||
{
|
||||
count: totalRepairIssues - repairsIssues.length,
|
||||
}
|
||||
)}
|
||||
</a>
|
||||
`
|
||||
: ""}
|
||||
</ha-card>
|
||||
`
|
||||
: ""}
|
||||
${canInstallUpdates.length
|
||||
? html`
|
||||
<ha-card outlined>
|
||||
<ha-config-updates
|
||||
.hass=${this.hass}
|
||||
.narrow=${this.narrow}
|
||||
.total=${totalUpdates}
|
||||
.updateEntities=${canInstallUpdates}
|
||||
></ha-config-updates>
|
||||
${totalUpdates > canInstallUpdates.length
|
||||
? html`
|
||||
<a class="button" href="/config/updates">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.updates.more_updates",
|
||||
{
|
||||
count: totalUpdates - canInstallUpdates.length,
|
||||
}
|
||||
)}
|
||||
</a>
|
||||
`
|
||||
: ""}
|
||||
</ha-card>
|
||||
`
|
||||
: ""}
|
||||
|
||||
<ha-card outlined>
|
||||
@@ -281,6 +254,11 @@ class HaConfigDashboard extends SubscribeMixin(LitElement) {
|
||||
`;
|
||||
}
|
||||
|
||||
protected firstUpdated(changedProps: PropertyValues): void {
|
||||
super.firstUpdated(changedProps);
|
||||
this._fetchIssues();
|
||||
}
|
||||
|
||||
protected override updated(changedProps: PropertyValues): void {
|
||||
super.updated(changedProps);
|
||||
|
||||
@@ -300,6 +278,23 @@ class HaConfigDashboard extends SubscribeMixin(LitElement) {
|
||||
}
|
||||
);
|
||||
|
||||
private async _fetchIssues(): Promise<void> {
|
||||
const repairsIssues = (await fetchRepairsIssues(this.hass)).issues;
|
||||
|
||||
this._repairsIssues = {
|
||||
issues: repairsIssues
|
||||
.sort((a, b) => severitySort[a.severity] - severitySort[b.severity])
|
||||
.slice(0, repairsIssues.length === 3 ? repairsIssues.length : 2),
|
||||
total: repairsIssues.length,
|
||||
};
|
||||
|
||||
const integrations: Set<string> = new Set();
|
||||
for (const issue of this._repairsIssues.issues) {
|
||||
integrations.add(issue.domain);
|
||||
}
|
||||
this.hass.loadBackendTranslation("issues", [...integrations]);
|
||||
}
|
||||
|
||||
private _showQuickBar(): void {
|
||||
showQuickBar(this, {
|
||||
commandMode: true,
|
||||
@@ -338,12 +333,9 @@ class HaConfigDashboard extends SubscribeMixin(LitElement) {
|
||||
color: var(--primary-text-color);
|
||||
}
|
||||
a.button {
|
||||
display: inline-block;
|
||||
color: var(--primary-text-color);
|
||||
padding: 6px 16px;
|
||||
margin: 8px 16px 16px 16px;
|
||||
border-radius: 32px;
|
||||
border: 1px solid var(--divider-color);
|
||||
display: block;
|
||||
color: var(--primary-color);
|
||||
padding: 16px;
|
||||
}
|
||||
.title {
|
||||
font-size: 16px;
|
||||
@@ -373,16 +365,6 @@ class HaConfigDashboard extends SubscribeMixin(LitElement) {
|
||||
.keep-together {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
hr {
|
||||
height: 1px;
|
||||
background-color: var(
|
||||
--ha-card-border-color,
|
||||
var(--divider-color, #e0e0e0)
|
||||
);
|
||||
border: none;
|
||||
margin-top: 0;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
||||
@@ -6,14 +6,10 @@ import "../../../../components/ha-area-picker";
|
||||
import "../../../../components/ha-dialog";
|
||||
import type { HaSwitch } from "../../../../components/ha-switch";
|
||||
import "../../../../components/ha-textfield";
|
||||
import {
|
||||
computeDeviceName,
|
||||
DeviceRegistryEntry,
|
||||
} from "../../../../data/device_registry";
|
||||
import { computeDeviceName } from "../../../../data/device_registry";
|
||||
import { haStyle, haStyleDialog } from "../../../../resources/styles";
|
||||
import { HomeAssistant } from "../../../../types";
|
||||
import { DeviceRegistryDetailDialogParams } from "./show-dialog-device-registry-detail";
|
||||
import "../../../../components/ha-alert";
|
||||
|
||||
@customElement("dialog-device-registry-detail")
|
||||
class DialogDeviceRegistryDetail extends LitElement {
|
||||
@@ -25,11 +21,11 @@ class DialogDeviceRegistryDetail extends LitElement {
|
||||
|
||||
@state() private _params?: DeviceRegistryDetailDialogParams;
|
||||
|
||||
@state() private _areaId!: string;
|
||||
@property() public _areaId?: string | null;
|
||||
|
||||
@state() private _disabledBy!: DeviceRegistryEntry["disabled_by"];
|
||||
@state() private _disabledBy!: string | null;
|
||||
|
||||
@state() private _submitting = false;
|
||||
@state() private _submitting?: boolean;
|
||||
|
||||
public async showDialog(
|
||||
params: DeviceRegistryDetailDialogParams
|
||||
@@ -37,7 +33,7 @@ class DialogDeviceRegistryDetail extends LitElement {
|
||||
this._params = params;
|
||||
this._error = undefined;
|
||||
this._nameByUser = this._params.device.name_by_user || "";
|
||||
this._areaId = this._params.device.area_id || "";
|
||||
this._areaId = this._params.device.area_id;
|
||||
this._disabledBy = this._params.device.disabled_by;
|
||||
await this.updateComplete;
|
||||
}
|
||||
|
||||
@@ -13,7 +13,6 @@ import {
|
||||
subscribeDeviceRegistry,
|
||||
} from "../../../data/device_registry";
|
||||
import {
|
||||
EntityRegistryEntry,
|
||||
EntityRegistryEntryUpdateParams,
|
||||
ExtEntityRegistryEntry,
|
||||
updateEntityRegistryEntry,
|
||||
@@ -26,7 +25,7 @@ import type { HomeAssistant } from "../../../types";
|
||||
export class HaEntityRegistryBasicEditor extends SubscribeMixin(LitElement) {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property({ attribute: false }) public entry!: ExtEntityRegistryEntry;
|
||||
@property() public entry!: ExtEntityRegistryEntry;
|
||||
|
||||
@state() private _origEntityId!: string;
|
||||
|
||||
@@ -34,7 +33,7 @@ export class HaEntityRegistryBasicEditor extends SubscribeMixin(LitElement) {
|
||||
|
||||
@state() private _areaId?: string | null;
|
||||
|
||||
@state() private _disabledBy!: EntityRegistryEntry["disabled_by"];
|
||||
@state() private _disabledBy!: string | null;
|
||||
|
||||
@state() private _hiddenBy!: string | null;
|
||||
|
||||
@@ -42,7 +41,7 @@ export class HaEntityRegistryBasicEditor extends SubscribeMixin(LitElement) {
|
||||
|
||||
@state() private _device?: DeviceRegistryEntry;
|
||||
|
||||
@state() private _submitting = false;
|
||||
@state() private _submitting?: boolean;
|
||||
|
||||
public async updateEntry(): Promise<void> {
|
||||
this._submitting = true;
|
||||
@@ -146,8 +145,8 @@ export class HaEntityRegistryBasicEditor extends SubscribeMixin(LitElement) {
|
||||
></ha-textfield>
|
||||
<ha-area-picker
|
||||
.hass=${this.hass}
|
||||
.value=${this._areaId || undefined}
|
||||
.placeholder=${this._device?.area_id || undefined}
|
||||
.value=${this._areaId}
|
||||
.placeholder=${this._device?.area_id}
|
||||
@value-changed=${this._areaPicked}
|
||||
></ha-area-picker>
|
||||
|
||||
@@ -183,8 +182,8 @@ export class HaEntityRegistryBasicEditor extends SubscribeMixin(LitElement) {
|
||||
name="hiddendisabled"
|
||||
value="enabled"
|
||||
.checked=${!this._hiddenBy && !this._disabledBy}
|
||||
.disabled=${!!this._device?.disabled_by ||
|
||||
(this._disabledBy !== null &&
|
||||
.disabled=${this._device?.disabled_by ||
|
||||
(this._disabledBy &&
|
||||
!(
|
||||
this._disabledBy === "user" ||
|
||||
this._disabledBy === "integration"
|
||||
@@ -201,8 +200,8 @@ export class HaEntityRegistryBasicEditor extends SubscribeMixin(LitElement) {
|
||||
name="hiddendisabled"
|
||||
value="hidden"
|
||||
.checked=${this._hiddenBy !== null}
|
||||
.disabled=${!!this._device?.disabled_by ||
|
||||
(this._disabledBy !== null &&
|
||||
.disabled=${this._device?.disabled_by ||
|
||||
(this._disabledBy &&
|
||||
!(
|
||||
this._disabledBy === "user" ||
|
||||
this._disabledBy === "integration"
|
||||
@@ -219,8 +218,8 @@ export class HaEntityRegistryBasicEditor extends SubscribeMixin(LitElement) {
|
||||
name="hiddendisabled"
|
||||
value="disabled"
|
||||
.checked=${this._disabledBy !== null}
|
||||
.disabled=${!!this._device?.disabled_by ||
|
||||
(this._disabledBy !== null &&
|
||||
.disabled=${this._device?.disabled_by ||
|
||||
(this._disabledBy &&
|
||||
!(
|
||||
this._disabledBy === "user" ||
|
||||
this._disabledBy === "integration"
|
||||
@@ -303,9 +302,3 @@ export class HaEntityRegistryBasicEditor extends SubscribeMixin(LitElement) {
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-registry-basic-editor": HaEntityRegistryBasicEditor;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,7 +53,6 @@ import {
|
||||
updateDeviceRegistryEntry,
|
||||
} from "../../../data/device_registry";
|
||||
import {
|
||||
EntityRegistryEntry,
|
||||
EntityRegistryEntryUpdateParams,
|
||||
ExtEntityRegistryEntry,
|
||||
fetchEntityRegistry,
|
||||
@@ -129,7 +128,7 @@ const SWITCH_AS_DOMAINS = ["cover", "fan", "light", "lock", "siren"];
|
||||
export class EntityRegistrySettings extends SubscribeMixin(LitElement) {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property({ type: Object }) public entry!: ExtEntityRegistryEntry;
|
||||
@property() public entry!: ExtEntityRegistryEntry;
|
||||
|
||||
@state() private _name!: string;
|
||||
|
||||
@@ -143,9 +142,9 @@ export class EntityRegistrySettings extends SubscribeMixin(LitElement) {
|
||||
|
||||
@state() private _areaId?: string | null;
|
||||
|
||||
@state() private _disabledBy!: EntityRegistryEntry["disabled_by"];
|
||||
@state() private _disabledBy!: string | null;
|
||||
|
||||
@state() private _hiddenBy!: EntityRegistryEntry["hidden_by"];
|
||||
@state() private _hiddenBy!: string | null;
|
||||
|
||||
@state() private _device?: DeviceRegistryEntry;
|
||||
|
||||
@@ -631,10 +630,9 @@ export class EntityRegistrySettings extends SubscribeMixin(LitElement) {
|
||||
name="hiddendisabled"
|
||||
value="enabled"
|
||||
.checked=${!this._hiddenBy && !this._disabledBy}
|
||||
.disabled=${(this._hiddenBy !== null &&
|
||||
this._hiddenBy !== "user") ||
|
||||
!!this._device?.disabled_by ||
|
||||
(this._disabledBy !== null &&
|
||||
.disabled=${(this._hiddenBy && this._hiddenBy !== "user") ||
|
||||
this._device?.disabled_by ||
|
||||
(this._disabledBy &&
|
||||
this._disabledBy !== "user" &&
|
||||
this._disabledBy !== "integration")}
|
||||
@change=${this._viewStatusChanged}
|
||||
|
||||
@@ -15,7 +15,6 @@ import { HassEntity, UnsubscribeFunc } from "home-assistant-js-websocket";
|
||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import { classMap } from "lit/directives/class-map";
|
||||
import { ifDefined } from "lit/directives/if-defined";
|
||||
import { styleMap } from "lit/directives/style-map";
|
||||
import memoize from "memoize-one";
|
||||
import type { HASSDomEvent } from "../../../common/dom/fire_event";
|
||||
@@ -86,11 +85,11 @@ export interface EntityRow extends StateEntity {
|
||||
export class HaConfigEntities extends SubscribeMixin(LitElement) {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property({ type: Boolean }) public isWide!: boolean;
|
||||
@property() public isWide!: boolean;
|
||||
|
||||
@property({ type: Boolean }) public narrow!: boolean;
|
||||
@property() public narrow!: boolean;
|
||||
|
||||
@property({ attribute: false }) public route!: Route;
|
||||
@property() public route!: Route;
|
||||
|
||||
@state() private _entities?: EntityRegistryEntry[];
|
||||
|
||||
@@ -175,7 +174,7 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) {
|
||||
type: "icon",
|
||||
template: (_, entry: EntityRow) => html`
|
||||
<ha-state-icon
|
||||
title=${ifDefined(entry.entity?.state)}
|
||||
.title=${entry.entity?.state}
|
||||
slot="item-icon"
|
||||
.state=${entry.entity}
|
||||
></ha-state-icon>
|
||||
@@ -238,10 +237,12 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) {
|
||||
hidden: narrow || !showDisabled,
|
||||
filterable: true,
|
||||
width: "15%",
|
||||
template: (disabled_by: EntityRegistryEntry["disabled_by"]) =>
|
||||
disabled_by === null
|
||||
? "—"
|
||||
: this.hass.localize(`config_entry.disabled_by.${disabled_by}`),
|
||||
template: (disabled_by) =>
|
||||
this.hass.localize(
|
||||
`ui.panel.config.devices.disabled_by.${disabled_by}`
|
||||
) ||
|
||||
disabled_by ||
|
||||
"—",
|
||||
},
|
||||
status: {
|
||||
title: this.hass.localize(
|
||||
@@ -1011,9 +1012,3 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) {
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-config-entities": HaConfigEntities;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
mdiCog,
|
||||
mdiDatabase,
|
||||
mdiDevices,
|
||||
mdiHeart,
|
||||
mdiInformation,
|
||||
mdiInformationOutline,
|
||||
mdiLifebuoy,
|
||||
@@ -321,6 +322,13 @@ export const configSections: { [name: string]: PageNavigation[] } = {
|
||||
iconColor: "#301A8E",
|
||||
component: "hassio",
|
||||
},
|
||||
{
|
||||
path: "/config/system_health",
|
||||
translationKey: "system_health",
|
||||
iconPath: mdiHeart,
|
||||
iconColor: "#507FfE",
|
||||
components: ["system_health", "hassio"],
|
||||
},
|
||||
],
|
||||
about: [
|
||||
{
|
||||
@@ -439,6 +447,10 @@ class HaPanelConfig extends HassRouterPage {
|
||||
tag: "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: {
|
||||
tag: "ha-config-section-updates",
|
||||
load: () => import("./core/ha-config-section-updates"),
|
||||
|
||||
@@ -14,8 +14,7 @@ import { customElement, property, state } from "lit/decorators";
|
||||
import { ifDefined } from "lit/directives/if-defined";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { isComponentLoaded } from "../../../common/config/is_component_loaded";
|
||||
import { fireEvent, HASSDomEvent } from "../../../common/dom/fire_event";
|
||||
import { protocolIntegrationPicked } from "../../../common/integrations/protocolIntegrationPicked";
|
||||
import type { HASSDomEvent } from "../../../common/dom/fire_event";
|
||||
import { navigate } from "../../../common/navigate";
|
||||
import { caseInsensitiveStringCompare } from "../../../common/string/compare";
|
||||
import type { LocalizeFunc } from "../../../common/translations/localize";
|
||||
@@ -50,10 +49,6 @@ import {
|
||||
fetchIntegrationManifests,
|
||||
IntegrationManifest,
|
||||
} from "../../../data/integration";
|
||||
import {
|
||||
getSupportedBrands,
|
||||
getSupportedBrandsLookup,
|
||||
} from "../../../data/supported_brands";
|
||||
import { scanUSBDevices } from "../../../data/usb";
|
||||
import { showConfigFlowDialog } from "../../../dialogs/config-flow/show-dialog-config-flow";
|
||||
import {
|
||||
@@ -682,84 +677,49 @@ class HaConfigIntegrations extends SubscribeMixin(LitElement) {
|
||||
if (!domain) {
|
||||
return;
|
||||
}
|
||||
|
||||
const handlers = await getConfigFlowHandlers(this.hass, "integration");
|
||||
|
||||
// Integration exists, so we can just create a flow
|
||||
if (handlers.includes(domain)) {
|
||||
const localize = await localizePromise;
|
||||
if (
|
||||
!(await showConfirmationDialog(this, {
|
||||
title: localize("ui.panel.config.integrations.confirm_new", {
|
||||
integration: domainToName(localize, domain),
|
||||
}),
|
||||
}))
|
||||
) {
|
||||
if (!handlers.includes(domain)) {
|
||||
if (HELPER_DOMAINS.includes(domain)) {
|
||||
navigate(`/config/helpers/add?domain=${domain}`, {
|
||||
replace: true,
|
||||
});
|
||||
return;
|
||||
}
|
||||
showConfigFlowDialog(this, {
|
||||
dialogClosedCallback: () => {
|
||||
this._handleFlowUpdated();
|
||||
},
|
||||
startFlowHandler: domain,
|
||||
manifest: this._manifests[domain],
|
||||
showAdvanced: this.hass.userData?.showAdvanced,
|
||||
});
|
||||
}
|
||||
|
||||
const supportedBrands = await getSupportedBrands(this.hass);
|
||||
const supportedBrandsIntegrations =
|
||||
getSupportedBrandsLookup(supportedBrands);
|
||||
|
||||
// Supported brand exists, so we can just create a flow
|
||||
if (Object.keys(supportedBrandsIntegrations).includes(domain)) {
|
||||
const brand = supportedBrandsIntegrations[domain];
|
||||
const slug = brand.supported_flows![0];
|
||||
|
||||
showConfirmationDialog(this, {
|
||||
text: this.hass.localize(
|
||||
"ui.panel.config.integrations.config_flow.supported_brand_flow",
|
||||
{
|
||||
supported_brand: brand.name,
|
||||
flow_domain_name: domainToName(this.hass.localize, slug),
|
||||
}
|
||||
const helpers = await getConfigFlowHandlers(this.hass, "helper");
|
||||
if (helpers.includes(domain)) {
|
||||
navigate(`/config/helpers/add?domain=${domain}`, {
|
||||
replace: true,
|
||||
});
|
||||
return;
|
||||
}
|
||||
showAlertDialog(this, {
|
||||
title: this.hass.localize(
|
||||
"ui.panel.config.integrations.config_flow.error"
|
||||
),
|
||||
text: this.hass.localize(
|
||||
"ui.panel.config.integrations.config_flow.no_config_flow"
|
||||
),
|
||||
confirm: () => {
|
||||
if (["zha", "zwave_js"].includes(slug)) {
|
||||
protocolIntegrationPicked(this, this.hass, slug);
|
||||
return;
|
||||
}
|
||||
|
||||
fireEvent(this, "handler-picked", {
|
||||
handler: slug,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// If not an integration or supported brand, try helper else show alert
|
||||
if (HELPER_DOMAINS.includes(domain)) {
|
||||
navigate(`/config/helpers/add?domain=${domain}`, {
|
||||
replace: true,
|
||||
});
|
||||
return;
|
||||
}
|
||||
const helpers = await getConfigFlowHandlers(this.hass, "helper");
|
||||
if (helpers.includes(domain)) {
|
||||
navigate(`/config/helpers/add?domain=${domain}`, {
|
||||
replace: true,
|
||||
});
|
||||
const localize = await localizePromise;
|
||||
if (
|
||||
!(await showConfirmationDialog(this, {
|
||||
title: localize("ui.panel.config.integrations.confirm_new", {
|
||||
integration: domainToName(localize, domain),
|
||||
}),
|
||||
}))
|
||||
) {
|
||||
return;
|
||||
}
|
||||
showAlertDialog(this, {
|
||||
title: this.hass.localize(
|
||||
"ui.panel.config.integrations.config_flow.error"
|
||||
),
|
||||
text: this.hass.localize(
|
||||
"ui.panel.config.integrations.config_flow.no_config_flow"
|
||||
),
|
||||
showConfigFlowDialog(this, {
|
||||
dialogClosedCallback: () => {
|
||||
this._handleFlowUpdated();
|
||||
},
|
||||
startFlowHandler: domain,
|
||||
manifest: this._manifests[domain],
|
||||
showAdvanced: this.hass.userData?.showAdvanced,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
mdiDotsVertical,
|
||||
mdiOpenInNew,
|
||||
} from "@mdi/js";
|
||||
import "@polymer/paper-item/paper-item";
|
||||
import "@polymer/paper-item";
|
||||
import "@polymer/paper-listbox";
|
||||
import "@polymer/paper-tooltip/paper-tooltip";
|
||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||
@@ -64,15 +64,13 @@ export class HaIntegrationCard extends LitElement {
|
||||
|
||||
@property() public domain!: string;
|
||||
|
||||
@property({ attribute: false }) public items!: ConfigEntryExtended[];
|
||||
@property() public items!: ConfigEntryExtended[];
|
||||
|
||||
@property({ attribute: false }) public manifest?: IntegrationManifest;
|
||||
@property() public manifest?: IntegrationManifest;
|
||||
|
||||
@property({ attribute: false })
|
||||
public entityRegistryEntries!: EntityRegistryEntry[];
|
||||
@property() public entityRegistryEntries!: EntityRegistryEntry[];
|
||||
|
||||
@property({ attribute: false })
|
||||
public deviceRegistryEntries!: DeviceRegistryEntry[];
|
||||
@property() public deviceRegistryEntries!: DeviceRegistryEntry[];
|
||||
|
||||
@property() public selectedConfigEntryId?: string;
|
||||
|
||||
@@ -181,7 +179,7 @@ export class HaIntegrationCard extends LitElement {
|
||||
const services = this._getServices(item, this.deviceRegistryEntries);
|
||||
const entities = this._getEntities(item, this.entityRegistryEntries);
|
||||
|
||||
let stateText: Parameters<typeof this.hass.localize> | undefined;
|
||||
let stateText: [string, ...unknown[]] | undefined;
|
||||
let stateTextExtra: TemplateResult | string | undefined;
|
||||
|
||||
if (item.disabled_by) {
|
||||
@@ -227,7 +225,7 @@ export class HaIntegrationCard extends LitElement {
|
||||
for (const [items, localizeKey] of [
|
||||
[devices, "devices"],
|
||||
[services, "services"],
|
||||
] as const) {
|
||||
] as [DeviceRegistryEntry[], string][]) {
|
||||
if (items.length === 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -12,13 +12,13 @@ import { brandsUrl } from "../../../util/brands-url";
|
||||
export class HaIntegrationHeader extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property() public banner?: string;
|
||||
@property() public banner!: string;
|
||||
|
||||
@property() public localizedDomainName?: string;
|
||||
|
||||
@property() public domain!: string;
|
||||
|
||||
@property() public label?: string;
|
||||
@property() public label!: string;
|
||||
|
||||
@property({ attribute: false }) public manifest?: IntegrationManifest;
|
||||
|
||||
|
||||
@@ -102,7 +102,7 @@ export class SystemLogCard extends LitElement {
|
||||
<paper-item @click=${this._openLog} .logItem=${item}>
|
||||
<paper-item-body two-line>
|
||||
<div class="row">${item.message[0]}</div>
|
||||
<div class="row-secondary" secondary>
|
||||
<div secondary>
|
||||
${this._timestamp(item)} –
|
||||
${html`(<span class=${item.level.toLowerCase()}
|
||||
>${this.hass.localize(
|
||||
@@ -209,11 +209,6 @@ export class SystemLogCard extends LitElement {
|
||||
.empty-content {
|
||||
direction: var(--direction);
|
||||
}
|
||||
|
||||
.row-secondary {
|
||||
direction: var(--direction);
|
||||
text-align: left;
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,91 +0,0 @@
|
||||
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 { createCloseHeading } from "../../../components/ha-dialog";
|
||||
import type { NetworkInterface } from "../../../data/hassio/network";
|
||||
import { haStyleDialog } from "../../../resources/styles";
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
import type { IPDetailDialogParams } from "./show-ip-detail-dialog";
|
||||
|
||||
@customElement("dialog-ip-detail")
|
||||
class DialogIPDetail extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@state() private _params?: IPDetailDialogParams;
|
||||
|
||||
@state() private _interface?: NetworkInterface;
|
||||
|
||||
public showDialog(params: IPDetailDialogParams): void {
|
||||
this._params = params;
|
||||
this._interface = this._params.interface;
|
||||
}
|
||||
|
||||
public closeDialog() {
|
||||
this._params = undefined;
|
||||
this._interface = undefined;
|
||||
fireEvent(this, "dialog-closed", { dialog: this.localName });
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
if (!this._interface) {
|
||||
return html``;
|
||||
}
|
||||
|
||||
const ipv4 = this._interface.ipv4;
|
||||
const ipv6 = this._interface.ipv6;
|
||||
|
||||
return html`
|
||||
<ha-dialog
|
||||
open
|
||||
@closed=${this.closeDialog}
|
||||
scrimClickAction
|
||||
escapeKeyAction
|
||||
.heading=${createCloseHeading(this.hass, "IP Information")}
|
||||
>
|
||||
${ipv4
|
||||
? html`
|
||||
<div>
|
||||
<h3>IPv4</h3>
|
||||
${ipv4.address
|
||||
? html`<div>IP Address: ${ipv4.address?.join(", ")}</div>`
|
||||
: ""}
|
||||
${ipv4.gateway ? html`<div>Gateway: ${ipv4.gateway}</div>` : ""}
|
||||
${ipv4.method ? html`<div>Method: ${ipv4.method}</div>` : ""}
|
||||
${ipv4.nameservers?.length
|
||||
? html`
|
||||
<div>Name Servers: ${ipv4.nameservers?.join(", ")}</div>
|
||||
`
|
||||
: ""}
|
||||
</div>
|
||||
`
|
||||
: ""}
|
||||
${ipv6
|
||||
? html`
|
||||
<div>
|
||||
<h3>IPv6</h3>
|
||||
${ipv6.address
|
||||
? html`<div>IP Address: ${ipv6.address?.join(", ")}</div>`
|
||||
: ""}
|
||||
${ipv6.gateway ? html`<div>Gateway: ${ipv6.gateway}</div>` : ""}
|
||||
${ipv6.method ? html`<div>Method: ${ipv6.method}</div>` : ""}
|
||||
${ipv6.nameservers?.length
|
||||
? html`
|
||||
<div>Name Servers: ${ipv6.nameservers?.join(", ")}</div>
|
||||
`
|
||||
: ""}
|
||||
</div>
|
||||
`
|
||||
: ""}
|
||||
</ha-dialog>
|
||||
`;
|
||||
}
|
||||
|
||||
static styles: CSSResultGroup = haStyleDialog;
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"dialog-ip-detail": DialogIPDetail;
|
||||
}
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
import { fireEvent } from "../../../common/dom/fire_event";
|
||||
import type { NetworkInterface } from "../../../data/hassio/network";
|
||||
|
||||
export interface IPDetailDialogParams {
|
||||
interface?: NetworkInterface;
|
||||
}
|
||||
|
||||
export const loadIPDetailDialog = () => import("./dialog-ip-detail");
|
||||
|
||||
export const showIPDetailDialog = (
|
||||
element: HTMLElement,
|
||||
dialogParams: IPDetailDialogParams
|
||||
): void => {
|
||||
fireEvent(element, "show-dialog", {
|
||||
dialogTag: "dialog-ip-detail",
|
||||
dialogImport: loadIPDetailDialog,
|
||||
dialogParams,
|
||||
});
|
||||
};
|
||||
@@ -1,16 +1,13 @@
|
||||
import "@material/mwc-button/mwc-button";
|
||||
import { ActionDetail } from "@material/mwc-list/mwc-list";
|
||||
import "@material/mwc-list/mwc-list";
|
||||
import "@material/mwc-list/mwc-list-item";
|
||||
import "@material/mwc-tab";
|
||||
import "@material/mwc-tab-bar";
|
||||
import { mdiDotsVertical } from "@mdi/js";
|
||||
import { PaperInputElement } from "@polymer/paper-input/paper-input";
|
||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { cache } from "lit/directives/cache";
|
||||
import "../../../components/ha-alert";
|
||||
import "../../../components/ha-button-menu";
|
||||
import "../../../components/ha-card";
|
||||
import "../../../components/ha-circular-progress";
|
||||
import "../../../components/ha-expansion-panel";
|
||||
import "../../../components/ha-formfield";
|
||||
@@ -32,7 +29,7 @@ import {
|
||||
showConfirmationDialog,
|
||||
} from "../../../dialogs/generic/show-dialog-box";
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
import { showIPDetailDialog } from "./show-ip-detail-dialog";
|
||||
import "../../../components/ha-card";
|
||||
|
||||
const IP_VERSIONS = ["ipv4", "ipv6"];
|
||||
|
||||
@@ -239,25 +236,9 @@ export class HassioNetwork extends LitElement {
|
||||
</ha-circular-progress>`
|
||||
: this.hass.localize("ui.common.save")}
|
||||
</mwc-button>
|
||||
<ha-button-menu corner="BOTTOM_START" @action=${this._handleAction}>
|
||||
<ha-icon-button
|
||||
slot="trigger"
|
||||
.label=${"ui.common.menu"}
|
||||
.path=${mdiDotsVertical}
|
||||
></ha-icon-button>
|
||||
<mwc-list-item>IP Information</mwc-list-item>
|
||||
</ha-button-menu>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
private _handleAction(ev: CustomEvent<ActionDetail>) {
|
||||
switch (ev.detail.index) {
|
||||
case 0:
|
||||
showIPDetailDialog(this, { interface: this._interface });
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private _selectAP(event) {
|
||||
this._wifiConfiguration = event.currentTarget.ap;
|
||||
this._dirty = true;
|
||||
|
||||
@@ -1,57 +0,0 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,7 @@ import { customElement, property, state } from "lit/decorators";
|
||||
import { fireEvent } from "../../../common/dom/fire_event";
|
||||
import { createCloseHeading } from "../../../components/ha-dialog";
|
||||
import "../../../components/ha-markdown";
|
||||
import { ignoreRepairsIssue, RepairsIssue } from "../../../data/repairs";
|
||||
import type { RepairsIssue } from "../../../data/repairs";
|
||||
import { haStyleDialog } from "../../../resources/styles";
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
import type { RepairsIssueDialogParams } from "./show-repair-issue-dialog";
|
||||
@@ -23,10 +23,6 @@ class DialogRepairsIssue extends LitElement {
|
||||
}
|
||||
|
||||
public closeDialog() {
|
||||
if (this._params?.dialogClosedCallback) {
|
||||
this._params.dialogClosedCallback();
|
||||
}
|
||||
|
||||
this._params = undefined;
|
||||
this._issue = undefined;
|
||||
fireEvent(this, "dialog-closed", { dialog: this.localName });
|
||||
@@ -43,6 +39,7 @@ class DialogRepairsIssue extends LitElement {
|
||||
@closed=${this.closeDialog}
|
||||
scrimClickAction
|
||||
escapeKeyAction
|
||||
.hideActions=${!this._issue.learn_more_url}
|
||||
.heading=${createCloseHeading(
|
||||
this.hass,
|
||||
this.hass.localize(
|
||||
@@ -53,24 +50,6 @@ class DialogRepairsIssue extends LitElement {
|
||||
)}
|
||||
>
|
||||
<div>
|
||||
<ha-alert
|
||||
.alertType=${this._issue.severity === "error" ||
|
||||
this._issue.severity === "critical"
|
||||
? "error"
|
||||
: "warning"}
|
||||
.title=${this.hass.localize(
|
||||
`ui.panel.config.repairs.${this._issue.severity}`
|
||||
)}
|
||||
>${this.hass.localize(
|
||||
"ui.panel.config.repairs.dialog.alert_not_fixable"
|
||||
)}
|
||||
${this._issue.breaks_in_ha_version
|
||||
? this.hass.localize(
|
||||
"ui.panel.config.repairs.dialog.breaks_in_version",
|
||||
{ version: this._issue.breaks_in_ha_version }
|
||||
)
|
||||
: ""}
|
||||
</ha-alert>
|
||||
<ha-markdown
|
||||
allowsvg
|
||||
breaks
|
||||
@@ -81,14 +60,19 @@ class DialogRepairsIssue extends LitElement {
|
||||
this._issue.translation_placeholders
|
||||
)}
|
||||
></ha-markdown>
|
||||
|
||||
${this._issue.breaks_in_ha_version
|
||||
? html`
|
||||
<br />This will no longer work as of the
|
||||
${this._issue.breaks_in_ha_version} release of Home Assistant.
|
||||
`
|
||||
: ""}
|
||||
<br />The issue is ${this._issue.severity} severity.<br />We can not
|
||||
automatically repair this issue for you.
|
||||
${this._issue.dismissed_version
|
||||
? html`
|
||||
<br /><span class="dismissed">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.repairs.dialog.ignored_in_version",
|
||||
{ version: this._issue.dismissed_version }
|
||||
)}</span
|
||||
>
|
||||
<br />This issue has been dismissed in version
|
||||
${this._issue.dismissed_version}.
|
||||
`
|
||||
: ""}
|
||||
</div>
|
||||
@@ -100,43 +84,20 @@ class DialogRepairsIssue extends LitElement {
|
||||
slot="primaryAction"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<mwc-button
|
||||
.label=${this.hass!.localize(
|
||||
"ui.panel.config.repairs.dialog.learn"
|
||||
)}
|
||||
></mwc-button>
|
||||
<mwc-button .label=${"Learn More"}></mwc-button>
|
||||
</a>
|
||||
`
|
||||
: ""}
|
||||
<mwc-button
|
||||
slot="secondaryAction"
|
||||
.label=${this._issue!.ignored
|
||||
? this.hass!.localize("ui.panel.config.repairs.dialog.unignore")
|
||||
: this.hass!.localize("ui.panel.config.repairs.dialog.ignore")}
|
||||
@click=${this._ignoreIssue}
|
||||
></mwc-button>
|
||||
</ha-dialog>
|
||||
`;
|
||||
}
|
||||
|
||||
private _ignoreIssue() {
|
||||
ignoreRepairsIssue(this.hass, this._issue!, !this._issue!.ignored);
|
||||
this.closeDialog();
|
||||
}
|
||||
|
||||
static styles: CSSResultGroup = [
|
||||
haStyleDialog,
|
||||
css`
|
||||
ha-alert {
|
||||
margin-bottom: 16px;
|
||||
display: block;
|
||||
}
|
||||
a {
|
||||
text-decoration: none;
|
||||
}
|
||||
.dismissed {
|
||||
font-style: italic;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
||||
@@ -1,62 +1,26 @@
|
||||
import { RequestSelectedDetail } from "@material/mwc-list/mwc-list-item-base";
|
||||
import { mdiDotsVertical } from "@mdi/js";
|
||||
import { UnsubscribeFunc } from "home-assistant-js-websocket";
|
||||
import { css, html, LitElement, TemplateResult } from "lit";
|
||||
import { css, html, LitElement, PropertyValues, TemplateResult } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
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 {
|
||||
RepairsIssue,
|
||||
severitySort,
|
||||
subscribeRepairsIssueRegistry,
|
||||
} from "../../../data/repairs";
|
||||
import type { RepairsIssue } from "../../../data/repairs";
|
||||
import { fetchRepairsIssues } from "../../../data/repairs";
|
||||
import "../../../layouts/hass-subpage";
|
||||
import { SubscribeMixin } from "../../../mixins/subscribe-mixin";
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
import "./ha-config-repairs";
|
||||
import { showIntegrationStartupDialog } from "./show-integration-startup-dialog";
|
||||
import { showSystemInformationDialog } from "./show-system-information-dialog";
|
||||
|
||||
@customElement("ha-config-repairs-dashboard")
|
||||
class HaConfigRepairsDashboard extends SubscribeMixin(LitElement) {
|
||||
class HaConfigRepairsDashboard extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property({ type: Boolean }) public narrow!: boolean;
|
||||
|
||||
@state() private _repairsIssues: RepairsIssue[] = [];
|
||||
|
||||
@state() private _showIgnored = false;
|
||||
|
||||
private _getFilteredIssues = memoizeOne(
|
||||
(showIgnored: boolean, repairsIssues: RepairsIssue[]) =>
|
||||
showIgnored
|
||||
? repairsIssues
|
||||
: repairsIssues.filter((issue) => !issue.ignored)
|
||||
);
|
||||
|
||||
public hassSubscribe(): UnsubscribeFunc[] {
|
||||
return [
|
||||
subscribeRepairsIssueRegistry(this.hass.connection!, (repairs) => {
|
||||
this._repairsIssues = repairs.issues.sort(
|
||||
(a, b) => severitySort[a.severity] - severitySort[b.severity]
|
||||
);
|
||||
const integrations: Set<string> = new Set();
|
||||
for (const issue of this._repairsIssues) {
|
||||
integrations.add(issue.domain);
|
||||
}
|
||||
this.hass.loadBackendTranslation("issues", [...integrations]);
|
||||
}),
|
||||
];
|
||||
protected firstUpdated(changedProps: PropertyValues): void {
|
||||
super.firstUpdated(changedProps);
|
||||
this._fetchIssues();
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
const issues = this._getFilteredIssues(
|
||||
this._showIgnored,
|
||||
this._repairsIssues
|
||||
);
|
||||
|
||||
return html`
|
||||
<hass-subpage
|
||||
back-path="/config/system"
|
||||
@@ -64,39 +28,6 @@ class HaConfigRepairsDashboard extends SubscribeMixin(LitElement) {
|
||||
.narrow=${this.narrow}
|
||||
.header=${this.hass.localize("ui.panel.config.repairs.caption")}
|
||||
>
|
||||
<div slot="toolbar-icon">
|
||||
<ha-button-menu corner="BOTTOM_START">
|
||||
<ha-icon-button
|
||||
slot="trigger"
|
||||
.label=${this.hass.localize("ui.common.menu")}
|
||||
.path=${mdiDotsVertical}
|
||||
></ha-icon-button>
|
||||
${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.hass.localize("ui.panel.config.repairs.hide_ignored")
|
||||
: this.hass.localize("ui.panel.config.repairs.show_ignored")}
|
||||
</mwc-list-item>
|
||||
</ha-button-menu>
|
||||
</div>
|
||||
<div class="content">
|
||||
<ha-card outlined>
|
||||
<div class="card-content">
|
||||
@@ -105,7 +36,8 @@ class HaConfigRepairsDashboard extends SubscribeMixin(LitElement) {
|
||||
<ha-config-repairs
|
||||
.hass=${this.hass}
|
||||
.narrow=${this.narrow}
|
||||
.repairsIssues=${issues}
|
||||
.repairsIssues=${this._repairsIssues}
|
||||
@update-issues=${this._fetchIssues}
|
||||
></ha-config-repairs>
|
||||
`
|
||||
: html`
|
||||
@@ -122,32 +54,13 @@ class HaConfigRepairsDashboard extends SubscribeMixin(LitElement) {
|
||||
`;
|
||||
}
|
||||
|
||||
private _showSystemInformationDialog(
|
||||
ev: CustomEvent<RequestSelectedDetail>
|
||||
): void {
|
||||
if (!shouldHandleRequestSelectedEvent(ev)) {
|
||||
return;
|
||||
private async _fetchIssues(): Promise<void> {
|
||||
this._repairsIssues = (await fetchRepairsIssues(this.hass)).issues;
|
||||
const integrations: Set<string> = new Set();
|
||||
for (const issue of this._repairsIssues) {
|
||||
integrations.add(issue.domain);
|
||||
}
|
||||
|
||||
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;
|
||||
this.hass.loadBackendTranslation("issues", [...integrations]);
|
||||
}
|
||||
|
||||
static styles = css`
|
||||
|
||||
@@ -2,6 +2,7 @@ import "@material/mwc-list/mwc-list";
|
||||
import { css, html, LitElement, TemplateResult } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { relativeTime } from "../../../common/datetime/relative_time";
|
||||
import { fireEvent } from "../../../common/dom/fire_event";
|
||||
import "../../../components/ha-alert";
|
||||
import "../../../components/ha-card";
|
||||
import "../../../components/ha-list-item";
|
||||
@@ -40,51 +41,43 @@ class HaConfigRepairs extends LitElement {
|
||||
})}
|
||||
</div>
|
||||
<mwc-list>
|
||||
${issues.map(
|
||||
(issue) => html`
|
||||
<ha-list-item
|
||||
twoline
|
||||
graphic="avatar"
|
||||
.hasMeta=${!this.narrow}
|
||||
.issue=${issue}
|
||||
class=${issue.ignored ? "ignored" : ""}
|
||||
@click=${this._openShowMoreDialog}
|
||||
>
|
||||
<img
|
||||
loading="lazy"
|
||||
src=${brandsUrl({
|
||||
domain: issue.domain,
|
||||
type: "icon",
|
||||
useFallback: true,
|
||||
darkOptimized: this.hass.themes?.darkMode,
|
||||
})}
|
||||
.title=${domainToName(this.hass.localize, issue.domain)}
|
||||
referrerpolicy="no-referrer"
|
||||
slot="graphic"
|
||||
/>
|
||||
<span
|
||||
>${this.hass.localize(
|
||||
`component.${issue.domain}.issues.${
|
||||
issue.translation_key || issue.issue_id
|
||||
}.title`
|
||||
)}</span
|
||||
>
|
||||
<span slot="secondary" class="secondary">
|
||||
${issue.created
|
||||
? relativeTime(new Date(issue.created), this.hass.locale)
|
||||
: ""}
|
||||
${issue.ignored
|
||||
? ` - ${this.hass.localize(
|
||||
"ui.panel.config.repairs.dialog.ignored_in_version_short",
|
||||
{ version: issue.dismissed_version }
|
||||
)}`
|
||||
: ""}
|
||||
</span>
|
||||
${!this.narrow
|
||||
? html`<ha-icon-next slot="meta"></ha-icon-next>`
|
||||
: ""}
|
||||
</ha-list-item>
|
||||
`
|
||||
${issues.map((issue) =>
|
||||
issue.ignored
|
||||
? ""
|
||||
: html`
|
||||
<ha-list-item
|
||||
twoline
|
||||
graphic="avatar"
|
||||
.hasMeta=${!this.narrow}
|
||||
.issue=${issue}
|
||||
@click=${this._openShowMoreDialog}
|
||||
>
|
||||
<img
|
||||
loading="lazy"
|
||||
src=${brandsUrl({
|
||||
domain: issue.domain,
|
||||
type: "icon",
|
||||
useFallback: true,
|
||||
darkOptimized: this.hass.themes?.darkMode,
|
||||
})}
|
||||
.title=${domainToName(this.hass.localize, issue.domain)}
|
||||
referrerpolicy="no-referrer"
|
||||
slot="graphic"
|
||||
/>
|
||||
<span
|
||||
>${this.hass.localize(
|
||||
`component.${issue.domain}.issues.${
|
||||
issue.translation_key || issue.issue_id
|
||||
}.title`
|
||||
)}</span
|
||||
>
|
||||
<span slot="secondary" class="secondary">
|
||||
${issue.created
|
||||
? relativeTime(new Date(issue.created), this.hass.locale)
|
||||
: ""}
|
||||
</span>
|
||||
</ha-list-item>
|
||||
`
|
||||
)}
|
||||
</mwc-list>
|
||||
`;
|
||||
@@ -93,11 +86,12 @@ class HaConfigRepairs extends LitElement {
|
||||
private _openShowMoreDialog(ev): void {
|
||||
const issue = ev.currentTarget.issue as RepairsIssue;
|
||||
if (issue.is_fixable) {
|
||||
showRepairsFlowDialog(this, issue);
|
||||
} else {
|
||||
showRepairsIssueDialog(this, {
|
||||
issue,
|
||||
showRepairsFlowDialog(this, issue, () => {
|
||||
// @ts-ignore
|
||||
fireEvent(this, "update-issues");
|
||||
});
|
||||
} else {
|
||||
showRepairsIssueDialog(this, { issue: (ev.currentTarget as any).issue });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -110,9 +104,6 @@ class HaConfigRepairs extends LitElement {
|
||||
padding: 16px;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
.ignored {
|
||||
opacity: var(--light-secondary-opacity);
|
||||
}
|
||||
button.show-more {
|
||||
color: var(--primary-color);
|
||||
text-align: left;
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
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: {},
|
||||
});
|
||||
};
|
||||
@@ -3,7 +3,6 @@ import type { RepairsIssue } from "../../../data/repairs";
|
||||
|
||||
export interface RepairsIssueDialogParams {
|
||||
issue: RepairsIssue;
|
||||
dialogClosedCallback?: () => void;
|
||||
}
|
||||
|
||||
export const loadRepairsIssueDialog = () => import("./dialog-repairs-issue");
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
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,
|
||||
});
|
||||
};
|
||||
@@ -1,15 +1,17 @@
|
||||
import "@material/mwc-button/mwc-button";
|
||||
import { UnsubscribeFunc } from "home-assistant-js-websocket";
|
||||
import { ActionDetail } from "@material/mwc-list";
|
||||
import "@material/mwc-list/mwc-list-item";
|
||||
import { mdiContentCopy } from "@mdi/js";
|
||||
import { UnsubscribeFunc } from "home-assistant-js-websocket/dist/types";
|
||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { isComponentLoaded } from "../../../common/config/is_component_loaded";
|
||||
import { formatDateTime } from "../../../common/datetime/format_date_time";
|
||||
import { fireEvent } from "../../../common/dom/fire_event";
|
||||
import { copyToClipboard } from "../../../common/util/copy-clipboard";
|
||||
import { subscribePollingCollection } from "../../../common/util/subscribe-polling";
|
||||
import "../../../components/ha-alert";
|
||||
import "../../../components/ha-button-menu";
|
||||
import "../../../components/ha-card";
|
||||
import { createCloseHeading } from "../../../components/ha-dialog";
|
||||
import "../../../components/ha-circular-progress";
|
||||
import "../../../components/ha-metric";
|
||||
import { fetchHassioStats, HassioStats } from "../../../data/hassio/common";
|
||||
import {
|
||||
@@ -23,11 +25,12 @@ import {
|
||||
SystemHealthInfo,
|
||||
} from "../../../data/system_health";
|
||||
import { showAlertDialog } from "../../../dialogs/generic/show-dialog-box";
|
||||
import { haStyleDialog } from "../../../resources/styles";
|
||||
import "../../../layouts/hass-subpage";
|
||||
import { SubscribeMixin } from "../../../mixins/subscribe-mixin";
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
import { documentationUrl } from "../../../util/documentation-url";
|
||||
import { showToast } from "../../../util/toast";
|
||||
import "../../../components/ha-circular-progress";
|
||||
import "./integrations-card";
|
||||
|
||||
const sortKeys = (a: string, b: string) => {
|
||||
if (a === "homeassistant") {
|
||||
@@ -50,40 +53,28 @@ export const UNHEALTHY_REASON_URL = {
|
||||
privileged: "/more-info/unsupported/privileged",
|
||||
};
|
||||
|
||||
@customElement("dialog-system-information")
|
||||
class DialogSystemInformation extends LitElement {
|
||||
@customElement("ha-config-system-health")
|
||||
class HaConfigSystemHealth extends SubscribeMixin(LitElement) {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@state() private _systemInfo?: SystemHealthInfo;
|
||||
@property({ type: Boolean }) public narrow!: boolean;
|
||||
|
||||
@state() private _resolutionInfo?: HassioResolution;
|
||||
@state() private _info?: SystemHealthInfo;
|
||||
|
||||
@state() private _supervisorStats?: HassioStats;
|
||||
|
||||
@state() private _resolutionInfo?: HassioResolution;
|
||||
|
||||
@state() private _coreStats?: HassioStats;
|
||||
|
||||
@state() private _opened = false;
|
||||
@state() private _error?: { code: string; message: string };
|
||||
|
||||
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 {
|
||||
public hassSubscribe(): Array<UnsubscribeFunc | Promise<UnsubscribeFunc>> {
|
||||
const subs: Array<UnsubscribeFunc | Promise<UnsubscribeFunc>> = [];
|
||||
if (isComponentLoaded(this.hass, "system_health")) {
|
||||
subs.push(
|
||||
subscribeSystemHealthInfo(this.hass!, (info) => {
|
||||
this._systemInfo = info;
|
||||
this._info = info;
|
||||
})
|
||||
);
|
||||
}
|
||||
@@ -102,51 +93,149 @@ class DialogSystemInformation extends LitElement {
|
||||
10000
|
||||
)
|
||||
);
|
||||
|
||||
fetchHassioResolution(this.hass).then((data) => {
|
||||
this._resolutionInfo = data;
|
||||
});
|
||||
}
|
||||
|
||||
this._subscriptions = subs;
|
||||
return subs;
|
||||
}
|
||||
|
||||
private _unsubscribe() {
|
||||
while (this._subscriptions?.length) {
|
||||
const unsub = this._subscriptions.pop()!;
|
||||
if (unsub instanceof Promise) {
|
||||
unsub.then((unsubFunc) => unsubFunc());
|
||||
} else {
|
||||
unsub();
|
||||
}
|
||||
}
|
||||
this._subscriptions = undefined;
|
||||
protected firstUpdated(changedProps) {
|
||||
super.firstUpdated(changedProps);
|
||||
|
||||
this._systemInfo = undefined;
|
||||
this._resolutionInfo = undefined;
|
||||
this._coreStats = undefined;
|
||||
this._supervisorStats = undefined;
|
||||
this.hass!.loadBackendTranslation("system_health");
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
if (!this._opened) {
|
||||
return html``;
|
||||
const sections: TemplateResult[] = [];
|
||||
|
||||
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`
|
||||
<ha-dialog
|
||||
open
|
||||
@closed=${this.closeDialog}
|
||||
scrimClickAction
|
||||
escapeKeyAction
|
||||
.heading=${createCloseHeading(
|
||||
this.hass,
|
||||
this.hass.localize("ui.panel.config.repairs.system_information")
|
||||
)}
|
||||
<hass-subpage
|
||||
.hass=${this.hass}
|
||||
.narrow=${this.narrow}
|
||||
back-path="/config/system"
|
||||
.header=${this.hass.localize("ui.panel.config.system_health.caption")}
|
||||
>
|
||||
<div>
|
||||
${this._error
|
||||
? 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
|
||||
? html`${this._resolutionInfo.unhealthy.length
|
||||
? html`<ha-alert alert-type="error">
|
||||
@@ -176,63 +265,66 @@ class DialogSystemInformation extends LitElement {
|
||||
: ""} `
|
||||
: ""}
|
||||
|
||||
<div>${sections}</div>
|
||||
|
||||
<ha-card outlined>
|
||||
<div class="card-content">${sections}</div>
|
||||
</ha-card>
|
||||
${!this._coreStats && !this._supervisorStats
|
||||
? ""
|
||||
: html`
|
||||
<div>
|
||||
${this._coreStats
|
||||
? html`
|
||||
<h3>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.system_health.core_stats"
|
||||
)}
|
||||
</h3>
|
||||
<ha-metric
|
||||
.heading=${this.hass.localize(
|
||||
"ui.panel.config.system_health.cpu_usage"
|
||||
)}
|
||||
.value=${this._coreStats.cpu_percent}
|
||||
></ha-metric>
|
||||
<ha-metric
|
||||
.heading=${this.hass.localize(
|
||||
"ui.panel.config.system_health.ram_usage"
|
||||
)}
|
||||
.value=${this._coreStats.memory_percent}
|
||||
></ha-metric>
|
||||
`
|
||||
: ""}
|
||||
${this._supervisorStats
|
||||
? html`
|
||||
<h3>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.system_health.supervisor_stats"
|
||||
)}
|
||||
</h3>
|
||||
<ha-metric
|
||||
.heading=${this.hass.localize(
|
||||
"ui.panel.config.system_health.cpu_usage"
|
||||
)}
|
||||
.value=${this._supervisorStats.cpu_percent}
|
||||
></ha-metric>
|
||||
<ha-metric
|
||||
.heading=${this.hass.localize(
|
||||
"ui.panel.config.system_health.ram_usage"
|
||||
)}
|
||||
.value=${this._supervisorStats.memory_percent}
|
||||
></ha-metric>
|
||||
`
|
||||
: ""}
|
||||
</div>
|
||||
<ha-card outlined>
|
||||
<div class="card-content">
|
||||
${this._coreStats
|
||||
? html`
|
||||
<h3>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.system_health.core_stats"
|
||||
)}
|
||||
</h3>
|
||||
<ha-metric
|
||||
.heading=${this.hass.localize(
|
||||
"ui.panel.config.system_health.cpu_usage"
|
||||
)}
|
||||
.value=${this._coreStats.cpu_percent}
|
||||
></ha-metric>
|
||||
<ha-metric
|
||||
.heading=${this.hass.localize(
|
||||
"ui.panel.config.system_health.ram_usage"
|
||||
)}
|
||||
.value=${this._coreStats.memory_percent}
|
||||
></ha-metric>
|
||||
`
|
||||
: ""}
|
||||
${this._supervisorStats
|
||||
? html`
|
||||
<h3>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.system_health.supervisor_stats"
|
||||
)}
|
||||
</h3>
|
||||
<ha-metric
|
||||
.heading=${this.hass.localize(
|
||||
"ui.panel.config.system_health.cpu_usage"
|
||||
)}
|
||||
.value=${this._supervisorStats.cpu_percent}
|
||||
></ha-metric>
|
||||
<ha-metric
|
||||
.heading=${this.hass.localize(
|
||||
"ui.panel.config.system_health.ram_usage"
|
||||
)}
|
||||
.value=${this._supervisorStats.memory_percent}
|
||||
></ha-metric>
|
||||
`
|
||||
: ""}
|
||||
</div>
|
||||
</ha-card>
|
||||
`}
|
||||
|
||||
<integrations-card
|
||||
.hass=${this.hass}
|
||||
.narrow=${this.narrow}
|
||||
></integrations-card>
|
||||
</div>
|
||||
<mwc-button
|
||||
slot="primaryAction"
|
||||
.label=${this.hass.localize("ui.panel.config.repairs.copy")}
|
||||
@click=${this._copyInfo}
|
||||
></mwc-button>
|
||||
</ha-dialog>
|
||||
</hass-subpage>
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -294,111 +386,17 @@ class DialogSystemInformation extends LitElement {
|
||||
});
|
||||
}
|
||||
|
||||
private _getSections(): TemplateResult[] {
|
||||
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> {
|
||||
private async _copyInfo(ev: CustomEvent<ActionDetail>): Promise<void> {
|
||||
const github = ev.detail.index === 1;
|
||||
let haContent: string | undefined;
|
||||
const domainParts: string[] = [];
|
||||
|
||||
for (const domain of Object.keys(this._systemInfo!).sort(sortKeys)) {
|
||||
const domainInfo = this._systemInfo![domain];
|
||||
for (const domain of Object.keys(this._info!).sort(sortKeys)) {
|
||||
const domainInfo = this._info![domain];
|
||||
let first = true;
|
||||
const parts = [
|
||||
`${
|
||||
domain !== "homeassistant"
|
||||
github && domain !== "homeassistant"
|
||||
? `<details><summary>${domainToName(
|
||||
this.hass.localize,
|
||||
domain
|
||||
@@ -410,7 +408,7 @@ class DialogSystemInformation extends LitElement {
|
||||
for (const key of Object.keys(domainInfo.info)) {
|
||||
let value: unknown;
|
||||
|
||||
if (domainInfo.info[key] && typeof domainInfo.info[key] === "object") {
|
||||
if (typeof domainInfo.info[key] === "object") {
|
||||
const info = domainInfo.info[key] as SystemCheckValueObject;
|
||||
|
||||
if (info.type === "pending") {
|
||||
@@ -423,11 +421,11 @@ class DialogSystemInformation extends LitElement {
|
||||
} else {
|
||||
value = domainInfo.info[key];
|
||||
}
|
||||
if (first) {
|
||||
if (github && first) {
|
||||
parts.push(`${key} | ${value}\n-- | --`);
|
||||
first = false;
|
||||
} else {
|
||||
parts.push(`${key} | ${value}`);
|
||||
parts.push(`${key}${github ? " | " : ": "}${value}`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -435,14 +433,16 @@ class DialogSystemInformation extends LitElement {
|
||||
haContent = parts.join("\n");
|
||||
} else {
|
||||
domainParts.push(parts.join("\n"));
|
||||
if (domain !== "homeassistant") {
|
||||
if (github && domain !== "homeassistant") {
|
||||
domainParts.push("</details>");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
await copyToClipboard(
|
||||
`${"## "}System Information\n${haContent}\n\n${domainParts.join("\n\n")}`
|
||||
`${github ? "## " : ""}System Health\n${haContent}\n\n${domainParts.join(
|
||||
"\n\n"
|
||||
)}`
|
||||
);
|
||||
|
||||
showToast(this, {
|
||||
@@ -450,50 +450,73 @@ class DialogSystemInformation extends LitElement {
|
||||
});
|
||||
}
|
||||
|
||||
static styles: CSSResultGroup = [
|
||||
haStyleDialog,
|
||||
css`
|
||||
ha-alert {
|
||||
margin-bottom: 16px;
|
||||
display: block;
|
||||
}
|
||||
table {
|
||||
width: 100%;
|
||||
}
|
||||
static styles: CSSResultGroup = css`
|
||||
.content {
|
||||
padding: 28px 20px 0;
|
||||
max-width: 1040px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
integrations-card {
|
||||
max-width: 600px;
|
||||
display: block;
|
||||
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 {
|
||||
width: 45%;
|
||||
}
|
||||
td:first-child {
|
||||
width: 45%;
|
||||
}
|
||||
|
||||
td:last-child {
|
||||
direction: ltr;
|
||||
}
|
||||
td:last-child {
|
||||
direction: ltr;
|
||||
}
|
||||
|
||||
.loading-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.loading-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.card-header {
|
||||
justify-content: space-between;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.card-header {
|
||||
justify-content: space-between;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.error {
|
||||
color: var(--error-color);
|
||||
}
|
||||
.error {
|
||||
color: var(--error-color);
|
||||
}
|
||||
|
||||
a.manage {
|
||||
text-decoration: none;
|
||||
}
|
||||
`,
|
||||
];
|
||||
a {
|
||||
color: var(--primary-color);
|
||||
}
|
||||
|
||||
a.manage {
|
||||
text-decoration: none;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"dialog-system-information": DialogSystemInformation;
|
||||
"ha-config-system-health": HaConfigSystemHealth;
|
||||
}
|
||||
}
|
||||
@@ -21,8 +21,8 @@ import type { HomeAssistant } from "../../../types";
|
||||
import { brandsUrl } from "../../../util/brands-url";
|
||||
import { documentationUrl } from "../../../util/documentation-url";
|
||||
|
||||
@customElement("integrations-startup-time")
|
||||
class IntegrationsStartupTime extends LitElement {
|
||||
@customElement("integrations-card")
|
||||
class IntegrationsCard extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property({ type: Boolean }) public narrow = false;
|
||||
@@ -45,47 +45,57 @@ class IntegrationsStartupTime extends LitElement {
|
||||
}
|
||||
|
||||
return html`
|
||||
<mwc-list>
|
||||
${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
|
||||
: "";
|
||||
<ha-card
|
||||
outlined
|
||||
.header=${this.hass.localize(
|
||||
"ui.panel.config.system_health.integration_start_time"
|
||||
)}
|
||||
>
|
||||
<mwc-list>
|
||||
${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);
|
||||
return html`
|
||||
<ha-clickable-list-item
|
||||
graphic="avatar"
|
||||
twoline
|
||||
hasMeta
|
||||
openNewTab
|
||||
@click=${this._entryClicked}
|
||||
href=${docLink}
|
||||
>
|
||||
<img
|
||||
loading="lazy"
|
||||
src=${brandsUrl({
|
||||
domain: setup.domain,
|
||||
type: "icon",
|
||||
useFallback: true,
|
||||
darkOptimized: this.hass.themes?.darkMode,
|
||||
})}
|
||||
referrerpolicy="no-referrer"
|
||||
slot="graphic"
|
||||
/>
|
||||
<span>
|
||||
${domainToName(this.hass.localize, setup.domain, manifest)}
|
||||
</span>
|
||||
<span slot="secondary">${setup.domain}</span>
|
||||
<div slot="meta">
|
||||
${setupSeconds ? html`${setupSeconds} s` : ""}
|
||||
</div>
|
||||
</ha-clickable-list-item>
|
||||
`;
|
||||
})}
|
||||
</mwc-list>
|
||||
const setupSeconds = setup.seconds?.toFixed(2);
|
||||
return html`
|
||||
<ha-clickable-list-item
|
||||
graphic="avatar"
|
||||
twoline
|
||||
hasMeta
|
||||
openNewTab
|
||||
@click=${this._entryClicked}
|
||||
href=${docLink}
|
||||
>
|
||||
<img
|
||||
loading="lazy"
|
||||
src=${brandsUrl({
|
||||
domain: setup.domain,
|
||||
type: "icon",
|
||||
useFallback: true,
|
||||
darkOptimized: this.hass.themes?.darkMode,
|
||||
})}
|
||||
referrerpolicy="no-referrer"
|
||||
slot="graphic"
|
||||
/>
|
||||
<span>
|
||||
${domainToName(this.hass.localize, setup.domain, manifest)}
|
||||
</span>
|
||||
<span slot="secondary">${setup.domain}</span>
|
||||
<div slot="meta">
|
||||
${setupSeconds ? html`${setupSeconds} s` : ""}
|
||||
</div>
|
||||
</ha-clickable-list-item>
|
||||
`;
|
||||
})}
|
||||
</mwc-list>
|
||||
</ha-card>
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -139,6 +149,6 @@ class IntegrationsStartupTime extends LitElement {
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"integrations-startup-time": IntegrationsStartupTime;
|
||||
"integrations-card": IntegrationsCard;
|
||||
}
|
||||
}
|
||||
@@ -30,13 +30,13 @@ import { showUserDetailDialog } from "./show-dialog-user-detail";
|
||||
export class HaConfigUsers extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property({ attribute: false }) public _users: User[] = [];
|
||||
@property() public _users: User[] = [];
|
||||
|
||||
@property({ type: Boolean }) public isWide!: boolean;
|
||||
@property() public isWide!: boolean;
|
||||
|
||||
@property({ type: Boolean }) public narrow!: boolean;
|
||||
@property() public narrow!: boolean;
|
||||
|
||||
@property({ attribute: false }) public route!: Route;
|
||||
@property() public route!: Route;
|
||||
|
||||
private _columns = memoizeOne(
|
||||
(narrow: boolean, localize: LocalizeFunc): DataTableColumnContainer => {
|
||||
@@ -76,8 +76,7 @@ export class HaConfigUsers extends LitElement {
|
||||
width: "20%",
|
||||
direction: "asc",
|
||||
hidden: narrow,
|
||||
template: (groupIds: User["group_ids"]) =>
|
||||
html` ${localize(`groups.${groupIds[0]}`)} `,
|
||||
template: (groupIds) => html` ${localize(`groups.${groupIds[0]}`)} `,
|
||||
},
|
||||
is_active: {
|
||||
title: this.hass.localize(
|
||||
@@ -239,9 +238,3 @@ export class HaConfigUsers extends LitElement {
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-config-users": HaConfigUsers;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -306,9 +306,6 @@ export class HuiHumidifierCard extends LitElement implements LovelaceCard {
|
||||
border-radius: 100%;
|
||||
color: var(--secondary-text-color);
|
||||
z-index: 25;
|
||||
inset-inline-start: initial;
|
||||
inset-inline-end: 0;
|
||||
direction: var(--direction);
|
||||
}
|
||||
|
||||
.content {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import "@material/mwc-button";
|
||||
import { mdiHelpCircle } from "@mdi/js";
|
||||
import { CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { fireEvent } from "../../../common/dom/fire_event";
|
||||
import { computeRTLDirection } from "../../../common/util/compute_rtl";
|
||||
@@ -197,7 +197,14 @@ export class HuiSaveConfig extends LitElement implements HassDialog {
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return [haStyleDialog];
|
||||
return [
|
||||
haStyleDialog,
|
||||
css`
|
||||
ha-switch {
|
||||
padding-bottom: 16px;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1218,7 +1218,7 @@
|
||||
"title": "{count} {count, plural,\n one {update}\n other {updates}\n}",
|
||||
"unable_to_fetch": "Unable to load updates",
|
||||
"version_available": "Version {version_available} is available",
|
||||
"more_updates": "Show all updates",
|
||||
"more_updates": "+{count} updates",
|
||||
"show": "show",
|
||||
"show_skipped": "Show skipped",
|
||||
"hide_skipped": "Hide skipped",
|
||||
@@ -1231,25 +1231,11 @@
|
||||
"description": "Find and fix issues with your Home Assistant instance",
|
||||
"title": "{count} {count, plural,\n one {repair}\n other {repairs}\n}",
|
||||
"no_repairs": "There are currently no repairs available",
|
||||
"more_repairs": "Show all repairs",
|
||||
"show_ignored": "Show ignored",
|
||||
"hide_ignored": "Hide ignored",
|
||||
"critical": "Critical",
|
||||
"error": "Error",
|
||||
"warning": "Warning",
|
||||
"system_information": "System information",
|
||||
"integration_startup_time": "Integration startup time",
|
||||
"copy": "Copy",
|
||||
"more_repairs": "+{count} repairs",
|
||||
"dialog": {
|
||||
"title": "Repair",
|
||||
"fix": "Repair",
|
||||
"learn": "Learn more",
|
||||
"ignore": "Ignore",
|
||||
"unignore": "Unignore",
|
||||
"alert_not_fixable": "We can not repair this issue for you.",
|
||||
"breaks_in_version": "This will break in version {version}. Please fix this issue before upgrading.",
|
||||
"ignored_in_version_short": "Ignored in version {version}",
|
||||
"ignored_in_version": "This issue was ignored in version {version}."
|
||||
"learn": "Learn more"
|
||||
}
|
||||
},
|
||||
"areas": {
|
||||
|
||||
Reference in New Issue
Block a user