diff --git a/hassio/src/addon-store/hassio-addon-store.ts b/hassio/src/addon-store/hassio-addon-store.ts index 10a319cdba..fc15fc38dc 100644 --- a/hassio/src/addon-store/hassio-addon-store.ts +++ b/hassio/src/addon-store/hassio-addon-store.ts @@ -11,6 +11,7 @@ import { PropertyValues, } from "lit-element"; import { html, TemplateResult } from "lit-html"; +import { atLeastVersion } from "../../../src/common/config/version"; import "../../../src/common/search/search-input"; import "../../../src/components/ha-button-menu"; import "../../../src/components/ha-svg-icon"; @@ -24,6 +25,7 @@ import { extractApiErrorMessage } from "../../../src/data/hassio/common"; import "../../../src/layouts/hass-loading-screen"; import "../../../src/layouts/hass-tabs-subpage"; import { HomeAssistant, Route } from "../../../src/types"; +import { showRegistriesDialog } from "../dialogs/registries/show-dialog-registries"; import { showRepositoriesDialog } from "../dialogs/repositories/show-dialog-repositories"; import { supervisorTabs } from "../hassio-tabs"; import "./hassio-addon-repository"; @@ -113,6 +115,12 @@ class HassioAddonStore extends LitElement { Reload + ${this.hass.userData?.showAdvanced && + atLeastVersion(this.hass.config.version, 0, 117) + ? html` + Registries + ` + : ""} ${repos.length === 0 ? html`` @@ -157,6 +165,9 @@ class HassioAddonStore extends LitElement { case 1: this.refreshData(); break; + case 2: + this._manageRegistries(); + break; } } @@ -173,6 +184,10 @@ class HassioAddonStore extends LitElement { }); } + private async _manageRegistries() { + showRegistriesDialog(this); + } + private async _loadData() { try { const addonsInfo = await fetchHassioAddonsInfo(this.hass); diff --git a/hassio/src/dialogs/registries/dialog-hassio-registries.ts b/hassio/src/dialogs/registries/dialog-hassio-registries.ts new file mode 100644 index 0000000000..fef6e3e0a8 --- /dev/null +++ b/hassio/src/dialogs/registries/dialog-hassio-registries.ts @@ -0,0 +1,245 @@ +import "@material/mwc-button/mwc-button"; +import "@material/mwc-icon-button/mwc-icon-button"; +import "@material/mwc-list/mwc-list-item"; +import { mdiDelete } from "@mdi/js"; +import { PaperInputElement } from "@polymer/paper-input/paper-input"; +import { + css, + CSSResult, + customElement, + html, + internalProperty, + LitElement, + property, + TemplateResult, +} from "lit-element"; +import "../../../../src/components/ha-circular-progress"; +import { createCloseHeading } from "../../../../src/components/ha-dialog"; +import "../../../../src/components/ha-svg-icon"; +import { extractApiErrorMessage } from "../../../../src/data/hassio/common"; +import { + addHassioDockerRegistry, + fetchHassioDockerRegistries, + removeHassioDockerRegistry, +} from "../../../../src/data/hassio/docker"; +import { showAlertDialog } from "../../../../src/dialogs/generic/show-dialog-box"; +import { haStyle, haStyleDialog } from "../../../../src/resources/styles"; +import type { HomeAssistant } from "../../../../src/types"; + +@customElement("dialog-hassio-registries") +class HassioRegistriesDialog extends LitElement { + @property({ attribute: false }) public hass!: HomeAssistant; + + @property({ attribute: false }) private _registries?: { + registry: string; + username: string; + }[]; + + @internalProperty() private _registry?: string; + + @internalProperty() private _username?: string; + + @internalProperty() private _password?: string; + + @internalProperty() private _opened = false; + + @internalProperty() private _addingRegistry = false; + + protected render(): TemplateResult { + return html` + + + ${this._addingRegistry + ? html` + + + + + + Add registry + + ` + : html`${this._registries?.length + ? this._registries.map((entry) => { + return html` + + ${entry.registry} + Username: ${entry.username} + + + + + `; + }) + : html` + + No registries configured + + `} + + Add new registry + `} + + + `; + } + + private _inputChanged(ev: Event) { + const target = ev.currentTarget as PaperInputElement; + this[`_${target.name}`] = target.value; + } + + public async showDialog(_dialogParams: any): Promise { + this._opened = true; + await this._loadRegistries(); + await this.updateComplete; + } + + public closeDialog(): void { + this._addingRegistry = false; + this._opened = false; + } + + public focus(): void { + this.updateComplete.then(() => + (this.shadowRoot?.querySelector( + "[dialogInitialFocus]" + ) as HTMLElement)?.focus() + ); + } + + private async _loadRegistries(): Promise { + const registries = await fetchHassioDockerRegistries(this.hass); + this._registries = Object.keys(registries!.registries).map((key) => ({ + registry: key, + username: registries.registries[key].username, + })); + } + + private _addRegistry(): void { + this._addingRegistry = true; + } + + private async _addNewRegistry(): Promise { + const data = {}; + data[this._registry!] = { + username: this._username, + password: this._password, + }; + + try { + await addHassioDockerRegistry(this.hass, data); + await this._loadRegistries(); + this._addingRegistry = false; + } catch (err) { + showAlertDialog(this, { + title: "Failed to add registry", + text: extractApiErrorMessage(err), + }); + } + } + + private async _removeRegistry(ev: Event): Promise { + const entry = (ev.currentTarget as any).entry; + + try { + await removeHassioDockerRegistry(this.hass, entry.registry); + await this._loadRegistries(); + } catch (err) { + showAlertDialog(this, { + title: "Failed to remove registry", + text: extractApiErrorMessage(err), + }); + } + } + + static get styles(): CSSResult[] { + return [ + haStyle, + haStyleDialog, + css` + ha-dialog.button-left { + --justify-action-buttons: flex-start; + } + paper-icon-item { + cursor: pointer; + } + .form { + color: var(--primary-text-color); + } + .option { + border: 1px solid var(--divider-color); + border-radius: 4px; + margin-top: 4px; + } + mwc-button { + margin-left: 8px; + } + mwc-icon-button { + color: var(--error-color); + margin: -10px; + } + mwc-list-item { + cursor: default; + } + mwc-list-item span[slot="secondary"] { + color: var(--secondary-text-color); + } + ha-paper-dropdown-menu { + display: block; + } + `, + ]; + } +} + +declare global { + interface HTMLElementTagNameMap { + "dialog-hassio-registries": HassioRegistriesDialog; + } +} diff --git a/hassio/src/dialogs/registries/show-dialog-registries.ts b/hassio/src/dialogs/registries/show-dialog-registries.ts new file mode 100644 index 0000000000..a9e871d17e --- /dev/null +++ b/hassio/src/dialogs/registries/show-dialog-registries.ts @@ -0,0 +1,13 @@ +import { fireEvent } from "../../../../src/common/dom/fire_event"; +import "./dialog-hassio-registries"; + +export const showRegistriesDialog = (element: HTMLElement): void => { + fireEvent(element, "show-dialog", { + dialogTag: "dialog-hassio-registries", + dialogImport: () => + import( + /* webpackChunkName: "dialog-hassio-registries" */ "./dialog-hassio-registries" + ), + dialogParams: {}, + }); +}; diff --git a/src/data/hassio/docker.ts b/src/data/hassio/docker.ts new file mode 100644 index 0000000000..4bc9a194c5 --- /dev/null +++ b/src/data/hassio/docker.ts @@ -0,0 +1,36 @@ +import { HomeAssistant } from "../../types"; +import { hassioApiResultExtractor, HassioResponse } from "./common"; + +interface HassioDockerRegistries { + [key: string]: { username: string; password?: string }; +} + +export const fetchHassioDockerRegistries = async (hass: HomeAssistant) => { + return hassioApiResultExtractor( + await hass.callApi>( + "GET", + "hassio/docker/registries" + ) + ); +}; + +export const addHassioDockerRegistry = async ( + hass: HomeAssistant, + data: HassioDockerRegistries +) => { + await hass.callApi>( + "POST", + "hassio/docker/registries", + data + ); +}; + +export const removeHassioDockerRegistry = async ( + hass: HomeAssistant, + registry: string +) => { + await hass.callApi>( + "DELETE", + `hassio/docker/registries/${registry}` + ); +};