mirror of
https://github.com/home-assistant/frontend.git
synced 2026-04-30 14:33:13 +00:00
Compare commits
2 Commits
devtools-a
...
dev
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8f002f2783 | ||
|
|
df754fcd0d |
@@ -189,6 +189,20 @@ export const updateBackupConfig = (
|
||||
config: BackupMutableConfig
|
||||
) => hass.callWS({ type: "backup/config/update", ...config });
|
||||
|
||||
export const saveBackupConfig = (hass: HomeAssistant, config: BackupConfig) =>
|
||||
updateBackupConfig(hass, {
|
||||
create_backup: {
|
||||
agent_ids: config.create_backup.agent_ids,
|
||||
include_folders: config.create_backup.include_folders ?? [],
|
||||
include_database: config.create_backup.include_database,
|
||||
include_addons: config.create_backup.include_addons ?? [],
|
||||
include_all_addons: config.create_backup.include_all_addons,
|
||||
password: config.create_backup.password,
|
||||
},
|
||||
retention: config.retention,
|
||||
schedule: config.schedule,
|
||||
});
|
||||
|
||||
export const getBackupDownloadUrl = (
|
||||
id: string,
|
||||
agentId: string,
|
||||
|
||||
@@ -1,110 +0,0 @@
|
||||
import { ResizeController } from "@lit-labs/observers/resize-controller";
|
||||
import type { LitElement, PropertyValues } from "lit";
|
||||
import { state } from "lit/decorators";
|
||||
import type { StyleInfo } from "lit/directives/style-map";
|
||||
import type { Constructor } from "../types";
|
||||
|
||||
/**
|
||||
* Public interface added by {@link MatchMinHeightMixin}.
|
||||
*
|
||||
* Declared separately so consumers can reference the mixin's contributed
|
||||
* members in their own type annotations, per the Lit mixin authoring guide.
|
||||
*/
|
||||
export declare class MatchMinHeightMixinInterface {
|
||||
/** Most recently observed height of `matchMinHeightTarget`, in pixels. */
|
||||
protected _matchedMinHeight?: number;
|
||||
|
||||
/**
|
||||
* `StyleInfo` exposing the matched height as a `min-height` declaration.
|
||||
* Pass to `styleMap` to keep a layout at least as tall as the target
|
||||
* element. Empty until a height has been observed.
|
||||
*/
|
||||
protected get _matchMinHeightStyle(): StyleInfo;
|
||||
|
||||
/**
|
||||
* Element whose height should be matched as a `min-height` floor. Override
|
||||
* with a getter that returns a `@query` result. Return `null` to pause
|
||||
* observation (e.g. while the element is not rendered).
|
||||
*/
|
||||
protected get matchMinHeightTarget(): HTMLElement | null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Mixin that observes a target element's height and exposes it as a
|
||||
* `min-height` style. Useful for keeping a sibling layout (e.g. a YAML
|
||||
* editor) at least as tall as another (e.g. a UI form) to avoid content
|
||||
* shift when toggling between them.
|
||||
*
|
||||
* Subclasses override `matchMinHeightTarget` (typically returning a
|
||||
* `@query`-decorated element) to specify which element to observe.
|
||||
*/
|
||||
export const MatchMinHeightMixin = <T extends Constructor<LitElement>>(
|
||||
superClass: T
|
||||
) => {
|
||||
class MatchMinHeightMixinClass extends superClass {
|
||||
@state() protected _matchedMinHeight?: number;
|
||||
|
||||
private _matchTarget: HTMLElement | null = null;
|
||||
|
||||
private _matchResize = new ResizeController(this, {
|
||||
target: null,
|
||||
callback: (entries) => {
|
||||
const height = entries[0]?.contentRect.height;
|
||||
if (height) {
|
||||
this._matchedMinHeight = height;
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
private static readonly DEFAULT_MATCH_TARGET: HTMLElement | null = null;
|
||||
|
||||
protected get matchMinHeightTarget(): HTMLElement | null {
|
||||
return MatchMinHeightMixinClass.DEFAULT_MATCH_TARGET;
|
||||
}
|
||||
|
||||
protected get _matchMinHeightStyle(): StyleInfo {
|
||||
return this._matchedMinHeight !== undefined
|
||||
? { "min-height": `${this._matchedMinHeight}px` }
|
||||
: {};
|
||||
}
|
||||
|
||||
protected firstUpdated(changedProperties: PropertyValues<this>) {
|
||||
super.firstUpdated?.(changedProperties);
|
||||
this._attachMatchTarget();
|
||||
}
|
||||
|
||||
protected updated(changedProperties: PropertyValues<this>) {
|
||||
super.updated?.(changedProperties);
|
||||
this._attachMatchTarget();
|
||||
}
|
||||
|
||||
public disconnectedCallback() {
|
||||
this._detachMatchTarget();
|
||||
super.disconnectedCallback();
|
||||
}
|
||||
|
||||
private _attachMatchTarget() {
|
||||
const element = this.matchMinHeightTarget;
|
||||
if (element === this._matchTarget) {
|
||||
return;
|
||||
}
|
||||
this._detachMatchTarget();
|
||||
if (!element) {
|
||||
return;
|
||||
}
|
||||
this._matchTarget = element;
|
||||
this._matchResize.observe(element);
|
||||
}
|
||||
|
||||
private _detachMatchTarget() {
|
||||
if (!this._matchTarget) {
|
||||
return;
|
||||
}
|
||||
this._matchResize.unobserve?.(this._matchTarget);
|
||||
this._matchTarget = null;
|
||||
}
|
||||
}
|
||||
|
||||
return MatchMinHeightMixinClass as unknown as Constructor<MatchMinHeightMixinInterface> &
|
||||
T;
|
||||
};
|
||||
@@ -0,0 +1,123 @@
|
||||
import { mdiPuzzle } from "@mdi/js";
|
||||
import type { CSSResultGroup } from "lit";
|
||||
import { css, html, LitElement } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import "../../../../../components/ha-card";
|
||||
import "../../../../../components/ha-icon-next";
|
||||
import "../../../../../components/ha-md-list";
|
||||
import "../../../../../components/ha-md-list-item";
|
||||
import "../../../../../components/ha-svg-icon";
|
||||
import {
|
||||
getSupervisorUpdateConfig,
|
||||
type SupervisorUpdateConfig,
|
||||
} from "../../../../../data/supervisor/update";
|
||||
import { haStyle } from "../../../../../resources/styles";
|
||||
import type { HomeAssistant } from "../../../../../types";
|
||||
|
||||
@customElement("ha-backup-overview-app-update-backup")
|
||||
class HaBackupOverviewAppUpdateBackup extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@state() private _supervisorUpdateConfig?: SupervisorUpdateConfig;
|
||||
|
||||
protected firstUpdated() {
|
||||
this._fetchSupervisorUpdateConfig();
|
||||
}
|
||||
|
||||
public connectedCallback() {
|
||||
super.connectedCallback();
|
||||
if (this.hasUpdated) {
|
||||
this._fetchSupervisorUpdateConfig();
|
||||
}
|
||||
}
|
||||
|
||||
private async _fetchSupervisorUpdateConfig() {
|
||||
try {
|
||||
this._supervisorUpdateConfig = await getSupervisorUpdateConfig(this.hass);
|
||||
} catch (err) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(err);
|
||||
}
|
||||
}
|
||||
|
||||
private _appUpdateBackupDescription() {
|
||||
if (!this._supervisorUpdateConfig) {
|
||||
return this.hass.localize(
|
||||
"ui.panel.config.backup.settings.app_update_backup.local_only"
|
||||
);
|
||||
}
|
||||
|
||||
if (!this._supervisorUpdateConfig.add_on_backup_before_update) {
|
||||
return this.hass.localize(
|
||||
"ui.panel.config.backup.schedule.update_preference.skip_backups"
|
||||
);
|
||||
}
|
||||
|
||||
const copies =
|
||||
this._supervisorUpdateConfig.add_on_backup_retain_copies || 1;
|
||||
|
||||
return `${this.hass.localize(
|
||||
"ui.panel.config.backup.schedule.update_preference.backup_before_update"
|
||||
)} ${this.hass.localize(
|
||||
"ui.panel.config.backup.overview.settings.schedule_copies_backups",
|
||||
{ count: copies }
|
||||
)}`;
|
||||
}
|
||||
|
||||
protected render() {
|
||||
return html`
|
||||
<ha-card>
|
||||
<div class="card-header">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.backup.overview.app_update_backup.title"
|
||||
)}
|
||||
</div>
|
||||
<div class="card-content">
|
||||
<ha-md-list>
|
||||
<ha-md-list-item
|
||||
type="link"
|
||||
href="/config/backup/app-update-backups"
|
||||
>
|
||||
<ha-svg-icon slot="start" .path=${mdiPuzzle}></ha-svg-icon>
|
||||
<div slot="headline">${this._appUpdateBackupDescription()}</div>
|
||||
<div slot="supporting-text">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.backup.overview.app_update_backup.description"
|
||||
)}
|
||||
</div>
|
||||
<ha-icon-next slot="end"></ha-icon-next>
|
||||
</ha-md-list-item>
|
||||
</ha-md-list>
|
||||
</div>
|
||||
</ha-card>
|
||||
`;
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return [
|
||||
haStyle,
|
||||
css`
|
||||
.card-header {
|
||||
padding-bottom: 8px;
|
||||
}
|
||||
|
||||
.card-content {
|
||||
padding-left: 0;
|
||||
padding-right: 0;
|
||||
padding-top: 0;
|
||||
}
|
||||
|
||||
ha-md-list {
|
||||
padding-top: 0;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-backup-overview-app-update-backup": HaBackupOverviewAppUpdateBackup;
|
||||
}
|
||||
}
|
||||
154
src/panels/config/backup/ha-config-backup-app-update-backups.ts
Normal file
154
src/panels/config/backup/ha-config-backup-app-update-backups.ts
Normal file
@@ -0,0 +1,154 @@
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import type { PropertyValues } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { isComponentLoaded } from "../../../common/config/is_component_loaded";
|
||||
import { debounce } from "../../../common/util/debounce";
|
||||
import "../../../components/ha-alert";
|
||||
import "../../../components/ha-card";
|
||||
import {
|
||||
getSupervisorUpdateConfig,
|
||||
updateSupervisorUpdateConfig,
|
||||
type SupervisorUpdateConfig,
|
||||
} from "../../../data/supervisor/update";
|
||||
import "../../../layouts/hass-subpage";
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
import "./components/config/ha-backup-config-addon";
|
||||
|
||||
@customElement("ha-config-backup-app-update-backups")
|
||||
class HaConfigBackupAppUpdateBackups extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property({ type: Boolean }) public narrow = false;
|
||||
|
||||
@state() private _supervisorUpdateConfig?: SupervisorUpdateConfig;
|
||||
|
||||
@state() private _error?: string;
|
||||
|
||||
protected willUpdate(changedProps: PropertyValues<this>): void {
|
||||
super.willUpdate(changedProps);
|
||||
|
||||
if (
|
||||
!this.hasUpdated &&
|
||||
this.hass &&
|
||||
isComponentLoaded(this.hass.config, "hassio")
|
||||
) {
|
||||
this._getSupervisorUpdateConfig();
|
||||
}
|
||||
}
|
||||
|
||||
protected render() {
|
||||
return html`
|
||||
<hass-subpage
|
||||
back-path="/config/backup/overview"
|
||||
.hass=${this.hass}
|
||||
.narrow=${this.narrow}
|
||||
.header=${this.hass.localize(
|
||||
"ui.panel.config.backup.app_update_backups.header"
|
||||
)}
|
||||
>
|
||||
<div class="content">
|
||||
<ha-card>
|
||||
<div class="card-content">
|
||||
<p>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.backup.settings.app_update_backup.description"
|
||||
)}
|
||||
</p>
|
||||
<p>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.backup.settings.app_update_backup.local_only"
|
||||
)}
|
||||
</p>
|
||||
${this._error
|
||||
? html`<ha-alert alert-type="error">${this._error}</ha-alert>`
|
||||
: nothing}
|
||||
<ha-backup-config-addon
|
||||
.hass=${this.hass}
|
||||
.supervisorUpdateConfig=${this._supervisorUpdateConfig}
|
||||
@update-config-changed=${this._supervisorUpdateConfigChanged}
|
||||
></ha-backup-config-addon>
|
||||
</div>
|
||||
</ha-card>
|
||||
</div>
|
||||
</hass-subpage>
|
||||
`;
|
||||
}
|
||||
|
||||
private async _getSupervisorUpdateConfig() {
|
||||
try {
|
||||
this._supervisorUpdateConfig = await getSupervisorUpdateConfig(this.hass);
|
||||
this._error = undefined;
|
||||
} catch (err: any) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(err);
|
||||
this._error = this.hass.localize(
|
||||
"ui.panel.config.backup.settings.app_update_backup.error_load",
|
||||
{
|
||||
error: err?.message || err,
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private async _supervisorUpdateConfigChanged(ev) {
|
||||
const config = ev.detail.value as SupervisorUpdateConfig;
|
||||
this._supervisorUpdateConfig = {
|
||||
...this._supervisorUpdateConfig,
|
||||
...config,
|
||||
} as SupervisorUpdateConfig;
|
||||
this._debounceSaveSupervisorUpdateConfig();
|
||||
}
|
||||
|
||||
private _debounceSaveSupervisorUpdateConfig = debounce(
|
||||
() => this._saveSupervisorUpdateConfig(),
|
||||
500
|
||||
);
|
||||
|
||||
private async _saveSupervisorUpdateConfig() {
|
||||
if (!this._supervisorUpdateConfig) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await updateSupervisorUpdateConfig(
|
||||
this.hass,
|
||||
this._supervisorUpdateConfig
|
||||
);
|
||||
this._error = undefined;
|
||||
} catch (err: any) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(err);
|
||||
this._error = this.hass.localize(
|
||||
"ui.panel.config.backup.settings.app_update_backup.error_save",
|
||||
{
|
||||
error: err?.message || err?.toString(),
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
static styles = css`
|
||||
p {
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
|
||||
.content {
|
||||
padding: 28px 20px 0;
|
||||
max-width: 690px;
|
||||
margin: 0 auto;
|
||||
gap: var(--ha-space-6);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.card-content {
|
||||
padding-bottom: 0;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-config-backup-app-update-backups": HaConfigBackupAppUpdateBackups;
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,9 @@
|
||||
import { mdiDotsVertical, mdiPlus, mdiUpload } from "@mdi/js";
|
||||
import type { CSSResultGroup, TemplateResult } from "lit";
|
||||
import type { CSSResultGroup, PropertyValues, TemplateResult } from "lit";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { fireEvent } from "../../../common/dom/fire_event";
|
||||
import { debounce } from "../../../common/util/debounce";
|
||||
import "../../../components/ha-button";
|
||||
import "../../../components/ha-card";
|
||||
import "../../../components/ha-dropdown";
|
||||
@@ -23,6 +24,7 @@ import {
|
||||
computeBackupAgentName,
|
||||
generateBackup,
|
||||
generateBackupWithAutomaticSettings,
|
||||
saveBackupConfig,
|
||||
} from "../../../data/backup";
|
||||
import type { ManagerStateEvent } from "../../../data/backup_manager";
|
||||
import type { CloudStatus } from "../../../data/cloud";
|
||||
@@ -32,10 +34,12 @@ import { haStyle } from "../../../resources/styles";
|
||||
import type { HomeAssistant, Route } from "../../../types";
|
||||
import { showAlertDialog } from "../../lovelace/custom-card-helpers";
|
||||
import "./components/overview/ha-backup-overview-backups";
|
||||
import "./components/overview/ha-backup-overview-app-update-backup";
|
||||
import "./components/overview/ha-backup-overview-onboarding";
|
||||
import "./components/overview/ha-backup-overview-progress";
|
||||
import "./components/overview/ha-backup-overview-settings";
|
||||
import "./components/overview/ha-backup-overview-summary";
|
||||
import "./components/config/ha-backup-config-encryption-key";
|
||||
import { showBackupOnboardingDialog } from "./dialogs/show-dialog-backup_onboarding";
|
||||
import { showGenerateBackupDialog } from "./dialogs/show-dialog-generate-backup";
|
||||
import { showNewBackupDialog } from "./dialogs/show-dialog-new-backup";
|
||||
@@ -68,10 +72,54 @@ class HaConfigBackupOverview extends LitElement {
|
||||
{ uploaded_bytes: number; total_bytes: number }
|
||||
> = {};
|
||||
|
||||
@state() private _config?: BackupConfig;
|
||||
|
||||
protected willUpdate(changedProperties: PropertyValues<this>): void {
|
||||
super.willUpdate(changedProperties);
|
||||
if (changedProperties.has("config") && !this._config) {
|
||||
this._config = this.config;
|
||||
}
|
||||
}
|
||||
|
||||
public connectedCallback(): void {
|
||||
super.connectedCallback();
|
||||
// Update config when the page is displayed (e.g. when coming back from a settings page)
|
||||
this._config = this.config;
|
||||
}
|
||||
|
||||
private _uploadBackup = async () => {
|
||||
await showUploadBackupDialog(this, {});
|
||||
};
|
||||
|
||||
private _encryptionKeyChanged(ev) {
|
||||
if (!this._config) {
|
||||
return;
|
||||
}
|
||||
|
||||
const password = ev.detail.value as string;
|
||||
this._config = {
|
||||
...this._config,
|
||||
create_backup: {
|
||||
...this._config.create_backup,
|
||||
password,
|
||||
},
|
||||
};
|
||||
|
||||
this._debounceSaveConfig();
|
||||
}
|
||||
|
||||
private _debounceSaveConfig = debounce(() => this._saveConfig(), 500);
|
||||
|
||||
private async _saveConfig() {
|
||||
if (!this._config) {
|
||||
return;
|
||||
}
|
||||
|
||||
await saveBackupConfig(this.hass, this._config);
|
||||
|
||||
fireEvent(this, "ha-refresh-backup-config");
|
||||
}
|
||||
|
||||
private _handleOnboardingButtonClick(ev) {
|
||||
ev.stopPropagation();
|
||||
this._setupAutomaticBackup(true);
|
||||
@@ -234,13 +282,41 @@ class HaConfigBackupOverview extends LitElement {
|
||||
.backups=${this.backups}
|
||||
></ha-backup-overview-backups>
|
||||
|
||||
${!this._needsOnboarding && this.config
|
||||
${!this._needsOnboarding && this._config
|
||||
? html`
|
||||
<ha-card>
|
||||
<div class="card-header">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.backup.settings.encryption_key.title"
|
||||
)}
|
||||
</div>
|
||||
<div class="card-content">
|
||||
<p>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.backup.settings.encryption_key.description"
|
||||
)}
|
||||
</p>
|
||||
<ha-backup-config-encryption-key
|
||||
.hass=${this.hass}
|
||||
.value=${this._config.create_backup.password}
|
||||
@value-changed=${this._encryptionKeyChanged}
|
||||
></ha-backup-config-encryption-key>
|
||||
</div>
|
||||
</ha-card>
|
||||
|
||||
<ha-backup-overview-settings
|
||||
.hass=${this.hass}
|
||||
.config=${this.config!}
|
||||
.config=${this._config}
|
||||
.agents=${this.agents}
|
||||
></ha-backup-overview-settings>
|
||||
|
||||
${this.hass.config.components.includes("hassio")
|
||||
? html`
|
||||
<ha-backup-overview-app-update-backup
|
||||
.hass=${this.hass}
|
||||
></ha-backup-overview-app-update-backup>
|
||||
`
|
||||
: nothing}
|
||||
`
|
||||
: nothing}
|
||||
</div>
|
||||
@@ -270,6 +346,10 @@ class HaConfigBackupOverview extends LitElement {
|
||||
return [
|
||||
haStyle,
|
||||
css`
|
||||
p {
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
|
||||
.content {
|
||||
padding: 28px 20px 0;
|
||||
max-width: 690px;
|
||||
@@ -283,10 +363,6 @@ class HaConfigBackupOverview extends LitElement {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
.card-content {
|
||||
padding-left: 0;
|
||||
padding-right: 0;
|
||||
}
|
||||
.loading {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ import "../../../components/ha-icon-button";
|
||||
import "../../../components/ha-icon-next";
|
||||
import "../../../components/ha-svg-icon";
|
||||
import type { BackupAgent, BackupConfig } from "../../../data/backup";
|
||||
import { updateBackupConfig } from "../../../data/backup";
|
||||
import { saveBackupConfig } from "../../../data/backup";
|
||||
import type { CloudStatus } from "../../../data/cloud";
|
||||
import {
|
||||
getSupervisorUpdateConfig,
|
||||
@@ -27,11 +27,9 @@ import "../../../layouts/hass-subpage";
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
import { brandsUrl } from "../../../util/brands-url";
|
||||
import { documentationUrl } from "../../../util/documentation-url";
|
||||
import "./components/config/ha-backup-config-addon";
|
||||
import "./components/config/ha-backup-config-agents";
|
||||
import "./components/config/ha-backup-config-data";
|
||||
import type { BackupConfigData } from "./components/config/ha-backup-config-data";
|
||||
import "./components/config/ha-backup-config-encryption-key";
|
||||
import "./components/config/ha-backup-config-schedule";
|
||||
import type { BackupConfigSchedule } from "./components/config/ha-backup-config-schedule";
|
||||
import { showLocalBackupLocationDialog } from "./dialogs/show-dialog-local-backup-location";
|
||||
@@ -79,7 +77,7 @@ class HaConfigBackupSettings extends LitElement {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(err);
|
||||
this._supervisorUpdateConfigError = this.hass.localize(
|
||||
"ui.panel.config.backup.settings.app_update_backup.error_load",
|
||||
"ui.panel.config.backup.settings.schedule.error_load",
|
||||
{
|
||||
error: err?.message || err,
|
||||
}
|
||||
@@ -315,57 +313,6 @@ class HaConfigBackupSettings extends LitElement {
|
||||
: nothing}
|
||||
</div>
|
||||
</ha-card>
|
||||
${supervisor
|
||||
? html`<ha-card>
|
||||
<div class="card-header">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.backup.settings.app_update_backup.title"
|
||||
)}
|
||||
</div>
|
||||
<div class="card-content">
|
||||
<p>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.backup.settings.app_update_backup.description"
|
||||
)}
|
||||
</p>
|
||||
<p>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.backup.settings.app_update_backup.local_only"
|
||||
)}
|
||||
</p>
|
||||
${this._supervisorUpdateConfigError
|
||||
? html`<ha-alert alert-type="error">
|
||||
${this._supervisorUpdateConfigError}
|
||||
</ha-alert>`
|
||||
: nothing}
|
||||
<ha-backup-config-addon
|
||||
.hass=${this.hass}
|
||||
.supervisorUpdateConfig=${this._supervisorUpdateConfig}
|
||||
@update-config-changed=${this
|
||||
._supervisorUpdateConfigChanged}
|
||||
></ha-backup-config-addon>
|
||||
</div>
|
||||
</ha-card>`
|
||||
: nothing}
|
||||
<ha-card>
|
||||
<div class="card-header">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.backup.settings.encryption_key.title"
|
||||
)}
|
||||
</div>
|
||||
<div class="card-content">
|
||||
<p>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.backup.settings.encryption_key.description"
|
||||
)}
|
||||
</p>
|
||||
<ha-backup-config-encryption-key
|
||||
.hass=${this.hass}
|
||||
.value=${this._config.create_backup.password}
|
||||
@value-changed=${this._encryptionKeyChanged}
|
||||
></ha-backup-config-encryption-key>
|
||||
</div>
|
||||
</ha-card>
|
||||
</div>
|
||||
</hass-subpage>
|
||||
`;
|
||||
@@ -438,18 +385,6 @@ class HaConfigBackupSettings extends LitElement {
|
||||
this._debounceSave();
|
||||
}
|
||||
|
||||
private _encryptionKeyChanged(ev) {
|
||||
const password = ev.detail.value as string;
|
||||
this._config = {
|
||||
...this._config!,
|
||||
create_backup: {
|
||||
...this._config!.create_backup,
|
||||
password: password,
|
||||
},
|
||||
};
|
||||
this._debounceSave();
|
||||
}
|
||||
|
||||
private _debounceSaveSupervisorUpdateConfig = debounce(
|
||||
() => this._saveSupervisorUpdateConfig(),
|
||||
500
|
||||
@@ -468,7 +403,7 @@ class HaConfigBackupSettings extends LitElement {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(err);
|
||||
this._supervisorUpdateConfigError = this.hass.localize(
|
||||
"ui.panel.config.backup.settings.app_update_backup.error_save",
|
||||
"ui.panel.config.backup.settings.schedule.error_save",
|
||||
{
|
||||
error: err?.message || err?.toString(),
|
||||
}
|
||||
@@ -479,18 +414,7 @@ class HaConfigBackupSettings extends LitElement {
|
||||
private _debounceSave = debounce(() => this._save(), 500);
|
||||
|
||||
private async _save() {
|
||||
await updateBackupConfig(this.hass, {
|
||||
create_backup: {
|
||||
agent_ids: this._config!.create_backup.agent_ids,
|
||||
include_folders: this._config!.create_backup.include_folders ?? [],
|
||||
include_database: this._config!.create_backup.include_database,
|
||||
include_addons: this._config!.create_backup.include_addons ?? [],
|
||||
include_all_addons: this._config!.create_backup.include_all_addons,
|
||||
password: this._config!.create_backup.password,
|
||||
},
|
||||
retention: this._config!.retention,
|
||||
schedule: this._config!.schedule,
|
||||
});
|
||||
await saveBackupConfig(this.hass, this._config!);
|
||||
fireEvent(this, "ha-refresh-backup-config");
|
||||
}
|
||||
|
||||
|
||||
@@ -125,6 +125,11 @@ class HaConfigBackup extends SubscribeMixin(HassRouterPage) {
|
||||
load: () => import("./ha-config-backup-settings"),
|
||||
cache: true,
|
||||
},
|
||||
"app-update-backups": {
|
||||
tag: "ha-config-backup-app-update-backups",
|
||||
load: () => import("./ha-config-backup-app-update-backups"),
|
||||
cache: true,
|
||||
},
|
||||
location: {
|
||||
tag: "ha-config-backup-location",
|
||||
load: () => import("./ha-config-backup-location"),
|
||||
|
||||
@@ -5,11 +5,9 @@ import { dump, JSON_SCHEMA, load } from "js-yaml";
|
||||
import type { CSSResultGroup, TemplateResult, PropertyValues } from "lit";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import { styleMap } from "lit/directives/style-map";
|
||||
import { until } from "lit/directives/until";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { storage } from "../../../../common/decorators/storage";
|
||||
import type { HASSDomEvent } from "../../../../common/dom/fire_event";
|
||||
import { computeDomain } from "../../../../common/entity/compute_domain";
|
||||
import { computeObjectId } from "../../../../common/entity/compute_object_id";
|
||||
import {
|
||||
@@ -25,7 +23,6 @@ import { showToast } from "../../../../util/toast";
|
||||
import "../../../../components/entity/ha-entity-picker";
|
||||
import "../../../../components/ha-alert";
|
||||
import "../../../../components/ha-button";
|
||||
import "../../../../components/ha-button-toggle-group";
|
||||
import "../../../../components/ha-card";
|
||||
import "../../../../components/buttons/ha-progress-button";
|
||||
import "../../../../components/ha-expansion-panel";
|
||||
@@ -42,14 +39,12 @@ import {
|
||||
serviceCallWillDisconnect,
|
||||
} from "../../../../data/service";
|
||||
import { haStyle } from "../../../../resources/styles";
|
||||
import type { HomeAssistant, ToggleButton } from "../../../../types";
|
||||
import type { HomeAssistant } from "../../../../types";
|
||||
import { documentationUrl } from "../../../../util/documentation-url";
|
||||
import { resolveMediaSource } from "../../../../data/media_source";
|
||||
import { MatchMinHeightMixin } from "../../../../mixins/match-min-height-mixin";
|
||||
import { withViewTransition } from "../../../../common/util/view-transition";
|
||||
|
||||
@customElement("developer-tools-action")
|
||||
class HaPanelDevAction extends MatchMinHeightMixin(LitElement) {
|
||||
class HaPanelDevAction extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property({ type: Boolean }) public narrow = false;
|
||||
@@ -85,12 +80,6 @@ class HaPanelDevAction extends MatchMinHeightMixin(LitElement) {
|
||||
|
||||
@query("#yaml-editor") private _yamlEditor?: HaYamlEditor;
|
||||
|
||||
@query(".ui-mode-content") private _uiModeContent?: HTMLElement;
|
||||
|
||||
protected get matchMinHeightTarget(): HTMLElement | null {
|
||||
return this._yamlMode ? null : (this._uiModeContent ?? null);
|
||||
}
|
||||
|
||||
protected willUpdate() {
|
||||
if (
|
||||
!this.hasUpdated &&
|
||||
@@ -128,21 +117,6 @@ class HaPanelDevAction extends MatchMinHeightMixin(LitElement) {
|
||||
this._serviceData?.action
|
||||
);
|
||||
|
||||
const modeButtons: ToggleButton[] = [
|
||||
{
|
||||
label: this.hass.localize(
|
||||
"ui.panel.config.developer-tools.tabs.actions.ui_mode"
|
||||
),
|
||||
value: "ui",
|
||||
},
|
||||
{
|
||||
label: this.hass.localize(
|
||||
"ui.panel.config.developer-tools.tabs.actions.yaml_mode"
|
||||
),
|
||||
value: "yaml",
|
||||
},
|
||||
];
|
||||
|
||||
const domain = this._serviceData?.action
|
||||
? computeDomain(this._serviceData?.action)
|
||||
: undefined;
|
||||
@@ -158,34 +132,14 @@ class HaPanelDevAction extends MatchMinHeightMixin(LitElement) {
|
||||
|
||||
return html`
|
||||
<div class="content">
|
||||
<p>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.developer-tools.tabs.actions.description"
|
||||
)}
|
||||
</p>
|
||||
<ha-card>
|
||||
<div class="card-header">
|
||||
<div class="header-row">
|
||||
<div class="header-title">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.developer-tools.tabs.actions.title"
|
||||
)}
|
||||
</div>
|
||||
<ha-button-toggle-group
|
||||
size="small"
|
||||
class="yaml-mode-toggle"
|
||||
.buttons=${modeButtons}
|
||||
.active=${this._yamlMode ? "yaml" : "ui"}
|
||||
.disabled=${!this._uiAvailable}
|
||||
@value-changed=${this._modeChanged}
|
||||
></ha-button-toggle-group>
|
||||
</div>
|
||||
<p class="secondary">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.developer-tools.tabs.actions.description"
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
${this._yamlMode
|
||||
? html`<div
|
||||
class="card-content"
|
||||
style=${styleMap(this._matchMinHeightStyle)}
|
||||
>
|
||||
? html`<div class="card-content">
|
||||
<ha-service-picker
|
||||
.hass=${this.hass}
|
||||
.value=${this._serviceData?.action}
|
||||
@@ -207,27 +161,44 @@ class HaPanelDevAction extends MatchMinHeightMixin(LitElement) {
|
||||
show-advanced
|
||||
show-service-id
|
||||
@value-changed=${this._serviceDataChanged}
|
||||
class="card-content ui-mode-content"
|
||||
class="card-content"
|
||||
></ha-service-control>
|
||||
`}
|
||||
${this._error !== undefined
|
||||
? html`<ha-alert alert-type="error">${this._error}</ha-alert>`
|
||||
: nothing}
|
||||
<div class="card-actions">
|
||||
</ha-card>
|
||||
</div>
|
||||
<div class="button-row">
|
||||
<div class="buttons">
|
||||
<div class="switch-mode-container">
|
||||
<ha-button
|
||||
appearance="plain"
|
||||
@click=${this._toggleYaml}
|
||||
.disabled=${!this._uiAvailable}
|
||||
>
|
||||
${this._yamlMode
|
||||
? this.hass.localize(
|
||||
"ui.panel.config.developer-tools.tabs.actions.ui_mode"
|
||||
)
|
||||
: this.hass.localize(
|
||||
"ui.panel.config.developer-tools.tabs.actions.yaml_mode"
|
||||
)}
|
||||
</ha-button>
|
||||
${!this._uiAvailable
|
||||
? html`<span class="error"
|
||||
>${this.hass.localize(
|
||||
"ui.panel.config.developer-tools.tabs.actions.no_template_ui_support"
|
||||
)}</span
|
||||
>`
|
||||
: nothing}
|
||||
<ha-progress-button raised @click=${this._callService}>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.developer-tools.tabs.actions.call_service"
|
||||
)}
|
||||
</ha-progress-button>
|
||||
: ""}
|
||||
</div>
|
||||
</ha-card>
|
||||
<ha-progress-button raised @click=${this._callService}>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.developer-tools.tabs.actions.call_service"
|
||||
)}
|
||||
</ha-progress-button>
|
||||
</div>
|
||||
</div>
|
||||
${this._response?.result
|
||||
? html`<div class="content response">
|
||||
@@ -468,7 +439,7 @@ class HaPanelDevAction extends MatchMinHeightMixin(LitElement) {
|
||||
}
|
||||
);
|
||||
|
||||
private async _callService(ev: Event) {
|
||||
private async _callService(ev) {
|
||||
const button = ev.currentTarget as HaProgressButton;
|
||||
|
||||
if (this._yamlMode && !this._yamlValid) {
|
||||
@@ -589,20 +560,13 @@ class HaPanelDevAction extends MatchMinHeightMixin(LitElement) {
|
||||
button.actionSuccess();
|
||||
}
|
||||
|
||||
private _modeChanged(ev: HASSDomEvent<{ value: string }>) {
|
||||
ev.stopPropagation();
|
||||
const yamlMode = ev.detail.value === "yaml";
|
||||
if (yamlMode === this._yamlMode) {
|
||||
return;
|
||||
}
|
||||
withViewTransition(() => {
|
||||
this._yamlMode = yamlMode;
|
||||
this._yamlValid = true;
|
||||
this._error = undefined;
|
||||
});
|
||||
private _toggleYaml() {
|
||||
this._yamlMode = !this._yamlMode;
|
||||
this._yamlValid = true;
|
||||
this._error = undefined;
|
||||
}
|
||||
|
||||
private _yamlChanged(ev: HASSDomEvent<{ value: any; isValid: boolean }>) {
|
||||
private _yamlChanged(ev) {
|
||||
if (!ev.detail.isValid) {
|
||||
this._yamlValid = false;
|
||||
return;
|
||||
@@ -638,7 +602,7 @@ class HaPanelDevAction extends MatchMinHeightMixin(LitElement) {
|
||||
}
|
||||
}
|
||||
|
||||
private _serviceDataChanged(ev: HASSDomEvent<{ value: any }>) {
|
||||
private _serviceDataChanged(ev) {
|
||||
if (this._serviceData?.action !== ev.detail.value.action) {
|
||||
this._error = undefined;
|
||||
}
|
||||
@@ -646,7 +610,7 @@ class HaPanelDevAction extends MatchMinHeightMixin(LitElement) {
|
||||
this._checkUiSupported();
|
||||
}
|
||||
|
||||
private _serviceChanged(ev: HASSDomEvent<{ value: any }>) {
|
||||
private _serviceChanged(ev) {
|
||||
ev.stopPropagation();
|
||||
if (ev.detail.value) {
|
||||
this._serviceData = { action: ev.detail.value, data: {} };
|
||||
@@ -703,55 +667,30 @@ class HaPanelDevAction extends MatchMinHeightMixin(LitElement) {
|
||||
max-width: 1200px;
|
||||
margin: auto;
|
||||
}
|
||||
.card-header {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--ha-space-1);
|
||||
.button-row {
|
||||
padding: var(--ha-space-2) var(--ha-space-4);
|
||||
border-top: 1px solid var(--divider-color);
|
||||
border-bottom: 1px solid var(--divider-color);
|
||||
background: var(--card-background-color);
|
||||
position: sticky;
|
||||
bottom: 0;
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
}
|
||||
.header-row {
|
||||
.button-row .buttons {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: var(--ha-space-2);
|
||||
max-width: 1200px;
|
||||
margin: auto;
|
||||
}
|
||||
.header-title {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
.secondary {
|
||||
margin: 0;
|
||||
font-size: var(--ha-font-size-m);
|
||||
font-weight: var(--ha-font-weight-normal);
|
||||
line-height: normal;
|
||||
letter-spacing: normal;
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
.card-content {
|
||||
.switch-mode-container {
|
||||
display: flex;
|
||||
align-items: stretch;
|
||||
justify-content: flex-start;
|
||||
flex-direction: column;
|
||||
gap: var(--ha-space-4);
|
||||
margin: var(--ha-space-2);
|
||||
--service-control-padding: 0;
|
||||
}
|
||||
.card-content ha-yaml-editor {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.yaml-mode-toggle {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.card-actions {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
gap: var(--ha-space-2);
|
||||
}
|
||||
.card-actions .error {
|
||||
flex: 1;
|
||||
color: var(--error-color);
|
||||
.switch-mode-container .error {
|
||||
margin-left: var(--ha-space-2);
|
||||
margin-inline-start: var(--ha-space-2);
|
||||
margin-inline-end: initial;
|
||||
}
|
||||
.attributes {
|
||||
width: 100%;
|
||||
|
||||
@@ -419,6 +419,7 @@ export class HuiDialogEditBadge
|
||||
.content {
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
gap: var(--ha-space-3);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -419,6 +419,7 @@ export class HuiDialogEditCard
|
||||
.content {
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
gap: var(--ha-space-3);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3566,7 +3566,7 @@
|
||||
"show_all": "Show all backups"
|
||||
},
|
||||
"settings": {
|
||||
"title": "Backup settings",
|
||||
"title": "Automatic backup",
|
||||
"configure": "Configure backup settings",
|
||||
"schedule": "Automatic backup schedule and retention",
|
||||
"schedule_copies_all": "and keep all backups",
|
||||
@@ -3612,6 +3612,10 @@
|
||||
"sat": "Sa",
|
||||
"sun": "Su"
|
||||
}
|
||||
},
|
||||
"app_update_backup": {
|
||||
"title": "App update backup",
|
||||
"description": "Backup behavior for app updates"
|
||||
}
|
||||
},
|
||||
"backups": {
|
||||
@@ -3623,13 +3627,15 @@
|
||||
"new_backup": "[%key:ui::panel::config::backup::overview::new_backup%]"
|
||||
},
|
||||
"settings": {
|
||||
"header": "Backup settings",
|
||||
"header": "Automatic backups",
|
||||
"menu": {
|
||||
"change_default_location": "Change default action location"
|
||||
},
|
||||
"schedule": {
|
||||
"title": "Automatic backups",
|
||||
"description": "Let Home Assistant take care of your backups by creating a scheduled backup that also removes older backups."
|
||||
"title": "Backup cycle",
|
||||
"description": "Let Home Assistant take care of your backups by creating a scheduled backup that also removes older backups.",
|
||||
"error_load": "Error loading Supervisor update config: {error}",
|
||||
"error_save": "Error saving Supervisor update config: {error}"
|
||||
},
|
||||
"data": {
|
||||
"title": "Backup data"
|
||||
@@ -3657,6 +3663,9 @@
|
||||
"error_save": "Error saving Supervisor update config: {error}"
|
||||
}
|
||||
},
|
||||
"app_update_backups": {
|
||||
"header": "App update backups"
|
||||
},
|
||||
"details": {
|
||||
"header": "Backup",
|
||||
"not_found": "Not found",
|
||||
@@ -3823,8 +3832,8 @@
|
||||
"column_description": "Description",
|
||||
"column_example": "Example",
|
||||
"fill_example_data": "Fill example data",
|
||||
"yaml_mode": "YAML mode",
|
||||
"ui_mode": "UI mode",
|
||||
"yaml_mode": "Go to YAML mode",
|
||||
"ui_mode": "Go to UI mode",
|
||||
"yaml_parameters": "Parameters only available in YAML mode",
|
||||
"all_parameters": "All available parameters",
|
||||
"accepts_target": "This action accepts a target, for example: `entity_id: light.bed_light`",
|
||||
|
||||
Reference in New Issue
Block a user