Getting started on Configuration Changes (#12309)

Co-authored-by: Bram Kragten <mail@bramkragten.nl>
This commit is contained in:
Zack Barett 2022-04-20 16:57:51 -05:00 committed by GitHub
parent aa562c21a8
commit cabe10ffdb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
27 changed files with 742 additions and 387 deletions

View File

@ -12,7 +12,7 @@ export const isNavigationClick = (e: MouseEvent) => {
const anchor = e const anchor = e
.composedPath() .composedPath()
.filter((n) => (n as HTMLElement).tagName === "A")[0] as .find((n) => (n as HTMLElement).tagName === "A") as
| HTMLAnchorElement | HTMLAnchorElement
| undefined; | undefined;
if ( if (

View File

@ -2,12 +2,12 @@ import "@polymer/paper-tooltip/paper-tooltip";
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { customElement, property } from "lit/decorators"; import { customElement, property } from "lit/decorators";
import { fireEvent } from "../common/dom/fire_event"; import { fireEvent } from "../common/dom/fire_event";
import { Analytics, AnalyticsPreferences } from "../data/analytics"; import type { Analytics, AnalyticsPreferences } from "../data/analytics";
import { haStyle } from "../resources/styles"; import { haStyle } from "../resources/styles";
import { HomeAssistant } from "../types"; import type { HomeAssistant } from "../types";
import "./ha-checkbox";
import type { HaCheckbox } from "./ha-checkbox";
import "./ha-settings-row"; import "./ha-settings-row";
import "./ha-switch";
import type { HaSwitch } from "./ha-switch";
const ADDITIONAL_PREFERENCES = [ const ADDITIONAL_PREFERENCES = [
{ {
@ -40,62 +40,62 @@ export class HaAnalytics extends LitElement {
return html` return html`
<ha-settings-row> <ha-settings-row>
<span slot="prefix">
<ha-checkbox
@change=${this._handleRowCheckboxClick}
.checked=${baseEnabled}
.preference=${"base"}
.disabled=${loading}
name="base"
>
</ha-checkbox>
</span>
<span slot="heading" data-for="base"> Basic analytics </span> <span slot="heading" data-for="base"> Basic analytics </span>
<span slot="description" data-for="base"> <span slot="description" data-for="base">
This includes information about your system. This includes information about your system.
</span> </span>
<ha-switch
@change=${this._handleRowClick}
.checked=${baseEnabled}
.preference=${"base"}
.disabled=${loading}
name="base"
>
</ha-switch>
</ha-settings-row> </ha-settings-row>
${ADDITIONAL_PREFERENCES.map( ${ADDITIONAL_PREFERENCES.map(
(preference) => (preference) =>
html`<ha-settings-row> html`
<span slot="prefix"> <ha-settings-row>
<ha-checkbox <span slot="heading" data-for=${preference.key}>
@change=${this._handleRowCheckboxClick} ${preference.title}
.checked=${this.analytics?.preferences[preference.key]} </span>
.preference=${preference.key} <span slot="description" data-for=${preference.key}>
name=${preference.key} ${preference.description}
> </span>
</ha-checkbox> <span>
${!baseEnabled <ha-switch
? html`<paper-tooltip animation-delay="0" position="right"> @change=${this._handleRowClick}
You need to enable basic analytics for this option to be .checked=${this.analytics?.preferences[preference.key]}
available .preference=${preference.key}
</paper-tooltip>` name=${preference.key}
: ""} >
</span> </ha-switch>
<span slot="heading" data-for=${preference.key}> ${!baseEnabled
${preference.title} ? html`
</span> <paper-tooltip animation-delay="0" position="right">
<span slot="description" data-for=${preference.key}> You need to enable basic analytics for this option to be
${preference.description} available
</span> </paper-tooltip>
</ha-settings-row>` `
: ""}
</span>
</ha-settings-row>
`
)} )}
<ha-settings-row> <ha-settings-row>
<span slot="prefix">
<ha-checkbox
@change=${this._handleRowCheckboxClick}
.checked=${this.analytics?.preferences.diagnostics}
.preference=${"diagnostics"}
.disabled=${loading}
name="diagnostics"
>
</ha-checkbox>
</span>
<span slot="heading" data-for="diagnostics"> Diagnostics </span> <span slot="heading" data-for="diagnostics"> Diagnostics </span>
<span slot="description" data-for="diagnostics"> <span slot="description" data-for="diagnostics">
Share crash reports when unexpected errors occur. Share crash reports when unexpected errors occur.
</span> </span>
<ha-switch
@change=${this._handleRowClick}
.checked=${this.analytics?.preferences.diagnostics}
.preference=${"diagnostics"}
.disabled=${loading}
name="diagnostics"
>
</ha-switch>
</ha-settings-row> </ha-settings-row>
`; `;
} }
@ -120,23 +120,23 @@ export class HaAnalytics extends LitElement {
}); });
} }
private _handleRowCheckboxClick(ev: Event) { private _handleRowClick(ev: Event) {
const checkbox = ev.currentTarget as HaCheckbox; const target = ev.currentTarget as HaSwitch;
const preference = (checkbox as any).preference; const preference = (target as any).preference;
const preferences = this.analytics ? { ...this.analytics.preferences } : {}; const preferences = this.analytics ? { ...this.analytics.preferences } : {};
if (preferences[preference] === checkbox.checked) { if (preferences[preference] === target.checked) {
return; return;
} }
preferences[preference] = checkbox.checked; preferences[preference] = target.checked;
if ( if (
ADDITIONAL_PREFERENCES.some((entry) => entry.key === preference) && ADDITIONAL_PREFERENCES.some((entry) => entry.key === preference) &&
checkbox.checked target.checked
) { ) {
preferences.base = true; preferences.base = true;
} else if (preference === "base" && !checkbox.checked) { } else if (preference === "base" && !target.checked) {
preferences.usage = false; preferences.usage = false;
preferences.statistics = false; preferences.statistics = false;
} }

View File

@ -0,0 +1,69 @@
import { ListItemBase } from "@material/mwc-list/mwc-list-item-base";
import { styles } from "@material/mwc-list/mwc-list-item.css";
import { css, CSSResult, html } from "lit";
import { customElement, property, query } from "lit/decorators";
@customElement("ha-clickable-list-item")
export class HaClickableListItem extends ListItemBase {
@property() public href?: string;
@property({ type: Boolean }) public disableHref = false;
// property used only in css
@property({ type: Boolean, reflect: true }) public rtl = false;
@query("a") private _anchor!: HTMLAnchorElement;
public render() {
const r = super.render();
const href = this.href || "";
return html`${this.disableHref
? html`<a aria-role="option">${r}</a>`
: html`<a aria-role="option" href=${href}>${r}</a>`}`;
}
firstUpdated() {
super.firstUpdated();
this.addEventListener("keydown", (ev) => {
if (ev.key === "Enter" || ev.key === " ") {
this._anchor.click();
}
});
}
static get styles(): CSSResult[] {
return [
styles,
css`
:host {
padding-left: 0px;
padding-right: 0px;
}
:host([rtl]) span {
margin-left: var(--mdc-list-item-graphic-margin, 20px) !important;
margin-right: 0px !important;
}
:host([graphic="avatar"]:not([twoLine])),
:host([graphic="icon"]:not([twoLine])) {
height: 48px;
}
a {
width: 100%;
height: 100%;
display: flex;
align-items: center;
padding-left: var(--mdc-list-side-padding, 20px);
padding-right: var(--mdc-list-side-padding, 20px);
font-weight: 500;
}
`,
];
}
}
declare global {
interface HTMLElementTagNameMap {
"ha-clickable-list-item": HaClickableListItem;
}
}

View File

@ -0,0 +1,92 @@
import "@material/mwc-list/mwc-list";
import "@material/mwc-list/mwc-list-item";
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { customElement, property } from "lit/decorators";
import type { PageNavigation } from "../layouts/hass-tabs-subpage";
import type { HomeAssistant } from "../types";
import "./ha-icon-next";
import "./ha-svg-icon";
import "./ha-clickable-list-item";
@customElement("ha-navigation-list")
class HaNavigationList extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property({ type: Boolean }) public narrow!: boolean;
@property({ attribute: false }) public pages!: PageNavigation[];
@property({ type: Boolean }) public hasSecondary = false;
public render(): TemplateResult {
return html`
<mwc-list>
${this.pages.map(
(page) => html`
<ha-clickable-list-item
graphic="avatar"
.twoline=${this.hasSecondary}
.hasMeta=${!this.narrow}
@click=${this._entryClicked}
href=${page.path}
>
<div
slot="graphic"
class=${page.iconColor ? "icon-background" : ""}
.style="background-color: ${page.iconColor || "undefined"}"
>
<ha-svg-icon .path=${page.iconPath}></ha-svg-icon>
</div>
<span>${page.name}</span>
${this.hasSecondary
? html`<span slot="secondary">${page.description}</span>`
: ""}
${!this.narrow
? html`<ha-icon-next slot="meta"></ha-icon-next>`
: ""}
</ha-clickable-list-item>
`
)}
</mwc-list>
`;
}
private _entryClicked(ev) {
ev.currentTarget.blur();
}
static styles: CSSResultGroup = css`
a {
text-decoration: none;
color: var(--primary-text-color);
position: relative;
display: block;
outline: 0;
}
ha-svg-icon,
ha-icon-next {
color: var(--secondary-text-color);
height: 24px;
width: 24px;
}
ha-svg-icon {
padding: 8px;
}
.icon-background {
border-radius: 50%;
}
.icon-background ha-svg-icon {
color: #fff;
}
mwc-list-item {
cursor: pointer;
font-size: var(--navigation-list-item-title-font-size);
}
`;
}
declare global {
interface HTMLElementTagNameMap {
"ha-navigation-list": HaNavigationList;
}
}

View File

@ -19,9 +19,9 @@ import memoizeOne from "memoize-one";
import { fireEvent } from "../../common/dom/fire_event"; import { fireEvent } from "../../common/dom/fire_event";
import type { LeafletModuleType } from "../../common/dom/setup-leaflet-map"; import type { LeafletModuleType } from "../../common/dom/setup-leaflet-map";
import type { HomeAssistant } from "../../types"; import type { HomeAssistant } from "../../types";
import "../ha-input-helper-text";
import "./ha-map"; import "./ha-map";
import type { HaMap } from "./ha-map"; import type { HaMap } from "./ha-map";
import "../ha-input-helper-text";
declare global { declare global {
// for fire event // for fire event
@ -297,7 +297,7 @@ export class HaLocationsEditor extends LitElement {
return css` return css`
ha-map { ha-map {
display: block; display: block;
height: 300px; height: 100%;
} }
`; `;
} }

View File

@ -202,7 +202,7 @@ class HaConfigAreaPage extends LitElement {
<hass-tabs-subpage <hass-tabs-subpage
.hass=${this.hass} .hass=${this.hass}
.narrow=${this.narrow} .narrow=${this.narrow}
.tabs=${configSections.devices} .tabs=${configSections.areas}
.route=${this.route} .route=${this.route}
> >
${this.narrow ${this.narrow

View File

@ -82,7 +82,7 @@ export class HaConfigAreasDashboard extends LitElement {
.narrow=${this.narrow} .narrow=${this.narrow}
.isWide=${this.isWide} .isWide=${this.isWide}
back-path="/config" back-path="/config"
.tabs=${configSections.devices} .tabs=${configSections.areas}
.route=${this.route} .route=${this.route}
> >
<ha-icon-button <ha-icon-button

View File

@ -34,7 +34,6 @@ import "../../../layouts/hass-loading-screen";
import "../../../layouts/hass-tabs-subpage-data-table"; import "../../../layouts/hass-tabs-subpage-data-table";
import { HomeAssistant, Route } from "../../../types"; import { HomeAssistant, Route } from "../../../types";
import { fileDownload } from "../../../util/file_download"; import { fileDownload } from "../../../util/file_download";
import { configSections } from "../ha-panel-config";
@customElement("ha-config-backup") @customElement("ha-config-backup")
class HaConfigBackup extends LitElement { class HaConfigBackup extends LitElement {
@ -129,13 +128,15 @@ class HaConfigBackup extends LitElement {
<hass-tabs-subpage-data-table <hass-tabs-subpage-data-table
.hass=${this.hass} .hass=${this.hass}
.narrow=${this.narrow} .narrow=${this.narrow}
back-path="/config" back-path="/config/system"
.route=${this.route} .route=${this.route}
.tabs=${configSections.backup}
.columns=${this._columns(this.narrow, this.hass.language)} .columns=${this._columns(this.narrow, this.hass.language)}
.data=${this._getItems(this._backupData.backups)} .data=${this._getItems(this._backupData.backups)}
.noDataText=${this.hass.localize("ui.panel.config.backup.no_bakcups")} .noDataText=${this.hass.localize("ui.panel.config.backup.no_bakcups")}
> >
<span slot="header"
>${this.hass.localize("ui.panel.config.backup.caption")}</span
>
<ha-fab <ha-fab
slot="fab" slot="fab"
?disabled=${this._backupData.backing_up} ?disabled=${this._backupData.backing_up}

View File

@ -38,7 +38,7 @@ class ConfigAnalytics extends LitElement {
: undefined; : undefined;
return html` return html`
<ha-card header="Analytics"> <ha-card outlined>
<div class="card-content"> <div class="card-content">
${error ? html`<div class="error">${error}</div>` : ""} ${error ? html`<div class="error">${error}</div>` : ""}
<p> <p>

View File

@ -3,11 +3,11 @@ import "@polymer/app-layout/app-toolbar/app-toolbar";
import { html } from "@polymer/polymer/lib/utils/html-tag"; import { html } from "@polymer/polymer/lib/utils/html-tag";
/* eslint-plugin-disable lit */ /* eslint-plugin-disable lit */
import { PolymerElement } from "@polymer/polymer/polymer-element"; import { PolymerElement } from "@polymer/polymer/polymer-element";
import "../../../layouts/hass-tabs-subpage"; import "../../../layouts/hass-subpage";
import LocalizeMixin from "../../../mixins/localize-mixin"; import LocalizeMixin from "../../../mixins/localize-mixin";
import "../../../styles/polymer-ha-style"; import "../../../styles/polymer-ha-style";
import { configSections } from "../ha-panel-config"; import "./ha-config-core-form";
import "./ha-config-section-core"; import "./ha-config-name-form";
/* /*
* @appliesMixin LocalizeMixin * @appliesMixin LocalizeMixin
@ -17,36 +17,29 @@ class HaConfigCore extends LocalizeMixin(PolymerElement) {
return html` return html`
<style include="iron-flex ha-style"> <style include="iron-flex ha-style">
.content { .content {
padding-bottom: 32px; padding: 28px 20px 0;
}
.border {
margin: 32px auto 0;
border-bottom: 1px solid rgba(0, 0, 0, 0.12);
max-width: 1040px; max-width: 1040px;
margin: 0 auto;
} }
.narrow .border { ha-config-name-form,
max-width: 640px; ha-config-core-form {
display: block;
margin-top: 24px;
} }
</style> </style>
<hass-tabs-subpage <hass-subpage
hass="[[hass]]" hass="[[hass]]"
narrow="[[narrow]]" narrow="[[narrow]]"
route="[[route]]" header="[[localize('ui.panel.config.core.caption')]]"
back-path="/config" back-path="/config/system"
tabs="[[_computeTabs()]]"
show-advanced="[[showAdvanced]]"
> >
<div class$="[[computeClasses(isWide)]]"> <div class="content">
<ha-config-section-core <ha-config-name-form hass="[[hass]]"></ha-config-name-form>
is-wide="[[isWide]]" <ha-config-core-form hass="[[hass]]"></ha-config-core-form>
show-advanced="[[showAdvanced]]"
hass="[[hass]]"
></ha-config-section-core>
</div> </div>
</hass-tabs-subpage> </hass-subpage>
`; `;
} }
@ -59,14 +52,6 @@ class HaConfigCore extends LocalizeMixin(PolymerElement) {
route: Object, route: Object,
}; };
} }
_computeTabs() {
return configSections.general;
}
computeClasses(isWide) {
return isWide ? "content" : "content narrow";
}
} }
customElements.define("ha-config-core", HaConfigCore); customElements.define("ha-config-core", HaConfigCore);

View File

@ -9,6 +9,7 @@ import {
} from "lit"; } from "lit";
import { customElement, property, state } from "lit/decorators"; import { customElement, property, state } from "lit/decorators";
import { isComponentLoaded } from "../../../common/config/is_component_loaded"; import { isComponentLoaded } from "../../../common/config/is_component_loaded";
import "../../../components/ha-alert";
import "../../../components/ha-card"; import "../../../components/ha-card";
import "../../../components/ha-checkbox"; import "../../../components/ha-checkbox";
import "../../../components/ha-network"; import "../../../components/ha-network";
@ -28,7 +29,7 @@ class ConfigNetwork extends LitElement {
@state() private _networkConfig?: NetworkConfig; @state() private _networkConfig?: NetworkConfig;
@state() private _error?: string; @state() private _error?: { code: string; message: string };
protected render(): TemplateResult { protected render(): TemplateResult {
if ( if (
@ -39,9 +40,15 @@ class ConfigNetwork extends LitElement {
} }
return html` return html`
<ha-card header="Network"> <ha-card outlined header="Network">
<div class="card-content"> <div class="card-content">
${this._error ? html`<div class="error">${this._error}</div>` : ""} ${this._error
? html`
<ha-alert alert-type="error"
>${this._error.message || this._error.code}</ha-alert
>
`
: ""}
<p> <p>
Configure which network adapters integrations will use. Currently Configure which network adapters integrations will use. Currently
this setting only affects multicast traffic. A restart is required this setting only affects multicast traffic. A restart is required

View File

@ -0,0 +1,43 @@
import { css, html, LitElement, TemplateResult } from "lit";
import { customElement, property } from "lit/decorators";
import "../../../layouts/hass-subpage";
import type { HomeAssistant, Route } from "../../../types";
import "./ha-config-analytics";
@customElement("ha-config-section-analytics")
class HaConfigSectionAnalytics extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property({ attribute: false }) public route!: Route;
@property({ type: Boolean }) public narrow!: boolean;
protected render(): TemplateResult {
return html`
<hass-subpage
back-path="/config/system"
.hass=${this.hass}
.narrow=${this.narrow}
.header=${this.hass.localize("ui.panel.config.analytics.caption")}
>
<div class="content">
<ha-config-analytics .hass=${this.hass}></ha-config-analytics>
</div>
</hass-subpage>
`;
}
static styles = css`
.content {
padding: 28px 20px 0;
max-width: 1040px;
margin: 0 auto;
}
`;
}
declare global {
interface HTMLElementTagNameMap {
"ha-config-section-analytics": HaConfigSectionAnalytics;
}
}

View File

@ -1,70 +0,0 @@
import "@material/mwc-button";
import { html } from "@polymer/polymer/lib/utils/html-tag";
/* eslint-plugin-disable lit */
import { PolymerElement } from "@polymer/polymer/polymer-element";
import "../../../components/buttons/ha-call-service-button";
import "../../../components/ha-card";
import LocalizeMixin from "../../../mixins/localize-mixin";
import "../../../styles/polymer-ha-style";
import "../ha-config-section";
import "./ha-config-analytics";
import "./ha-config-core-form";
import "./ha-config-name-form";
import "./ha-config-network";
import "./ha-config-url-form";
/*
* @appliesMixin LocalizeMixin
*/
class HaConfigSectionCore extends LocalizeMixin(PolymerElement) {
static get template() {
return html`
<ha-config-section is-wide="[[isWide]]">
<span slot="header"
>[[localize('ui.panel.config.core.section.core.header')]]</span
>
<span slot="introduction"
>[[localize('ui.panel.config.core.section.core.introduction')]]</span
>
<ha-config-name-form hass="[[hass]]"></ha-config-name-form>
<ha-config-core-form hass="[[hass]]"></ha-config-core-form>
<ha-config-url-form hass="[[hass]]"></ha-config-url-form>
<ha-config-network hass="[[hass]]"></ha-config-network>
<ha-config-analytics hass="[[hass]]"></ha-config-analytics>
</ha-config-section>
`;
}
static get properties() {
return {
hass: {
type: Object,
},
isWide: {
type: Boolean,
value: false,
},
validating: {
type: Boolean,
value: false,
},
isValid: {
type: Boolean,
value: null,
},
validateLog: {
type: String,
value: "",
},
showAdvanced: Boolean,
};
}
}
customElements.define("ha-config-section-core", HaConfigSectionCore);

View File

@ -0,0 +1,49 @@
import { css, html, LitElement, TemplateResult } from "lit";
import { customElement, property } from "lit/decorators";
import "../../../layouts/hass-subpage";
import type { HomeAssistant, Route } from "../../../types";
import "./ha-config-network";
import "./ha-config-url-form";
@customElement("ha-config-section-network")
class HaConfigSectionNetwork extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property({ attribute: false }) public route!: Route;
@property({ type: Boolean }) public narrow!: boolean;
protected render(): TemplateResult {
return html`
<hass-subpage
back-path="/config/system"
.hass=${this.hass}
.narrow=${this.narrow}
.header=${this.hass.localize("ui.panel.config.network.caption")}
>
<div class="content">
<ha-config-url-form .hass=${this.hass}></ha-config-url-form>
<ha-config-network .hass=${this.hass}></ha-config-network>
</div>
</hass-subpage>
`;
}
static styles = css`
.content {
padding: 28px 20px 0;
max-width: 1040px;
margin: 0 auto;
}
ha-config-network {
display: block;
margin-top: 24px;
}
`;
}
declare global {
interface HTMLElementTagNameMap {
"ha-config-section-network": HaConfigSectionNetwork;
}
}

View File

@ -0,0 +1,40 @@
import { css, html, LitElement, TemplateResult } from "lit";
import { customElement, property } from "lit/decorators";
import "../../../layouts/hass-subpage";
import type { HomeAssistant, Route } from "../../../types";
import "./ha-config-analytics";
@customElement("ha-config-section-storage")
class HaConfigSectionStorage extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property({ attribute: false }) public route!: Route;
@property({ type: Boolean }) public narrow!: boolean;
protected render(): TemplateResult {
return html`
<hass-subpage
back-path="/config/system"
.hass=${this.hass}
.narrow=${this.narrow}
>
<div class="content"></div>
</hass-subpage>
`;
}
static styles = css`
.content {
padding: 28px 20px 0;
max-width: 1040px;
margin: 0 auto;
}
`;
}
declare global {
interface HTMLElementTagNameMap {
"ha-config-section-storage": HaConfigSectionStorage;
}
}

View File

@ -0,0 +1,115 @@
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { customElement, property } from "lit/decorators";
import "../../../components/ha-card";
import "../../../components/ha-navigation-list";
import { CloudStatus } from "../../../data/cloud";
import "../../../layouts/hass-subpage";
import { haStyle } from "../../../resources/styles";
import type { HomeAssistant } from "../../../types";
import "../ha-config-section";
import { configSections } from "../ha-panel-config";
@customElement("ha-config-system-navigation")
class HaConfigSystemNavigation extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property({ type: Boolean, reflect: true })
public narrow!: boolean;
@property({ type: Boolean }) public isWide!: boolean;
@property({ attribute: false }) public cloudStatus?: CloudStatus;
@property({ type: Boolean }) public showAdvanced!: boolean;
protected render(): TemplateResult {
const pages = configSections.general.map((page) => ({
...page,
name: page.translationKey
? this.hass.localize(page.translationKey)
: page.name,
}));
return html`
<hass-subpage
back-path="/config"
.header=${this.hass.localize("ui.panel.config.dashboard.system.title")}
>
<ha-config-section
.narrow=${this.narrow}
.isWide=${this.isWide}
full-width
>
<ha-card>
${this.narrow
? html`<div class="title">
${this.hass.localize(
"ui.panel.config.dashboard.system.title"
)}
</div>`
: ""}
<ha-navigation-list
.hass=${this.hass}
.narrow=${this.narrow}
.pages=${pages}
></ha-navigation-list>
</ha-card>
</ha-config-section>
</hass-subpage>
`;
}
static get styles(): CSSResultGroup {
return [
haStyle,
css`
ha-card {
margin-bottom: env(safe-area-inset-bottom);
}
:host(:not([narrow])) ha-card {
margin-bottom: max(24px, env(safe-area-inset-bottom));
}
ha-config-section {
margin: auto;
margin-top: -32px;
max-width: 600px;
}
ha-card {
overflow: hidden;
}
ha-card a {
text-decoration: none;
color: var(--primary-text-color);
}
.title {
font-size: 16px;
padding: 16px;
padding-bottom: 0;
}
:host([narrow]) ha-card {
border-radius: 0;
box-shadow: unset;
}
:host([narrow]) ha-config-section {
margin-top: -42px;
}
ha-navigation-list {
--navigation-list-item-title-font-size: 16px;
}
`,
];
}
}
declare global {
interface HTMLElementTagNameMap {
"ha-config-system-navigation": HaConfigSystemNavigation;
}
}

View File

@ -9,17 +9,17 @@ import {
} from "lit"; } from "lit";
import { customElement, property, state } from "lit/decorators"; import { customElement, property, state } from "lit/decorators";
import { isComponentLoaded } from "../../../common/config/is_component_loaded"; import { isComponentLoaded } from "../../../common/config/is_component_loaded";
import "../../../components/ha-card"; import { isIPAddress } from "../../../common/string/is_ip_address";
import "../../../components/ha-switch";
import "../../../components/ha-alert"; import "../../../components/ha-alert";
import "../../../components/ha-card";
import "../../../components/ha-formfield"; import "../../../components/ha-formfield";
import "../../../components/ha-switch";
import "../../../components/ha-textfield"; import "../../../components/ha-textfield";
import type { HaTextField } from "../../../components/ha-textfield"; import type { HaTextField } from "../../../components/ha-textfield";
import { CloudStatus, fetchCloudStatus } from "../../../data/cloud"; import { CloudStatus, fetchCloudStatus } from "../../../data/cloud";
import { saveCoreConfig } from "../../../data/core"; import { saveCoreConfig } from "../../../data/core";
import type { PolymerChangedEvent } from "../../../polymer-types"; import type { PolymerChangedEvent } from "../../../polymer-types";
import type { HomeAssistant } from "../../../types"; import type { HomeAssistant } from "../../../types";
import { isIPAddress } from "../../../common/string/is_ip_address";
@customElement("ha-config-url-form") @customElement("ha-config-url-form")
class ConfigUrlForm extends LitElement { class ConfigUrlForm extends LitElement {
@ -74,7 +74,10 @@ class ConfigUrlForm extends LitElement {
} }
return html` return html`
<ha-card .header=${this.hass.localize("ui.panel.config.url.caption")}> <ha-card
outlined
.header=${this.hass.localize("ui.panel.config.url.caption")}
>
<div class="card-content"> <div class="card-content">
${!canEdit ${!canEdit
? html` ? html`
@ -335,6 +338,7 @@ class ConfigUrlForm extends LitElement {
a { a {
color: var(--primary-color); color: var(--primary-color);
text-decoration: none;
} }
`; `;
} }

View File

@ -34,6 +34,7 @@ import { updateCanInstall, UpdateEntity } from "../../../data/update";
import { showAlertDialog } from "../../../dialogs/generic/show-dialog-box"; import { showAlertDialog } from "../../../dialogs/generic/show-dialog-box";
import { showQuickBar } from "../../../dialogs/quick-bar/show-dialog-quick-bar"; import { showQuickBar } from "../../../dialogs/quick-bar/show-dialog-quick-bar";
import "../../../layouts/ha-app-layout"; import "../../../layouts/ha-app-layout";
import { PageNavigation } from "../../../layouts/hass-tabs-subpage";
import { haStyle } from "../../../resources/styles"; import { haStyle } from "../../../resources/styles";
import { HomeAssistant } from "../../../types"; import { HomeAssistant } from "../../../types";
import { documentationUrl } from "../../../util/documentation-url"; import { documentationUrl } from "../../../util/documentation-url";
@ -119,10 +120,26 @@ class HaConfigDashboard extends LitElement {
private _notifyUpdates = false; private _notifyUpdates = false;
private _pages = memoizeOne((clouStatus, isLoaded) => {
const pages: PageNavigation[] = [];
if (clouStatus && isLoaded) {
pages.push({
component: "cloud",
path: "/config/cloud",
name: "Home Assistant Cloud",
info: this.cloudStatus,
iconPath: mdiCloudLock,
iconColor: "#3B808E",
});
}
return [...pages, ...configSections.dashboard];
});
protected render(): TemplateResult { protected render(): TemplateResult {
const canInstallUpdates = this._filterUpdateEntitiesWithInstall( const canInstallUpdates = this._filterUpdateEntitiesWithInstall(
this.hass.states this.hass.states
); );
return html` return html`
<ha-app-layout> <ha-app-layout>
<app-header fixed slot="header"> <app-header fixed slot="header">
@ -175,30 +192,14 @@ class HaConfigDashboard extends LitElement {
${this.hass.localize("panel.config")} ${this.hass.localize("panel.config")}
</div>` </div>`
: ""} : ""}
${this.cloudStatus && isComponentLoaded(this.hass, "cloud")
? html`
<ha-config-navigation
.hass=${this.hass}
.narrow=${this.narrow}
.showAdvanced=${this.showAdvanced}
.pages=${[
{
component: "cloud",
path: "/config/cloud",
name: "Home Assistant Cloud",
info: this.cloudStatus,
iconPath: mdiCloudLock,
iconColor: "#3B808E",
},
]}
></ha-config-navigation>
`
: ""}
<ha-config-navigation <ha-config-navigation
.hass=${this.hass} .hass=${this.hass}
.narrow=${this.narrow} .narrow=${this.narrow}
.showAdvanced=${this.showAdvanced} .showAdvanced=${this.showAdvanced}
.pages=${configSections.dashboard} .pages=${this._pages(
this.cloudStatus,
isComponentLoaded(this.hass, "cloud")
)}
></ha-config-navigation> ></ha-config-navigation>
</ha-card> </ha-card>
<div class="tips"> <div class="tips">

View File

@ -1,13 +1,14 @@
import "@polymer/paper-item/paper-icon-item"; import "@material/mwc-list/mwc-list";
import "@polymer/paper-item/paper-item-body"; import "@material/mwc-list/mwc-list-item";
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import { html, LitElement, TemplateResult } from "lit";
import { customElement, property } from "lit/decorators"; import { customElement, property } from "lit/decorators";
import { canShowPage } from "../../../common/config/can_show_page"; import { canShowPage } from "../../../common/config/can_show_page";
import "../../../components/ha-card"; import "../../../components/ha-card";
import "../../../components/ha-icon-next"; import "../../../components/ha-icon-next";
import { CloudStatus, CloudStatusLoggedIn } from "../../../data/cloud"; import "../../../components/ha-navigation-list";
import { PageNavigation } from "../../../layouts/hass-tabs-subpage"; import type { CloudStatus, CloudStatusLoggedIn } from "../../../data/cloud";
import { HomeAssistant } from "../../../types"; import type { PageNavigation } from "../../../layouts/hass-tabs-subpage";
import type { HomeAssistant } from "../../../types";
@customElement("ha-config-navigation") @customElement("ha-config-navigation")
class HaConfigNavigation extends LitElement { class HaConfigNavigation extends LitElement {
@ -15,129 +16,71 @@ class HaConfigNavigation extends LitElement {
@property({ type: Boolean }) public narrow!: boolean; @property({ type: Boolean }) public narrow!: boolean;
@property() public showAdvanced!: boolean; @property({ attribute: false }) public pages!: PageNavigation[];
@property() public pages!: PageNavigation[];
protected render(): TemplateResult { protected render(): TemplateResult {
const pages = this.pages
.filter((page) =>
page.path === "#external-app-configuration"
? this.hass.auth.external?.config.hasSettingsScreen
: canShowPage(this.hass, page)
)
.map((page) => ({
...page,
name:
page.name ||
this.hass.localize(
`ui.panel.config.dashboard.${page.translationKey}.title`
),
description:
page.component === "cloud" && (page.info as CloudStatus)
? page.info.logged_in
? `
${this.hass.localize(
"ui.panel.config.cloud.description_login",
"email",
(page.info as CloudStatusLoggedIn).email
)}
`
: `
${this.hass.localize(
"ui.panel.config.cloud.description_features"
)}
`
: `
${
page.description ||
this.hass.localize(
`ui.panel.config.dashboard.${page.translationKey}.description`
)
}
`,
}));
return html` return html`
${this.pages.map((page) => <ha-navigation-list
( hasSecondary
page.path === "#external-app-configuration" .hass=${this.hass}
? this.hass.auth.external?.config.hasSettingsScreen .narrow=${this.narrow}
: canShowPage(this.hass, page) .pages=${pages}
) @click=${this._entryClicked}
? html` ></ha-navigation-list>
<a href=${page.path} role="option" tabindex="-1">
<paper-icon-item @click=${this._entryClicked}>
<div
class=${page.iconColor ? "icon-background" : ""}
slot="item-icon"
.style="background-color: ${page.iconColor || "undefined"}"
>
<ha-svg-icon .path=${page.iconPath}></ha-svg-icon>
</div>
<paper-item-body two-line>
${page.name ||
this.hass.localize(
`ui.panel.config.dashboard.${page.translationKey}.title`
)}
${page.component === "cloud" && (page.info as CloudStatus)
? page.info.logged_in
? html`
<div secondary>
${this.hass.localize(
"ui.panel.config.cloud.description_login",
"email",
(page.info as CloudStatusLoggedIn).email
)}
</div>
`
: html`
<div secondary>
${this.hass.localize(
"ui.panel.config.cloud.description_features"
)}
</div>
`
: html`
<div secondary>
${page.description ||
this.hass.localize(
`ui.panel.config.dashboard.${page.translationKey}.description`
)}
</div>
`}
</paper-item-body>
${!this.narrow ? html`<ha-icon-next></ha-icon-next>` : ""}
</paper-icon-item>
</a>
`
: ""
)}
`; `;
} }
private _entryClicked(ev) { private _entryClicked(ev) {
ev.currentTarget.blur(); const anchor = ev
if ( .composedPath()
ev.currentTarget.parentElement.href.endsWith( .find((n) => (n as HTMLElement).tagName === "A") as
"#external-app-configuration" | HTMLAnchorElement
) | undefined;
) {
if (anchor?.href?.endsWith("#external-app-configuration")) {
ev.preventDefault(); ev.preventDefault();
this.hass.auth.external!.fireMessage({ this.hass.auth.external!.fireMessage({
type: "config_screen/show", type: "config_screen/show",
}); });
} }
} }
static get styles(): CSSResultGroup {
return css`
a {
text-decoration: none;
color: var(--primary-text-color);
position: relative;
display: block;
outline: 0;
}
ha-svg-icon,
ha-icon-next {
color: var(--secondary-text-color);
height: 24px;
width: 24px;
}
ha-svg-icon {
padding: 8px;
}
.iron-selected paper-item::before,
a:not(.iron-selected):focus::before {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
pointer-events: none;
content: "";
transition: opacity 15ms linear;
will-change: opacity;
}
a:not(.iron-selected):focus::before {
background-color: currentColor;
opacity: var(--dark-divider-opacity);
}
.iron-selected paper-item:focus::before,
.iron-selected:focus paper-item::before {
opacity: 0.2;
}
.icon-background {
border-radius: 50%;
}
.icon-background ha-svg-icon {
color: #fff;
}
`;
}
} }
declare global { declare global {

View File

@ -4,12 +4,15 @@ import {
mdiBadgeAccountHorizontal, mdiBadgeAccountHorizontal,
mdiCellphoneCog, mdiCellphoneCog,
mdiCog, mdiCog,
mdiCpu32Bit,
mdiDevices, mdiDevices,
mdiHomeAssistant, mdiHomeAssistant,
mdiInformation, mdiInformation,
mdiInformationOutline,
mdiLightningBolt, mdiLightningBolt,
mdiMapMarkerRadius, mdiMapMarkerRadius,
mdiMathLog, mdiMathLog,
mdiNetwork,
mdiNfcVariant, mdiNfcVariant,
mdiPalette, mdiPalette,
mdiPaletteSwatch, mdiPaletteSwatch,
@ -20,6 +23,7 @@ import {
mdiShape, mdiShape,
mdiSofa, mdiSofa,
mdiTools, mdiTools,
mdiUpdate,
mdiViewDashboard, mdiViewDashboard,
} from "@mdi/js"; } from "@mdi/js";
import { PolymerElement } from "@polymer/polymer"; import { PolymerElement } from "@polymer/polymer";
@ -58,11 +62,11 @@ export const configSections: { [name: string]: PageNavigation[] } = {
core: true, core: true,
}, },
{ {
path: "/config/blueprint", path: "/config/areas",
translationKey: "blueprints", translationKey: "areas",
iconPath: mdiPaletteSwatch, iconPath: mdiSofa,
iconColor: "#64B5F6", iconColor: "#E48629",
component: "blueprint", components: ["zone"],
}, },
{ {
path: "/config/backup", path: "/config/backup",
@ -74,8 +78,8 @@ export const configSections: { [name: string]: PageNavigation[] } = {
{ {
path: "/hassio", path: "/hassio",
translationKey: "supervisor", translationKey: "supervisor",
iconPath: mdiHomeAssistant, iconPath: mdiPuzzle,
iconColor: "#4084CD", iconColor: "#F1C447",
component: "hassio", component: "hassio",
}, },
{ {
@ -97,7 +101,7 @@ export const configSections: { [name: string]: PageNavigation[] } = {
translationKey: "people", translationKey: "people",
iconPath: mdiAccount, iconPath: mdiAccount,
iconColor: "#E48629", iconColor: "#E48629",
components: ["person", "zone", "users"], components: ["person", "users"],
}, },
{ {
path: "#external-app-configuration", path: "#external-app-configuration",
@ -106,9 +110,16 @@ export const configSections: { [name: string]: PageNavigation[] } = {
iconColor: "#8E24AA", iconColor: "#8E24AA",
}, },
{ {
path: "/config/server_control", path: "/config/system",
translationKey: "settings", translationKey: "system",
iconPath: mdiCog, iconPath: mdiCog,
iconColor: "#301ABE",
core: true,
},
{
path: "/config/info",
translationKey: "about",
iconPath: mdiInformationOutline,
iconColor: "#4A5963", iconColor: "#4A5963",
core: true, core: true,
}, },
@ -148,11 +159,11 @@ export const configSections: { [name: string]: PageNavigation[] } = {
core: true, core: true,
}, },
{ {
component: "areas", component: "helpers",
path: "/config/areas", path: "/config/helpers",
translationKey: "ui.panel.config.areas.caption", translationKey: "ui.panel.config.helpers.caption",
iconPath: mdiSofa, iconPath: mdiTools,
iconColor: "#2D338F", iconColor: "#4D2EA4",
core: true, core: true,
}, },
], ],
@ -178,16 +189,6 @@ export const configSections: { [name: string]: PageNavigation[] } = {
iconPath: mdiScriptText, iconPath: mdiScriptText,
iconColor: "#518C43", iconColor: "#518C43",
}, },
{
component: "helpers",
path: "/config/helpers",
translationKey: "ui.panel.config.helpers.caption",
iconPath: mdiTools,
iconColor: "#4D2EA4",
core: true,
},
],
blueprints: [
{ {
component: "blueprint", component: "blueprint",
path: "/config/blueprint", path: "/config/blueprint",
@ -232,13 +233,6 @@ export const configSections: { [name: string]: PageNavigation[] } = {
iconPath: mdiAccount, iconPath: mdiAccount,
iconColor: "#E48629", iconColor: "#E48629",
}, },
{
component: "zone",
path: "/config/zone",
translationKey: "ui.panel.config.zone.caption",
iconPath: mdiMapMarkerRadius,
iconColor: "#E48629",
},
{ {
component: "users", component: "users",
path: "/config/users", path: "/config/users",
@ -249,6 +243,23 @@ export const configSections: { [name: string]: PageNavigation[] } = {
advancedOnly: true, advancedOnly: true,
}, },
], ],
areas: [
{
component: "areas",
path: "/config/areas",
translationKey: "ui.panel.config.areas.caption",
iconPath: mdiSofa,
iconColor: "#2D338F",
core: true,
},
{
component: "zone",
path: "/config/location",
translationKey: "ui.panel.config.zone.caption",
iconPath: mdiMapMarkerRadius,
iconColor: "#E48629",
},
],
general: [ general: [
{ {
component: "core", component: "core",
@ -274,6 +285,45 @@ export const configSections: { [name: string]: PageNavigation[] } = {
iconColor: "#4A5963", iconColor: "#4A5963",
core: true, core: true,
}, },
{
path: "/config/backup",
translationKey: "ui.panel.config.backup.caption",
iconPath: mdiBackupRestore,
iconColor: "#4084CD",
component: "backup",
},
{
path: "/config/analytics",
translationKey: "ui.panel.config.analytics.caption",
iconPath: mdiShape,
iconColor: "#f1c447",
},
{
path: "/config/hardware",
translationKey: "ui.panel.config.hardware.caption",
iconPath: mdiCpu32Bit,
iconColor: "#4A5963",
},
{
path: "/config/network",
translationKey: "ui.panel.config.network.caption",
iconPath: mdiNetwork,
iconColor: "#B1345C",
},
{
path: "/config/storage",
translationKey: "ui.panel.config.storage.caption",
iconPath: mdiServer,
iconColor: "#518C43",
},
{
path: "/config/update",
translationKey: "ui.panel.config.updates.caption",
iconPath: mdiUpdate,
iconColor: "#4A5963",
},
],
about: [
{ {
component: "info", component: "info",
path: "/config/info", path: "/config/info",
@ -296,6 +346,10 @@ class HaPanelConfig extends HassRouterPage {
protected routerOptions: RouterOptions = { protected routerOptions: RouterOptions = {
defaultPage: "dashboard", defaultPage: "dashboard",
routes: { routes: {
analytics: {
tag: "ha-config-section-analytics",
load: () => import("./core/ha-config-section-analytics"),
},
areas: { areas: {
tag: "ha-config-areas", tag: "ha-config-areas",
load: () => import("./areas/ha-config-areas"), load: () => import("./areas/ha-config-areas"),
@ -328,9 +382,9 @@ class HaPanelConfig extends HassRouterPage {
tag: "ha-config-devices", tag: "ha-config-devices",
load: () => import("./devices/ha-config-devices"), load: () => import("./devices/ha-config-devices"),
}, },
server_control: { system: {
tag: "ha-config-server-control", tag: "ha-config-system-navigation",
load: () => import("./server_control/ha-config-server-control"), load: () => import("./core/ha-config-system-navigation"),
}, },
logs: { logs: {
tag: "ha-config-logs", tag: "ha-config-logs",
@ -362,6 +416,10 @@ class HaPanelConfig extends HassRouterPage {
tag: "ha-config-lovelace", tag: "ha-config-lovelace",
load: () => import("./lovelace/ha-config-lovelace"), load: () => import("./lovelace/ha-config-lovelace"),
}, },
network: {
tag: "ha-config-section-network",
load: () => import("./core/ha-config-section-network"),
},
person: { person: {
tag: "ha-config-person", tag: "ha-config-person",
load: () => import("./person/ha-config-person"), load: () => import("./person/ha-config-person"),
@ -378,11 +436,15 @@ class HaPanelConfig extends HassRouterPage {
tag: "ha-config-helpers", tag: "ha-config-helpers",
load: () => import("./helpers/ha-config-helpers"), load: () => import("./helpers/ha-config-helpers"),
}, },
storage: {
tag: "ha-config-section-storage",
load: () => import("./core/ha-config-section-storage"),
},
users: { users: {
tag: "ha-config-users", tag: "ha-config-users",
load: () => import("./users/ha-config-users"), load: () => import("./users/ha-config-users"),
}, },
zone: { location: {
tag: "ha-config-zone", tag: "ha-config-zone",
load: () => import("./zone/ha-config-zone"), load: () => import("./zone/ha-config-zone"),
}, },

View File

@ -6,21 +6,29 @@ import { customElement, property, state } from "lit/decorators";
import memoizeOne from "memoize-one"; import memoizeOne from "memoize-one";
import { computeStateDomain } from "../../../common/entity/compute_state_domain"; import { computeStateDomain } from "../../../common/entity/compute_state_domain";
import { domainIcon } from "../../../common/entity/domain_icon"; import { domainIcon } from "../../../common/entity/domain_icon";
import { navigate } from "../../../common/navigate";
import { LocalizeFunc } from "../../../common/translations/localize"; import { LocalizeFunc } from "../../../common/translations/localize";
import { extractSearchParam } from "../../../common/url/search-params";
import { import {
DataTableColumnContainer, DataTableColumnContainer,
RowClickedEvent, RowClickedEvent,
} from "../../../components/data-table/ha-data-table"; } from "../../../components/data-table/ha-data-table";
import "../../../components/ha-fab"; import "../../../components/ha-fab";
import "../../../components/ha-icon-overflow-menu";
import "../../../components/ha-icon"; import "../../../components/ha-icon";
import "../../../components/ha-icon-overflow-menu";
import "../../../components/ha-svg-icon"; import "../../../components/ha-svg-icon";
import { ConfigEntry, getConfigEntries } from "../../../data/config_entries"; import { ConfigEntry, getConfigEntries } from "../../../data/config_entries";
import { getConfigFlowHandlers } from "../../../data/config_flow";
import { import {
EntityRegistryEntry, EntityRegistryEntry,
subscribeEntityRegistry, subscribeEntityRegistry,
} from "../../../data/entity_registry"; } from "../../../data/entity_registry";
import { domainToName } from "../../../data/integration"; import { domainToName } from "../../../data/integration";
import { showConfigFlowDialog } from "../../../dialogs/config-flow/show-dialog-config-flow";
import {
showAlertDialog,
showConfirmationDialog,
} from "../../../dialogs/generic/show-dialog-box";
import "../../../layouts/hass-loading-screen"; import "../../../layouts/hass-loading-screen";
import "../../../layouts/hass-tabs-subpage-data-table"; import "../../../layouts/hass-tabs-subpage-data-table";
import { SubscribeMixin } from "../../../mixins/subscribe-mixin"; import { SubscribeMixin } from "../../../mixins/subscribe-mixin";
@ -29,14 +37,6 @@ import { showEntityEditorDialog } from "../entities/show-dialog-entity-editor";
import { configSections } from "../ha-panel-config"; import { configSections } from "../ha-panel-config";
import { HELPER_DOMAINS } from "./const"; import { HELPER_DOMAINS } from "./const";
import { showHelperDetailDialog } from "./show-dialog-helper-detail"; import { showHelperDetailDialog } from "./show-dialog-helper-detail";
import { navigate } from "../../../common/navigate";
import { extractSearchParam } from "../../../common/url/search-params";
import { getConfigFlowHandlers } from "../../../data/config_flow";
import { showConfigFlowDialog } from "../../../dialogs/config-flow/show-dialog-config-flow";
import {
showAlertDialog,
showConfirmationDialog,
} from "../../../dialogs/generic/show-dialog-box";
// This groups items by a key but only returns last entry per key. // This groups items by a key but only returns last entry per key.
const groupByOne = <T>( const groupByOne = <T>(
@ -196,7 +196,7 @@ export class HaConfigHelpers extends SubscribeMixin(LitElement) {
.narrow=${this.narrow} .narrow=${this.narrow}
back-path="/config" back-path="/config"
.route=${this.route} .route=${this.route}
.tabs=${configSections.automations} .tabs=${configSections.devices}
.columns=${this._columns(this.narrow, this.hass.localize)} .columns=${this._columns(this.narrow, this.hass.localize)}
.data=${this._getItems( .data=${this._getItems(
this._stateItems, this._stateItems,

View File

@ -1,7 +1,7 @@
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { property } from "lit/decorators"; import { property } from "lit/decorators";
import "../../../layouts/hass-tabs-subpage";
import "../../../components/ha-logo-svg"; import "../../../components/ha-logo-svg";
import "../../../layouts/hass-tabs-subpage";
import { haStyle } from "../../../resources/styles"; import { haStyle } from "../../../resources/styles";
import { HomeAssistant, Route } from "../../../types"; import { HomeAssistant, Route } from "../../../types";
import { documentationUrl } from "../../../util/documentation-url"; import { documentationUrl } from "../../../util/documentation-url";
@ -34,7 +34,7 @@ class HaConfigInfo extends LitElement {
.narrow=${this.narrow} .narrow=${this.narrow}
back-path="/config" back-path="/config"
.route=${this.route} .route=${this.route}
.tabs=${configSections.general} .tabs=${configSections.about}
> >
<div class="about"> <div class="about">
<a <a

View File

@ -1,11 +1,11 @@
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { customElement, property, query, state } from "lit/decorators"; import { customElement, property, query, state } from "lit/decorators";
import { extractSearchParam } from "../../../common/url/search-params";
import "../../../components/search-input";
import "../../../layouts/hass-subpage";
import "../../../layouts/hass-tabs-subpage"; import "../../../layouts/hass-tabs-subpage";
import { haStyle } from "../../../resources/styles"; import { haStyle } from "../../../resources/styles";
import { HomeAssistant, Route } from "../../../types"; import { HomeAssistant, Route } from "../../../types";
import { configSections } from "../ha-panel-config";
import "../../../components/search-input";
import { extractSearchParam } from "../../../common/url/search-params";
import "./error-log-card"; import "./error-log-card";
import "./system-log-card"; import "./system-log-card";
import type { SystemLogCard } from "./system-log-card"; import type { SystemLogCard } from "./system-log-card";
@ -62,12 +62,11 @@ export class HaConfigLogs extends LitElement {
`; `;
return html` return html`
<hass-tabs-subpage <hass-subpage
.hass=${this.hass} .hass=${this.hass}
.narrow=${this.narrow} .narrow=${this.narrow}
back-path="/config" .header=${this.hass.localize("ui.panel.config.logs.caption")}
.route=${this.route} back-path="/config/system"
.tabs=${configSections.general}
> >
${search} ${search}
<div class="content"> <div class="content">
@ -80,7 +79,7 @@ export class HaConfigLogs extends LitElement {
.filter=${this._filter} .filter=${this._filter}
></error-log-card> ></error-log-card>
</div> </div>
</hass-tabs-subpage> </hass-subpage>
`; `;
} }

View File

@ -8,11 +8,11 @@ import "../../../components/buttons/ha-call-service-button";
import "../../../components/ha-card"; import "../../../components/ha-card";
import { checkCoreConfig } from "../../../data/core"; import { checkCoreConfig } from "../../../data/core";
import { domainToName } from "../../../data/integration"; import { domainToName } from "../../../data/integration";
import "../../../layouts/hass-subpage";
import "../../../layouts/hass-tabs-subpage"; import "../../../layouts/hass-tabs-subpage";
import { haStyle } from "../../../resources/styles"; import { haStyle } from "../../../resources/styles";
import { HomeAssistant, Route } from "../../../types"; import { HomeAssistant, Route } from "../../../types";
import "../ha-config-section"; import "../ha-config-section";
import { configSections } from "../ha-panel-config";
@customElement("ha-config-server-control") @customElement("ha-config-server-control")
export class HaConfigServerControl extends LitElement { export class HaConfigServerControl extends LitElement {
@ -49,12 +49,10 @@ export class HaConfigServerControl extends LitElement {
protected render(): TemplateResult { protected render(): TemplateResult {
return html` return html`
<hass-tabs-subpage <hass-subpage
.hass=${this.hass} .hass=${this.hass}
.narrow=${this.narrow} .narrow=${this.narrow}
.route=${this.route} back-path="/config/system"
back-path="/config"
.tabs=${configSections.general}
.showAdvanced=${this.showAdvanced} .showAdvanced=${this.showAdvanced}
> >
<ha-config-section .isWide=${this.isWide}> <ha-config-section .isWide=${this.isWide}>
@ -203,7 +201,7 @@ export class HaConfigServerControl extends LitElement {
` `
: ""} : ""}
</ha-config-section> </ha-config-section>
</hass-tabs-subpage> </hass-subpage>
`; `;
} }

View File

@ -157,7 +157,7 @@ export class HaConfigUsers extends LitElement {
.narrow=${this.narrow} .narrow=${this.narrow}
.route=${this.route} .route=${this.route}
backPath="/config" backPath="/config"
.tabs=${configSections.persons} .tabs=${configSections.areas}
.columns=${this._columns(this.narrow, this.hass.localize)} .columns=${this._columns(this.narrow, this.hass.localize)}
.data=${this._users} .data=${this._users}
@row-click=${this._editUser} @row-click=${this._editUser}

View File

@ -228,7 +228,7 @@ export class HaConfigZone extends SubscribeMixin(LitElement) {
.narrow=${this.narrow} .narrow=${this.narrow}
.route=${this.route} .route=${this.route}
back-path="/config" back-path="/config"
.tabs=${configSections.persons} .tabs=${configSections.areas}
> >
${this.narrow ${this.narrow
? html` ? html`

View File

@ -1067,23 +1067,19 @@
"dashboard": { "dashboard": {
"devices": { "devices": {
"title": "Devices & Services", "title": "Devices & Services",
"description": "Integrations, devices, entities and areas" "description": "Integrations, devices, entities and helpers"
}, },
"automations": { "automations": {
"title": "Automations & Scenes", "title": "Automations & Scenes",
"description": "Manage automations, scenes, scripts and helpers" "description": "Manage automations, scenes, scripts and blueprints"
}, },
"backup": { "backup": {
"title": "Backup", "title": "Backup",
"description": "Generate backups of your Home Assistant configuration" "description": "Generate backups of your Home Assistant configuration"
}, },
"blueprints": {
"title": "Blueprints",
"description": "Pre-made automations and scripts by the community"
},
"supervisor": { "supervisor": {
"title": "Add-ons, Backups & Supervisor", "title": "Add-ons",
"description": "Create backups, check logs or reboot your system" "description": "Extend the function around Home Assistant"
}, },
"dashboards": { "dashboards": {
"title": "Dashboards", "title": "Dashboards",
@ -1098,16 +1094,24 @@
"description": "Trigger automations when an NFC tag, QR code, etc. is scanned" "description": "Trigger automations when an NFC tag, QR code, etc. is scanned"
}, },
"people": { "people": {
"title": "People & Zones", "title": "People",
"description": "Manage the people and zones that Home Assistant tracks" "description": "Manage the people that Home Assistant tracks"
},
"areas": {
"title": "Areas & Zones",
"description": "Manage areas & zones that Home Assistant tracks"
}, },
"companion": { "companion": {
"title": "Companion App", "title": "Companion App",
"description": "Location and notifications" "description": "Location and notifications"
}, },
"settings": { "system": {
"title": "Settings", "title": "System",
"description": "Basic settings, server controls, logs and info" "description": "Create backups, check logs or reboot your system"
},
"about": {
"title": "About",
"description": "Version, system health and links to documentation"
} }
}, },
"common": { "common": {
@ -1117,6 +1121,7 @@
"learn_more": "Learn more" "learn_more": "Learn more"
}, },
"updates": { "updates": {
"caption": "Updates",
"no_update_entities": { "no_update_entities": {
"title": "Unable to check for updates", "title": "Unable to check for updates",
"description": "You do not have any integrations that provide updates." "description": "You do not have any integrations that provide updates."
@ -1168,7 +1173,7 @@
} }
}, },
"backup": { "backup": {
"caption": "[%key:ui::panel::config::dashboard::backup::title%]", "caption": "Backups",
"create_backup": "[%key:supervisor::backup::create_backup%]", "create_backup": "[%key:supervisor::backup::create_backup%]",
"creating_backup": "Backup is currently being created", "creating_backup": "Backup is currently being created",
"download_backup": "[%key:supervisor::backup::download_backup%]", "download_backup": "[%key:supervisor::backup::download_backup%]",
@ -1458,6 +1463,9 @@
"internal_url_https_error_title": "Invalid local network URL", "internal_url_https_error_title": "Invalid local network URL",
"internal_url_https_error_description": "You have configured an HTTPS certificate in Home Assistant. This means that your internal URL needs to be set to a domain covered by the certficate." "internal_url_https_error_description": "You have configured an HTTPS certificate in Home Assistant. This means that your internal URL needs to be set to a domain covered by the certficate."
}, },
"hardware": {
"caption": "Hardware"
},
"info": { "info": {
"caption": "Info", "caption": "Info",
"copy_menu": "Copy menu", "copy_menu": "Copy menu",
@ -1764,7 +1772,7 @@
"geo_location": { "geo_location": {
"label": "Geolocation", "label": "Geolocation",
"source": "Source", "source": "Source",
"zone": "Zone", "zone": "Location",
"event": "Event", "event": "Event",
"enter": "Enter", "enter": "Enter",
"leave": "Leave" "leave": "Leave"
@ -3087,6 +3095,15 @@
"tips": { "tips": {
"tip": "Tip!", "tip": "Tip!",
"join": "Join the community on our {forums}, {twitter}, {discord}, {blog} or {newsletter}" "join": "Join the community on our {forums}, {twitter}, {discord}, {blog} or {newsletter}"
},
"analytics": {
"caption": "Analytics"
},
"network": {
"caption": "Network"
},
"storage": {
"caption": "Storage"
} }
}, },
"lovelace": { "lovelace": {
@ -3680,7 +3697,7 @@
} }
}, },
"map": { "map": {
"edit_zones": "Edit Zones" "edit_zones": "Edit zones"
}, },
"profile": { "profile": {
"current_user": "You are currently logged in as {fullName}.", "current_user": "You are currently logged in as {fullName}.",