mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-12 11:56:34 +00:00
Refactor dev-info page (#2624)
* Refactor dev-info page * What * Fix custom UI check * Apply suggestions from code review Co-Authored-By: balloob <paulus@home-assistant.io> * Apply suggestions from code review Co-Authored-By: balloob <paulus@home-assistant.io> * Address comments * TSC * Apply suggestions from code review Co-Authored-By: balloob <paulus@home-assistant.io> * TSC
This commit is contained in:
parent
73b500db64
commit
c964ea30e0
4
src/data/error_log.ts
Normal file
4
src/data/error_log.ts
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
import { HomeAssistant } from "../types";
|
||||||
|
|
||||||
|
export const fetchErrorLog = (hass: HomeAssistant) =>
|
||||||
|
hass.callApi<string>("GET", "error_log");
|
13
src/data/system_log.ts
Normal file
13
src/data/system_log.ts
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import { HomeAssistant } from "../types";
|
||||||
|
|
||||||
|
export interface LoggedError {
|
||||||
|
message: string;
|
||||||
|
level: string;
|
||||||
|
source: string;
|
||||||
|
// unix timestamp in seconds
|
||||||
|
timestamp: number;
|
||||||
|
exception: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const fetchSystemLog = (hass: HomeAssistant) =>
|
||||||
|
hass.callApi<LoggedError[]>("GET", "error/all");
|
77
src/panels/dev-info/dialog-system-log-detail.ts
Normal file
77
src/panels/dev-info/dialog-system-log-detail.ts
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
import {
|
||||||
|
LitElement,
|
||||||
|
html,
|
||||||
|
css,
|
||||||
|
PropertyDeclarations,
|
||||||
|
CSSResult,
|
||||||
|
TemplateResult,
|
||||||
|
} from "lit-element";
|
||||||
|
import "@polymer/paper-dialog/paper-dialog";
|
||||||
|
import "@polymer/paper-dialog-scrollable/paper-dialog-scrollable";
|
||||||
|
|
||||||
|
import { SystemLogDetailDialogParams } from "./show-dialog-system-log-detail";
|
||||||
|
import { PolymerChangedEvent } from "../../polymer-types";
|
||||||
|
import { haStyleDialog } from "../../resources/ha-style";
|
||||||
|
|
||||||
|
class DialogSystemLogDetail extends LitElement {
|
||||||
|
private _params?: SystemLogDetailDialogParams;
|
||||||
|
|
||||||
|
static get properties(): PropertyDeclarations {
|
||||||
|
return {
|
||||||
|
_params: {},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public async showDialog(params: SystemLogDetailDialogParams): Promise<void> {
|
||||||
|
this._params = params;
|
||||||
|
await this.updateComplete;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected render(): TemplateResult | void {
|
||||||
|
if (!this._params) {
|
||||||
|
return html``;
|
||||||
|
}
|
||||||
|
const item = this._params.item;
|
||||||
|
|
||||||
|
return html`
|
||||||
|
<paper-dialog
|
||||||
|
with-backdrop
|
||||||
|
opened
|
||||||
|
@opened-changed="${this._openedChanged}"
|
||||||
|
>
|
||||||
|
<h2>Log Details (${item.level})</h2>
|
||||||
|
<paper-dialog-scrollable>
|
||||||
|
<p>${new Date(item.timestamp * 1000)}</p>
|
||||||
|
${item.message
|
||||||
|
? html`
|
||||||
|
<pre>${item.message}</pre>
|
||||||
|
`
|
||||||
|
: html``}
|
||||||
|
${item.exception
|
||||||
|
? html`
|
||||||
|
<pre>${item.exception}</pre>
|
||||||
|
`
|
||||||
|
: html``}
|
||||||
|
</paper-dialog-scrollable>
|
||||||
|
</paper-dialog>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _openedChanged(ev: PolymerChangedEvent<boolean>): void {
|
||||||
|
if (!(ev.detail as any).value) {
|
||||||
|
this._params = undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static get styles(): CSSResult[] {
|
||||||
|
return [haStyleDialog, css``];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"dialog-system-log-detail": DialogSystemLogDetail;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
customElements.define("dialog-system-log-detail", DialogSystemLogDetail);
|
73
src/panels/dev-info/error-log-card.ts
Normal file
73
src/panels/dev-info/error-log-card.ts
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
import {
|
||||||
|
LitElement,
|
||||||
|
html,
|
||||||
|
CSSResult,
|
||||||
|
css,
|
||||||
|
PropertyDeclarations,
|
||||||
|
TemplateResult,
|
||||||
|
} from "lit-element";
|
||||||
|
import "@polymer/paper-icon-button/paper-icon-button";
|
||||||
|
import "@polymer/paper-button/paper-button";
|
||||||
|
|
||||||
|
import { HomeAssistant } from "../../types";
|
||||||
|
import { fetchErrorLog } from "../../data/error_log";
|
||||||
|
|
||||||
|
class ErrorLogCard extends LitElement {
|
||||||
|
public hass?: HomeAssistant;
|
||||||
|
private _errorLog?: string;
|
||||||
|
|
||||||
|
static get properties(): PropertyDeclarations {
|
||||||
|
return {
|
||||||
|
hass: {},
|
||||||
|
_errorLog: {},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
protected render(): TemplateResult | void {
|
||||||
|
return html`
|
||||||
|
<p class="error-log-intro">
|
||||||
|
${this._errorLog
|
||||||
|
? html`
|
||||||
|
<paper-icon-button
|
||||||
|
icon="hass:refresh"
|
||||||
|
@click=${this._refreshErrorLog}
|
||||||
|
></paper-icon-button>
|
||||||
|
`
|
||||||
|
: html`
|
||||||
|
<paper-button raised @click=${this._refreshErrorLog}>
|
||||||
|
Load Full Home Assistant Log
|
||||||
|
</paper-button>
|
||||||
|
`}
|
||||||
|
</p>
|
||||||
|
<div class="error-log">${this._errorLog}</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
static get styles(): CSSResult {
|
||||||
|
return css`
|
||||||
|
.error-log-intro {
|
||||||
|
text-align: center;
|
||||||
|
margin: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
paper-icon-button {
|
||||||
|
float: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-log {
|
||||||
|
@apply --paper-font-code)
|
||||||
|
clear: both;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
margin: 16px;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async _refreshErrorLog(): Promise<void> {
|
||||||
|
this._errorLog = "Loading error log…";
|
||||||
|
const log = await fetchErrorLog(this.hass!);
|
||||||
|
this._errorLog = log || "No errors have been reported.";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
customElements.define("error-log-card", ErrorLogCard);
|
@ -1,59 +0,0 @@
|
|||||||
import "@polymer/paper-dialog-scrollable/paper-dialog-scrollable";
|
|
||||||
import "@polymer/paper-dialog/paper-dialog";
|
|
||||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
|
||||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
|
||||||
|
|
||||||
import "../../resources/ha-style";
|
|
||||||
|
|
||||||
import EventsMixin from "../../mixins/events-mixin";
|
|
||||||
|
|
||||||
/*
|
|
||||||
* @appliesMixin EventsMixin
|
|
||||||
*/
|
|
||||||
class HaLoadedComponents extends EventsMixin(PolymerElement) {
|
|
||||||
static get template() {
|
|
||||||
return html`
|
|
||||||
<style include="ha-style-dialog">
|
|
||||||
paper-dialog {
|
|
||||||
max-width: 500px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
<paper-dialog id="dialog" with-backdrop="" opened="{{_opened}}">
|
|
||||||
<h2>Loaded Components</h2>
|
|
||||||
<paper-dialog-scrollable id="scrollable">
|
|
||||||
<p>The following components are currently loaded:</p>
|
|
||||||
<ul>
|
|
||||||
<template is="dom-repeat" items="[[_components]]">
|
|
||||||
<li>[[item]]</li>
|
|
||||||
</template>
|
|
||||||
</ul>
|
|
||||||
</paper-dialog-scrollable>
|
|
||||||
</paper-dialog>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
||||||
static get properties() {
|
|
||||||
return {
|
|
||||||
_hass: Object,
|
|
||||||
_components: Array,
|
|
||||||
|
|
||||||
_opened: {
|
|
||||||
type: Boolean,
|
|
||||||
value: false,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
ready() {
|
|
||||||
super.ready();
|
|
||||||
}
|
|
||||||
|
|
||||||
showDialog({ hass }) {
|
|
||||||
this.hass = hass;
|
|
||||||
this._opened = true;
|
|
||||||
this._components = this.hass.config.components.sort();
|
|
||||||
setTimeout(() => this.$.dialog.center(), 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
customElements.define("ha-loaded-components", HaLoadedComponents);
|
|
@ -1,416 +0,0 @@
|
|||||||
import "@polymer/app-layout/app-header-layout/app-header-layout";
|
|
||||||
import "@polymer/app-layout/app-header/app-header";
|
|
||||||
import "@polymer/app-layout/app-toolbar/app-toolbar";
|
|
||||||
import "@polymer/paper-card/paper-card";
|
|
||||||
import "@polymer/paper-dialog-scrollable/paper-dialog-scrollable";
|
|
||||||
import "@polymer/paper-dialog/paper-dialog";
|
|
||||||
import "@polymer/paper-icon-button/paper-icon-button";
|
|
||||||
import "@polymer/paper-item/paper-item-body";
|
|
||||||
import "@polymer/paper-item/paper-item";
|
|
||||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
|
||||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
|
||||||
|
|
||||||
import "../../components/buttons/ha-call-service-button";
|
|
||||||
import "../../components/ha-menu-button";
|
|
||||||
import "../../resources/ha-style";
|
|
||||||
|
|
||||||
import formatDateTime from "../../common/datetime/format_date_time";
|
|
||||||
import formatTime from "../../common/datetime/format_time";
|
|
||||||
|
|
||||||
import EventsMixin from "../../mixins/events-mixin";
|
|
||||||
import LocalizeMixin from "../../mixins/localize-mixin";
|
|
||||||
|
|
||||||
const OPT_IN_PANEL = "states";
|
|
||||||
let registeredDialog = false;
|
|
||||||
|
|
||||||
class HaPanelDevInfo extends EventsMixin(LocalizeMixin(PolymerElement)) {
|
|
||||||
static get template() {
|
|
||||||
return html`
|
|
||||||
<style include="iron-positioning ha-style">
|
|
||||||
:host {
|
|
||||||
-ms-user-select: initial;
|
|
||||||
-webkit-user-select: initial;
|
|
||||||
-moz-user-select: initial;
|
|
||||||
}
|
|
||||||
|
|
||||||
.content {
|
|
||||||
padding: 16px 0px 16px 0;
|
|
||||||
direction: ltr;
|
|
||||||
}
|
|
||||||
|
|
||||||
.about {
|
|
||||||
text-align: center;
|
|
||||||
line-height: 2em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.version {
|
|
||||||
@apply --paper-font-headline;
|
|
||||||
}
|
|
||||||
|
|
||||||
.develop {
|
|
||||||
@apply --paper-font-subhead;
|
|
||||||
}
|
|
||||||
|
|
||||||
.about a {
|
|
||||||
color: var(--dark-primary-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
.error-log-intro {
|
|
||||||
text-align: center;
|
|
||||||
margin: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
paper-icon-button {
|
|
||||||
float: right;
|
|
||||||
}
|
|
||||||
|
|
||||||
.error-log {
|
|
||||||
@apply --paper-font-code)
|
|
||||||
clear: both;
|
|
||||||
white-space: pre-wrap;
|
|
||||||
margin: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.system-log-intro {
|
|
||||||
margin: 16px;
|
|
||||||
border-top: 1px solid var(--light-primary-color);
|
|
||||||
padding-top: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
paper-card {
|
|
||||||
display: block;
|
|
||||||
padding-top: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
paper-item {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.header {
|
|
||||||
@apply --paper-font-title;
|
|
||||||
}
|
|
||||||
|
|
||||||
paper-dialog {
|
|
||||||
border-radius: 2px;
|
|
||||||
direction: ltr;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media all and (max-width: 450px), all and (max-height: 500px) {
|
|
||||||
paper-dialog {
|
|
||||||
margin: 0;
|
|
||||||
width: 100%;
|
|
||||||
max-height: calc(100% - 64px);
|
|
||||||
|
|
||||||
position: fixed !important;
|
|
||||||
bottom: 0px;
|
|
||||||
left: 0px;
|
|
||||||
right: 0px;
|
|
||||||
overflow: scroll;
|
|
||||||
border-bottom-left-radius: 0px;
|
|
||||||
border-bottom-right-radius: 0px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.loading-container {
|
|
||||||
@apply --layout-vertical;
|
|
||||||
@apply --layout-center-center;
|
|
||||||
height: 100px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<app-header-layout has-scrolling-region>
|
|
||||||
<app-header slot="header" fixed>
|
|
||||||
<app-toolbar>
|
|
||||||
<ha-menu-button narrow='[[narrow]]' show-menu='[[showMenu]]'></ha-menu-button>
|
|
||||||
<div main-title>About</div>
|
|
||||||
</app-toolbar>
|
|
||||||
</app-header>
|
|
||||||
|
|
||||||
<div class='content'>
|
|
||||||
<div class='about'>
|
|
||||||
<p class='version'>
|
|
||||||
<a href='https://www.home-assistant.io'><img src="/static/icons/favicon-192x192.png" height="192" /></a><br />
|
|
||||||
Home Assistant<br />
|
|
||||||
[[hass.config.version]]
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
Path to configuration.yaml: [[hass.config.config_dir]]
|
|
||||||
<br><a href="#" on-click="_showComponents">[[loadedComponents.length]] Loaded Components</a>
|
|
||||||
</p>
|
|
||||||
<p class='develop'>
|
|
||||||
<a href='https://www.home-assistant.io/developers/credits/' target='_blank'>
|
|
||||||
Developed by a bunch of awesome people.
|
|
||||||
</a>
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
Published under the Apache 2.0 license<br />
|
|
||||||
Source:
|
|
||||||
<a href='https://github.com/home-assistant/home-assistant' target='_blank'>server</a> —
|
|
||||||
<a href='https://github.com/home-assistant/home-assistant-polymer' target='_blank'>frontend-ui</a>
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
Built using
|
|
||||||
<a href='https://www.python.org'>Python 3</a>,
|
|
||||||
<a href='https://www.polymer-project.org' target='_blank'>Polymer</a>,
|
|
||||||
Icons by <a href='https://www.google.com/design/icons/' target='_blank'>Google</a> and <a href='https://MaterialDesignIcons.com' target='_blank'>MaterialDesignIcons.com</a>.
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
Frontend JavaScript version: [[jsVersion]]
|
|
||||||
<template is='dom-if' if='[[customUiList.length]]'>
|
|
||||||
<div>
|
|
||||||
Custom UIs:
|
|
||||||
<template is='dom-repeat' items='[[customUiList]]'>
|
|
||||||
<div>
|
|
||||||
<a href='[[item.url]]' target='_blank'>[[item.name]]</a>: [[item.version]]
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
<a href="[[_nonDefaultLink()]]">[[_nonDefaultLinkText()]]</a>
|
|
||||||
<div id="love" style="cursor:pointer;" on-click="_toggleDefaultPage">[[_defaultPageText()]]</div
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="system-log-intro">
|
|
||||||
<paper-card>
|
|
||||||
<template is='dom-if' if='[[updating]]'>
|
|
||||||
<div class='loading-container'>
|
|
||||||
<paper-spinner active></paper-spinner>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
<template is='dom-if' if='[[!updating]]'>
|
|
||||||
<template is='dom-if' if='[[!items.length]]'>
|
|
||||||
<div class='card-content'>There are no new issues!</div>
|
|
||||||
</template>
|
|
||||||
<template is='dom-repeat' items='[[items]]'>
|
|
||||||
<paper-item on-click='openLog'>
|
|
||||||
<paper-item-body two-line>
|
|
||||||
<div class="row">
|
|
||||||
[[item.message]]
|
|
||||||
</div>
|
|
||||||
<div secondary>
|
|
||||||
[[formatTime(item.timestamp)]] [[item.source]] ([[item.level]])
|
|
||||||
</div>
|
|
||||||
</paper-item-body>
|
|
||||||
</paper-item>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<div class='card-actions'>
|
|
||||||
<ha-call-service-button
|
|
||||||
hass='[[hass]]'
|
|
||||||
domain='system_log'
|
|
||||||
service='clear'
|
|
||||||
>Clear</ha-call-service-button>
|
|
||||||
<ha-progress-button
|
|
||||||
on-click='_fetchData'
|
|
||||||
>Refresh</ha-progress-button>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</paper-card>
|
|
||||||
</div>
|
|
||||||
<p class='error-log-intro'>
|
|
||||||
<template is='dom-if' if='[[!errorLog]]'>
|
|
||||||
<paper-button raised on-click='refreshErrorLog'>Load Full Home Assistant Log</paper-button>
|
|
||||||
</template>
|
|
||||||
<template is='dom-if' if='[[errorLog]]'>
|
|
||||||
<paper-icon-button icon='hass:refresh' on-click='refreshErrorLog'></paper-icon-button>
|
|
||||||
</template>
|
|
||||||
</p>
|
|
||||||
<div class='error-log'>[[errorLog]]</div>
|
|
||||||
</div>
|
|
||||||
</app-header-layout>
|
|
||||||
|
|
||||||
<paper-dialog with-backdrop id="showlog">
|
|
||||||
<h2>Log Details ([[selectedItem.level]])</h2>
|
|
||||||
<paper-dialog-scrollable id="scrollable">
|
|
||||||
<p>[[fullTimeStamp(selectedItem.timestamp)]]</p>
|
|
||||||
<template is='dom-if' if='[[selectedItem.message]]'>
|
|
||||||
<pre>[[selectedItem.message]]</pre>
|
|
||||||
</template>
|
|
||||||
<template is='dom-if' if='[[selectedItem.exception]]'>
|
|
||||||
<pre>[[selectedItem.exception]]</pre>
|
|
||||||
</template>
|
|
||||||
</paper-dialog-scrollable>
|
|
||||||
</paper-dialog>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
||||||
static get properties() {
|
|
||||||
return {
|
|
||||||
hass: Object,
|
|
||||||
|
|
||||||
narrow: {
|
|
||||||
type: Boolean,
|
|
||||||
value: false,
|
|
||||||
},
|
|
||||||
|
|
||||||
showMenu: {
|
|
||||||
type: Boolean,
|
|
||||||
value: false,
|
|
||||||
},
|
|
||||||
|
|
||||||
errorLog: {
|
|
||||||
type: String,
|
|
||||||
value: "",
|
|
||||||
},
|
|
||||||
|
|
||||||
updating: {
|
|
||||||
type: Boolean,
|
|
||||||
value: true,
|
|
||||||
},
|
|
||||||
|
|
||||||
items: {
|
|
||||||
type: Array,
|
|
||||||
value: [],
|
|
||||||
},
|
|
||||||
|
|
||||||
selectedItem: Object,
|
|
||||||
|
|
||||||
jsVersion: {
|
|
||||||
type: String,
|
|
||||||
value: __BUILD__,
|
|
||||||
},
|
|
||||||
|
|
||||||
customUiList: {
|
|
||||||
type: Array,
|
|
||||||
value: window.CUSTOM_UI_LIST || [],
|
|
||||||
},
|
|
||||||
|
|
||||||
loadedComponents: {
|
|
||||||
type: Array,
|
|
||||||
value: [],
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
ready() {
|
|
||||||
super.ready();
|
|
||||||
this.addEventListener("hass-service-called", (ev) =>
|
|
||||||
this.serviceCalled(ev)
|
|
||||||
);
|
|
||||||
// Fix for overlay showing on top of dialog.
|
|
||||||
this.$.showlog.addEventListener("iron-overlay-opened", (ev) => {
|
|
||||||
if (ev.target.withBackdrop) {
|
|
||||||
ev.target.parentNode.insertBefore(ev.target.backdropElement, ev.target);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
serviceCalled(ev) {
|
|
||||||
// Check if this is for us
|
|
||||||
if (ev.detail.success && ev.detail.domain === "system_log") {
|
|
||||||
// Do the right thing depending on service
|
|
||||||
if (ev.detail.service === "clear") {
|
|
||||||
this.items = [];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
connectedCallback() {
|
|
||||||
super.connectedCallback();
|
|
||||||
this.$.scrollable.dialogElement = this.$.showlog;
|
|
||||||
this._fetchData();
|
|
||||||
this.loadedComponents = this.hass.config.components;
|
|
||||||
|
|
||||||
if (!registeredDialog) {
|
|
||||||
registeredDialog = true;
|
|
||||||
this.fire("register-dialog", {
|
|
||||||
dialogShowEvent: "show-loaded-components",
|
|
||||||
dialogTag: "ha-loaded-components",
|
|
||||||
dialogImport: () =>
|
|
||||||
import(/* webpackChunkName: "ha-loaded-components" */ "./ha-loaded-components"),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!window.CUSTOM_UI_LIST) {
|
|
||||||
// Give custom UI an opportunity to load.
|
|
||||||
setTimeout(() => {
|
|
||||||
this.customUiList = window.CUSTOM_UI_LIST || [];
|
|
||||||
}, 1000);
|
|
||||||
} else {
|
|
||||||
this.customUiList = window.CUSTOM_UI_LIST;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
refreshErrorLog(ev) {
|
|
||||||
if (ev) ev.preventDefault();
|
|
||||||
|
|
||||||
this.errorLog = "Loading error log…";
|
|
||||||
|
|
||||||
this.hass.callApi("GET", "error_log").then((log) => {
|
|
||||||
this.errorLog = log || "No errors have been reported.";
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
fullTimeStamp(date) {
|
|
||||||
return new Date(date * 1000);
|
|
||||||
}
|
|
||||||
|
|
||||||
formatTime(date) {
|
|
||||||
const today = new Date().setHours(0, 0, 0, 0);
|
|
||||||
const dateTime = new Date(date * 1000);
|
|
||||||
const dateTimeDay = new Date(date * 1000).setHours(0, 0, 0, 0);
|
|
||||||
|
|
||||||
return dateTimeDay < today
|
|
||||||
? formatDateTime(dateTime, this.hass.language)
|
|
||||||
: formatTime(dateTime, this.hass.language);
|
|
||||||
}
|
|
||||||
|
|
||||||
openLog(event) {
|
|
||||||
this.selectedItem = event.model.item;
|
|
||||||
this.$.showlog.open();
|
|
||||||
}
|
|
||||||
|
|
||||||
_fetchData() {
|
|
||||||
this.updating = true;
|
|
||||||
this.hass.callApi("get", "error/all").then((items) => {
|
|
||||||
this.items = items;
|
|
||||||
this.updating = false;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
_nonDefaultLink() {
|
|
||||||
if (
|
|
||||||
localStorage.defaultPage === OPT_IN_PANEL &&
|
|
||||||
OPT_IN_PANEL === "states"
|
|
||||||
) {
|
|
||||||
return "/lovelace";
|
|
||||||
}
|
|
||||||
return "/states";
|
|
||||||
}
|
|
||||||
|
|
||||||
_nonDefaultLinkText() {
|
|
||||||
if (
|
|
||||||
localStorage.defaultPage === OPT_IN_PANEL &&
|
|
||||||
OPT_IN_PANEL === "states"
|
|
||||||
) {
|
|
||||||
return "Go to the Lovelace UI";
|
|
||||||
}
|
|
||||||
return "Go to the states UI";
|
|
||||||
}
|
|
||||||
|
|
||||||
_defaultPageText() {
|
|
||||||
return `>> ${
|
|
||||||
localStorage.defaultPage === OPT_IN_PANEL ? "Remove" : "Set"
|
|
||||||
} ${OPT_IN_PANEL} as default page on this device <<`;
|
|
||||||
}
|
|
||||||
|
|
||||||
_toggleDefaultPage() {
|
|
||||||
if (localStorage.defaultPage === OPT_IN_PANEL) {
|
|
||||||
delete localStorage.defaultPage;
|
|
||||||
} else {
|
|
||||||
localStorage.defaultPage = OPT_IN_PANEL;
|
|
||||||
}
|
|
||||||
this.$.love.innerText = this._defaultPageText();
|
|
||||||
}
|
|
||||||
|
|
||||||
_showComponents() {
|
|
||||||
this.fire("show-loaded-components", {
|
|
||||||
hass: this.hass,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
customElements.define("ha-panel-dev-info", HaPanelDevInfo);
|
|
215
src/panels/dev-info/ha-panel-dev-info.ts
Normal file
215
src/panels/dev-info/ha-panel-dev-info.ts
Normal file
@ -0,0 +1,215 @@
|
|||||||
|
import {
|
||||||
|
LitElement,
|
||||||
|
html,
|
||||||
|
PropertyDeclarations,
|
||||||
|
CSSResult,
|
||||||
|
css,
|
||||||
|
TemplateResult,
|
||||||
|
} from "lit-element";
|
||||||
|
import "@polymer/app-layout/app-header-layout/app-header-layout";
|
||||||
|
import "@polymer/app-layout/app-header/app-header";
|
||||||
|
import "@polymer/app-layout/app-toolbar/app-toolbar";
|
||||||
|
import "../../components/ha-menu-button";
|
||||||
|
|
||||||
|
import { HomeAssistant } from "../../types";
|
||||||
|
import { haStyle } from "../../resources/ha-style";
|
||||||
|
|
||||||
|
import "./system-log-card";
|
||||||
|
import "./error-log-card";
|
||||||
|
|
||||||
|
const JS_VERSION = __BUILD__;
|
||||||
|
const OPT_IN_PANEL = "states";
|
||||||
|
|
||||||
|
class HaPanelDevInfo extends LitElement {
|
||||||
|
public hass?: HomeAssistant;
|
||||||
|
public narrow?: boolean;
|
||||||
|
public showMenu?: boolean;
|
||||||
|
|
||||||
|
static get properties(): PropertyDeclarations {
|
||||||
|
return {
|
||||||
|
hass: {},
|
||||||
|
narrow: {},
|
||||||
|
showMenu: {},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
protected render(): TemplateResult | void {
|
||||||
|
const hass = this.hass;
|
||||||
|
if (!hass) {
|
||||||
|
return html``;
|
||||||
|
}
|
||||||
|
const customUiList: Array<{ name: string; url: string; version: string }> =
|
||||||
|
(window as any).CUSTOM_UI_LIST || [];
|
||||||
|
|
||||||
|
const nonDefaultLink =
|
||||||
|
localStorage.defaultPage === OPT_IN_PANEL && OPT_IN_PANEL === "states"
|
||||||
|
? "/lovelace"
|
||||||
|
: "/states";
|
||||||
|
|
||||||
|
const nonDefaultLinkText =
|
||||||
|
localStorage.defaultPage === OPT_IN_PANEL && OPT_IN_PANEL === "states"
|
||||||
|
? "Go to the Lovelace UI"
|
||||||
|
: "Go to the states UI";
|
||||||
|
|
||||||
|
const defaultPageText = `${
|
||||||
|
localStorage.defaultPage === OPT_IN_PANEL ? "Remove" : "Set"
|
||||||
|
} ${OPT_IN_PANEL} as default page on this device`;
|
||||||
|
|
||||||
|
return html`
|
||||||
|
<app-header-layout has-scrolling-region>
|
||||||
|
<app-header slot="header" fixed>
|
||||||
|
<app-toolbar>
|
||||||
|
<ha-menu-button
|
||||||
|
.narrow="${this.narrow}"
|
||||||
|
showMenu="${this.showMenu}"
|
||||||
|
></ha-menu-button>
|
||||||
|
<div main-title>About</div>
|
||||||
|
</app-toolbar>
|
||||||
|
</app-header>
|
||||||
|
|
||||||
|
<div class="content">
|
||||||
|
<div class="about">
|
||||||
|
<p class="version">
|
||||||
|
<a href="https://www.home-assistant.io"
|
||||||
|
><img src="/static/icons/favicon-192x192.png" height="192"/></a
|
||||||
|
><br />
|
||||||
|
Home Assistant<br />
|
||||||
|
${hass.config.version}
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Path to configuration.yaml: ${hass.config.config_dir}
|
||||||
|
</p>
|
||||||
|
<p class="develop">
|
||||||
|
<a
|
||||||
|
href="https://www.home-assistant.io/developers/credits/"
|
||||||
|
target="_blank"
|
||||||
|
>
|
||||||
|
Developed by a bunch of awesome people.
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Published under the Apache 2.0 license<br />
|
||||||
|
Source:
|
||||||
|
<a
|
||||||
|
href="https://github.com/home-assistant/home-assistant"
|
||||||
|
target="_blank"
|
||||||
|
>server</a
|
||||||
|
>
|
||||||
|
—
|
||||||
|
<a
|
||||||
|
href="https://github.com/home-assistant/home-assistant-polymer"
|
||||||
|
target="_blank"
|
||||||
|
>frontend-ui</a
|
||||||
|
>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Built using
|
||||||
|
<a href="https://www.python.org">Python 3</a>,
|
||||||
|
<a href="https://www.polymer-project.org" target="_blank"
|
||||||
|
>Polymer</a
|
||||||
|
>, Icons by
|
||||||
|
<a href="https://www.google.com/design/icons/" target="_blank"
|
||||||
|
>Google</a
|
||||||
|
>
|
||||||
|
and
|
||||||
|
<a href="https://MaterialDesignIcons.com" target="_blank"
|
||||||
|
>MaterialDesignIcons.com</a
|
||||||
|
>.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Frontend JavaScript version: ${JS_VERSION}
|
||||||
|
${customUiList.length > 0
|
||||||
|
? html`
|
||||||
|
<div>
|
||||||
|
Custom UIs:
|
||||||
|
${customUiList.map(
|
||||||
|
(item) => html`
|
||||||
|
<div>
|
||||||
|
<a href="${item.url}" target="_blank">
|
||||||
|
${item.name}</a
|
||||||
|
>: ${item.version}
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
: ""}
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<a href="${nonDefaultLink}">${nonDefaultLinkText}</a><br />
|
||||||
|
<paper-button @click="${this._toggleDefaultPage}" raised>
|
||||||
|
${defaultPageText}
|
||||||
|
</paper-button>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<system-log-card .hass=${this.hass}></system-log-card>
|
||||||
|
<error-log-card .hass=${this.hass}></error-log-card>
|
||||||
|
</div>
|
||||||
|
</app-header-layout>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected firstUpdated(changedProps): void {
|
||||||
|
super.firstUpdated(changedProps);
|
||||||
|
|
||||||
|
// Legacy custom UI can be slow to register, give them time.
|
||||||
|
const customUI = ((window as any).CUSTOM_UI_LIST || []).length;
|
||||||
|
setTimeout(() => {
|
||||||
|
if (((window as any).CUSTOM_UI_LIST || []).length !== customUI.length) {
|
||||||
|
this.requestUpdate();
|
||||||
|
}
|
||||||
|
}, 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected _toggleDefaultPage(): void {
|
||||||
|
if (localStorage.defaultPage === OPT_IN_PANEL) {
|
||||||
|
delete localStorage.defaultPage;
|
||||||
|
} else {
|
||||||
|
localStorage.defaultPage = OPT_IN_PANEL;
|
||||||
|
}
|
||||||
|
this.requestUpdate();
|
||||||
|
}
|
||||||
|
|
||||||
|
static get styles(): CSSResult[] {
|
||||||
|
return [
|
||||||
|
haStyle,
|
||||||
|
css`
|
||||||
|
:host {
|
||||||
|
-ms-user-select: initial;
|
||||||
|
-webkit-user-select: initial;
|
||||||
|
-moz-user-select: initial;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
padding: 16px 0px 16px 0;
|
||||||
|
direction: ltr;
|
||||||
|
}
|
||||||
|
|
||||||
|
.about {
|
||||||
|
text-align: center;
|
||||||
|
line-height: 2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.version {
|
||||||
|
@apply --paper-font-headline;
|
||||||
|
}
|
||||||
|
|
||||||
|
.develop {
|
||||||
|
@apply --paper-font-subhead;
|
||||||
|
}
|
||||||
|
|
||||||
|
.about a {
|
||||||
|
color: var(--dark-primary-color);
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"ha-panel-dev-info": HaPanelDevInfo;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
customElements.define("ha-panel-dev-info", HaPanelDevInfo);
|
36
src/panels/dev-info/show-dialog-system-log-detail.ts
Normal file
36
src/panels/dev-info/show-dialog-system-log-detail.ts
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
import { fireEvent } from "../../common/dom/fire_event";
|
||||||
|
import { LoggedError } from "../../data/system_log";
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
// for fire event
|
||||||
|
interface HASSDomEvents {
|
||||||
|
"show-dialog-system-log-detail": SystemLogDetailDialogParams;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let registeredDialog = false;
|
||||||
|
const dialogShowEvent = "show-dialog-system-log-detail";
|
||||||
|
const dialogTag = "dialog-system-log-detail";
|
||||||
|
|
||||||
|
export interface SystemLogDetailDialogParams {
|
||||||
|
item: LoggedError;
|
||||||
|
}
|
||||||
|
|
||||||
|
const registerDialog = (element: HTMLElement) =>
|
||||||
|
fireEvent(element, "register-dialog", {
|
||||||
|
dialogShowEvent,
|
||||||
|
dialogTag,
|
||||||
|
dialogImport: () =>
|
||||||
|
import(/* webpackChunkName: "system-log-detail-dialog" */ "./dialog-system-log-detail"),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const showSystemLogDetailDialog = (
|
||||||
|
element: HTMLElement,
|
||||||
|
systemLogDetailParams: SystemLogDetailDialogParams
|
||||||
|
): void => {
|
||||||
|
if (!registeredDialog) {
|
||||||
|
registeredDialog = true;
|
||||||
|
registerDialog(element);
|
||||||
|
}
|
||||||
|
fireEvent(element, dialogShowEvent, systemLogDetailParams);
|
||||||
|
};
|
148
src/panels/dev-info/system-log-card.ts
Normal file
148
src/panels/dev-info/system-log-card.ts
Normal file
@ -0,0 +1,148 @@
|
|||||||
|
import {
|
||||||
|
LitElement,
|
||||||
|
html,
|
||||||
|
CSSResult,
|
||||||
|
css,
|
||||||
|
PropertyDeclarations,
|
||||||
|
TemplateResult,
|
||||||
|
} from "lit-element";
|
||||||
|
import "@polymer/paper-card/paper-card";
|
||||||
|
import "@polymer/paper-icon-button/paper-icon-button";
|
||||||
|
import "@polymer/paper-item/paper-item-body";
|
||||||
|
import "@polymer/paper-item/paper-item";
|
||||||
|
import "@polymer/paper-spinner/paper-spinner";
|
||||||
|
import "../../components/buttons/ha-call-service-button";
|
||||||
|
import "../../components/buttons/ha-progress-button";
|
||||||
|
import { HomeAssistant } from "../../types";
|
||||||
|
import { LoggedError, fetchSystemLog } from "../../data/system_log";
|
||||||
|
import formatDateTime from "../../common/datetime/format_date_time";
|
||||||
|
import formatTime from "../../common/datetime/format_time";
|
||||||
|
import { showSystemLogDetailDialog } from "./show-dialog-system-log-detail";
|
||||||
|
|
||||||
|
const formatLogTime = (date, language: string) => {
|
||||||
|
const today = new Date().setHours(0, 0, 0, 0);
|
||||||
|
const dateTime = new Date(date * 1000);
|
||||||
|
const dateTimeDay = new Date(date * 1000).setHours(0, 0, 0, 0);
|
||||||
|
|
||||||
|
return dateTimeDay < today
|
||||||
|
? formatDateTime(dateTime, language)
|
||||||
|
: formatTime(dateTime, language);
|
||||||
|
};
|
||||||
|
|
||||||
|
class SystemLogCard extends LitElement {
|
||||||
|
public hass?: HomeAssistant;
|
||||||
|
private _items?: LoggedError[];
|
||||||
|
|
||||||
|
static get properties(): PropertyDeclarations {
|
||||||
|
return {
|
||||||
|
hass: {},
|
||||||
|
_items: {},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
protected render(): TemplateResult | void {
|
||||||
|
return html`
|
||||||
|
<div class="system-log-intro">
|
||||||
|
<paper-card>
|
||||||
|
${this._items === undefined
|
||||||
|
? html`
|
||||||
|
<div class="loading-container">
|
||||||
|
<paper-spinner active></paper-spinner>
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
: html`
|
||||||
|
${this._items.length === 0
|
||||||
|
? html`
|
||||||
|
<div class="card-content">There are no new issues!</div>
|
||||||
|
`
|
||||||
|
: this._items.map(
|
||||||
|
(item) => html`
|
||||||
|
<paper-item @click=${this._openLog} .logItem=${item}>
|
||||||
|
<paper-item-body two-line>
|
||||||
|
<div class="row">
|
||||||
|
${item.message}
|
||||||
|
</div>
|
||||||
|
<div secondary>
|
||||||
|
${formatLogTime(
|
||||||
|
item.timestamp,
|
||||||
|
this.hass!.language
|
||||||
|
)}
|
||||||
|
${item.source} (${item.level})
|
||||||
|
</div>
|
||||||
|
</paper-item-body>
|
||||||
|
</paper-item>
|
||||||
|
`
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div class="card-actions">
|
||||||
|
<ha-call-service-button
|
||||||
|
.hass=${this.hass}
|
||||||
|
domain="system_log"
|
||||||
|
service="clear"
|
||||||
|
>Clear</ha-call-service-button
|
||||||
|
>
|
||||||
|
<ha-progress-button @click=${this._fetchData}
|
||||||
|
>Refresh</ha-progress-button
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
`}
|
||||||
|
</paper-card>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected firstUpdated(changedProps): void {
|
||||||
|
super.firstUpdated(changedProps);
|
||||||
|
this._fetchData();
|
||||||
|
this.addEventListener("hass-service-called", (ev) =>
|
||||||
|
this.serviceCalled(ev)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected serviceCalled(ev): void {
|
||||||
|
// Check if this is for us
|
||||||
|
if (ev.detail.success && ev.detail.domain === "system_log") {
|
||||||
|
// Do the right thing depending on service
|
||||||
|
if (ev.detail.service === "clear") {
|
||||||
|
this._items = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async _fetchData(): Promise<void> {
|
||||||
|
this._items = undefined;
|
||||||
|
this._items = await fetchSystemLog(this.hass!);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _openLog(ev: Event): void {
|
||||||
|
const item = (ev.currentTarget as any).logItem;
|
||||||
|
showSystemLogDetailDialog(this, { item });
|
||||||
|
}
|
||||||
|
|
||||||
|
static get styles(): CSSResult {
|
||||||
|
return css`
|
||||||
|
paper-card {
|
||||||
|
display: block;
|
||||||
|
padding-top: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
paper-item {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.system-log-intro {
|
||||||
|
margin: 16px;
|
||||||
|
border-top: 1px solid var(--light-primary-color);
|
||||||
|
padding-top: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-container {
|
||||||
|
@apply --layout-vertical;
|
||||||
|
@apply --layout-center-center;
|
||||||
|
height: 100px;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
customElements.define("system-log-card", SystemLogCard);
|
@ -11,6 +11,7 @@ import { PaperDialogElement } from "@polymer/paper-dialog/paper-dialog";
|
|||||||
|
|
||||||
import { moveCard } from "../config-util";
|
import { moveCard } from "../config-util";
|
||||||
import { MoveCardViewDialogParams } from "./show-move-card-view-dialog";
|
import { MoveCardViewDialogParams } from "./show-move-card-view-dialog";
|
||||||
|
import { PolymerChangedEvent } from "../../../../polymer-types";
|
||||||
|
|
||||||
export class HuiDialogMoveCardView extends LitElement {
|
export class HuiDialogMoveCardView extends LitElement {
|
||||||
private _params?: MoveCardViewDialogParams;
|
private _params?: MoveCardViewDialogParams;
|
||||||
@ -91,7 +92,7 @@ export class HuiDialogMoveCardView extends LitElement {
|
|||||||
this._dialog.close();
|
this._dialog.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
private _openedChanged(ev: MouseEvent) {
|
private _openedChanged(ev: PolymerChangedEvent<boolean>): void {
|
||||||
if (!(ev.detail as any).value) {
|
if (!(ev.detail as any).value) {
|
||||||
this._params = undefined;
|
this._params = undefined;
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,12 @@
|
|||||||
// Force file to be a module to augment global scope.
|
// Force file to be a module to augment global scope.
|
||||||
export {};
|
export {};
|
||||||
|
|
||||||
|
export interface PolymerChangedEvent<T> extends Event {
|
||||||
|
detail: {
|
||||||
|
value: T;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
// for fire event
|
// for fire event
|
||||||
interface HASSDomEvents {
|
interface HASSDomEvents {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user