Compare commits

..
Author SHA1 Message Date
Ludeeus 2a4cebf724 Block update on error and not running state 2021-03-01 23:12:13 +00:00
197 changed files with 3246 additions and 22381 deletions
+1 -1
View File
@@ -7,7 +7,7 @@ on:
branches:
- dev
paths:
- src/translations/en.json
- translations/en.json
env:
NODE_VERSION: 12
-5
View File
@@ -85,11 +85,6 @@ gulp.task("copy-translations-app", async () => {
copyTranslations(staticDir);
});
gulp.task("copy-translations-supervisor", async () => {
const staticDir = paths.hassio_output_static;
copyTranslations(staticDir);
});
gulp.task("copy-static-app", async () => {
const staticDir = paths.app_output_static;
// Basic static files
-6
View File
@@ -10,8 +10,6 @@ require("./gen-icons-json.js");
require("./webpack.js");
require("./compress.js");
require("./rollup.js");
require("./gather-static.js");
require("./translations.js");
gulp.task(
"develop-hassio",
@@ -22,8 +20,6 @@ gulp.task(
"clean-hassio",
"gen-icons-json",
"gen-index-hassio-dev",
"build-supervisor-translations",
"copy-translations-supervisor",
env.useRollup() ? "rollup-watch-hassio" : "webpack-watch-hassio"
)
);
@@ -36,8 +32,6 @@ gulp.task(
},
"clean-hassio",
"gen-icons-json",
"build-supervisor-translations",
"copy-translations-supervisor",
env.useRollup() ? "rollup-prod-hassio" : "webpack-prod-hassio",
"gen-index-hassio-prod",
...// Don't compress running tests
+36 -72
View File
@@ -266,7 +266,6 @@ gulp.task(taskName, function () {
TRANSLATION_FRAGMENTS.forEach((fragment) => {
delete data.ui.panel[fragment];
});
delete data.supervisor;
return data;
})
)
@@ -343,62 +342,6 @@ gulp.task(
}
);
gulp.task("build-translation-fragment-supervisor", function () {
return gulp
.src(fullDir + "/*.json")
.pipe(transform((data) => data.supervisor))
.pipe(gulp.dest(workDir + "/supervisor"));
});
gulp.task("build-translation-flatten-supervisor", function () {
return gulp
.src(workDir + "/supervisor/*.json")
.pipe(
transform(function (data) {
// Polymer.AppLocalizeBehavior requires flattened json
return flatten(data);
})
)
.pipe(gulp.dest(outDir));
});
gulp.task("build-translation-write-metadata", function writeMetadata() {
return gulp
.src(
[
path.join(paths.translations_src, "translationMetadata.json"),
workDir + "/testMetadata.json",
workDir + "/translationFingerprints.json",
],
{ allowEmpty: true }
)
.pipe(merge({}))
.pipe(
transform(function (data) {
const newData = {};
Object.entries(data).forEach(([key, value]) => {
// Filter out translations without native name.
if (value.nativeName) {
newData[key] = value;
} else {
console.warn(
`Skipping language ${key}. Native name was not translated.`
);
}
});
return newData;
})
)
.pipe(
transform((data) => ({
fragments: TRANSLATION_FRAGMENTS,
translations: data,
}))
)
.pipe(rename("translationMetadata.json"))
.pipe(gulp.dest(workDir));
});
gulp.task(
"build-translations",
gulp.series(
@@ -410,20 +353,41 @@ gulp.task(
gulp.parallel(...splitTasks),
"build-flattened-translations",
"build-translation-fingerprints",
"build-translation-write-metadata"
)
);
gulp.task(
"build-supervisor-translations",
gulp.series(
"clean-translations",
"ensure-translations-build-dir",
"build-master-translation",
"build-merged-translations",
"build-translation-fragment-supervisor",
"build-translation-flatten-supervisor",
"build-translation-fingerprints",
"build-translation-write-metadata"
function writeMetadata() {
return gulp
.src(
[
path.join(paths.translations_src, "translationMetadata.json"),
workDir + "/testMetadata.json",
workDir + "/translationFingerprints.json",
],
{ allowEmpty: true }
)
.pipe(merge({}))
.pipe(
transform(function (data) {
const newData = {};
Object.entries(data).forEach(([key, value]) => {
// Filter out translations without native name.
if (value.nativeName) {
newData[key] = value;
} else {
console.warn(
`Skipping language ${key}. Native name was not translated.`
);
}
});
return newData;
})
)
.pipe(
transform((data) => ({
fragments: TRANSLATION_FRAGMENTS,
translations: data,
}))
)
.pipe(rename("translationMetadata.json"))
.pipe(gulp.dest(workDir));
}
)
);
+1 -6
View File
@@ -137,12 +137,7 @@ gulp.task("webpack-watch-hassio", () => {
isProdBuild: false,
latestBuild: true,
})
).watch({ ignored: /build-translations/ }, doneHandler());
gulp.watch(
path.join(paths.translations_src, "en.json"),
gulp.series("build-supervisor-translations", "copy-translations-supervisor")
);
).watch({}, doneHandler());
});
gulp.task("webpack-prod-hassio", () =>
-1
View File
@@ -34,7 +34,6 @@ module.exports = {
hassio_dir: path.resolve(__dirname, "../hassio"),
hassio_output_root: path.resolve(__dirname, "../hassio/build"),
hassio_output_static: path.resolve(__dirname, "../hassio/build/static"),
hassio_output_latest: path.resolve(
__dirname,
"../hassio/build/frontend_latest"
+1 -1
View File
@@ -100,7 +100,7 @@ class HcLayout extends LitElement {
display: block;
margin: 0;
}
.hero {
border-radius: 4px 4px 0 0;
}
-1
View File
@@ -94,7 +94,6 @@ class HcLovelace extends LitElement {
return css`
:host {
min-height: 100vh;
height: 0;
display: flex;
flex-direction: column;
box-sizing: border-box;
-383
View File
@@ -1,383 +0,0 @@
import { DemoTrace } from "./types";
export const basicTrace: DemoTrace = {
trace: {
last_action: "action/2",
last_condition: "condition/0",
run_id: "0",
state: "stopped",
timestamp: {
start: "2021-03-25T04:36:51.223693+00:00",
finish: "2021-03-25T04:36:51.266132+00:00",
},
trigger: "state of input_boolean.toggle_1",
domain: "automation",
item_id: "1615419646544",
action_trace: {
"action/0": [
{
path: "action/0",
timestamp: "2021-03-25T04:36:51.243018+00:00",
changed_variables: {
trigger: {
platform: "state",
entity_id: "input_boolean.toggle_1",
from_state: {
entity_id: "input_boolean.toggle_1",
state: "on",
attributes: {
editable: true,
friendly_name: "Toggle 1",
},
last_changed: "2021-03-24T19:03:59.141440+00:00",
last_updated: "2021-03-24T19:03:59.141440+00:00",
context: {
id: "5d0918eb379214d07554bdab6a08bcff",
parent_id: null,
user_id: null,
},
},
to_state: {
entity_id: "input_boolean.toggle_1",
state: "off",
attributes: {
editable: true,
friendly_name: "Toggle 1",
},
last_changed: "2021-03-25T04:36:51.220696+00:00",
last_updated: "2021-03-25T04:36:51.220696+00:00",
context: {
id: "664d6d261450a9ecea6738e97269a149",
parent_id: null,
user_id: "d1b4e89da01445fa8bc98e39fac477ca",
},
},
for: null,
attribute: null,
description: "state of input_boolean.toggle_1",
},
context: {
id: "6cfcae368e7b3686fad6c59e83ae76c9",
parent_id: "664d6d261450a9ecea6738e97269a149",
user_id: null,
},
},
result: {
params: {
domain: "input_boolean",
service: "toggle",
service_data: {},
target: {
entity_id: ["input_boolean.toggle_4"],
},
},
running_script: false,
limit: 10,
},
},
],
"action/1": [
{
path: "action/1",
timestamp: "2021-03-25T04:36:51.252406+00:00",
result: {
choice: 0,
},
},
],
"action/1/choose/0": [
{
path: "action/1/choose/0",
timestamp: "2021-03-25T04:36:51.254569+00:00",
result: {
result: true,
},
},
],
"action/1/choose/0/conditions/0": [
{
path: "action/1/choose/0/conditions/0",
timestamp: "2021-03-25T04:36:51.254697+00:00",
result: {
result: true,
},
},
],
"action/1/choose/0/sequence/0": [
{
path: "action/1/choose/0/sequence/0",
timestamp: "2021-03-25T04:36:51.257360+00:00",
result: {
params: {
domain: "input_boolean",
service: "toggle",
service_data: {},
target: {
entity_id: ["input_boolean.toggle_2"],
},
},
running_script: false,
limit: 10,
},
},
],
"action/1/choose/0/sequence/1": [
{
path: "action/1/choose/0/sequence/1",
timestamp: "2021-03-25T04:36:51.260658+00:00",
result: {
params: {
domain: "input_boolean",
service: "toggle",
service_data: {},
target: {
entity_id: ["input_boolean.toggle_3"],
},
},
running_script: false,
limit: 10,
},
},
],
"action/2": [
{
path: "action/2",
timestamp: "2021-03-25T04:36:51.264159+00:00",
result: {
params: {
domain: "input_boolean",
service: "toggle",
service_data: {},
target: {
entity_id: ["input_boolean.toggle_4"],
},
},
running_script: false,
limit: 10,
},
},
],
},
condition_trace: {
"condition/0": [
{
path: "condition/0",
timestamp: "2021-03-25T04:36:51.228243+00:00",
changed_variables: {
trigger: {
platform: "state",
entity_id: "input_boolean.toggle_1",
from_state: {
entity_id: "input_boolean.toggle_1",
state: "on",
attributes: {
editable: true,
friendly_name: "Toggle 1",
},
last_changed: "2021-03-24T19:03:59.141440+00:00",
last_updated: "2021-03-24T19:03:59.141440+00:00",
context: {
id: "5d0918eb379214d07554bdab6a08bcff",
parent_id: null,
user_id: null,
},
},
to_state: {
entity_id: "input_boolean.toggle_1",
state: "off",
attributes: {
editable: true,
friendly_name: "Toggle 1",
},
last_changed: "2021-03-25T04:36:51.220696+00:00",
last_updated: "2021-03-25T04:36:51.220696+00:00",
context: {
id: "664d6d261450a9ecea6738e97269a149",
parent_id: null,
user_id: "d1b4e89da01445fa8bc98e39fac477ca",
},
},
for: null,
attribute: null,
description: "state of input_boolean.toggle_1",
},
},
result: {
result: true,
},
},
],
},
config: {
id: "1615419646544",
alias: "Ensure Party mode",
description: "",
trigger: [
{
platform: "state",
entity_id: "input_boolean.toggle_1",
},
],
condition: [
{
condition: "template",
alias: "Test if Paulus is home",
value_template: "{{ true }}",
},
],
action: [
{
service: "input_boolean.toggle",
target: {
entity_id: "input_boolean.toggle_4",
},
},
{
choose: [
{
alias: "If toggle 3 is on",
conditions: [
{
condition: "template",
value_template:
"{{ is_state('input_boolean.toggle_3', 'on') }}",
},
],
sequence: [
{
service: "input_boolean.toggle",
alias: "Toggle 2 while 3 is on",
target: {
entity_id: "input_boolean.toggle_2",
},
},
{
service: "input_boolean.toggle",
alias: "Toggle 3",
target: {
entity_id: "input_boolean.toggle_3",
},
},
],
},
],
default: [
{
service: "input_boolean.toggle",
alias: "Toggle 2",
target: {
entity_id: "input_boolean.toggle_2",
},
},
],
},
{
service: "input_boolean.toggle",
target: {
entity_id: "input_boolean.toggle_4",
},
},
],
mode: "single",
},
context: {
id: "6cfcae368e7b3686fad6c59e83ae76c9",
parent_id: "664d6d261450a9ecea6738e97269a149",
user_id: null,
},
variables: {
trigger: {
platform: "state",
entity_id: "input_boolean.toggle_1",
from_state: {
entity_id: "input_boolean.toggle_1",
state: "on",
attributes: {
editable: true,
friendly_name: "Toggle 1",
},
last_changed: "2021-03-24T19:03:59.141440+00:00",
last_updated: "2021-03-24T19:03:59.141440+00:00",
context: {
id: "5d0918eb379214d07554bdab6a08bcff",
parent_id: null,
user_id: null,
},
},
to_state: {
entity_id: "input_boolean.toggle_1",
state: "off",
attributes: {
editable: true,
friendly_name: "Toggle 1",
},
last_changed: "2021-03-25T04:36:51.220696+00:00",
last_updated: "2021-03-25T04:36:51.220696+00:00",
context: {
id: "664d6d261450a9ecea6738e97269a149",
parent_id: null,
user_id: "d1b4e89da01445fa8bc98e39fac477ca",
},
},
for: null,
attribute: null,
description: "state of input_boolean.toggle_1",
},
},
},
logbookEntries: [
{
name: "Ensure Party mode",
message: "has been triggered by state of input_boolean.toggle_1",
source: "state of input_boolean.toggle_1",
entity_id: "automation.toggle_toggles",
context_id: "6cfcae368e7b3686fad6c59e83ae76c9",
when: "2021-03-25T04:36:51.240832+00:00",
domain: "automation",
},
{
when: "2021-03-25T04:36:51.249828+00:00",
name: "Toggle 4",
state: "on",
entity_id: "input_boolean.toggle_4",
context_entity_id: "automation.toggle_toggles",
context_entity_id_name: "Ensure Party mode",
context_event_type: "automation_triggered",
context_domain: "automation",
context_name: "Ensure Party mode",
},
{
when: "2021-03-25T04:36:51.258947+00:00",
name: "Toggle 2",
state: "on",
entity_id: "input_boolean.toggle_2",
context_entity_id: "automation.toggle_toggles",
context_entity_id_name: "Ensure Party mode",
context_event_type: "automation_triggered",
context_domain: "automation",
context_name: "Ensure Party mode",
},
{
when: "2021-03-25T04:36:51.261806+00:00",
name: "Toggle 3",
state: "off",
entity_id: "input_boolean.toggle_3",
context_entity_id: "automation.toggle_toggles",
context_entity_id_name: "Ensure Party mode",
context_event_type: "automation_triggered",
context_domain: "automation",
context_name: "Ensure Party mode",
},
{
when: "2021-03-25T04:36:51.265246+00:00",
name: "Toggle 4",
state: "off",
entity_id: "input_boolean.toggle_4",
context_entity_id: "automation.toggle_toggles",
context_entity_id_name: "Ensure Party mode",
context_event_type: "automation_triggered",
context_domain: "automation",
context_name: "Ensure Party mode",
},
],
};
@@ -1,248 +0,0 @@
import { DemoTrace } from "./types";
export const motionLightTrace: DemoTrace = {
trace: {
last_action: "action/3",
last_condition: null,
run_id: "1",
state: "stopped",
timestamp: {
start: "2021-03-14T06:07:01.768006+00:00",
finish: "2021-03-14T06:07:53.287525+00:00",
},
trigger: "state of binary_sensor.pauluss_macbook_pro_camera_in_use",
domain: "automation",
item_id: "1614732497392",
action_trace: {
"action/0": [
{
path: "action/0",
timestamp: "2021-03-14T06:07:01.771038+00:00",
changed_variables: {
trigger: {
platform: "state",
entity_id: "binary_sensor.pauluss_macbook_pro_camera_in_use",
from_state: {
entity_id: "binary_sensor.pauluss_macbook_pro_camera_in_use",
state: "off",
attributes: {
friendly_name: "Pauluss MacBook Pro Camera In Use",
icon: "mdi:camera-off",
},
last_changed: "2021-03-14T06:06:29.235325+00:00",
last_updated: "2021-03-14T06:06:29.235325+00:00",
context: {
id: "ad4864c5ce957c38a07b50378eeb245d",
parent_id: null,
user_id: null,
},
},
to_state: {
entity_id: "binary_sensor.pauluss_macbook_pro_camera_in_use",
state: "on",
attributes: {
friendly_name: "Pauluss MacBook Pro Camera In Use",
icon: "mdi:camera",
},
last_changed: "2021-03-14T06:07:01.762009+00:00",
last_updated: "2021-03-14T06:07:01.762009+00:00",
context: {
id: "e22ddfd5f11dc4aad9a52fc10dab613b",
parent_id: null,
user_id: null,
},
},
for: null,
attribute: null,
description:
"state of binary_sensor.pauluss_macbook_pro_camera_in_use",
},
context: {
id: "43b6ee9293a551c5cc14e8eb60af54ba",
parent_id: "e22ddfd5f11dc4aad9a52fc10dab613b",
user_id: null,
},
},
},
],
"action/1": [
{ path: "action/1", timestamp: "2021-03-14T06:07:01.875316+00:00" },
],
"action/2": [
{
path: "action/2",
timestamp: "2021-03-14T06:07:53.195013+00:00",
changed_variables: {
wait: {
remaining: null,
trigger: {
platform: "state",
entity_id: "binary_sensor.pauluss_macbook_pro_camera_in_use",
from_state: {
entity_id: "binary_sensor.pauluss_macbook_pro_camera_in_use",
state: "on",
attributes: {
friendly_name: "Pauluss MacBook Pro Camera In Use",
icon: "mdi:camera",
},
last_changed: "2021-03-14T06:07:01.762009+00:00",
last_updated: "2021-03-14T06:07:01.762009+00:00",
context: {
id: "e22ddfd5f11dc4aad9a52fc10dab613b",
parent_id: null,
user_id: null,
},
},
to_state: {
entity_id: "binary_sensor.pauluss_macbook_pro_camera_in_use",
state: "off",
attributes: {
friendly_name: "Pauluss MacBook Pro Camera In Use",
icon: "mdi:camera-off",
},
last_changed: "2021-03-14T06:07:53.186755+00:00",
last_updated: "2021-03-14T06:07:53.186755+00:00",
context: {
id: "b2308cc91d509ea8e0c623331ab178d6",
parent_id: null,
user_id: null,
},
},
for: null,
attribute: null,
description:
"state of binary_sensor.pauluss_macbook_pro_camera_in_use",
},
},
},
},
],
"action/3": [
{
path: "action/3",
timestamp: "2021-03-14T06:07:53.196014+00:00",
},
],
},
condition_trace: {},
config: {
mode: "restart",
max_exceeded: "silent",
trigger: [
{
platform: "state",
entity_id: "binary_sensor.pauluss_macbook_pro_camera_in_use",
from: "off",
to: "on",
},
],
action: [
{
service: "light.turn_on",
target: {
entity_id: "light.elgato_key_light_air",
},
},
{
wait_for_trigger: [
{
platform: "state",
entity_id: "binary_sensor.pauluss_macbook_pro_camera_in_use",
from: "on",
to: "off",
},
],
},
{
delay: 0,
},
{
service: "light.turn_off",
target: {
entity_id: "light.elgato_key_light_air",
},
},
],
id: "1614732497392",
alias: "Auto Elgato",
description: "",
},
context: {
id: "43b6ee9293a551c5cc14e8eb60af54ba",
parent_id: "e22ddfd5f11dc4aad9a52fc10dab613b",
user_id: null,
},
variables: {
trigger: {
platform: "state",
entity_id: "binary_sensor.pauluss_macbook_pro_camera_in_use",
from_state: {
entity_id: "binary_sensor.pauluss_macbook_pro_camera_in_use",
state: "off",
attributes: {
friendly_name: "Pauluss MacBook Pro Camera In Use",
icon: "mdi:camera-off",
},
last_changed: "2021-03-14T06:06:29.235325+00:00",
last_updated: "2021-03-14T06:06:29.235325+00:00",
context: {
id: "ad4864c5ce957c38a07b50378eeb245d",
parent_id: null,
user_id: null,
},
},
to_state: {
entity_id: "binary_sensor.pauluss_macbook_pro_camera_in_use",
state: "on",
attributes: {
friendly_name: "Pauluss MacBook Pro Camera In Use",
icon: "mdi:camera",
},
last_changed: "2021-03-14T06:07:01.762009+00:00",
last_updated: "2021-03-14T06:07:01.762009+00:00",
context: {
id: "e22ddfd5f11dc4aad9a52fc10dab613b",
parent_id: null,
user_id: null,
},
},
for: null,
attribute: null,
description: "state of binary_sensor.pauluss_macbook_pro_camera_in_use",
},
},
},
logbookEntries: [
{
name: "Auto Elgato",
message:
"has been triggered by state of binary_sensor.pauluss_macbook_pro_camera_in_use",
source: "state of binary_sensor.pauluss_macbook_pro_camera_in_use",
entity_id: "automation.auto_elgato",
when: "2021-03-14T06:07:01.768492+00:00",
domain: "automation",
},
{
when: "2021-03-14T06:07:01.872187+00:00",
name: "Elgato Key Light Air",
state: "on",
entity_id: "light.elgato_key_light_air",
context_entity_id: "automation.auto_elgato",
context_entity_id_name: "Auto Elgato",
context_event_type: "automation_triggered",
context_domain: "automation",
context_name: "Auto Elgato",
},
{
when: "2021-03-14T06:07:53.284505+00:00",
name: "Elgato Key Light Air",
state: "off",
entity_id: "light.elgato_key_light_air",
context_entity_id: "automation.auto_elgato",
context_entity_id_name: "Auto Elgato",
context_event_type: "automation_triggered",
context_domain: "automation",
context_name: "Auto Elgato",
},
],
};
-7
View File
@@ -1,7 +0,0 @@
import { AutomationTraceExtended } from "../../../../src/data/trace";
import { LogbookEntry } from "../../../../src/data/logbook";
export interface DemoTrace {
trace: AutomationTraceExtended;
logbookEntries: LogbookEntry[];
}
@@ -1,64 +0,0 @@
import {
customElement,
html,
css,
LitElement,
TemplateResult,
property,
} from "lit-element";
import "../../../src/components/ha-card";
import "../../../src/components/trace/hat-trace";
import { provideHass } from "../../../src/fake_data/provide_hass";
import { HomeAssistant } from "../../../src/types";
import { DemoTrace } from "../data/traces/types";
import { basicTrace } from "../data/traces/basic_trace";
import { motionLightTrace } from "../data/traces/motion-light-trace";
const traces: DemoTrace[] = [basicTrace, motionLightTrace];
@customElement("demo-automation-trace")
export class DemoAutomationTrace extends LitElement {
@property({ attribute: false }) hass?: HomeAssistant;
protected render(): TemplateResult {
if (!this.hass) {
return html``;
}
return html`
${traces.map(
(trace) => html`
<ha-card .heading=${trace.trace.config.alias}>
<div class="card-content">
<hat-trace
.hass=${this.hass}
.trace=${trace.trace}
.logbookEntries=${trace.logbookEntries}
></hat-trace>
</div>
</ha-card>
`
)}
`;
}
protected firstUpdated(changedProps) {
super.firstUpdated(changedProps);
const hass = provideHass(this);
hass.updateTranslations(null, "en");
}
static get styles() {
return css`
ha-card {
max-width: 600px;
margin: 24px;
}
`;
}
}
declare global {
interface HTMLElementTagNameMap {
"demo-automation-trace": DemoAutomationTrace;
}
}
+1 -5
View File
@@ -81,8 +81,4 @@ class DemoMoreInfoLight extends LitElement {
}
}
declare global {
interface HTMLElementTagNameMap {
"demo-more-info-light": DemoMoreInfoLight;
}
}
customElements.define("demo-more-info-light", DemoMoreInfoLight);
+35 -7
View File
@@ -111,9 +111,29 @@ class HaGallery extends PolymerElement {
</template>
</ha-card>
<ha-card header="Other Demos">
<div class="card-content intro"></div>
<template is="dom-repeat" items="[[_restDemos]]">
<ha-card header="More Info Demos">
<div class="card-content intro">
<p>
More info screens show up when an entity is clicked.
</p>
</div>
<template is="dom-repeat" items="[[_moreInfoDemos]]">
<a href="#[[item]]">
<paper-item>
<paper-item-body>{{ item }}</paper-item-body>
<ha-icon icon="hass:chevron-right"></ha-icon>
</paper-item>
</a>
</template>
</ha-card>
<ha-card header="Util Demos">
<div class="card-content intro">
<p>
Test pages for our utility functions.
</p>
</div>
<template is="dom-repeat" items="[[_utilDemos]]">
<a href="#[[item]]">
<paper-item>
<paper-item-body>{{ item }}</paper-item-body>
@@ -158,9 +178,13 @@ class HaGallery extends PolymerElement {
type: Array,
computed: "_computeLovelace(_demos)",
},
_restDemos: {
_moreInfoDemos: {
type: Array,
computed: "_computeRest(_demos)",
computed: "_computeMoreInfos(_demos)",
},
_utilDemos: {
type: Array,
computed: "_computeUtil(_demos)",
},
};
}
@@ -213,8 +237,12 @@ class HaGallery extends PolymerElement {
return demos.filter((demo) => demo.includes("hui"));
}
_computeRest(demos) {
return demos.filter((demo) => !demo.includes("hui"));
_computeMoreInfos(demos) {
return demos.filter((demo) => demo.includes("more-info"));
}
_computeUtil(demos) {
return demos.filter((demo) => demo.includes("util"));
}
}
@@ -15,7 +15,6 @@ import {
HassioAddonInfo,
HassioAddonRepository,
} from "../../../src/data/hassio/addon";
import { Supervisor } from "../../../src/data/supervisor/supervisor";
import { HomeAssistant } from "../../../src/types";
import "../components/hassio-card-content";
import { filterAndSort } from "../components/hassio-filter-addons";
@@ -24,8 +23,6 @@ import { hassioStyle } from "../resources/hassio-style";
class HassioAddonRepositoryEl extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property({ attribute: false }) public supervisor!: Supervisor;
@property({ attribute: false }) public repo!: HassioAddonRepository;
@property({ attribute: false }) public addons!: HassioAddonInfo[];
@@ -57,11 +54,7 @@ class HassioAddonRepositoryEl extends LitElement {
return html`
<div class="content">
<p class="description">
${this.supervisor.localize(
"store.no_results_found",
"repository",
repo.name
)}
No results found in "${repo.name}."
</p>
</div>
`;
@@ -90,13 +83,11 @@ class HassioAddonRepositoryEl extends LitElement {
: mdiPuzzle}
.iconTitle=${addon.installed
? addon.update_available
? this.supervisor.localize(
"common.new_version_available"
)
: this.supervisor.localize("addon.installed")
? "New version available"
: "Add-on is installed"
: addon.available
? this.supervisor.localize("addon.not_installed")
: this.supervisor.localize("addon.not_available")}
? "Add-on is not installed"
: "Add-on is not available on your system"}
.iconClass=${addon.installed
? addon.update_available
? "update"
+17 -31
View File
@@ -14,9 +14,7 @@ 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 { navigate } from "../../../src/common/navigate";
import "../../../src/common/search/search-input";
import { extractSearchParam } from "../../../src/common/url/search-params";
import "../../../src/components/ha-button-menu";
import "../../../src/components/ha-svg-icon";
import {
@@ -79,16 +77,13 @@ class HassioAddonStore extends LitElement {
return html`
<hass-tabs-subpage
.hass=${this.hass}
.localizeFunc=${this.supervisor.localize}
.narrow=${this.narrow}
.route=${this.route}
.tabs=${supervisorTabs}
hassio
main-page
supervisor
.tabs=${supervisorTabs}
>
<span slot="header">
${this.supervisor.localize("panel.store")}
</span>
<span slot="header">Add-on Store</span>
<ha-button-menu
corner="BOTTOM_START"
slot="toolbar-icon"
@@ -98,15 +93,15 @@ class HassioAddonStore extends LitElement {
<ha-svg-icon .path=${mdiDotsVertical}></ha-svg-icon>
</mwc-icon-button>
<mwc-list-item>
${this.supervisor.localize("store.repositories")}
Repositories
</mwc-list-item>
<mwc-list-item>
${this.supervisor.localize("common.reload")}
Reload
</mwc-list-item>
${this.hass.userData?.showAdvanced &&
atLeastVersion(this.hass.config.version, 0, 117)
? html`<mwc-list-item>
${this.supervisor.localize("store.registries")}
Registries
</mwc-list-item>`
: ""}
</ha-button-menu>
@@ -127,9 +122,11 @@ class HassioAddonStore extends LitElement {
${!this.hass.userData?.showAdvanced
? html`
<div class="advanced">
Missing add-ons? Enable advanced mode on
<a href="/profile" target="_top">
${this.supervisor.localize("store.missing_addons")}
your profile page
</a>
.
</div>
`
: ""}
@@ -139,12 +136,6 @@ class HassioAddonStore extends LitElement {
protected firstUpdated(changedProps: PropertyValues) {
super.firstUpdated(changedProps);
const repositoryUrl = extractSearchParam("repository_url");
navigate(this, "/hassio/store", true);
if (repositoryUrl) {
this._manageRepositories(repositoryUrl);
}
this.addEventListener("hass-api-called", (ev) => this.apiCalled(ev));
this._loadData();
}
@@ -167,7 +158,6 @@ class HassioAddonStore extends LitElement {
.repo=${repo}
.addons=${filteredAddons}
.filter=${filter!}
.supervisor=${this.supervisor}
></hassio-addon-repository>
`
: html``;
@@ -178,7 +168,7 @@ class HassioAddonStore extends LitElement {
private _handleAction(ev: CustomEvent<ActionDetail>) {
switch (ev.detail.index) {
case 0:
this._manageRepositoriesClicked();
this._manageRepositories();
break;
case 1:
this.refreshData();
@@ -195,25 +185,21 @@ class HassioAddonStore extends LitElement {
}
}
private _manageRepositoriesClicked() {
this._manageRepositories();
}
private async _manageRepositories(url?: string) {
private async _manageRepositories() {
showRepositoriesDialog(this, {
supervisor: this.supervisor,
url,
repos: this.supervisor.addon.repositories,
loadData: () => this._loadData(),
});
}
private async _manageRegistries() {
showRegistriesDialog(this, { supervisor: this.supervisor });
showRegistriesDialog(this);
}
private async _loadData() {
fireEvent(this, "supervisor-collection-refresh", { collection: "addon" });
fireEvent(this, "supervisor-collection-refresh", {
collection: "supervisor",
fireEvent(this, "supervisor-colllection-refresh", { colllection: "addon" });
fireEvent(this, "supervisor-colllection-refresh", {
colllection: "supervisor",
});
}
@@ -25,7 +25,6 @@ import {
fetchHassioHardwareAudio,
HassioHardwareAudioDevice,
} from "../../../../src/data/hassio/hardware";
import { Supervisor } from "../../../../src/data/supervisor/supervisor";
import { haStyle } from "../../../../src/resources/styles";
import { HomeAssistant } from "../../../../src/types";
import { suggestAddonRestart } from "../../dialogs/suggestAddonRestart";
@@ -35,8 +34,6 @@ import { hassioStyle } from "../../resources/hassio-style";
class HassioAddonAudio extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property({ attribute: false }) public supervisor!: Supervisor;
@property({ attribute: false }) public addon!: HassioAddonDetails;
@internalProperty() private _error?: string;
@@ -51,16 +48,12 @@ class HassioAddonAudio extends LitElement {
protected render(): TemplateResult {
return html`
<ha-card
.header=${this.supervisor.localize("addon.configuration.audio.header")}
>
<ha-card header="Audio">
<div class="card-content">
${this._error ? html` <div class="errors">${this._error}</div> ` : ""}
<paper-dropdown-menu
.label=${this.supervisor.localize(
"addon.configuration.audio.input"
)}
label="Input"
@iron-select=${this._setInputDevice}
>
<paper-listbox
@@ -71,17 +64,15 @@ class HassioAddonAudio extends LitElement {
${this._inputDevices &&
this._inputDevices.map((item) => {
return html`
<paper-item device=${item.device || ""}>
${item.name}
</paper-item>
<paper-item device=${item.device || ""}
>${item.name}</paper-item
>
`;
})}
</paper-listbox>
</paper-dropdown-menu>
<paper-dropdown-menu
.label=${this.supervisor.localize(
"addon.configuration.audio.output"
)}
label="Output"
@iron-select=${this._setOutputDevice}
>
<paper-listbox
@@ -102,7 +93,7 @@ class HassioAddonAudio extends LitElement {
</div>
<div class="card-actions">
<ha-progress-button @click=${this._saveSettings}>
${this.supervisor.localize("common.save")}
Save
</ha-progress-button>
</div>
</ha-card>
@@ -161,7 +152,7 @@ class HassioAddonAudio extends LitElement {
const noDevice: HassioHardwareAudioDevice = {
device: "default",
name: this.supervisor.localize("addon.configuration.audio.default"),
name: "Default",
};
try {
@@ -198,7 +189,7 @@ class HassioAddonAudio extends LitElement {
try {
await setHassioAddonOption(this.hass, this.addon.slug, data);
if (this.addon?.state === "started") {
await suggestAddonRestart(this, this.hass, this.supervisor, this.addon);
await suggestAddonRestart(this, this.hass, this.addon);
}
} catch {
this._error = "Failed to set addon audio device";
@@ -9,7 +9,6 @@ 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";
@@ -21,8 +20,6 @@ import "./hassio-addon-network";
class HassioAddonConfigDashboard extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property({ attribute: false }) public supervisor!: Supervisor;
@property({ attribute: false }) public addon?: HassioAddonDetails;
protected render(): TemplateResult {
@@ -42,7 +39,6 @@ class HassioAddonConfigDashboard extends LitElement {
<hassio-addon-config
.hass=${this.hass}
.addon=${this.addon}
.supervisor=${this.supervisor}
></hassio-addon-config>
`
: ""}
@@ -51,7 +47,6 @@ class HassioAddonConfigDashboard extends LitElement {
<hassio-addon-network
.hass=${this.hass}
.addon=${this.addon}
.supervisor=${this.supervisor}
></hassio-addon-network>
`
: ""}
@@ -60,12 +55,11 @@ class HassioAddonConfigDashboard extends LitElement {
<hassio-addon-audio
.hass=${this.hass}
.addon=${this.addon}
.supervisor=${this.supervisor}
></hassio-addon-audio>
`
: ""}
`
: this.supervisor.localize("addon.configuration.no_configuration")}
: "This add-on does not expose configuration for you to mess with.... 👋"}
</div>
`;
}
@@ -32,7 +32,6 @@ import {
setHassioAddonOption,
} from "../../../../src/data/hassio/addon";
import { extractApiErrorMessage } from "../../../../src/data/hassio/common";
import { Supervisor } from "../../../../src/data/supervisor/supervisor";
import { showConfirmationDialog } from "../../../../src/dialogs/generic/show-dialog-box";
import { haStyle } from "../../../../src/resources/styles";
import type { HomeAssistant } from "../../../../src/types";
@@ -47,8 +46,6 @@ class HassioAddonConfig extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property({ attribute: false }) public supervisor!: Supervisor;
@property({ type: Boolean }) private _configHasChanged = false;
@property({ type: Boolean }) private _valid = true;
@@ -65,15 +62,6 @@ class HassioAddonConfig extends LitElement {
@query("ha-yaml-editor") private _editor?: HaYamlEditor;
public computeLabel = (entry: HaFormSchema): string => {
return (
this.addon.translations[this.hass.language]?.configuration?.[entry.name]
?.name ||
this.addon.translations.en?.configuration?.[entry.name].name ||
entry.name
);
};
private _filteredShchema = memoizeOne(
(options: Record<string, unknown>, schema: HaFormSchema[]) => {
return schema.filter((entry) => entry.name in options || entry.required);
@@ -93,25 +81,17 @@ class HassioAddonConfig extends LitElement {
<h1>${this.addon.name}</h1>
<ha-card>
<div class="header">
<h2>
${this.supervisor.localize("addon.configuration.options.header")}
</h2>
<h2>Configuration</h2>
<div class="card-menu">
<ha-button-menu corner="BOTTOM_START" @action=${this._handleAction}>
<mwc-icon-button slot="trigger">
<ha-svg-icon .path=${mdiDotsVertical}></ha-svg-icon>
</mwc-icon-button>
<mwc-list-item .disabled=${!this._canShowSchema}>
${this._yamlMode
? this.supervisor.localize(
"addon.configuration.options.edit_in_ui"
)
: this.supervisor.localize(
"addon.configuration.options.edit_in_yaml"
)}
${this._yamlMode ? "Edit in UI" : "Edit in YAML"}
</mwc-list-item>
<mwc-list-item class="warning">
${this.supervisor.localize("common.reset_defaults")}
Reset to defaults
</mwc-list-item>
</ha-button-menu>
</div>
@@ -122,7 +102,6 @@ class HassioAddonConfig extends LitElement {
? html`<ha-form
.data=${this._options!}
@value-changed=${this._configChanged}
.computeLabel=${this.computeLabel}
.schema=${this._showOptional
? this.addon.schema!
: this._filteredShchema(
@@ -138,20 +117,12 @@ class HassioAddonConfig extends LitElement {
(this._canShowSchema && this.addon.schema) ||
this._valid
? ""
: html`
<div class="errors">
${this.supervisor.localize(
"addon.configuration.options.invalid_yaml"
)}
</div>
`}
: html` <div class="errors">Invalid YAML</div> `}
</div>
${hasHiddenOptions
? html`<ha-formfield
class="show-additional"
.label=${this.supervisor.localize(
"addon.configuration.options.show_unused_optional"
)}
label="Show unused optional configuration options"
>
<ha-switch
@change=${this._toggleOptional}
@@ -165,7 +136,7 @@ class HassioAddonConfig extends LitElement {
@click=${this._saveTapped}
.disabled=${!this._configHasChanged || !this._valid}
>
${this.supervisor.localize("common.save")}
Save
</ha-progress-button>
</div>
</ha-card>
@@ -230,10 +201,10 @@ class HassioAddonConfig extends LitElement {
button.progress = true;
const confirmed = await showConfirmationDialog(this, {
title: this.supervisor.localize("confirm.reset_options.title"),
text: this.supervisor.localize("confirm.reset_options.text"),
confirmText: this.supervisor.localize("common.reset_options"),
dismissText: this.supervisor.localize("common.cancel"),
title: this.addon.name,
text: "Are you sure you want to reset all your options?",
confirmText: "reset options",
dismissText: "no",
});
if (!confirmed) {
@@ -255,11 +226,9 @@ class HassioAddonConfig extends LitElement {
};
fireEvent(this, "hass-api-called", eventdata);
} catch (err) {
this._error = this.supervisor.localize(
"addon.common.update_available",
"error",
extractApiErrorMessage(err)
);
this._error = `Failed to reset addon configuration, ${extractApiErrorMessage(
err
)}`;
}
button.progress = false;
}
@@ -283,14 +252,12 @@ class HassioAddonConfig extends LitElement {
};
fireEvent(this, "hass-api-called", eventdata);
if (this.addon?.state === "started") {
await suggestAddonRestart(this, this.hass, this.supervisor, this.addon);
await suggestAddonRestart(this, this.hass, this.addon);
}
} catch (err) {
this._error = this.supervisor.localize(
"addon.configuration.options.failed_to_save",
"error",
extractApiErrorMessage(err)
);
this._error = `Failed to save addon configuration, ${extractApiErrorMessage(
err
)}`;
}
button.progress = false;
}
@@ -19,7 +19,6 @@ import {
setHassioAddonOption,
} from "../../../../src/data/hassio/addon";
import { extractApiErrorMessage } from "../../../../src/data/hassio/common";
import { Supervisor } from "../../../../src/data/supervisor/supervisor";
import { haStyle } from "../../../../src/resources/styles";
import { HomeAssistant } from "../../../../src/types";
import { suggestAddonRestart } from "../../dialogs/suggestAddonRestart";
@@ -39,8 +38,6 @@ interface NetworkItemInput extends PaperInputElement {
class HassioAddonNetwork extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property({ attribute: false }) public supervisor!: Supervisor;
@property({ attribute: false }) public addon!: HassioAddonDetails;
@internalProperty() private _error?: string;
@@ -58,30 +55,16 @@ class HassioAddonNetwork extends LitElement {
}
return html`
<ha-card
.header=${this.supervisor.localize(
"addon.configuration.network.header"
)}
>
<ha-card header="Network">
<div class="card-content">
${this._error ? html` <div class="errors">${this._error}</div> ` : ""}
<table>
<tbody>
<tr>
<th>
${this.supervisor.localize(
"addon.configuration.network.container"
)}
</th>
<th>
${this.supervisor.localize(
"addon.configuration.network.host"
)}
</th>
<th>
${this.supervisor.localize("common.description")}
</th>
<th>Container</th>
<th>Host</th>
<th>Description</th>
</tr>
${this._config!.map((item) => {
return html`
@@ -90,15 +73,13 @@ class HassioAddonNetwork extends LitElement {
<td>
<paper-input
@value-changed=${this._configChanged}
placeholder="${this.supervisor.localize(
"addon.configuration.network.disabled"
)}"
placeholder="disabled"
.value=${item.host ? String(item.host) : ""}
.container=${item.container}
no-label-float
></paper-input>
</td>
<td>${this._computeDescription(item)}</td>
<td>${item.description}</td>
</tr>
`;
})}
@@ -107,10 +88,10 @@ class HassioAddonNetwork extends LitElement {
</div>
<div class="card-actions">
<ha-progress-button class="warning" @click=${this._resetTapped}>
${this.supervisor.localize("common.reset_defaults")}
Reset to defaults
</ha-progress-button>
<ha-progress-button @click=${this._saveTapped}>
${this.supervisor.localize("common.save")}
Save
</ha-progress-button>
</div>
</ha-card>
@@ -124,15 +105,6 @@ class HassioAddonNetwork extends LitElement {
}
}
private _computeDescription = (item: NetworkItem): string => {
return (
this.addon.translations[this.hass.language]?.network?.[item.container]
?.description ||
this.addon.translations.en?.network?.[item.container]?.description ||
item.description
);
};
private _setNetworkConfig(): void {
const network = this.addon.network || {};
const description = this.addon.network_description || {};
@@ -175,14 +147,12 @@ class HassioAddonNetwork extends LitElement {
};
fireEvent(this, "hass-api-called", eventdata);
if (this.addon?.state === "started") {
await suggestAddonRestart(this, this.hass, this.supervisor, this.addon);
await suggestAddonRestart(this, this.hass, this.addon);
}
} catch (err) {
this._error = this.supervisor.localize(
"addon.failed_to_reset",
"error",
extractApiErrorMessage(err)
);
this._error = `Failed to set addon network configuration, ${extractApiErrorMessage(
err
)}`;
}
button.progress = false;
@@ -211,14 +181,12 @@ class HassioAddonNetwork extends LitElement {
};
fireEvent(this, "hass-api-called", eventdata);
if (this.addon?.state === "started") {
await suggestAddonRestart(this, this.hass, this.supervisor, this.addon);
await suggestAddonRestart(this, this.hass, this.addon);
}
} catch (err) {
this._error = this.supervisor.localize(
"addon.failed_to_save",
"error",
extractApiErrorMessage(err)
);
this._error = `Failed to set addon network configuration, ${extractApiErrorMessage(
err
)}`;
}
button.progress = false;
}
@@ -1,4 +1,3 @@
import "../../../../src/components/ha-card";
import {
css,
CSSResult,
@@ -20,14 +19,11 @@ import "../../../../src/layouts/hass-loading-screen";
import { haStyle } from "../../../../src/resources/styles";
import { HomeAssistant } from "../../../../src/types";
import { hassioStyle } from "../../resources/hassio-style";
import { Supervisor } from "../../../../src/data/supervisor/supervisor";
@customElement("hassio-addon-documentation-tab")
class HassioAddonDocumentationDashboard extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property({ attribute: false }) public supervisor!: Supervisor;
@property({ attribute: false }) public addon?: HassioAddonDetails;
@internalProperty() private _error?: string;
@@ -85,11 +81,9 @@ class HassioAddonDocumentationDashboard extends LitElement {
this.addon!.slug
);
} catch (err) {
this._error = this.supervisor.localize(
"addon.documentation.get_logs",
"error",
extractApiErrorMessage(err)
);
this._error = `Failed to get addon documentation, ${extractApiErrorMessage(
err
)}`;
}
}
}
+10 -19
View File
@@ -21,7 +21,6 @@ import { extractSearchParam } from "../../../src/common/url/search-params";
import "../../../src/components/ha-circular-progress";
import {
fetchHassioAddonInfo,
fetchHassioAddonsInfo,
HassioAddonDetails,
} from "../../../src/data/hassio/addon";
import { extractApiErrorMessage } from "../../../src/data/hassio/common";
@@ -80,7 +79,7 @@ class HassioAddonDashboard extends LitElement {
const addonTabs: PageNavigation[] = [
{
translationKey: "addon.panel.info",
name: "Info",
path: `/hassio/addon/${this.addon.slug}/info`,
iconPath: mdiInformationVariant,
},
@@ -88,7 +87,7 @@ class HassioAddonDashboard extends LitElement {
if (this.addon.documentation) {
addonTabs.push({
translationKey: "addon.panel.documentation",
name: "Documentation",
path: `/hassio/addon/${this.addon.slug}/documentation`,
iconPath: mdiFileDocument,
});
@@ -97,12 +96,12 @@ class HassioAddonDashboard extends LitElement {
if (this.addon.version) {
addonTabs.push(
{
translationKey: "addon.panel.configuration",
name: "Configuration",
path: `/hassio/addon/${this.addon.slug}/config`,
iconPath: mdiCogs,
},
{
translationKey: "addon.panel.log",
name: "Log",
path: `/hassio/addon/${this.addon.slug}/logs`,
iconPath: mdiMathLog,
}
@@ -114,12 +113,11 @@ class HassioAddonDashboard extends LitElement {
return html`
<hass-tabs-subpage
.hass=${this.hass}
.localizeFunc=${this.supervisor.localize}
.narrow=${this.narrow}
.backPath=${this.addon.version ? "/hassio/dashboard" : "/hassio/store"}
.route=${route}
hassio
.tabs=${addonTabs}
supervisor
>
<span slot="header">${this.addon.name}</span>
<hassio-addon-router
@@ -174,16 +172,9 @@ class HassioAddonDashboard extends LitElement {
protected async firstUpdated(): Promise<void> {
if (this.route.path === "") {
const requestedAddon = extractSearchParam("addon");
if (requestedAddon) {
const addonsInfo = await fetchHassioAddonsInfo(this.hass);
const validAddon = addonsInfo.addons
.some((addon) => addon.slug === requestedAddon);
if (!validAddon) {
this._error = this.supervisor.localize("my.error_addon_not_found");
} else {
navigate(this, `/hassio/addon/${requestedAddon}`, true);
}
const addon = extractSearchParam("addon");
if (addon) {
navigate(this, `/hassio/addon/${addon}`, true);
}
}
this.addEventListener("hass-api-called", (ev) => this._apiCalled(ev));
@@ -199,8 +190,8 @@ class HassioAddonDashboard extends LitElement {
const path: string = pathSplit[pathSplit.length - 1];
if (["uninstall", "install", "update", "start", "stop"].includes(path)) {
fireEvent(this, "supervisor-collection-refresh", {
collection: "supervisor",
fireEvent(this, "supervisor-colllection-refresh", {
colllection: "supervisor",
});
}
+159 -235
View File
@@ -50,7 +50,6 @@ import {
startHassioAddon,
stopHassioAddon,
uninstallHassioAddon,
updateHassioAddon,
validateHassioAddonOption,
} from "../../../../src/data/hassio/addon";
import {
@@ -69,8 +68,8 @@ import { HomeAssistant } from "../../../../src/types";
import { bytesToString } from "../../../../src/util/bytes-to-string";
import "../../components/hassio-card-content";
import "../../components/supervisor-metric";
import { showDialogSupervisorAddonUpdate } from "../../dialogs/addon/show-dialog-addon-update";
import { showHassioMarkdownDialog } from "../../dialogs/markdown/show-dialog-hassio-markdown";
import { showDialogSupervisorUpdate } from "../../dialogs/update/show-dialog-update";
import { hassioStyle } from "../../resources/hassio-style";
import { addonArchIsSupported } from "../../util/addon";
@@ -80,6 +79,63 @@ const STAGE_ICON = {
deprecated: mdiExclamationThick,
};
const PERMIS_DESC = {
stage: {
title: "Add-on Stage",
description: `Add-ons can have one of three stages:\n\n<ha-svg-icon path="${STAGE_ICON.stable}"></ha-svg-icon> **Stable**: These are add-ons ready to be used in production.\n\n<ha-svg-icon path="${STAGE_ICON.experimental}"></ha-svg-icon> **Experimental**: These may contain bugs, and may be unfinished.\n\n<ha-svg-icon path="${STAGE_ICON.deprecated}"></ha-svg-icon> **Deprecated**: These add-ons will no longer receive any updates.`,
},
rating: {
title: "Add-on Security Rating",
description:
"Home Assistant provides a security rating to each of the add-ons, which indicates the risks involved when using this add-on. The more access an add-on requires on your system, the lower the score, thus raising the possible security risks.\n\nA score is on a scale from 1 to 6. Where 1 is the lowest score (considered the most insecure and highest risk) and a score of 6 is the highest score (considered the most secure and lowest risk).",
},
host_network: {
title: "Host Network",
description:
"Add-ons usually run in their own isolated network layer, which prevents them from accessing the network of the host operating system. In some cases, this network isolation can limit add-ons in providing their services and therefore, the isolation can be lifted by the add-on author, giving the add-on full access to the network capabilities of the host machine. This gives the add-on more networking capabilities but lowers the security, hence, the security rating of the add-on will be lowered when this option is used by the add-on.",
},
homeassistant_api: {
title: "Home Assistant API Access",
description:
"This add-on is allowed to access your running Home Assistant instance directly via the Home Assistant API. This mode handles authentication for the add-on as well, which enables an add-on to interact with Home Assistant without the need for additional authentication tokens.",
},
full_access: {
title: "Full Hardware Access",
description:
"This add-on is given full access to the hardware of your system, by request of the add-on author. Access is comparable to the privileged mode in Docker. Since this opens up possible security risks, this feature impacts the add-on security score negatively.\n\nThis level of access is not granted automatically and needs to be confirmed by you. To do this, you need to disable the protection mode on the add-on manually. Only disable the protection mode if you know, need AND trust the source of this add-on.",
},
hassio_api: {
title: "Supervisor API Access",
description:
"The add-on was given access to the Supervisor API, by request of the add-on author. By default, the add-on can access general version information of your system. When the add-on requests 'manager' or 'admin' level access to the API, it will gain access to control multiple parts of your Home Assistant system. This permission is indicated by this badge and will impact the security score of the addon negatively.",
},
docker_api: {
title: "Full Docker Access",
description:
"The add-on author has requested the add-on to have management access to the Docker instance running on your system. This mode gives the add-on full access and control to your entire Home Assistant system, which adds security risks, and could damage your system when misused. Therefore, this feature impacts the add-on security score negatively.\n\nThis level of access is not granted automatically and needs to be confirmed by you. To do this, you need to disable the protection mode on the add-on manually. Only disable the protection mode if you know, need AND trust the source of this add-on.",
},
host_pid: {
title: "Host Processes Namespace",
description:
"Usually, the processes the add-on runs, are isolated from all other system processes. The add-on author has requested the add-on to have access to the system processes running on the host system instance, and allow the add-on to spawn processes on the host system as well. This mode gives the add-on full access and control to your entire Home Assistant system, which adds security risks, and could damage your system when misused. Therefore, this feature impacts the add-on security score negatively.\n\nThis level of access is not granted automatically and needs to be confirmed by you. To do this, you need to disable the protection mode on the add-on manually. Only disable the protection mode if you know, need AND trust the source of this add-on.",
},
apparmor: {
title: "AppArmor",
description:
"AppArmor ('Application Armor') is a Linux kernel security module that restricts add-ons capabilities like network access, raw socket access, and permission to read, write, or execute specific files.\n\nAdd-on authors can provide their security profiles, optimized for the add-on, or request it to be disabled. If AppArmor is disabled, it will raise security risks and therefore, has a negative impact on the security score of the add-on.",
},
auth_api: {
title: "Home Assistant Authentication",
description:
"An add-on can authenticate users against Home Assistant, allowing add-ons to give users the possibility to log into applications running inside add-ons, using their Home Assistant username/password. This badge indicates if the add-on author requests this capability.",
},
ingress: {
title: "Ingress",
description:
"This add-on is using Ingress to embed its interface securely into Home Assistant.",
},
};
@customElement("hassio-addon-info")
class HassioAddonInfo extends LitElement {
@property({ type: Boolean }) public narrow!: boolean;
@@ -106,11 +162,11 @@ class HassioAddonInfo extends LitElement {
: undefined;
const metrics = [
{
description: this.supervisor.localize("addon.dashboard.cpu_usage"),
description: "Add-on CPU Usage",
value: this._metrics?.cpu_percent,
},
{
description: this.supervisor.localize("addon.dashboard.ram_usage"),
description: "Add-on RAM Usage",
value: this._metrics?.memory_percent,
tooltip: `${bytesToString(this._metrics?.memory_usage)}/${bytesToString(
this._metrics?.memory_limit
@@ -120,28 +176,14 @@ class HassioAddonInfo extends LitElement {
return html`
${this.addon.update_available
? html`
<ha-card
.header="${this.supervisor.localize(
"common.update_available",
"count",
1
)}🎉"
>
<ha-card header="Update available! 🎉">
<div class="card-content">
<hassio-card-content
.hass=${this.hass}
.title="${this.supervisor.localize(
"addon.dashboard.new_update_available",
"name",
this.addon.name,
"version",
this.addon.version_latest
)}"
.description="${this.supervisor.localize(
"common.running_version",
"version",
this.addon.version
)}"
.title="${this.addon.name} ${this.addon
.version_latest} is available"
.description="You are currently running version ${this.addon
.version}"
icon=${mdiArrowUpBoldCircle}
iconClass="update"
></hassio-card-content>
@@ -152,32 +194,29 @@ class HassioAddonInfo extends LitElement {
)
? html`
<p class="warning">
${this.supervisor.localize(
"addon.dashboard.not_available_arch"
)}
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">
${this.supervisor.localize(
"addon.dashboard.not_available_arch",
"core_version_installed",
this.supervisor.core.version,
"core_version_needed",
addonStoreInfo.homeassistant
)}
You are running Home Assistant
${this.supervisor.core.version}, to update to this
version of the add-on you need at least version
${addonStoreInfo.homeassistant} of Home Assistant
</p>
`
: ""}
</div>
<div class="card-actions">
<mwc-button @click=${this._updateClicked}>
${this.supervisor.localize("common.update")}
Update
</mwc-button>
${this.addon.changelog
? html`
<mwc-button @click=${this._openChangelog}>
${this.supervisor.localize("addon.dashboard.changelog")}
Changelog
</mwc-button>
`
: ""}
@@ -188,19 +227,12 @@ class HassioAddonInfo extends LitElement {
${!this.addon.protected
? html`
<ha-card class="warning">
<h1 class="card-header">${this.supervisor.localize(
"addon.dashboard.protection_mode.title"
)}
</h1>
<h1 class="card-header">Warning: Protection mode is disabled!</h1>
<div class="card-content">
${this.supervisor.localize("addon.dashboard.protection_mode.content")}
Protection mode on this add-on is disabled! This gives the add-on full access to the entire system, which adds security risks, and could damage your system when used incorrectly. Only disable the protection mode if you know, need AND trust the source of this add-on.
</div>
<div class="card-actions protection-enable">
<mwc-button @click=${this._protectionToggled}>
${this.supervisor.localize(
"addon.dashboard.protection_mode.enable"
)}
</mwc-button>
<mwc-button @click=${this._protectionToggled}>Enable Protection mode</mwc-button>
</div>
</div>
</ha-card>
@@ -217,18 +249,14 @@ class HassioAddonInfo extends LitElement {
${this._computeIsRunning
? html`
<ha-svg-icon
.title=${this.supervisor.localize(
"dashboard.addon_running"
)}
title="Add-on is running"
class="running"
.path=${mdiCircle}
></ha-svg-icon>
`
: html`
<ha-svg-icon
.title=${this.supervisor.localize(
"dashboard.addon_stopped"
)}
title="Add-on is stopped"
class="stopped"
.path=${mdiCircle}
></ha-svg-icon>
@@ -242,29 +270,21 @@ class HassioAddonInfo extends LitElement {
? html`
Current version: ${this.addon.version}
<div class="changelog" @click=${this._openChangelog}>
(<span class="changelog-link">${
this.supervisor.localize("addon.dashboard.changelog")}</span
>)
(<span class="changelog-link">changelog</span>)
</div>
`
: html`<span class="changelog-link" @click=${this._openChangelog}>${
this.supervisor.localize("addon.dashboard.changelog")
}</span>`}
: html`<span class="changelog-link" @click=${this._openChangelog}
>Changelog</span
>`}
</div>
<div class="description light-color">
${this.addon.description}.<br />
${this.supervisor.localize(
"addon.dashboard.visit_addon_page",
"name",
html`<a
href="${this.addon.url!}"
target="_blank"
rel="noreferrer"
>
${this.addon.name}
</a>`
)}
Visit
<a href="${this.addon.url!}" target="_blank" rel="noreferrer">
${this.addon.name} page</a
>
for details.
</div>
<div class="addon-container">
<div>
@@ -285,9 +305,7 @@ class HassioAddonInfo extends LitElement {
})}
@click=${this._showMoreInfo}
id="stage"
.label=${this.supervisor.localize(
"addon.dashboard.capability.label.stage"
)}
label="stage"
description=""
>
<ha-svg-icon
@@ -313,9 +331,7 @@ class HassioAddonInfo extends LitElement {
<ha-label-badge
@click=${this._showMoreInfo}
id="host_network"
.label=${this.supervisor.localize(
"addon.dashboard.capability.label.host"
)}
label="host"
description=""
>
<ha-svg-icon .path=${mdiNetwork}></ha-svg-icon>
@@ -327,9 +343,7 @@ class HassioAddonInfo extends LitElement {
<ha-label-badge
@click=${this._showMoreInfo}
id="full_access"
.label=${this.supervisor.localize(
"addon.dashboard.capability.label.hardware"
)}
label="hardware"
description=""
>
<ha-svg-icon .path=${mdiChip}></ha-svg-icon>
@@ -341,9 +355,7 @@ class HassioAddonInfo extends LitElement {
<ha-label-badge
@click=${this._showMoreInfo}
id="homeassistant_api"
.label=${this.supervisor.localize(
"addon.dashboard.capability.label.hass"
)}
label="hass"
description=""
>
<ha-svg-icon .path=${mdiHomeAssistant}></ha-svg-icon>
@@ -355,12 +367,8 @@ class HassioAddonInfo extends LitElement {
<ha-label-badge
@click=${this._showMoreInfo}
id="hassio_api"
.label=${this.supervisor.localize(
"addon.dashboard.capability.label.hassio"
)}
.description=${this.supervisor.localize(
`addon.dashboard.capability.role.${this.addon.hassio_role}`
) || this.addon.hassio_role}
label="hassio"
.description=${this.addon.hassio_role}
>
<ha-svg-icon .path=${mdiHomeAssistant}></ha-svg-icon>
</ha-label-badge>
@@ -371,9 +379,7 @@ class HassioAddonInfo extends LitElement {
<ha-label-badge
@click=${this._showMoreInfo}
id="docker_api"
.label=".${this.supervisor.localize(
"addon.dashboard.capability.label.docker"
)}"
label="docker"
description=""
>
<ha-svg-icon .path=${mdiDocker}></ha-svg-icon>
@@ -385,9 +391,7 @@ class HassioAddonInfo extends LitElement {
<ha-label-badge
@click=${this._showMoreInfo}
id="host_pid"
.label=${this.supervisor.localize(
"addon.dashboard.capability.label.host_pid"
)}
label="host pid"
description=""
>
<ha-svg-icon .path=${mdiPound}></ha-svg-icon>
@@ -400,9 +404,7 @@ class HassioAddonInfo extends LitElement {
@click=${this._showMoreInfo}
class=${this._computeApparmorClassName}
id="apparmor"
.label=${this.supervisor.localize(
"addon.dashboard.capability.label.apparmor"
)}
label="apparmor"
description=""
>
<ha-svg-icon .path=${mdiShield}></ha-svg-icon>
@@ -414,9 +416,7 @@ class HassioAddonInfo extends LitElement {
<ha-label-badge
@click=${this._showMoreInfo}
id="auth_api"
.label=${this.supervisor.localize(
"addon.dashboard.capability.label.auth"
)}
label="auth"
description=""
>
<ha-svg-icon .path=${mdiKey}></ha-svg-icon>
@@ -428,9 +428,7 @@ class HassioAddonInfo extends LitElement {
<ha-label-badge
@click=${this._showMoreInfo}
id="ingress"
.label=${this.supervisor.localize(
"addon.dashboard.capability.label.ingress"
)}
label="ingress"
description=""
>
<ha-svg-icon
@@ -451,14 +449,10 @@ class HassioAddonInfo extends LitElement {
>
<ha-settings-row ?three-line=${this.narrow}>
<span slot="heading">
${this.supervisor.localize(
"addon.dashboard.option.boot.title"
)}
Start on boot
</span>
<span slot="description">
${this.supervisor.localize(
"addon.dashboard.option.boot.description"
)}
Make the add-on start during a system boot
</span>
<ha-switch
@change=${this._startOnBootToggled}
@@ -471,14 +465,10 @@ class HassioAddonInfo extends LitElement {
? html`
<ha-settings-row ?three-line=${this.narrow}>
<span slot="heading">
${this.supervisor.localize(
"addon.dashboard.option.watchdog.title"
)}
Watchdog
</span>
<span slot="description">
${this.supervisor.localize(
"addon.dashboard.option.watchdog.description"
)}
This will start the add-on if it crashes
</span>
<ha-switch
@change=${this._watchdogToggled}
@@ -493,14 +483,11 @@ class HassioAddonInfo extends LitElement {
? html`
<ha-settings-row ?three-line=${this.narrow}>
<span slot="heading">
${this.supervisor.localize(
"addon.dashboard.option.auto_update.title"
)}
Auto update
</span>
<span slot="description">
${this.supervisor.localize(
"addon.dashboard.option.auto_update.description"
)}
Auto update the add-on when there is a new
version available
</span>
<ha-switch
@change=${this._autoUpdateToggled}
@@ -510,22 +497,21 @@ class HassioAddonInfo extends LitElement {
</ha-settings-row>
`
: ""}
${!this._computeCannotIngressSidebar && this.addon.ingress
${this.addon.ingress
? html`
<ha-settings-row ?three-line=${this.narrow}>
<span slot="heading">
${this.supervisor.localize(
"addon.dashboard.option.ingress_panel.title"
)}
Show in sidebar
</span>
<span slot="description">
${this.supervisor.localize(
"addon.dashboard.option.ingress_panel.description"
)}
${this._computeCannotIngressSidebar
? "This option requires Home Assistant 0.92 or later."
: "Add this add-on to your sidebar"}
</span>
<ha-switch
@change=${this._panelToggled}
.checked=${this.addon.ingress_panel}
.disabled=${this._computeCannotIngressSidebar}
haptic
></ha-switch>
</ha-settings-row>
@@ -535,14 +521,10 @@ class HassioAddonInfo extends LitElement {
? html`
<ha-settings-row ?three-line=${this.narrow}>
<span slot="heading">
${this.supervisor.localize(
"addon.dashboard.option.protected.title"
)}
Protection mode
</span>
<span slot="description">
${this.supervisor.localize(
"addon.dashboard.option.protected.description"
)}
Blocks elevated system access from the add-on
</span>
<ha-switch
@change=${this._protectionToggled}
@@ -560,7 +542,7 @@ class HassioAddonInfo extends LitElement {
${this.addon.state === "started"
? html`<ha-settings-row ?three-line=${this.narrow}>
<span slot="heading">
${this.supervisor.localize("addon.dashboard.hostname")}
Hostname
</span>
<code slot="description">
${this.addon.hostname}
@@ -587,20 +569,17 @@ class HassioAddonInfo extends LitElement {
)
? html`
<p class="warning">
${this.supervisor.localize(
"addon.dashboard.not_available_arch"
)}
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">
${this.supervisor.localize(
"addon.dashboard.not_available_version",
"core_version_installed",
this.supervisor.core.version,
"core_version_needed",
addonStoreInfo!.homeassistant
)}
You are running Home Assistant
${this.supervisor.core.version}, to install this add-on you
need at least version ${addonStoreInfo!.homeassistant} of
Home Assistant
</p>
`
: ""}
@@ -614,18 +593,18 @@ class HassioAddonInfo extends LitElement {
class="warning"
@click=${this._stopClicked}
>
${this.supervisor.localize("addon.dashboard.stop")}
Stop
</ha-progress-button>
<ha-progress-button
class="warning"
@click=${this._restartClicked}
>
${this.supervisor.localize("addon.dashboard.restart")}
Restart
</ha-progress-button>
`
: html`
<ha-progress-button @click=${this._startClicked}>
${this.supervisor.localize("addon.dashboard.start")}
Start
</ha-progress-button>
`
: html`
@@ -633,7 +612,7 @@ class HassioAddonInfo extends LitElement {
.disabled=${!this.addon.available}
@click=${this._installClicked}
>
${this.supervisor.localize("addon.dashboard.install")}
Install
</ha-progress-button>
`}
</div>
@@ -648,9 +627,7 @@ class HassioAddonInfo extends LitElement {
rel="noopener"
>
<mwc-button>
${this.supervisor.localize(
"addon.dashboard.open_web_ui"
)}
Open web UI
</mwc-button>
</a>
`
@@ -658,9 +635,7 @@ class HassioAddonInfo extends LitElement {
${this._computeShowIngressUI
? html`
<mwc-button @click=${this._openIngress}>
${this.supervisor.localize(
"addon.dashboard.open_web_ui"
)}
Open web UI
</mwc-button>
`
: ""}
@@ -668,7 +643,7 @@ class HassioAddonInfo extends LitElement {
class="warning"
@click=${this._uninstallClicked}
>
${this.supervisor.localize("addon.dashboard.uninstall")}
Uninstall
</ha-progress-button>
${this.addon.build
? html`
@@ -677,7 +652,7 @@ class HassioAddonInfo extends LitElement {
.hass=${this.hass}
.path="hassio/addons/${this.addon.slug}/rebuild"
>
${this.supervisor.localize("addon.dashboard.rebuild")}
Rebuild
</ha-call-api-button>
`
: ""}`
@@ -737,21 +712,8 @@ class HassioAddonInfo extends LitElement {
private _showMoreInfo(ev): void {
const id = ev.currentTarget.id;
showHassioMarkdownDialog(this, {
title: this.supervisor.localize(`addon.dashboard.capability.${id}.title`),
content:
id === "stage"
? this.supervisor.localize(
`addon.dashboard.capability.${id}.description`,
"icon_stable",
`<ha-svg-icon path="${STAGE_ICON.stable}"></ha-svg-icon>`,
"icon_experimental",
`<ha-svg-icon path="${STAGE_ICON.experimental}"></ha-svg-icon>`,
"icon_deprecated",
`<ha-svg-icon path="${STAGE_ICON.deprecated}"></ha-svg-icon>`
)
: this.supervisor.localize(
`addon.dashboard.capability.${id}.description`
),
title: PERMIS_DESC[id].title,
content: PERMIS_DESC[id].description,
});
}
@@ -804,11 +766,9 @@ class HassioAddonInfo extends LitElement {
};
fireEvent(this, "hass-api-called", eventdata);
} catch (err) {
this._error = this.supervisor.localize(
"addon.failed_to_save",
"error",
extractApiErrorMessage(err)
);
this._error = `Failed to set addon option, ${extractApiErrorMessage(
err
)}`;
}
}
@@ -826,11 +786,9 @@ class HassioAddonInfo extends LitElement {
};
fireEvent(this, "hass-api-called", eventdata);
} catch (err) {
this._error = this.supervisor.localize(
"addon.failed_to_save",
"error",
extractApiErrorMessage(err)
);
this._error = `Failed to set addon option, ${extractApiErrorMessage(
err
)}`;
}
}
@@ -848,11 +806,9 @@ class HassioAddonInfo extends LitElement {
};
fireEvent(this, "hass-api-called", eventdata);
} catch (err) {
this._error = this.supervisor.localize(
"addon.failed_to_save",
"error",
extractApiErrorMessage(err)
);
this._error = `Failed to set addon option, ${extractApiErrorMessage(
err
)}`;
}
}
@@ -870,11 +826,9 @@ class HassioAddonInfo extends LitElement {
};
fireEvent(this, "hass-api-called", eventdata);
} catch (err) {
this._error = this.supervisor.localize(
"addon.failed_to_save",
"error",
extractApiErrorMessage(err)
);
this._error = `Failed to set addon security option, ${extractApiErrorMessage(
err
)}`;
}
}
@@ -892,11 +846,9 @@ class HassioAddonInfo extends LitElement {
};
fireEvent(this, "hass-api-called", eventdata);
} catch (err) {
this._error = this.supervisor.localize(
"addon.failed_to_save",
"error",
extractApiErrorMessage(err)
);
this._error = `Failed to set addon option, ${extractApiErrorMessage(
err
)}`;
}
}
@@ -907,14 +859,12 @@ class HassioAddonInfo extends LitElement {
this.addon.slug
);
showHassioMarkdownDialog(this, {
title: this.supervisor.localize("addon.dashboard.changelog"),
title: "Changelog",
content,
});
} catch (err) {
showAlertDialog(this, {
title: this.supervisor.localize(
"addon.dashboard.action_error.get_changelog"
),
title: "Failed to get addon changelog",
text: extractApiErrorMessage(err),
});
}
@@ -934,7 +884,7 @@ class HassioAddonInfo extends LitElement {
fireEvent(this, "hass-api-called", eventdata);
} catch (err) {
showAlertDialog(this, {
title: this.supervisor.localize("addon.dashboard.action_error.install"),
title: "Failed to install addon",
text: extractApiErrorMessage(err),
});
}
@@ -955,7 +905,7 @@ class HassioAddonInfo extends LitElement {
fireEvent(this, "hass-api-called", eventdata);
} catch (err) {
showAlertDialog(this, {
title: this.supervisor.localize("addon.dashboard.action_error.stop"),
title: "Failed to stop addon",
text: extractApiErrorMessage(err),
});
}
@@ -976,7 +926,7 @@ class HassioAddonInfo extends LitElement {
fireEvent(this, "hass-api-called", eventdata);
} catch (err) {
showAlertDialog(this, {
title: this.supervisor.localize("addon.dashboard.action_error.restart"),
title: "Failed to restart addon",
text: extractApiErrorMessage(err),
});
}
@@ -984,32 +934,12 @@ class HassioAddonInfo extends LitElement {
}
private async _updateClicked(): Promise<void> {
showDialogSupervisorUpdate(this, {
showDialogSupervisorAddonUpdate(this, {
addon: this.addon,
supervisor: this.supervisor,
name: this.addon.name,
version: this.addon.version_latest,
snapshotParams: {
name: `addon_${this.addon.slug}_${this.addon.version}`,
addons: [this.addon.slug],
homeassistant: false,
},
updateHandler: async () => await this._updateAddon(),
});
}
private async _updateAddon(): Promise<void> {
await updateHassioAddon(this.hass, this.addon.slug);
fireEvent(this, "supervisor-collection-refresh", {
collection: "addon",
});
const eventdata = {
success: true,
response: undefined,
path: "update",
};
fireEvent(this, "hass-api-called", eventdata);
}
private async _startClicked(ev: CustomEvent): Promise<void> {
const button = ev.currentTarget as any;
button.progress = true;
@@ -1020,15 +950,11 @@ class HassioAddonInfo extends LitElement {
);
if (!validate.valid) {
await showConfirmationDialog(this, {
title: this.supervisor.localize(
"addon.dashboard.action_error.start_invalid_config"
),
title: "Failed to start addon - configuration validation failed!",
text: validate.message.split(" Got ")[0],
confirm: () => this._openConfiguration(),
confirmText: this.supervisor.localize(
"addon.dashboard.action_error.go_to_config"
),
dismissText: this.supervisor.localize("common.cancel"),
confirmText: "Go to configuration",
dismissText: "Cancel",
});
button.progress = false;
return;
@@ -1053,7 +979,7 @@ class HassioAddonInfo extends LitElement {
fireEvent(this, "hass-api-called", eventdata);
} catch (err) {
showAlertDialog(this, {
title: this.supervisor.localize("addon.dashboard.action_error.start"),
title: "Failed to start addon",
text: extractApiErrorMessage(err),
});
}
@@ -1091,9 +1017,7 @@ class HassioAddonInfo extends LitElement {
fireEvent(this, "hass-api-called", eventdata);
} catch (err) {
showAlertDialog(this, {
title: this.supervisor.localize(
"addon.dashboard.action_error.uninstall"
),
title: "Failed to uninstall addon",
text: extractApiErrorMessage(err),
});
}
@@ -9,7 +9,6 @@ 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";
@@ -19,8 +18,6 @@ import "./hassio-addon-logs";
class HassioAddonLogDashboard extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property({ attribute: false }) public supervisor!: Supervisor;
@property({ attribute: false }) public addon?: HassioAddonDetails;
protected render(): TemplateResult {
@@ -31,7 +28,6 @@ class HassioAddonLogDashboard extends LitElement {
<div class="content">
<hassio-addon-logs
.hass=${this.hass}
.supervisor=${this.supervisor}
.addon=${this.addon}
></hassio-addon-logs>
</div>
+2 -11
View File
@@ -15,7 +15,6 @@ import {
HassioAddonDetails,
} from "../../../../src/data/hassio/addon";
import { extractApiErrorMessage } from "../../../../src/data/hassio/common";
import { Supervisor } from "../../../../src/data/supervisor/supervisor";
import { haStyle } from "../../../../src/resources/styles";
import { HomeAssistant } from "../../../../src/types";
import "../../components/hassio-ansi-to-html";
@@ -25,8 +24,6 @@ import { hassioStyle } from "../../resources/hassio-style";
class HassioAddonLogs extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property({ attribute: false }) public supervisor!: Supervisor;
@property({ attribute: false }) public addon!: HassioAddonDetails;
@internalProperty() private _error?: string;
@@ -51,9 +48,7 @@ class HassioAddonLogs extends LitElement {
: ""}
</div>
<div class="card-actions">
<mwc-button @click=${this._refresh}>
${this.supervisor.localize("common.refresh")}
</mwc-button>
<mwc-button @click=${this._refresh}>Refresh</mwc-button>
</div>
</ha-card>
`;
@@ -81,11 +76,7 @@ class HassioAddonLogs extends LitElement {
try {
this._content = await fetchHassioAddonLogs(this.hass, this.addon.slug);
} catch (err) {
this._error = this.supervisor.localize(
"addon.logs.get_logs",
"error",
extractApiErrorMessage(err)
);
this._error = `Failed to get addon logs, ${extractApiErrorMessage(err)}`;
}
}
+1 -1
View File
@@ -44,7 +44,7 @@ class HassioCardContent extends LitElement {
${this.iconImage
? html`
<div class="icon_image ${this.iconClass}">
<img src="${this.iconImage}" .title=${this.iconTitle} />
<img src="${this.iconImage}" title="${this.iconTitle}" />
<div></div>
</div>
`
+2 -2
View File
@@ -26,9 +26,9 @@ class SupervisorMetric extends LitElement {
<span slot="heading">
${this.description}
</span>
<div slot="description" .title=${this.tooltip ?? ""}>
<div slot="description" title="${this.tooltip ?? ""}">
<span class="value">
${roundedValue} %
${roundedValue}%
</span>
<ha-bar
class="${classMap({
+7 -11
View File
@@ -27,15 +27,17 @@ class HassioAddons extends LitElement {
protected render(): TemplateResult {
return html`
<div class="content">
<h1>${this.supervisor.localize("dashboard.addons")}</h1>
<h1>Add-ons</h1>
<div class="card-group">
${!this.supervisor.supervisor.addons?.length
? html`
<ha-card>
<div class="card-content">
You don't have any add-ons installed yet. Head over to
<button class="link" @click=${this._openStore}>
${this.supervisor.localize("dashboard.no_addons")}
the add-on store
</button>
to get started!
</div>
</ha-card>
`
@@ -56,16 +58,10 @@ class HassioAddons extends LitElement {
? mdiArrowUpBoldCircle
: mdiPuzzle}
.iconTitle=${addon.state !== "started"
? this.supervisor.localize(
"dashboard.addon_stopped"
)
? "Add-on is stopped"
: addon.update_available!
? this.supervisor.localize(
"dashboard.addon_new_version"
)
: this.supervisor.localize(
"dashboard.addon_running"
)}
? "New version available"
: "Add-on is running"}
.iconClass=${addon.update_available
? addon.state === "started"
? "update"
+3 -6
View File
@@ -29,16 +29,13 @@ class HassioDashboard extends LitElement {
return html`
<hass-tabs-subpage
.hass=${this.hass}
.localizeFunc=${this.supervisor.localize}
.narrow=${this.narrow}
hassio
main-page
.route=${this.route}
.tabs=${supervisorTabs}
main-page
supervisor
>
<span slot="header">
${this.supervisor.localize("panel.dashboard")}
</span>
<span slot="header">Dashboard</span>
<div class="content">
<hassio-update
.hass=${this.hass}
+33 -90
View File
@@ -10,40 +10,30 @@ 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-card";
import "../../../src/components/ha-settings-row";
import "../../../src/components/ha-svg-icon";
import {
extractApiErrorMessage,
HassioResponse,
ignoreSupervisorError,
ignoredStatusCodes,
} from "../../../src/data/hassio/common";
import { HassioHassOSInfo } from "../../../src/data/hassio/host";
import {
HassioHomeAssistantInfo,
HassioSupervisorInfo,
} from "../../../src/data/hassio/supervisor";
import { updateCore } from "../../../src/data/supervisor/core";
import {
Supervisor,
supervisorApiWsRequest,
} from "../../../src/data/supervisor/supervisor";
import { Supervisor } from "../../../src/data/supervisor/supervisor";
import {
showAlertDialog,
showConfirmationDialog,
} from "../../../src/dialogs/generic/show-dialog-box";
import { haStyle } from "../../../src/resources/styles";
import { HomeAssistant } from "../../../src/types";
import { showDialogSupervisorUpdate } from "../dialogs/update/show-dialog-update";
import { showDialogSupervisorCoreUpdate } from "../dialogs/core/show-dialog-core-update";
import { hassioStyle } from "../resources/hassio-style";
const computeVersion = (key: string, version: string): string => {
return key === "os" ? version : `${key}-${version}`;
};
@customElement("hassio-update")
export class HassioUpdate extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@@ -69,12 +59,9 @@ export class HassioUpdate extends LitElement {
return html`
<div class="content">
<h1>
${this.supervisor.localize(
"common.update_available",
"count",
updatesAvailable
)}
🎉
${updatesAvailable > 1
? "Updates Available 🎉"
: "Update Available 🎉"}
</h1>
<div class="card-group">
${this._renderUpdateCard(
@@ -123,30 +110,14 @@ export class HassioUpdate extends LitElement {
<div class="icon">
<ha-svg-icon .path=${mdiHomeAssistant}></ha-svg-icon>
</div>
<div class="update-heading">${name}</div>
<ha-settings-row two-line>
<span slot="heading">
${this.supervisor.localize("common.version")}
</span>
<span slot="description">
${computeVersion(key, object.version!)}
</span>
</ha-settings-row>
<ha-settings-row two-line>
<span slot="heading">
${this.supervisor.localize("common.newest_version")}
</span>
<span slot="description">
${computeVersion(key, object.version_latest!)}
</span>
</ha-settings-row>
<div class="update-heading">${name} ${object.version_latest}</div>
<div class="warning">
You are currently running version ${object.version}
</div>
</div>
<div class="card-actions">
<a href="${releaseNotesUrl}" target="_blank" rel="noreferrer">
<mwc-button>
${this.supervisor.localize("common.release_notes")}
</mwc-button>
<mwc-button>Release notes</mwc-button>
</a>
<ha-progress-button
.apiPath=${apiPath}
@@ -155,7 +126,7 @@ export class HassioUpdate extends LitElement {
.version=${object.version_latest}
@click=${this._confirmUpdate}
>
${this.supervisor.localize("common.update")}
Update
</ha-progress-button>
</div>
</ha-card>
@@ -165,35 +136,15 @@ export class HassioUpdate extends LitElement {
private async _confirmUpdate(ev): Promise<void> {
const item = ev.currentTarget;
if (item.key === "core") {
showDialogSupervisorUpdate(this, {
supervisor: this.supervisor,
name: "Home Assistant Core",
version: this.supervisor.core.version_latest,
snapshotParams: {
name: `core_${this.supervisor.core.version}`,
folders: ["homeassistant"],
homeassistant: true,
},
updateHandler: async () => this._updateCore(),
});
showDialogSupervisorCoreUpdate(this, { supervisor: this.supervisor });
return;
}
item.progress = true;
const confirmed = await showConfirmationDialog(this, {
title: this.supervisor.localize(
"confirm.update.title",
"name",
item.name
),
text: this.supervisor.localize(
"confirm.update.text",
"name",
item.name,
"version",
computeVersion(item.key, item.version)
),
confirmText: this.supervisor.localize("common.update"),
dismissText: this.supervisor.localize("common.cancel"),
title: `Update ${item.name}`,
text: `Are you sure you want to update ${item.name} to version ${item.version}?`,
confirmText: "update",
dismissText: "cancel",
});
if (!confirmed) {
@@ -201,24 +152,20 @@ export class HassioUpdate extends LitElement {
return;
}
try {
if (atLeastVersion(this.hass.config.version, 2021, 2, 4)) {
await supervisorApiWsRequest(this.hass.connection, {
method: "post",
endpoint: item.apiPath.replace("hassio", ""),
timeout: null,
});
} else {
await this.hass.callApi<HassioResponse<void>>("POST", item.apiPath);
}
fireEvent(this, "supervisor-collection-refresh", {
collection: item.key,
await this.hass.callApi<HassioResponse<void>>("POST", item.apiPath);
fireEvent(this, "supervisor-colllection-refresh", {
colllection: 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)
if (this.hass.connection.connected && !ignoreSupervisorError(err)) {
if (
this.hass.connection.connected &&
err.status_code &&
!ignoredStatusCodes.has(err.status_code)
) {
showAlertDialog(this, {
title: this.supervisor.localize("common.error.update_failed"),
title: "Update failed",
text: extractApiErrorMessage(err),
});
}
@@ -226,13 +173,6 @@ export class HassioUpdate extends LitElement {
item.progress = false;
}
private async _updateCore(): Promise<void> {
await updateCore(this.hass);
fireEvent(this, "supervisor-collection-refresh", {
collection: "core",
});
}
static get styles(): CSSResult[] {
return [
haStyle,
@@ -250,6 +190,9 @@ export class HassioUpdate extends LitElement {
margin-bottom: 0.5em;
color: var(--primary-text-color);
}
.warning {
color: var(--secondary-text-color);
}
.card-content {
height: calc(100% - 47px);
box-sizing: border-box;
@@ -257,13 +200,13 @@ export class HassioUpdate extends LitElement {
.card-actions {
text-align: right;
}
.errors {
color: var(--error-color);
padding: 16px;
}
a {
text-decoration: none;
}
ha-settings-row {
padding: 0;
--paper-item-body-two-line-min-height: 32px;
}
`,
];
}
@@ -6,6 +6,7 @@ import {
html,
internalProperty,
LitElement,
property,
TemplateResult,
} from "lit-element";
import { fireEvent } from "../../../../src/common/dom/fire_event";
@@ -15,18 +16,24 @@ import "../../../../src/components/ha-settings-row";
import "../../../../src/components/ha-svg-icon";
import "../../../../src/components/ha-switch";
import {
extractApiErrorMessage,
ignoreSupervisorError,
} from "../../../../src/data/hassio/common";
HassioAddonDetails,
updateHassioAddon,
} from "../../../../src/data/hassio/addon";
import { extractApiErrorMessage } from "../../../../src/data/hassio/common";
import { Supervisor } from "../../../../src/data/supervisor/supervisor";
import { createHassioPartialSnapshot } from "../../../../src/data/hassio/snapshot";
import { haStyle, haStyleDialog } from "../../../../src/resources/styles";
import type { HomeAssistant } from "../../../../src/types";
import { SupervisorDialogSupervisorUpdateParams } from "./show-dialog-update";
import { SupervisorDialogSupervisorAddonUpdateParams } from "./show-dialog-addon-update";
@customElement("dialog-supervisor-addon-update")
class DialogSupervisorAddonUpdate extends LitElement {
@property({ attribute: false }) public supervisor!: Supervisor;
@customElement("dialog-supervisor-update")
class DialogSupervisorUpdate extends LitElement {
public hass!: HomeAssistant;
public addon!: HassioAddonDetails;
@internalProperty() private _opened = false;
@internalProperty() private _createSnapshot = true;
@@ -35,22 +42,20 @@ class DialogSupervisorUpdate extends LitElement {
@internalProperty() private _error?: string;
@internalProperty()
private _dialogParams?: SupervisorDialogSupervisorUpdateParams;
public async showDialog(
params: SupervisorDialogSupervisorUpdateParams
params: SupervisorDialogSupervisorAddonUpdateParams
): Promise<void> {
this._opened = true;
this._dialogParams = params;
this.addon = params.addon;
this.supervisor = params.supervisor;
await this.updateComplete;
}
public closeDialog(): void {
this._action = null;
this._createSnapshot = true;
this._opened = false;
this._error = undefined;
this._dialogParams = undefined;
fireEvent(this, "dialog-closed", { dialog: this.localName });
}
@@ -63,77 +68,52 @@ class DialogSupervisorUpdate extends LitElement {
}
protected render(): TemplateResult {
if (!this._dialogParams) {
return html``;
}
return html`
<ha-dialog .open=${this._opened} scrimClickAction escapeKeyAction>
${this._action === null
? html`<slot name="heading">
<h2 id="title" class="header_title">
${this._dialogParams.supervisor.localize(
"confirm.update.title",
"name",
this._dialogParams.name
)}
Update ${this.addon.name}
</h2>
</slot>
<div>
${this._dialogParams.supervisor.localize(
"confirm.update.text",
"name",
this._dialogParams.name,
"version",
this._dialogParams.version
)}
Are you sure you want to update the ${this.addon.name} add-on to
version ${this.addon.version_latest}?
</div>
<ha-settings-row>
<span slot="heading">
${this._dialogParams.supervisor.localize(
"dialog.update.snapshot"
)}
Snapshot
</span>
<span slot="description">
${this._dialogParams.supervisor.localize(
"dialog.update.create_snapshot",
"name",
this._dialogParams.name
)}
Create a snapshot of the ${this.addon.name} add-on before
updating
</span>
<ha-switch
.checked=${this._createSnapshot}
haptic
title="Create snapshot"
@click=${this._toggleSnapshot}
>
</ha-switch>
</ha-settings-row>
<mwc-button @click=${this.closeDialog} slot="secondaryAction">
${this._dialogParams.supervisor.localize("common.cancel")}
Cancel
</mwc-button>
<mwc-button
.disabled=${this._error !== undefined}
.disabled=${this._error !== undefined ||
this.supervisor.info.state !== "running"}
@click=${this._update}
slot="primaryAction"
>
${this._dialogParams.supervisor.localize("common.update")}
Update
</mwc-button>`
: html`<ha-circular-progress alt="Updating" size="large" active>
</ha-circular-progress>
<p class="progress-text">
${this._action === "update"
? this._dialogParams.supervisor.localize(
"dialog.update.updating",
"name",
this._dialogParams.name,
"version",
this._dialogParams.version
)
: this._dialogParams.supervisor.localize(
"dialog.update.snapshotting",
"name",
this._dialogParams.name
)}
? `Updating ${this.addon.name} to version ${this.addon.version_latest}`
: "Creating snapshot of Home Assistant Core"}
</p>`}
${this._error ? html`<p class="error">${this._error}</p>` : ""}
</ha-dialog>
@@ -148,10 +128,11 @@ class DialogSupervisorUpdate extends LitElement {
if (this._createSnapshot) {
this._action = "snapshot";
try {
await createHassioPartialSnapshot(
this.hass,
this._dialogParams!.snapshotParams
);
await createHassioPartialSnapshot(this.hass, {
name: `addon_${this.addon.slug}_${this.addon.version}`,
addons: [this.addon.slug],
homeassistant: false,
});
} catch (err) {
this._error = extractApiErrorMessage(err);
this._action = null;
@@ -161,15 +142,16 @@ class DialogSupervisorUpdate extends LitElement {
this._action = "update";
try {
await this._dialogParams!.updateHandler!();
await updateHassioAddon(this.hass, this.addon.slug);
} catch (err) {
if (this.hass.connection.connected && !ignoreSupervisorError(err)) {
this._error = extractApiErrorMessage(err);
}
this._error = extractApiErrorMessage(err);
this._action = null;
return;
}
fireEvent(this, "supervisor-colllection-refresh", { colllection: "addon" });
fireEvent(this, "supervisor-colllection-refresh", {
colllection: "supervisor",
});
this.closeDialog();
}
@@ -203,6 +185,6 @@ class DialogSupervisorUpdate extends LitElement {
declare global {
interface HTMLElementTagNameMap {
"dialog-supervisor-update": DialogSupervisorUpdate;
"dialog-supervisor-addon-update": DialogSupervisorAddonUpdate;
}
}
@@ -0,0 +1,19 @@
import { fireEvent } from "../../../../src/common/dom/fire_event";
import { HassioAddonDetails } from "../../../../src/data/hassio/addon";
import { Supervisor } from "../../../../src/data/supervisor/supervisor";
export interface SupervisorDialogSupervisorAddonUpdateParams {
addon: HassioAddonDetails;
supervisor: Supervisor;
}
export const showDialogSupervisorAddonUpdate = (
element: HTMLElement,
dialogParams: SupervisorDialogSupervisorAddonUpdateParams
): void => {
fireEvent(element, "show-dialog", {
dialogTag: "dialog-supervisor-addon-update",
dialogImport: () => import("./dialog-supervisor-addon-update"),
dialogParams,
});
};
@@ -0,0 +1,182 @@
import "@material/mwc-button/mwc-button";
import {
css,
CSSResult,
customElement,
html,
internalProperty,
LitElement,
property,
TemplateResult,
} from "lit-element";
import { fireEvent } from "../../../../src/common/dom/fire_event";
import "../../../../src/components/ha-circular-progress";
import "../../../../src/components/ha-dialog";
import "../../../../src/components/ha-settings-row";
import "../../../../src/components/ha-svg-icon";
import "../../../../src/components/ha-switch";
import { extractApiErrorMessage } from "../../../../src/data/hassio/common";
import { createHassioPartialSnapshot } from "../../../../src/data/hassio/snapshot";
import { updateCore } from "../../../../src/data/supervisor/core";
import { Supervisor } from "../../../../src/data/supervisor/supervisor";
import { haStyle, haStyleDialog } from "../../../../src/resources/styles";
import type { HomeAssistant } from "../../../../src/types";
import { SupervisorDialogSupervisorCoreUpdateParams } from "./show-dialog-core-update";
@customElement("dialog-supervisor-core-update")
class DialogSupervisorCoreUpdate extends LitElement {
@property({ attribute: false }) public supervisor!: Supervisor;
public hass!: HomeAssistant;
@internalProperty() private _opened = false;
@internalProperty() private _createSnapshot = true;
@internalProperty() private _action: "snapshot" | "update" | null = null;
@internalProperty() private _error?: string;
public async showDialog(
params: SupervisorDialogSupervisorCoreUpdateParams
): Promise<void> {
this._opened = true;
this.supervisor = params.supervisor;
await this.updateComplete;
}
public closeDialog(): void {
this._action = null;
this._createSnapshot = true;
this._opened = false;
this._error = undefined;
fireEvent(this, "dialog-closed", { dialog: this.localName });
}
public focus(): void {
this.updateComplete.then(() =>
(this.shadowRoot?.querySelector(
"[dialogInitialFocus]"
) as HTMLElement)?.focus()
);
}
protected render(): TemplateResult {
return html`
<ha-dialog .open=${this._opened} scrimClickAction escapeKeyAction>
${this._action === null
? html`<slot name="heading">
<h2 id="title" class="header_title">
Update Home Assistant Core
</h2>
</slot>
<div>
Are you sure you want to update Home Assistant Core to version
${this.supervisor.core.version_latest}?
</div>
<ha-settings-row three-rows>
<span slot="heading">
Snapshot
</span>
<span slot="description">
Create a snapshot of Home Assistant Core before updating
</span>
<ha-switch
.checked=${this._createSnapshot}
haptic
title="Create snapshot"
@click=${this._toggleSnapshot}
>
</ha-switch>
</ha-settings-row>
<mwc-button @click=${this.closeDialog} slot="secondaryAction">
Cancel
</mwc-button>
<mwc-button
.disabled=${this._error !== undefined ||
this.supervisor.info.state !== "running"}
@click=${this._update}
slot="primaryAction"
>
Update
</mwc-button>`
: html`<ha-circular-progress alt="Updating" size="large" active>
</ha-circular-progress>
<p class="progress-text">
${this._action === "update"
? `Updating Home Assistant Core to version ${this.supervisor.core.version_latest}`
: "Creating snapshot of Home Assistant Core"}
</p>`}
${this._error ? html`<p class="error">${this._error}</p>` : ""}
</ha-dialog>
`;
}
private _toggleSnapshot() {
this._createSnapshot = !this._createSnapshot;
}
private async _update() {
if (this._createSnapshot) {
this._action = "snapshot";
try {
await createHassioPartialSnapshot(this.hass, {
name: `core_${this.supervisor.core.version}`,
folders: ["homeassistant"],
homeassistant: true,
});
} catch (err) {
this._error = extractApiErrorMessage(err);
this._action = null;
return;
}
}
this._action = "update";
try {
await updateCore(this.hass);
} catch (err) {
if (this.hass.connection.connected) {
this._error = extractApiErrorMessage(err);
this._action = null;
return;
}
}
fireEvent(this, "supervisor-colllection-refresh", { colllection: "core" });
this.closeDialog();
}
static get styles(): CSSResult[] {
return [
haStyle,
haStyleDialog,
css`
.form {
color: var(--primary-text-color);
}
ha-settings-row {
margin-top: 32px;
padding: 0;
}
ha-circular-progress {
display: block;
margin: 32px;
text-align: center;
}
.progress-text {
text-align: center;
}
`,
];
}
}
declare global {
interface HTMLElementTagNameMap {
"dialog-supervisor-core-update": DialogSupervisorCoreUpdate;
}
}
@@ -0,0 +1,17 @@
import { fireEvent } from "../../../../src/common/dom/fire_event";
import { Supervisor } from "../../../../src/data/supervisor/supervisor";
export interface SupervisorDialogSupervisorCoreUpdateParams {
supervisor: Supervisor;
}
export const showDialogSupervisorCoreUpdate = (
element: HTMLElement,
dialogParams: SupervisorDialogSupervisorCoreUpdateParams
): void => {
fireEvent(element, "show-dialog", {
dialogTag: "dialog-supervisor-core-update",
dialogImport: () => import("./dialog-supervisor-core-update"),
dialogParams,
});
};
@@ -18,6 +18,7 @@ import {
} from "lit-element";
import { cache } from "lit-html/directives/cache";
import { fireEvent } from "../../../../src/common/dom/fire_event";
import "../../../../src/components/ha-chips";
import "../../../../src/components/ha-circular-progress";
import "../../../../src/components/ha-dialog";
import "../../../../src/components/ha-expansion-panel";
@@ -34,7 +35,6 @@ import {
updateNetworkInterface,
WifiConfiguration,
} from "../../../../src/data/hassio/network";
import { Supervisor } from "../../../../src/data/supervisor/supervisor";
import {
showAlertDialog,
showConfirmationDialog,
@@ -51,8 +51,6 @@ export class DialogHassioNetwork extends LitElement
implements HassDialog<HassioNetworkDialogParams> {
@property({ attribute: false }) public hass!: HomeAssistant;
@property({ attribute: false }) public supervisor!: Supervisor;
@internalProperty() private _accessPoints?: AccessPoints;
@internalProperty() private _curTabIndex = 0;
@@ -75,8 +73,7 @@ export class DialogHassioNetwork extends LitElement
this._params = params;
this._dirty = false;
this._curTabIndex = 0;
this.supervisor = params.supervisor;
this._interfaces = params.supervisor.network.interfaces.sort((a, b) => {
this._interfaces = params.network.interfaces.sort((a, b) => {
return a.primary > b.primary ? -1 : 1;
});
this._interface = { ...this._interfaces[this._curTabIndex] };
@@ -107,7 +104,7 @@ export class DialogHassioNetwork extends LitElement
<div slot="heading">
<ha-header-bar>
<span slot="title">
${this.supervisor.localize("dialog.network.title")}
Network settings
</span>
<mwc-icon-button slot="actionItems" dialogAction="cancel">
<ha-svg-icon .path=${mdiClose}></ha-svg-icon>
@@ -142,13 +139,7 @@ export class DialogHassioNetwork extends LitElement
? html`
<ha-expansion-panel header="Wi-Fi" outlined>
${this._interface?.wifi?.ssid
? html`<p>
${this.supervisor.localize(
"dialog.network.connected_to",
"ssid",
this._interface?.wifi?.ssid
)}
</p>`
? html`<p>Connected to: ${this._interface?.wifi?.ssid}</p>`
: ""}
<mwc-button
class="scan"
@@ -158,7 +149,7 @@ export class DialogHassioNetwork extends LitElement
${this._scanning
? html`<ha-circular-progress active size="small">
</ha-circular-progress>`
: this.supervisor.localize("dialog.network.scan_ap")}
: "Scan for accesspoints"}
</mwc-button>
${this._accessPoints &&
this._accessPoints.accesspoints &&
@@ -190,11 +181,7 @@ export class DialogHassioNetwork extends LitElement
${this._wifiConfiguration
? html`
<div class="radio-row">
<ha-formfield
.label=${this.supervisor.localize(
"dialog.network.open"
)}
>
<ha-formfield label="open">
<ha-radio
@change=${this._handleRadioValueChangedAp}
.ap=${this._wifiConfiguration}
@@ -206,11 +193,7 @@ export class DialogHassioNetwork extends LitElement
>
</ha-radio>
</ha-formfield>
<ha-formfield
.label=${this.supervisor.localize(
"dialog.network.wep"
)}
>
<ha-formfield label="wep">
<ha-radio
@change=${this._handleRadioValueChangedAp}
.ap=${this._wifiConfiguration}
@@ -220,11 +203,7 @@ export class DialogHassioNetwork extends LitElement
>
</ha-radio>
</ha-formfield>
<ha-formfield
.label=${this.supervisor.localize(
"dialog.network.wpa"
)}
>
<ha-formfield label="wpa-psk">
<ha-radio
@change=${this._handleRadioValueChangedAp}
.ap=${this._wifiConfiguration}
@@ -258,21 +237,18 @@ export class DialogHassioNetwork extends LitElement
: ""}
${this._dirty
? html`<div class="warning">
${this.supervisor.localize("dialog.network.warning")}
If you are changing the Wi-Fi, IP or gateway addresses, you might
lose the connection!
</div>`
: ""}
</div>
<div class="buttons">
<mwc-button
.label=${this.supervisor.localize("common.cancel")}
@click=${this.closeDialog}
>
</mwc-button>
<mwc-button label="close" @click=${this.closeDialog}> </mwc-button>
<mwc-button @click=${this._updateNetwork} .disabled=${!this._dirty}>
${this._processing
? html`<ha-circular-progress active size="small">
</ha-circular-progress>`
: this.supervisor.localize("common.save")}
: "Save"}
</mwc-button>
</div>`;
}
@@ -309,9 +285,7 @@ export class DialogHassioNetwork extends LitElement
outlined
>
<div class="radio-row">
<ha-formfield
.label=${this.supervisor.localize("dialog.network.dhcp")}
>
<ha-formfield label="DHCP">
<ha-radio
@change=${this._handleRadioValueChanged}
.version=${version}
@@ -321,9 +295,7 @@ export class DialogHassioNetwork extends LitElement
>
</ha-radio>
</ha-formfield>
<ha-formfield
.label=${this.supervisor.localize("dialog.network.static")}
>
<ha-formfield label="Static">
<ha-radio
@change=${this._handleRadioValueChanged}
.version=${version}
@@ -333,10 +305,7 @@ export class DialogHassioNetwork extends LitElement
>
</ha-radio>
</ha-formfield>
<ha-formfield
.label=${this.supervisor.localize("dialog.network.disabled")}
class="warning"
>
<ha-formfield label="Disabled" class="warning">
<ha-radio
@change=${this._handleRadioValueChanged}
.version=${version}
@@ -352,7 +321,7 @@ export class DialogHassioNetwork extends LitElement
<paper-input
class="flex-auto"
id="address"
.label=${this.supervisor.localize("dialog.network.ip_netmask")}
label="IP address/Netmask"
.version=${version}
.value=${this._toString(this._interface![version].address)}
@value-changed=${this._handleInputValueChanged}
@@ -361,7 +330,7 @@ export class DialogHassioNetwork extends LitElement
<paper-input
class="flex-auto"
id="gateway"
.label=${this.supervisor.localize("dialog.network.gateway")}
label="Gateway address"
.version=${version}
.value=${this._interface![version].gateway}
@value-changed=${this._handleInputValueChanged}
@@ -370,7 +339,7 @@ export class DialogHassioNetwork extends LitElement
<paper-input
class="flex-auto"
id="nameservers"
.label=${this.supervisor.localize("dialog.network.dns_servers")}
label="DNS servers"
.version=${version}
.value=${this._toString(this._interface![version].nameservers)}
@value-changed=${this._handleInputValueChanged}
@@ -455,7 +424,7 @@ export class DialogHassioNetwork extends LitElement
);
} catch (err) {
showAlertDialog(this, {
title: this.supervisor.localize("dialog.network.failed_to_change"),
title: "Failed to change network settings",
text: extractApiErrorMessage(err),
});
this._processing = false;
@@ -468,9 +437,10 @@ export class DialogHassioNetwork extends LitElement
private async _handleTabActivated(ev: CustomEvent): Promise<void> {
if (this._dirty) {
const confirm = await showConfirmationDialog(this, {
text: this.supervisor.localize("dialog.network.unsaved"),
confirmText: this.supervisor.localize("common.yes"),
dismissText: this.supervisor.localize("common.no"),
text:
"You have unsaved changes, these will get lost if you change tabs, do you want to continue?",
confirmText: "yes",
dismissText: "no",
});
if (!confirm) {
this.requestUpdate("_interface");
@@ -1,9 +1,9 @@
import { fireEvent } from "../../../../src/common/dom/fire_event";
import { Supervisor } from "../../../../src/data/supervisor/supervisor";
import { NetworkInfo } from "../../../../src/data/hassio/network";
import "./dialog-hassio-network";
export interface HassioNetworkDialogParams {
supervisor: Supervisor;
network: NetworkInfo;
loadData: () => Promise<void>;
}
@@ -22,18 +22,14 @@ import {
fetchHassioDockerRegistries,
removeHassioDockerRegistry,
} from "../../../../src/data/hassio/docker";
import { Supervisor } from "../../../../src/data/supervisor/supervisor";
import { showAlertDialog } from "../../../../src/dialogs/generic/show-dialog-box";
import { haStyle, haStyleDialog } from "../../../../src/resources/styles";
import type { HomeAssistant } from "../../../../src/types";
import { RegistriesDialogParams } from "./show-dialog-registries";
@customElement("dialog-hassio-registries")
class HassioRegistriesDialog extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property({ attribute: false }) public supervisor!: Supervisor;
@property({ attribute: false }) private _registries?: {
registry: string;
username: string;
@@ -59,8 +55,8 @@ class HassioRegistriesDialog extends LitElement {
.heading=${createCloseHeading(
this.hass,
this._addingRegistry
? this.supervisor.localize("dialog.registries.title_add")
: this.supervisor.localize("dialog.registries.title_manage")
? "Add New Docker Registry"
: "Manage Docker Registries"
)}
>
<div class="form">
@@ -70,9 +66,7 @@ class HassioRegistriesDialog extends LitElement {
@value-changed=${this._inputChanged}
class="flex-auto"
name="registry"
.label=${this.supervisor.localize(
"dialog.registries.registry"
)}
label="Registry"
required
auto-validate
></paper-input>
@@ -80,9 +74,7 @@ class HassioRegistriesDialog extends LitElement {
@value-changed=${this._inputChanged}
class="flex-auto"
name="username"
.label=${this.supervisor.localize(
"dialog.registries.username"
)}
label="Username"
required
auto-validate
></paper-input>
@@ -90,9 +82,7 @@ class HassioRegistriesDialog extends LitElement {
@value-changed=${this._inputChanged}
class="flex-auto"
name="password"
.label=${this.supervisor.localize(
"dialog.registries.password"
)}
label="Password"
type="password"
required
auto-validate
@@ -104,7 +94,7 @@ class HassioRegistriesDialog extends LitElement {
)}
@click=${this._addNewRegistry}
>
${this.supervisor.localize("dialog.registries.add_registry")}
Add registry
</mwc-button>
`
: html`${this._registries?.length
@@ -113,16 +103,11 @@ class HassioRegistriesDialog extends LitElement {
<mwc-list-item class="option" hasMeta twoline>
<span>${entry.registry}</span>
<span slot="secondary"
>${this.supervisor.localize(
"dialog.registries.username"
)}:
${entry.username}</span
>Username: ${entry.username}</span
>
<mwc-icon-button
.entry=${entry}
.title=${this.supervisor.localize(
"dialog.registries.remove"
)}
title="Remove"
slot="meta"
@click=${this._removeRegistry}
>
@@ -133,17 +118,11 @@ class HassioRegistriesDialog extends LitElement {
})
: html`
<mwc-list-item>
<span
>${this.supervisor.localize(
"dialog.registries.no_registries"
)}</span
>
<span>No registries configured</span>
</mwc-list-item>
`}
<mwc-button @click=${this._addRegistry}>
${this.supervisor.localize(
"dialog.registries.add_new_registry"
)}
Add new registry
</mwc-button> `}
</div>
</ha-dialog>
@@ -155,9 +134,8 @@ class HassioRegistriesDialog extends LitElement {
this[`_${target.name}`] = target.value;
}
public async showDialog(dialogParams: RegistriesDialogParams): Promise<void> {
public async showDialog(_dialogParams: any): Promise<void> {
this._opened = true;
this.supervisor = dialogParams.supervisor;
await this._loadRegistries();
await this.updateComplete;
}
@@ -200,7 +178,7 @@ class HassioRegistriesDialog extends LitElement {
this._addingRegistry = false;
} catch (err) {
showAlertDialog(this, {
title: this.supervisor.localize("dialog.registries.failed_to_add"),
title: "Failed to add registry",
text: extractApiErrorMessage(err),
});
}
@@ -214,7 +192,7 @@ class HassioRegistriesDialog extends LitElement {
await this._loadRegistries();
} catch (err) {
showAlertDialog(this, {
title: this.supervisor.localize("dialog.registries.failed_to_remove"),
title: "Failed to remove registry",
text: extractApiErrorMessage(err),
});
}
@@ -1,18 +1,10 @@
import { fireEvent } from "../../../../src/common/dom/fire_event";
import { Supervisor } from "../../../../src/data/supervisor/supervisor";
import "./dialog-hassio-registries";
export interface RegistriesDialogParams {
supervisor: Supervisor;
}
export const showRegistriesDialog = (
element: HTMLElement,
dialogParams: RegistriesDialogParams
): void => {
export const showRegistriesDialog = (element: HTMLElement): void => {
fireEvent(element, "show-dialog", {
dialogTag: "dialog-hassio-registries",
dialogImport: () => import("./dialog-hassio-registries"),
dialogParams,
dialogParams: {},
});
};
@@ -17,9 +17,8 @@ import {
TemplateResult,
} from "lit-element";
import memoizeOne from "memoize-one";
import { fireEvent } from "../../../../src/common/dom/fire_event";
import "../../../../src/components/ha-circular-progress";
import { createCloseHeading } from "../../../../src/components/ha-dialog";
import "../../../../src/components/ha-dialog";
import "../../../../src/components/ha-svg-icon";
import {
fetchHassioAddonsInfo,
@@ -35,29 +34,27 @@ import { HassioRepositoryDialogParams } from "./show-dialog-repositories";
class HassioRepositoriesDialog extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property({ attribute: false }) private _repos: HassioAddonRepository[] = [];
@property({ attribute: false })
private _dialogParams?: HassioRepositoryDialogParams;
@query("#repository_input", true) private _optionInput?: PaperInputElement;
@internalProperty() private _repositories?: HassioAddonRepository[];
@internalProperty() private _dialogParams?: HassioRepositoryDialogParams;
@internalProperty() private _opened = false;
@internalProperty() private _prosessing = false;
@internalProperty() private _error?: string;
public async showDialog(
dialogParams: HassioRepositoryDialogParams
): Promise<void> {
this._dialogParams = dialogParams;
public async showDialog(_dialogParams: any): Promise<void> {
this._dialogParams = _dialogParams;
this._repos = _dialogParams.repos;
this._opened = true;
await this._loadData();
await this.updateComplete;
}
public closeDialog(): void {
this._dialogParams = undefined;
this._opened = false;
this._error = "";
}
@@ -69,20 +66,14 @@ class HassioRepositoriesDialog extends LitElement {
);
protected render(): TemplateResult {
if (!this._dialogParams?.supervisor || this._repositories === undefined) {
return html``;
}
const repositories = this._filteredRepositories(this._repositories);
const repositories = this._filteredRepositories(this._repos);
return html`
<ha-dialog
.open=${this._opened}
@closing=${this.closeDialog}
scrimClickAction
escapeKeyAction
.heading=${createCloseHeading(
this.hass,
this._dialogParams!.supervisor.localize("dialog.repositories.title")
)}
heading="Manage add-on repositories"
>
${this._error ? html`<div class="error">${this._error}</div>` : ""}
<div class="form">
@@ -97,9 +88,7 @@ class HassioRepositoriesDialog extends LitElement {
</paper-item-body>
<mwc-icon-button
.slug=${repo.slug}
.title=${this._dialogParams!.supervisor.localize(
"dialog.repositories.remove"
)}
title="Remove"
@click=${this._removeRepository}
>
<ha-svg-icon .path=${mdiDelete}></ha-svg-icon>
@@ -116,23 +105,18 @@ class HassioRepositoriesDialog extends LitElement {
<paper-input
class="flex-auto"
id="repository_input"
.value=${this._dialogParams!.url || ""}
.label=${this._dialogParams!.supervisor.localize(
"dialog.repositories.add"
)}
label="Add repository"
@keydown=${this._handleKeyAdd}
></paper-input>
<mwc-button @click=${this._addRepository}>
${this._prosessing
? html`<ha-circular-progress active></ha-circular-progress>`
: this._dialogParams!.supervisor.localize(
"dialog.repositories.add"
)}
: "Add"}
</mwc-button>
</div>
</div>
<mwc-button slot="primaryAction" @click=${this.closeDialog}>
${this._dialogParams?.supervisor.localize("common.close")}
<mwc-button slot="primaryAction" @click="${this.closeDialog}">
Close
</mwc-button>
</ha-dialog>
`;
@@ -163,11 +147,6 @@ class HassioRepositoriesDialog extends LitElement {
ha-paper-dropdown-menu {
display: block;
}
ha-circular-progress {
display: block;
margin: 32px;
text-align: center;
}
`,
];
}
@@ -188,25 +167,13 @@ class HassioRepositoriesDialog extends LitElement {
this._addRepository();
}
private async _loadData(): Promise<void> {
try {
const addonsinfo = await fetchHassioAddonsInfo(this.hass);
this._repositories = addonsinfo.repositories;
fireEvent(this, "supervisor-collection-refresh", { collection: "addon" });
} catch (err) {
this._error = extractApiErrorMessage(err);
}
}
private async _addRepository() {
const input = this._optionInput;
if (!input || !input.value) {
return;
}
this._prosessing = true;
const repositories = this._filteredRepositories(this._repositories!);
const repositories = this._filteredRepositories(this._repos);
const newRepositories = repositories.map((repo) => {
return repo.source;
});
@@ -216,7 +183,11 @@ class HassioRepositoriesDialog extends LitElement {
await setSupervisorOption(this.hass, {
addons_repositories: newRepositories,
});
await this._loadData();
const addonsInfo = await fetchHassioAddonsInfo(this.hass);
this._repos = addonsInfo.repositories;
await this._dialogParams!.loadData();
input.value = "";
} catch (err) {
@@ -227,7 +198,7 @@ class HassioRepositoriesDialog extends LitElement {
private async _removeRepository(ev: Event) {
const slug = (ev.currentTarget as any).slug;
const repositories = this._filteredRepositories(this._repositories!);
const repositories = this._filteredRepositories(this._repos);
const repository = repositories.find((repo) => {
return repo.slug === slug;
});
@@ -246,7 +217,11 @@ class HassioRepositoriesDialog extends LitElement {
await setSupervisorOption(this.hass, {
addons_repositories: newRepositories,
});
await this._loadData();
const addonsInfo = await fetchHassioAddonsInfo(this.hass);
this._repos = addonsInfo.repositories;
await this._dialogParams!.loadData();
} catch (err) {
this._error = extractApiErrorMessage(err);
}
@@ -1,10 +1,10 @@
import { fireEvent } from "../../../../src/common/dom/fire_event";
import { Supervisor } from "../../../../src/data/supervisor/supervisor";
import { HassioAddonRepository } from "../../../../src/data/hassio/addon";
import "./dialog-hassio-repositories";
export interface HassioRepositoryDialogParams {
supervisor: Supervisor;
url?: string;
repos: HassioAddonRepository[];
loadData: () => Promise<void>;
}
export const showRepositoriesDialog = (
+5 -11
View File
@@ -4,7 +4,6 @@ import {
restartHassioAddon,
} from "../../../src/data/hassio/addon";
import { extractApiErrorMessage } from "../../../src/data/hassio/common";
import { Supervisor } from "../../../src/data/supervisor/supervisor";
import {
showAlertDialog,
showConfirmationDialog,
@@ -14,25 +13,20 @@ import { HomeAssistant } from "../../../src/types";
export const suggestAddonRestart = async (
element: LitElement,
hass: HomeAssistant,
supervisor: Supervisor,
addon: HassioAddonDetails
): Promise<void> => {
const confirmed = await showConfirmationDialog(element, {
title: supervisor.localize("common.restart_name", "name", addon.name),
text: supervisor.localize("dialog.restart_addon.text"),
confirmText: supervisor.localize("dialog.restart_addon.confirm_text"),
dismissText: supervisor.localize("common.cancel"),
title: addon.name,
text: "Do you want to restart the add-on with your changes?",
confirmText: "restart add-on",
dismissText: "no",
});
if (confirmed) {
try {
await restartHassioAddon(hass, addon.slug);
} catch (err) {
showAlertDialog(element, {
title: supervisor.localize(
"common.failed_to_restart_name",
"name",
addon.name
),
title: "Failed to restart",
text: extractApiErrorMessage(err),
});
}
@@ -1,21 +0,0 @@
import { fireEvent } from "../../../../src/common/dom/fire_event";
import { Supervisor } from "../../../../src/data/supervisor/supervisor";
export interface SupervisorDialogSupervisorUpdateParams {
supervisor: Supervisor;
name: string;
version: string;
snapshotParams: any;
updateHandler: () => Promise<void>;
}
export const showDialogSupervisorUpdate = (
element: HTMLElement,
dialogParams: SupervisorDialogSupervisorUpdateParams
): void => {
fireEvent(element, "show-dialog", {
dialogTag: "dialog-supervisor-update",
dialogImport: () => import("./dialog-supervisor-update"),
dialogParams,
});
};
+13 -3
View File
@@ -3,7 +3,7 @@ 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 { Supervisor } from "../../src/data/supervisor/supervisor";
import { supervisorCollection } 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";
@@ -14,8 +14,6 @@ import { SupervisorBaseElement } from "./supervisor-base-element";
export class HassioMain extends SupervisorBaseElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property({ attribute: false }) public supervisor!: Supervisor;
@property({ attribute: false }) public panel!: HassioPanelInfo;
@property({ type: Boolean }) public narrow!: boolean;
@@ -74,6 +72,18 @@ export class HassioMain extends SupervisorBaseElement {
}
protected render() {
if (!this.supervisor || !this.hass) {
return html`<hass-loading-screen></hass-loading-screen>`;
}
if (
Object.keys(supervisorCollection).some(
(colllection) => !this.supervisor![colllection]
)
) {
return html`<hass-loading-screen></hass-loading-screen>`;
}
return html`
<hassio-router
.hass=${this.hass}
+10 -21
View File
@@ -19,12 +19,8 @@ import {
} from "../../src/panels/my/ha-panel-my";
import { navigate } from "../../src/common/navigate";
import { HomeAssistant, Route } from "../../src/types";
import { Supervisor } from "../../src/data/supervisor/supervisor";
const REDIRECTS: Redirects = {
supervisor: {
redirect: "/hassio/dashboard",
},
supervisor_logs: {
redirect: "/hassio/system",
},
@@ -37,27 +33,22 @@ const REDIRECTS: Redirects = {
supervisor_store: {
redirect: "/hassio/store",
},
supervisor: {
redirect: "/hassio/dashboard",
},
supervisor_addon: {
redirect: "/hassio/addon",
params: {
addon: "string",
},
},
supervisor_add_addon_repository: {
redirect: "/hassio/store",
params: {
repository_url: "url",
},
},
};
@customElement("hassio-my-redirect")
class HassioMyRedirect extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property({ attribute: false }) public supervisor!: Supervisor;
@property({ attribute: false }) public route!: Route;
@property() public route!: Route;
@internalProperty() public _error?: TemplateResult | string;
@@ -67,17 +58,15 @@ class HassioMyRedirect extends LitElement {
const redirect = REDIRECTS[path];
if (!redirect) {
this._error = this.supervisor.localize(
"my.not_supported",
"link",
html`<a
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
>
${this.supervisor.localize("my.faq_link")}
</a>`
);
for the supported redirects and the version they where introduced.`;
return;
}
@@ -85,7 +74,7 @@ class HassioMyRedirect extends LitElement {
try {
url = this._createRedirectUrl(redirect);
} catch (err) {
this._error = this.supervisor.localize("my.error");
this._error = "An unknown error occured";
return;
}
+1 -15
View File
@@ -7,10 +7,7 @@ import {
property,
TemplateResult,
} from "lit-element";
import {
Supervisor,
supervisorCollection,
} from "../../src/data/supervisor/supervisor";
import { Supervisor } from "../../src/data/supervisor/supervisor";
import { HomeAssistant, Route } from "../../src/types";
import "./hassio-panel-router";
@@ -25,17 +22,6 @@ class HassioPanel extends LitElement {
@property({ attribute: false }) public route!: Route;
protected render(): TemplateResult {
if (!this.hass) {
return html`<hass-loading-screen></hass-loading-screen>`;
}
if (
Object.keys(supervisorCollection).some(
(collection) => !this.supervisor[collection]
)
) {
return html`<hass-loading-screen></hass-loading-screen>`;
}
return html`
<hassio-panel-router
.hass=${this.hass}
+4 -4
View File
@@ -3,22 +3,22 @@ import type { PageNavigation } from "../../src/layouts/hass-tabs-subpage";
export const supervisorTabs: PageNavigation[] = [
{
translationKey: "panel.dashboard",
name: "Dashboard",
path: `/hassio/dashboard`,
iconPath: mdiViewDashboard,
},
{
translationKey: "panel.store",
name: "Add-on Store",
path: `/hassio/store`,
iconPath: mdiStore,
},
{
translationKey: "panel.snapshots",
name: "Snapshots",
path: `/hassio/snapshots`,
iconPath: mdiBackupRestore,
},
{
translationKey: "panel.system",
name: "System",
path: `/hassio/system`,
iconPath: mdiCogs,
},
+34 -48
View File
@@ -55,8 +55,8 @@ import { hassioStyle } from "../resources/hassio-style";
interface CheckboxItem {
slug: string;
name: string;
checked: boolean;
name?: string;
}
@customElement("hassio-snapshots")
@@ -84,12 +84,13 @@ class HassioSnapshots extends LitElement {
@internalProperty() private _folderList: CheckboxItem[] = [
{
slug: "homeassistant",
name: "Home Assistant configuration",
checked: true,
},
{ slug: "ssl", checked: true },
{ slug: "share", checked: true },
{ slug: "media", checked: true },
{ slug: "addons/local", checked: true },
{ slug: "ssl", name: "SSL", checked: true },
{ slug: "share", name: "Share", checked: true },
{ slug: "media", name: "Media", checked: true },
{ slug: "addons/local", name: "Local add-ons", checked: true },
];
@internalProperty() private _error = "";
@@ -103,16 +104,13 @@ class HassioSnapshots extends LitElement {
return html`
<hass-tabs-subpage
.hass=${this.hass}
.localizeFunc=${this.supervisor.localize}
.narrow=${this.narrow}
hassio
main-page
.route=${this.route}
.tabs=${supervisorTabs}
main-page
supervisor
>
<span slot="header">
${this.supervisor.localize("panel.snapshots")}
</span>
<span slot="header">Snapshots</span>
<ha-button-menu
corner="BOTTOM_START"
slot="toolbar-icon"
@@ -122,50 +120,50 @@ class HassioSnapshots extends LitElement {
<ha-svg-icon .path=${mdiDotsVertical}></ha-svg-icon>
</mwc-icon-button>
<mwc-list-item>
${this.supervisor.localize("common.reload")}
Reload
</mwc-list-item>
${atLeastVersion(this.hass.config.version, 0, 116)
? html`<mwc-list-item>
${this.supervisor.localize("snapshot.upload_snapshot")}
Upload snapshot
</mwc-list-item>`
: ""}
</ha-button-menu>
<div class="content">
<h1>
${this.supervisor.localize("snapshot.create_snapshot")}
Create Snapshot
</h1>
<p class="description">
${this.supervisor.localize("snapshot.description")}
Snapshots allow you to easily backup and restore all data of your
Home Assistant instance.
</p>
<div class="card-group">
<ha-card>
<div class="card-content">
<paper-input
autofocus
.label=${this.supervisor.localize("snapshot.name")}
label="Name"
name="snapshotName"
.value=${this._snapshotName}
@value-changed=${this._handleTextValueChanged}
></paper-input>
${this.supervisor.localize("snapshot.type")}:
Type:
<paper-radio-group
name="snapshotType"
type="${this.supervisor.localize("snapshot.type")}"
.selected=${this._snapshotType}
@selected-changed=${this._handleRadioValueChanged}
>
<paper-radio-button name="full">
${this.supervisor.localize("snapshot.full_snapshot")}
Full snapshot
</paper-radio-button>
<paper-radio-button name="partial">
${this.supervisor.localize("snapshot.partial_snapshot")}
Partial snapshot
</paper-radio-button>
</paper-radio-group>
${this._snapshotType === "full"
? undefined
: html`
${this.supervisor.localize("snapshot.folders")}:
Folders:
${this._folderList.map(
(folder, idx) => html`
<paper-checkbox
@@ -173,13 +171,11 @@ class HassioSnapshots extends LitElement {
.checked=${folder.checked}
@checked-changed=${this._folderChecked}
>
${this.supervisor.localize(
`snapshot.folder.${folder.slug}`
)}
${folder.name}
</paper-checkbox>
`
)}
${this.supervisor.localize("snapshot.addons")}:
Add-ons:
${this._addonList.map(
(addon, idx) => html`
<paper-checkbox
@@ -192,18 +188,18 @@ class HassioSnapshots extends LitElement {
`
)}
`}
${this.supervisor.localize("snapshot.security")}:
Security:
<paper-checkbox
name="snapshotHasPassword"
.checked=${this._snapshotHasPassword}
@checked-changed=${this._handleCheckboxValueChanged}
>
${this.supervisor.localize("snapshot.password_protection")}
Password protection
</paper-checkbox>
${this._snapshotHasPassword
? html`
<paper-input
.label=${this.supervisor.localize("snapshot.password")}
label="Password"
type="password"
name="snapshotPassword"
.value=${this._snapshotPassword}
@@ -218,22 +214,18 @@ class HassioSnapshots extends LitElement {
<div class="card-actions">
<ha-progress-button
@click=${this._createSnapshot}
.title=${this.supervisor.info.state !== "running"
? this.supervisor.localize(
"snapshot.create_blocked_not_running",
"state",
this.supervisor.info.state
)
: ""}
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"}
>
${this.supervisor.localize("snapshot.create")}
Create
</ha-progress-button>
</div>
</ha-card>
</div>
<h1>${this.supervisor.localize("snapshot.available_snapshots")}</h1>
<h1>Available Snapshots</h1>
<div class="card-group">
${this._snapshots === undefined
? undefined
@@ -241,7 +233,7 @@ class HassioSnapshots extends LitElement {
? html`
<ha-card>
<div class="card-content">
${this.supervisor.localize("snapshot.no_snapshots")}
You don't have any snapshots yet.
</div>
</ha-card>
`
@@ -342,12 +334,8 @@ class HassioSnapshots extends LitElement {
private async _createSnapshot(ev: CustomEvent): Promise<void> {
if (this.supervisor.info.state !== "running") {
await showAlertDialog(this, {
title: this.supervisor.localize("snapshot.could_not_create"),
text: this.supervisor.localize(
"snapshot.create_blocked_not_running",
"state",
this.supervisor.info.state
),
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;
@@ -355,7 +343,7 @@ class HassioSnapshots extends LitElement {
this._error = "";
if (this._snapshotHasPassword && !this._snapshotPassword.length) {
this._error = this.supervisor.localize("snapshot.enter_password");
this._error = "Please enter a password.";
button.progress = false;
return;
}
@@ -404,9 +392,7 @@ class HassioSnapshots extends LitElement {
private _computeDetails(snapshot: HassioSnapshot) {
const type =
snapshot.type === "full"
? this.supervisor.localize("snapshot.full_snapshot")
: this.supervisor.localize("snapshot.partial_snapshot");
snapshot.type === "full" ? "Full snapshot" : "Partial snapshot";
return snapshot.protected ? `${type}, password protected` : type;
}
+65 -131
View File
@@ -6,7 +6,6 @@ import {
PropertyValues,
} from "lit-element";
import { atLeastVersion } from "../../src/common/config/version";
import { computeLocalize } from "../../src/common/translations/localize";
import { fetchHassioAddonsInfo } from "../../src/data/hassio/addon";
import { HassioResponse } from "../../src/data/hassio/common";
import {
@@ -23,28 +22,25 @@ import {
import { fetchSupervisorStore } from "../../src/data/supervisor/store";
import {
getSupervisorEventCollection,
subscribeSupervisorEvents,
Supervisor,
SupervisorObject,
supervisorCollection,
} from "../../src/data/supervisor/supervisor";
import { ProvideHassLitMixin } from "../../src/mixins/provide-hass-lit-mixin";
import { urlSyncMixin } from "../../src/state/url-sync-mixin";
import { HomeAssistant } from "../../src/types";
import { getTranslation } from "../../src/util/common-translation";
declare global {
interface HASSDomEvents {
"supervisor-update": Partial<Supervisor>;
"supervisor-collection-refresh": { collection: SupervisorObject };
"supervisor-colllection-refresh": { colllection: SupervisorObject };
}
}
export class SupervisorBaseElement extends urlSyncMixin(
ProvideHassLitMixin(LitElement)
) {
@property({ attribute: false }) public supervisor: Partial<Supervisor> = {
localize: () => "",
};
@property({ attribute: false }) public supervisor?: Supervisor;
@internalProperty() private _unsubs: Record<string, UnsubscribeFunc> = {};
@@ -53,13 +49,6 @@ export class SupervisorBaseElement extends urlSyncMixin(
Collection<unknown>
> = {};
@internalProperty() private _language = "en";
public connectedCallback(): void {
super.connectedCallback();
this._initializeLocalize();
}
public disconnectedCallback() {
super.disconnectedCallback();
Object.keys(this._unsubs).forEach((unsub) => {
@@ -67,155 +56,100 @@ export class SupervisorBaseElement extends urlSyncMixin(
});
}
protected updated(changedProperties: PropertyValues) {
super.updated(changedProperties);
if (changedProperties.has("hass")) {
const oldHass = changedProperties.get("hass") as
| HomeAssistant
| undefined;
if (
oldHass !== undefined &&
oldHass.language !== undefined &&
oldHass.language !== this.hass.language
) {
this._language = this.hass.language;
}
}
if (changedProperties.has("_language")) {
if (changedProperties.get("_language") !== this._language) {
this._initializeLocalize();
}
}
if (changedProperties.has("_collections")) {
if (this._collections) {
const unsubs = Object.keys(this._unsubs);
for (const collection of Object.keys(this._collections)) {
if (!unsubs.includes(collection)) {
this._unsubs[collection] = this._collections[
collection
].subscribe((data) =>
this._updateSupervisor({ [collection]: data })
);
}
}
}
}
}
protected _updateSupervisor(obj: Partial<Supervisor>): void {
this.supervisor = { ...this.supervisor, ...obj };
this.supervisor = { ...this.supervisor!, ...obj };
}
protected firstUpdated(changedProps: PropertyValues): void {
super.firstUpdated(changedProps);
if (
this._language !== this.hass.language &&
this.hass.language !== undefined
) {
this._language = this.hass.language;
}
this._initializeLocalize();
this._initSupervisor();
}
private async _initializeLocalize() {
const { language, data } = await getTranslation(
null,
this._language,
"/api/hassio/app/static/translations"
);
this.supervisor = {
...this.supervisor,
localize: await computeLocalize(this.constructor.prototype, language, {
[language]: data,
}),
};
}
private async _handleSupervisorStoreRefreshEvent(ev) {
const collection = ev.detail.collection;
const colllection = ev.detail.colllection;
if (atLeastVersion(this.hass.config.version, 2021, 2, 4)) {
this._collections[collection].refresh();
this._collections[colllection].refresh();
return;
}
const response = await this.hass.callApi<HassioResponse<any>>(
"GET",
`hassio${supervisorCollection[collection]}`
`hassio${supervisorCollection[colllection]}`
);
this._updateSupervisor({ [collection]: response.data });
this._updateSupervisor({ [colllection]: response.data });
}
private async _initSupervisor(): Promise<void> {
this.addEventListener(
"supervisor-collection-refresh",
"supervisor-colllection-refresh",
this._handleSupervisorStoreRefreshEvent
);
if (atLeastVersion(this.hass.config.version, 2021, 2, 4)) {
Object.keys(supervisorCollection).forEach((collection) => {
if (collection in this._collections) {
this._collections[collection].refresh();
Object.keys(supervisorCollection).forEach((colllection) => {
this._unsubs[colllection] = subscribeSupervisorEvents(
this.hass,
(data) => this._updateSupervisor({ [colllection]: data }),
colllection,
supervisorCollection[colllection]
);
if (this._collections[colllection]) {
this._collections[colllection].refresh();
} else {
this._collections[collection] = getSupervisorEventCollection(
this._collections[colllection] = getSupervisorEventCollection(
this.hass.connection,
collection,
supervisorCollection[collection]
colllection,
supervisorCollection[colllection]
);
}
});
Object.keys(this._collections).forEach((collection) => {
if (
this.supervisor === undefined ||
this.supervisor[collection] === undefined
) {
if (this.supervisor === undefined) {
Object.keys(this._collections).forEach((collection) =>
this._updateSupervisor({
[collection]: this._collections[collection].state,
});
}
});
} else {
const [
addon,
supervisor,
host,
core,
info,
os,
network,
resolution,
store,
] = await Promise.all([
fetchHassioAddonsInfo(this.hass),
fetchHassioSupervisorInfo(this.hass),
fetchHassioHostInfo(this.hass),
fetchHassioHomeAssistantInfo(this.hass),
fetchHassioInfo(this.hass),
fetchHassioHassOsInfo(this.hass),
fetchNetworkInfo(this.hass),
fetchHassioResolution(this.hass),
fetchSupervisorStore(this.hass),
]);
this._updateSupervisor({
addon,
supervisor,
host,
core,
info,
os,
network,
resolution,
store,
});
this.addEventListener("supervisor-update", (ev) =>
this._updateSupervisor(ev.detail)
);
})
);
}
return;
}
const [
addon,
supervisor,
host,
core,
info,
os,
network,
resolution,
store,
] = await Promise.all([
fetchHassioAddonsInfo(this.hass),
fetchHassioSupervisorInfo(this.hass),
fetchHassioHostInfo(this.hass),
fetchHassioHomeAssistantInfo(this.hass),
fetchHassioInfo(this.hass),
fetchHassioHassOsInfo(this.hass),
fetchNetworkInfo(this.hass),
fetchHassioResolution(this.hass),
fetchSupervisorStore(this.hass),
]);
this.supervisor = {
addon,
supervisor,
host,
core,
info,
os,
network,
resolution,
store,
};
this.addEventListener("supervisor-update", (ev) =>
this._updateSupervisor(ev.detail)
);
}
}
+16 -50
View File
@@ -10,7 +10,6 @@ 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";
@@ -20,7 +19,7 @@ import {
fetchHassioStats,
HassioStats,
} from "../../../src/data/hassio/common";
import { restartCore, updateCore } from "../../../src/data/supervisor/core";
import { restartCore } from "../../../src/data/supervisor/core";
import { Supervisor } from "../../../src/data/supervisor/supervisor";
import {
showAlertDialog,
@@ -30,7 +29,7 @@ import { haStyle } from "../../../src/resources/styles";
import { HomeAssistant } from "../../../src/types";
import { bytesToString } from "../../../src/util/bytes-to-string";
import "../components/supervisor-metric";
import { showDialogSupervisorUpdate } from "../dialogs/update/show-dialog-update";
import { showDialogSupervisorCoreUpdate } from "../dialogs/core/show-dialog-core-update";
import { hassioStyle } from "../resources/hassio-style";
@customElement("hassio-core-info")
@@ -44,11 +43,11 @@ class HassioCoreInfo extends LitElement {
protected render(): TemplateResult | void {
const metrics = [
{
description: this.supervisor.localize("system.core.cpu_usage"),
description: "Core CPU Usage",
value: this._metrics?.cpu_percent,
},
{
description: this.supervisor.localize("system.core.ram_usage"),
description: "Core RAM Usage",
value: this._metrics?.memory_percent,
tooltip: `${bytesToString(this._metrics?.memory_usage)}/${bytesToString(
this._metrics?.memory_limit
@@ -62,7 +61,7 @@ class HassioCoreInfo extends LitElement {
<div>
<ha-settings-row>
<span slot="heading">
${this.supervisor.localize("common.version")}
Version
</span>
<span slot="description">
core-${this.supervisor.core.version}
@@ -70,7 +69,7 @@ class HassioCoreInfo extends LitElement {
</ha-settings-row>
<ha-settings-row>
<span slot="heading">
${this.supervisor.localize("common.newest_version")}
Newest Version
</span>
<span slot="description">
core-${this.supervisor.core.version_latest}
@@ -78,10 +77,10 @@ class HassioCoreInfo extends LitElement {
${this.supervisor.core.update_available
? html`
<ha-progress-button
.title=${this.supervisor.localize("common.update")}
title="Update the core"
@click=${this._coreUpdate}
>
${this.supervisor.localize("common.update")}
Update
</ha-progress-button>
`
: ""}
@@ -105,13 +104,9 @@ class HassioCoreInfo extends LitElement {
slot="primaryAction"
class="warning"
@click=${this._coreRestart}
.title=${this.supervisor.localize(
"common.restart_name",
"name",
"Core"
)}
title="Restart Home Assistant Core"
>
${this.supervisor.localize("common.restart_name", "name", "Core")}
Restart Core
</ha-progress-button>
</div>
</ha-card>
@@ -131,18 +126,10 @@ class HassioCoreInfo extends LitElement {
button.progress = true;
const confirmed = await showConfirmationDialog(this, {
title: this.supervisor.localize(
"confirm.restart.title",
"name",
"Home Assistant Core"
),
text: this.supervisor.localize(
"confirm.restart.text",
"name",
"Home Assistant Core"
),
confirmText: this.supervisor.localize("common.restart"),
dismissText: this.supervisor.localize("common.cancel"),
title: "Restart Home Assistant Core",
text: "Are you sure you want to restart Home Assistant Core",
confirmText: "restart",
dismissText: "cancel",
});
if (!confirmed) {
@@ -155,11 +142,7 @@ class HassioCoreInfo extends LitElement {
} catch (err) {
if (this.hass.connection.connected) {
showAlertDialog(this, {
title: this.supervisor.localize(
"common.failed_to_restart_name",
"name",
"Home AssistantCore"
),
title: "Failed to restart Home Assistant Core",
text: extractApiErrorMessage(err),
});
}
@@ -169,24 +152,7 @@ class HassioCoreInfo extends LitElement {
}
private async _coreUpdate(): Promise<void> {
showDialogSupervisorUpdate(this, {
supervisor: this.supervisor,
name: "Home Assistant Core",
version: this.supervisor.core.version_latest,
snapshotParams: {
name: `core_${this.supervisor.core.version}`,
folders: ["homeassistant"],
homeassistant: true,
},
updateHandler: async () => await this._updateCore(),
});
}
private async _updateCore(): Promise<void> {
await updateCore(this.hass);
fireEvent(this, "supervisor-collection-refresh", {
collection: "core",
});
showDialogSupervisorCoreUpdate(this, { supervisor: this.supervisor });
}
static get styles(): CSSResult[] {
+63 -72
View File
@@ -21,7 +21,7 @@ import "../../../src/components/ha-card";
import "../../../src/components/ha-settings-row";
import {
extractApiErrorMessage,
ignoreSupervisorError,
ignoredStatusCodes,
} from "../../../src/data/hassio/common";
import { fetchHassioHardwareInfo } from "../../../src/data/hassio/hardware";
import {
@@ -65,7 +65,7 @@ class HassioHostInfo extends LitElement {
const metrics = [
{
description: this.supervisor.localize("system.host.used_space"),
description: "Used Space",
value: this._getUsedSpace(
this.supervisor.host.disk_used,
this.supervisor.host.disk_total
@@ -80,13 +80,14 @@ class HassioHostInfo extends LitElement {
${this.supervisor.host.features.includes("hostname")
? html`<ha-settings-row>
<span slot="heading">
${this.supervisor.localize("system.host.hostname")}
Hostname
</span>
<span slot="description">
${this.supervisor.host.hostname}
</span>
<mwc-button
.label=${this.supervisor.localize("system.host.change")}
title="Change the hostname"
label="Change"
@click=${this._changeHostnameClicked}
>
</mwc-button>
@@ -95,13 +96,14 @@ class HassioHostInfo extends LitElement {
${this.supervisor.host.features.includes("network")
? html` <ha-settings-row>
<span slot="heading">
${this.supervisor.localize("system.host.ip_address")}
IP Address
</span>
<span slot="description">
${primaryIpAddress}
</span>
<mwc-button
.label=${this.supervisor.localize("system.host.change")}
title="Change the network"
label="Change"
@click=${this._changeNetworkClicked}
>
</mwc-button>
@@ -110,15 +112,18 @@ class HassioHostInfo extends LitElement {
<ha-settings-row>
<span slot="heading">
${this.supervisor.localize("system.host.operating_system")}
Operating System
</span>
<span slot="description">
${this.supervisor.host.operating_system}
</span>
${this.supervisor.os.update_available
? html`
<ha-progress-button @click=${this._osUpdate}>
${this.supervisor.localize("commmon.update")}
<ha-progress-button
title="Update the host OS"
@click=${this._osUpdate}
>
Update
</ha-progress-button>
`
: ""}
@@ -126,7 +131,7 @@ class HassioHostInfo extends LitElement {
${!this.supervisor.host.features.includes("hassos")
? html`<ha-settings-row>
<span slot="heading">
${this.supervisor.localize("system.host.docker_version")}
Docker version
</span>
<span slot="description">
${this.supervisor.info.docker}
@@ -136,7 +141,7 @@ class HassioHostInfo extends LitElement {
${this.supervisor.host.deployment
? html`<ha-settings-row>
<span slot="heading">
${this.supervisor.localize("system.host.deployment")}
Deployment
</span>
<span slot="description">
${this.supervisor.host.deployment}
@@ -149,13 +154,11 @@ class HassioHostInfo extends LitElement {
this.supervisor.host.disk_life_time >= 10
? html` <ha-settings-row>
<span slot="heading">
${this.supervisor.localize(
"system.host.emmc_lifetime_used"
)}
eMMC Lifetime Used
</span>
<span slot="description">
${this.supervisor.host.disk_life_time - 10} % -
${this.supervisor.host.disk_life_time} %
${this.supervisor.host.disk_life_time - 10}% -
${this.supervisor.host.disk_life_time}%
</span>
</ha-settings-row>`
: ""}
@@ -174,18 +177,23 @@ class HassioHostInfo extends LitElement {
<div class="card-actions">
${this.supervisor.host.features.includes("reboot")
? html`
<ha-progress-button class="warning" @click=${this._hostReboot}>
${this.supervisor.localize("system.host.reboot_host")}
<ha-progress-button
title="Reboot the host OS"
class="warning"
@click=${this._hostReboot}
>
Reboot Host
</ha-progress-button>
`
: ""}
${this.supervisor.host.features.includes("shutdown")
? html`
<ha-progress-button
title="Shutdown the host OS"
class="warning"
@click=${this._hostShutdown}
>
${this.supervisor.localize("system.host.shutdown_host")}
Shutdown Host
</ha-progress-button>
`
: ""}
@@ -197,12 +205,14 @@ class HassioHostInfo extends LitElement {
<mwc-icon-button slot="trigger">
<ha-svg-icon .path=${mdiDotsVertical}></ha-svg-icon>
</mwc-icon-button>
<mwc-list-item>
${this.supervisor.localize("system.host.hardware")}
<mwc-list-item title="Show a list of hardware">
Hardware
</mwc-list-item>
${this.supervisor.host.features.includes("hassos")
? html`<mwc-list-item>
${this.supervisor.localize("system.host.import_from_usb")}
? html`<mwc-list-item
title="Load HassOS configs or updates from USB"
>
Import from USB
</mwc-list-item>`
: ""}
</ha-button-menu>
@@ -241,14 +251,12 @@ class HassioHostInfo extends LitElement {
try {
const content = await fetchHassioHardwareInfo(this.hass);
showHassioMarkdownDialog(this, {
title: this.supervisor.localize("system.host.hardware"),
title: "Hardware",
content: `<pre>${safeDump(content, { indent: 2 })}</pre>`,
});
} catch (err) {
showAlertDialog(this, {
title: this.supervisor.localize(
"system.host.failed_to_get_hardware_list"
),
title: "Failed to get hardware list",
text: extractApiErrorMessage(err),
});
}
@@ -259,10 +267,10 @@ class HassioHostInfo extends LitElement {
button.progress = true;
const confirmed = await showConfirmationDialog(this, {
title: this.supervisor.localize("system.host.reboot_host"),
text: this.supervisor.localize("system.host.confirm_reboot"),
confirmText: this.supervisor.localize("system.host.reboot_host"),
dismissText: this.supervisor.localize("common.cancel"),
title: "Reboot",
text: "Are you sure you want to reboot the host?",
confirmText: "reboot host",
dismissText: "no",
});
if (!confirmed) {
@@ -274,9 +282,9 @@ class HassioHostInfo extends LitElement {
await rebootHost(this.hass);
} catch (err) {
// Ignore connection errors, these are all expected
if (this.hass.connection.connected && !ignoreSupervisorError(err)) {
if (err.status_code && !ignoredStatusCodes.has(err.status_code)) {
showAlertDialog(this, {
title: this.supervisor.localize("system.host.failed_to_reboot"),
title: "Failed to reboot",
text: extractApiErrorMessage(err),
});
}
@@ -289,10 +297,10 @@ class HassioHostInfo extends LitElement {
button.progress = true;
const confirmed = await showConfirmationDialog(this, {
title: this.supervisor.localize("system.host.shutdown_host"),
text: this.supervisor.localize("system.host.confirm_shutdown"),
confirmText: this.supervisor.localize("system.host.shutdown_host"),
dismissText: this.supervisor.localize("common.cancel"),
title: "Shutdown",
text: "Are you sure you want to shutdown the host?",
confirmText: "shutdown host",
dismissText: "no",
});
if (!confirmed) {
@@ -304,9 +312,9 @@ class HassioHostInfo extends LitElement {
await shutdownHost(this.hass);
} catch (err) {
// Ignore connection errors, these are all expected
if (this.hass.connection.connected && !ignoreSupervisorError(err)) {
if (err.status_code && !ignoredStatusCodes.has(err.status_code)) {
showAlertDialog(this, {
title: this.supervisor.localize("system.host.failed_to_shutdown"),
title: "Failed to shutdown",
text: extractApiErrorMessage(err),
});
}
@@ -319,19 +327,9 @@ class HassioHostInfo extends LitElement {
button.progress = true;
const confirmed = await showConfirmationDialog(this, {
title: this.supervisor.localize(
"confirm.update.title",
"name",
"Home Assistant Operating System"
),
text: this.supervisor.localize(
"confirm.update.text",
"name",
"Home Assistant Operating System",
"version",
this.supervisor.os.version_latest
),
confirmText: this.supervisor.localize("common.update"),
title: "Update",
text: "Are you sure you want to update the OS?",
confirmText: "update os",
dismissText: "no",
});
@@ -342,15 +340,11 @@ class HassioHostInfo extends LitElement {
try {
await updateOS(this.hass);
fireEvent(this, "supervisor-collection-refresh", { collection: "os" });
fireEvent(this, "supervisor-colllection-refresh", { colllection: "os" });
} catch (err) {
if (this.hass.connection.connected) {
showAlertDialog(this, {
title: this.supervisor.localize(
"common.failed_to_update_name",
"name",
"Home Assistant Operating System"
),
title: "Failed to update",
text: extractApiErrorMessage(err),
});
}
@@ -360,7 +354,7 @@ class HassioHostInfo extends LitElement {
private async _changeNetworkClicked(): Promise<void> {
showNetworkDialog(this, {
supervisor: this.supervisor,
network: this.supervisor.network!,
loadData: () => this._loadData(),
});
}
@@ -368,22 +362,21 @@ class HassioHostInfo extends LitElement {
private async _changeHostnameClicked(): Promise<void> {
const curHostname: string = this.supervisor.host.hostname;
const hostname = await showPromptDialog(this, {
title: this.supervisor.localize("system.host.change_hostname"),
inputLabel: this.supervisor.localize("system.host.new_hostname"),
title: "Change Hostname",
inputLabel: "Please enter a new hostname:",
inputType: "string",
defaultValue: curHostname,
confirmText: this.supervisor.localize("common.update"),
});
if (hostname && hostname !== curHostname) {
try {
await changeHostOptions(this.hass, { hostname });
fireEvent(this, "supervisor-collection-refresh", {
collection: "host",
fireEvent(this, "supervisor-colllection-refresh", {
colllection: "host",
});
} catch (err) {
showAlertDialog(this, {
title: this.supervisor.localize("system.host.failed_to_set_hostname"),
title: "Setting hostname failed",
text: extractApiErrorMessage(err),
});
}
@@ -393,14 +386,12 @@ class HassioHostInfo extends LitElement {
private async _importFromUSB(): Promise<void> {
try {
await configSyncOS(this.hass);
fireEvent(this, "supervisor-collection-refresh", {
collection: "host",
fireEvent(this, "supervisor-colllection-refresh", {
colllection: "host",
});
} catch (err) {
showAlertDialog(this, {
title: this.supervisor.localize(
"system.host.failed_to_import_from_usb"
),
title: "Failed to import from USB",
text: extractApiErrorMessage(err),
});
}
@@ -408,8 +399,8 @@ class HassioHostInfo extends LitElement {
private async _loadData(): Promise<void> {
if (atLeastVersion(this.hass.config.version, 2021, 2, 4)) {
fireEvent(this, "supervisor-collection-refresh", {
collection: "network",
fireEvent(this, "supervisor-colllection-refresh", {
colllection: "network",
});
} else {
const network = await fetchNetworkInfo(this.hass);
+122 -155
View File
@@ -37,26 +37,54 @@ import { documentationUrl } from "../../../src/util/documentation-url";
import "../components/supervisor-metric";
import { hassioStyle } from "../resources/hassio-style";
const UNSUPPORTED_REASON_URL = {
container: "/more-info/unsupported/container",
dbus: "/more-info/unsupported/dbus",
docker_configuration: "/more-info/unsupported/docker_configuration",
docker_version: "/more-info/unsupported/docker_version",
job_conditions: "/more-info/unsupported/job_conditions",
lxc: "/more-info/unsupported/lxc",
network_manager: "/more-info/unsupported/network_manager",
os: "/more-info/unsupported/os",
privileged: "/more-info/unsupported/privileged",
systemd: "/more-info/unsupported/systemd",
content_trust: "/more-info/unsupported/content_trust",
const UNSUPPORTED_REASON = {
container: {
title: "Containers known to cause issues",
url: "/more-info/unsupported/container",
},
dbus: { title: "DBUS", url: "/more-info/unsupported/dbus" },
docker_configuration: {
title: "Docker Configuration",
url: "/more-info/unsupported/docker_configuration",
},
docker_version: {
title: "Docker Version",
url: "/more-info/unsupported/docker_version",
},
job_conditions: {
title: "Ignored job conditions",
url: "/more-info/unsupported/job_conditions",
},
lxc: { title: "LXC", url: "/more-info/unsupported/lxc" },
network_manager: {
title: "Network Manager",
url: "/more-info/unsupported/network_manager",
},
os: { title: "Operating System", url: "/more-info/unsupported/os" },
privileged: {
title: "Supervisor is not privileged",
url: "/more-info/unsupported/privileged",
},
systemd: { title: "Systemd", url: "/more-info/unsupported/systemd" },
};
const UNHEALTHY_REASON_URL = {
privileged: "/more-info/unsupported/privileged",
supervisor: "/more-info/unhealthy/supervisor",
setup: "/more-info/unhealthy/setup",
docker: "/more-info/unhealthy/docker",
untrusted: "/more-info/unhealthy/untrusted",
const UNHEALTHY_REASON = {
privileged: {
title: "Supervisor is not privileged",
url: "/more-info/unsupported/privileged",
},
supervisor: {
title: "Supervisor was not able to update",
url: "/more-info/unhealthy/supervisor",
},
setup: {
title: "Setup of the Supervisor failed",
url: "/more-info/unhealthy/setup",
},
docker: {
title: "The Docker environment is not working properly",
url: "/more-info/unhealthy/docker",
},
};
@customElement("hassio-supervisor-info")
@@ -70,11 +98,11 @@ class HassioSupervisorInfo extends LitElement {
protected render(): TemplateResult | void {
const metrics = [
{
description: this.supervisor.localize("system.supervisor.cpu_usage"),
description: "Supervisor CPU Usage",
value: this._metrics?.cpu_percent,
},
{
description: this.supervisor.localize("system.supervisor.ram_usage"),
description: "Supervisor RAM Usage",
value: this._metrics?.memory_percent,
tooltip: `${bytesToString(this._metrics?.memory_usage)}/${bytesToString(
this._metrics?.memory_limit
@@ -87,7 +115,7 @@ class HassioSupervisorInfo extends LitElement {
<div>
<ha-settings-row>
<span slot="heading">
${this.supervisor.localize("common.version")}
Version
</span>
<span slot="description">
supervisor-${this.supervisor.supervisor.version}
@@ -95,7 +123,7 @@ class HassioSupervisorInfo extends LitElement {
</ha-settings-row>
<ha-settings-row>
<span slot="heading">
${this.supervisor.localize("common.newest_version")}
Newest Version
</span>
<span slot="description">
supervisor-${this.supervisor.supervisor.version_latest}
@@ -103,19 +131,17 @@ class HassioSupervisorInfo extends LitElement {
${this.supervisor.supervisor.update_available
? html`
<ha-progress-button
.title=${this.supervisor.localize(
"system.supervisor.update_supervisor"
)}
title="Update the supervisor"
@click=${this._supervisorUpdate}
>
${this.supervisor.localize("common.update")}
Update
</ha-progress-button>
`
: ""}
</ha-settings-row>
<ha-settings-row>
<span slot="heading">
${this.supervisor.localize("system.supervisor.channel")}
Channel
</span>
<span slot="description">
${this.supervisor.supervisor.channel}
@@ -124,26 +150,18 @@ class HassioSupervisorInfo extends LitElement {
? html`
<ha-progress-button
@click=${this._toggleBeta}
.title=${this.supervisor.localize(
"system.supervisor.leave_beta_description"
)}
title="Get stable updates for Home Assistant, supervisor and host"
>
${this.supervisor.localize(
"system.supervisor.leave_beta_action"
)}
Leave beta channel
</ha-progress-button>
`
: this.supervisor.supervisor.channel === "stable"
? html`
<ha-progress-button
@click=${this._toggleBeta}
.title=${this.supervisor.localize(
"system.supervisor.join_beta_description"
)}
title="Get beta updates for Home Assistant (RCs), supervisor and host"
>
${this.supervisor.localize(
"system.supervisor.join_beta_action"
)}
Join beta channel
</ha-progress-button>
`
: ""}
@@ -152,20 +170,16 @@ class HassioSupervisorInfo extends LitElement {
${this.supervisor.supervisor.supported
? html` <ha-settings-row three-line>
<span slot="heading">
${this.supervisor.localize(
"system.supervisor.share_diagnostics"
)}
Share Diagnostics
</span>
<div slot="description" class="diagnostics-description">
${this.supervisor.localize(
"system.supervisor.share_diagnostics_description"
)}
Share crash reports and diagnostic information.
<button
class="link"
.title=${this.supervisor.localize("common.show_more")}
title="Show more information about this"
@click=${this._diagnosticsInformationDialog}
>
${this.supervisor.localize("common.learn_more")}
Learn more
</button>
</div>
<ha-switch
@@ -175,12 +189,10 @@ class HassioSupervisorInfo extends LitElement {
></ha-switch>
</ha-settings-row>`
: html`<div class="error">
${this.supervisor.localize(
"system.supervisor.unsupported_title"
)}
You are running an unsupported installation.
<button
class="link"
.title=${this.supervisor.localize("common.learn_more")}
title="Learn more about how you can make your system compliant"
@click=${this._unsupportedDialog}
>
Learn more
@@ -188,12 +200,10 @@ class HassioSupervisorInfo extends LitElement {
</div>`}
${!this.supervisor.supervisor.healthy
? html`<div class="error">
${this.supervisor.localize(
"system.supervisor.unhealthy_title"
)}
Your installation is running in an unhealthy state.
<button
class="link"
.title=${this.supervisor.localize("common.learn_more")}
title="Learn more about why your system is marked as unhealthy"
@click=${this._unhealthyDialog}
>
Learn more
@@ -217,26 +227,16 @@ class HassioSupervisorInfo extends LitElement {
<div class="card-actions">
<ha-progress-button
@click=${this._supervisorReload}
.title=${this.supervisor.localize(
"system.supervisor.reload_supervisor"
)}
title="Reload parts of the Supervisor"
>
${this.supervisor.localize("system.supervisor.reload_supervisor")}
Reload Supervisor
</ha-progress-button>
<ha-progress-button
class="warning"
@click=${this._supervisorRestart}
.title=${this.supervisor.localize(
"common.restart_name",
"name",
"Supervisor"
)}
title="Restart the Supervisor"
>
${this.supervisor.localize(
"common.restart_name",
"name",
"Supervisor"
)}
Restart Supervisor
</ha-progress-button>
</div>
</ha-card>
@@ -257,23 +257,23 @@ class HassioSupervisorInfo extends LitElement {
if (this.supervisor.supervisor.channel === "stable") {
const confirmed = await showConfirmationDialog(this, {
title: this.supervisor.localize("system.supervisor.warning"),
text: html`${this.supervisor.localize("system.supervisor.beta_warning")}
title: "WARNING",
text: html` Beta releases are for testers and early adopters and can
contain unstable code changes.
<br />
<b>
${this.supervisor.localize("system.supervisor.beta_backup")}
Make sure you have backups of your data before you activate this
feature.
</b>
<br /><br />
${this.supervisor.localize("system.supervisor.beta_release_items")}
This includes beta releases for:
<li>Home Assistant Core</li>
<li>Home Assistant Supervisor</li>
<li>Home Assistant Operating System</li>
<br />
${this.supervisor.localize("system.supervisor.join_beta_action")}`,
confirmText: this.supervisor.localize(
"system.supervisor.beta_join_confirm"
),
dismissText: this.supervisor.localize("common.cancel"),
Do you want to join the beta channel?`,
confirmText: "join beta",
dismissText: "no",
});
if (!confirmed) {
@@ -291,9 +291,7 @@ class HassioSupervisorInfo extends LitElement {
await this._reloadSupervisor();
} catch (err) {
showAlertDialog(this, {
title: this.supervisor.localize(
"system.supervisor.failed_to_set_option"
),
title: "Failed to set supervisor option",
text: extractApiErrorMessage(err),
});
} finally {
@@ -309,7 +307,7 @@ class HassioSupervisorInfo extends LitElement {
await this._reloadSupervisor();
} catch (err) {
showAlertDialog(this, {
title: this.supervisor.localize("system.supervisor.failed_to_reload"),
title: "Failed to reload the supervisor",
text: extractApiErrorMessage(err),
});
} finally {
@@ -319,8 +317,8 @@ class HassioSupervisorInfo extends LitElement {
private async _reloadSupervisor(): Promise<void> {
await reloadSupervisor(this.hass);
fireEvent(this, "supervisor-collection-refresh", {
collection: "supervisor",
fireEvent(this, "supervisor-colllection-refresh", {
colllection: "supervisor",
});
}
@@ -329,18 +327,10 @@ class HassioSupervisorInfo extends LitElement {
button.progress = true;
const confirmed = await showConfirmationDialog(this, {
title: this.supervisor.localize(
"confirm.restart.title",
"name",
"Supervisor"
),
text: this.supervisor.localize(
"confirm.restart.text",
"name",
"Supervisor"
),
confirmText: this.supervisor.localize("common.restart"),
dismissText: this.supervisor.localize("common.cancel"),
title: "Restart the Supervisor",
text: "Are you sure you want to restart the Supervisor",
confirmText: "restart",
dismissText: "cancel",
});
if (!confirmed) {
@@ -352,11 +342,7 @@ class HassioSupervisorInfo extends LitElement {
await restartSupervisor(this.hass);
} catch (err) {
showAlertDialog(this, {
title: this.supervisor.localize(
"common.failed_to_restart_name",
"name",
"Supervisor"
),
title: "Failed to restart the supervisor",
text: extractApiErrorMessage(err),
});
} finally {
@@ -369,20 +355,10 @@ class HassioSupervisorInfo extends LitElement {
button.progress = true;
const confirmed = await showConfirmationDialog(this, {
title: this.supervisor.localize(
"confirm.update.title",
"name",
"Supervisor"
),
text: this.supervisor.localize(
"confirm.update.text",
"name",
"Supervisor",
"version",
this.supervisor.supervisor.version_latest
),
confirmText: this.supervisor.localize("common.update"),
dismissText: this.supervisor.localize("common.cancel"),
title: "Update Supervisor",
text: `Are you sure you want to update supervisor to version ${this.supervisor.supervisor.version_latest}?`,
confirmText: "update",
dismissText: "cancel",
});
if (!confirmed) {
@@ -392,16 +368,12 @@ class HassioSupervisorInfo extends LitElement {
try {
await updateSupervisor(this.hass);
fireEvent(this, "supervisor-collection-refresh", {
collection: "supervisor",
fireEvent(this, "supervisor-colllection-refresh", {
colllection: "supervisor",
});
} catch (err) {
showAlertDialog(this, {
title: this.supervisor.localize(
"common.failed_to_update_name",
"name",
"Supervisor"
),
title: "Failed to update the supervisor",
text: extractApiErrorMessage(err),
});
} finally {
@@ -411,41 +383,40 @@ class HassioSupervisorInfo extends LitElement {
private async _diagnosticsInformationDialog(): Promise<void> {
await showAlertDialog(this, {
title: this.supervisor.localize(
"system.supervisor.share_diagonstics_title"
),
text: this.supervisor.localize(
"system.supervisor.share_diagonstics_description",
"line_break",
html`<br /><br />`
),
title: "Help Improve Home Assistant",
text: html`Would you want to automatically share crash reports and
diagnostic information when the supervisor encounters unexpected errors?
<br /><br />
This will allow us to fix the problems, the information is only
accessible to the Home Assistant Core team and will not be shared with
others.
<br /><br />
The data does not include any private/sensitive information and you can
disable this in settings at any time you want.`,
});
}
private async _unsupportedDialog(): Promise<void> {
await showAlertDialog(this, {
title: this.supervisor.localize("system.supervisor.unsupported_title"),
text: html`${this.supervisor.localize(
"system.supervisor.unsupported_description"
)} <br /><br />
title: "You are running an unsupported installation",
text: html`Below is a list of issues found with your installation, click
on the links to learn how you can resolve the issues. <br /><br />
<ul>
${this.supervisor.resolution.unsupported.map(
(reason) => html`
(issue) => html`
<li>
${UNSUPPORTED_REASON_URL[reason]
${UNSUPPORTED_REASON[issue]
? html`<a
href="${documentationUrl(
this.hass,
UNSUPPORTED_REASON_URL[reason]
UNSUPPORTED_REASON[issue].url
)}"
target="_blank"
rel="noreferrer"
>
${this.supervisor.localize(
`system.supervisor.unsupported_reason.${reason}`
) || reason}
${UNSUPPORTED_REASON[issue].title}
</a>`
: reason}
: issue}
</li>
`
)}
@@ -455,28 +426,26 @@ class HassioSupervisorInfo extends LitElement {
private async _unhealthyDialog(): Promise<void> {
await showAlertDialog(this, {
title: this.supervisor.localize("system.supervisor.unhealthy_title"),
text: html`${this.supervisor.localize(
"system.supervisor.unhealthy_description"
)} <br /><br />
title: "Your installation is unhealthy",
text: html`Running an unhealthy installation will cause issues. Below is a
list of issues found with your installation, click on the links to learn
how you can resolve the issues. <br /><br />
<ul>
${this.supervisor.resolution.unhealthy.map(
(reason) => html`
(issue) => html`
<li>
${UNHEALTHY_REASON_URL[reason]
${UNHEALTHY_REASON[issue]
? html`<a
href="${documentationUrl(
this.hass,
UNHEALTHY_REASON_URL[reason]
UNHEALTHY_REASON[issue].url
)}"
target="_blank"
rel="noreferrer"
>
${this.supervisor.localize(
`system.supervisor.unhealthy_reason.${reason}`
) || reason}
${UNHEALTHY_REASON[issue].title}
</a>`
: reason}
: issue}
</li>
`
)}
@@ -492,9 +461,7 @@ class HassioSupervisorInfo extends LitElement {
await setSupervisorOption(this.hass, data);
} catch (err) {
showAlertDialog(this, {
title: this.supervisor.localize(
"system.supervisor.failed_to_set_option"
),
title: "Failed to set supervisor option",
text: extractApiErrorMessage(err),
});
}
+11 -17
View File
@@ -16,7 +16,6 @@ import "../../../src/components/buttons/ha-progress-button";
import "../../../src/components/ha-card";
import { extractApiErrorMessage } from "../../../src/data/hassio/common";
import { fetchHassioLogs } from "../../../src/data/hassio/supervisor";
import { Supervisor } from "../../../src/data/supervisor/supervisor";
import "../../../src/layouts/hass-loading-screen";
import { haStyle } from "../../../src/resources/styles";
import { HomeAssistant } from "../../../src/types";
@@ -59,8 +58,6 @@ const logProviders: LogProvider[] = [
class HassioSupervisorLog extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property({ attribute: false }) public supervisor!: Supervisor;
@internalProperty() private _error?: string;
@internalProperty() private _selectedLogProvider = "supervisor";
@@ -79,7 +76,7 @@ class HassioSupervisorLog extends LitElement {
${this.hass.userData?.showAdvanced
? html`
<paper-dropdown-menu
.label=${this.supervisor.localize("system.log.log_provider")}
label="Log Provider"
@iron-select=${this._setLogProvider}
>
<paper-listbox
@@ -89,9 +86,9 @@ class HassioSupervisorLog extends LitElement {
>
${logProviders.map((provider) => {
return html`
<paper-item provider=${provider.key}>
${provider.name}
</paper-item>
<paper-item provider=${provider.key}
>${provider.name}</paper-item
>
`;
})}
</paper-listbox>
@@ -101,13 +98,14 @@ class HassioSupervisorLog extends LitElement {
<div class="card-content" id="content">
${this._content
? html`<hassio-ansi-to-html .content=${this._content}>
</hassio-ansi-to-html>`
? html`<hassio-ansi-to-html
.content=${this._content}
></hassio-ansi-to-html>`
: html`<hass-loading-screen no-toolbar></hass-loading-screen>`}
</div>
<div class="card-actions">
<ha-progress-button @click=${this._refresh}>
${this.supervisor.localize("common.refresh")}
Refresh
</ha-progress-button>
</div>
</ha-card>
@@ -136,13 +134,9 @@ class HassioSupervisorLog extends LitElement {
this._selectedLogProvider
);
} catch (err) {
this._error = this.supervisor.localize(
"system.log.get_logs",
"provider",
this._selectedLogProvider,
"error",
extractApiErrorMessage(err)
);
this._error = `Failed to get supervisor logs, ${extractApiErrorMessage(
err
)}`;
}
}
+4 -10
View File
@@ -32,16 +32,13 @@ class HassioSystem extends LitElement {
return html`
<hass-tabs-subpage
.hass=${this.hass}
.localizeFunc=${this.supervisor.localize}
.narrow=${this.narrow}
hassio
main-page
.route=${this.route}
.tabs=${supervisorTabs}
main-page
supervisor
>
<span slot="header">
${this.supervisor.localize("panel.system")}
</span>
<span slot="header">System</span>
<div class="content">
<div class="card-group">
<hassio-core-info
@@ -57,10 +54,7 @@ class HassioSystem extends LitElement {
.supervisor=${this.supervisor}
></hassio-host-info>
</div>
<hassio-supervisor-log
.hass=${this.hass}
.supervisor=${this.supervisor}
></hassio-supervisor-log>
<hassio-supervisor-log .hass=${this.hass}></hassio-supervisor-log>
</div>
</hass-tabs-subpage>
`;
+12 -14
View File
@@ -23,17 +23,16 @@
"license": "Apache-2.0",
"dependencies": {
"@braintree/sanitize-url": "^5.0.0",
"@codemirror/commands": "^0.18.0",
"@codemirror/gutter": "^0.18.0",
"@codemirror/highlight": "^0.18.0",
"@codemirror/history": "^0.18.0",
"@codemirror/legacy-modes": "^0.18.0",
"@codemirror/rectangular-selection": "^0.18.0",
"@codemirror/search": "^0.18.0",
"@codemirror/state": "^0.18.0",
"@codemirror/stream-parser": "^0.18.0",
"@codemirror/text": "^0.18.0",
"@codemirror/view": "^0.18.0",
"@codemirror/commands": "^0.17.0",
"@codemirror/gutter": "^0.17.0",
"@codemirror/highlight": "^0.17.0",
"@codemirror/history": "^0.17.0",
"@codemirror/legacy-modes": "^0.17.0",
"@codemirror/search": "^0.17.0",
"@codemirror/state": "^0.17.0",
"@codemirror/stream-parser": "^0.17.0",
"@codemirror/text": "^0.17.0",
"@codemirror/view": "^0.17.0",
"@formatjs/intl-getcanonicallocales": "^1.4.6",
"@formatjs/intl-pluralrules": "^3.4.10",
"@fullcalendar/common": "5.1.0",
@@ -91,6 +90,8 @@
"@polymer/paper-tooltip": "^3.0.1",
"@polymer/polymer": "3.1.0",
"@thomasloven/round-slider": "0.5.2",
"@types/chromecast-caf-sender": "^1.0.3",
"@types/sortablejs": "^1.10.6",
"@vaadin/vaadin-combo-box": "^5.0.10",
"@vaadin/vaadin-date-picker": "^4.0.7",
"@vibrant/color": "^3.2.1-alpha.1",
@@ -132,7 +133,6 @@
"sortablejs": "^1.10.2",
"superstruct": "^0.10.13",
"tinykeys": "^1.1.1",
"tsparticles": "^1.19.2",
"unfetch": "^4.1.0",
"vis-data": "^7.1.1",
"vis-network": "^8.5.4",
@@ -166,7 +166,6 @@
"@rollup/plugin-replace": "^2.3.2",
"@types/chai": "^4.1.7",
"@types/chromecast-caf-receiver": "^5.0.11",
"@types/chromecast-caf-sender": "^1.0.3",
"@types/codemirror": "^0.0.97",
"@types/hls.js": "^0.12.3",
"@types/js-yaml": "^3.12.1",
@@ -176,7 +175,6 @@
"@types/memoize-one": "4.1.0",
"@types/mocha": "^7.0.2",
"@types/resize-observer-browser": "^0.1.3",
"@types/sortablejs": "^1.10.6",
"@types/webspeechapi": "^0.0.29",
"@typescript-eslint/eslint-plugin": "^4.4.0",
"@typescript-eslint/parser": "^4.4.0",
+2 -2
View File
@@ -2,12 +2,12 @@ from setuptools import setup, find_packages
setup(
name="home-assistant-frontend",
version="20210324.0",
version="20210301.0",
description="The Home Assistant frontend",
url="https://github.com/home-assistant/home-assistant-polymer",
author="The Home Assistant Authors",
author_email="[email protected]",
license="Apache-2.0",
license="Apache License 2.0",
packages=find_packages(include=["hass_frontend", "hass_frontend.*"]),
include_package_data=True,
zip_safe=False,
+3 -4
View File
@@ -8,10 +8,9 @@ export const atLeastVersion = (
return (
Number(haMajor) > major ||
(Number(haMajor) === major &&
(patch === undefined
? Number(haMinor) >= minor
: Number(haMinor) > minor)) ||
(Number(haMajor) === major && (patch === undefined
? Number(haMinor) >= minor
: Number(haMinor) > minor)) ||
(patch !== undefined &&
Number(haMajor) === major &&
Number(haMinor) === minor &&
-3
View File
@@ -56,8 +56,6 @@ export const FIXED_DOMAIN_ICONS = {
export const FIXED_DEVICE_CLASS_ICONS = {
current: "hass:current-ac",
carbon_dioxide: "mdi:molecule-co2",
carbon_monoxide: "mdi:molecule-co",
energy: "hass:flash",
humidity: "hass:water-percent",
illuminance: "hass:brightness-5",
@@ -105,7 +103,6 @@ export const DOMAINS_WITH_MORE_INFO = [
"lock",
"media_player",
"person",
"remote",
"script",
"sun",
"timer",
File diff suppressed because it is too large Load Diff
-399
View File
@@ -1,399 +0,0 @@
export enum EntityTouch {
None = 0,
AsOld = 1,
AsNew = 2,
}
interface Item<K, V> {
previous: Item<K, V> | undefined;
next: Item<K, V> | undefined;
key: K;
value: V;
}
export class LinkedMap<K, V> implements Map<K, V> {
readonly [Symbol.toStringTag] = "LinkedMap";
private _map: Map<K, Item<K, V>>;
private _head: Item<K, V> | undefined;
private _tail: Item<K, V> | undefined;
private _size: number;
private _state: number;
constructor() {
this._map = new Map<K, Item<K, V>>();
this._head = undefined;
this._tail = undefined;
this._size = 0;
this._state = 0;
}
clear(): void {
this._map.clear();
this._head = undefined;
this._tail = undefined;
this._size = 0;
this._state++;
}
isEmpty(): boolean {
return !this._head && !this._tail;
}
get size(): number {
return this._size;
}
get first(): V | undefined {
return this._head?.value;
}
get last(): V | undefined {
return this._tail?.value;
}
has(key: K): boolean {
return this._map.has(key);
}
get(key: K, touch: EntityTouch = EntityTouch.None): V | undefined {
const item = this._map.get(key);
if (!item) {
return undefined;
}
if (touch !== EntityTouch.None) {
this.touch(item, touch);
}
return item.value;
}
set(key: K, value: V, touch: EntityTouch = EntityTouch.None): this {
let item = this._map.get(key);
if (item) {
item.value = value;
if (touch !== EntityTouch.None) {
this.touch(item, touch);
}
} else {
item = { key, value, next: undefined, previous: undefined };
switch (touch) {
case EntityTouch.None:
this.addItemLast(item);
break;
case EntityTouch.AsOld:
this.addItemFirst(item);
break;
case EntityTouch.AsNew:
this.addItemLast(item);
break;
default:
this.addItemLast(item);
break;
}
this._map.set(key, item);
this._size++;
}
return this;
}
delete(key: K): boolean {
return !!this.remove(key);
}
remove(key: K): V | undefined {
const item = this._map.get(key);
if (!item) {
return undefined;
}
this._map.delete(key);
this.removeItem(item);
this._size--;
return item.value;
}
shift(): V | undefined {
if (!this._head && !this._tail) {
return undefined;
}
if (!this._head || !this._tail) {
throw new Error("Invalid list");
}
const item = this._head;
this._map.delete(item.key);
this.removeItem(item);
this._size--;
return item.value;
}
forEach(
callbackfn: (value: V, key: K, map: LinkedMap<K, V>) => void,
thisArg?: any
): void {
const state = this._state;
let current = this._head;
while (current) {
if (thisArg) {
callbackfn.bind(thisArg)(current.value, current.key, this);
} else {
callbackfn(current.value, current.key, this);
}
if (this._state !== state) {
throw new Error(`LinkedMap got modified during iteration.`);
}
current = current.next;
}
}
keys(): IterableIterator<K> {
// eslint-disable-next-line @typescript-eslint/no-this-alias
const map = this;
const state = this._state;
let current = this._head;
const iterator: IterableIterator<K> = {
[Symbol.iterator]() {
return iterator;
},
next(): IteratorResult<K> {
if (map._state !== state) {
throw new Error(`LinkedMap got modified during iteration.`);
}
if (current) {
const result = { value: current.key, done: false };
current = current.next;
return result;
}
return { value: undefined, done: true };
},
};
return iterator;
}
values(): IterableIterator<V> {
// eslint-disable-next-line @typescript-eslint/no-this-alias
const map = this;
const state = this._state;
let current = this._head;
const iterator: IterableIterator<V> = {
[Symbol.iterator]() {
return iterator;
},
next(): IteratorResult<V> {
if (map._state !== state) {
throw new Error(`LinkedMap got modified during iteration.`);
}
if (current) {
const result = {
value: current.value,
done: false,
};
current = current.next;
return result;
}
return { value: undefined, done: true };
},
};
return iterator;
}
entries(): IterableIterator<[K, V]> {
// eslint-disable-next-line @typescript-eslint/no-this-alias
const map = this;
const state = this._state;
let current = this._head;
const iterator: IterableIterator<[K, V]> = {
[Symbol.iterator]() {
return iterator;
},
next(): IteratorResult<[K, V]> {
if (map._state !== state) {
throw new Error(`LinkedMap got modified during iteration.`);
}
if (current) {
const result: IteratorResult<[K, V]> = {
value: [current.key, current.value],
done: false,
};
current = current.next;
return result;
}
return {
value: undefined,
done: true,
};
},
};
return iterator;
}
[Symbol.iterator](): IterableIterator<[K, V]> {
return this.entries();
}
protected trimOld(newSize: number) {
if (newSize >= this.size) {
return;
}
if (newSize === 0) {
this.clear();
return;
}
let current = this._head;
let currentSize = this.size;
while (current && currentSize > newSize) {
this._map.delete(current.key);
current = current.next;
currentSize--;
}
this._head = current;
this._size = currentSize;
if (current) {
current.previous = undefined;
}
this._state++;
}
private addItemFirst(item: Item<K, V>): void {
// First time Insert
if (!this._head && !this._tail) {
this._tail = item;
} else if (!this._head) {
throw new Error("Invalid list");
} else {
item.next = this._head;
this._head.previous = item;
}
this._head = item;
this._state++;
}
private addItemLast(item: Item<K, V>): void {
// First time Insert
if (!this._head && !this._tail) {
this._head = item;
} else if (!this._tail) {
throw new Error("Invalid list");
} else {
item.previous = this._tail;
this._tail.next = item;
}
this._tail = item;
this._state++;
}
private removeItem(item: Item<K, V>): void {
if (item === this._head && item === this._tail) {
this._head = undefined;
this._tail = undefined;
} else if (item === this._head) {
// This can only happend if size === 1 which is handle
// by the case above.
if (!item.next) {
throw new Error("Invalid list");
}
item.next.previous = undefined;
this._head = item.next;
} else if (item === this._tail) {
// This can only happend if size === 1 which is handle
// by the case above.
if (!item.previous) {
throw new Error("Invalid list");
}
item.previous.next = undefined;
this._tail = item.previous;
} else {
const next = item.next;
const previous = item.previous;
if (!next || !previous) {
throw new Error("Invalid list");
}
next.previous = previous;
previous.next = next;
}
item.next = undefined;
item.previous = undefined;
this._state++;
}
private touch(item: Item<K, V>, touch: EntityTouch): void {
if (!this._head || !this._tail) {
throw new Error("Invalid list");
}
if (touch !== EntityTouch.AsOld && touch !== EntityTouch.AsNew) {
return;
}
if (touch === EntityTouch.AsOld) {
if (item === this._head) {
return;
}
const next = item.next;
const previous = item.previous;
// Unlink the item
if (item === this._tail) {
// previous must be defined since item was not head but is tail
// So there are more than on item in the map
previous!.next = undefined;
this._tail = previous;
} else {
// Both next and previous are not undefined since item was neither head nor tail.
next!.previous = previous;
previous!.next = next;
}
// Insert the node at head
item.previous = undefined;
item.next = this._head;
this._head.previous = item;
this._head = item;
this._state++;
} else if (touch === EntityTouch.AsNew) {
if (item === this._tail) {
return;
}
const next = item.next;
const previous = item.previous;
// Unlink the item.
if (item === this._head) {
// next must be defined since item was not tail but is head
// So there are more than on item in the map
next!.previous = undefined;
this._head = next;
} else {
// Both next and previous are not undefined since item was neither head nor tail.
next!.previous = previous;
previous!.next = next;
}
item.next = undefined;
item.previous = this._tail;
this._tail.next = item;
this._tail = item;
this._state++;
}
}
toJSON(): [K, V][] {
const data: [K, V][] = [];
this.forEach((value, key) => {
data.push([key, value]);
});
return data;
}
fromJSON(data: [K, V][]): void {
this.clear();
for (const [key, value] of data) {
this.set(key, value);
}
}
}
-51
View File
@@ -1,51 +0,0 @@
import { EntityTouch, LinkedMap } from "./linked-map";
export class LRUCache<K, V> extends LinkedMap<K, V> {
private _limit: number;
private _ratio: number;
constructor(limit: number, ratio = 1) {
super();
this._limit = limit;
this._ratio = Math.min(Math.max(0, ratio), 1);
}
get limit(): number {
return this._limit;
}
set limit(limit: number) {
this._limit = limit;
this.checkTrim();
}
get ratio(): number {
return this._ratio;
}
set ratio(ratio: number) {
this._ratio = Math.min(Math.max(0, ratio), 1);
this.checkTrim();
}
get(key: K, touch: EntityTouch = EntityTouch.AsNew): V | undefined {
return super.get(key, touch);
}
peek(key: K): V | undefined {
return super.get(key, EntityTouch.None);
}
set(key: K, value: V): this {
super.set(key, value, EntityTouch.AsNew);
this.checkTrim();
return this;
}
private checkTrim() {
if (this.size > this._limit) {
this.trimOld(Math.round(this._limit * this._ratio));
}
}
}
+6 -49
View File
@@ -1,4 +1,4 @@
import { createMatches, FuzzyScore, fuzzyScore } from "./filter";
import { fuzzyScore } from "./filter";
/**
* Determine whether a sequence of letters exists in another string,
@@ -11,8 +11,7 @@ import { createMatches, FuzzyScore, fuzzyScore } from "./filter";
*/
export const fuzzySequentialMatch = (filter: string, ...words: string[]) => {
let topScore = Number.NEGATIVE_INFINITY;
let topScores: FuzzyScore | undefined;
let topScore = 0;
for (const word of words) {
const scores = fuzzyScore(
@@ -32,29 +31,19 @@ export const fuzzySequentialMatch = (filter: string, ...words: string[]) => {
// The VS Code implementation of filter treats a score of "0" as just barely a match
// But we will typically use this matcher in a .filter(), which interprets 0 as a failure.
// By shifting all scores up by 1, we allow "0" matches, while retaining score precedence
const score = scores[0] === 0 ? 1 : scores[0];
const score = scores[0] + 1;
if (score > topScore) {
topScore = score;
topScores = scores;
}
}
if (topScore === Number.NEGATIVE_INFINITY) {
return undefined;
}
return {
score: topScore,
decoratedText: getDecoratedText(filter, words[0]), // Need to change this to account for any N words
};
return topScore;
};
export interface ScorableTextItem {
score?: number;
text: string;
altText?: string;
decoratedText?: string;
}
type FuzzyFilterSort = <T extends ScorableTextItem>(
@@ -65,45 +54,13 @@ type FuzzyFilterSort = <T extends ScorableTextItem>(
export const fuzzyFilterSort: FuzzyFilterSort = (filter, items) => {
return items
.map((item) => {
const match = item.altText
item.score = item.altText
? fuzzySequentialMatch(filter, item.text, item.altText)
: fuzzySequentialMatch(filter, item.text);
item.score = match?.score;
item.decoratedText = match?.decoratedText;
return item;
})
.filter((item) => item.score !== undefined)
.filter((item) => item.score !== undefined && item.score > 0)
.sort(({ score: scoreA = 0 }, { score: scoreB = 0 }) =>
scoreA > scoreB ? -1 : scoreA < scoreB ? 1 : 0
);
};
export const getDecoratedText = (pattern: string, word: string) => {
const r = fuzzyScore(
pattern,
pattern.toLowerCase(),
0,
word,
word.toLowerCase(),
0,
true
);
if (r) {
const matches = createMatches(r);
let actualWord = "";
let pos = 0;
for (const match of matches) {
actualWord += word.substring(pos, match.start);
actualWord +=
"^" + word.substring(match.start, match.end).split("").join("^");
pos = match.end;
}
actualWord += word.substring(pos);
console.log(actualWord);
return actualWord;
}
return undefined;
};
-17
View File
@@ -1,17 +0,0 @@
const isTemplateRegex = new RegExp("{%|{{|{#");
export const isTemplate = (value: string): boolean =>
isTemplateRegex.test(value);
export const hasTemplate = (value: unknown): boolean => {
if (!value) {
return false;
}
if (typeof value === "string") {
return isTemplate(value);
}
if (typeof value === "object") {
const values = Array.isArray(value) ? value : Object.values(value!);
return values.some((val) => hasTemplate(val));
}
return false;
};
+5 -10
View File
@@ -236,19 +236,20 @@ export class HaDataTable extends LitElement {
"auto-height": this.autoHeight,
})}"
role="table"
aria-rowcount=${this._filteredData.length + 1}
aria-rowcount=${this._filteredData.length}
style=${styleMap({
height: this.autoHeight
? `${(this._filteredData.length || 1) * 53 + 57}px`
: `calc(100% - ${this._headerHeight}px)`,
})}
>
<div class="mdc-data-table__header-row" role="row" aria-rowindex="1">
<div class="mdc-data-table__header-row" role="row">
${this.selectable
? html`
<div
class="mdc-data-table__header-cell mdc-data-table__header-cell--checkbox"
role="columnheader"
scope="col"
>
<ha-checkbox
class="mdc-data-table__row-checkbox"
@@ -291,13 +292,7 @@ export class HaDataTable extends LitElement {
})
: ""}
role="columnheader"
aria-sort=${ifDefined(
sorted
? this._sortDirection === "desc"
? "descending"
: "ascending"
: undefined
)}
scope="col"
@click=${this._handleHeaderClick}
.columnId=${key}
>
@@ -343,7 +338,7 @@ export class HaDataTable extends LitElement {
}
return html`
<div
aria-rowindex=${index! + 2}
aria-rowindex=${index}
role="row"
.rowId=${row[this.id]}
@click=${this._handleRowClick}
+8 -17
View File
@@ -17,7 +17,6 @@ import { forwardHaptic } from "../../data/haptics";
import { HomeAssistant } from "../../types";
import "../ha-icon-button";
import "../ha-switch";
import "../ha-formfield";
const isOn = (stateObj?: HassEntity) =>
stateObj !== undefined &&
@@ -30,8 +29,6 @@ export class HaEntityToggle extends LitElement {
@property() public stateObj?: HassEntity;
@property() public label?: string;
@internalProperty() private _isOn = false;
protected render(): TemplateResult {
@@ -58,21 +55,15 @@ export class HaEntityToggle extends LitElement {
`;
}
const switchTemplate = html`<ha-switch
aria-label=${`Toggle ${computeStateName(this.stateObj)} ${
this._isOn ? "off" : "on"
}`}
.checked=${this._isOn}
.disabled=${UNAVAILABLE_STATES.includes(this.stateObj.state)}
@change=${this._toggleChanged}
></ha-switch>`;
if (!this.label) {
return switchTemplate;
}
return html`
<ha-formfield .label=${this.label}>${switchTemplate}</ha-formfield>
<ha-switch
aria-label=${`Toggle ${computeStateName(this.stateObj)} ${
this._isOn ? "off" : "on"
}`}
.checked=${this._isOn}
.disabled=${UNAVAILABLE_STATES.includes(this.stateObj.state)}
@change=${this._toggleChanged}
></ha-switch>
`;
}
+3 -3
View File
@@ -140,7 +140,7 @@ export class HaAreaPicker extends SubscribeMixin(LitElement) {
this._devices = devices;
}),
subscribeEntityRegistry(this.hass.connection!, (entities) => {
this._entities = entities;
this._entities = entities.filter((entity) => entity.area_id);
}),
];
}
@@ -193,13 +193,13 @@ export class HaAreaPicker extends SubscribeMixin(LitElement) {
deviceEntityLookup[entity.device_id].push(entity);
}
inputDevices = devices;
inputEntities = entities.filter((entity) => entity.area_id);
inputEntities = entities;
} else {
if (deviceFilter) {
inputDevices = devices;
}
if (entityFilter) {
inputEntities = entities.filter((entity) => entity.area_id);
inputEntities = entities;
}
}
-75
View File
@@ -1,75 +0,0 @@
// @ts-ignore
import chipStyles from "@material/chips/dist/mdc.chips.min.css";
import { ripple } from "@material/mwc-ripple/ripple-directive";
import "./ha-icon";
import {
css,
CSSResult,
customElement,
html,
LitElement,
property,
TemplateResult,
unsafeCSS,
} from "lit-element";
declare global {
// for fire event
interface HASSDomEvents {
"chip-clicked": { index: string };
}
}
@customElement("ha-chip")
export class HaChip extends LitElement {
@property() public index = 0;
@property({type: Boolean}) public hasIcon = false;
protected render(): TemplateResult {
return html`
<div class="mdc-chip" .index=${this.index}>
${this.hasIcon
? html`<div class="mdc-chip__icon mdc-chip__icon--leading">
<slot name="icon"></slot>
</div>`
: null}
<div class="mdc-chip__ripple" .ripple="${ripple()}"></div>
<span role="gridcell">
<span role="button" tabindex="0" class="mdc-chip__primary-action">
<span class="mdc-chip__text"><slot></slot></span>
</span>
</span>
</div>
`;
}
static get styles(): CSSResult {
return css`
${unsafeCSS(chipStyles)}
.mdc-chip {
margin: 4px;
background-color: var(
--ha-chip-background-color,
rgba(var(--rgb-primary-text-color), 0.15)
);
color: var(--ha-chip-text-color, var(--primary-text-color));
}
.mdc-chip:hover {
color: var(--ha-chip-text-color, var(--primary-text-color));
}
.mdc-chip__icon--leading {
--mdc-icon-size: 20px;
color: var(--ha-chip-icon-color, var(--ha-chip-text-color));
}
`;
}
}
declare global {
interface HTMLElementTagNameMap {
"ha-chip": HaChip;
}
}
@@ -1,5 +1,6 @@
// @ts-ignore
import chipStyles from "@material/chips/dist/mdc.chips.min.css";
import { ripple } from "@material/mwc-ripple/ripple-directive";
import {
css,
CSSResult,
@@ -11,7 +12,6 @@ import {
unsafeCSS,
} from "lit-element";
import { fireEvent } from "../common/dom/fire_event";
import "./ha-chip";
declare global {
// for fire event
@@ -20,8 +20,8 @@ declare global {
}
}
@customElement("ha-chip-set")
export class HaChipSet extends LitElement {
@customElement("ha-chips")
export class HaChips extends LitElement {
@property() public items = [];
protected render(): TemplateResult {
@@ -33,9 +33,18 @@ export class HaChipSet extends LitElement {
${this.items.map(
(item, idx) =>
html`
<ha-chip .index=${idx} @click=${this._handleClick}>
${item}
</ha-chip>
<div class="mdc-chip" .index=${idx} @click=${this._handleClick}>
<div class="mdc-chip__ripple" .ripple="${ripple()}"></div>
<span role="gridcell">
<span
role="button"
tabindex="0"
class="mdc-chip__primary-action"
>
<span class="mdc-chip__text">${item}</span>
</span>
</span>
</div>
`
)}
</div>
@@ -51,12 +60,16 @@ export class HaChipSet extends LitElement {
static get styles(): CSSResult {
return css`
${unsafeCSS(chipStyles)}
.mdc-chip {
background-color: rgba(var(--rgb-primary-text-color), 0.15);
color: var(--primary-text-color);
}
`;
}
}
declare global {
interface HTMLElementTagNameMap {
"ha-chip-set": HaChipSet;
"ha-chips": HaChips;
}
}
+1 -1
View File
@@ -101,7 +101,7 @@ class HaClimateState extends LitElement {
)}-${formatNumber(
this.stateObj.attributes.target_humidity_high,
this.hass!.language
)} %`;
)}%`;
}
if (this.stateObj.attributes.humidity != null) {
+33 -42
View File
@@ -1,3 +1,4 @@
import type { StreamLanguage } from "@codemirror/stream-parser";
import type { EditorView, KeyBinding, ViewUpdate } from "@codemirror/view";
import {
customElement,
@@ -15,6 +16,10 @@ declare global {
}
}
const modeTag = Symbol("mode");
const readOnlyTag = Symbol("readOnly");
const saveKeyBinding: KeyBinding = {
key: "Mod-s",
run: (view: EditorView) => {
@@ -37,7 +42,7 @@ export class HaCodeEditor extends UpdatingElement {
@internalProperty() private _value = "";
private _loadedCodeMirror?: typeof import("../resources/codemirror");
@internalProperty() private _langs?: Record<string, StreamLanguage<unknown>>;
public set value(value: string) {
this._value = value;
@@ -47,17 +52,6 @@ export class HaCodeEditor extends UpdatingElement {
return this.codemirror ? this.codemirror.state.doc.toString() : this._value;
}
public get hasComments(): boolean {
if (!this.codemirror || !this._loadedCodeMirror) {
return false;
}
const className = this._loadedCodeMirror.HighlightStyle.get(
this.codemirror.state,
this._loadedCodeMirror.tags.comment
);
return !!this.shadowRoot!.querySelector(`span.${className}`);
}
public connectedCallback() {
super.connectedCallback();
if (!this.codemirror) {
@@ -77,16 +71,16 @@ export class HaCodeEditor extends UpdatingElement {
if (changedProps.has("mode")) {
this.codemirror.dispatch({
effects: this._loadedCodeMirror!.langCompartment!.reconfigure(
this._mode
),
reconfigure: {
[modeTag]: this._mode,
},
});
}
if (changedProps.has("readOnly")) {
this.codemirror.dispatch({
effects: this._loadedCodeMirror!.readonlyCompartment!.reconfigure(
this._loadedCodeMirror!.EditorView!.editable.of(!this.readOnly)
),
reconfigure: {
[readOnlyTag]: !this.readOnly,
},
});
}
if (changedProps.has("_value") && this._value !== this.value) {
@@ -110,11 +104,13 @@ export class HaCodeEditor extends UpdatingElement {
}
private get _mode() {
return this._loadedCodeMirror!.langs[this.mode];
return this._langs![this.mode];
}
private async _load(): Promise<void> {
this._loadedCodeMirror = await loadCodeMirror();
const loaded = await loadCodeMirror();
this._langs = loaded.langs;
const shadowRoot = this.attachShadow({ mode: "open" });
@@ -128,33 +124,28 @@ export class HaCodeEditor extends UpdatingElement {
shadowRoot.appendChild(container);
this.codemirror = new this._loadedCodeMirror.EditorView({
state: this._loadedCodeMirror.EditorState.create({
this.codemirror = new loaded.EditorView({
state: loaded.EditorState.create({
doc: this._value,
extensions: [
this._loadedCodeMirror.lineNumbers(),
this._loadedCodeMirror.EditorState.allowMultipleSelections.of(true),
this._loadedCodeMirror.history(),
this._loadedCodeMirror.highlightSelectionMatches(),
this._loadedCodeMirror.highlightActiveLine(),
this._loadedCodeMirror.drawSelection(),
this._loadedCodeMirror.rectangularSelection(),
this._loadedCodeMirror.keymap.of([
...this._loadedCodeMirror.defaultKeymap,
...this._loadedCodeMirror.searchKeymap,
...this._loadedCodeMirror.historyKeymap,
...this._loadedCodeMirror.tabKeyBindings,
loaded.lineNumbers(),
loaded.history(),
loaded.highlightSelectionMatches(),
loaded.keymap.of([
...loaded.defaultKeymap,
...loaded.searchKeymap,
...loaded.historyKeymap,
...loaded.tabKeyBindings,
saveKeyBinding,
] as KeyBinding[]),
this._loadedCodeMirror.langCompartment.of(this._mode),
this._loadedCodeMirror.theme,
this._loadedCodeMirror.Prec.fallback(
this._loadedCodeMirror.highlightStyle
loaded.tagExtension(modeTag, this._mode),
loaded.theme,
loaded.Prec.fallback(loaded.highlightStyle),
loaded.tagExtension(
readOnlyTag,
loaded.EditorView.editable.of(!this.readOnly)
),
this._loadedCodeMirror.readonlyCompartment.of(
this._loadedCodeMirror.EditorView.editable.of(!this.readOnly)
),
this._loadedCodeMirror.EditorView.updateListener.of((update) =>
loaded.EditorView.updateListener.of((update) =>
this._onUpdate(update)
),
],
+25 -35
View File
@@ -36,7 +36,6 @@ interface ExtHassService extends Omit<HassService, "fields"> {
example?: any;
selector?: Selector;
}[];
hasSelector: string[];
}
@customElement("ha-service-control")
@@ -53,6 +52,8 @@ export class HaServiceControl extends LitElement {
@property({ type: Boolean }) public showAdvanced?: boolean;
@internalProperty() private _serviceData?: ExtHassService;
@internalProperty() private _checkedKeys = new Set();
@query("ha-yaml-editor") private _yamlEditor?: HaYamlEditor;
@@ -69,11 +70,13 @@ export class HaServiceControl extends LitElement {
this._checkedKeys = new Set();
}
const serviceData = this._getServiceInfo(this.value?.service);
this._serviceData = this.value?.service
? this._getServiceInfo(this.value.service)
: undefined;
if (
serviceData &&
"target" in serviceData &&
this._serviceData &&
"target" in this._serviceData &&
(this.value?.data?.entity_id ||
this.value?.data?.area_id ||
this.value?.data?.device_id)
@@ -116,7 +119,7 @@ export class HaServiceControl extends LitElement {
return ENTITY_COMPONENT_DOMAINS.includes(domain) ? [domain] : null;
});
private _getServiceInfo = memoizeOne((service?: string):
private _getServiceInfo = memoizeOne((service: string):
| ExtHassService
| undefined => {
if (!service) {
@@ -144,29 +147,23 @@ export class HaServiceControl extends LitElement {
return {
...serviceDomains[domain][serviceName],
fields,
hasSelector: fields.length
? fields.filter((field) => field.selector).map((field) => field.key)
: [],
};
});
protected render() {
const serviceData = this._getServiceInfo(this.value?.service);
const shouldRenderServiceDataYaml =
(serviceData?.fields.length && !serviceData.hasSelector.length) ||
(serviceData &&
Object.keys(this.value?.data || {}).some(
(key) => !serviceData!.hasSelector.includes(key)
));
const legacy =
this._serviceData?.fields.length &&
!this._serviceData.fields.some((field) => field.selector);
const entityId =
shouldRenderServiceDataYaml &&
serviceData?.fields.find((field) => field.key === "entity_id");
legacy &&
this._serviceData?.fields.find((field) => field.key === "entity_id");
const hasOptional = Boolean(
!shouldRenderServiceDataYaml &&
serviceData?.fields.some((field) => field.selector && !field.required)
!legacy &&
this._serviceData?.fields.some(
(field) => field.selector && !field.required
)
);
return html`<ha-service-picker
@@ -174,8 +171,8 @@ export class HaServiceControl extends LitElement {
.value=${this.value?.service}
@value-changed=${this._serviceChanged}
></ha-service-picker>
<p>${serviceData?.description}</p>
${serviceData && "target" in serviceData
<p>${this._serviceData?.description}</p>
${this._serviceData && "target" in this._serviceData
? html`<ha-settings-row .narrow=${this.narrow}>
${hasOptional
? html`<div slot="prefix" class="checkbox-spacer"></div>`
@@ -191,8 +188,8 @@ export class HaServiceControl extends LitElement {
)}</span
><ha-selector
.hass=${this.hass}
.selector=${serviceData.target
? { target: serviceData.target }
.selector=${this._serviceData.target
? { target: this._serviceData.target }
: {
target: {
entity: { domain: computeDomain(this.value!.service) },
@@ -212,7 +209,7 @@ export class HaServiceControl extends LitElement {
allow-custom-entity
></ha-entity-picker>`
: ""}
${shouldRenderServiceDataYaml
${legacy
? html`<ha-yaml-editor
.label=${this.hass.localize(
"ui.components.service-control.service_data"
@@ -221,12 +218,8 @@ export class HaServiceControl extends LitElement {
.defaultValue=${this.value?.data}
@value-changed=${this._dataChanged}
></ha-yaml-editor>`
: serviceData?.fields.map((dataField) =>
dataField.selector &&
(!dataField.advanced ||
this.showAdvanced ||
(this.value?.data &&
this.value.data[dataField.key] !== undefined))
: this._serviceData?.fields.map((dataField) =>
dataField.selector && (!dataField.advanced || this.showAdvanced)
? html`<ha-settings-row .narrow=${this.narrow}>
${dataField.required
? hasOptional
@@ -335,10 +328,7 @@ export class HaServiceControl extends LitElement {
ev.stopPropagation();
const key = (ev.currentTarget as any).key;
const value = ev.detail.value;
if (
this.value?.data?.[key] === value ||
(!this.value?.data?.[key] && (value === "" || value === undefined))
) {
if (this.value?.data && this.value.data[key] === value) {
return;
}
-10
View File
@@ -107,10 +107,6 @@ export class PaperTimeInput extends PolymerElement {
#millisec {
width: 38px;
}
.no-suffix {
margin-left: -2px;
}
</style>
<label hidden$="[[hideLabel]]">[[label]]</label>
@@ -138,7 +134,6 @@ export class PaperTimeInput extends PolymerElement {
<!-- Min Input -->
<paper-input
class$="[[_computeClassNames(enableSecond)]]"
id="min"
type="number"
value="{{min}}"
@@ -160,7 +155,6 @@ export class PaperTimeInput extends PolymerElement {
<!-- Sec Input -->
<paper-input
class$="[[_computeClassNames(enableMillisecond)]]"
id="sec"
type="number"
value="{{sec}}"
@@ -485,10 +479,6 @@ export class PaperTimeInput extends PolymerElement {
_equal(n1, n2) {
return n1 === n2;
}
_computeClassNames(hasSuffix) {
return hasSuffix ? " " : "no-suffix";
}
}
customElements.define("paper-time-input", PaperTimeInput);
-119
View File
@@ -1,119 +0,0 @@
import { mdiCircleOutline } from "@mdi/js";
import {
LitElement,
customElement,
html,
css,
property,
TemplateResult,
internalProperty,
} from "lit-element";
import { buttonLinkStyle } from "../../resources/styles";
import "../ha-svg-icon";
@customElement("ha-timeline")
class HaTimeline extends LitElement {
@property({ type: Boolean, reflect: true }) public label = false;
@property({ type: Boolean }) public lastItem = false;
@property({ type: String }) public icon?: string;
@property({ attribute: false }) public moreItems?: TemplateResult[];
@internalProperty() private _showMore = false;
protected render() {
return html`
<div class="timeline-start">
${this.label
? ""
: html`
<ha-svg-icon .path=${this.icon || mdiCircleOutline}></ha-svg-icon>
`}
${this.lastItem ? "" : html`<div class="line"></div>`}
</div>
<div class="content">
<slot></slot>
${!this.moreItems
? ""
: html`
<div>
${this._showMore ||
// If there is only 1 item hidden behind "show more", just show it
// instead of showing the more info link. We're not animals.
this.moreItems.length === 1
? this.moreItems
: html`
<button class="link" @click=${this._handleShowMore}>
Show ${this.moreItems.length} more items
</button>
`}
</div>
`}
</div>
`;
}
private _handleShowMore() {
this._showMore = true;
}
static get styles() {
return [
css`
:host {
display: flex;
flex-direction: row;
}
:host(:not([lastItem])) {
min-height: 50px;
}
:host([label]) {
margin-top: -8px;
font-style: italic;
color: var(--timeline-label-color, var(--secondary-text-color));
}
.timeline-start {
display: flex;
flex-direction: column;
align-items: center;
margin-right: 8px;
width: 24px;
}
ha-svg-icon {
color: var(
--timeline-ball-color,
var(--timeline-color, var(--secondary-text-color))
);
}
.line {
flex: 1;
width: 2px;
background-color: var(
--timeline-line-color,
var(--timeline-color, var(--secondary-text-color))
);
margin: 4px 0;
}
.content {
margin-top: 2px;
}
:host(:not([lastItem])) .content {
padding-bottom: 16px;
}
:host([label]) .content {
margin-top: 0;
padding-top: 6px;
}
`,
buttonLinkStyle,
];
}
}
declare global {
interface HTMLElementTagNameMap {
"ha-timeline": HaTimeline;
}
}
-460
View File
@@ -1,460 +0,0 @@
import {
css,
CSSResult,
customElement,
html,
LitElement,
property,
TemplateResult,
} from "lit-element";
import { formatDateTimeWithSeconds } from "../../common/datetime/format_date_time";
import {
AutomationTraceExtended,
ChooseActionTrace,
getDataFromPath,
} from "../../data/trace";
import { HomeAssistant } from "../../types";
import "./ha-timeline";
import {
mdiCheckCircleOutline,
mdiCircle,
mdiCircleOutline,
mdiPauseCircleOutline,
mdiRecordCircleOutline,
mdiStopCircleOutline,
} from "@mdi/js";
import { LogbookEntry } from "../../data/logbook";
import {
ChooseAction,
ChooseActionChoice,
getActionType,
} from "../../data/script";
import relativeTime from "../../common/datetime/relative_time";
const LOGBOOK_ENTRIES_BEFORE_FOLD = 2;
const pathToName = (path: string) => path.split("/").join(" ");
/* eslint max-classes-per-file: "off" */
// Report time entry when more than this time has passed
const SIGNIFICANT_TIME_CHANGE = 1000; // 1 seconds
const isSignificantTimeChange = (a: Date, b: Date) =>
Math.abs(b.getTime() - a.getTime()) > SIGNIFICANT_TIME_CHANGE;
class RenderedTimeTracker {
private lastReportedTime: Date;
constructor(
private hass: HomeAssistant,
private entries: TemplateResult[],
trace: AutomationTraceExtended
) {
this.lastReportedTime = new Date(trace.timestamp.start);
}
setLastReportedTime(date: Date) {
this.lastReportedTime = date;
}
renderTime(from: Date, to: Date): void {
this.entries.push(html`
<ha-timeline label>
${relativeTime(from, this.hass.localize, {
compareTime: to,
includeTense: false,
})}
later
</ha-timeline>
`);
this.lastReportedTime = to;
}
maybeRenderTime(timestamp: Date): boolean {
if (!isSignificantTimeChange(timestamp, this.lastReportedTime)) {
this.lastReportedTime = timestamp;
return false;
}
this.renderTime(this.lastReportedTime, timestamp);
return true;
}
}
class LogbookRenderer {
private curIndex: number;
private pendingItems: Array<[Date, LogbookEntry]> = [];
constructor(
private entries: TemplateResult[],
private timeTracker: RenderedTimeTracker,
private logbookEntries: LogbookEntry[]
) {
// Skip the "automation got triggered item"
this.curIndex =
logbookEntries.length > 0 && logbookEntries[0].domain === "automation"
? 1
: 0;
}
get curItem() {
return this.logbookEntries[this.curIndex];
}
get hasNext() {
return this.curIndex !== this.logbookEntries.length;
}
maybeRenderItem() {
const logbookEntry = this.curItem;
this.curIndex++;
const entryDate = new Date(logbookEntry.when);
if (this.pendingItems.length === 0) {
this.pendingItems.push([entryDate, logbookEntry]);
return;
}
const previousEntryDate = this.pendingItems[
this.pendingItems.length - 1
][0];
// If logbook entry is too long after the last one,
// add a time passed label
if (isSignificantTimeChange(previousEntryDate, entryDate)) {
this._renderLogbookEntries();
this.timeTracker.renderTime(previousEntryDate, entryDate);
}
this.pendingItems.push([entryDate, logbookEntry]);
}
flush() {
if (this.pendingItems.length > 0) {
this._renderLogbookEntries();
}
}
private _renderLogbookEntries() {
this.timeTracker.maybeRenderTime(this.pendingItems[0][0]);
const parts: TemplateResult[] = [];
let i;
for (
i = 0;
i < Math.min(this.pendingItems.length, LOGBOOK_ENTRIES_BEFORE_FOLD);
i++
) {
parts.push(this._renderLogbookEntryHelper(this.pendingItems[i][1]));
}
let moreItems: TemplateResult[] | undefined;
// If we didn't render all items, push rest into `moreItems`
if (i < this.pendingItems.length) {
moreItems = [];
for (; i < this.pendingItems.length; i++) {
moreItems.push(this._renderLogbookEntryHelper(this.pendingItems[i][1]));
}
}
this.entries.push(html`
<ha-timeline .icon=${mdiCircleOutline} .moreItems=${moreItems}>
${parts}
</ha-timeline>
`);
// Clear rendered items.
this.timeTracker.setLastReportedTime(
this.pendingItems[this.pendingItems.length - 1][0]
);
this.pendingItems = [];
}
private _renderLogbookEntryHelper(entry: LogbookEntry) {
return html`${entry.name} (${entry.entity_id}) turned ${entry.state}<br />`;
}
}
class ActionRenderer {
private curIndex = 0;
private keys: string[];
constructor(
private entries: TemplateResult[],
private trace: AutomationTraceExtended,
private logbookRenderer: LogbookRenderer,
private timeTracker: RenderedTimeTracker
) {
this.keys = Object.keys(trace.action_trace);
}
get curItem() {
return this._getItem(this.curIndex);
}
get hasNext() {
return this.curIndex !== this.keys.length;
}
renderItem() {
this.curIndex = this._renderItem(this.curIndex);
}
private _getItem(index: number) {
return this.trace.action_trace[this.keys[index]];
}
private _renderItem(
index: number,
actionType?: ReturnType<typeof getActionType>
): number {
const value = this._getItem(index);
const timestamp = new Date(value[0].timestamp);
// Render all logbook items that are in front of this item.
while (
this.logbookRenderer.hasNext &&
new Date(this.logbookRenderer.curItem.when) < timestamp
) {
this.logbookRenderer.maybeRenderItem();
}
this.logbookRenderer.flush();
this.timeTracker.maybeRenderTime(timestamp);
const path = value[0].path;
let data;
try {
data = getDataFromPath(this.trace.config, path);
} catch (err) {
this.entries.push(
html`Unable to extract path ${path}. Download trace and report as bug`
);
return index + 1;
}
const isTopLevel = path.split("/").length === 2;
if (!isTopLevel && !actionType) {
this._renderEntry(path.replace(/\//g, " "));
return index + 1;
}
if (!actionType) {
actionType = getActionType(data);
}
if (actionType === "choose") {
return this._handleChoose(index);
}
this._renderEntry(data.alias || actionType);
return index + 1;
}
private _handleChoose(index: number): number {
// startLevel: choose root config
// +1: 'default
// +2: executed sequence
// +1: 'choose'
// +2: current choice
// +3: 'conditions'
// +4: evaluated condition
// +3: 'sequence'
// +4: executed sequence
const startLevel = this.keys[index].split("/").length - 1;
const chooseTrace = this._getItem(index)[0] as ChooseActionTrace;
const defaultExecuted = chooseTrace.result.choice === "default";
const chooseConfig = this._getDataFromPath(
this.keys[index]
) as ChooseAction;
const name = chooseConfig.alias || "Choose";
if (defaultExecuted) {
this._renderEntry(`${name}: Default action executed`);
} else {
const choiceConfig = this._getDataFromPath(
`${this.keys[index]}/choose/${chooseTrace.result.choice}`
) as ChooseActionChoice;
const choiceName =
choiceConfig.alias || `Choice ${chooseTrace.result.choice}`;
this._renderEntry(`${name}: ${choiceName} executed`);
}
let i;
// Skip over conditions
for (i = index + 1; i < this.keys.length; i++) {
const parts = this.keys[i].split("/");
// We're done if no more sequence in current level
if (parts.length <= startLevel) {
return i;
}
// We're going to skip all conditions
if (parts[startLevel + 3] === "sequence") {
break;
}
}
// Render choice
while (i < this.keys.length) {
const path = this.keys[i];
const parts = path.split("/");
// We're done if no more sequence in current level
if (parts.length <= startLevel) {
return i;
}
// We know it's an action sequence, so force the type like that
// for rendering.
i = this._renderItem(i, getActionType(this._getDataFromPath(path)));
}
return i;
}
private _renderEntry(description: string) {
this.entries.push(html`
<ha-timeline .icon=${mdiRecordCircleOutline}>
${description}
</ha-timeline>
`);
}
private _getDataFromPath(path: string) {
return getDataFromPath(this.trace.config, path);
}
}
@customElement("hat-trace")
export class HaAutomationTracer extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property({ attribute: false }) private trace?: AutomationTraceExtended;
@property({ attribute: false }) private logbookEntries?: LogbookEntry[];
protected render(): TemplateResult {
if (!this.trace) {
return html``;
}
const entries = [
html`
<ha-timeline .icon=${mdiCircle}>
Triggered by the ${this.trace.variables.trigger.description} at
${formatDateTimeWithSeconds(
new Date(this.trace.timestamp.start),
this.hass.language
)}
</ha-timeline>
`,
];
if (this.trace.condition_trace) {
for (const [path, value] of Object.entries(this.trace.condition_trace)) {
entries.push(html`
<ha-timeline
?lastItem=${!value[0].result.result}
class="condition"
.icon=${value[0].result.result
? mdiCheckCircleOutline
: mdiStopCircleOutline}
>
${getDataFromPath(this.trace!.config, path).alias ||
pathToName(path)}
${value[0].result.result ? "passed" : "failed"}
</ha-timeline>
`);
}
}
if (this.trace.action_trace && this.logbookEntries) {
const timeTracker = new RenderedTimeTracker(
this.hass,
entries,
this.trace
);
const logbookRenderer = new LogbookRenderer(
entries,
timeTracker,
this.logbookEntries
);
const actionRenderer = new ActionRenderer(
entries,
this.trace,
logbookRenderer,
timeTracker
);
while (actionRenderer.hasNext) {
actionRenderer.renderItem();
}
while (logbookRenderer.hasNext) {
logbookRenderer.maybeRenderItem();
}
logbookRenderer.flush();
}
// null means it was stopped by a condition
if (this.trace.last_action !== null) {
entries.push(html`
<ha-timeline
lastItem
.icon=${this.trace.timestamp.finish
? mdiCircle
: mdiPauseCircleOutline}
>
${this.trace.timestamp.finish
? html`Finished at
${formatDateTimeWithSeconds(
new Date(this.trace.timestamp.finish),
this.hass.language
)}
(runtime:
${(
(new Date(this.trace.timestamp.finish!).getTime() -
new Date(this.trace.timestamp.start).getTime()) /
1000
).toFixed(2)}
seconds)`
: "Still running"}
</ha-timeline>
`);
}
return html`${entries}`;
}
static get styles(): CSSResult[] {
return [
css`
ha-timeline[lastItem].condition {
--timeline-ball-color: var(--error-color);
}
`,
];
}
}
declare global {
interface HTMLElementTagNameMap {
"hat-trace": HaAutomationTracer;
}
}
+1 -19
View File
@@ -28,17 +28,6 @@ export interface ManualAutomationConfig {
action: Action[];
mode?: typeof MODES[number];
max?: number;
max_exceeded?:
| "silent"
| "critical"
| "fatal"
| "error"
| "warning"
| "warn"
| "info"
| "debug"
| "notset";
variables?: Record<string, unknown>;
}
export interface BlueprintAutomationConfig extends ManualAutomationConfig {
@@ -56,7 +45,7 @@ export interface StateTrigger {
entity_id: string;
attribute?: string;
from?: string | number;
to?: string | string[] | number;
to?: string | number;
for?: string | number | ForDict;
}
@@ -160,13 +149,11 @@ export type Trigger =
export interface LogicalCondition {
condition: "and" | "not" | "or";
alias?: string;
conditions: Condition[];
}
export interface StateCondition {
condition: "state";
alias?: string;
entity_id: string;
attribute?: string;
state: string | number;
@@ -175,7 +162,6 @@ export interface StateCondition {
export interface NumericStateCondition {
condition: "numeric_state";
alias?: string;
entity_id: string;
attribute?: string;
above?: number;
@@ -185,7 +171,6 @@ export interface NumericStateCondition {
export interface SunCondition {
condition: "sun";
alias?: string;
after_offset: number;
before_offset: number;
after: "sunrise" | "sunset";
@@ -194,14 +179,12 @@ export interface SunCondition {
export interface ZoneCondition {
condition: "zone";
alias?: string;
entity_id: string;
zone: string;
}
export interface TimeCondition {
condition: "time";
alias?: string;
after?: string;
before?: string;
weekday?: string | string[];
@@ -209,7 +192,6 @@ export interface TimeCondition {
export interface TemplateCondition {
condition: "template";
alias?: string;
value_template: string;
}
+2 -5
View File
@@ -3,18 +3,15 @@ import { HaFormSchema } from "../components/ha-form/ha-form";
import { HomeAssistant } from "../types";
export interface DeviceAutomation {
alias?: string;
device_id: string;
domain: string;
entity_id?: string;
entity_id: string;
type?: string;
subtype?: string;
event?: string;
}
export interface DeviceAction extends DeviceAutomation {
entity_id: string;
}
export type DeviceAction = DeviceAutomation;
export interface DeviceCondition extends DeviceAutomation {
condition: string;
+6 -10
View File
@@ -16,10 +16,6 @@ export type AddonStartup =
export type AddonState = "started" | "stopped" | null;
export type AddonRepository = "core" | "local" | string;
interface AddonTranslations {
[key: string]: Record<string, Record<string, Record<string, string>>>;
}
export interface HassioAddonInfo {
advanced: boolean;
available: boolean;
@@ -86,7 +82,6 @@ export interface HassioAddonDetails extends HassioAddonInfo {
slug: string;
startup: AddonStartup;
stdin: boolean;
translations: AddonTranslations;
watchdog: null | boolean;
webui: null | string;
}
@@ -309,12 +304,13 @@ export const updateHassioAddon = async (
method: "post",
timeout: null,
});
} else {
await hass.callApi<HassioResponse<void>>(
"POST",
`hassio/addons/${slug}/update`
);
return;
}
await hass.callApi<HassioResponse<void>>(
"POST",
`hassio/addons/${slug}/update`
);
};
export const restartHassioAddon = async (
+1 -16
View File
@@ -28,22 +28,7 @@ export const extractApiErrorMessage = (error: any): string => {
: error;
};
const ignoredStatusCodes = new Set([502, 503, 504]);
export const ignoreSupervisorError = (error): boolean => {
if (error && error.status_code && ignoredStatusCodes.has(error.status_code)) {
return true;
}
if (
error &&
error.message &&
(error.message.includes("ERR_CONNECTION_CLOSED") ||
error.message.includes("ERR_CONNECTION_RESET"))
) {
return true;
}
return false;
};
export const ignoredStatusCodes = new Set([502, 503, 504]);
export const fetchHassioStats = async (
hass: HomeAssistant,
-1
View File
@@ -105,7 +105,6 @@ export const createHassioFullSnapshot = async (
endpoint: "/snapshots/new/full",
method: "post",
timeout: null,
data,
});
return;
}
+7 -38
View File
@@ -14,9 +14,7 @@ export interface LogbookEntry {
message?: string;
entity_id?: string;
icon?: string;
source?: string;
domain?: string;
context_id?: string;
domain: string;
context_user_id?: string;
context_event_type?: string;
context_domain?: string;
@@ -31,20 +29,6 @@ const DATA_CACHE: {
[cacheKey: string]: { [entityId: string]: Promise<LogbookEntry[]> };
} = {};
export const getLogbookDataForContext = async (
hass: HomeAssistant,
startDate: string,
contextId?: string
) =>
getLogbookDataFromServer(
hass,
startDate,
undefined,
undefined,
undefined,
contextId
);
export const getLogbookData = async (
hass: HomeAssistant,
startDate: string,
@@ -116,30 +100,15 @@ export const getLogbookDataCache = async (
const getLogbookDataFromServer = async (
hass: HomeAssistant,
startDate: string,
endDate?: string,
endDate: string,
entityId?: string,
entitymatchesOnly?: boolean,
contextId?: string
entity_matches_only?: boolean
) => {
const params = new URLSearchParams();
const url = `logbook/${startDate}?end_time=${endDate}${
entityId ? `&entity=${entityId}` : ""
}${entity_matches_only ? `&entity_matches_only` : ""}`;
if (endDate) {
params.append("end_time", endDate);
}
if (entityId) {
params.append("entity", entityId);
}
if (entitymatchesOnly) {
params.append("entity_matches_only", "");
}
if (contextId) {
params.append("context_id", contextId);
}
return hass.callApi<LogbookEntry[]>(
"GET",
`logbook/${startDate}?${params.toString()}`
);
return hass.callApi<LogbookEntry[]>("GET", url);
};
export const clearLogbookCache = (startDate: string, endDate: string) => {
-16
View File
@@ -1,16 +0,0 @@
import {
HassEntityAttributeBase,
HassEntityBase,
} from "home-assistant-js-websocket";
export const REMOTE_SUPPORT_LEARN_COMMAND = 1;
export const REMOTE_SUPPORT_DELETE_COMMAND = 2;
export const REMOTE_SUPPORT_ACTIVITY = 4;
export type RemoteEntity = HassEntityBase & {
attributes: HassEntityAttributeBase & {
current_activity: string | null;
activity_list: string[] | null;
[key: string]: any;
};
};
+2 -56
View File
@@ -29,14 +29,12 @@ export interface ScriptConfig {
}
export interface EventAction {
alias?: string;
event: string;
event_data?: Record<string, any>;
event_data_template?: Record<string, any>;
}
export interface ServiceAction {
alias?: string;
service: string;
entity_id?: string;
target?: HassServiceTarget;
@@ -44,7 +42,6 @@ export interface ServiceAction {
}
export interface DeviceAction {
alias?: string;
device_id: string;
domain: string;
entity_id: string;
@@ -58,36 +55,30 @@ export interface DelayActionParts {
days?: number;
}
export interface DelayAction {
alias?: string;
delay: number | Partial<DelayActionParts> | string;
delay: number | Partial<DelayActionParts>;
}
export interface SceneAction {
alias?: string;
scene: string;
}
export interface WaitAction {
alias?: string;
wait_template: string;
timeout?: number;
continue_on_timeout?: boolean;
}
export interface WaitForTriggerAction {
alias?: string;
wait_for_trigger: Trigger[];
timeout?: number;
continue_on_timeout?: boolean;
}
export interface RepeatAction {
alias?: string;
repeat: CountRepeat | WhileRepeat | UntilRepeat;
}
interface BaseRepeat {
alias?: string;
sequence: Action[];
}
@@ -103,15 +94,8 @@ export interface UntilRepeat extends BaseRepeat {
until: Condition[];
}
export interface ChooseActionChoice {
alias?: string;
conditions: string | Condition[];
sequence: Action[];
}
export interface ChooseAction {
alias?: string;
choose: ChooseActionChoice[];
choose: [{ conditions: Condition[]; sequence: Action[] }];
default?: Action[];
}
@@ -165,41 +149,3 @@ export const getScriptEditorInitData = () => {
inititialScriptEditorData = undefined;
return data;
};
export const getActionType = (action: Action) => {
// Check based on config_validation.py#determine_script_action
if ("delay" in action) {
return "delay";
}
if ("wait_template" in action) {
return "wait_template";
}
if ("condition" in action) {
return "check_condition";
}
if ("event" in action) {
return "fire_event";
}
if ("device_id" in action) {
return "device_action";
}
if ("scene" in action) {
return "activate_scene";
}
if ("repeat" in action) {
return "repeat";
}
if ("choose" in action) {
return "choose";
}
if ("wait_for_trigger" in action) {
return "wait_for_trigger";
}
if ("variables" in action) {
return "variables";
}
if ("service" in action) {
return "service";
}
return "unknown";
};
-8
View File
@@ -1,8 +0,0 @@
import { HomeAssistant } from "../types";
import { Action } from "./script";
export const callExecuteScript = (hass: HomeAssistant, sequence: Action[]) =>
hass.callWS({
type: "execute_script",
sequence,
});
+3 -2
View File
@@ -14,7 +14,8 @@ export const updateCore = async (hass: HomeAssistant) => {
method: "post",
timeout: null,
});
} else {
await hass.callApi<HassioResponse<void>>("POST", `hassio/core/update`);
return;
}
await hass.callApi<HassioResponse<void>>("POST", `hassio/core/update`);
};
-3
View File
@@ -1,6 +1,5 @@
import { Connection, getCollection } from "home-assistant-js-websocket";
import { Store } from "home-assistant-js-websocket/dist/store";
import { LocalizeFunc } from "../../common/translations/localize";
import { HomeAssistant } from "../../types";
import { HassioAddonsInfo } from "../hassio/addon";
import { HassioHassOSInfo, HassioHostInfo } from "../hassio/host";
@@ -47,7 +46,6 @@ interface supervisorApiRequest {
method?: "get" | "post" | "delete" | "put";
force_rest?: boolean;
data?: any;
timeout?: number | null;
}
export interface SupervisorEvent {
@@ -67,7 +65,6 @@ export interface Supervisor {
os: HassioHassOSInfo;
addon: HassioAddonsInfo;
store: SupervisorStore;
localize: LocalizeFunc;
}
export const supervisorApiWsRequest = <T>(
-140
View File
@@ -1,140 +0,0 @@
import { HomeAssistant, Context } from "../types";
import { AutomationConfig } from "./automation";
interface TraceVariables extends Record<string, unknown> {
trigger: {
description: string;
[key: string]: unknown;
};
}
interface BaseTrace {
path: string;
timestamp: string;
changed_variables?: Record<string, unknown>;
}
export interface ConditionTrace extends BaseTrace {
result: { result: boolean };
}
export interface CallServiceActionTrace extends BaseTrace {
result: {
limit: number;
running_script: boolean;
params: Record<string, unknown>;
};
}
export interface ChooseActionTrace extends BaseTrace {
result: { choice: number | "default" };
}
export interface ChooseChoiceActionTrace extends BaseTrace {
result: { result: boolean };
}
export type ActionTrace =
| BaseTrace
| CallServiceActionTrace
| ChooseActionTrace
| ChooseChoiceActionTrace;
export interface AutomationTrace {
domain: string;
item_id: string;
last_action: string | null;
last_condition: string | null;
run_id: string;
state: "running" | "stopped" | "debugged";
timestamp: {
start: string;
finish: string | null;
};
trigger: unknown;
}
export interface AutomationTraceExtended extends AutomationTrace {
condition_trace: Record<string, ConditionTrace[]>;
action_trace: Record<string, ActionTrace[]>;
context: Context;
variables: TraceVariables;
config: AutomationConfig;
}
interface TraceTypes {
automation: {
short: AutomationTrace;
extended: AutomationTraceExtended;
};
}
export const loadTrace = <T extends keyof TraceTypes>(
hass: HomeAssistant,
domain: T,
item_id: string,
run_id: string
): Promise<TraceTypes[T]["extended"]> =>
hass.callWS({
type: "trace/get",
domain,
item_id,
run_id,
});
export const loadTraces = <T extends keyof TraceTypes>(
hass: HomeAssistant,
domain: T,
item_id: string
): Promise<Array<TraceTypes[T]["short"]>> =>
hass.callWS({
type: "trace/list",
domain,
item_id,
});
export type TraceContexts = Record<
string,
{ run_id: string; domain: string; item_id: string }
>;
export const loadTraceContexts = (
hass: HomeAssistant,
domain?: string,
item_id?: string
): Promise<TraceContexts> =>
hass.callWS({
type: "trace/contexts",
domain,
item_id,
});
export const getDataFromPath = (
config: AutomationConfig,
path: string
): any => {
const parts = path.split("/").reverse();
let result: any = config;
while (parts.length) {
const raw = parts.pop()!;
const asNumber = Number(raw);
if (isNaN(asNumber)) {
result = result[raw];
continue;
}
if (Array.isArray(result)) {
result = result[asNumber];
continue;
}
if (asNumber !== 0) {
throw new Error("If config is not an array, can only return index 0");
}
}
return result;
};
-58
View File
@@ -28,34 +28,6 @@ export interface ZWaveJSNode {
status: number;
}
export interface ZWaveJSNodeConfigParams {
property: number;
value: any;
configuration_value_type: string;
metadata: ZWaveJSNodeConfigParamMetadata;
}
export interface ZWaveJSNodeConfigParamMetadata {
description: string;
label: string;
max: number;
min: number;
readable: boolean;
writeable: boolean;
type: string;
unit: string;
states: { [key: number]: string };
}
export interface ZWaveJSSetConfigParamData {
type: string;
entry_id: string;
node_id: number;
property: number;
property_key?: number;
value: string | number;
}
export enum NodeStatus {
Unknown,
Asleep,
@@ -86,36 +58,6 @@ export const fetchNodeStatus = (
node_id,
});
export const fetchNodeConfigParameters = (
hass: HomeAssistant,
entry_id: string,
node_id: number
): Promise<ZWaveJSNodeConfigParams[]> =>
hass.callWS({
type: "zwave_js/get_config_parameters",
entry_id,
node_id,
});
export const setNodeConfigParameter = (
hass: HomeAssistant,
entry_id: string,
node_id: number,
property: number,
value: number,
property_key?: number
): Promise<unknown> => {
const data: ZWaveJSSetConfigParamData = {
type: "zwave_js/set_config_parameter",
entry_id,
node_id,
property,
value,
property_key,
};
return hass.callWS(data);
};
export const getIdentifiersFromDevice = function (
device: DeviceRegistryEntry
): ZWaveJSNodeIdentifiers | undefined {
@@ -5,7 +5,6 @@ import { html } from "@polymer/polymer/lib/utils/html-tag";
/* eslint-plugin-disable lit */
import { PolymerElement } from "@polymer/polymer/polymer-element";
import { fireEvent } from "../../../common/dom/fire_event";
import { FORMAT_NUMBER } from "../../../data/alarm_control_panel";
import LocalizeMixin from "../../../mixins/localize-mixin";
class MoreInfoAlarmControlPanel extends LocalizeMixin(PolymerElement) {
@@ -27,7 +26,6 @@ class MoreInfoAlarmControlPanel extends LocalizeMixin(PolymerElement) {
flex-direction: column;
}
.pad mwc-button {
padding: 8px;
width: 80px;
}
.actions mwc-button {
@@ -45,7 +43,6 @@ class MoreInfoAlarmControlPanel extends LocalizeMixin(PolymerElement) {
label="[[localize('ui.card.alarm_control_panel.code')]]"
value="{{_enteredCode}}"
type="password"
inputmode="[[_inputMode]]"
disabled="[[!_inputEnabled]]"
></paper-input>
@@ -56,21 +53,21 @@ class MoreInfoAlarmControlPanel extends LocalizeMixin(PolymerElement) {
on-click="_digitClicked"
disabled="[[!_inputEnabled]]"
data-digit="1"
outlined
raised
>1</mwc-button
>
<mwc-button
on-click="_digitClicked"
disabled="[[!_inputEnabled]]"
data-digit="4"
outlined
raised
>4</mwc-button
>
<mwc-button
on-click="_digitClicked"
disabled="[[!_inputEnabled]]"
data-digit="7"
outlined
raised
>7</mwc-button
>
</div>
@@ -79,28 +76,28 @@ class MoreInfoAlarmControlPanel extends LocalizeMixin(PolymerElement) {
on-click="_digitClicked"
disabled="[[!_inputEnabled]]"
data-digit="2"
outlined
raised
>2</mwc-button
>
<mwc-button
on-click="_digitClicked"
disabled="[[!_inputEnabled]]"
data-digit="5"
outlined
raised
>5</mwc-button
>
<mwc-button
on-click="_digitClicked"
disabled="[[!_inputEnabled]]"
data-digit="8"
outlined
raised
>8</mwc-button
>
<mwc-button
on-click="_digitClicked"
disabled="[[!_inputEnabled]]"
data-digit="0"
outlined
raised
>0</mwc-button
>
</div>
@@ -109,27 +106,27 @@ class MoreInfoAlarmControlPanel extends LocalizeMixin(PolymerElement) {
on-click="_digitClicked"
disabled="[[!_inputEnabled]]"
data-digit="3"
outlined
raised
>3</mwc-button
>
<mwc-button
on-click="_digitClicked"
disabled="[[!_inputEnabled]]"
data-digit="6"
outlined
raised
>6</mwc-button
>
<mwc-button
on-click="_digitClicked"
disabled="[[!_inputEnabled]]"
data-digit="9"
outlined
raised
>9</mwc-button
>
<mwc-button
on-click="_clearEnteredCode"
disabled="[[!_inputEnabled]]"
outlined
raised
>
[[localize('ui.card.alarm_control_panel.clear_code')]]
</mwc-button>
@@ -204,10 +201,6 @@ class MoreInfoAlarmControlPanel extends LocalizeMixin(PolymerElement) {
type: Boolean,
value: false,
},
_inputMode: {
type: String,
computed: "_getInputMode(_codeFormat)",
},
};
}
@@ -244,12 +237,8 @@ class MoreInfoAlarmControlPanel extends LocalizeMixin(PolymerElement) {
}
}
_getInputMode(format) {
return this._isNumber(format) ? "numeric" : "text";
}
_isNumber(format) {
return format === FORMAT_NUMBER;
return format === "Number";
}
_validateCode(code, format, armVisible, codeArmRequired) {
@@ -1,93 +0,0 @@
import "@polymer/paper-item/paper-item";
import "@polymer/paper-listbox/paper-listbox";
import {
css,
CSSResult,
customElement,
html,
LitElement,
property,
TemplateResult,
} from "lit-element";
import { supportsFeature } from "../../../common/entity/supports-feature";
import "../../../components/ha-attributes";
import "../../../components/ha-paper-dropdown-menu";
import { RemoteEntity, REMOTE_SUPPORT_ACTIVITY } from "../../../data/remote";
import { HomeAssistant } from "../../../types";
const filterExtraAttributes = "activity_list,current_activity";
@customElement("more-info-remote")
class MoreInfoRemote extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property() public stateObj?: RemoteEntity;
protected render(): TemplateResult {
if (!this.hass || !this.stateObj) {
return html``;
}
const stateObj = this.stateObj;
return html`
${supportsFeature(stateObj, REMOTE_SUPPORT_ACTIVITY)
? html`
<ha-paper-dropdown-menu
.label=${this.hass!.localize(
"ui.dialogs.more_info_control.remote.activity"
)}
>
<paper-listbox
slot="dropdown-content"
.selected=${stateObj.attributes.current_activity}
@iron-select=${this.handleActivityChanged}
attr-for-selected="item-name"
>
${stateObj.attributes.activity_list!.map(
(activity) => html`
<paper-item .itemName=${activity}>
${activity}
</paper-item>
`
)}
</paper-listbox>
</ha-paper-dropdown-menu>
`
: ""}
<ha-attributes
.stateObj=${this.stateObj}
.extraFilters=${filterExtraAttributes}
></ha-attributes>
`;
}
private handleActivityChanged(ev: CustomEvent) {
const oldVal = this.stateObj!.attributes.current_activity;
const newVal = ev.detail.item.itemName;
if (!newVal || oldVal === newVal) {
return;
}
this.hass.callService("remote", "turn_on", {
entity_id: this.stateObj!.entity_id,
activity: newVal,
});
}
static get styles(): CSSResult {
return css`
paper-item {
cursor: pointer;
}
`;
}
}
declare global {
interface HTMLElementTagNameMap {
"more-info-remote": MoreInfoRemote;
}
}
@@ -123,7 +123,7 @@ class MoreInfoVacuum extends LitElement {
<div>
<span>
<ha-icon .icon=${stateObj.attributes.battery_icon}></ha-icon>
${stateObj.attributes.battery_level} %
${stateObj.attributes.battery_level}%
</span>
</div>
`
+7 -21
View File
@@ -9,12 +9,10 @@ import {
TemplateResult,
} from "lit-element";
import { isComponentLoaded } from "../../common/config/is_component_loaded";
import { closeDialog } from "../make-dialog-manager";
import { computeStateDomain } from "../../common/entity/compute_state_domain";
import { throttle } from "../../common/util/throttle";
import "../../components/ha-circular-progress";
import "../../components/state-history-charts";
import { TraceContexts, loadTraceContexts } from "../../data/trace";
import { getLogbookData, LogbookEntry } from "../../data/logbook";
import "../../panels/logbook/ha-logbook";
import { haStyle, haStyleScrollbar } from "../../resources/styles";
@@ -28,8 +26,6 @@ export class MoreInfoLogbook extends LitElement {
@internalProperty() private _logbookEntries?: LogbookEntry[];
@internalProperty() private _traceContexts?: TraceContexts;
@internalProperty() private _persons = {};
private _lastLogbookDate?: Date;
@@ -67,7 +63,6 @@ export class MoreInfoLogbook extends LitElement {
relative-time
.hass=${this.hass}
.entries=${this._logbookEntries}
.traceContexts=${this._traceContexts}
.userIdToName=${this._persons}
></ha-logbook>
`
@@ -80,11 +75,6 @@ export class MoreInfoLogbook extends LitElement {
protected firstUpdated(): void {
this._fetchPersonNames();
this.addEventListener("click", (ev) => {
if ((ev.composedPath()[0] as HTMLElement).tagName === "A") {
setTimeout(() => closeDialog("ha-more-info-dialog"), 500);
}
});
}
protected updated(changedProps: PropertyValues): void {
@@ -125,21 +115,17 @@ export class MoreInfoLogbook extends LitElement {
this._lastLogbookDate ||
new Date(new Date().getTime() - 24 * 60 * 60 * 1000);
const now = new Date();
const [newEntries, traceContexts] = await Promise.all([
getLogbookData(
this.hass,
lastDate.toISOString(),
now.toISOString(),
this.entityId,
true
),
loadTraceContexts(this.hass),
]);
const newEntries = await getLogbookData(
this.hass,
lastDate.toISOString(),
now.toISOString(),
this.entityId,
true
);
this._logbookEntries = this._logbookEntries
? [...newEntries, ...this._logbookEntries]
: newEntries;
this._lastLogbookDate = now;
this._traceContexts = traceContexts;
}
private _fetchPersonNames() {
@@ -21,7 +21,6 @@ const LAZY_LOADED_MORE_INFO_CONTROL = {
lock: () => import("./controls/more-info-lock"),
media_player: () => import("./controls/more-info-media_player"),
person: () => import("./controls/more-info-person"),
remote: () => import("./controls/more-info-remote"),
script: () => import("./controls/more-info-script"),
sun: () => import("./controls/more-info-sun"),
timer: () => import("./controls/more-info-timer"),
@@ -20,7 +20,7 @@ export class HuiNotificationDrawer extends EventsMixin(
) {
static get template() {
return html`
<style>
<style include="paper-material-styles">
app-toolbar {
color: var(--primary-text-color);
border-bottom: 1px solid var(--divider-color);
+11 -26
View File
@@ -13,7 +13,6 @@ import {
property,
PropertyValues,
query,
TemplateResult,
} from "lit-element";
import { ifDefined } from "lit-html/directives/if-defined";
import { styleMap } from "lit-html/directives/style-map";
@@ -217,13 +216,11 @@ export class QuickBar extends LitElement {
slot="graphic"
></ha-svg-icon>`
: html`<ha-icon .icon=${item.icon} slot="graphic"></ha-icon>`}
${item.decoratedText
? this._renderDecoratedText(item.decoratedText)
: item.text}
${item.text}
${item.altText
? html`
<span slot="secondary" class="item-text secondary"
>${this._renderDecoratedText(item.altText)}</span
>${item.altText}</span
>
`
: null}
@@ -231,20 +228,6 @@ export class QuickBar extends LitElement {
`;
}
private _renderDecoratedText(text: string) {
const decoratedText: TemplateResult[] = [];
for (let i = 0; i < text.length; i++) {
if (text[i] === "^") {
decoratedText.push(html`<b>${text[i + 1]}</b>`);
i++;
} else {
decoratedText.push(html`${text[i]}`);
}
}
return decoratedText;
}
private async processItemAndCloseDialog(item: QuickBarItem, index: number) {
this._addSpinnerToCommandItem(index);
@@ -346,9 +329,7 @@ export class QuickBar extends LitElement {
...this._generateReloadCommands(),
...this._generateServerControlCommands(),
...this._generateNavigationCommands(),
]
.sort((a, b) => compare(a.text.toLowerCase(), b.text.toLowerCase()))
.filter((item) => !item.text.includes("x"));
].sort((a, b) => compare(a.text.toLowerCase(), b.text.toLowerCase()));
}
private _generateReloadCommands(): QuickBarItem[] {
@@ -497,10 +478,10 @@ export class QuickBar extends LitElement {
: items;
}
private _filterItems = (
items: QuickBarItem[],
filter: string
): QuickBarItem[] => fuzzyFilterSort<QuickBarItem>(filter.trimLeft(), items);
private _filterItems = memoizeOne(
(items: QuickBarItem[], filter: string): QuickBarItem[] =>
fuzzyFilterSort<QuickBarItem>(filter.trimLeft(), items)
);
static get styles() {
return [
@@ -550,6 +531,10 @@ export class QuickBar extends LitElement {
mwc-list-item {
width: 100%;
}
mwc-list-item.command-item {
text-transform: capitalize;
}
`,
];
}
+1 -20
View File
@@ -9,7 +9,6 @@ import "../resources/safari-14-attachshadow-patch";
import { createCustomPanelElement } from "../util/custom-panel/create-custom-panel-element";
import { loadCustomPanel } from "../util/custom-panel/load-custom-panel";
import { setCustomPanelProperties } from "../util/custom-panel/set-custom-panel-properties";
import { baseEntrypointStyles } from "../resources/styles";
declare global {
interface Window {
@@ -42,7 +41,6 @@ function initialize(
properties: Record<string, unknown>
) {
const style = document.createElement("style");
style.innerHTML = "body{margin:0}";
document.head.appendChild(style);
@@ -88,24 +86,7 @@ function initialize(
(err) => {
// eslint-disable-next-line
console.error(err, panel);
let errorScreen;
if (panel.url_path === "hassio") {
import("../layouts/supervisor-error-screen");
errorScreen = document.createElement(
"supervisor-error-screen"
) as any;
} else {
import("../layouts/hass-error-screen");
errorScreen = document.createElement("hass-error-screen") as any;
errorScreen.error = `Unable to load the panel source: ${err}.`;
}
const errorStyle = document.createElement("style");
errorStyle.innerHTML = baseEntrypointStyles.cssText;
document.body.appendChild(errorStyle);
errorScreen.hass = properties.hass;
document.body.appendChild(errorScreen);
alert(`Unable to load the panel source: ${err}.`);
}
);
}
+6 -41
View File
@@ -2,19 +2,15 @@
// eslint-disable-next-line spaced-comment
/// <reference path="../types/service-worker.d.ts" />
/* eslint-env serviceworker */
import { cacheNames, RouteHandler } from "workbox-core";
import { cacheNames } from "workbox-core";
import { cleanupOutdatedCaches, precacheAndRoute } from "workbox-precaching";
import { registerRoute, setCatchHandler } from "workbox-routing";
import { registerRoute } from "workbox-routing";
import {
CacheFirst,
NetworkOnly,
StaleWhileRevalidate,
} from "workbox-strategies";
const noFallBackRegEx = new RegExp(
`${location.host}/(api|static|auth|frontend_latest|frontend_es5|local)/.*`
);
// Clean up caches from older workboxes and old service workers.
// Will help with cleaning up Workbox v4 stuff
cleanupOutdatedCaches();
@@ -22,17 +18,13 @@ cleanupOutdatedCaches();
function initRouting() {
precacheAndRoute(
// @ts-ignore
WB_MANIFEST,
{
// Ignore all URL parameters.
ignoreURLParametersMatching: [/.*/],
}
WB_MANIFEST
);
// Cache static content (including translations) on first access.
registerRoute(
new RegExp(`${location.host}/(static|frontend_latest|frontend_es5)/.+`),
new CacheFirst({ matchOptions: { ignoreSearch: true } })
new CacheFirst()
);
// Get api from network.
@@ -49,14 +41,8 @@ function initRouting() {
new NetworkOnly()
);
// For the root "/" we ignore search
registerRoute(
new RegExp(`^${location.host}/(\\?.*)?$`),
new StaleWhileRevalidate({ matchOptions: { ignoreSearch: true } })
);
// For rest of the files (on Home Assistant domain only) try both cache and network.
// This includes "/states" response and user files from "/local".
// This includes the root "/" or "/states" response and user files from "/local".
// First access might bring stale data from cache, but a single refresh will bring updated
// file.
registerRoute(new RegExp(`${location.host}/.*`), new StaleWhileRevalidate());
@@ -172,15 +158,8 @@ function initPushNotifications() {
self.addEventListener("install", (event) => {
// Delete all runtime caching, so that index.html has to be refetched.
// And add the new index.html back to the runtime cache
const cacheName = cacheNames.runtime;
event.waitUntil(
caches.delete(cacheName).then(() =>
caches.open(cacheName).then((cache) => {
cache.add("/");
})
)
);
event.waitUntil(caches.delete(cacheName));
});
self.addEventListener("activate", () => {
@@ -198,19 +177,5 @@ self.addEventListener("message", (message) => {
}
});
const catchHandler: RouteHandler = async (options) => {
const dest = (options.request as Request).destination;
const url = (options.request as Request).url;
if (dest !== "document" || noFallBackRegEx.test(url)) {
return Response.error();
}
// eslint-disable-next-line no-console
console.log("Using fallback for:", url);
return (await caches.match("/", { ignoreSearch: true })) || Response.error();
};
initRouting();
setCatchHandler(catchHandler);
initPushNotifications();
+1 -1
View File
@@ -7,7 +7,7 @@ import { computeLocalize } from "../common/translations/localize";
import { DEFAULT_PANEL } from "../data/panel";
import { translationMetadata } from "../resources/translations-metadata";
import { HomeAssistant } from "../types";
import { getTranslation, getLocalLanguage } from "../util/hass-translation";
import { getLocalLanguage, getTranslation } from "../util/hass-translation";
import { demoConfig } from "./demo_config";
import { demoPanels } from "./demo_panels";
import { demoServices } from "./demo_services";
+1 -1
View File
@@ -2,7 +2,7 @@
<html>
<head>
<title>Home Assistant</title>
<link rel="modulepreload" href="<%= latestPageJS %>" crossorigin="use-credentials" />
<link rel="preload" href="<%= latestPageJS %>" as="script" crossorigin="use-credentials" />
<%= renderTemplate('_header') %>
<style>
.content {
+2 -2
View File
@@ -2,8 +2,8 @@
<html>
<head>
<% if (!useWDS) { %>
<link rel="modulepreload" href="<%= latestCoreJS %>" crossorigin="use-credentials" />
<link rel="modulepreload" href="<%= latestAppJS %>" crossorigin="use-credentials" />
<link rel="preload" href="<%= latestCoreJS %>" as="script" crossorigin="use-credentials" />
<link rel="preload" href="<%= latestAppJS %>" as="script" crossorigin="use-credentials" />
<% } %>
<%= renderTemplate('_header') %>
<title>Home Assistant</title>
+19 -35
View File
@@ -2,21 +2,29 @@
<html>
<head>
<title>Home Assistant</title>
<link rel="modulepreload" href="<%= latestPageJS %>" crossorigin="use-credentials" />
<link rel="preload" href="<%= latestPageJS %>" as="script" crossorigin="use-credentials" />
<%= renderTemplate('_header') %>
<style>
html {
color: var(--primary-text-color, #212121);
background-color: #0277bd !important;
}
@media (prefers-color-scheme: dark) {
html {
background-color: #111111;
color: #e1e1e1;
}
ha-onboarding {
--primary-text-color: #e1e1e1;
--secondary-text-color: #9b9b9b;
--disabled-text-color: #6f6f6f;
--mdc-theme-surface: #1e1e1e;
--ha-card-background: #1e1e1e;
}
}
.content {
box-sizing: border-box;
padding: 20px 16px;
border-radius: 4px;
max-width: 432px;
margin: 64px auto 0;
box-shadow: rgba(0, 0, 0, 0.25) 0px 54px 55px, rgba(0, 0, 0, 0.12) 0px -12px 30px, rgba(0, 0, 0, 0.12) 0px 4px 6px, rgba(0, 0, 0, 0.17) 0px 12px 13px, rgba(0, 0, 0, 0.09) 0px -3px 5px;
background-color: #fff;
max-width: 400px;
margin: 0 auto;
}
.header {
@@ -31,36 +39,12 @@
.header img {
margin-right: 16px;
}
@media (prefers-color-scheme: dark) {
html {
color: #e1e1e1;
}
ha-onboarding {
--primary-text-color: #e1e1e1;
--secondary-text-color: #9b9b9b;
--disabled-text-color: #6f6f6f;
--mdc-theme-surface: #1e1e1e;
--ha-card-background: #1e1e1e;
}
.content {
background-color: #111111;
}
}
@media (max-width: 450px) {
.content {
min-height: 100%;
margin: 0;
}
}
</style>
</head>
<body id='particles'>
<body>
<div class="content">
<div class="header">
<img src="/static/icons/favicon-192x192.png" height="52" width="52" />
<img src="/static/icons/favicon-192x192.png" height="52" />
Home Assistant
</div>
@@ -92,4 +76,4 @@
})();
</script>
</body>
</html>
</html>
+6 -22
View File
@@ -9,8 +9,7 @@ import {
TemplateResult,
} from "lit-element";
import { HomeAssistant } from "../types";
import "../components/ha-menu-button";
import "../components/ha-icon-button-arrow-prev";
import "./hass-subpage";
@customElement("hass-error-screen")
class HassErrorScreen extends LitElement {
@@ -18,37 +17,22 @@ class HassErrorScreen extends LitElement {
@property({ type: Boolean }) public toolbar = true;
@property({ type: Boolean }) public rootnav = false;
@property() public narrow?: boolean;
@property() public error?: string;
protected render(): TemplateResult {
return html`
${this.toolbar
? html`<div class="toolbar">
${this.rootnav
? html`
<ha-menu-button
.hass=${this.hass}
.narrow=${this.narrow}
></ha-menu-button>
`
: html`
<ha-icon-button-arrow-prev
.hass=${this.hass}
@click=${this._handleBack}
></ha-icon-button-arrow-prev>
`}
<ha-icon-button-arrow-prev
.hass=${this.hass}
@click=${this._handleBack}
></ha-icon-button-arrow-prev>
</div>`
: ""}
<div class="content">
<h3>${this.error}</h3>
<slot>
<mwc-button @click=${this._handleBack}>
${this.hass?.localize("ui.panel.error.go_back") || "go back"}
</mwc-button>
<mwc-button @click=${this._handleBack}>go back</mwc-button>
</slot>
</div>
`;

Some files were not shown because too many files have changed in this diff Show More