Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 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: 15 additions & 0 deletions hassio/src/addon-store/hassio-addon-store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand All @@ -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";
Expand Down Expand Up @@ -113,6 +115,12 @@ class HassioAddonStore extends LitElement {
<mwc-list-item>
Reload
</mwc-list-item>
${this.hass.userData?.showAdvanced &&
atLeastVersion(this.hass.config.version, 0, 117)
? html`<mwc-list-item>
Registries
</mwc-list-item>`
: ""}
</ha-button-menu>
${repos.length === 0
? html`<hass-loading-screen no-toolbar></hass-loading-screen>`
Expand Down Expand Up @@ -157,6 +165,9 @@ class HassioAddonStore extends LitElement {
case 1:
this.refreshData();
break;
case 2:
this._manageRegistries();
break;
}
}

Expand All @@ -173,6 +184,10 @@ class HassioAddonStore extends LitElement {
});
}

private async _manageRegistries() {
showRegistriesDialog(this);
}

private async _loadData() {
try {
const addonsInfo = await fetchHassioAddonsInfo(this.hass);
Expand Down
256 changes: 256 additions & 0 deletions hassio/src/dialogs/registries/dialog-hassio-registries.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,256 @@
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 "@polymer/paper-input/paper-input";
import type { PaperInputElement } from "@polymer/paper-input/paper-input";
import {
css,
CSSResult,
customElement,
html,
internalProperty,
LitElement,
property,
query,
TemplateResult,
} from "lit-element";
import "../../../../src/components/ha-circular-progress";
import "../../../../src/components/ha-dialog";
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;
}[];

@query("#registry") private _newRegistry?: PaperInputElement;

@query("#username") private _newUsername?: PaperInputElement;

@query("#password") private _newPassword?: PaperInputElement;
Comment thread
ludeeus marked this conversation as resolved.
Outdated

@internalProperty() private _opened = false;

@internalProperty() private _addingRegistry = false;

@internalProperty() private _entriesAreFilled = false;

protected render(): TemplateResult {
return html`
<ha-dialog
.open=${this._opened}
@closing=${this.closeDialog}
scrimClickAction
escapeKeyAction
.heading=${createCloseHeading(
this.hass,
this._addingRegistry
? "Add New Docker Registry"
: "Manage Docker Registries"
)}
>
<div class="form" @keyup=${this._inputChanged}>
${this._addingRegistry
? html`
<paper-input
class="flex-auto"
id="registry"
label="Registry"
required
auto-validate
></paper-input>
<paper-input
class="flex-auto"
id="username"
label="Username"
required
auto-validate
></paper-input>
<paper-input
class="flex-auto"
id="password"
label="Password"
type="password"
required
auto-validate
></paper-input>

<mwc-button
?disabled=${!this._entriesAreFilled}
@click=${this._addNewRegistry}
>
Add registry
</mwc-button>
`
: html`${this._registries?.length
? this._registries.map((entry) => {
return html`
<mwc-list-item class="option" hasMeta twoline>
<span>${entry.registry}</span>
<span slot="secondary"
>Username: ${entry.username}</span
>
<mwc-icon-button
.entry=${entry}
title="Remove"
slot="meta"
@click=${this._removeRegistry}
>
<ha-svg-icon .path=${mdiDelete}></ha-svg-icon>
</mwc-icon-button>
</mwc-list-item>
`;
})
: html`
<mwc-list-item>
<span>No registries configured</span>
</mwc-list-item>
`}
<mwc-button @click=${this._addRegistry}>
Add new registry
</mwc-button> `}
</div>
</ha-dialog>
`;
}

private _inputChanged() {
this._entriesAreFilled = Boolean(
this._newRegistry?.value &&
this._newUsername?.value &&
this._newPassword?.value
);
}
Comment thread
ludeeus marked this conversation as resolved.
Outdated

public async showDialog(_dialogParams: any): Promise<void> {
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<void> {
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<void> {
if (
!this._newRegistry?.value ||
!this._newUsername?.value ||
!this._newPassword?.value
) {
return;
}

const data = {};
data[this._newRegistry.value] = {
username: this._newUsername.value,
password: this._newPassword.value,
};

try {
await addHassioDockerRegistry(this.hass, data);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if thas call returns the newly created entry, the next call would not be needed and it could just be appended?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It does not, most of the supervisor API does not return for POST calls

await this._loadRegistries();
this._addingRegistry = false;
} catch (err) {
showAlertDialog(this, {
title: "Failed to add registry",
text: extractApiErrorMessage(err),
});
}
}

private async _removeRegistry(ev: Event): Promise<void> {
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;
}
}
13 changes: 13 additions & 0 deletions hassio/src/dialogs/registries/show-dialog-registries.ts
Original file line number Diff line number Diff line change
@@ -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: {},
});
};
36 changes: 36 additions & 0 deletions src/data/hassio/docker.ts
Original file line number Diff line number Diff line change
@@ -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<HassioResponse<HassioDockerRegistries>>(
"GET",
"hassio/docker/registries"
)
);
};

export const addHassioDockerRegistry = async (
hass: HomeAssistant,
data: HassioDockerRegistries
) => {
await hass.callApi<HassioResponse<HassioDockerRegistries>>(
"POST",
"hassio/docker/registries",
data
);
};

export const removeHassioDockerRegistry = async (
hass: HomeAssistant,
registry: string
) => {
await hass.callApi<HassioResponse<void>>(
"DELETE",
`hassio/docker/registries/${registry}`
);
};