mirror of
https://github.com/home-assistant/frontend.git
synced 2026-10-30 17:43:39 +00:00
Compare commits
9
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5618b95304 | ||
|
|
4152579b39 | ||
|
|
318e935b98 | ||
|
|
a14c59ef6c | ||
|
|
18c88d18d6 | ||
|
|
f79da7299f | ||
|
|
291f0a92f5 | ||
|
|
938b207b8d | ||
|
|
c74a2e4765 |
No files matched your search
@@ -58,6 +58,10 @@ export class HaAnsiToHtml extends LitElement {
|
||||
// The hidden lines each expand row shows when clicked.
|
||||
private _expandGaps = new WeakMap<HTMLElement, HTMLDivElement[]>();
|
||||
|
||||
public get hasLines(): boolean {
|
||||
return !!this._pre?.childElementCount;
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return html`<pre class=${classMap({ wrap: !this.wrapDisabled })}></pre>`;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
import { ResizeController } from "@lit-labs/observers/resize-controller";
|
||||
import { css, html, LitElement, unsafeCSS } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { ifDefined } from "lit/directives/if-defined";
|
||||
import { styleMap } from "lit/directives/style-map";
|
||||
import "../animation/ha-fade-in";
|
||||
import "./ha-skeleton-text";
|
||||
|
||||
// Varied widths so the placeholder reads as log lines rather than a block
|
||||
const LINE_WIDTHS = [72, 58, 86, 64, 44, 78, 52, 90, 60, 70];
|
||||
|
||||
const LINE_HEIGHT_EM = 1.5;
|
||||
|
||||
/**
|
||||
* Placeholder for log lines while a log loads. It covers its nearest
|
||||
* positioned ancestor and shows as many whole lines as fit, so it never
|
||||
* scrolls. Set `lines` to show that many lines in the flow instead. Like the
|
||||
* data table's loading rows, the label is given to assistive technology
|
||||
* rather than shown.
|
||||
*
|
||||
* @cssprop --ha-skeleton-log-padding - The space around the lines. Defaults to `var(--ha-space-2)`.
|
||||
*/
|
||||
@customElement("ha-skeleton-log")
|
||||
export class HaSkeletonLog extends LitElement {
|
||||
@property() public label?: string;
|
||||
|
||||
@property({ type: Number, reflect: true }) public lines?: number;
|
||||
|
||||
private _fitLineCount = new ResizeController(this, {
|
||||
callback: (entries) => {
|
||||
const height = entries[0]?.contentRect.height ?? 0;
|
||||
|
||||
const lineHeight =
|
||||
parseFloat(getComputedStyle(this).fontSize) * LINE_HEIGHT_EM;
|
||||
|
||||
return lineHeight ? Math.floor(height / lineHeight) : 0;
|
||||
},
|
||||
});
|
||||
|
||||
protected render() {
|
||||
return html`
|
||||
<ha-fade-in .duration=${300} easing="ease-in">
|
||||
<div role="progressbar" aria-label=${ifDefined(this.label)}>
|
||||
${Array.from(
|
||||
{ length: this.lines ?? this._fitLineCount.value ?? 0 },
|
||||
(_, i) =>
|
||||
html`<div class="line">
|
||||
<ha-skeleton-text
|
||||
style=${styleMap({
|
||||
"--ha-skeleton-text-width": `${LINE_WIDTHS[i % LINE_WIDTHS.length]}%`,
|
||||
})}
|
||||
></ha-skeleton-text>
|
||||
</div>`
|
||||
)}
|
||||
</div>
|
||||
</ha-fade-in>
|
||||
`;
|
||||
}
|
||||
|
||||
static styles = css`
|
||||
:host {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
box-sizing: border-box;
|
||||
padding: var(--ha-skeleton-log-padding, var(--ha-space-2));
|
||||
overflow: hidden;
|
||||
}
|
||||
:host([lines]) {
|
||||
display: block;
|
||||
position: static;
|
||||
}
|
||||
.line {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: ${unsafeCSS(LINE_HEIGHT_EM)}em;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-skeleton-log": HaSkeletonLog;
|
||||
}
|
||||
}
|
||||
@@ -19,6 +19,7 @@ import type { HaLogViewer } from "../../../components/ha-log-viewer";
|
||||
import "../../../components/ha-svg-icon";
|
||||
import "../../../components/input/ha-input-search";
|
||||
import type { HaInputSearch } from "../../../components/input/ha-input-search";
|
||||
import "../../../components/skeleton/ha-skeleton-log";
|
||||
import {
|
||||
connectionContext,
|
||||
internationalizationContext,
|
||||
@@ -51,6 +52,9 @@ class DialogLiveLog extends DialogMixin<LiveLogDialogParams>(LitElement) {
|
||||
|
||||
@state() private _live = false;
|
||||
|
||||
// The backend answers the subscription once it has read the backlog
|
||||
@state() private _loading = false;
|
||||
|
||||
@state() private _search = "";
|
||||
|
||||
@state() private _wrapLines = true;
|
||||
@@ -84,9 +88,11 @@ class DialogLiveLog extends DialogMixin<LiveLogDialogParams>(LitElement) {
|
||||
(lines) => this._addLines(lines)
|
||||
);
|
||||
this._unsub = unsub;
|
||||
this._loading = true;
|
||||
unsub.then(
|
||||
() => {
|
||||
if (this._unsub === unsub) {
|
||||
this._loading = false;
|
||||
this._live = true;
|
||||
}
|
||||
},
|
||||
@@ -95,6 +101,7 @@ class DialogLiveLog extends DialogMixin<LiveLogDialogParams>(LitElement) {
|
||||
return;
|
||||
}
|
||||
this._unsub = undefined;
|
||||
this._loading = false;
|
||||
if (err?.code === ERR_UNKNOWN_COMMAND) {
|
||||
this._unsupported = true;
|
||||
} else if (
|
||||
@@ -130,6 +137,7 @@ class DialogLiveLog extends DialogMixin<LiveLogDialogParams>(LitElement) {
|
||||
// _subscribe reports subscription errors, so only ignore them here
|
||||
this._unsub?.then((unsub) => unsub()).catch(() => undefined);
|
||||
this._unsub = undefined;
|
||||
this._loading = false;
|
||||
this._live = false;
|
||||
}
|
||||
|
||||
@@ -209,12 +217,18 @@ class DialogLiveLog extends DialogMixin<LiveLogDialogParams>(LitElement) {
|
||||
${
|
||||
this._lineCount
|
||||
? nothing
|
||||
: html`<div class="waiting">
|
||||
${this._i18n.localize(
|
||||
"ui.panel.config.integrations.config_entry.live_log.waiting",
|
||||
{ integration: this.params.name }
|
||||
)}
|
||||
</div>`
|
||||
: this._loading
|
||||
? html`<ha-skeleton-log
|
||||
.label=${this._i18n.localize(
|
||||
"ui.panel.config.logs.loading_log"
|
||||
)}
|
||||
></ha-skeleton-log>`
|
||||
: html`<div class="waiting">
|
||||
${this._i18n.localize(
|
||||
"ui.panel.config.integrations.config_entry.live_log.waiting",
|
||||
{ integration: this.params.name }
|
||||
)}
|
||||
</div>`
|
||||
}
|
||||
</ha-log-viewer>
|
||||
${
|
||||
|
||||
@@ -35,8 +35,8 @@ import "../../../components/ha-card";
|
||||
import "../../../components/ha-dropdown";
|
||||
import "../../../components/ha-dropdown-item";
|
||||
import "../../../components/ha-icon-button";
|
||||
import "../../../components/ha-spinner";
|
||||
import "../../../components/ha-svg-icon";
|
||||
import "../../../components/skeleton/ha-skeleton-log";
|
||||
|
||||
import { getSignedPath } from "../../../data/auth";
|
||||
|
||||
@@ -337,9 +337,10 @@ class ErrorLogCard extends LitElement {
|
||||
<div id="scroll-top-marker"></div>
|
||||
${
|
||||
this._loadingPrevState === "loading"
|
||||
? html`<div class="loading-old">
|
||||
<ha-spinner></ha-spinner>
|
||||
</div>`
|
||||
? html`<ha-skeleton-log
|
||||
lines="3"
|
||||
.label=${localize("ui.panel.config.logs.loading_log")}
|
||||
></ha-skeleton-log>`
|
||||
: nothing
|
||||
}
|
||||
${
|
||||
@@ -350,9 +351,13 @@ class ErrorLogCard extends LitElement {
|
||||
)}
|
||||
</ha-alert>`
|
||||
: this._loadingState === "loading"
|
||||
? html`<div>
|
||||
${localize("ui.panel.config.logs.loading_log")}
|
||||
</div>`
|
||||
? // Lines kept while reconnecting stay visible, unless the search hides them all
|
||||
this._ansiToHtmlElement?.hasLines &&
|
||||
!(this.filter && this._noSearchResults)
|
||||
? nothing
|
||||
: html`<ha-skeleton-log
|
||||
.label=${localize("ui.panel.config.logs.loading_log")}
|
||||
></ha-skeleton-log>`
|
||||
: this._loadingState === "empty"
|
||||
? html`<div>
|
||||
${
|
||||
@@ -749,6 +754,8 @@ class ErrorLogCard extends LitElement {
|
||||
// The backlog is sent again on every subscription
|
||||
this._integrationLines = [];
|
||||
this._ansiToHtmlElement?.clear();
|
||||
// Show the skeleton again now the retained lines are gone
|
||||
this.requestUpdate();
|
||||
|
||||
// Each subscription starts with the integration's backlog, which are
|
||||
// merged in time order with the lines sent meanwhile once all arrived.
|
||||
@@ -1106,6 +1113,14 @@ class ErrorLogCard extends LitElement {
|
||||
overflow-wrap: break-word;
|
||||
}
|
||||
|
||||
ha-skeleton-log {
|
||||
--ha-skeleton-log-padding: var(--ha-space-4);
|
||||
}
|
||||
|
||||
ha-skeleton-log[lines] {
|
||||
--ha-skeleton-log-padding: var(--ha-space-2) 0;
|
||||
}
|
||||
|
||||
.error-log > div:hover {
|
||||
background-color: var(--secondary-background-color);
|
||||
}
|
||||
@@ -1131,13 +1146,6 @@ class ErrorLogCard extends LitElement {
|
||||
color: var(--warning-color);
|
||||
}
|
||||
|
||||
.loading-old {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
justify-content: center;
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
ha-assist-chip {
|
||||
--ha-assist-chip-container-shape: 10px;
|
||||
--ha-assist-chip-trailing-space: 8px;
|
||||
|
||||
@@ -2,10 +2,12 @@ import { mdiDotsVertical, mdiDownload, mdiRefresh, mdiText } from "@mdi/js";
|
||||
import type { PropertyValues } from "lit";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { styleMap } from "lit/directives/style-map";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { fireEvent } from "../../../common/dom/fire_event";
|
||||
import type { HASSDomCurrentTargetEvent } from "../../../common/dom/fire_event";
|
||||
import type { LocalizeFunc } from "../../../common/translations/localize";
|
||||
import "../../../components/animation/ha-fade-in";
|
||||
import "../../../components/buttons/ha-call-service-button";
|
||||
import "../../../components/ha-alert";
|
||||
import "../../../components/ha-card";
|
||||
@@ -14,7 +16,7 @@ import "../../../components/ha-dropdown-item";
|
||||
import "../../../components/ha-icon-button";
|
||||
import "../../../components/ha-list";
|
||||
import "../../../components/ha-list-item";
|
||||
import "../../../components/ha-spinner";
|
||||
import "../../../components/skeleton/ha-skeleton-text";
|
||||
import { getSignedPath } from "../../../data/auth";
|
||||
import {
|
||||
getCoreLogFileDownloadUnavailableReason,
|
||||
@@ -34,6 +36,14 @@ import { showSystemLogDetailDialog } from "./show-dialog-system-log-detail";
|
||||
import { formatSystemLogTime } from "./util";
|
||||
import type { HaDropdownSelectEvent } from "../../../components/ha-dropdown";
|
||||
|
||||
// Primary and secondary line widths, in percent, for the loading rows
|
||||
const SKELETON_ROW_WIDTHS = [
|
||||
[72, 40],
|
||||
[56, 48],
|
||||
[84, 36],
|
||||
[64, 44],
|
||||
];
|
||||
|
||||
@customElement("system-log-card")
|
||||
export class SystemLogCard extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
@@ -128,157 +138,168 @@ export class SystemLogCard extends LitElement {
|
||||
return html`
|
||||
<div class="system-log-intro">
|
||||
<ha-card outlined>
|
||||
<div class="header">
|
||||
<h1 class="card-header">
|
||||
${
|
||||
this.header ||
|
||||
this.hass.localize("ui.panel.config.logs.caption")
|
||||
}
|
||||
</h1>
|
||||
<div class="header-buttons">
|
||||
${
|
||||
logFileDownloadUnavailableReason
|
||||
? nothing
|
||||
: html`<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>
|
||||
|
||||
${
|
||||
logFileDownloadUnavailableReason && !this.integrations?.length
|
||||
? nothing
|
||||
: html`<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>
|
||||
${
|
||||
logFileDownloadUnavailableReason
|
||||
? html`<ha-alert alert-type="warning">
|
||||
${this.hass.localize(
|
||||
`ui.panel.config.logs.log_file_disabled.${logFileDownloadUnavailableReason}`
|
||||
)}
|
||||
</ha-alert>`
|
||||
: nothing
|
||||
}
|
||||
${
|
||||
this._items === undefined
|
||||
? html`
|
||||
<div class="loading-container">
|
||||
<ha-spinner></ha-spinner>
|
||||
? html`<ha-fade-in .duration=${300} easing="ease-in">
|
||||
<div
|
||||
class="skeleton"
|
||||
role="progressbar"
|
||||
aria-label=${this.hass.localize(
|
||||
"ui.panel.config.logs.loading_log"
|
||||
)}
|
||||
>
|
||||
${SKELETON_ROW_WIDTHS.map(
|
||||
([primary, secondary]) => html`
|
||||
<div class="skeleton-row">
|
||||
<ha-skeleton-text
|
||||
class="primary"
|
||||
style=${styleMap({
|
||||
"--ha-skeleton-text-width": `${primary}%`,
|
||||
})}
|
||||
></ha-skeleton-text>
|
||||
<ha-skeleton-text
|
||||
class="secondary"
|
||||
style=${styleMap({
|
||||
"--ha-skeleton-text-width": `${secondary}%`,
|
||||
})}
|
||||
></ha-skeleton-text>
|
||||
</div>
|
||||
`
|
||||
)}
|
||||
</div>
|
||||
`
|
||||
: html`
|
||||
<div class="header">
|
||||
<h1 class="card-header">
|
||||
${
|
||||
this.header ||
|
||||
this.hass.localize("ui.panel.config.logs.caption")
|
||||
}
|
||||
</h1>
|
||||
<div class="header-buttons">
|
||||
${
|
||||
logFileDownloadUnavailableReason
|
||||
? nothing
|
||||
: html`<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>
|
||||
|
||||
${
|
||||
logFileDownloadUnavailableReason &&
|
||||
!this.integrations?.length
|
||||
? nothing
|
||||
: html`<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>`
|
||||
}
|
||||
</ha-fade-in>`
|
||||
: this._items.length === 0
|
||||
? html`
|
||||
<div class="card-content empty-content">
|
||||
${this.hass.localize("ui.panel.config.logs.no_issues")}
|
||||
</div>
|
||||
</div>
|
||||
${
|
||||
logFileDownloadUnavailableReason
|
||||
? html`<ha-alert alert-type="warning">
|
||||
${this.hass.localize(
|
||||
`ui.panel.config.logs.log_file_disabled.${logFileDownloadUnavailableReason}`
|
||||
)}
|
||||
</ha-alert>`
|
||||
: nothing
|
||||
}
|
||||
${
|
||||
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 || this.integrations?.length)
|
||||
? 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_integration",
|
||||
{
|
||||
integration: formatListWithAnds(
|
||||
this.hass.locale,
|
||||
this.integrations!.map((domain) =>
|
||||
domainToName(this.hass.localize, domain)
|
||||
)
|
||||
),
|
||||
}
|
||||
)
|
||||
}
|
||||
</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>
|
||||
`
|
||||
: filteredItems.length === 0 &&
|
||||
(this.filter || this.integrations?.length)
|
||||
? 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_integration",
|
||||
{
|
||||
integration: formatListWithAnds(
|
||||
this.hass.locale,
|
||||
this.integrations!.map((domain) =>
|
||||
domainToName(
|
||||
this.hass.localize,
|
||||
domain
|
||||
)
|
||||
)
|
||||
),
|
||||
}
|
||||
)
|
||||
}
|
||||
</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
|
||||
>`
|
||||
}
|
||||
|
||||
<div class="card-actions">
|
||||
<ha-call-service-button domain="system_log" service="clear"
|
||||
>${this.hass.localize(
|
||||
"ui.panel.config.logs.clear"
|
||||
)}</ha-call-service-button
|
||||
>
|
||||
</div>
|
||||
`
|
||||
)}</ha-list
|
||||
>`
|
||||
}
|
||||
${
|
||||
this._items === undefined
|
||||
? nothing
|
||||
: html`<div class="card-actions">
|
||||
<ha-call-service-button domain="system_log" service="clear"
|
||||
>${this.hass.localize(
|
||||
"ui.panel.config.logs.clear"
|
||||
)}</ha-call-service-button
|
||||
>
|
||||
</div>`
|
||||
}
|
||||
</ha-card>
|
||||
</div>
|
||||
@@ -368,11 +389,25 @@ export class SystemLogCard extends LitElement {
|
||||
margin: 16px;
|
||||
}
|
||||
|
||||
.loading-container {
|
||||
height: 100px;
|
||||
.skeleton {
|
||||
padding: var(--ha-space-2) 0;
|
||||
}
|
||||
|
||||
.skeleton-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
gap: var(--ha-space-2);
|
||||
height: 72px;
|
||||
padding: 0 var(--ha-space-5);
|
||||
}
|
||||
|
||||
.skeleton-row .primary {
|
||||
font-size: var(--ha-font-size-l);
|
||||
}
|
||||
|
||||
.skeleton-row .secondary {
|
||||
font-size: var(--ha-font-size-m);
|
||||
}
|
||||
|
||||
.secondary {
|
||||
|
||||
@@ -9,6 +9,7 @@ vi.mock("../../../../src/components/input/ha-input-search", () => ({}));
|
||||
vi.mock("../../../../src/components/ha-button", () => ({}));
|
||||
vi.mock("../../../../src/components/ha-dialog", () => ({}));
|
||||
vi.mock("../../../../src/components/ha-dialog-footer", () => ({}));
|
||||
vi.mock("../../../../src/components/skeleton/ha-skeleton-log", () => ({}));
|
||||
vi.mock("../../../../src/components/ha-log-viewer", () => {
|
||||
class HaLogViewerStub extends HTMLElement {
|
||||
public lines: string[] = [];
|
||||
|
||||
Reference in new issue
Block a user