Correct Supervisor API types in the frontend (#54284)

Compare the hassio and supervisor data modules with the Supervisor
handlers that build the responses, and fix the mismatches.

Add fields the API returns that were missing, including system_managed,
boot_config, dns, host_uts, ingress_port, udev, usb and video. Correct
two names that never matched the wire: services_role is services, and
auto_uart is uart. Drop installed, which belongs to the store model
rather than the app info payload, and drop repositories from the app
list, which the list handler does not return.

Mark the many fields the Supervisor can return as null, and replace the
placeholder issue and suggestion string arrays in the resolution types
with the objects the API sends.


Claude-Session: https://claude.ai/code/session_01YDqKNfAAvMV7zJJNodsHfK

Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
Paulus Schoutsen
2026-09-21 18:13:19 +02:00
committed by GitHub
co-authored by Claude
parent d0220b6a72
commit 1ac4382dc5
20 changed files with 174 additions and 103 deletions
+1
View File
@@ -13,6 +13,7 @@ const HARDWARE_INFO: HardwareInfo = {
hassio_board_id: "green",
manufacturer: "homeassistant",
model: "green",
revision: null,
},
dongle: null,
config_entries: [],
+24 -22
View File
@@ -32,11 +32,11 @@ const DEMO_ADDONS: HassioAddonInfo[] = [
detached: false,
homeassistant: "2025.7.0",
icon: false,
installed: true,
logo: false,
repository: "d5369777",
stage: "stable",
state: "started",
system_managed: false,
update_available: false,
url: "https://github.com/music-assistant/home-assistant-addon",
version: "2.6.3",
@@ -53,11 +53,11 @@ const DEMO_ADDONS: HassioAddonInfo[] = [
detached: false,
homeassistant: "2025.7.0",
icon: false,
installed: true,
logo: false,
repository: "5c53de3b",
stage: "stable",
state: "started",
system_managed: false,
update_available: false,
url: "https://esphome.io/",
version: "2025.7.3",
@@ -114,13 +114,14 @@ const addonDetails = (addon: HassioAddonInfo): HassioAddonDetails => ({
audio_output: null,
audio: false,
auth_api: false,
auto_uart: false,
auto_update: false,
boot: "auto",
boot_config: "auto",
changelog: false,
devices: [],
devicetree: false,
discovery: [],
dns: [],
docker_api: false,
documentation: false,
full_access: false,
@@ -133,8 +134,10 @@ const addonDetails = (addon: HassioAddonInfo): HassioAddonDetails => ({
host_ipc: false,
host_network: false,
host_pid: false,
host_uts: false,
ingress_entry: null,
ingress_panel: false,
ingress_port: null,
ingress_url: null,
ingress: false,
ip_address: "172.30.33.2",
@@ -148,13 +151,16 @@ const addonDetails = (addon: HassioAddonInfo): HassioAddonDetails => ({
protected: true,
rating: 6,
schema: CONFIG_SCHEMAS[addon.slug] ?? null,
services_role: [],
services: [],
signed: false,
startup: "application",
stdin: false,
system_managed: false,
system_managed_config_entry: null,
translations: {},
uart: false,
udev: false,
usb: false,
video: false,
watchdog: true,
webui: null,
});
@@ -181,11 +187,14 @@ const ADDON_STATS: HassioStats = {
blk_read: 12300000,
blk_write: 4500000,
cpu_percent: 1.4,
cpu_system_usage: 1420000000000,
cpu_usage: 19800000000,
memory_limit: 3900000000,
memory_percent: 4.2,
memory_usage: 163000000,
network_rx: 8900000,
network_tx: 2300000,
online_cpus: 4,
};
export const mockHassioSupervisor = (hass: MockHomeAssistant) => {
@@ -209,9 +218,13 @@ export const mockHassioSupervisor = (hass: MockHomeAssistant) => {
wait_boot: 5,
timezone: "Europe/Amsterdam",
logging: "info",
auto_update: true,
country: "NL",
debug: false,
debug_block: false,
detect_blocking_io: false,
diagnostics: true,
feature_flags: {},
addons: DEMO_ADDONS as any,
addons_repositories: [
"https://github.com/music-assistant/home-assistant-addon",
@@ -224,22 +237,6 @@ export const mockHassioSupervisor = (hass: MockHomeAssistant) => {
if (msg.endpoint === "/addons") {
const data: HassioAddonsInfo = {
addons: DEMO_ADDONS,
repositories: [
{
slug: "d5369777",
name: "Music Assistant",
source: "https://github.com/music-assistant/home-assistant-addon",
url: "https://github.com/music-assistant/home-assistant-addon",
maintainer: "Music Assistant",
},
{
slug: "5c53de3b",
name: "ESPHome",
source: "https://github.com/esphome/home-assistant-addon",
url: "https://esphome.io/",
maintainer: "ESPHome",
},
],
};
return data;
}
@@ -264,6 +261,7 @@ export const mockHassioSupervisor = (hass: MockHomeAssistant) => {
hostname: "homeassistant",
logging: "info",
machine: "green",
machine_id: "0123456789abcdef0123456789abcdef",
state: "running",
operating_system: "Home Assistant OS 18.2",
supervisor: "2026.07.1",
@@ -301,6 +299,7 @@ export const mockHassioSupervisor = (hass: MockHomeAssistant) => {
update_available: false,
version: "18.2",
version_latest: "18.2",
version_pending: null,
data_disk: "Home Assistant Green (mmcblk0)",
};
return data;
@@ -334,8 +333,9 @@ export const mockHassioSupervisor = (hass: MockHomeAssistant) => {
const data: NetworkInfo = {
interfaces: [
{
connected: true,
mac: "aa:bb:cc:dd:ee:ff",
primary: true,
privacy: false,
interface: "eth0",
enabled: true,
type: "ethernet",
@@ -354,6 +354,8 @@ export const mockHassioSupervisor = (hass: MockHomeAssistant) => {
gateway: "172.30.32.1",
interface: "hassio",
},
host_internet: true,
supervisor_internet: true,
};
return data;
}
+11 -11
View File
@@ -28,24 +28,24 @@ export interface HardwareInfo {
export interface HardwareInfoEntry {
board: HardwareInfoBoardInfo | null;
dongle: HardwareInfoDongleInfo | null;
config_entries: string[];
name: string;
url?: string;
config_entries: string[] | null;
name: string | null;
url: string | null;
}
export interface HardwareInfoBoardInfo {
manufacturer: string;
model?: string;
revision?: string;
hassio_board_id?: string;
model: string | null;
revision: string | null;
hassio_board_id: string | null;
}
export interface HardwareInfoDongleInfo {
manufacturer: string;
description: string;
pid?: string;
vid?: string;
serial_number?: string;
manufacturer: string | null;
description: string | null;
pid: string;
vid: string;
serial_number: string | null;
}
export interface SystemStatusStreamMessage {
+18 -12
View File
@@ -17,11 +17,12 @@ export type AddonCapability = Exclude<
>;
export type AddonStage = "stable" | "experimental" | "deprecated";
export type AddonAppArmour = "disable" | "default" | "profile";
export type AddonRole = "default" | "homeassistant" | "manager" | "admin";
export type AddonRole =
"default" | "homeassistant" | "backup" | "manager" | "admin";
export type AddonStartup =
"initialize" | "system" | "services" | "application" | "once";
export type AddonState =
"startup" | "started" | "stopped" | "unknown" | "error" | null;
"startup" | "started" | "stopped" | "unknown" | "error";
export type AddonRepository = "core" | "local" | string;
interface AddonFieldTranslation {
@@ -41,15 +42,15 @@ export interface HassioAddonInfo {
build: boolean;
description: string;
detached: boolean;
homeassistant: string;
homeassistant: string | null;
icon: boolean;
installed: boolean;
logo: boolean;
name: string;
repository: AddonRepository;
slug: string;
stage: AddonStage;
state: AddonState;
system_managed: boolean;
update_available: boolean;
url: string | null;
version_latest: string;
@@ -63,13 +64,14 @@ export interface HassioAddonDetails extends HassioAddonInfo {
audio_output: null | string;
audio: boolean;
auth_api: boolean;
auto_uart: boolean;
auto_update: boolean;
boot: "auto" | "manual";
boot_config: "auto" | "manual" | "manual_only";
changelog: boolean;
devices: string[];
devicetree: boolean;
discovery: string[];
dns: string[];
docker_api: boolean;
documentation: boolean;
full_access: boolean;
@@ -82,8 +84,10 @@ export interface HassioAddonDetails extends HassioAddonInfo {
host_ipc: boolean;
host_network: boolean;
host_pid: boolean;
host_uts: boolean;
ingress_entry: null | string;
ingress_panel: boolean;
ingress_panel: boolean | null;
ingress_port: number | null;
ingress_url: null | string;
ingress: boolean;
ip_address: string;
@@ -91,27 +95,29 @@ export interface HassioAddonDetails extends HassioAddonInfo {
long_description: null | string;
machine: any;
network_description: null | Record<string, string>;
network: null | Record<string, number>;
network: null | Record<string, number | null>;
options: Record<string, unknown>;
privileged: any;
protected: boolean;
rating: number;
schema: HaFormSchema[] | null;
services_role: string[];
services: string[];
signed: boolean;
slug: string;
startup: AddonStartup;
stdin: boolean;
system_managed: boolean;
system_managed_config_entry: string | null;
translations: Record<string, AddonTranslations>;
watchdog: null | boolean;
uart: boolean;
udev: boolean;
usb: boolean;
video: boolean;
watchdog: boolean;
webui: null | string;
}
export interface HassioAddonsInfo {
addons: HassioAddonInfo[];
repositories: HassioAddonRepository[];
}
export interface HassioAddonSetSecurityParams {
@@ -201,7 +207,7 @@ export const validateHassioAddonOption = async (
callWS: CallWS,
slug: string,
data?: any
): Promise<{ message: string; valid: boolean }> => {
): Promise<{ message: string; valid: boolean; pwned: boolean | null }> => {
return callWS({
type: "supervisor/api",
endpoint: `/addons/${slug}/options/validate`,
+4 -1
View File
@@ -13,12 +13,15 @@ export interface HassioResponse<T> {
export interface HassioStats {
blk_read: number;
blk_write: number;
cpu_percent: number;
cpu_percent: number | null;
cpu_system_usage: number;
cpu_usage: number;
memory_limit: number;
memory_percent: number;
memory_usage: number;
network_rx: number;
network_tx: number;
online_cpus: number;
}
export const extractApiErrorMessage = (error: any): string =>
+6 -4
View File
@@ -1,13 +1,15 @@
import type { HomeAssistant } from "../../types";
type HassioDockerRegistries = Record<
type HassioDockerRegistries = Record<string, { username: string }>;
type HassioDockerRegistryCredentials = Record<
string,
{ username: string; password?: string }
{ username: string; password: string }
>;
export const fetchHassioDockerRegistries = async (
hass: HomeAssistant
): Promise<HassioDockerRegistries> =>
): Promise<{ registries: HassioDockerRegistries }> =>
hass.callWS({
type: "supervisor/api",
endpoint: `/docker/registries`,
@@ -16,7 +18,7 @@ export const fetchHassioDockerRegistries = async (
export const addHassioDockerRegistry = async (
hass: HomeAssistant,
data: HassioDockerRegistries
data: HassioDockerRegistryCredentials
) => {
await hass.callWS({
type: "supervisor/api",
+1
View File
@@ -15,6 +15,7 @@ interface HassioHardwareAudioList {
export interface HardwareDevice {
attributes: Record<string, string>;
by_id: null | string;
children: string[];
dev_path: string;
name: string;
subsystem: string;
+10 -9
View File
@@ -2,19 +2,19 @@ import type { HomeAssistant } from "../../types";
export interface HassioHostInfo {
agent_version: string;
chassis: string;
cpe: string;
deployment: string;
chassis: string | null;
cpe: string | null;
deployment: string | null;
disk_life_time: number | null;
disk_free: number;
disk_total: number;
disk_used: number;
features: string[];
hostname: string;
kernel: string;
operating_system: string;
boot_timestamp: number;
startup_time: number;
hostname: string | null;
kernel: string | null;
operating_system: string | null;
boot_timestamp: number | null;
startup_time: number | null;
}
export interface HassioHassOSInfo {
@@ -23,7 +23,8 @@ export interface HassioHassOSInfo {
update_available: boolean;
version_latest: string | null;
version: string | null;
data_disk: string;
version_pending: string | null;
data_disk: string | null;
}
export interface Datadisk {
+9 -4
View File
@@ -5,15 +5,18 @@ interface IpConfiguration {
gateway: string | null;
method: "disabled" | "static" | "auto";
nameservers: string[];
ready: boolean | null;
route_metric: number | null;
}
export interface NetworkInterface {
connected: boolean;
mac: string;
primary: boolean;
privacy: boolean;
interface: string;
enabled: boolean;
ipv4?: Partial<IpConfiguration>;
ipv6?: Partial<IpConfiguration>;
ipv4?: Partial<IpConfiguration> | null;
ipv6?: Partial<IpConfiguration> | null;
type: "ethernet" | "wireless" | "vlan";
wifi?: Partial<WifiConfiguration> | null;
}
@@ -41,13 +44,15 @@ export interface WifiConfiguration {
mode: "infrastructure" | "mesh" | "adhoc" | "ap";
auth: "open" | "wep" | "wpa-psk";
ssid: string;
signal: number;
signal: number | null;
psk?: string;
}
export interface NetworkInfo {
interfaces: NetworkInterface[];
docker: DockerNetwork;
host_internet: boolean | null;
supervisor_internet: boolean;
}
export const fetchNetworkInfo = async (
+20 -2
View File
@@ -1,10 +1,28 @@
import type { HomeAssistant, TranslationDict } from "../../types";
export interface ResolutionIssue {
type: string;
context: string;
reference: string | null;
reference_extra: Record<string, unknown> | null;
uuid: string;
}
export interface ResolutionSuggestion extends ResolutionIssue {
auto: boolean;
}
export interface ResolutionCheck {
enabled: boolean;
slug: string;
}
export interface HassioResolution {
unsupported: (keyof TranslationDict["ui"]["dialogs"]["unsupported"]["reasons"])[];
unhealthy: (keyof TranslationDict["ui"]["dialogs"]["unhealthy"]["reasons"])[];
issues: string[];
suggestions: string[];
issues: ResolutionIssue[];
suggestions: ResolutionSuggestion[];
checks: ResolutionCheck[];
}
export const fetchHassioResolution = async (
+36 -13
View File
@@ -1,37 +1,59 @@
import type { HomeAssistant, PanelInfo } from "../../types";
import type { SupervisorArch } from "../supervisor/supervisor";
import type { AddonState } from "./addon";
import { supervisorUrl, type HassioResponse } from "./common";
export interface HassioHomeAssistantInfo {
arch: SupervisorArch;
arch: SupervisorArch | null;
audio_input: string | null;
audio_output: string | null;
backups_exclude_database: boolean;
boot: boolean;
duplicate_log_file: boolean;
image: string;
ip_address: string;
machine: string;
machine: string | null;
port: number;
ssl: boolean;
update_available: boolean;
version_latest: string;
version: string;
wait_boot: number;
version_latest: string | null;
version: string | null;
watchdog: boolean;
}
export interface HassioSupervisorInfoAddon {
icon: boolean;
name: string;
repository: string;
slug: string;
state: AddonState;
update_available: boolean;
version: string;
version_latest: string;
}
export interface HassioSupervisorInfoRepository {
name: string;
slug: string;
}
export interface HassioSupervisorInfo {
addons: string[];
addons_repositories: string[];
addons: HassioSupervisorInfoAddon[];
addons_repositories: HassioSupervisorInfoRepository[];
arch: SupervisorArch;
auto_update: boolean;
channel: string;
country: string | null;
debug: boolean;
debug_block: boolean;
detect_blocking_io: boolean;
diagnostics: boolean | null;
feature_flags: Record<string, boolean>;
healthy: boolean;
ip_address: string;
logging: string;
supported: boolean;
timezone: string;
timezone: string | null;
update_available: boolean;
version: string;
version_latest: string;
@@ -43,11 +65,12 @@ export interface HassioInfo {
channel: string;
docker: string;
features: string[];
hassos: null;
homeassistant: string;
hostname: string;
hassos: string | null;
homeassistant: string | null;
hostname: string | null;
logging: string;
machine: string;
machine: string | null;
machine_id: string | null;
state:
| "initialize"
| "setup"
@@ -57,7 +80,7 @@ export interface HassioInfo {
| "shutdown"
| "stopping"
| "close";
operating_system: string;
operating_system: string | null;
supervisor: string;
supported: boolean;
supported_arch: SupervisorArch[];
+5 -4
View File
@@ -14,6 +14,7 @@ export interface IPv4ConfiguredAddress {
export interface Adapter {
name: string;
index: number | null;
enabled: boolean;
auto: boolean;
default: boolean;
@@ -27,9 +28,9 @@ export interface NetworkConfig {
}
export interface NetworkUrls {
internal: string;
external: string;
cloud: string;
internal: string | null;
external: string | null;
cloud: string | null;
}
export const getNetworkConfig = (hass: HomeAssistant) =>
@@ -41,7 +42,7 @@ export const setNetworkConfig = (
hass: HomeAssistant,
configured_adapters: string[]
) =>
hass.callWS<string[]>({
hass.callWS<{ configured_adapters: string[] }>({
type: "network/configure",
config: {
configured_adapters: configured_adapters,
+11 -5
View File
@@ -14,8 +14,12 @@ export enum SupervisorMountUsage {
export enum SupervisorMountState {
ACTIVE = "active",
ACTIVATING = "activating",
DEACTIVATING = "deactivating",
FAILED = "failed",
UNKNOWN = "unknown",
INACTIVE = "inactive",
MAINTENANCE = "maintenance",
RELOADING = "reloading",
}
interface MountOptions {
@@ -26,14 +30,16 @@ export type CIFSVersion = "auto" | "1.0" | "2.0";
interface SupervisorMountBase {
name: string;
usage: SupervisorMountUsage;
usage: SupervisorMountUsage | null;
type: SupervisorMountType;
read_only: boolean;
server: string;
port: number;
port?: number;
}
export interface SupervisorMountResponse extends SupervisorMountBase {
state: SupervisorMountState | null;
user_path: string | null;
}
export interface SupervisorNFSMount extends SupervisorMountResponse {
@@ -44,7 +50,7 @@ export interface SupervisorNFSMount extends SupervisorMountResponse {
export interface SupervisorCIFSMount extends SupervisorMountResponse {
type: SupervisorMountType.CIFS;
share: string;
version?: CIFSVersion;
version?: CIFSVersion | null;
}
export type SupervisorMount = SupervisorNFSMount | SupervisorCIFSMount;
@@ -54,7 +60,7 @@ export type SupervisorNFSMountRequestParams = SupervisorNFSMount;
export interface SupervisorCIFSMountRequestParams extends SupervisorCIFSMount {
username?: string;
password?: string;
version?: CIFSVersion;
version?: CIFSVersion | null;
}
export type SupervisorMountRequestParams =
+1 -1
View File
@@ -2,7 +2,7 @@ import type { HomeAssistant } from "../../types";
export interface SupervisorUpdateConfig {
add_on_backup_before_update: boolean;
add_on_backup_retain_copies?: number;
add_on_backup_retain_copies: number;
core_backup_before_update: boolean;
}
@@ -897,7 +897,9 @@ class SupervisorAppInfo extends MobileAwareMixin(LitElement) {
this._uninstalling
}
@change=${this._panelToggled}
.checked=${this._currentAddon.ingress_panel}
.checked=${
this._currentAddon.ingress_panel || false
}
haptic
></ha-switch>
</ha-row-item>
@@ -194,9 +194,9 @@ class HaConfigSystemNavigation extends LitElement {
const hardwareInfo: HardwareInfo = await this.hass.callWS({
type: "hardware/info",
});
this._boardName = hardwareInfo?.hardware.find(
(hw) => hw.board !== null
)?.name;
this._boardName =
hardwareInfo?.hardware.find((hw) => hw.board !== null)?.name ??
undefined;
} else if (isHassioLoaded) {
const osData: HassioHassOSInfo = await fetchHassioHassOsInfo(this.hass);
if (osData.board) {
@@ -214,7 +214,7 @@ class HaConfigHardwareOverview extends SubscribeMixin(LitElement) {
const dongles = this._hardwareInfo?.hardware.filter(
(hw) =>
hw.dongle !== null &&
(!hw.config_entries.length ||
(!hw.config_entries?.length ||
hw.config_entries.some(
(entryId) =>
this._configEntries?.[entryId] &&
@@ -223,19 +223,19 @@ class HaConfigHardwareOverview extends SubscribeMixin(LitElement) {
);
if (boardData) {
boardConfigEntries = boardData.config_entries
boardConfigEntries = (boardData.config_entries ?? [])
.map((id) => this._configEntries?.[id])
.filter(
(entry) => entry?.supports_options && !entry.disabled_by
) as ConfigEntry[];
boardId = boardData.board!.hassio_board_id;
boardName = boardData.name;
boardId = boardData.board!.hassio_board_id ?? undefined;
boardName = boardData.name ?? undefined;
documentationURL = sanitizeHttpUrl(boardData.url);
imageURL = hardwareBrandsUrl(
{
category: "boards",
manufacturer: boardData.board!.manufacturer,
model: boardData.board!.model,
model: boardData.board!.model ?? undefined,
darkOptimized: this.hass.themes?.darkMode,
},
this.hass.auth.data.hassUrl
@@ -355,7 +355,7 @@ class HaConfigHardwareOverview extends SubscribeMixin(LitElement) {
dongles?.length
? html`<ha-card outlined>
${dongles.map((dongle) => {
const configEntry = dongle.config_entries
const configEntry = (dongle.config_entries ?? [])
.map((id) => this._configEntries?.[id])
.filter(
(entry) => entry?.supports_options && !entry.disabled_by
@@ -32,7 +32,7 @@ export class HassioHostname extends LitElement {
private async _fetchHostInfo() {
const hostInfo = await fetchHassioHostInfo(this.hass);
this._hostname = hostInfo.hostname;
this._hostname = hostInfo.hostname ?? undefined;
}
protected render() {
@@ -203,7 +203,7 @@ class DialogSystemInformation extends LitElement {
.heading=${this.hass.localize(
"ui.panel.config.system_health.cpu_usage"
)}
.value=${this._coreStats.cpu_percent}
.value=${this._coreStats.cpu_percent ?? 0}
></ha-metric>
<ha-metric
.heading=${this.hass.localize(
@@ -226,7 +226,7 @@ class DialogSystemInformation extends LitElement {
.heading=${this.hass.localize(
"ui.panel.config.system_health.cpu_usage"
)}
.value=${this._supervisorStats.cpu_percent}
.value=${this._supervisorStats.cpu_percent ?? 0}
></ha-metric>
<ha-metric
.heading=${this.hass.localize(
@@ -37,7 +37,7 @@ interface MoveDatadiskFormState {
const calculateMoveTime = memoizeOne((hostInfo: HassioHostInfo): number => {
// Assume a speed of 30 MB/s.
const moveTime = (hostInfo.disk_used * 1000) / 60 / 30;
const rebootTime = (hostInfo.startup_time * 4) / 60;
const rebootTime = ((hostInfo.startup_time ?? 0) * 4) / 60;
return Math.ceil((moveTime + rebootTime) / 10) * 10;
});
@@ -145,7 +145,7 @@ class MoveDatadiskDialog extends DirtyStateProviderMixin<MoveDatadiskFormState>(
${this.hass.localize(
"ui.panel.config.storage.datadisk.description",
{
current_path: this._osInfo.data_disk,
current_path: this._osInfo.data_disk ?? "",
time: calculateMoveTime(this._hostInfo),
}
)}