mirror of
https://github.com/home-assistant/frontend.git
synced 2025-04-24 21:37:21 +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}
|
||||
.indeterminate=${this._checkedRows.length &&
|
||||
this._checkedRows.length !== this._checkableRowsCount}
|
||||
.checked=${this._checkedRows.length ===
|
||||
this._checkableRowsCount}
|
||||
.checked=${this._checkedRows.length &&
|
||||
this._checkedRows.length === this._checkableRowsCount}
|
||||
>
|
||||
</ha-checkbox>
|
||||
</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";
|
||||
import "../../../components/entity/ha-battery-icon";
|
||||
import "../../../components/ha-button-menu";
|
||||
import "../../../components/ha-check-list-item";
|
||||
import "../../../components/ha-fab";
|
||||
import "../../../components/ha-icon-button";
|
||||
import "../../../components/ha-check-list-item";
|
||||
import { AreaRegistryEntry } from "../../../data/area_registry";
|
||||
import { ConfigEntry } from "../../../data/config_entries";
|
||||
import {
|
||||
@ -36,6 +36,7 @@ import "../../../layouts/hass-tabs-subpage-data-table";
|
||||
import { haStyle } from "../../../resources/styles";
|
||||
import { HomeAssistant, Route } from "../../../types";
|
||||
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";
|
||||
|
||||
interface DeviceRowData extends DeviceRegistryEntry {
|
||||
@ -408,6 +409,10 @@ export class HaConfigDeviceDashboard extends LitElement {
|
||||
(filteredConfigEntry.domain === "zha" ||
|
||||
filteredConfigEntry.domain === "zwave_js")}
|
||||
>
|
||||
<ha-integration-overflow-menu
|
||||
.hass=${this.hass}
|
||||
slot="toolbar-icon"
|
||||
></ha-integration-overflow-menu>
|
||||
${!filteredConfigEntry
|
||||
? ""
|
||||
: filteredConfigEntry.domain === "zwave_js"
|
||||
|
@ -61,6 +61,7 @@ import { SubscribeMixin } from "../../../mixins/subscribe-mixin";
|
||||
import { haStyle } from "../../../resources/styles";
|
||||
import type { HomeAssistant, Route } from "../../../types";
|
||||
import { configSections } from "../ha-panel-config";
|
||||
import "../integrations/ha-integration-overflow-menu";
|
||||
import { DialogEntityEditor } from "./dialog-entity-editor";
|
||||
import {
|
||||
loadEntityEditorDialog,
|
||||
@ -526,6 +527,10 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) {
|
||||
id="entity_id"
|
||||
.hasFab=${includeZHAFab}
|
||||
>
|
||||
<ha-integration-overflow-menu
|
||||
.hass=${this.hass}
|
||||
slot="toolbar-icon"
|
||||
></ha-integration-overflow-menu>
|
||||
${this._selectedEntities.length
|
||||
? html`
|
||||
<div
|
||||
|
@ -479,6 +479,11 @@ class HaPanelConfig extends HassRouterPage {
|
||||
"./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 { showEntityEditorDialog } from "../entities/show-dialog-entity-editor";
|
||||
import { configSections } from "../ha-panel-config";
|
||||
import "../integrations/ha-integration-overflow-menu";
|
||||
import { HELPER_DOMAINS } from "./const";
|
||||
import { showHelperDetailDialog } from "./show-dialog-helper-detail";
|
||||
|
||||
@ -210,6 +211,10 @@ export class HaConfigHelpers extends SubscribeMixin(LitElement) {
|
||||
"ui.panel.config.helpers.picker.no_helpers"
|
||||
)}
|
||||
>
|
||||
<ha-integration-overflow-menu
|
||||
.hass=${this.hass}
|
||||
slot="toolbar-icon"
|
||||
></ha-integration-overflow-menu>
|
||||
<ha-fab
|
||||
slot="fab"
|
||||
.label=${this.hass.localize(
|
||||
|
@ -13,21 +13,20 @@ import {
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { ifDefined } from "lit/directives/if-defined";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { isComponentLoaded } from "../../../common/config/is_component_loaded";
|
||||
import type { HASSDomEvent } from "../../../common/dom/fire_event";
|
||||
import { navigate } from "../../../common/navigate";
|
||||
import "../../../components/search-input";
|
||||
import { caseInsensitiveStringCompare } from "../../../common/string/compare";
|
||||
import type { LocalizeFunc } from "../../../common/translations/localize";
|
||||
import { extractSearchParam } from "../../../common/url/search-params";
|
||||
import { nextRender } from "../../../common/util/render-status";
|
||||
import "../../../components/ha-button-menu";
|
||||
import "../../../components/ha-check-list-item";
|
||||
import "../../../components/ha-checkbox";
|
||||
import "../../../components/ha-fab";
|
||||
import "../../../components/ha-icon-button";
|
||||
import "../../../components/ha-svg-icon";
|
||||
import "../../../components/ha-check-list-item";
|
||||
|
||||
import { isComponentLoaded } from "../../../common/config/is_component_loaded";
|
||||
import "../../../components/search-input";
|
||||
import { ConfigEntry, getConfigEntries } from "../../../data/config_entries";
|
||||
import {
|
||||
getConfigFlowHandlers,
|
||||
@ -40,6 +39,7 @@ import {
|
||||
DeviceRegistryEntry,
|
||||
subscribeDeviceRegistry,
|
||||
} from "../../../data/device_registry";
|
||||
import { fetchDiagnosticHandlers } from "../../../data/diagnostics";
|
||||
import {
|
||||
EntityRegistryEntry,
|
||||
subscribeEntityRegistry,
|
||||
@ -62,12 +62,12 @@ import { SubscribeMixin } from "../../../mixins/subscribe-mixin";
|
||||
import { haStyle } from "../../../resources/styles";
|
||||
import type { HomeAssistant, Route } from "../../../types";
|
||||
import { configSections } from "../ha-panel-config";
|
||||
import { HELPER_DOMAINS } from "../helpers/const";
|
||||
import "./ha-config-flow-card";
|
||||
import "./ha-ignored-config-entry-card";
|
||||
import "./ha-integration-card";
|
||||
import type { HaIntegrationCard } from "./ha-integration-card";
|
||||
import { fetchDiagnosticHandlers } from "../../../data/diagnostics";
|
||||
import { HELPER_DOMAINS } from "../helpers/const";
|
||||
import "./ha-integration-overflow-menu";
|
||||
|
||||
export interface ConfigEntryUpdatedEvent {
|
||||
entry: ConfigEntry;
|
||||
@ -302,36 +302,46 @@ class HaConfigIntegrations extends SubscribeMixin(LitElement) {
|
||||
this._filter
|
||||
);
|
||||
|
||||
const filterMenu = html`<div
|
||||
slot=${ifDefined(this.narrow ? "toolbar-icon" : "suffix")}
|
||||
>
|
||||
${!this._showDisabled && this.narrow && disabledCount
|
||||
? html`<span class="badge">${disabledCount}</span>`
|
||||
: ""}
|
||||
<ha-button-menu
|
||||
corner="BOTTOM_START"
|
||||
multi
|
||||
@action=${this._handleMenuAction}
|
||||
@click=${this._preventDefault}
|
||||
>
|
||||
<ha-icon-button
|
||||
slot="trigger"
|
||||
.label=${this.hass.localize("ui.common.menu")}
|
||||
.path=${mdiFilterVariant}
|
||||
>
|
||||
</ha-icon-button>
|
||||
<ha-check-list-item left .selected=${this._showIgnored}>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.integrations.ignore.show_ignored"
|
||||
)}
|
||||
</ha-check-list-item>
|
||||
<ha-check-list-item left .selected=${this._showDisabled}>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.integrations.disable.show_disabled"
|
||||
)}
|
||||
</ha-check-list-item>
|
||||
</ha-button-menu>
|
||||
</div>`;
|
||||
const filterMenu = html`
|
||||
<div slot=${ifDefined(this.narrow ? "toolbar-icon" : "suffix")}>
|
||||
<div class="menu-badge-container">
|
||||
${!this._showDisabled && this.narrow && disabledCount
|
||||
? html`<span class="badge">${disabledCount}</span>`
|
||||
: ""}
|
||||
<ha-button-menu
|
||||
corner="BOTTOM_START"
|
||||
multi
|
||||
@action=${this._handleMenuAction}
|
||||
@click=${this._preventDefault}
|
||||
>
|
||||
<ha-icon-button
|
||||
slot="trigger"
|
||||
.label=${this.hass.localize("ui.common.menu")}
|
||||
.path=${mdiFilterVariant}
|
||||
>
|
||||
</ha-icon-button>
|
||||
<ha-check-list-item left .selected=${this._showIgnored}>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.integrations.ignore.show_ignored"
|
||||
)}
|
||||
</ha-check-list-item>
|
||||
<ha-check-list-item left .selected=${this._showDisabled}>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.integrations.disable.show_disabled"
|
||||
)}
|
||||
</ha-check-list-item>
|
||||
</ha-button-menu>
|
||||
</div>
|
||||
${this.narrow
|
||||
? html`
|
||||
<ha-integration-overflow-menu
|
||||
.hass=${this.hass}
|
||||
slot="toolbar-icon"
|
||||
></ha-integration-overflow-menu>
|
||||
`
|
||||
: ""}
|
||||
</div>
|
||||
`;
|
||||
|
||||
return html`
|
||||
<hass-tabs-subpage
|
||||
@ -357,6 +367,10 @@ class HaConfigIntegrations extends SubscribeMixin(LitElement) {
|
||||
${filterMenu}
|
||||
`
|
||||
: html`
|
||||
<ha-integration-overflow-menu
|
||||
.hass=${this.hass}
|
||||
slot="toolbar-icon"
|
||||
></ha-integration-overflow-menu>
|
||||
<div class="search">
|
||||
<search-input
|
||||
.hass=${this.hass}
|
||||
@ -797,10 +811,13 @@ class HaConfigIntegrations extends SubscribeMixin(LitElement) {
|
||||
padding: 0px 4px;
|
||||
color: var(--text-primary-color);
|
||||
position: absolute;
|
||||
right: 14px;
|
||||
top: 8px;
|
||||
right: 0px;
|
||||
top: 4px;
|
||||
font-size: 0.65em;
|
||||
}
|
||||
.menu-badge-container {
|
||||
position: relative;
|
||||
}
|
||||
ha-button-menu {
|
||||
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"
|
||||
}
|
||||
},
|
||||
"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": {
|
||||
"title": "MQTT",
|
||||
"description_publish": "Publish a packet",
|
||||
|
Loading…
x
Reference in New Issue
Block a user