From 6def14718d9288441f9737c0fa3372ed045708c1 Mon Sep 17 00:00:00 2001 From: Petar Petrov Date: Tue, 26 May 2026 16:22:47 +0300 Subject: [PATCH 1/8] Group pending updates by category on updates page --- .../config/core/ha-config-section-updates.ts | 220 +++++++++++++++--- src/translations/en.json | 4 + 2 files changed, 198 insertions(+), 26 deletions(-) diff --git a/src/panels/config/core/ha-config-section-updates.ts b/src/panels/config/core/ha-config-section-updates.ts index a9f80d220e85..7effbeeee19e 100644 --- a/src/panels/config/core/ha-config-section-updates.ts +++ b/src/panels/config/core/ha-config-section-updates.ts @@ -11,10 +11,13 @@ import { LitElement, css, html, nothing } from "lit"; import { customElement, property, state } from "lit/decorators"; import memoizeOne from "memoize-one"; import { isComponentLoaded } from "../../../common/config/is_component_loaded"; +import { caseInsensitiveStringCompare } from "../../../common/string/compare"; import "../../../components/ha-card"; import "../../../components/ha-dropdown"; import type { HaDropdownSelectEvent } from "../../../components/ha-dropdown"; import "../../../components/ha-dropdown-item"; +import type { EntitySources } from "../../../data/entity/entity_sources"; +import { fetchEntitySourcesWithCache } from "../../../data/entity/entity_sources"; import { extractApiErrorMessage } from "../../../data/hassio/common"; import type { HassioSupervisorInfo, @@ -25,6 +28,8 @@ import { reloadSupervisor, setSupervisorOption, } from "../../../data/hassio/supervisor"; +import { domainToName } from "../../../data/integration"; +import type { UpdateEntity } from "../../../data/update"; import { checkForEntityUpdates, filterUpdateEntitiesParameterized, @@ -35,6 +40,17 @@ import type { HomeAssistant } from "../../../types"; import "../dashboard/ha-config-updates"; import { showJoinBetaDialog } from "./updates/show-dialog-join-beta"; +interface UpdateGroup { + key: string; + title: string; + entities: UpdateEntity[]; + showUpdateAll: boolean; +} + +const SYSTEM_KEY = "__system__"; +const APPS_KEY = "__apps__"; +const INTEGRATIONS_KEY = "__integrations__"; + @customElement("ha-config-section-updates") class HaConfigSectionUpdates extends LitElement { @property({ attribute: false }) public hass!: HomeAssistant; @@ -47,16 +63,22 @@ class HaConfigSectionUpdates extends LitElement { @state() private _supervisorInfo?: HassioSupervisorInfo; + @state() private _entitySources?: EntitySources; + protected firstUpdated(changedProps: PropertyValues) { super.firstUpdated(changedProps); if (isComponentLoaded(this.hass.config, "hassio")) { this._refreshSupervisorInfo(); } + + fetchEntitySourcesWithCache(this.hass).then((sources) => { + this._entitySources = sources; + }); } protected render(): TemplateResult { - const canInstallUpdates = this._filterInstallableUpdateEntities( + const installableUpdates = this._filterInstallableUpdateEntities( this.hass.states, this._showSkipped ); @@ -65,6 +87,8 @@ class HaConfigSectionUpdates extends LitElement { this._showSkipped ); + const groups = this._groupUpdates(installableUpdates, this._entitySources); + return html`
- ${canInstallUpdates.length - ? html` - -
+ ${groups.map( + (group) => html` + +
+
- ${this.hass.localize("ui.panel.config.updates.title", { - count: canInstallUpdates.length, - })} + ${group.title}
- + ${group.showUpdateAll + ? html` + + ` + : nothing}
- - ` - : nothing} + +
+
+ ` + )} ${notInstallableUpdates.length ? html`
-
- ${this.hass.localize( - "ui.panel.config.updates.title_not_installable", - { - count: notInstallableUpdates.length, - } - )} +
+
+ ${this.hass.localize( + "ui.panel.config.updates.title_not_installable", + { + count: notInstallableUpdates.length, + } + )} +
` : nothing} - ${canInstallUpdates.length + notInstallableUpdates.length + ${groups.length + notInstallableUpdates.length ? nothing : html` @@ -211,6 +250,13 @@ class HaConfigSectionUpdates extends LitElement { checkForEntityUpdates(this, this.hass); } + private _updateAll(ev: Event) { + const group = (ev.currentTarget as any).group as UpdateGroup; + this.hass.callService("update", "install", { + entity_id: group.entities.map((entity) => entity.entity_id), + }); + } + private _filterInstallableUpdateEntities = memoizeOne( (entities: HassEntities, showSkipped: boolean) => filterUpdateEntitiesParameterized(entities, showSkipped, false) @@ -221,6 +267,106 @@ class HaConfigSectionUpdates extends LitElement { filterUpdateEntitiesParameterized(entities, showSkipped, true) ); + private _groupUpdates = memoizeOne( + ( + entities: UpdateEntity[], + entitySources: EntitySources | undefined + ): UpdateGroup[] => { + if (!entities.length) { + return []; + } + + const localize = this.hass.localize; + + const systemEntities: UpdateEntity[] = []; + const appEntities: UpdateEntity[] = []; + const byDomain = new Map(); + const otherIntegrationEntities: UpdateEntity[] = []; + + for (const entity of entities) { + const title = entity.attributes.title || ""; + if ( + title === "Home Assistant Core" || + title === "Home Assistant Operating System" || + title === "Home Assistant Supervisor" + ) { + systemEntities.push(entity); + continue; + } + const sourceDomain = entitySources?.[entity.entity_id]?.domain; + const domain = + sourceDomain ?? this.hass.entities[entity.entity_id]?.platform; + if (domain === "hassio") { + appEntities.push(entity); + continue; + } + if (!domain) { + otherIntegrationEntities.push(entity); + continue; + } + if (!byDomain.has(domain)) { + byDomain.set(domain, []); + } + byDomain.get(domain)!.push(entity); + } + + const multiInstanceGroups: UpdateGroup[] = []; + byDomain.forEach((entries, domain) => { + if (entries.length >= 2) { + multiInstanceGroups.push({ + key: domain, + title: domainToName(localize, domain), + entities: entries, + showUpdateAll: true, + }); + } else { + otherIntegrationEntities.push(...entries); + } + }); + + multiInstanceGroups.sort((a, b) => + caseInsensitiveStringCompare( + a.title, + b.title, + this.hass.locale.language + ) + ); + + const groups: UpdateGroup[] = []; + + if (systemEntities.length) { + groups.push({ + key: SYSTEM_KEY, + title: localize("ui.panel.config.updates.group_system"), + entities: systemEntities, + showUpdateAll: false, + }); + } + + groups.push(...multiInstanceGroups); + + if (otherIntegrationEntities.length) { + groups.push({ + key: INTEGRATIONS_KEY, + title: localize("ui.panel.config.updates.group_integrations"), + entities: otherIntegrationEntities, + showUpdateAll: true, + }); + } + + if (appEntities.length) { + groups.push({ + key: APPS_KEY, + title: localize("ui.panel.config.updates.group_apps"), + entities: appEntities, + showUpdateAll: true, + }); + } + + return groups; + } + ); + static styles = css` .content { padding: 28px 20px 0; @@ -247,11 +393,33 @@ class HaConfigSectionUpdates extends LitElement { padding: 0; } - .title { + .card-header { + display: flex; + align-items: center; + justify-content: space-between; + gap: var(--ha-space-2); padding: var(--ha-space-4) var(--ha-space-4) 0; + } + + .title { font-size: var(--ha-font-size-l); } + .update-all { + background: none; + border: none; + color: var(--primary-color); + cursor: pointer; + font: inherit; + font-size: var(--ha-font-size-m); + padding: var(--ha-space-1) var(--ha-space-2); + } + .update-all:focus-visible { + outline: 2px solid var(--primary-color); + outline-offset: 2px; + border-radius: 4px; + } + .no-updates { padding: 16px; } diff --git a/src/translations/en.json b/src/translations/en.json index eafe0bcab1b3..9d11d974bb10 100644 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -2744,6 +2744,10 @@ "updates": { "caption": "Updates", "description": "Manage updates of Home Assistant, apps, and devices", + "group_system": "Home Assistant", + "group_integrations": "Integrations", + "group_apps": "Apps", + "update_all": "Update all", "no_updates": "No updates available", "no_update_entities": { "title": "Unable to check for updates", From aacb8e3c099e1be4d1889fd6a2afae41878a1726 Mon Sep 17 00:00:00 2001 From: Petar Petrov Date: Tue, 26 May 2026 16:44:07 +0300 Subject: [PATCH 2/8] Use ha-button for Update all and localize integration names --- .../config/core/ha-config-section-updates.ts | 48 +++++++++++-------- 1 file changed, 29 insertions(+), 19 deletions(-) diff --git a/src/panels/config/core/ha-config-section-updates.ts b/src/panels/config/core/ha-config-section-updates.ts index 7effbeeee19e..eaec8bc6483b 100644 --- a/src/panels/config/core/ha-config-section-updates.ts +++ b/src/panels/config/core/ha-config-section-updates.ts @@ -12,6 +12,7 @@ import { customElement, property, state } from "lit/decorators"; import memoizeOne from "memoize-one"; import { isComponentLoaded } from "../../../common/config/is_component_loaded"; import { caseInsensitiveStringCompare } from "../../../common/string/compare"; +import "../../../components/ha-button"; import "../../../components/ha-card"; import "../../../components/ha-dropdown"; import type { HaDropdownSelectEvent } from "../../../components/ha-dropdown"; @@ -65,6 +66,8 @@ class HaConfigSectionUpdates extends LitElement { @state() private _entitySources?: EntitySources; + @state() private _loadedIntegrationTitles = new Set(); + protected firstUpdated(changedProps: PropertyValues) { super.firstUpdated(changedProps); @@ -77,6 +80,27 @@ class HaConfigSectionUpdates extends LitElement { }); } + protected updated(changedProps: PropertyValues) { + super.updated(changedProps); + this._loadIntegrationTitles(); + } + + private async _loadIntegrationTitles() { + const domains = new Set(); + for (const entity of Object.values(this.hass.states)) { + if (!entity.entity_id.startsWith("update.")) continue; + const platform = this.hass.entities[entity.entity_id]?.platform; + if (platform && !this._loadedIntegrationTitles.has(platform)) { + domains.add(platform); + } + } + if (!domains.size) return; + const toLoad = Array.from(domains); + toLoad.forEach((d) => this._loadedIntegrationTitles.add(d)); + await this.hass.loadBackendTranslation("title", toLoad); + this.requestUpdate(); + } + protected render(): TemplateResult { const installableUpdates = this._filterInstallableUpdateEntities( this.hass.states, @@ -152,15 +176,16 @@ class HaConfigSectionUpdates extends LitElement {
${group.showUpdateAll ? html` - + ` : nothing}
@@ -398,28 +423,13 @@ class HaConfigSectionUpdates extends LitElement { align-items: center; justify-content: space-between; gap: var(--ha-space-2); - padding: var(--ha-space-4) var(--ha-space-4) 0; + padding: var(--ha-space-4) var(--ha-space-2) 0 var(--ha-space-4); } .title { font-size: var(--ha-font-size-l); } - .update-all { - background: none; - border: none; - color: var(--primary-color); - cursor: pointer; - font: inherit; - font-size: var(--ha-font-size-m); - padding: var(--ha-space-1) var(--ha-space-2); - } - .update-all:focus-visible { - outline: 2px solid var(--primary-color); - outline-offset: 2px; - border-radius: 4px; - } - .no-updates { padding: 16px; } From 109d21aa817a1d5b083b72c3fe79147618f58925 Mon Sep 17 00:00:00 2001 From: Petar Petrov Date: Tue, 26 May 2026 16:46:43 +0300 Subject: [PATCH 3/8] Surface errors when Update all fails --- .../config/core/ha-config-section-updates.ts | 16 ++++++++++++---- src/translations/en.json | 1 + 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/src/panels/config/core/ha-config-section-updates.ts b/src/panels/config/core/ha-config-section-updates.ts index eaec8bc6483b..58c0349d9e84 100644 --- a/src/panels/config/core/ha-config-section-updates.ts +++ b/src/panels/config/core/ha-config-section-updates.ts @@ -275,11 +275,19 @@ class HaConfigSectionUpdates extends LitElement { checkForEntityUpdates(this, this.hass); } - private _updateAll(ev: Event) { + private async _updateAll(ev: Event) { const group = (ev.currentTarget as any).group as UpdateGroup; - this.hass.callService("update", "install", { - entity_id: group.entities.map((entity) => entity.entity_id), - }); + try { + await this.hass.callService("update", "install", { + entity_id: group.entities.map((entity) => entity.entity_id), + }); + } catch (err: any) { + showAlertDialog(this, { + title: this.hass.localize("ui.panel.config.updates.update_all_failed"), + text: err.message, + warning: true, + }); + } } private _filterInstallableUpdateEntities = memoizeOne( diff --git a/src/translations/en.json b/src/translations/en.json index 9d11d974bb10..e18f771e4cee 100644 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -2748,6 +2748,7 @@ "group_integrations": "Integrations", "group_apps": "Apps", "update_all": "Update all", + "update_all_failed": "Failed to start updates", "no_updates": "No updates available", "no_update_entities": { "title": "Unable to check for updates", From 9e56fd379e8bbfebb2cc56c7a43d7870fdb06fb8 Mon Sep 17 00:00:00 2001 From: Petar Petrov Date: Tue, 26 May 2026 16:52:39 +0300 Subject: [PATCH 4/8] Use getUpdateType for system/addon classification --- .../config/core/ha-config-section-updates.ts | 27 +++++++++---------- 1 file changed, 12 insertions(+), 15 deletions(-) diff --git a/src/panels/config/core/ha-config-section-updates.ts b/src/panels/config/core/ha-config-section-updates.ts index 58c0349d9e84..7b962750bb59 100644 --- a/src/panels/config/core/ha-config-section-updates.ts +++ b/src/panels/config/core/ha-config-section-updates.ts @@ -34,6 +34,7 @@ import type { UpdateEntity } from "../../../data/update"; import { checkForEntityUpdates, filterUpdateEntitiesParameterized, + getUpdateType, } from "../../../data/update"; import { showAlertDialog } from "../../../dialogs/generic/show-dialog-box"; import "../../../layouts/hass-subpage"; @@ -317,22 +318,18 @@ class HaConfigSectionUpdates extends LitElement { const otherIntegrationEntities: UpdateEntity[] = []; for (const entity of entities) { - const title = entity.attributes.title || ""; - if ( - title === "Home Assistant Core" || - title === "Home Assistant Operating System" || - title === "Home Assistant Supervisor" - ) { - systemEntities.push(entity); - continue; - } - const sourceDomain = entitySources?.[entity.entity_id]?.domain; - const domain = - sourceDomain ?? this.hass.entities[entity.entity_id]?.platform; - if (domain === "hassio") { - appEntities.push(entity); - continue; + if (entitySources) { + const type = getUpdateType(entity, entitySources); + if (type === "home_assistant" || type === "home_assistant_os") { + systemEntities.push(entity); + continue; + } + if (type === "addon") { + appEntities.push(entity); + continue; + } } + const domain = this.hass.entities[entity.entity_id]?.platform; if (!domain) { otherIntegrationEntities.push(entity); continue; From 5a2e4b0da5c507c96737f50173dec369cec90b95 Mon Sep 17 00:00:00 2001 From: Petar Petrov Date: Wed, 27 May 2026 11:34:04 +0300 Subject: [PATCH 5/8] Address review: loading state, try/catch sources, install helper --- src/data/update.ts | 5 +++ .../config/core/ha-config-section-updates.ts | 36 +++++++++++++++---- 2 files changed, 35 insertions(+), 6 deletions(-) diff --git a/src/data/update.ts b/src/data/update.ts index a358e37dcd6b..3aed146b7842 100644 --- a/src/data/update.ts +++ b/src/data/update.ts @@ -133,6 +133,11 @@ export const filterUpdateEntitiesParameterized = ( return updateCanInstall(entity, showSkipped); }); +export const installUpdates = (hass: HomeAssistant, entityIds: string[]) => + hass.callService("update", "install", { + entity_id: entityIds, + }); + export const checkForEntityUpdates = async ( element: HTMLElement, hass: HomeAssistant diff --git a/src/panels/config/core/ha-config-section-updates.ts b/src/panels/config/core/ha-config-section-updates.ts index 7b962750bb59..921211523005 100644 --- a/src/panels/config/core/ha-config-section-updates.ts +++ b/src/panels/config/core/ha-config-section-updates.ts @@ -35,6 +35,8 @@ import { checkForEntityUpdates, filterUpdateEntitiesParameterized, getUpdateType, + installUpdates, + updateIsInstalling, } from "../../../data/update"; import { showAlertDialog } from "../../../dialogs/generic/show-dialog-box"; import "../../../layouts/hass-subpage"; @@ -69,6 +71,8 @@ class HaConfigSectionUpdates extends LitElement { @state() private _loadedIntegrationTitles = new Set(); + @state() private _loadingGroups = new Set(); + protected firstUpdated(changedProps: PropertyValues) { super.firstUpdated(changedProps); @@ -76,9 +80,15 @@ class HaConfigSectionUpdates extends LitElement { this._refreshSupervisorInfo(); } - fetchEntitySourcesWithCache(this.hass).then((sources) => { - this._entitySources = sources; - }); + this._loadEntitySources(); + } + + private async _loadEntitySources() { + try { + this._entitySources = await fetchEntitySourcesWithCache(this.hass); + } catch (_err) { + // Non-fatal: grouping falls back to entity registry platform lookup. + } } protected updated(changedProps: PropertyValues) { @@ -180,6 +190,7 @@ class HaConfigSectionUpdates extends LitElement { @@ -276,18 +287,31 @@ class HaConfigSectionUpdates extends LitElement { checkForEntityUpdates(this, this.hass); } + private _isGroupLoading(group: UpdateGroup): boolean { + return ( + this._loadingGroups.has(group.key) || + group.entities.every(updateIsInstalling) + ); + } + private async _updateAll(ev: Event) { const group = (ev.currentTarget as any).group as UpdateGroup; + this._loadingGroups = new Set(this._loadingGroups).add(group.key); try { - await this.hass.callService("update", "install", { - entity_id: group.entities.map((entity) => entity.entity_id), - }); + await installUpdates( + this.hass, + group.entities.map((entity) => entity.entity_id) + ); } catch (err: any) { showAlertDialog(this, { title: this.hass.localize("ui.panel.config.updates.update_all_failed"), text: err.message, warning: true, }); + } finally { + const next = new Set(this._loadingGroups); + next.delete(group.key); + this._loadingGroups = next; } } From ac120ad309139fe7dcad026ff7e23416132cb851 Mon Sep 17 00:00:00 2001 From: Petar Petrov Date: Thu, 28 May 2026 08:53:56 +0300 Subject: [PATCH 6/8] Remove loading state from Update all button --- .../config/core/ha-config-section-updates.ts | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/src/panels/config/core/ha-config-section-updates.ts b/src/panels/config/core/ha-config-section-updates.ts index 921211523005..d21e16a3a0ec 100644 --- a/src/panels/config/core/ha-config-section-updates.ts +++ b/src/panels/config/core/ha-config-section-updates.ts @@ -36,7 +36,6 @@ import { filterUpdateEntitiesParameterized, getUpdateType, installUpdates, - updateIsInstalling, } from "../../../data/update"; import { showAlertDialog } from "../../../dialogs/generic/show-dialog-box"; import "../../../layouts/hass-subpage"; @@ -71,8 +70,6 @@ class HaConfigSectionUpdates extends LitElement { @state() private _loadedIntegrationTitles = new Set(); - @state() private _loadingGroups = new Set(); - protected firstUpdated(changedProps: PropertyValues) { super.firstUpdated(changedProps); @@ -190,7 +187,6 @@ class HaConfigSectionUpdates extends LitElement { @@ -287,16 +283,8 @@ class HaConfigSectionUpdates extends LitElement { checkForEntityUpdates(this, this.hass); } - private _isGroupLoading(group: UpdateGroup): boolean { - return ( - this._loadingGroups.has(group.key) || - group.entities.every(updateIsInstalling) - ); - } - private async _updateAll(ev: Event) { const group = (ev.currentTarget as any).group as UpdateGroup; - this._loadingGroups = new Set(this._loadingGroups).add(group.key); try { await installUpdates( this.hass, @@ -308,10 +296,6 @@ class HaConfigSectionUpdates extends LitElement { text: err.message, warning: true, }); - } finally { - const next = new Set(this._loadingGroups); - next.delete(group.key); - this._loadingGroups = next; } } From 479b25532b1ec610fe6fbc0364b137e490d1dbbd Mon Sep 17 00:00:00 2001 From: Petar Petrov Date: Thu, 28 May 2026 09:01:43 +0300 Subject: [PATCH 7/8] Add isSystemUpdate helper to classify HA Core/OS/Supervisor reliably --- src/data/update.ts | 13 +++++++++++ .../config/core/ha-config-section-updates.ts | 23 +++++++++---------- 2 files changed, 24 insertions(+), 12 deletions(-) diff --git a/src/data/update.ts b/src/data/update.ts index 3aed146b7842..baeccea8cb06 100644 --- a/src/data/update.ts +++ b/src/data/update.ts @@ -87,6 +87,19 @@ const HOME_ASSISTANT_CORE_TITLE = "Home Assistant Core"; const HOME_ASSISTANT_SUPERVISOR_TITLE = "Home Assistant Supervisor"; const HOME_ASSISTANT_OS_TITLE = "Home Assistant Operating System"; +// The hassio integration sets these as hard-coded `_attr_title` on the Core, +// Operating System, and Supervisor update entities. They are not translated, +// so a title comparison is the reliable way to identify them without depending +// on the (lazily-fetched) entity sources. +export const isSystemUpdate = (entity: UpdateEntity): boolean => { + const title = entity.attributes.title || ""; + return ( + title === HOME_ASSISTANT_CORE_TITLE || + title === HOME_ASSISTANT_OS_TITLE || + title === HOME_ASSISTANT_SUPERVISOR_TITLE + ); +}; + export const filterUpdateEntities = ( entities: HassEntities, language?: string diff --git a/src/panels/config/core/ha-config-section-updates.ts b/src/panels/config/core/ha-config-section-updates.ts index d21e16a3a0ec..3290b7511929 100644 --- a/src/panels/config/core/ha-config-section-updates.ts +++ b/src/panels/config/core/ha-config-section-updates.ts @@ -34,8 +34,8 @@ import type { UpdateEntity } from "../../../data/update"; import { checkForEntityUpdates, filterUpdateEntitiesParameterized, - getUpdateType, installUpdates, + isSystemUpdate, } from "../../../data/update"; import { showAlertDialog } from "../../../dialogs/generic/show-dialog-box"; import "../../../layouts/hass-subpage"; @@ -326,18 +326,17 @@ class HaConfigSectionUpdates extends LitElement { const otherIntegrationEntities: UpdateEntity[] = []; for (const entity of entities) { - if (entitySources) { - const type = getUpdateType(entity, entitySources); - if (type === "home_assistant" || type === "home_assistant_os") { - systemEntities.push(entity); - continue; - } - if (type === "addon") { - appEntities.push(entity); - continue; - } + if (isSystemUpdate(entity)) { + systemEntities.push(entity); + continue; + } + const domain = + entitySources?.[entity.entity_id]?.domain ?? + this.hass.entities[entity.entity_id]?.platform; + if (domain === "hassio") { + appEntities.push(entity); + continue; } - const domain = this.hass.entities[entity.entity_id]?.platform; if (!domain) { otherIntegrationEntities.push(entity); continue; From 8894c357ecfbb46cd0f4ef9bd554ffcb6d649f9c Mon Sep 17 00:00:00 2001 From: Petar Petrov Date: Thu, 28 May 2026 09:02:29 +0300 Subject: [PATCH 8/8] Use extractApiErrorMessage in Update all error handler --- src/panels/config/core/ha-config-section-updates.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/panels/config/core/ha-config-section-updates.ts b/src/panels/config/core/ha-config-section-updates.ts index 3290b7511929..4229ae6e6f82 100644 --- a/src/panels/config/core/ha-config-section-updates.ts +++ b/src/panels/config/core/ha-config-section-updates.ts @@ -293,7 +293,7 @@ class HaConfigSectionUpdates extends LitElement { } catch (err: any) { showAlertDialog(this, { title: this.hass.localize("ui.panel.config.updates.update_all_failed"), - text: err.message, + text: extractApiErrorMessage(err), warning: true, }); }