Add Supervisor logs to core page (#12410)

This commit is contained in:
Zack Barett 2022-04-25 10:35:03 -05:00 committed by GitHub
parent 8e55c83996
commit 3f04abfa9d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 195 additions and 55 deletions

View File

@ -1,4 +1,9 @@
import { HomeAssistant } from "../types"; import { HomeAssistant } from "../types";
export interface LogProvider {
key: string;
name: string;
}
export const fetchErrorLog = (hass: HomeAssistant) => export const fetchErrorLog = (hass: HomeAssistant) =>
hass.callApi<string>("GET", "error_log"); hass.callApi<string>("GET", "error_log");

View File

@ -1,11 +1,53 @@
import "@material/mwc-button"; import "@material/mwc-button";
import "@material/mwc-list/mwc-list-item";
import { mdiRefresh } from "@mdi/js"; import { mdiRefresh } from "@mdi/js";
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import {
import { property, state } from "lit/decorators"; css,
CSSResultGroup,
html,
LitElement,
PropertyValues,
TemplateResult,
} from "lit";
import { customElement, property, state } from "lit/decorators";
import { isComponentLoaded } from "../../../common/config/is_component_loaded";
import "../../../components/ha-alert";
import "../../../components/ha-card";
import "../../../components/ha-icon-button"; import "../../../components/ha-icon-button";
import { fetchErrorLog } from "../../../data/error_log"; import "../../../components/ha-select";
import { fetchErrorLog, LogProvider } from "../../../data/error_log";
import { extractApiErrorMessage } from "../../../data/hassio/common";
import { fetchHassioLogs } from "../../../data/hassio/supervisor";
import { HomeAssistant } from "../../../types"; import { HomeAssistant } from "../../../types";
const logProviders: LogProvider[] = [
{
key: "supervisor",
name: "Supervisor",
},
{
key: "core",
name: "Home Assistant Core",
},
{
key: "host",
name: "Host",
},
{
key: "dns",
name: "DNS",
},
{
key: "audio",
name: "Audio",
},
{
key: "multicast",
name: "Multicast",
},
];
@customElement("error-log-card")
class ErrorLogCard extends LitElement { class ErrorLogCard extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant; @property({ attribute: false }) public hass!: HomeAssistant;
@ -13,37 +55,69 @@ class ErrorLogCard extends LitElement {
@state() private _isLogLoaded = false; @state() private _isLogLoaded = false;
@state() private _errorHTML!: TemplateResult[] | string; @state() private _logHTML!: TemplateResult[] | string;
@state() private _error?: string;
@state() private _selectedLogProvider?: string;
protected render(): TemplateResult { protected render(): TemplateResult {
return html` return html`
<div class="error-log-intro"> <div class="error-log-intro">
${this._errorHTML ${this._error
? html`<ha-alert alert-type="error">${this._error}</ha-alert>`
: ""}
${this._logHTML
? html` ? html`
<ha-card> <ha-card>
<div class="header">
${this.hass.userData?.showAdvanced &&
isComponentLoaded(this.hass, "hassio")
? html`
<ha-select
.label=${this.hass.localize(
"ui.panel.config.logs.log_provider"
)}
@selected=${this._setLogProvider}
.value=${this._selectedLogProvider}
>
${logProviders.map(
(provider) => html`
<mwc-list-item .value=${provider.key}>
${provider.name}
</mwc-list-item>
`
)}
</ha-select>
`
: ""}
<ha-icon-button <ha-icon-button
.path=${mdiRefresh} .path=${mdiRefresh}
@click=${this._refreshErrorLog} @click=${this._refresh}
.label=${this.hass.localize("ui.common.refresh")} .label=${this.hass.localize("ui.common.refresh")}
></ha-icon-button> ></ha-icon-button>
<div class="card-content error-log">${this._errorHTML}</div> </div>
<div class="card-content error-log">${this._logHTML}</div>
</ha-card> </ha-card>
` `
: html` : ""}
<mwc-button raised @click=${this._refreshErrorLog}> ${!this._logHTML
? html`
<mwc-button raised @click=${this._refreshLogs}>
${this.hass.localize("ui.panel.config.logs.load_full_log")} ${this.hass.localize("ui.panel.config.logs.load_full_log")}
</mwc-button> </mwc-button>
`} `
: ""}
</div> </div>
`; `;
} }
protected firstUpdated(changedProps) { protected firstUpdated(changedProps: PropertyValues) {
super.firstUpdated(changedProps); super.firstUpdated(changedProps);
if (this.hass?.config.safe_mode) { if (this.hass?.config.safe_mode) {
this.hass.loadFragmentTranslation("config"); this.hass.loadFragmentTranslation("config");
this._refreshErrorLog(); this._refreshLogs();
} }
} }
@ -51,52 +125,56 @@ class ErrorLogCard extends LitElement {
super.updated(changedProps); super.updated(changedProps);
if (changedProps.has("filter") && this._isLogLoaded) { if (changedProps.has("filter") && this._isLogLoaded) {
this._refreshErrorLog(); this._refreshLogs();
} }
} }
static get styles(): CSSResultGroup { private async _setLogProvider(ev): Promise<void> {
return css` const provider = ev.target.value;
.error-log-intro { if (provider === this._selectedLogProvider) {
text-align: center; return;
margin: 16px;
} }
ha-icon-button { this._selectedLogProvider = provider;
float: right; this._refreshLogs();
} }
.error-log { private async _refresh(ev: CustomEvent): Promise<void> {
font-family: var(--code-font-family, monospace); const button = ev.currentTarget as any;
clear: both; button.progress = true;
text-align: left;
padding-top: 12px; await this._refreshLogs();
button.progress = false;
} }
.error-log > div:hover { private async _refreshLogs(): Promise<void> {
background-color: var(--secondary-background-color); this._logHTML = this.hass.localize("ui.panel.config.logs.loading_log");
let log: string;
if (!this._selectedLogProvider && isComponentLoaded(this.hass, "hassio")) {
this._selectedLogProvider = "core";
} }
.error { if (this._selectedLogProvider) {
color: var(--error-color); try {
log = await fetchHassioLogs(this.hass, this._selectedLogProvider);
} catch (err: any) {
this._error = this.hass.localize(
"ui.panel.config.logs.failed_get_logs",
"provider",
this._selectedLogProvider,
"error",
extractApiErrorMessage(err)
);
return;
}
} else {
log = await fetchErrorLog(this.hass!);
} }
.warning {
color: var(--warning-color);
}
:host-context([style*="direction: rtl;"]) mwc-button {
direction: rtl;
}
`;
}
private async _refreshErrorLog(): Promise<void> {
this._errorHTML = this.hass.localize("ui.panel.config.logs.loading_log");
const log = await fetchErrorLog(this.hass!);
this._isLogLoaded = true; this._isLogLoaded = true;
this._errorHTML = log this._logHTML = log
? log ? log
.split("\n") .split("\n")
.filter((entry) => { .filter((entry) => {
@ -123,6 +201,61 @@ class ErrorLogCard extends LitElement {
}) })
: this.hass.localize("ui.panel.config.logs.no_errors"); : this.hass.localize("ui.panel.config.logs.no_errors");
} }
static styles: CSSResultGroup = css`
.error-log-intro {
text-align: center;
margin: 16px;
} }
customElements.define("error-log-card", ErrorLogCard); .header {
display: flex;
justify-content: space-between;
padding: 16px;
}
ha-select {
display: block;
max-width: 500px;
width: 100%;
}
ha-icon-button {
float: right;
}
.error-log {
font-family: var(--code-font-family, monospace);
clear: both;
text-align: left;
padding-top: 12px;
}
.error-log > div {
overflow: auto;
overflow-wrap: break-word;
}
.error-log > div:hover {
background-color: var(--secondary-background-color);
}
.error {
color: var(--error-color);
}
.warning {
color: var(--warning-color);
}
:host-context([style*="direction: rtl;"]) mwc-button {
direction: rtl;
}
`;
}
declare global {
interface HTMLElementTagNameMap {
"error-log-card": ErrorLogCard;
}
}

View File

@ -1514,6 +1514,7 @@
"description": "View the Home Assistant logs", "description": "View the Home Assistant logs",
"details": "Log Details ({level})", "details": "Log Details ({level})",
"search": "Search logs", "search": "Search logs",
"failed_get_logs": "Failed to get {provider} logs, {error}",
"no_issues_search": "No issues found for search term ''{term}''", "no_issues_search": "No issues found for search term ''{term}''",
"load_full_log": "Load Full Home Assistant Log", "load_full_log": "Load Full Home Assistant Log",
"loading_log": "Loading error log…", "loading_log": "Loading error log…",
@ -1522,6 +1523,7 @@
"clear": "Clear", "clear": "Clear",
"refresh": "Refresh", "refresh": "Refresh",
"copy": "Copy log entry", "copy": "Copy log entry",
"log_provider": "Log Provider",
"multiple_messages": "message first occurred at {time} and shows up {counter} times", "multiple_messages": "message first occurred at {time} and shows up {counter} times",
"level": { "level": {
"critical": "CRITICAL", "critical": "CRITICAL",