mirror of
https://github.com/home-assistant/frontend.git
synced 2026-07-18 00:57:04 +00:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 88ef6fe7ad |
+32
-3
@@ -15,10 +15,26 @@ export interface HttpConfig {
|
||||
ssl_profile?: "modern" | "intermediate";
|
||||
}
|
||||
|
||||
// The slot the running HTTP server was actually started with.
|
||||
export type ActiveConfigType = "stable" | "pending" | "default";
|
||||
|
||||
// A stored config slot carries metadata alongside the editable fields:
|
||||
// - created_at: when the slot was staged
|
||||
// - error: null while healthy; set once a slot could not be applied or a
|
||||
// pending trial was not confirmed (then it is kept for display, not retried)
|
||||
export interface HttpConfigWithMeta extends HttpConfig {
|
||||
created_at?: string;
|
||||
error?: string | null;
|
||||
}
|
||||
|
||||
export interface HttpConfigState {
|
||||
stable: HttpConfig;
|
||||
pending: HttpConfig | null;
|
||||
stable: HttpConfigWithMeta;
|
||||
pending: HttpConfigWithMeta | null;
|
||||
revert_at: string | null;
|
||||
// Added in the "active HTTP config slot" backend change; optional so the
|
||||
// frontend keeps working against cores without it.
|
||||
active_config_type?: ActiveConfigType;
|
||||
default?: HttpConfigWithMeta;
|
||||
}
|
||||
|
||||
export const HTTP_CONFIG_FIELDS: (keyof HttpConfig)[] = [
|
||||
@@ -40,6 +56,19 @@ export interface SaveHttpConfigResult {
|
||||
restart: boolean;
|
||||
}
|
||||
|
||||
// Keep only the editable fields; the backend storage schema rejects unknown
|
||||
// keys, so the created_at/error metadata that rides along on a fetched slot
|
||||
// must be dropped before configuring.
|
||||
export const stripHttpConfigMeta = (config: HttpConfig): HttpConfig => {
|
||||
const stripped: Partial<Record<keyof HttpConfig, unknown>> = {};
|
||||
for (const key of HTTP_CONFIG_FIELDS) {
|
||||
if (config[key] !== undefined) {
|
||||
stripped[key] = config[key];
|
||||
}
|
||||
}
|
||||
return stripped as HttpConfig;
|
||||
};
|
||||
|
||||
export const fetchHttpConfig = (hass: HomeAssistant) =>
|
||||
hass.callWS<HttpConfigState>({ type: "http/config" });
|
||||
|
||||
@@ -49,7 +78,7 @@ export const saveHttpConfig = (
|
||||
) =>
|
||||
hass.callWS<SaveHttpConfigResult>({
|
||||
type: "http/config/configure",
|
||||
config,
|
||||
config: config ? stripHttpConfigMeta(config) : null,
|
||||
});
|
||||
|
||||
export const promoteHttpConfig = (hass: HomeAssistant) =>
|
||||
|
||||
@@ -250,7 +250,14 @@ export class HomeAssistantAppEl extends QuickBarMixin(HassElement) {
|
||||
// The check re-runs on the next reconnect; ignore transient failures.
|
||||
return;
|
||||
}
|
||||
if (!httpConfig.pending || this._httpPendingDialogOpen) {
|
||||
// Only prompt for an active trial. A pending config with an error was
|
||||
// already reverted/failed and is kept only for display in the config form,
|
||||
// so it must not pop the confirm/revert dialog.
|
||||
if (
|
||||
!httpConfig.pending ||
|
||||
httpConfig.pending.error ||
|
||||
this._httpPendingDialogOpen
|
||||
) {
|
||||
return;
|
||||
}
|
||||
this._httpPendingDialogOpen = true;
|
||||
|
||||
@@ -19,7 +19,11 @@ import {
|
||||
HTTP_CONFIG_FIELDS,
|
||||
saveHttpConfig,
|
||||
} from "../../../data/http";
|
||||
import type { HttpConfig } from "../../../data/http";
|
||||
import type {
|
||||
ActiveConfigType,
|
||||
HttpConfig,
|
||||
HttpConfigWithMeta,
|
||||
} from "../../../data/http";
|
||||
import { showConfirmationDialog } from "../../../dialogs/generic/show-dialog-box";
|
||||
import { haStyle } from "../../../resources/styles";
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
@@ -161,6 +165,11 @@ class HaConfigHttpForm extends LitElement {
|
||||
|
||||
@state() private _showNoChanges = false;
|
||||
|
||||
@state() private _activeConfigType?: ActiveConfigType;
|
||||
|
||||
// A pending config that was reverted/failed and kept only for display.
|
||||
@state() private _revertedPending?: HttpConfigWithMeta;
|
||||
|
||||
@query("ha-form") private _form?: HaForm;
|
||||
|
||||
@query("ha-alert") private _firstAlert?: HTMLElement;
|
||||
@@ -201,6 +210,40 @@ class HaConfigHttpForm extends LitElement {
|
||||
<p class="description">
|
||||
${this.hass.localize("ui.panel.config.network.http.description")}
|
||||
</p>
|
||||
${
|
||||
this._activeConfigType === "default"
|
||||
? html`
|
||||
<ha-alert alert-type="warning">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.network.http.running_default"
|
||||
)}
|
||||
</ha-alert>
|
||||
`
|
||||
: nothing
|
||||
}
|
||||
${
|
||||
this._revertedPending
|
||||
? html`
|
||||
<ha-alert alert-type="warning">
|
||||
${
|
||||
this._revertedPending.error === "not_promoted"
|
||||
? this.hass.localize(
|
||||
"ui.panel.config.network.http.reverted_not_confirmed"
|
||||
)
|
||||
: this.hass.localize(
|
||||
"ui.panel.config.network.http.reverted_failed",
|
||||
{ error: this._revertedPending.error ?? "" }
|
||||
)
|
||||
}
|
||||
<ha-button slot="action" @click=${this._reviewReverted}>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.network.http.reverted_action"
|
||||
)}
|
||||
</ha-button>
|
||||
</ha-alert>
|
||||
`
|
||||
: nothing
|
||||
}
|
||||
${
|
||||
portChanged
|
||||
? html`
|
||||
@@ -266,16 +309,30 @@ class HaConfigHttpForm extends LitElement {
|
||||
|
||||
private async _fetchConfig(): Promise<void> {
|
||||
try {
|
||||
// Pending is exclusively handled by the global confirm/revert dialog, so
|
||||
// the form only ever displays stable.
|
||||
const { stable } = await fetchHttpConfig(this.hass);
|
||||
const { stable, pending, active_config_type } = await fetchHttpConfig(
|
||||
this.hass
|
||||
);
|
||||
this._stable = stable;
|
||||
this._config = { ...stable };
|
||||
this._activeConfigType = active_config_type;
|
||||
// An active trial pending (no error) is handled by the global
|
||||
// confirm/revert dialog. A pending carrying an error was reverted or
|
||||
// failed to apply and is kept only so we can surface it here.
|
||||
this._revertedPending = pending?.error ? pending : undefined;
|
||||
} catch (err: any) {
|
||||
this._error = err.message;
|
||||
}
|
||||
}
|
||||
|
||||
private _reviewReverted(): void {
|
||||
if (!this._revertedPending) {
|
||||
return;
|
||||
}
|
||||
// Load the reverted values into the form so the user can fix and re-save.
|
||||
this._config = { ...this._revertedPending };
|
||||
this._revertedPending = undefined;
|
||||
}
|
||||
|
||||
private _computeLabel = (
|
||||
schema: SchemaUnion<ReturnType<typeof SCHEMA>>
|
||||
): string => {
|
||||
|
||||
@@ -8684,6 +8684,10 @@
|
||||
"port_warning": "Clients such as the Home Assistant mobile apps will lose their connection until you update the URL in their settings. If Home Assistant is not confirmed reachable on the new port, the change is rolled back automatically after 5 minutes.",
|
||||
"invalid_host": "Enter a valid IP address.",
|
||||
"invalid_network": "Enter a valid IP address or network.",
|
||||
"running_default": "Your saved HTTP configuration could not be applied, so Home Assistant is running on the built-in default configuration.",
|
||||
"reverted_not_confirmed": "The last HTTP configuration change was not confirmed in time and was rolled back. Home Assistant is running on the previous configuration.",
|
||||
"reverted_failed": "The last HTTP configuration change could not be applied and was rolled back. Home Assistant is running on the previous configuration. Reason: {error}",
|
||||
"reverted_action": "Review the change",
|
||||
"save_confirm": {
|
||||
"title": "Restart required",
|
||||
"text": "Saving will restart Home Assistant to apply the new HTTP settings.",
|
||||
|
||||
Reference in New Issue
Block a user