@@ -176,13 +185,32 @@ class ErrorLogCard extends LitElement {
`
: nothing}
-
+ ${this._downloadSupported
+ ? html`
+
+ `
+ : this._logsFileLink
+ ? html`
+
+
+
+ `
+ : nothing}
${!this._streamSupported || this._error
? html`
+ ${this._streamSupported &&
+ this._loadingState !== "loading" &&
+ !this._error
+ ? html`
+
+ Live
+
`
+ : nothing}
${this.show === false
? html`
-
-
- ${this.hass.localize("ui.panel.config.logs.download_full_log")}
-
+ ${this._downloadSupported
+ ? html`
+
+
+ ${this.hass.localize(
+ "ui.panel.config.logs.download_logs"
+ )}
+
+ `
+ : nothing}
${this.hass.localize("ui.panel.config.logs.load_logs")}
@@ -268,6 +310,9 @@ class ErrorLogCard extends LitElement {
11
);
}
+ if (this._downloadSupported === undefined && this.hass) {
+ this._downloadSupported = downloadFileSupported(this.hass);
+ }
}
protected firstUpdated(changedProps: PropertyValues) {
@@ -331,7 +376,7 @@ class ErrorLogCard extends LitElement {
);
}
- private async _downloadFullLog(): Promise {
+ private async _downloadLogs(): Promise {
if (this._streamSupported) {
showDownloadLogsDialog(this, {
header: this.header,
@@ -378,6 +423,18 @@ class ErrorLogCard extends LitElement {
isComponentLoaded(this.hass, "hassio") &&
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(
this.hass,
this.provider,
@@ -438,6 +495,17 @@ class ErrorLogCard extends LitElement {
} else {
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 {
@@ -597,6 +665,9 @@ class ErrorLogCard extends LitElement {
}
static styles: CSSResultGroup = css`
+ :host {
+ direction: var(--direction);
+ }
.error-log-intro {
text-align: center;
margin: 16px;
@@ -646,7 +717,7 @@ class ErrorLogCard extends LitElement {
position: relative;
font-family: var(--code-font-family, monospace);
clear: both;
- text-align: left;
+ text-align: start;
padding-top: 12px;
padding-bottom: 12px;
overflow-y: scroll;
@@ -713,6 +784,36 @@ class ErrorLogCard extends LitElement {
--ha-assist-chip-container-shape: 10px;
--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);
+ }
`;
}
diff --git a/src/translations/en.json b/src/translations/en.json
index 0c3cd4698d8f..248a77bebc74 100644
--- a/src/translations/en.json
+++ b/src/translations/en.json
@@ -2492,7 +2492,7 @@
"show_full_logs": "Show full logs",
"select_number_of_lines": "Select number of lines to download",
"lines": "Lines",
- "download_full_log": "Download full log",
+ "download_logs": "Download logs",
"scroll_down_button": "New logs - Click to scroll",
"provider_not_found": "Log provider not found",
"provider_not_available": "Logs for ''{provider}'' are not available on your system.",
diff --git a/src/util/file_download.ts b/src/util/file_download.ts
index f163b146a55c..dc9ba6b54ad7 100644
--- a/src/util/file_download.ts
+++ b/src/util/file_download.ts
@@ -1,3 +1,6 @@
+import type { HomeAssistant } from "../types";
+import { isIosApp } from "./is_ios";
+
export const fileDownload = (href: string, filename = ""): void => {
const a = document.createElement("a");
a.target = "_blank";
@@ -8,3 +11,6 @@ export const fileDownload = (href: string, filename = ""): void => {
a.dispatchEvent(new MouseEvent("click"));
document.body.removeChild(a);
};
+
+export const downloadFileSupported = (hass: HomeAssistant): boolean =>
+ !isIosApp(hass) || !!hass.auth.external?.config.downloadFileSupported;
diff --git a/src/util/is_ios.ts b/src/util/is_ios.ts
new file mode 100644
index 000000000000..b7f72ed2f562
--- /dev/null
+++ b/src/util/is_ios.ts
@@ -0,0 +1,5 @@
+import type { HomeAssistant } from "../types";
+import { isSafari } from "./is_safari";
+
+export const isIosApp = (hass: HomeAssistant): boolean =>
+ isSafari && !!hass.auth.external;