Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
26 changes: 24 additions & 2 deletions src/data/integration.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,32 @@
import { LocalizeFunc } from "../common/translations/localize";
import { HomeAssistant } from "../types";

export const integrationDocsUrl = (domain: string) =>
`https://www.home-assistant.io/integrations/${domain}`;
export interface IntegrationManifest {
is_built_in: boolean;
domain: string;
name: string;
config_flow: boolean;
documentation: string;
dependencies?: string[];
after_dependencies?: string[];
codeowners?: string[];
requirements?: string[];
ssdp?: Array<{ manufacturer?: string; modelName?: string; st?: string }>;
zeroconf?: string[];
homekit?: { models: string[] };
quality_scale?: string;
}

export const integrationIssuesUrl = (domain: string) =>
`https://github.com/home-assistant/home-assistant/issues?q=is%3Aissue+is%3Aopen+label%3A%22integration%3A+${domain}%22`;

export const domainToName = (localize: LocalizeFunc, domain: string) =>
localize(`domain.${domain}`) || domain;

export const fetchIntegrationManifests = (hass: HomeAssistant) =>
hass.callWS<IntegrationManifest[]>({ type: "manifest/list" });

export const fetchIntegrationManifest = (
hass: HomeAssistant,
integration: string
) => hass.callWS<IntegrationManifest>({ type: "manifest/get", integration });
2 changes: 2 additions & 0 deletions src/data/system_log.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,6 @@ export const fetchSystemLog = (hass: HomeAssistant) =>
export const getLoggedErrorIntegration = (item: LoggedError) =>
item.name.startsWith("homeassistant.components.")
? item.name.split(".")[2]
: item.name.startsWith("custom_components.")
? item.name.split(".")[1]
: undefined;
103 changes: 69 additions & 34 deletions src/panels/developer-tools/info/integrations-card.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,75 +9,110 @@ import {
} from "lit-element";
import memoizeOne from "memoize-one";
import {
integrationDocsUrl,
integrationIssuesUrl,
IntegrationManifest,
fetchIntegrationManifests,
} from "../../../data/integration";
import { HomeAssistant } from "../../../types";

@customElement("integrations-card")
class IntegrationsCard extends LitElement {
@property() public hass!: HomeAssistant;

@property() private _manifests?: { [domain: string]: IntegrationManifest };

private _sortedIntegrations = memoizeOne((components: string[]) => {
return components.filter((comp) => !comp.includes(".")).sort();
});

firstUpdated(changedProps) {
super.firstUpdated(changedProps);
this._fetchManifests();
}

protected render(): TemplateResult {
return html`
<ha-card header="Integrations">
<table class="card-content">
<tbody>
${this._sortedIntegrations(this.hass!.config.components).map(
(domain) => html`
<tr>
<td>
<img
loading="lazy"
src="https://brands.home-assistant.io/_/${domain}/icon.png"
referrerpolicy="no-referrer"
/>
</td>
<td>${domain}</td>
<td>
<a
href=${integrationDocsUrl(domain)}
target="_blank"
rel="noreferrer"
>
Documentation
</a>
</td>
<td>
<a
href=${integrationIssuesUrl(domain)}
target="_blank"
rel="noreferrer"
>
Issues
</a>
</td>
</tr>
`
(domain) => {
const manifest = this._manifests && this._manifests[domain];
return html`
<tr>
<td>
<img
loading="lazy"
src="https://brands.home-assistant.io/_/${domain}/icon.png"
referrerpolicy="no-referrer"
/>
</td>
<td class="name">
${manifest?.name}<br />
<span class="domain">${domain}</span>
</td>
${!manifest
? ""
: html`
<td>
<a
href=${manifest.documentation}
target="_blank"
rel="noreferrer"
>
Documentation
</a>
</td>
${!manifest.is_built_in
? ""
: html`
<td>
<a
href=${integrationIssuesUrl(domain)}
target="_blank"
rel="noreferrer"
>
Issues
</a>
</td>
`}
`}
</tr>
`;
}
)}
</tbody>
</table>
</ha-card>
`;
}

private async _fetchManifests() {
const manifests = {};
for (const manifest of await fetchIntegrationManifests(this.hass)) {
manifests[manifest.domain] = manifest;
}
this._manifests = manifests;
}

static get styles(): CSSResult {
return css`
td {
line-height: 2em;
padding: 0 8px;
}
td:first-child {
padding-left: 0;
}
td.name {
padding: 8px;
}
.domain {
color: var(--secondary-text-color);
}
img {
display: block;
max-height: 24px;
max-width: 24px;
max-height: 40px;
max-width: 40px;
}
a {
color: var(--primary-color);
Expand Down
62 changes: 48 additions & 14 deletions src/panels/developer-tools/logs/dialog-system-log-detail.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,9 @@ import {
import "../../../components/dialog/ha-paper-dialog";
import {
domainToName,
integrationDocsUrl,
integrationIssuesUrl,
IntegrationManifest,
fetchIntegrationManifest,
} from "../../../data/integration";
import { getLoggedErrorIntegration } from "../../../data/system_log";
import { PolymerChangedEvent } from "../../../polymer-types";
Expand All @@ -25,11 +26,25 @@ class DialogSystemLogDetail extends LitElement {

@property() private _params?: SystemLogDetailDialogParams;

@property() private _manifest?: IntegrationManifest;

public async showDialog(params: SystemLogDetailDialogParams): Promise<void> {
this._params = params;
this._manifest = undefined;
await this.updateComplete;
}

protected updated(changedProps) {
super.updated(changedProps);
if (!changedProps.has("_params") || !this._params) {
return;
}
const integration = getLoggedErrorIntegration(this._params.item);
if (integration) {
this._fetchManifest(integration);
}
}

protected render(): TemplateResult {
if (!this._params) {
return html``;
Expand Down Expand Up @@ -58,19 +73,30 @@ class DialogSystemLogDetail extends LitElement {
${integration
? html`
<br />
Integration: ${domainToName(this.hass.localize, integration)}
(<a
href=${integrationDocsUrl(integration)}
target="_blank"
rel="noreferrer"
>documentation</a
>,
<a
href=${integrationIssuesUrl(integration)}
target="_blank"
rel="noreferrer"
>issues</a
>)
Integration:
${this._manifest
? this._manifest.name
: domainToName(this.hass.localize, integration)}
${!this._manifest ||
// Can happen with custom integrations
!this._manifest.documentation
? ""
: html`
(<a
href=${this._manifest.documentation}
target="_blank"
rel="noreferrer"
>documentation</a
>${!this._manifest.is_built_in
? ""
: html`,
<a
href=${integrationIssuesUrl(integration)}
target="_blank"
rel="noreferrer"
>issues</a
>`})
`}
`
: ""}
<br />
Expand Down Expand Up @@ -100,6 +126,14 @@ class DialogSystemLogDetail extends LitElement {
`;
}

private async _fetchManifest(integration: string) {
try {
this._manifest = await fetchIntegrationManifest(this.hass, integration);
} catch (err) {
// Ignore if loading manifest fails. Probably bad JSON in manifest
}
}

private _openedChanged(ev: PolymerChangedEvent<boolean>): void {
if (!(ev.detail as any).value) {
this._params = undefined;
Expand Down