diff --git a/hassio/src/addon-view/config/hassio-addon-config.ts b/hassio/src/addon-view/config/hassio-addon-config.ts
index 52c3d73930..273bca178b 100644
--- a/hassio/src/addon-view/config/hassio-addon-config.ts
+++ b/hassio/src/addon-view/config/hassio-addon-config.ts
@@ -3,6 +3,7 @@ import { ActionDetail } from "@material/mwc-list";
import "@material/mwc-list/mwc-list-item";
import { mdiDotsVertical } from "@mdi/js";
import "@polymer/iron-autogrow-textarea/iron-autogrow-textarea";
+import { DEFAULT_SCHEMA, Type } from "js-yaml";
import {
css,
CSSResultGroup,
@@ -11,7 +12,7 @@ import {
PropertyValues,
TemplateResult,
} from "lit";
-import { customElement, property, state, query } from "lit/decorators";
+import { customElement, property, query, state } from "lit/decorators";
import memoizeOne from "memoize-one";
import { fireEvent } from "../../../../src/common/dom/fire_event";
import "../../../../src/components/buttons/ha-progress-button";
@@ -27,6 +28,7 @@ import {
HassioAddonDetails,
HassioAddonSetOptionParams,
setHassioAddonOption,
+ validateHassioAddonOption,
} from "../../../../src/data/hassio/addon";
import { extractApiErrorMessage } from "../../../../src/data/hassio/common";
import { Supervisor } from "../../../../src/data/supervisor/supervisor";
@@ -38,6 +40,13 @@ import { hassioStyle } from "../../resources/hassio-style";
const SUPPORTED_UI_TYPES = ["string", "select", "boolean", "integer", "float"];
+const ADDON_YAML_SCHEMA = DEFAULT_SCHEMA.extend([
+ new Type("!secret", {
+ kind: "scalar",
+ construct: (data) => `!secret ${data}`,
+ }),
+]);
+
@customElement("hassio-addon-config")
class HassioAddonConfig extends LitElement {
@property({ attribute: false }) public addon!: HassioAddonDetails;
@@ -125,6 +134,7 @@ class HassioAddonConfig extends LitElement {
>`
: html` `}
${this._error ? html`
${this._error}
` : ""}
${!this._yamlMode ||
@@ -269,6 +279,14 @@ class HassioAddonConfig extends LitElement {
this._error = undefined;
try {
+ const validation = await validateHassioAddonOption(
+ this.hass,
+ this.addon.slug,
+ this._editor?.value
+ );
+ if (!validation.valid) {
+ throw Error(validation.message);
+ }
await setHassioAddonOption(this.hass, this.addon.slug, {
options: this._yamlMode ? this._editor?.value : this._options,
});
diff --git a/src/components/ha-yaml-editor.ts b/src/components/ha-yaml-editor.ts
index 8d79034c3e..8ff961aa10 100644
--- a/src/components/ha-yaml-editor.ts
+++ b/src/components/ha-yaml-editor.ts
@@ -1,4 +1,4 @@
-import { dump, load } from "js-yaml";
+import { DEFAULT_SCHEMA, dump, load, Schema } from "js-yaml";
import { html, LitElement, TemplateResult } from "lit";
import { customElement, property, state } from "lit/decorators";
import { fireEvent } from "../common/dom/fire_event";
@@ -20,6 +20,8 @@ const isEmpty = (obj: Record): boolean => {
export class HaYamlEditor extends LitElement {
@property() public value?: any;
+ @property({ attribute: false }) public yamlSchema: Schema = DEFAULT_SCHEMA;
+
@property() public defaultValue?: any;
@property() public isValid = true;
@@ -30,7 +32,10 @@ export class HaYamlEditor extends LitElement {
public setValue(value): void {
try {
- this._yaml = value && !isEmpty(value) ? dump(value) : "";
+ this._yaml =
+ value && !isEmpty(value)
+ ? dump(value, { schema: this.yamlSchema })
+ : "";
} catch (err) {
// eslint-disable-next-line no-console
console.error(err, value);
@@ -67,7 +72,7 @@ export class HaYamlEditor extends LitElement {
if (this._yaml) {
try {
- parsed = load(this._yaml);
+ parsed = load(this._yaml, { schema: this.yamlSchema });
} catch (err) {
// Invalid YAML
isValid = false;
diff --git a/src/data/hassio/addon.ts b/src/data/hassio/addon.ts
index 902bd88816..c2e43ac046 100644
--- a/src/data/hassio/addon.ts
+++ b/src/data/hassio/addon.ts
@@ -212,13 +212,15 @@ export const setHassioAddonOption = async (
export const validateHassioAddonOption = async (
hass: HomeAssistant,
- slug: string
+ slug: string,
+ data?: any
): Promise<{ message: string; valid: boolean }> => {
if (atLeastVersion(hass.config.version, 2021, 2, 4)) {
return hass.callWS({
type: "supervisor/api",
endpoint: `/addons/${slug}/options/validate`,
method: "post",
+ data,
});
}