diff --git a/demo/src/stubs/backup.ts b/demo/src/stubs/backup.ts index d49b576d10..789316e91f 100644 --- a/demo/src/stubs/backup.ts +++ b/demo/src/stubs/backup.ts @@ -1,7 +1,6 @@ import type { BackupAgentsInfo, BackupConfig, - BackupContent, BackupInfo, } from "../../../src/data/backup"; import { BackupScheduleRecurrence } from "../../../src/data/backup"; @@ -28,7 +27,16 @@ const backupInfo: BackupInfo = { "backup.local": { size: 1024 * 1024 * 512, protected: true }, "cloud.cloud": { size: 1024 * 1024 * 512, protected: true }, }, - } as BackupContent, + failed_agent_ids: [], + failed_addons: [], + failed_folders: [], + extra_metadata: {}, + addons: [], + database_included: true, + folders: [], + homeassistant_included: true, + homeassistant_version: "2026.9.0", + }, ], agent_errors: {}, last_attempted_automatic_backup: recent, diff --git a/demo/src/stubs/cloud.ts b/demo/src/stubs/cloud.ts index 6716259e88..52418822c0 100644 --- a/demo/src/stubs/cloud.ts +++ b/demo/src/stubs/cloud.ts @@ -20,12 +20,14 @@ const demoWebhooks: Webhook[] = [ domain: "automation", name: "Front door motion", local_only: false, + allowed_methods: ["POST", "PUT"], }, { webhook_id: "demo_companion_app", domain: "mobile_app", name: "Companion app", local_only: false, + allowed_methods: ["POST", "PUT"], }, ]; @@ -49,12 +51,13 @@ const demoCloudhooks = Object.fromEntries( // object directly and reset on reload. const cloudStatus: CloudStatusLoggedIn = { logged_in: true, + auto_login: null, cloud: "connected", cloud_last_disconnect_reason: null, email: "demo@home-assistant.io", google_registered: false, + google_local_connected: false, google_entities: emptyFilter(), - google_domains: ["light", "switch", "climate", "cover"], alexa_registered: true, alexa_entities: emptyFilter(), remote_domain: "demo-instance.ui.nabu.casa", @@ -75,10 +78,11 @@ const cloudStatus: CloudStatusLoggedIn = { alexa_enabled: true, remote_enabled: true, remote_allow_remote_enable: true, - strict_connection: "disabled", - google_secure_devices_pin: undefined, + google_secure_devices_pin: null, cloudhooks: demoCloudhooks, + alexa_default_expose: null, alexa_report_state: true, + google_default_expose: null, google_report_state: true, tts_default_voice: ["en-US", "JennyNeural"], cloud_ice_servers_enabled: false, diff --git a/gallery/src/pages/misc/cloud-account.ts b/gallery/src/pages/misc/cloud-account.ts index 81820d13f2..5f06cc0a92 100644 --- a/gallery/src/pages/misc/cloud-account.ts +++ b/gallery/src/pages/misc/cloud-account.ts @@ -121,12 +121,14 @@ const demoWebhooks: Webhook[] = [ domain: "automation", name: "Front door motion", local_only: false, + allowed_methods: ["POST", "PUT"], }, { webhook_id: "demo_companion_app", domain: "mobile_app", name: "Companion app", local_only: false, + allowed_methods: ["POST", "PUT"], }, ]; @@ -155,12 +157,13 @@ const buildCloudStatus = (scenario: CloudDemoScenario): CloudStatusLoggedIn => { : {}; return { logged_in: true, + auto_login: null, cloud: "connected", cloud_last_disconnect_reason: null, email: "demo@home-assistant.io", google_registered: scenario.google, + google_local_connected: false, google_entities: emptyFilter(), - google_domains: ["light", "switch", "climate", "cover"], alexa_registered: scenario.alexa, alexa_entities: emptyFilter(), remote_domain: "demo-instance.ui.nabu.casa", @@ -181,10 +184,11 @@ const buildCloudStatus = (scenario: CloudDemoScenario): CloudStatusLoggedIn => { alexa_enabled: scenario.alexa, remote_enabled: scenario.remote, remote_allow_remote_enable: true, - strict_connection: "disabled", - google_secure_devices_pin: undefined, + google_secure_devices_pin: null, cloudhooks, + alexa_default_expose: null, alexa_report_state: true, + google_default_expose: null, google_report_state: true, tts_default_voice: ["en-US", "JennyNeural"], cloud_ice_servers_enabled: scenario.webrtc, diff --git a/src/data/alexa.ts b/src/data/alexa.ts index fa13a1862b..3619a5188e 100644 --- a/src/data/alexa.ts +++ b/src/data/alexa.ts @@ -2,7 +2,7 @@ import type { HomeAssistant } from "../types"; export interface AlexaEntity { entity_id: string; - display_categories: string[]; + display_categories: string[] | null; interfaces: string[]; } @@ -10,7 +10,7 @@ export const fetchCloudAlexaEntities = (hass: HomeAssistant) => hass.callWS({ type: "cloud/alexa/entities" }); export const fetchCloudAlexaEntity = (hass: HomeAssistant, entity_id: string) => - hass.callWS({ + hass.callWS({ type: "cloud/alexa/entities/get", entity_id, }); diff --git a/src/data/backup.ts b/src/data/backup.ts index 5dd4bc6417..53957924b7 100644 --- a/src/data/backup.ts +++ b/src/data/backup.ts @@ -47,7 +47,7 @@ export interface BackupConfig { last_attempted_automatic_backup: string | null; last_completed_automatic_backup: string | null; next_automatic_backup: string | null; - next_automatic_backup_additional?: boolean; + next_automatic_backup_additional: boolean; create_backup: { agent_ids: string[]; include_addons: string[] | null; @@ -60,7 +60,7 @@ export interface BackupConfig { retention: Retention; schedule: { recurrence: BackupScheduleRecurrence; - time?: string | null; + time: string | null; days: BackupDay[]; }; agents: BackupAgentsConfig; @@ -79,9 +79,9 @@ export interface BackupMutableConfig { }; retention?: Retention; schedule?: { - recurrence: BackupScheduleRecurrence; + recurrence?: BackupScheduleRecurrence; time?: string | null; - days?: BackupDay[] | null; + days?: BackupDay[]; }; agents?: BackupAgentsConfig; } @@ -114,21 +114,22 @@ export interface BackupContent { date: string; name: string; agents: Record; - failed_agent_ids?: string[]; - failed_addons?: AddonInfo[]; - failed_folders?: string[]; - extra_metadata?: { + failed_agent_ids: string[]; + failed_addons: AddonInfo[]; + failed_folders: string[]; + extra_metadata: { "supervisor.addon_update"?: string; "supervisor.app_update"?: string; + [key: string]: string | boolean | undefined; }; - with_automatic_settings: boolean; + with_automatic_settings: boolean | null; } export interface BackupData { addons: BackupAddon[]; database_included: boolean; folders: string[]; - homeassistant_version: string; + homeassistant_version: string | null; homeassistant_included: boolean; } @@ -141,7 +142,7 @@ export interface BackupAddon { export interface BackupContentExtended extends BackupContent, BackupData {} export interface BackupInfo { - backups: BackupContent[]; + backups: BackupContentExtended[]; agent_errors: Record; last_attempted_automatic_backup: string | null; last_completed_automatic_backup: string | null; @@ -152,6 +153,7 @@ export interface BackupInfo { } export interface BackupDetails { + agent_errors: Record; backup: BackupContentExtended; } @@ -232,7 +234,10 @@ export const fetchBackupAgentsInfo = ( type: "backup/agents/info", }); -export const deleteBackup = (hass: HomeAssistant, id: string): Promise => +export const deleteBackup = ( + hass: HomeAssistant, + id: string +): Promise<{ agent_errors: Record }> => hass.callWS({ type: "backup/delete", backup_id: id, @@ -241,7 +246,7 @@ export const deleteBackup = (hass: HomeAssistant, id: string): Promise => export const generateBackup = ( hass: HomeAssistant, params: GenerateBackupParams -): Promise<{ backup_id: string }> => +): Promise<{ backup_job_id: string }> => hass.callWS({ type: "backup/generate", ...params, @@ -249,7 +254,7 @@ export const generateBackup = ( export const generateBackupWithAutomaticSettings = ( hass: HomeAssistant -): Promise => +): Promise<{ backup_job_id: string }> => hass.callWS({ type: "backup/generate_with_automatic_settings", }); @@ -257,7 +262,7 @@ export const generateBackupWithAutomaticSettings = ( export const restoreBackup = ( hass: HomeAssistant, params: RestoreBackupParams -): Promise<{ backup_id: string }> => +): Promise => hass.callWS({ type: "backup/restore", ...params, diff --git a/src/data/backup_manager.ts b/src/data/backup_manager.ts index debc044bd0..bc41f21a7d 100644 --- a/src/data/backup_manager.ts +++ b/src/data/backup_manager.ts @@ -1,7 +1,7 @@ import type { HomeAssistant } from "../types"; export type BackupManagerState = - "idle" | "create_backup" | "receive_backup" | "restore_backup"; + "idle" | "create_backup" | "receive_backup" | "restore_backup" | "blocked"; export type CreateBackupStage = | "addon_repositories" @@ -39,7 +39,8 @@ export type RestoreBackupStage = | "remove_delta_addons" | "remove_delta_apps"; -export type RestoreBackupState = "completed" | "failed" | "in_progress"; +export type RestoreBackupState = + "completed" | "core_restart" | "failed" | "in_progress"; interface IdleEvent { manager_state: "idle"; @@ -47,22 +48,29 @@ interface IdleEvent { interface CreateBackupEvent { manager_state: "create_backup"; + reason: string | null; stage: CreateBackupStage | null; state: CreateBackupState; } interface ReceiveBackupEvent { manager_state: "receive_backup"; + reason: string | null; stage: ReceiveBackupStage | null; state: ReceiveBackupState; } interface RestoreBackupEvent { manager_state: "restore_backup"; + reason: string | null; stage: RestoreBackupStage | null; state: RestoreBackupState; } +interface BlockedEvent { + manager_state: "blocked"; +} + export interface UploadBackupEvent { manager_state: BackupManagerState; agent_id: string; @@ -71,10 +79,14 @@ export interface UploadBackupEvent { } export type ManagerState = - "idle" | "create_backup" | "receive_backup" | "restore_backup"; + "idle" | "create_backup" | "receive_backup" | "restore_backup" | "blocked"; export type ManagerStateEvent = - IdleEvent | CreateBackupEvent | ReceiveBackupEvent | RestoreBackupEvent; + | IdleEvent + | CreateBackupEvent + | ReceiveBackupEvent + | RestoreBackupEvent + | BlockedEvent; export type BackupSubscriptionEvent = ManagerStateEvent | UploadBackupEvent; diff --git a/src/data/backup_onboarding.ts b/src/data/backup_onboarding.ts index 3609c03761..8899e5804f 100644 --- a/src/data/backup_onboarding.ts +++ b/src/data/backup_onboarding.ts @@ -8,7 +8,7 @@ import type { export interface BackupOnboardingInfo { state: BackupManagerState; - last_action_event?: { + last_action_event: { manager_state: BackupManagerState; stage: RestoreBackupStage | null; state: RestoreBackupState; diff --git a/src/data/cloud.ts b/src/data/cloud.ts index 84e45221db..ae859785d6 100644 --- a/src/data/cloud.ts +++ b/src/data/cloud.ts @@ -1,8 +1,6 @@ import type { EntityDomainFilter } from "../common/entity/entity_domain_filter"; import type { HomeAssistant } from "../types"; -type StrictConnectionMode = "disabled" | "guard_page" | "drop_connection"; - export interface CloudAutoLogin { email: string; failed: string | null; @@ -27,10 +25,11 @@ export interface CloudPreferences { alexa_enabled: boolean; remote_enabled: boolean; remote_allow_remote_enable: boolean; - strict_connection: StrictConnectionMode; - google_secure_devices_pin: string | undefined; + google_secure_devices_pin: string | null; cloudhooks: Record; + alexa_default_expose: string[] | null; alexa_report_state: boolean; + google_default_expose: string[] | null; google_report_state: boolean; tts_default_voice: [string, string]; cloud_ice_servers_enabled: boolean; @@ -43,18 +42,19 @@ export type RemoteCertificateStatus = export interface CloudStatusLoggedIn { logged_in: true; + auto_login: null; cloud: "disconnected" | "connecting" | "connected"; cloud_last_disconnect_reason: { clean: boolean; reason: string } | null; email: string; google_registered: boolean; + google_local_connected: boolean; google_entities: EntityDomainFilter; - google_domains: string[]; alexa_registered: boolean; alexa_entities: EntityDomainFilter; prefs: CloudPreferences; - remote_domain: string | undefined; + remote_domain: string | null; remote_connected: boolean; - remote_certificate: undefined | CertificateInformation; + remote_certificate: CertificateInformation | null; remote_certificate_status: RemoteCertificateStatus | null; http_use_ssl: boolean; active_subscription: boolean; @@ -115,7 +115,7 @@ export interface CloudLoginMFA extends CloudLoginBase { export type CloudEvent = | { type: "login" | "logout" | "auto_login_cancelled" } - | { type: "auto_login_failed"; translation_key: string }; + | { type: "auto_login_failed"; translation_key: string | null }; export const cloudLogin = ({ hass, @@ -196,7 +196,6 @@ export const updateCloudPref = ( google_secure_devices_pin?: CloudPreferences["google_secure_devices_pin"]; tts_default_voice?: CloudPreferences["tts_default_voice"]; remote_allow_remote_enable?: CloudPreferences["remote_allow_remote_enable"]; - strict_connection?: CloudPreferences["strict_connection"]; cloud_ice_servers_enabled?: CloudPreferences["cloud_ice_servers_enabled"]; } ) => diff --git a/src/data/google_assistant.ts b/src/data/google_assistant.ts index 761eaba344..c1d2282a6c 100644 --- a/src/data/google_assistant.ts +++ b/src/data/google_assistant.ts @@ -4,7 +4,7 @@ export interface GoogleEntity { entity_id: string; traits: string[]; might_2fa: boolean; - disable_2fa?: boolean; + disable_2fa?: boolean | null; } export const fetchCloudGoogleEntities = (hass: HomeAssistant) => diff --git a/src/data/onboarding.ts b/src/data/onboarding.ts index 303190072b..b557d2ffec 100644 --- a/src/data/onboarding.ts +++ b/src/data/onboarding.ts @@ -1,11 +1,11 @@ import type { HomeAssistant } from "../types"; import { handleFetchPromise } from "../util/hass-call-api"; -import type { CloudStatus } from "./cloud"; export interface InstallationType { installation_type: - | "Home Assistant Operating System" + | "Home Assistant OS" | "Home Assistant Container" + | "Unsupported Third Party Container" | "Home Assistant Supervised" | "Home Assistant Core" | "Unknown"; @@ -49,6 +49,10 @@ export interface CloudLoginMFA extends CloudLoginBase { code: string; } +export interface OnboardingCloudStatus { + logged_in: boolean; +} + export const fetchOnboardingOverview = () => fetch(`${__HASS_URL__}/api/onboarding`, { credentials: "same-origin" }); @@ -115,7 +119,7 @@ export const loginHaCloud = async ( }) ); -export const fetchHaCloudStatus = async (): Promise => +export const fetchHaCloudStatus = async (): Promise => handleFetchPromise(fetch("/api/onboarding/cloud/status")); export const signOutHaCloud = async () => diff --git a/src/data/webhook.ts b/src/data/webhook.ts index a08e0c13fc..13117fba8d 100644 --- a/src/data/webhook.ts +++ b/src/data/webhook.ts @@ -5,6 +5,7 @@ export interface Webhook { domain: string; name: string; local_only: boolean; + allowed_methods: string[]; } export interface WebhookError { code: number; diff --git a/src/onboarding/onboarding-restore-backup.ts b/src/onboarding/onboarding-restore-backup.ts index 58b09ec881..01e8649541 100644 --- a/src/onboarding/onboarding-restore-backup.ts +++ b/src/onboarding/onboarding-restore-backup.ts @@ -13,11 +13,11 @@ import { type BackupOnboardingConfig, type BackupOnboardingInfo, } from "../data/backup_onboarding"; -import type { CloudStatus } from "../data/cloud"; import { fetchHaCloudStatus, signOutHaCloud, waitForIntegration, + type OnboardingCloudStatus, } from "../data/onboarding"; import { showToast } from "../util/toast"; import "./onboarding-loading"; @@ -53,7 +53,7 @@ class OnboardingRestoreBackup extends LitElement { @state() private _failed?: boolean; - @state() private _cloudStatus?: CloudStatus; + @state() private _cloudStatus?: OnboardingCloudStatus; @storage({ key: "onboarding-restore-backup-backup-id", diff --git a/src/panels/config/backup/components/config/ha-backup-config-schedule.ts b/src/panels/config/backup/components/config/ha-backup-config-schedule.ts index e2a7a987aa..c0eca788b9 100644 --- a/src/panels/config/backup/components/config/ha-backup-config-schedule.ts +++ b/src/panels/config/backup/components/config/ha-backup-config-schedule.ts @@ -101,7 +101,10 @@ class HaBackupConfigSchedule extends LitElement { ...this.value, schedule: { recurrence: data.recurrence, - time: data.time_option === BackupScheduleTime.CUSTOM ? data.time : null, + time: + data.time_option === BackupScheduleTime.CUSTOM + ? (data.time ?? null) + : null, days: data.recurrence === BackupScheduleRecurrence.CUSTOM_DAYS ? data.days diff --git a/src/panels/config/backup/components/ha-backup-data-picker.ts b/src/panels/config/backup/components/ha-backup-data-picker.ts index d06636ffd4..cae0a4cd52 100644 --- a/src/panels/config/backup/components/ha-backup-data-picker.ts +++ b/src/panels/config/backup/components/ha-backup-data-picker.ts @@ -28,7 +28,7 @@ import "./ha-backup-formfield-label"; interface CheckBoxItem { label: string; id: string; - version?: string; + version?: string | null; } const ITEM_ICONS = { diff --git a/src/panels/config/backup/components/overview/ha-backup-overview-progress.ts b/src/panels/config/backup/components/overview/ha-backup-overview-progress.ts index 1d6d6832a8..e2d26f1f00 100644 --- a/src/panels/config/backup/components/overview/ha-backup-overview-progress.ts +++ b/src/panels/config/backup/components/overview/ha-backup-overview-progress.ts @@ -93,7 +93,7 @@ export class HaBackupOverviewProgress extends LitElement { private get _heading() { const managerState = this.manager.manager_state; - if (managerState === "idle") { + if (managerState === "idle" || managerState === "blocked") { return ""; } return this.hass.localize( @@ -102,7 +102,10 @@ export class HaBackupOverviewProgress extends LitElement { } private get _isUploadStage(): boolean { - if (this.manager.manager_state === "idle") { + if ( + this.manager.manager_state === "idle" || + this.manager.manager_state === "blocked" + ) { return false; } return this.manager.stage === "upload_to_agents"; diff --git a/src/panels/config/cloud/account/cloud-remote-pref.ts b/src/panels/config/cloud/account/cloud-remote-pref.ts index a50dc8a303..2fc17716c3 100644 --- a/src/panels/config/cloud/account/cloud-remote-pref.ts +++ b/src/panels/config/cloud/account/cloud-remote-pref.ts @@ -39,7 +39,7 @@ export class CloudRemotePref extends LitElement { return nothing; } - const { remote_enabled, remote_allow_remote_enable, strict_connection } = + const { remote_enabled, remote_allow_remote_enable } = this.cloudStatus.prefs; const { @@ -120,17 +120,7 @@ export class CloudRemotePref extends LitElement { )} > ` - : strict_connection === "drop_connection" - ? html`${this.hass.localize( - `ui.panel.config.cloud.account.remote.drop_connection_warning` - )}` - : nothing + : nothing }

${this.hass.localize( diff --git a/src/translations/en.json b/src/translations/en.json index 3cd09b3948..bc447cd260 100644 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -6837,8 +6837,6 @@ "security_options": "Security options", "external_activation": "Allow external activation of remote access", "external_activation_secondary": "If you disable remote access on this page, having this setting enabled allows you to reactivate it remotely via your Nabu Casa account.", - "drop_connection_warning_title": "Remote login has been deactivated", - "drop_connection_warning": "The below security options may make it appear the system is not running.", "certificate_info": "Certificate information", "certificate_expire": "Certificate renewal at {date}", "more_info": "More details", diff --git a/test/data/backup.test.ts b/test/data/backup.test.ts index fd5f150c44..e502076b6c 100644 --- a/test/data/backup.test.ts +++ b/test/data/backup.test.ts @@ -19,6 +19,7 @@ const makeConfig = (overrides: Partial = {}): BackupConfig => last_attempted_automatic_backup: null, last_completed_automatic_backup: null, next_automatic_backup: null, + next_automatic_backup_additional: false, create_backup: { agent_ids: [CLOUD_AGENT], include_addons: null, @@ -29,7 +30,11 @@ const makeConfig = (overrides: Partial = {}): BackupConfig => password: null, }, retention: {}, - schedule: { recurrence: BackupScheduleRecurrence.DAILY, days: [] }, + schedule: { + recurrence: BackupScheduleRecurrence.DAILY, + time: null, + days: [], + }, agents: {}, ...overrides, }) satisfies BackupConfig; diff --git a/test/panels/config/cloud/cloud-account-status.test.ts b/test/panels/config/cloud/cloud-account-status.test.ts index f87dab72b7..f7b60e152e 100644 --- a/test/panels/config/cloud/cloud-account-status.test.ts +++ b/test/panels/config/cloud/cloud-account-status.test.ts @@ -19,6 +19,7 @@ const makeConfig = (overrides: Partial = {}): BackupConfig => last_attempted_automatic_backup: null, last_completed_automatic_backup: null, next_automatic_backup: null, + next_automatic_backup_additional: false, create_backup: { agent_ids: [CLOUD_AGENT], include_addons: null, @@ -29,7 +30,11 @@ const makeConfig = (overrides: Partial = {}): BackupConfig => password: null, }, retention: {}, - schedule: { recurrence: BackupScheduleRecurrence.DAILY, days: [] }, + schedule: { + recurrence: BackupScheduleRecurrence.DAILY, + time: null, + days: [], + }, agents: {}, ...overrides, }) satisfies BackupConfig; @@ -48,10 +53,11 @@ const makePrefs = ( alexa_enabled: true, remote_enabled: true, remote_allow_remote_enable: true, - strict_connection: "disabled", - google_secure_devices_pin: undefined, + google_secure_devices_pin: null, cloudhooks: {}, + alexa_default_expose: null, alexa_report_state: true, + google_default_expose: null, google_report_state: true, tts_default_voice: ["en-US", "JennyNeural"], cloud_ice_servers_enabled: true, @@ -65,18 +71,19 @@ const makeStatus = ( overrides: Partial = {} ): CloudStatusLoggedIn => ({ logged_in: true, + auto_login: null, cloud: "connected", cloud_last_disconnect_reason: null, email: "test@home-assistant.io", google_registered: true, + google_local_connected: false, google_entities: emptyFilter(), - google_domains: [], alexa_registered: true, alexa_entities: emptyFilter(), prefs: makePrefs(prefsOverrides), - remote_domain: undefined, + remote_domain: null, remote_connected: true, - remote_certificate: undefined, + remote_certificate: null, remote_certificate_status: "ready", http_use_ssl: false, active_subscription: true,