mirror of
https://github.com/home-assistant/frontend.git
synced 2026-07-17 16:46:44 +00:00
Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 78d4072049 | |||
| f2827d0ab6 | |||
| 22b83b20fc | |||
| 65f7d65462 |
@@ -44,6 +44,15 @@ interface AddonTranslations {
|
|||||||
configuration?: Record<string, AddonFieldTranslation>;
|
configuration?: Record<string, AddonFieldTranslation>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface AddonNetworkIsolationParams {
|
||||||
|
interface: string;
|
||||||
|
ipv4: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AddonNetworkIsolation extends AddonNetworkIsolationParams {
|
||||||
|
driver: "macvlan";
|
||||||
|
}
|
||||||
|
|
||||||
export interface HassioAddonInfo {
|
export interface HassioAddonInfo {
|
||||||
advanced: boolean;
|
advanced: boolean;
|
||||||
available: boolean;
|
available: boolean;
|
||||||
@@ -100,6 +109,9 @@ export interface HassioAddonDetails extends HassioAddonInfo {
|
|||||||
long_description: null | string;
|
long_description: null | string;
|
||||||
machine: any;
|
machine: any;
|
||||||
network_description: null | Record<string, string>;
|
network_description: null | Record<string, string>;
|
||||||
|
network_isolation: AddonNetworkIsolation | null;
|
||||||
|
network_isolation_available: boolean;
|
||||||
|
network_isolation_mac: string | null;
|
||||||
network: null | Record<string, number>;
|
network: null | Record<string, number>;
|
||||||
options: Record<string, unknown>;
|
options: Record<string, unknown>;
|
||||||
privileged: any;
|
privileged: any;
|
||||||
@@ -143,6 +155,7 @@ export interface HassioAddonSetOptionParams {
|
|||||||
auto_update?: boolean;
|
auto_update?: boolean;
|
||||||
ingress_panel?: boolean;
|
ingress_panel?: boolean;
|
||||||
network?: Record<string, unknown> | null;
|
network?: Record<string, unknown> | null;
|
||||||
|
network_isolation?: AddonNetworkIsolationParams | null;
|
||||||
watchdog?: boolean;
|
watchdog?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ export interface NetworkInterface {
|
|||||||
ipv6?: Partial<IpConfiguration>;
|
ipv6?: Partial<IpConfiguration>;
|
||||||
type: "ethernet" | "wireless" | "vlan";
|
type: "ethernet" | "wireless" | "vlan";
|
||||||
wifi?: Partial<WifiConfiguration> | null;
|
wifi?: Partial<WifiConfiguration> | null;
|
||||||
|
network_isolation_capable?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface DockerNetwork {
|
export interface DockerNetwork {
|
||||||
|
|||||||
@@ -29,11 +29,13 @@ class SupervisorAppConfigDashboard extends LitElement {
|
|||||||
const hasConfiguration =
|
const hasConfiguration =
|
||||||
(this.addon.options && Object.keys(this.addon.options).length) ||
|
(this.addon.options && Object.keys(this.addon.options).length) ||
|
||||||
(this.addon.schema && Object.keys(this.addon.schema).length);
|
(this.addon.schema && Object.keys(this.addon.schema).length);
|
||||||
|
const hasNetwork =
|
||||||
|
this.addon.network || this.addon.network_isolation_available;
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
<div class="content">
|
<div class="content">
|
||||||
${this.addon.system_managed &&
|
${this.addon.system_managed &&
|
||||||
(hasConfiguration || this.addon.network || this.addon.audio)
|
(hasConfiguration || hasNetwork || this.addon.audio)
|
||||||
? html`
|
? html`
|
||||||
<supervisor-app-system-managed
|
<supervisor-app-system-managed
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
@@ -42,7 +44,7 @@ class SupervisorAppConfigDashboard extends LitElement {
|
|||||||
></supervisor-app-system-managed>
|
></supervisor-app-system-managed>
|
||||||
`
|
`
|
||||||
: nothing}
|
: nothing}
|
||||||
${hasConfiguration || this.addon.network || this.addon.audio
|
${hasConfiguration || hasNetwork || this.addon.audio
|
||||||
? html`
|
? html`
|
||||||
${hasConfiguration
|
${hasConfiguration
|
||||||
? html`
|
? html`
|
||||||
@@ -54,7 +56,7 @@ class SupervisorAppConfigDashboard extends LitElement {
|
|||||||
></supervisor-app-config>
|
></supervisor-app-config>
|
||||||
`
|
`
|
||||||
: nothing}
|
: nothing}
|
||||||
${this.addon.network
|
${hasNetwork
|
||||||
? html`
|
? html`
|
||||||
<supervisor-app-network
|
<supervisor-app-network
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
|
|||||||
@@ -9,22 +9,44 @@ import "../../../../../components/ha-card";
|
|||||||
import "../../../../../components/ha-form/ha-form";
|
import "../../../../../components/ha-form/ha-form";
|
||||||
import type { HaFormSchema } from "../../../../../components/ha-form/types";
|
import type { HaFormSchema } from "../../../../../components/ha-form/types";
|
||||||
import "../../../../../components/ha-formfield";
|
import "../../../../../components/ha-formfield";
|
||||||
|
import "../../../../../components/ha-select";
|
||||||
|
import type { HaSelectSelectEvent } from "../../../../../components/ha-select";
|
||||||
|
import "../../../../../components/ha-switch";
|
||||||
|
import type { HaSwitch } from "../../../../../components/ha-switch";
|
||||||
|
import "../../../../../components/input/ha-input";
|
||||||
|
import type { HaInput } from "../../../../../components/input/ha-input";
|
||||||
import type {
|
import type {
|
||||||
|
AddonNetworkIsolationParams,
|
||||||
HassioAddonDetails,
|
HassioAddonDetails,
|
||||||
HassioAddonSetOptionParams,
|
HassioAddonSetOptionParams,
|
||||||
} from "../../../../../data/hassio/addon";
|
} from "../../../../../data/hassio/addon";
|
||||||
import { setHassioAddonOption } from "../../../../../data/hassio/addon";
|
import { setHassioAddonOption } from "../../../../../data/hassio/addon";
|
||||||
import { extractApiErrorMessage } from "../../../../../data/hassio/common";
|
import { extractApiErrorMessage } from "../../../../../data/hassio/common";
|
||||||
|
import type { NetworkInterface } from "../../../../../data/hassio/network";
|
||||||
|
import { fetchNetworkInfo } from "../../../../../data/hassio/network";
|
||||||
import { DirtyStateProviderMixin } from "../../../../../mixins/dirty-state-provider-mixin";
|
import { DirtyStateProviderMixin } from "../../../../../mixins/dirty-state-provider-mixin";
|
||||||
import { haStyle } from "../../../../../resources/styles";
|
import { haStyle } from "../../../../../resources/styles";
|
||||||
import type { HomeAssistant } from "../../../../../types";
|
import type { HomeAssistant } from "../../../../../types";
|
||||||
import { supervisorAppsStyle } from "../../resources/supervisor-apps-style";
|
import { supervisorAppsStyle } from "../../resources/supervisor-apps-style";
|
||||||
import { suggestSupervisorAppRestart } from "../dialogs/suggestSupervisorAppRestart";
|
import { suggestSupervisorAppRestart } from "../dialogs/suggestSupervisorAppRestart";
|
||||||
|
|
||||||
|
interface NetworkConfig {
|
||||||
|
ports: Record<string, number | null>;
|
||||||
|
isolation: AddonNetworkIsolationParams | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const isValidIpv4 = (address: string): boolean => {
|
||||||
|
const parts = address.split(".");
|
||||||
|
return (
|
||||||
|
parts.length === 4 &&
|
||||||
|
parts.every((part) => /^\d{1,3}$/.test(part) && Number(part) <= 255)
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
@customElement("supervisor-app-network")
|
@customElement("supervisor-app-network")
|
||||||
class SupervisorAppNetwork extends DirtyStateProviderMixin<
|
class SupervisorAppNetwork extends DirtyStateProviderMixin<NetworkConfig>()(
|
||||||
Record<string, number | null>
|
LitElement
|
||||||
>()(LitElement) {
|
) {
|
||||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
|
|
||||||
@property({ attribute: false }) public addon!: HassioAddonDetails;
|
@property({ attribute: false }) public addon!: HassioAddonDetails;
|
||||||
@@ -35,17 +57,19 @@ class SupervisorAppNetwork extends DirtyStateProviderMixin<
|
|||||||
|
|
||||||
@state() private _error?: string;
|
@state() private _error?: string;
|
||||||
|
|
||||||
@state() private _config?: Record<string, number | null>;
|
@state() private _config?: NetworkConfig;
|
||||||
|
|
||||||
|
@state() private _isolationInterfaces?: NetworkInterface[];
|
||||||
|
|
||||||
protected render() {
|
protected render() {
|
||||||
if (!this._config) {
|
if (!this._config) {
|
||||||
return nothing;
|
return nothing;
|
||||||
}
|
}
|
||||||
|
|
||||||
const config = this._config;
|
const ports = this._config.ports;
|
||||||
|
|
||||||
const hasHiddenOptions = Object.keys(config).find(
|
const hasHiddenOptions = Object.keys(ports).find(
|
||||||
(entry) => config[entry] === null
|
(entry) => ports[entry] === null
|
||||||
);
|
);
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
@@ -56,23 +80,29 @@ class SupervisorAppNetwork extends DirtyStateProviderMixin<
|
|||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<div class="card-content">
|
<div class="card-content">
|
||||||
<p>
|
|
||||||
${this.hass.localize(
|
|
||||||
"ui.panel.config.apps.configuration.network.introduction"
|
|
||||||
)}
|
|
||||||
</p>
|
|
||||||
${this._error
|
${this._error
|
||||||
? html`<ha-alert alert-type="error">${this._error}</ha-alert>`
|
? html`<ha-alert alert-type="error">${this._error}</ha-alert>`
|
||||||
: nothing}
|
: nothing}
|
||||||
|
${this.addon.network
|
||||||
<ha-form
|
? html`
|
||||||
.disabled=${this.disabled}
|
<p>
|
||||||
.data=${this._config}
|
${this.hass.localize(
|
||||||
@value-changed=${this._configChanged}
|
"ui.panel.config.apps.configuration.network.introduction"
|
||||||
.computeLabel=${this._computeLabel}
|
)}
|
||||||
.computeHelper=${this._computeHelper}
|
</p>
|
||||||
.schema=${this._createSchema(this._config, this._showOptional)}
|
<ha-form
|
||||||
></ha-form>
|
.disabled=${this.disabled}
|
||||||
|
.data=${ports}
|
||||||
|
@value-changed=${this._configChanged}
|
||||||
|
.computeLabel=${this._computeLabel}
|
||||||
|
.computeHelper=${this._computeHelper}
|
||||||
|
.schema=${this._createSchema(ports, this._showOptional)}
|
||||||
|
></ha-form>
|
||||||
|
`
|
||||||
|
: nothing}
|
||||||
|
${this.addon.network_isolation_available
|
||||||
|
? this._renderIsolation()
|
||||||
|
: nothing}
|
||||||
</div>
|
</div>
|
||||||
${hasHiddenOptions
|
${hasHiddenOptions
|
||||||
? html`<ha-formfield
|
? html`<ha-formfield
|
||||||
@@ -110,21 +140,129 @@ class SupervisorAppNetwork extends DirtyStateProviderMixin<
|
|||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _renderIsolation() {
|
||||||
|
const isolation = this._config!.isolation;
|
||||||
|
|
||||||
|
return html`
|
||||||
|
<div class="isolation">
|
||||||
|
<ha-formfield
|
||||||
|
.label=${this.hass.localize(
|
||||||
|
"ui.panel.config.apps.configuration.network.isolation.title"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<ha-switch
|
||||||
|
.checked=${isolation !== null}
|
||||||
|
.disabled=${this.disabled}
|
||||||
|
@change=${this._isolationToggled}
|
||||||
|
></ha-switch>
|
||||||
|
</ha-formfield>
|
||||||
|
<p class="secondary">
|
||||||
|
${this.hass.localize(
|
||||||
|
"ui.panel.config.apps.configuration.network.isolation.description"
|
||||||
|
)}
|
||||||
|
</p>
|
||||||
|
${isolation
|
||||||
|
? html`
|
||||||
|
<ha-select
|
||||||
|
.label=${this.hass.localize(
|
||||||
|
"ui.panel.config.apps.configuration.network.isolation.interface"
|
||||||
|
)}
|
||||||
|
.value=${isolation.interface}
|
||||||
|
.disabled=${this.disabled}
|
||||||
|
.options=${(this._isolationInterfaces || []).map((iface) => ({
|
||||||
|
value: iface.interface,
|
||||||
|
label: iface.ipv4?.address?.length
|
||||||
|
? `${iface.interface} (${iface.ipv4.address.join(", ")})`
|
||||||
|
: iface.interface,
|
||||||
|
}))}
|
||||||
|
@selected=${this._isolationInterfaceChanged}
|
||||||
|
></ha-select>
|
||||||
|
<ha-input
|
||||||
|
.label=${this.hass.localize(
|
||||||
|
"ui.panel.config.apps.configuration.network.isolation.ip_address"
|
||||||
|
)}
|
||||||
|
.hint=${this.hass.localize(
|
||||||
|
"ui.panel.config.apps.configuration.network.isolation.ip_address_helper"
|
||||||
|
)}
|
||||||
|
.value=${isolation.ipv4}
|
||||||
|
.disabled=${this.disabled}
|
||||||
|
@change=${this._isolationAddressChanged}
|
||||||
|
></ha-input>
|
||||||
|
${this.addon.network_isolation_mac
|
||||||
|
? html`
|
||||||
|
<p class="mac">
|
||||||
|
<span class="secondary">
|
||||||
|
${this.hass.localize(
|
||||||
|
"ui.panel.config.apps.configuration.network.isolation.mac_address"
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
<code>${this.addon.network_isolation_mac}</code>
|
||||||
|
</p>
|
||||||
|
`
|
||||||
|
: nothing}
|
||||||
|
<ha-alert alert-type="info">
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
${this.hass.localize(
|
||||||
|
"ui.panel.config.apps.configuration.network.isolation.info.separate_device"
|
||||||
|
)}
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
${this.hass.localize(
|
||||||
|
"ui.panel.config.apps.configuration.network.isolation.info.ipv6"
|
||||||
|
)}
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
${this.hass.localize(
|
||||||
|
"ui.panel.config.apps.configuration.network.isolation.info.host_reachability"
|
||||||
|
)}
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
${this.hass.localize(
|
||||||
|
"ui.panel.config.apps.configuration.network.isolation.info.host_interfaces"
|
||||||
|
)}
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</ha-alert>
|
||||||
|
`
|
||||||
|
: nothing}
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
protected willUpdate(changedProperties: PropertyValues<this>): void {
|
protected willUpdate(changedProperties: PropertyValues<this>): void {
|
||||||
super.willUpdate(changedProperties);
|
super.willUpdate(changedProperties);
|
||||||
if (changedProperties.has("addon")) {
|
if (changedProperties.has("addon")) {
|
||||||
this._setNetworkConfig();
|
this._setNetworkConfig();
|
||||||
|
if (
|
||||||
|
this.addon.network_isolation_available &&
|
||||||
|
this._isolationInterfaces === undefined
|
||||||
|
) {
|
||||||
|
this._loadIsolationInterfaces();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async _loadIsolationInterfaces(): Promise<void> {
|
||||||
|
this._isolationInterfaces = [];
|
||||||
|
try {
|
||||||
|
const { interfaces } = await fetchNetworkInfo(this.hass);
|
||||||
|
this._isolationInterfaces = interfaces.filter(
|
||||||
|
(iface) => iface.network_isolation_capable
|
||||||
|
);
|
||||||
|
} catch (err: any) {
|
||||||
|
this._error = extractApiErrorMessage(err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private _createSchema = memoizeOne(
|
private _createSchema = memoizeOne(
|
||||||
(
|
(
|
||||||
config: Record<string, number | null>,
|
ports: Record<string, number | null>,
|
||||||
showOptional: boolean
|
showOptional: boolean
|
||||||
): HaFormSchema[] =>
|
): HaFormSchema[] =>
|
||||||
(showOptional
|
(showOptional
|
||||||
? Object.keys(config)
|
? Object.keys(ports)
|
||||||
: Object.keys(config).filter((entry) => config[entry] !== null)
|
: Object.keys(ports).filter((entry) => ports[entry] !== null)
|
||||||
).map((entry) => ({
|
).map((entry) => ({
|
||||||
name: entry,
|
name: entry,
|
||||||
selector: {
|
selector: {
|
||||||
@@ -147,14 +285,58 @@ class SupervisorAppNetwork extends DirtyStateProviderMixin<
|
|||||||
item.name;
|
item.name;
|
||||||
|
|
||||||
private _setNetworkConfig(): void {
|
private _setNetworkConfig(): void {
|
||||||
const config = this.addon.network || {};
|
const config: NetworkConfig = {
|
||||||
|
ports: this.addon.network || {},
|
||||||
|
isolation: this.addon.network_isolation
|
||||||
|
? {
|
||||||
|
interface: this.addon.network_isolation.interface,
|
||||||
|
ipv4: this.addon.network_isolation.ipv4,
|
||||||
|
}
|
||||||
|
: null,
|
||||||
|
};
|
||||||
this._config = config;
|
this._config = config;
|
||||||
this._initDirtyTracking({ type: "shallow" }, config);
|
this._initDirtyTracking({ type: "deep" }, config);
|
||||||
}
|
}
|
||||||
|
|
||||||
private _configChanged(ev: CustomEvent): void {
|
private _configChanged(ev: CustomEvent): void {
|
||||||
this._config = ev.detail.value;
|
this._config = { ...this._config!, ports: ev.detail.value };
|
||||||
this._updateDirtyState(ev.detail.value);
|
this._updateDirtyState(this._config);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _isolationToggled(ev: Event): void {
|
||||||
|
const enabled = (ev.target as HaSwitch).checked;
|
||||||
|
this._config = {
|
||||||
|
...this._config!,
|
||||||
|
isolation: enabled
|
||||||
|
? {
|
||||||
|
interface:
|
||||||
|
this.addon.network_isolation?.interface ||
|
||||||
|
this._isolationInterfaces?.[0]?.interface ||
|
||||||
|
"",
|
||||||
|
ipv4: this.addon.network_isolation?.ipv4 || "",
|
||||||
|
}
|
||||||
|
: null,
|
||||||
|
};
|
||||||
|
this._updateDirtyState(this._config);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _isolationInterfaceChanged(ev: HaSelectSelectEvent): void {
|
||||||
|
this._config = {
|
||||||
|
...this._config!,
|
||||||
|
isolation: { ...this._config!.isolation!, interface: ev.detail.value },
|
||||||
|
};
|
||||||
|
this._updateDirtyState(this._config);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _isolationAddressChanged(ev: Event): void {
|
||||||
|
this._config = {
|
||||||
|
...this._config!,
|
||||||
|
isolation: {
|
||||||
|
...this._config!.isolation!,
|
||||||
|
ipv4: (ev.target as HaInput).value || "",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
this._updateDirtyState(this._config);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _resetTapped(ev: CustomEvent): Promise<void> {
|
private async _resetTapped(ev: CustomEvent): Promise<void> {
|
||||||
@@ -163,9 +345,13 @@ class SupervisorAppNetwork extends DirtyStateProviderMixin<
|
|||||||
}
|
}
|
||||||
|
|
||||||
const button = ev.currentTarget as any;
|
const button = ev.currentTarget as any;
|
||||||
const data: HassioAddonSetOptionParams = {
|
const data: HassioAddonSetOptionParams = {};
|
||||||
network: null,
|
if (this.addon.network) {
|
||||||
};
|
data.network = null;
|
||||||
|
}
|
||||||
|
if (this.addon.network_isolation_available) {
|
||||||
|
data.network_isolation = null;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await setHassioAddonOption(this.hass.callWS, this.addon.slug, data);
|
await setHassioAddonOption(this.hass.callWS, this.addon.slug, data);
|
||||||
@@ -203,14 +389,36 @@ class SupervisorAppNetwork extends DirtyStateProviderMixin<
|
|||||||
const button = ev.currentTarget as any;
|
const button = ev.currentTarget as any;
|
||||||
|
|
||||||
this._error = undefined;
|
this._error = undefined;
|
||||||
const networkconfiguration: Record<string, number | null> = {};
|
const { ports, isolation } = this._config!;
|
||||||
Object.entries(this._config!).forEach(([key, value]) => {
|
|
||||||
networkconfiguration[key] = value ?? null;
|
|
||||||
});
|
|
||||||
|
|
||||||
const data: HassioAddonSetOptionParams = {
|
if (this.addon.network_isolation_available && isolation) {
|
||||||
network: networkconfiguration,
|
if (!isolation.interface) {
|
||||||
};
|
this._error = this.hass.localize(
|
||||||
|
"ui.panel.config.apps.configuration.network.isolation.no_interface"
|
||||||
|
);
|
||||||
|
button.actionError();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!isValidIpv4(isolation.ipv4)) {
|
||||||
|
this._error = this.hass.localize(
|
||||||
|
"ui.panel.config.apps.configuration.network.isolation.invalid_ip"
|
||||||
|
);
|
||||||
|
button.actionError();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const data: HassioAddonSetOptionParams = {};
|
||||||
|
if (this.addon.network) {
|
||||||
|
const networkconfiguration: Record<string, number | null> = {};
|
||||||
|
Object.entries(ports).forEach(([key, value]) => {
|
||||||
|
networkconfiguration[key] = value ?? null;
|
||||||
|
});
|
||||||
|
data.network = networkconfiguration;
|
||||||
|
}
|
||||||
|
if (this.addon.network_isolation_available) {
|
||||||
|
data.network_isolation = isolation;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await setHassioAddonOption(this.hass.callWS, this.addon.slug, data);
|
await setHassioAddonOption(this.hass.callWS, this.addon.slug, data);
|
||||||
@@ -254,6 +462,36 @@ class SupervisorAppNetwork extends DirtyStateProviderMixin<
|
|||||||
.show-optional {
|
.show-optional {
|
||||||
padding: 16px;
|
padding: 16px;
|
||||||
}
|
}
|
||||||
|
ha-form + .isolation {
|
||||||
|
margin-top: var(--ha-space-6);
|
||||||
|
}
|
||||||
|
.isolation .secondary {
|
||||||
|
margin-top: var(--ha-space-1);
|
||||||
|
color: var(--secondary-text-color);
|
||||||
|
}
|
||||||
|
.isolation ha-select,
|
||||||
|
.isolation ha-input {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.isolation ha-input {
|
||||||
|
margin-top: var(--ha-space-4);
|
||||||
|
}
|
||||||
|
.isolation .mac {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
.isolation .mac code {
|
||||||
|
display: block;
|
||||||
|
margin-top: var(--ha-space-1);
|
||||||
|
}
|
||||||
|
.isolation ha-alert {
|
||||||
|
display: block;
|
||||||
|
margin-top: var(--ha-space-4);
|
||||||
|
}
|
||||||
|
.isolation ha-alert ul {
|
||||||
|
margin: 0;
|
||||||
|
padding-inline-start: var(--ha-space-4);
|
||||||
|
}
|
||||||
`,
|
`,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -832,6 +832,7 @@ class SupervisorAppInfo extends MobileAwareMixin(LitElement) {
|
|||||||
</span>
|
</span>
|
||||||
<code slot="headline"> ${this._currentAddon.hostname} </code>
|
<code slot="headline"> ${this._currentAddon.hostname} </code>
|
||||||
</ha-row-item>
|
</ha-row-item>
|
||||||
|
${this._renderNetworkIsolationRows()}
|
||||||
${metrics.map(
|
${metrics.map(
|
||||||
(metric) => html`
|
(metric) => html`
|
||||||
<supervisor-app-metric
|
<supervisor-app-metric
|
||||||
@@ -842,11 +843,46 @@ class SupervisorAppInfo extends MobileAwareMixin(LitElement) {
|
|||||||
`
|
`
|
||||||
)}`
|
)}`
|
||||||
: nothing}
|
: nothing}
|
||||||
|
${this._currentAddon.version &&
|
||||||
|
this._currentAddon.state !== "started" &&
|
||||||
|
this._currentAddon.network_isolation
|
||||||
|
? html`<wa-divider></wa-divider>
|
||||||
|
${this._renderNetworkIsolationRows()}`
|
||||||
|
: nothing}
|
||||||
</div>
|
</div>
|
||||||
</ha-card>
|
</ha-card>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _renderNetworkIsolationRows() {
|
||||||
|
const addon = this._currentAddon;
|
||||||
|
if (!addon.version || !addon.network_isolation) {
|
||||||
|
return nothing;
|
||||||
|
}
|
||||||
|
return html`
|
||||||
|
<ha-row-item>
|
||||||
|
<span slot="supporting-text">
|
||||||
|
${this.i18n.localize(
|
||||||
|
"ui.panel.config.apps.dashboard.network_isolation_ip"
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
<code slot="headline"> ${addon.network_isolation.ipv4} </code>
|
||||||
|
</ha-row-item>
|
||||||
|
${addon.network_isolation_mac
|
||||||
|
? html`
|
||||||
|
<ha-row-item>
|
||||||
|
<span slot="supporting-text">
|
||||||
|
${this.i18n.localize(
|
||||||
|
"ui.panel.config.apps.dashboard.network_isolation_mac"
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
<code slot="headline"> ${addon.network_isolation_mac} </code>
|
||||||
|
</ha-row-item>
|
||||||
|
`
|
||||||
|
: nothing}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
protected render(): TemplateResult {
|
protected render(): TemplateResult {
|
||||||
return html`
|
return html`
|
||||||
${"protected" in this._currentAddon && !this._currentAddon.protected
|
${"protected" in this._currentAddon && !this._currentAddon.protected
|
||||||
|
|||||||
@@ -2904,6 +2904,8 @@
|
|||||||
"current_version": "Current version: {version}",
|
"current_version": "Current version: {version}",
|
||||||
"changelog": "Changelog",
|
"changelog": "Changelog",
|
||||||
"hostname": "Hostname",
|
"hostname": "Hostname",
|
||||||
|
"network_isolation_ip": "IP address on your network",
|
||||||
|
"network_isolation_mac": "MAC address on your network",
|
||||||
"visit_app_page": "Visit {name} page for more details.",
|
"visit_app_page": "Visit {name} page for more details.",
|
||||||
"start": "Start",
|
"start": "Start",
|
||||||
"stop": "Stop",
|
"stop": "Stop",
|
||||||
@@ -3078,7 +3080,23 @@
|
|||||||
"header": "Network",
|
"header": "Network",
|
||||||
"introduction": "Configure the network ports that this app uses.",
|
"introduction": "Configure the network ports that this app uses.",
|
||||||
"show_disabled": "Show disabled ports",
|
"show_disabled": "Show disabled ports",
|
||||||
"reset_defaults": "Reset to defaults"
|
"reset_defaults": "Reset to defaults",
|
||||||
|
"isolation": {
|
||||||
|
"title": "Isolated network access",
|
||||||
|
"description": "Run this app with its own IP address on a selected network instead of sharing the host network. Ingress and the web UI link keep working as before.",
|
||||||
|
"interface": "Network interface",
|
||||||
|
"ip_address": "IP address",
|
||||||
|
"ip_address_helper": "Choose an address outside the DHCP range of your router, or reserve it in your router.",
|
||||||
|
"mac_address": "MAC address of this app on your network",
|
||||||
|
"no_interface": "Select a network interface to use isolated network access.",
|
||||||
|
"invalid_ip": "Enter a valid IPv4 address, like 192.168.1.50.",
|
||||||
|
"info": {
|
||||||
|
"separate_device": "The app joins the selected network as a separate device with its own IP and MAC address.",
|
||||||
|
"ipv6": "IPv6 needs no setup: the app automatically gets its IPv6 addresses from your network.",
|
||||||
|
"host_reachability": "Your Home Assistant system and the app cannot reach each other through their addresses on the selected network. This is part of the isolation — ingress, the web UI link, and other Home Assistant features are not affected.",
|
||||||
|
"host_interfaces": "Apps that need access to all network interfaces of your system may not work as expected with isolated network access."
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"audio": {
|
"audio": {
|
||||||
"header": "Audio",
|
"header": "Audio",
|
||||||
|
|||||||
Reference in New Issue
Block a user