mirror of
https://github.com/home-assistant/frontend.git
synced 2026-10-20 21:01:30 +00:00
Compare commits
8
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ba9bebb74f | ||
|
|
9b0fe2dc8e | ||
|
|
b1bec11874 | ||
|
|
168173a755 | ||
|
|
1097da5a9e | ||
|
|
6d89c35892 | ||
|
|
dd96234d57 | ||
|
|
4269bb953d |
No files matched your search
@@ -0,0 +1,51 @@
|
||||
import {
|
||||
decodeQueryParams,
|
||||
type QueryParamConfig,
|
||||
type QueryParamValues,
|
||||
type SearchParamsSource,
|
||||
} from "./query-params";
|
||||
|
||||
// `filter` is the name the search had before, still read until 2027.5. It is
|
||||
// in the configs so writing the search replaces it.
|
||||
export const logSearchQueryParamConfig = {
|
||||
string: ["search", "filter"],
|
||||
} as const satisfies QueryParamConfig;
|
||||
|
||||
export const logsQueryParamConfig = {
|
||||
string: ["provider", "search", "filter"],
|
||||
boolean: [{ key: "raw", trueValue: "1" }],
|
||||
} as const satisfies QueryParamConfig;
|
||||
|
||||
export type LogsQueryParams = QueryParamValues<typeof logsQueryParamConfig>;
|
||||
|
||||
export const DEFAULT_LOG_PROVIDER = "core";
|
||||
|
||||
export const logSearchFromUrl = (searchParams: SearchParamsSource): string => {
|
||||
const { search, filter } = decodeQueryParams(
|
||||
searchParams,
|
||||
logSearchQueryParamConfig
|
||||
);
|
||||
|
||||
return search ?? filter ?? "";
|
||||
};
|
||||
|
||||
export const logProviderFromUrl = (searchParams: SearchParamsSource): string =>
|
||||
decodeQueryParams(searchParams, logsQueryParamConfig).provider ??
|
||||
DEFAULT_LOG_PROVIDER;
|
||||
|
||||
export const logRawFromUrl = (searchParams: SearchParamsSource): boolean =>
|
||||
decodeQueryParams(searchParams, logsQueryParamConfig).raw ?? false;
|
||||
|
||||
/**
|
||||
* Params for the logs page. The default provider and no search are left out,
|
||||
* and raw only applies to Core, the other providers only have raw logs.
|
||||
*/
|
||||
export const logsQueryParamsFromState = (
|
||||
provider: string,
|
||||
search: string,
|
||||
raw = false
|
||||
): LogsQueryParams => ({
|
||||
provider: provider === DEFAULT_LOG_PROVIDER ? undefined : provider,
|
||||
search,
|
||||
raw: provider === DEFAULT_LOG_PROVIDER && raw,
|
||||
});
|
||||
@@ -3,12 +3,19 @@ import {
|
||||
type CSSResultGroup,
|
||||
html,
|
||||
LitElement,
|
||||
type PropertyValues,
|
||||
type TemplateResult,
|
||||
} from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { extractSearchParam } from "../../../../../common/url/search-params";
|
||||
import { QueryParamsController } from "../../../../../common/controllers/query-params-controller";
|
||||
import type { HASSDomTargetEvent } from "../../../../../common/dom/fire_event";
|
||||
import {
|
||||
logSearchFromUrl,
|
||||
logSearchQueryParamConfig,
|
||||
} from "../../../../../common/url/logs-query-params";
|
||||
import "../../../../../components/ha-spinner";
|
||||
import "../../../../../components/input/ha-input-search";
|
||||
import type { HaInputSearch } from "../../../../../components/input/ha-input-search";
|
||||
import type { HassioAddonDetails } from "../../../../../data/hassio/addon";
|
||||
import { haStyle } from "../../../../../resources/styles";
|
||||
import type { HomeAssistant } from "../../../../../types";
|
||||
@@ -21,7 +28,24 @@ class SupervisorAppLogDashboard extends LitElement {
|
||||
|
||||
@property({ attribute: false }) public addon?: HassioAddonDetails;
|
||||
|
||||
@state() private _filter = extractSearchParam("filter") || "";
|
||||
@state() private _filter = "";
|
||||
|
||||
private _queryParams = new QueryParamsController(
|
||||
this,
|
||||
logSearchQueryParamConfig,
|
||||
{
|
||||
enabled: () => true,
|
||||
navigated: () => this._applyUrl(),
|
||||
}
|
||||
);
|
||||
|
||||
protected willUpdate(changedProps: PropertyValues<this>): void {
|
||||
super.willUpdate(changedProps);
|
||||
|
||||
if (!this.hasUpdated) {
|
||||
this._applyUrl();
|
||||
}
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
if (!this.addon) {
|
||||
@@ -32,6 +56,7 @@ class SupervisorAppLogDashboard extends LitElement {
|
||||
<ha-input-search
|
||||
appearance="outlined"
|
||||
@input=${this._filterChanged}
|
||||
@blur=${this._flushUrl}
|
||||
.value=${this._filter}
|
||||
.placeholder=${this.hass.localize("ui.panel.config.logs.search")}
|
||||
></ha-input-search>
|
||||
@@ -47,8 +72,25 @@ class SupervisorAppLogDashboard extends LitElement {
|
||||
`;
|
||||
}
|
||||
|
||||
private async _filterChanged(ev: InputEvent) {
|
||||
this._filter = (ev.target as HTMLInputElement).value;
|
||||
private _filterChanged(ev: HASSDomTargetEvent<HaInputSearch>) {
|
||||
this._filter = ev.target.value ?? "";
|
||||
this._queryParams.write({ search: this._filter }, true);
|
||||
}
|
||||
|
||||
private _flushUrl() {
|
||||
this._queryParams.flush();
|
||||
}
|
||||
|
||||
// A link without a search clears it, like the logs page
|
||||
private _applyUrl() {
|
||||
const searchParams = this._queryParams.searchParams();
|
||||
|
||||
if (!searchParams) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._filter = logSearchFromUrl(searchParams);
|
||||
this._queryParams.write({ search: this._filter });
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
import type { CSSResultGroup, PropertyValues, TemplateResult } from "lit";
|
||||
import { css, html, LitElement } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { keyed } from "lit/directives/keyed";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { fireEvent } from "../../../common/dom/fire_event";
|
||||
import { goBack, navigate } from "../../../common/navigate";
|
||||
@@ -140,14 +141,17 @@ class HaConfigAppDashboard extends LitElement {
|
||||
back-path=${this._fromStore ? "/config/apps/available" : "/config/apps"}
|
||||
>
|
||||
<span slot="header">${this._addon.name}</span>
|
||||
<supervisor-app-router
|
||||
.route=${route}
|
||||
.narrow=${this.narrow}
|
||||
.hass=${this.hass}
|
||||
.addon=${this._addon}
|
||||
.controlEnabled=${this._controlEnabled}
|
||||
@system-managed-take-control=${this._enableControl}
|
||||
></supervisor-app-router>
|
||||
${keyed(
|
||||
this._addon.slug,
|
||||
html`<supervisor-app-router
|
||||
.route=${route}
|
||||
.narrow=${this.narrow}
|
||||
.hass=${this.hass}
|
||||
.addon=${this._addon}
|
||||
.controlEnabled=${this._controlEnabled}
|
||||
@system-managed-take-control=${this._enableControl}
|
||||
></supervisor-app-router>`
|
||||
)}
|
||||
</hass-tabs-subpage>
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -321,7 +321,7 @@ export class HaConfigEntryRow extends LitElement {
|
||||
ERROR_STATES.includes(item.state)
|
||||
? html`
|
||||
<a
|
||||
href=${`/config/logs?filter=${encodeURIComponent(item.domain)}`}
|
||||
href=${`/config/logs?search=${encodeURIComponent(item.domain)}`}
|
||||
>
|
||||
<ha-dropdown-item value="logs">
|
||||
<ha-svg-icon
|
||||
|
||||
@@ -128,7 +128,7 @@ export const renderConfigEntryError = (
|
||||
}
|
||||
return html`
|
||||
<br />
|
||||
<a href=${`/config/logs?filter=${encodeURIComponent(entry.domain)}`}>
|
||||
<a href=${`/config/logs?search=${encodeURIComponent(entry.domain)}`}>
|
||||
${hass.localize(
|
||||
"ui.panel.config.integrations.config_entry.check_the_logs"
|
||||
)}
|
||||
@@ -441,7 +441,7 @@ class HaConfigIntegrationPage extends SubscribeMixin(LitElement) {
|
||||
${
|
||||
this._logInfo
|
||||
? html`<a
|
||||
href=${`/config/logs?filter=${encodeURIComponent(this.domain)}`}
|
||||
href=${`/config/logs?search=${encodeURIComponent(this.domain)}`}
|
||||
>
|
||||
<ha-dropdown-item>
|
||||
<ha-svg-icon
|
||||
|
||||
+1
-1
@@ -628,7 +628,7 @@ class ZWaveJSConfigDashboard extends LitElement {
|
||||
} else {
|
||||
stateTextExtra = html`
|
||||
<br />
|
||||
<a href="/config/logs?filter=zwave_js"
|
||||
<a href="/config/logs?search=zwave_js"
|
||||
>${this.hass.localize(
|
||||
"ui.panel.config.integrations.config_entry.check_the_logs"
|
||||
)}</a
|
||||
|
||||
@@ -13,10 +13,20 @@ import { customElement, property, query, state } from "lit/decorators";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { isComponentLoaded } from "../../../common/config/is_component_loaded";
|
||||
import { atLeastVersion } from "../../../common/config/version";
|
||||
import { QueryParamsController } from "../../../common/controllers/query-params-controller";
|
||||
import type { HASSDomTargetEvent } from "../../../common/dom/fire_event";
|
||||
import { navigate } from "../../../common/navigate";
|
||||
import { stringCompare } from "../../../common/string/compare";
|
||||
import { extractSearchParam } from "../../../common/url/search-params";
|
||||
import { currentPath } from "../../../common/url/current-path";
|
||||
import {
|
||||
DEFAULT_LOG_PROVIDER,
|
||||
logProviderFromUrl,
|
||||
logRawFromUrl,
|
||||
logSearchFromUrl,
|
||||
logsQueryParamConfig,
|
||||
logsQueryParamsFromState,
|
||||
} from "../../../common/url/logs-query-params";
|
||||
import { createQueryString } from "../../../common/url/query-params";
|
||||
import "../../../components/ha-app-icon";
|
||||
import "../../../components/ha-button";
|
||||
import "../../../components/ha-generic-picker";
|
||||
@@ -80,7 +90,7 @@ export class HaConfigLogs extends LitElement {
|
||||
|
||||
@property({ attribute: false }) public route!: Route;
|
||||
|
||||
@state() private _filter = extractSearchParam("filter") || "";
|
||||
@state() private _filter = "";
|
||||
|
||||
@state() private _detail = false;
|
||||
|
||||
@@ -88,10 +98,18 @@ export class HaConfigLogs extends LitElement {
|
||||
|
||||
@query("ha-generic-picker") private providerPicker?: HaGenericPicker;
|
||||
|
||||
@state() private _selectedLogProvider = "core";
|
||||
@state() private _selectedLogProvider = DEFAULT_LOG_PROVIDER;
|
||||
|
||||
@state() private _logProviders = logProviders;
|
||||
|
||||
// The provider in the URL is only checked once the apps are loaded
|
||||
private _providersLoaded = false;
|
||||
|
||||
private _queryParams = new QueryParamsController(this, logsQueryParamConfig, {
|
||||
enabled: () => true,
|
||||
navigated: () => this._applyUrl(),
|
||||
});
|
||||
|
||||
public connectedCallback() {
|
||||
super.connectedCallback();
|
||||
const systemLog = this.systemLog;
|
||||
@@ -100,13 +118,26 @@ export class HaConfigLogs extends LitElement {
|
||||
}
|
||||
}
|
||||
|
||||
protected willUpdate(changedProps: PropertyValues<this>): void {
|
||||
super.willUpdate(changedProps);
|
||||
|
||||
if (!this.hasUpdated) {
|
||||
this._applyUrl();
|
||||
}
|
||||
}
|
||||
|
||||
protected firstUpdated(changedProps: PropertyValues<this>): void {
|
||||
super.firstUpdated(changedProps);
|
||||
this._init();
|
||||
}
|
||||
|
||||
private async _filterChanged(ev: HASSDomTargetEvent<HaInputSearch>) {
|
||||
private _filterChanged(ev: HASSDomTargetEvent<HaInputSearch>) {
|
||||
this._filter = ev.target.value ?? "";
|
||||
this._writeUrl(true);
|
||||
}
|
||||
|
||||
private _flushUrl() {
|
||||
this._queryParams.flush();
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
@@ -115,6 +146,7 @@ export class HaConfigLogs extends LitElement {
|
||||
<ha-input-search
|
||||
appearance="outlined"
|
||||
@input=${this._filterChanged}
|
||||
@blur=${this._flushUrl}
|
||||
.value=${this._filter}
|
||||
.placeholder=${this.hass.localize("ui.panel.config.logs.search")}
|
||||
></ha-input-search>
|
||||
@@ -195,6 +227,7 @@ export class HaConfigLogs extends LitElement {
|
||||
|
||||
private _showDetail() {
|
||||
this._detail = !this._detail;
|
||||
this._writeUrl();
|
||||
}
|
||||
|
||||
private _openPicker(ev: Event) {
|
||||
@@ -207,39 +240,87 @@ export class HaConfigLogs extends LitElement {
|
||||
if (!provider) {
|
||||
return;
|
||||
}
|
||||
this._selectedLogProvider = provider;
|
||||
this._filter = "";
|
||||
navigate(`/config/logs?provider=${this._selectedLogProvider}`);
|
||||
|
||||
// Keep the search typed so far on the entry Back returns to
|
||||
this._queryParams.flush();
|
||||
|
||||
// A new provider is a new history entry, without the search
|
||||
const queryString = createQueryString(
|
||||
logsQueryParamsFromState(provider, ""),
|
||||
logsQueryParamConfig
|
||||
);
|
||||
|
||||
// From the current path, so a trailing slash still matches the controller
|
||||
navigate(`${currentPath()}${queryString ? `?${queryString}` : ""}`);
|
||||
}
|
||||
|
||||
private async _init() {
|
||||
if (isComponentLoaded(this.hass.config, "hassio")) {
|
||||
await this._getInstalledAddons();
|
||||
}
|
||||
const providerKey = extractSearchParam("provider");
|
||||
if (providerKey) {
|
||||
if (
|
||||
isComponentLoaded(this.hass.config, "hassio") &&
|
||||
this._logProviders.find((p) => p.key === providerKey)
|
||||
) {
|
||||
this._selectedLogProvider = providerKey;
|
||||
} else {
|
||||
navigate("/config/logs", { replace: true });
|
||||
showAlertDialog(this, {
|
||||
title:
|
||||
this.hass.localize("ui.panel.config.logs.provider_not_found") ||
|
||||
"Log provider not found",
|
||||
text: this.hass.localize(
|
||||
"ui.panel.config.logs.provider_not_available",
|
||||
{
|
||||
provider:
|
||||
this._logProviders.find((p) => p.key === providerKey)?.name ||
|
||||
providerKey,
|
||||
}
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
// Keep a search typed while the apps were loading
|
||||
this._queryParams.flush();
|
||||
this._providersLoaded = true;
|
||||
this._applyUrl();
|
||||
}
|
||||
|
||||
// The URL is the state of the page: no provider is Core, no search is none
|
||||
// and no raw is Core's condensed view
|
||||
private _applyUrl() {
|
||||
const searchParams = this._queryParams.searchParams();
|
||||
|
||||
if (!searchParams) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._filter = logSearchFromUrl(searchParams);
|
||||
this._detail = logRawFromUrl(searchParams);
|
||||
|
||||
if (!this._providersLoaded) {
|
||||
return;
|
||||
}
|
||||
|
||||
const provider = logProviderFromUrl(searchParams);
|
||||
|
||||
const providerFound =
|
||||
provider === DEFAULT_LOG_PROVIDER ||
|
||||
(isComponentLoaded(this.hass.config, "hassio") &&
|
||||
this._logProviders.some((p) => p.key === provider));
|
||||
|
||||
this._selectedLogProvider = providerFound ? provider : DEFAULT_LOG_PROVIDER;
|
||||
|
||||
// Before the alert adds its history entry, so closing it doesn't go back
|
||||
// to the missing provider
|
||||
this._writeUrl();
|
||||
|
||||
if (!providerFound) {
|
||||
showAlertDialog(this, {
|
||||
title:
|
||||
this.hass.localize("ui.panel.config.logs.provider_not_found") ||
|
||||
"Log provider not found",
|
||||
text: this.hass.localize(
|
||||
"ui.panel.config.logs.provider_not_available",
|
||||
{
|
||||
provider:
|
||||
this._logProviders.find((p) => p.key === provider)?.name ||
|
||||
provider,
|
||||
}
|
||||
),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private _writeUrl(debounced = false) {
|
||||
// Until the apps are loaded, keep the provider the URL asks for
|
||||
const provider = this._providersLoaded
|
||||
? this._selectedLogProvider
|
||||
: logProviderFromUrl(this._queryParams.searchParams() ?? "");
|
||||
|
||||
this._queryParams.write(
|
||||
logsQueryParamsFromState(provider, this._filter, this._detail),
|
||||
debounced
|
||||
);
|
||||
}
|
||||
|
||||
private async _getInstalledAddons() {
|
||||
|
||||
@@ -309,6 +309,8 @@ export const getMyRedirects = (): Redirects => ({
|
||||
redirect: "/config/logs",
|
||||
params: {
|
||||
provider: "string?",
|
||||
search: "string?",
|
||||
raw: "string?",
|
||||
},
|
||||
},
|
||||
repairs: {
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import {
|
||||
logProviderFromUrl,
|
||||
logRawFromUrl,
|
||||
logSearchFromUrl,
|
||||
logsQueryParamConfig,
|
||||
logsQueryParamsFromState,
|
||||
} from "../../../src/common/url/logs-query-params";
|
||||
import { updateQueryString } from "../../../src/common/url/query-params";
|
||||
|
||||
describe("logSearchFromUrl", () => {
|
||||
it("reads search, then the old filter name", () => {
|
||||
expect(logSearchFromUrl("?search=zwave_js")).toBe("zwave_js");
|
||||
expect(logSearchFromUrl("?filter=zwave_js")).toBe("zwave_js");
|
||||
expect(logSearchFromUrl("?search=new&filter=old")).toBe("new");
|
||||
expect(logSearchFromUrl("")).toBe("");
|
||||
});
|
||||
});
|
||||
|
||||
describe("logProviderFromUrl", () => {
|
||||
it("defaults to Core", () => {
|
||||
expect(logProviderFromUrl("?provider=supervisor")).toBe("supervisor");
|
||||
expect(logProviderFromUrl("?search=x")).toBe("core");
|
||||
});
|
||||
});
|
||||
|
||||
describe("logsQueryParamsFromState", () => {
|
||||
it("leaves out Core and an empty search, and replaces filter", () => {
|
||||
expect(
|
||||
updateQueryString(
|
||||
"?filter=old&other=1",
|
||||
logsQueryParamsFromState("core", "old"),
|
||||
logsQueryParamConfig
|
||||
)
|
||||
).toBe("other=1&search=old");
|
||||
expect(
|
||||
updateQueryString(
|
||||
"?provider=dns&search=x",
|
||||
logsQueryParamsFromState("core", ""),
|
||||
logsQueryParamConfig
|
||||
)
|
||||
).toBe("");
|
||||
});
|
||||
|
||||
it("only writes raw for Core", () => {
|
||||
expect(
|
||||
updateQueryString(
|
||||
"",
|
||||
logsQueryParamsFromState("core", "", true),
|
||||
logsQueryParamConfig
|
||||
)
|
||||
).toBe("raw=1");
|
||||
expect(
|
||||
updateQueryString(
|
||||
"?raw=1",
|
||||
logsQueryParamsFromState("supervisor", "", true),
|
||||
logsQueryParamConfig
|
||||
)
|
||||
).toBe("provider=supervisor");
|
||||
expect(logRawFromUrl("?raw=1")).toBe(true);
|
||||
expect(logRawFromUrl("?raw=true")).toBe(false);
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user