mirror of
https://github.com/home-assistant/frontend.git
synced 2025-09-02 13:00:27 +00:00
Compare commits
90 Commits
fix-number
...
combine-ap
Author | SHA1 | Date | |
---|---|---|---|
![]() |
21574edb44 | ||
![]() |
42023ed4a5 | ||
![]() |
8bfc8ece9d | ||
![]() |
2d3cf7d84d | ||
![]() |
520ef8f1df | ||
![]() |
f251d4267f | ||
![]() |
2052a5351c | ||
![]() |
9807d0aede | ||
![]() |
a41afcd714 | ||
![]() |
d93d2b5945 | ||
![]() |
d54a129605 | ||
![]() |
77911980cb | ||
![]() |
d51fd1e2f9 | ||
![]() |
fe54f8eb16 | ||
![]() |
fc7c4af27a | ||
![]() |
09e7600d86 | ||
![]() |
17410874e3 | ||
![]() |
03d4174163 | ||
![]() |
99eff73b0d | ||
![]() |
acefa39796 | ||
![]() |
c01c0528a6 | ||
![]() |
0ec58007c9 | ||
![]() |
e8daf88729 | ||
![]() |
ab74c7f7eb | ||
![]() |
6b673c7f44 | ||
![]() |
53510a3cb9 | ||
![]() |
d4d38a880d | ||
![]() |
18783d5e3b | ||
![]() |
eb235cb552 | ||
![]() |
435a6b6d53 | ||
![]() |
8d13745c6b | ||
![]() |
14c7cfc64c | ||
![]() |
c7821b9cee | ||
![]() |
a1d66aef0c | ||
![]() |
e275f1f4b9 | ||
![]() |
48de8b0739 | ||
![]() |
b75dc0efe0 | ||
![]() |
1d498349c5 | ||
![]() |
5cdcec699b | ||
![]() |
cd72287d99 | ||
![]() |
c8717bfa32 | ||
![]() |
83de75b689 | ||
![]() |
e5ea762cbc | ||
![]() |
01df01cd66 | ||
![]() |
2c07a2c825 | ||
![]() |
c3f50ba0fb | ||
![]() |
c04419fd09 | ||
![]() |
9c7af0dfce | ||
![]() |
b66d14e980 | ||
![]() |
6a553e9554 | ||
![]() |
4273b72d71 | ||
![]() |
9ccfa79199 | ||
![]() |
fe3d22d4f8 | ||
![]() |
e06642e892 | ||
![]() |
5199e946a1 | ||
![]() |
17aff2f9b8 | ||
![]() |
f7c7ac44f7 | ||
![]() |
62dd0a561e | ||
![]() |
858eacddea | ||
![]() |
471bb5169c | ||
![]() |
9d89aa329c | ||
![]() |
4e4d8bdc5e | ||
![]() |
a30ec32ac1 | ||
![]() |
d79e5dd8fb | ||
![]() |
92b116c0da | ||
![]() |
da3f911deb | ||
![]() |
9d82ce8ab4 | ||
![]() |
db9597d2e7 | ||
![]() |
8ea6baaf5d | ||
![]() |
1ed03842c0 | ||
![]() |
362b419814 | ||
![]() |
bffcccc1fe | ||
![]() |
b8e9a4ce9f | ||
![]() |
bdff3fd452 | ||
![]() |
1fc51f0087 | ||
![]() |
9a088a21da | ||
![]() |
1160d27004 | ||
![]() |
b4e5740050 | ||
![]() |
12bb3f5796 | ||
![]() |
ff62fdb69d | ||
![]() |
4ebf32cb1f | ||
![]() |
5afb8a77a9 | ||
![]() |
48ed33af95 | ||
![]() |
4a64cd4464 | ||
![]() |
8ae1a1b558 | ||
![]() |
ef1dd8b761 | ||
![]() |
3766f44787 | ||
![]() |
178605664e | ||
![]() |
0cf8004b8d | ||
![]() |
00412c7216 |
@@ -4,7 +4,7 @@
|
||||
"dockerfile": "Dockerfile",
|
||||
"context": ".."
|
||||
},
|
||||
"appPort": 8123,
|
||||
"appPort": "8124:8123",
|
||||
"context": "..",
|
||||
"postCreateCommand": "script/bootstrap",
|
||||
"extensions": [
|
||||
|
138
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
Normal file
138
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
Normal file
@@ -0,0 +1,138 @@
|
||||
name: Report a bug with the UI, Frontend or Lovelace
|
||||
about: Report an issue related to the Home Assistant frontend.
|
||||
labels: bug
|
||||
title: ""
|
||||
issue_body: true
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Make sure you are running the [latest version of Home Assistant][releases] before reporting an issue.
|
||||
|
||||
If you have a feature or enhancement request for the frontend, please [start an discussion][fr] instead of creating an issue.
|
||||
|
||||
**Please not not report issues for custom Lovelace cards.**
|
||||
|
||||
[fr]: https://github.com/home-assistant/frontend/discussions
|
||||
[releases]: https://github.com/home-assistant/home-assistant/releases
|
||||
- type: checkboxes
|
||||
attributes:
|
||||
label: Checklist
|
||||
description: Please verify that you've followed these steps
|
||||
options:
|
||||
- label: I have updated to the latest available Home Assistant version.
|
||||
required: true
|
||||
- label: I have cleared the cache of my browser.
|
||||
required: true
|
||||
- label: I have tried a different browser to see if it is related to my browser.
|
||||
required: true
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
## The problem
|
||||
- type: textarea
|
||||
validations:
|
||||
required: true
|
||||
attributes:
|
||||
label: Describe the issue you are experiencing
|
||||
description: Provide a clear and concise description of what the bug is.
|
||||
- type: textarea
|
||||
validations:
|
||||
required: true
|
||||
attributes:
|
||||
label: Describe the behavior you expected
|
||||
description: Describe what you expected to happen or it should look/behave.
|
||||
- type: textarea
|
||||
validations:
|
||||
required: true
|
||||
attributes:
|
||||
label: Steps to reproduce the issue
|
||||
description: |
|
||||
Please tell us exactly how to reproduce your issue.
|
||||
Provide clear and concise step by step instructions and add code snippets if needed.
|
||||
value: |
|
||||
1.
|
||||
2.
|
||||
3.
|
||||
...
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
## Environment
|
||||
- type: input
|
||||
validations:
|
||||
required: true
|
||||
attributes:
|
||||
label: What version of Home Assistant Core has the issue?
|
||||
placeholder: core-
|
||||
description: >
|
||||
Can be found in the Configuration panel -> Info.
|
||||
- type: input
|
||||
attributes:
|
||||
label: What was the last working version of Home Assistant Core?
|
||||
placeholder: core-
|
||||
description: >
|
||||
If known, otherwise leave blank.
|
||||
- type: input
|
||||
attributes:
|
||||
label: In which browser are you experiencing the issue with?
|
||||
placeholder: Google Chrome 88.0.4324.150
|
||||
description: >
|
||||
Provide the full name and don't forget to add the version!
|
||||
- type: input
|
||||
attributes:
|
||||
label: Which operating system are you using to run this browser?
|
||||
placeholder: macOS Big Sur (1.11)
|
||||
description: >
|
||||
Don't forget to add the version!
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
# Details
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: State of relevant entities
|
||||
description: >
|
||||
If your issue is about how an entity is shown in the UI, please add the
|
||||
state and attributes for all situations. You can find this information
|
||||
at Developer Tools -> States.
|
||||
value: |
|
||||
```yaml
|
||||
# Paste your state here.
|
||||
|
||||
```
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Problem-relevant frontend configuration
|
||||
description: >
|
||||
An example configuration that caused the problem for you, e.g., the YAML
|
||||
configuration of the used cards. Fill this out even if it seems
|
||||
unimportant to you. Please be sure to remove personal information like
|
||||
passwords, private URLs and other credentials.
|
||||
value: |
|
||||
```yaml
|
||||
# Paste your YAML here.
|
||||
|
||||
```
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Javascript errors shown in your browser console/inspector
|
||||
description: >
|
||||
If you come across any Javascript or other error logs, e.g., in your
|
||||
browser console/inspector please provide them.
|
||||
value: |
|
||||
```txt
|
||||
# Paste your logs here.
|
||||
|
||||
```
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
## Additional information
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
If you have any additional information for us, use the field below.
|
||||
Please note, you can attach screenshots or screen recordings here,
|
||||
by dragging and dropping files in the field below.
|
@@ -48,7 +48,7 @@ class HcCast extends LitElement {
|
||||
|
||||
protected render(): TemplateResult {
|
||||
if (this.lovelaceConfig === undefined) {
|
||||
return html` <hass-loading-screen no-toolbar></hass-loading-screen>> `;
|
||||
return html`<hass-loading-screen no-toolbar></hass-loading-screen>`;
|
||||
}
|
||||
|
||||
const error =
|
||||
|
@@ -98,8 +98,12 @@ class HcLayout extends LitElement {
|
||||
line-height: 32px;
|
||||
padding: 24px 16px 16px;
|
||||
display: block;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.hero {
|
||||
border-radius: 4px 4px 0 0;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
font-size: 14px;
|
||||
color: var(--secondary-text-color);
|
||||
|
@@ -11,19 +11,18 @@ import {
|
||||
PropertyValues,
|
||||
} from "lit-element";
|
||||
import { html, TemplateResult } from "lit-html";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { atLeastVersion } from "../../../src/common/config/version";
|
||||
import { fireEvent } from "../../../src/common/dom/fire_event";
|
||||
import "../../../src/common/search/search-input";
|
||||
import "../../../src/components/ha-button-menu";
|
||||
import "../../../src/components/ha-svg-icon";
|
||||
import {
|
||||
fetchHassioAddonsInfo,
|
||||
HassioAddonInfo,
|
||||
HassioAddonRepository,
|
||||
reloadHassioAddons,
|
||||
} from "../../../src/data/hassio/addon";
|
||||
import { extractApiErrorMessage } from "../../../src/data/hassio/common";
|
||||
import { fetchHassioSupervisorInfo } from "../../../src/data/hassio/supervisor";
|
||||
import { Supervisor } from "../../../src/data/supervisor/supervisor";
|
||||
import "../../../src/layouts/hass-loading-screen";
|
||||
import "../../../src/layouts/hass-tabs-subpage";
|
||||
import { HomeAssistant, Route } from "../../../src/types";
|
||||
@@ -51,46 +50,27 @@ const sortRepos = (a: HassioAddonRepository, b: HassioAddonRepository) => {
|
||||
class HassioAddonStore extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property({ attribute: false }) public supervisor!: Supervisor;
|
||||
|
||||
@property({ type: Boolean }) public narrow!: boolean;
|
||||
|
||||
@property({ attribute: false }) public route!: Route;
|
||||
|
||||
@property({ attribute: false }) private _addons?: HassioAddonInfo[];
|
||||
|
||||
@property({ attribute: false }) private _repos?: HassioAddonRepository[];
|
||||
|
||||
@internalProperty() private _filter?: string;
|
||||
|
||||
public async refreshData() {
|
||||
this._repos = undefined;
|
||||
this._addons = undefined;
|
||||
this._filter = undefined;
|
||||
await reloadHassioAddons(this.hass);
|
||||
await this._loadData();
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
const repos: TemplateResult[] = [];
|
||||
let repos: TemplateResult[] = [];
|
||||
|
||||
if (this._repos) {
|
||||
for (const repo of this._repos) {
|
||||
const addons = this._addons!.filter(
|
||||
(addon) => addon.repository === repo.slug
|
||||
);
|
||||
|
||||
if (addons.length === 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
repos.push(html`
|
||||
<hassio-addon-repository
|
||||
.hass=${this.hass}
|
||||
.repo=${repo}
|
||||
.addons=${addons}
|
||||
.filter=${this._filter!}
|
||||
></hassio-addon-repository>
|
||||
`);
|
||||
}
|
||||
if (this.supervisor.addon.repositories) {
|
||||
repos = this.addonRepositories(
|
||||
this.supervisor.addon.repositories,
|
||||
this.supervisor.addon.addons
|
||||
);
|
||||
}
|
||||
|
||||
return html`
|
||||
@@ -159,6 +139,27 @@ class HassioAddonStore extends LitElement {
|
||||
this._loadData();
|
||||
}
|
||||
|
||||
private addonRepositories = memoizeOne(
|
||||
(repositories: HassioAddonRepository[], addons: HassioAddonInfo[]) => {
|
||||
return repositories.sort(sortRepos).map((repo) => {
|
||||
const filteredAddons = addons.filter(
|
||||
(addon) => addon.repository === repo.slug
|
||||
);
|
||||
|
||||
return filteredAddons.length !== 0
|
||||
? html`
|
||||
<hassio-addon-repository
|
||||
.hass=${this.hass}
|
||||
.repo=${repo}
|
||||
.addons=${filteredAddons}
|
||||
.filter=${this._filter!}
|
||||
></hassio-addon-repository>
|
||||
`
|
||||
: html``;
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
private _handleAction(ev: CustomEvent<ActionDetail>) {
|
||||
switch (ev.detail.index) {
|
||||
case 0:
|
||||
@@ -181,7 +182,7 @@ class HassioAddonStore extends LitElement {
|
||||
|
||||
private async _manageRepositories() {
|
||||
showRepositoriesDialog(this, {
|
||||
repos: this._repos!,
|
||||
repos: this.supervisor.addon.repositories,
|
||||
loadData: () => this._loadData(),
|
||||
});
|
||||
}
|
||||
@@ -191,18 +192,8 @@ class HassioAddonStore extends LitElement {
|
||||
}
|
||||
|
||||
private async _loadData() {
|
||||
try {
|
||||
const [addonsInfo, supervisor] = await Promise.all([
|
||||
fetchHassioAddonsInfo(this.hass),
|
||||
fetchHassioSupervisorInfo(this.hass),
|
||||
]);
|
||||
fireEvent(this, "supervisor-update", { supervisor });
|
||||
this._repos = addonsInfo.repositories;
|
||||
this._repos.sort(sortRepos);
|
||||
this._addons = addonsInfo.addons;
|
||||
} catch (err) {
|
||||
alert(extractApiErrorMessage(err));
|
||||
}
|
||||
fireEvent(this, "supervisor-store-refresh", { store: "addon" });
|
||||
fireEvent(this, "supervisor-store-refresh", { store: "supervisor" });
|
||||
}
|
||||
|
||||
private async _filterChanged(e) {
|
||||
|
@@ -29,7 +29,7 @@ class HassioAddonConfigDashboard extends LitElement {
|
||||
const hasOptions =
|
||||
this.addon.options && Object.keys(this.addon.options).length;
|
||||
const hasSchema =
|
||||
this.addon.schema && Object.keys(this.addon.schema).length;
|
||||
hasOptions && this.addon.schema && Object.keys(this.addon.schema).length;
|
||||
|
||||
return html`
|
||||
<div class="content">
|
||||
|
@@ -94,7 +94,7 @@ class HassioAddonConfig extends LitElement {
|
||||
? ""
|
||||
: html` <div class="errors">Invalid YAML</div> `}
|
||||
</div>
|
||||
<div class="card-actions">
|
||||
<div class="card-actions right">
|
||||
<ha-progress-button
|
||||
@click=${this._saveTapped}
|
||||
.disabled=${!this._configHasChanged || !this._valid}
|
||||
@@ -108,10 +108,12 @@ class HassioAddonConfig extends LitElement {
|
||||
|
||||
protected firstUpdated(changedProps) {
|
||||
super.firstUpdated(changedProps);
|
||||
this._canShowSchema = !this.addon.schema.find(
|
||||
// @ts-ignore
|
||||
(entry) => !SUPPORTED_UI_TYPES.includes(entry.type) || entry.multiple
|
||||
);
|
||||
this._canShowSchema =
|
||||
Object.keys(this.addon.options).length !== 0 &&
|
||||
!this.addon.schema!.find(
|
||||
// @ts-ignore
|
||||
(entry) => !SUPPORTED_UI_TYPES.includes(entry.type) || entry.multiple
|
||||
);
|
||||
this._yamlMode = !this._canShowSchema;
|
||||
}
|
||||
|
||||
@@ -148,13 +150,11 @@ class HassioAddonConfig extends LitElement {
|
||||
if (this.addon.schema && this._canShowSchema && !this._yamlMode) {
|
||||
this._valid = true;
|
||||
this._configHasChanged = true;
|
||||
this._options! = ev.detail.value;
|
||||
} else {
|
||||
this._configHasChanged = true;
|
||||
this._valid = ev.detail.isValid;
|
||||
}
|
||||
if (this._valid) {
|
||||
this._options! = ev.detail.value;
|
||||
}
|
||||
}
|
||||
|
||||
private async _resetTapped(ev: CustomEvent): Promise<void> {
|
||||
@@ -202,8 +202,9 @@ class HassioAddonConfig extends LitElement {
|
||||
|
||||
try {
|
||||
await setHassioAddonOption(this.hass, this.addon.slug, {
|
||||
options: this._options!,
|
||||
options: this._yamlMode ? this._editor?.value : this._options,
|
||||
});
|
||||
|
||||
this._configHasChanged = false;
|
||||
const eventdata = {
|
||||
success: true,
|
||||
@@ -271,6 +272,9 @@ class HassioAddonConfig extends LitElement {
|
||||
margin-block: 0px;
|
||||
font-weight: normal;
|
||||
}
|
||||
.card-actions.right {
|
||||
justify-content: flex-end;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
@@ -9,16 +9,24 @@ import {
|
||||
CSSResult,
|
||||
customElement,
|
||||
html,
|
||||
internalProperty,
|
||||
LitElement,
|
||||
property,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { fireEvent } from "../../../src/common/dom/fire_event";
|
||||
import { navigate } from "../../../src/common/navigate";
|
||||
import { extractSearchParam } from "../../../src/common/url/search-params";
|
||||
import "../../../src/components/ha-circular-progress";
|
||||
import {
|
||||
fetchHassioAddonInfo,
|
||||
HassioAddonDetails,
|
||||
} from "../../../src/data/hassio/addon";
|
||||
import { extractApiErrorMessage } from "../../../src/data/hassio/common";
|
||||
import { Supervisor } from "../../../src/data/supervisor/supervisor";
|
||||
import "../../../src/layouts/hass-error-screen";
|
||||
import "../../../src/layouts/hass-loading-screen";
|
||||
import "../../../src/layouts/hass-tabs-subpage";
|
||||
import type { PageNavigation } from "../../../src/layouts/hass-tabs-subpage";
|
||||
import { haStyle } from "../../../src/resources/styles";
|
||||
@@ -35,12 +43,16 @@ import "./log/hassio-addon-logs";
|
||||
class HassioAddonDashboard extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property({ attribute: false }) public supervisor!: Supervisor;
|
||||
|
||||
@property({ attribute: false }) public route!: Route;
|
||||
|
||||
@property({ attribute: false }) public addon?: HassioAddonDetails;
|
||||
|
||||
@property({ type: Boolean }) public narrow!: boolean;
|
||||
|
||||
@internalProperty() _error?: string;
|
||||
|
||||
private _computeTail = memoizeOne((route: Route) => {
|
||||
const dividerPos = route.path.indexOf("/", 1);
|
||||
return dividerPos === -1
|
||||
@@ -55,8 +67,14 @@ class HassioAddonDashboard extends LitElement {
|
||||
});
|
||||
|
||||
protected render(): TemplateResult {
|
||||
if (this._error) {
|
||||
return html`<hass-error-screen
|
||||
.error=${this._error}
|
||||
></hass-error-screen>`;
|
||||
}
|
||||
|
||||
if (!this.addon) {
|
||||
return html`<ha-circular-progress active></ha-circular-progress>`;
|
||||
return html`<hass-loading-screen></hass-loading-screen>`;
|
||||
}
|
||||
|
||||
const addonTabs: PageNavigation[] = [
|
||||
@@ -106,6 +124,7 @@ class HassioAddonDashboard extends LitElement {
|
||||
.route=${route}
|
||||
.narrow=${this.narrow}
|
||||
.hass=${this.hass}
|
||||
.supervisor=${this.supervisor}
|
||||
.addon=${this.addon}
|
||||
></hassio-addon-router>
|
||||
</hass-tabs-subpage>
|
||||
@@ -152,30 +171,51 @@ class HassioAddonDashboard extends LitElement {
|
||||
}
|
||||
|
||||
protected async firstUpdated(): Promise<void> {
|
||||
await this._routeDataChanged(this.route);
|
||||
if (this.route.path === "") {
|
||||
const addon = extractSearchParam("addon");
|
||||
if (addon) {
|
||||
navigate(this, `/hassio/addon/${addon}`, true);
|
||||
}
|
||||
}
|
||||
this.addEventListener("hass-api-called", (ev) => this._apiCalled(ev));
|
||||
}
|
||||
|
||||
private async _apiCalled(ev): Promise<void> {
|
||||
const path: string = ev.detail.path;
|
||||
const pathSplit: string[] = ev.detail.path?.split("/");
|
||||
|
||||
if (!path) {
|
||||
if (!pathSplit || pathSplit.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const path: string = pathSplit[pathSplit.length - 1];
|
||||
|
||||
if (["uninstall", "install", "update", "start", "stop"].includes(path)) {
|
||||
fireEvent(this, "supervisor-store-refresh", { store: "supervisor" });
|
||||
}
|
||||
|
||||
if (path === "uninstall") {
|
||||
history.back();
|
||||
window.history.back();
|
||||
} else {
|
||||
await this._routeDataChanged(this.route);
|
||||
await this._routeDataChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private async _routeDataChanged(routeData: Route): Promise<void> {
|
||||
const addon = routeData.path.split("/")[1];
|
||||
protected updated(changedProperties) {
|
||||
if (changedProperties.has("route") && !this.addon) {
|
||||
this._routeDataChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private async _routeDataChanged(): Promise<void> {
|
||||
const addon = this.route.path.split("/")[1];
|
||||
if (!addon) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const addoninfo = await fetchHassioAddonInfo(this.hass, addon);
|
||||
this.addon = addoninfo;
|
||||
} catch {
|
||||
} catch (err) {
|
||||
this._error = `Error fetching addon info: ${extractApiErrorMessage(err)}`;
|
||||
this.addon = undefined;
|
||||
}
|
||||
}
|
||||
|
@@ -1,5 +1,6 @@
|
||||
import { customElement, property } from "lit-element";
|
||||
import { HassioAddonDetails } from "../../../src/data/hassio/addon";
|
||||
import { Supervisor } from "../../../src/data/supervisor/supervisor";
|
||||
import {
|
||||
HassRouterPage,
|
||||
RouterOptions,
|
||||
@@ -17,6 +18,8 @@ class HassioAddonRouter extends HassRouterPage {
|
||||
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property({ attribute: false }) public supervisor!: Supervisor;
|
||||
|
||||
@property({ attribute: false }) public addon!: HassioAddonDetails;
|
||||
|
||||
protected routerOptions: RouterOptions = {
|
||||
@@ -41,6 +44,7 @@ class HassioAddonRouter extends HassRouterPage {
|
||||
protected updatePageEl(el) {
|
||||
el.route = this.routeTail;
|
||||
el.hass = this.hass;
|
||||
el.supervisor = this.supervisor;
|
||||
el.addon = this.addon;
|
||||
el.narrow = this.narrow;
|
||||
}
|
||||
|
@@ -9,6 +9,7 @@ import {
|
||||
} from "lit-element";
|
||||
import "../../../../src/components/ha-circular-progress";
|
||||
import { HassioAddonDetails } from "../../../../src/data/hassio/addon";
|
||||
import { Supervisor } from "../../../../src/data/supervisor/supervisor";
|
||||
import { haStyle } from "../../../../src/resources/styles";
|
||||
import { HomeAssistant } from "../../../../src/types";
|
||||
import { hassioStyle } from "../../resources/hassio-style";
|
||||
@@ -20,6 +21,8 @@ class HassioAddonInfoDashboard extends LitElement {
|
||||
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property({ attribute: false }) public supervisor!: Supervisor;
|
||||
|
||||
@property({ attribute: false }) public addon?: HassioAddonDetails;
|
||||
|
||||
protected render(): TemplateResult {
|
||||
@@ -32,6 +35,7 @@ class HassioAddonInfoDashboard extends LitElement {
|
||||
<hassio-addon-info
|
||||
.narrow=${this.narrow}
|
||||
.hass=${this.hass}
|
||||
.supervisor=${this.supervisor}
|
||||
.addon=${this.addon}
|
||||
></hassio-addon-info>
|
||||
</div>
|
||||
|
@@ -43,10 +43,13 @@ import {
|
||||
HassioAddonSetOptionParams,
|
||||
HassioAddonSetSecurityParams,
|
||||
installHassioAddon,
|
||||
restartHassioAddon,
|
||||
setHassioAddonOption,
|
||||
setHassioAddonSecurity,
|
||||
startHassioAddon,
|
||||
stopHassioAddon,
|
||||
uninstallHassioAddon,
|
||||
updateHassioAddon,
|
||||
validateHassioAddonOption,
|
||||
} from "../../../../src/data/hassio/addon";
|
||||
import {
|
||||
@@ -54,6 +57,7 @@ import {
|
||||
fetchHassioStats,
|
||||
HassioStats,
|
||||
} from "../../../../src/data/hassio/common";
|
||||
import { Supervisor } from "../../../../src/data/supervisor/supervisor";
|
||||
import {
|
||||
showAlertDialog,
|
||||
showConfirmationDialog,
|
||||
@@ -65,6 +69,7 @@ import "../../components/hassio-card-content";
|
||||
import "../../components/supervisor-metric";
|
||||
import { showHassioMarkdownDialog } from "../../dialogs/markdown/show-dialog-hassio-markdown";
|
||||
import { hassioStyle } from "../../resources/hassio-style";
|
||||
import { addonArchIsSupported } from "../../util/addon";
|
||||
|
||||
const STAGE_ICON = {
|
||||
stable: mdiCheckCircle,
|
||||
@@ -137,6 +142,8 @@ class HassioAddonInfo extends LitElement {
|
||||
|
||||
@property({ attribute: false }) public addon!: HassioAddonDetails;
|
||||
|
||||
@property({ attribute: false }) public supervisor!: Supervisor;
|
||||
|
||||
@internalProperty() private _metrics?: HassioStats;
|
||||
|
||||
@internalProperty() private _error?: string;
|
||||
@@ -170,21 +177,31 @@ class HassioAddonInfo extends LitElement {
|
||||
iconClass="update"
|
||||
></hassio-card-content>
|
||||
${!this.addon.available
|
||||
? html`
|
||||
<p>
|
||||
This update is no longer compatible with your system.
|
||||
</p>
|
||||
`
|
||||
? !addonArchIsSupported(
|
||||
this.supervisor.info.supported_arch,
|
||||
this.addon.arch
|
||||
)
|
||||
? html`
|
||||
<p>
|
||||
This add-on is not compatible with the processor of
|
||||
your device or the operating system you have installed
|
||||
on your device.
|
||||
</p>
|
||||
`
|
||||
: html`
|
||||
<p>
|
||||
You are running Home Assistant
|
||||
${this.supervisor.core.version}, to update to this
|
||||
version of the add-on you need at least version
|
||||
${this.addon.homeassistant} of Home Assistant
|
||||
</p>
|
||||
`
|
||||
: ""}
|
||||
</div>
|
||||
<div class="card-actions">
|
||||
<ha-call-api-button
|
||||
.hass=${this.hass}
|
||||
.disabled=${!this.addon.available}
|
||||
path="hassio/addons/${this.addon.slug}/update"
|
||||
>
|
||||
<ha-progress-button @click=${this._updateClicked}>
|
||||
Update
|
||||
</ha-call-api-button>
|
||||
</ha-progress-button>
|
||||
${this.addon.changelog
|
||||
? html`
|
||||
<mwc-button @click=${this._openChangelog}>
|
||||
@@ -534,87 +551,102 @@ class HassioAddonInfo extends LitElement {
|
||||
</div>
|
||||
</div>
|
||||
${this._error ? html` <div class="errors">${this._error}</div> ` : ""}
|
||||
${!this.addon.available
|
||||
? !addonArchIsSupported(
|
||||
this.supervisor.info.supported_arch,
|
||||
this.addon.arch
|
||||
)
|
||||
? html`
|
||||
<p class="warning">
|
||||
This add-on is not compatible with the processor of your
|
||||
device or the operating system you have installed on your
|
||||
device.
|
||||
</p>
|
||||
`
|
||||
: html`
|
||||
<p class="warning">
|
||||
You are running Home Assistant
|
||||
${this.supervisor.core.version}, to install this add-on you
|
||||
need at least version ${this.addon.homeassistant} of Home
|
||||
Assistant
|
||||
</p>
|
||||
`
|
||||
: ""}
|
||||
</div>
|
||||
<div class="card-actions">
|
||||
${this.addon.version
|
||||
? html`
|
||||
${this._computeIsRunning
|
||||
? html`
|
||||
<ha-call-api-button
|
||||
class="warning"
|
||||
.hass=${this.hass}
|
||||
.path="hassio/addons/${this.addon.slug}/stop"
|
||||
>
|
||||
Stop
|
||||
</ha-call-api-button>
|
||||
<ha-call-api-button
|
||||
class="warning"
|
||||
.hass=${this.hass}
|
||||
.path="hassio/addons/${this.addon.slug}/restart"
|
||||
>
|
||||
Restart
|
||||
</ha-call-api-button>
|
||||
`
|
||||
: html`
|
||||
<ha-progress-button @click=${this._startClicked}>
|
||||
Start
|
||||
</ha-progress-button>
|
||||
`}
|
||||
${this._computeShowWebUI
|
||||
? html`
|
||||
<a
|
||||
href=${this._pathWebui!}
|
||||
tabindex="-1"
|
||||
target="_blank"
|
||||
class="right"
|
||||
rel="noopener"
|
||||
>
|
||||
<mwc-button>
|
||||
<div>
|
||||
${this.addon.version
|
||||
? this._computeIsRunning
|
||||
? html`
|
||||
<ha-progress-button
|
||||
class="warning"
|
||||
@click=${this._stopClicked}
|
||||
>
|
||||
Stop
|
||||
</ha-progress-button>
|
||||
<ha-progress-button
|
||||
class="warning"
|
||||
@click=${this._restartClicked}
|
||||
>
|
||||
Restart
|
||||
</ha-progress-button>
|
||||
`
|
||||
: html`
|
||||
<ha-progress-button @click=${this._startClicked}>
|
||||
Start
|
||||
</ha-progress-button>
|
||||
`
|
||||
: html`
|
||||
<ha-progress-button
|
||||
.disabled=${!this.addon.available}
|
||||
@click=${this._installClicked}
|
||||
>
|
||||
Install
|
||||
</ha-progress-button>
|
||||
`}
|
||||
</div>
|
||||
<div>
|
||||
${this.addon.version
|
||||
? html` ${this._computeShowWebUI
|
||||
? html`
|
||||
<a
|
||||
href=${this._pathWebui!}
|
||||
tabindex="-1"
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
>
|
||||
<mwc-button>
|
||||
Open web UI
|
||||
</mwc-button>
|
||||
</a>
|
||||
`
|
||||
: ""}
|
||||
${this._computeShowIngressUI
|
||||
? html`
|
||||
<mwc-button @click=${this._openIngress}>
|
||||
Open web UI
|
||||
</mwc-button>
|
||||
</a>
|
||||
`
|
||||
: ""}
|
||||
${this._computeShowIngressUI
|
||||
? html`
|
||||
<mwc-button class="right" @click=${this._openIngress}>
|
||||
Open web UI
|
||||
</mwc-button>
|
||||
`
|
||||
: ""}
|
||||
<ha-progress-button
|
||||
class=" right warning"
|
||||
@click=${this._uninstallClicked}
|
||||
>
|
||||
Uninstall
|
||||
</ha-progress-button>
|
||||
${this.addon.build
|
||||
? html`
|
||||
<ha-call-api-button
|
||||
class="warning right"
|
||||
.hass=${this.hass}
|
||||
.path="hassio/addons/${this.addon.slug}/rebuild"
|
||||
>
|
||||
Rebuild
|
||||
</ha-call-api-button>
|
||||
`
|
||||
: ""}
|
||||
`
|
||||
: html`
|
||||
${!this.addon.available
|
||||
? html`
|
||||
<p class="warning">
|
||||
This add-on is not available on your system.
|
||||
</p>
|
||||
`
|
||||
: ""}
|
||||
<ha-progress-button
|
||||
.disabled=${!this.addon.available}
|
||||
@click=${this._installClicked}
|
||||
>
|
||||
Install
|
||||
</ha-progress-button>
|
||||
`}
|
||||
`
|
||||
: ""}
|
||||
<ha-progress-button
|
||||
class="warning"
|
||||
@click=${this._uninstallClicked}
|
||||
>
|
||||
Uninstall
|
||||
</ha-progress-button>
|
||||
${this.addon.build
|
||||
? html`
|
||||
<ha-call-api-button
|
||||
class="warning"
|
||||
.hass=${this.hass}
|
||||
.path="hassio/addons/${this.addon.slug}/rebuild"
|
||||
>
|
||||
Rebuild
|
||||
</ha-call-api-button>
|
||||
`
|
||||
: ""}`
|
||||
: ""}
|
||||
</div>
|
||||
</div>
|
||||
</ha-card>
|
||||
|
||||
@@ -848,6 +880,82 @@ class HassioAddonInfo extends LitElement {
|
||||
button.progress = false;
|
||||
}
|
||||
|
||||
private async _stopClicked(ev: CustomEvent): Promise<void> {
|
||||
const button = ev.currentTarget as any;
|
||||
button.progress = true;
|
||||
|
||||
try {
|
||||
await stopHassioAddon(this.hass, this.addon.slug);
|
||||
const eventdata = {
|
||||
success: true,
|
||||
response: undefined,
|
||||
path: "stop",
|
||||
};
|
||||
fireEvent(this, "hass-api-called", eventdata);
|
||||
} catch (err) {
|
||||
showAlertDialog(this, {
|
||||
title: "Failed to stop addon",
|
||||
text: extractApiErrorMessage(err),
|
||||
});
|
||||
}
|
||||
button.progress = false;
|
||||
}
|
||||
|
||||
private async _restartClicked(ev: CustomEvent): Promise<void> {
|
||||
const button = ev.currentTarget as any;
|
||||
button.progress = true;
|
||||
|
||||
try {
|
||||
await restartHassioAddon(this.hass, this.addon.slug);
|
||||
const eventdata = {
|
||||
success: true,
|
||||
response: undefined,
|
||||
path: "stop",
|
||||
};
|
||||
fireEvent(this, "hass-api-called", eventdata);
|
||||
} catch (err) {
|
||||
showAlertDialog(this, {
|
||||
title: "Failed to restart addon",
|
||||
text: extractApiErrorMessage(err),
|
||||
});
|
||||
}
|
||||
button.progress = false;
|
||||
}
|
||||
|
||||
private async _updateClicked(ev: CustomEvent): Promise<void> {
|
||||
const button = ev.currentTarget as any;
|
||||
button.progress = true;
|
||||
|
||||
const confirmed = await showConfirmationDialog(this, {
|
||||
title: this.addon.name,
|
||||
text: "Are you sure you want to update this add-on?",
|
||||
confirmText: "update add-on",
|
||||
dismissText: "no",
|
||||
});
|
||||
|
||||
if (!confirmed) {
|
||||
button.progress = false;
|
||||
return;
|
||||
}
|
||||
|
||||
this._error = undefined;
|
||||
try {
|
||||
await updateHassioAddon(this.hass, this.addon.slug);
|
||||
const eventdata = {
|
||||
success: true,
|
||||
response: undefined,
|
||||
path: "update",
|
||||
};
|
||||
fireEvent(this, "hass-api-called", eventdata);
|
||||
} catch (err) {
|
||||
showAlertDialog(this, {
|
||||
title: "Failed to update addon",
|
||||
text: extractApiErrorMessage(err),
|
||||
});
|
||||
}
|
||||
button.progress = false;
|
||||
}
|
||||
|
||||
private async _startClicked(ev: CustomEvent): Promise<void> {
|
||||
const button = ev.currentTarget as any;
|
||||
button.progress = true;
|
||||
@@ -856,10 +964,10 @@ class HassioAddonInfo extends LitElement {
|
||||
this.hass,
|
||||
this.addon.slug
|
||||
);
|
||||
if (!validate.data.valid) {
|
||||
if (!validate.valid) {
|
||||
await showConfirmationDialog(this, {
|
||||
title: "Failed to start addon - configuration validation failed!",
|
||||
text: validate.data.message.split(" Got ")[0],
|
||||
text: validate.message.split(" Got ")[0],
|
||||
confirm: () => this._openConfiguration(),
|
||||
confirmText: "Go to configuration",
|
||||
dismissText: "Cancel",
|
||||
@@ -879,6 +987,12 @@ class HassioAddonInfo extends LitElement {
|
||||
try {
|
||||
await startHassioAddon(this.hass, this.addon.slug);
|
||||
this.addon = await fetchHassioAddonInfo(this.hass, this.addon.slug);
|
||||
const eventdata = {
|
||||
success: true,
|
||||
response: undefined,
|
||||
path: "start",
|
||||
};
|
||||
fireEvent(this, "hass-api-called", eventdata);
|
||||
} catch (err) {
|
||||
showAlertDialog(this, {
|
||||
title: "Failed to start addon",
|
||||
@@ -994,9 +1108,6 @@ class HassioAddonInfo extends LitElement {
|
||||
font-weight: 500;
|
||||
color: var(--primary-color);
|
||||
}
|
||||
.right {
|
||||
float: right;
|
||||
}
|
||||
protection-enable mwc-button {
|
||||
--mdc-theme-primary: white;
|
||||
}
|
||||
@@ -1019,7 +1130,8 @@ class HassioAddonInfo extends LitElement {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
.card-actions {
|
||||
display: flow-root;
|
||||
justify-content: space-between;
|
||||
display: flex;
|
||||
}
|
||||
.security h3 {
|
||||
margin-bottom: 8px;
|
||||
@@ -1055,18 +1167,16 @@ class HassioAddonInfo extends LitElement {
|
||||
}
|
||||
|
||||
.addon-options {
|
||||
max-width: 50%;
|
||||
}
|
||||
.addon-options.started {
|
||||
max-width: 90%;
|
||||
}
|
||||
|
||||
.addon-container {
|
||||
display: grid;
|
||||
grid-auto-flow: column;
|
||||
grid-template-columns: 1fr auto;
|
||||
grid-template-columns: 60% 40%;
|
||||
}
|
||||
.addon-container div:last-of-type {
|
||||
|
||||
.addon-container > div:last-of-type {
|
||||
align-self: end;
|
||||
}
|
||||
|
||||
|
@@ -10,6 +10,7 @@ import {
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { fireEvent } from "../../../src/common/dom/fire_event";
|
||||
import "../../../src/components/buttons/ha-progress-button";
|
||||
import "../../../src/components/ha-card";
|
||||
import "../../../src/components/ha-svg-icon";
|
||||
@@ -64,6 +65,7 @@ export class HassioUpdate extends LitElement {
|
||||
<div class="card-group">
|
||||
${this._renderUpdateCard(
|
||||
"Home Assistant Core",
|
||||
"core",
|
||||
this.supervisor.core,
|
||||
"hassio/homeassistant/update",
|
||||
`https://${
|
||||
@@ -72,6 +74,7 @@ export class HassioUpdate extends LitElement {
|
||||
)}
|
||||
${this._renderUpdateCard(
|
||||
"Supervisor",
|
||||
"supervisor",
|
||||
this.supervisor.supervisor,
|
||||
"hassio/supervisor/update",
|
||||
`https://github.com//home-assistant/hassio/releases/tag/${this.supervisor.supervisor.version_latest}`
|
||||
@@ -79,6 +82,7 @@ export class HassioUpdate extends LitElement {
|
||||
${this.supervisor.host.features.includes("hassos")
|
||||
? this._renderUpdateCard(
|
||||
"Operating System",
|
||||
"os",
|
||||
this.supervisor.os,
|
||||
"hassio/os/update",
|
||||
`https://github.com//home-assistant/hassos/releases/tag/${this.supervisor.os.version_latest}`
|
||||
@@ -91,6 +95,7 @@ export class HassioUpdate extends LitElement {
|
||||
|
||||
private _renderUpdateCard(
|
||||
name: string,
|
||||
key: string,
|
||||
object: HassioHomeAssistantInfo | HassioSupervisorInfo | HassioHassOSInfo,
|
||||
apiPath: string,
|
||||
releaseNotesUrl: string
|
||||
@@ -116,6 +121,7 @@ export class HassioUpdate extends LitElement {
|
||||
<ha-progress-button
|
||||
.apiPath=${apiPath}
|
||||
.name=${name}
|
||||
.key=${key}
|
||||
.version=${object.version_latest}
|
||||
@click=${this._confirmUpdate}
|
||||
>
|
||||
@@ -142,6 +148,7 @@ export class HassioUpdate extends LitElement {
|
||||
}
|
||||
try {
|
||||
await this.hass.callApi<HassioResponse<void>>("POST", item.apiPath);
|
||||
fireEvent(this, "supervisor-store-refresh", { store: item.key });
|
||||
} catch (err) {
|
||||
// Only show an error if the status code was not expected (user behind proxy)
|
||||
// or no status at all(connection terminated)
|
||||
|
@@ -22,7 +22,11 @@ import {
|
||||
fetchHassioSnapshotInfo,
|
||||
HassioSnapshotDetail,
|
||||
} from "../../../../src/data/hassio/snapshot";
|
||||
import { showConfirmationDialog } from "../../../../src/dialogs/generic/show-dialog-box";
|
||||
import { Supervisor } from "../../../../src/data/supervisor/supervisor";
|
||||
import {
|
||||
showAlertDialog,
|
||||
showConfirmationDialog,
|
||||
} from "../../../../src/dialogs/generic/show-dialog-box";
|
||||
import { PolymerChangedEvent } from "../../../../src/polymer-types";
|
||||
import { haStyle, haStyleDialog } from "../../../../src/resources/styles";
|
||||
import { HomeAssistant } from "../../../../src/types";
|
||||
@@ -75,6 +79,8 @@ interface FolderItem {
|
||||
class HassioSnapshotDialog extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property({ attribute: false }) public supervisor?: Supervisor;
|
||||
|
||||
@internalProperty() private _error?: string;
|
||||
|
||||
@internalProperty() private _onboarding = false;
|
||||
@@ -102,6 +108,7 @@ class HassioSnapshotDialog extends LitElement {
|
||||
|
||||
this._dialogParams = params;
|
||||
this._onboarding = params.onboarding ?? false;
|
||||
this.supervisor = params.supervisor;
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
@@ -298,6 +305,16 @@ class HassioSnapshotDialog extends LitElement {
|
||||
}
|
||||
|
||||
private async _partialRestoreClicked() {
|
||||
if (
|
||||
this.supervisor !== undefined &&
|
||||
this.supervisor.info.state !== "running"
|
||||
) {
|
||||
await showAlertDialog(this, {
|
||||
title: "Could not restore snapshot",
|
||||
text: `Restoring a snapshot is not possible right now because the system is in ${this.supervisor.info.state} state.`,
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (
|
||||
!(await showConfirmationDialog(this, {
|
||||
title: "Are you sure you want partially to restore this snapshot?",
|
||||
@@ -359,6 +376,16 @@ class HassioSnapshotDialog extends LitElement {
|
||||
}
|
||||
|
||||
private async _fullRestoreClicked() {
|
||||
if (
|
||||
this.supervisor !== undefined &&
|
||||
this.supervisor.info.state !== "running"
|
||||
) {
|
||||
await showAlertDialog(this, {
|
||||
title: "Could not restore snapshot",
|
||||
text: `Restoring a snapshot is not possible right now because the system is in ${this.supervisor.info.state} state.`,
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (
|
||||
!(await showConfirmationDialog(this, {
|
||||
title:
|
||||
|
@@ -1,9 +1,11 @@
|
||||
import { fireEvent } from "../../../../src/common/dom/fire_event";
|
||||
import { Supervisor } from "../../../../src/data/supervisor/supervisor";
|
||||
|
||||
export interface HassioSnapshotDialogParams {
|
||||
slug: string;
|
||||
onDelete?: () => void;
|
||||
onboarding?: boolean;
|
||||
supervisor?: Supervisor;
|
||||
}
|
||||
|
||||
export const showHassioSnapshotDialog = (
|
||||
|
@@ -3,7 +3,9 @@ import { atLeastVersion } from "../../src/common/config/version";
|
||||
import { applyThemesOnElement } from "../../src/common/dom/apply_themes_on_element";
|
||||
import { fireEvent } from "../../src/common/dom/fire_event";
|
||||
import { HassioPanelInfo } from "../../src/data/hassio/supervisor";
|
||||
import { supervisorStore } from "../../src/data/supervisor/supervisor";
|
||||
import { makeDialogManager } from "../../src/dialogs/make-dialog-manager";
|
||||
import "../../src/layouts/hass-loading-screen";
|
||||
import { HomeAssistant, Route } from "../../src/types";
|
||||
import "./hassio-router";
|
||||
import { SupervisorBaseElement } from "./supervisor-base-element";
|
||||
@@ -71,8 +73,15 @@ export class HassioMain extends SupervisorBaseElement {
|
||||
|
||||
protected render() {
|
||||
if (!this.supervisor || !this.hass) {
|
||||
return html``;
|
||||
return html`<hass-loading-screen></hass-loading-screen>`;
|
||||
}
|
||||
|
||||
if (
|
||||
Object.keys(supervisorStore).some((store) => !this.supervisor![store])
|
||||
) {
|
||||
return html`<hass-loading-screen></hass-loading-screen>`;
|
||||
}
|
||||
|
||||
return html`
|
||||
<hassio-router
|
||||
.hass=${this.hass}
|
||||
|
128
hassio/src/hassio-my-redirect.ts
Normal file
128
hassio/src/hassio-my-redirect.ts
Normal file
@@ -0,0 +1,128 @@
|
||||
import {
|
||||
customElement,
|
||||
html,
|
||||
internalProperty,
|
||||
LitElement,
|
||||
property,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import { sanitizeUrl } from "@braintree/sanitize-url";
|
||||
import {
|
||||
createSearchParam,
|
||||
extractSearchParamsObject,
|
||||
} from "../../src/common/url/search-params";
|
||||
import "../../src/layouts/hass-error-screen";
|
||||
import {
|
||||
ParamType,
|
||||
Redirect,
|
||||
Redirects,
|
||||
} from "../../src/panels/my/ha-panel-my";
|
||||
import { navigate } from "../../src/common/navigate";
|
||||
import { HomeAssistant, Route } from "../../src/types";
|
||||
|
||||
const REDIRECTS: Redirects = {
|
||||
supervisor_logs: {
|
||||
redirect: "/hassio/system",
|
||||
},
|
||||
supervisor_info: {
|
||||
redirect: "/hassio/system",
|
||||
},
|
||||
supervisor_snapshots: {
|
||||
redirect: "/hassio/snapshots",
|
||||
},
|
||||
supervisor_store: {
|
||||
redirect: "/hassio/store",
|
||||
},
|
||||
supervisor: {
|
||||
redirect: "/hassio/dashboard",
|
||||
},
|
||||
supervisor_addon: {
|
||||
redirect: "/hassio/addon",
|
||||
params: {
|
||||
addon: "string",
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@customElement("hassio-my-redirect")
|
||||
class HassioMyRedirect extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property() public route!: Route;
|
||||
|
||||
@internalProperty() public _error?: TemplateResult | string;
|
||||
|
||||
connectedCallback() {
|
||||
super.connectedCallback();
|
||||
const path = this.route.path.substr(1);
|
||||
const redirect = REDIRECTS[path];
|
||||
|
||||
if (!redirect) {
|
||||
this._error = html`This redirect is not supported by your Home Assistant
|
||||
instance. Check the
|
||||
<a
|
||||
target="_blank"
|
||||
rel="noreferrer noopener"
|
||||
href="https://my.home-assistant.io/faq.html#supported-pages"
|
||||
>My Home Assistant FAQ</a
|
||||
>
|
||||
for the supported redirects and the version they where introduced.`;
|
||||
return;
|
||||
}
|
||||
|
||||
let url: string;
|
||||
try {
|
||||
url = this._createRedirectUrl(redirect);
|
||||
} catch (err) {
|
||||
this._error = "An unknown error occured";
|
||||
return;
|
||||
}
|
||||
|
||||
navigate(this, url, true);
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
if (this._error) {
|
||||
return html`<hass-error-screen
|
||||
.error=${this._error}
|
||||
></hass-error-screen>`;
|
||||
}
|
||||
return html``;
|
||||
}
|
||||
|
||||
private _createRedirectUrl(redirect: Redirect): string {
|
||||
const params = this._createRedirectParams(redirect);
|
||||
return `${redirect.redirect}${params}`;
|
||||
}
|
||||
|
||||
private _createRedirectParams(redirect: Redirect): string {
|
||||
const params = extractSearchParamsObject();
|
||||
if (!redirect.params && !Object.keys(params).length) {
|
||||
return "";
|
||||
}
|
||||
const resultParams = {};
|
||||
Object.entries(redirect.params || {}).forEach(([key, type]) => {
|
||||
if (!params[key] || !this._checkParamType(type, params[key])) {
|
||||
throw Error();
|
||||
}
|
||||
resultParams[key] = params[key];
|
||||
});
|
||||
return `?${createSearchParam(resultParams)}`;
|
||||
}
|
||||
|
||||
private _checkParamType(type: ParamType, value: string) {
|
||||
if (type === "string") {
|
||||
return true;
|
||||
}
|
||||
if (type === "url") {
|
||||
return value && value === sanitizeUrl(value);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"hassio-my-redirect": HassioMyRedirect;
|
||||
}
|
||||
}
|
@@ -41,6 +41,10 @@ class HassioRouter extends HassRouterPage {
|
||||
tag: "hassio-ingress-view",
|
||||
load: () => import("./ingress-view/hassio-ingress-view"),
|
||||
},
|
||||
_my_redirect: {
|
||||
tag: "hassio-my-redirect",
|
||||
load: () => import("./hassio-my-redirect"),
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -49,12 +53,13 @@ class HassioRouter extends HassRouterPage {
|
||||
const route = el.nodeName === "HASSIO-PANEL" ? this.route : this.routeTail;
|
||||
|
||||
el.hass = this.hass;
|
||||
el.supervisor = this.supervisor;
|
||||
el.narrow = this.narrow;
|
||||
el.route = route;
|
||||
|
||||
if (el.localName === "hassio-ingress-view") {
|
||||
el.ingressPanel = this.panel.config && this.panel.config.ingress;
|
||||
} else {
|
||||
el.supervisor = this.supervisor;
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -41,6 +41,7 @@ import {
|
||||
reloadHassioSnapshots,
|
||||
} from "../../../src/data/hassio/snapshot";
|
||||
import { Supervisor } from "../../../src/data/supervisor/supervisor";
|
||||
import { showAlertDialog } from "../../../src/dialogs/generic/show-dialog-box";
|
||||
import "../../../src/layouts/hass-tabs-subpage";
|
||||
import { PolymerChangedEvent } from "../../../src/polymer-types";
|
||||
import { haStyle } from "../../../src/resources/styles";
|
||||
@@ -211,7 +212,13 @@ class HassioSnapshots extends LitElement {
|
||||
: undefined}
|
||||
</div>
|
||||
<div class="card-actions">
|
||||
<ha-progress-button @click=${this._createSnapshot}>
|
||||
<ha-progress-button
|
||||
@click=${this._createSnapshot}
|
||||
title="${this.supervisor.info.state !== "running"
|
||||
? `Creating a snapshot is not possible right now because the system is in ${this.supervisor.info.state} state.`
|
||||
: ""}"
|
||||
.disabled=${this.supervisor.info.state !== "running"}
|
||||
>
|
||||
Create
|
||||
</ha-progress-button>
|
||||
</div>
|
||||
@@ -325,6 +332,12 @@ class HassioSnapshots extends LitElement {
|
||||
}
|
||||
|
||||
private async _createSnapshot(ev: CustomEvent): Promise<void> {
|
||||
if (this.supervisor.info.state !== "running") {
|
||||
await showAlertDialog(this, {
|
||||
title: "Could not create snapshot",
|
||||
text: `Creating a snapshot is not possible right now because the system is in ${this.supervisor.info.state} state.`,
|
||||
});
|
||||
}
|
||||
const button = ev.currentTarget as any;
|
||||
button.progress = true;
|
||||
|
||||
@@ -386,6 +399,7 @@ class HassioSnapshots extends LitElement {
|
||||
private _snapshotClicked(ev) {
|
||||
showHassioSnapshotDialog(this, {
|
||||
slug: ev.currentTarget!.snapshot.slug,
|
||||
supervisor: this.supervisor,
|
||||
onDelete: () => this._updateSnapshots(),
|
||||
});
|
||||
}
|
||||
@@ -395,6 +409,7 @@ class HassioSnapshots extends LitElement {
|
||||
showSnapshot: (slug: string) =>
|
||||
showHassioSnapshotDialog(this, {
|
||||
slug,
|
||||
supervisor: this.supervisor,
|
||||
onDelete: () => this._updateSnapshots(),
|
||||
}),
|
||||
reloadSnapshot: () => this.refreshData(),
|
||||
|
@@ -1,4 +1,16 @@
|
||||
import { LitElement, property, PropertyValues } from "lit-element";
|
||||
import { Collection, UnsubscribeFunc } from "home-assistant-js-websocket";
|
||||
import {
|
||||
internalProperty,
|
||||
LitElement,
|
||||
property,
|
||||
PropertyValues,
|
||||
} from "lit-element";
|
||||
import { atLeastVersion } from "../../src/common/config/version";
|
||||
import { fetchHassioAddonsInfo } from "../../src/data/hassio/addon";
|
||||
import {
|
||||
hassioApiResultExtractor,
|
||||
HassioResponse,
|
||||
} from "../../src/data/hassio/common";
|
||||
import {
|
||||
fetchHassioHassOsInfo,
|
||||
fetchHassioHostInfo,
|
||||
@@ -10,13 +22,23 @@ import {
|
||||
fetchHassioInfo,
|
||||
fetchHassioSupervisorInfo,
|
||||
} from "../../src/data/hassio/supervisor";
|
||||
import { Supervisor } from "../../src/data/supervisor/supervisor";
|
||||
import {
|
||||
getSupervisorEventCollection,
|
||||
subscribeSupervisorEvents,
|
||||
Supervisor,
|
||||
supervisorApiRequest,
|
||||
SupervisorAPIRequestParams,
|
||||
supervisorApiWsRequest,
|
||||
SupervisorObject,
|
||||
supervisorStore,
|
||||
} from "../../src/data/supervisor/supervisor";
|
||||
import { ProvideHassLitMixin } from "../../src/mixins/provide-hass-lit-mixin";
|
||||
import { urlSyncMixin } from "../../src/state/url-sync-mixin";
|
||||
|
||||
declare global {
|
||||
interface HASSDomEvents {
|
||||
"supervisor-update": Partial<Supervisor>;
|
||||
"supervisor-store-refresh": { store: SupervisorObject };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,20 +47,84 @@ export class SupervisorBaseElement extends urlSyncMixin(
|
||||
) {
|
||||
@property({ attribute: false }) public supervisor?: Supervisor;
|
||||
|
||||
@internalProperty() private _unsubs: Record<string, UnsubscribeFunc> = {};
|
||||
|
||||
@internalProperty() private _collections: Record<
|
||||
string,
|
||||
Collection<unknown>
|
||||
> = {};
|
||||
|
||||
public disconnectedCallback() {
|
||||
super.disconnectedCallback();
|
||||
Object.keys(this._unsubs).forEach((unsub) => {
|
||||
this._unsubs[unsub]();
|
||||
});
|
||||
}
|
||||
|
||||
protected _updateSupervisor(obj: Partial<Supervisor>): void {
|
||||
this.supervisor = { ...this.supervisor!, ...obj };
|
||||
this.supervisor = {
|
||||
...this.supervisor!,
|
||||
...obj,
|
||||
callApi: (params) => this._callAPI(params),
|
||||
};
|
||||
}
|
||||
|
||||
protected firstUpdated(changedProps: PropertyValues): void {
|
||||
super.firstUpdated(changedProps);
|
||||
this._initSupervisor();
|
||||
this.addEventListener("supervisor-update", (ev) =>
|
||||
this._updateSupervisor(ev.detail)
|
||||
}
|
||||
|
||||
private async _handleSupervisorStoreRefreshEvent(ev) {
|
||||
const store = ev.detail.store;
|
||||
if (atLeastVersion(this.hass.config.version, 2021, 2, 4)) {
|
||||
this._collections[store].refresh();
|
||||
return;
|
||||
}
|
||||
|
||||
const response = await this.hass.callApi<HassioResponse<any>>(
|
||||
"GET",
|
||||
`hassio${supervisorStore[store]}`
|
||||
);
|
||||
this._updateSupervisor({ [store]: response.data });
|
||||
}
|
||||
|
||||
private async _initSupervisor(): Promise<void> {
|
||||
this.addEventListener(
|
||||
"supervisor-store-refresh",
|
||||
this._handleSupervisorStoreRefreshEvent
|
||||
);
|
||||
|
||||
if (atLeastVersion(this.hass.config.version, 2021, 2, 4)) {
|
||||
Object.keys(supervisorStore).forEach((store) => {
|
||||
this._unsubs[store] = subscribeSupervisorEvents(
|
||||
this.hass,
|
||||
(data) => this._updateSupervisor({ [store]: data }),
|
||||
store,
|
||||
supervisorStore[store]
|
||||
);
|
||||
if (this._collections[store]) {
|
||||
this._collections[store].refresh();
|
||||
} else {
|
||||
this._collections[store] = getSupervisorEventCollection(
|
||||
this.hass.connection,
|
||||
store,
|
||||
supervisorStore[store]
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
if (this.supervisor === undefined) {
|
||||
Object.keys(this._collections).forEach((collection) =>
|
||||
this._updateSupervisor({
|
||||
[collection]: this._collections[collection].state,
|
||||
})
|
||||
);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const [
|
||||
addon,
|
||||
supervisor,
|
||||
host,
|
||||
core,
|
||||
@@ -47,6 +133,7 @@ export class SupervisorBaseElement extends urlSyncMixin(
|
||||
network,
|
||||
resolution,
|
||||
] = await Promise.all([
|
||||
fetchHassioAddonsInfo(this.hass),
|
||||
fetchHassioSupervisorInfo(this.hass),
|
||||
fetchHassioHostInfo(this.hass),
|
||||
fetchHassioHomeAssistantInfo(this.hass),
|
||||
@@ -57,6 +144,7 @@ export class SupervisorBaseElement extends urlSyncMixin(
|
||||
]);
|
||||
|
||||
this.supervisor = {
|
||||
addon,
|
||||
supervisor,
|
||||
host,
|
||||
core,
|
||||
@@ -64,6 +152,48 @@ export class SupervisorBaseElement extends urlSyncMixin(
|
||||
os,
|
||||
network,
|
||||
resolution,
|
||||
callApi: (params) => this._callAPI(params),
|
||||
};
|
||||
|
||||
this.addEventListener("supervisor-update", (ev) =>
|
||||
this._updateSupervisor(ev.detail)
|
||||
);
|
||||
}
|
||||
|
||||
private async _callAPI<T>(params: SupervisorAPIRequestParams): Promise<T> {
|
||||
const hasHass = this.hass !== undefined;
|
||||
const canUseWS =
|
||||
!params.rest &&
|
||||
hasHass &&
|
||||
atLeastVersion(this.hass.config.version, 2021, 2, 4);
|
||||
|
||||
if (canUseWS) {
|
||||
const connection = hasHass ? this.hass.connection : params.connection;
|
||||
if (connection === undefined) {
|
||||
throw Error(`No connection found, aborting API call - ${params}`);
|
||||
}
|
||||
const requestParams: supervisorApiRequest = {
|
||||
...params,
|
||||
};
|
||||
delete requestParams.rest;
|
||||
delete requestParams.connection;
|
||||
return await supervisorApiWsRequest<T>(connection, requestParams);
|
||||
} else {
|
||||
const method =
|
||||
params.method === "post"
|
||||
? "POST"
|
||||
: params.method === "put"
|
||||
? "PUT"
|
||||
: params.method === "delete"
|
||||
? "DELETE"
|
||||
: "GET";
|
||||
return hassioApiResultExtractor<T>(
|
||||
await this.hass.callApi<HassioResponse<T>>(
|
||||
method,
|
||||
`hassio${params.endpoint}`,
|
||||
params.data
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -10,6 +10,7 @@ import {
|
||||
property,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import { fireEvent } from "../../../src/common/dom/fire_event";
|
||||
import "../../../src/components/buttons/ha-progress-button";
|
||||
import "../../../src/components/ha-button-menu";
|
||||
import "../../../src/components/ha-card";
|
||||
@@ -166,6 +167,7 @@ class HassioCoreInfo extends LitElement {
|
||||
|
||||
try {
|
||||
await updateCore(this.hass);
|
||||
fireEvent(this, "supervisor-store-refresh", { store: "core" });
|
||||
} catch (err) {
|
||||
showAlertDialog(this, {
|
||||
title: "Failed to update Home Assistant Core",
|
||||
|
@@ -13,6 +13,7 @@ import {
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { atLeastVersion } from "../../../src/common/config/version";
|
||||
import { fireEvent } from "../../../src/common/dom/fire_event";
|
||||
import "../../../src/components/buttons/ha-progress-button";
|
||||
import "../../../src/components/ha-button-menu";
|
||||
@@ -26,7 +27,6 @@ import { fetchHassioHardwareInfo } from "../../../src/data/hassio/hardware";
|
||||
import {
|
||||
changeHostOptions,
|
||||
configSyncOS,
|
||||
fetchHassioHostInfo,
|
||||
rebootHost,
|
||||
shutdownHost,
|
||||
updateOS,
|
||||
@@ -150,6 +150,18 @@ class HassioHostInfo extends LitElement {
|
||||
: ""}
|
||||
</div>
|
||||
<div>
|
||||
${this.supervisor.host.disk_life_time !== "" &&
|
||||
this.supervisor.host.disk_life_time >= 10
|
||||
? html` <ha-settings-row>
|
||||
<span slot="heading">
|
||||
eMMC Lifetime Used
|
||||
</span>
|
||||
<span slot="description">
|
||||
${this.supervisor.host.disk_life_time - 10}% -
|
||||
${this.supervisor.host.disk_life_time}%
|
||||
</span>
|
||||
</ha-settings-row>`
|
||||
: ""}
|
||||
${metrics.map(
|
||||
(metric) =>
|
||||
html`
|
||||
@@ -328,6 +340,7 @@ class HassioHostInfo extends LitElement {
|
||||
|
||||
try {
|
||||
await updateOS(this.hass);
|
||||
fireEvent(this, "supervisor-store-refresh", { store: "os" });
|
||||
} catch (err) {
|
||||
showAlertDialog(this, {
|
||||
title: "Failed to update",
|
||||
@@ -356,8 +369,7 @@ class HassioHostInfo extends LitElement {
|
||||
if (hostname && hostname !== curHostname) {
|
||||
try {
|
||||
await changeHostOptions(this.hass, { hostname });
|
||||
const host = await fetchHassioHostInfo(this.hass);
|
||||
fireEvent(this, "supervisor-update", { host });
|
||||
fireEvent(this, "supervisor-store-refresh", { store: "host" });
|
||||
} catch (err) {
|
||||
showAlertDialog(this, {
|
||||
title: "Setting hostname failed",
|
||||
@@ -370,8 +382,7 @@ class HassioHostInfo extends LitElement {
|
||||
private async _importFromUSB(): Promise<void> {
|
||||
try {
|
||||
await configSyncOS(this.hass);
|
||||
const host = await fetchHassioHostInfo(this.hass);
|
||||
fireEvent(this, "supervisor-update", { host });
|
||||
fireEvent(this, "supervisor-store-refresh", { store: "host" });
|
||||
} catch (err) {
|
||||
showAlertDialog(this, {
|
||||
title: "Failed to import from USB",
|
||||
@@ -381,8 +392,12 @@ class HassioHostInfo extends LitElement {
|
||||
}
|
||||
|
||||
private async _loadData(): Promise<void> {
|
||||
const network = await fetchNetworkInfo(this.hass);
|
||||
fireEvent(this, "supervisor-update", { network });
|
||||
if (atLeastVersion(this.hass.config.version, 2021, 2, 4)) {
|
||||
fireEvent(this, "supervisor-store-refresh", { store: "network" });
|
||||
} else {
|
||||
const network = await fetchNetworkInfo(this.hass);
|
||||
fireEvent(this, "supervisor-update", { network });
|
||||
}
|
||||
}
|
||||
|
||||
static get styles(): CSSResult[] {
|
||||
|
@@ -19,7 +19,6 @@ import {
|
||||
HassioStats,
|
||||
} from "../../../src/data/hassio/common";
|
||||
import {
|
||||
fetchHassioSupervisorInfo,
|
||||
reloadSupervisor,
|
||||
restartSupervisor,
|
||||
setSupervisorOption,
|
||||
@@ -318,8 +317,7 @@ class HassioSupervisorInfo extends LitElement {
|
||||
|
||||
private async _reloadSupervisor(): Promise<void> {
|
||||
await reloadSupervisor(this.hass);
|
||||
const supervisor = await fetchHassioSupervisorInfo(this.hass);
|
||||
fireEvent(this, "supervisor-update", { supervisor });
|
||||
fireEvent(this, "supervisor-store-refresh", { store: "supervisor" });
|
||||
}
|
||||
|
||||
private async _supervisorRestart(ev: CustomEvent): Promise<void> {
|
||||
@@ -368,6 +366,7 @@ class HassioSupervisorInfo extends LitElement {
|
||||
|
||||
try {
|
||||
await updateSupervisor(this.hass);
|
||||
fireEvent(this, "supervisor-store-refresh", { store: "supervisor" });
|
||||
} catch (err) {
|
||||
showAlertDialog(this, {
|
||||
title: "Failed to update the supervisor",
|
||||
|
7
hassio/src/util/addon.ts
Normal file
7
hassio/src/util/addon.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import memoizeOne from "memoize-one";
|
||||
import { SupervisorArch } from "../../../src/data/supervisor/supervisor";
|
||||
|
||||
export const addonArchIsSupported = memoizeOne(
|
||||
(supported_archs: SupervisorArch[], addon_archs: SupervisorArch[]) =>
|
||||
addon_archs.some((arch) => supported_archs.includes(arch))
|
||||
);
|
@@ -22,6 +22,7 @@
|
||||
"author": "Paulus Schoutsen <Paulus@PaulusSchoutsen.nl> (http://paulusschoutsen.nl)",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@braintree/sanitize-url": "^5.0.0",
|
||||
"@formatjs/intl-getcanonicallocales": "^1.4.6",
|
||||
"@formatjs/intl-pluralrules": "^3.4.10",
|
||||
"@fullcalendar/common": "5.1.0",
|
||||
@@ -100,7 +101,7 @@
|
||||
"fuse.js": "^6.0.0",
|
||||
"google-timezones-json": "^1.0.2",
|
||||
"hls.js": "^0.13.2",
|
||||
"home-assistant-js-websocket": "^5.4.1",
|
||||
"home-assistant-js-websocket": "^5.8.1",
|
||||
"idb-keyval": "^3.2.0",
|
||||
"intl-messageformat": "^8.3.9",
|
||||
"js-yaml": "^3.13.1",
|
||||
@@ -109,7 +110,7 @@
|
||||
"lit-element": "^2.4.0",
|
||||
"lit-html": "^1.3.0",
|
||||
"lit-virtualizer": "^0.4.2",
|
||||
"marked": "^1.1.1",
|
||||
"marked": "2.0.0",
|
||||
"mdn-polyfills": "^5.16.0",
|
||||
"memoize-one": "^5.0.2",
|
||||
"node-vibrant": "3.2.1-alpha.1",
|
||||
@@ -160,7 +161,7 @@
|
||||
"@types/js-yaml": "^3.12.1",
|
||||
"@types/leaflet": "^1.4.3",
|
||||
"@types/leaflet-draw": "^1.0.1",
|
||||
"@types/marked": "^1.1.0",
|
||||
"@types/marked": "^1.2.2",
|
||||
"@types/memoize-one": "4.1.0",
|
||||
"@types/mocha": "^7.0.2",
|
||||
"@types/resize-observer-browser": "^0.1.3",
|
||||
|
2
setup.py
2
setup.py
@@ -2,7 +2,7 @@ from setuptools import setup, find_packages
|
||||
|
||||
setup(
|
||||
name="home-assistant-frontend",
|
||||
version="20210127.1",
|
||||
version="20210208.0",
|
||||
description="The Home Assistant frontend",
|
||||
url="https://github.com/home-assistant/home-assistant-polymer",
|
||||
author="The Home Assistant Authors",
|
||||
|
@@ -1,11 +1,15 @@
|
||||
export const atLeastVersion = (
|
||||
version: string,
|
||||
major: number,
|
||||
minor: number
|
||||
minor: number,
|
||||
patch?: number
|
||||
): boolean => {
|
||||
const [haMajor, haMinor] = version.split(".", 2);
|
||||
const [haMajor, haMinor, haPatch] = version.split(".", 3);
|
||||
return (
|
||||
Number(haMajor) > major ||
|
||||
(Number(haMajor) === major && Number(haMinor) >= minor)
|
||||
(Number(haMajor) === major && Number(haMinor) >= minor) ||
|
||||
(patch !== undefined &&
|
||||
Number(haMajor) === major && Number(haMinor) === minor &&
|
||||
Number(haPatch) >= patch)
|
||||
);
|
||||
};
|
||||
|
@@ -6,3 +6,16 @@ export const extractSearchParamsObject = (): Record<string, string> => {
|
||||
}
|
||||
return query;
|
||||
};
|
||||
|
||||
export const extractSearchParam = (param: string): string | null => {
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
return urlParams.get(param);
|
||||
};
|
||||
|
||||
export const createSearchParam = (params: Record<string, string>): string => {
|
||||
const urlParams = new URLSearchParams();
|
||||
Object.entries(params).forEach(([key, value]) => {
|
||||
urlParams.append(key, value);
|
||||
});
|
||||
return urlParams.toString();
|
||||
};
|
||||
|
@@ -1,10 +1,5 @@
|
||||
import "@material/mwc-icon-button/mwc-icon-button";
|
||||
import { mdiClose, mdiMenuDown, mdiMenuUp } from "@mdi/js";
|
||||
import "@polymer/paper-input/paper-input";
|
||||
import "@polymer/paper-item/paper-item";
|
||||
import "@polymer/paper-item/paper-item-body";
|
||||
import "@polymer/paper-listbox/paper-listbox";
|
||||
import "@vaadin/vaadin-combo-box/theme/material/vaadin-combo-box-light";
|
||||
import { UnsubscribeFunc } from "home-assistant-js-websocket";
|
||||
import {
|
||||
css,
|
||||
@@ -38,7 +33,7 @@ import {
|
||||
import { SubscribeMixin } from "../../mixins/subscribe-mixin";
|
||||
import { PolymerChangedEvent } from "../../polymer-types";
|
||||
import { HomeAssistant } from "../../types";
|
||||
import "../ha-svg-icon";
|
||||
import { HaComboBox } from "../ha-combo-box";
|
||||
|
||||
interface Device {
|
||||
name: string;
|
||||
@@ -115,7 +110,7 @@ export class HaDevicePicker extends SubscribeMixin(LitElement) {
|
||||
@property({ type: Boolean })
|
||||
private _opened?: boolean;
|
||||
|
||||
@query("vaadin-combo-box-light", true) private _comboBox!: HTMLElement;
|
||||
@query("ha-combo-box", true) private _comboBox!: HaComboBox;
|
||||
|
||||
private _init = false;
|
||||
|
||||
@@ -244,15 +239,11 @@ export class HaDevicePicker extends SubscribeMixin(LitElement) {
|
||||
);
|
||||
|
||||
public open() {
|
||||
this.updateComplete.then(() => {
|
||||
(this.shadowRoot?.querySelector("vaadin-combo-box-light") as any)?.open();
|
||||
});
|
||||
this._comboBox?.open();
|
||||
}
|
||||
|
||||
public focus() {
|
||||
this.updateComplete.then(() => {
|
||||
this.shadowRoot?.querySelector("paper-input")?.focus();
|
||||
});
|
||||
this._comboBox?.focus();
|
||||
}
|
||||
|
||||
public hassSubscribe(): UnsubscribeFunc[] {
|
||||
@@ -292,70 +283,28 @@ export class HaDevicePicker extends SubscribeMixin(LitElement) {
|
||||
return html``;
|
||||
}
|
||||
return html`
|
||||
<vaadin-combo-box-light
|
||||
<ha-combo-box
|
||||
.hass=${this.hass}
|
||||
.label=${this.label === undefined && this.hass
|
||||
? this.hass.localize("ui.components.device-picker.device")
|
||||
: this.label}
|
||||
.value=${this._value}
|
||||
.renderer=${rowRenderer}
|
||||
item-value-path="id"
|
||||
item-id-path="id"
|
||||
item-label-path="name"
|
||||
.value=${this._value}
|
||||
.renderer=${rowRenderer}
|
||||
@opened-changed=${this._openedChanged}
|
||||
@value-changed=${this._deviceChanged}
|
||||
>
|
||||
<paper-input
|
||||
.label=${this.label === undefined && this.hass
|
||||
? this.hass.localize("ui.components.device-picker.device")
|
||||
: this.label}
|
||||
class="input"
|
||||
autocapitalize="none"
|
||||
autocomplete="off"
|
||||
autocorrect="off"
|
||||
spellcheck="false"
|
||||
>
|
||||
${this.value
|
||||
? html`
|
||||
<mwc-icon-button
|
||||
.label=${this.hass.localize(
|
||||
"ui.components.device-picker.clear"
|
||||
)}
|
||||
slot="suffix"
|
||||
class="clear-button"
|
||||
@click=${this._clearValue}
|
||||
>
|
||||
<ha-svg-icon .path=${mdiClose}></ha-svg-icon>
|
||||
</mwc-icon-button>
|
||||
`
|
||||
: ""}
|
||||
|
||||
<mwc-icon-button
|
||||
.label=${this.hass.localize(
|
||||
"ui.components.device-picker.show_devices"
|
||||
)}
|
||||
slot="suffix"
|
||||
class="toggle-button"
|
||||
>
|
||||
<ha-svg-icon
|
||||
.path=${this._opened ? mdiMenuUp : mdiMenuDown}
|
||||
></ha-svg-icon>
|
||||
</mwc-icon-button>
|
||||
</paper-input>
|
||||
</vaadin-combo-box-light>
|
||||
></ha-combo-box>
|
||||
`;
|
||||
}
|
||||
|
||||
private _clearValue(ev: Event) {
|
||||
ev.stopPropagation();
|
||||
this._setValue("");
|
||||
}
|
||||
|
||||
private get _value() {
|
||||
return this.value || "";
|
||||
}
|
||||
|
||||
private _openedChanged(ev: PolymerChangedEvent<boolean>) {
|
||||
this._opened = ev.detail.value;
|
||||
}
|
||||
|
||||
private _deviceChanged(ev: PolymerChangedEvent<string>) {
|
||||
ev.stopPropagation();
|
||||
const newValue = ev.detail.value;
|
||||
|
||||
if (newValue !== this._value) {
|
||||
@@ -363,6 +312,10 @@ export class HaDevicePicker extends SubscribeMixin(LitElement) {
|
||||
}
|
||||
}
|
||||
|
||||
private _openedChanged(ev: PolymerChangedEvent<boolean>) {
|
||||
this._opened = ev.detail.value;
|
||||
}
|
||||
|
||||
private _setValue(value: string) {
|
||||
this.value = value;
|
||||
setTimeout(() => {
|
||||
|
@@ -1,116 +0,0 @@
|
||||
import "@polymer/paper-input/paper-input";
|
||||
import "@polymer/paper-item/paper-item";
|
||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
||||
/* eslint-plugin-disable lit */
|
||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||
import "@vaadin/vaadin-combo-box/theme/material/vaadin-combo-box-light";
|
||||
import { EventsMixin } from "../mixins/events-mixin";
|
||||
import "./ha-icon-button";
|
||||
|
||||
class HaComboBox extends EventsMixin(PolymerElement) {
|
||||
static get template() {
|
||||
return html`
|
||||
<style>
|
||||
paper-input > ha-icon-button {
|
||||
--mdc-icon-button-size: 24px;
|
||||
padding: 2px;
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
[hidden] {
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
<vaadin-combo-box-light
|
||||
items="[[_items]]"
|
||||
item-value-path="[[itemValuePath]]"
|
||||
item-label-path="[[itemLabelPath]]"
|
||||
value="{{value}}"
|
||||
opened="{{opened}}"
|
||||
allow-custom-value="[[allowCustomValue]]"
|
||||
on-change="_fireChanged"
|
||||
>
|
||||
<paper-input
|
||||
autofocus="[[autofocus]]"
|
||||
label="[[label]]"
|
||||
class="input"
|
||||
value="[[value]]"
|
||||
>
|
||||
<ha-icon-button
|
||||
slot="suffix"
|
||||
class="clear-button"
|
||||
icon="hass:close"
|
||||
hidden$="[[!value]]"
|
||||
>Clear</ha-icon-button
|
||||
>
|
||||
<ha-icon-button
|
||||
slot="suffix"
|
||||
class="toggle-button"
|
||||
icon="[[_computeToggleIcon(opened)]]"
|
||||
hidden$="[[!items.length]]"
|
||||
>Toggle</ha-icon-button
|
||||
>
|
||||
</paper-input>
|
||||
<template>
|
||||
<style>
|
||||
paper-item {
|
||||
margin: -5px -10px;
|
||||
padding: 0;
|
||||
}
|
||||
</style>
|
||||
<paper-item>[[_computeItemLabel(item, itemLabelPath)]]</paper-item>
|
||||
</template>
|
||||
</vaadin-combo-box-light>
|
||||
`;
|
||||
}
|
||||
|
||||
static get properties() {
|
||||
return {
|
||||
allowCustomValue: Boolean,
|
||||
items: {
|
||||
type: Object,
|
||||
observer: "_itemsChanged",
|
||||
},
|
||||
_items: Object,
|
||||
itemLabelPath: String,
|
||||
itemValuePath: String,
|
||||
autofocus: Boolean,
|
||||
label: String,
|
||||
opened: {
|
||||
type: Boolean,
|
||||
value: false,
|
||||
observer: "_openedChanged",
|
||||
},
|
||||
value: {
|
||||
type: String,
|
||||
notify: true,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
_openedChanged(newVal) {
|
||||
if (!newVal) {
|
||||
this._items = this.items;
|
||||
}
|
||||
}
|
||||
|
||||
_itemsChanged(newVal) {
|
||||
if (!this.opened) {
|
||||
this._items = newVal;
|
||||
}
|
||||
}
|
||||
|
||||
_computeToggleIcon(opened) {
|
||||
return opened ? "hass:menu-up" : "hass:menu-down";
|
||||
}
|
||||
|
||||
_computeItemLabel(item, itemLabelPath) {
|
||||
return itemLabelPath ? item[itemLabelPath] : item;
|
||||
}
|
||||
|
||||
_fireChanged(ev) {
|
||||
ev.stopPropagation();
|
||||
this.fire("change");
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("ha-combo-box", HaComboBox);
|
177
src/components/ha-combo-box.ts
Normal file
177
src/components/ha-combo-box.ts
Normal file
@@ -0,0 +1,177 @@
|
||||
import "@material/mwc-icon-button/mwc-icon-button";
|
||||
import { mdiClose, mdiMenuDown, mdiMenuUp } from "@mdi/js";
|
||||
import "@polymer/paper-input/paper-input";
|
||||
import "@polymer/paper-item/paper-item";
|
||||
import "@polymer/paper-item/paper-item-body";
|
||||
import "@polymer/paper-listbox/paper-listbox";
|
||||
import "@vaadin/vaadin-combo-box/theme/material/vaadin-combo-box-light";
|
||||
import {
|
||||
css,
|
||||
CSSResult,
|
||||
customElement,
|
||||
html,
|
||||
LitElement,
|
||||
property,
|
||||
query,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import { fireEvent } from "../common/dom/fire_event";
|
||||
import { PolymerChangedEvent } from "../polymer-types";
|
||||
import { HomeAssistant } from "../types";
|
||||
import "./ha-svg-icon";
|
||||
|
||||
const defaultRowRenderer = (
|
||||
root: HTMLElement,
|
||||
_owner,
|
||||
model: { item: any }
|
||||
) => {
|
||||
if (!root.firstElementChild) {
|
||||
root.innerHTML = `
|
||||
<style>
|
||||
paper-item {
|
||||
margin: -5px -10px;
|
||||
padding: 0;
|
||||
}
|
||||
</style>
|
||||
<paper-item></paper-item>
|
||||
`;
|
||||
}
|
||||
|
||||
root.querySelector("paper-item")!.textContent = model.item;
|
||||
};
|
||||
|
||||
@customElement("ha-combo-box")
|
||||
export class HaComboBox extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property() public label?: string;
|
||||
|
||||
@property() public value?: string;
|
||||
|
||||
@property() public items?: [];
|
||||
|
||||
@property() public filteredItems?: [];
|
||||
|
||||
@property({ attribute: "allow-custom-value", type: Boolean })
|
||||
public allowCustomValue?: boolean;
|
||||
|
||||
@property({ attribute: "item-value-path" }) public itemValuePath?: string;
|
||||
|
||||
@property({ attribute: "item-label-path" }) public itemLabelPath?: string;
|
||||
|
||||
@property({ attribute: "item-id-path" }) public itemIdPath?: string;
|
||||
|
||||
@property() public renderer?: (
|
||||
root: HTMLElement,
|
||||
owner: HTMLElement,
|
||||
model: { item: any }
|
||||
) => void;
|
||||
|
||||
@property({ type: Boolean })
|
||||
private _opened?: boolean;
|
||||
|
||||
@query("vaadin-combo-box-light", true) private _comboBox!: HTMLElement;
|
||||
|
||||
public open() {
|
||||
this.updateComplete.then(() => {
|
||||
(this._comboBox as any)?.open();
|
||||
});
|
||||
}
|
||||
|
||||
public focus() {
|
||||
this.updateComplete.then(() => {
|
||||
this.shadowRoot?.querySelector("paper-input")?.focus();
|
||||
});
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return html`
|
||||
<vaadin-combo-box-light
|
||||
.itemValuePath=${this.itemValuePath}
|
||||
.itemIdPath=${this.itemIdPath}
|
||||
.itemLabelPath=${this.itemLabelPath}
|
||||
.value=${this.value}
|
||||
.items=${this.items}
|
||||
.filteredItems=${this.filteredItems}
|
||||
.renderer=${this.renderer || defaultRowRenderer}
|
||||
.allowCustomValue=${this.allowCustomValue}
|
||||
@opened-changed=${this._openedChanged}
|
||||
@filter-changed=${this._filterChanged}
|
||||
@value-changed=${this._valueChanged}
|
||||
>
|
||||
<paper-input
|
||||
.label=${this.label}
|
||||
class="input"
|
||||
autocapitalize="none"
|
||||
autocomplete="off"
|
||||
autocorrect="off"
|
||||
spellcheck="false"
|
||||
>
|
||||
${this.value
|
||||
? html`
|
||||
<mwc-icon-button
|
||||
.label=${this.hass.localize("ui.components.combo-box.clear")}
|
||||
slot="suffix"
|
||||
class="clear-button"
|
||||
@click=${this._clearValue}
|
||||
>
|
||||
<ha-svg-icon .path=${mdiClose}></ha-svg-icon>
|
||||
</mwc-icon-button>
|
||||
`
|
||||
: ""}
|
||||
|
||||
<mwc-icon-button
|
||||
.label=${this.hass.localize("ui.components.combo-box.show")}
|
||||
slot="suffix"
|
||||
class="toggle-button"
|
||||
>
|
||||
<ha-svg-icon
|
||||
.path=${this._opened ? mdiMenuUp : mdiMenuDown}
|
||||
></ha-svg-icon>
|
||||
</mwc-icon-button>
|
||||
</paper-input>
|
||||
</vaadin-combo-box-light>
|
||||
`;
|
||||
}
|
||||
|
||||
private _clearValue(ev: Event) {
|
||||
ev.stopPropagation();
|
||||
fireEvent(this, "value-changed", { value: undefined });
|
||||
}
|
||||
|
||||
private _openedChanged(ev: PolymerChangedEvent<boolean>) {
|
||||
this._opened = ev.detail.value;
|
||||
// @ts-ignore
|
||||
fireEvent(this, ev.type, ev.detail);
|
||||
}
|
||||
|
||||
private _filterChanged(ev: PolymerChangedEvent<boolean>) {
|
||||
// @ts-ignore
|
||||
fireEvent(this, ev.type, ev.detail);
|
||||
}
|
||||
|
||||
private _valueChanged(ev: PolymerChangedEvent<string>) {
|
||||
ev.stopPropagation();
|
||||
const newValue = ev.detail.value;
|
||||
|
||||
if (newValue !== this.value) {
|
||||
fireEvent(this, "value-changed", { value: newValue });
|
||||
}
|
||||
}
|
||||
|
||||
static get styles(): CSSResult {
|
||||
return css`
|
||||
paper-input > mwc-icon-button {
|
||||
--mdc-icon-button-size: 24px;
|
||||
padding: 2px;
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-combo-box": HaComboBox;
|
||||
}
|
||||
}
|
@@ -1,6 +1,9 @@
|
||||
import { mdiEye, mdiEyeOff } from "@mdi/js";
|
||||
import "@polymer/paper-input/paper-input";
|
||||
import type { PaperInputElement } from "@polymer/paper-input/paper-input";
|
||||
import {
|
||||
css,
|
||||
CSSResult,
|
||||
customElement,
|
||||
html,
|
||||
internalProperty,
|
||||
@@ -10,12 +13,13 @@ import {
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
import "../ha-icon-button";
|
||||
import "../ha-svg-icon";
|
||||
import type {
|
||||
HaFormElement,
|
||||
HaFormStringData,
|
||||
HaFormStringSchema,
|
||||
} from "./ha-form";
|
||||
import "@material/mwc-icon-button/mwc-icon-button";
|
||||
|
||||
@customElement("ha-form-string")
|
||||
export class HaFormString extends LitElement implements HaFormElement {
|
||||
@@ -48,16 +52,17 @@ export class HaFormString extends LitElement implements HaFormElement {
|
||||
.autoValidate=${this.schema.required}
|
||||
@value-changed=${this._valueChanged}
|
||||
>
|
||||
<ha-icon-button
|
||||
<mwc-icon-button
|
||||
toggles
|
||||
slot="suffix"
|
||||
.icon=${this._unmaskedPassword ? "hass:eye-off" : "hass:eye"}
|
||||
id="iconButton"
|
||||
title="Click to toggle between masked and clear password"
|
||||
@click=${this._toggleUnmaskedPassword}
|
||||
tabindex="-1"
|
||||
>
|
||||
</ha-icon-button>
|
||||
><ha-svg-icon
|
||||
.path=${this._unmaskedPassword ? mdiEyeOff : mdiEye}
|
||||
></ha-svg-icon>
|
||||
</mwc-icon-button>
|
||||
</paper-input>
|
||||
`
|
||||
: html`
|
||||
@@ -98,6 +103,15 @@ export class HaFormString extends LitElement implements HaFormElement {
|
||||
}
|
||||
return "text";
|
||||
}
|
||||
|
||||
static get styles(): CSSResult {
|
||||
return css`
|
||||
mwc-icon-button {
|
||||
--mdc-icon-button-size: 24px;
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
|
@@ -202,9 +202,8 @@ export class HaForm extends LitElement implements HaFormElement {
|
||||
ev.stopPropagation();
|
||||
const schema = (ev.target as HaFormElement).schema as HaFormSchema;
|
||||
const data = this.data as HaFormDataContainer;
|
||||
data[schema.name] = ev.detail.value;
|
||||
fireEvent(this, "value-changed", {
|
||||
value: { ...data },
|
||||
value: { ...data, [schema.name]: ev.detail.value },
|
||||
});
|
||||
}
|
||||
|
||||
|
@@ -46,7 +46,7 @@ export class HaNumberSelector extends LitElement {
|
||||
class=${classMap({ single: this.selector.number.mode === "box" })}
|
||||
.min=${this.selector.number.min}
|
||||
.max=${this.selector.number.max}
|
||||
.value=${this._value}
|
||||
.value=${this.value}
|
||||
.step=${this.selector.number.step}
|
||||
type="number"
|
||||
auto-validate
|
||||
@@ -65,16 +65,21 @@ export class HaNumberSelector extends LitElement {
|
||||
}
|
||||
|
||||
private _handleInputChange(ev) {
|
||||
const value = ev.detail.value;
|
||||
if (this._value === value) {
|
||||
ev.stopPropagation();
|
||||
const value =
|
||||
ev.detail.value === "" || isNaN(ev.detail.value)
|
||||
? undefined
|
||||
: Number(ev.detail.value);
|
||||
if (this.value === value) {
|
||||
return;
|
||||
}
|
||||
fireEvent(this, "value-changed", { value });
|
||||
}
|
||||
|
||||
private _handleSliderChange(ev) {
|
||||
const value = ev.target.value;
|
||||
if (this._value === value) {
|
||||
ev.stopPropagation();
|
||||
const value = Number(ev.target.value);
|
||||
if (this.value === value) {
|
||||
return;
|
||||
}
|
||||
fireEvent(this, "value-changed", { value });
|
||||
|
73
src/components/ha-selector/ha-selector-select.ts
Normal file
73
src/components/ha-selector/ha-selector-select.ts
Normal file
@@ -0,0 +1,73 @@
|
||||
import {
|
||||
css,
|
||||
CSSResult,
|
||||
customElement,
|
||||
html,
|
||||
LitElement,
|
||||
property,
|
||||
} from "lit-element";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
import { HomeAssistant } from "../../types";
|
||||
import { SelectSelector } from "../../data/selector";
|
||||
import "../ha-paper-dropdown-menu";
|
||||
|
||||
@customElement("ha-selector-select")
|
||||
export class HaSelectSelector extends LitElement {
|
||||
@property() public hass!: HomeAssistant;
|
||||
|
||||
@property() public selector!: SelectSelector;
|
||||
|
||||
@property() public value?: string;
|
||||
|
||||
@property() public label?: string;
|
||||
|
||||
protected render() {
|
||||
return html`<ha-paper-dropdown-menu .label=${this.label}>
|
||||
<paper-listbox
|
||||
slot="dropdown-content"
|
||||
attr-for-selected="item-value"
|
||||
.selected=${this.value}
|
||||
@selected-item-changed=${this._valueChanged}
|
||||
>
|
||||
${this.selector.select.options.map(
|
||||
(item: string) => html`
|
||||
<paper-item .itemValue=${item}>
|
||||
${item}
|
||||
</paper-item>
|
||||
`
|
||||
)}
|
||||
</paper-listbox>
|
||||
</ha-paper-dropdown-menu>`;
|
||||
}
|
||||
|
||||
private _valueChanged(ev) {
|
||||
if (!ev.detail.value) {
|
||||
return;
|
||||
}
|
||||
fireEvent(this, "value-changed", {
|
||||
value: ev.detail.value.itemValue,
|
||||
});
|
||||
}
|
||||
|
||||
static get styles(): CSSResult {
|
||||
return css`
|
||||
ha-paper-dropdown-menu {
|
||||
width: 100%;
|
||||
min-width: 200px;
|
||||
display: block;
|
||||
}
|
||||
paper-listbox {
|
||||
min-width: 200px;
|
||||
}
|
||||
paper-item {
|
||||
cursor: pointer;
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-selector-select": HaSelectSelector;
|
||||
}
|
||||
}
|
@@ -3,7 +3,11 @@ import "@material/mwc-list/mwc-list-item";
|
||||
import "@material/mwc-tab-bar/mwc-tab-bar";
|
||||
import "@material/mwc-tab/mwc-tab";
|
||||
import "@polymer/paper-input/paper-input";
|
||||
import { HassEntity, UnsubscribeFunc } from "home-assistant-js-websocket";
|
||||
import {
|
||||
HassEntity,
|
||||
HassServiceTarget,
|
||||
UnsubscribeFunc,
|
||||
} from "home-assistant-js-websocket";
|
||||
import {
|
||||
css,
|
||||
CSSResult,
|
||||
@@ -20,7 +24,6 @@ import {
|
||||
subscribeEntityRegistry,
|
||||
} from "../../data/entity_registry";
|
||||
import { TargetSelector } from "../../data/selector";
|
||||
import { Target } from "../../data/target";
|
||||
import { SubscribeMixin } from "../../mixins/subscribe-mixin";
|
||||
import { HomeAssistant } from "../../types";
|
||||
import "../ha-target-picker";
|
||||
@@ -31,7 +34,7 @@ export class HaTargetSelector extends SubscribeMixin(LitElement) {
|
||||
|
||||
@property() public selector!: TargetSelector;
|
||||
|
||||
@property() public value?: Target;
|
||||
@property() public value?: HassServiceTarget;
|
||||
|
||||
@property() public label?: string;
|
||||
|
||||
@@ -59,7 +62,8 @@ export class HaTargetSelector extends SubscribeMixin(LitElement) {
|
||||
const oldSelector = changedProperties.get("selector");
|
||||
if (
|
||||
oldSelector !== this.selector &&
|
||||
this.selector.target.device?.integration
|
||||
(this.selector.target.device?.integration ||
|
||||
this.selector.target.entity?.integration)
|
||||
) {
|
||||
this._loadConfigEntries();
|
||||
}
|
||||
@@ -84,11 +88,15 @@ export class HaTargetSelector extends SubscribeMixin(LitElement) {
|
||||
}
|
||||
|
||||
private _filterEntities(entity: HassEntity): boolean {
|
||||
if (this.selector.target.entity?.integration) {
|
||||
if (
|
||||
this.selector.target.entity?.integration ||
|
||||
this.selector.target.device?.integration
|
||||
) {
|
||||
if (
|
||||
!this._entityPlaformLookup ||
|
||||
this._entityPlaformLookup[entity.entity_id] !==
|
||||
this.selector.target.entity.integration
|
||||
(this.selector.target.entity?.integration ||
|
||||
this.selector.target.device?.integration)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
@@ -118,7 +126,10 @@ export class HaTargetSelector extends SubscribeMixin(LitElement) {
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
if (this.selector.target.device?.integration) {
|
||||
if (
|
||||
this.selector.target.device?.integration ||
|
||||
this.selector.target.entity?.integration
|
||||
) {
|
||||
if (
|
||||
!this._configEntries?.some((entry) =>
|
||||
device.config_entries.includes(entry.entry_id)
|
||||
@@ -132,14 +143,16 @@ export class HaTargetSelector extends SubscribeMixin(LitElement) {
|
||||
|
||||
private async _loadConfigEntries() {
|
||||
this._configEntries = (await getConfigEntries(this.hass)).filter(
|
||||
(entry) => entry.domain === this.selector.target.device?.integration
|
||||
(entry) =>
|
||||
entry.domain ===
|
||||
(this.selector.target.device?.integration ||
|
||||
this.selector.target.entity?.integration)
|
||||
);
|
||||
}
|
||||
|
||||
static get styles(): CSSResult {
|
||||
return css`
|
||||
ha-target-picker {
|
||||
margin: 0 -8px;
|
||||
display: block;
|
||||
}
|
||||
`;
|
||||
|
@@ -12,6 +12,7 @@ import "./ha-selector-target";
|
||||
import "./ha-selector-time";
|
||||
import "./ha-selector-object";
|
||||
import "./ha-selector-text";
|
||||
import "./ha-selector-select";
|
||||
|
||||
@customElement("ha-selector")
|
||||
export class HaSelector extends LitElement {
|
||||
|
290
src/components/ha-service-control.ts
Normal file
290
src/components/ha-service-control.ts
Normal file
@@ -0,0 +1,290 @@
|
||||
import { HassService, HassServiceTarget } from "home-assistant-js-websocket";
|
||||
import {
|
||||
css,
|
||||
CSSResult,
|
||||
customElement,
|
||||
html,
|
||||
internalProperty,
|
||||
LitElement,
|
||||
property,
|
||||
PropertyValues,
|
||||
query,
|
||||
} from "lit-element";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { fireEvent } from "../common/dom/fire_event";
|
||||
import { computeDomain } from "../common/entity/compute_domain";
|
||||
import { computeObjectId } from "../common/entity/compute_object_id";
|
||||
import { ENTITY_COMPONENT_DOMAINS } from "../data/entity";
|
||||
import { Selector } from "../data/selector";
|
||||
import { PolymerChangedEvent } from "../polymer-types";
|
||||
import { HomeAssistant } from "../types";
|
||||
import "./ha-selector/ha-selector";
|
||||
import "./ha-service-picker";
|
||||
import "./ha-settings-row";
|
||||
import "./ha-yaml-editor";
|
||||
import type { HaYamlEditor } from "./ha-yaml-editor";
|
||||
|
||||
interface ExtHassService extends Omit<HassService, "fields"> {
|
||||
fields: {
|
||||
key: string;
|
||||
name?: string;
|
||||
description: string;
|
||||
required?: boolean;
|
||||
default?: any;
|
||||
example?: any;
|
||||
selector?: Selector;
|
||||
}[];
|
||||
}
|
||||
|
||||
@customElement("ha-service-control")
|
||||
export class HaServiceControl extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property({ attribute: false }) public value?: {
|
||||
service: string;
|
||||
target?: HassServiceTarget;
|
||||
data?: Record<string, any>;
|
||||
};
|
||||
|
||||
@property({ reflect: true, type: Boolean }) public narrow!: boolean;
|
||||
|
||||
@internalProperty() private _serviceData?: ExtHassService;
|
||||
|
||||
@query("ha-yaml-editor") private _yamlEditor?: HaYamlEditor;
|
||||
|
||||
protected updated(changedProperties: PropertyValues) {
|
||||
if (!changedProperties.has("value")) {
|
||||
return;
|
||||
}
|
||||
this._serviceData = this.value?.service
|
||||
? this._getServiceInfo(this.value.service)
|
||||
: undefined;
|
||||
|
||||
if (
|
||||
this._serviceData &&
|
||||
"target" in this._serviceData &&
|
||||
this.value?.data?.entity_id
|
||||
) {
|
||||
this.value = {
|
||||
...this.value,
|
||||
target: { ...this.value.target, entity_id: this.value.data.entity_id },
|
||||
};
|
||||
delete this.value.data!.entity_id;
|
||||
}
|
||||
|
||||
if (this.value?.data) {
|
||||
const yamlEditor = this._yamlEditor;
|
||||
if (yamlEditor && yamlEditor.value !== this.value.data) {
|
||||
yamlEditor.setValue(this.value.data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private _domainFilter = memoizeOne((service: string) => {
|
||||
const domain = computeDomain(service);
|
||||
return ENTITY_COMPONENT_DOMAINS.includes(domain) ? [domain] : null;
|
||||
});
|
||||
|
||||
private _getServiceInfo = memoizeOne((service: string):
|
||||
| ExtHassService
|
||||
| undefined => {
|
||||
if (!service) {
|
||||
return undefined;
|
||||
}
|
||||
const domain = computeDomain(service);
|
||||
const serviceName = computeObjectId(service);
|
||||
const serviceDomains = this.hass.services;
|
||||
if (!(domain in serviceDomains)) {
|
||||
return undefined;
|
||||
}
|
||||
if (!(serviceName in serviceDomains[domain])) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const fields = Object.entries(
|
||||
serviceDomains[domain][serviceName].fields
|
||||
).map(([key, value]) => {
|
||||
return {
|
||||
key,
|
||||
...value,
|
||||
selector: value.selector as Selector | undefined,
|
||||
};
|
||||
});
|
||||
return {
|
||||
...serviceDomains[domain][serviceName],
|
||||
fields,
|
||||
};
|
||||
});
|
||||
|
||||
protected render() {
|
||||
const legacy =
|
||||
this._serviceData?.fields.length &&
|
||||
!this._serviceData.fields.some((field) => field.selector);
|
||||
|
||||
const entityId =
|
||||
legacy &&
|
||||
this._serviceData?.fields.find((field) => field.key === "entity_id");
|
||||
|
||||
return html`<ha-service-picker
|
||||
.hass=${this.hass}
|
||||
.value=${this.value?.service}
|
||||
@value-changed=${this._serviceChanged}
|
||||
></ha-service-picker>
|
||||
${this._serviceData && "target" in this._serviceData
|
||||
? html`<ha-selector
|
||||
.hass=${this.hass}
|
||||
.selector=${this._serviceData.target
|
||||
? { target: this._serviceData.target }
|
||||
: {
|
||||
target: {
|
||||
entity: { domain: computeDomain(this.value!.service) },
|
||||
},
|
||||
}}
|
||||
@value-changed=${this._targetChanged}
|
||||
.value=${this.value?.target}
|
||||
></ha-selector>`
|
||||
: entityId
|
||||
? html`<ha-entity-picker
|
||||
.hass=${this.hass}
|
||||
.value=${this.value?.data?.entity_id}
|
||||
.label=${entityId.description}
|
||||
.includeDomains=${this._domainFilter(this.value!.service)}
|
||||
@value-changed=${this._entityPicked}
|
||||
allow-custom-entity
|
||||
></ha-entity-picker>`
|
||||
: ""}
|
||||
${legacy
|
||||
? html`<ha-yaml-editor
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.actions.type.service.service_data"
|
||||
)}
|
||||
.name=${"data"}
|
||||
.defaultValue=${this.value?.data}
|
||||
@value-changed=${this._dataChanged}
|
||||
></ha-yaml-editor>`
|
||||
: this._serviceData?.fields.map((dataField) =>
|
||||
dataField.selector
|
||||
? html`<ha-settings-row .narrow=${this.narrow}>
|
||||
<span slot="heading">${dataField.name || dataField.key}</span>
|
||||
<span slot="description">${dataField?.description}</span
|
||||
><ha-selector
|
||||
.hass=${this.hass}
|
||||
.selector=${dataField.selector}
|
||||
.key=${dataField.key}
|
||||
@value-changed=${this._serviceDataChanged}
|
||||
.value=${(this.value?.data &&
|
||||
this.value.data[dataField.key]) ||
|
||||
dataField.default}
|
||||
></ha-selector
|
||||
></ha-settings-row>`
|
||||
: ""
|
||||
)} `;
|
||||
}
|
||||
|
||||
private _serviceChanged(ev: PolymerChangedEvent<string>) {
|
||||
ev.stopPropagation();
|
||||
if (ev.detail.value === this.value?.service) {
|
||||
return;
|
||||
}
|
||||
fireEvent(this, "value-changed", {
|
||||
value: { service: ev.detail.value || "", data: {} },
|
||||
});
|
||||
}
|
||||
|
||||
private _entityPicked(ev: CustomEvent) {
|
||||
ev.stopPropagation();
|
||||
const newValue = ev.detail.value;
|
||||
if (this.value?.data?.entity_id === newValue) {
|
||||
return;
|
||||
}
|
||||
let value;
|
||||
if (!newValue && this.value?.data) {
|
||||
value = { ...this.value };
|
||||
delete value.data.entity_id;
|
||||
} else {
|
||||
value = {
|
||||
...this.value,
|
||||
data: { ...this.value?.data, entity_id: ev.detail.value },
|
||||
};
|
||||
}
|
||||
fireEvent(this, "value-changed", {
|
||||
value,
|
||||
});
|
||||
}
|
||||
|
||||
private _targetChanged(ev: CustomEvent) {
|
||||
ev.stopPropagation();
|
||||
const newValue = ev.detail.value;
|
||||
if (this.value?.target === newValue) {
|
||||
return;
|
||||
}
|
||||
let value;
|
||||
if (!newValue) {
|
||||
value = { ...this.value };
|
||||
delete value.target;
|
||||
} else {
|
||||
value = { ...this.value, target: ev.detail.value };
|
||||
}
|
||||
fireEvent(this, "value-changed", {
|
||||
value,
|
||||
});
|
||||
}
|
||||
|
||||
private _serviceDataChanged(ev: CustomEvent) {
|
||||
ev.stopPropagation();
|
||||
const key = (ev.currentTarget as any).key;
|
||||
const value = ev.detail.value;
|
||||
if (this.value?.data && this.value.data[key] === value) {
|
||||
return;
|
||||
}
|
||||
|
||||
const data = { ...this.value?.data, [key]: value };
|
||||
|
||||
if (value === "" || value === undefined) {
|
||||
delete data[key];
|
||||
}
|
||||
|
||||
fireEvent(this, "value-changed", {
|
||||
value: {
|
||||
...this.value,
|
||||
data,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
private _dataChanged(ev: CustomEvent) {
|
||||
ev.stopPropagation();
|
||||
if (!ev.detail.isValid) {
|
||||
return;
|
||||
}
|
||||
fireEvent(this, "value-changed", {
|
||||
value: {
|
||||
...this.value,
|
||||
data: ev.detail.value,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
static get styles(): CSSResult {
|
||||
return css`
|
||||
ha-settings-row {
|
||||
padding: 0;
|
||||
}
|
||||
ha-settings-row {
|
||||
--paper-time-input-justify-content: flex-end;
|
||||
}
|
||||
:host(:not([narrow])) ha-settings-row paper-input {
|
||||
width: 60%;
|
||||
}
|
||||
:host(:not([narrow])) ha-settings-row ha-selector {
|
||||
width: 60%;
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-service-control": HaServiceControl;
|
||||
}
|
||||
}
|
@@ -1,60 +0,0 @@
|
||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
||||
/* eslint-plugin-disable lit */
|
||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||
import LocalizeMixin from "../mixins/localize-mixin";
|
||||
import "./ha-combo-box";
|
||||
|
||||
/*
|
||||
* @appliesMixin LocalizeMixin
|
||||
*/
|
||||
class HaServicePicker extends LocalizeMixin(PolymerElement) {
|
||||
static get template() {
|
||||
return html`
|
||||
<ha-combo-box
|
||||
label="[[localize('ui.components.service-picker.service')]]"
|
||||
items="[[_services]]"
|
||||
value="{{value}}"
|
||||
allow-custom-value=""
|
||||
></ha-combo-box>
|
||||
`;
|
||||
}
|
||||
|
||||
static get properties() {
|
||||
return {
|
||||
hass: {
|
||||
type: Object,
|
||||
observer: "_hassChanged",
|
||||
},
|
||||
_services: Array,
|
||||
value: {
|
||||
type: String,
|
||||
notify: true,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
_hassChanged(hass, oldHass) {
|
||||
if (!hass) {
|
||||
this._services = [];
|
||||
return;
|
||||
}
|
||||
if (oldHass && hass.services === oldHass.services) {
|
||||
return;
|
||||
}
|
||||
const result = [];
|
||||
|
||||
Object.keys(hass.services)
|
||||
.sort()
|
||||
.forEach((domain) => {
|
||||
const services = Object.keys(hass.services[domain]).sort();
|
||||
|
||||
for (let i = 0; i < services.length; i++) {
|
||||
result.push(`${domain}.${services[i]}`);
|
||||
}
|
||||
});
|
||||
|
||||
this._services = result;
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("ha-service-picker", HaServicePicker);
|
121
src/components/ha-service-picker.ts
Normal file
121
src/components/ha-service-picker.ts
Normal file
@@ -0,0 +1,121 @@
|
||||
import { html, internalProperty, LitElement, property } from "lit-element";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { fireEvent } from "../common/dom/fire_event";
|
||||
import { HomeAssistant } from "../types";
|
||||
import "./ha-combo-box";
|
||||
|
||||
const rowRenderer = (
|
||||
root: HTMLElement,
|
||||
_owner,
|
||||
model: { item: { service: string; description: string } }
|
||||
) => {
|
||||
if (!root.firstElementChild) {
|
||||
root.innerHTML = `
|
||||
<style>
|
||||
paper-item {
|
||||
margin: -10px 0;
|
||||
padding: 0;
|
||||
}
|
||||
</style>
|
||||
<paper-item>
|
||||
<paper-item-body two-line="">
|
||||
<div class='name'>[[item.description]]</div>
|
||||
<div secondary>[[item.service]]</div>
|
||||
</paper-item-body>
|
||||
</paper-item>
|
||||
`;
|
||||
}
|
||||
|
||||
root.querySelector(".name")!.textContent = model.item.description;
|
||||
root.querySelector("[secondary]")!.textContent = model.item.service;
|
||||
};
|
||||
|
||||
class HaServicePicker extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property() public value?: string;
|
||||
|
||||
@internalProperty() private _filter?: string;
|
||||
|
||||
protected render() {
|
||||
return html`
|
||||
<ha-combo-box
|
||||
.hass=${this.hass}
|
||||
.label=${this.hass.localize("ui.components.service-picker.service")}
|
||||
.filteredItems=${this._filteredServices(
|
||||
this.hass.services,
|
||||
this._filter
|
||||
)}
|
||||
.value=${this.value}
|
||||
.renderer=${rowRenderer}
|
||||
item-value-path="service"
|
||||
item-label-path="description"
|
||||
allow-custom-value
|
||||
@filter-changed=${this._filterChanged}
|
||||
@value-changed=${this._valueChanged}
|
||||
></ha-combo-box>
|
||||
`;
|
||||
}
|
||||
|
||||
private _services = memoizeOne((services: HomeAssistant["services"]): {
|
||||
service: string;
|
||||
description: string;
|
||||
}[] => {
|
||||
if (!services) {
|
||||
return [];
|
||||
}
|
||||
const result: { service: string; description: string }[] = [];
|
||||
|
||||
Object.keys(services)
|
||||
.sort()
|
||||
.forEach((domain) => {
|
||||
const services_keys = Object.keys(services[domain]).sort();
|
||||
|
||||
for (const service of services_keys) {
|
||||
result.push({
|
||||
service: `${domain}.${service}`,
|
||||
description:
|
||||
services[domain][service].description || `${domain}.${service}`,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return result;
|
||||
});
|
||||
|
||||
private _filteredServices = memoizeOne(
|
||||
(services: HomeAssistant["services"], filter?: string) => {
|
||||
if (!services) {
|
||||
return [];
|
||||
}
|
||||
const processedServices = this._services(services);
|
||||
|
||||
if (!filter) {
|
||||
return processedServices;
|
||||
}
|
||||
return processedServices.filter(
|
||||
(service) =>
|
||||
service.service.toLowerCase().includes(filter) ||
|
||||
service.description.toLowerCase().includes(filter)
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
private _filterChanged(ev: CustomEvent): void {
|
||||
this._filter = ev.detail.value.toLowerCase();
|
||||
}
|
||||
|
||||
private _valueChanged(ev) {
|
||||
this.value = ev.detail.value;
|
||||
fireEvent(this, "change");
|
||||
fireEvent(this, "value-changed", { value: this.value });
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("ha-service-picker", HaServicePicker);
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-service-picker": HaServicePicker;
|
||||
}
|
||||
}
|
@@ -45,6 +45,7 @@ export class HaSettingsRow extends LitElement {
|
||||
min-height: calc(
|
||||
var(--paper-item-body-two-line-min-height, 72px) - 16px
|
||||
);
|
||||
flex: 1;
|
||||
}
|
||||
:host([narrow]) {
|
||||
align-items: normal;
|
||||
|
@@ -10,7 +10,10 @@ import {
|
||||
mdiUnfoldMoreVertical,
|
||||
} from "@mdi/js";
|
||||
import "@polymer/paper-tooltip/paper-tooltip";
|
||||
import { UnsubscribeFunc } from "home-assistant-js-websocket";
|
||||
import {
|
||||
HassServiceTarget,
|
||||
UnsubscribeFunc,
|
||||
} from "home-assistant-js-websocket";
|
||||
import {
|
||||
css,
|
||||
CSSResult,
|
||||
@@ -41,7 +44,6 @@ import {
|
||||
EntityRegistryEntry,
|
||||
subscribeEntityRegistry,
|
||||
} from "../data/entity_registry";
|
||||
import { Target } from "../data/target";
|
||||
import { SubscribeMixin } from "../mixins/subscribe-mixin";
|
||||
import { HomeAssistant } from "../types";
|
||||
import "./device/ha-device-picker";
|
||||
@@ -56,7 +58,7 @@ import "./ha-svg-icon";
|
||||
export class HaTargetPicker extends SubscribeMixin(LitElement) {
|
||||
@property() public hass!: HomeAssistant;
|
||||
|
||||
@property() public value?: Target;
|
||||
@property() public value?: HassServiceTarget;
|
||||
|
||||
@property() public label?: string;
|
||||
|
||||
@@ -530,6 +532,9 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) {
|
||||
.items {
|
||||
z-index: 2;
|
||||
}
|
||||
.mdc-chip-set {
|
||||
padding: 4px 0;
|
||||
}
|
||||
.mdc-chip.add {
|
||||
color: rgba(0, 0, 0, 0.87);
|
||||
}
|
||||
|
@@ -1,5 +1,7 @@
|
||||
import { atLeastVersion } from "../../common/config/version";
|
||||
import { HaFormSchema } from "../../components/ha-form/ha-form";
|
||||
import { HomeAssistant } from "../../types";
|
||||
import { SupervisorArch } from "../supervisor/supervisor";
|
||||
import { hassioApiResultExtractor, HassioResponse } from "./common";
|
||||
|
||||
export interface HassioAddonInfo {
|
||||
@@ -24,7 +26,7 @@ export interface HassioAddonInfo {
|
||||
|
||||
export interface HassioAddonDetails extends HassioAddonInfo {
|
||||
apparmor: "disable" | "default" | "profile";
|
||||
arch: "armhf" | "aarch64" | "i386" | "amd64";
|
||||
arch: SupervisorArch[];
|
||||
audio_input: null | string;
|
||||
audio_output: null | string;
|
||||
audio: boolean;
|
||||
@@ -63,7 +65,7 @@ export interface HassioAddonDetails extends HassioAddonInfo {
|
||||
privileged: any;
|
||||
protected: boolean;
|
||||
rating: "1-6";
|
||||
schema: HaFormSchema[];
|
||||
schema: HaFormSchema[] | null;
|
||||
services_role: string[];
|
||||
slug: string;
|
||||
startup: "initialize" | "system" | "services" | "application" | "once";
|
||||
@@ -101,10 +103,28 @@ export interface HassioAddonSetOptionParams {
|
||||
}
|
||||
|
||||
export const reloadHassioAddons = async (hass: HomeAssistant) => {
|
||||
if (atLeastVersion(hass.config.version, 2021, 2, 4)) {
|
||||
await hass.callWS({
|
||||
type: "supervisor/api",
|
||||
endpoint: "/addons/reload",
|
||||
method: "post",
|
||||
});
|
||||
return;
|
||||
}
|
||||
await hass.callApi<HassioResponse<void>>("POST", `hassio/addons/reload`);
|
||||
};
|
||||
|
||||
export const fetchHassioAddonsInfo = async (hass: HomeAssistant) => {
|
||||
export const fetchHassioAddonsInfo = async (
|
||||
hass: HomeAssistant
|
||||
): Promise<HassioAddonsInfo> => {
|
||||
if (atLeastVersion(hass.config.version, 2021, 2, 4)) {
|
||||
return await hass.callWS({
|
||||
type: "supervisor/api",
|
||||
endpoint: "/addons",
|
||||
method: "get",
|
||||
});
|
||||
}
|
||||
|
||||
return hassioApiResultExtractor(
|
||||
await hass.callApi<HassioResponse<HassioAddonsInfo>>("GET", `hassio/addons`)
|
||||
);
|
||||
@@ -113,7 +133,15 @@ export const fetchHassioAddonsInfo = async (hass: HomeAssistant) => {
|
||||
export const fetchHassioAddonInfo = async (
|
||||
hass: HomeAssistant,
|
||||
slug: string
|
||||
) => {
|
||||
): Promise<HassioAddonDetails> => {
|
||||
if (atLeastVersion(hass.config.version, 2021, 2, 4)) {
|
||||
return await hass.callWS({
|
||||
type: "supervisor/api",
|
||||
endpoint: `/addons/${slug}/info`,
|
||||
method: "get",
|
||||
});
|
||||
}
|
||||
|
||||
return hassioApiResultExtractor(
|
||||
await hass.callApi<HassioResponse<HassioAddonDetails>>(
|
||||
"GET",
|
||||
@@ -148,6 +176,16 @@ export const setHassioAddonOption = async (
|
||||
slug: string,
|
||||
data: HassioAddonSetOptionParams
|
||||
) => {
|
||||
if (atLeastVersion(hass.config.version, 2021, 2, 4)) {
|
||||
await hass.callWS({
|
||||
type: "supervisor/api",
|
||||
endpoint: `/addons/${slug}/options`,
|
||||
method: "post",
|
||||
data,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
await hass.callApi<HassioResponse<void>>(
|
||||
"POST",
|
||||
`hassio/addons/${slug}/options`,
|
||||
@@ -158,21 +196,64 @@ export const setHassioAddonOption = async (
|
||||
export const validateHassioAddonOption = async (
|
||||
hass: HomeAssistant,
|
||||
slug: string
|
||||
) => {
|
||||
return await hass.callApi<
|
||||
HassioResponse<{ message: string; valid: boolean }>
|
||||
>("POST", `hassio/addons/${slug}/options/validate`);
|
||||
): Promise<{ message: string; valid: boolean }> => {
|
||||
if (atLeastVersion(hass.config.version, 2021, 2, 4)) {
|
||||
return await hass.callWS({
|
||||
type: "supervisor/api",
|
||||
endpoint: `/addons/${slug}/options/validate`,
|
||||
method: "post",
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
await hass.callApi<HassioResponse<{ message: string; valid: boolean }>>(
|
||||
"POST",
|
||||
`hassio/addons/${slug}/options/validate`
|
||||
)
|
||||
).data;
|
||||
};
|
||||
|
||||
export const startHassioAddon = async (hass: HomeAssistant, slug: string) => {
|
||||
if (atLeastVersion(hass.config.version, 2021, 2, 4)) {
|
||||
return await hass.callWS({
|
||||
type: "supervisor/api",
|
||||
endpoint: `/addons/${slug}/start`,
|
||||
method: "post",
|
||||
timeout: null,
|
||||
});
|
||||
}
|
||||
|
||||
return hass.callApi<string>("POST", `hassio/addons/${slug}/start`);
|
||||
};
|
||||
|
||||
export const stopHassioAddon = async (hass: HomeAssistant, slug: string) => {
|
||||
if (atLeastVersion(hass.config.version, 2021, 2, 4)) {
|
||||
return await hass.callWS({
|
||||
type: "supervisor/api",
|
||||
endpoint: `/addons/${slug}/stop`,
|
||||
method: "post",
|
||||
timeout: null,
|
||||
});
|
||||
}
|
||||
|
||||
return hass.callApi<string>("POST", `hassio/addons/${slug}/stop`);
|
||||
};
|
||||
|
||||
export const setHassioAddonSecurity = async (
|
||||
hass: HomeAssistant,
|
||||
slug: string,
|
||||
data: HassioAddonSetSecurityParams
|
||||
) => {
|
||||
if (atLeastVersion(hass.config.version, 2021, 2, 4)) {
|
||||
await hass.callWS({
|
||||
type: "supervisor/api",
|
||||
endpoint: `/addons/${slug}/security`,
|
||||
method: "post",
|
||||
data,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
await hass.callApi<HassioResponse<void>>(
|
||||
"POST",
|
||||
`hassio/addons/${slug}/security`,
|
||||
@@ -180,15 +261,61 @@ export const setHassioAddonSecurity = async (
|
||||
);
|
||||
};
|
||||
|
||||
export const installHassioAddon = async (hass: HomeAssistant, slug: string) => {
|
||||
return hass.callApi<HassioResponse<void>>(
|
||||
export const installHassioAddon = async (
|
||||
hass: HomeAssistant,
|
||||
slug: string
|
||||
): Promise<void> => {
|
||||
if (atLeastVersion(hass.config.version, 2021, 2, 4)) {
|
||||
await hass.callWS({
|
||||
type: "supervisor/api",
|
||||
endpoint: `/addons/${slug}/install`,
|
||||
method: "post",
|
||||
timeout: null,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
await hass.callApi<HassioResponse<void>>(
|
||||
"POST",
|
||||
`hassio/addons/${slug}/install`
|
||||
);
|
||||
};
|
||||
|
||||
export const restartHassioAddon = async (hass: HomeAssistant, slug: string) => {
|
||||
return hass.callApi<HassioResponse<void>>(
|
||||
export const updateHassioAddon = async (
|
||||
hass: HomeAssistant,
|
||||
slug: string
|
||||
): Promise<void> => {
|
||||
if (atLeastVersion(hass.config.version, 2021, 2, 4)) {
|
||||
await hass.callWS({
|
||||
type: "supervisor/api",
|
||||
endpoint: `/addons/${slug}/update`,
|
||||
method: "post",
|
||||
timeout: null,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
await hass.callApi<HassioResponse<void>>(
|
||||
"POST",
|
||||
`hassio/addons/${slug}/update`
|
||||
);
|
||||
};
|
||||
|
||||
export const restartHassioAddon = async (
|
||||
hass: HomeAssistant,
|
||||
slug: string
|
||||
): Promise<void> => {
|
||||
if (atLeastVersion(hass.config.version, 2021, 2, 4)) {
|
||||
await hass.callWS({
|
||||
type: "supervisor/api",
|
||||
endpoint: `/addons/${slug}/restart`,
|
||||
method: "post",
|
||||
timeout: null,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
await hass.callApi<HassioResponse<void>>(
|
||||
"POST",
|
||||
`hassio/addons/${slug}/restart`
|
||||
);
|
||||
@@ -198,6 +325,16 @@ export const uninstallHassioAddon = async (
|
||||
hass: HomeAssistant,
|
||||
slug: string
|
||||
) => {
|
||||
if (atLeastVersion(hass.config.version, 2021, 2, 4)) {
|
||||
await hass.callWS({
|
||||
type: "supervisor/api",
|
||||
endpoint: `/addons/${slug}/uninstall`,
|
||||
method: "post",
|
||||
timeout: null,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
await hass.callApi<HassioResponse<void>>(
|
||||
"POST",
|
||||
`hassio/addons/${slug}/uninstall`
|
||||
|
@@ -1,3 +1,4 @@
|
||||
import { atLeastVersion } from "../../common/config/version";
|
||||
import { HomeAssistant } from "../../types";
|
||||
|
||||
export interface HassioResponse<T> {
|
||||
@@ -33,6 +34,14 @@ export const fetchHassioStats = async (
|
||||
hass: HomeAssistant,
|
||||
container: string
|
||||
): Promise<HassioStats> => {
|
||||
if (atLeastVersion(hass.config.version, 2021, 2, 4)) {
|
||||
return await hass.callWS({
|
||||
type: "supervisor/api",
|
||||
endpoint: `/${container}/stats`,
|
||||
method: "get",
|
||||
});
|
||||
}
|
||||
|
||||
return hassioApiResultExtractor(
|
||||
await hass.callApi<HassioResponse<HassioStats>>(
|
||||
"GET",
|
||||
|
@@ -1,3 +1,4 @@
|
||||
import { atLeastVersion } from "../../common/config/version";
|
||||
import { HomeAssistant } from "../../types";
|
||||
import { hassioApiResultExtractor, HassioResponse } from "./common";
|
||||
|
||||
@@ -5,7 +6,17 @@ interface HassioDockerRegistries {
|
||||
[key: string]: { username: string; password?: string };
|
||||
}
|
||||
|
||||
export const fetchHassioDockerRegistries = async (hass: HomeAssistant) => {
|
||||
export const fetchHassioDockerRegistries = async (
|
||||
hass: HomeAssistant
|
||||
): Promise<HassioDockerRegistries> => {
|
||||
if (atLeastVersion(hass.config.version, 2021, 2, 4)) {
|
||||
return await hass.callWS({
|
||||
type: "supervisor/api",
|
||||
endpoint: `/docker/registries`,
|
||||
method: "get",
|
||||
});
|
||||
}
|
||||
|
||||
return hassioApiResultExtractor(
|
||||
await hass.callApi<HassioResponse<HassioDockerRegistries>>(
|
||||
"GET",
|
||||
@@ -18,6 +29,16 @@ export const addHassioDockerRegistry = async (
|
||||
hass: HomeAssistant,
|
||||
data: HassioDockerRegistries
|
||||
) => {
|
||||
if (atLeastVersion(hass.config.version, 2021, 2, 4)) {
|
||||
await hass.callWS({
|
||||
type: "supervisor/api",
|
||||
endpoint: `/docker/registries`,
|
||||
method: "post",
|
||||
data,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
await hass.callApi<HassioResponse<HassioDockerRegistries>>(
|
||||
"POST",
|
||||
"hassio/docker/registries",
|
||||
@@ -29,6 +50,15 @@ export const removeHassioDockerRegistry = async (
|
||||
hass: HomeAssistant,
|
||||
registry: string
|
||||
) => {
|
||||
if (atLeastVersion(hass.config.version, 2021, 2, 4)) {
|
||||
await hass.callWS({
|
||||
type: "supervisor/api",
|
||||
endpoint: `/docker/registries/${registry}`,
|
||||
method: "delete",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
await hass.callApi<HassioResponse<void>>(
|
||||
"DELETE",
|
||||
`hassio/docker/registries/${registry}`
|
||||
|
@@ -1,3 +1,4 @@
|
||||
import { atLeastVersion } from "../../common/config/version";
|
||||
import { HomeAssistant } from "../../types";
|
||||
import { hassioApiResultExtractor, HassioResponse } from "./common";
|
||||
|
||||
@@ -21,7 +22,17 @@ export interface HassioHardwareInfo {
|
||||
audio: Record<string, unknown>;
|
||||
}
|
||||
|
||||
export const fetchHassioHardwareAudio = async (hass: HomeAssistant) => {
|
||||
export const fetchHassioHardwareAudio = async (
|
||||
hass: HomeAssistant
|
||||
): Promise<HassioHardwareAudioList> => {
|
||||
if (atLeastVersion(hass.config.version, 2021, 2, 4)) {
|
||||
return await hass.callWS({
|
||||
type: "supervisor/api",
|
||||
endpoint: `/hardware/audio`,
|
||||
method: "get",
|
||||
});
|
||||
}
|
||||
|
||||
return hassioApiResultExtractor(
|
||||
await hass.callApi<HassioResponse<HassioHardwareAudioList>>(
|
||||
"GET",
|
||||
@@ -30,7 +41,17 @@ export const fetchHassioHardwareAudio = async (hass: HomeAssistant) => {
|
||||
);
|
||||
};
|
||||
|
||||
export const fetchHassioHardwareInfo = async (hass: HomeAssistant) => {
|
||||
export const fetchHassioHardwareInfo = async (
|
||||
hass: HomeAssistant
|
||||
): Promise<HassioHardwareInfo> => {
|
||||
if (atLeastVersion(hass.config.version, 2021, 2, 4)) {
|
||||
return await hass.callWS({
|
||||
type: "supervisor/api",
|
||||
endpoint: `/hardware/info`,
|
||||
method: "get",
|
||||
});
|
||||
}
|
||||
|
||||
return hassioApiResultExtractor(
|
||||
await hass.callApi<HassioResponse<HassioHardwareInfo>>(
|
||||
"GET",
|
||||
|
@@ -1,3 +1,4 @@
|
||||
import { atLeastVersion } from "../../common/config/version";
|
||||
import { HomeAssistant } from "../../types";
|
||||
import { hassioApiResultExtractor, HassioResponse } from "./common";
|
||||
|
||||
@@ -5,6 +6,7 @@ export type HassioHostInfo = {
|
||||
chassis: string;
|
||||
cpe: string;
|
||||
deployment: string;
|
||||
disk_life_time: number | "";
|
||||
disk_free: number;
|
||||
disk_total: number;
|
||||
disk_used: number;
|
||||
@@ -22,7 +24,17 @@ export interface HassioHassOSInfo {
|
||||
version: string | null;
|
||||
}
|
||||
|
||||
export const fetchHassioHostInfo = async (hass: HomeAssistant) => {
|
||||
export const fetchHassioHostInfo = async (
|
||||
hass: HomeAssistant
|
||||
): Promise<HassioHostInfo> => {
|
||||
if (atLeastVersion(hass.config.version, 2021, 2, 4)) {
|
||||
return await hass.callWS({
|
||||
type: "supervisor/api",
|
||||
endpoint: "/host/info",
|
||||
method: "get",
|
||||
});
|
||||
}
|
||||
|
||||
const response = await hass.callApi<HassioResponse<HassioHostInfo>>(
|
||||
"GET",
|
||||
"hassio/host/info"
|
||||
@@ -30,7 +42,17 @@ export const fetchHassioHostInfo = async (hass: HomeAssistant) => {
|
||||
return hassioApiResultExtractor(response);
|
||||
};
|
||||
|
||||
export const fetchHassioHassOsInfo = async (hass: HomeAssistant) => {
|
||||
export const fetchHassioHassOsInfo = async (
|
||||
hass: HomeAssistant
|
||||
): Promise<HassioHassOSInfo> => {
|
||||
if (atLeastVersion(hass.config.version, 2021, 2, 4)) {
|
||||
return await hass.callWS({
|
||||
type: "supervisor/api",
|
||||
endpoint: "/os/info",
|
||||
method: "get",
|
||||
});
|
||||
}
|
||||
|
||||
return hassioApiResultExtractor(
|
||||
await hass.callApi<HassioResponse<HassioHassOSInfo>>(
|
||||
"GET",
|
||||
@@ -40,22 +62,67 @@ export const fetchHassioHassOsInfo = async (hass: HomeAssistant) => {
|
||||
};
|
||||
|
||||
export const rebootHost = async (hass: HomeAssistant) => {
|
||||
if (atLeastVersion(hass.config.version, 2021, 2, 4)) {
|
||||
return await hass.callWS({
|
||||
type: "supervisor/api",
|
||||
endpoint: "/host/reboot",
|
||||
method: "post",
|
||||
timeout: null,
|
||||
});
|
||||
}
|
||||
|
||||
return hass.callApi<HassioResponse<void>>("POST", "hassio/host/reboot");
|
||||
};
|
||||
|
||||
export const shutdownHost = async (hass: HomeAssistant) => {
|
||||
if (atLeastVersion(hass.config.version, 2021, 2, 4)) {
|
||||
return await hass.callWS({
|
||||
type: "supervisor/api",
|
||||
endpoint: "/host/shutdown",
|
||||
method: "post",
|
||||
timeout: null,
|
||||
});
|
||||
}
|
||||
|
||||
return hass.callApi<HassioResponse<void>>("POST", "hassio/host/shutdown");
|
||||
};
|
||||
|
||||
export const updateOS = async (hass: HomeAssistant) => {
|
||||
if (atLeastVersion(hass.config.version, 2021, 2, 4)) {
|
||||
return await hass.callWS({
|
||||
type: "supervisor/api",
|
||||
endpoint: "/os/update",
|
||||
method: "post",
|
||||
timeout: null,
|
||||
});
|
||||
}
|
||||
|
||||
return hass.callApi<HassioResponse<void>>("POST", "hassio/os/update");
|
||||
};
|
||||
|
||||
export const configSyncOS = async (hass: HomeAssistant) => {
|
||||
if (atLeastVersion(hass.config.version, 2021, 2, 4)) {
|
||||
return await hass.callWS({
|
||||
type: "supervisor/api",
|
||||
endpoint: "os/config/sync",
|
||||
method: "post",
|
||||
timeout: null,
|
||||
});
|
||||
}
|
||||
|
||||
return hass.callApi<HassioResponse<void>>("POST", "hassio/os/config/sync");
|
||||
};
|
||||
|
||||
export const changeHostOptions = async (hass: HomeAssistant, options: any) => {
|
||||
if (atLeastVersion(hass.config.version, 2021, 2, 4)) {
|
||||
return await hass.callWS({
|
||||
type: "supervisor/api",
|
||||
endpoint: "/host/options",
|
||||
method: "post",
|
||||
data: options,
|
||||
});
|
||||
}
|
||||
|
||||
return hass.callApi<HassioResponse<void>>(
|
||||
"POST",
|
||||
"hassio/host/options",
|
||||
|
@@ -1,26 +1,49 @@
|
||||
import { atLeastVersion } from "../../common/config/version";
|
||||
import { HomeAssistant } from "../../types";
|
||||
import { HassioResponse } from "./common";
|
||||
import { CreateSessionResponse } from "./supervisor";
|
||||
|
||||
export const createHassioSession = async (hass: HomeAssistant) => {
|
||||
const response = await hass.callApi<HassioResponse<CreateSessionResponse>>(
|
||||
"POST",
|
||||
"hassio/ingress/session"
|
||||
);
|
||||
document.cookie = `ingress_session=${
|
||||
response.data.session
|
||||
};path=/api/hassio_ingress/;SameSite=Strict${
|
||||
function setIngressCookie(session: string): string {
|
||||
document.cookie = `ingress_session=${session};path=/api/hassio_ingress/;SameSite=Strict${
|
||||
location.protocol === "https:" ? ";Secure" : ""
|
||||
}`;
|
||||
return response.data.session;
|
||||
return session;
|
||||
}
|
||||
|
||||
export const createHassioSession = async (
|
||||
hass: HomeAssistant
|
||||
): Promise<string> => {
|
||||
if (atLeastVersion(hass.config.version, 2021, 2, 4)) {
|
||||
const wsResponse: { session: string } = await hass.callWS({
|
||||
type: "supervisor/api",
|
||||
endpoint: "/ingress/session",
|
||||
method: "post",
|
||||
});
|
||||
return setIngressCookie(wsResponse.session);
|
||||
}
|
||||
|
||||
const restResponse: { data: { session: string } } = await hass.callApi<
|
||||
HassioResponse<CreateSessionResponse>
|
||||
>("POST", "hassio/ingress/session");
|
||||
return setIngressCookie(restResponse.data.session);
|
||||
};
|
||||
|
||||
export const validateHassioSession = async (
|
||||
hass: HomeAssistant,
|
||||
session: string
|
||||
) =>
|
||||
await hass.callApi<HassioResponse<null>>(
|
||||
): Promise<void> => {
|
||||
if (atLeastVersion(hass.config.version, 2021, 2, 4)) {
|
||||
await hass.callWS({
|
||||
type: "supervisor/api",
|
||||
endpoint: "/ingress/validate_session",
|
||||
method: "post",
|
||||
data: session,
|
||||
});
|
||||
}
|
||||
|
||||
await hass.callApi<HassioResponse<void>>(
|
||||
"POST",
|
||||
"hassio/ingress/validate_session",
|
||||
{ session }
|
||||
);
|
||||
};
|
||||
|
@@ -1,3 +1,4 @@
|
||||
import { atLeastVersion } from "../../common/config/version";
|
||||
import { HomeAssistant } from "../../types";
|
||||
import { hassioApiResultExtractor, HassioResponse } from "./common";
|
||||
|
||||
@@ -51,7 +52,17 @@ export interface NetworkInfo {
|
||||
docker: DockerNetwork;
|
||||
}
|
||||
|
||||
export const fetchNetworkInfo = async (hass: HomeAssistant) => {
|
||||
export const fetchNetworkInfo = async (
|
||||
hass: HomeAssistant
|
||||
): Promise<NetworkInfo> => {
|
||||
if (atLeastVersion(hass.config.version, 2021, 2, 4)) {
|
||||
return await hass.callWS({
|
||||
type: "supervisor/api",
|
||||
endpoint: "/network/info",
|
||||
method: "get",
|
||||
});
|
||||
}
|
||||
|
||||
return hassioApiResultExtractor(
|
||||
await hass.callApi<HassioResponse<NetworkInfo>>(
|
||||
"GET",
|
||||
@@ -65,6 +76,17 @@ export const updateNetworkInterface = async (
|
||||
network_interface: string,
|
||||
options: Partial<NetworkInterface>
|
||||
) => {
|
||||
if (atLeastVersion(hass.config.version, 2021, 2, 4)) {
|
||||
await hass.callWS({
|
||||
type: "supervisor/api",
|
||||
endpoint: `/network/interface/${network_interface}/update`,
|
||||
method: "post",
|
||||
data: options,
|
||||
timeout: null,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
await hass.callApi<HassioResponse<NetworkInfo>>(
|
||||
"POST",
|
||||
`hassio/network/interface/${network_interface}/update`,
|
||||
@@ -75,7 +97,16 @@ export const updateNetworkInterface = async (
|
||||
export const accesspointScan = async (
|
||||
hass: HomeAssistant,
|
||||
network_interface: string
|
||||
) => {
|
||||
): Promise<AccessPoints> => {
|
||||
if (atLeastVersion(hass.config.version, 2021, 2, 4)) {
|
||||
return await hass.callWS({
|
||||
type: "supervisor/api",
|
||||
endpoint: `/network/interface/${network_interface}/accesspoints`,
|
||||
method: "get",
|
||||
timeout: null,
|
||||
});
|
||||
}
|
||||
|
||||
return hassioApiResultExtractor(
|
||||
await hass.callApi<HassioResponse<AccessPoints>>(
|
||||
"GET",
|
||||
|
@@ -1,3 +1,4 @@
|
||||
import { atLeastVersion } from "../../common/config/version";
|
||||
import { HomeAssistant } from "../../types";
|
||||
import { hassioApiResultExtractor, HassioResponse } from "./common";
|
||||
|
||||
@@ -8,7 +9,17 @@ export interface HassioResolution {
|
||||
suggestions: string[];
|
||||
}
|
||||
|
||||
export const fetchHassioResolution = async (hass: HomeAssistant) => {
|
||||
export const fetchHassioResolution = async (
|
||||
hass: HomeAssistant
|
||||
): Promise<HassioResolution> => {
|
||||
if (atLeastVersion(hass.config.version, 2021, 2, 4)) {
|
||||
return await hass.callWS({
|
||||
type: "supervisor/api",
|
||||
endpoint: "/resolution/info",
|
||||
method: "get",
|
||||
});
|
||||
}
|
||||
|
||||
return hassioApiResultExtractor(
|
||||
await hass.callApi<HassioResponse<HassioResolution>>(
|
||||
"GET",
|
||||
|
@@ -1,3 +1,4 @@
|
||||
import { atLeastVersion } from "../../common/config/version";
|
||||
import { HomeAssistant } from "../../types";
|
||||
import { hassioApiResultExtractor, HassioResponse } from "./common";
|
||||
|
||||
@@ -33,7 +34,18 @@ export interface HassioPartialSnapshotCreateParams {
|
||||
password?: string;
|
||||
}
|
||||
|
||||
export const fetchHassioSnapshots = async (hass: HomeAssistant) => {
|
||||
export const fetchHassioSnapshots = async (
|
||||
hass: HomeAssistant
|
||||
): Promise<HassioSnapshot[]> => {
|
||||
if (atLeastVersion(hass.config.version, 2021, 2, 4)) {
|
||||
const data: { snapshots: HassioSnapshot[] } = await hass.callWS({
|
||||
type: "supervisor/api",
|
||||
endpoint: `/snapshots`,
|
||||
method: "get",
|
||||
});
|
||||
return data.snapshots;
|
||||
}
|
||||
|
||||
return hassioApiResultExtractor(
|
||||
await hass.callApi<HassioResponse<{ snapshots: HassioSnapshot[] }>>(
|
||||
"GET",
|
||||
@@ -45,8 +57,15 @@ export const fetchHassioSnapshots = async (hass: HomeAssistant) => {
|
||||
export const fetchHassioSnapshotInfo = async (
|
||||
hass: HomeAssistant,
|
||||
snapshot: string
|
||||
) => {
|
||||
): Promise<HassioSnapshotDetail> => {
|
||||
if (hass) {
|
||||
if (atLeastVersion(hass.config.version, 2021, 2, 4)) {
|
||||
return await hass.callWS({
|
||||
type: "supervisor/api",
|
||||
endpoint: `/snapshots/${snapshot}/info`,
|
||||
method: "get",
|
||||
});
|
||||
}
|
||||
return hassioApiResultExtractor(
|
||||
await hass.callApi<HassioResponse<HassioSnapshotDetail>>(
|
||||
"GET",
|
||||
@@ -63,6 +82,15 @@ export const fetchHassioSnapshotInfo = async (
|
||||
};
|
||||
|
||||
export const reloadHassioSnapshots = async (hass: HomeAssistant) => {
|
||||
if (atLeastVersion(hass.config.version, 2021, 2, 4)) {
|
||||
await hass.callWS({
|
||||
type: "supervisor/api",
|
||||
endpoint: "/snapshots/reload",
|
||||
method: "post",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
await hass.callApi<HassioResponse<void>>("POST", `hassio/snapshots/reload`);
|
||||
};
|
||||
|
||||
@@ -70,6 +98,15 @@ export const createHassioFullSnapshot = async (
|
||||
hass: HomeAssistant,
|
||||
data: HassioFullSnapshotCreateParams
|
||||
) => {
|
||||
if (atLeastVersion(hass.config.version, 2021, 2, 4)) {
|
||||
await hass.callWS({
|
||||
type: "supervisor/api",
|
||||
endpoint: "/snapshots/new/full",
|
||||
method: "post",
|
||||
timeout: null,
|
||||
});
|
||||
return;
|
||||
}
|
||||
await hass.callApi<HassioResponse<void>>(
|
||||
"POST",
|
||||
`hassio/snapshots/new/full`,
|
||||
@@ -81,6 +118,17 @@ export const createHassioPartialSnapshot = async (
|
||||
hass: HomeAssistant,
|
||||
data: HassioFullSnapshotCreateParams
|
||||
) => {
|
||||
if (atLeastVersion(hass.config.version, 2021, 2, 4)) {
|
||||
await hass.callWS({
|
||||
type: "supervisor/api",
|
||||
endpoint: "/snapshots/new/partial",
|
||||
method: "post",
|
||||
timeout: null,
|
||||
data,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
await hass.callApi<HassioResponse<void>>(
|
||||
"POST",
|
||||
`hassio/snapshots/new/partial`,
|
||||
|
@@ -1,9 +1,11 @@
|
||||
import { atLeastVersion } from "../../common/config/version";
|
||||
import { HomeAssistant, PanelInfo } from "../../types";
|
||||
import { SupervisorArch } from "../supervisor/supervisor";
|
||||
import { HassioAddonInfo, HassioAddonRepository } from "./addon";
|
||||
import { hassioApiResultExtractor, HassioResponse } from "./common";
|
||||
|
||||
export type HassioHomeAssistantInfo = {
|
||||
arch: string;
|
||||
arch: SupervisorArch;
|
||||
audio_input: string | null;
|
||||
audio_output: string | null;
|
||||
boot: boolean;
|
||||
@@ -22,7 +24,7 @@ export type HassioHomeAssistantInfo = {
|
||||
export type HassioSupervisorInfo = {
|
||||
addons: HassioAddonInfo[];
|
||||
addons_repositories: HassioAddonRepository[];
|
||||
arch: string;
|
||||
arch: SupervisorArch;
|
||||
channel: string;
|
||||
debug: boolean;
|
||||
debug_block: boolean;
|
||||
@@ -39,7 +41,7 @@ export type HassioSupervisorInfo = {
|
||||
};
|
||||
|
||||
export type HassioInfo = {
|
||||
arch: string;
|
||||
arch: SupervisorArch;
|
||||
channel: string;
|
||||
docker: string;
|
||||
features: string[];
|
||||
@@ -48,10 +50,19 @@ export type HassioInfo = {
|
||||
hostname: string;
|
||||
logging: string;
|
||||
machine: string;
|
||||
state:
|
||||
| "initialize"
|
||||
| "setup"
|
||||
| "startup"
|
||||
| "running"
|
||||
| "freeze"
|
||||
| "shutdown"
|
||||
| "stopping"
|
||||
| "close";
|
||||
operating_system: string;
|
||||
supervisor: string;
|
||||
supported: boolean;
|
||||
supported_arch: string[];
|
||||
supported_arch: SupervisorArch[];
|
||||
timezone: string;
|
||||
};
|
||||
|
||||
@@ -73,18 +84,57 @@ export interface SupervisorOptions {
|
||||
}
|
||||
|
||||
export const reloadSupervisor = async (hass: HomeAssistant) => {
|
||||
if (atLeastVersion(hass.config.version, 2021, 2, 4)) {
|
||||
await hass.callWS({
|
||||
type: "supervisor/api",
|
||||
endpoint: "/supervisor/reload",
|
||||
method: "post",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
await hass.callApi<HassioResponse<void>>("POST", `hassio/supervisor/reload`);
|
||||
};
|
||||
|
||||
export const restartSupervisor = async (hass: HomeAssistant) => {
|
||||
if (atLeastVersion(hass.config.version, 2021, 2, 4)) {
|
||||
await hass.callWS({
|
||||
type: "supervisor/api",
|
||||
endpoint: "/supervisor/restart",
|
||||
method: "post",
|
||||
timeout: null,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
await hass.callApi<HassioResponse<void>>("POST", `hassio/supervisor/restart`);
|
||||
};
|
||||
|
||||
export const updateSupervisor = async (hass: HomeAssistant) => {
|
||||
if (atLeastVersion(hass.config.version, 2021, 2, 4)) {
|
||||
await hass.callWS({
|
||||
type: "supervisor/api",
|
||||
endpoint: "/supervisor/update",
|
||||
method: "post",
|
||||
timeout: null,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
await hass.callApi<HassioResponse<void>>("POST", `hassio/supervisor/update`);
|
||||
};
|
||||
|
||||
export const fetchHassioHomeAssistantInfo = async (hass: HomeAssistant) => {
|
||||
export const fetchHassioHomeAssistantInfo = async (
|
||||
hass: HomeAssistant
|
||||
): Promise<HassioHomeAssistantInfo> => {
|
||||
if (atLeastVersion(hass.config.version, 2021, 2, 4)) {
|
||||
return await hass.callWS({
|
||||
type: "supervisor/api",
|
||||
endpoint: "/core/info",
|
||||
method: "get",
|
||||
});
|
||||
}
|
||||
|
||||
return hassioApiResultExtractor(
|
||||
await hass.callApi<HassioResponse<HassioHomeAssistantInfo>>(
|
||||
"GET",
|
||||
@@ -93,7 +143,17 @@ export const fetchHassioHomeAssistantInfo = async (hass: HomeAssistant) => {
|
||||
);
|
||||
};
|
||||
|
||||
export const fetchHassioSupervisorInfo = async (hass: HomeAssistant) => {
|
||||
export const fetchHassioSupervisorInfo = async (
|
||||
hass: HomeAssistant
|
||||
): Promise<HassioSupervisorInfo> => {
|
||||
if (atLeastVersion(hass.config.version, 2021, 2, 4)) {
|
||||
return await hass.callWS({
|
||||
type: "supervisor/api",
|
||||
endpoint: "/supervisor/info",
|
||||
method: "get",
|
||||
});
|
||||
}
|
||||
|
||||
return hassioApiResultExtractor(
|
||||
await hass.callApi<HassioResponse<HassioSupervisorInfo>>(
|
||||
"GET",
|
||||
@@ -102,7 +162,17 @@ export const fetchHassioSupervisorInfo = async (hass: HomeAssistant) => {
|
||||
);
|
||||
};
|
||||
|
||||
export const fetchHassioInfo = async (hass: HomeAssistant) => {
|
||||
export const fetchHassioInfo = async (
|
||||
hass: HomeAssistant
|
||||
): Promise<HassioInfo> => {
|
||||
if (atLeastVersion(hass.config.version, 2021, 2, 4)) {
|
||||
return await hass.callWS({
|
||||
type: "supervisor/api",
|
||||
endpoint: "/info",
|
||||
method: "get",
|
||||
});
|
||||
}
|
||||
|
||||
return hassioApiResultExtractor(
|
||||
await hass.callApi<HassioResponse<HassioInfo>>("GET", "hassio/info")
|
||||
);
|
||||
@@ -119,6 +189,16 @@ export const setSupervisorOption = async (
|
||||
hass: HomeAssistant,
|
||||
data: SupervisorOptions
|
||||
) => {
|
||||
if (atLeastVersion(hass.config.version, 2021, 2, 4)) {
|
||||
await hass.callWS({
|
||||
type: "supervisor/api",
|
||||
endpoint: "/supervisor/options",
|
||||
method: "post",
|
||||
data,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
await hass.callApi<HassioResponse<void>>(
|
||||
"POST",
|
||||
"hassio/supervisor/options",
|
||||
|
@@ -4,7 +4,6 @@ import { computeStateDomain } from "../common/entity/compute_state_domain";
|
||||
import { computeStateName } from "../common/entity/compute_state_name";
|
||||
import { LocalizeFunc } from "../common/translations/localize";
|
||||
import { HomeAssistant } from "../types";
|
||||
import { UNAVAILABLE_STATES } from "./entity";
|
||||
|
||||
const DOMAINS_USE_LAST_UPDATED = ["climate", "humidifier", "water_heater"];
|
||||
const LINE_ATTRIBUTES_TO_KEEP = [
|
||||
@@ -201,23 +200,6 @@ const processLineChartEntities = (
|
||||
};
|
||||
};
|
||||
|
||||
const isNumerical = (states: HassEntity[]): boolean => {
|
||||
if (states.every((state) => UNAVAILABLE_STATES.includes(state.state))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (
|
||||
states.some(
|
||||
(state) =>
|
||||
isNaN(parseFloat(state.state)) &&
|
||||
!UNAVAILABLE_STATES.includes(state.state)
|
||||
)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
export const computeHistory = (
|
||||
hass: HomeAssistant,
|
||||
stateHistory: HassEntity[][],
|
||||
@@ -249,8 +231,6 @@ export const computeHistory = (
|
||||
unit = hass.config.unit_system.temperature;
|
||||
} else if (computeStateDomain(stateInfo[0]) === "humidifier") {
|
||||
unit = "%";
|
||||
} else if (isNumerical(stateInfo)) {
|
||||
unit = " ";
|
||||
}
|
||||
|
||||
if (!unit) {
|
||||
|
@@ -2,6 +2,7 @@ import {
|
||||
Connection,
|
||||
getCollection,
|
||||
HassEventBase,
|
||||
HassServiceTarget,
|
||||
} from "home-assistant-js-websocket";
|
||||
import { HASSDomEvent } from "../common/dom/fire_event";
|
||||
import { HuiErrorCard } from "../panels/lovelace/cards/hui-error-card";
|
||||
@@ -120,8 +121,8 @@ export interface ToggleActionConfig extends BaseActionConfig {
|
||||
export interface CallServiceActionConfig extends BaseActionConfig {
|
||||
action: "call-service";
|
||||
service: string;
|
||||
target?: HassServiceTarget;
|
||||
service_data?: {
|
||||
entity_id?: string | [string];
|
||||
[key: string]: any;
|
||||
};
|
||||
}
|
||||
|
@@ -1,6 +1,7 @@
|
||||
import {
|
||||
HassEntityAttributeBase,
|
||||
HassEntityBase,
|
||||
HassServiceTarget,
|
||||
} from "home-assistant-js-websocket";
|
||||
import { computeObjectId } from "../common/entity/compute_object_id";
|
||||
import { navigate } from "../common/navigate";
|
||||
@@ -36,6 +37,7 @@ export interface EventAction {
|
||||
export interface ServiceAction {
|
||||
service: string;
|
||||
entity_id?: string;
|
||||
target?: HassServiceTarget;
|
||||
data?: Record<string, any>;
|
||||
}
|
||||
|
||||
|
@@ -8,8 +8,8 @@ export type Selector =
|
||||
| TimeSelector
|
||||
| ActionSelector
|
||||
| StringSelector
|
||||
| ObjectSelector;
|
||||
|
||||
| ObjectSelector
|
||||
| SelectSelector;
|
||||
export interface EntitySelector {
|
||||
entity: {
|
||||
integration?: string;
|
||||
@@ -95,3 +95,9 @@ export interface ObjectSelector {
|
||||
// eslint-disable-next-line @typescript-eslint/ban-types
|
||||
object: {};
|
||||
}
|
||||
|
||||
export interface SelectSelector {
|
||||
select: {
|
||||
options: string[];
|
||||
};
|
||||
}
|
||||
|
@@ -1,3 +1,4 @@
|
||||
import { atLeastVersion } from "../../common/config/version";
|
||||
import { HomeAssistant } from "../../types";
|
||||
import { HassioResponse } from "../hassio/common";
|
||||
|
||||
@@ -6,5 +7,15 @@ export const restartCore = async (hass: HomeAssistant) => {
|
||||
};
|
||||
|
||||
export const updateCore = async (hass: HomeAssistant) => {
|
||||
if (atLeastVersion(hass.config.version, 2021, 2, 4)) {
|
||||
await hass.callWS({
|
||||
type: "supervisor/api",
|
||||
endpoint: "/core/update",
|
||||
method: "post",
|
||||
timeout: null,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
await hass.callApi<HassioResponse<void>>("POST", `hassio/core/update`);
|
||||
};
|
||||
|
@@ -1,3 +1,7 @@
|
||||
import { Connection, getCollection } from "home-assistant-js-websocket";
|
||||
import { Store } from "home-assistant-js-websocket/dist/store";
|
||||
import { HomeAssistant } from "../../types";
|
||||
import { HassioAddonsInfo } from "../hassio/addon";
|
||||
import { HassioHassOSInfo, HassioHostInfo } from "../hassio/host";
|
||||
import { NetworkInfo } from "../hassio/network";
|
||||
import { HassioResolution } from "../hassio/resolution";
|
||||
@@ -7,6 +11,54 @@ import {
|
||||
HassioSupervisorInfo,
|
||||
} from "../hassio/supervisor";
|
||||
|
||||
export const supervisorWSbaseCommand = {
|
||||
type: "supervisor/api",
|
||||
method: "GET",
|
||||
};
|
||||
|
||||
export const supervisorStore = {
|
||||
host: "/host/info",
|
||||
supervisor: "/supervisor/info",
|
||||
info: "/info",
|
||||
core: "/core/info",
|
||||
network: "/network/info",
|
||||
resolution: "/resolution/info",
|
||||
os: "/os/info",
|
||||
addon: "/addons",
|
||||
};
|
||||
|
||||
export type SupervisorArch = "armhf" | "armv7" | "aarch64" | "i386" | "amd64";
|
||||
export type SupervisorObject =
|
||||
| "host"
|
||||
| "supervisor"
|
||||
| "info"
|
||||
| "core"
|
||||
| "network"
|
||||
| "resolution"
|
||||
| "os"
|
||||
| "addon";
|
||||
|
||||
interface supervisorApiRequest {
|
||||
endpoint: string;
|
||||
method?: "get" | "post" | "delete" | "put";
|
||||
data?: any;
|
||||
}
|
||||
|
||||
export interface SupervisorEvent {
|
||||
event: string;
|
||||
update_key?: SupervisorObject;
|
||||
data?: any;
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
export interface SupervisorAPIRequestParams {
|
||||
connection?: any;
|
||||
rest?: boolean;
|
||||
data?: any;
|
||||
endpoint: string;
|
||||
method?: "get" | "post" | "delete" | "put";
|
||||
}
|
||||
|
||||
export interface Supervisor {
|
||||
host: HassioHostInfo;
|
||||
supervisor: HassioSupervisorInfo;
|
||||
@@ -15,4 +67,78 @@ export interface Supervisor {
|
||||
network: NetworkInfo;
|
||||
resolution: HassioResolution;
|
||||
os: HassioHassOSInfo;
|
||||
addon: HassioAddonsInfo;
|
||||
callApi<T>(params: SupervisorAPIRequestParams): Promise<T>;
|
||||
}
|
||||
|
||||
export const supervisorApiWsRequest = <T>(
|
||||
conn: Connection,
|
||||
request: supervisorApiRequest
|
||||
): Promise<T> =>
|
||||
conn.sendMessagePromise<T>({ ...supervisorWSbaseCommand, ...request });
|
||||
|
||||
async function processEvent(
|
||||
conn: Connection,
|
||||
store: Store<any>,
|
||||
event: SupervisorEvent,
|
||||
key: string
|
||||
) {
|
||||
if (
|
||||
!event.data ||
|
||||
event.data.event !== "supervisor-update" ||
|
||||
event.data.update_key !== key
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (Object.keys(event.data.data).length === 0) {
|
||||
const data = await supervisorApiWsRequest<any>(conn, {
|
||||
endpoint: supervisorStore[key],
|
||||
});
|
||||
store.setState(data);
|
||||
return;
|
||||
}
|
||||
|
||||
const state = store.state;
|
||||
if (state === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
store.setState({
|
||||
...state,
|
||||
...event.data.data,
|
||||
});
|
||||
}
|
||||
|
||||
const subscribeSupervisorEventUpdates = (
|
||||
conn: Connection,
|
||||
store: Store<unknown>,
|
||||
key: string
|
||||
) =>
|
||||
conn.subscribeEvents(
|
||||
(event) => processEvent(conn, store, event as SupervisorEvent, key),
|
||||
"supervisor_event"
|
||||
);
|
||||
|
||||
export const getSupervisorEventCollection = (
|
||||
conn: Connection,
|
||||
key: string,
|
||||
endpoint: string
|
||||
) =>
|
||||
getCollection(
|
||||
conn,
|
||||
`_supervisor${key}Event`,
|
||||
() => supervisorApiWsRequest(conn, { endpoint }),
|
||||
(connection, store) =>
|
||||
subscribeSupervisorEventUpdates(connection, store, key)
|
||||
);
|
||||
|
||||
export const subscribeSupervisorEvents = (
|
||||
hass: HomeAssistant,
|
||||
onChange: (event) => void,
|
||||
key: string,
|
||||
endpoint: string
|
||||
) =>
|
||||
getSupervisorEventCollection(hass.connection, key, endpoint).subscribe(
|
||||
onChange
|
||||
);
|
||||
|
@@ -1,5 +0,0 @@
|
||||
export interface Target {
|
||||
entity_id?: string[];
|
||||
device_id?: string[];
|
||||
area_id?: string[];
|
||||
}
|
@@ -32,6 +32,7 @@ import {
|
||||
} from "../../data/device_registry";
|
||||
import { haStyleDialog } from "../../resources/styles";
|
||||
import type { HomeAssistant } from "../../types";
|
||||
import { showAlertDialog } from "../generic/show-dialog-box";
|
||||
import { DataEntryFlowDialogParams } from "./show-dialog-data-entry-flow";
|
||||
import "./step-flow-abort";
|
||||
import "./step-flow-create-entry";
|
||||
@@ -105,9 +106,20 @@ class DataEntryFlowDialog extends LitElement {
|
||||
|
||||
this._loading = true;
|
||||
const curInstance = this._instance;
|
||||
const step = await (params.continueFlowId
|
||||
? params.flowConfig.fetchFlow(this.hass, params.continueFlowId)
|
||||
: params.flowConfig.createFlow(this.hass, params.startFlowHandler!));
|
||||
let step: DataEntryFlowStep;
|
||||
try {
|
||||
step = await (params.continueFlowId
|
||||
? params.flowConfig.fetchFlow(this.hass, params.continueFlowId)
|
||||
: params.flowConfig.createFlow(this.hass, params.startFlowHandler!));
|
||||
} catch (err) {
|
||||
this._step = undefined;
|
||||
this._params = undefined;
|
||||
showAlertDialog(this, {
|
||||
title: "Error",
|
||||
text: "Config flow could not be loaded",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Happens if second showDialog called
|
||||
if (curInstance !== this._instance) {
|
||||
|
@@ -380,22 +380,24 @@ export class QuickBar extends LitElement {
|
||||
QuickBarNavigationItem,
|
||||
"action"
|
||||
>[] {
|
||||
return Object.keys(this.hass.panels).map((panelKey) => {
|
||||
const panel = this.hass.panels[panelKey];
|
||||
const translationKey = getPanelNameTranslationKey(panel);
|
||||
return Object.keys(this.hass.panels)
|
||||
.filter((panelKey) => panelKey !== "_my_redirect")
|
||||
.map((panelKey) => {
|
||||
const panel = this.hass.panels[panelKey];
|
||||
const translationKey = getPanelNameTranslationKey(panel);
|
||||
|
||||
const text = this.hass.localize(
|
||||
"ui.dialogs.quick-bar.commands.navigation.navigate_to",
|
||||
"panel",
|
||||
this.hass.localize(translationKey) || panel.title || panel.url_path
|
||||
);
|
||||
const text = this.hass.localize(
|
||||
"ui.dialogs.quick-bar.commands.navigation.navigate_to",
|
||||
"panel",
|
||||
this.hass.localize(translationKey) || panel.title || panel.url_path
|
||||
);
|
||||
|
||||
return {
|
||||
text,
|
||||
icon: getPanelIcon(panel) || DEFAULT_NAVIGATION_ICON,
|
||||
path: `/${panel.url_path}`,
|
||||
};
|
||||
});
|
||||
return {
|
||||
text,
|
||||
icon: getPanelIcon(panel) || DEFAULT_NAVIGATION_ICON,
|
||||
path: `/${panel.url_path}`,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
private _generateNavigationConfigSectionCommands(): Partial<
|
||||
|
@@ -15,7 +15,8 @@ export const demoConfig: HassConfig = {
|
||||
time_zone: "America/Los_Angeles",
|
||||
config_dir: "/config",
|
||||
version: "DEMO",
|
||||
whitelist_external_dirs: [],
|
||||
allowlist_external_dirs: [],
|
||||
allowlist_external_urls: [],
|
||||
config_source: "storage",
|
||||
safe_mode: false,
|
||||
state: STATE_RUNNING,
|
||||
|
@@ -1,4 +1,4 @@
|
||||
<meta name='viewport' content='width=device-width, viewport-fit=cover'>
|
||||
<meta name='viewport' content='width=device-width, user-scalable=no, viewport-fit=cover'>
|
||||
<style>
|
||||
body {
|
||||
font-family: Roboto, sans-serif;
|
||||
|
@@ -70,10 +70,14 @@ class HassErrorScreen extends LitElement {
|
||||
color: var(--primary-text-color);
|
||||
height: calc(100% - var(--header-height));
|
||||
display: flex;
|
||||
padding: 16px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
}
|
||||
a {
|
||||
color: var(--primary-color);
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
@@ -183,7 +183,7 @@ export class HomeAssistantAppEl extends QuickBarMixin(HassElement) {
|
||||
this._hiddenTimeout = undefined;
|
||||
// setTimeout can be delayed in the background and only fire
|
||||
// when we switch to the tab or app again (Hey Android!)
|
||||
if (!document.hidden) {
|
||||
if (document.hidden) {
|
||||
this._suspendApp();
|
||||
}
|
||||
}, 300000);
|
||||
|
@@ -29,6 +29,7 @@ const COMPONENTS = {
|
||||
logbook: () => import("../panels/logbook/ha-panel-logbook"),
|
||||
mailbox: () => import("../panels/mailbox/ha-panel-mailbox"),
|
||||
map: () => import("../panels/map/ha-panel-map"),
|
||||
my: () => import("../panels/my/ha-panel-my"),
|
||||
profile: () => import("../panels/profile/ha-panel-profile"),
|
||||
"shopping-list": () =>
|
||||
import("../panels/shopping-list/ha-panel-shopping-list"),
|
||||
|
@@ -42,7 +42,6 @@ import "./types/ha-automation-action-wait_template";
|
||||
const OPTIONS = [
|
||||
"condition",
|
||||
"delay",
|
||||
"device_id",
|
||||
"event",
|
||||
"scene",
|
||||
"service",
|
||||
@@ -50,6 +49,7 @@ const OPTIONS = [
|
||||
"wait_for_trigger",
|
||||
"repeat",
|
||||
"choose",
|
||||
"device_id",
|
||||
];
|
||||
|
||||
const getType = (action: Action) => {
|
||||
@@ -99,6 +99,8 @@ export default class HaAutomationActionRow extends LitElement {
|
||||
|
||||
@property() public totalActions!: number;
|
||||
|
||||
@property({ type: Boolean }) public narrow = false;
|
||||
|
||||
@internalProperty() private _warnings?: string[];
|
||||
|
||||
@internalProperty() private _uiModeAvailable = true;
|
||||
@@ -116,8 +118,9 @@ export default class HaAutomationActionRow extends LitElement {
|
||||
this._yamlMode = true;
|
||||
}
|
||||
|
||||
if (this._yamlMode && this._yamlEditor) {
|
||||
this._yamlEditor.setValue(this.action);
|
||||
const yamlEditor = this._yamlEditor;
|
||||
if (this._yamlMode && yamlEditor && yamlEditor.value !== this.action) {
|
||||
yamlEditor.setValue(this.action);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -242,6 +245,7 @@ export default class HaAutomationActionRow extends LitElement {
|
||||
${dynamicElement(`ha-automation-action-${type}`, {
|
||||
hass: this.hass,
|
||||
action: this.action,
|
||||
narrow: this.narrow,
|
||||
})}
|
||||
</div>
|
||||
`}
|
||||
|
@@ -18,6 +18,8 @@ import { HaDeviceAction } from "./types/ha-automation-action-device_id";
|
||||
export default class HaAutomationAction extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property({ type: Boolean }) public narrow = false;
|
||||
|
||||
@property() public actions!: Action[];
|
||||
|
||||
protected render() {
|
||||
@@ -28,6 +30,7 @@ export default class HaAutomationAction extends LitElement {
|
||||
.index=${idx}
|
||||
.totalActions=${this.actions.length}
|
||||
.action=${action}
|
||||
.narrow=${this.narrow}
|
||||
@duplicate=${this._duplicateAction}
|
||||
@move-action=${this._move}
|
||||
@value-changed=${this._actionChanged}
|
||||
|
@@ -22,13 +22,17 @@ export class HaDelayAction extends LitElement implements ActionElement {
|
||||
let data: HaFormTimeData = {};
|
||||
|
||||
if (typeof this.action.delay !== "object") {
|
||||
const parts = this.action.delay?.toString().split(":") || [];
|
||||
data = {
|
||||
hours: Number(parts[0]),
|
||||
minutes: Number(parts[1]),
|
||||
seconds: Number(parts[2]),
|
||||
milliseconds: Number(parts[3]),
|
||||
};
|
||||
if (isNaN(this.action.delay)) {
|
||||
const parts = this.action.delay?.toString().split(":") || [];
|
||||
data = {
|
||||
hours: Number(parts[0]) || 0,
|
||||
minutes: Number(parts[1]) || 0,
|
||||
seconds: Number(parts[2]) || 0,
|
||||
milliseconds: Number(parts[3]) || 0,
|
||||
};
|
||||
} else {
|
||||
data = { seconds: this.action.delay };
|
||||
}
|
||||
} else {
|
||||
const { days, minutes, seconds, milliseconds } = this.action.delay;
|
||||
let { hours } = this.action.delay || 0;
|
||||
@@ -46,7 +50,8 @@ export class HaDelayAction extends LitElement implements ActionElement {
|
||||
.data=${data}
|
||||
enableMillisecond
|
||||
@value-changed=${this._valueChanged}
|
||||
></ha-time-input>
|
||||
>
|
||||
</ha-time-input>
|
||||
`;
|
||||
}
|
||||
|
||||
|
@@ -1,30 +1,24 @@
|
||||
import "@polymer/paper-input/paper-input";
|
||||
import {
|
||||
customElement,
|
||||
internalProperty,
|
||||
LitElement,
|
||||
property,
|
||||
PropertyValues,
|
||||
query,
|
||||
} from "lit-element";
|
||||
import { html } from "lit-html";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { any, assert, object, optional, string } from "superstruct";
|
||||
import { fireEvent } from "../../../../../common/dom/fire_event";
|
||||
import { computeDomain } from "../../../../../common/entity/compute_domain";
|
||||
import { computeObjectId } from "../../../../../common/entity/compute_object_id";
|
||||
import "../../../../../components/entity/ha-entity-picker";
|
||||
import "../../../../../components/ha-service-picker";
|
||||
import "../../../../../components/ha-yaml-editor";
|
||||
import type { HaYamlEditor } from "../../../../../components/ha-yaml-editor";
|
||||
import { ServiceAction } from "../../../../../data/script";
|
||||
import type { PolymerChangedEvent } from "../../../../../polymer-types";
|
||||
import type { HomeAssistant } from "../../../../../types";
|
||||
import { EntityIdOrAll } from "../../../../../common/structs/is-entity-id";
|
||||
import { ActionElement, handleChangeEvent } from "../ha-automation-action-row";
|
||||
import { ActionElement } from "../ha-automation-action-row";
|
||||
import "../../../../../components/ha-service-control";
|
||||
|
||||
const actionStruct = object({
|
||||
service: optional(string()),
|
||||
entity_id: optional(EntityIdOrAll),
|
||||
target: optional(any()),
|
||||
data: optional(any()),
|
||||
});
|
||||
|
||||
@@ -34,36 +28,14 @@ export class HaServiceAction extends LitElement implements ActionElement {
|
||||
|
||||
@property({ attribute: false }) public action!: ServiceAction;
|
||||
|
||||
@query("ha-yaml-editor", true) private _yamlEditor?: HaYamlEditor;
|
||||
@property({ type: Boolean }) public narrow = false;
|
||||
|
||||
private _actionData?: ServiceAction["data"];
|
||||
@internalProperty() private _action!: ServiceAction;
|
||||
|
||||
public static get defaultConfig() {
|
||||
return { service: "", data: {} };
|
||||
}
|
||||
|
||||
private _domain = memoizeOne((service: string) => [computeDomain(service)]);
|
||||
|
||||
private _getServiceData = memoizeOne((service: string) => {
|
||||
if (!service) {
|
||||
return [];
|
||||
}
|
||||
const domain = computeDomain(service);
|
||||
const serviceName = computeObjectId(service);
|
||||
const serviceDomains = this.hass.services;
|
||||
if (!(domain in serviceDomains)) {
|
||||
return [];
|
||||
}
|
||||
if (!(serviceName in serviceDomains[domain])) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const fields = serviceDomains[domain][serviceName].fields;
|
||||
return Object.keys(fields).map((field) => {
|
||||
return { key: field, ...fields[field] };
|
||||
});
|
||||
});
|
||||
|
||||
protected updated(changedProperties: PropertyValues) {
|
||||
if (!changedProperties.has("action")) {
|
||||
return;
|
||||
@@ -73,73 +45,32 @@ export class HaServiceAction extends LitElement implements ActionElement {
|
||||
} catch (error) {
|
||||
fireEvent(this, "ui-mode-not-available", error);
|
||||
}
|
||||
if (this._actionData && this._actionData !== this.action.data) {
|
||||
if (this._yamlEditor) {
|
||||
this._yamlEditor.setValue(this.action.data);
|
||||
}
|
||||
if (this.action.entity_id) {
|
||||
this._action = {
|
||||
...this.action,
|
||||
data: { ...this.action.data, entity_id: this.action.entity_id },
|
||||
};
|
||||
delete this._action.entity_id;
|
||||
} else {
|
||||
this._action = this.action;
|
||||
}
|
||||
this._actionData = this.action.data;
|
||||
}
|
||||
|
||||
protected render() {
|
||||
const { service, data, entity_id } = this.action;
|
||||
|
||||
const serviceData = this._getServiceData(service);
|
||||
const entity = serviceData.find((attr) => attr.key === "entity_id");
|
||||
|
||||
return html`
|
||||
<ha-service-picker
|
||||
<ha-service-control
|
||||
.narrow=${this.narrow}
|
||||
.hass=${this.hass}
|
||||
.value=${service}
|
||||
@value-changed=${this._serviceChanged}
|
||||
></ha-service-picker>
|
||||
${entity
|
||||
? html`
|
||||
<ha-entity-picker
|
||||
.hass=${this.hass}
|
||||
.value=${entity_id}
|
||||
.label=${entity.description}
|
||||
@value-changed=${this._entityPicked}
|
||||
.includeDomains=${this._domain(service)}
|
||||
allow-custom-entity
|
||||
></ha-entity-picker>
|
||||
`
|
||||
: ""}
|
||||
<ha-yaml-editor
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.actions.type.service.service_data"
|
||||
)}
|
||||
.name=${"data"}
|
||||
.defaultValue=${data}
|
||||
@value-changed=${this._dataChanged}
|
||||
></ha-yaml-editor>
|
||||
.value=${this._action}
|
||||
@value-changed=${this._actionChanged}
|
||||
></ha-service-control>
|
||||
`;
|
||||
}
|
||||
|
||||
private _dataChanged(ev: CustomEvent): void {
|
||||
ev.stopPropagation();
|
||||
if (!ev.detail.isValid) {
|
||||
return;
|
||||
private _actionChanged(ev) {
|
||||
if (ev.detail.value === this._action) {
|
||||
ev.stopPropagation();
|
||||
}
|
||||
this._actionData = ev.detail.value;
|
||||
handleChangeEvent(this, ev);
|
||||
}
|
||||
|
||||
private _serviceChanged(ev: PolymerChangedEvent<string>) {
|
||||
ev.stopPropagation();
|
||||
if (ev.detail.value === this.action.service) {
|
||||
return;
|
||||
}
|
||||
fireEvent(this, "value-changed", {
|
||||
value: { ...this.action, service: ev.detail.value },
|
||||
});
|
||||
}
|
||||
|
||||
private _entityPicked(ev: PolymerChangedEvent<string>) {
|
||||
ev.stopPropagation();
|
||||
fireEvent(this, "value-changed", {
|
||||
value: { ...this.action, entity_id: ev.detail.value },
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -252,10 +252,7 @@ export class HaBlueprintAutomationEditor extends LitElement {
|
||||
if (!name) {
|
||||
return;
|
||||
}
|
||||
let newVal = ev.detail.value;
|
||||
if (target.type === "number") {
|
||||
newVal = Number(newVal);
|
||||
}
|
||||
const newVal = ev.detail.value;
|
||||
if ((this.config![name] || "") === newVal) {
|
||||
return;
|
||||
}
|
||||
|
@@ -42,7 +42,7 @@ export class HaManualAutomationEditor extends LitElement {
|
||||
@property() public stateObj?: HassEntity;
|
||||
|
||||
protected render() {
|
||||
return html`<ha-config-section .isWide=${this.isWide}>
|
||||
return html`<ha-config-section vertical .isWide=${this.isWide}>
|
||||
${!this.narrow
|
||||
? html` <span slot="header">${this.config.alias}</span> `
|
||||
: ""}
|
||||
@@ -151,7 +151,7 @@ export class HaManualAutomationEditor extends LitElement {
|
||||
</ha-card>
|
||||
</ha-config-section>
|
||||
|
||||
<ha-config-section .isWide=${this.isWide}>
|
||||
<ha-config-section vertical .isWide=${this.isWide}>
|
||||
<span slot="header">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.triggers.header"
|
||||
@@ -180,7 +180,7 @@ export class HaManualAutomationEditor extends LitElement {
|
||||
></ha-automation-trigger>
|
||||
</ha-config-section>
|
||||
|
||||
<ha-config-section .isWide=${this.isWide}>
|
||||
<ha-config-section vertical .isWide=${this.isWide}>
|
||||
<span slot="header">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.conditions.header"
|
||||
@@ -209,7 +209,7 @@ export class HaManualAutomationEditor extends LitElement {
|
||||
></ha-automation-condition>
|
||||
</ha-config-section>
|
||||
|
||||
<ha-config-section .isWide=${this.isWide}>
|
||||
<ha-config-section vertical .isWide=${this.isWide}>
|
||||
<span slot="header">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.actions.header"
|
||||
@@ -235,6 +235,7 @@ export class HaManualAutomationEditor extends LitElement {
|
||||
.actions=${this.config.action}
|
||||
@value-changed=${this._actionChanged}
|
||||
.hass=${this.hass}
|
||||
.narrow=${this.narrow}
|
||||
></ha-automation-action>
|
||||
</ha-config-section>`;
|
||||
}
|
||||
|
@@ -39,17 +39,21 @@ class DialogImportBlueprint extends LitElement {
|
||||
|
||||
@internalProperty() private _result?: BlueprintImportResult;
|
||||
|
||||
@internalProperty() private _url?: string;
|
||||
|
||||
@query("#input") private _input?: PaperInputElement;
|
||||
|
||||
public showDialog(params): void {
|
||||
this._params = params;
|
||||
this._error = undefined;
|
||||
this._url = this._params.url;
|
||||
}
|
||||
|
||||
public closeDialog(): void {
|
||||
this._error = undefined;
|
||||
this._result = undefined;
|
||||
this._params = undefined;
|
||||
this._url = undefined;
|
||||
fireEvent(this, "dialog-closed", { dialog: this.localName });
|
||||
}
|
||||
|
||||
@@ -123,6 +127,7 @@ class DialogImportBlueprint extends LitElement {
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.blueprint.add.url"
|
||||
)}
|
||||
.value=${this._url}
|
||||
dialogInitialFocus
|
||||
></paper-input>`}
|
||||
</div>
|
||||
@@ -171,6 +176,7 @@ class DialogImportBlueprint extends LitElement {
|
||||
}
|
||||
|
||||
private async _import() {
|
||||
this._url = undefined;
|
||||
this._importing = true;
|
||||
this._error = undefined;
|
||||
try {
|
||||
|
@@ -7,10 +7,13 @@ import {
|
||||
html,
|
||||
LitElement,
|
||||
property,
|
||||
PropertyValues,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { fireEvent } from "../../../common/dom/fire_event";
|
||||
import { navigate } from "../../../common/navigate";
|
||||
import { extractSearchParam } from "../../../common/url/search-params";
|
||||
import { DataTableColumnContainer } from "../../../components/data-table/ha-data-table";
|
||||
import "../../../components/entity/ha-entity-toggle";
|
||||
import "../../../components/ha-fab";
|
||||
@@ -155,6 +158,17 @@ class HaBlueprintOverview extends LitElement {
|
||||
})
|
||||
);
|
||||
|
||||
protected firstUpdated(changedProps: PropertyValues) {
|
||||
super.firstUpdated(changedProps);
|
||||
if (this.route.path === "/import") {
|
||||
const url = extractSearchParam("blueprint_url");
|
||||
navigate(this, "/config/blueprint/dashboard", true);
|
||||
if (url) {
|
||||
this._addBlueprint(url);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return html`
|
||||
<hass-tabs-subpage-data-table
|
||||
@@ -228,8 +242,11 @@ class HaBlueprintOverview extends LitElement {
|
||||
});
|
||||
}
|
||||
|
||||
private _addBlueprint() {
|
||||
showAddBlueprintDialog(this, { importedCallback: () => this._reload() });
|
||||
private _addBlueprint(url?: string) {
|
||||
showAddBlueprintDialog(this, {
|
||||
url,
|
||||
importedCallback: () => this._reload(),
|
||||
});
|
||||
}
|
||||
|
||||
private _reload() {
|
||||
|
@@ -80,13 +80,16 @@ export class HaConfigSection extends LitElement {
|
||||
font-weight: var(--paper-font-subhead_-_font-weight);
|
||||
line-height: var(--paper-font-subhead_-_line-height);
|
||||
width: 100%;
|
||||
max-width: 400px;
|
||||
margin-right: 40px;
|
||||
opacity: var(--dark-primary-opacity);
|
||||
font-size: 14px;
|
||||
padding-bottom: 20px;
|
||||
}
|
||||
|
||||
.horizontal .intro {
|
||||
max-width: 400px;
|
||||
margin-right: 40px;
|
||||
}
|
||||
|
||||
.panel {
|
||||
margin-top: -24px;
|
||||
}
|
||||
|
@@ -18,9 +18,11 @@ import {
|
||||
import { classMap } from "lit-html/directives/class-map";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { HASSDomEvent } from "../../../common/dom/fire_event";
|
||||
import { navigate } from "../../../common/navigate";
|
||||
import "../../../common/search/search-input";
|
||||
import { caseInsensitiveCompare } from "../../../common/string/compare";
|
||||
import { LocalizeFunc } from "../../../common/translations/localize";
|
||||
import { extractSearchParam } from "../../../common/url/search-params";
|
||||
import { nextRender } from "../../../common/util/render-status";
|
||||
import "../../../components/ha-button-menu";
|
||||
import "../../../components/ha-card";
|
||||
@@ -222,8 +224,15 @@ class HaConfigIntegrations extends SubscribeMixin(LitElement) {
|
||||
protected firstUpdated(changed: PropertyValues) {
|
||||
super.firstUpdated(changed);
|
||||
this._loadConfigEntries();
|
||||
this.hass.loadBackendTranslation("title", undefined, true);
|
||||
const localizePromise = this.hass.loadBackendTranslation(
|
||||
"title",
|
||||
undefined,
|
||||
true
|
||||
);
|
||||
this._fetchManifests();
|
||||
if (this.route.path === "/add") {
|
||||
this._handleAdd(localizePromise);
|
||||
}
|
||||
}
|
||||
|
||||
protected updated(changed: PropertyValues) {
|
||||
@@ -535,11 +544,15 @@ class HaConfigIntegrations extends SubscribeMixin(LitElement) {
|
||||
);
|
||||
}
|
||||
|
||||
private _handleFlowUpdated() {
|
||||
this._loadConfigEntries();
|
||||
getConfigFlowInProgressCollection(this.hass.connection).refresh();
|
||||
}
|
||||
|
||||
private _createFlow() {
|
||||
showConfigFlowDialog(this, {
|
||||
dialogClosedCallback: () => {
|
||||
this._loadConfigEntries();
|
||||
getConfigFlowInProgressCollection(this.hass.connection).refresh();
|
||||
this._handleFlowUpdated();
|
||||
},
|
||||
showAdvanced: this.showAdvanced,
|
||||
});
|
||||
@@ -551,8 +564,7 @@ class HaConfigIntegrations extends SubscribeMixin(LitElement) {
|
||||
showConfigFlowDialog(this, {
|
||||
continueFlowId: (ev.target! as any).flowId,
|
||||
dialogClosedCallback: () => {
|
||||
this._loadConfigEntries();
|
||||
getConfigFlowInProgressCollection(this.hass.connection).refresh();
|
||||
this._handleFlowUpdated();
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -649,6 +661,33 @@ class HaConfigIntegrations extends SubscribeMixin(LitElement) {
|
||||
}
|
||||
}
|
||||
|
||||
private async _handleAdd(localizePromise: Promise<LocalizeFunc>) {
|
||||
const domain = extractSearchParam("domain");
|
||||
navigate(this, "/config/integrations", true);
|
||||
if (!domain) {
|
||||
return;
|
||||
}
|
||||
const localize = await localizePromise;
|
||||
if (
|
||||
!(await showConfirmationDialog(this, {
|
||||
title: localize(
|
||||
"ui.panel.config.integrations.confirm_new",
|
||||
"integration",
|
||||
domainToName(localize, domain)
|
||||
),
|
||||
}))
|
||||
) {
|
||||
return;
|
||||
}
|
||||
showConfigFlowDialog(this, {
|
||||
dialogClosedCallback: () => {
|
||||
this._handleFlowUpdated();
|
||||
},
|
||||
startFlowHandler: domain,
|
||||
showAdvanced: this.hass.userData?.showAdvanced,
|
||||
});
|
||||
}
|
||||
|
||||
static get styles(): CSSResult[] {
|
||||
return [
|
||||
haStyle,
|
||||
|
@@ -221,7 +221,7 @@ export class HaSceneEditor extends SubscribeMixin(
|
||||
>
|
||||
${this._config
|
||||
? html`
|
||||
<ha-config-section .isWide=${this.isWide}>
|
||||
<ha-config-section vertical .isWide=${this.isWide}>
|
||||
${!this.narrow
|
||||
? html` <span slot="header">${name}</span> `
|
||||
: ""}
|
||||
@@ -253,7 +253,7 @@ export class HaSceneEditor extends SubscribeMixin(
|
||||
</ha-card>
|
||||
</ha-config-section>
|
||||
|
||||
<ha-config-section .isWide=${this.isWide}>
|
||||
<ha-config-section vertical .isWide=${this.isWide}>
|
||||
<div slot="header">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.scene.editor.devices.header"
|
||||
@@ -324,7 +324,7 @@ export class HaSceneEditor extends SubscribeMixin(
|
||||
|
||||
${this.showAdvanced
|
||||
? html`
|
||||
<ha-config-section .isWide=${this.isWide}>
|
||||
<ha-config-section vertical .isWide=${this.isWide}>
|
||||
<div slot="header">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.scene.editor.entities.header"
|
||||
|
@@ -189,7 +189,7 @@ export class HaScriptEditor extends KeyboardShortcutMixin(LitElement) {
|
||||
>
|
||||
${this._config
|
||||
? html`
|
||||
<ha-config-section .isWide=${this.isWide}>
|
||||
<ha-config-section vertical .isWide=${this.isWide}>
|
||||
${!this.narrow
|
||||
? html`
|
||||
<span slot="header">${this._config.alias}</span>
|
||||
@@ -313,7 +313,7 @@ export class HaScriptEditor extends KeyboardShortcutMixin(LitElement) {
|
||||
</ha-card>
|
||||
</ha-config-section>
|
||||
|
||||
<ha-config-section .isWide=${this.isWide}>
|
||||
<ha-config-section vertical .isWide=${this.isWide}>
|
||||
<span slot="header">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.script.editor.sequence"
|
||||
@@ -350,7 +350,7 @@ export class HaScriptEditor extends KeyboardShortcutMixin(LitElement) {
|
||||
`
|
||||
: this._mode === "yaml"
|
||||
? html`
|
||||
<ha-config-section .isWide=${false}>
|
||||
<ha-config-section vertical .isWide=${false}>
|
||||
${!this.narrow
|
||||
? html`<span slot="header">${this._config?.alias}</span>`
|
||||
: ``}
|
||||
|
@@ -22,7 +22,7 @@ import {
|
||||
FORMAT_NUMBER,
|
||||
} from "../../../data/alarm_control_panel";
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
import { findEntities } from "../common/find-entites";
|
||||
import { findEntities } from "../common/find-entities";
|
||||
import { createEntityNotFoundWarning } from "../components/hui-warning";
|
||||
import type { LovelaceCard } from "../types";
|
||||
import { AlarmPanelCardConfig } from "./types";
|
||||
|
@@ -32,7 +32,7 @@ import { LightEntity } from "../../../data/light";
|
||||
import { ActionHandlerEvent } from "../../../data/lovelace";
|
||||
import { HomeAssistant } from "../../../types";
|
||||
import { actionHandler } from "../common/directives/action-handler-directive";
|
||||
import { findEntities } from "../common/find-entites";
|
||||
import { findEntities } from "../common/find-entities";
|
||||
import { handleAction } from "../common/handle-action";
|
||||
import { hasAction } from "../common/has-action";
|
||||
import { createEntityNotFoundWarning } from "../components/hui-warning";
|
||||
@@ -272,6 +272,7 @@ export class HuiButtonCard extends LitElement implements LovelaceCard {
|
||||
height: auto;
|
||||
color: var(--paper-item-icon-color, #44739e);
|
||||
--mdc-icon-size: 100%;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
ha-icon,
|
||||
|
@@ -25,7 +25,7 @@ import type {
|
||||
} from "../../../types";
|
||||
import "../../calendar/ha-full-calendar";
|
||||
import type { HAFullCalendar } from "../../calendar/ha-full-calendar";
|
||||
import { findEntities } from "../common/find-entites";
|
||||
import { findEntities } from "../common/find-entities";
|
||||
import { installResizeObserver } from "../common/install-resize-observer";
|
||||
import "../components/hui-warning";
|
||||
import type { LovelaceCard, LovelaceCardEditor } from "../types";
|
||||
|
@@ -14,7 +14,7 @@ import { computeDomain } from "../../../common/entity/compute_domain";
|
||||
import "../../../components/ha-card";
|
||||
import { HomeAssistant } from "../../../types";
|
||||
import { computeCardSize } from "../common/compute-card-size";
|
||||
import { findEntities } from "../common/find-entites";
|
||||
import { findEntities } from "../common/find-entities";
|
||||
import { processConfigEntities } from "../common/process-config-entities";
|
||||
import "../components/hui-entities-toggle";
|
||||
import { createHeaderFooterElement } from "../create-element/create-header-footer-element";
|
||||
|
@@ -21,7 +21,7 @@ import "../../../components/ha-icon";
|
||||
import { UNAVAILABLE_STATES } from "../../../data/entity";
|
||||
import { HomeAssistant } from "../../../types";
|
||||
import { computeCardSize } from "../common/compute-card-size";
|
||||
import { findEntities } from "../common/find-entites";
|
||||
import { findEntities } from "../common/find-entities";
|
||||
import { hasConfigOrEntityChanged } from "../common/has-changed";
|
||||
import { createEntityNotFoundWarning } from "../components/hui-warning";
|
||||
import { createHeaderFooterElement } from "../create-element/create-header-footer-element";
|
||||
|
@@ -8,6 +8,7 @@ import { LovelaceCardConfig } from "../../../data/lovelace";
|
||||
import { HomeAssistant } from "../../../types";
|
||||
import { computeCardSize } from "../common/compute-card-size";
|
||||
import { evaluateFilter } from "../common/evaluate-filter";
|
||||
import { findEntities } from "../common/find-entities";
|
||||
import { processConfigEntities } from "../common/process-config-entities";
|
||||
import { createCardElement } from "../create-element/create-card-element";
|
||||
import { EntityFilterEntityConfig } from "../entity-rows/types";
|
||||
@@ -15,6 +16,30 @@ import { LovelaceCard } from "../types";
|
||||
import { EntityFilterCardConfig } from "./types";
|
||||
|
||||
class EntityFilterCard extends UpdatingElement implements LovelaceCard {
|
||||
public static getStubConfig(
|
||||
hass: HomeAssistant,
|
||||
entities: string[],
|
||||
entitiesFallback: string[]
|
||||
): EntityFilterCardConfig {
|
||||
const maxEntities = 3;
|
||||
const foundEntities = findEntities(
|
||||
hass,
|
||||
maxEntities,
|
||||
entities,
|
||||
entitiesFallback,
|
||||
["light", "switch", "sensor"]
|
||||
);
|
||||
|
||||
return {
|
||||
type: "entity-filter",
|
||||
entities: foundEntities,
|
||||
state_filter: [
|
||||
foundEntities[0] ? hass.states[foundEntities[0]].state : "",
|
||||
],
|
||||
card: { type: "entities" },
|
||||
};
|
||||
}
|
||||
|
||||
@property({ attribute: false }) public hass?: HomeAssistant;
|
||||
|
||||
@property() public isPanel = false;
|
||||
|
@@ -14,13 +14,12 @@ import { styleMap } from "lit-html/directives/style-map";
|
||||
import { applyThemesOnElement } from "../../../common/dom/apply_themes_on_element";
|
||||
import { fireEvent } from "../../../common/dom/fire_event";
|
||||
import { computeStateName } from "../../../common/entity/compute_state_name";
|
||||
import { stateIcon } from "../../../common/entity/state_icon";
|
||||
import { isValidEntityId } from "../../../common/entity/valid_entity_id";
|
||||
import "../../../components/ha-card";
|
||||
import "../../../components/ha-gauge";
|
||||
import { UNAVAILABLE } from "../../../data/entity";
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
import { findEntities } from "../common/find-entites";
|
||||
import { findEntities } from "../common/find-entities";
|
||||
import { hasConfigOrEntityChanged } from "../common/has-changed";
|
||||
import { createEntityNotFoundWarning } from "../components/hui-warning";
|
||||
import type { LovelaceCard, LovelaceCardEditor } from "../types";
|
||||
@@ -140,11 +139,8 @@ class HuiGaugeCard extends LitElement implements LovelaceCard {
|
||||
"--gauge-color": this._computeSeverity(state),
|
||||
})}
|
||||
></ha-gauge>
|
||||
<div class="row">
|
||||
<ha-icon .icon=${this._config.icon || stateIcon(stateObj)}></ha-icon>
|
||||
<div class="name">
|
||||
${this._config.name || computeStateName(stateObj)}
|
||||
</div>
|
||||
<div class="name">
|
||||
${this._config.name || computeStateName(stateObj)}
|
||||
</div>
|
||||
</ha-card>
|
||||
`;
|
||||
@@ -236,25 +232,13 @@ class HuiGaugeCard extends LitElement implements LovelaceCard {
|
||||
max-width: 250px;
|
||||
}
|
||||
|
||||
.row {
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
margin-top: 4px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.name {
|
||||
text-align: center;
|
||||
line-height: initial;
|
||||
color: var(--secondary-text-color);
|
||||
color: var(--primary-text-color);
|
||||
width: 100%;
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
ha-icon {
|
||||
color: var(--state-icon-color, #44739e);
|
||||
margin-right: 4px;
|
||||
font-size: 15px;
|
||||
margin-top: 8px;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
@@ -27,7 +27,7 @@ import {
|
||||
} from "../../../data/lovelace";
|
||||
import { HomeAssistant } from "../../../types";
|
||||
import { actionHandler } from "../common/directives/action-handler-directive";
|
||||
import { findEntities } from "../common/find-entites";
|
||||
import { findEntities } from "../common/find-entities";
|
||||
import { handleAction } from "../common/handle-action";
|
||||
import { hasAction } from "../common/has-action";
|
||||
import { processConfigEntities } from "../common/process-config-entities";
|
||||
|
@@ -21,7 +21,7 @@ import "../../../components/ha-icon-button";
|
||||
import { UNAVAILABLE_STATES } from "../../../data/entity";
|
||||
import { HumidifierEntity } from "../../../data/humidifier";
|
||||
import { HomeAssistant } from "../../../types";
|
||||
import { findEntities } from "../common/find-entites";
|
||||
import { findEntities } from "../common/find-entities";
|
||||
import { hasConfigOrEntityChanged } from "../common/has-changed";
|
||||
import { createEntityNotFoundWarning } from "../components/hui-warning";
|
||||
import { LovelaceCard, LovelaceCardEditor } from "../types";
|
||||
|
@@ -26,7 +26,7 @@ import { LightEntity, SUPPORT_BRIGHTNESS } from "../../../data/light";
|
||||
import { ActionHandlerEvent } from "../../../data/lovelace";
|
||||
import { HomeAssistant } from "../../../types";
|
||||
import { actionHandler } from "../common/directives/action-handler-directive";
|
||||
import { findEntities } from "../common/find-entites";
|
||||
import { findEntities } from "../common/find-entities";
|
||||
import { handleAction } from "../common/handle-action";
|
||||
import { hasAction } from "../common/has-action";
|
||||
import { hasConfigOrEntityChanged } from "../common/has-changed";
|
||||
|
@@ -19,7 +19,7 @@ import "../../../components/ha-circular-progress";
|
||||
import { getLogbookData, LogbookEntry } from "../../../data/logbook";
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
import "../../logbook/ha-logbook";
|
||||
import { findEntities } from "../common/find-entites";
|
||||
import { findEntities } from "../common/find-entities";
|
||||
import { processConfigEntities } from "../common/process-config-entities";
|
||||
import "../components/hui-warning";
|
||||
import type { EntityConfig } from "../entity-rows/types";
|
||||
|
@@ -35,7 +35,7 @@ import "../../../components/ha-icon-button";
|
||||
import { fetchRecent } from "../../../data/history";
|
||||
import { HomeAssistant } from "../../../types";
|
||||
import "../../map/ha-entity-marker";
|
||||
import { findEntities } from "../common/find-entites";
|
||||
import { findEntities } from "../common/find-entities";
|
||||
import { installResizeObserver } from "../common/install-resize-observer";
|
||||
import { processConfigEntities } from "../common/process-config-entities";
|
||||
import { EntityConfig } from "../entity-rows/types";
|
||||
|
@@ -40,7 +40,7 @@ import {
|
||||
SUPPORT_TURN_ON,
|
||||
} from "../../../data/media-player";
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
import { findEntities } from "../common/find-entites";
|
||||
import { findEntities } from "../common/find-entities";
|
||||
import { hasConfigOrEntityChanged } from "../common/has-changed";
|
||||
import { installResizeObserver } from "../common/install-resize-observer";
|
||||
import "../components/hui-marquee";
|
||||
@@ -332,7 +332,11 @@ export class HuiMediaControlCard extends LitElement implements LovelaceCard {
|
||||
protected updated(changedProps: PropertyValues): void {
|
||||
super.updated(changedProps);
|
||||
|
||||
if (!this._config || !this.hass || (!changedProps.has("_config") && !changedProps.has("hass"))) {
|
||||
if (
|
||||
!this._config ||
|
||||
!this.hass ||
|
||||
(!changedProps.has("_config") && !changedProps.has("hass"))
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@@ -12,7 +12,7 @@ import {
|
||||
import { applyThemesOnElement } from "../../../common/dom/apply_themes_on_element";
|
||||
import "../../../components/ha-card";
|
||||
import { HomeAssistant } from "../../../types";
|
||||
import { findEntities } from "../common/find-entites";
|
||||
import { findEntities } from "../common/find-entities";
|
||||
import { LovelaceElement, LovelaceElementConfig } from "../elements/types";
|
||||
import { LovelaceCard } from "../types";
|
||||
import { createStyledHuiElement } from "./picture-elements/create-styled-hui-element";
|
||||
|
@@ -20,7 +20,7 @@ import { UNAVAILABLE_STATES } from "../../../data/entity";
|
||||
import { ActionHandlerEvent } from "../../../data/lovelace";
|
||||
import { HomeAssistant } from "../../../types";
|
||||
import { actionHandler } from "../common/directives/action-handler-directive";
|
||||
import { findEntities } from "../common/find-entites";
|
||||
import { findEntities } from "../common/find-entities";
|
||||
import { handleAction } from "../common/handle-action";
|
||||
import { hasAction } from "../common/has-action";
|
||||
import { hasConfigOrEntityChanged } from "../common/has-changed";
|
||||
|
@@ -22,7 +22,7 @@ import "../../../components/ha-icon-button";
|
||||
import { ActionHandlerEvent } from "../../../data/lovelace";
|
||||
import { HomeAssistant } from "../../../types";
|
||||
import { actionHandler } from "../common/directives/action-handler-directive";
|
||||
import { findEntities } from "../common/find-entites";
|
||||
import { findEntities } from "../common/find-entities";
|
||||
import { handleAction } from "../common/handle-action";
|
||||
import { hasAction } from "../common/has-action";
|
||||
import { hasConfigOrEntityChanged } from "../common/has-changed";
|
||||
|
@@ -17,7 +17,7 @@ import "../../../components/ha-card";
|
||||
import "../../../components/ha-icon";
|
||||
import { HomeAssistant } from "../../../types";
|
||||
import { actionHandler } from "../common/directives/action-handler-directive";
|
||||
import { findEntities } from "../common/find-entites";
|
||||
import { findEntities } from "../common/find-entities";
|
||||
import { hasConfigOrEntityChanged } from "../common/has-changed";
|
||||
import { createEntityNotFoundWarning } from "../components/hui-warning";
|
||||
import { LovelaceCard, LovelaceCardEditor } from "../types";
|
||||
|
@@ -1,7 +1,7 @@
|
||||
import { HassEntity } from "home-assistant-js-websocket/dist/types";
|
||||
import { customElement } from "lit-element";
|
||||
import { HomeAssistant } from "../../../types";
|
||||
import { findEntities } from "../common/find-entites";
|
||||
import { findEntities } from "../common/find-entities";
|
||||
import { GraphHeaderFooterConfig } from "../header-footer/types";
|
||||
import { LovelaceCardEditor } from "../types";
|
||||
import { HuiEntityCard } from "./hui-entity-card";
|
||||
|
@@ -31,7 +31,7 @@ import {
|
||||
} from "../../../data/climate";
|
||||
import { UNAVAILABLE } from "../../../data/entity";
|
||||
import { HomeAssistant } from "../../../types";
|
||||
import { findEntities } from "../common/find-entites";
|
||||
import { findEntities } from "../common/find-entities";
|
||||
import { hasConfigOrEntityChanged } from "../common/has-changed";
|
||||
import { createEntityNotFoundWarning } from "../components/hui-warning";
|
||||
import { LovelaceCard, LovelaceCardEditor } from "../types";
|
||||
@@ -132,27 +132,30 @@ export class HuiThermostatCard extends LitElement implements LovelaceCard {
|
||||
></round-slider>
|
||||
`;
|
||||
|
||||
const currentTemperature = !isNaN(stateObj.attributes.current_temperature)
|
||||
? svg`
|
||||
<svg viewBox="0 0 40 20">
|
||||
<text
|
||||
x="50%"
|
||||
dx="1"
|
||||
y="60%"
|
||||
text-anchor="middle"
|
||||
style="font-size: 13px;"
|
||||
>
|
||||
${formatNumber(
|
||||
stateObj.attributes.current_temperature,
|
||||
this.hass!.language
|
||||
)}
|
||||
<tspan dx="-3" dy="-6.5" style="font-size: 4px;">
|
||||
${this.hass.config.unit_system.temperature}
|
||||
</tspan>
|
||||
</text>
|
||||
</svg>
|
||||
`
|
||||
: "";
|
||||
const currentTemperature = svg`
|
||||
<svg viewBox="0 0 40 20">
|
||||
<text
|
||||
x="50%"
|
||||
dx="1"
|
||||
y="60%"
|
||||
text-anchor="middle"
|
||||
style="font-size: 13px;"
|
||||
>
|
||||
${
|
||||
stateObj.attributes.current_temperature !== null &&
|
||||
!isNaN(stateObj.attributes.current_temperature)
|
||||
? svg`${formatNumber(
|
||||
stateObj.attributes.current_temperature,
|
||||
this.hass!.language
|
||||
)}
|
||||
<tspan dx="-3" dy="-6.5" style="font-size: 4px;">
|
||||
${this.hass.config.unit_system.temperature}
|
||||
</tspan>`
|
||||
: ""
|
||||
}
|
||||
</text>
|
||||
</svg>
|
||||
`;
|
||||
|
||||
const setValues = svg`
|
||||
<svg id="set-values">
|
||||
|
@@ -35,7 +35,7 @@ import {
|
||||
weatherSVGStyles,
|
||||
} from "../../../data/weather";
|
||||
import { actionHandler } from "../common/directives/action-handler-directive";
|
||||
import { findEntities } from "../common/find-entites";
|
||||
import { findEntities } from "../common/find-entities";
|
||||
import { handleAction } from "../common/handle-action";
|
||||
import { hasAction } from "../common/has-action";
|
||||
import { hasConfigOrEntityChanged } from "../common/has-changed";
|
||||
|
@@ -92,7 +92,7 @@ export interface EntityFilterCardConfig extends LovelaceCardConfig {
|
||||
type: "entity-filter";
|
||||
entities: Array<EntityFilterEntityConfig | string>;
|
||||
state_filter: Array<{ key: string } | string>;
|
||||
card: Partial<LovelaceCardConfig>;
|
||||
card?: Partial<LovelaceCardConfig>;
|
||||
show_empty?: boolean;
|
||||
}
|
||||
|
||||
@@ -115,7 +115,6 @@ export interface GaugeCardConfig extends LovelaceCardConfig {
|
||||
max?: number;
|
||||
severity?: SeverityConfig;
|
||||
theme?: string;
|
||||
icon?: string;
|
||||
}
|
||||
|
||||
export interface ConfigEntity extends EntityConfig {
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user