Compare commits

..

1 Commits

Author SHA1 Message Date
copilot-swe-agent[bot] 32939406fa Update apps info page layout 2026-06-16 15:55:12 +00:00
10 changed files with 73 additions and 140 deletions
@@ -16,12 +16,14 @@ interface CacheResult<T> {
* @param args extra arguments to pass to the function to fetch the data
* @returns
*/
export const timeCachePromiseFunc = async <T, H = HomeAssistant>(
export const timeCachePromiseFunc = async <T>(
cacheKey: string,
cacheTime: number,
func: (hass: H, ...args: any[]) => Promise<T>,
generateCacheKey: ((hass: H, lastResult: T) => unknown) | undefined,
hass: H,
func: (hass: HomeAssistant, ...args: any[]) => Promise<T>,
generateCacheKey:
| ((hass: HomeAssistant, lastResult: T) => unknown)
| undefined,
hass: HomeAssistant,
...args: any[]
): Promise<T> => {
const anyHass = hass as any;
+1 -1
View File
@@ -181,7 +181,7 @@ export interface RestoreBackupParams {
restore_homeassistant?: boolean;
}
export const fetchBackupConfig = (hass: Pick<HomeAssistant, "callWS">) =>
export const fetchBackupConfig = (hass: HomeAssistant) =>
hass.callWS<{ config: BackupConfig }>({ type: "backup/config/info" });
export const updateBackupConfig = (
+3 -4
View File
@@ -7,12 +7,11 @@ interface EntitySource {
export type EntitySources = Record<string, EntitySource>;
const fetchEntitySources = (
hass: Pick<HomeAssistant, "callWS">
): Promise<EntitySources> => hass.callWS({ type: "entity/source" });
const fetchEntitySources = (hass: HomeAssistant): Promise<EntitySources> =>
hass.callWS({ type: "entity/source" });
export const fetchEntitySourcesWithCache = (
hass: Pick<HomeAssistant, "callWS" | "states">
hass: HomeAssistant
): Promise<EntitySources> =>
timeCachePromiseFunc(
"_entitySources",
+1 -3
View File
@@ -6,9 +6,7 @@ export interface SupervisorUpdateConfig {
core_backup_before_update: boolean;
}
export const getSupervisorUpdateConfig = async (
hass: Pick<HomeAssistant, "callWS">
) =>
export const getSupervisorUpdateConfig = async (hass: HomeAssistant) =>
hass.callWS<SupervisorUpdateConfig>({
type: "hassio/update/config/info",
});
+1 -4
View File
@@ -77,10 +77,7 @@ export const updateButtonIsDisabled = (entity: UpdateEntity): boolean =>
export const updateIsInstalling = (entity: UpdateEntity): boolean =>
!!entity.attributes.in_progress;
export const updateReleaseNotes = (
hass: Pick<HomeAssistant, "callWS">,
entityId: string
) =>
export const updateReleaseNotes = (hass: HomeAssistant, entityId: string) =>
hass.callWS<string | null>({
type: "update/release_notes",
entity_id: entityId,
@@ -1,14 +1,9 @@
import { consume } from "@lit/context";
import type { HassConfig } from "home-assistant-js-websocket";
import { css, html, LitElement, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import { isComponentLoaded } from "../../../common/config/is_component_loaded";
import { BINARY_STATE_OFF } from "../../../common/const";
import { relativeTime } from "../../../common/datetime/relative_time";
import { consumeLocalize } from "../../../common/decorators/consume-context-entry";
import { transform } from "../../../common/decorators/transform";
import { supportsFeature } from "../../../common/entity/supports-feature";
import type { LocalizeFunc } from "../../../common/translations/localize";
import "../../../components/buttons/ha-progress-button";
import "../../../components/ha-alert";
import "../../../components/ha-button";
@@ -20,18 +15,10 @@ import "../../../components/item/ha-row-item";
import "../../../components/progress/ha-progress-bar";
import type { BackupConfig } from "../../../data/backup";
import { fetchBackupConfig } from "../../../data/backup";
import {
apiContext,
configContext,
formattersContext,
internationalizationContext,
statesContext,
} from "../../../data/context";
import { UNAVAILABLE, UNKNOWN } from "../../../data/entity/entity";
import type { EntitySources } from "../../../data/entity/entity_sources";
import { fetchEntitySourcesWithCache } from "../../../data/entity/entity_sources";
import { getSupervisorUpdateConfig } from "../../../data/supervisor/update";
import type { FrontendLocaleData } from "../../../data/translation";
import type { UpdateEntity, UpdateType } from "../../../data/update";
import {
getUpdateType,
@@ -41,49 +28,15 @@ import {
updateIsInstalling,
updateReleaseNotes,
} from "../../../data/update";
import type {
HomeAssistant,
HomeAssistantApi,
HomeAssistantConfig,
HomeAssistantFormatters,
HomeAssistantInternationalization,
} from "../../../types";
import type { HomeAssistant } from "../../../types";
import { showAlertDialog } from "../../generic/show-dialog-box";
@customElement("more-info-update")
class MoreInfoUpdate extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property({ attribute: false }) public stateObj?: UpdateEntity;
@state()
@consumeLocalize()
private _localize!: LocalizeFunc;
@state()
@consume({ context: internationalizationContext, subscribe: true })
@transform<HomeAssistantInternationalization, FrontendLocaleData>({
transformer: ({ locale }) => locale,
})
private _locale!: FrontendLocaleData;
@state()
@consume({ context: formattersContext, subscribe: true })
private _formatters!: HomeAssistantFormatters;
@state()
@consume({ context: apiContext, subscribe: true })
private _api!: HomeAssistantApi;
@state()
@consume({ context: statesContext, subscribe: true })
private _states!: HomeAssistant["states"];
@state()
@consume({ context: configContext, subscribe: true })
@transform<HomeAssistantConfig, HassConfig>({
transformer: ({ config }) => config,
})
private _config!: HassConfig;
@state() private _releaseNotes?: string | null;
@state() private _error?: string;
@@ -98,7 +51,7 @@ class MoreInfoUpdate extends LitElement {
private async _fetchBackupConfig() {
try {
const { config } = await fetchBackupConfig(this._api);
const { config } = await fetchBackupConfig(this.hass);
this._backupConfig = config;
} catch (err) {
// ignore error, because user will get a manual backup option
@@ -109,7 +62,7 @@ class MoreInfoUpdate extends LitElement {
private async _fetchUpdateBackupConfig(type: UpdateType) {
try {
const config = await getSupervisorUpdateConfig(this._api);
const config = await getSupervisorUpdateConfig(this.hass);
// for home assistant and OS updates
if (this._isHaOrOsUpdate(type)) {
@@ -128,10 +81,7 @@ class MoreInfoUpdate extends LitElement {
}
private async _fetchEntitySources() {
this._entitySources = await fetchEntitySourcesWithCache({
callWS: this._api.callWS,
states: this._states,
});
this._entitySources = await fetchEntitySourcesWithCache(this.hass);
}
private _isHaOrOsUpdate(type: UpdateType): boolean {
@@ -161,10 +111,10 @@ class MoreInfoUpdate extends LitElement {
if (!isBackupConfigValid) {
return {
title: this._localize(
title: this.hass.localize(
"ui.dialogs.more_info_control.update.create_backup.manual"
),
description: this._localize(
description: this.hass.localize(
"ui.dialogs.more_info_control.update.create_backup.manual_description"
),
};
@@ -177,22 +127,22 @@ class MoreInfoUpdate extends LitElement {
const now = new Date();
return {
title: this._localize(
title: this.hass.localize(
"ui.dialogs.more_info_control.update.create_backup.automatic"
),
description: lastAutomaticBackupDate
? this._localize(
? this.hass.localize(
"ui.dialogs.more_info_control.update.create_backup.automatic_description_last",
{
relative_time: relativeTime(
lastAutomaticBackupDate,
this._locale,
this.hass.locale,
now,
true
),
}
)
: this._localize(
: this.hass.localize(
"ui.dialogs.more_info_control.update.create_backup.automatic_description_none"
),
};
@@ -202,11 +152,11 @@ class MoreInfoUpdate extends LitElement {
if (updateType === "addon") {
const version = this.stateObj.attributes.installed_version;
return {
title: this._localize(
title: this.hass.localize(
"ui.dialogs.more_info_control.update.create_backup.app"
),
description: version
? this._localize(
? this.hass.localize(
"ui.dialogs.more_info_control.update.create_backup.app_description",
{ version: version }
)
@@ -216,7 +166,7 @@ class MoreInfoUpdate extends LitElement {
// Fallback to generic UI
return {
title: this._localize(
title: this.hass.localize(
"ui.dialogs.more_info_control.update.create_backup.generic"
),
};
@@ -224,7 +174,7 @@ class MoreInfoUpdate extends LitElement {
protected render() {
if (
!this._localize ||
!this.hass ||
!this.stateObj ||
this.stateObj.state === UNAVAILABLE ||
this.stateObj.state === UNKNOWN
@@ -252,26 +202,26 @@ class MoreInfoUpdate extends LitElement {
: nothing}
<div class="row">
<div class="key">
${this._formatters.formatEntityAttributeName(
${this.hass.formatEntityAttributeName(
this.stateObj,
"installed_version"
)}
</div>
<div class="value">
${this.stateObj.attributes.installed_version ??
this._localize("state.default.unavailable")}
this.hass.localize("state.default.unavailable")}
</div>
</div>
<div class="row">
<div class="key">
${this._formatters.formatEntityAttributeName(
${this.hass.formatEntityAttributeName(
this.stateObj,
"latest_version"
)}
</div>
<div class="value">
${this.stateObj.attributes.latest_version ??
this._localize("state.default.unavailable")}
this.hass.localize("state.default.unavailable")}
</div>
</div>
@@ -283,7 +233,7 @@ class MoreInfoUpdate extends LitElement {
target="_blank"
rel="noreferrer"
>
${this._localize(
${this.hass.localize(
"ui.dialogs.more_info_control.update.release_announcement"
)}
</a>
@@ -350,7 +300,7 @@ class MoreInfoUpdate extends LitElement {
appearance="plain"
@click=${this._handleClearSkipped}
>
${this._localize(
${this.hass.localize(
"ui.dialogs.more_info_control.update.clear_skipped"
)}
</ha-button>
@@ -363,7 +313,9 @@ class MoreInfoUpdate extends LitElement {
this.stateObj.state === BINARY_STATE_OFF ||
updateIsInstalling(this.stateObj)}
>
${this._localize("ui.dialogs.more_info_control.update.skip")}
${this.hass.localize(
"ui.dialogs.more_info_control.update.skip"
)}
</ha-button>
`}
${supportsFeature(this.stateObj, UpdateEntityFeature.INSTALL)
@@ -373,7 +325,7 @@ class MoreInfoUpdate extends LitElement {
.loading=${updateIsInstalling(this.stateObj)}
.disabled=${updateButtonIsDisabled(this.stateObj)}
>
${this._localize(
${this.hass.localize(
"ui.dialogs.more_info_control.update.update"
)}
</ha-button>
@@ -400,7 +352,7 @@ class MoreInfoUpdate extends LitElement {
this._fetchEntitySources().then(() => {
const type = getUpdateType(this.stateObj!, this._entitySources!);
if (
isComponentLoaded(this._config, "hassio") &&
isComponentLoaded(this.hass.config, "hassio") &&
["addon", "home_assistant", "home_assistant_os"].includes(type)
) {
this._fetchUpdateBackupConfig(type);
@@ -422,7 +374,7 @@ class MoreInfoUpdate extends LitElement {
private async _fetchReleaseNotes() {
try {
this._releaseNotes = await updateReleaseNotes(
this._api,
this.hass,
this.stateObj!.entity_id
);
} catch (err: any) {
@@ -453,7 +405,7 @@ class MoreInfoUpdate extends LitElement {
installData.version = this.stateObj!.attributes.latest_version;
}
this._api.callService("update", "install", installData);
this.hass.callService("update", "install", installData);
}
private _createBackupChanged(ev) {
@@ -463,22 +415,22 @@ class MoreInfoUpdate extends LitElement {
private _handleSkip(): void {
if (this.stateObj!.attributes.auto_update) {
showAlertDialog(this, {
title: this._localize(
title: this.hass.localize(
"ui.dialogs.more_info_control.update.auto_update_enabled_title"
),
text: this._localize(
text: this.hass.localize(
"ui.dialogs.more_info_control.update.auto_update_enabled_text"
),
});
return;
}
this._api.callService("update", "skip", {
this.hass.callService("update", "skip", {
entity_id: this.stateObj!.entity_id,
});
}
private _handleClearSkipped(): void {
this._api.callService("update", "clear_skipped", {
this.hass.callService("update", "clear_skipped", {
entity_id: this.stateObj!.entity_id,
});
}
+1 -1
View File
@@ -170,7 +170,7 @@ class DialogRestart extends LitElement {
</ha-list-base>
<ha-expansion-panel
.header=${this.hass.localize(
"ui.dialogs.restart.more_options"
"ui.dialogs.restart.advanced_options"
)}
>
<ha-list-base>
+21 -37
View File
@@ -33,29 +33,19 @@ class HaConfigAppsInfo extends LitElement {
<div class="content">
<ha-card outlined>
<div class="card-content">
<div class="header">
<div class="icon-container">
<ha-svg-icon class="icon" .path=${mdiPuzzle}></ha-svg-icon>
<h1>
${this.hass.localize(
"ui.panel.config.apps.info.what_is_an_app"
)}
</h1>
</div>
<h1>
${this.hass.localize(
"ui.panel.config.apps.info.apps_unavailable"
)}
</h1>
<p>
${this.hass.localize(
"ui.panel.config.apps.info.what_is_an_app_description"
)}
</p>
</div>
</ha-card>
<ha-card outlined>
<div class="card-content">
<h2>
${this.hass.localize(
"ui.panel.config.apps.info.why_not_available"
)}
</h2>
<p>
${this.hass.localize(
"ui.panel.config.apps.info.why_not_available_description"
@@ -107,42 +97,36 @@ class HaConfigAppsInfo extends LitElement {
.content {
max-width: 600px;
margin: 0 auto;
padding: var(--ha-space-4);
display: flex;
flex-direction: column;
gap: var(--ha-space-4);
padding: var(--ha-space-8) var(--ha-space-4);
}
.card-content {
padding: var(--ha-space-4);
padding: var(--ha-space-8) var(--ha-space-8) var(--ha-space-6);
}
.header {
.icon-container {
width: 64px;
height: 64px;
border-radius: 50%;
display: flex;
align-items: center;
gap: var(--ha-space-3);
margin-bottom: var(--ha-space-4);
justify-content: center;
margin-bottom: var(--ha-space-6);
background: var(--primary-color);
}
.icon {
width: 40px;
height: 40px;
flex-shrink: 0;
color: var(--primary-color);
width: 36px;
height: 36px;
color: var(--text-primary-color);
}
h1 {
margin: 0;
margin: 0 0 var(--ha-space-4);
font-size: var(--ha-font-size-xl);
font-weight: 500;
}
h2 {
margin: 0 0 var(--ha-space-3);
font-size: var(--ha-font-size-l);
font-weight: 500;
}
p {
margin: 0 0 var(--ha-space-3);
line-height: var(--ha-line-height-normal);
@@ -151,7 +135,7 @@ class HaConfigAppsInfo extends LitElement {
ha-alert {
display: block;
margin-top: var(--ha-space-2);
margin-top: var(--ha-space-4);
}
.card-actions {
@@ -160,7 +144,7 @@ class HaConfigAppsInfo extends LitElement {
align-items: center;
flex-wrap: wrap;
gap: var(--ha-space-2);
padding: var(--ha-space-2);
padding: var(--ha-space-2) var(--ha-space-4);
border-top: var(--ha-border-width-sm) solid var(--divider-color);
}
@@ -313,7 +313,7 @@ class HaBackupConfigData extends LitElement {
</span>
<span slot="supporting-text">
${this.hass.localize(
"ui.panel.config.backup.data.share_folder_desc"
"ui.panel.config.backup.data.share_folder_description"
)}
</span>
<ha-switch
+5 -4
View File
@@ -2066,7 +2066,7 @@
},
"restart": {
"heading": "Restart Home Assistant",
"more_options": "More options",
"advanced_options": "Advanced options",
"backup_in_progress": "A backup is currently being created. The action will automatically proceed once the backup process is complete.",
"upload_in_progress": "A backup upload is currently in progress. The action will automatically proceed once the upload process is complete.",
"restore_in_progress": "A backup restore is currently in progress. The action will automatically proceed once the restore process is complete.",
@@ -2683,7 +2683,7 @@
},
"developer_tools": {
"main": "Developer tools",
"secondary": "Tools to inspect and debug your system"
"secondary": "Advanced tools for inspecting and debugging your system"
},
"about": {
"main": "About",
@@ -2834,10 +2834,11 @@
"description": "Install and manage apps",
"error_loading": "Error loading apps",
"info": {
"apps_unavailable": "Apps are not available",
"what_is_an_app": "What is an app?",
"what_is_an_app_description": "Apps allow you to extend the functionality around Home Assistant by installing additional applications. They run alongside Home Assistant and provide extra features like network monitoring, code editors, database management, and more.",
"why_not_available": "Why you see this page instead of an app store",
"why_not_available_description": "Apps are only available if you've used the Home Assistant Operating System installation method. If you installed Home Assistant using any other method then you cannot use apps. Often you can achieve the same result manually, refer to the documentation by the vendor of the application you'd like to install.",
"why_not_available_description": "Apps are only available with the Home Assistant Operating System installation method. If you installed Home Assistant using another method, you cannot use apps. You can often achieve the same result manually by following the documentation from the application vendor.",
"installation_hint": "Apps require the Home Assistant Operating System. Learn more about installation methods in the documentation.",
"learn_more": "Learn more",
"dismiss": "Don't show again"
@@ -3437,7 +3438,7 @@
"media": "Media",
"media_description": "For example, camera recordings.",
"share_folder": "Share folder",
"share_folder_desc": "Folder that is often used by apps for custom or older configurations.",
"share_folder_description": "Folder that is often used by apps for advanced or older configurations.",
"local_apps": "Local apps folder",
"local_apps_description": "Folder that contains the data of your local apps.",
"apps": "Apps",