Compare commits

...

12 Commits

Author SHA1 Message Date
Wendelin
1c20818e33 Fix add to list item 2025-11-24 10:33:38 +01:00
Simon Lamon
ba9bab38c9 Migrate updates dropdown to ha-dropdown (#28039)
* Migrate upgrade dropdown

* Apply suggestion from @MindFreeze

* Update src/panels/config/core/ha-config-section-updates.ts

---------

Co-authored-by: Petar Petrov <MindFreeze@users.noreply.github.com>
2025-11-24 09:06:55 +00:00
Wendelin
085b3884af Fix add to selected action (#28062) 2025-11-24 11:01:58 +02:00
Simon Lamon
a9c816ed9c Migrate analytics dropdown to ha-dropdown (#28038)
* Migrate analytics dropdown

* Apply suggestion from @MindFreeze

* Apply suggestion from @MindFreeze

---------

Co-authored-by: Petar Petrov <MindFreeze@users.noreply.github.com>
2025-11-24 08:52:22 +00:00
Nico Wiedemann
c3201eecf3 Show blueprint usage count in overview (#28054)
* Show blueprint usage count in overview

* feat(blueprint): add usage count functionality and update table rendering

* feat(blueprint): enhance usage count display with interactive chip

* fix(blueprint): convert _handleUsageClick to an arrow function for consistent context binding

* Update src/panels/config/blueprint/ha-blueprint-overview.ts

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

---------

Co-authored-by: Petar Petrov <MindFreeze@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-11-24 07:07:04 +00:00
dependabot[bot]
bd67a3b90a Bump actions/checkout from 5.0.0 to 6.0.0 (#28056) 2025-11-24 07:28:51 +01:00
dependabot[bot]
e59ab45e60 Bump github/codeql-action from 4.31.3 to 4.31.4 (#28057) 2025-11-24 07:13:43 +01:00
Franck Nijhof
4674384f7d Allow deleting helpers directly from helpers panel (#28044)
* Allow deleting helpers directly from helpers panel

* Fix error handling in helper deletion logic

Address review feedback by improving the logic in _deleteHelper:
- Throw error when entity is not found in registry or component not loaded
- Move return statement outside of nested condition to ensure proper control flow
- Prevent fallthrough to config entry deletion for old-style helpers

Addresses review comment from @MindFreeze

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>
2025-11-23 15:20:20 +01:00
steinmn
2e7c95a8cc Translate dashboard panel url error message (#27306)
* Translate dashboard panel url error message

* Use backend provided translations
2025-11-23 14:54:21 +01:00
Simon Lamon
9630b175a1 Remove open from ha-dropdown (#28037) 2025-11-22 14:30:06 +02:00
Petar Petrov
be319503f7 Update color scheme in ZHA network visualization (#28032) 2025-11-21 19:09:37 +01:00
Paul Bottein
9b1fe28018 Rename defaultPanel to default_panel (#28035) 2025-11-21 16:24:34 +01:00
24 changed files with 300 additions and 101 deletions

View File

@@ -21,7 +21,7 @@ jobs:
url: ${{ steps.deploy.outputs.NETLIFY_LIVE_URL || steps.deploy.outputs.NETLIFY_URL }}
steps:
- name: Check out files from GitHub
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
with:
ref: dev
@@ -56,7 +56,7 @@ jobs:
url: ${{ steps.deploy.outputs.NETLIFY_LIVE_URL || steps.deploy.outputs.NETLIFY_URL }}
steps:
- name: Check out files from GitHub
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
with:
ref: master

View File

@@ -24,7 +24,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Check out files from GitHub
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
- name: Setup Node
uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
with:
@@ -58,7 +58,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Check out files from GitHub
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
- name: Setup Node
uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
with:
@@ -76,7 +76,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Check out files from GitHub
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
- name: Setup Node
uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
with:
@@ -100,7 +100,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Check out files from GitHub
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
- name: Setup Node
uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
with:

View File

@@ -23,7 +23,7 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
with:
# We must fetch at least the immediate parents so that if this is
# a pull request then we can checkout the head.
@@ -36,14 +36,14 @@ jobs:
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@014f16e7ab1402f30e7c3329d33797e7948572db # v4.31.3
uses: github/codeql-action/init@e12f0178983d466f2f6028f5cc7a6d786fd97f4b # v4.31.4
with:
languages: ${{ matrix.language }}
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@014f16e7ab1402f30e7c3329d33797e7948572db # v4.31.3
uses: github/codeql-action/autobuild@e12f0178983d466f2f6028f5cc7a6d786fd97f4b # v4.31.4
# Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl
@@ -57,4 +57,4 @@ jobs:
# make release
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@014f16e7ab1402f30e7c3329d33797e7948572db # v4.31.3
uses: github/codeql-action/analyze@e12f0178983d466f2f6028f5cc7a6d786fd97f4b # v4.31.4

View File

@@ -22,7 +22,7 @@ jobs:
url: ${{ steps.deploy.outputs.NETLIFY_LIVE_URL || steps.deploy.outputs.NETLIFY_URL }}
steps:
- name: Check out files from GitHub
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
with:
ref: dev
@@ -57,7 +57,7 @@ jobs:
url: ${{ steps.deploy.outputs.NETLIFY_LIVE_URL || steps.deploy.outputs.NETLIFY_URL }}
steps:
- name: Check out files from GitHub
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
with:
ref: master

View File

@@ -16,7 +16,7 @@ jobs:
url: ${{ steps.deploy.outputs.NETLIFY_LIVE_URL || steps.deploy.outputs.NETLIFY_URL }}
steps:
- name: Check out files from GitHub
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
- name: Setup Node
uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0

View File

@@ -21,7 +21,7 @@ jobs:
if: github.repository == 'home-assistant/frontend' && contains(github.event.pull_request.labels.*.name, 'needs design preview')
steps:
- name: Check out files from GitHub
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
- name: Setup Node
uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0

View File

@@ -20,7 +20,7 @@ jobs:
contents: write
steps:
- name: Checkout the repository
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
- name: Set up Python ${{ env.PYTHON_VERSION }}
uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6

View File

@@ -23,7 +23,7 @@ jobs:
contents: write # Required to upload release assets
steps:
- name: Checkout the repository
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
- name: Set up Python ${{ env.PYTHON_VERSION }}
uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
@@ -91,7 +91,7 @@ jobs:
contents: write # Required to upload release assets
steps:
- name: Checkout the repository
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
- name: Setup Node
uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
with:
@@ -120,7 +120,7 @@ jobs:
contents: write # Required to upload release assets
steps:
- name: Checkout the repository
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
- name: Setup Node
uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
with:

View File

@@ -14,7 +14,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout the repository
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
- name: Upload Translations
run: |

View File

@@ -11,7 +11,7 @@ A compact, accessible dropdown menu for choosing actions or settings. `ha-dropdo
### Example usage (composition)
```html
<ha-dropdown open>
<ha-dropdown>
<ha-button slot="trigger" with-caret>Dropdown</ha-button>
<ha-dropdown-item>

View File

@@ -28,7 +28,7 @@ export class DemoHaDropdown extends LitElement {
<div class=${mode}>
<ha-card header="ha-button in ${mode}">
<div class="card-content">
<ha-dropdown open>
<ha-dropdown>
<ha-button slot="trigger" with-caret>Dropdown</ha-button>
<ha-dropdown-item>

View File

@@ -3,7 +3,7 @@ import type { Connection } from "home-assistant-js-websocket";
export interface CoreFrontendUserData {
showAdvanced?: boolean;
showEntityIdPicker?: boolean;
defaultPanel?: string;
default_panel?: string;
}
export interface SidebarFrontendUserData {
@@ -12,7 +12,7 @@ export interface SidebarFrontendUserData {
}
export interface CoreFrontendSystemData {
defaultPanel?: string;
default_panel?: string;
}
export interface HomeFrontendSystemData {

View File

@@ -9,8 +9,8 @@ export const getLegacyDefaultPanelUrlPath = (): string | null => {
};
export const getDefaultPanelUrlPath = (hass: HomeAssistant): string =>
hass.userData?.defaultPanel ||
hass.systemData?.defaultPanel ||
hass.userData?.default_panel ||
hass.systemData?.default_panel ||
getLegacyDefaultPanelUrlPath() ||
DEFAULT_PANEL;

View File

@@ -2,14 +2,15 @@ import { LitElement, css, html, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import "../../components/ha-alert";
import "../../components/ha-icon";
import "../../components/ha-list-item";
import "../../components/ha-md-list-item";
import "../../components/ha-spinner";
import type {
ExternalEntityAddToActions,
ExternalEntityAddToAction,
ExternalEntityAddToActions,
} from "../../external_app/external_messaging";
import { showToast } from "../../util/toast";
import { fireEvent } from "../../common/dom/fire_event";
import type { HomeAssistant } from "../../types";
@customElement("ha-more-info-add-to")
@@ -51,6 +52,7 @@ export class HaMoreInfoAddTo extends LitElement {
app_payload: action.app_payload,
},
});
fireEvent(this, "add-to-action-selected");
} catch (err: any) {
showToast(this, {
message: this.hass.localize(
@@ -91,19 +93,18 @@ export class HaMoreInfoAddTo extends LitElement {
<div class="actions-list">
${this._externalActions.actions.map(
(action) => html`
<ha-list-item
graphic="icon"
<ha-md-list-item
type="button"
.disabled=${!action.enabled}
.action=${action}
.twoline=${!!action.details}
@click=${this._actionSelected}
>
<ha-icon slot="start" .icon=${action.mdi_icon}></ha-icon>
<span>${action.name}</span>
${action.details
? html`<span slot="secondary">${action.details}</span>`
? html`<span slot="supporting-text">${action.details}</span>`
: nothing}
<ha-icon slot="graphic" .icon=${action.mdi_icon}></ha-icon>
</ha-list-item>
</ha-md-list-item>
`
)}
</div>
@@ -149,4 +150,8 @@ declare global {
interface HTMLElementTagNameMap {
"ha-more-info-add-to": HaMoreInfoAddTo;
}
interface HASSDomEvents {
"add-to-action-selected": undefined;
}
}

View File

@@ -645,6 +645,7 @@ export class MoreInfoDialog extends LitElement {
<ha-more-info-add-to
.hass=${this.hass}
.entityId=${entityId}
@add-to-action-selected=${this._goBack}
></ha-more-info-add-to>
`
: nothing

View File

@@ -43,6 +43,7 @@ import {
} from "../../../data/blueprint";
import { showScriptEditor } from "../../../data/script";
import { findRelated } from "../../../data/search";
import "../../../components/chips/ha-assist-chip";
import {
showAlertDialog,
showConfirmationDialog,
@@ -60,6 +61,7 @@ type BlueprintMetaDataPath = BlueprintMetaData & {
error: boolean;
type: "automation" | "script";
fullpath: string;
usageCount?: number;
};
const createNewFunctions = {
@@ -128,14 +130,20 @@ class HaBlueprintOverview extends LitElement {
})
private _filter = "";
@state() private _usageCounts: Record<string, number> = {};
private _usageCountRequest = 0;
private _processedBlueprints = memoizeOne(
(
blueprints: Record<string, Blueprints>,
localize: LocalizeFunc
localize: LocalizeFunc,
usageCounts: Record<string, number>
): BlueprintMetaDataPath[] => {
const result: any[] = [];
Object.entries(blueprints).forEach(([type, typeBlueprints]) =>
Object.entries(typeBlueprints).forEach(([path, blueprint]) => {
const fullpath = `${type}/${path}`;
if ("error" in blueprint) {
result.push({
name: blueprint.error,
@@ -145,7 +153,8 @@ class HaBlueprintOverview extends LitElement {
),
error: true,
path,
fullpath: `${type}/${path}`,
fullpath,
usageCount: 0,
});
} else {
result.push({
@@ -156,7 +165,8 @@ class HaBlueprintOverview extends LitElement {
),
error: false,
path,
fullpath: `${type}/${path}`,
fullpath,
usageCount: usageCounts[fullpath] || 0,
});
}
})
@@ -189,6 +199,34 @@ class HaBlueprintOverview extends LitElement {
filterable: true,
flex: 2,
},
usage_count: {
title: localize(
"ui.panel.config.blueprint.overview.headers.usage_count"
),
sortable: true,
valueColumn: "usageCount",
type: "numeric",
minWidth: "100px",
maxWidth: "120px",
template: (blueprint) => {
const count = blueprint.usageCount ?? 0;
return html`
<ha-assist-chip
filled
.active=${count > 0}
label=${String(count)}
title=${blueprint.error
? String(count)
: this.hass.localize(
`ui.panel.config.blueprint.overview.view_${blueprint.type}`
)}
?disabled=${blueprint.error}
data-fullpath=${blueprint.fullpath}
@click=${this._handleUsageClick}
></ha-assist-chip>
`;
},
},
fullpath: {
title: "fullpath",
hidden: true,
@@ -266,6 +304,7 @@ class HaBlueprintOverview extends LitElement {
protected firstUpdated(changedProps: PropertyValues) {
super.firstUpdated(changedProps);
this._loadUsageCounts();
if (this.route.path === "/import") {
const url = extractSearchParam("blueprint_url");
navigate("/config/blueprint/dashboard", { replace: true });
@@ -275,6 +314,13 @@ class HaBlueprintOverview extends LitElement {
}
}
protected updated(changedProps: PropertyValues) {
super.updated(changedProps);
if (changedProps.has("blueprints")) {
this._loadUsageCounts();
}
}
protected render(): TemplateResult {
return html`
<hass-tabs-subpage-data-table
@@ -284,7 +330,11 @@ class HaBlueprintOverview extends LitElement {
.route=${this.route}
.tabs=${configSections.automations}
.columns=${this._columns(this.hass.localize)}
.data=${this._processedBlueprints(this.blueprints, this.hass.localize)}
.data=${this._processedBlueprints(
this.blueprints,
this.hass.localize,
this._usageCounts
)}
id="fullpath"
.noDataText=${this.hass.localize(
"ui.panel.config.blueprint.overview.no_blueprints"
@@ -380,10 +430,51 @@ class HaBlueprintOverview extends LitElement {
fireEvent(this, "reload-blueprints");
}
private async _loadUsageCounts() {
if (!this.blueprints) {
return;
}
const request = ++this._usageCountRequest;
const usageCounts: Record<string, number> = {};
const blueprintList = this._processedBlueprints(
this.blueprints,
this.hass.localize,
{}
);
await Promise.all(
blueprintList.map(async (blueprint) => {
if (blueprint.error) {
usageCounts[blueprint.fullpath] = 0;
return;
}
try {
const related = await findRelated(
this.hass,
`${blueprint.domain}_blueprint`,
blueprint.path
);
const count =
(related.automation?.length || 0) + (related.script?.length || 0);
usageCounts[blueprint.fullpath] = count;
} catch (_err) {
usageCounts[blueprint.fullpath] = 0;
}
})
);
if (request === this._usageCountRequest) {
this._usageCounts = usageCounts;
}
}
private _handleRowClicked(ev: HASSDomEvent<RowClickedEvent>) {
const blueprint = this._processedBlueprints(
this.blueprints,
this.hass.localize
this.hass.localize,
this._usageCounts
).find((b) => b.fullpath === ev.detail.id)!;
if (blueprint.error) {
showAlertDialog(this, {
@@ -397,6 +488,25 @@ class HaBlueprintOverview extends LitElement {
this._createNew(blueprint);
}
private _handleUsageClick = (ev: Event) => {
ev.stopPropagation();
ev.preventDefault();
const target = ev.currentTarget as HTMLElement | null;
const fullpath = target?.dataset.fullpath;
if (!fullpath) {
return;
}
const blueprint = this._processedBlueprints(
this.blueprints,
this.hass.localize,
this._usageCounts
).find((item) => item.fullpath === fullpath);
if (!blueprint || blueprint.error) {
return;
}
this._showUsed(blueprint);
};
private _showUsed = (blueprint: BlueprintMetaDataPath) => {
navigate(
`/config/${blueprint.domain}/dashboard?blueprint=${encodeURIComponent(

View File

@@ -2,9 +2,7 @@ import { mdiDotsVertical, mdiDownload } from "@mdi/js";
import type { TemplateResult } from "lit";
import { css, html, LitElement, nothing } from "lit";
import { customElement, property } from "lit/decorators";
import "../../../components/ha-button-menu";
import "../../../components/ha-icon-button";
import "../../../components/ha-list-item";
import "../../../components/ha-svg-icon";
import { getSignedPath } from "../../../data/auth";
import "../../../layouts/hass-subpage";
@@ -14,6 +12,8 @@ import {
downloadFileSupported,
fileDownload,
} from "../../../util/file_download";
import "../../../components/ha-dropdown-item";
import "../../../components/ha-dropdown";
@customElement("ha-config-section-analytics")
class HaConfigSectionAnalytics extends LitElement {
@@ -33,22 +33,19 @@ class HaConfigSectionAnalytics extends LitElement {
>
${downloadFileSupported(this.hass)
? html`
<ha-button-menu
@action=${this._handleOverflowAction}
<ha-dropdown
@wa-select=${this._handleOverflowAction}
slot="toolbar-icon"
>
<ha-icon-button slot="trigger" .path=${mdiDotsVertical}>
</ha-icon-button>
<ha-list-item graphic="icon">
<ha-svg-icon
slot="graphic"
.path=${mdiDownload}
></ha-svg-icon>
<ha-dropdown-item .value=${"download_device_info"}>
<ha-svg-icon slot="icon" .path=${mdiDownload}></ha-svg-icon>
${this.hass.localize(
"ui.panel.config.analytics.download_device_info"
)}
</ha-list-item>
</ha-button-menu>
</ha-dropdown-item>
</ha-dropdown>
`
: nothing}
<div class="content">
@@ -58,9 +55,16 @@ class HaConfigSectionAnalytics extends LitElement {
`;
}
private async _handleOverflowAction(): Promise<void> {
const signedPath = await getSignedPath(this.hass, "/api/analytics/devices");
fileDownload(signedPath.path);
private async _handleOverflowAction(
ev: CustomEvent<{ item: { value: string } }>
): Promise<void> {
if (ev.detail.item.value === "download_device_info") {
const signedPath = await getSignedPath(
this.hass,
"/api/analytics/devices"
);
fileDownload(signedPath.path);
}
}
static styles = css`

View File

@@ -1,4 +1,3 @@
import type { RequestSelectedDetail } from "@material/mwc-list/mwc-list-item";
import { mdiDotsVertical, mdiRefresh } from "@mdi/js";
import type { HassEntities } from "home-assistant-js-websocket";
import type { TemplateResult } from "lit";
@@ -6,13 +5,9 @@ import { LitElement, css, html } 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-alert";
import "../../../components/ha-bar";
import "../../../components/ha-button-menu";
import "../../../components/ha-card";
import "../../../components/ha-check-list-item";
import "../../../components/ha-list-item";
import "../../../components/ha-metric";
import { extractApiErrorMessage } from "../../../data/hassio/common";
import type {
@@ -33,6 +28,9 @@ import "../../../layouts/hass-subpage";
import type { HomeAssistant } from "../../../types";
import "../dashboard/ha-config-updates";
import { showJoinBetaDialog } from "./updates/show-dialog-join-beta";
import "../../../components/ha-dropdown";
import "../../../components/ha-dropdown-item";
import "@home-assistant/webawesome/dist/components/divider/divider";
@customElement("ha-config-section-updates")
class HaConfigSectionUpdates extends LitElement {
@@ -73,24 +71,25 @@ class HaConfigSectionUpdates extends LitElement {
.path=${mdiRefresh}
@click=${this._checkUpdates}
></ha-icon-button>
<ha-button-menu multi>
<ha-dropdown @wa-select=${this._handleOverflowAction}>
<ha-icon-button
slot="trigger"
.label=${this.hass.localize("ui.common.menu")}
.path=${mdiDotsVertical}
></ha-icon-button>
<ha-check-list-item
left
@request-selected=${this._toggleSkipped}
.selected=${this._showSkipped}
<ha-dropdown-item
type="checkbox"
value="show_skipped"
.checked=${this._showSkipped}
>
${this.hass.localize("ui.panel.config.updates.show_skipped")}
</ha-check-list-item>
</ha-dropdown-item>
${this._supervisorInfo
? html`
<li divider role="separator"></li>
<ha-list-item
@request-selected=${this._toggleBeta}
<wa-divider></wa-divider>
<ha-dropdown-item
value="toggle_beta"
.disabled=${this._supervisorInfo.channel === "dev"}
>
${this._supervisorInfo.channel === "stable"
@@ -98,10 +97,10 @@ class HaConfigSectionUpdates extends LitElement {
: this.hass.localize(
"ui.panel.config.updates.leave_beta"
)}
</ha-list-item>
</ha-dropdown-item>
`
: ""}
</ha-button-menu>
</ha-dropdown>
</div>
<div class="content">
<ha-card outlined>
@@ -133,27 +132,19 @@ class HaConfigSectionUpdates extends LitElement {
this._supervisorInfo = await fetchHassioSupervisorInfo(this.hass);
}
private _toggleSkipped(ev: CustomEvent<RequestSelectedDetail>): void {
if (ev.detail.source !== "property") {
return;
}
this._showSkipped = !this._showSkipped;
}
private async _toggleBeta(
ev: CustomEvent<RequestSelectedDetail>
private async _handleOverflowAction(
ev: CustomEvent<{ item: { value: string } }>
): Promise<void> {
if (!shouldHandleRequestSelectedEvent(ev)) {
return;
}
if (this._supervisorInfo!.channel === "stable") {
showJoinBetaDialog(this, {
join: async () => this._setChannel("beta"),
});
} else {
this._setChannel("stable");
if (ev.detail.item.value === "toggle_beta") {
if (this._supervisorInfo!.channel === "stable") {
showJoinBetaDialog(this, {
join: async () => this._setChannel("beta"),
});
} else {
this._setChannel("stable");
}
} else if (ev.detail.item.value === "show_skipped") {
this._showSkipped = !this._showSkipped;
}
}

View File

@@ -5,6 +5,7 @@ import {
mdiCancel,
mdiChevronRight,
mdiCog,
mdiDelete,
mdiDotsVertical,
mdiMenuDown,
mdiPencilOff,
@@ -109,10 +110,11 @@ import { configSections } from "../ha-panel-config";
import "../integrations/ha-integration-overflow-menu";
import { renderConfigEntryError } from "../integrations/ha-config-integration-page";
import { showLabelDetailDialog } from "../labels/show-dialog-label-detail";
import { isHelperDomain } from "./const";
import { isHelperDomain, type HelperDomain } from "./const";
import { showHelperDetailDialog } from "./show-dialog-helper-detail";
import { slugify } from "../../../common/string/slugify";
import { isComponentLoaded } from "../../../common/config/is_component_loaded";
import { HELPERS_CRUD } from "../../../data/helpers_crud";
import {
fetchDiagnosticHandlers,
getConfigEntryDiagnosticsDownloadUrl,
@@ -451,6 +453,19 @@ export class HaConfigHelpers extends SubscribeMixin(LitElement) {
},
]
: []),
...(helper.editable && helper.entity
? [
{
divider: true,
},
{
path: mdiDelete,
label: this.hass.localize("ui.common.delete"),
warning: true,
action: () => this._deleteHelper(helper),
},
]
: []),
]}
>
</ha-icon-overflow-menu>
@@ -1280,6 +1295,62 @@ ${rejected
}
}
private async _deleteHelper(helper: HelperItem) {
if (!helper.entity_id) {
return;
}
const confirmed = await showConfirmationDialog(this, {
title: this.hass.localize(
"ui.panel.config.helpers.picker.delete_confirm_title"
),
text: this.hass.localize(
"ui.panel.config.helpers.picker.delete_confirm_text",
{ name: helper.name }
),
confirmText: this.hass.localize("ui.common.delete"),
dismissText: this.hass.localize("ui.common.cancel"),
destructive: true,
});
if (!confirmed) {
return;
}
try {
// For old-style helpers (input_boolean, etc.), use HELPERS_CRUD
if (isHelperDomain(helper.type)) {
const entityReg = this._entityReg.find(
(e) => e.entity_id === helper.entity_id
);
if (
!entityReg?.unique_id ||
!isComponentLoaded(this.hass, helper.type)
) {
throw new Error(
this.hass.localize("ui.panel.config.helpers.picker.delete_failed")
);
}
await HELPERS_CRUD[helper.type as HelperDomain].delete(
this.hass,
entityReg.unique_id
);
return;
}
// For config entry-based helpers, delete the config entry
if (helper.configEntry) {
await deleteConfigEntry(this.hass, helper.configEntry.entry_id);
}
} catch (err: any) {
showAlertDialog(this, {
text:
err.message ||
this.hass.localize("ui.panel.config.helpers.picker.delete_failed"),
});
}
}
private _createHelper() {
showHelperDetailDialog(this, {});
}

View File

@@ -295,7 +295,7 @@ export class ZHANetworkVisualizationPage extends LitElement {
color:
route.route_status === "Active"
? primaryColor
: style.getPropertyValue("--disabled-color"),
: style.getPropertyValue("--dark-primary-color"),
type: ["Child", "Parent"].includes(neighbor.relationship)
? "solid"
: "dotted",
@@ -335,7 +335,7 @@ export class ZHANetworkVisualizationPage extends LitElement {
symbolSize: 5,
lineStyle: {
width: 1,
color: style.getPropertyValue("--disabled-color"),
color: style.getPropertyValue("--dark-primary-color"),
type: "dotted",
},
ignoreForceLayout: true,

View File

@@ -62,7 +62,7 @@ export class DialogLovelaceDashboardDetail extends LitElement {
return nothing;
}
const defaultPanelUrlPath =
this.hass.systemData?.defaultPanel || DEFAULT_PANEL;
this.hass.systemData?.default_panel || DEFAULT_PANEL;
const titleInvalid = !this._data.title || !this._data.title.trim();
return html`
@@ -260,7 +260,7 @@ export class DialogLovelaceDashboardDetail extends LitElement {
return;
}
const defaultPanel = this.hass.systemData?.defaultPanel || DEFAULT_PANEL;
const defaultPanel = this.hass.systemData?.default_panel || DEFAULT_PANEL;
// Add warning dialog to saying that this will change the default dashboard for all users
const confirm = await showConfirmationDialog(this, {
title: this.hass.localize(
@@ -284,7 +284,7 @@ export class DialogLovelaceDashboardDetail extends LitElement {
saveFrontendSystemData(this.hass.connection, "core", {
...this.hass.systemData,
defaultPanel: urlPath === defaultPanel ? undefined : urlPath,
default_panel: urlPath === defaultPanel ? undefined : urlPath,
});
}
@@ -309,7 +309,20 @@ export class DialogLovelaceDashboardDetail extends LitElement {
}
this.closeDialog();
} catch (err: any) {
this._error = { base: err?.message || "Unknown error" };
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",
};
} finally {
this._submitting = false;
}

View File

@@ -404,7 +404,7 @@ export class HaConfigLovelaceDashboards extends LitElement {
return html` <hass-loading-screen></hass-loading-screen> `;
}
const defaultPanel = this.hass.systemData?.defaultPanel || DEFAULT_PANEL;
const defaultPanel = this.hass.systemData?.default_panel || DEFAULT_PANEL;
return html`
<hass-tabs-subpage-data-table

View File

@@ -25,7 +25,7 @@ class HaPickDashboardRow extends LitElement {
}
protected render(): TemplateResult {
const value = this.hass.userData?.defaultPanel || USE_SYSTEM_VALUE;
const value = this.hass.userData?.default_panel || USE_SYSTEM_VALUE;
return html`
<ha-settings-row .narrow=${this.narrow}>
<span slot="heading">
@@ -84,12 +84,12 @@ class HaPickDashboardRow extends LitElement {
return;
}
const urlPath = value === USE_SYSTEM_VALUE ? undefined : value;
if (urlPath === this.hass.userData?.defaultPanel) {
if (urlPath === this.hass.userData?.default_panel) {
return;
}
saveFrontendUserData(this.hass.connection, "core", {
...this.hass.userData,
defaultPanel: urlPath,
default_panel: urlPath,
});
}
}

View File

@@ -3265,7 +3265,10 @@
"create_helper": "Create helper",
"no_helpers": "Looks like you don't have any helpers yet!",
"search": "Search {number} {number, plural,\n one {helper}\n other {helpers}\n}",
"error_information": "Error information"
"error_information": "Error information",
"delete_confirm_title": "Delete helper?",
"delete_confirm_text": "Are you sure you want to delete {name}?",
"delete_failed": "Failed to delete helper"
},
"dialog": {
"create": "Create",
@@ -4793,7 +4796,8 @@
"headers": {
"name": "Name",
"type": "Type",
"file_name": "File name"
"file_name": "File name",
"usage_count": "In use"
},
"types": {
"automation": "Automation",