mirror of
https://github.com/home-assistant/frontend.git
synced 2025-04-26 06:17:20 +00:00
Add configuration panel for Application Credentials (#12344)
Co-authored-by: Zack Barett <zackbarett@hey.com> Co-authored-by: Zack <zackbarett@hey.com>
This commit is contained in:
parent
ca37aff47d
commit
00c5d3dbbb
@ -269,8 +269,8 @@ export class HaDataTable extends LitElement {
|
|||||||
@change=${this._handleHeaderRowCheckboxClick}
|
@change=${this._handleHeaderRowCheckboxClick}
|
||||||
.indeterminate=${this._checkedRows.length &&
|
.indeterminate=${this._checkedRows.length &&
|
||||||
this._checkedRows.length !== this._checkableRowsCount}
|
this._checkedRows.length !== this._checkableRowsCount}
|
||||||
.checked=${this._checkedRows.length ===
|
.checked=${this._checkedRows.length &&
|
||||||
this._checkableRowsCount}
|
this._checkedRows.length === this._checkableRowsCount}
|
||||||
>
|
>
|
||||||
</ha-checkbox>
|
</ha-checkbox>
|
||||||
</div>
|
</div>
|
||||||
|
44
src/data/application_credential.ts
Normal file
44
src/data/application_credential.ts
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
import { HomeAssistant } from "../types";
|
||||||
|
|
||||||
|
export interface ApplicationCredentialsConfig {
|
||||||
|
domains: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ApplicationCredential {
|
||||||
|
id: string;
|
||||||
|
domain: string;
|
||||||
|
client_id: string;
|
||||||
|
client_secret: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const fetchApplicationCredentialsConfig = async (hass: HomeAssistant) =>
|
||||||
|
hass.callWS<ApplicationCredentialsConfig>({
|
||||||
|
type: "application_credentials/config",
|
||||||
|
});
|
||||||
|
|
||||||
|
export const fetchApplicationCredentials = async (hass: HomeAssistant) =>
|
||||||
|
hass.callWS<ApplicationCredential[]>({
|
||||||
|
type: "application_credentials/list",
|
||||||
|
});
|
||||||
|
|
||||||
|
export const createApplicationCredential = async (
|
||||||
|
hass: HomeAssistant,
|
||||||
|
domain: string,
|
||||||
|
clientId: string,
|
||||||
|
clientSecret: string
|
||||||
|
) =>
|
||||||
|
hass.callWS<ApplicationCredential>({
|
||||||
|
type: "application_credentials/create",
|
||||||
|
domain,
|
||||||
|
client_id: clientId,
|
||||||
|
client_secret: clientSecret,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const deleteApplicationCredential = async (
|
||||||
|
hass: HomeAssistant,
|
||||||
|
applicationCredentialsId: string
|
||||||
|
) =>
|
||||||
|
hass.callWS<void>({
|
||||||
|
type: "application_credentials/delete",
|
||||||
|
application_credentials_id: applicationCredentialsId,
|
||||||
|
});
|
@ -0,0 +1,224 @@
|
|||||||
|
import "@material/mwc-button";
|
||||||
|
import "@material/mwc-list/mwc-list-item";
|
||||||
|
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||||
|
import { customElement, property, state } from "lit/decorators";
|
||||||
|
import { ComboBoxLitRenderer } from "lit-vaadin-helpers";
|
||||||
|
import { fireEvent } from "../../../common/dom/fire_event";
|
||||||
|
import "../../../components/ha-circular-progress";
|
||||||
|
import "../../../components/ha-combo-box";
|
||||||
|
import { createCloseHeading } from "../../../components/ha-dialog";
|
||||||
|
import "../../../components/ha-textfield";
|
||||||
|
import {
|
||||||
|
fetchApplicationCredentialsConfig,
|
||||||
|
createApplicationCredential,
|
||||||
|
ApplicationCredential,
|
||||||
|
} from "../../../data/application_credential";
|
||||||
|
import { domainToName } from "../../../data/integration";
|
||||||
|
import { PolymerChangedEvent } from "../../../polymer-types";
|
||||||
|
import { haStyleDialog } from "../../../resources/styles";
|
||||||
|
import { HomeAssistant } from "../../../types";
|
||||||
|
import { AddApplicationCredentialDialogParams } from "./show-dialog-add-application-credential";
|
||||||
|
|
||||||
|
interface Domain {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const rowRenderer: ComboBoxLitRenderer<Domain> = (item) => html`<mwc-list-item>
|
||||||
|
<span>${item.name}</span>
|
||||||
|
</mwc-list-item>`;
|
||||||
|
|
||||||
|
@customElement("dialog-add-application-credential")
|
||||||
|
export class DialogAddApplicationCredential extends LitElement {
|
||||||
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
|
|
||||||
|
@state() private _loading = false;
|
||||||
|
|
||||||
|
// Error message when can't talk to server etc
|
||||||
|
@state() private _error?: string;
|
||||||
|
|
||||||
|
@state() private _params?: AddApplicationCredentialDialogParams;
|
||||||
|
|
||||||
|
@state() private _domain?: string;
|
||||||
|
|
||||||
|
@state() private _clientId?: string;
|
||||||
|
|
||||||
|
@state() private _clientSecret?: string;
|
||||||
|
|
||||||
|
@state() private _domains?: Domain[];
|
||||||
|
|
||||||
|
public showDialog(params: AddApplicationCredentialDialogParams) {
|
||||||
|
this._params = params;
|
||||||
|
this._domain = "";
|
||||||
|
this._clientId = "";
|
||||||
|
this._clientSecret = "";
|
||||||
|
this._error = undefined;
|
||||||
|
this._loading = false;
|
||||||
|
this._fetchConfig();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async _fetchConfig() {
|
||||||
|
const config = await fetchApplicationCredentialsConfig(this.hass);
|
||||||
|
this._domains = config.domains.map((domain) => ({
|
||||||
|
id: domain,
|
||||||
|
name: domainToName(this.hass.localize, domain),
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected render(): TemplateResult {
|
||||||
|
if (!this._params || !this._domains) {
|
||||||
|
return html``;
|
||||||
|
}
|
||||||
|
return html`
|
||||||
|
<ha-dialog
|
||||||
|
open
|
||||||
|
@closed=${this.closeDialog}
|
||||||
|
scrimClickAction
|
||||||
|
escapeKeyAction
|
||||||
|
.heading=${createCloseHeading(
|
||||||
|
this.hass,
|
||||||
|
this.hass.localize(
|
||||||
|
"ui.panel.config.application_credentials.editor.caption"
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
${this._error ? html` <div class="error">${this._error}</div> ` : ""}
|
||||||
|
<ha-combo-box
|
||||||
|
name="domain"
|
||||||
|
.hass=${this.hass}
|
||||||
|
.label=${this.hass.localize(
|
||||||
|
"ui.panel.config.application_credentials.editor.domain"
|
||||||
|
)}
|
||||||
|
.value=${this._domain}
|
||||||
|
.renderer=${rowRenderer}
|
||||||
|
.items=${this._domains}
|
||||||
|
item-id-path="id"
|
||||||
|
item-value-path="id"
|
||||||
|
item-label-path="name"
|
||||||
|
required
|
||||||
|
@value-changed=${this._handleDomainPicked}
|
||||||
|
></ha-combo-box>
|
||||||
|
<ha-textfield
|
||||||
|
class="clientId"
|
||||||
|
name="clientId"
|
||||||
|
.label=${this.hass.localize(
|
||||||
|
"ui.panel.config.application_credentials.editor.client_id"
|
||||||
|
)}
|
||||||
|
.value=${this._clientId}
|
||||||
|
required
|
||||||
|
@input=${this._handleValueChanged}
|
||||||
|
error-message=${this.hass.localize("ui.common.error_required")}
|
||||||
|
dialogInitialFocus
|
||||||
|
></ha-textfield>
|
||||||
|
<ha-textfield
|
||||||
|
.label=${this.hass.localize(
|
||||||
|
"ui.panel.config.application_credentials.editor.client_secret"
|
||||||
|
)}
|
||||||
|
type="password"
|
||||||
|
name="clientSecret"
|
||||||
|
.value=${this._clientSecret}
|
||||||
|
required
|
||||||
|
@input=${this._handleValueChanged}
|
||||||
|
error-message=${this.hass.localize("ui.common.error_required")}
|
||||||
|
></ha-textfield>
|
||||||
|
</div>
|
||||||
|
${this._loading
|
||||||
|
? html`
|
||||||
|
<div slot="primaryAction" class="submit-spinner">
|
||||||
|
<ha-circular-progress active></ha-circular-progress>
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
: html`
|
||||||
|
<mwc-button
|
||||||
|
slot="primaryAction"
|
||||||
|
.disabled=${!this._domain ||
|
||||||
|
!this._clientId ||
|
||||||
|
!this._clientSecret}
|
||||||
|
@click=${this._createApplicationCredential}
|
||||||
|
>
|
||||||
|
${this.hass.localize(
|
||||||
|
"ui.panel.config.application_credentials.editor.create"
|
||||||
|
)}
|
||||||
|
</mwc-button>
|
||||||
|
`}
|
||||||
|
</ha-dialog>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
public closeDialog() {
|
||||||
|
this._params = undefined;
|
||||||
|
this._domains = undefined;
|
||||||
|
fireEvent(this, "dialog-closed", { dialog: this.localName });
|
||||||
|
}
|
||||||
|
|
||||||
|
private async _handleDomainPicked(ev: PolymerChangedEvent<string>) {
|
||||||
|
const target = ev.target as any;
|
||||||
|
if (target.selectedItem) {
|
||||||
|
this._domain = target.selectedItem.id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private _handleValueChanged(ev: CustomEvent) {
|
||||||
|
this._error = undefined;
|
||||||
|
const name = (ev.target as any).name;
|
||||||
|
const value = (ev.target as any).value;
|
||||||
|
this[`_${name}`] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async _createApplicationCredential(ev) {
|
||||||
|
ev.preventDefault();
|
||||||
|
if (!this._domain || !this._clientId || !this._clientSecret) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this._loading = true;
|
||||||
|
this._error = "";
|
||||||
|
|
||||||
|
let applicationCredential: ApplicationCredential;
|
||||||
|
try {
|
||||||
|
applicationCredential = await createApplicationCredential(
|
||||||
|
this.hass,
|
||||||
|
this._domain,
|
||||||
|
this._clientId,
|
||||||
|
this._clientSecret
|
||||||
|
);
|
||||||
|
} catch (err: any) {
|
||||||
|
this._loading = false;
|
||||||
|
this._error = err.message;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this._params!.applicationCredentialAddedCallback(applicationCredential);
|
||||||
|
this.closeDialog();
|
||||||
|
}
|
||||||
|
|
||||||
|
static get styles(): CSSResultGroup {
|
||||||
|
return [
|
||||||
|
haStyleDialog,
|
||||||
|
css`
|
||||||
|
ha-dialog {
|
||||||
|
--mdc-dialog-max-width: 500px;
|
||||||
|
--dialog-z-index: 10;
|
||||||
|
}
|
||||||
|
.row {
|
||||||
|
display: flex;
|
||||||
|
padding: 8px 0;
|
||||||
|
}
|
||||||
|
ha-combo-box {
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 24px;
|
||||||
|
}
|
||||||
|
ha-textfield {
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 24px;
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"dialog-add-application-credential": DialogAddApplicationCredential;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,259 @@
|
|||||||
|
import { mdiDelete, mdiPlus } from "@mdi/js";
|
||||||
|
import { css, CSSResultGroup, html, LitElement, PropertyValues } from "lit";
|
||||||
|
import { customElement, property, query, state } from "lit/decorators";
|
||||||
|
import { classMap } from "lit/directives/class-map";
|
||||||
|
import memoizeOne from "memoize-one";
|
||||||
|
import type { HASSDomEvent } from "../../../common/dom/fire_event";
|
||||||
|
import { LocalizeFunc } from "../../../common/translations/localize";
|
||||||
|
import {
|
||||||
|
DataTableColumnContainer,
|
||||||
|
SelectionChangedEvent,
|
||||||
|
} from "../../../components/data-table/ha-data-table";
|
||||||
|
import "../../../components/data-table/ha-data-table-icon";
|
||||||
|
import "../../../components/ha-fab";
|
||||||
|
import "../../../components/ha-help-tooltip";
|
||||||
|
import "../../../components/ha-svg-icon";
|
||||||
|
import {
|
||||||
|
ApplicationCredential,
|
||||||
|
deleteApplicationCredential,
|
||||||
|
fetchApplicationCredentials,
|
||||||
|
} from "../../../data/application_credential";
|
||||||
|
import { domainToName } from "../../../data/integration";
|
||||||
|
import { showConfirmationDialog } from "../../../dialogs/generic/show-dialog-box";
|
||||||
|
import "../../../layouts/hass-tabs-subpage-data-table";
|
||||||
|
import type { HaTabsSubpageDataTable } from "../../../layouts/hass-tabs-subpage-data-table";
|
||||||
|
import { HomeAssistant, Route } from "../../../types";
|
||||||
|
import { configSections } from "../ha-panel-config";
|
||||||
|
import { showAddApplicationCredentialDialog } from "./show-dialog-add-application-credential";
|
||||||
|
|
||||||
|
@customElement("ha-config-application-credentials")
|
||||||
|
export class HaConfigApplicationCredentials extends LitElement {
|
||||||
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
|
|
||||||
|
@state() public _applicationCredentials: ApplicationCredential[] = [];
|
||||||
|
|
||||||
|
@property() public isWide!: boolean;
|
||||||
|
|
||||||
|
@property() public narrow!: boolean;
|
||||||
|
|
||||||
|
@property() public route!: Route;
|
||||||
|
|
||||||
|
@state() private _selected: string[] = [];
|
||||||
|
|
||||||
|
@query("hass-tabs-subpage-data-table", true)
|
||||||
|
private _dataTable!: HaTabsSubpageDataTable;
|
||||||
|
|
||||||
|
private _columns = memoizeOne(
|
||||||
|
(narrow: boolean, localize: LocalizeFunc): DataTableColumnContainer => {
|
||||||
|
const columns: DataTableColumnContainer<ApplicationCredential> = {
|
||||||
|
clientId: {
|
||||||
|
title: localize(
|
||||||
|
"ui.panel.config.application_credentials.picker.headers.client_id"
|
||||||
|
),
|
||||||
|
width: "25%",
|
||||||
|
direction: "asc",
|
||||||
|
grows: true,
|
||||||
|
template: (_, entry: ApplicationCredential) =>
|
||||||
|
html`${entry.client_id}`,
|
||||||
|
},
|
||||||
|
application: {
|
||||||
|
title: localize(
|
||||||
|
"ui.panel.config.application_credentials.picker.headers.application"
|
||||||
|
),
|
||||||
|
sortable: true,
|
||||||
|
width: "20%",
|
||||||
|
direction: "asc",
|
||||||
|
hidden: narrow,
|
||||||
|
template: (_, entry) => html`${domainToName(localize, entry.domain)}`,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
return columns;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
protected firstUpdated(changedProperties: PropertyValues) {
|
||||||
|
super.firstUpdated(changedProperties);
|
||||||
|
this._loadTranslations();
|
||||||
|
this._fetchApplicationCredentials();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected render() {
|
||||||
|
return html`
|
||||||
|
<hass-tabs-subpage-data-table
|
||||||
|
.hass=${this.hass}
|
||||||
|
.narrow=${this.narrow}
|
||||||
|
.route=${this.route}
|
||||||
|
backPath="/config"
|
||||||
|
.tabs=${configSections.devices}
|
||||||
|
.columns=${this._columns(this.narrow, this.hass.localize)}
|
||||||
|
.data=${this._applicationCredentials}
|
||||||
|
hasFab
|
||||||
|
selectable
|
||||||
|
@selection-changed=${this._handleSelectionChanged}
|
||||||
|
>
|
||||||
|
${this._selected.length
|
||||||
|
? html`
|
||||||
|
<div
|
||||||
|
class=${classMap({
|
||||||
|
"header-toolbar": this.narrow,
|
||||||
|
"table-header": !this.narrow,
|
||||||
|
})}
|
||||||
|
slot="header"
|
||||||
|
>
|
||||||
|
<p class="selected-txt">
|
||||||
|
${this.hass.localize(
|
||||||
|
"ui.panel.config.application_credentials.picker.selected",
|
||||||
|
"number",
|
||||||
|
this._selected.length
|
||||||
|
)}
|
||||||
|
</p>
|
||||||
|
<div class="header-btns">
|
||||||
|
${!this.narrow
|
||||||
|
? html`
|
||||||
|
<mwc-button
|
||||||
|
@click=${this._removeSelected}
|
||||||
|
class="warning"
|
||||||
|
>${this.hass.localize(
|
||||||
|
"ui.panel.config.application_credentials.picker.remove_selected.button"
|
||||||
|
)}</mwc-button
|
||||||
|
>
|
||||||
|
`
|
||||||
|
: html`
|
||||||
|
<ha-icon-button
|
||||||
|
class="warning"
|
||||||
|
id="remove-btn"
|
||||||
|
@click=${this._removeSelected}
|
||||||
|
.path=${mdiDelete}
|
||||||
|
.label=${this.hass.localize("ui.common.remove")}
|
||||||
|
></ha-icon-button>
|
||||||
|
<ha-help-tooltip
|
||||||
|
.label=${this.hass.localize(
|
||||||
|
"ui.panel.config.application_credentials.picker.remove_selected.button"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
</ha-help-tooltip>
|
||||||
|
`}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
: html``}
|
||||||
|
<ha-fab
|
||||||
|
slot="fab"
|
||||||
|
.label=${this.hass.localize(
|
||||||
|
"ui.panel.config.application_credentials.picker.add_application_credential"
|
||||||
|
)}
|
||||||
|
extended
|
||||||
|
@click=${this._addApplicationCredential}
|
||||||
|
>
|
||||||
|
<ha-svg-icon slot="icon" .path=${mdiPlus}></ha-svg-icon>
|
||||||
|
</ha-fab>
|
||||||
|
</hass-tabs-subpage-data-table>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _handleSelectionChanged(
|
||||||
|
ev: HASSDomEvent<SelectionChangedEvent>
|
||||||
|
): void {
|
||||||
|
this._selected = ev.detail.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _removeSelected() {
|
||||||
|
showConfirmationDialog(this, {
|
||||||
|
title: this.hass.localize(
|
||||||
|
`ui.panel.config.application_credentials.picker.remove_selected.confirm_title`,
|
||||||
|
"number",
|
||||||
|
this._selected.length
|
||||||
|
),
|
||||||
|
text: this.hass.localize(
|
||||||
|
"ui.panel.config.application_credentials.picker.remove_selected.confirm_text"
|
||||||
|
),
|
||||||
|
confirmText: this.hass.localize("ui.common.remove"),
|
||||||
|
dismissText: this.hass.localize("ui.common.cancel"),
|
||||||
|
confirm: async () => {
|
||||||
|
await Promise.all(
|
||||||
|
this._selected.map(async (applicationCredential) => {
|
||||||
|
await deleteApplicationCredential(this.hass, applicationCredential);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
this._dataTable.clearSelection();
|
||||||
|
this._fetchApplicationCredentials();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private async _loadTranslations() {
|
||||||
|
await this.hass.loadBackendTranslation("title", undefined, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async _fetchApplicationCredentials() {
|
||||||
|
this._applicationCredentials = await fetchApplicationCredentials(this.hass);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _addApplicationCredential() {
|
||||||
|
showAddApplicationCredentialDialog(this, {
|
||||||
|
applicationCredentialAddedCallback: async (
|
||||||
|
applicationCredential: ApplicationCredential
|
||||||
|
) => {
|
||||||
|
if (applicationCredential) {
|
||||||
|
this._applicationCredentials = [
|
||||||
|
...this._applicationCredentials,
|
||||||
|
applicationCredential,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
static get styles(): CSSResultGroup {
|
||||||
|
return css`
|
||||||
|
.table-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
height: 56px;
|
||||||
|
background-color: var(--mdc-text-field-fill-color, whitesmoke);
|
||||||
|
border-bottom: 1px solid
|
||||||
|
var(--mdc-text-field-idle-line-color, rgba(0, 0, 0, 0.42));
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
.header-toolbar {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
color: var(--secondary-text-color);
|
||||||
|
position: relative;
|
||||||
|
top: -4px;
|
||||||
|
}
|
||||||
|
.selected-txt {
|
||||||
|
font-weight: bold;
|
||||||
|
padding-left: 16px;
|
||||||
|
}
|
||||||
|
.table-header .selected-txt {
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
.header-toolbar .selected-txt {
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
.header-toolbar .header-btns {
|
||||||
|
margin-right: -12px;
|
||||||
|
}
|
||||||
|
.header-btns {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
.header-btns > mwc-button,
|
||||||
|
.header-btns > ha-icon-button {
|
||||||
|
margin: 8px;
|
||||||
|
}
|
||||||
|
ha-button-menu {
|
||||||
|
margin-left: 8px;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"ha-config-application-credentials": HaConfigApplicationCredentials;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,22 @@
|
|||||||
|
import { fireEvent } from "../../../common/dom/fire_event";
|
||||||
|
import { ApplicationCredential } from "../../../data/application_credential";
|
||||||
|
|
||||||
|
export interface AddApplicationCredentialDialogParams {
|
||||||
|
applicationCredentialAddedCallback: (
|
||||||
|
applicationCredential: ApplicationCredential
|
||||||
|
) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const loadAddApplicationCredentialDialog = () =>
|
||||||
|
import("./dialog-add-application-credential");
|
||||||
|
|
||||||
|
export const showAddApplicationCredentialDialog = (
|
||||||
|
element: HTMLElement,
|
||||||
|
dialogParams: AddApplicationCredentialDialogParams
|
||||||
|
): void => {
|
||||||
|
fireEvent(element, "show-dialog", {
|
||||||
|
dialogTag: "dialog-add-application-credential",
|
||||||
|
dialogImport: loadAddApplicationCredentialDialog,
|
||||||
|
dialogParams,
|
||||||
|
});
|
||||||
|
};
|
@ -16,9 +16,9 @@ import {
|
|||||||
} from "../../../components/data-table/ha-data-table";
|
} from "../../../components/data-table/ha-data-table";
|
||||||
import "../../../components/entity/ha-battery-icon";
|
import "../../../components/entity/ha-battery-icon";
|
||||||
import "../../../components/ha-button-menu";
|
import "../../../components/ha-button-menu";
|
||||||
|
import "../../../components/ha-check-list-item";
|
||||||
import "../../../components/ha-fab";
|
import "../../../components/ha-fab";
|
||||||
import "../../../components/ha-icon-button";
|
import "../../../components/ha-icon-button";
|
||||||
import "../../../components/ha-check-list-item";
|
|
||||||
import { AreaRegistryEntry } from "../../../data/area_registry";
|
import { AreaRegistryEntry } from "../../../data/area_registry";
|
||||||
import { ConfigEntry } from "../../../data/config_entries";
|
import { ConfigEntry } from "../../../data/config_entries";
|
||||||
import {
|
import {
|
||||||
@ -36,6 +36,7 @@ import "../../../layouts/hass-tabs-subpage-data-table";
|
|||||||
import { haStyle } from "../../../resources/styles";
|
import { haStyle } from "../../../resources/styles";
|
||||||
import { HomeAssistant, Route } from "../../../types";
|
import { HomeAssistant, Route } from "../../../types";
|
||||||
import { configSections } from "../ha-panel-config";
|
import { configSections } from "../ha-panel-config";
|
||||||
|
import "../integrations/ha-integration-overflow-menu";
|
||||||
import { showZWaveJSAddNodeDialog } from "../integrations/integration-panels/zwave_js/show-dialog-zwave_js-add-node";
|
import { showZWaveJSAddNodeDialog } from "../integrations/integration-panels/zwave_js/show-dialog-zwave_js-add-node";
|
||||||
|
|
||||||
interface DeviceRowData extends DeviceRegistryEntry {
|
interface DeviceRowData extends DeviceRegistryEntry {
|
||||||
@ -408,6 +409,10 @@ export class HaConfigDeviceDashboard extends LitElement {
|
|||||||
(filteredConfigEntry.domain === "zha" ||
|
(filteredConfigEntry.domain === "zha" ||
|
||||||
filteredConfigEntry.domain === "zwave_js")}
|
filteredConfigEntry.domain === "zwave_js")}
|
||||||
>
|
>
|
||||||
|
<ha-integration-overflow-menu
|
||||||
|
.hass=${this.hass}
|
||||||
|
slot="toolbar-icon"
|
||||||
|
></ha-integration-overflow-menu>
|
||||||
${!filteredConfigEntry
|
${!filteredConfigEntry
|
||||||
? ""
|
? ""
|
||||||
: filteredConfigEntry.domain === "zwave_js"
|
: filteredConfigEntry.domain === "zwave_js"
|
||||||
|
@ -61,6 +61,7 @@ import { SubscribeMixin } from "../../../mixins/subscribe-mixin";
|
|||||||
import { haStyle } from "../../../resources/styles";
|
import { haStyle } from "../../../resources/styles";
|
||||||
import type { HomeAssistant, Route } from "../../../types";
|
import type { HomeAssistant, Route } from "../../../types";
|
||||||
import { configSections } from "../ha-panel-config";
|
import { configSections } from "../ha-panel-config";
|
||||||
|
import "../integrations/ha-integration-overflow-menu";
|
||||||
import { DialogEntityEditor } from "./dialog-entity-editor";
|
import { DialogEntityEditor } from "./dialog-entity-editor";
|
||||||
import {
|
import {
|
||||||
loadEntityEditorDialog,
|
loadEntityEditorDialog,
|
||||||
@ -526,6 +527,10 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) {
|
|||||||
id="entity_id"
|
id="entity_id"
|
||||||
.hasFab=${includeZHAFab}
|
.hasFab=${includeZHAFab}
|
||||||
>
|
>
|
||||||
|
<ha-integration-overflow-menu
|
||||||
|
.hass=${this.hass}
|
||||||
|
slot="toolbar-icon"
|
||||||
|
></ha-integration-overflow-menu>
|
||||||
${this._selectedEntities.length
|
${this._selectedEntities.length
|
||||||
? html`
|
? html`
|
||||||
<div
|
<div
|
||||||
|
@ -479,6 +479,11 @@ class HaPanelConfig extends HassRouterPage {
|
|||||||
"./integrations/integration-panels/zwave_js/zwave_js-config-router"
|
"./integrations/integration-panels/zwave_js/zwave_js-config-router"
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
application_credentials: {
|
||||||
|
tag: "ha-config-application-credentials",
|
||||||
|
load: () =>
|
||||||
|
import("./application_credentials/ha-config-application-credentials"),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -35,6 +35,7 @@ import { SubscribeMixin } from "../../../mixins/subscribe-mixin";
|
|||||||
import { HomeAssistant, Route } from "../../../types";
|
import { HomeAssistant, Route } from "../../../types";
|
||||||
import { showEntityEditorDialog } from "../entities/show-dialog-entity-editor";
|
import { showEntityEditorDialog } from "../entities/show-dialog-entity-editor";
|
||||||
import { configSections } from "../ha-panel-config";
|
import { configSections } from "../ha-panel-config";
|
||||||
|
import "../integrations/ha-integration-overflow-menu";
|
||||||
import { HELPER_DOMAINS } from "./const";
|
import { HELPER_DOMAINS } from "./const";
|
||||||
import { showHelperDetailDialog } from "./show-dialog-helper-detail";
|
import { showHelperDetailDialog } from "./show-dialog-helper-detail";
|
||||||
|
|
||||||
@ -210,6 +211,10 @@ export class HaConfigHelpers extends SubscribeMixin(LitElement) {
|
|||||||
"ui.panel.config.helpers.picker.no_helpers"
|
"ui.panel.config.helpers.picker.no_helpers"
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
|
<ha-integration-overflow-menu
|
||||||
|
.hass=${this.hass}
|
||||||
|
slot="toolbar-icon"
|
||||||
|
></ha-integration-overflow-menu>
|
||||||
<ha-fab
|
<ha-fab
|
||||||
slot="fab"
|
slot="fab"
|
||||||
.label=${this.hass.localize(
|
.label=${this.hass.localize(
|
||||||
|
@ -13,21 +13,20 @@ import {
|
|||||||
import { customElement, property, state } from "lit/decorators";
|
import { customElement, property, state } from "lit/decorators";
|
||||||
import { ifDefined } from "lit/directives/if-defined";
|
import { ifDefined } from "lit/directives/if-defined";
|
||||||
import memoizeOne from "memoize-one";
|
import memoizeOne from "memoize-one";
|
||||||
|
import { isComponentLoaded } from "../../../common/config/is_component_loaded";
|
||||||
import type { HASSDomEvent } from "../../../common/dom/fire_event";
|
import type { HASSDomEvent } from "../../../common/dom/fire_event";
|
||||||
import { navigate } from "../../../common/navigate";
|
import { navigate } from "../../../common/navigate";
|
||||||
import "../../../components/search-input";
|
|
||||||
import { caseInsensitiveStringCompare } from "../../../common/string/compare";
|
import { caseInsensitiveStringCompare } from "../../../common/string/compare";
|
||||||
import type { LocalizeFunc } from "../../../common/translations/localize";
|
import type { LocalizeFunc } from "../../../common/translations/localize";
|
||||||
import { extractSearchParam } from "../../../common/url/search-params";
|
import { extractSearchParam } from "../../../common/url/search-params";
|
||||||
import { nextRender } from "../../../common/util/render-status";
|
import { nextRender } from "../../../common/util/render-status";
|
||||||
import "../../../components/ha-button-menu";
|
import "../../../components/ha-button-menu";
|
||||||
|
import "../../../components/ha-check-list-item";
|
||||||
import "../../../components/ha-checkbox";
|
import "../../../components/ha-checkbox";
|
||||||
import "../../../components/ha-fab";
|
import "../../../components/ha-fab";
|
||||||
import "../../../components/ha-icon-button";
|
import "../../../components/ha-icon-button";
|
||||||
import "../../../components/ha-svg-icon";
|
import "../../../components/ha-svg-icon";
|
||||||
import "../../../components/ha-check-list-item";
|
import "../../../components/search-input";
|
||||||
|
|
||||||
import { isComponentLoaded } from "../../../common/config/is_component_loaded";
|
|
||||||
import { ConfigEntry, getConfigEntries } from "../../../data/config_entries";
|
import { ConfigEntry, getConfigEntries } from "../../../data/config_entries";
|
||||||
import {
|
import {
|
||||||
getConfigFlowHandlers,
|
getConfigFlowHandlers,
|
||||||
@ -40,6 +39,7 @@ import {
|
|||||||
DeviceRegistryEntry,
|
DeviceRegistryEntry,
|
||||||
subscribeDeviceRegistry,
|
subscribeDeviceRegistry,
|
||||||
} from "../../../data/device_registry";
|
} from "../../../data/device_registry";
|
||||||
|
import { fetchDiagnosticHandlers } from "../../../data/diagnostics";
|
||||||
import {
|
import {
|
||||||
EntityRegistryEntry,
|
EntityRegistryEntry,
|
||||||
subscribeEntityRegistry,
|
subscribeEntityRegistry,
|
||||||
@ -62,12 +62,12 @@ import { SubscribeMixin } from "../../../mixins/subscribe-mixin";
|
|||||||
import { haStyle } from "../../../resources/styles";
|
import { haStyle } from "../../../resources/styles";
|
||||||
import type { HomeAssistant, Route } from "../../../types";
|
import type { HomeAssistant, Route } from "../../../types";
|
||||||
import { configSections } from "../ha-panel-config";
|
import { configSections } from "../ha-panel-config";
|
||||||
|
import { HELPER_DOMAINS } from "../helpers/const";
|
||||||
import "./ha-config-flow-card";
|
import "./ha-config-flow-card";
|
||||||
import "./ha-ignored-config-entry-card";
|
import "./ha-ignored-config-entry-card";
|
||||||
import "./ha-integration-card";
|
import "./ha-integration-card";
|
||||||
import type { HaIntegrationCard } from "./ha-integration-card";
|
import type { HaIntegrationCard } from "./ha-integration-card";
|
||||||
import { fetchDiagnosticHandlers } from "../../../data/diagnostics";
|
import "./ha-integration-overflow-menu";
|
||||||
import { HELPER_DOMAINS } from "../helpers/const";
|
|
||||||
|
|
||||||
export interface ConfigEntryUpdatedEvent {
|
export interface ConfigEntryUpdatedEvent {
|
||||||
entry: ConfigEntry;
|
entry: ConfigEntry;
|
||||||
@ -302,36 +302,46 @@ class HaConfigIntegrations extends SubscribeMixin(LitElement) {
|
|||||||
this._filter
|
this._filter
|
||||||
);
|
);
|
||||||
|
|
||||||
const filterMenu = html`<div
|
const filterMenu = html`
|
||||||
slot=${ifDefined(this.narrow ? "toolbar-icon" : "suffix")}
|
<div slot=${ifDefined(this.narrow ? "toolbar-icon" : "suffix")}>
|
||||||
>
|
<div class="menu-badge-container">
|
||||||
${!this._showDisabled && this.narrow && disabledCount
|
${!this._showDisabled && this.narrow && disabledCount
|
||||||
? html`<span class="badge">${disabledCount}</span>`
|
? html`<span class="badge">${disabledCount}</span>`
|
||||||
: ""}
|
: ""}
|
||||||
<ha-button-menu
|
<ha-button-menu
|
||||||
corner="BOTTOM_START"
|
corner="BOTTOM_START"
|
||||||
multi
|
multi
|
||||||
@action=${this._handleMenuAction}
|
@action=${this._handleMenuAction}
|
||||||
@click=${this._preventDefault}
|
@click=${this._preventDefault}
|
||||||
>
|
>
|
||||||
<ha-icon-button
|
<ha-icon-button
|
||||||
slot="trigger"
|
slot="trigger"
|
||||||
.label=${this.hass.localize("ui.common.menu")}
|
.label=${this.hass.localize("ui.common.menu")}
|
||||||
.path=${mdiFilterVariant}
|
.path=${mdiFilterVariant}
|
||||||
>
|
>
|
||||||
</ha-icon-button>
|
</ha-icon-button>
|
||||||
<ha-check-list-item left .selected=${this._showIgnored}>
|
<ha-check-list-item left .selected=${this._showIgnored}>
|
||||||
${this.hass.localize(
|
${this.hass.localize(
|
||||||
"ui.panel.config.integrations.ignore.show_ignored"
|
"ui.panel.config.integrations.ignore.show_ignored"
|
||||||
)}
|
)}
|
||||||
</ha-check-list-item>
|
</ha-check-list-item>
|
||||||
<ha-check-list-item left .selected=${this._showDisabled}>
|
<ha-check-list-item left .selected=${this._showDisabled}>
|
||||||
${this.hass.localize(
|
${this.hass.localize(
|
||||||
"ui.panel.config.integrations.disable.show_disabled"
|
"ui.panel.config.integrations.disable.show_disabled"
|
||||||
)}
|
)}
|
||||||
</ha-check-list-item>
|
</ha-check-list-item>
|
||||||
</ha-button-menu>
|
</ha-button-menu>
|
||||||
</div>`;
|
</div>
|
||||||
|
${this.narrow
|
||||||
|
? html`
|
||||||
|
<ha-integration-overflow-menu
|
||||||
|
.hass=${this.hass}
|
||||||
|
slot="toolbar-icon"
|
||||||
|
></ha-integration-overflow-menu>
|
||||||
|
`
|
||||||
|
: ""}
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
<hass-tabs-subpage
|
<hass-tabs-subpage
|
||||||
@ -357,6 +367,10 @@ class HaConfigIntegrations extends SubscribeMixin(LitElement) {
|
|||||||
${filterMenu}
|
${filterMenu}
|
||||||
`
|
`
|
||||||
: html`
|
: html`
|
||||||
|
<ha-integration-overflow-menu
|
||||||
|
.hass=${this.hass}
|
||||||
|
slot="toolbar-icon"
|
||||||
|
></ha-integration-overflow-menu>
|
||||||
<div class="search">
|
<div class="search">
|
||||||
<search-input
|
<search-input
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
@ -797,10 +811,13 @@ class HaConfigIntegrations extends SubscribeMixin(LitElement) {
|
|||||||
padding: 0px 4px;
|
padding: 0px 4px;
|
||||||
color: var(--text-primary-color);
|
color: var(--text-primary-color);
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: 14px;
|
right: 0px;
|
||||||
top: 8px;
|
top: 4px;
|
||||||
font-size: 0.65em;
|
font-size: 0.65em;
|
||||||
}
|
}
|
||||||
|
.menu-badge-container {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
ha-button-menu {
|
ha-button-menu {
|
||||||
color: var(--primary-text-color);
|
color: var(--primary-text-color);
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,45 @@
|
|||||||
|
import { mdiDotsVertical } from "@mdi/js";
|
||||||
|
import { html, LitElement } from "lit";
|
||||||
|
import { customElement, property } from "lit/decorators";
|
||||||
|
import "../../../components/ha-button-menu";
|
||||||
|
import "../../../components/ha-clickable-list-item";
|
||||||
|
import "../../../components/ha-icon-button";
|
||||||
|
import type { HomeAssistant } from "../../../types";
|
||||||
|
|
||||||
|
@customElement("ha-integration-overflow-menu")
|
||||||
|
export class HaIntegrationOverflowMenu extends LitElement {
|
||||||
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
|
|
||||||
|
protected render() {
|
||||||
|
return html`
|
||||||
|
<ha-button-menu activatable corner="BOTTOM_START">
|
||||||
|
<ha-icon-button
|
||||||
|
slot="trigger"
|
||||||
|
.label=${this.hass.localize("ui.common.menu")}
|
||||||
|
.path=${mdiDotsVertical}
|
||||||
|
></ha-icon-button>
|
||||||
|
<ha-clickable-list-item
|
||||||
|
@click=${this._entryClicked}
|
||||||
|
href="/config/application_credentials"
|
||||||
|
aria-label=${this.hass.localize(
|
||||||
|
"ui.panel.config.application_credentials.caption"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
${this.hass.localize(
|
||||||
|
"ui.panel.config.application_credentials.caption"
|
||||||
|
)}
|
||||||
|
</ha-clickable-list-item>
|
||||||
|
</ha-button-menu>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _entryClicked(ev) {
|
||||||
|
ev.currentTarget.blur();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"ha-integration-overflow-menu": HaIntegrationOverflowMenu;
|
||||||
|
}
|
||||||
|
}
|
@ -2855,6 +2855,30 @@
|
|||||||
"create": "Create"
|
"create": "Create"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"application_credentials": {
|
||||||
|
"caption": "Application Credentials",
|
||||||
|
"description": "Manage the OAuth Application Credentials used by Integrations",
|
||||||
|
"editor": {
|
||||||
|
"caption": "Add Application Credential",
|
||||||
|
"create": "Create",
|
||||||
|
"domain": "Integration",
|
||||||
|
"client_id": "OAuth Client ID",
|
||||||
|
"client_secret": "OAuth Client Secret"
|
||||||
|
},
|
||||||
|
"picker": {
|
||||||
|
"add_application_credential": "Add Application Credential",
|
||||||
|
"headers": {
|
||||||
|
"client_id": "OAuth Client ID",
|
||||||
|
"application": "Integration"
|
||||||
|
},
|
||||||
|
"remove_selected": {
|
||||||
|
"button": "Remove selected",
|
||||||
|
"confirm_title": "Do you want to remove {number} {number, plural,\n one {credential}\n other {credentialss}\n}?",
|
||||||
|
"confirm_text": "Application Credentials in use by an integration may not be removed."
|
||||||
|
},
|
||||||
|
"selected": "{number} selected"
|
||||||
|
}
|
||||||
|
},
|
||||||
"mqtt": {
|
"mqtt": {
|
||||||
"title": "MQTT",
|
"title": "MQTT",
|
||||||
"description_publish": "Publish a packet",
|
"description_publish": "Publish a packet",
|
||||||
|
Loading…
x
Reference in New Issue
Block a user