mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-19 07:16:39 +00:00
Allow reset of otbr network, thread panel fixes (#15815)
This commit is contained in:
parent
c9d709152a
commit
24c3ddb96b
@ -9,3 +9,24 @@ export const getOTBRInfo = (hass: HomeAssistant): Promise<OTBRInfo> =>
|
|||||||
hass.callWS({
|
hass.callWS({
|
||||||
type: "otbr/info",
|
type: "otbr/info",
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const OTBRCreateNetwork = (hass: HomeAssistant): Promise<void> =>
|
||||||
|
hass.callWS({
|
||||||
|
type: "otbr/create_network",
|
||||||
|
});
|
||||||
|
|
||||||
|
export const OTBRSetNetwork = (
|
||||||
|
hass: HomeAssistant,
|
||||||
|
dataset_id: string
|
||||||
|
): Promise<void> =>
|
||||||
|
hass.callWS({
|
||||||
|
type: "otbr/set_network",
|
||||||
|
dataset_id,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const OTBRGetExtendedAddress = (
|
||||||
|
hass: HomeAssistant
|
||||||
|
): Promise<{ extended_address: string }> =>
|
||||||
|
hass.callWS({
|
||||||
|
type: "otbr/get_extended_address",
|
||||||
|
});
|
||||||
|
@ -4,6 +4,7 @@ export interface ThreadRouter {
|
|||||||
brand: "google" | "apple" | "homeassistant";
|
brand: "google" | "apple" | "homeassistant";
|
||||||
server: string;
|
server: string;
|
||||||
extended_pan_id: string;
|
extended_pan_id: string;
|
||||||
|
extended_address: string;
|
||||||
model_name: string | null;
|
model_name: string | null;
|
||||||
network_name: string;
|
network_name: string;
|
||||||
vendor_name: string;
|
vendor_name: string;
|
||||||
@ -87,3 +88,12 @@ export const removeThreadDataSet = (
|
|||||||
type: "thread/delete_dataset",
|
type: "thread/delete_dataset",
|
||||||
dataset_id,
|
dataset_id,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const setPreferredThreadDataSet = (
|
||||||
|
hass: HomeAssistant,
|
||||||
|
dataset_id: string
|
||||||
|
): Promise<void> =>
|
||||||
|
hass.callWS({
|
||||||
|
type: "thread/set_preferred_dataset",
|
||||||
|
dataset_id,
|
||||||
|
});
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import "@material/mwc-button";
|
import "@material/mwc-button";
|
||||||
|
import { ActionDetail } from "@material/mwc-list";
|
||||||
import {
|
import {
|
||||||
mdiDeleteOutline,
|
mdiDeleteOutline,
|
||||||
mdiDevices,
|
mdiDevices,
|
||||||
@ -14,11 +15,18 @@ import { extractSearchParam } from "../../../../../common/url/search-params";
|
|||||||
import "../../../../../components/ha-card";
|
import "../../../../../components/ha-card";
|
||||||
import { getSignedPath } from "../../../../../data/auth";
|
import { getSignedPath } from "../../../../../data/auth";
|
||||||
import { getConfigEntryDiagnosticsDownloadUrl } from "../../../../../data/diagnostics";
|
import { getConfigEntryDiagnosticsDownloadUrl } from "../../../../../data/diagnostics";
|
||||||
import { getOTBRInfo } from "../../../../../data/otbr";
|
import {
|
||||||
|
getOTBRInfo,
|
||||||
|
OTBRCreateNetwork,
|
||||||
|
OTBRGetExtendedAddress,
|
||||||
|
OTBRInfo,
|
||||||
|
OTBRSetNetwork,
|
||||||
|
} from "../../../../../data/otbr";
|
||||||
import {
|
import {
|
||||||
addThreadDataSet,
|
addThreadDataSet,
|
||||||
listThreadDataSets,
|
listThreadDataSets,
|
||||||
removeThreadDataSet,
|
removeThreadDataSet,
|
||||||
|
setPreferredThreadDataSet,
|
||||||
subscribeDiscoverThreadRouters,
|
subscribeDiscoverThreadRouters,
|
||||||
ThreadDataSet,
|
ThreadDataSet,
|
||||||
ThreadRouter,
|
ThreadRouter,
|
||||||
@ -54,6 +62,8 @@ export class ThreadConfigPanel extends SubscribeMixin(LitElement) {
|
|||||||
|
|
||||||
@state() private _datasets: ThreadDataSet[] = [];
|
@state() private _datasets: ThreadDataSet[] = [];
|
||||||
|
|
||||||
|
@state() private _otbrInfo?: OTBRInfo & { extended_address?: string };
|
||||||
|
|
||||||
protected render(): TemplateResult {
|
protected render(): TemplateResult {
|
||||||
const networks = this._groupRoutersByNetwork(this._routers, this._datasets);
|
const networks = this._groupRoutersByNetwork(this._routers, this._datasets);
|
||||||
|
|
||||||
@ -82,11 +92,13 @@ export class ThreadConfigPanel extends SubscribeMixin(LitElement) {
|
|||||||
"ui.panel.config.thread.add_dataset_from_tlv"
|
"ui.panel.config.thread.add_dataset_from_tlv"
|
||||||
)}</mwc-list-item
|
)}</mwc-list-item
|
||||||
>
|
>
|
||||||
<mwc-list-item @click=${this._addOTBR}
|
${!this._otbrInfo
|
||||||
|
? html`<mwc-list-item @click=${this._addOTBR}
|
||||||
>${this.hass.localize(
|
>${this.hass.localize(
|
||||||
"ui.panel.config.thread.add_open_thread_border_router"
|
"ui.panel.config.thread.add_open_thread_border_router"
|
||||||
)}</mwc-list-item
|
)}</mwc-list-item
|
||||||
>
|
>`
|
||||||
|
: ""}
|
||||||
</ha-button-menu>
|
</ha-button-menu>
|
||||||
<div class="content">
|
<div class="content">
|
||||||
<h1>${this.hass.localize("ui.panel.config.thread.my_network")}</h1>
|
<h1>${this.hass.localize("ui.panel.config.thread.my_network")}</h1>
|
||||||
@ -150,7 +162,13 @@ export class ThreadConfigPanel extends SubscribeMixin(LitElement) {
|
|||||||
</div>
|
</div>
|
||||||
${network.routers.map(
|
${network.routers.map(
|
||||||
(router) =>
|
(router) =>
|
||||||
html`<ha-list-item noninteractive twoline graphic="avatar">
|
html`<ha-list-item
|
||||||
|
class="router"
|
||||||
|
twoline
|
||||||
|
graphic="avatar"
|
||||||
|
.hasMeta=${router.extended_address ===
|
||||||
|
this._otbrInfo?.extended_address}
|
||||||
|
>
|
||||||
<img
|
<img
|
||||||
slot="graphic"
|
slot="graphic"
|
||||||
.src=${brandsUrl({
|
.src=${brandsUrl({
|
||||||
@ -166,22 +184,69 @@ export class ThreadConfigPanel extends SubscribeMixin(LitElement) {
|
|||||||
/>
|
/>
|
||||||
${router.model_name || router.server.replace(".local.", "")}
|
${router.model_name || router.server.replace(".local.", "")}
|
||||||
<span slot="secondary">${router.server}</span>
|
<span slot="secondary">${router.server}</span>
|
||||||
|
${router.extended_address === this._otbrInfo?.extended_address
|
||||||
|
? html`<ha-button-menu
|
||||||
|
corner="BOTTOM_START"
|
||||||
|
slot="meta"
|
||||||
|
@action=${this._handleRouterAction}
|
||||||
|
>
|
||||||
|
<ha-icon-button
|
||||||
|
.label=${this.hass.localize(
|
||||||
|
"ui.common.overflow_menu"
|
||||||
|
)}
|
||||||
|
.path=${mdiDotsVertical}
|
||||||
|
slot="trigger"
|
||||||
|
></ha-icon-button
|
||||||
|
><ha-list-item
|
||||||
|
>${this.hass.localize(
|
||||||
|
"ui.panel.config.thread.reset_border_router"
|
||||||
|
)}</ha-list-item
|
||||||
|
>${network.dataset?.preferred
|
||||||
|
? ""
|
||||||
|
: html`<ha-list-item
|
||||||
|
>${this.hass.localize(
|
||||||
|
"ui.panel.config.thread.add_to_my_network"
|
||||||
|
)}</ha-list-item
|
||||||
|
></ha-button-menu
|
||||||
|
>`}</ha-button-menu
|
||||||
|
>`
|
||||||
|
: ""}
|
||||||
</ha-list-item>`
|
</ha-list-item>`
|
||||||
)}`
|
)}`
|
||||||
: html`<div class="card-content no-routers">
|
: html`<div class="card-content no-routers">
|
||||||
<ha-svg-icon .path=${mdiDevices}></ha-svg-icon>
|
<ha-svg-icon .path=${mdiDevices}></ha-svg-icon>
|
||||||
${this.hass.localize("ui.panel.config.thread.no_border_routers")}
|
${network.dataset?.extended_pan_id &&
|
||||||
|
this._otbrInfo?.active_dataset_tlvs?.includes(
|
||||||
|
network.dataset.extended_pan_id
|
||||||
|
)
|
||||||
|
? html`${this.hass.localize(
|
||||||
|
"ui.panel.config.thread.no_routers_otbr_network"
|
||||||
|
)}
|
||||||
|
<mwc-button @click=${this._resetBorderRouter}
|
||||||
|
>${this.hass.localize(
|
||||||
|
"ui.panel.config.thread.reset_border_router"
|
||||||
|
)}</mwc-button
|
||||||
|
>`
|
||||||
|
: this.hass.localize("ui.panel.config.thread.no_border_routers")}
|
||||||
</div> `}
|
</div> `}
|
||||||
|
${network.dataset && !network.dataset.preferred
|
||||||
|
? html`<div class="card-actions">
|
||||||
|
<mwc-button
|
||||||
|
.datasetId=${network.dataset.dataset_id}
|
||||||
|
@click=${this._setPreferred}
|
||||||
|
>Make preferred network</mwc-button
|
||||||
|
>
|
||||||
|
</div>`
|
||||||
|
: ""}
|
||||||
</ha-card>`;
|
</ha-card>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _showDatasetInfo(ev: Event) {
|
private async _showDatasetInfo(ev: Event) {
|
||||||
const dataset = (ev.currentTarget as any).networkDataset as ThreadDataSet;
|
const dataset = (ev.currentTarget as any).networkDataset as ThreadDataSet;
|
||||||
if (isComponentLoaded(this.hass, "otbr")) {
|
if (this._otbrInfo) {
|
||||||
const otbrInfo = await getOTBRInfo(this.hass);
|
|
||||||
if (
|
if (
|
||||||
dataset.extended_pan_id &&
|
dataset.extended_pan_id &&
|
||||||
otbrInfo.active_dataset_tlvs?.includes(dataset.extended_pan_id)
|
this._otbrInfo.active_dataset_tlvs?.includes(dataset.extended_pan_id)
|
||||||
) {
|
) {
|
||||||
showAlertDialog(this, {
|
showAlertDialog(this, {
|
||||||
title: dataset.network_name,
|
title: dataset.network_name,
|
||||||
@ -189,8 +254,8 @@ export class ThreadConfigPanel extends SubscribeMixin(LitElement) {
|
|||||||
Dataset id: ${dataset.dataset_id}<br />
|
Dataset id: ${dataset.dataset_id}<br />
|
||||||
Pan id: ${dataset.pan_id}<br />
|
Pan id: ${dataset.pan_id}<br />
|
||||||
Extended Pan id: ${dataset.extended_pan_id}<br />
|
Extended Pan id: ${dataset.extended_pan_id}<br />
|
||||||
OTBR URL: ${otbrInfo.url}<br />
|
OTBR URL: ${this._otbrInfo.url}<br />
|
||||||
Active dataset TLVs: ${otbrInfo.active_dataset_tlvs}`,
|
Active dataset TLVs: ${this._otbrInfo.active_dataset_tlvs}`,
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -236,18 +301,21 @@ export class ThreadConfigPanel extends SubscribeMixin(LitElement) {
|
|||||||
let preferred: ThreadNetwork | undefined;
|
let preferred: ThreadNetwork | undefined;
|
||||||
const networks: { [key: string]: ThreadNetwork } = {};
|
const networks: { [key: string]: ThreadNetwork } = {};
|
||||||
for (const router of routers) {
|
for (const router of routers) {
|
||||||
const network = router.network_name;
|
const network = router.extended_pan_id;
|
||||||
if (network in networks) {
|
if (network in networks) {
|
||||||
networks[network].routers!.push(router);
|
networks[network].routers!.push(router);
|
||||||
} else {
|
} else {
|
||||||
networks[network] = { name: network, routers: [router] };
|
networks[network] = { name: router.network_name, routers: [router] };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (const dataset of datasets) {
|
for (const dataset of datasets) {
|
||||||
const network = dataset.network_name;
|
const network = dataset.extended_pan_id;
|
||||||
|
if (!network) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
if (dataset.preferred) {
|
if (dataset.preferred) {
|
||||||
preferred = {
|
preferred = {
|
||||||
name: network,
|
name: dataset.network_name,
|
||||||
dataset: dataset,
|
dataset: dataset,
|
||||||
routers: networks[network]?.routers,
|
routers: networks[network]?.routers,
|
||||||
};
|
};
|
||||||
@ -257,7 +325,7 @@ export class ThreadConfigPanel extends SubscribeMixin(LitElement) {
|
|||||||
if (network in networks) {
|
if (network in networks) {
|
||||||
networks[network].dataset = dataset;
|
networks[network].dataset = dataset;
|
||||||
} else {
|
} else {
|
||||||
networks[network] = { name: network, dataset: dataset };
|
networks[network] = { name: dataset.network_name, dataset: dataset };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
@ -269,10 +337,24 @@ export class ThreadConfigPanel extends SubscribeMixin(LitElement) {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
private _refresh() {
|
private async _refresh() {
|
||||||
listThreadDataSets(this.hass).then((datasets) => {
|
listThreadDataSets(this.hass).then((datasets) => {
|
||||||
this._datasets = datasets.datasets;
|
this._datasets = datasets.datasets;
|
||||||
});
|
});
|
||||||
|
if (!isComponentLoaded(this.hass, "otbr")) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const _otbrAddress = OTBRGetExtendedAddress(this.hass);
|
||||||
|
const _otbrInfo = getOTBRInfo(this.hass);
|
||||||
|
const [otbrAddress, otbrInfo] = await Promise.all([
|
||||||
|
_otbrAddress,
|
||||||
|
_otbrInfo,
|
||||||
|
]);
|
||||||
|
this._otbrInfo = { ...otbrAddress, ...otbrInfo };
|
||||||
|
} catch (err) {
|
||||||
|
this._otbrInfo = undefined;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _signUrl(ev) {
|
private async _signUrl(ev) {
|
||||||
@ -295,6 +377,74 @@ export class ThreadConfigPanel extends SubscribeMixin(LitElement) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _handleRouterAction(ev: CustomEvent<ActionDetail>) {
|
||||||
|
switch (ev.detail.index) {
|
||||||
|
case 0:
|
||||||
|
this._resetBorderRouter();
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
this._setDataset();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async _resetBorderRouter() {
|
||||||
|
const confirm = await showConfirmationDialog(this, {
|
||||||
|
title: this.hass.localize(
|
||||||
|
"ui.panel.config.thread.confirm_reset_border_router"
|
||||||
|
),
|
||||||
|
text: this.hass.localize(
|
||||||
|
"ui.panel.config.thread.confirm_reset_border_router_text"
|
||||||
|
),
|
||||||
|
});
|
||||||
|
if (!confirm) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
await OTBRCreateNetwork(this.hass);
|
||||||
|
} catch (err: any) {
|
||||||
|
showAlertDialog(this, {
|
||||||
|
title: this.hass.localize("ui.panel.config.thread.otbr_config_failed"),
|
||||||
|
text: err.message,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
this._refresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async _setDataset() {
|
||||||
|
const networks = this._groupRoutersByNetwork(this._routers, this._datasets);
|
||||||
|
const preferedDatasetId = networks.preferred?.dataset?.dataset_id;
|
||||||
|
if (!preferedDatasetId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const confirm = await showConfirmationDialog(this, {
|
||||||
|
title: this.hass.localize(
|
||||||
|
"ui.panel.config.thread.confirm_set_dataset_border_router"
|
||||||
|
),
|
||||||
|
text: this.hass.localize(
|
||||||
|
"ui.panel.config.thread.confirm_set_dataset_border_router_text"
|
||||||
|
),
|
||||||
|
});
|
||||||
|
if (!confirm) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
await OTBRSetNetwork(this.hass, preferedDatasetId);
|
||||||
|
} catch (err: any) {
|
||||||
|
showAlertDialog(this, {
|
||||||
|
title: this.hass.localize("ui.panel.config.thread.otbr_config_failed"),
|
||||||
|
text: err.message,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
this._refresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async _setPreferred(ev) {
|
||||||
|
const datasetId = ev.target.datasetId;
|
||||||
|
await setPreferredThreadDataSet(this.hass, datasetId);
|
||||||
|
this._refresh();
|
||||||
|
}
|
||||||
|
|
||||||
private async _addTLV() {
|
private async _addTLV() {
|
||||||
const tlv = await showPromptDialog(this, {
|
const tlv = await showPromptDialog(this, {
|
||||||
title: this.hass.localize("ui.panel.config.thread.add_dataset"),
|
title: this.hass.localize("ui.panel.config.thread.add_dataset"),
|
||||||
@ -355,6 +505,12 @@ export class ThreadConfigPanel extends SubscribeMixin(LitElement) {
|
|||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
direction: ltr;
|
direction: ltr;
|
||||||
}
|
}
|
||||||
|
ha-list-item.router {
|
||||||
|
--mdc-list-side-padding: 16px;
|
||||||
|
--mdc-list-item-meta-size: 48px;
|
||||||
|
cursor: default;
|
||||||
|
overflow: visible;
|
||||||
|
}
|
||||||
ha-button-menu a {
|
ha-button-menu a {
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
@ -365,6 +521,7 @@ export class ThreadConfigPanel extends SubscribeMixin(LitElement) {
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
text-align: center;
|
||||||
}
|
}
|
||||||
.no-routers ha-svg-icon {
|
.no-routers ha-svg-icon {
|
||||||
background-color: var(--light-primary-color);
|
background-color: var(--light-primary-color);
|
||||||
|
@ -3292,10 +3292,18 @@
|
|||||||
"my_network": "My network",
|
"my_network": "My network",
|
||||||
"no_preferred_network": "You don't have a preferred network yet.",
|
"no_preferred_network": "You don't have a preferred network yet.",
|
||||||
"add_open_thread_border_router": "Add an OpenThread border router",
|
"add_open_thread_border_router": "Add an OpenThread border router",
|
||||||
|
"reset_border_router": "Reset border router",
|
||||||
|
"add_to_my_network": "Add to my network",
|
||||||
|
"no_routers_otbr_network": "No border routers where found, maybe the border router is not configured correctly. You can try to reset it to the factory settings.",
|
||||||
"add_dataset_from_tlv": "Add dataset from TLV",
|
"add_dataset_from_tlv": "Add dataset from TLV",
|
||||||
"add_dataset": "Add Thread dataset",
|
"add_dataset": "Add Thread dataset",
|
||||||
"add_dataset_label": "Operational dataset TLV",
|
"add_dataset_label": "Operational dataset TLV",
|
||||||
"add_dataset_button": "Add dataset",
|
"add_dataset_button": "Add dataset",
|
||||||
|
"confirm_reset_border_router": "Reset border router?",
|
||||||
|
"confirm_reset_border_router_text": "This will reset the Home Assistant border router to its factory defaults and form a new Thread network. The old network may no longer be available, and any devices that were attached to this network may need to be recomissioned.",
|
||||||
|
"confirm_set_dataset_border_router": "Reconfigure border router?",
|
||||||
|
"confirm_set_dataset_border_router_text": "This will reconfigure the Home Assistant border router to use a different Thread network. The old network may no longer be available, and any devices that were attached to this network may need to be recomissioned.",
|
||||||
|
"otbr_config_failed": "Failed to configure the border router",
|
||||||
"confirm_delete_dataset": "Delete {name} dataset?",
|
"confirm_delete_dataset": "Delete {name} dataset?",
|
||||||
"confirm_delete_dataset_text": "This network will be removed from Home Assistant.",
|
"confirm_delete_dataset_text": "This network will be removed from Home Assistant.",
|
||||||
"no_border_routers": "No border routers found",
|
"no_border_routers": "No border routers found",
|
||||||
|
Loading…
x
Reference in New Issue
Block a user