mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-13 20:36:35 +00:00
Check for empty logs (#22675)
* Fix download logs default lines + translations + iOS, add live logs indicator * Fix rtl in error-log-card * Fix downloadFileSupported
This commit is contained in:
parent
38da01abfa
commit
e908fbb48e
@ -264,6 +264,7 @@ export interface ExternalConfig {
|
|||||||
hasAssist: boolean;
|
hasAssist: boolean;
|
||||||
hasBarCodeScanner: number;
|
hasBarCodeScanner: number;
|
||||||
canSetupImprov: boolean;
|
canSetupImprov: boolean;
|
||||||
|
downloadFileSupported: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ExternalMessaging {
|
export class ExternalMessaging {
|
||||||
|
@ -17,19 +17,21 @@ import type { HomeAssistant } from "../../../types";
|
|||||||
import { fileDownload } from "../../../util/file_download";
|
import { fileDownload } from "../../../util/file_download";
|
||||||
import type { DownloadLogsDialogParams } from "./show-dialog-download-logs";
|
import type { DownloadLogsDialogParams } from "./show-dialog-download-logs";
|
||||||
|
|
||||||
|
const DEFAULT_LINE_COUNT = 500;
|
||||||
|
|
||||||
@customElement("dialog-download-logs")
|
@customElement("dialog-download-logs")
|
||||||
class DownloadLogsDialog extends LitElement {
|
class DownloadLogsDialog extends LitElement {
|
||||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
|
|
||||||
@state() private _dialogParams?: DownloadLogsDialogParams;
|
@state() private _dialogParams?: DownloadLogsDialogParams;
|
||||||
|
|
||||||
@state() private _lineCount = 100;
|
@state() private _lineCount = DEFAULT_LINE_COUNT;
|
||||||
|
|
||||||
@query("ha-md-dialog") private _dialogElement!: HaMdDialog;
|
@query("ha-md-dialog") private _dialogElement!: HaMdDialog;
|
||||||
|
|
||||||
public showDialog(dialogParams: DownloadLogsDialogParams) {
|
public showDialog(dialogParams: DownloadLogsDialogParams) {
|
||||||
this._dialogParams = dialogParams;
|
this._dialogParams = dialogParams;
|
||||||
this._lineCount = this._dialogParams?.defaultLineCount ?? 100;
|
this._lineCount = this._dialogParams?.defaultLineCount || 500;
|
||||||
}
|
}
|
||||||
|
|
||||||
public closeDialog() {
|
public closeDialog() {
|
||||||
@ -38,7 +40,7 @@ class DownloadLogsDialog extends LitElement {
|
|||||||
|
|
||||||
private _dialogClosed() {
|
private _dialogClosed() {
|
||||||
this._dialogParams = undefined;
|
this._dialogParams = undefined;
|
||||||
this._lineCount = 100;
|
this._lineCount = DEFAULT_LINE_COUNT;
|
||||||
fireEvent(this, "dialog-closed", { dialog: this.localName });
|
fireEvent(this, "dialog-closed", { dialog: this.localName });
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -48,7 +50,7 @@ class DownloadLogsDialog extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const numberOfLinesOptions = [100, 500, 1000, 5000, 10000];
|
const numberOfLinesOptions = [100, 500, 1000, 5000, 10000];
|
||||||
if (!numberOfLinesOptions.includes(this._lineCount)) {
|
if (!numberOfLinesOptions.includes(this._lineCount) && this._lineCount) {
|
||||||
numberOfLinesOptions.push(this._lineCount);
|
numberOfLinesOptions.push(this._lineCount);
|
||||||
numberOfLinesOptions.sort((a, b) => a - b);
|
numberOfLinesOptions.sort((a, b) => a - b);
|
||||||
}
|
}
|
||||||
@ -63,7 +65,7 @@ class DownloadLogsDialog extends LitElement {
|
|||||||
.path=${mdiClose}
|
.path=${mdiClose}
|
||||||
></ha-icon-button>
|
></ha-icon-button>
|
||||||
<span slot="title" id="dialog-light-color-favorite-title">
|
<span slot="title" id="dialog-light-color-favorite-title">
|
||||||
${this.hass.localize("ui.panel.config.logs.download_full_log")}
|
${this.hass.localize("ui.panel.config.logs.download_logs")}
|
||||||
</span>
|
</span>
|
||||||
<span slot="subtitle">
|
<span slot="subtitle">
|
||||||
${this._dialogParams.header}${this._dialogParams.boot === 0
|
${this._dialogParams.header}${this._dialogParams.boot === 0
|
||||||
@ -95,7 +97,7 @@ class DownloadLogsDialog extends LitElement {
|
|||||||
<ha-button @click=${this.closeDialog}>
|
<ha-button @click=${this.closeDialog}>
|
||||||
${this.hass.localize("ui.common.cancel")}
|
${this.hass.localize("ui.common.cancel")}
|
||||||
</ha-button>
|
</ha-button>
|
||||||
<ha-button @click=${this._dowloadLogs}>
|
<ha-button @click=${this._downloadLogs}>
|
||||||
${this.hass.localize("ui.common.download")}
|
${this.hass.localize("ui.common.download")}
|
||||||
</ha-button>
|
</ha-button>
|
||||||
</div>
|
</div>
|
||||||
@ -103,7 +105,7 @@ class DownloadLogsDialog extends LitElement {
|
|||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _dowloadLogs() {
|
private async _downloadLogs() {
|
||||||
const provider = this._dialogParams!.provider;
|
const provider = this._dialogParams!.provider;
|
||||||
const boot = this._dialogParams!.boot;
|
const boot = this._dialogParams!.boot;
|
||||||
|
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import "@material/mwc-list/mwc-list-item";
|
import "@material/mwc-list/mwc-list-item";
|
||||||
import {
|
import {
|
||||||
mdiArrowCollapseDown,
|
mdiArrowCollapseDown,
|
||||||
|
mdiCircle,
|
||||||
mdiDownload,
|
mdiDownload,
|
||||||
mdiMenuDown,
|
mdiMenuDown,
|
||||||
mdiRefresh,
|
mdiRefresh,
|
||||||
@ -40,10 +41,14 @@ import {
|
|||||||
fetchHassioBoots,
|
fetchHassioBoots,
|
||||||
fetchHassioLogs,
|
fetchHassioLogs,
|
||||||
fetchHassioLogsFollow,
|
fetchHassioLogsFollow,
|
||||||
|
getHassioLogDownloadLinesUrl,
|
||||||
getHassioLogDownloadUrl,
|
getHassioLogDownloadUrl,
|
||||||
} from "../../../data/hassio/supervisor";
|
} from "../../../data/hassio/supervisor";
|
||||||
import type { HomeAssistant } from "../../../types";
|
import type { HomeAssistant } from "../../../types";
|
||||||
import { fileDownload } from "../../../util/file_download";
|
import {
|
||||||
|
downloadFileSupported,
|
||||||
|
fileDownload,
|
||||||
|
} from "../../../util/file_download";
|
||||||
import type { HASSDomEvent } from "../../../common/dom/fire_event";
|
import type { HASSDomEvent } from "../../../common/dom/fire_event";
|
||||||
import type { ConnectionStatus } from "../../../data/connection-status";
|
import type { ConnectionStatus } from "../../../data/connection-status";
|
||||||
import { atLeastVersion } from "../../../common/config/version";
|
import { atLeastVersion } from "../../../common/config/version";
|
||||||
@ -109,6 +114,10 @@ class ErrorLogCard extends LitElement {
|
|||||||
|
|
||||||
@state() private _boots?: number[];
|
@state() private _boots?: number[];
|
||||||
|
|
||||||
|
@state() private _downloadSupported;
|
||||||
|
|
||||||
|
@state() private _logsFileLink;
|
||||||
|
|
||||||
protected render(): TemplateResult {
|
protected render(): TemplateResult {
|
||||||
return html`
|
return html`
|
||||||
<div class="error-log-intro">
|
<div class="error-log-intro">
|
||||||
@ -176,13 +185,32 @@ class ErrorLogCard extends LitElement {
|
|||||||
</ha-menu>
|
</ha-menu>
|
||||||
`
|
`
|
||||||
: nothing}
|
: nothing}
|
||||||
<ha-icon-button
|
${this._downloadSupported
|
||||||
.path=${mdiDownload}
|
? html`
|
||||||
@click=${this._downloadFullLog}
|
<ha-icon-button
|
||||||
.label=${this.hass.localize(
|
.path=${mdiDownload}
|
||||||
"ui.panel.config.logs.download_full_log"
|
@click=${this._downloadLogs}
|
||||||
)}
|
.label=${this.hass.localize(
|
||||||
></ha-icon-button>
|
"ui.panel.config.logs.download_logs"
|
||||||
|
)}
|
||||||
|
></ha-icon-button>
|
||||||
|
`
|
||||||
|
: this._logsFileLink
|
||||||
|
? html`
|
||||||
|
<a
|
||||||
|
href=${this._logsFileLink}
|
||||||
|
target="_blank"
|
||||||
|
class="download-link"
|
||||||
|
>
|
||||||
|
<ha-icon-button
|
||||||
|
.path=${mdiDownload}
|
||||||
|
.label=${this.hass.localize(
|
||||||
|
"ui.panel.config.logs.download_logs"
|
||||||
|
)}
|
||||||
|
></ha-icon-button>
|
||||||
|
</a>
|
||||||
|
`
|
||||||
|
: nothing}
|
||||||
${!this._streamSupported || this._error
|
${!this._streamSupported || this._error
|
||||||
? html`<ha-icon-button
|
? html`<ha-icon-button
|
||||||
.path=${mdiRefresh}
|
.path=${mdiRefresh}
|
||||||
@ -242,13 +270,27 @@ class ErrorLogCard extends LitElement {
|
|||||||
slot="trailingIcon"
|
slot="trailingIcon"
|
||||||
></ha-svg-icon>
|
></ha-svg-icon>
|
||||||
</ha-button>
|
</ha-button>
|
||||||
|
${this._streamSupported &&
|
||||||
|
this._loadingState !== "loading" &&
|
||||||
|
!this._error
|
||||||
|
? html`<div class="live-indicator">
|
||||||
|
<ha-svg-icon path=${mdiCircle}></ha-svg-icon>
|
||||||
|
Live
|
||||||
|
</div>`
|
||||||
|
: nothing}
|
||||||
</ha-card>
|
</ha-card>
|
||||||
${this.show === false
|
${this.show === false
|
||||||
? html`
|
? html`
|
||||||
<ha-button outlined @click=${this._downloadFullLog}>
|
${this._downloadSupported
|
||||||
<ha-svg-icon .path=${mdiDownload}></ha-svg-icon>
|
? html`
|
||||||
${this.hass.localize("ui.panel.config.logs.download_full_log")}
|
<ha-button outlined @click=${this._downloadLogs}>
|
||||||
</ha-button>
|
<ha-svg-icon .path=${mdiDownload}></ha-svg-icon>
|
||||||
|
${this.hass.localize(
|
||||||
|
"ui.panel.config.logs.download_logs"
|
||||||
|
)}
|
||||||
|
</ha-button>
|
||||||
|
`
|
||||||
|
: nothing}
|
||||||
<mwc-button raised @click=${this._showLogs}>
|
<mwc-button raised @click=${this._showLogs}>
|
||||||
${this.hass.localize("ui.panel.config.logs.load_logs")}
|
${this.hass.localize("ui.panel.config.logs.load_logs")}
|
||||||
</mwc-button>
|
</mwc-button>
|
||||||
@ -268,6 +310,9 @@ class ErrorLogCard extends LitElement {
|
|||||||
11
|
11
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
if (this._downloadSupported === undefined && this.hass) {
|
||||||
|
this._downloadSupported = downloadFileSupported(this.hass);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected firstUpdated(changedProps: PropertyValues) {
|
protected firstUpdated(changedProps: PropertyValues) {
|
||||||
@ -331,7 +376,7 @@ class ErrorLogCard extends LitElement {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _downloadFullLog(): Promise<void> {
|
private async _downloadLogs(): Promise<void> {
|
||||||
if (this._streamSupported) {
|
if (this._streamSupported) {
|
||||||
showDownloadLogsDialog(this, {
|
showDownloadLogsDialog(this, {
|
||||||
header: this.header,
|
header: this.header,
|
||||||
@ -378,6 +423,18 @@ class ErrorLogCard extends LitElement {
|
|||||||
isComponentLoaded(this.hass, "hassio") &&
|
isComponentLoaded(this.hass, "hassio") &&
|
||||||
this.provider
|
this.provider
|
||||||
) {
|
) {
|
||||||
|
// check if there are any logs at all
|
||||||
|
const testResponse = await fetchHassioLogs(
|
||||||
|
this.hass,
|
||||||
|
this.provider,
|
||||||
|
`entries=:-1:`,
|
||||||
|
this._boot
|
||||||
|
);
|
||||||
|
const testLogs = await testResponse.text();
|
||||||
|
if (!testLogs.trim()) {
|
||||||
|
this._loadingState = "empty";
|
||||||
|
}
|
||||||
|
|
||||||
const response = await fetchHassioLogsFollow(
|
const response = await fetchHassioLogsFollow(
|
||||||
this.hass,
|
this.hass,
|
||||||
this.provider,
|
this.provider,
|
||||||
@ -438,6 +495,17 @@ class ErrorLogCard extends LitElement {
|
|||||||
} else {
|
} else {
|
||||||
this._newLogsIndicator = true;
|
this._newLogsIndicator = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!this._downloadSupported) {
|
||||||
|
const downloadUrl = getHassioLogDownloadLinesUrl(
|
||||||
|
this.provider,
|
||||||
|
this._numberOfLines,
|
||||||
|
this._boot
|
||||||
|
);
|
||||||
|
getSignedPath(this.hass, downloadUrl).then((signedUrl) => {
|
||||||
|
this._logsFileLink = signedUrl.path;
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -597,6 +665,9 @@ class ErrorLogCard extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static styles: CSSResultGroup = css`
|
static styles: CSSResultGroup = css`
|
||||||
|
:host {
|
||||||
|
direction: var(--direction);
|
||||||
|
}
|
||||||
.error-log-intro {
|
.error-log-intro {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
margin: 16px;
|
margin: 16px;
|
||||||
@ -646,7 +717,7 @@ class ErrorLogCard extends LitElement {
|
|||||||
position: relative;
|
position: relative;
|
||||||
font-family: var(--code-font-family, monospace);
|
font-family: var(--code-font-family, monospace);
|
||||||
clear: both;
|
clear: both;
|
||||||
text-align: left;
|
text-align: start;
|
||||||
padding-top: 12px;
|
padding-top: 12px;
|
||||||
padding-bottom: 12px;
|
padding-bottom: 12px;
|
||||||
overflow-y: scroll;
|
overflow-y: scroll;
|
||||||
@ -713,6 +784,36 @@ class ErrorLogCard extends LitElement {
|
|||||||
--ha-assist-chip-container-shape: 10px;
|
--ha-assist-chip-container-shape: 10px;
|
||||||
--md-assist-chip-trailing-space: 8px;
|
--md-assist-chip-trailing-space: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@keyframes breathe {
|
||||||
|
from {
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.live-indicator {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
inset-inline-end: 16px;
|
||||||
|
border-top-right-radius: 8px;
|
||||||
|
border-top-left-radius: 8px;
|
||||||
|
background-color: var(--primary-color);
|
||||||
|
color: var(--text-primary-color);
|
||||||
|
padding: 4px 8px;
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
.live-indicator ha-svg-icon {
|
||||||
|
animation: breathe 1s cubic-bezier(0.5, 0, 1, 1) infinite alternate;
|
||||||
|
height: 14px;
|
||||||
|
width: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.download-link {
|
||||||
|
color: var(--text-color);
|
||||||
|
}
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2492,7 +2492,7 @@
|
|||||||
"show_full_logs": "Show full logs",
|
"show_full_logs": "Show full logs",
|
||||||
"select_number_of_lines": "Select number of lines to download",
|
"select_number_of_lines": "Select number of lines to download",
|
||||||
"lines": "Lines",
|
"lines": "Lines",
|
||||||
"download_full_log": "Download full log",
|
"download_logs": "Download logs",
|
||||||
"scroll_down_button": "New logs - Click to scroll",
|
"scroll_down_button": "New logs - Click to scroll",
|
||||||
"provider_not_found": "Log provider not found",
|
"provider_not_found": "Log provider not found",
|
||||||
"provider_not_available": "Logs for ''{provider}'' are not available on your system.",
|
"provider_not_available": "Logs for ''{provider}'' are not available on your system.",
|
||||||
|
@ -1,3 +1,6 @@
|
|||||||
|
import type { HomeAssistant } from "../types";
|
||||||
|
import { isIosApp } from "./is_ios";
|
||||||
|
|
||||||
export const fileDownload = (href: string, filename = ""): void => {
|
export const fileDownload = (href: string, filename = ""): void => {
|
||||||
const a = document.createElement("a");
|
const a = document.createElement("a");
|
||||||
a.target = "_blank";
|
a.target = "_blank";
|
||||||
@ -8,3 +11,6 @@ export const fileDownload = (href: string, filename = ""): void => {
|
|||||||
a.dispatchEvent(new MouseEvent("click"));
|
a.dispatchEvent(new MouseEvent("click"));
|
||||||
document.body.removeChild(a);
|
document.body.removeChild(a);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const downloadFileSupported = (hass: HomeAssistant): boolean =>
|
||||||
|
!isIosApp(hass) || !!hass.auth.external?.config.downloadFileSupported;
|
||||||
|
5
src/util/is_ios.ts
Normal file
5
src/util/is_ios.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
import type { HomeAssistant } from "../types";
|
||||||
|
import { isSafari } from "./is_safari";
|
||||||
|
|
||||||
|
export const isIosApp = (hass: HomeAssistant): boolean =>
|
||||||
|
isSafari && !!hass.auth.external;
|
Loading…
x
Reference in New Issue
Block a user