mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-25 18:26:35 +00:00
Landingpage add core checks before show errors (#24493)
* Check core only if supervisor or observer is unresponsive * Improve core check * Revert test code * Remove unused prop * Combine network info with core check * Combine ping and network info * Add 30 sec timeout before show errors * Update landing-page/src/ha-landing-page.ts * Assume supervisor update on failed ping * Fix typo
This commit is contained in:
parent
68960ba03d
commit
690cd47945
@ -22,6 +22,8 @@ import {
|
|||||||
import { fireEvent } from "../../../src/common/dom/fire_event";
|
import { fireEvent } from "../../../src/common/dom/fire_event";
|
||||||
import { fileDownload } from "../../../src/util/file_download";
|
import { fileDownload } from "../../../src/util/file_download";
|
||||||
import { getSupervisorLogs, getSupervisorLogsFollow } from "../data/supervisor";
|
import { getSupervisorLogs, getSupervisorLogsFollow } from "../data/supervisor";
|
||||||
|
import { waitForSeconds } from "../../../src/common/util/wait";
|
||||||
|
import { ASSUME_CORE_START_SECONDS } from "../ha-landing-page";
|
||||||
|
|
||||||
const ERROR_CHECK = /^[\d\s-:]+(ERROR|CRITICAL)(.*)/gm;
|
const ERROR_CHECK = /^[\d\s-:]+(ERROR|CRITICAL)(.*)/gm;
|
||||||
declare global {
|
declare global {
|
||||||
@ -216,7 +218,7 @@ class LandingPageLogs extends LitElement {
|
|||||||
// eslint-disable-next-line no-console
|
// eslint-disable-next-line no-console
|
||||||
console.error(err);
|
console.error(err);
|
||||||
|
|
||||||
// fallback to observerlogs if there is a problem with supervisor
|
// fallback to observer logs if there is a problem with supervisor
|
||||||
this._loadObserverLogs();
|
this._loadObserverLogs();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -251,6 +253,9 @@ class LandingPageLogs extends LitElement {
|
|||||||
|
|
||||||
this._scheduleObserverLogs();
|
this._scheduleObserverLogs();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
// wait because there is a moment where landingpage is down and core is not up yet
|
||||||
|
await waitForSeconds(ASSUME_CORE_START_SECONDS);
|
||||||
|
|
||||||
// eslint-disable-next-line no-console
|
// eslint-disable-next-line no-console
|
||||||
console.error(err);
|
console.error(err);
|
||||||
this._error = true;
|
this._error = true;
|
||||||
|
@ -1,13 +1,7 @@
|
|||||||
import "@material/mwc-linear-progress/mwc-linear-progress";
|
import "@material/mwc-linear-progress/mwc-linear-progress";
|
||||||
import {
|
import memoizeOne from "memoize-one";
|
||||||
type CSSResultGroup,
|
import { type CSSResultGroup, LitElement, css, html, nothing } from "lit";
|
||||||
LitElement,
|
import { customElement, property } from "lit/decorators";
|
||||||
type PropertyValues,
|
|
||||||
css,
|
|
||||||
html,
|
|
||||||
nothing,
|
|
||||||
} from "lit";
|
|
||||||
import { customElement, property, state } from "lit/decorators";
|
|
||||||
import type {
|
import type {
|
||||||
LandingPageKeys,
|
LandingPageKeys,
|
||||||
LocalizeFunc,
|
LocalizeFunc,
|
||||||
@ -16,34 +10,24 @@ import "../../../src/components/ha-button";
|
|||||||
import "../../../src/components/ha-alert";
|
import "../../../src/components/ha-alert";
|
||||||
import {
|
import {
|
||||||
ALTERNATIVE_DNS_SERVERS,
|
ALTERNATIVE_DNS_SERVERS,
|
||||||
getSupervisorNetworkInfo,
|
|
||||||
pingSupervisor,
|
|
||||||
setSupervisorNetworkDns,
|
setSupervisorNetworkDns,
|
||||||
|
type NetworkInfo,
|
||||||
} from "../data/supervisor";
|
} from "../data/supervisor";
|
||||||
import { fireEvent } from "../../../src/common/dom/fire_event";
|
|
||||||
import { showAlertDialog } from "../../../src/dialogs/generic/show-dialog-box";
|
import { showAlertDialog } from "../../../src/dialogs/generic/show-dialog-box";
|
||||||
|
import type { NetworkInterface } from "../../../src/data/hassio/network";
|
||||||
const SCHEDULE_FETCH_NETWORK_INFO_SECONDS = 5;
|
import { fireEvent } from "../../../src/common/dom/fire_event";
|
||||||
|
|
||||||
@customElement("landing-page-network")
|
@customElement("landing-page-network")
|
||||||
class LandingPageNetwork extends LitElement {
|
class LandingPageNetwork extends LitElement {
|
||||||
@property({ attribute: false })
|
@property({ attribute: false })
|
||||||
public localize!: LocalizeFunc<LandingPageKeys>;
|
public localize!: LocalizeFunc<LandingPageKeys>;
|
||||||
|
|
||||||
@state() private _networkIssue = false;
|
@property({ attribute: false }) public networkInfo?: NetworkInfo;
|
||||||
|
|
||||||
@state() private _getNetworkInfoError = false;
|
@property({ type: Boolean }) public error = false;
|
||||||
|
|
||||||
@state() private _dnsPrimaryInterfaceNameservers?: string;
|
|
||||||
|
|
||||||
@state() private _dnsPrimaryInterface?: string;
|
|
||||||
|
|
||||||
protected render() {
|
protected render() {
|
||||||
if (!this._networkIssue && !this._getNetworkInfoError) {
|
if (this.error) {
|
||||||
return nothing;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this._getNetworkInfoError) {
|
|
||||||
return html`
|
return html`
|
||||||
<ha-alert alert-type="error">
|
<ha-alert alert-type="error">
|
||||||
<p>${this.localize("network_issue.error_get_network_info")}</p>
|
<p>${this.localize("network_issue.error_get_network_info")}</p>
|
||||||
@ -51,6 +35,16 @@ class LandingPageNetwork extends LitElement {
|
|||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let dnsPrimaryInterfaceNameservers: string | undefined;
|
||||||
|
|
||||||
|
const primaryInterface = this._getPrimaryInterface(
|
||||||
|
this.networkInfo?.interfaces
|
||||||
|
);
|
||||||
|
if (primaryInterface) {
|
||||||
|
dnsPrimaryInterfaceNameservers =
|
||||||
|
this._getPrimaryNameservers(primaryInterface);
|
||||||
|
}
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
<ha-alert
|
<ha-alert
|
||||||
alert-type="warning"
|
alert-type="warning"
|
||||||
@ -58,11 +52,11 @@ class LandingPageNetwork extends LitElement {
|
|||||||
>
|
>
|
||||||
<p>
|
<p>
|
||||||
${this.localize("network_issue.description", {
|
${this.localize("network_issue.description", {
|
||||||
dns: this._dnsPrimaryInterfaceNameservers || "?",
|
dns: dnsPrimaryInterfaceNameservers || "?",
|
||||||
})}
|
})}
|
||||||
</p>
|
</p>
|
||||||
<p>${this.localize("network_issue.resolve_different")}</p>
|
<p>${this.localize("network_issue.resolve_different")}</p>
|
||||||
${!this._dnsPrimaryInterfaceNameservers
|
${!dnsPrimaryInterfaceNameservers
|
||||||
? html`
|
? html`
|
||||||
<p>
|
<p>
|
||||||
<b>${this.localize("network_issue.no_primary_interface")} </b>
|
<b>${this.localize("network_issue.no_primary_interface")} </b>
|
||||||
@ -74,7 +68,7 @@ class LandingPageNetwork extends LitElement {
|
|||||||
({ translationKey }, key) =>
|
({ translationKey }, key) =>
|
||||||
html`<ha-button
|
html`<ha-button
|
||||||
.index=${key}
|
.index=${key}
|
||||||
.disabled=${!this._dnsPrimaryInterfaceNameservers}
|
.disabled=${!dnsPrimaryInterfaceNameservers}
|
||||||
@click=${this._setDns}
|
@click=${this._setDns}
|
||||||
>${this.localize(translationKey)}</ha-button
|
>${this.localize(translationKey)}</ha-button
|
||||||
>`
|
>`
|
||||||
@ -84,97 +78,40 @@ class LandingPageNetwork extends LitElement {
|
|||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected firstUpdated(_changedProperties: PropertyValues): void {
|
private _getPrimaryInterface = memoizeOne((interfaces?: NetworkInterface[]) =>
|
||||||
super.firstUpdated(_changedProperties);
|
interfaces?.find((intf) => intf.primary && intf.enabled)
|
||||||
this._pingSupervisor();
|
);
|
||||||
}
|
|
||||||
|
|
||||||
private _schedulePingSupervisor() {
|
private _getPrimaryNameservers = memoizeOne(
|
||||||
setTimeout(
|
(primaryInterface: NetworkInterface) =>
|
||||||
() => this._pingSupervisor(),
|
[
|
||||||
SCHEDULE_FETCH_NETWORK_INFO_SECONDS * 1000
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async _pingSupervisor() {
|
|
||||||
try {
|
|
||||||
const response = await pingSupervisor();
|
|
||||||
if (!response.ok) {
|
|
||||||
throw new Error("Failed to ping supervisor, assume update in progress");
|
|
||||||
}
|
|
||||||
this._fetchSupervisorInfo();
|
|
||||||
} catch (err) {
|
|
||||||
// eslint-disable-next-line no-console
|
|
||||||
console.error(err);
|
|
||||||
this._schedulePingSupervisor();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private _scheduleFetchSupervisorInfo() {
|
|
||||||
setTimeout(
|
|
||||||
() => this._fetchSupervisorInfo(),
|
|
||||||
SCHEDULE_FETCH_NETWORK_INFO_SECONDS * 1000
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async _fetchSupervisorInfo() {
|
|
||||||
let data;
|
|
||||||
try {
|
|
||||||
const response = await getSupervisorNetworkInfo();
|
|
||||||
if (!response.ok) {
|
|
||||||
throw new Error("Failed to fetch network info");
|
|
||||||
}
|
|
||||||
|
|
||||||
({ data } = await response.json());
|
|
||||||
} catch (err) {
|
|
||||||
// eslint-disable-next-line no-console
|
|
||||||
console.error(err);
|
|
||||||
this._getNetworkInfoError = true;
|
|
||||||
this._dnsPrimaryInterfaceNameservers = undefined;
|
|
||||||
this._dnsPrimaryInterface = undefined;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this._getNetworkInfoError = false;
|
|
||||||
|
|
||||||
const primaryInterface = data.interfaces.find(
|
|
||||||
(intf) => intf.primary && intf.enabled
|
|
||||||
);
|
|
||||||
if (primaryInterface) {
|
|
||||||
this._dnsPrimaryInterfaceNameservers = [
|
|
||||||
...(primaryInterface.ipv4?.nameservers || []),
|
...(primaryInterface.ipv4?.nameservers || []),
|
||||||
...(primaryInterface.ipv6?.nameservers || []),
|
...(primaryInterface.ipv6?.nameservers || []),
|
||||||
].join(", ");
|
].join(", ")
|
||||||
|
);
|
||||||
this._dnsPrimaryInterface = primaryInterface.interface;
|
|
||||||
} else {
|
|
||||||
this._dnsPrimaryInterfaceNameservers = undefined;
|
|
||||||
this._dnsPrimaryInterface = undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!data.host_internet) {
|
|
||||||
this._networkIssue = true;
|
|
||||||
} else {
|
|
||||||
this._networkIssue = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
fireEvent(this, "value-changed", {
|
|
||||||
value: this._networkIssue,
|
|
||||||
});
|
|
||||||
this._scheduleFetchSupervisorInfo();
|
|
||||||
}
|
|
||||||
|
|
||||||
private async _setDns(ev) {
|
private async _setDns(ev) {
|
||||||
|
const primaryInterface = this._getPrimaryInterface(
|
||||||
|
this.networkInfo?.interfaces
|
||||||
|
);
|
||||||
|
|
||||||
const index = ev.target?.index;
|
const index = ev.target?.index;
|
||||||
try {
|
try {
|
||||||
|
const dnsPrimaryInterface = primaryInterface?.interface;
|
||||||
|
if (!dnsPrimaryInterface) {
|
||||||
|
throw new Error("No primary interface found");
|
||||||
|
}
|
||||||
|
|
||||||
const response = await setSupervisorNetworkDns(
|
const response = await setSupervisorNetworkDns(
|
||||||
index,
|
index,
|
||||||
this._dnsPrimaryInterface!
|
dnsPrimaryInterface
|
||||||
);
|
);
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw new Error("Failed to set DNS");
|
throw new Error("Failed to set DNS");
|
||||||
}
|
}
|
||||||
this._networkIssue = false;
|
|
||||||
|
// notify landing page to trigger a network info reload
|
||||||
|
fireEvent(this, "dns-set");
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
// eslint-disable-next-line no-console
|
// eslint-disable-next-line no-console
|
||||||
console.error(err);
|
console.error(err);
|
||||||
@ -205,4 +142,7 @@ declare global {
|
|||||||
interface HTMLElementTagNameMap {
|
interface HTMLElementTagNameMap {
|
||||||
"landing-page-network": LandingPageNetwork;
|
"landing-page-network": LandingPageNetwork;
|
||||||
}
|
}
|
||||||
|
interface HASSDomEvents {
|
||||||
|
"dns-set": undefined;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,17 @@
|
|||||||
import type { LandingPageKeys } from "../../../src/common/translations/localize";
|
import type { LandingPageKeys } from "../../../src/common/translations/localize";
|
||||||
|
import type { HassioResponse } from "../../../src/data/hassio/common";
|
||||||
|
import type {
|
||||||
|
DockerNetwork,
|
||||||
|
NetworkInterface,
|
||||||
|
} from "../../../src/data/hassio/network";
|
||||||
|
import { handleFetchPromise } from "../../../src/util/hass-call-api";
|
||||||
|
|
||||||
|
export interface NetworkInfo {
|
||||||
|
interfaces: NetworkInterface[];
|
||||||
|
docker: DockerNetwork;
|
||||||
|
host_internet: boolean;
|
||||||
|
supervisor_internet: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
export const ALTERNATIVE_DNS_SERVERS: {
|
export const ALTERNATIVE_DNS_SERVERS: {
|
||||||
ipv4: string[];
|
ipv4: string[];
|
||||||
@ -37,8 +50,11 @@ export async function pingSupervisor() {
|
|||||||
return fetch("/supervisor-api/supervisor/ping");
|
return fetch("/supervisor-api/supervisor/ping");
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getSupervisorNetworkInfo() {
|
export async function getSupervisorNetworkInfo(): Promise<NetworkInfo> {
|
||||||
return fetch("/supervisor-api/network/info");
|
const responseData = await handleFetchPromise<HassioResponse<NetworkInfo>>(
|
||||||
|
fetch("/supervisor-api/network/info")
|
||||||
|
);
|
||||||
|
return responseData?.data;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const setSupervisorNetworkDns = async (
|
export const setSupervisorNetworkDns = async (
|
||||||
|
@ -10,36 +10,56 @@ import { extractSearchParam } from "../../src/common/url/search-params";
|
|||||||
import { onBoardingStyles } from "../../src/onboarding/styles";
|
import { onBoardingStyles } from "../../src/onboarding/styles";
|
||||||
import { makeDialogManager } from "../../src/dialogs/make-dialog-manager";
|
import { makeDialogManager } from "../../src/dialogs/make-dialog-manager";
|
||||||
import { LandingPageBaseElement } from "./landing-page-base-element";
|
import { LandingPageBaseElement } from "./landing-page-base-element";
|
||||||
|
import {
|
||||||
|
getSupervisorNetworkInfo,
|
||||||
|
pingSupervisor,
|
||||||
|
type NetworkInfo,
|
||||||
|
} from "./data/supervisor";
|
||||||
|
|
||||||
const SCHEDULE_CORE_CHECK_SECONDS = 5;
|
export const ASSUME_CORE_START_SECONDS = 30;
|
||||||
|
const SCHEDULE_CORE_CHECK_SECONDS = 1;
|
||||||
|
const SCHEDULE_FETCH_NETWORK_INFO_SECONDS = 5;
|
||||||
|
|
||||||
@customElement("ha-landing-page")
|
@customElement("ha-landing-page")
|
||||||
class HaLandingPage extends LandingPageBaseElement {
|
class HaLandingPage extends LandingPageBaseElement {
|
||||||
@property({ attribute: false }) public translationFragment = "landing-page";
|
@property({ attribute: false }) public translationFragment = "landing-page";
|
||||||
|
|
||||||
@state() private _networkIssue = false;
|
|
||||||
|
|
||||||
@state() private _supervisorError = false;
|
@state() private _supervisorError = false;
|
||||||
|
|
||||||
|
@state() private _networkInfo?: NetworkInfo;
|
||||||
|
|
||||||
|
@state() private _coreStatusChecked = false;
|
||||||
|
|
||||||
|
@state() private _networkInfoError = false;
|
||||||
|
|
||||||
|
@state() private _coreCheckActive = false;
|
||||||
|
|
||||||
private _mobileApp =
|
private _mobileApp =
|
||||||
extractSearchParam("redirect_uri") === "homeassistant://auth-callback";
|
extractSearchParam("redirect_uri") === "homeassistant://auth-callback";
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
const networkIssue = this._networkInfo && !this._networkInfo.host_internet;
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
<ha-card>
|
<ha-card>
|
||||||
<div class="card-content">
|
<div class="card-content">
|
||||||
<h1>${this.localize("header")}</h1>
|
<h1>${this.localize("header")}</h1>
|
||||||
${!this._networkIssue && !this._supervisorError
|
${!networkIssue && !this._supervisorError
|
||||||
? html`
|
? html`
|
||||||
<p>${this.localize("subheader")}</p>
|
<p>${this.localize("subheader")}</p>
|
||||||
<mwc-linear-progress indeterminate></mwc-linear-progress>
|
<mwc-linear-progress indeterminate></mwc-linear-progress>
|
||||||
`
|
`
|
||||||
: nothing}
|
: nothing}
|
||||||
<landing-page-network
|
${networkIssue || this._networkInfoError
|
||||||
@value-changed=${this._networkInfoChanged}
|
? html`
|
||||||
.localize=${this.localize}
|
<landing-page-network
|
||||||
></landing-page-network>
|
.localize=${this.localize}
|
||||||
|
.networkInfo=${this._networkInfo}
|
||||||
|
.error=${this._networkInfoError}
|
||||||
|
@dns-set=${this._fetchSupervisorInfo}
|
||||||
|
></landing-page-network>
|
||||||
|
`
|
||||||
|
: nothing}
|
||||||
${this._supervisorError
|
${this._supervisorError
|
||||||
? html`
|
? html`
|
||||||
<ha-alert
|
<ha-alert
|
||||||
@ -88,24 +108,66 @@ class HaLandingPage extends LandingPageBaseElement {
|
|||||||
}
|
}
|
||||||
import("../../src/components/ha-language-picker");
|
import("../../src/components/ha-language-picker");
|
||||||
|
|
||||||
this._scheduleCoreCheck();
|
this._fetchSupervisorInfo(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
private _scheduleCoreCheck() {
|
private _scheduleFetchSupervisorInfo() {
|
||||||
setTimeout(
|
setTimeout(
|
||||||
() => this._checkCoreAvailability(),
|
() => this._fetchSupervisorInfo(true),
|
||||||
SCHEDULE_CORE_CHECK_SECONDS * 1000
|
// on assumed core start check every second, otherwise every 5 seconds
|
||||||
|
(this._coreCheckActive
|
||||||
|
? SCHEDULE_CORE_CHECK_SECONDS
|
||||||
|
: SCHEDULE_FETCH_NETWORK_INFO_SECONDS) * 1000
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _scheduleTurnOffCoreCheck() {
|
||||||
|
setTimeout(() => {
|
||||||
|
this._coreCheckActive = false;
|
||||||
|
}, ASSUME_CORE_START_SECONDS * 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async _fetchSupervisorInfo(schedule = false) {
|
||||||
|
try {
|
||||||
|
const response = await pingSupervisor();
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error("ping-failed");
|
||||||
|
}
|
||||||
|
|
||||||
|
this._networkInfo = await getSupervisorNetworkInfo();
|
||||||
|
this._networkInfoError = false;
|
||||||
|
this._coreStatusChecked = false;
|
||||||
|
} catch (err: any) {
|
||||||
|
if (!this._coreStatusChecked) {
|
||||||
|
// wait before show errors, because we assume that core is starting
|
||||||
|
this._coreCheckActive = true;
|
||||||
|
this._scheduleTurnOffCoreCheck();
|
||||||
|
}
|
||||||
|
await this._checkCoreAvailability();
|
||||||
|
|
||||||
|
// assume supervisor update if ping fails -> don't show an error
|
||||||
|
if (!this._coreCheckActive && err.message !== "ping-failed") {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.error(err);
|
||||||
|
this._networkInfoError = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (schedule) {
|
||||||
|
this._scheduleFetchSupervisorInfo();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private async _checkCoreAvailability() {
|
private async _checkCoreAvailability() {
|
||||||
try {
|
try {
|
||||||
const response = await fetch("/manifest.json");
|
const response = await fetch("/manifest.json");
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
location.reload();
|
location.reload();
|
||||||
|
} else {
|
||||||
|
throw new Error("Failed to fetch manifest");
|
||||||
}
|
}
|
||||||
} finally {
|
} catch (_err) {
|
||||||
this._scheduleCoreCheck();
|
this._coreStatusChecked = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -113,10 +175,6 @@ class HaLandingPage extends LandingPageBaseElement {
|
|||||||
this._supervisorError = true;
|
this._supervisorError = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _networkInfoChanged(ev: CustomEvent) {
|
|
||||||
this._networkIssue = ev.detail.value;
|
|
||||||
}
|
|
||||||
|
|
||||||
private _languageChanged(ev: CustomEvent) {
|
private _languageChanged(ev: CustomEvent) {
|
||||||
const language = ev.detail.value;
|
const language = ev.detail.value;
|
||||||
if (language !== this.language && language) {
|
if (language !== this.language && language) {
|
||||||
|
6
src/common/util/wait.ts
Normal file
6
src/common/util/wait.ts
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
export const waitForMs = (ms: number) =>
|
||||||
|
new Promise((resolve) => {
|
||||||
|
setTimeout(resolve, ms);
|
||||||
|
});
|
||||||
|
|
||||||
|
export const waitForSeconds = (seconds: number) => waitForMs(seconds * 1000);
|
@ -21,7 +21,7 @@ export interface NetworkInterface {
|
|||||||
wifi?: Partial<WifiConfiguration> | null;
|
wifi?: Partial<WifiConfiguration> | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface DockerNetwork {
|
export interface DockerNetwork {
|
||||||
address: string;
|
address: string;
|
||||||
dns: string;
|
dns: string;
|
||||||
gateway: string;
|
gateway: string;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user