mirror of
https://github.com/home-assistant/frontend.git
synced 2026-03-03 22:07:47 +00:00
Compare commits
8 Commits
ha-input
...
log-classi
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ba22a12a20 | ||
|
|
098b54f749 | ||
|
|
4c6a7091a6 | ||
|
|
322cb35526 | ||
|
|
c34f6bea2b | ||
|
|
41bf0652b0 | ||
|
|
23af40743b | ||
|
|
c4326b4f3a |
@@ -200,7 +200,7 @@
|
||||
"husky": "9.1.7",
|
||||
"jsdom": "28.1.0",
|
||||
"jszip": "3.10.1",
|
||||
"lint-staged": "16.2.7",
|
||||
"lint-staged": "16.3.0",
|
||||
"lit-analyzer": "2.0.3",
|
||||
"lodash.merge": "4.6.2",
|
||||
"lodash.template": "4.5.0",
|
||||
|
||||
@@ -37,9 +37,11 @@ export interface LovelaceViewHeaderConfig {
|
||||
badges_wrap?: "wrap" | "scroll";
|
||||
}
|
||||
|
||||
export const DEFAULT_FOOTER_MAX_WIDTH_PX = 600;
|
||||
|
||||
export interface LovelaceViewFooterConfig {
|
||||
card?: LovelaceCardConfig;
|
||||
column_span?: number;
|
||||
max_width?: number;
|
||||
}
|
||||
|
||||
export interface LovelaceViewSidebarConfig {
|
||||
|
||||
@@ -7,10 +7,23 @@ export type SystemLogLevel =
|
||||
| "info"
|
||||
| "debug";
|
||||
|
||||
export type SystemLogErrorType =
|
||||
| "auth"
|
||||
| "connection"
|
||||
| "invalid_response"
|
||||
| "rate_limit"
|
||||
| "server"
|
||||
| "slow_setup"
|
||||
| "timeout"
|
||||
| "ssl"
|
||||
| "statistics"
|
||||
| "dns";
|
||||
|
||||
export interface LoggedError {
|
||||
name: string;
|
||||
message: [string];
|
||||
level: SystemLogLevel;
|
||||
error_type?: SystemLogErrorType;
|
||||
source: [string, number];
|
||||
exception: string;
|
||||
count: number;
|
||||
|
||||
@@ -110,6 +110,13 @@ class DialogSystemLogDetail extends LitElement {
|
||||
${item.name}<br />
|
||||
${this.hass.localize("ui.panel.config.logs.detail.source")}:
|
||||
${item.source.join(":")}
|
||||
<br />
|
||||
${this.hass.localize("ui.panel.config.logs.classification")}:
|
||||
${item.error_type
|
||||
? this.hass.localize(
|
||||
`ui.panel.config.logs.error_type.${item.error_type}`
|
||||
)
|
||||
: this.hass.localize("ui.panel.config.logs.other")}
|
||||
${integration
|
||||
? html`
|
||||
<br />
|
||||
|
||||
@@ -1,10 +1,16 @@
|
||||
import {
|
||||
mdiDotsVertical,
|
||||
mdiChevronDown,
|
||||
mdiChip,
|
||||
mdiDns,
|
||||
mdiDownload,
|
||||
mdiFilterVariant,
|
||||
mdiFilterVariantRemove,
|
||||
mdiPackageVariant,
|
||||
mdiPuzzle,
|
||||
mdiRadar,
|
||||
mdiRefresh,
|
||||
mdiText,
|
||||
mdiVolumeHigh,
|
||||
} from "@mdi/js";
|
||||
import type { CSSResultGroup, TemplateResult } from "lit";
|
||||
@@ -17,10 +23,14 @@ import { navigate } from "../../../common/navigate";
|
||||
import { stringCompare } from "../../../common/string/compare";
|
||||
import { extractSearchParam } from "../../../common/url/search-params";
|
||||
import "../../../components/ha-button";
|
||||
import "../../../components/chips/ha-assist-chip";
|
||||
import "../../../components/ha-dropdown";
|
||||
import "../../../components/ha-dropdown-item";
|
||||
import "../../../components/ha-generic-picker";
|
||||
import "../../../components/ha-icon-button";
|
||||
import type { HaGenericPicker } from "../../../components/ha-generic-picker";
|
||||
import type { PickerComboBoxItem } from "../../../components/ha-picker-combo-box";
|
||||
import "../../../components/search-input";
|
||||
import "../../../components/search-input-outlined";
|
||||
import type { LogProvider } from "../../../data/error_log";
|
||||
import { fetchHassioAddonsInfo } from "../../../data/hassio/addon";
|
||||
import { showAlertDialog } from "../../../dialogs/generic/show-dialog-box";
|
||||
@@ -28,6 +38,7 @@ import "../../../layouts/hass-subpage";
|
||||
import { mdiHomeAssistant } from "../../../resources/home-assistant-logo-svg";
|
||||
import { haStyle } from "../../../resources/styles";
|
||||
import type { HomeAssistant, Route, ValueChangedEvent } from "../../../types";
|
||||
import type { HaDropdownSelectEvent } from "../../../components/ha-dropdown";
|
||||
import "./error-log-card";
|
||||
import "./system-log-card";
|
||||
import type { SystemLogCard } from "./system-log-card";
|
||||
@@ -81,13 +92,9 @@ export class HaConfigLogs extends LitElement {
|
||||
|
||||
@state() private _logProviders = logProviders;
|
||||
|
||||
public connectedCallback() {
|
||||
super.connectedCallback();
|
||||
const systemLog = this.systemLog;
|
||||
if (systemLog && systemLog.loaded) {
|
||||
systemLog.fetchData();
|
||||
}
|
||||
}
|
||||
@state() private _showSystemLogFilters = false;
|
||||
|
||||
@state() private _systemLogFiltersCount = 0;
|
||||
|
||||
protected firstUpdated(changedProps): void {
|
||||
super.firstUpdated(changedProps);
|
||||
@@ -98,37 +105,140 @@ export class HaConfigLogs extends LitElement {
|
||||
this._filter = ev.detail.value;
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
const search = this.narrow
|
||||
? html`
|
||||
<div slot="header">
|
||||
<search-input
|
||||
class="header"
|
||||
@value-changed=${this._filterChanged}
|
||||
.hass=${this.hass}
|
||||
.filter=${this._filter}
|
||||
.label=${this.hass.localize("ui.panel.config.logs.search")}
|
||||
></search-input>
|
||||
</div>
|
||||
`
|
||||
: html`
|
||||
<div class="search">
|
||||
<search-input
|
||||
@value-changed=${this._filterChanged}
|
||||
.hass=${this.hass}
|
||||
.filter=${this._filter}
|
||||
.label=${this.hass.localize("ui.panel.config.logs.search")}
|
||||
></search-input>
|
||||
</div>
|
||||
`;
|
||||
private _toggleSystemLogFilters = () => {
|
||||
this._showSystemLogFilters = !this._showSystemLogFilters;
|
||||
};
|
||||
|
||||
private _handleSystemLogFiltersChanged(ev: CustomEvent) {
|
||||
this._showSystemLogFilters = ev.detail.open;
|
||||
this._systemLogFiltersCount = ev.detail.count;
|
||||
}
|
||||
|
||||
private _downloadSystemLog = () => {
|
||||
this.systemLog?.downloadLogs();
|
||||
};
|
||||
|
||||
private _refreshSystemLog = () => {
|
||||
this.systemLog?.fetchData();
|
||||
};
|
||||
|
||||
private _clearSystemLog = () => {
|
||||
this.systemLog?.clearLogs();
|
||||
};
|
||||
|
||||
private _clearSystemLogFilters = () => {
|
||||
this.systemLog?.clearFilters();
|
||||
};
|
||||
|
||||
private _handleSystemLogOverflowAction(ev: HaDropdownSelectEvent): void {
|
||||
if (ev.detail.item.value === "show-full-logs") {
|
||||
this._showDetail();
|
||||
}
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
const showSystemLog = this._selectedLogProvider === "core" && !this._detail;
|
||||
const selectedProvider = this._getActiveProvider(this._selectedLogProvider);
|
||||
const header =
|
||||
selectedProvider?.primary ||
|
||||
this.hass.localize("ui.panel.config.logs.caption");
|
||||
|
||||
const searchRow = html`
|
||||
<div
|
||||
class="search-row ${showSystemLog
|
||||
? "with-filters"
|
||||
: ""} ${showSystemLog && this._showSystemLogFilters && !this.narrow
|
||||
? "with-pane"
|
||||
: ""}"
|
||||
>
|
||||
${showSystemLog
|
||||
? this._showSystemLogFilters && !this.narrow
|
||||
? html`
|
||||
<div class="filter-controls">
|
||||
<div class="relative filter-button">
|
||||
<ha-assist-chip
|
||||
.label=${this.hass.localize(
|
||||
"ui.components.subpage-data-table.filters"
|
||||
)}
|
||||
active
|
||||
@click=${this._toggleSystemLogFilters}
|
||||
>
|
||||
<ha-svg-icon
|
||||
slot="icon"
|
||||
.path=${mdiFilterVariant}
|
||||
></ha-svg-icon>
|
||||
</ha-assist-chip>
|
||||
${this._systemLogFiltersCount
|
||||
? html`<div class="badge">
|
||||
${this._systemLogFiltersCount}
|
||||
</div>`
|
||||
: nothing}
|
||||
</div>
|
||||
<ha-icon-button
|
||||
.path=${mdiFilterVariantRemove}
|
||||
.label=${this.hass.localize(
|
||||
"ui.components.subpage-data-table.clear_filter"
|
||||
)}
|
||||
.disabled=${!this._systemLogFiltersCount}
|
||||
@click=${this._clearSystemLogFilters}
|
||||
></ha-icon-button>
|
||||
</div>
|
||||
`
|
||||
: html`
|
||||
<div class="relative filter-button">
|
||||
<ha-assist-chip
|
||||
.label=${this.hass.localize(
|
||||
"ui.components.subpage-data-table.filters"
|
||||
)}
|
||||
.active=${this._showSystemLogFilters ||
|
||||
Boolean(this._systemLogFiltersCount)}
|
||||
@click=${this._toggleSystemLogFilters}
|
||||
>
|
||||
<ha-svg-icon
|
||||
slot="icon"
|
||||
.path=${mdiFilterVariant}
|
||||
></ha-svg-icon>
|
||||
</ha-assist-chip>
|
||||
${this._systemLogFiltersCount
|
||||
? html`<div class="badge">
|
||||
${this._systemLogFiltersCount}
|
||||
</div>`
|
||||
: nothing}
|
||||
</div>
|
||||
`
|
||||
: nothing}
|
||||
|
||||
<search-input-outlined
|
||||
class="search-input"
|
||||
.hass=${this.hass}
|
||||
.filter=${this._filter}
|
||||
.label=${this.hass.localize("ui.panel.config.logs.search")}
|
||||
.placeholder=${this.hass.localize("ui.panel.config.logs.search")}
|
||||
@value-changed=${this._filterChanged}
|
||||
></search-input-outlined>
|
||||
|
||||
${showSystemLog
|
||||
? html`
|
||||
<ha-assist-chip
|
||||
class="clear-chip"
|
||||
.label=${this.hass.localize("ui.panel.config.logs.clear")}
|
||||
.disabled=${!this.systemLog?.hasItems}
|
||||
@click=${this._clearSystemLog}
|
||||
></ha-assist-chip>
|
||||
`
|
||||
: nothing}
|
||||
</div>
|
||||
`;
|
||||
|
||||
const search = this.narrow
|
||||
? html`<div slot="header">${searchRow}</div>`
|
||||
: searchRow;
|
||||
|
||||
return html`
|
||||
<hass-subpage
|
||||
.hass=${this.hass}
|
||||
.narrow=${this.narrow}
|
||||
.header=${this.hass.localize("ui.panel.config.logs.caption")}
|
||||
.header=${header}
|
||||
back-path="/config/system"
|
||||
>
|
||||
${isComponentLoaded(this.hass, "hassio") && this._logProviders
|
||||
@@ -164,17 +274,48 @@ export class HaConfigLogs extends LitElement {
|
||||
</ha-generic-picker>
|
||||
`
|
||||
: nothing}
|
||||
${showSystemLog
|
||||
? html`
|
||||
<ha-icon-button
|
||||
slot="toolbar-icon"
|
||||
.path=${mdiDownload}
|
||||
@click=${this._downloadSystemLog}
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.logs.download_logs"
|
||||
)}
|
||||
></ha-icon-button>
|
||||
<ha-icon-button
|
||||
slot="toolbar-icon"
|
||||
.path=${mdiRefresh}
|
||||
@click=${this._refreshSystemLog}
|
||||
.label=${this.hass.localize("ui.common.refresh")}
|
||||
></ha-icon-button>
|
||||
<ha-dropdown
|
||||
slot="toolbar-icon"
|
||||
@wa-select=${this._handleSystemLogOverflowAction}
|
||||
>
|
||||
<ha-icon-button
|
||||
slot="trigger"
|
||||
.path=${mdiDotsVertical}
|
||||
.label=${this.hass.localize("ui.common.menu")}
|
||||
></ha-icon-button>
|
||||
<ha-dropdown-item value="show-full-logs">
|
||||
<ha-svg-icon slot="icon" .path=${mdiText}></ha-svg-icon>
|
||||
${this.hass.localize("ui.panel.config.logs.show_full_logs")}
|
||||
</ha-dropdown-item>
|
||||
</ha-dropdown>
|
||||
`
|
||||
: nothing}
|
||||
${search}
|
||||
<div class="content">
|
||||
${this._selectedLogProvider === "core" && !this._detail
|
||||
${showSystemLog
|
||||
? html`
|
||||
<system-log-card
|
||||
.hass=${this.hass}
|
||||
.header=${this._logProviders.find(
|
||||
(p) => p.key === this._selectedLogProvider
|
||||
)!.name}
|
||||
.filter=${this._filter}
|
||||
@switch-log-view=${this._showDetail}
|
||||
.showFilters=${this._showSystemLogFilters}
|
||||
@system-log-filters-changed=${this
|
||||
._handleSystemLogFiltersChanged}
|
||||
></system-log-card>
|
||||
`
|
||||
: html`<error-log-card
|
||||
@@ -194,6 +335,7 @@ export class HaConfigLogs extends LitElement {
|
||||
|
||||
private _showDetail() {
|
||||
this._detail = !this._detail;
|
||||
this._showSystemLogFilters = false;
|
||||
}
|
||||
|
||||
private _openPicker(ev: Event) {
|
||||
@@ -208,6 +350,8 @@ export class HaConfigLogs extends LitElement {
|
||||
}
|
||||
this._selectedLogProvider = provider;
|
||||
this._filter = "";
|
||||
this._showSystemLogFilters = false;
|
||||
this._systemLogFiltersCount = 0;
|
||||
navigate(`/config/logs?provider=${this._selectedLogProvider}`);
|
||||
}
|
||||
|
||||
@@ -342,24 +486,101 @@ export class HaConfigLogs extends LitElement {
|
||||
-webkit-user-select: initial;
|
||||
-moz-user-select: initial;
|
||||
}
|
||||
.search {
|
||||
.search-row {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 2;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 56px;
|
||||
width: 100%;
|
||||
gap: var(--ha-space-4);
|
||||
padding: 0 var(--ha-space-4);
|
||||
background: var(--primary-background-color);
|
||||
border-bottom: 1px solid var(--divider-color);
|
||||
box-sizing: border-box;
|
||||
}
|
||||
search-input {
|
||||
|
||||
.search-row.with-pane {
|
||||
display: grid;
|
||||
grid-template-columns:
|
||||
var(--sidepane-width, 250px) minmax(0, 1fr)
|
||||
auto;
|
||||
align-items: center;
|
||||
gap: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.search-row.with-pane .filter-controls {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
min-width: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
padding: 0 var(--ha-space-4);
|
||||
border-inline-end: 1px solid var(--divider-color);
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.search-row.with-pane .search-input {
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
margin-inline-start: var(--ha-space-4);
|
||||
}
|
||||
|
||||
.search-row.with-pane .clear-chip {
|
||||
justify-self: end;
|
||||
margin-inline-start: var(--ha-space-4);
|
||||
margin-inline-end: var(--ha-space-4);
|
||||
}
|
||||
|
||||
search-input-outlined {
|
||||
display: block;
|
||||
--mdc-text-field-fill-color: var(--sidebar-background-color);
|
||||
--mdc-text-field-idle-line-color: var(--divider-color);
|
||||
flex: 1;
|
||||
}
|
||||
search-input.header {
|
||||
--mdc-ripple-color: transparant;
|
||||
margin-left: -16px;
|
||||
margin-inline-start: -16px;
|
||||
margin-inline-end: initial;
|
||||
|
||||
.relative {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.badge {
|
||||
position: absolute;
|
||||
top: -4px;
|
||||
right: -4px;
|
||||
inset-inline-end: -4px;
|
||||
inset-inline-start: initial;
|
||||
min-width: 16px;
|
||||
box-sizing: border-box;
|
||||
border-radius: var(--ha-border-radius-circle);
|
||||
font-size: var(--ha-font-size-xs);
|
||||
font-weight: var(--ha-font-weight-normal);
|
||||
background-color: var(--primary-color);
|
||||
line-height: var(--ha-line-height-normal);
|
||||
text-align: center;
|
||||
padding: 0 2px;
|
||||
color: var(--text-primary-color);
|
||||
}
|
||||
.content {
|
||||
direction: ltr;
|
||||
height: calc(
|
||||
100vh -
|
||||
1px - var(--header-height, 0px) - var(
|
||||
--safe-area-inset-top,
|
||||
0px
|
||||
) - var(--safe-area-inset-bottom, 0px) -
|
||||
56px
|
||||
);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
ha-assist-chip {
|
||||
--ha-assist-chip-container-shape: 10px;
|
||||
--ha-assist-chip-container-color: var(--card-background-color);
|
||||
}
|
||||
|
||||
.clear-chip {
|
||||
white-space: nowrap;
|
||||
}
|
||||
ha-generic-picker {
|
||||
--md-list-item-leading-icon-color: var(--ha-color-primary-50);
|
||||
|
||||
@@ -1,21 +1,15 @@
|
||||
import { mdiDotsVertical, mdiDownload, mdiRefresh, mdiText } from "@mdi/js";
|
||||
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/ha-card";
|
||||
import "../../../components/ha-dropdown";
|
||||
import "../../../components/ha-dropdown-item";
|
||||
import "../../../components/ha-icon-button";
|
||||
import "../../../components/ha-filter-states";
|
||||
import "../../../components/ha-list";
|
||||
import "../../../components/ha-list-item";
|
||||
import "../../../components/ha-spinner";
|
||||
import { getSignedPath } from "../../../data/auth";
|
||||
import { getErrorLogDownloadUrl } from "../../../data/error_log";
|
||||
import { domainToName } from "../../../data/integration";
|
||||
import type { LoggedError } from "../../../data/system_log";
|
||||
import type { LoggedError, SystemLogErrorType } from "../../../data/system_log";
|
||||
import {
|
||||
fetchSystemLog,
|
||||
getLoggedErrorIntegration,
|
||||
@@ -25,7 +19,6 @@ import type { HomeAssistant } from "../../../types";
|
||||
import { fileDownload } from "../../../util/file_download";
|
||||
import { showSystemLogDetailDialog } from "./show-dialog-system-log-detail";
|
||||
import { formatSystemLogTime } from "./util";
|
||||
import type { HaDropdownSelectEvent } from "../../../components/ha-dropdown";
|
||||
|
||||
@customElement("system-log-card")
|
||||
export class SystemLogCard extends LitElement {
|
||||
@@ -33,15 +26,45 @@ export class SystemLogCard extends LitElement {
|
||||
|
||||
@property() public filter = "";
|
||||
|
||||
@property() public header?: string;
|
||||
|
||||
public loaded = false;
|
||||
@property({ type: Boolean, attribute: "show-filters" })
|
||||
public showFilters = false;
|
||||
|
||||
@state() private _items?: LoggedError[];
|
||||
|
||||
@state() private _levelFilter: string[] = [];
|
||||
|
||||
@state() private _errorTypeFilter: (SystemLogErrorType | "unknown")[] = [];
|
||||
|
||||
public async fetchData(): Promise<void> {
|
||||
this._items = undefined;
|
||||
this._items = await fetchSystemLog(this.hass!);
|
||||
this._items = await fetchSystemLog(this.hass);
|
||||
}
|
||||
|
||||
public async clearLogs(): Promise<void> {
|
||||
await this.hass.callService("system_log", "clear");
|
||||
this._items = [];
|
||||
}
|
||||
|
||||
public async downloadLogs(): Promise<void> {
|
||||
const timeString = new Date().toISOString().replace(/:/g, "-");
|
||||
const downloadUrl = getErrorLogDownloadUrl(this.hass);
|
||||
const logFileName = `home-assistant_${timeString}.log`;
|
||||
const signedUrl = await getSignedPath(this.hass, downloadUrl);
|
||||
fileDownload(signedUrl.path, logFileName);
|
||||
}
|
||||
|
||||
public get activeFiltersCount(): number {
|
||||
return this._levelFilter.length + this._errorTypeFilter.length;
|
||||
}
|
||||
|
||||
public get hasItems(): boolean {
|
||||
return (this._items?.length || 0) > 0;
|
||||
}
|
||||
|
||||
public clearFilters(): void {
|
||||
this._levelFilter = [];
|
||||
this._errorTypeFilter = [];
|
||||
this._notifyFiltersState();
|
||||
}
|
||||
|
||||
private _timestamp(item: LoggedError): string {
|
||||
@@ -64,8 +87,30 @@ export class SystemLogCard extends LitElement {
|
||||
}
|
||||
|
||||
private _getFilteredItems = memoizeOne(
|
||||
(localize: LocalizeFunc, items: LoggedError[], filter: string) =>
|
||||
(
|
||||
localize: LocalizeFunc,
|
||||
items: LoggedError[],
|
||||
filter: string,
|
||||
levelFilter: string[],
|
||||
errorTypeFilter: (SystemLogErrorType | "unknown")[]
|
||||
) =>
|
||||
items.filter((item: LoggedError) => {
|
||||
if (levelFilter.length && !levelFilter.includes(item.level)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (errorTypeFilter.length) {
|
||||
const matchesKnown =
|
||||
item.error_type !== undefined &&
|
||||
errorTypeFilter.includes(item.error_type);
|
||||
const matchesUnknown =
|
||||
item.error_type === undefined &&
|
||||
errorTypeFilter.includes("unknown");
|
||||
if (!matchesKnown && !matchesUnknown) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (filter) {
|
||||
const integration = getLoggedErrorIntegration(item);
|
||||
return (
|
||||
@@ -74,6 +119,14 @@ export class SystemLogCard extends LitElement {
|
||||
) ||
|
||||
item.source[0].toLowerCase().includes(filter) ||
|
||||
item.name.toLowerCase().includes(filter) ||
|
||||
(item.error_type &&
|
||||
(item.error_type.includes(filter) ||
|
||||
this.hass
|
||||
.localize(
|
||||
`ui.panel.config.logs.error_type.${item.error_type}`
|
||||
)
|
||||
.toLowerCase()
|
||||
.includes(filter))) ||
|
||||
(integration &&
|
||||
domainToName(localize, integration)
|
||||
.toLowerCase()
|
||||
@@ -82,203 +135,203 @@ export class SystemLogCard extends LitElement {
|
||||
this._multipleMessages(item).toLowerCase().includes(filter)
|
||||
);
|
||||
}
|
||||
|
||||
return item;
|
||||
})
|
||||
);
|
||||
|
||||
protected render() {
|
||||
const filteredItems = this._items
|
||||
? this._getFilteredItems(
|
||||
this.hass.localize,
|
||||
this._items,
|
||||
this.filter.toLowerCase()
|
||||
)
|
||||
: [];
|
||||
if (this._items === undefined) {
|
||||
return html`
|
||||
<div class="loading-container">
|
||||
<ha-spinner></ha-spinner>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
const filteredItems = this._getFilteredItems(
|
||||
this.hass.localize,
|
||||
this._items,
|
||||
this.filter.toLowerCase(),
|
||||
this._levelFilter,
|
||||
this._errorTypeFilter
|
||||
);
|
||||
|
||||
const levels = [...new Set(this._items.map((item) => item.level))];
|
||||
const errorTypes = [
|
||||
...new Set(
|
||||
this._items
|
||||
.map((item) => item.error_type)
|
||||
.filter((type): type is SystemLogErrorType => Boolean(type))
|
||||
),
|
||||
];
|
||||
|
||||
const integrations = filteredItems.length
|
||||
? filteredItems.map((item) => getLoggedErrorIntegration(item))
|
||||
: [];
|
||||
return html`
|
||||
<div class="system-log-intro">
|
||||
<ha-card outlined>
|
||||
${this._items === undefined
|
||||
? html`
|
||||
<div class="loading-container">
|
||||
<ha-spinner></ha-spinner>
|
||||
</div>
|
||||
`
|
||||
: html`
|
||||
<div class="header">
|
||||
<h1 class="card-header">${this.header || "Logs"}</h1>
|
||||
<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-dropdown @wa-select=${this._handleOverflowAction}>
|
||||
<ha-icon-button
|
||||
slot="trigger"
|
||||
.path=${mdiDotsVertical}
|
||||
.label=${this.hass.localize("ui.common.menu")}
|
||||
></ha-icon-button>
|
||||
<ha-dropdown-item value="show-full-logs">
|
||||
<ha-svg-icon slot="icon" .path=${mdiText}></ha-svg-icon>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.logs.show_full_logs"
|
||||
)}
|
||||
</ha-dropdown-item>
|
||||
</ha-dropdown>
|
||||
</div>
|
||||
</div>
|
||||
${this._items.length === 0
|
||||
? html`
|
||||
<div class="card-content empty-content">
|
||||
${this.hass.localize("ui.panel.config.logs.no_issues")}
|
||||
</div>
|
||||
const hasActiveFilters = this.activeFiltersCount > 0;
|
||||
|
||||
const listContent =
|
||||
this._items.length === 0
|
||||
? html`
|
||||
<div class="card-content empty-content">
|
||||
${this.hass.localize("ui.panel.config.logs.no_issues")}
|
||||
</div>
|
||||
`
|
||||
: filteredItems.length === 0 && (this.filter || hasActiveFilters)
|
||||
? html`
|
||||
<div class="card-content">
|
||||
${this.filter
|
||||
? this.hass.localize(
|
||||
"ui.panel.config.logs.no_issues_search",
|
||||
{
|
||||
term: this.filter,
|
||||
}
|
||||
)
|
||||
: this.hass.localize("ui.panel.config.logs.no_issues")}
|
||||
</div>
|
||||
`
|
||||
: html`
|
||||
<div class="list-wrapper">
|
||||
<ha-list>
|
||||
${filteredItems.map(
|
||||
(item, idx) => html`
|
||||
<ha-list-item
|
||||
@click=${this._openLog}
|
||||
.logItem=${item}
|
||||
twoline
|
||||
>
|
||||
${item.message[0]}
|
||||
<span slot="secondary" class="secondary">
|
||||
${this._timestamp(item)} –
|
||||
${html`(<span class=${item.level}
|
||||
>${this.hass.localize(
|
||||
`ui.panel.config.logs.level.${item.level}`
|
||||
)}</span
|
||||
>) `}
|
||||
${item.error_type
|
||||
? html`(<span class="error-type-text"
|
||||
>${this.hass.localize(
|
||||
`ui.panel.config.logs.error_type.${item.error_type}`
|
||||
)}</span
|
||||
>) `
|
||||
: nothing}
|
||||
${integrations[idx]
|
||||
? `${domainToName(
|
||||
this.hass.localize,
|
||||
integrations[idx]!
|
||||
)}${
|
||||
isCustomIntegrationError(item)
|
||||
? ` (${this.hass.localize(
|
||||
"ui.panel.config.logs.custom_integration"
|
||||
)})`
|
||||
: ""
|
||||
}`
|
||||
: item.source[0]}
|
||||
${item.count > 1
|
||||
? html` - ${this._multipleMessages(item)} `
|
||||
: nothing}
|
||||
</span>
|
||||
</ha-list-item>
|
||||
`
|
||||
: filteredItems.length === 0 && this.filter
|
||||
? html`<div class="card-content">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.logs.no_issues_search",
|
||||
{ term: this.filter }
|
||||
)}
|
||||
</div>`
|
||||
: html`<ha-list
|
||||
>${filteredItems.map(
|
||||
(item, idx) => html`
|
||||
<ha-list-item
|
||||
@click=${this._openLog}
|
||||
.logItem=${item}
|
||||
twoline
|
||||
>
|
||||
${item.message[0]}
|
||||
<span slot="secondary" class="secondary">
|
||||
${this._timestamp(item)} –
|
||||
${html`(<span class=${item.level}
|
||||
>${this.hass.localize(
|
||||
`ui.panel.config.logs.level.${item.level}`
|
||||
)}</span
|
||||
>) `}
|
||||
${integrations[idx]
|
||||
? `${domainToName(
|
||||
this.hass!.localize,
|
||||
integrations[idx]!
|
||||
)}${
|
||||
isCustomIntegrationError(item)
|
||||
? ` (${this.hass.localize(
|
||||
"ui.panel.config.logs.custom_integration"
|
||||
)})`
|
||||
: ""
|
||||
}`
|
||||
: item.source[0]}
|
||||
${item.count > 1
|
||||
? html` - ${this._multipleMessages(item)} `
|
||||
: nothing}
|
||||
</span>
|
||||
</ha-list-item>
|
||||
`
|
||||
)}</ha-list
|
||||
>`}
|
||||
)}
|
||||
</ha-list>
|
||||
</div>
|
||||
`;
|
||||
|
||||
<div class="card-actions">
|
||||
<ha-call-service-button
|
||||
.hass=${this.hass}
|
||||
domain="system_log"
|
||||
service="clear"
|
||||
>${this.hass.localize(
|
||||
"ui.panel.config.logs.clear"
|
||||
)}</ha-call-service-button
|
||||
>
|
||||
</div>
|
||||
`}
|
||||
</ha-card>
|
||||
</div>
|
||||
`;
|
||||
return this.showFilters
|
||||
? html`
|
||||
<div class="content-layout">
|
||||
<div class="pane">
|
||||
<div class="pane-content">
|
||||
<ha-filter-states
|
||||
.hass=${this.hass}
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.logs.level_filter"
|
||||
)}
|
||||
.states=${levels.map((level) => ({
|
||||
value: level,
|
||||
label: this.hass.localize(
|
||||
`ui.panel.config.logs.level.${level}`
|
||||
),
|
||||
}))}
|
||||
.value=${this._levelFilter}
|
||||
@data-table-filter-changed=${this._levelFilterChanged}
|
||||
></ha-filter-states>
|
||||
<ha-filter-states
|
||||
.hass=${this.hass}
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.logs.classification"
|
||||
)}
|
||||
.states=${[
|
||||
...errorTypes.map((errorType) => ({
|
||||
value: errorType,
|
||||
label: this.hass.localize(
|
||||
`ui.panel.config.logs.error_type.${errorType}`
|
||||
),
|
||||
})),
|
||||
{
|
||||
value: "unknown",
|
||||
label: this.hass.localize("ui.panel.config.logs.other"),
|
||||
},
|
||||
]}
|
||||
.value=${this._errorTypeFilter}
|
||||
@data-table-filter-changed=${this._errorTypeFilterChanged}
|
||||
></ha-filter-states>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="content-main">${listContent}</div>
|
||||
</div>
|
||||
`
|
||||
: listContent;
|
||||
}
|
||||
|
||||
protected firstUpdated(changedProps): void {
|
||||
super.firstUpdated(changedProps);
|
||||
this.fetchData();
|
||||
this.loaded = true;
|
||||
this.addEventListener("hass-service-called", (ev) =>
|
||||
this.serviceCalled(ev)
|
||||
this._notifyFiltersState();
|
||||
}
|
||||
|
||||
private _levelFilterChanged(ev): void {
|
||||
this._levelFilter = ev.detail.value || [];
|
||||
this._notifyFiltersState();
|
||||
}
|
||||
|
||||
private _errorTypeFilterChanged(ev): void {
|
||||
this._errorTypeFilter = ev.detail.value || [];
|
||||
this._notifyFiltersState();
|
||||
}
|
||||
|
||||
private _notifyFiltersState(): void {
|
||||
this.dispatchEvent(
|
||||
new CustomEvent("system-log-filters-changed", {
|
||||
detail: {
|
||||
open: this.showFilters,
|
||||
count: this.activeFiltersCount,
|
||||
},
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
protected serviceCalled(ev): void {
|
||||
// Check if this is for us
|
||||
if (ev.detail.success && ev.detail.domain === "system_log") {
|
||||
// Do the right thing depending on service
|
||||
if (ev.detail.service === "clear") {
|
||||
this._items = [];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private _handleOverflowAction(ev: HaDropdownSelectEvent) {
|
||||
if (ev.detail.item.value === "show-full-logs") {
|
||||
// @ts-ignore
|
||||
fireEvent(this, "switch-log-view");
|
||||
}
|
||||
}
|
||||
|
||||
private async _downloadLogs() {
|
||||
const timeString = new Date().toISOString().replace(/:/g, "-");
|
||||
const downloadUrl = getErrorLogDownloadUrl(this.hass);
|
||||
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 });
|
||||
}
|
||||
|
||||
static styles = css`
|
||||
ha-card {
|
||||
padding-top: 8px;
|
||||
:host {
|
||||
display: block;
|
||||
direction: var(--direction);
|
||||
min-height: 0;
|
||||
height: 100%;
|
||||
background: var(--primary-background-color);
|
||||
}
|
||||
|
||||
:host {
|
||||
direction: var(--direction);
|
||||
}
|
||||
ha-list {
|
||||
direction: ltr;
|
||||
}
|
||||
|
||||
.header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: 0 16px;
|
||||
}
|
||||
|
||||
.header-buttons {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.card-header {
|
||||
color: var(--ha-card-header-color, var(--primary-text-color));
|
||||
font-family: var(--ha-card-header-font-family, inherit);
|
||||
font-size: var(--ha-card-header-font-size, var(--ha-font-size-2xl));
|
||||
letter-spacing: -0.012em;
|
||||
line-height: var(--ha-line-height-expanded);
|
||||
display: block;
|
||||
margin-block-start: 0px;
|
||||
font-weight: var(--ha-font-weight-normal);
|
||||
}
|
||||
|
||||
.system-log-intro {
|
||||
margin: 16px;
|
||||
background: var(--card-background-color);
|
||||
}
|
||||
|
||||
.loading-container {
|
||||
@@ -288,6 +341,39 @@ export class SystemLogCard extends LitElement {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.content-layout {
|
||||
display: flex;
|
||||
min-height: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.content-main {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
background: var(--card-background-color);
|
||||
}
|
||||
|
||||
.list-wrapper {
|
||||
border-top: 1px solid var(--divider-color);
|
||||
min-height: 100%;
|
||||
background: var(--card-background-color);
|
||||
}
|
||||
|
||||
.pane {
|
||||
flex: 0 0 var(--sidepane-width, 250px);
|
||||
width: var(--sidepane-width, 250px);
|
||||
border-inline-end: 1px solid var(--divider-color);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 0;
|
||||
background: var(--primary-background-color);
|
||||
}
|
||||
|
||||
.pane-content {
|
||||
overflow: auto;
|
||||
background: var(--primary-background-color);
|
||||
}
|
||||
|
||||
.error {
|
||||
color: var(--error-color);
|
||||
}
|
||||
@@ -296,15 +382,33 @@ export class SystemLogCard extends LitElement {
|
||||
color: var(--warning-color);
|
||||
}
|
||||
|
||||
.error-type-text {
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
|
||||
.card-content {
|
||||
border-top: 1px solid var(--divider-color);
|
||||
padding-top: 16px;
|
||||
padding-bottom: 16px;
|
||||
min-height: 100%;
|
||||
background: var(--card-background-color);
|
||||
}
|
||||
|
||||
.row-secondary {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
@media (max-width: 900px) {
|
||||
.content-layout {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.pane {
|
||||
width: 100%;
|
||||
border-inline-end: none;
|
||||
border-top: 1px solid var(--divider-color);
|
||||
}
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
|
||||
@@ -22,7 +22,6 @@ export class EnergyOverviewViewStrategy extends ReactiveElement {
|
||||
dense_section_placement: true,
|
||||
max_columns: 3,
|
||||
footer: {
|
||||
column_span: 1.1,
|
||||
card: {
|
||||
type: "energy-date-selection",
|
||||
collection_key: collectionKey,
|
||||
|
||||
@@ -23,7 +23,6 @@ export class EnergyViewStrategy extends ReactiveElement {
|
||||
max_columns: 3,
|
||||
sections: [],
|
||||
footer: {
|
||||
column_span: 1.1,
|
||||
card: {
|
||||
type: "energy-date-selection",
|
||||
collection_key: collectionKey,
|
||||
|
||||
@@ -21,7 +21,6 @@ export class GasViewStrategy extends ReactiveElement {
|
||||
max_columns: 3,
|
||||
sections: [{ type: "grid", cards: [], column_span: 3 }],
|
||||
footer: {
|
||||
column_span: 1.1,
|
||||
card: {
|
||||
type: "energy-date-selection",
|
||||
collection_key: collectionKey,
|
||||
|
||||
@@ -22,7 +22,6 @@ export class WaterViewStrategy extends ReactiveElement {
|
||||
max_columns: 3,
|
||||
sections: [{ type: "grid", cards: [], column_span: 3 }],
|
||||
footer: {
|
||||
column_span: 1.1,
|
||||
card: {
|
||||
type: "energy-date-selection",
|
||||
collection_key: collectionKey,
|
||||
|
||||
@@ -91,7 +91,6 @@ export class HuiDialogEditViewFooter extends LitElement {
|
||||
<hui-view-footer-settings-editor
|
||||
.hass=${this.hass}
|
||||
.config=${this._config}
|
||||
.maxColumns=${this._params.maxColumns}
|
||||
@config-changed=${this._configChanged}
|
||||
></hui-view-footer-settings-editor>
|
||||
`;
|
||||
@@ -106,7 +105,7 @@ export class HuiDialogEditViewFooter extends LitElement {
|
||||
.hass=${this.hass}
|
||||
.open=${this._open}
|
||||
header-title=${title}
|
||||
.width=${this._yamlMode ? "full" : "large"}
|
||||
width="medium"
|
||||
@closed=${this._dialogClosed}
|
||||
class=${this._yamlMode ? "yaml-mode" : ""}
|
||||
>
|
||||
|
||||
@@ -1,53 +1,48 @@
|
||||
import { html, LitElement } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import "../../../../components/ha-form/ha-form";
|
||||
import type {
|
||||
HaFormSchema,
|
||||
SchemaUnion,
|
||||
} from "../../../../components/ha-form/types";
|
||||
import type { LovelaceViewFooterConfig } from "../../../../data/lovelace/config/view";
|
||||
import {
|
||||
DEFAULT_FOOTER_MAX_WIDTH_PX,
|
||||
type LovelaceViewFooterConfig,
|
||||
} from "../../../../data/lovelace/config/view";
|
||||
import type { HomeAssistant } from "../../../../types";
|
||||
|
||||
const SCHEMA = [
|
||||
{
|
||||
name: "max_width",
|
||||
selector: {
|
||||
number: {
|
||||
min: 100,
|
||||
max: 1600,
|
||||
step: 10,
|
||||
unit_of_measurement: "px",
|
||||
},
|
||||
},
|
||||
},
|
||||
] as const satisfies HaFormSchema[];
|
||||
|
||||
@customElement("hui-view-footer-settings-editor")
|
||||
export class HuiViewFooterSettingsEditor extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property({ attribute: false }) public config?: LovelaceViewFooterConfig;
|
||||
|
||||
@property({ attribute: false }) public maxColumns = 4;
|
||||
|
||||
private _schema = memoizeOne(
|
||||
(maxColumns: number) =>
|
||||
[
|
||||
{
|
||||
name: "column_span",
|
||||
selector: {
|
||||
number: {
|
||||
min: 1,
|
||||
max: maxColumns,
|
||||
slider_ticks: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
] as const satisfies HaFormSchema[]
|
||||
);
|
||||
|
||||
protected render() {
|
||||
const data = {
|
||||
column_span: this.config?.column_span || 1,
|
||||
max_width: this.config?.max_width || DEFAULT_FOOTER_MAX_WIDTH_PX,
|
||||
};
|
||||
|
||||
const schema = this._schema(this.maxColumns);
|
||||
|
||||
return html`
|
||||
<ha-form
|
||||
.hass=${this.hass}
|
||||
.data=${data}
|
||||
.schema=${schema}
|
||||
.schema=${SCHEMA}
|
||||
.computeLabel=${this._computeLabel}
|
||||
.computeHelper=${this._computeHelper}
|
||||
@value-changed=${this._valueChanged}
|
||||
></ha-form>
|
||||
`;
|
||||
@@ -65,19 +60,10 @@ export class HuiViewFooterSettingsEditor extends LitElement {
|
||||
fireEvent(this, "config-changed", { config });
|
||||
}
|
||||
|
||||
private _computeLabel = (
|
||||
schema: SchemaUnion<ReturnType<typeof this._schema>>
|
||||
) =>
|
||||
private _computeLabel = (schema: SchemaUnion<typeof SCHEMA>) =>
|
||||
this.hass.localize(
|
||||
`ui.panel.lovelace.editor.edit_view_footer.settings.${schema.name}`
|
||||
);
|
||||
|
||||
private _computeHelper = (
|
||||
schema: SchemaUnion<ReturnType<typeof this._schema>>
|
||||
) =>
|
||||
this.hass.localize(
|
||||
`ui.panel.lovelace.editor.edit_view_footer.settings.${schema.name}_helper`
|
||||
) || "";
|
||||
}
|
||||
|
||||
declare global {
|
||||
|
||||
@@ -4,7 +4,6 @@ import type { LovelaceViewFooterConfig } from "../../../../data/lovelace/config/
|
||||
export interface EditViewFooterDialogParams {
|
||||
saveConfig: (config: LovelaceViewFooterConfig) => void;
|
||||
config: LovelaceViewFooterConfig;
|
||||
maxColumns: number;
|
||||
}
|
||||
|
||||
export const showEditViewFooterDialog = (
|
||||
|
||||
@@ -7,9 +7,10 @@ import { styleMap } from "lit/directives/style-map";
|
||||
import "../../../components/ha-ripple";
|
||||
import "../../../components/ha-svg-icon";
|
||||
import type { LovelaceCardConfig } from "../../../data/lovelace/config/card";
|
||||
import type {
|
||||
LovelaceViewConfig,
|
||||
LovelaceViewFooterConfig,
|
||||
import {
|
||||
DEFAULT_FOOTER_MAX_WIDTH_PX,
|
||||
type LovelaceViewConfig,
|
||||
type LovelaceViewFooterConfig,
|
||||
} from "../../../data/lovelace/config/view";
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
import type { HuiCard } from "../cards/hui-card";
|
||||
@@ -19,7 +20,6 @@ import { showEditCardDialog } from "../editor/card-editor/show-edit-card-dialog"
|
||||
import { replaceView } from "../editor/config-util";
|
||||
import { showEditViewFooterDialog } from "../editor/view-footer/show-edit-view-footer-dialog";
|
||||
import type { Lovelace } from "../types";
|
||||
import { DEFAULT_MAX_COLUMNS } from "./hui-sections-view";
|
||||
|
||||
@customElement("hui-view-footer")
|
||||
export class HuiViewFooter extends LitElement {
|
||||
@@ -97,13 +97,8 @@ export class HuiViewFooter extends LitElement {
|
||||
}
|
||||
|
||||
private _configure() {
|
||||
const viewConfig = this.lovelace.config.views[
|
||||
this.viewIndex
|
||||
] as LovelaceViewConfig;
|
||||
|
||||
showEditViewFooterDialog(this, {
|
||||
config: this.config || {},
|
||||
maxColumns: viewConfig.max_columns || DEFAULT_MAX_COLUMNS,
|
||||
saveConfig: (newConfig: LovelaceViewFooterConfig) => {
|
||||
this._saveFooterConfig(newConfig);
|
||||
},
|
||||
@@ -180,13 +175,11 @@ export class HuiViewFooter extends LitElement {
|
||||
|
||||
if (!card && !editMode) return nothing;
|
||||
|
||||
const columnSpan = this.config?.column_span || 1;
|
||||
|
||||
return html`
|
||||
<div
|
||||
class=${classMap({ wrapper: true, "edit-mode": editMode })}
|
||||
style=${styleMap({
|
||||
"--footer-column-span": String(columnSpan),
|
||||
"--footer-max-width": `${this.config?.max_width || DEFAULT_FOOTER_MAX_WIDTH_PX}px`,
|
||||
})}
|
||||
>
|
||||
${editMode
|
||||
@@ -228,23 +221,18 @@ export class HuiViewFooter extends LitElement {
|
||||
|
||||
:host([sticky]) {
|
||||
position: sticky;
|
||||
bottom: 0;
|
||||
bottom: var(--row-gap);
|
||||
z-index: 4;
|
||||
}
|
||||
|
||||
.wrapper {
|
||||
padding: var(--ha-space-4) 0;
|
||||
padding-bottom: max(
|
||||
var(--ha-space-4),
|
||||
var(--safe-area-inset-bottom, 0px)
|
||||
padding: var(--ha-space-2) 0;
|
||||
padding-bottom: calc(
|
||||
max(var(--ha-space-2), var(--safe-area-inset-bottom, 0px))
|
||||
);
|
||||
box-sizing: content-box;
|
||||
margin: 0 auto;
|
||||
max-width: calc(
|
||||
var(--footer-column-span, 1) / var(--column-count, 1) * 100% +
|
||||
(var(--footer-column-span, 1) - var(--column-count, 1)) /
|
||||
var(--column-count, 1) * var(--column-gap, 32px)
|
||||
);
|
||||
max-width: var(--footer-max-width, 600px);
|
||||
}
|
||||
|
||||
.wrapper:not(.edit-mode) {
|
||||
@@ -315,7 +303,7 @@ export class HuiViewFooter extends LitElement {
|
||||
border-bottom-left-radius: 0px;
|
||||
border-bottom-right-radius: 0px;
|
||||
background: var(--secondary-background-color);
|
||||
--mdc-icon-button-size: 36px;
|
||||
--ha-icon-button-size: 36px;
|
||||
--mdc-icon-size: 20px;
|
||||
color: var(--primary-text-color);
|
||||
}
|
||||
|
||||
@@ -4195,6 +4195,21 @@
|
||||
"info": "INFO",
|
||||
"debug": "DEBUG"
|
||||
},
|
||||
"error_type": {
|
||||
"auth": "Authentication",
|
||||
"connection": "Connection",
|
||||
"invalid_response": "Invalid response",
|
||||
"rate_limit": "Rate limit",
|
||||
"server": "Server",
|
||||
"slow_setup": "Slow setup",
|
||||
"timeout": "Timeout",
|
||||
"ssl": "TLS/SSL",
|
||||
"statistics": "Statistics",
|
||||
"dns": "DNS"
|
||||
},
|
||||
"level_filter": "Level",
|
||||
"classification": "Classification",
|
||||
"other": "Other",
|
||||
"custom_integration": "custom integration",
|
||||
"error_from_custom_integration": "This error originated from a custom integration.",
|
||||
"show_full_logs": "Show raw logs",
|
||||
@@ -4219,6 +4234,7 @@
|
||||
"integration": "[%key:ui::panel::config::integrations::integration%]",
|
||||
"documentation": "documentation",
|
||||
"issues": "issues",
|
||||
"error_type": "Error type",
|
||||
"first_occurred": "First occurred",
|
||||
"number_of_occurrences": "{count} {count, plural,\n one {occurrence}\n other {occurrences}\n}",
|
||||
"last_logged": "Last logged"
|
||||
@@ -8496,8 +8512,7 @@
|
||||
"edit_yaml": "[%key:ui::panel::lovelace::editor::edit_view::edit_yaml%]",
|
||||
"saving_failed": "[%key:ui::panel::lovelace::editor::edit_view::saving_failed%]",
|
||||
"settings": {
|
||||
"column_span": "Width",
|
||||
"column_span_helper": "[%key:ui::panel::lovelace::editor::edit_section::settings::column_span_helper%]"
|
||||
"max_width": "Max width"
|
||||
}
|
||||
},
|
||||
"edit_badges": {
|
||||
|
||||
35
yarn.lock
35
yarn.lock
@@ -6735,10 +6735,10 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"commander@npm:^14.0.2":
|
||||
version: 14.0.2
|
||||
resolution: "commander@npm:14.0.2"
|
||||
checksum: 10/2d202db5e5f9bb770112a3c1579b893d17ac6f6d932183077308bdd96d0f87f0bbe6a68b5b9ed2cf3b2514be6bb7de637480703c0e2db9741ee1b383237deb26
|
||||
"commander@npm:^14.0.3":
|
||||
version: 14.0.3
|
||||
resolution: "commander@npm:14.0.3"
|
||||
checksum: 10/dfa9ebe2a433d277de5cb0252d23b10a543d245d892db858d23b516336a835c50fd4f52bee4cd13c705cc8acb6f03dc632c73dd806f7d06d3353eb09953dd17a
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -9312,7 +9312,7 @@ __metadata:
|
||||
leaflet: "npm:1.9.4"
|
||||
leaflet-draw: "patch:leaflet-draw@npm%3A1.0.4#./.yarn/patches/leaflet-draw-npm-1.0.4-0ca0ebcf65.patch"
|
||||
leaflet.markercluster: "npm:1.5.3"
|
||||
lint-staged: "npm:16.2.7"
|
||||
lint-staged: "npm:16.3.0"
|
||||
lit: "npm:3.3.2"
|
||||
lit-analyzer: "npm:2.0.3"
|
||||
lit-html: "npm:3.3.2"
|
||||
@@ -10629,20 +10629,20 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"lint-staged@npm:16.2.7":
|
||||
version: 16.2.7
|
||||
resolution: "lint-staged@npm:16.2.7"
|
||||
"lint-staged@npm:16.3.0":
|
||||
version: 16.3.0
|
||||
resolution: "lint-staged@npm:16.3.0"
|
||||
dependencies:
|
||||
commander: "npm:^14.0.2"
|
||||
commander: "npm:^14.0.3"
|
||||
listr2: "npm:^9.0.5"
|
||||
micromatch: "npm:^4.0.8"
|
||||
nano-spawn: "npm:^2.0.0"
|
||||
pidtree: "npm:^0.6.0"
|
||||
string-argv: "npm:^0.3.2"
|
||||
yaml: "npm:^2.8.1"
|
||||
tinyexec: "npm:^1.0.2"
|
||||
yaml: "npm:^2.8.2"
|
||||
bin:
|
||||
lint-staged: bin/lint-staged.js
|
||||
checksum: 10/c1fd7685300800ea6d3f073cb450f9e3d2a83966e7c6785ea9608a08e77e1e8e5f1958f77b98ccd7d423daa53bb36016d6fd96a98d22234d0f7f56d7b3f360f2
|
||||
checksum: 10/0ae6d4bbef000b06f36af8c16e825aa653c4ccca2c2f664a596f2f3a65c4b89f876a822c412837596651e0b54aa0ac926b697fb65d3ff959d2d89a13e506ec00
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -11936,15 +11936,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"pidtree@npm:^0.6.0":
|
||||
version: 0.6.0
|
||||
resolution: "pidtree@npm:0.6.0"
|
||||
bin:
|
||||
pidtree: bin/pidtree.js
|
||||
checksum: 10/ea67fb3159e170fd069020e0108ba7712df9f0fd13c8db9b2286762856ddce414fb33932e08df4bfe36e91fe860b51852aee49a6f56eb4714b69634343add5df
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"pinst@npm:3.0.0":
|
||||
version: 3.0.0
|
||||
resolution: "pinst@npm:3.0.0"
|
||||
@@ -15604,7 +15595,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"yaml@npm:^2.8.1":
|
||||
"yaml@npm:^2.8.2":
|
||||
version: 2.8.2
|
||||
resolution: "yaml@npm:2.8.2"
|
||||
bin:
|
||||
|
||||
Reference in New Issue
Block a user