mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-19 15:26:36 +00:00
Add initial DHCP discovery panel (#25086)
This commit is contained in:
parent
3647722824
commit
1c15116052
83
src/data/dhcp.ts
Normal file
83
src/data/dhcp.ts
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
import {
|
||||||
|
createCollection,
|
||||||
|
type Connection,
|
||||||
|
type UnsubscribeFunc,
|
||||||
|
} from "home-assistant-js-websocket";
|
||||||
|
import type { Store } from "home-assistant-js-websocket/dist/store";
|
||||||
|
import type { DataTableRowData } from "../components/data-table/ha-data-table";
|
||||||
|
|
||||||
|
export interface DHCPDiscoveryData extends DataTableRowData {
|
||||||
|
mac_address: string;
|
||||||
|
hostname: string;
|
||||||
|
ip_address: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface DHCPRemoveDiscoveryData {
|
||||||
|
mac_address: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface DHCPSubscriptionMessage {
|
||||||
|
add?: DHCPDiscoveryData[];
|
||||||
|
change?: DHCPDiscoveryData[];
|
||||||
|
remove?: DHCPRemoveDiscoveryData[];
|
||||||
|
}
|
||||||
|
|
||||||
|
const subscribeDHCPDiscoveryUpdates = (
|
||||||
|
conn: Connection,
|
||||||
|
store: Store<DHCPDiscoveryData[]>
|
||||||
|
): Promise<UnsubscribeFunc> =>
|
||||||
|
conn.subscribeMessage<DHCPSubscriptionMessage>(
|
||||||
|
(event) => {
|
||||||
|
const data = [...(store.state || [])];
|
||||||
|
if (event.add) {
|
||||||
|
for (const deviceData of event.add) {
|
||||||
|
const index = data.findIndex(
|
||||||
|
(d) => d.mac_address === deviceData.mac_address
|
||||||
|
);
|
||||||
|
if (index === -1) {
|
||||||
|
data.push(deviceData);
|
||||||
|
} else {
|
||||||
|
data[index] = deviceData;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (event.change) {
|
||||||
|
for (const deviceData of event.change) {
|
||||||
|
const index = data.findIndex(
|
||||||
|
(d) => d.mac_address === deviceData.mac_address
|
||||||
|
);
|
||||||
|
if (index !== -1) {
|
||||||
|
data[index] = deviceData;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (event.remove) {
|
||||||
|
for (const deviceData of event.remove) {
|
||||||
|
const index = data.findIndex(
|
||||||
|
(d) => d.mac_address === deviceData.mac_address
|
||||||
|
);
|
||||||
|
if (index !== -1) {
|
||||||
|
data.splice(index, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
store.setState(data, true);
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: `dhcp/subscribe_discovery`,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
export const subscribeDHCPDiscovery = (
|
||||||
|
conn: Connection,
|
||||||
|
callbackFunction: (dhcpDiscoveryData: DHCPDiscoveryData[]) => void
|
||||||
|
) =>
|
||||||
|
createCollection<DHCPDiscoveryData[]>(
|
||||||
|
"_dhcpDiscoveryRows",
|
||||||
|
() => Promise.resolve<DHCPDiscoveryData[]>([]), // empty array as initial state
|
||||||
|
|
||||||
|
subscribeDHCPDiscoveryUpdates,
|
||||||
|
conn,
|
||||||
|
callbackFunction
|
||||||
|
);
|
@ -6,6 +6,7 @@ import { debounce } from "../common/util/debounce";
|
|||||||
|
|
||||||
export const integrationsWithPanel = {
|
export const integrationsWithPanel = {
|
||||||
bluetooth: "config/bluetooth",
|
bluetooth: "config/bluetooth",
|
||||||
|
dhcp: "config/dhcp",
|
||||||
matter: "config/matter",
|
matter: "config/matter",
|
||||||
mqtt: "config/mqtt",
|
mqtt: "config/mqtt",
|
||||||
thread: "config/thread",
|
thread: "config/thread",
|
||||||
|
@ -8,6 +8,7 @@ import type { DeviceRegistryEntry } from "../../../../data/device_registry";
|
|||||||
import { haStyle } from "../../../../resources/styles";
|
import { haStyle } from "../../../../resources/styles";
|
||||||
import type { HomeAssistant } from "../../../../types";
|
import type { HomeAssistant } from "../../../../types";
|
||||||
import { createSearchParam } from "../../../../common/url/search-params";
|
import { createSearchParam } from "../../../../common/url/search-params";
|
||||||
|
import { isComponentLoaded } from "../../../../common/config/is_component_loaded";
|
||||||
|
|
||||||
@customElement("ha-device-info-card")
|
@customElement("ha-device-info-card")
|
||||||
export class HaDeviceCard extends LitElement {
|
export class HaDeviceCard extends LitElement {
|
||||||
@ -103,7 +104,8 @@ export class HaDeviceCard extends LitElement {
|
|||||||
${this._getAddresses().map(
|
${this._getAddresses().map(
|
||||||
([type, value]) => html`
|
([type, value]) => html`
|
||||||
<div class="extra-info">
|
<div class="extra-info">
|
||||||
${type === "bluetooth"
|
${type === "bluetooth" &&
|
||||||
|
isComponentLoaded(this.hass, "bluetooth")
|
||||||
? html`${titleCase(type)}
|
? html`${titleCase(type)}
|
||||||
<a
|
<a
|
||||||
href="/config/bluetooth/advertisement-monitor?${createSearchParam(
|
href="/config/bluetooth/advertisement-monitor?${createSearchParam(
|
||||||
@ -111,8 +113,16 @@ export class HaDeviceCard extends LitElement {
|
|||||||
)}"
|
)}"
|
||||||
>${value.toUpperCase()}</a
|
>${value.toUpperCase()}</a
|
||||||
>`
|
>`
|
||||||
: html`${type === "mac" ? "MAC" : titleCase(type)}:
|
: type === "mac" && isComponentLoaded(this.hass, "dhcp")
|
||||||
${value.toUpperCase()}`}
|
? html`${titleCase(type)}
|
||||||
|
<a
|
||||||
|
href="/config/dhcp?${createSearchParam({
|
||||||
|
mac_address: value,
|
||||||
|
})}"
|
||||||
|
>${value.toUpperCase()}</a
|
||||||
|
>`
|
||||||
|
: html`${type === "mac" ? "MAC" : titleCase(type)}:
|
||||||
|
${value.toUpperCase()}`}
|
||||||
</div>
|
</div>
|
||||||
`
|
`
|
||||||
)}
|
)}
|
||||||
|
@ -555,6 +555,11 @@ class HaPanelConfig extends SubscribeMixin(HassRouterPage) {
|
|||||||
"./integrations/integration-panels/bluetooth/bluetooth-config-dashboard-router"
|
"./integrations/integration-panels/bluetooth/bluetooth-config-dashboard-router"
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
dhcp: {
|
||||||
|
tag: "dhcp-config-panel",
|
||||||
|
load: () =>
|
||||||
|
import("./integrations/integration-panels/dhcp/dhcp-config-panel"),
|
||||||
|
},
|
||||||
application_credentials: {
|
application_credentials: {
|
||||||
tag: "ha-config-application-credentials",
|
tag: "ha-config-application-credentials",
|
||||||
load: () =>
|
load: () =>
|
||||||
|
@ -0,0 +1,111 @@
|
|||||||
|
import type { CSSResultGroup, TemplateResult, PropertyValues } from "lit";
|
||||||
|
import { html, LitElement } from "lit";
|
||||||
|
import { customElement, property, state } from "lit/decorators";
|
||||||
|
import memoizeOne from "memoize-one";
|
||||||
|
import type { UnsubscribeFunc } from "home-assistant-js-websocket";
|
||||||
|
import type { LocalizeFunc } from "../../../../../common/translations/localize";
|
||||||
|
import type { DataTableColumnContainer } from "../../../../../components/data-table/ha-data-table";
|
||||||
|
import { extractSearchParamsObject } from "../../../../../common/url/search-params";
|
||||||
|
import "../../../../../components/ha-fab";
|
||||||
|
import "../../../../../components/ha-icon-button";
|
||||||
|
import "../../../../../layouts/hass-tabs-subpage-data-table";
|
||||||
|
import { haStyle } from "../../../../../resources/styles";
|
||||||
|
import type { HomeAssistant, Route } from "../../../../../types";
|
||||||
|
import type { DHCPDiscoveryData } from "../../../../../data/dhcp";
|
||||||
|
import { SubscribeMixin } from "../../../../../mixins/subscribe-mixin";
|
||||||
|
|
||||||
|
import { subscribeDHCPDiscovery } from "../../../../../data/dhcp";
|
||||||
|
|
||||||
|
@customElement("dhcp-config-panel")
|
||||||
|
export class DHCPConfigPanel extends SubscribeMixin(LitElement) {
|
||||||
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
|
|
||||||
|
@property({ attribute: false }) public route!: Route;
|
||||||
|
|
||||||
|
@state() private _macAddress?: string;
|
||||||
|
|
||||||
|
@property({ type: Boolean }) public narrow = false;
|
||||||
|
|
||||||
|
@property({ attribute: "is-wide", type: Boolean }) public isWide = false;
|
||||||
|
|
||||||
|
@state() private _data: DHCPDiscoveryData[] = [];
|
||||||
|
|
||||||
|
public hassSubscribe(): UnsubscribeFunc[] {
|
||||||
|
return [
|
||||||
|
subscribeDHCPDiscovery(this.hass.connection, (data) => {
|
||||||
|
this._data = data;
|
||||||
|
}),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
private _columns = memoizeOne(
|
||||||
|
(localize: LocalizeFunc): DataTableColumnContainer => {
|
||||||
|
const columns: DataTableColumnContainer<DHCPDiscoveryData> = {
|
||||||
|
mac_address: {
|
||||||
|
title: localize("ui.panel.config.dhcp.mac_address"),
|
||||||
|
sortable: true,
|
||||||
|
filterable: true,
|
||||||
|
showNarrow: true,
|
||||||
|
main: true,
|
||||||
|
hideable: false,
|
||||||
|
moveable: false,
|
||||||
|
direction: "asc",
|
||||||
|
},
|
||||||
|
hostname: {
|
||||||
|
title: localize("ui.panel.config.dhcp.hostname"),
|
||||||
|
filterable: true,
|
||||||
|
sortable: true,
|
||||||
|
},
|
||||||
|
ip_address: {
|
||||||
|
title: localize("ui.panel.config.dhcp.ip_address"),
|
||||||
|
filterable: true,
|
||||||
|
sortable: true,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
return columns;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
private _dataWithIds = memoizeOne((data) =>
|
||||||
|
data.map((row) => ({
|
||||||
|
...row,
|
||||||
|
id: row.mac_address,
|
||||||
|
}))
|
||||||
|
);
|
||||||
|
|
||||||
|
protected willUpdate(changedProps: PropertyValues) {
|
||||||
|
super.willUpdate(changedProps);
|
||||||
|
|
||||||
|
if (this.hasUpdated) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const searchParams = extractSearchParamsObject();
|
||||||
|
const macAddress = searchParams.mac_address;
|
||||||
|
if (macAddress) {
|
||||||
|
this._macAddress = macAddress.toUpperCase();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected render(): TemplateResult {
|
||||||
|
return html`
|
||||||
|
<hass-tabs-subpage-data-table
|
||||||
|
.hass=${this.hass}
|
||||||
|
.narrow=${this.narrow}
|
||||||
|
.route=${this.route}
|
||||||
|
.columns=${this._columns(this.hass.localize)}
|
||||||
|
.data=${this._dataWithIds(this._data)}
|
||||||
|
filter=${this._macAddress || ""}
|
||||||
|
></hass-tabs-subpage-data-table>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
static styles: CSSResultGroup = haStyle;
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"dhcp-config-panel": DHCPConfigPanel;
|
||||||
|
}
|
||||||
|
}
|
@ -110,6 +110,10 @@ export const getMyRedirects = (): Redirects => ({
|
|||||||
component: "bluetooth",
|
component: "bluetooth",
|
||||||
redirect: "/config/bluetooth",
|
redirect: "/config/bluetooth",
|
||||||
},
|
},
|
||||||
|
config_dhcp: {
|
||||||
|
component: "dhcp",
|
||||||
|
redirect: "/config/dhcp",
|
||||||
|
},
|
||||||
config_energy: {
|
config_energy: {
|
||||||
component: "energy",
|
component: "energy",
|
||||||
redirect: "/config/energy/dashboard",
|
redirect: "/config/energy/dashboard",
|
||||||
|
@ -5482,6 +5482,12 @@
|
|||||||
"service_uuids": "Service UUIDs",
|
"service_uuids": "Service UUIDs",
|
||||||
"copy_to_clipboard": "[%key:ui::panel::config::automation::editor::copy_to_clipboard%]"
|
"copy_to_clipboard": "[%key:ui::panel::config::automation::editor::copy_to_clipboard%]"
|
||||||
},
|
},
|
||||||
|
"dhcp": {
|
||||||
|
"title": "DHCP discovery",
|
||||||
|
"mac_address": "MAC Address",
|
||||||
|
"hostname": "Hostname",
|
||||||
|
"ip_address": "IP Address"
|
||||||
|
},
|
||||||
"thread": {
|
"thread": {
|
||||||
"other_networks": "Other networks",
|
"other_networks": "Other networks",
|
||||||
"my_network": "Preferred network",
|
"my_network": "Preferred network",
|
||||||
|
Loading…
x
Reference in New Issue
Block a user