mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-27 11:16:35 +00:00
Reorder config entries in device pages (#17105)
This commit is contained in:
parent
d6f8941098
commit
23ac7501b3
@ -1,6 +1,6 @@
|
|||||||
import { UnsubscribeFunc } from "home-assistant-js-websocket";
|
import type { UnsubscribeFunc } from "home-assistant-js-websocket";
|
||||||
import { HomeAssistant } from "../types";
|
import type { HomeAssistant } from "../types";
|
||||||
import { IntegrationType } from "./integration";
|
import type { IntegrationManifest, IntegrationType } from "./integration";
|
||||||
|
|
||||||
export interface ConfigEntry {
|
export interface ConfigEntry {
|
||||||
entry_id: string;
|
entry_id: string;
|
||||||
@ -143,3 +143,23 @@ export const enableConfigEntry = (hass: HomeAssistant, configEntryId: string) =>
|
|||||||
entry_id: configEntryId,
|
entry_id: configEntryId,
|
||||||
disabled_by: null,
|
disabled_by: null,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const sortConfigEntries = (
|
||||||
|
configEntries: ConfigEntry[],
|
||||||
|
manifestLookup: { [domain: string]: IntegrationManifest }
|
||||||
|
): ConfigEntry[] => {
|
||||||
|
const sortedConfigEntries = [...configEntries];
|
||||||
|
|
||||||
|
const getScore = (entry: ConfigEntry) => {
|
||||||
|
const manifest = manifestLookup[entry.domain] as
|
||||||
|
| IntegrationManifest
|
||||||
|
| undefined;
|
||||||
|
const isHelper = manifest?.integration_type === "helper";
|
||||||
|
return isHelper ? -1 : 1;
|
||||||
|
};
|
||||||
|
|
||||||
|
const configEntriesCompare = (a: ConfigEntry, b: ConfigEntry) =>
|
||||||
|
getScore(b) - getScore(a);
|
||||||
|
|
||||||
|
return sortedConfigEntries.sort(configEntriesCompare);
|
||||||
|
};
|
||||||
|
@ -41,6 +41,7 @@ import {
|
|||||||
ConfigEntry,
|
ConfigEntry,
|
||||||
disableConfigEntry,
|
disableConfigEntry,
|
||||||
DisableConfigEntryResult,
|
DisableConfigEntryResult,
|
||||||
|
sortConfigEntries,
|
||||||
} from "../../../data/config_entries";
|
} from "../../../data/config_entries";
|
||||||
import {
|
import {
|
||||||
computeDeviceName,
|
computeDeviceName,
|
||||||
@ -60,7 +61,7 @@ import {
|
|||||||
findBatteryEntity,
|
findBatteryEntity,
|
||||||
updateEntityRegistryEntry,
|
updateEntityRegistryEntry,
|
||||||
} from "../../../data/entity_registry";
|
} from "../../../data/entity_registry";
|
||||||
import { domainToName } from "../../../data/integration";
|
import { IntegrationManifest, domainToName } from "../../../data/integration";
|
||||||
import { SceneEntities, showSceneEditor } from "../../../data/scene";
|
import { SceneEntities, showSceneEditor } from "../../../data/scene";
|
||||||
import { findRelated, RelatedResult } from "../../../data/search";
|
import { findRelated, RelatedResult } from "../../../data/search";
|
||||||
import {
|
import {
|
||||||
@ -115,6 +116,8 @@ export class HaConfigDevicePage extends LitElement {
|
|||||||
|
|
||||||
@property({ attribute: false }) public areas!: AreaRegistryEntry[];
|
@property({ attribute: false }) public areas!: AreaRegistryEntry[];
|
||||||
|
|
||||||
|
@property({ attribute: false }) public manifests!: IntegrationManifest[];
|
||||||
|
|
||||||
@property() public deviceId!: string;
|
@property() public deviceId!: string;
|
||||||
|
|
||||||
@property({ type: Boolean, reflect: true }) public narrow!: boolean;
|
@property({ type: Boolean, reflect: true }) public narrow!: boolean;
|
||||||
@ -145,14 +148,24 @@ export class HaConfigDevicePage extends LitElement {
|
|||||||
);
|
);
|
||||||
|
|
||||||
private _integrations = memoizeOne(
|
private _integrations = memoizeOne(
|
||||||
(device: DeviceRegistryEntry, entries: ConfigEntry[]): ConfigEntry[] => {
|
(
|
||||||
|
device: DeviceRegistryEntry,
|
||||||
|
entries: ConfigEntry[],
|
||||||
|
manifests: IntegrationManifest[]
|
||||||
|
): ConfigEntry[] => {
|
||||||
const entryLookup: { [entryId: string]: ConfigEntry } = {};
|
const entryLookup: { [entryId: string]: ConfigEntry } = {};
|
||||||
for (const entry of entries) {
|
for (const entry of entries) {
|
||||||
entryLookup[entry.entry_id] = entry;
|
entryLookup[entry.entry_id] = entry;
|
||||||
}
|
}
|
||||||
return device.config_entries
|
const manifestLookup: { [domain: string]: IntegrationManifest } = {};
|
||||||
.map((entry) => entryLookup[entry])
|
for (const manifest of manifests) {
|
||||||
.filter(Boolean);
|
manifestLookup[manifest.domain] = manifest;
|
||||||
|
}
|
||||||
|
const deviceEntries = device.config_entries
|
||||||
|
.filter((entId) => entId in entryLookup)
|
||||||
|
.map((entry) => entryLookup[entry]);
|
||||||
|
|
||||||
|
return sortConfigEntries(deviceEntries, manifestLookup);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -292,7 +305,11 @@ export class HaConfigDevicePage extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const deviceName = computeDeviceName(device, this.hass);
|
const deviceName = computeDeviceName(device, this.hass);
|
||||||
const integrations = this._integrations(device, this.entries);
|
const integrations = this._integrations(
|
||||||
|
device,
|
||||||
|
this.entries,
|
||||||
|
this.manifests
|
||||||
|
);
|
||||||
const entities = this._entities(this.deviceId, this.entities);
|
const entities = this._entities(this.deviceId, this.entities);
|
||||||
const entitiesByCategory = this._entitiesByCategory(entities);
|
const entitiesByCategory = this._entitiesByCategory(entities);
|
||||||
const batteryEntity = this._batteryEntity(entities);
|
const batteryEntity = this._batteryEntity(entities);
|
||||||
@ -920,7 +937,7 @@ export class HaConfigDevicePage extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let links = await Promise.all(
|
let links = await Promise.all(
|
||||||
this._integrations(device, this.entries).map(
|
this._integrations(device, this.entries, this.manifests).map(
|
||||||
async (entry): Promise<boolean | { link: string; domain: string }> => {
|
async (entry): Promise<boolean | { link: string; domain: string }> => {
|
||||||
if (entry.state !== "loaded") {
|
if (entry.state !== "loaded") {
|
||||||
return false;
|
return false;
|
||||||
@ -983,50 +1000,55 @@ export class HaConfigDevicePage extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const buttons: DeviceAction[] = [];
|
const buttons: DeviceAction[] = [];
|
||||||
this._integrations(device, this.entries).forEach((entry) => {
|
this._integrations(device, this.entries, this.manifests).forEach(
|
||||||
if (entry.state !== "loaded" || !entry.supports_remove_device) {
|
(entry) => {
|
||||||
return;
|
if (entry.state !== "loaded" || !entry.supports_remove_device) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
buttons.push({
|
||||||
|
action: async () => {
|
||||||
|
const confirmed = await showConfirmationDialog(this, {
|
||||||
|
text:
|
||||||
|
this._integrations(device, this.entries, this.manifests)
|
||||||
|
.length > 1
|
||||||
|
? this.hass.localize(
|
||||||
|
`ui.panel.config.devices.confirm_delete_integration`,
|
||||||
|
{
|
||||||
|
integration: domainToName(
|
||||||
|
this.hass.localize,
|
||||||
|
entry.domain
|
||||||
|
),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
: this.hass.localize(
|
||||||
|
`ui.panel.config.devices.confirm_delete`
|
||||||
|
),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!confirmed) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await removeConfigEntryFromDevice(
|
||||||
|
this.hass!,
|
||||||
|
this.deviceId,
|
||||||
|
entry.entry_id
|
||||||
|
);
|
||||||
|
},
|
||||||
|
classes: "warning",
|
||||||
|
icon: mdiDelete,
|
||||||
|
label:
|
||||||
|
this._integrations(device, this.entries, this.manifests).length > 1
|
||||||
|
? this.hass.localize(
|
||||||
|
`ui.panel.config.devices.delete_device_integration`,
|
||||||
|
{
|
||||||
|
integration: domainToName(this.hass.localize, entry.domain),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
: this.hass.localize(`ui.panel.config.devices.delete_device`),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
buttons.push({
|
);
|
||||||
action: async () => {
|
|
||||||
const confirmed = await showConfirmationDialog(this, {
|
|
||||||
text:
|
|
||||||
this._integrations(device, this.entries).length > 1
|
|
||||||
? this.hass.localize(
|
|
||||||
`ui.panel.config.devices.confirm_delete_integration`,
|
|
||||||
{
|
|
||||||
integration: domainToName(
|
|
||||||
this.hass.localize,
|
|
||||||
entry.domain
|
|
||||||
),
|
|
||||||
}
|
|
||||||
)
|
|
||||||
: this.hass.localize(`ui.panel.config.devices.confirm_delete`),
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!confirmed) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
await removeConfigEntryFromDevice(
|
|
||||||
this.hass!,
|
|
||||||
this.deviceId,
|
|
||||||
entry.entry_id
|
|
||||||
);
|
|
||||||
},
|
|
||||||
classes: "warning",
|
|
||||||
icon: mdiDelete,
|
|
||||||
label:
|
|
||||||
this._integrations(device, this.entries).length > 1
|
|
||||||
? this.hass.localize(
|
|
||||||
`ui.panel.config.devices.delete_device_integration`,
|
|
||||||
{
|
|
||||||
integration: domainToName(this.hass.localize, entry.domain),
|
|
||||||
}
|
|
||||||
)
|
|
||||||
: this.hass.localize(`ui.panel.config.devices.delete_device`),
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
if (buttons.length > 0) {
|
if (buttons.length > 0) {
|
||||||
this._deleteButtons = buttons;
|
this._deleteButtons = buttons;
|
||||||
@ -1061,9 +1083,11 @@ export class HaConfigDevicePage extends LitElement {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const domains = this._integrations(device, this.entries).map(
|
const domains = this._integrations(
|
||||||
(int) => int.domain
|
device,
|
||||||
);
|
this.entries,
|
||||||
|
this.manifests
|
||||||
|
).map((int) => int.domain);
|
||||||
|
|
||||||
if (domains.includes("mqtt")) {
|
if (domains.includes("mqtt")) {
|
||||||
const mqtt = await import(
|
const mqtt = await import(
|
||||||
@ -1103,9 +1127,11 @@ export class HaConfigDevicePage extends LitElement {
|
|||||||
|
|
||||||
const deviceAlerts: DeviceAlert[] = [];
|
const deviceAlerts: DeviceAlert[] = [];
|
||||||
|
|
||||||
const domains = this._integrations(device, this.entries).map(
|
const domains = this._integrations(
|
||||||
(int) => int.domain
|
device,
|
||||||
);
|
this.entries,
|
||||||
|
this.manifests
|
||||||
|
).map((int) => int.domain);
|
||||||
|
|
||||||
if (domains.includes("zwave_js")) {
|
if (domains.includes("zwave_js")) {
|
||||||
const zwave = await import(
|
const zwave = await import(
|
||||||
|
@ -25,7 +25,7 @@ import "../../../components/ha-check-list-item";
|
|||||||
import "../../../components/ha-fab";
|
import "../../../components/ha-fab";
|
||||||
import "../../../components/ha-icon-button";
|
import "../../../components/ha-icon-button";
|
||||||
import { AreaRegistryEntry } from "../../../data/area_registry";
|
import { AreaRegistryEntry } from "../../../data/area_registry";
|
||||||
import { ConfigEntry } from "../../../data/config_entries";
|
import { ConfigEntry, sortConfigEntries } from "../../../data/config_entries";
|
||||||
import {
|
import {
|
||||||
computeDeviceName,
|
computeDeviceName,
|
||||||
DeviceEntityLookup,
|
DeviceEntityLookup,
|
||||||
@ -36,7 +36,7 @@ import {
|
|||||||
findBatteryChargingEntity,
|
findBatteryChargingEntity,
|
||||||
findBatteryEntity,
|
findBatteryEntity,
|
||||||
} from "../../../data/entity_registry";
|
} from "../../../data/entity_registry";
|
||||||
import { domainToName } from "../../../data/integration";
|
import { IntegrationManifest, domainToName } from "../../../data/integration";
|
||||||
import "../../../layouts/hass-tabs-subpage-data-table";
|
import "../../../layouts/hass-tabs-subpage-data-table";
|
||||||
import { haStyle } from "../../../resources/styles";
|
import { haStyle } from "../../../resources/styles";
|
||||||
import { HomeAssistant, Route } from "../../../types";
|
import { HomeAssistant, Route } from "../../../types";
|
||||||
@ -68,6 +68,8 @@ export class HaConfigDeviceDashboard extends LitElement {
|
|||||||
|
|
||||||
@property() public areas!: AreaRegistryEntry[];
|
@property() public areas!: AreaRegistryEntry[];
|
||||||
|
|
||||||
|
@property() public manifests!: IntegrationManifest[];
|
||||||
|
|
||||||
@property() public route!: Route;
|
@property() public route!: Route;
|
||||||
|
|
||||||
@state() private _searchParms = new URLSearchParams(window.location.search);
|
@state() private _searchParms = new URLSearchParams(window.location.search);
|
||||||
@ -149,6 +151,7 @@ export class HaConfigDeviceDashboard extends LitElement {
|
|||||||
entries: ConfigEntry[],
|
entries: ConfigEntry[],
|
||||||
entities: EntityRegistryEntry[],
|
entities: EntityRegistryEntry[],
|
||||||
areas: AreaRegistryEntry[],
|
areas: AreaRegistryEntry[],
|
||||||
|
manifests: IntegrationManifest[],
|
||||||
filters: URLSearchParams,
|
filters: URLSearchParams,
|
||||||
showDisabled: boolean,
|
showDisabled: boolean,
|
||||||
localize: LocalizeFunc
|
localize: LocalizeFunc
|
||||||
@ -186,6 +189,11 @@ export class HaConfigDeviceDashboard extends LitElement {
|
|||||||
areaLookup[area.area_id] = area;
|
areaLookup[area.area_id] = area;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const manifestLookup: { [domain: string]: IntegrationManifest } = {};
|
||||||
|
for (const manifest of manifests) {
|
||||||
|
manifestLookup[manifest.domain] = manifest;
|
||||||
|
}
|
||||||
|
|
||||||
let filterConfigEntry: ConfigEntry | undefined;
|
let filterConfigEntry: ConfigEntry | undefined;
|
||||||
|
|
||||||
const filteredDomains = new Set<string>();
|
const filteredDomains = new Set<string>();
|
||||||
@ -217,47 +225,51 @@ export class HaConfigDeviceDashboard extends LitElement {
|
|||||||
outputDevices = outputDevices.filter((device) => !device.disabled_by);
|
outputDevices = outputDevices.filter((device) => !device.disabled_by);
|
||||||
}
|
}
|
||||||
|
|
||||||
outputDevices = outputDevices.map((device) => ({
|
outputDevices = outputDevices.map((device) => {
|
||||||
...device,
|
const deviceEntries = sortConfigEntries(
|
||||||
name: computeDeviceName(
|
device.config_entries
|
||||||
device,
|
.filter((entId) => entId in entryLookup)
|
||||||
this.hass,
|
.map((entId) => entryLookup[entId]),
|
||||||
deviceEntityLookup[device.id]
|
manifestLookup
|
||||||
),
|
);
|
||||||
model:
|
return {
|
||||||
device.model ||
|
...device,
|
||||||
`<${localize("ui.panel.config.devices.data_table.unknown")}>`,
|
name: computeDeviceName(
|
||||||
manufacturer:
|
device,
|
||||||
device.manufacturer ||
|
this.hass,
|
||||||
`<${localize("ui.panel.config.devices.data_table.unknown")}>`,
|
deviceEntityLookup[device.id]
|
||||||
area:
|
),
|
||||||
device.area_id && areaLookup[device.area_id]
|
model:
|
||||||
? areaLookup[device.area_id].name
|
device.model ||
|
||||||
: "—",
|
`<${localize("ui.panel.config.devices.data_table.unknown")}>`,
|
||||||
integration: device.config_entries.length
|
manufacturer:
|
||||||
? device.config_entries
|
device.manufacturer ||
|
||||||
.filter((entId) => entId in entryLookup)
|
`<${localize("ui.panel.config.devices.data_table.unknown")}>`,
|
||||||
.map(
|
area:
|
||||||
(entId) =>
|
device.area_id && areaLookup[device.area_id]
|
||||||
localize(`component.${entryLookup[entId].domain}.title`) ||
|
? areaLookup[device.area_id].name
|
||||||
entryLookup[entId].domain
|
: "—",
|
||||||
)
|
integration: deviceEntries.length
|
||||||
.join(", ")
|
? deviceEntries
|
||||||
: this.hass.localize(
|
.map(
|
||||||
"ui.panel.config.devices.data_table.no_integration"
|
(entry) =>
|
||||||
),
|
localize(`component.${entry.domain}.title`) || entry.domain
|
||||||
domains: device.config_entries
|
)
|
||||||
.filter((entId) => entId in entryLookup)
|
.join(", ")
|
||||||
.map((entId) => entryLookup[entId].domain),
|
: this.hass.localize(
|
||||||
battery_entity: [
|
"ui.panel.config.devices.data_table.no_integration"
|
||||||
this._batteryEntity(device.id, deviceEntityLookup),
|
),
|
||||||
this._batteryChargingEntity(device.id, deviceEntityLookup),
|
domains: deviceEntries.map((entry) => entry.domain),
|
||||||
],
|
battery_entity: [
|
||||||
battery_level:
|
this._batteryEntity(device.id, deviceEntityLookup),
|
||||||
this.hass.states[
|
this._batteryChargingEntity(device.id, deviceEntityLookup),
|
||||||
this._batteryEntity(device.id, deviceEntityLookup) || ""
|
],
|
||||||
]?.state,
|
battery_level:
|
||||||
}));
|
this.hass.states[
|
||||||
|
this._batteryEntity(device.id, deviceEntityLookup) || ""
|
||||||
|
]?.state,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
this._numHiddenDevices = startLength - outputDevices.length;
|
this._numHiddenDevices = startLength - outputDevices.length;
|
||||||
return {
|
return {
|
||||||
@ -429,6 +441,7 @@ export class HaConfigDeviceDashboard extends LitElement {
|
|||||||
this.entries,
|
this.entries,
|
||||||
this.entities,
|
this.entities,
|
||||||
this.areas,
|
this.areas,
|
||||||
|
this.manifests,
|
||||||
this._searchParms,
|
this._searchParms,
|
||||||
this._showDisabled,
|
this._showDisabled,
|
||||||
this.hass.localize
|
this.hass.localize
|
||||||
@ -565,6 +578,7 @@ export class HaConfigDeviceDashboard extends LitElement {
|
|||||||
this.entries,
|
this.entries,
|
||||||
this.entities,
|
this.entities,
|
||||||
this.areas,
|
this.areas,
|
||||||
|
this.manifests,
|
||||||
this._searchParms,
|
this._searchParms,
|
||||||
this._showDisabled,
|
this._showDisabled,
|
||||||
this.hass.localize
|
this.hass.localize
|
||||||
|
@ -14,6 +14,10 @@ import {
|
|||||||
EntityRegistryEntry,
|
EntityRegistryEntry,
|
||||||
subscribeEntityRegistry,
|
subscribeEntityRegistry,
|
||||||
} from "../../../data/entity_registry";
|
} from "../../../data/entity_registry";
|
||||||
|
import {
|
||||||
|
IntegrationManifest,
|
||||||
|
fetchIntegrationManifests,
|
||||||
|
} from "../../../data/integration";
|
||||||
import {
|
import {
|
||||||
HassRouterPage,
|
HassRouterPage,
|
||||||
RouterOptions,
|
RouterOptions,
|
||||||
@ -47,6 +51,8 @@ class HaConfigDevices extends HassRouterPage {
|
|||||||
|
|
||||||
@state() private _configEntries: ConfigEntry[] = [];
|
@state() private _configEntries: ConfigEntry[] = [];
|
||||||
|
|
||||||
|
@state() private _manifests: IntegrationManifest[] = [];
|
||||||
|
|
||||||
@state()
|
@state()
|
||||||
private _entityRegistryEntries: EntityRegistryEntry[] = [];
|
private _entityRegistryEntries: EntityRegistryEntry[] = [];
|
||||||
|
|
||||||
@ -99,6 +105,7 @@ class HaConfigDevices extends HassRouterPage {
|
|||||||
|
|
||||||
pageEl.entities = this._entityRegistryEntries;
|
pageEl.entities = this._entityRegistryEntries;
|
||||||
pageEl.entries = this._configEntries;
|
pageEl.entries = this._configEntries;
|
||||||
|
pageEl.manifests = this._manifests;
|
||||||
pageEl.devices = this._deviceRegistryEntries;
|
pageEl.devices = this._deviceRegistryEntries;
|
||||||
pageEl.areas = this._areas;
|
pageEl.areas = this._areas;
|
||||||
pageEl.narrow = this.narrow;
|
pageEl.narrow = this.narrow;
|
||||||
@ -111,6 +118,10 @@ class HaConfigDevices extends HassRouterPage {
|
|||||||
getConfigEntries(this.hass).then((configEntries) => {
|
getConfigEntries(this.hass).then((configEntries) => {
|
||||||
this._configEntries = configEntries;
|
this._configEntries = configEntries;
|
||||||
});
|
});
|
||||||
|
fetchIntegrationManifests(this.hass).then((manifests) => {
|
||||||
|
this._manifests = manifests;
|
||||||
|
});
|
||||||
|
|
||||||
if (this._unsubs) {
|
if (this._unsubs) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user