Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 2 additions & 13 deletions src/panels/config/apps/app-view/config/supervisor-app-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import { haStyle } from "../../../../../resources/styles";
import type { HomeAssistant } from "../../../../../types";
import { supervisorAppsStyle } from "../../resources/supervisor-apps-style";
import { suggestSupervisorAppRestart } from "../dialogs/suggestSupervisorAppRestart";
import { stringSchemaEntrySelector } from "../util/string-schema-selector";

const SUPPORTED_UI_TYPES = [
"string",
Expand All @@ -53,8 +54,6 @@ const secretTag = defineScalarTag("!secret", {

const ADDON_YAML_SCHEMA = YAML11_SCHEMA.withTags(secretTag);

const MASKED_FIELDS = ["password", "secret", "token"];

@customElement("supervisor-app-config")
class SupervisorAppConfig extends DirtyStateProviderMixin<
Record<string, unknown>
Expand Down Expand Up @@ -181,17 +180,7 @@ class SupervisorAppConfig extends DirtyStateProviderMixin<
return { select: { options: entry.options, multiple: entry.multiple } };
}
if (entry.type === "string") {
return entry.multiple
? { select: { options: [], multiple: true, custom_value: true } }
: {
text: {
type: entry.format
? entry.format
: MASKED_FIELDS.includes(entry.name)
? "password"
: "text",
},
};
return stringSchemaEntrySelector(entry);
}
if (entry.type === "boolean") {
return { boolean: {} };
Expand Down
37 changes: 37 additions & 0 deletions src/panels/config/apps/app-view/util/string-schema-selector.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import type { Selector } from "../../../../../data/selector";

export const MASKED_FIELDS = ["password", "secret", "token"];

export interface StringSchemaEntry {
name: string;
multiple?: boolean;
format?: "email" | "password" | "url";
options?: string[];
}

export const stringSchemaEntrySelector = (
entry: StringSchemaEntry
): Selector => {
if (entry.multiple) {
return {
select: {
options: entry.options ?? [],
multiple: true,
custom_value: true,
},
};
}
// Single-value fields keep the text selector even when options are
// available: the single-value custom_value picker cannot store an empty
// string (clearing emits undefined, dropping the key on save), which
// would silently change stored-value semantics versus the text field.
return {
text: {
type: entry.format
? entry.format
: MASKED_FIELDS.includes(entry.name)
? "password"
: "text",
},
};
};
65 changes: 65 additions & 0 deletions test/panels/config/apps/string-schema-selector.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { describe, expect, it } from "vitest";
import { stringSchemaEntrySelector } from "../../../../src/panels/config/apps/app-view/util/string-schema-selector";

describe("stringSchemaEntrySelector", () => {
it("renders multi-value fields as a multi-select with supervisor options", () => {
expect(
stringSchemaEntrySelector({
name: "enabled_shares",
multiple: true,
options: ["addons", "backup", "config"],
})
).toEqual({
select: {
options: ["addons", "backup", "config"],
multiple: true,
custom_value: true,
},
});
});

it("renders multi-value fields without options as an empty multi-select", () => {
expect(
stringSchemaEntrySelector({ name: "veto_files", multiple: true })
).toEqual({
select: { options: [], multiple: true, custom_value: true },
});
});

it("keeps single-value fields as text even when options are present", () => {
expect(
stringSchemaEntrySelector({
name: "share_on_port",
options: ["443", "8443", "10000"],
})
).toEqual({ text: { type: "text" } });
});

it("keeps masked fields as password text even when options are present", () => {
expect(
stringSchemaEntrySelector({ name: "token", options: ["a", "b"] })
).toEqual({ text: { type: "password" } });
});

it("keeps format-typed fields as text even when options are present", () => {
expect(
stringSchemaEntrySelector({
name: "contact",
format: "email",
options: ["a@b.c"],
})
).toEqual({ text: { type: "email" } });
});

it("renders plain single-value fields without options as text", () => {
expect(stringSchemaEntrySelector({ name: "workgroup" })).toEqual({
text: { type: "text" },
});
});

it("renders masked fields without options as password text", () => {
expect(stringSchemaEntrySelector({ name: "password" })).toEqual({
text: { type: "password" },
});
});
});