Compare commits

...

17 Commits

Author SHA1 Message Date
Bram Kragten
188fe44c2e Bumped version to 20241106.2 2024-11-08 13:01:19 +01:00
Wendelin
25c02b1219 Fix live-logs loading (#22737) 2024-11-08 13:00:40 +01:00
Wendelin
e5e84acd07 Fix join-beta text (#22733) 2024-11-08 13:00:40 +01:00
tzagim
722ccc017f Fix for RTL languages in logs (#22727)
Fix for RTL Languages (log)
2024-11-08 13:00:39 +01:00
Bram Kragten
8a4b96f1ff Bumped version to 20241106.1 2024-11-07 21:40:38 +01:00
Paul Bottein
17b6bf0673 Enable auto-scroll for drag and drop (#22725) 2024-11-07 21:39:45 +01:00
Bram Kragten
387392713c move download logs button, switch between raw and normal logs (#22721) 2024-11-07 21:39:44 +01:00
Wendelin
125ad9c794 Fix logs live-indicator on older boots (#22719) 2024-11-07 21:39:43 +01:00
Bram Kragten
ae33b10cb2 Add support for helper text in form boolean (#22711) 2024-11-07 21:39:43 +01:00
Wendelin
1181ddcbbf Fix hassio logs for core < 2024.11 (#22708) 2024-11-07 21:39:42 +01:00
Paul Bottein
f7103febdf Fix typo for fixed background attribute (#22707) 2024-11-07 21:39:41 +01:00
Bram Kragten
6e8c1f1a63 Update value of password field on change event (#22706) 2024-11-07 21:39:40 +01:00
Bram Kragten
ce39b1a2c8 20241106.0 (#22695) 2024-11-06 14:05:23 +01:00
Paul Bottein
a6971d61d1 20241105.0 (#22679) 2024-11-05 18:47:04 +01:00
Bram Kragten
452cfee2cd Merge branch 'dev' 2024-11-04 19:04:13 +01:00
Paul Bottein
deb077b5e9 20241031.0 (#22613) 2024-10-31 15:14:02 +01:00
Paul Bottein
79e223cf8e 20241030.0 (#22599) 2024-10-30 16:35:54 +01:00
17 changed files with 270 additions and 154 deletions

View File

@@ -510,6 +510,7 @@ class DemoHaForm extends LitElement {
.computeError=${(error) => translations[error] || error}
.computeLabel=${(schema) =>
translations[schema.name] || schema.name}
.computeHelper=${() => "Helper text"}
@value-changed=${(e) => {
this.data[idx] = e.detail.value;
this.requestUpdate();

View File

@@ -47,7 +47,6 @@ class HassioAddonLogDashboard extends LitElement {
.localizeFunc=${this.supervisor.localize}
.header=${this.addon.name}
.provider=${this.addon.slug}
show
.filter=${this._filter}
>
</error-log-card>

View File

@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "home-assistant-frontend"
version = "20241106.0"
version = "20241106.2"
license = {text = "Apache-2.0"}
description = "The Home Assistant frontend"
readme = "README.md"

View File

@@ -1,6 +1,6 @@
import "@material/mwc-formfield";
import type { TemplateResult } from "lit";
import { html, LitElement } from "lit";
import type { CSSResultGroup, TemplateResult } from "lit";
import { css, html, LitElement, nothing } from "lit";
import { customElement, property, query } from "lit/decorators";
import { fireEvent } from "../../common/dom/fire_event";
import type {
@@ -19,6 +19,8 @@ export class HaFormBoolean extends LitElement implements HaFormElement {
@property() public label!: string;
@property() public helper?: string;
@property({ type: Boolean }) public disabled = false;
@query("ha-checkbox", true) private _input?: HTMLElement;
@@ -37,6 +39,12 @@ export class HaFormBoolean extends LitElement implements HaFormElement {
.disabled=${this.disabled}
@change=${this._valueChanged}
></ha-checkbox>
<span slot="label">
<p class="primary">${this.label}</p>
${this.helper
? html`<p class="secondary">${this.helper}</p>`
: nothing}
</span>
</mwc-formfield>
`;
}
@@ -46,6 +54,28 @@ export class HaFormBoolean extends LitElement implements HaFormElement {
value: (ev.target as HaCheckbox).checked,
});
}
static get styles(): CSSResultGroup {
return css`
ha-formfield {
display: flex;
min-height: 56px;
align-items: center;
--mdc-typography-body2-font-size: 1em;
}
p {
margin: 0;
}
.secondary {
direction: var(--direction);
padding-top: 4px;
box-sizing: border-box;
color: var(--secondary-text-color);
font-size: 0.875rem;
font-weight: var(--mdc-typography-body2-font-weight, 400);
}
`;
}
}
declare global {

View File

@@ -117,8 +117,8 @@ export class HaPasswordField extends LitElement {
.autocapitalize=${this.autocapitalize}
.type=${this._unmaskedPassword ? "text" : "password"}
.suffix=${html`<div style="width: 24px"></div>`}
@input=${this._handleInputChange}
@change=${this._reDispatchEvent}
@input=${this._handleInputEvent}
@change=${this._handleChangeEvent}
></ha-textfield>
<ha-icon-button
toggles
@@ -153,11 +153,16 @@ export class HaPasswordField extends LitElement {
}
@eventOptions({ passive: true })
private _handleInputChange(ev) {
private _handleInputEvent(ev) {
this.value = ev.target.value;
}
@eventOptions({ passive: true })
private _handleChangeEvent(ev) {
this.value = ev.target.value;
this._reDispatchEvent(ev);
}
private _reDispatchEvent(oldEvent: Event) {
const newEvent = new Event(oldEvent.type, oldEvent);
this.dispatchEvent(newEvent);

View File

@@ -135,6 +135,10 @@ export class HaSortable extends LitElement {
const Sortable = (await import("../resources/sortable")).default;
const options: SortableInstance.Options = {
scroll: true,
// Force the autoscroll fallback because it works better than the native one
forceAutoScrollFallback: true,
scrollSpeed: 20,
animation: 150,
...this.options,
onChoose: this._handleChoose,

View File

@@ -185,6 +185,15 @@ export const fetchHassioInfo = async (
export const fetchHassioBoots = async (hass: HomeAssistant) =>
hass.callApi<HassioResponse<HassioBoots>>("GET", `hassio/host/logs/boots`);
export const fetchHassioLogsLegacy = async (
hass: HomeAssistant,
provider: string
) =>
hass.callApi<string>(
"GET",
`hassio/${provider.includes("_") ? `addons/${provider}` : provider}/logs`
);
export const fetchHassioLogs = async (
hass: HomeAssistant,
provider: string,

View File

@@ -48,7 +48,7 @@ export class DialogJoinBeta
${this.hass.localize("ui.dialogs.join_beta_channel.backup")}
</ha-alert>
<p>
${this.hass.localize("ui.dialogs.join_beta_channel.warning")}
${this.hass.localize("ui.dialogs.join_beta_channel.warning")}.<br />
${this.hass.localize("ui.dialogs.join_beta_channel.release_items")}
</p>
<ul>

View File

@@ -11,6 +11,7 @@ import {
mdiRefresh,
mdiWrap,
mdiWrapDisabled,
mdiFolderTextOutline,
} from "@mdi/js";
import {
css,
@@ -49,6 +50,7 @@ import {
fetchHassioBoots,
fetchHassioLogs,
fetchHassioLogsFollow,
fetchHassioLogsLegacy,
getHassioLogDownloadLinesUrl,
getHassioLogDownloadUrl,
} from "../../../data/hassio/supervisor";
@@ -57,7 +59,7 @@ import {
downloadFileSupported,
fileDownload,
} from "../../../util/file_download";
import type { HASSDomEvent } from "../../../common/dom/fire_event";
import { fireEvent, type HASSDomEvent } from "../../../common/dom/fire_event";
import type { ConnectionStatus } from "../../../data/connection-status";
import { atLeastVersion } from "../../../common/config/version";
import { isComponentLoaded } from "../../../common/config/is_component_loaded";
@@ -78,9 +80,10 @@ class ErrorLogCard extends LitElement {
@property() public header?: string;
@property() public provider!: string;
@property() public provider?: string;
@property({ type: Boolean, attribute: true }) public show = false;
@property({ attribute: "allow-switch", type: Boolean }) public allowSwitch =
false;
@query(".error-log") private _logElement?: HTMLElement;
@@ -129,26 +132,32 @@ class ErrorLogCard extends LitElement {
@state() private _wrapLines = true;
@state() private _downloadSupported;
@state() private _downloadSupported?: boolean;
@state() private _logsFileLink;
@state() private _logsFileLink?: string;
protected render(): TemplateResult {
const streaming =
this._streamSupported &&
this.provider &&
isComponentLoaded(this.hass, "hassio") &&
this._loadingState !== "loading";
const hasBoots = this._streamSupported && Array.isArray(this._boots);
const localize = this.localizeFunc || this.hass.localize;
return html`
<div class="error-log-intro">
${this._error
? html`<ha-alert alert-type="error">${this._error}</ha-alert>`
: nothing}
<ha-card outlined class=${classMap({ hidden: this.show === false })}>
<ha-card outlined>
<div class="header">
<h1 class="card-header">
${this.header || localize("ui.panel.config.logs.show_full_logs")}
</h1>
<div class="action-buttons">
${this._streamSupported &&
Array.isArray(this._boots) &&
this._showBootsSelect
${hasBoots && this._showBootsSelect
? html`
<ha-assist-chip
.title=${localize(
@@ -174,7 +183,7 @@ class ErrorLogCard extends LitElement {
id="boots-menu"
positioning="fixed"
>
${this._boots.map(
${this._boots!.map(
(boot) => html`
<ha-md-menu-item
.value=${boot}
@@ -231,27 +240,40 @@ class ErrorLogCard extends LitElement {
`ui.panel.config.logs.${this._wrapLines ? "full_width" : "wrap_lines"}`
)}
></ha-icon-button>
${!this._streamSupported || this._error
${!streaming || this._error
? html`<ha-icon-button
.path=${mdiRefresh}
@click=${this._loadLogs}
.label=${localize("ui.common.refresh")}
></ha-icon-button>`
: nothing}
${this._streamSupported && Array.isArray(this._boots)
${(this.allowSwitch && this.provider === "core") || hasBoots
? html`
<ha-button-menu @action=${this._handleOverflowAction}>
<ha-icon-button slot="trigger" .path=${mdiDotsVertical}>
</ha-icon-button>
<ha-list-item graphic="icon">
<ha-svg-icon
slot="graphic"
.path=${mdiFormatListNumbered}
></ha-svg-icon>
${localize(
`ui.panel.config.logs.${this._showBootsSelect ? "hide" : "show"}_haos_boots`
)}
</ha-list-item>
${this.allowSwitch && this.provider === "core"
? html`<ha-list-item graphic="icon">
<ha-svg-icon
slot="graphic"
.path=${mdiFolderTextOutline}
></ha-svg-icon>
${this.hass.localize(
"ui.panel.config.logs.show_condensed_logs"
)}
</ha-list-item>`
: nothing}
${hasBoots
? html`<ha-list-item graphic="icon">
<ha-svg-icon
slot="graphic"
.path=${mdiFormatListNumbered}
></ha-svg-icon>
${localize(
`ui.panel.config.logs.${this._showBootsSelect ? "hide" : "show"}_haos_boots`
)}
</ha-list-item>`
: nothing}
</ha-button-menu>
`
: nothing}
@@ -304,47 +326,44 @@ class ErrorLogCard extends LitElement {
slot="trailingIcon"
></ha-svg-icon>
</ha-button>
${this._streamSupported &&
this._loadingState !== "loading" &&
!this._error
${streaming && this._boot === 0 && !this._error
? html`<div class="live-indicator">
<ha-svg-icon path=${mdiCircle}></ha-svg-icon>
Live
</div>`
: nothing}
</ha-card>
${this.show === false
? html`
${this._downloadSupported
? html`
<ha-button outlined @click=${this._downloadLogs}>
<ha-svg-icon .path=${mdiDownload}></ha-svg-icon>
${localize("ui.panel.config.logs.download_logs")}
</ha-button>
`
: nothing}
<mwc-button raised @click=${this._showLogs}>
${localize("ui.panel.config.logs.load_logs")}
</mwc-button>
`
: nothing}
</div>
`;
}
public connectedCallback() {
super.connectedCallback();
if (this._streamSupported === undefined) {
protected willUpdate(changedProps: PropertyValues) {
super.willUpdate(changedProps);
if (
this._downloadSupported === undefined ||
this._streamSupported === undefined
) {
this._downloadSupported = downloadFileSupported(this.hass);
this._streamSupported = atLeastVersion(
this.hass.config.version,
2024,
11
);
}
if (this._downloadSupported === undefined && this.hass) {
this._downloadSupported = downloadFileSupported(this.hass);
if (changedProps.has("provider")) {
this._boot = 0;
this._loadLogs();
}
if (this.hasUpdated) {
return;
}
// just needs to be loaded once, because only the host endpoints provide boots information
this._loadBoots();
window.addEventListener("connection-status", this._handleConnectionStatus);
this.hass.loadFragmentTranslation("config");
}
protected firstUpdated(changedProps: PropertyValues) {
@@ -354,28 +373,11 @@ class ErrorLogCard extends LitElement {
this._scrolledToTopController.callback = this._handleTopScroll;
this._scrolledToTopController.observe(this._scrollTopMarkerElement!);
window.addEventListener("connection-status", this._handleConnectionStatus);
if (this.hass?.config.recovery_mode || this.show) {
this.hass.loadFragmentTranslation("config");
}
// just needs to be loaded once, because only the host endpoints provide boots information
this._loadBoots();
}
protected updated(changedProps) {
super.updated(changedProps);
if (
(changedProps.has("show") && this.show) ||
(changedProps.has("provider") && this.show)
) {
this._boot = 0;
this._loadLogs();
}
if (this._newLogsIndicator && this._scrolledToBottomController.value) {
this._newLogsIndicator = false;
}
@@ -409,7 +411,7 @@ class ErrorLogCard extends LitElement {
}
private async _downloadLogs(): Promise<void> {
if (this._streamSupported) {
if (this._streamSupported && this.provider) {
showDownloadLogsDialog(this, {
header: this.header,
provider: this.provider,
@@ -431,10 +433,6 @@ class ErrorLogCard extends LitElement {
}
}
private _showLogs(): void {
this.show = true;
}
private async _loadLogs(): Promise<void> {
this._error = undefined;
this._loadingState = "loading";
@@ -446,15 +444,16 @@ class ErrorLogCard extends LitElement {
try {
if (this._logStreamAborter) {
this._logStreamAborter.abort();
this._logStreamAborter = undefined;
}
this._logStreamAborter = new AbortController();
if (
this._streamSupported &&
isComponentLoaded(this.hass, "hassio") &&
this.provider
) {
this._logStreamAborter = new AbortController();
// check if there are any logs at all
const testResponse = await fetchHassioLogs(
this.hass,
@@ -545,8 +544,7 @@ class ErrorLogCard extends LitElement {
this._streamSupported = false;
let logs = "";
if (isComponentLoaded(this.hass, "hassio") && this.provider) {
const repsonse = await fetchHassioLogs(this.hass, this.provider);
logs = await repsonse.text();
logs = await fetchHassioLogsLegacy(this.hass, this.provider);
} else {
logs = await fetchErrorLog(this.hass);
}
@@ -598,60 +596,62 @@ class ErrorLogCard extends LitElement {
if (ev.detail === "disconnected" && this._logStreamAborter) {
this._logStreamAborter.abort();
}
if (ev.detail === "connected" && this.show) {
if (ev.detail === "connected") {
this._loadLogs();
}
};
private async _loadMoreLogs() {
if (
this._firstCursor &&
this._loadingPrevState !== "loading" &&
this._loadingState === "loaded" &&
this._logElement
!this._firstCursor ||
this._loadingPrevState === "loading" ||
this._loadingState !== "loaded" ||
!this._logElement ||
!this.provider
) {
const scrolledToBottom = this._scrolledToBottomController.value;
const scrollPositionFromBottom =
this._logElement.scrollHeight - this._logElement.scrollTop;
this._loadingPrevState = "loading";
const response = await fetchHassioLogs(
this.hass,
this.provider,
`entries=${this._firstCursor}:-100:100`,
this._boot
);
return;
}
const scrolledToBottom = this._scrolledToBottomController.value;
const scrollPositionFromBottom =
this._logElement.scrollHeight - this._logElement.scrollTop;
this._loadingPrevState = "loading";
const response = await fetchHassioLogs(
this.hass,
this.provider,
`entries=${this._firstCursor}:-100:100`,
this._boot
);
if (response.headers.has("X-First-Cursor")) {
if (this._firstCursor === response.headers.get("X-First-Cursor")!) {
this._loadingPrevState = "end";
return;
}
this._firstCursor = response.headers.get("X-First-Cursor")!;
}
const body = await response.text();
if (body) {
const lines = body
.split("\n")
.filter((line) => line.trim() !== "")
.reverse();
this._ansiToHtmlElement?.parseLinesToColoredPre(lines, true);
this._numberOfLines! += lines.length;
this._loadingPrevState = "loaded";
} else {
if (response.headers.has("X-First-Cursor")) {
if (this._firstCursor === response.headers.get("X-First-Cursor")!) {
this._loadingPrevState = "end";
return;
}
this._firstCursor = response.headers.get("X-First-Cursor")!;
}
if (scrolledToBottom) {
this._scrollToBottom();
} else if (this._loadingPrevState !== "end" && this._logElement) {
window.requestAnimationFrame(() => {
this._logElement!.scrollTop =
this._logElement!.scrollHeight - scrollPositionFromBottom;
});
}
const body = await response.text();
if (body) {
const lines = body
.split("\n")
.filter((line) => line.trim() !== "")
.reverse();
this._ansiToHtmlElement?.parseLinesToColoredPre(lines, true);
this._numberOfLines! += lines.length;
this._loadingPrevState = "loaded";
} else {
this._loadingPrevState = "end";
}
if (scrolledToBottom) {
this._scrollToBottom();
} else if (this._loadingPrevState !== "end" && this._logElement) {
window.requestAnimationFrame(() => {
this._logElement!.scrollTop =
this._logElement!.scrollHeight - scrollPositionFromBottom;
});
}
}
@@ -693,7 +693,15 @@ class ErrorLogCard extends LitElement {
}
private _handleOverflowAction(ev: CustomEvent<ActionDetail>) {
switch (ev.detail.index) {
let index = ev.detail.index;
if (this.provider === "core") {
index--;
}
switch (index) {
case -1:
// @ts-ignore
fireEvent(this, "switch-log-view");
break;
case 0:
this._showBootsSelect = !this._showBootsSelect;
break;
@@ -770,8 +778,8 @@ class ErrorLogCard extends LitElement {
overflow-y: scroll;
min-height: var(--error-log-card-height, calc(100vh - 240px));
max-height: var(--error-log-card-height, calc(100vh - 240px));
border-top: 1px solid var(--divider-color);
direction: ltr;
}
@media all and (max-width: 870px) {

View File

@@ -3,20 +3,20 @@ import type { CSSResultGroup, TemplateResult } from "lit";
import { css, html, LitElement } from "lit";
import { customElement, property, query, state } from "lit/decorators";
import { isComponentLoaded } from "../../../common/config/is_component_loaded";
import { navigate } from "../../../common/navigate";
import { extractSearchParam } from "../../../common/url/search-params";
import "../../../components/ha-button-menu";
import "../../../components/ha-button";
import "../../../components/ha-button-menu";
import "../../../components/search-input";
import type { LogProvider } from "../../../data/error_log";
import { fetchHassioAddonsInfo } from "../../../data/hassio/addon";
import { showAlertDialog } from "../../../dialogs/generic/show-dialog-box";
import "../../../layouts/hass-subpage";
import { haStyle } from "../../../resources/styles";
import type { HomeAssistant, Route } from "../../../types";
import "./error-log-card";
import "./system-log-card";
import type { SystemLogCard } from "./system-log-card";
import { showAlertDialog } from "../../../dialogs/generic/show-dialog-box";
import { navigate } from "../../../common/navigate";
const logProviders: LogProvider[] = [
{
@@ -57,6 +57,8 @@ export class HaConfigLogs extends LitElement {
@state() private _filter = extractSearchParam("filter") || "";
@state() private _detail = false;
@query("system-log-card") private systemLog?: SystemLogCard;
@state() private _selectedLogProvider = "core";
@@ -141,7 +143,7 @@ export class HaConfigLogs extends LitElement {
: ""}
${search}
<div class="content">
${this._selectedLogProvider === "core"
${this._selectedLogProvider === "core" && !this._detail
? html`
<system-log-card
.hass=${this.hass}
@@ -149,23 +151,28 @@ export class HaConfigLogs extends LitElement {
(p) => p.key === this._selectedLogProvider
)!.name}
.filter=${this._filter}
@switch-log-view=${this._showDetail}
></system-log-card>
`
: ""}
<error-log-card
.hass=${this.hass}
.header=${this._logProviders.find(
(p) => p.key === this._selectedLogProvider
)!.name}
.filter=${this._filter}
.provider=${this._selectedLogProvider}
.show=${this._selectedLogProvider !== "core"}
></error-log-card>
: html`<error-log-card
.hass=${this.hass}
.header=${this._logProviders.find(
(p) => p.key === this._selectedLogProvider
)!.name}
.filter=${this._filter}
.provider=${this._selectedLogProvider}
@switch-log-view=${this._showDetail}
allow-switch
></error-log-card>`}
</div>
</hass-subpage>
`;
}
private _showDetail() {
this._detail = !this._detail;
}
private _selectProvider(ev) {
this._selectedLogProvider = (ev.currentTarget as any).provider;
this._filter = "";

View File

@@ -1,16 +1,20 @@
import { mdiRefresh } from "@mdi/js";
import "@material/mwc-list/mwc-list";
import { mdiDotsVertical, mdiDownload, mdiRefresh, mdiText } from "@mdi/js";
import type { CSSResultGroup } from "lit";
import { css, html, LitElement, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import memoizeOne from "memoize-one";
import { fireEvent } from "../../../common/dom/fire_event";
import type { LocalizeFunc } from "../../../common/translations/localize";
import "../../../components/buttons/ha-call-service-button";
import "../../../components/buttons/ha-progress-button";
import "../../../components/ha-button-menu";
import "../../../components/ha-card";
import "../../../components/ha-circular-progress";
import "../../../components/ha-icon-button";
import "../../../components/ha-list-item";
import { getSignedPath } from "../../../data/auth";
import { getErrorLogDownloadUrl } from "../../../data/error_log";
import { domainToName } from "../../../data/integration";
import type { LoggedError } from "../../../data/system_log";
import {
@@ -19,6 +23,7 @@ import {
isCustomIntegrationError,
} from "../../../data/system_log";
import type { HomeAssistant } from "../../../types";
import { fileDownload } from "../../../util/file_download";
import { showSystemLogDetailDialog } from "./show-dialog-system-log-detail";
import { formatSystemLogTime } from "./util";
@@ -104,11 +109,34 @@ export class SystemLogCard extends LitElement {
: html`
<div class="header">
<h1 class="card-header">${this.header || "Logs"}</h1>
<ha-icon-button
.path=${mdiRefresh}
@click=${this.fetchData}
.label=${this.hass.localize("ui.common.refresh")}
></ha-icon-button>
<div class="header-buttons">
<ha-icon-button
.path=${mdiDownload}
@click=${this._downloadLogs}
.label=${this.hass.localize(
"ui.panel.config.logs.download_logs"
)}
></ha-icon-button>
<ha-icon-button
.path=${mdiRefresh}
@click=${this.fetchData}
.label=${this.hass.localize("ui.common.refresh")}
></ha-icon-button>
<ha-button-menu @action=${this._handleOverflowAction}>
<ha-icon-button slot="trigger" .path=${mdiDotsVertical}>
</ha-icon-button>
<ha-list-item graphic="icon">
<ha-svg-icon
slot="graphic"
.path=${mdiText}
></ha-svg-icon>
${this.hass.localize(
"ui.panel.config.logs.show_full_logs"
)}
</ha-list-item>
</ha-button-menu>
</div>
</div>
${this._items.length === 0
? html`
@@ -195,6 +223,19 @@ export class SystemLogCard extends LitElement {
}
}
private _handleOverflowAction() {
// @ts-ignore
fireEvent(this, "switch-log-view");
}
private async _downloadLogs() {
const timeString = new Date().toISOString().replace(/:/g, "-");
const downloadUrl = getErrorLogDownloadUrl;
const logFileName = `home-assistant_${timeString}.log`;
const signedUrl = await getSignedPath(this.hass, downloadUrl);
fileDownload(signedUrl.path, logFileName);
}
private _openLog(ev: Event): void {
const item = (ev.currentTarget as any).logItem;
showSystemLogDetailDialog(this, { item });
@@ -203,7 +244,7 @@ export class SystemLogCard extends LitElement {
static get styles(): CSSResultGroup {
return css`
ha-card {
padding-top: 16px;
padding-top: 8px;
}
.header {
@@ -212,6 +253,11 @@ export class SystemLogCard extends LitElement {
padding: 0 16px;
}
.header-buttons {
display: flex;
align-items: center;
}
.card-header {
color: var(--ha-card-header-color, var(--primary-text-color));
font-family: var(--ha-card-header-font-family, inherit);
@@ -243,6 +289,10 @@ export class SystemLogCard extends LitElement {
color: var(--warning-color);
}
.card-content {
border-top: 1px solid var(--divider-color);
}
.card-actions,
.empty-content {
direction: var(--direction);

View File

@@ -31,7 +31,7 @@ export class HuiRecoveryModeCard extends LitElement implements LovelaceCard {
"ui.panel.lovelace.cards.recovery-mode.description"
)}
</div>
<error-log-card .hass=${this.hass}></error-log-card>
<error-log-card .hass=${this.hass} provider="core"></error-log-card>
</ha-card>
`;
}

View File

@@ -47,7 +47,7 @@ class HuiViewContainer extends LitElement {
private _isFixedBackground(background?: BackgroundConfig) {
if (typeof background === "string") {
return background.includes(" fixed");
return background.split(" ").includes("fixed");
}
return false;
}
@@ -126,7 +126,7 @@ class HuiViewContainer extends LitElement {
);
background-attachment: scroll !important;
}
:host(:not(fixed-background)) {
:host(:not([fixed-background])) {
background: var(
--view-background,
var(--lovelace-background, var(--primary-background-color))

View File

@@ -1,10 +1,11 @@
import type Sortable from "sortablejs";
import SortableCore, {
OnSpill,
AutoScroll,
OnSpill,
} from "sortablejs/modular/sortable.core.esm";
SortableCore.mount(OnSpill, new AutoScroll());
SortableCore.mount(OnSpill);
SortableCore.mount(new AutoScroll());
export default SortableCore as typeof Sortable;

View File

@@ -158,6 +158,7 @@ export const connectionMixin = <T extends Constructor<HassBaseEl>>(
},
callApi: async (method, path, parameters, headers) =>
hassCallApi(auth, method, path, parameters, headers),
// callApiRaw introduced in 2024.11
callApiRaw: async (method, path, parameters, headers, signal) =>
hassCallApiRaw(auth, method, path, parameters, headers, signal),
fetchWithAuth: (

View File

@@ -2470,9 +2470,9 @@
"search": "Search logs",
"failed_get_logs": "Failed to get {provider} logs, {error}",
"no_issues_search": "No issues found for search term ''{term}''",
"load_logs": "Load full logs",
"load_logs": "Load logs",
"nr_of_lines": "Number of lines",
"loading_log": "Loading full log…",
"loading_log": "Loading log…",
"no_errors": "No errors have been reported",
"no_issues": "There are no new issues!",
"clear": "Clear",
@@ -2489,7 +2489,8 @@
},
"custom_integration": "custom integration",
"error_from_custom_integration": "This error originated from a custom integration.",
"show_full_logs": "Show full logs",
"show_full_logs": "Show raw logs",
"show_condensed_logs": "Show condensed logs",
"select_number_of_lines": "Select number of lines to download",
"lines": "Lines",
"download_logs": "Download logs",

View File

@@ -259,7 +259,7 @@ export interface HomeAssistant {
parameters?: Record<string, any>,
headers?: Record<string, string>
): Promise<T>;
callApiRaw(
callApiRaw( // introduced in 2024.11
method: "GET" | "POST" | "PUT" | "DELETE",
path: string,
parameters?: Record<string, any>,