mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-12 03:46: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 { MoveCardViewDialogParams } from "./show-move-card-view-dialog";
|
||||
import { PolymerChangedEvent } from "../../../../polymer-types";
|
||||
|
||||
export class HuiDialogMoveCardView extends LitElement {
|
||||
private _params?: MoveCardViewDialogParams;
|
||||
@ -91,7 +92,7 @@ export class HuiDialogMoveCardView extends LitElement {
|
||||
this._dialog.close();
|
||||
}
|
||||
|
||||
private _openedChanged(ev: MouseEvent) {
|
||||
private _openedChanged(ev: PolymerChangedEvent<boolean>): void {
|
||||
if (!(ev.detail as any).value) {
|
||||
this._params = undefined;
|
||||
}
|
||||
|
@ -1,6 +1,12 @@
|
||||
// Force file to be a module to augment global scope.
|
||||
export {};
|
||||
|
||||
export interface PolymerChangedEvent<T> extends Event {
|
||||
detail: {
|
||||
value: T;
|
||||
};
|
||||
}
|
||||
|
||||
declare global {
|
||||
// for fire event
|
||||
interface HASSDomEvents {
|
||||
|
Loading…
x
Reference in New Issue
Block a user