mirror of
https://github.com/home-assistant/frontend.git
synced 2026-04-02 08:33:31 +01:00
Add configuration to built-in panels (#29572)
Co-authored-by: Norbert Rittel <norbert@rittel.de> Co-authored-by: Petar Petrov <MindFreeze@users.noreply.github.com>
This commit is contained in:
@@ -8,12 +8,17 @@ import {
|
||||
mdiPlayBoxMultiple,
|
||||
mdiTooltipAccount,
|
||||
} from "@mdi/js";
|
||||
import type { HomeAssistant, PanelInfo } from "../types";
|
||||
import type { PageNavigation } from "../layouts/hass-tabs-subpage";
|
||||
import type { LocalizeKeys } from "../common/translations/localize";
|
||||
import type { PageNavigation } from "../layouts/hass-tabs-subpage";
|
||||
import type { HomeAssistant, PanelInfo } from "../types";
|
||||
|
||||
export const HOME_PANEL = "home";
|
||||
export const NOT_FOUND_PANEL = "notfound";
|
||||
export const PROFILE_PANEL = "profile";
|
||||
export const LOVELACE_PANEL = "lovelace";
|
||||
|
||||
/** Panel to show when no panel is picked. */
|
||||
export const DEFAULT_PANEL = "home";
|
||||
export const DEFAULT_PANEL = HOME_PANEL;
|
||||
|
||||
export const hasLegacyOverviewPanel = (hass: HomeAssistant): boolean =>
|
||||
Boolean(hass.panels.lovelace?.config);
|
||||
@@ -30,7 +35,7 @@ export const getDefaultPanelUrlPath = (hass: HomeAssistant): string => {
|
||||
getLegacyDefaultPanelUrlPath() ||
|
||||
DEFAULT_PANEL;
|
||||
// If default panel is lovelace and no old overview exists, fall back to home
|
||||
if (defaultPanel === "lovelace" && !hasLegacyOverviewPanel(hass)) {
|
||||
if (defaultPanel === LOVELACE_PANEL && !hasLegacyOverviewPanel(hass)) {
|
||||
return DEFAULT_PANEL;
|
||||
}
|
||||
return defaultPanel;
|
||||
@@ -39,12 +44,16 @@ export const getDefaultPanelUrlPath = (hass: HomeAssistant): string => {
|
||||
export const getDefaultPanel = (hass: HomeAssistant): PanelInfo => {
|
||||
const panel = getDefaultPanelUrlPath(hass);
|
||||
|
||||
return (panel ? hass.panels[panel] : undefined) ?? hass.panels[DEFAULT_PANEL];
|
||||
return (
|
||||
(panel ? hass.panels[panel] : undefined) ??
|
||||
hass.panels[DEFAULT_PANEL] ??
|
||||
hass.panels[NOT_FOUND_PANEL]
|
||||
);
|
||||
};
|
||||
|
||||
export const getPanelNameTranslationKey = (panel: PanelInfo) => {
|
||||
if (panel.url_path === "profile") {
|
||||
return "panel.profile" as const;
|
||||
if ([PROFILE_PANEL, NOT_FOUND_PANEL].includes(panel.url_path)) {
|
||||
return `panel.${panel.url_path}` as const;
|
||||
}
|
||||
|
||||
return `panel.${panel.title}` as const;
|
||||
@@ -137,4 +146,22 @@ export const PANEL_ICON_PATHS = {
|
||||
export const getPanelIconPath = (panel: PanelInfo): string | undefined =>
|
||||
PANEL_ICON_PATHS[panel.url_path];
|
||||
|
||||
export const FIXED_PANELS = ["profile", "config"];
|
||||
export const FIXED_PANELS = [PROFILE_PANEL, "config", NOT_FOUND_PANEL];
|
||||
|
||||
export interface PanelMutableParams {
|
||||
title?: string | null;
|
||||
icon?: string | null;
|
||||
require_admin?: boolean | null;
|
||||
show_in_sidebar?: boolean | null;
|
||||
}
|
||||
|
||||
export const updatePanel = (
|
||||
hass: HomeAssistant,
|
||||
urlPath: string,
|
||||
updates: PanelMutableParams
|
||||
) =>
|
||||
hass.callWS({
|
||||
type: "frontend/update_panel",
|
||||
url_path: urlPath,
|
||||
...updates,
|
||||
});
|
||||
|
||||
@@ -35,6 +35,7 @@ const COMPONENTS = {
|
||||
security: () => import("../panels/security/ha-panel-security"),
|
||||
climate: () => import("../panels/climate/ha-panel-climate"),
|
||||
home: () => import("../panels/home/ha-panel-home"),
|
||||
notfound: () => import("../panels/notfound/ha-panel-notfound"),
|
||||
};
|
||||
|
||||
@customElement("partial-panel-resolver")
|
||||
|
||||
@@ -58,7 +58,8 @@ class PanelClimate extends LitElement {
|
||||
oldHass.entities !== this.hass.entities ||
|
||||
oldHass.devices !== this.hass.devices ||
|
||||
oldHass.areas !== this.hass.areas ||
|
||||
oldHass.floors !== this.hass.floors
|
||||
oldHass.floors !== this.hass.floors ||
|
||||
oldHass.panels !== this.hass.panels
|
||||
) {
|
||||
if (this.hass.config.state === "RUNNING") {
|
||||
this._debounceRegistriesChanged();
|
||||
|
||||
@@ -103,10 +103,12 @@ const processAreasForClimate = (
|
||||
heading_style: "subtitle",
|
||||
type: "heading",
|
||||
heading: area.name,
|
||||
tap_action: {
|
||||
action: "navigate",
|
||||
navigation_path: `/home/areas-${area.area_id}`,
|
||||
},
|
||||
tap_action: hass.panels.home
|
||||
? {
|
||||
action: "navigate",
|
||||
navigation_path: `/home/areas-${area.area_id}`,
|
||||
}
|
||||
: undefined,
|
||||
});
|
||||
cards.push(...areaCards);
|
||||
}
|
||||
|
||||
@@ -102,11 +102,15 @@ export class DialogLovelaceDashboardDetail extends LitElement {
|
||||
: html`
|
||||
<ha-form
|
||||
autofocus
|
||||
.schema=${this._schema(this._params)}
|
||||
.schema=${this._schema(
|
||||
this._params,
|
||||
this._data?.require_admin
|
||||
)}
|
||||
.data=${this._data}
|
||||
.hass=${this.hass}
|
||||
.error=${this._error}
|
||||
.computeLabel=${this._computeLabel}
|
||||
.computeHelper=${this._computeHelper}
|
||||
@value-changed=${this._valueChanged}
|
||||
></ha-form>
|
||||
`}
|
||||
@@ -155,7 +159,7 @@ export class DialogLovelaceDashboardDetail extends LitElement {
|
||||
}
|
||||
|
||||
private _schema = memoizeOne(
|
||||
(params: LovelaceDashboardDetailsDialogParams) =>
|
||||
(params: LovelaceDashboardDetailsDialogParams, requireAdmin?: boolean) =>
|
||||
[
|
||||
{
|
||||
name: "title",
|
||||
@@ -183,6 +187,7 @@ export class DialogLovelaceDashboardDetail extends LitElement {
|
||||
{
|
||||
name: "require_admin",
|
||||
required: true,
|
||||
disabled: params.isDefault && !requireAdmin,
|
||||
selector: {
|
||||
boolean: {},
|
||||
},
|
||||
@@ -210,6 +215,15 @@ export class DialogLovelaceDashboardDetail extends LitElement {
|
||||
}`
|
||||
);
|
||||
|
||||
private _computeHelper = (
|
||||
entry: SchemaUnion<ReturnType<typeof this._schema>>
|
||||
): string =>
|
||||
entry.name === "require_admin" && entry.disabled
|
||||
? this.hass.localize(
|
||||
"ui.panel.config.lovelace.dashboards.panel_detail.require_admin_helper"
|
||||
)
|
||||
: "";
|
||||
|
||||
private _valueChanged(ev: CustomEvent) {
|
||||
this._error = undefined;
|
||||
const value = ev.detail.value;
|
||||
|
||||
247
src/panels/config/lovelace/dashboards/dialog-panel-detail.ts
Normal file
247
src/panels/config/lovelace/dashboards/dialog-panel-detail.ts
Normal file
@@ -0,0 +1,247 @@
|
||||
import { mdiDotsVertical, mdiRestart } from "@mdi/js";
|
||||
import { html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import "../../../../components/ha-button";
|
||||
import "../../../../components/ha-dialog-footer";
|
||||
import "../../../../components/ha-dropdown";
|
||||
import "../../../../components/ha-dropdown-item";
|
||||
import "../../../../components/ha-form/ha-form";
|
||||
import "../../../../components/ha-icon-button";
|
||||
import "../../../../components/ha-svg-icon";
|
||||
import "../../../../components/ha-dialog";
|
||||
import type { SchemaUnion } from "../../../../components/ha-form/types";
|
||||
import type { PanelMutableParams } from "../../../../data/panel";
|
||||
import { haStyleDialog } from "../../../../resources/styles";
|
||||
import type { HomeAssistant } from "../../../../types";
|
||||
import type { PanelDetailDialogParams } from "./show-dialog-panel-detail";
|
||||
|
||||
interface PanelDetailData {
|
||||
title: string;
|
||||
icon?: string;
|
||||
require_admin: boolean;
|
||||
show_in_sidebar: boolean;
|
||||
}
|
||||
|
||||
@customElement("dialog-panel-detail")
|
||||
export class DialogPanelDetail extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@state() private _params?: PanelDetailDialogParams;
|
||||
|
||||
@state() private _data?: PanelDetailData;
|
||||
|
||||
@state() private _error?: Record<string, string>;
|
||||
|
||||
@state() private _submitting = false;
|
||||
|
||||
@state() private _open = false;
|
||||
|
||||
public showDialog(params: PanelDetailDialogParams): void {
|
||||
this._params = params;
|
||||
this._error = undefined;
|
||||
this._data = {
|
||||
title: params.title,
|
||||
icon: params.icon,
|
||||
require_admin: params.requireAdmin,
|
||||
show_in_sidebar: params.showInSidebar,
|
||||
};
|
||||
this._open = true;
|
||||
}
|
||||
|
||||
public closeDialog(): void {
|
||||
this._open = false;
|
||||
}
|
||||
|
||||
private _dialogClosed(): void {
|
||||
this._params = undefined;
|
||||
this._data = undefined;
|
||||
fireEvent(this, "dialog-closed", { dialog: this.localName });
|
||||
}
|
||||
|
||||
protected render() {
|
||||
if (!this._params || !this._data) {
|
||||
return nothing;
|
||||
}
|
||||
|
||||
const titleInvalid = !this._data.title || !this._data.title.trim();
|
||||
|
||||
return html`
|
||||
<ha-dialog
|
||||
.hass=${this.hass}
|
||||
.open=${this._open}
|
||||
prevent-scrim-close
|
||||
header-title=${this.hass.localize(
|
||||
"ui.panel.config.lovelace.dashboards.panel_detail.edit_panel"
|
||||
)}
|
||||
@closed=${this._dialogClosed}
|
||||
>
|
||||
<ha-dropdown slot="headerActionItems" placement="bottom-end">
|
||||
<ha-icon-button
|
||||
slot="trigger"
|
||||
.label=${this.hass.localize("ui.common.menu")}
|
||||
.path=${mdiDotsVertical}
|
||||
></ha-icon-button>
|
||||
<ha-dropdown-item @click=${this._resetPanel}>
|
||||
<ha-svg-icon slot="icon" .path=${mdiRestart}></ha-svg-icon>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.lovelace.dashboards.panel_detail.reset_to_default"
|
||||
)}
|
||||
</ha-dropdown-item>
|
||||
</ha-dropdown>
|
||||
<ha-form
|
||||
autofocus
|
||||
.schema=${this._schema(
|
||||
this._params.isDefault,
|
||||
this._data.require_admin
|
||||
)}
|
||||
.data=${this._data}
|
||||
.hass=${this.hass}
|
||||
.error=${this._error}
|
||||
.computeLabel=${this._computeLabel}
|
||||
.computeHelper=${this._computeHelper}
|
||||
@value-changed=${this._valueChanged}
|
||||
></ha-form>
|
||||
|
||||
<ha-dialog-footer slot="footer">
|
||||
<ha-button
|
||||
appearance="plain"
|
||||
slot="secondaryAction"
|
||||
@click=${this.closeDialog}
|
||||
>
|
||||
${this.hass.localize("ui.common.cancel")}
|
||||
</ha-button>
|
||||
<ha-button
|
||||
slot="primaryAction"
|
||||
@click=${this._updatePanel}
|
||||
.disabled=${titleInvalid || this._submitting}
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.lovelace.dashboards.detail.update"
|
||||
)}
|
||||
</ha-button>
|
||||
</ha-dialog-footer>
|
||||
</ha-dialog>
|
||||
`;
|
||||
}
|
||||
|
||||
private _schema = memoizeOne(
|
||||
(isDefault: boolean, requireAdmin: boolean) =>
|
||||
[
|
||||
{
|
||||
name: "title",
|
||||
required: true,
|
||||
selector: { text: {} },
|
||||
},
|
||||
{
|
||||
name: "icon",
|
||||
required: false,
|
||||
selector: { icon: {} },
|
||||
},
|
||||
{
|
||||
name: "require_admin",
|
||||
required: true,
|
||||
disabled: isDefault && !requireAdmin,
|
||||
selector: { boolean: {} },
|
||||
},
|
||||
{
|
||||
name: "show_in_sidebar",
|
||||
required: true,
|
||||
selector: { boolean: {} },
|
||||
},
|
||||
] as const
|
||||
);
|
||||
|
||||
private _computeLabel = (
|
||||
entry: SchemaUnion<ReturnType<typeof this._schema>>
|
||||
): string =>
|
||||
this.hass.localize(
|
||||
`ui.panel.config.lovelace.dashboards.panel_detail.${entry.name}`
|
||||
);
|
||||
|
||||
private _computeHelper = (
|
||||
entry: SchemaUnion<ReturnType<typeof this._schema>>
|
||||
): string =>
|
||||
entry.name === "require_admin" && entry.disabled
|
||||
? this.hass.localize(
|
||||
"ui.panel.config.lovelace.dashboards.panel_detail.require_admin_helper"
|
||||
)
|
||||
: "";
|
||||
|
||||
private _valueChanged(ev: CustomEvent) {
|
||||
this._error = undefined;
|
||||
this._data = ev.detail.value;
|
||||
}
|
||||
|
||||
private async _handleError(err: any) {
|
||||
let localizedErrorMessage: string | undefined;
|
||||
if (err?.translation_domain && err?.translation_key) {
|
||||
const localize = await this.hass.loadBackendTranslation(
|
||||
"exceptions",
|
||||
err.translation_domain
|
||||
);
|
||||
localizedErrorMessage = localize(
|
||||
`component.${err.translation_domain}.exceptions.${err.translation_key}.message`,
|
||||
err.translation_placeholders
|
||||
);
|
||||
}
|
||||
this._error = {
|
||||
base: localizedErrorMessage || err?.message || "Unknown error",
|
||||
};
|
||||
}
|
||||
|
||||
private async _resetPanel() {
|
||||
this._submitting = true;
|
||||
try {
|
||||
await this._params!.updatePanel({
|
||||
title: null,
|
||||
icon: null,
|
||||
require_admin: null,
|
||||
show_in_sidebar: null,
|
||||
});
|
||||
this.closeDialog();
|
||||
} catch (err: any) {
|
||||
this._handleError(err);
|
||||
} finally {
|
||||
this._submitting = false;
|
||||
}
|
||||
}
|
||||
|
||||
private async _updatePanel() {
|
||||
this._submitting = true;
|
||||
try {
|
||||
const updates: PanelMutableParams = {};
|
||||
|
||||
if (this._data!.title !== this._params!.title) {
|
||||
updates.title = this._data!.title;
|
||||
}
|
||||
if ((this._data!.icon || undefined) !== this._params!.icon) {
|
||||
updates.icon = this._data!.icon || null;
|
||||
}
|
||||
if (this._data!.require_admin !== this._params!.requireAdmin) {
|
||||
updates.require_admin = this._data!.require_admin;
|
||||
}
|
||||
if (this._data!.show_in_sidebar !== this._params!.showInSidebar) {
|
||||
updates.show_in_sidebar = this._data!.show_in_sidebar;
|
||||
}
|
||||
|
||||
if (Object.keys(updates).length > 0) {
|
||||
await this._params!.updatePanel(updates);
|
||||
}
|
||||
this.closeDialog();
|
||||
} catch (err: any) {
|
||||
this._handleError(err);
|
||||
} finally {
|
||||
this._submitting = false;
|
||||
}
|
||||
}
|
||||
|
||||
static styles = haStyleDialog;
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"dialog-panel-detail": DialogPanelDetail;
|
||||
}
|
||||
}
|
||||
@@ -50,8 +50,12 @@ import {
|
||||
DEFAULT_PANEL,
|
||||
getPanelIcon,
|
||||
getPanelTitle,
|
||||
updatePanel,
|
||||
} from "../../../../data/panel";
|
||||
import { showConfirmationDialog } from "../../../../dialogs/generic/show-dialog-box";
|
||||
import {
|
||||
showAlertDialog,
|
||||
showConfirmationDialog,
|
||||
} from "../../../../dialogs/generic/show-dialog-box";
|
||||
import "../../../../layouts/hass-loading-screen";
|
||||
import "../../../../layouts/hass-tabs-subpage-data-table";
|
||||
import type { HomeAssistant, Route } from "../../../../types";
|
||||
@@ -60,6 +64,7 @@ import { showNewDashboardDialog } from "../../dashboard/show-dialog-new-dashboar
|
||||
import { lovelaceTabs } from "../ha-config-lovelace";
|
||||
import { showDashboardConfigureStrategyDialog } from "./show-dialog-lovelace-dashboard-configure-strategy";
|
||||
import { showDashboardDetailDialog } from "./show-dialog-lovelace-dashboard-detail";
|
||||
import { showPanelDetailDialog } from "./show-dialog-panel-detail";
|
||||
|
||||
export const PANEL_DASHBOARDS = [
|
||||
"home",
|
||||
@@ -282,6 +287,17 @@ export class HaConfigLovelaceDashboards extends LitElement {
|
||||
action: () => this._handleSetAsDefault(dashboard),
|
||||
disabled: dashboard.default,
|
||||
},
|
||||
...(dashboard.type === "built_in"
|
||||
? [
|
||||
{
|
||||
path: mdiPencil,
|
||||
label: this.hass.localize(
|
||||
"ui.panel.config.lovelace.dashboards.picker.edit"
|
||||
),
|
||||
action: () => this._handleEditPanel(dashboard),
|
||||
},
|
||||
]
|
||||
: []),
|
||||
...(dashboard.type === "user_created" &&
|
||||
dashboard.mode === "storage"
|
||||
? [
|
||||
@@ -313,23 +329,27 @@ export class HaConfigLovelaceDashboards extends LitElement {
|
||||
);
|
||||
|
||||
private _getItems = memoize(
|
||||
(dashboards: LovelaceDashboard[], defaultUrlPath: string | null) => {
|
||||
(
|
||||
dashboards: LovelaceDashboard[],
|
||||
defaultUrlPath: string | null,
|
||||
panels: HomeAssistant["panels"]
|
||||
) => {
|
||||
const result: DataTableItem[] = [];
|
||||
|
||||
PANEL_DASHBOARDS.forEach((panel) => {
|
||||
const panelInfo = this.hass.panels[panel];
|
||||
const panelInfo = panels[panel];
|
||||
if (!panelInfo) {
|
||||
return;
|
||||
}
|
||||
const item: DataTableItem = {
|
||||
icon: getPanelIcon(panelInfo),
|
||||
title: getPanelTitle(this.hass, panelInfo) || panelInfo.url_path,
|
||||
show_in_sidebar: true,
|
||||
show_in_sidebar: panelInfo.title != null,
|
||||
mode: "storage",
|
||||
url_path: panelInfo.url_path,
|
||||
filename: "",
|
||||
default: defaultUrlPath === panelInfo.url_path,
|
||||
require_admin: false,
|
||||
require_admin: panelInfo.require_admin || false,
|
||||
type: "built_in",
|
||||
localized_type: this._localizeType("built_in"),
|
||||
};
|
||||
@@ -381,7 +401,11 @@ export class HaConfigLovelaceDashboards extends LitElement {
|
||||
this._dashboards,
|
||||
this.hass.localize
|
||||
)}
|
||||
.data=${this._getItems(this._dashboards, defaultPanel)}
|
||||
.data=${this._getItems(
|
||||
this._dashboards,
|
||||
defaultPanel,
|
||||
this.hass.panels
|
||||
)}
|
||||
.initialGroupColumn=${this._activeGrouping}
|
||||
.initialCollapsedGroups=${this._activeCollapsed}
|
||||
.initialSorting=${this._activeSorting}
|
||||
@@ -452,11 +476,42 @@ export class HaConfigLovelaceDashboards extends LitElement {
|
||||
this._openDetailDialog(dashboard, urlPath);
|
||||
}
|
||||
|
||||
private _handleEditPanel(item: DataTableItem) {
|
||||
const panelInfo = this.hass.panels[item.url_path];
|
||||
if (!panelInfo) {
|
||||
return;
|
||||
}
|
||||
const defaultPanel = this.hass.systemData?.default_panel || DEFAULT_PANEL;
|
||||
showPanelDetailDialog(this, {
|
||||
urlPath: panelInfo.url_path,
|
||||
title: getPanelTitle(this.hass, panelInfo) || panelInfo.url_path,
|
||||
icon: getPanelIcon(panelInfo),
|
||||
requireAdmin: panelInfo.require_admin || false,
|
||||
showInSidebar: panelInfo.title != null,
|
||||
isDefault: panelInfo.url_path === defaultPanel,
|
||||
updatePanel: async (values) => {
|
||||
await updatePanel(this.hass!, panelInfo.url_path, values);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
private _handleSetAsDefault = async (item: DataTableItem) => {
|
||||
if (item.default) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (item.require_admin) {
|
||||
showAlertDialog(this, {
|
||||
title: this.hass.localize(
|
||||
"ui.panel.config.lovelace.dashboards.detail.set_default_admin_only_title"
|
||||
),
|
||||
text: this.hass.localize(
|
||||
"ui.panel.config.lovelace.dashboards.detail.set_default_admin_only_text"
|
||||
),
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const confirm = await showConfirmationDialog(this, {
|
||||
title: this.hass.localize(
|
||||
"ui.panel.config.lovelace.dashboards.detail.set_default_confirm_title"
|
||||
@@ -524,9 +579,11 @@ export class HaConfigLovelaceDashboards extends LitElement {
|
||||
urlPath?: string,
|
||||
defaultConfig?: LovelaceRawConfig
|
||||
): Promise<void> {
|
||||
const defaultPanel = this.hass.systemData?.default_panel || DEFAULT_PANEL;
|
||||
showDashboardDetailDialog(this, {
|
||||
dashboard,
|
||||
urlPath,
|
||||
isDefault: dashboard?.url_path === defaultPanel,
|
||||
createDashboard: async (values: LovelaceDashboardCreateParams) => {
|
||||
const created = await createDashboard(this.hass!, values);
|
||||
this._dashboards = this._dashboards!.concat(created).sort(
|
||||
|
||||
@@ -8,6 +8,7 @@ import type {
|
||||
export interface LovelaceDashboardDetailsDialogParams {
|
||||
dashboard?: LovelaceDashboard;
|
||||
urlPath?: string;
|
||||
isDefault?: boolean;
|
||||
createDashboard?: (values: LovelaceDashboardCreateParams) => Promise<unknown>;
|
||||
updateDashboard: (
|
||||
updates: Partial<LovelaceDashboardMutableParams>
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import type { PanelMutableParams } from "../../../../data/panel";
|
||||
|
||||
export interface PanelDetailDialogParams {
|
||||
urlPath: string;
|
||||
title: string;
|
||||
icon?: string;
|
||||
requireAdmin: boolean;
|
||||
showInSidebar: boolean;
|
||||
isDefault: boolean;
|
||||
updatePanel: (updates: PanelMutableParams) => Promise<unknown>;
|
||||
}
|
||||
|
||||
export const loadPanelDetailDialog = () => import("./dialog-panel-detail");
|
||||
|
||||
export const showPanelDetailDialog = (
|
||||
element: HTMLElement,
|
||||
dialogParams: PanelDetailDialogParams
|
||||
) => {
|
||||
fireEvent(element, "show-dialog", {
|
||||
dialogTag: "dialog-panel-detail",
|
||||
dialogImport: loadPanelDetailDialog,
|
||||
dialogParams,
|
||||
});
|
||||
};
|
||||
@@ -101,7 +101,8 @@ class PanelHome extends LitElement {
|
||||
oldHass.entities !== this.hass.entities ||
|
||||
oldHass.devices !== this.hass.devices ||
|
||||
oldHass.areas !== this.hass.areas ||
|
||||
oldHass.floors !== this.hass.floors
|
||||
oldHass.floors !== this.hass.floors ||
|
||||
oldHass.panels !== this.hass.panels
|
||||
) {
|
||||
if (this.hass.config.state === "RUNNING") {
|
||||
this._debounceRegistriesChanged();
|
||||
|
||||
@@ -58,7 +58,8 @@ class PanelLight extends LitElement {
|
||||
oldHass.entities !== this.hass.entities ||
|
||||
oldHass.devices !== this.hass.devices ||
|
||||
oldHass.areas !== this.hass.areas ||
|
||||
oldHass.floors !== this.hass.floors
|
||||
oldHass.floors !== this.hass.floors ||
|
||||
oldHass.panels !== this.hass.panels
|
||||
) {
|
||||
if (this.hass.config.state === "RUNNING") {
|
||||
this._debounceRegistriesChanged();
|
||||
|
||||
@@ -64,10 +64,12 @@ const processAreasForLight = (
|
||||
heading_style: "subtitle",
|
||||
type: "heading",
|
||||
heading: area.name,
|
||||
tap_action: {
|
||||
action: "navigate",
|
||||
navigation_path: `/home/areas-${area.area_id}`,
|
||||
},
|
||||
tap_action: hass.panels.home
|
||||
? {
|
||||
action: "navigate",
|
||||
navigation_path: `/home/areas-${area.area_id}`,
|
||||
}
|
||||
: undefined,
|
||||
badges: [
|
||||
// Toggle buttons for mobile
|
||||
{
|
||||
|
||||
@@ -796,7 +796,7 @@ class HuiEnergyDistrubutionCard
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
${this._config.link_dashboard
|
||||
${this._config.link_dashboard && this.hass.panels.energy
|
||||
? html`
|
||||
<div class="card-actions">
|
||||
<ha-button
|
||||
|
||||
@@ -102,10 +102,12 @@ export class HomeAreaViewStrategy extends ReactiveElement {
|
||||
type: "heading",
|
||||
heading: getSummaryLabel(hass.localize, "light"),
|
||||
icon: HOME_SUMMARIES_ICONS.light,
|
||||
tap_action: {
|
||||
action: "navigate",
|
||||
navigation_path: "/light?historyBack=1",
|
||||
},
|
||||
tap_action: hass.panels.light
|
||||
? {
|
||||
action: "navigate",
|
||||
navigation_path: "/light?historyBack=1",
|
||||
}
|
||||
: undefined,
|
||||
} satisfies HeadingCardConfig,
|
||||
...light.map(computeTileCard),
|
||||
],
|
||||
@@ -120,10 +122,12 @@ export class HomeAreaViewStrategy extends ReactiveElement {
|
||||
type: "heading",
|
||||
heading: getSummaryLabel(hass.localize, "climate"),
|
||||
icon: HOME_SUMMARIES_ICONS.climate,
|
||||
tap_action: {
|
||||
action: "navigate",
|
||||
navigation_path: "/climate?historyBack=1",
|
||||
},
|
||||
tap_action: hass.panels.climate
|
||||
? {
|
||||
action: "navigate",
|
||||
navigation_path: "/climate?historyBack=1",
|
||||
}
|
||||
: undefined,
|
||||
} satisfies HeadingCardConfig,
|
||||
...climate.map(computeTileCard),
|
||||
],
|
||||
@@ -138,10 +142,12 @@ export class HomeAreaViewStrategy extends ReactiveElement {
|
||||
type: "heading",
|
||||
heading: getSummaryLabel(hass.localize, "security"),
|
||||
icon: HOME_SUMMARIES_ICONS.security,
|
||||
tap_action: {
|
||||
action: "navigate",
|
||||
navigation_path: "/security?historyBack=1",
|
||||
},
|
||||
tap_action: hass.panels.security
|
||||
? {
|
||||
action: "navigate",
|
||||
navigation_path: "/security?historyBack=1",
|
||||
}
|
||||
: undefined,
|
||||
} satisfies HeadingCardConfig,
|
||||
...security.map(computeTileCard),
|
||||
],
|
||||
|
||||
@@ -222,11 +222,16 @@ export class HomeOverviewViewStrategy extends ReactiveElement {
|
||||
generateEntityFilter(hass, filter)
|
||||
);
|
||||
|
||||
const hasLights = findEntities(allEntities, lightsFilters).length > 0;
|
||||
const hasLights =
|
||||
hass.panels.light && findEntities(allEntities, lightsFilters).length > 0;
|
||||
const hasMediaPlayers =
|
||||
findEntities(allEntities, mediaPlayerFilter).length > 0;
|
||||
const hasClimate = findEntities(allEntities, climateFilters).length > 0;
|
||||
const hasSecurity = findEntities(allEntities, securityFilters).length > 0;
|
||||
const hasClimate =
|
||||
hass.panels.climate &&
|
||||
findEntities(allEntities, climateFilters).length > 0;
|
||||
const hasSecurity =
|
||||
hass.panels.security &&
|
||||
findEntities(allEntities, securityFilters).length > 0;
|
||||
|
||||
const weatherFilter = generateEntityFilter(hass, {
|
||||
domain: "weather",
|
||||
@@ -243,9 +248,11 @@ export class HomeOverviewViewStrategy extends ReactiveElement {
|
||||
: undefined;
|
||||
|
||||
const hasEnergy =
|
||||
energyPrefs?.energy_sources.some(
|
||||
hass.panels.energy &&
|
||||
(energyPrefs?.energy_sources.some(
|
||||
(source) => source.type === "grid" && !!source.stat_energy_from
|
||||
) ?? false;
|
||||
) ??
|
||||
false);
|
||||
|
||||
// Build summary cards (used in both mobile section and sidebar)
|
||||
const summaryCards: LovelaceCardConfig[] = [
|
||||
|
||||
110
src/panels/notfound/ha-panel-notfound.ts
Normal file
110
src/panels/notfound/ha-panel-notfound.ts
Normal file
@@ -0,0 +1,110 @@
|
||||
import type { PropertyValues } from "lit";
|
||||
import { LitElement, html, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import type { LovelaceConfig } from "../../data/lovelace/config/types";
|
||||
import { NOT_FOUND_PANEL } from "../../data/panel";
|
||||
import type { HomeAssistant, PanelInfo, Route } from "../../types";
|
||||
import type { EmptyStateCardConfig } from "../lovelace/cards/types";
|
||||
import "../lovelace/hui-root";
|
||||
import type { Lovelace } from "../lovelace/types";
|
||||
|
||||
@customElement("ha-panel-notfound")
|
||||
class HaPanelNotFound extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property({ type: Boolean }) public narrow = false;
|
||||
|
||||
@property({ attribute: false }) public route?: Route;
|
||||
|
||||
@property({ attribute: false }) public panel?: PanelInfo;
|
||||
|
||||
@state() private _lovelace?: Lovelace;
|
||||
|
||||
public willUpdate(changedProps: PropertyValues) {
|
||||
if (!this.hasUpdated) {
|
||||
this._setup();
|
||||
return;
|
||||
}
|
||||
|
||||
if (changedProps.has("hass")) {
|
||||
const oldHass = changedProps.get("hass") as this["hass"];
|
||||
if (oldHass && oldHass.localize !== this.hass.localize) {
|
||||
this._setLovelace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async _setup() {
|
||||
await this.hass.loadFragmentTranslation("lovelace");
|
||||
this._setLovelace();
|
||||
}
|
||||
|
||||
protected render() {
|
||||
if (!this._lovelace) {
|
||||
return nothing;
|
||||
}
|
||||
|
||||
return html`
|
||||
<hui-root
|
||||
.hass=${this.hass}
|
||||
.narrow=${this.narrow}
|
||||
.lovelace=${this._lovelace}
|
||||
.route=${this.route}
|
||||
.panel=${this.panel}
|
||||
no-edit
|
||||
></hui-root>
|
||||
`;
|
||||
}
|
||||
|
||||
private _setLovelace() {
|
||||
const config: LovelaceConfig = {
|
||||
views: [
|
||||
{
|
||||
type: "panel",
|
||||
cards: [
|
||||
{
|
||||
type: "empty-state",
|
||||
content_only: true,
|
||||
icon: "mdi:lock",
|
||||
title: this.hass.localize("ui.panel.notfound.no_access_title"),
|
||||
content: this.hass.localize(
|
||||
"ui.panel.notfound.no_access_content"
|
||||
),
|
||||
buttons: [
|
||||
{
|
||||
text: this.hass.localize(
|
||||
"ui.panel.notfound.no_access_go_to_profile"
|
||||
),
|
||||
tap_action: {
|
||||
action: "navigate",
|
||||
navigation_path: "/profile/general",
|
||||
},
|
||||
},
|
||||
],
|
||||
} as EmptyStateCardConfig,
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
this._lovelace = {
|
||||
config: config,
|
||||
rawConfig: config,
|
||||
editMode: false,
|
||||
urlPath: NOT_FOUND_PANEL,
|
||||
mode: "generated",
|
||||
locale: this.hass.locale,
|
||||
enableFullEditMode: () => undefined,
|
||||
saveConfig: async () => undefined,
|
||||
deleteConfig: async () => undefined,
|
||||
setEditMode: () => undefined,
|
||||
showToast: () => undefined,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-panel-notfound": HaPanelNotFound;
|
||||
}
|
||||
}
|
||||
@@ -58,7 +58,8 @@ class PanelSecurity extends LitElement {
|
||||
oldHass.entities !== this.hass.entities ||
|
||||
oldHass.devices !== this.hass.devices ||
|
||||
oldHass.areas !== this.hass.areas ||
|
||||
oldHass.floors !== this.hass.floors
|
||||
oldHass.floors !== this.hass.floors ||
|
||||
oldHass.panels !== this.hass.panels
|
||||
) {
|
||||
if (this.hass.config.state === "RUNNING") {
|
||||
this._debounceRegistriesChanged();
|
||||
|
||||
@@ -91,10 +91,12 @@ const processAreasForSecurity = (
|
||||
heading_style: "subtitle",
|
||||
type: "heading",
|
||||
heading: area.name,
|
||||
tap_action: {
|
||||
action: "navigate",
|
||||
navigation_path: `/home/areas-${area.area_id}`,
|
||||
},
|
||||
tap_action: hass.panels.home
|
||||
? {
|
||||
action: "navigate",
|
||||
navigation_path: `/home/areas-${area.area_id}`,
|
||||
}
|
||||
: undefined,
|
||||
});
|
||||
cards.push(...areaCards);
|
||||
}
|
||||
|
||||
@@ -15,7 +15,8 @@
|
||||
"light": "Lights",
|
||||
"security": "Security",
|
||||
"climate": "Climate",
|
||||
"home": "Overview"
|
||||
"home": "Overview",
|
||||
"notfound": "Page not found"
|
||||
},
|
||||
"state": {
|
||||
"default": {
|
||||
@@ -4251,7 +4252,7 @@
|
||||
"title": "Title",
|
||||
"conf_mode": "Configuration method",
|
||||
"default": "Default",
|
||||
"require_admin": "Admin only",
|
||||
"require_admin": "Admin-only",
|
||||
"sidebar": "In sidebar",
|
||||
"filename": "Filename",
|
||||
"url": "Open",
|
||||
@@ -4280,7 +4281,7 @@
|
||||
"title_required": "Title is required.",
|
||||
"url": "URL",
|
||||
"url_error_msg": "The URL should contain a - and cannot contain spaces or special characters, except for _ and -",
|
||||
"require_admin": "Admin only",
|
||||
"require_admin": "Admin-only",
|
||||
"delete": "Delete",
|
||||
"update": "Update",
|
||||
"create": "Create",
|
||||
@@ -4288,7 +4289,18 @@
|
||||
"remove_default": "Remove as default",
|
||||
"set_default_confirm_title": "Set as default dashboard?",
|
||||
"set_default_confirm_text": "This dashboard will be shown to all users when opening Home Assistant.",
|
||||
"set_default_confirm_note": "Users who have chosen a specific dashboard in their profile will not be affected. They must set it back to \"Auto (use system settings)\" to use this dashboard."
|
||||
"set_default_confirm_note": "Users who have chosen a specific dashboard in their profile will not be affected. They must set it back to \"Auto (use system settings)\" to use this dashboard.",
|
||||
"set_default_admin_only_title": "Can't set as default",
|
||||
"set_default_admin_only_text": "This dashboard is set to admin-only. Disable this limitation before setting it as default."
|
||||
},
|
||||
"panel_detail": {
|
||||
"edit_panel": "Edit panel",
|
||||
"title": "[%key:ui::panel::config::lovelace::dashboards::detail::title%]",
|
||||
"icon": "[%key:ui::panel::config::lovelace::dashboards::detail::icon%]",
|
||||
"require_admin": "[%key:ui::panel::config::lovelace::dashboards::detail::require_admin%]",
|
||||
"show_in_sidebar": "[%key:ui::panel::config::lovelace::dashboards::detail::show_sidebar%]",
|
||||
"reset_to_default": "Reset to default",
|
||||
"require_admin_helper": "Can't be enabled because this dashboard is set as default"
|
||||
}
|
||||
},
|
||||
"resources": {
|
||||
@@ -9618,6 +9630,11 @@
|
||||
"map": {
|
||||
"edit_zones": "Edit zones"
|
||||
},
|
||||
"notfound": {
|
||||
"no_access_title": "No access",
|
||||
"no_access_content": "You don't have access to this page. Contact an administrator or change the default dashboard in your profile settings.",
|
||||
"no_access_go_to_profile": "Go to profile"
|
||||
},
|
||||
"profile": {
|
||||
"tabs": {
|
||||
"general": "General",
|
||||
|
||||
@@ -141,6 +141,7 @@ export interface PanelInfo<T = Record<string, any> | null> {
|
||||
url_path: string;
|
||||
config_panel_domain?: string;
|
||||
default_visible?: boolean;
|
||||
require_admin?: boolean;
|
||||
}
|
||||
|
||||
export type Panels = Record<string, PanelInfo>;
|
||||
|
||||
Reference in New Issue
Block a user