mirror of
https://github.com/home-assistant/frontend.git
synced 2026-08-22 22:58:07 +00:00
Compare commits
58
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d0f799afc8 | ||
|
|
4cf3aa18b1 | ||
|
|
ba59b47fe6 | ||
|
|
2dc72222f2 | ||
|
|
71f1388a3f | ||
|
|
ca3cac4ed3 | ||
|
|
41852460e1 | ||
|
|
9ec4e083d9 | ||
|
|
9560a1c4a7 | ||
|
|
4f5a47ace7 | ||
|
|
01c4d662f2 | ||
|
|
194024edb9 | ||
|
|
bef0d3a6a1 | ||
|
|
47a024b795 | ||
|
|
39847f9c9d | ||
|
|
f24f21ca91 | ||
|
|
c8ea37eec0 | ||
|
|
b71f452795 | ||
|
|
7ea1ece169 | ||
|
|
aece3a37c0 | ||
|
|
705871f8dc | ||
|
|
4a11975349 | ||
|
|
4d3d27f2c4 | ||
|
|
d784a30d42 | ||
|
|
35f776284b | ||
|
|
f659a6fe37 | ||
|
|
ad53c99fc4 | ||
|
|
fa0172d00c | ||
|
|
845411b48c | ||
|
|
d715867b09 | ||
|
|
0ca2cdfbed | ||
|
|
0d1c72386e | ||
|
|
c91779dffe | ||
|
|
3853cc9214 | ||
|
|
a66b3f6b80 | ||
|
|
c97ec32343 | ||
|
|
2abba7e445 | ||
|
|
f887c27ad1 | ||
|
|
6ee8d74899 | ||
|
|
f196c72563 | ||
|
|
419e564441 | ||
|
|
de97b54c95 | ||
|
|
07001f7b5c | ||
|
|
bee17fce64 | ||
|
|
718904a853 | ||
|
|
72af4a69d6 | ||
|
|
fe50f4229c | ||
|
|
ca4de877c1 | ||
|
|
1dfecf9618 | ||
|
|
0a3505ed89 | ||
|
|
33cbf7eabe | ||
|
|
935d97ce1a | ||
|
|
9f73f0ca8d | ||
|
|
d8cdbac15e | ||
|
|
03b8c1348c | ||
|
|
25a0be7672 | ||
|
|
08f1ce2d54 | ||
|
|
bea20d0495 |
@@ -7,7 +7,7 @@ on:
|
||||
branches:
|
||||
- dev
|
||||
paths:
|
||||
- translations/en.json
|
||||
- src/translations/en.json
|
||||
|
||||
env:
|
||||
NODE_VERSION: 12
|
||||
|
||||
@@ -85,6 +85,11 @@ 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
|
||||
|
||||
@@ -10,6 +10,8 @@ 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",
|
||||
@@ -20,6 +22,8 @@ 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"
|
||||
)
|
||||
);
|
||||
@@ -32,6 +36,8 @@ 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
|
||||
|
||||
@@ -266,6 +266,7 @@ gulp.task(taskName, function () {
|
||||
TRANSLATION_FRAGMENTS.forEach((fragment) => {
|
||||
delete data.ui.panel[fragment];
|
||||
});
|
||||
delete data.supervisor;
|
||||
return data;
|
||||
})
|
||||
)
|
||||
@@ -342,6 +343,62 @@ 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(
|
||||
@@ -353,41 +410,20 @@ gulp.task(
|
||||
gulp.parallel(...splitTasks),
|
||||
"build-flattened-translations",
|
||||
"build-translation-fingerprints",
|
||||
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));
|
||||
}
|
||||
"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"
|
||||
)
|
||||
);
|
||||
|
||||
@@ -137,7 +137,12 @@ gulp.task("webpack-watch-hassio", () => {
|
||||
isProdBuild: false,
|
||||
latestBuild: true,
|
||||
})
|
||||
).watch({}, doneHandler());
|
||||
).watch({ ignored: /build-translations/ }, doneHandler());
|
||||
|
||||
gulp.watch(
|
||||
path.join(paths.translations_src, "en.json"),
|
||||
gulp.series("build-supervisor-translations", "copy-translations-supervisor")
|
||||
);
|
||||
});
|
||||
|
||||
gulp.task("webpack-prod-hassio", () =>
|
||||
|
||||
@@ -34,6 +34,7 @@ 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"
|
||||
|
||||
@@ -100,7 +100,7 @@ class HcLayout extends LitElement {
|
||||
display: block;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
|
||||
.hero {
|
||||
border-radius: 4px 4px 0 0;
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ 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";
|
||||
@@ -23,6 +24,8 @@ 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[];
|
||||
@@ -54,7 +57,11 @@ class HassioAddonRepositoryEl extends LitElement {
|
||||
return html`
|
||||
<div class="content">
|
||||
<p class="description">
|
||||
No results found in "${repo.name}."
|
||||
${this.supervisor.localize(
|
||||
"store.no_results_found",
|
||||
"repository",
|
||||
repo.name
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
`;
|
||||
@@ -83,11 +90,13 @@ class HassioAddonRepositoryEl extends LitElement {
|
||||
: mdiPuzzle}
|
||||
.iconTitle=${addon.installed
|
||||
? addon.update_available
|
||||
? "New version available"
|
||||
: "Add-on is installed"
|
||||
? this.supervisor.localize(
|
||||
"common.new_version_available"
|
||||
)
|
||||
: this.supervisor.localize("addon.installed")
|
||||
: addon.available
|
||||
? "Add-on is not installed"
|
||||
: "Add-on is not available on your system"}
|
||||
? this.supervisor.localize("addon.not_installed")
|
||||
: this.supervisor.localize("addon.not_available")}
|
||||
.iconClass=${addon.installed
|
||||
? addon.update_available
|
||||
? "update"
|
||||
|
||||
@@ -14,7 +14,9 @@ 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 {
|
||||
@@ -77,13 +79,16 @@ class HassioAddonStore extends LitElement {
|
||||
return html`
|
||||
<hass-tabs-subpage
|
||||
.hass=${this.hass}
|
||||
.localizeFunc=${this.supervisor.localize}
|
||||
.narrow=${this.narrow}
|
||||
.route=${this.route}
|
||||
hassio
|
||||
main-page
|
||||
.tabs=${supervisorTabs}
|
||||
main-page
|
||||
supervisor
|
||||
>
|
||||
<span slot="header">Add-on Store</span>
|
||||
<span slot="header">
|
||||
${this.supervisor.localize("panel.store")}
|
||||
</span>
|
||||
<ha-button-menu
|
||||
corner="BOTTOM_START"
|
||||
slot="toolbar-icon"
|
||||
@@ -93,15 +98,15 @@ class HassioAddonStore extends LitElement {
|
||||
<ha-svg-icon .path=${mdiDotsVertical}></ha-svg-icon>
|
||||
</mwc-icon-button>
|
||||
<mwc-list-item>
|
||||
Repositories
|
||||
${this.supervisor.localize("store.repositories")}
|
||||
</mwc-list-item>
|
||||
<mwc-list-item>
|
||||
Reload
|
||||
${this.supervisor.localize("common.reload")}
|
||||
</mwc-list-item>
|
||||
${this.hass.userData?.showAdvanced &&
|
||||
atLeastVersion(this.hass.config.version, 0, 117)
|
||||
? html`<mwc-list-item>
|
||||
Registries
|
||||
${this.supervisor.localize("store.registries")}
|
||||
</mwc-list-item>`
|
||||
: ""}
|
||||
</ha-button-menu>
|
||||
@@ -122,11 +127,9 @@ class HassioAddonStore extends LitElement {
|
||||
${!this.hass.userData?.showAdvanced
|
||||
? html`
|
||||
<div class="advanced">
|
||||
Missing add-ons? Enable advanced mode on
|
||||
<a href="/profile" target="_top">
|
||||
your profile page
|
||||
${this.supervisor.localize("store.missing_addons")}
|
||||
</a>
|
||||
.
|
||||
</div>
|
||||
`
|
||||
: ""}
|
||||
@@ -136,6 +139,12 @@ 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();
|
||||
}
|
||||
@@ -158,6 +167,7 @@ class HassioAddonStore extends LitElement {
|
||||
.repo=${repo}
|
||||
.addons=${filteredAddons}
|
||||
.filter=${filter!}
|
||||
.supervisor=${this.supervisor}
|
||||
></hassio-addon-repository>
|
||||
`
|
||||
: html``;
|
||||
@@ -168,7 +178,7 @@ class HassioAddonStore extends LitElement {
|
||||
private _handleAction(ev: CustomEvent<ActionDetail>) {
|
||||
switch (ev.detail.index) {
|
||||
case 0:
|
||||
this._manageRepositories();
|
||||
this._manageRepositoriesClicked();
|
||||
break;
|
||||
case 1:
|
||||
this.refreshData();
|
||||
@@ -185,21 +195,25 @@ class HassioAddonStore extends LitElement {
|
||||
}
|
||||
}
|
||||
|
||||
private async _manageRepositories() {
|
||||
private _manageRepositoriesClicked() {
|
||||
this._manageRepositories();
|
||||
}
|
||||
|
||||
private async _manageRepositories(url?: string) {
|
||||
showRepositoriesDialog(this, {
|
||||
repos: this.supervisor.addon.repositories,
|
||||
loadData: () => this._loadData(),
|
||||
supervisor: this.supervisor,
|
||||
url,
|
||||
});
|
||||
}
|
||||
|
||||
private async _manageRegistries() {
|
||||
showRegistriesDialog(this);
|
||||
showRegistriesDialog(this, { supervisor: this.supervisor });
|
||||
}
|
||||
|
||||
private async _loadData() {
|
||||
fireEvent(this, "supervisor-colllection-refresh", { colllection: "addon" });
|
||||
fireEvent(this, "supervisor-colllection-refresh", {
|
||||
colllection: "supervisor",
|
||||
fireEvent(this, "supervisor-collection-refresh", { collection: "addon" });
|
||||
fireEvent(this, "supervisor-collection-refresh", {
|
||||
collection: "supervisor",
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -25,6 +25,7 @@ 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";
|
||||
@@ -34,6 +35,8 @@ 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;
|
||||
@@ -48,12 +51,16 @@ class HassioAddonAudio extends LitElement {
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return html`
|
||||
<ha-card header="Audio">
|
||||
<ha-card
|
||||
.header=${this.supervisor.localize("addon.configuration.audio.header")}
|
||||
>
|
||||
<div class="card-content">
|
||||
${this._error ? html` <div class="errors">${this._error}</div> ` : ""}
|
||||
|
||||
<paper-dropdown-menu
|
||||
label="Input"
|
||||
.label=${this.supervisor.localize(
|
||||
"addon.configuration.audio.input"
|
||||
)}
|
||||
@iron-select=${this._setInputDevice}
|
||||
>
|
||||
<paper-listbox
|
||||
@@ -64,15 +71,17 @@ 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="Output"
|
||||
.label=${this.supervisor.localize(
|
||||
"addon.configuration.audio.output"
|
||||
)}
|
||||
@iron-select=${this._setOutputDevice}
|
||||
>
|
||||
<paper-listbox
|
||||
@@ -93,7 +102,7 @@ class HassioAddonAudio extends LitElement {
|
||||
</div>
|
||||
<div class="card-actions">
|
||||
<ha-progress-button @click=${this._saveSettings}>
|
||||
Save
|
||||
${this.supervisor.localize("common.save")}
|
||||
</ha-progress-button>
|
||||
</div>
|
||||
</ha-card>
|
||||
@@ -152,7 +161,7 @@ class HassioAddonAudio extends LitElement {
|
||||
|
||||
const noDevice: HassioHardwareAudioDevice = {
|
||||
device: "default",
|
||||
name: "Default",
|
||||
name: this.supervisor.localize("addon.configuration.audio.default"),
|
||||
};
|
||||
|
||||
try {
|
||||
@@ -189,7 +198,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.addon);
|
||||
await suggestAddonRestart(this, this.hass, this.supervisor, this.addon);
|
||||
}
|
||||
} catch {
|
||||
this._error = "Failed to set addon audio device";
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
} from "lit-element";
|
||||
import "../../../../src/components/ha-circular-progress";
|
||||
import { HassioAddonDetails } from "../../../../src/data/hassio/addon";
|
||||
import { Supervisor } from "../../../../src/data/supervisor/supervisor";
|
||||
import { haStyle } from "../../../../src/resources/styles";
|
||||
import { HomeAssistant } from "../../../../src/types";
|
||||
import { hassioStyle } from "../../resources/hassio-style";
|
||||
@@ -20,6 +21,8 @@ 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 {
|
||||
@@ -39,6 +42,7 @@ class HassioAddonConfigDashboard extends LitElement {
|
||||
<hassio-addon-config
|
||||
.hass=${this.hass}
|
||||
.addon=${this.addon}
|
||||
.supervisor=${this.supervisor}
|
||||
></hassio-addon-config>
|
||||
`
|
||||
: ""}
|
||||
@@ -47,6 +51,7 @@ class HassioAddonConfigDashboard extends LitElement {
|
||||
<hassio-addon-network
|
||||
.hass=${this.hass}
|
||||
.addon=${this.addon}
|
||||
.supervisor=${this.supervisor}
|
||||
></hassio-addon-network>
|
||||
`
|
||||
: ""}
|
||||
@@ -55,11 +60,12 @@ class HassioAddonConfigDashboard extends LitElement {
|
||||
<hassio-addon-audio
|
||||
.hass=${this.hass}
|
||||
.addon=${this.addon}
|
||||
.supervisor=${this.supervisor}
|
||||
></hassio-addon-audio>
|
||||
`
|
||||
: ""}
|
||||
`
|
||||
: "This add-on does not expose configuration for you to mess with.... 👋"}
|
||||
: this.supervisor.localize("addon.configuration.no_configuration")}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -32,6 +32,7 @@ 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";
|
||||
@@ -46,6 +47,8 @@ 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;
|
||||
@@ -62,6 +65,15 @@ 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);
|
||||
@@ -81,17 +93,25 @@ class HassioAddonConfig extends LitElement {
|
||||
<h1>${this.addon.name}</h1>
|
||||
<ha-card>
|
||||
<div class="header">
|
||||
<h2>Configuration</h2>
|
||||
<h2>
|
||||
${this.supervisor.localize("addon.configuration.options.header")}
|
||||
</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 ? "Edit in UI" : "Edit in YAML"}
|
||||
${this._yamlMode
|
||||
? this.supervisor.localize(
|
||||
"addon.configuration.options.edit_in_ui"
|
||||
)
|
||||
: this.supervisor.localize(
|
||||
"addon.configuration.options.edit_in_yaml"
|
||||
)}
|
||||
</mwc-list-item>
|
||||
<mwc-list-item class="warning">
|
||||
Reset to defaults
|
||||
${this.supervisor.localize("common.reset_defaults")}
|
||||
</mwc-list-item>
|
||||
</ha-button-menu>
|
||||
</div>
|
||||
@@ -102,6 +122,7 @@ 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(
|
||||
@@ -117,12 +138,20 @@ class HassioAddonConfig extends LitElement {
|
||||
(this._canShowSchema && this.addon.schema) ||
|
||||
this._valid
|
||||
? ""
|
||||
: html` <div class="errors">Invalid YAML</div> `}
|
||||
: html`
|
||||
<div class="errors">
|
||||
${this.supervisor.localize(
|
||||
"addon.configuration.options.invalid_yaml"
|
||||
)}
|
||||
</div>
|
||||
`}
|
||||
</div>
|
||||
${hasHiddenOptions
|
||||
? html`<ha-formfield
|
||||
class="show-additional"
|
||||
label="Show unused optional configuration options"
|
||||
.label=${this.supervisor.localize(
|
||||
"addon.configuration.options.show_unused_optional"
|
||||
)}
|
||||
>
|
||||
<ha-switch
|
||||
@change=${this._toggleOptional}
|
||||
@@ -136,7 +165,7 @@ class HassioAddonConfig extends LitElement {
|
||||
@click=${this._saveTapped}
|
||||
.disabled=${!this._configHasChanged || !this._valid}
|
||||
>
|
||||
Save
|
||||
${this.supervisor.localize("common.save")}
|
||||
</ha-progress-button>
|
||||
</div>
|
||||
</ha-card>
|
||||
@@ -201,10 +230,10 @@ class HassioAddonConfig extends LitElement {
|
||||
button.progress = true;
|
||||
|
||||
const confirmed = await showConfirmationDialog(this, {
|
||||
title: this.addon.name,
|
||||
text: "Are you sure you want to reset all your options?",
|
||||
confirmText: "reset options",
|
||||
dismissText: "no",
|
||||
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"),
|
||||
});
|
||||
|
||||
if (!confirmed) {
|
||||
@@ -226,9 +255,11 @@ class HassioAddonConfig extends LitElement {
|
||||
};
|
||||
fireEvent(this, "hass-api-called", eventdata);
|
||||
} catch (err) {
|
||||
this._error = `Failed to reset addon configuration, ${extractApiErrorMessage(
|
||||
err
|
||||
)}`;
|
||||
this._error = this.supervisor.localize(
|
||||
"addon.common.update_available",
|
||||
"error",
|
||||
extractApiErrorMessage(err)
|
||||
);
|
||||
}
|
||||
button.progress = false;
|
||||
}
|
||||
@@ -252,12 +283,14 @@ class HassioAddonConfig extends LitElement {
|
||||
};
|
||||
fireEvent(this, "hass-api-called", eventdata);
|
||||
if (this.addon?.state === "started") {
|
||||
await suggestAddonRestart(this, this.hass, this.addon);
|
||||
await suggestAddonRestart(this, this.hass, this.supervisor, this.addon);
|
||||
}
|
||||
} catch (err) {
|
||||
this._error = `Failed to save addon configuration, ${extractApiErrorMessage(
|
||||
err
|
||||
)}`;
|
||||
this._error = this.supervisor.localize(
|
||||
"addon.configuration.options.failed_to_save",
|
||||
"error",
|
||||
extractApiErrorMessage(err)
|
||||
);
|
||||
}
|
||||
button.progress = false;
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ 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";
|
||||
@@ -38,6 +39,8 @@ 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;
|
||||
@@ -55,16 +58,30 @@ class HassioAddonNetwork extends LitElement {
|
||||
}
|
||||
|
||||
return html`
|
||||
<ha-card header="Network">
|
||||
<ha-card
|
||||
.header=${this.supervisor.localize(
|
||||
"addon.configuration.network.header"
|
||||
)}
|
||||
>
|
||||
<div class="card-content">
|
||||
${this._error ? html` <div class="errors">${this._error}</div> ` : ""}
|
||||
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th>Container</th>
|
||||
<th>Host</th>
|
||||
<th>Description</th>
|
||||
<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>
|
||||
</tr>
|
||||
${this._config!.map((item) => {
|
||||
return html`
|
||||
@@ -73,13 +90,15 @@ class HassioAddonNetwork extends LitElement {
|
||||
<td>
|
||||
<paper-input
|
||||
@value-changed=${this._configChanged}
|
||||
placeholder="disabled"
|
||||
placeholder="${this.supervisor.localize(
|
||||
"addon.configuration.network.disabled"
|
||||
)}"
|
||||
.value=${item.host ? String(item.host) : ""}
|
||||
.container=${item.container}
|
||||
no-label-float
|
||||
></paper-input>
|
||||
</td>
|
||||
<td>${item.description}</td>
|
||||
<td>${this._computeDescription(item)}</td>
|
||||
</tr>
|
||||
`;
|
||||
})}
|
||||
@@ -88,10 +107,10 @@ class HassioAddonNetwork extends LitElement {
|
||||
</div>
|
||||
<div class="card-actions">
|
||||
<ha-progress-button class="warning" @click=${this._resetTapped}>
|
||||
Reset to defaults
|
||||
${this.supervisor.localize("common.reset_defaults")}
|
||||
</ha-progress-button>
|
||||
<ha-progress-button @click=${this._saveTapped}>
|
||||
Save
|
||||
${this.supervisor.localize("common.save")}
|
||||
</ha-progress-button>
|
||||
</div>
|
||||
</ha-card>
|
||||
@@ -105,6 +124,15 @@ 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 || {};
|
||||
@@ -147,12 +175,14 @@ class HassioAddonNetwork extends LitElement {
|
||||
};
|
||||
fireEvent(this, "hass-api-called", eventdata);
|
||||
if (this.addon?.state === "started") {
|
||||
await suggestAddonRestart(this, this.hass, this.addon);
|
||||
await suggestAddonRestart(this, this.hass, this.supervisor, this.addon);
|
||||
}
|
||||
} catch (err) {
|
||||
this._error = `Failed to set addon network configuration, ${extractApiErrorMessage(
|
||||
err
|
||||
)}`;
|
||||
this._error = this.supervisor.localize(
|
||||
"addon.failed_to_reset",
|
||||
"error",
|
||||
extractApiErrorMessage(err)
|
||||
);
|
||||
}
|
||||
|
||||
button.progress = false;
|
||||
@@ -181,12 +211,14 @@ class HassioAddonNetwork extends LitElement {
|
||||
};
|
||||
fireEvent(this, "hass-api-called", eventdata);
|
||||
if (this.addon?.state === "started") {
|
||||
await suggestAddonRestart(this, this.hass, this.addon);
|
||||
await suggestAddonRestart(this, this.hass, this.supervisor, this.addon);
|
||||
}
|
||||
} catch (err) {
|
||||
this._error = `Failed to set addon network configuration, ${extractApiErrorMessage(
|
||||
err
|
||||
)}`;
|
||||
this._error = this.supervisor.localize(
|
||||
"addon.failed_to_save",
|
||||
"error",
|
||||
extractApiErrorMessage(err)
|
||||
);
|
||||
}
|
||||
button.progress = false;
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import "../../../../src/components/ha-card";
|
||||
import {
|
||||
css,
|
||||
CSSResult,
|
||||
@@ -19,11 +20,14 @@ 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;
|
||||
@@ -81,9 +85,11 @@ class HassioAddonDocumentationDashboard extends LitElement {
|
||||
this.addon!.slug
|
||||
);
|
||||
} catch (err) {
|
||||
this._error = `Failed to get addon documentation, ${extractApiErrorMessage(
|
||||
err
|
||||
)}`;
|
||||
this._error = this.supervisor.localize(
|
||||
"addon.documentation.get_logs",
|
||||
"error",
|
||||
extractApiErrorMessage(err)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ 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";
|
||||
@@ -79,7 +80,7 @@ class HassioAddonDashboard extends LitElement {
|
||||
|
||||
const addonTabs: PageNavigation[] = [
|
||||
{
|
||||
name: "Info",
|
||||
translationKey: "addon.panel.info",
|
||||
path: `/hassio/addon/${this.addon.slug}/info`,
|
||||
iconPath: mdiInformationVariant,
|
||||
},
|
||||
@@ -87,7 +88,7 @@ class HassioAddonDashboard extends LitElement {
|
||||
|
||||
if (this.addon.documentation) {
|
||||
addonTabs.push({
|
||||
name: "Documentation",
|
||||
translationKey: "addon.panel.documentation",
|
||||
path: `/hassio/addon/${this.addon.slug}/documentation`,
|
||||
iconPath: mdiFileDocument,
|
||||
});
|
||||
@@ -96,12 +97,12 @@ class HassioAddonDashboard extends LitElement {
|
||||
if (this.addon.version) {
|
||||
addonTabs.push(
|
||||
{
|
||||
name: "Configuration",
|
||||
translationKey: "addon.panel.configuration",
|
||||
path: `/hassio/addon/${this.addon.slug}/config`,
|
||||
iconPath: mdiCogs,
|
||||
},
|
||||
{
|
||||
name: "Log",
|
||||
translationKey: "addon.panel.log",
|
||||
path: `/hassio/addon/${this.addon.slug}/logs`,
|
||||
iconPath: mdiMathLog,
|
||||
}
|
||||
@@ -113,11 +114,12 @@ 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
|
||||
@@ -172,9 +174,16 @@ class HassioAddonDashboard extends LitElement {
|
||||
|
||||
protected async firstUpdated(): Promise<void> {
|
||||
if (this.route.path === "") {
|
||||
const addon = extractSearchParam("addon");
|
||||
if (addon) {
|
||||
navigate(this, `/hassio/addon/${addon}`, true);
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
this.addEventListener("hass-api-called", (ev) => this._apiCalled(ev));
|
||||
@@ -190,8 +199,8 @@ class HassioAddonDashboard extends LitElement {
|
||||
const path: string = pathSplit[pathSplit.length - 1];
|
||||
|
||||
if (["uninstall", "install", "update", "start", "stop"].includes(path)) {
|
||||
fireEvent(this, "supervisor-colllection-refresh", {
|
||||
colllection: "supervisor",
|
||||
fireEvent(this, "supervisor-collection-refresh", {
|
||||
collection: "supervisor",
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -50,6 +50,7 @@ import {
|
||||
startHassioAddon,
|
||||
stopHassioAddon,
|
||||
uninstallHassioAddon,
|
||||
updateHassioAddon,
|
||||
validateHassioAddonOption,
|
||||
} from "../../../../src/data/hassio/addon";
|
||||
import {
|
||||
@@ -68,8 +69,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";
|
||||
|
||||
@@ -79,63 +80,6 @@ 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;
|
||||
@@ -162,11 +106,11 @@ class HassioAddonInfo extends LitElement {
|
||||
: undefined;
|
||||
const metrics = [
|
||||
{
|
||||
description: "Add-on CPU Usage",
|
||||
description: this.supervisor.localize("addon.dashboard.cpu_usage"),
|
||||
value: this._metrics?.cpu_percent,
|
||||
},
|
||||
{
|
||||
description: "Add-on RAM Usage",
|
||||
description: this.supervisor.localize("addon.dashboard.ram_usage"),
|
||||
value: this._metrics?.memory_percent,
|
||||
tooltip: `${bytesToString(this._metrics?.memory_usage)}/${bytesToString(
|
||||
this._metrics?.memory_limit
|
||||
@@ -176,14 +120,28 @@ class HassioAddonInfo extends LitElement {
|
||||
return html`
|
||||
${this.addon.update_available
|
||||
? html`
|
||||
<ha-card header="Update available! 🎉">
|
||||
<ha-card
|
||||
.header="${this.supervisor.localize(
|
||||
"common.update_available",
|
||||
"count",
|
||||
1
|
||||
)}🎉"
|
||||
>
|
||||
<div class="card-content">
|
||||
<hassio-card-content
|
||||
.hass=${this.hass}
|
||||
.title="${this.addon.name} ${this.addon
|
||||
.version_latest} is available"
|
||||
.description="You are currently running version ${this.addon
|
||||
.version}"
|
||||
.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
|
||||
)}"
|
||||
icon=${mdiArrowUpBoldCircle}
|
||||
iconClass="update"
|
||||
></hassio-card-content>
|
||||
@@ -194,29 +152,32 @@ class HassioAddonInfo extends LitElement {
|
||||
)
|
||||
? html`
|
||||
<p class="warning">
|
||||
This add-on is not compatible with the processor of
|
||||
your device or the operating system you have installed
|
||||
on your device.
|
||||
${this.supervisor.localize(
|
||||
"addon.dashboard.not_available_arch"
|
||||
)}
|
||||
</p>
|
||||
`
|
||||
: html`
|
||||
<p class="warning">
|
||||
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
|
||||
${this.supervisor.localize(
|
||||
"addon.dashboard.not_available_arch",
|
||||
"core_version_installed",
|
||||
this.supervisor.core.version,
|
||||
"core_version_needed",
|
||||
addonStoreInfo.homeassistant
|
||||
)}
|
||||
</p>
|
||||
`
|
||||
: ""}
|
||||
</div>
|
||||
<div class="card-actions">
|
||||
<mwc-button @click=${this._updateClicked}>
|
||||
Update
|
||||
${this.supervisor.localize("common.update")}
|
||||
</mwc-button>
|
||||
${this.addon.changelog
|
||||
? html`
|
||||
<mwc-button @click=${this._openChangelog}>
|
||||
Changelog
|
||||
${this.supervisor.localize("addon.dashboard.changelog")}
|
||||
</mwc-button>
|
||||
`
|
||||
: ""}
|
||||
@@ -227,12 +188,19 @@ class HassioAddonInfo extends LitElement {
|
||||
${!this.addon.protected
|
||||
? html`
|
||||
<ha-card class="warning">
|
||||
<h1 class="card-header">Warning: Protection mode is disabled!</h1>
|
||||
<h1 class="card-header">${this.supervisor.localize(
|
||||
"addon.dashboard.protection_mode.title"
|
||||
)}
|
||||
</h1>
|
||||
<div class="card-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.
|
||||
${this.supervisor.localize("addon.dashboard.protection_mode.content")}
|
||||
</div>
|
||||
<div class="card-actions protection-enable">
|
||||
<mwc-button @click=${this._protectionToggled}>Enable Protection mode</mwc-button>
|
||||
<mwc-button @click=${this._protectionToggled}>
|
||||
${this.supervisor.localize(
|
||||
"addon.dashboard.protection_mode.enable"
|
||||
)}
|
||||
</mwc-button>
|
||||
</div>
|
||||
</div>
|
||||
</ha-card>
|
||||
@@ -249,14 +217,18 @@ class HassioAddonInfo extends LitElement {
|
||||
${this._computeIsRunning
|
||||
? html`
|
||||
<ha-svg-icon
|
||||
title="Add-on is running"
|
||||
.title=${this.supervisor.localize(
|
||||
"dashboard.addon_running"
|
||||
)}
|
||||
class="running"
|
||||
.path=${mdiCircle}
|
||||
></ha-svg-icon>
|
||||
`
|
||||
: html`
|
||||
<ha-svg-icon
|
||||
title="Add-on is stopped"
|
||||
.title=${this.supervisor.localize(
|
||||
"dashboard.addon_stopped"
|
||||
)}
|
||||
class="stopped"
|
||||
.path=${mdiCircle}
|
||||
></ha-svg-icon>
|
||||
@@ -270,21 +242,29 @@ class HassioAddonInfo extends LitElement {
|
||||
? html`
|
||||
Current version: ${this.addon.version}
|
||||
<div class="changelog" @click=${this._openChangelog}>
|
||||
(<span class="changelog-link">changelog</span>)
|
||||
(<span class="changelog-link">
|
||||
${this.supervisor.localize("addon.dashboard.changelog")} </span
|
||||
>)
|
||||
</div>
|
||||
`
|
||||
: html`<span class="changelog-link" @click=${this._openChangelog}
|
||||
>Changelog</span
|
||||
>`}
|
||||
: html`<span class="changelog-link" @click=${this._openChangelog}>
|
||||
${this.supervisor.localize("addon.dashboard.changelog")}
|
||||
</span>`}
|
||||
</div>
|
||||
|
||||
<div class="description light-color">
|
||||
${this.addon.description}.<br />
|
||||
Visit
|
||||
<a href="${this.addon.url!}" target="_blank" rel="noreferrer">
|
||||
${this.addon.name} page</a
|
||||
>
|
||||
for details.
|
||||
${this.supervisor.localize(
|
||||
"addon.dashboard.visit_addon_page",
|
||||
"name",
|
||||
html`<a
|
||||
href="${this.addon.url!}"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
${this.addon.name}
|
||||
</a>`
|
||||
)}
|
||||
</div>
|
||||
<div class="addon-container">
|
||||
<div>
|
||||
@@ -305,7 +285,9 @@ class HassioAddonInfo extends LitElement {
|
||||
})}
|
||||
@click=${this._showMoreInfo}
|
||||
id="stage"
|
||||
label="stage"
|
||||
.label=${this.supervisor.localize(
|
||||
"addon.dashboard.capability.label.stage"
|
||||
)}
|
||||
description=""
|
||||
>
|
||||
<ha-svg-icon
|
||||
@@ -331,7 +313,9 @@ class HassioAddonInfo extends LitElement {
|
||||
<ha-label-badge
|
||||
@click=${this._showMoreInfo}
|
||||
id="host_network"
|
||||
label="host"
|
||||
.label=${this.supervisor.localize(
|
||||
"addon.dashboard.capability.label.host"
|
||||
)}
|
||||
description=""
|
||||
>
|
||||
<ha-svg-icon .path=${mdiNetwork}></ha-svg-icon>
|
||||
@@ -343,7 +327,9 @@ class HassioAddonInfo extends LitElement {
|
||||
<ha-label-badge
|
||||
@click=${this._showMoreInfo}
|
||||
id="full_access"
|
||||
label="hardware"
|
||||
.label=${this.supervisor.localize(
|
||||
"addon.dashboard.capability.label.hardware"
|
||||
)}
|
||||
description=""
|
||||
>
|
||||
<ha-svg-icon .path=${mdiChip}></ha-svg-icon>
|
||||
@@ -355,7 +341,9 @@ class HassioAddonInfo extends LitElement {
|
||||
<ha-label-badge
|
||||
@click=${this._showMoreInfo}
|
||||
id="homeassistant_api"
|
||||
label="hass"
|
||||
.label=${this.supervisor.localize(
|
||||
"addon.dashboard.capability.label.hass"
|
||||
)}
|
||||
description=""
|
||||
>
|
||||
<ha-svg-icon .path=${mdiHomeAssistant}></ha-svg-icon>
|
||||
@@ -367,8 +355,12 @@ class HassioAddonInfo extends LitElement {
|
||||
<ha-label-badge
|
||||
@click=${this._showMoreInfo}
|
||||
id="hassio_api"
|
||||
label="hassio"
|
||||
.description=${this.addon.hassio_role}
|
||||
.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}
|
||||
>
|
||||
<ha-svg-icon .path=${mdiHomeAssistant}></ha-svg-icon>
|
||||
</ha-label-badge>
|
||||
@@ -379,7 +371,9 @@ class HassioAddonInfo extends LitElement {
|
||||
<ha-label-badge
|
||||
@click=${this._showMoreInfo}
|
||||
id="docker_api"
|
||||
label="docker"
|
||||
.label=".${this.supervisor.localize(
|
||||
"addon.dashboard.capability.label.docker"
|
||||
)}"
|
||||
description=""
|
||||
>
|
||||
<ha-svg-icon .path=${mdiDocker}></ha-svg-icon>
|
||||
@@ -391,7 +385,9 @@ class HassioAddonInfo extends LitElement {
|
||||
<ha-label-badge
|
||||
@click=${this._showMoreInfo}
|
||||
id="host_pid"
|
||||
label="host pid"
|
||||
.label=${this.supervisor.localize(
|
||||
"addon.dashboard.capability.label.host_pid"
|
||||
)}
|
||||
description=""
|
||||
>
|
||||
<ha-svg-icon .path=${mdiPound}></ha-svg-icon>
|
||||
@@ -404,7 +400,9 @@ class HassioAddonInfo extends LitElement {
|
||||
@click=${this._showMoreInfo}
|
||||
class=${this._computeApparmorClassName}
|
||||
id="apparmor"
|
||||
label="apparmor"
|
||||
.label=${this.supervisor.localize(
|
||||
"addon.dashboard.capability.label.apparmor"
|
||||
)}
|
||||
description=""
|
||||
>
|
||||
<ha-svg-icon .path=${mdiShield}></ha-svg-icon>
|
||||
@@ -416,7 +414,9 @@ class HassioAddonInfo extends LitElement {
|
||||
<ha-label-badge
|
||||
@click=${this._showMoreInfo}
|
||||
id="auth_api"
|
||||
label="auth"
|
||||
.label=${this.supervisor.localize(
|
||||
"addon.dashboard.capability.label.auth"
|
||||
)}
|
||||
description=""
|
||||
>
|
||||
<ha-svg-icon .path=${mdiKey}></ha-svg-icon>
|
||||
@@ -428,7 +428,9 @@ class HassioAddonInfo extends LitElement {
|
||||
<ha-label-badge
|
||||
@click=${this._showMoreInfo}
|
||||
id="ingress"
|
||||
label="ingress"
|
||||
.label=${this.supervisor.localize(
|
||||
"addon.dashboard.capability.label.ingress"
|
||||
)}
|
||||
description=""
|
||||
>
|
||||
<ha-svg-icon
|
||||
@@ -449,10 +451,14 @@ class HassioAddonInfo extends LitElement {
|
||||
>
|
||||
<ha-settings-row ?three-line=${this.narrow}>
|
||||
<span slot="heading">
|
||||
Start on boot
|
||||
${this.supervisor.localize(
|
||||
"addon.dashboard.option.boot.title"
|
||||
)}
|
||||
</span>
|
||||
<span slot="description">
|
||||
Make the add-on start during a system boot
|
||||
${this.supervisor.localize(
|
||||
"addon.dashboard.option.boot.description"
|
||||
)}
|
||||
</span>
|
||||
<ha-switch
|
||||
@change=${this._startOnBootToggled}
|
||||
@@ -465,10 +471,14 @@ class HassioAddonInfo extends LitElement {
|
||||
? html`
|
||||
<ha-settings-row ?three-line=${this.narrow}>
|
||||
<span slot="heading">
|
||||
Watchdog
|
||||
${this.supervisor.localize(
|
||||
"addon.dashboard.option.watchdog.title"
|
||||
)}
|
||||
</span>
|
||||
<span slot="description">
|
||||
This will start the add-on if it crashes
|
||||
${this.supervisor.localize(
|
||||
"addon.dashboard.option.watchdog.description"
|
||||
)}
|
||||
</span>
|
||||
<ha-switch
|
||||
@change=${this._watchdogToggled}
|
||||
@@ -483,11 +493,14 @@ class HassioAddonInfo extends LitElement {
|
||||
? html`
|
||||
<ha-settings-row ?three-line=${this.narrow}>
|
||||
<span slot="heading">
|
||||
Auto update
|
||||
${this.supervisor.localize(
|
||||
"addon.dashboard.option.auto_update.title"
|
||||
)}
|
||||
</span>
|
||||
<span slot="description">
|
||||
Auto update the add-on when there is a new
|
||||
version available
|
||||
${this.supervisor.localize(
|
||||
"addon.dashboard.option.auto_update.description"
|
||||
)}
|
||||
</span>
|
||||
<ha-switch
|
||||
@change=${this._autoUpdateToggled}
|
||||
@@ -497,21 +510,22 @@ class HassioAddonInfo extends LitElement {
|
||||
</ha-settings-row>
|
||||
`
|
||||
: ""}
|
||||
${this.addon.ingress
|
||||
${!this._computeCannotIngressSidebar && this.addon.ingress
|
||||
? html`
|
||||
<ha-settings-row ?three-line=${this.narrow}>
|
||||
<span slot="heading">
|
||||
Show in sidebar
|
||||
${this.supervisor.localize(
|
||||
"addon.dashboard.option.ingress_panel.title"
|
||||
)}
|
||||
</span>
|
||||
<span slot="description">
|
||||
${this._computeCannotIngressSidebar
|
||||
? "This option requires Home Assistant 0.92 or later."
|
||||
: "Add this add-on to your sidebar"}
|
||||
${this.supervisor.localize(
|
||||
"addon.dashboard.option.ingress_panel.description"
|
||||
)}
|
||||
</span>
|
||||
<ha-switch
|
||||
@change=${this._panelToggled}
|
||||
.checked=${this.addon.ingress_panel}
|
||||
.disabled=${this._computeCannotIngressSidebar}
|
||||
haptic
|
||||
></ha-switch>
|
||||
</ha-settings-row>
|
||||
@@ -521,10 +535,14 @@ class HassioAddonInfo extends LitElement {
|
||||
? html`
|
||||
<ha-settings-row ?three-line=${this.narrow}>
|
||||
<span slot="heading">
|
||||
Protection mode
|
||||
${this.supervisor.localize(
|
||||
"addon.dashboard.option.protected.title"
|
||||
)}
|
||||
</span>
|
||||
<span slot="description">
|
||||
Blocks elevated system access from the add-on
|
||||
${this.supervisor.localize(
|
||||
"addon.dashboard.option.protected.description"
|
||||
)}
|
||||
</span>
|
||||
<ha-switch
|
||||
@change=${this._protectionToggled}
|
||||
@@ -542,7 +560,7 @@ class HassioAddonInfo extends LitElement {
|
||||
${this.addon.state === "started"
|
||||
? html`<ha-settings-row ?three-line=${this.narrow}>
|
||||
<span slot="heading">
|
||||
Hostname
|
||||
${this.supervisor.localize("addon.dashboard.hostname")}
|
||||
</span>
|
||||
<code slot="description">
|
||||
${this.addon.hostname}
|
||||
@@ -569,17 +587,20 @@ class HassioAddonInfo extends LitElement {
|
||||
)
|
||||
? html`
|
||||
<p class="warning">
|
||||
This add-on is not compatible with the processor of your
|
||||
device or the operating system you have installed on your
|
||||
device.
|
||||
${this.supervisor.localize(
|
||||
"addon.dashboard.not_available_arch"
|
||||
)}
|
||||
</p>
|
||||
`
|
||||
: html`
|
||||
<p class="warning">
|
||||
You are running Home Assistant
|
||||
${this.supervisor.core.version}, to install this add-on you
|
||||
need at least version ${addonStoreInfo!.homeassistant} of
|
||||
Home Assistant
|
||||
${this.supervisor.localize(
|
||||
"addon.dashboard.not_available_version",
|
||||
"core_version_installed",
|
||||
this.supervisor.core.version,
|
||||
"core_version_needed",
|
||||
addonStoreInfo!.homeassistant
|
||||
)}
|
||||
</p>
|
||||
`
|
||||
: ""}
|
||||
@@ -593,18 +614,18 @@ class HassioAddonInfo extends LitElement {
|
||||
class="warning"
|
||||
@click=${this._stopClicked}
|
||||
>
|
||||
Stop
|
||||
${this.supervisor.localize("addon.dashboard.stop")}
|
||||
</ha-progress-button>
|
||||
<ha-progress-button
|
||||
class="warning"
|
||||
@click=${this._restartClicked}
|
||||
>
|
||||
Restart
|
||||
${this.supervisor.localize("addon.dashboard.restart")}
|
||||
</ha-progress-button>
|
||||
`
|
||||
: html`
|
||||
<ha-progress-button @click=${this._startClicked}>
|
||||
Start
|
||||
${this.supervisor.localize("addon.dashboard.start")}
|
||||
</ha-progress-button>
|
||||
`
|
||||
: html`
|
||||
@@ -612,7 +633,7 @@ class HassioAddonInfo extends LitElement {
|
||||
.disabled=${!this.addon.available}
|
||||
@click=${this._installClicked}
|
||||
>
|
||||
Install
|
||||
${this.supervisor.localize("addon.dashboard.install")}
|
||||
</ha-progress-button>
|
||||
`}
|
||||
</div>
|
||||
@@ -627,7 +648,9 @@ class HassioAddonInfo extends LitElement {
|
||||
rel="noopener"
|
||||
>
|
||||
<mwc-button>
|
||||
Open web UI
|
||||
${this.supervisor.localize(
|
||||
"addon.dashboard.open_web_ui"
|
||||
)}
|
||||
</mwc-button>
|
||||
</a>
|
||||
`
|
||||
@@ -635,7 +658,9 @@ class HassioAddonInfo extends LitElement {
|
||||
${this._computeShowIngressUI
|
||||
? html`
|
||||
<mwc-button @click=${this._openIngress}>
|
||||
Open web UI
|
||||
${this.supervisor.localize(
|
||||
"addon.dashboard.open_web_ui"
|
||||
)}
|
||||
</mwc-button>
|
||||
`
|
||||
: ""}
|
||||
@@ -643,7 +668,7 @@ class HassioAddonInfo extends LitElement {
|
||||
class="warning"
|
||||
@click=${this._uninstallClicked}
|
||||
>
|
||||
Uninstall
|
||||
${this.supervisor.localize("addon.dashboard.uninstall")}
|
||||
</ha-progress-button>
|
||||
${this.addon.build
|
||||
? html`
|
||||
@@ -652,7 +677,7 @@ class HassioAddonInfo extends LitElement {
|
||||
.hass=${this.hass}
|
||||
.path="hassio/addons/${this.addon.slug}/rebuild"
|
||||
>
|
||||
Rebuild
|
||||
${this.supervisor.localize("addon.dashboard.rebuild")}
|
||||
</ha-call-api-button>
|
||||
`
|
||||
: ""}`
|
||||
@@ -712,8 +737,21 @@ class HassioAddonInfo extends LitElement {
|
||||
private _showMoreInfo(ev): void {
|
||||
const id = ev.currentTarget.id;
|
||||
showHassioMarkdownDialog(this, {
|
||||
title: PERMIS_DESC[id].title,
|
||||
content: PERMIS_DESC[id].description,
|
||||
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`
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -766,9 +804,11 @@ class HassioAddonInfo extends LitElement {
|
||||
};
|
||||
fireEvent(this, "hass-api-called", eventdata);
|
||||
} catch (err) {
|
||||
this._error = `Failed to set addon option, ${extractApiErrorMessage(
|
||||
err
|
||||
)}`;
|
||||
this._error = this.supervisor.localize(
|
||||
"addon.failed_to_save",
|
||||
"error",
|
||||
extractApiErrorMessage(err)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -786,9 +826,11 @@ class HassioAddonInfo extends LitElement {
|
||||
};
|
||||
fireEvent(this, "hass-api-called", eventdata);
|
||||
} catch (err) {
|
||||
this._error = `Failed to set addon option, ${extractApiErrorMessage(
|
||||
err
|
||||
)}`;
|
||||
this._error = this.supervisor.localize(
|
||||
"addon.failed_to_save",
|
||||
"error",
|
||||
extractApiErrorMessage(err)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -806,9 +848,11 @@ class HassioAddonInfo extends LitElement {
|
||||
};
|
||||
fireEvent(this, "hass-api-called", eventdata);
|
||||
} catch (err) {
|
||||
this._error = `Failed to set addon option, ${extractApiErrorMessage(
|
||||
err
|
||||
)}`;
|
||||
this._error = this.supervisor.localize(
|
||||
"addon.failed_to_save",
|
||||
"error",
|
||||
extractApiErrorMessage(err)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -826,9 +870,11 @@ class HassioAddonInfo extends LitElement {
|
||||
};
|
||||
fireEvent(this, "hass-api-called", eventdata);
|
||||
} catch (err) {
|
||||
this._error = `Failed to set addon security option, ${extractApiErrorMessage(
|
||||
err
|
||||
)}`;
|
||||
this._error = this.supervisor.localize(
|
||||
"addon.failed_to_save",
|
||||
"error",
|
||||
extractApiErrorMessage(err)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -846,9 +892,11 @@ class HassioAddonInfo extends LitElement {
|
||||
};
|
||||
fireEvent(this, "hass-api-called", eventdata);
|
||||
} catch (err) {
|
||||
this._error = `Failed to set addon option, ${extractApiErrorMessage(
|
||||
err
|
||||
)}`;
|
||||
this._error = this.supervisor.localize(
|
||||
"addon.failed_to_save",
|
||||
"error",
|
||||
extractApiErrorMessage(err)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -859,12 +907,14 @@ class HassioAddonInfo extends LitElement {
|
||||
this.addon.slug
|
||||
);
|
||||
showHassioMarkdownDialog(this, {
|
||||
title: "Changelog",
|
||||
title: this.supervisor.localize("addon.dashboard.changelog"),
|
||||
content,
|
||||
});
|
||||
} catch (err) {
|
||||
showAlertDialog(this, {
|
||||
title: "Failed to get addon changelog",
|
||||
title: this.supervisor.localize(
|
||||
"addon.dashboard.action_error.get_changelog"
|
||||
),
|
||||
text: extractApiErrorMessage(err),
|
||||
});
|
||||
}
|
||||
@@ -884,7 +934,7 @@ class HassioAddonInfo extends LitElement {
|
||||
fireEvent(this, "hass-api-called", eventdata);
|
||||
} catch (err) {
|
||||
showAlertDialog(this, {
|
||||
title: "Failed to install addon",
|
||||
title: this.supervisor.localize("addon.dashboard.action_error.install"),
|
||||
text: extractApiErrorMessage(err),
|
||||
});
|
||||
}
|
||||
@@ -905,7 +955,7 @@ class HassioAddonInfo extends LitElement {
|
||||
fireEvent(this, "hass-api-called", eventdata);
|
||||
} catch (err) {
|
||||
showAlertDialog(this, {
|
||||
title: "Failed to stop addon",
|
||||
title: this.supervisor.localize("addon.dashboard.action_error.stop"),
|
||||
text: extractApiErrorMessage(err),
|
||||
});
|
||||
}
|
||||
@@ -926,7 +976,7 @@ class HassioAddonInfo extends LitElement {
|
||||
fireEvent(this, "hass-api-called", eventdata);
|
||||
} catch (err) {
|
||||
showAlertDialog(this, {
|
||||
title: "Failed to restart addon",
|
||||
title: this.supervisor.localize("addon.dashboard.action_error.restart"),
|
||||
text: extractApiErrorMessage(err),
|
||||
});
|
||||
}
|
||||
@@ -934,12 +984,32 @@ class HassioAddonInfo extends LitElement {
|
||||
}
|
||||
|
||||
private async _updateClicked(): Promise<void> {
|
||||
showDialogSupervisorAddonUpdate(this, {
|
||||
addon: this.addon,
|
||||
showDialogSupervisorUpdate(this, {
|
||||
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;
|
||||
@@ -950,11 +1020,15 @@ class HassioAddonInfo extends LitElement {
|
||||
);
|
||||
if (!validate.valid) {
|
||||
await showConfirmationDialog(this, {
|
||||
title: "Failed to start addon - configuration validation failed!",
|
||||
title: this.supervisor.localize(
|
||||
"addon.dashboard.action_error.start_invalid_config"
|
||||
),
|
||||
text: validate.message.split(" Got ")[0],
|
||||
confirm: () => this._openConfiguration(),
|
||||
confirmText: "Go to configuration",
|
||||
dismissText: "Cancel",
|
||||
confirmText: this.supervisor.localize(
|
||||
"addon.dashboard.action_error.go_to_config"
|
||||
),
|
||||
dismissText: this.supervisor.localize("common.cancel"),
|
||||
});
|
||||
button.progress = false;
|
||||
return;
|
||||
@@ -979,7 +1053,7 @@ class HassioAddonInfo extends LitElement {
|
||||
fireEvent(this, "hass-api-called", eventdata);
|
||||
} catch (err) {
|
||||
showAlertDialog(this, {
|
||||
title: "Failed to start addon",
|
||||
title: this.supervisor.localize("addon.dashboard.action_error.start"),
|
||||
text: extractApiErrorMessage(err),
|
||||
});
|
||||
}
|
||||
@@ -1017,7 +1091,9 @@ class HassioAddonInfo extends LitElement {
|
||||
fireEvent(this, "hass-api-called", eventdata);
|
||||
} catch (err) {
|
||||
showAlertDialog(this, {
|
||||
title: "Failed to uninstall addon",
|
||||
title: this.supervisor.localize(
|
||||
"addon.dashboard.action_error.uninstall"
|
||||
),
|
||||
text: extractApiErrorMessage(err),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
} from "lit-element";
|
||||
import "../../../../src/components/ha-circular-progress";
|
||||
import { HassioAddonDetails } from "../../../../src/data/hassio/addon";
|
||||
import { Supervisor } from "../../../../src/data/supervisor/supervisor";
|
||||
import { haStyle } from "../../../../src/resources/styles";
|
||||
import { HomeAssistant } from "../../../../src/types";
|
||||
import { hassioStyle } from "../../resources/hassio-style";
|
||||
@@ -18,6 +19,8 @@ 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 {
|
||||
@@ -28,6 +31,7 @@ class HassioAddonLogDashboard extends LitElement {
|
||||
<div class="content">
|
||||
<hassio-addon-logs
|
||||
.hass=${this.hass}
|
||||
.supervisor=${this.supervisor}
|
||||
.addon=${this.addon}
|
||||
></hassio-addon-logs>
|
||||
</div>
|
||||
|
||||
@@ -15,6 +15,7 @@ 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";
|
||||
@@ -24,6 +25,8 @@ 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;
|
||||
@@ -48,7 +51,9 @@ class HassioAddonLogs extends LitElement {
|
||||
: ""}
|
||||
</div>
|
||||
<div class="card-actions">
|
||||
<mwc-button @click=${this._refresh}>Refresh</mwc-button>
|
||||
<mwc-button @click=${this._refresh}>
|
||||
${this.supervisor.localize("common.refresh")}
|
||||
</mwc-button>
|
||||
</div>
|
||||
</ha-card>
|
||||
`;
|
||||
@@ -76,7 +81,11 @@ class HassioAddonLogs extends LitElement {
|
||||
try {
|
||||
this._content = await fetchHassioAddonLogs(this.hass, this.addon.slug);
|
||||
} catch (err) {
|
||||
this._error = `Failed to get addon logs, ${extractApiErrorMessage(err)}`;
|
||||
this._error = this.supervisor.localize(
|
||||
"addon.logs.get_logs",
|
||||
"error",
|
||||
extractApiErrorMessage(err)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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>
|
||||
`
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -27,17 +27,15 @@ class HassioAddons extends LitElement {
|
||||
protected render(): TemplateResult {
|
||||
return html`
|
||||
<div class="content">
|
||||
<h1>Add-ons</h1>
|
||||
<h1>${this.supervisor.localize("dashboard.addons")}</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}>
|
||||
the add-on store
|
||||
${this.supervisor.localize("dashboard.no_addons")}
|
||||
</button>
|
||||
to get started!
|
||||
</div>
|
||||
</ha-card>
|
||||
`
|
||||
@@ -58,10 +56,16 @@ class HassioAddons extends LitElement {
|
||||
? mdiArrowUpBoldCircle
|
||||
: mdiPuzzle}
|
||||
.iconTitle=${addon.state !== "started"
|
||||
? "Add-on is stopped"
|
||||
? this.supervisor.localize(
|
||||
"dashboard.addon_stopped"
|
||||
)
|
||||
: addon.update_available!
|
||||
? "New version available"
|
||||
: "Add-on is running"}
|
||||
? this.supervisor.localize(
|
||||
"dashboard.addon_new_version"
|
||||
)
|
||||
: this.supervisor.localize(
|
||||
"dashboard.addon_running"
|
||||
)}
|
||||
.iconClass=${addon.update_available
|
||||
? addon.state === "started"
|
||||
? "update"
|
||||
|
||||
@@ -29,13 +29,16 @@ 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">Dashboard</span>
|
||||
<span slot="header">
|
||||
${this.supervisor.localize("panel.dashboard")}
|
||||
</span>
|
||||
<div class="content">
|
||||
<hassio-update
|
||||
.hass=${this.hass}
|
||||
|
||||
@@ -10,30 +10,40 @@ 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,
|
||||
ignoredStatusCodes,
|
||||
ignoreSupervisorError,
|
||||
} from "../../../src/data/hassio/common";
|
||||
import { HassioHassOSInfo } from "../../../src/data/hassio/host";
|
||||
import {
|
||||
HassioHomeAssistantInfo,
|
||||
HassioSupervisorInfo,
|
||||
} from "../../../src/data/hassio/supervisor";
|
||||
import { Supervisor } from "../../../src/data/supervisor/supervisor";
|
||||
import { updateCore } from "../../../src/data/supervisor/core";
|
||||
import {
|
||||
Supervisor,
|
||||
supervisorApiWsRequest,
|
||||
} 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 { showDialogSupervisorCoreUpdate } from "../dialogs/core/show-dialog-core-update";
|
||||
import { showDialogSupervisorUpdate } from "../dialogs/update/show-dialog-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;
|
||||
@@ -59,9 +69,12 @@ export class HassioUpdate extends LitElement {
|
||||
return html`
|
||||
<div class="content">
|
||||
<h1>
|
||||
${updatesAvailable > 1
|
||||
? "Updates Available 🎉"
|
||||
: "Update Available 🎉"}
|
||||
${this.supervisor.localize(
|
||||
"common.update_available",
|
||||
"count",
|
||||
updatesAvailable
|
||||
)}
|
||||
🎉
|
||||
</h1>
|
||||
<div class="card-group">
|
||||
${this._renderUpdateCard(
|
||||
@@ -110,14 +123,30 @@ export class HassioUpdate extends LitElement {
|
||||
<div class="icon">
|
||||
<ha-svg-icon .path=${mdiHomeAssistant}></ha-svg-icon>
|
||||
</div>
|
||||
<div class="update-heading">${name} ${object.version_latest}</div>
|
||||
<div class="warning">
|
||||
You are currently running version ${object.version}
|
||||
</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>
|
||||
<div class="card-actions">
|
||||
<a href="${releaseNotesUrl}" target="_blank" rel="noreferrer">
|
||||
<mwc-button>Release notes</mwc-button>
|
||||
<mwc-button>
|
||||
${this.supervisor.localize("common.release_notes")}
|
||||
</mwc-button>
|
||||
</a>
|
||||
<ha-progress-button
|
||||
.apiPath=${apiPath}
|
||||
@@ -126,7 +155,7 @@ export class HassioUpdate extends LitElement {
|
||||
.version=${object.version_latest}
|
||||
@click=${this._confirmUpdate}
|
||||
>
|
||||
Update
|
||||
${this.supervisor.localize("common.update")}
|
||||
</ha-progress-button>
|
||||
</div>
|
||||
</ha-card>
|
||||
@@ -136,15 +165,35 @@ export class HassioUpdate extends LitElement {
|
||||
private async _confirmUpdate(ev): Promise<void> {
|
||||
const item = ev.currentTarget;
|
||||
if (item.key === "core") {
|
||||
showDialogSupervisorCoreUpdate(this, { supervisor: this.supervisor });
|
||||
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(),
|
||||
});
|
||||
return;
|
||||
}
|
||||
item.progress = true;
|
||||
const confirmed = await showConfirmationDialog(this, {
|
||||
title: `Update ${item.name}`,
|
||||
text: `Are you sure you want to update ${item.name} to version ${item.version}?`,
|
||||
confirmText: "update",
|
||||
dismissText: "cancel",
|
||||
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"),
|
||||
});
|
||||
|
||||
if (!confirmed) {
|
||||
@@ -152,20 +201,24 @@ export class HassioUpdate extends LitElement {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await this.hass.callApi<HassioResponse<void>>("POST", item.apiPath);
|
||||
fireEvent(this, "supervisor-colllection-refresh", {
|
||||
colllection: item.key,
|
||||
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,
|
||||
});
|
||||
} 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 &&
|
||||
err.status_code &&
|
||||
!ignoredStatusCodes.has(err.status_code)
|
||||
) {
|
||||
if (this.hass.connection.connected && !ignoreSupervisorError(err)) {
|
||||
showAlertDialog(this, {
|
||||
title: "Update failed",
|
||||
title: this.supervisor.localize("common.error.update_failed"),
|
||||
text: extractApiErrorMessage(err),
|
||||
});
|
||||
}
|
||||
@@ -173,6 +226,13 @@ 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,
|
||||
@@ -190,9 +250,6 @@ 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;
|
||||
@@ -200,13 +257,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;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
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,
|
||||
});
|
||||
};
|
||||
@@ -1,182 +0,0 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
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,
|
||||
});
|
||||
};
|
||||
@@ -35,6 +35,7 @@ import {
|
||||
updateNetworkInterface,
|
||||
WifiConfiguration,
|
||||
} from "../../../../src/data/hassio/network";
|
||||
import { Supervisor } from "../../../../src/data/supervisor/supervisor";
|
||||
import {
|
||||
showAlertDialog,
|
||||
showConfirmationDialog,
|
||||
@@ -51,6 +52,8 @@ 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;
|
||||
@@ -73,7 +76,8 @@ export class DialogHassioNetwork extends LitElement
|
||||
this._params = params;
|
||||
this._dirty = false;
|
||||
this._curTabIndex = 0;
|
||||
this._interfaces = params.network.interfaces.sort((a, b) => {
|
||||
this.supervisor = params.supervisor;
|
||||
this._interfaces = params.supervisor.network.interfaces.sort((a, b) => {
|
||||
return a.primary > b.primary ? -1 : 1;
|
||||
});
|
||||
this._interface = { ...this._interfaces[this._curTabIndex] };
|
||||
@@ -104,7 +108,7 @@ export class DialogHassioNetwork extends LitElement
|
||||
<div slot="heading">
|
||||
<ha-header-bar>
|
||||
<span slot="title">
|
||||
Network settings
|
||||
${this.supervisor.localize("dialog.network.title")}
|
||||
</span>
|
||||
<mwc-icon-button slot="actionItems" dialogAction="cancel">
|
||||
<ha-svg-icon .path=${mdiClose}></ha-svg-icon>
|
||||
@@ -139,7 +143,13 @@ export class DialogHassioNetwork extends LitElement
|
||||
? html`
|
||||
<ha-expansion-panel header="Wi-Fi" outlined>
|
||||
${this._interface?.wifi?.ssid
|
||||
? html`<p>Connected to: ${this._interface?.wifi?.ssid}</p>`
|
||||
? html`<p>
|
||||
${this.supervisor.localize(
|
||||
"dialog.network.connected_to",
|
||||
"ssid",
|
||||
this._interface?.wifi?.ssid
|
||||
)}
|
||||
</p>`
|
||||
: ""}
|
||||
<mwc-button
|
||||
class="scan"
|
||||
@@ -149,7 +159,7 @@ export class DialogHassioNetwork extends LitElement
|
||||
${this._scanning
|
||||
? html`<ha-circular-progress active size="small">
|
||||
</ha-circular-progress>`
|
||||
: "Scan for accesspoints"}
|
||||
: this.supervisor.localize("dialog.network.scan_ap")}
|
||||
</mwc-button>
|
||||
${this._accessPoints &&
|
||||
this._accessPoints.accesspoints &&
|
||||
@@ -181,7 +191,11 @@ export class DialogHassioNetwork extends LitElement
|
||||
${this._wifiConfiguration
|
||||
? html`
|
||||
<div class="radio-row">
|
||||
<ha-formfield label="open">
|
||||
<ha-formfield
|
||||
.label=${this.supervisor.localize(
|
||||
"dialog.network.open"
|
||||
)}
|
||||
>
|
||||
<ha-radio
|
||||
@change=${this._handleRadioValueChangedAp}
|
||||
.ap=${this._wifiConfiguration}
|
||||
@@ -193,7 +207,11 @@ export class DialogHassioNetwork extends LitElement
|
||||
>
|
||||
</ha-radio>
|
||||
</ha-formfield>
|
||||
<ha-formfield label="wep">
|
||||
<ha-formfield
|
||||
.label=${this.supervisor.localize(
|
||||
"dialog.network.wep"
|
||||
)}
|
||||
>
|
||||
<ha-radio
|
||||
@change=${this._handleRadioValueChangedAp}
|
||||
.ap=${this._wifiConfiguration}
|
||||
@@ -203,7 +221,11 @@ export class DialogHassioNetwork extends LitElement
|
||||
>
|
||||
</ha-radio>
|
||||
</ha-formfield>
|
||||
<ha-formfield label="wpa-psk">
|
||||
<ha-formfield
|
||||
.label=${this.supervisor.localize(
|
||||
"dialog.network.wpa"
|
||||
)}
|
||||
>
|
||||
<ha-radio
|
||||
@change=${this._handleRadioValueChangedAp}
|
||||
.ap=${this._wifiConfiguration}
|
||||
@@ -237,18 +259,21 @@ export class DialogHassioNetwork extends LitElement
|
||||
: ""}
|
||||
${this._dirty
|
||||
? html`<div class="warning">
|
||||
If you are changing the Wi-Fi, IP or gateway addresses, you might
|
||||
lose the connection!
|
||||
${this.supervisor.localize("dialog.network.warning")}
|
||||
</div>`
|
||||
: ""}
|
||||
</div>
|
||||
<div class="buttons">
|
||||
<mwc-button label="close" @click=${this.closeDialog}> </mwc-button>
|
||||
<mwc-button
|
||||
.label=${this.supervisor.localize("common.cancel")}
|
||||
@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>`
|
||||
: "Save"}
|
||||
: this.supervisor.localize("common.save")}
|
||||
</mwc-button>
|
||||
</div>`;
|
||||
}
|
||||
@@ -285,7 +310,9 @@ export class DialogHassioNetwork extends LitElement
|
||||
outlined
|
||||
>
|
||||
<div class="radio-row">
|
||||
<ha-formfield label="DHCP">
|
||||
<ha-formfield
|
||||
.label=${this.supervisor.localize("dialog.network.dhcp")}
|
||||
>
|
||||
<ha-radio
|
||||
@change=${this._handleRadioValueChanged}
|
||||
.version=${version}
|
||||
@@ -295,7 +322,9 @@ export class DialogHassioNetwork extends LitElement
|
||||
>
|
||||
</ha-radio>
|
||||
</ha-formfield>
|
||||
<ha-formfield label="Static">
|
||||
<ha-formfield
|
||||
.label=${this.supervisor.localize("dialog.network.static")}
|
||||
>
|
||||
<ha-radio
|
||||
@change=${this._handleRadioValueChanged}
|
||||
.version=${version}
|
||||
@@ -305,7 +334,10 @@ export class DialogHassioNetwork extends LitElement
|
||||
>
|
||||
</ha-radio>
|
||||
</ha-formfield>
|
||||
<ha-formfield label="Disabled" class="warning">
|
||||
<ha-formfield
|
||||
.label=${this.supervisor.localize("dialog.network.disabled")}
|
||||
class="warning"
|
||||
>
|
||||
<ha-radio
|
||||
@change=${this._handleRadioValueChanged}
|
||||
.version=${version}
|
||||
@@ -321,7 +353,7 @@ export class DialogHassioNetwork extends LitElement
|
||||
<paper-input
|
||||
class="flex-auto"
|
||||
id="address"
|
||||
label="IP address/Netmask"
|
||||
.label=${this.supervisor.localize("dialog.network.ip_netmask")}
|
||||
.version=${version}
|
||||
.value=${this._toString(this._interface![version].address)}
|
||||
@value-changed=${this._handleInputValueChanged}
|
||||
@@ -330,7 +362,7 @@ export class DialogHassioNetwork extends LitElement
|
||||
<paper-input
|
||||
class="flex-auto"
|
||||
id="gateway"
|
||||
label="Gateway address"
|
||||
.label=${this.supervisor.localize("dialog.network.gateway")}
|
||||
.version=${version}
|
||||
.value=${this._interface![version].gateway}
|
||||
@value-changed=${this._handleInputValueChanged}
|
||||
@@ -339,7 +371,7 @@ export class DialogHassioNetwork extends LitElement
|
||||
<paper-input
|
||||
class="flex-auto"
|
||||
id="nameservers"
|
||||
label="DNS servers"
|
||||
.label=${this.supervisor.localize("dialog.network.dns_servers")}
|
||||
.version=${version}
|
||||
.value=${this._toString(this._interface![version].nameservers)}
|
||||
@value-changed=${this._handleInputValueChanged}
|
||||
@@ -424,7 +456,7 @@ export class DialogHassioNetwork extends LitElement
|
||||
);
|
||||
} catch (err) {
|
||||
showAlertDialog(this, {
|
||||
title: "Failed to change network settings",
|
||||
title: this.supervisor.localize("dialog.network.failed_to_change"),
|
||||
text: extractApiErrorMessage(err),
|
||||
});
|
||||
this._processing = false;
|
||||
@@ -437,10 +469,9 @@ export class DialogHassioNetwork extends LitElement
|
||||
private async _handleTabActivated(ev: CustomEvent): Promise<void> {
|
||||
if (this._dirty) {
|
||||
const confirm = await showConfirmationDialog(this, {
|
||||
text:
|
||||
"You have unsaved changes, these will get lost if you change tabs, do you want to continue?",
|
||||
confirmText: "yes",
|
||||
dismissText: "no",
|
||||
text: this.supervisor.localize("dialog.network.unsaved"),
|
||||
confirmText: this.supervisor.localize("common.yes"),
|
||||
dismissText: this.supervisor.localize("common.no"),
|
||||
});
|
||||
if (!confirm) {
|
||||
this.requestUpdate("_interface");
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { fireEvent } from "../../../../src/common/dom/fire_event";
|
||||
import { NetworkInfo } from "../../../../src/data/hassio/network";
|
||||
import { Supervisor } from "../../../../src/data/supervisor/supervisor";
|
||||
import "./dialog-hassio-network";
|
||||
|
||||
export interface HassioNetworkDialogParams {
|
||||
network: NetworkInfo;
|
||||
supervisor: Supervisor;
|
||||
loadData: () => Promise<void>;
|
||||
}
|
||||
|
||||
|
||||
@@ -22,14 +22,18 @@ 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;
|
||||
@@ -55,8 +59,8 @@ class HassioRegistriesDialog extends LitElement {
|
||||
.heading=${createCloseHeading(
|
||||
this.hass,
|
||||
this._addingRegistry
|
||||
? "Add New Docker Registry"
|
||||
: "Manage Docker Registries"
|
||||
? this.supervisor.localize("dialog.registries.title_add")
|
||||
: this.supervisor.localize("dialog.registries.title_manage")
|
||||
)}
|
||||
>
|
||||
<div class="form">
|
||||
@@ -66,7 +70,9 @@ class HassioRegistriesDialog extends LitElement {
|
||||
@value-changed=${this._inputChanged}
|
||||
class="flex-auto"
|
||||
name="registry"
|
||||
label="Registry"
|
||||
.label=${this.supervisor.localize(
|
||||
"dialog.registries.registry"
|
||||
)}
|
||||
required
|
||||
auto-validate
|
||||
></paper-input>
|
||||
@@ -74,7 +80,9 @@ class HassioRegistriesDialog extends LitElement {
|
||||
@value-changed=${this._inputChanged}
|
||||
class="flex-auto"
|
||||
name="username"
|
||||
label="Username"
|
||||
.label=${this.supervisor.localize(
|
||||
"dialog.registries.username"
|
||||
)}
|
||||
required
|
||||
auto-validate
|
||||
></paper-input>
|
||||
@@ -82,7 +90,9 @@ class HassioRegistriesDialog extends LitElement {
|
||||
@value-changed=${this._inputChanged}
|
||||
class="flex-auto"
|
||||
name="password"
|
||||
label="Password"
|
||||
.label=${this.supervisor.localize(
|
||||
"dialog.registries.password"
|
||||
)}
|
||||
type="password"
|
||||
required
|
||||
auto-validate
|
||||
@@ -94,7 +104,7 @@ class HassioRegistriesDialog extends LitElement {
|
||||
)}
|
||||
@click=${this._addNewRegistry}
|
||||
>
|
||||
Add registry
|
||||
${this.supervisor.localize("dialog.registries.add_registry")}
|
||||
</mwc-button>
|
||||
`
|
||||
: html`${this._registries?.length
|
||||
@@ -103,11 +113,16 @@ class HassioRegistriesDialog extends LitElement {
|
||||
<mwc-list-item class="option" hasMeta twoline>
|
||||
<span>${entry.registry}</span>
|
||||
<span slot="secondary"
|
||||
>Username: ${entry.username}</span
|
||||
>${this.supervisor.localize(
|
||||
"dialog.registries.username"
|
||||
)}:
|
||||
${entry.username}</span
|
||||
>
|
||||
<mwc-icon-button
|
||||
.entry=${entry}
|
||||
title="Remove"
|
||||
.title=${this.supervisor.localize(
|
||||
"dialog.registries.remove"
|
||||
)}
|
||||
slot="meta"
|
||||
@click=${this._removeRegistry}
|
||||
>
|
||||
@@ -118,11 +133,17 @@ class HassioRegistriesDialog extends LitElement {
|
||||
})
|
||||
: html`
|
||||
<mwc-list-item>
|
||||
<span>No registries configured</span>
|
||||
<span
|
||||
>${this.supervisor.localize(
|
||||
"dialog.registries.no_registries"
|
||||
)}</span
|
||||
>
|
||||
</mwc-list-item>
|
||||
`}
|
||||
<mwc-button @click=${this._addRegistry}>
|
||||
Add new registry
|
||||
${this.supervisor.localize(
|
||||
"dialog.registries.add_new_registry"
|
||||
)}
|
||||
</mwc-button> `}
|
||||
</div>
|
||||
</ha-dialog>
|
||||
@@ -134,8 +155,9 @@ class HassioRegistriesDialog extends LitElement {
|
||||
this[`_${target.name}`] = target.value;
|
||||
}
|
||||
|
||||
public async showDialog(_dialogParams: any): Promise<void> {
|
||||
public async showDialog(dialogParams: RegistriesDialogParams): Promise<void> {
|
||||
this._opened = true;
|
||||
this.supervisor = dialogParams.supervisor;
|
||||
await this._loadRegistries();
|
||||
await this.updateComplete;
|
||||
}
|
||||
@@ -178,7 +200,7 @@ class HassioRegistriesDialog extends LitElement {
|
||||
this._addingRegistry = false;
|
||||
} catch (err) {
|
||||
showAlertDialog(this, {
|
||||
title: "Failed to add registry",
|
||||
title: this.supervisor.localize("dialog.registries.failed_to_add"),
|
||||
text: extractApiErrorMessage(err),
|
||||
});
|
||||
}
|
||||
@@ -192,7 +214,7 @@ class HassioRegistriesDialog extends LitElement {
|
||||
await this._loadRegistries();
|
||||
} catch (err) {
|
||||
showAlertDialog(this, {
|
||||
title: "Failed to remove registry",
|
||||
title: this.supervisor.localize("dialog.registries.failed_to_remove"),
|
||||
text: extractApiErrorMessage(err),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,10 +1,18 @@
|
||||
import { fireEvent } from "../../../../src/common/dom/fire_event";
|
||||
import { Supervisor } from "../../../../src/data/supervisor/supervisor";
|
||||
import "./dialog-hassio-registries";
|
||||
|
||||
export const showRegistriesDialog = (element: HTMLElement): void => {
|
||||
export interface RegistriesDialogParams {
|
||||
supervisor: Supervisor;
|
||||
}
|
||||
|
||||
export const showRegistriesDialog = (
|
||||
element: HTMLElement,
|
||||
dialogParams: RegistriesDialogParams
|
||||
): void => {
|
||||
fireEvent(element, "show-dialog", {
|
||||
dialogTag: "dialog-hassio-registries",
|
||||
dialogImport: () => import("./dialog-hassio-registries"),
|
||||
dialogParams: {},
|
||||
dialogParams,
|
||||
});
|
||||
};
|
||||
|
||||
@@ -17,8 +17,9 @@ 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 "../../../../src/components/ha-dialog";
|
||||
import { createCloseHeading } from "../../../../src/components/ha-dialog";
|
||||
import "../../../../src/components/ha-svg-icon";
|
||||
import {
|
||||
fetchHassioAddonsInfo,
|
||||
@@ -34,27 +35,29 @@ 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: any): Promise<void> {
|
||||
this._dialogParams = _dialogParams;
|
||||
this._repos = _dialogParams.repos;
|
||||
public async showDialog(
|
||||
dialogParams: HassioRepositoryDialogParams
|
||||
): Promise<void> {
|
||||
this._dialogParams = dialogParams;
|
||||
this._opened = true;
|
||||
await this._loadData();
|
||||
await this.updateComplete;
|
||||
}
|
||||
|
||||
public closeDialog(): void {
|
||||
this._dialogParams = undefined;
|
||||
this._opened = false;
|
||||
this._error = "";
|
||||
}
|
||||
@@ -66,14 +69,20 @@ class HassioRepositoriesDialog extends LitElement {
|
||||
);
|
||||
|
||||
protected render(): TemplateResult {
|
||||
const repositories = this._filteredRepositories(this._repos);
|
||||
if (!this._dialogParams?.supervisor || this._repositories === undefined) {
|
||||
return html``;
|
||||
}
|
||||
const repositories = this._filteredRepositories(this._repositories);
|
||||
return html`
|
||||
<ha-dialog
|
||||
.open=${this._opened}
|
||||
@closing=${this.closeDialog}
|
||||
scrimClickAction
|
||||
escapeKeyAction
|
||||
heading="Manage add-on repositories"
|
||||
.heading=${createCloseHeading(
|
||||
this.hass,
|
||||
this._dialogParams!.supervisor.localize("dialog.repositories.title")
|
||||
)}
|
||||
>
|
||||
${this._error ? html`<div class="error">${this._error}</div>` : ""}
|
||||
<div class="form">
|
||||
@@ -88,7 +97,9 @@ class HassioRepositoriesDialog extends LitElement {
|
||||
</paper-item-body>
|
||||
<mwc-icon-button
|
||||
.slug=${repo.slug}
|
||||
title="Remove"
|
||||
.title=${this._dialogParams!.supervisor.localize(
|
||||
"dialog.repositories.remove"
|
||||
)}
|
||||
@click=${this._removeRepository}
|
||||
>
|
||||
<ha-svg-icon .path=${mdiDelete}></ha-svg-icon>
|
||||
@@ -105,18 +116,23 @@ class HassioRepositoriesDialog extends LitElement {
|
||||
<paper-input
|
||||
class="flex-auto"
|
||||
id="repository_input"
|
||||
label="Add repository"
|
||||
.value=${this._dialogParams!.url || ""}
|
||||
.label=${this._dialogParams!.supervisor.localize(
|
||||
"dialog.repositories.add"
|
||||
)}
|
||||
@keydown=${this._handleKeyAdd}
|
||||
></paper-input>
|
||||
<mwc-button @click=${this._addRepository}>
|
||||
${this._prosessing
|
||||
? html`<ha-circular-progress active></ha-circular-progress>`
|
||||
: "Add"}
|
||||
: this._dialogParams!.supervisor.localize(
|
||||
"dialog.repositories.add"
|
||||
)}
|
||||
</mwc-button>
|
||||
</div>
|
||||
</div>
|
||||
<mwc-button slot="primaryAction" @click="${this.closeDialog}">
|
||||
Close
|
||||
<mwc-button slot="primaryAction" @click=${this.closeDialog}>
|
||||
${this._dialogParams?.supervisor.localize("common.close")}
|
||||
</mwc-button>
|
||||
</ha-dialog>
|
||||
`;
|
||||
@@ -147,6 +163,11 @@ class HassioRepositoriesDialog extends LitElement {
|
||||
ha-paper-dropdown-menu {
|
||||
display: block;
|
||||
}
|
||||
ha-circular-progress {
|
||||
display: block;
|
||||
margin: 32px;
|
||||
text-align: center;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
@@ -167,13 +188,25 @@ 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._repos);
|
||||
const repositories = this._filteredRepositories(this._repositories!);
|
||||
const newRepositories = repositories.map((repo) => {
|
||||
return repo.source;
|
||||
});
|
||||
@@ -183,11 +216,7 @@ class HassioRepositoriesDialog extends LitElement {
|
||||
await setSupervisorOption(this.hass, {
|
||||
addons_repositories: newRepositories,
|
||||
});
|
||||
|
||||
const addonsInfo = await fetchHassioAddonsInfo(this.hass);
|
||||
this._repos = addonsInfo.repositories;
|
||||
|
||||
await this._dialogParams!.loadData();
|
||||
await this._loadData();
|
||||
|
||||
input.value = "";
|
||||
} catch (err) {
|
||||
@@ -198,7 +227,7 @@ class HassioRepositoriesDialog extends LitElement {
|
||||
|
||||
private async _removeRepository(ev: Event) {
|
||||
const slug = (ev.currentTarget as any).slug;
|
||||
const repositories = this._filteredRepositories(this._repos);
|
||||
const repositories = this._filteredRepositories(this._repositories!);
|
||||
const repository = repositories.find((repo) => {
|
||||
return repo.slug === slug;
|
||||
});
|
||||
@@ -217,11 +246,7 @@ class HassioRepositoriesDialog extends LitElement {
|
||||
await setSupervisorOption(this.hass, {
|
||||
addons_repositories: newRepositories,
|
||||
});
|
||||
|
||||
const addonsInfo = await fetchHassioAddonsInfo(this.hass);
|
||||
this._repos = addonsInfo.repositories;
|
||||
|
||||
await this._dialogParams!.loadData();
|
||||
await this._loadData();
|
||||
} catch (err) {
|
||||
this._error = extractApiErrorMessage(err);
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { fireEvent } from "../../../../src/common/dom/fire_event";
|
||||
import { HassioAddonRepository } from "../../../../src/data/hassio/addon";
|
||||
import { Supervisor } from "../../../../src/data/supervisor/supervisor";
|
||||
import "./dialog-hassio-repositories";
|
||||
|
||||
export interface HassioRepositoryDialogParams {
|
||||
repos: HassioAddonRepository[];
|
||||
loadData: () => Promise<void>;
|
||||
supervisor: Supervisor;
|
||||
url?: string;
|
||||
}
|
||||
|
||||
export const showRepositoriesDialog = (
|
||||
|
||||
@@ -4,6 +4,7 @@ import {
|
||||
restartHassioAddon,
|
||||
} from "../../../src/data/hassio/addon";
|
||||
import { extractApiErrorMessage } from "../../../src/data/hassio/common";
|
||||
import { Supervisor } from "../../../src/data/supervisor/supervisor";
|
||||
import {
|
||||
showAlertDialog,
|
||||
showConfirmationDialog,
|
||||
@@ -13,20 +14,25 @@ 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: addon.name,
|
||||
text: "Do you want to restart the add-on with your changes?",
|
||||
confirmText: "restart add-on",
|
||||
dismissText: "no",
|
||||
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"),
|
||||
});
|
||||
if (confirmed) {
|
||||
try {
|
||||
await restartHassioAddon(hass, addon.slug);
|
||||
} catch (err) {
|
||||
showAlertDialog(element, {
|
||||
title: "Failed to restart",
|
||||
title: supervisor.localize(
|
||||
"common.failed_to_restart_name",
|
||||
"name",
|
||||
addon.name
|
||||
),
|
||||
text: extractApiErrorMessage(err),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -0,0 +1,116 @@
|
||||
import {
|
||||
CSSResult,
|
||||
customElement,
|
||||
html,
|
||||
LitElement,
|
||||
property,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
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-settings-row";
|
||||
import "../../../../src/components/ha-svg-icon";
|
||||
import { extractApiErrorMessage } from "../../../../src/data/hassio/common";
|
||||
import { setCheckOptions } from "../../../../src/data/hassio/resolution";
|
||||
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 { SystemChecksParams } from "./show-dialog-system-checks";
|
||||
|
||||
@customElement("dialog-hassio-system-checks")
|
||||
class HassioSystemChecksDialog extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property({ attribute: false }) public supervisor?: Supervisor;
|
||||
|
||||
protected render(): TemplateResult {
|
||||
if (!this.supervisor) {
|
||||
return html``;
|
||||
}
|
||||
|
||||
return html`
|
||||
<ha-dialog
|
||||
@closing=${this.closeDialog}
|
||||
.heading=${createCloseHeading(
|
||||
this.hass,
|
||||
this.supervisor.localize("dialog.system_check.title")
|
||||
)}
|
||||
hideActions
|
||||
open
|
||||
>
|
||||
<div class="form">
|
||||
${this.supervisor.resolution.checks.map(
|
||||
(check) => html`
|
||||
<ha-settings-row three-line>
|
||||
<span slot="heading">
|
||||
${this.supervisor!.localize(
|
||||
`dialog.system_check.check.${check.slug}.title`
|
||||
) || check.slug}
|
||||
</span>
|
||||
<span slot="description">
|
||||
${this.supervisor!.localize(
|
||||
`dialog.system_check.check.${check.slug}.description`
|
||||
)}
|
||||
</span>
|
||||
<ha-switch
|
||||
.slug=${check.slug}
|
||||
@change=${this._checkToggled}
|
||||
.checked=${check.enabled}
|
||||
haptic
|
||||
></ha-switch>
|
||||
</ha-settings-row>
|
||||
`
|
||||
)}
|
||||
</div>
|
||||
</ha-dialog>
|
||||
`;
|
||||
}
|
||||
|
||||
public async showDialog(dialogParams: SystemChecksParams): Promise<void> {
|
||||
this.supervisor = dialogParams.supervisor;
|
||||
await this.updateComplete;
|
||||
}
|
||||
|
||||
public closeDialog(): void {
|
||||
this.supervisor = undefined;
|
||||
fireEvent(this, "dialog-closed", { dialog: this.localName });
|
||||
}
|
||||
|
||||
public focus(): void {
|
||||
this.updateComplete.then(() =>
|
||||
(this.shadowRoot?.querySelector(
|
||||
"[dialogInitialFocus]"
|
||||
) as HTMLElement)?.focus()
|
||||
);
|
||||
}
|
||||
|
||||
private async _checkToggled(ev: Event): Promise<void> {
|
||||
const check = ev.currentTarget as any;
|
||||
|
||||
try {
|
||||
await setCheckOptions(this.hass, check.slug, { enabled: check.checked });
|
||||
fireEvent(this, "supervisor-collection-refresh", {
|
||||
collection: "resolution",
|
||||
});
|
||||
} catch (err) {
|
||||
showAlertDialog(this, {
|
||||
title: this.supervisor!.localize(
|
||||
"dialog.system_check.failed_to_set_option"
|
||||
),
|
||||
text: extractApiErrorMessage(err),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
static get styles(): CSSResult[] {
|
||||
return [haStyle, haStyleDialog];
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"dialog-hassio-system-checks": HassioSystemChecksDialog;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
import { fireEvent } from "../../../../src/common/dom/fire_event";
|
||||
import { Supervisor } from "../../../../src/data/supervisor/supervisor";
|
||||
import "./dialog-hassio-system-checks";
|
||||
|
||||
export interface SystemChecksParams {
|
||||
supervisor: Supervisor;
|
||||
}
|
||||
|
||||
export const showSystemChecksDialog = (
|
||||
element: HTMLElement,
|
||||
dialogParams: SystemChecksParams
|
||||
): void => {
|
||||
fireEvent(element, "show-dialog", {
|
||||
dialogTag: "dialog-hassio-system-checks",
|
||||
dialogImport: () => import("./dialog-hassio-system-checks"),
|
||||
dialogParams,
|
||||
});
|
||||
};
|
||||
+60
-42
@@ -6,7 +6,6 @@ import {
|
||||
html,
|
||||
internalProperty,
|
||||
LitElement,
|
||||
property,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import { fireEvent } from "../../../../src/common/dom/fire_event";
|
||||
@@ -16,24 +15,18 @@ import "../../../../src/components/ha-settings-row";
|
||||
import "../../../../src/components/ha-svg-icon";
|
||||
import "../../../../src/components/ha-switch";
|
||||
import {
|
||||
HassioAddonDetails,
|
||||
updateHassioAddon,
|
||||
} from "../../../../src/data/hassio/addon";
|
||||
import { extractApiErrorMessage } from "../../../../src/data/hassio/common";
|
||||
import { Supervisor } from "../../../../src/data/supervisor/supervisor";
|
||||
extractApiErrorMessage,
|
||||
ignoreSupervisorError,
|
||||
} from "../../../../src/data/hassio/common";
|
||||
import { createHassioPartialSnapshot } from "../../../../src/data/hassio/snapshot";
|
||||
import { haStyle, haStyleDialog } from "../../../../src/resources/styles";
|
||||
import type { HomeAssistant } from "../../../../src/types";
|
||||
import { SupervisorDialogSupervisorAddonUpdateParams } from "./show-dialog-addon-update";
|
||||
|
||||
@customElement("dialog-supervisor-addon-update")
|
||||
class DialogSupervisorAddonUpdate extends LitElement {
|
||||
@property({ attribute: false }) public supervisor!: Supervisor;
|
||||
import { SupervisorDialogSupervisorUpdateParams } from "./show-dialog-update";
|
||||
|
||||
@customElement("dialog-supervisor-update")
|
||||
class DialogSupervisorUpdate extends LitElement {
|
||||
public hass!: HomeAssistant;
|
||||
|
||||
public addon!: HassioAddonDetails;
|
||||
|
||||
@internalProperty() private _opened = false;
|
||||
|
||||
@internalProperty() private _createSnapshot = true;
|
||||
@@ -42,20 +35,22 @@ class DialogSupervisorAddonUpdate extends LitElement {
|
||||
|
||||
@internalProperty() private _error?: string;
|
||||
|
||||
@internalProperty()
|
||||
private _dialogParams?: SupervisorDialogSupervisorUpdateParams;
|
||||
|
||||
public async showDialog(
|
||||
params: SupervisorDialogSupervisorAddonUpdateParams
|
||||
params: SupervisorDialogSupervisorUpdateParams
|
||||
): Promise<void> {
|
||||
this._opened = true;
|
||||
this.addon = params.addon;
|
||||
this.supervisor = params.supervisor;
|
||||
this._dialogParams = params;
|
||||
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 });
|
||||
}
|
||||
|
||||
@@ -68,52 +63,77 @@ class DialogSupervisorAddonUpdate 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">
|
||||
Update ${this.addon.name}
|
||||
${this._dialogParams.supervisor.localize(
|
||||
"confirm.update.title",
|
||||
"name",
|
||||
this._dialogParams.name
|
||||
)}
|
||||
</h2>
|
||||
</slot>
|
||||
<div>
|
||||
Are you sure you want to update the ${this.addon.name} add-on to
|
||||
version ${this.addon.version_latest}?
|
||||
${this._dialogParams.supervisor.localize(
|
||||
"confirm.update.text",
|
||||
"name",
|
||||
this._dialogParams.name,
|
||||
"version",
|
||||
this._dialogParams.version
|
||||
)}
|
||||
</div>
|
||||
|
||||
<ha-settings-row>
|
||||
<span slot="heading">
|
||||
Snapshot
|
||||
${this._dialogParams.supervisor.localize(
|
||||
"dialog.update.snapshot"
|
||||
)}
|
||||
</span>
|
||||
<span slot="description">
|
||||
Create a snapshot of the ${this.addon.name} add-on before
|
||||
updating
|
||||
${this._dialogParams.supervisor.localize(
|
||||
"dialog.update.create_snapshot",
|
||||
"name",
|
||||
this._dialogParams.name
|
||||
)}
|
||||
</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
|
||||
${this._dialogParams.supervisor.localize("common.cancel")}
|
||||
</mwc-button>
|
||||
<mwc-button
|
||||
.disabled=${this._error !== undefined ||
|
||||
this.supervisor.info.state !== "running"}
|
||||
.disabled=${this._error !== undefined}
|
||||
@click=${this._update}
|
||||
slot="primaryAction"
|
||||
>
|
||||
Update
|
||||
${this._dialogParams.supervisor.localize("common.update")}
|
||||
</mwc-button>`
|
||||
: html`<ha-circular-progress alt="Updating" size="large" active>
|
||||
</ha-circular-progress>
|
||||
<p class="progress-text">
|
||||
${this._action === "update"
|
||||
? `Updating ${this.addon.name} to version ${this.addon.version_latest}`
|
||||
: "Creating snapshot of Home Assistant Core"}
|
||||
? 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
|
||||
)}
|
||||
</p>`}
|
||||
${this._error ? html`<p class="error">${this._error}</p>` : ""}
|
||||
</ha-dialog>
|
||||
@@ -128,11 +148,10 @@ class DialogSupervisorAddonUpdate extends LitElement {
|
||||
if (this._createSnapshot) {
|
||||
this._action = "snapshot";
|
||||
try {
|
||||
await createHassioPartialSnapshot(this.hass, {
|
||||
name: `addon_${this.addon.slug}_${this.addon.version}`,
|
||||
addons: [this.addon.slug],
|
||||
homeassistant: false,
|
||||
});
|
||||
await createHassioPartialSnapshot(
|
||||
this.hass,
|
||||
this._dialogParams!.snapshotParams
|
||||
);
|
||||
} catch (err) {
|
||||
this._error = extractApiErrorMessage(err);
|
||||
this._action = null;
|
||||
@@ -142,16 +161,15 @@ class DialogSupervisorAddonUpdate extends LitElement {
|
||||
|
||||
this._action = "update";
|
||||
try {
|
||||
await updateHassioAddon(this.hass, this.addon.slug);
|
||||
await this._dialogParams!.updateHandler!();
|
||||
} catch (err) {
|
||||
this._error = extractApiErrorMessage(err);
|
||||
if (this.hass.connection.connected && !ignoreSupervisorError(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();
|
||||
}
|
||||
|
||||
@@ -185,6 +203,6 @@ class DialogSupervisorAddonUpdate extends LitElement {
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"dialog-supervisor-addon-update": DialogSupervisorAddonUpdate;
|
||||
"dialog-supervisor-update": DialogSupervisorUpdate;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
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,
|
||||
});
|
||||
};
|
||||
@@ -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 { supervisorCollection } from "../../src/data/supervisor/supervisor";
|
||||
import { Supervisor } 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,6 +14,8 @@ 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;
|
||||
@@ -72,18 +74,6 @@ 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}
|
||||
|
||||
@@ -19,8 +19,12 @@ 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",
|
||||
},
|
||||
@@ -33,22 +37,27 @@ 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() public route!: Route;
|
||||
@property({ attribute: false }) public supervisor!: Supervisor;
|
||||
|
||||
@property({ attribute: false }) public route!: Route;
|
||||
|
||||
@internalProperty() public _error?: TemplateResult | string;
|
||||
|
||||
@@ -58,15 +67,17 @@ class HassioMyRedirect extends LitElement {
|
||||
const redirect = REDIRECTS[path];
|
||||
|
||||
if (!redirect) {
|
||||
this._error = html`This redirect is not supported by your Home Assistant
|
||||
instance. Check the
|
||||
<a
|
||||
this._error = this.supervisor.localize(
|
||||
"my.not_supported",
|
||||
"link",
|
||||
html`<a
|
||||
target="_blank"
|
||||
rel="noreferrer noopener"
|
||||
href="https://my.home-assistant.io/faq.html#supported-pages"
|
||||
>My Home Assistant FAQ</a
|
||||
>
|
||||
for the supported redirects and the version they where introduced.`;
|
||||
${this.supervisor.localize("my.faq_link")}
|
||||
</a>`
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -74,7 +85,7 @@ class HassioMyRedirect extends LitElement {
|
||||
try {
|
||||
url = this._createRedirectUrl(redirect);
|
||||
} catch (err) {
|
||||
this._error = "An unknown error occured";
|
||||
this._error = this.supervisor.localize("my.error");
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,10 @@ import {
|
||||
property,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import { Supervisor } from "../../src/data/supervisor/supervisor";
|
||||
import {
|
||||
Supervisor,
|
||||
supervisorCollection,
|
||||
} from "../../src/data/supervisor/supervisor";
|
||||
import { HomeAssistant, Route } from "../../src/types";
|
||||
import "./hassio-panel-router";
|
||||
|
||||
@@ -22,6 +25,17 @@ 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}
|
||||
|
||||
@@ -3,22 +3,22 @@ import type { PageNavigation } from "../../src/layouts/hass-tabs-subpage";
|
||||
|
||||
export const supervisorTabs: PageNavigation[] = [
|
||||
{
|
||||
name: "Dashboard",
|
||||
translationKey: "panel.dashboard",
|
||||
path: `/hassio/dashboard`,
|
||||
iconPath: mdiViewDashboard,
|
||||
},
|
||||
{
|
||||
name: "Add-on Store",
|
||||
translationKey: "panel.store",
|
||||
path: `/hassio/store`,
|
||||
iconPath: mdiStore,
|
||||
},
|
||||
{
|
||||
name: "Snapshots",
|
||||
translationKey: "panel.snapshots",
|
||||
path: `/hassio/snapshots`,
|
||||
iconPath: mdiBackupRestore,
|
||||
},
|
||||
{
|
||||
name: "System",
|
||||
translationKey: "panel.system",
|
||||
path: `/hassio/system`,
|
||||
iconPath: mdiCogs,
|
||||
},
|
||||
|
||||
@@ -55,8 +55,8 @@ import { hassioStyle } from "../resources/hassio-style";
|
||||
|
||||
interface CheckboxItem {
|
||||
slug: string;
|
||||
name: string;
|
||||
checked: boolean;
|
||||
name?: string;
|
||||
}
|
||||
|
||||
@customElement("hassio-snapshots")
|
||||
@@ -84,13 +84,12 @@ class HassioSnapshots extends LitElement {
|
||||
@internalProperty() private _folderList: CheckboxItem[] = [
|
||||
{
|
||||
slug: "homeassistant",
|
||||
name: "Home Assistant configuration",
|
||||
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 },
|
||||
{ slug: "ssl", checked: true },
|
||||
{ slug: "share", checked: true },
|
||||
{ slug: "media", checked: true },
|
||||
{ slug: "addons/local", checked: true },
|
||||
];
|
||||
|
||||
@internalProperty() private _error = "";
|
||||
@@ -104,13 +103,16 @@ 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">Snapshots</span>
|
||||
<span slot="header">
|
||||
${this.supervisor.localize("panel.snapshots")}
|
||||
</span>
|
||||
<ha-button-menu
|
||||
corner="BOTTOM_START"
|
||||
slot="toolbar-icon"
|
||||
@@ -120,50 +122,50 @@ class HassioSnapshots extends LitElement {
|
||||
<ha-svg-icon .path=${mdiDotsVertical}></ha-svg-icon>
|
||||
</mwc-icon-button>
|
||||
<mwc-list-item>
|
||||
Reload
|
||||
${this.supervisor.localize("common.reload")}
|
||||
</mwc-list-item>
|
||||
${atLeastVersion(this.hass.config.version, 0, 116)
|
||||
? html`<mwc-list-item>
|
||||
Upload snapshot
|
||||
${this.supervisor.localize("snapshot.upload_snapshot")}
|
||||
</mwc-list-item>`
|
||||
: ""}
|
||||
</ha-button-menu>
|
||||
|
||||
<div class="content">
|
||||
<h1>
|
||||
Create Snapshot
|
||||
${this.supervisor.localize("snapshot.create_snapshot")}
|
||||
</h1>
|
||||
<p class="description">
|
||||
Snapshots allow you to easily backup and restore all data of your
|
||||
Home Assistant instance.
|
||||
${this.supervisor.localize("snapshot.description")}
|
||||
</p>
|
||||
<div class="card-group">
|
||||
<ha-card>
|
||||
<div class="card-content">
|
||||
<paper-input
|
||||
autofocus
|
||||
label="Name"
|
||||
.label=${this.supervisor.localize("snapshot.name")}
|
||||
name="snapshotName"
|
||||
.value=${this._snapshotName}
|
||||
@value-changed=${this._handleTextValueChanged}
|
||||
></paper-input>
|
||||
Type:
|
||||
${this.supervisor.localize("snapshot.type")}:
|
||||
<paper-radio-group
|
||||
name="snapshotType"
|
||||
type="${this.supervisor.localize("snapshot.type")}"
|
||||
.selected=${this._snapshotType}
|
||||
@selected-changed=${this._handleRadioValueChanged}
|
||||
>
|
||||
<paper-radio-button name="full">
|
||||
Full snapshot
|
||||
${this.supervisor.localize("snapshot.full_snapshot")}
|
||||
</paper-radio-button>
|
||||
<paper-radio-button name="partial">
|
||||
Partial snapshot
|
||||
${this.supervisor.localize("snapshot.partial_snapshot")}
|
||||
</paper-radio-button>
|
||||
</paper-radio-group>
|
||||
${this._snapshotType === "full"
|
||||
? undefined
|
||||
: html`
|
||||
Folders:
|
||||
${this.supervisor.localize("snapshot.folders")}:
|
||||
${this._folderList.map(
|
||||
(folder, idx) => html`
|
||||
<paper-checkbox
|
||||
@@ -171,11 +173,13 @@ class HassioSnapshots extends LitElement {
|
||||
.checked=${folder.checked}
|
||||
@checked-changed=${this._folderChecked}
|
||||
>
|
||||
${folder.name}
|
||||
${this.supervisor.localize(
|
||||
`snapshot.folder.${folder.slug}`
|
||||
)}
|
||||
</paper-checkbox>
|
||||
`
|
||||
)}
|
||||
Add-ons:
|
||||
${this.supervisor.localize("snapshot.addons")}:
|
||||
${this._addonList.map(
|
||||
(addon, idx) => html`
|
||||
<paper-checkbox
|
||||
@@ -188,18 +192,18 @@ class HassioSnapshots extends LitElement {
|
||||
`
|
||||
)}
|
||||
`}
|
||||
Security:
|
||||
${this.supervisor.localize("snapshot.security")}:
|
||||
<paper-checkbox
|
||||
name="snapshotHasPassword"
|
||||
.checked=${this._snapshotHasPassword}
|
||||
@checked-changed=${this._handleCheckboxValueChanged}
|
||||
>
|
||||
Password protection
|
||||
${this.supervisor.localize("snapshot.password_protection")}
|
||||
</paper-checkbox>
|
||||
${this._snapshotHasPassword
|
||||
? html`
|
||||
<paper-input
|
||||
label="Password"
|
||||
.label=${this.supervisor.localize("snapshot.password")}
|
||||
type="password"
|
||||
name="snapshotPassword"
|
||||
.value=${this._snapshotPassword}
|
||||
@@ -214,18 +218,22 @@ class HassioSnapshots extends LitElement {
|
||||
<div class="card-actions">
|
||||
<ha-progress-button
|
||||
@click=${this._createSnapshot}
|
||||
title="${this.supervisor.info.state !== "running"
|
||||
? `Creating a snapshot is not possible right now because the system is in ${this.supervisor.info.state} state.`
|
||||
: ""}"
|
||||
.title=${this.supervisor.info.state !== "running"
|
||||
? this.supervisor.localize(
|
||||
"snapshot.create_blocked_not_running",
|
||||
"state",
|
||||
this.supervisor.info.state
|
||||
)
|
||||
: ""}
|
||||
.disabled=${this.supervisor.info.state !== "running"}
|
||||
>
|
||||
Create
|
||||
${this.supervisor.localize("snapshot.create")}
|
||||
</ha-progress-button>
|
||||
</div>
|
||||
</ha-card>
|
||||
</div>
|
||||
|
||||
<h1>Available Snapshots</h1>
|
||||
<h1>${this.supervisor.localize("snapshot.available_snapshots")}</h1>
|
||||
<div class="card-group">
|
||||
${this._snapshots === undefined
|
||||
? undefined
|
||||
@@ -233,7 +241,7 @@ class HassioSnapshots extends LitElement {
|
||||
? html`
|
||||
<ha-card>
|
||||
<div class="card-content">
|
||||
You don't have any snapshots yet.
|
||||
${this.supervisor.localize("snapshot.no_snapshots")}
|
||||
</div>
|
||||
</ha-card>
|
||||
`
|
||||
@@ -334,8 +342,12 @@ class HassioSnapshots extends LitElement {
|
||||
private async _createSnapshot(ev: CustomEvent): Promise<void> {
|
||||
if (this.supervisor.info.state !== "running") {
|
||||
await showAlertDialog(this, {
|
||||
title: "Could not create snapshot",
|
||||
text: `Creating a snapshot is not possible right now because the system is in ${this.supervisor.info.state} state.`,
|
||||
title: this.supervisor.localize("snapshot.could_not_create"),
|
||||
text: this.supervisor.localize(
|
||||
"snapshot.create_blocked_not_running",
|
||||
"state",
|
||||
this.supervisor.info.state
|
||||
),
|
||||
});
|
||||
}
|
||||
const button = ev.currentTarget as any;
|
||||
@@ -343,7 +355,7 @@ class HassioSnapshots extends LitElement {
|
||||
|
||||
this._error = "";
|
||||
if (this._snapshotHasPassword && !this._snapshotPassword.length) {
|
||||
this._error = "Please enter a password.";
|
||||
this._error = this.supervisor.localize("snapshot.enter_password");
|
||||
button.progress = false;
|
||||
return;
|
||||
}
|
||||
@@ -392,7 +404,9 @@ class HassioSnapshots extends LitElement {
|
||||
|
||||
private _computeDetails(snapshot: HassioSnapshot) {
|
||||
const type =
|
||||
snapshot.type === "full" ? "Full snapshot" : "Partial snapshot";
|
||||
snapshot.type === "full"
|
||||
? this.supervisor.localize("snapshot.full_snapshot")
|
||||
: this.supervisor.localize("snapshot.partial_snapshot");
|
||||
return snapshot.protected ? `${type}, password protected` : type;
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ 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 {
|
||||
@@ -22,25 +23,28 @@ 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-colllection-refresh": { colllection: SupervisorObject };
|
||||
"supervisor-collection-refresh": { collection: SupervisorObject };
|
||||
}
|
||||
}
|
||||
|
||||
export class SupervisorBaseElement extends urlSyncMixin(
|
||||
ProvideHassLitMixin(LitElement)
|
||||
) {
|
||||
@property({ attribute: false }) public supervisor?: Supervisor;
|
||||
@property({ attribute: false }) public supervisor: Partial<Supervisor> = {
|
||||
localize: () => "",
|
||||
};
|
||||
|
||||
@internalProperty() private _unsubs: Record<string, UnsubscribeFunc> = {};
|
||||
|
||||
@@ -49,6 +53,13 @@ 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) => {
|
||||
@@ -56,100 +67,155 @@ 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 colllection = ev.detail.colllection;
|
||||
const collection = ev.detail.collection;
|
||||
if (atLeastVersion(this.hass.config.version, 2021, 2, 4)) {
|
||||
this._collections[colllection].refresh();
|
||||
this._collections[collection].refresh();
|
||||
return;
|
||||
}
|
||||
|
||||
const response = await this.hass.callApi<HassioResponse<any>>(
|
||||
"GET",
|
||||
`hassio${supervisorCollection[colllection]}`
|
||||
`hassio${supervisorCollection[collection]}`
|
||||
);
|
||||
this._updateSupervisor({ [colllection]: response.data });
|
||||
this._updateSupervisor({ [collection]: response.data });
|
||||
}
|
||||
|
||||
private async _initSupervisor(): Promise<void> {
|
||||
this.addEventListener(
|
||||
"supervisor-colllection-refresh",
|
||||
"supervisor-collection-refresh",
|
||||
this._handleSupervisorStoreRefreshEvent
|
||||
);
|
||||
|
||||
if (atLeastVersion(this.hass.config.version, 2021, 2, 4)) {
|
||||
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();
|
||||
Object.keys(supervisorCollection).forEach((collection) => {
|
||||
if (collection in this._collections) {
|
||||
this._collections[collection].refresh();
|
||||
} else {
|
||||
this._collections[colllection] = getSupervisorEventCollection(
|
||||
this._collections[collection] = getSupervisorEventCollection(
|
||||
this.hass.connection,
|
||||
colllection,
|
||||
supervisorCollection[colllection]
|
||||
collection,
|
||||
supervisorCollection[collection]
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
if (this.supervisor === undefined) {
|
||||
Object.keys(this._collections).forEach((collection) =>
|
||||
Object.keys(this._collections).forEach((collection) => {
|
||||
if (
|
||||
this.supervisor === undefined ||
|
||||
this.supervisor[collection] === undefined
|
||||
) {
|
||||
this._updateSupervisor({
|
||||
[collection]: this._collections[collection].state,
|
||||
})
|
||||
);
|
||||
}
|
||||
return;
|
||||
});
|
||||
}
|
||||
});
|
||||
} 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)
|
||||
);
|
||||
}
|
||||
|
||||
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)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
property,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import { fireEvent } from "../../../src/common/dom/fire_event";
|
||||
import "../../../src/components/buttons/ha-progress-button";
|
||||
import "../../../src/components/ha-button-menu";
|
||||
import "../../../src/components/ha-card";
|
||||
@@ -19,7 +20,7 @@ import {
|
||||
fetchHassioStats,
|
||||
HassioStats,
|
||||
} from "../../../src/data/hassio/common";
|
||||
import { restartCore } from "../../../src/data/supervisor/core";
|
||||
import { restartCore, updateCore } from "../../../src/data/supervisor/core";
|
||||
import { Supervisor } from "../../../src/data/supervisor/supervisor";
|
||||
import {
|
||||
showAlertDialog,
|
||||
@@ -29,7 +30,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 { showDialogSupervisorCoreUpdate } from "../dialogs/core/show-dialog-core-update";
|
||||
import { showDialogSupervisorUpdate } from "../dialogs/update/show-dialog-update";
|
||||
import { hassioStyle } from "../resources/hassio-style";
|
||||
|
||||
@customElement("hassio-core-info")
|
||||
@@ -43,11 +44,11 @@ class HassioCoreInfo extends LitElement {
|
||||
protected render(): TemplateResult | void {
|
||||
const metrics = [
|
||||
{
|
||||
description: "Core CPU Usage",
|
||||
description: this.supervisor.localize("system.core.cpu_usage"),
|
||||
value: this._metrics?.cpu_percent,
|
||||
},
|
||||
{
|
||||
description: "Core RAM Usage",
|
||||
description: this.supervisor.localize("system.core.ram_usage"),
|
||||
value: this._metrics?.memory_percent,
|
||||
tooltip: `${bytesToString(this._metrics?.memory_usage)}/${bytesToString(
|
||||
this._metrics?.memory_limit
|
||||
@@ -61,7 +62,7 @@ class HassioCoreInfo extends LitElement {
|
||||
<div>
|
||||
<ha-settings-row>
|
||||
<span slot="heading">
|
||||
Version
|
||||
${this.supervisor.localize("common.version")}
|
||||
</span>
|
||||
<span slot="description">
|
||||
core-${this.supervisor.core.version}
|
||||
@@ -69,7 +70,7 @@ class HassioCoreInfo extends LitElement {
|
||||
</ha-settings-row>
|
||||
<ha-settings-row>
|
||||
<span slot="heading">
|
||||
Newest Version
|
||||
${this.supervisor.localize("common.newest_version")}
|
||||
</span>
|
||||
<span slot="description">
|
||||
core-${this.supervisor.core.version_latest}
|
||||
@@ -77,10 +78,10 @@ class HassioCoreInfo extends LitElement {
|
||||
${this.supervisor.core.update_available
|
||||
? html`
|
||||
<ha-progress-button
|
||||
title="Update the core"
|
||||
.title=${this.supervisor.localize("common.update")}
|
||||
@click=${this._coreUpdate}
|
||||
>
|
||||
Update
|
||||
${this.supervisor.localize("common.update")}
|
||||
</ha-progress-button>
|
||||
`
|
||||
: ""}
|
||||
@@ -104,9 +105,13 @@ class HassioCoreInfo extends LitElement {
|
||||
slot="primaryAction"
|
||||
class="warning"
|
||||
@click=${this._coreRestart}
|
||||
title="Restart Home Assistant Core"
|
||||
.title=${this.supervisor.localize(
|
||||
"common.restart_name",
|
||||
"name",
|
||||
"Core"
|
||||
)}
|
||||
>
|
||||
Restart Core
|
||||
${this.supervisor.localize("common.restart_name", "name", "Core")}
|
||||
</ha-progress-button>
|
||||
</div>
|
||||
</ha-card>
|
||||
@@ -126,10 +131,18 @@ class HassioCoreInfo extends LitElement {
|
||||
button.progress = true;
|
||||
|
||||
const confirmed = await showConfirmationDialog(this, {
|
||||
title: "Restart Home Assistant Core",
|
||||
text: "Are you sure you want to restart Home Assistant Core",
|
||||
confirmText: "restart",
|
||||
dismissText: "cancel",
|
||||
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"),
|
||||
});
|
||||
|
||||
if (!confirmed) {
|
||||
@@ -142,7 +155,11 @@ class HassioCoreInfo extends LitElement {
|
||||
} catch (err) {
|
||||
if (this.hass.connection.connected) {
|
||||
showAlertDialog(this, {
|
||||
title: "Failed to restart Home Assistant Core",
|
||||
title: this.supervisor.localize(
|
||||
"common.failed_to_restart_name",
|
||||
"name",
|
||||
"Home AssistantCore"
|
||||
),
|
||||
text: extractApiErrorMessage(err),
|
||||
});
|
||||
}
|
||||
@@ -152,7 +169,24 @@ class HassioCoreInfo extends LitElement {
|
||||
}
|
||||
|
||||
private async _coreUpdate(): Promise<void> {
|
||||
showDialogSupervisorCoreUpdate(this, { supervisor: this.supervisor });
|
||||
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",
|
||||
});
|
||||
}
|
||||
|
||||
static get styles(): CSSResult[] {
|
||||
|
||||
@@ -21,7 +21,7 @@ import "../../../src/components/ha-card";
|
||||
import "../../../src/components/ha-settings-row";
|
||||
import {
|
||||
extractApiErrorMessage,
|
||||
ignoredStatusCodes,
|
||||
ignoreSupervisorError,
|
||||
} from "../../../src/data/hassio/common";
|
||||
import { fetchHassioHardwareInfo } from "../../../src/data/hassio/hardware";
|
||||
import {
|
||||
@@ -65,7 +65,7 @@ class HassioHostInfo extends LitElement {
|
||||
|
||||
const metrics = [
|
||||
{
|
||||
description: "Used Space",
|
||||
description: this.supervisor.localize("system.host.used_space"),
|
||||
value: this._getUsedSpace(
|
||||
this.supervisor.host.disk_used,
|
||||
this.supervisor.host.disk_total
|
||||
@@ -80,14 +80,13 @@ class HassioHostInfo extends LitElement {
|
||||
${this.supervisor.host.features.includes("hostname")
|
||||
? html`<ha-settings-row>
|
||||
<span slot="heading">
|
||||
Hostname
|
||||
${this.supervisor.localize("system.host.hostname")}
|
||||
</span>
|
||||
<span slot="description">
|
||||
${this.supervisor.host.hostname}
|
||||
</span>
|
||||
<mwc-button
|
||||
title="Change the hostname"
|
||||
label="Change"
|
||||
.label=${this.supervisor.localize("system.host.change")}
|
||||
@click=${this._changeHostnameClicked}
|
||||
>
|
||||
</mwc-button>
|
||||
@@ -96,14 +95,13 @@ class HassioHostInfo extends LitElement {
|
||||
${this.supervisor.host.features.includes("network")
|
||||
? html` <ha-settings-row>
|
||||
<span slot="heading">
|
||||
IP Address
|
||||
${this.supervisor.localize("system.host.ip_address")}
|
||||
</span>
|
||||
<span slot="description">
|
||||
${primaryIpAddress}
|
||||
</span>
|
||||
<mwc-button
|
||||
title="Change the network"
|
||||
label="Change"
|
||||
.label=${this.supervisor.localize("system.host.change")}
|
||||
@click=${this._changeNetworkClicked}
|
||||
>
|
||||
</mwc-button>
|
||||
@@ -112,18 +110,15 @@ class HassioHostInfo extends LitElement {
|
||||
|
||||
<ha-settings-row>
|
||||
<span slot="heading">
|
||||
Operating System
|
||||
${this.supervisor.localize("system.host.operating_system")}
|
||||
</span>
|
||||
<span slot="description">
|
||||
${this.supervisor.host.operating_system}
|
||||
</span>
|
||||
${this.supervisor.os.update_available
|
||||
? html`
|
||||
<ha-progress-button
|
||||
title="Update the host OS"
|
||||
@click=${this._osUpdate}
|
||||
>
|
||||
Update
|
||||
<ha-progress-button @click=${this._osUpdate}>
|
||||
${this.supervisor.localize("commmon.update")}
|
||||
</ha-progress-button>
|
||||
`
|
||||
: ""}
|
||||
@@ -131,7 +126,7 @@ class HassioHostInfo extends LitElement {
|
||||
${!this.supervisor.host.features.includes("hassos")
|
||||
? html`<ha-settings-row>
|
||||
<span slot="heading">
|
||||
Docker version
|
||||
${this.supervisor.localize("system.host.docker_version")}
|
||||
</span>
|
||||
<span slot="description">
|
||||
${this.supervisor.info.docker}
|
||||
@@ -141,7 +136,7 @@ class HassioHostInfo extends LitElement {
|
||||
${this.supervisor.host.deployment
|
||||
? html`<ha-settings-row>
|
||||
<span slot="heading">
|
||||
Deployment
|
||||
${this.supervisor.localize("system.host.deployment")}
|
||||
</span>
|
||||
<span slot="description">
|
||||
${this.supervisor.host.deployment}
|
||||
@@ -154,11 +149,13 @@ class HassioHostInfo extends LitElement {
|
||||
this.supervisor.host.disk_life_time >= 10
|
||||
? html` <ha-settings-row>
|
||||
<span slot="heading">
|
||||
eMMC Lifetime Used
|
||||
${this.supervisor.localize(
|
||||
"system.host.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>`
|
||||
: ""}
|
||||
@@ -177,23 +174,18 @@ class HassioHostInfo extends LitElement {
|
||||
<div class="card-actions">
|
||||
${this.supervisor.host.features.includes("reboot")
|
||||
? html`
|
||||
<ha-progress-button
|
||||
title="Reboot the host OS"
|
||||
class="warning"
|
||||
@click=${this._hostReboot}
|
||||
>
|
||||
Reboot Host
|
||||
<ha-progress-button class="warning" @click=${this._hostReboot}>
|
||||
${this.supervisor.localize("system.host.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}
|
||||
>
|
||||
Shutdown Host
|
||||
${this.supervisor.localize("system.host.shutdown_host")}
|
||||
</ha-progress-button>
|
||||
`
|
||||
: ""}
|
||||
@@ -205,14 +197,12 @@ class HassioHostInfo extends LitElement {
|
||||
<mwc-icon-button slot="trigger">
|
||||
<ha-svg-icon .path=${mdiDotsVertical}></ha-svg-icon>
|
||||
</mwc-icon-button>
|
||||
<mwc-list-item title="Show a list of hardware">
|
||||
Hardware
|
||||
<mwc-list-item>
|
||||
${this.supervisor.localize("system.host.hardware")}
|
||||
</mwc-list-item>
|
||||
${this.supervisor.host.features.includes("hassos")
|
||||
? html`<mwc-list-item
|
||||
title="Load HassOS configs or updates from USB"
|
||||
>
|
||||
Import from USB
|
||||
? html`<mwc-list-item>
|
||||
${this.supervisor.localize("system.host.import_from_usb")}
|
||||
</mwc-list-item>`
|
||||
: ""}
|
||||
</ha-button-menu>
|
||||
@@ -251,12 +241,14 @@ class HassioHostInfo extends LitElement {
|
||||
try {
|
||||
const content = await fetchHassioHardwareInfo(this.hass);
|
||||
showHassioMarkdownDialog(this, {
|
||||
title: "Hardware",
|
||||
title: this.supervisor.localize("system.host.hardware"),
|
||||
content: `<pre>${safeDump(content, { indent: 2 })}</pre>`,
|
||||
});
|
||||
} catch (err) {
|
||||
showAlertDialog(this, {
|
||||
title: "Failed to get hardware list",
|
||||
title: this.supervisor.localize(
|
||||
"system.host.failed_to_get_hardware_list"
|
||||
),
|
||||
text: extractApiErrorMessage(err),
|
||||
});
|
||||
}
|
||||
@@ -267,10 +259,10 @@ class HassioHostInfo extends LitElement {
|
||||
button.progress = true;
|
||||
|
||||
const confirmed = await showConfirmationDialog(this, {
|
||||
title: "Reboot",
|
||||
text: "Are you sure you want to reboot the host?",
|
||||
confirmText: "reboot host",
|
||||
dismissText: "no",
|
||||
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"),
|
||||
});
|
||||
|
||||
if (!confirmed) {
|
||||
@@ -282,9 +274,9 @@ class HassioHostInfo extends LitElement {
|
||||
await rebootHost(this.hass);
|
||||
} catch (err) {
|
||||
// Ignore connection errors, these are all expected
|
||||
if (err.status_code && !ignoredStatusCodes.has(err.status_code)) {
|
||||
if (this.hass.connection.connected && !ignoreSupervisorError(err)) {
|
||||
showAlertDialog(this, {
|
||||
title: "Failed to reboot",
|
||||
title: this.supervisor.localize("system.host.failed_to_reboot"),
|
||||
text: extractApiErrorMessage(err),
|
||||
});
|
||||
}
|
||||
@@ -297,10 +289,10 @@ class HassioHostInfo extends LitElement {
|
||||
button.progress = true;
|
||||
|
||||
const confirmed = await showConfirmationDialog(this, {
|
||||
title: "Shutdown",
|
||||
text: "Are you sure you want to shutdown the host?",
|
||||
confirmText: "shutdown host",
|
||||
dismissText: "no",
|
||||
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"),
|
||||
});
|
||||
|
||||
if (!confirmed) {
|
||||
@@ -312,9 +304,9 @@ class HassioHostInfo extends LitElement {
|
||||
await shutdownHost(this.hass);
|
||||
} catch (err) {
|
||||
// Ignore connection errors, these are all expected
|
||||
if (err.status_code && !ignoredStatusCodes.has(err.status_code)) {
|
||||
if (this.hass.connection.connected && !ignoreSupervisorError(err)) {
|
||||
showAlertDialog(this, {
|
||||
title: "Failed to shutdown",
|
||||
title: this.supervisor.localize("system.host.failed_to_shutdown"),
|
||||
text: extractApiErrorMessage(err),
|
||||
});
|
||||
}
|
||||
@@ -327,9 +319,19 @@ class HassioHostInfo extends LitElement {
|
||||
button.progress = true;
|
||||
|
||||
const confirmed = await showConfirmationDialog(this, {
|
||||
title: "Update",
|
||||
text: "Are you sure you want to update the OS?",
|
||||
confirmText: "update os",
|
||||
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"),
|
||||
dismissText: "no",
|
||||
});
|
||||
|
||||
@@ -340,11 +342,15 @@ class HassioHostInfo extends LitElement {
|
||||
|
||||
try {
|
||||
await updateOS(this.hass);
|
||||
fireEvent(this, "supervisor-colllection-refresh", { colllection: "os" });
|
||||
fireEvent(this, "supervisor-collection-refresh", { collection: "os" });
|
||||
} catch (err) {
|
||||
if (this.hass.connection.connected) {
|
||||
showAlertDialog(this, {
|
||||
title: "Failed to update",
|
||||
title: this.supervisor.localize(
|
||||
"common.failed_to_update_name",
|
||||
"name",
|
||||
"Home Assistant Operating System"
|
||||
),
|
||||
text: extractApiErrorMessage(err),
|
||||
});
|
||||
}
|
||||
@@ -354,7 +360,7 @@ class HassioHostInfo extends LitElement {
|
||||
|
||||
private async _changeNetworkClicked(): Promise<void> {
|
||||
showNetworkDialog(this, {
|
||||
network: this.supervisor.network!,
|
||||
supervisor: this.supervisor,
|
||||
loadData: () => this._loadData(),
|
||||
});
|
||||
}
|
||||
@@ -362,21 +368,22 @@ class HassioHostInfo extends LitElement {
|
||||
private async _changeHostnameClicked(): Promise<void> {
|
||||
const curHostname: string = this.supervisor.host.hostname;
|
||||
const hostname = await showPromptDialog(this, {
|
||||
title: "Change Hostname",
|
||||
inputLabel: "Please enter a new hostname:",
|
||||
title: this.supervisor.localize("system.host.change_hostname"),
|
||||
inputLabel: this.supervisor.localize("system.host.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-colllection-refresh", {
|
||||
colllection: "host",
|
||||
fireEvent(this, "supervisor-collection-refresh", {
|
||||
collection: "host",
|
||||
});
|
||||
} catch (err) {
|
||||
showAlertDialog(this, {
|
||||
title: "Setting hostname failed",
|
||||
title: this.supervisor.localize("system.host.failed_to_set_hostname"),
|
||||
text: extractApiErrorMessage(err),
|
||||
});
|
||||
}
|
||||
@@ -386,12 +393,14 @@ class HassioHostInfo extends LitElement {
|
||||
private async _importFromUSB(): Promise<void> {
|
||||
try {
|
||||
await configSyncOS(this.hass);
|
||||
fireEvent(this, "supervisor-colllection-refresh", {
|
||||
colllection: "host",
|
||||
fireEvent(this, "supervisor-collection-refresh", {
|
||||
collection: "host",
|
||||
});
|
||||
} catch (err) {
|
||||
showAlertDialog(this, {
|
||||
title: "Failed to import from USB",
|
||||
title: this.supervisor.localize(
|
||||
"system.host.failed_to_import_from_usb"
|
||||
),
|
||||
text: extractApiErrorMessage(err),
|
||||
});
|
||||
}
|
||||
@@ -399,8 +408,8 @@ class HassioHostInfo extends LitElement {
|
||||
|
||||
private async _loadData(): Promise<void> {
|
||||
if (atLeastVersion(this.hass.config.version, 2021, 2, 4)) {
|
||||
fireEvent(this, "supervisor-colllection-refresh", {
|
||||
colllection: "network",
|
||||
fireEvent(this, "supervisor-collection-refresh", {
|
||||
collection: "network",
|
||||
});
|
||||
} else {
|
||||
const network = await fetchNetworkInfo(this.hass);
|
||||
|
||||
@@ -37,54 +37,24 @@ import { documentationUrl } from "../../../src/util/documentation-url";
|
||||
import "../components/supervisor-metric";
|
||||
import { hassioStyle } from "../resources/hassio-style";
|
||||
|
||||
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 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",
|
||||
};
|
||||
|
||||
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",
|
||||
},
|
||||
const UNHEALTHY_REASON_URL = {
|
||||
privileged: "/more-info/unsupported/privileged",
|
||||
supervisor: "/more-info/unhealthy/supervisor",
|
||||
setup: "/more-info/unhealthy/setup",
|
||||
docker: "/more-info/unhealthy/docker",
|
||||
};
|
||||
|
||||
@customElement("hassio-supervisor-info")
|
||||
@@ -98,11 +68,11 @@ class HassioSupervisorInfo extends LitElement {
|
||||
protected render(): TemplateResult | void {
|
||||
const metrics = [
|
||||
{
|
||||
description: "Supervisor CPU Usage",
|
||||
description: this.supervisor.localize("system.supervisor.cpu_usage"),
|
||||
value: this._metrics?.cpu_percent,
|
||||
},
|
||||
{
|
||||
description: "Supervisor RAM Usage",
|
||||
description: this.supervisor.localize("system.supervisor.ram_usage"),
|
||||
value: this._metrics?.memory_percent,
|
||||
tooltip: `${bytesToString(this._metrics?.memory_usage)}/${bytesToString(
|
||||
this._metrics?.memory_limit
|
||||
@@ -115,7 +85,7 @@ class HassioSupervisorInfo extends LitElement {
|
||||
<div>
|
||||
<ha-settings-row>
|
||||
<span slot="heading">
|
||||
Version
|
||||
${this.supervisor.localize("common.version")}
|
||||
</span>
|
||||
<span slot="description">
|
||||
supervisor-${this.supervisor.supervisor.version}
|
||||
@@ -123,7 +93,7 @@ class HassioSupervisorInfo extends LitElement {
|
||||
</ha-settings-row>
|
||||
<ha-settings-row>
|
||||
<span slot="heading">
|
||||
Newest Version
|
||||
${this.supervisor.localize("common.newest_version")}
|
||||
</span>
|
||||
<span slot="description">
|
||||
supervisor-${this.supervisor.supervisor.version_latest}
|
||||
@@ -131,17 +101,19 @@ class HassioSupervisorInfo extends LitElement {
|
||||
${this.supervisor.supervisor.update_available
|
||||
? html`
|
||||
<ha-progress-button
|
||||
title="Update the supervisor"
|
||||
.title=${this.supervisor.localize(
|
||||
"system.supervisor.update_supervisor"
|
||||
)}
|
||||
@click=${this._supervisorUpdate}
|
||||
>
|
||||
Update
|
||||
${this.supervisor.localize("common.update")}
|
||||
</ha-progress-button>
|
||||
`
|
||||
: ""}
|
||||
</ha-settings-row>
|
||||
<ha-settings-row>
|
||||
<span slot="heading">
|
||||
Channel
|
||||
${this.supervisor.localize("system.supervisor.channel")}
|
||||
</span>
|
||||
<span slot="description">
|
||||
${this.supervisor.supervisor.channel}
|
||||
@@ -150,18 +122,26 @@ class HassioSupervisorInfo extends LitElement {
|
||||
? html`
|
||||
<ha-progress-button
|
||||
@click=${this._toggleBeta}
|
||||
title="Get stable updates for Home Assistant, supervisor and host"
|
||||
.title=${this.supervisor.localize(
|
||||
"system.supervisor.leave_beta_description"
|
||||
)}
|
||||
>
|
||||
Leave beta channel
|
||||
${this.supervisor.localize(
|
||||
"system.supervisor.leave_beta_action"
|
||||
)}
|
||||
</ha-progress-button>
|
||||
`
|
||||
: this.supervisor.supervisor.channel === "stable"
|
||||
? html`
|
||||
<ha-progress-button
|
||||
@click=${this._toggleBeta}
|
||||
title="Get beta updates for Home Assistant (RCs), supervisor and host"
|
||||
.title=${this.supervisor.localize(
|
||||
"system.supervisor.join_beta_description"
|
||||
)}
|
||||
>
|
||||
Join beta channel
|
||||
${this.supervisor.localize(
|
||||
"system.supervisor.join_beta_action"
|
||||
)}
|
||||
</ha-progress-button>
|
||||
`
|
||||
: ""}
|
||||
@@ -170,16 +150,20 @@ class HassioSupervisorInfo extends LitElement {
|
||||
${this.supervisor.supervisor.supported
|
||||
? html` <ha-settings-row three-line>
|
||||
<span slot="heading">
|
||||
Share Diagnostics
|
||||
${this.supervisor.localize(
|
||||
"system.supervisor.share_diagnostics"
|
||||
)}
|
||||
</span>
|
||||
<div slot="description" class="diagnostics-description">
|
||||
Share crash reports and diagnostic information.
|
||||
${this.supervisor.localize(
|
||||
"system.supervisor.share_diagnostics_description"
|
||||
)}
|
||||
<button
|
||||
class="link"
|
||||
title="Show more information about this"
|
||||
.title=${this.supervisor.localize("common.show_more")}
|
||||
@click=${this._diagnosticsInformationDialog}
|
||||
>
|
||||
Learn more
|
||||
${this.supervisor.localize("common.learn_more")}
|
||||
</button>
|
||||
</div>
|
||||
<ha-switch
|
||||
@@ -189,10 +173,12 @@ class HassioSupervisorInfo extends LitElement {
|
||||
></ha-switch>
|
||||
</ha-settings-row>`
|
||||
: html`<div class="error">
|
||||
You are running an unsupported installation.
|
||||
${this.supervisor.localize(
|
||||
"system.supervisor.unsupported_title"
|
||||
)}
|
||||
<button
|
||||
class="link"
|
||||
title="Learn more about how you can make your system compliant"
|
||||
.title=${this.supervisor.localize("common.learn_more")}
|
||||
@click=${this._unsupportedDialog}
|
||||
>
|
||||
Learn more
|
||||
@@ -200,10 +186,12 @@ class HassioSupervisorInfo extends LitElement {
|
||||
</div>`}
|
||||
${!this.supervisor.supervisor.healthy
|
||||
? html`<div class="error">
|
||||
Your installation is running in an unhealthy state.
|
||||
${this.supervisor.localize(
|
||||
"system.supervisor.unhealthy_title"
|
||||
)}
|
||||
<button
|
||||
class="link"
|
||||
title="Learn more about why your system is marked as unhealthy"
|
||||
.title=${this.supervisor.localize("common.learn_more")}
|
||||
@click=${this._unhealthyDialog}
|
||||
>
|
||||
Learn more
|
||||
@@ -227,16 +215,26 @@ class HassioSupervisorInfo extends LitElement {
|
||||
<div class="card-actions">
|
||||
<ha-progress-button
|
||||
@click=${this._supervisorReload}
|
||||
title="Reload parts of the Supervisor"
|
||||
.title=${this.supervisor.localize(
|
||||
"system.supervisor.reload_supervisor"
|
||||
)}
|
||||
>
|
||||
Reload Supervisor
|
||||
${this.supervisor.localize("system.supervisor.reload_supervisor")}
|
||||
</ha-progress-button>
|
||||
<ha-progress-button
|
||||
class="warning"
|
||||
@click=${this._supervisorRestart}
|
||||
title="Restart the Supervisor"
|
||||
.title=${this.supervisor.localize(
|
||||
"common.restart_name",
|
||||
"name",
|
||||
"Supervisor"
|
||||
)}
|
||||
>
|
||||
Restart Supervisor
|
||||
${this.supervisor.localize(
|
||||
"common.restart_name",
|
||||
"name",
|
||||
"Supervisor"
|
||||
)}
|
||||
</ha-progress-button>
|
||||
</div>
|
||||
</ha-card>
|
||||
@@ -257,23 +255,23 @@ class HassioSupervisorInfo extends LitElement {
|
||||
|
||||
if (this.supervisor.supervisor.channel === "stable") {
|
||||
const confirmed = await showConfirmationDialog(this, {
|
||||
title: "WARNING",
|
||||
text: html` Beta releases are for testers and early adopters and can
|
||||
contain unstable code changes.
|
||||
title: this.supervisor.localize("system.supervisor.warning"),
|
||||
text: html`${this.supervisor.localize("system.supervisor.beta_warning")}
|
||||
<br />
|
||||
<b>
|
||||
Make sure you have backups of your data before you activate this
|
||||
feature.
|
||||
${this.supervisor.localize("system.supervisor.beta_backup")}
|
||||
</b>
|
||||
<br /><br />
|
||||
This includes beta releases for:
|
||||
${this.supervisor.localize("system.supervisor.beta_release_items")}
|
||||
<li>Home Assistant Core</li>
|
||||
<li>Home Assistant Supervisor</li>
|
||||
<li>Home Assistant Operating System</li>
|
||||
<br />
|
||||
Do you want to join the beta channel?`,
|
||||
confirmText: "join beta",
|
||||
dismissText: "no",
|
||||
${this.supervisor.localize("system.supervisor.join_beta_action")}`,
|
||||
confirmText: this.supervisor.localize(
|
||||
"system.supervisor.beta_join_confirm"
|
||||
),
|
||||
dismissText: this.supervisor.localize("common.cancel"),
|
||||
});
|
||||
|
||||
if (!confirmed) {
|
||||
@@ -291,7 +289,9 @@ class HassioSupervisorInfo extends LitElement {
|
||||
await this._reloadSupervisor();
|
||||
} catch (err) {
|
||||
showAlertDialog(this, {
|
||||
title: "Failed to set supervisor option",
|
||||
title: this.supervisor.localize(
|
||||
"system.supervisor.failed_to_set_option"
|
||||
),
|
||||
text: extractApiErrorMessage(err),
|
||||
});
|
||||
} finally {
|
||||
@@ -307,7 +307,7 @@ class HassioSupervisorInfo extends LitElement {
|
||||
await this._reloadSupervisor();
|
||||
} catch (err) {
|
||||
showAlertDialog(this, {
|
||||
title: "Failed to reload the supervisor",
|
||||
title: this.supervisor.localize("system.supervisor.failed_to_reload"),
|
||||
text: extractApiErrorMessage(err),
|
||||
});
|
||||
} finally {
|
||||
@@ -317,8 +317,8 @@ class HassioSupervisorInfo extends LitElement {
|
||||
|
||||
private async _reloadSupervisor(): Promise<void> {
|
||||
await reloadSupervisor(this.hass);
|
||||
fireEvent(this, "supervisor-colllection-refresh", {
|
||||
colllection: "supervisor",
|
||||
fireEvent(this, "supervisor-collection-refresh", {
|
||||
collection: "supervisor",
|
||||
});
|
||||
}
|
||||
|
||||
@@ -327,10 +327,18 @@ class HassioSupervisorInfo extends LitElement {
|
||||
button.progress = true;
|
||||
|
||||
const confirmed = await showConfirmationDialog(this, {
|
||||
title: "Restart the Supervisor",
|
||||
text: "Are you sure you want to restart the Supervisor",
|
||||
confirmText: "restart",
|
||||
dismissText: "cancel",
|
||||
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"),
|
||||
});
|
||||
|
||||
if (!confirmed) {
|
||||
@@ -342,7 +350,11 @@ class HassioSupervisorInfo extends LitElement {
|
||||
await restartSupervisor(this.hass);
|
||||
} catch (err) {
|
||||
showAlertDialog(this, {
|
||||
title: "Failed to restart the supervisor",
|
||||
title: this.supervisor.localize(
|
||||
"common.failed_to_restart_name",
|
||||
"name",
|
||||
"Supervisor"
|
||||
),
|
||||
text: extractApiErrorMessage(err),
|
||||
});
|
||||
} finally {
|
||||
@@ -355,10 +367,20 @@ class HassioSupervisorInfo extends LitElement {
|
||||
button.progress = true;
|
||||
|
||||
const confirmed = await showConfirmationDialog(this, {
|
||||
title: "Update Supervisor",
|
||||
text: `Are you sure you want to update supervisor to version ${this.supervisor.supervisor.version_latest}?`,
|
||||
confirmText: "update",
|
||||
dismissText: "cancel",
|
||||
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"),
|
||||
});
|
||||
|
||||
if (!confirmed) {
|
||||
@@ -368,12 +390,16 @@ class HassioSupervisorInfo extends LitElement {
|
||||
|
||||
try {
|
||||
await updateSupervisor(this.hass);
|
||||
fireEvent(this, "supervisor-colllection-refresh", {
|
||||
colllection: "supervisor",
|
||||
fireEvent(this, "supervisor-collection-refresh", {
|
||||
collection: "supervisor",
|
||||
});
|
||||
} catch (err) {
|
||||
showAlertDialog(this, {
|
||||
title: "Failed to update the supervisor",
|
||||
title: this.supervisor.localize(
|
||||
"common.failed_to_update_name",
|
||||
"name",
|
||||
"Supervisor"
|
||||
),
|
||||
text: extractApiErrorMessage(err),
|
||||
});
|
||||
} finally {
|
||||
@@ -383,40 +409,41 @@ class HassioSupervisorInfo extends LitElement {
|
||||
|
||||
private async _diagnosticsInformationDialog(): Promise<void> {
|
||||
await showAlertDialog(this, {
|
||||
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.`,
|
||||
title: this.supervisor.localize(
|
||||
"system.supervisor.share_diagonstics_title"
|
||||
),
|
||||
text: this.supervisor.localize(
|
||||
"system.supervisor.share_diagonstics_description",
|
||||
"line_break",
|
||||
html`<br /><br />`
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
private async _unsupportedDialog(): Promise<void> {
|
||||
await showAlertDialog(this, {
|
||||
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 />
|
||||
title: this.supervisor.localize("system.supervisor.unsupported_title"),
|
||||
text: html`${this.supervisor.localize(
|
||||
"system.supervisor.unsupported_description"
|
||||
)} <br /><br />
|
||||
<ul>
|
||||
${this.supervisor.resolution.unsupported.map(
|
||||
(issue) => html`
|
||||
(reason) => html`
|
||||
<li>
|
||||
${UNSUPPORTED_REASON[issue]
|
||||
${UNSUPPORTED_REASON_URL[reason]
|
||||
? html`<a
|
||||
href="${documentationUrl(
|
||||
this.hass,
|
||||
UNSUPPORTED_REASON[issue].url
|
||||
UNSUPPORTED_REASON_URL[reason]
|
||||
)}"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
${UNSUPPORTED_REASON[issue].title}
|
||||
${this.supervisor.localize(
|
||||
`system.supervisor.unsupported_reason.${reason}`
|
||||
) || reason}
|
||||
</a>`
|
||||
: issue}
|
||||
: reason}
|
||||
</li>
|
||||
`
|
||||
)}
|
||||
@@ -426,26 +453,28 @@ class HassioSupervisorInfo extends LitElement {
|
||||
|
||||
private async _unhealthyDialog(): Promise<void> {
|
||||
await showAlertDialog(this, {
|
||||
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 />
|
||||
title: this.supervisor.localize("system.supervisor.unhealthy_title"),
|
||||
text: html`${this.supervisor.localize(
|
||||
"system.supervisor.unhealthy_description"
|
||||
)} <br /><br />
|
||||
<ul>
|
||||
${this.supervisor.resolution.unhealthy.map(
|
||||
(issue) => html`
|
||||
(reason) => html`
|
||||
<li>
|
||||
${UNHEALTHY_REASON[issue]
|
||||
${UNHEALTHY_REASON_URL[reason]
|
||||
? html`<a
|
||||
href="${documentationUrl(
|
||||
this.hass,
|
||||
UNHEALTHY_REASON[issue].url
|
||||
UNHEALTHY_REASON_URL[reason]
|
||||
)}"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
${UNHEALTHY_REASON[issue].title}
|
||||
${this.supervisor.localize(
|
||||
`system.supervisor.unhealthy_reason.${reason}`
|
||||
) || reason}
|
||||
</a>`
|
||||
: issue}
|
||||
: reason}
|
||||
</li>
|
||||
`
|
||||
)}
|
||||
@@ -461,7 +490,9 @@ class HassioSupervisorInfo extends LitElement {
|
||||
await setSupervisorOption(this.hass, data);
|
||||
} catch (err) {
|
||||
showAlertDialog(this, {
|
||||
title: "Failed to set supervisor option",
|
||||
title: this.supervisor.localize(
|
||||
"system.supervisor.failed_to_set_option"
|
||||
),
|
||||
text: extractApiErrorMessage(err),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ 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";
|
||||
@@ -58,6 +59,8 @@ 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";
|
||||
@@ -76,7 +79,7 @@ class HassioSupervisorLog extends LitElement {
|
||||
${this.hass.userData?.showAdvanced
|
||||
? html`
|
||||
<paper-dropdown-menu
|
||||
label="Log Provider"
|
||||
.label=${this.supervisor.localize("system.log.log_provider")}
|
||||
@iron-select=${this._setLogProvider}
|
||||
>
|
||||
<paper-listbox
|
||||
@@ -86,9 +89,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>
|
||||
@@ -98,14 +101,13 @@ 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}>
|
||||
Refresh
|
||||
${this.supervisor.localize("common.refresh")}
|
||||
</ha-progress-button>
|
||||
</div>
|
||||
</ha-card>
|
||||
@@ -134,9 +136,13 @@ class HassioSupervisorLog extends LitElement {
|
||||
this._selectedLogProvider
|
||||
);
|
||||
} catch (err) {
|
||||
this._error = `Failed to get supervisor logs, ${extractApiErrorMessage(
|
||||
err
|
||||
)}`;
|
||||
this._error = this.supervisor.localize(
|
||||
"system.log.get_logs",
|
||||
"provider",
|
||||
this._selectedLogProvider,
|
||||
"error",
|
||||
extractApiErrorMessage(err)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { ActionDetail } from "@material/mwc-list";
|
||||
import { mdiDotsVertical } from "@mdi/js";
|
||||
import {
|
||||
css,
|
||||
CSSResult,
|
||||
@@ -11,6 +13,7 @@ import { Supervisor } from "../../../src/data/supervisor/supervisor";
|
||||
import "../../../src/layouts/hass-tabs-subpage";
|
||||
import { haStyle } from "../../../src/resources/styles";
|
||||
import { HomeAssistant, Route } from "../../../src/types";
|
||||
import { showSystemChecksDialog } from "../dialogs/system_checks/show-dialog-system-checks";
|
||||
import { supervisorTabs } from "../hassio-tabs";
|
||||
import { hassioStyle } from "../resources/hassio-style";
|
||||
import "./hassio-core-info";
|
||||
@@ -32,13 +35,31 @@ 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">System</span>
|
||||
<span slot="header">
|
||||
${this.supervisor.localize("panel.system")}
|
||||
</span>
|
||||
${this.hass.userData?.showAdvanced
|
||||
? html` <ha-button-menu
|
||||
corner="BOTTOM_START"
|
||||
slot="toolbar-icon"
|
||||
@action=${this._handleAction}
|
||||
>
|
||||
<mwc-icon-button slot="trigger" alt="menu">
|
||||
<ha-svg-icon .path=${mdiDotsVertical}></ha-svg-icon>
|
||||
</mwc-icon-button>
|
||||
<mwc-list-item>
|
||||
${this.supervisor.localize("system.system_checks")}
|
||||
</mwc-list-item>
|
||||
</ha-button-menu>`
|
||||
: ""}
|
||||
|
||||
<div class="content">
|
||||
<div class="card-group">
|
||||
<hassio-core-info
|
||||
@@ -54,12 +75,23 @@ class HassioSystem extends LitElement {
|
||||
.supervisor=${this.supervisor}
|
||||
></hassio-host-info>
|
||||
</div>
|
||||
<hassio-supervisor-log .hass=${this.hass}></hassio-supervisor-log>
|
||||
<hassio-supervisor-log
|
||||
.hass=${this.hass}
|
||||
.supervisor=${this.supervisor}
|
||||
></hassio-supervisor-log>
|
||||
</div>
|
||||
</hass-tabs-subpage>
|
||||
`;
|
||||
}
|
||||
|
||||
private _handleAction(ev: CustomEvent<ActionDetail>) {
|
||||
switch (ev.detail.index) {
|
||||
case 0:
|
||||
showSystemChecksDialog(this, { supervisor: this.supervisor });
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static get styles(): CSSResult[] {
|
||||
return [
|
||||
haStyle,
|
||||
|
||||
+13
-12
@@ -23,16 +23,17 @@
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@braintree/sanitize-url": "^5.0.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",
|
||||
"@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",
|
||||
"@formatjs/intl-getcanonicallocales": "^1.4.6",
|
||||
"@formatjs/intl-pluralrules": "^3.4.10",
|
||||
"@fullcalendar/common": "5.1.0",
|
||||
@@ -90,8 +91,6 @@
|
||||
"@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",
|
||||
@@ -166,6 +165,7 @@
|
||||
"@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",
|
||||
@@ -175,6 +175,7 @@
|
||||
"@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,7 +2,7 @@ from setuptools import setup, find_packages
|
||||
|
||||
setup(
|
||||
name="home-assistant-frontend",
|
||||
version="20210301.0",
|
||||
version="20210302.0",
|
||||
description="The Home Assistant frontend",
|
||||
url="https://github.com/home-assistant/home-assistant-polymer",
|
||||
author="The Home Assistant Authors",
|
||||
|
||||
@@ -8,9 +8,10 @@ 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 &&
|
||||
|
||||
@@ -103,6 +103,7 @@ export const DOMAINS_WITH_MORE_INFO = [
|
||||
"lock",
|
||||
"media_player",
|
||||
"person",
|
||||
"remote",
|
||||
"script",
|
||||
"sun",
|
||||
"timer",
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
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;
|
||||
};
|
||||
@@ -17,6 +17,7 @@ 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 &&
|
||||
@@ -29,6 +30,8 @@ export class HaEntityToggle extends LitElement {
|
||||
|
||||
@property() public stateObj?: HassEntity;
|
||||
|
||||
@property() public label?: string;
|
||||
|
||||
@internalProperty() private _isOn = false;
|
||||
|
||||
protected render(): TemplateResult {
|
||||
@@ -55,15 +58,21 @@ 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-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>
|
||||
<ha-formfield .label=${this.label}>${switchTemplate}</ha-formfield>
|
||||
`;
|
||||
}
|
||||
|
||||
|
||||
@@ -140,7 +140,7 @@ export class HaAreaPicker extends SubscribeMixin(LitElement) {
|
||||
this._devices = devices;
|
||||
}),
|
||||
subscribeEntityRegistry(this.hass.connection!, (entities) => {
|
||||
this._entities = entities.filter((entity) => entity.area_id);
|
||||
this._entities = entities;
|
||||
}),
|
||||
];
|
||||
}
|
||||
@@ -193,13 +193,13 @@ export class HaAreaPicker extends SubscribeMixin(LitElement) {
|
||||
deviceEntityLookup[entity.device_id].push(entity);
|
||||
}
|
||||
inputDevices = devices;
|
||||
inputEntities = entities;
|
||||
inputEntities = entities.filter((entity) => entity.area_id);
|
||||
} else {
|
||||
if (deviceFilter) {
|
||||
inputDevices = devices;
|
||||
}
|
||||
if (entityFilter) {
|
||||
inputEntities = entities;
|
||||
inputEntities = entities.filter((entity) => entity.area_id);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -101,7 +101,7 @@ class HaClimateState extends LitElement {
|
||||
)}-${formatNumber(
|
||||
this.stateObj.attributes.target_humidity_high,
|
||||
this.hass!.language
|
||||
)}%`;
|
||||
)} %`;
|
||||
}
|
||||
|
||||
if (this.stateObj.attributes.humidity != null) {
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import type { StreamLanguage } from "@codemirror/stream-parser";
|
||||
import type { EditorView, KeyBinding, ViewUpdate } from "@codemirror/view";
|
||||
import {
|
||||
customElement,
|
||||
@@ -16,10 +15,6 @@ declare global {
|
||||
}
|
||||
}
|
||||
|
||||
const modeTag = Symbol("mode");
|
||||
|
||||
const readOnlyTag = Symbol("readOnly");
|
||||
|
||||
const saveKeyBinding: KeyBinding = {
|
||||
key: "Mod-s",
|
||||
run: (view: EditorView) => {
|
||||
@@ -42,7 +37,7 @@ export class HaCodeEditor extends UpdatingElement {
|
||||
|
||||
@internalProperty() private _value = "";
|
||||
|
||||
@internalProperty() private _langs?: Record<string, StreamLanguage<unknown>>;
|
||||
private _loadedCodeMirror?: typeof import("../resources/codemirror");
|
||||
|
||||
public set value(value: string) {
|
||||
this._value = value;
|
||||
@@ -52,6 +47,17 @@ 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) {
|
||||
@@ -71,16 +77,16 @@ export class HaCodeEditor extends UpdatingElement {
|
||||
|
||||
if (changedProps.has("mode")) {
|
||||
this.codemirror.dispatch({
|
||||
reconfigure: {
|
||||
[modeTag]: this._mode,
|
||||
},
|
||||
effects: this._loadedCodeMirror!.langCompartment!.reconfigure(
|
||||
this._mode
|
||||
),
|
||||
});
|
||||
}
|
||||
if (changedProps.has("readOnly")) {
|
||||
this.codemirror.dispatch({
|
||||
reconfigure: {
|
||||
[readOnlyTag]: !this.readOnly,
|
||||
},
|
||||
effects: this._loadedCodeMirror!.readonlyCompartment!.reconfigure(
|
||||
this._loadedCodeMirror!.EditorView!.editable.of(!this.readOnly)
|
||||
),
|
||||
});
|
||||
}
|
||||
if (changedProps.has("_value") && this._value !== this.value) {
|
||||
@@ -104,13 +110,11 @@ export class HaCodeEditor extends UpdatingElement {
|
||||
}
|
||||
|
||||
private get _mode() {
|
||||
return this._langs![this.mode];
|
||||
return this._loadedCodeMirror!.langs[this.mode];
|
||||
}
|
||||
|
||||
private async _load(): Promise<void> {
|
||||
const loaded = await loadCodeMirror();
|
||||
|
||||
this._langs = loaded.langs;
|
||||
this._loadedCodeMirror = await loadCodeMirror();
|
||||
|
||||
const shadowRoot = this.attachShadow({ mode: "open" });
|
||||
|
||||
@@ -124,28 +128,33 @@ export class HaCodeEditor extends UpdatingElement {
|
||||
|
||||
shadowRoot.appendChild(container);
|
||||
|
||||
this.codemirror = new loaded.EditorView({
|
||||
state: loaded.EditorState.create({
|
||||
this.codemirror = new this._loadedCodeMirror.EditorView({
|
||||
state: this._loadedCodeMirror.EditorState.create({
|
||||
doc: this._value,
|
||||
extensions: [
|
||||
loaded.lineNumbers(),
|
||||
loaded.history(),
|
||||
loaded.highlightSelectionMatches(),
|
||||
loaded.keymap.of([
|
||||
...loaded.defaultKeymap,
|
||||
...loaded.searchKeymap,
|
||||
...loaded.historyKeymap,
|
||||
...loaded.tabKeyBindings,
|
||||
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,
|
||||
saveKeyBinding,
|
||||
] as KeyBinding[]),
|
||||
loaded.tagExtension(modeTag, this._mode),
|
||||
loaded.theme,
|
||||
loaded.Prec.fallback(loaded.highlightStyle),
|
||||
loaded.tagExtension(
|
||||
readOnlyTag,
|
||||
loaded.EditorView.editable.of(!this.readOnly)
|
||||
this._loadedCodeMirror.langCompartment.of(this._mode),
|
||||
this._loadedCodeMirror.theme,
|
||||
this._loadedCodeMirror.Prec.fallback(
|
||||
this._loadedCodeMirror.highlightStyle
|
||||
),
|
||||
loaded.EditorView.updateListener.of((update) =>
|
||||
this._loadedCodeMirror.readonlyCompartment.of(
|
||||
this._loadedCodeMirror.EditorView.editable.of(!this.readOnly)
|
||||
),
|
||||
this._loadedCodeMirror.EditorView.updateListener.of((update) =>
|
||||
this._onUpdate(update)
|
||||
),
|
||||
],
|
||||
|
||||
@@ -16,6 +16,10 @@ 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;
|
||||
@@ -82,6 +86,7 @@ export interface HassioAddonDetails extends HassioAddonInfo {
|
||||
slug: string;
|
||||
startup: AddonStartup;
|
||||
stdin: boolean;
|
||||
translations: AddonTranslations;
|
||||
watchdog: null | boolean;
|
||||
webui: null | string;
|
||||
}
|
||||
@@ -304,13 +309,12 @@ export const updateHassioAddon = async (
|
||||
method: "post",
|
||||
timeout: null,
|
||||
});
|
||||
return;
|
||||
} else {
|
||||
await hass.callApi<HassioResponse<void>>(
|
||||
"POST",
|
||||
`hassio/addons/${slug}/update`
|
||||
);
|
||||
}
|
||||
|
||||
await hass.callApi<HassioResponse<void>>(
|
||||
"POST",
|
||||
`hassio/addons/${slug}/update`
|
||||
);
|
||||
};
|
||||
|
||||
export const restartHassioAddon = async (
|
||||
|
||||
@@ -28,7 +28,22 @@ export const extractApiErrorMessage = (error: any): string => {
|
||||
: error;
|
||||
};
|
||||
|
||||
export const ignoredStatusCodes = new Set([502, 503, 504]);
|
||||
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 fetchHassioStats = async (
|
||||
hass: HomeAssistant,
|
||||
|
||||
@@ -7,6 +7,7 @@ export interface HassioResolution {
|
||||
unhealthy: string[];
|
||||
issues: string[];
|
||||
suggestions: string[];
|
||||
checks: { slug: string; enabled: boolean }[];
|
||||
}
|
||||
|
||||
export const fetchHassioResolution = async (
|
||||
@@ -27,3 +28,25 @@ export const fetchHassioResolution = async (
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
export const setCheckOptions = async (
|
||||
hass: HomeAssistant,
|
||||
check: string,
|
||||
data: Record<string, any>
|
||||
): Promise<void> => {
|
||||
if (atLeastVersion(hass.config.version, 2021, 2, 4)) {
|
||||
await hass.callWS({
|
||||
type: "supervisor/api",
|
||||
endpoint: `/resolution/check/${check}`,
|
||||
data,
|
||||
method: "post",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
await hass.callApi<HassioResponse<HassioResolution>>(
|
||||
"POST",
|
||||
`hassio/resolution/check/${check}`,
|
||||
data
|
||||
);
|
||||
};
|
||||
|
||||
@@ -105,6 +105,7 @@ export const createHassioFullSnapshot = async (
|
||||
endpoint: "/snapshots/new/full",
|
||||
method: "post",
|
||||
timeout: null,
|
||||
data,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
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;
|
||||
};
|
||||
};
|
||||
+1
-1
@@ -55,7 +55,7 @@ export interface DelayActionParts {
|
||||
days?: number;
|
||||
}
|
||||
export interface DelayAction {
|
||||
delay: number | Partial<DelayActionParts>;
|
||||
delay: number | Partial<DelayActionParts> | string;
|
||||
}
|
||||
|
||||
export interface SceneAction {
|
||||
|
||||
@@ -14,8 +14,7 @@ export const updateCore = async (hass: HomeAssistant) => {
|
||||
method: "post",
|
||||
timeout: null,
|
||||
});
|
||||
return;
|
||||
} else {
|
||||
await hass.callApi<HassioResponse<void>>("POST", `hassio/core/update`);
|
||||
}
|
||||
|
||||
await hass.callApi<HassioResponse<void>>("POST", `hassio/core/update`);
|
||||
};
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
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";
|
||||
@@ -46,6 +47,7 @@ interface supervisorApiRequest {
|
||||
method?: "get" | "post" | "delete" | "put";
|
||||
force_rest?: boolean;
|
||||
data?: any;
|
||||
timeout?: number | null;
|
||||
}
|
||||
|
||||
export interface SupervisorEvent {
|
||||
@@ -65,6 +67,7 @@ export interface Supervisor {
|
||||
os: HassioHassOSInfo;
|
||||
addon: HassioAddonsInfo;
|
||||
store: SupervisorStore;
|
||||
localize: LocalizeFunc;
|
||||
}
|
||||
|
||||
export const supervisorApiWsRequest = <T>(
|
||||
|
||||
@@ -5,6 +5,7 @@ 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) {
|
||||
@@ -26,6 +27,7 @@ class MoreInfoAlarmControlPanel extends LocalizeMixin(PolymerElement) {
|
||||
flex-direction: column;
|
||||
}
|
||||
.pad mwc-button {
|
||||
padding: 8px;
|
||||
width: 80px;
|
||||
}
|
||||
.actions mwc-button {
|
||||
@@ -43,6 +45,7 @@ class MoreInfoAlarmControlPanel extends LocalizeMixin(PolymerElement) {
|
||||
label="[[localize('ui.card.alarm_control_panel.code')]]"
|
||||
value="{{_enteredCode}}"
|
||||
type="password"
|
||||
inputmode="[[_inputMode]]"
|
||||
disabled="[[!_inputEnabled]]"
|
||||
></paper-input>
|
||||
|
||||
@@ -53,21 +56,21 @@ class MoreInfoAlarmControlPanel extends LocalizeMixin(PolymerElement) {
|
||||
on-click="_digitClicked"
|
||||
disabled="[[!_inputEnabled]]"
|
||||
data-digit="1"
|
||||
raised
|
||||
outlined
|
||||
>1</mwc-button
|
||||
>
|
||||
<mwc-button
|
||||
on-click="_digitClicked"
|
||||
disabled="[[!_inputEnabled]]"
|
||||
data-digit="4"
|
||||
raised
|
||||
outlined
|
||||
>4</mwc-button
|
||||
>
|
||||
<mwc-button
|
||||
on-click="_digitClicked"
|
||||
disabled="[[!_inputEnabled]]"
|
||||
data-digit="7"
|
||||
raised
|
||||
outlined
|
||||
>7</mwc-button
|
||||
>
|
||||
</div>
|
||||
@@ -76,28 +79,28 @@ class MoreInfoAlarmControlPanel extends LocalizeMixin(PolymerElement) {
|
||||
on-click="_digitClicked"
|
||||
disabled="[[!_inputEnabled]]"
|
||||
data-digit="2"
|
||||
raised
|
||||
outlined
|
||||
>2</mwc-button
|
||||
>
|
||||
<mwc-button
|
||||
on-click="_digitClicked"
|
||||
disabled="[[!_inputEnabled]]"
|
||||
data-digit="5"
|
||||
raised
|
||||
outlined
|
||||
>5</mwc-button
|
||||
>
|
||||
<mwc-button
|
||||
on-click="_digitClicked"
|
||||
disabled="[[!_inputEnabled]]"
|
||||
data-digit="8"
|
||||
raised
|
||||
outlined
|
||||
>8</mwc-button
|
||||
>
|
||||
<mwc-button
|
||||
on-click="_digitClicked"
|
||||
disabled="[[!_inputEnabled]]"
|
||||
data-digit="0"
|
||||
raised
|
||||
outlined
|
||||
>0</mwc-button
|
||||
>
|
||||
</div>
|
||||
@@ -106,27 +109,27 @@ class MoreInfoAlarmControlPanel extends LocalizeMixin(PolymerElement) {
|
||||
on-click="_digitClicked"
|
||||
disabled="[[!_inputEnabled]]"
|
||||
data-digit="3"
|
||||
raised
|
||||
outlined
|
||||
>3</mwc-button
|
||||
>
|
||||
<mwc-button
|
||||
on-click="_digitClicked"
|
||||
disabled="[[!_inputEnabled]]"
|
||||
data-digit="6"
|
||||
raised
|
||||
outlined
|
||||
>6</mwc-button
|
||||
>
|
||||
<mwc-button
|
||||
on-click="_digitClicked"
|
||||
disabled="[[!_inputEnabled]]"
|
||||
data-digit="9"
|
||||
raised
|
||||
outlined
|
||||
>9</mwc-button
|
||||
>
|
||||
<mwc-button
|
||||
on-click="_clearEnteredCode"
|
||||
disabled="[[!_inputEnabled]]"
|
||||
raised
|
||||
outlined
|
||||
>
|
||||
[[localize('ui.card.alarm_control_panel.clear_code')]]
|
||||
</mwc-button>
|
||||
@@ -201,6 +204,10 @@ class MoreInfoAlarmControlPanel extends LocalizeMixin(PolymerElement) {
|
||||
type: Boolean,
|
||||
value: false,
|
||||
},
|
||||
_inputMode: {
|
||||
type: String,
|
||||
computed: "_getInputMode(_codeFormat)",
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -237,8 +244,12 @@ class MoreInfoAlarmControlPanel extends LocalizeMixin(PolymerElement) {
|
||||
}
|
||||
}
|
||||
|
||||
_getInputMode(format) {
|
||||
return this._isNumber(format) ? "numeric" : "text";
|
||||
}
|
||||
|
||||
_isNumber(format) {
|
||||
return format === "Number";
|
||||
return format === FORMAT_NUMBER;
|
||||
}
|
||||
|
||||
_validateCode(code, format, armVisible, codeArmRequired) {
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
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>
|
||||
`
|
||||
|
||||
@@ -21,6 +21,7 @@ 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"),
|
||||
|
||||
@@ -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 { getLocalLanguage, getTranslation } from "../util/hass-translation";
|
||||
import { getTranslation, getLocalLanguage } from "../util/hass-translation";
|
||||
import { demoConfig } from "./demo_config";
|
||||
import { demoPanels } from "./demo_panels";
|
||||
import { demoServices } from "./demo_services";
|
||||
|
||||
@@ -16,6 +16,7 @@ import memoizeOne from "memoize-one";
|
||||
import { isComponentLoaded } from "../common/config/is_component_loaded";
|
||||
import { restoreScroll } from "../common/decorators/restore-scroll";
|
||||
import { navigate } from "../common/navigate";
|
||||
import { LocalizeFunc } from "../common/translations/localize";
|
||||
import { computeRTL } from "../common/util/compute_rtl";
|
||||
import "../components/ha-icon";
|
||||
import "../components/ha-icon-button-arrow-prev";
|
||||
@@ -40,7 +41,9 @@ export interface PageNavigation {
|
||||
class HassTabsSubpage extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property({ type: Boolean }) public hassio = false;
|
||||
@property({ type: Boolean }) public supervisor = false;
|
||||
|
||||
@property({ attribute: false }) public localizeFunc?: LocalizeFunc;
|
||||
|
||||
@property({ type: String, attribute: "back-path" }) public backPath?: string;
|
||||
|
||||
@@ -48,9 +51,9 @@ class HassTabsSubpage extends LitElement {
|
||||
|
||||
@property({ type: Boolean, attribute: "main-page" }) public mainPage = false;
|
||||
|
||||
@property() public route!: Route;
|
||||
@property({ attribute: false }) public route!: Route;
|
||||
|
||||
@property() public tabs!: PageNavigation[];
|
||||
@property({ attribute: false }) public tabs!: PageNavigation[];
|
||||
|
||||
@property({ type: Boolean, reflect: true }) public narrow = false;
|
||||
|
||||
@@ -71,7 +74,8 @@ class HassTabsSubpage extends LitElement {
|
||||
showAdvanced: boolean | undefined,
|
||||
_components,
|
||||
_language,
|
||||
_narrow
|
||||
_narrow,
|
||||
localizeFunc
|
||||
) => {
|
||||
const shownTabs = tabs.filter(
|
||||
(page) =>
|
||||
@@ -91,7 +95,7 @@ class HassTabsSubpage extends LitElement {
|
||||
.active=${page === activeTab}
|
||||
.narrow=${this.narrow}
|
||||
.name=${page.translationKey
|
||||
? this.hass.localize(page.translationKey)
|
||||
? localizeFunc(page.translationKey)
|
||||
: page.name}
|
||||
>
|
||||
${page.iconPath
|
||||
@@ -130,7 +134,8 @@ class HassTabsSubpage extends LitElement {
|
||||
this.hass.userData?.showAdvanced,
|
||||
this.hass.config.components,
|
||||
this.hass.language,
|
||||
this.narrow
|
||||
this.narrow,
|
||||
this.localizeFunc || this.hass.localize
|
||||
);
|
||||
const showTabs = tabs.length > 1 || !this.narrow;
|
||||
return html`
|
||||
@@ -138,7 +143,7 @@ class HassTabsSubpage extends LitElement {
|
||||
${this.mainPage
|
||||
? html`
|
||||
<ha-menu-button
|
||||
.hassio=${this.hassio}
|
||||
.hassio=${this.supervisor}
|
||||
.hass=${this.hass}
|
||||
.narrow=${this.narrow}
|
||||
></ha-menu-button>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { LitElement, property, PropertyValues } from "lit-element";
|
||||
import { computeLocalize, LocalizeFunc } from "../common/translations/localize";
|
||||
import { Constructor, Resources } from "../types";
|
||||
import { getLocalLanguage, getTranslation } from "../util/hass-translation";
|
||||
import { getTranslation, getLocalLanguage } from "../util/hass-translation";
|
||||
|
||||
const empty = () => "";
|
||||
|
||||
|
||||
@@ -193,12 +193,16 @@ export default class HaAutomationActionRow extends LitElement {
|
||||
</div>
|
||||
${this._warnings
|
||||
? html`<div class="warning">
|
||||
UI editor is not supported for this config:
|
||||
${this.hass.localize("ui.errors.config.editor_not_supported")}:
|
||||
<br />
|
||||
<ul>
|
||||
${this._warnings.map((warning) => html`<li>${warning}</li>`)}
|
||||
</ul>
|
||||
You can still edit your config in YAML.
|
||||
${this._warnings!.length > 0 && this._warnings![0] !== undefined
|
||||
? html` <ul>
|
||||
${this._warnings!.map(
|
||||
(warning) => html`<li>${warning}</li>`
|
||||
)}
|
||||
</ul>`
|
||||
: ""}
|
||||
${this.hass.localize("ui.errors.config.edit_in_yaml_supported")}
|
||||
</div>`
|
||||
: ""}
|
||||
${yamlMode
|
||||
@@ -212,7 +216,11 @@ export default class HaAutomationActionRow extends LitElement {
|
||||
)}
|
||||
`
|
||||
: ""}
|
||||
<h2>Edit in YAML</h2>
|
||||
<h2>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.edit_yaml"
|
||||
)}
|
||||
</h2>
|
||||
<ha-yaml-editor
|
||||
.defaultValue=${this.action}
|
||||
@value-changed=${this._onYamlChange}
|
||||
@@ -329,6 +337,7 @@ export default class HaAutomationActionRow extends LitElement {
|
||||
}
|
||||
|
||||
private _switchYamlMode() {
|
||||
this._warnings = undefined;
|
||||
this._yamlMode = !this._yamlMode;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,13 @@
|
||||
import "@polymer/paper-input/paper-input";
|
||||
import { customElement, html, LitElement, property } from "lit-element";
|
||||
import {
|
||||
customElement,
|
||||
html,
|
||||
LitElement,
|
||||
property,
|
||||
PropertyValues,
|
||||
} from "lit-element";
|
||||
import { fireEvent } from "../../../../../common/dom/fire_event";
|
||||
import { hasTemplate } from "../../../../../common/string/has-template";
|
||||
import "../../../../../components/entity/ha-entity-picker";
|
||||
import { HaFormTimeData } from "../../../../../components/ha-form/ha-form";
|
||||
import "../../../../../components/ha-service-picker";
|
||||
@@ -14,45 +21,57 @@ export class HaDelayAction extends LitElement implements ActionElement {
|
||||
|
||||
@property() public action!: DelayAction;
|
||||
|
||||
@property() public _timeData!: HaFormTimeData;
|
||||
|
||||
public static get defaultConfig() {
|
||||
return { delay: "" };
|
||||
}
|
||||
|
||||
protected render() {
|
||||
let data: HaFormTimeData = {};
|
||||
protected updated(changedProperties: PropertyValues) {
|
||||
if (!changedProperties.has("action")) {
|
||||
return;
|
||||
}
|
||||
// Check for templates in action. If found, revert to YAML mode.
|
||||
if (this.action && hasTemplate(this.action)) {
|
||||
fireEvent(
|
||||
this,
|
||||
"ui-mode-not-available",
|
||||
Error(this.hass.localize("ui.errors.config.no_template_editor_support"))
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (typeof this.action.delay !== "object") {
|
||||
if (isNaN(this.action.delay)) {
|
||||
if (typeof this.action.delay === "string" || isNaN(this.action.delay)) {
|
||||
const parts = this.action.delay?.toString().split(":") || [];
|
||||
data = {
|
||||
this._timeData = {
|
||||
hours: Number(parts[0]) || 0,
|
||||
minutes: Number(parts[1]) || 0,
|
||||
seconds: Number(parts[2]) || 0,
|
||||
milliseconds: Number(parts[3]) || 0,
|
||||
};
|
||||
} else {
|
||||
data = { seconds: this.action.delay };
|
||||
this._timeData = { seconds: this.action.delay };
|
||||
}
|
||||
} else {
|
||||
const { days, minutes, seconds, milliseconds } = this.action.delay;
|
||||
let { hours } = this.action.delay || 0;
|
||||
hours = (hours || 0) + (days || 0) * 24;
|
||||
data = {
|
||||
hours: hours,
|
||||
minutes: minutes,
|
||||
seconds: seconds,
|
||||
milliseconds: milliseconds,
|
||||
};
|
||||
return;
|
||||
}
|
||||
const { days, minutes, seconds, milliseconds } = this.action.delay;
|
||||
let { hours } = this.action.delay || 0;
|
||||
hours = (hours || 0) + (days || 0) * 24;
|
||||
this._timeData = {
|
||||
hours: hours,
|
||||
minutes: minutes,
|
||||
seconds: seconds,
|
||||
milliseconds: milliseconds,
|
||||
};
|
||||
}
|
||||
|
||||
return html`
|
||||
<ha-time-input
|
||||
.data=${data}
|
||||
enableMillisecond
|
||||
@value-changed=${this._valueChanged}
|
||||
>
|
||||
</ha-time-input>
|
||||
`;
|
||||
protected render() {
|
||||
return html`<ha-time-input
|
||||
.data=${this._timeData}
|
||||
enableMillisecond
|
||||
@value-changed=${this._valueChanged}
|
||||
></ha-time-input>`;
|
||||
}
|
||||
|
||||
private _valueChanged(ev: CustomEvent) {
|
||||
|
||||
@@ -16,6 +16,7 @@ import type { HomeAssistant } from "../../../../../types";
|
||||
import { EntityIdOrAll } from "../../../../../common/structs/is-entity-id";
|
||||
import { ActionElement } from "../ha-automation-action-row";
|
||||
import "../../../../../components/ha-service-control";
|
||||
import { hasTemplate } from "../../../../../common/string/has-template";
|
||||
|
||||
const actionStruct = object({
|
||||
service: optional(string()),
|
||||
@@ -46,6 +47,15 @@ export class HaServiceAction extends LitElement implements ActionElement {
|
||||
assert(this.action, actionStruct);
|
||||
} catch (error) {
|
||||
fireEvent(this, "ui-mode-not-available", error);
|
||||
return;
|
||||
}
|
||||
if (this.action && hasTemplate(this.action)) {
|
||||
fireEvent(
|
||||
this,
|
||||
"ui-mode-not-available",
|
||||
Error(this.hass.localize("ui.errors.config.no_template_editor_support"))
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (this.action.entity_id) {
|
||||
this._action = {
|
||||
|
||||
@@ -63,7 +63,11 @@ export default class HaAutomationConditionEditor extends LitElement {
|
||||
)}
|
||||
`
|
||||
: ""}
|
||||
<h2>Edit in YAML</h2>
|
||||
<h2>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.edit_yaml"
|
||||
)}
|
||||
</h2>
|
||||
<ha-yaml-editor
|
||||
.defaultValue=${this.condition}
|
||||
@value-changed=${this._onYamlChange}
|
||||
|
||||
@@ -197,7 +197,11 @@ export class HaAutomationEditor extends KeyboardShortcutMixin(LitElement) {
|
||||
${this.narrow
|
||||
? html` <span slot="header">${this._config?.alias}</span> `
|
||||
: ""}
|
||||
<div class="content">
|
||||
<div
|
||||
class="content ${classMap({
|
||||
"yaml-mode": this._mode === "yaml",
|
||||
})}"
|
||||
>
|
||||
${this._errors
|
||||
? html` <div class="errors">${this._errors}</div> `
|
||||
: ""}
|
||||
@@ -223,52 +227,52 @@ export class HaAutomationEditor extends KeyboardShortcutMixin(LitElement) {
|
||||
`
|
||||
: this._mode === "yaml"
|
||||
? html`
|
||||
<ha-config-section .isWide=${false}>
|
||||
${!this.narrow
|
||||
? html`
|
||||
<span slot="header">${this._config.alias}</span>
|
||||
`
|
||||
: ``}
|
||||
<ha-card>
|
||||
<div class="card-content">
|
||||
<ha-yaml-editor
|
||||
.defaultValue=${this._preprocessYaml()}
|
||||
@value-changed=${this._yamlChanged}
|
||||
></ha-yaml-editor>
|
||||
<mwc-button @click=${this._copyYaml}>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.copy_to_clipboard"
|
||||
)}
|
||||
</mwc-button>
|
||||
</div>
|
||||
${stateObj
|
||||
? html`
|
||||
<div
|
||||
class="card-actions layout horizontal justified center"
|
||||
>
|
||||
<div class="layout horizontal center">
|
||||
<ha-entity-toggle
|
||||
.hass=${this.hass}
|
||||
.stateObj=${stateObj}
|
||||
></ha-entity-toggle>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.enable_disable"
|
||||
)}
|
||||
</div>
|
||||
<mwc-button
|
||||
@click=${this._runActions}
|
||||
.stateObj=${stateObj}
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.card.automation.trigger"
|
||||
)}
|
||||
</mwc-button>
|
||||
</div>
|
||||
`
|
||||
: ""}
|
||||
</ha-card>
|
||||
<ha-config-section> </ha-config-section
|
||||
></ha-config-section>
|
||||
${!this.narrow
|
||||
? html`
|
||||
<ha-card
|
||||
><div class="card-header">
|
||||
${this._config.alias}
|
||||
</div>
|
||||
${stateObj
|
||||
? html`
|
||||
<div
|
||||
class="card-actions layout horizontal justified center"
|
||||
>
|
||||
<ha-entity-toggle
|
||||
.hass=${this.hass}
|
||||
.stateObj=${stateObj}
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.enable_disable"
|
||||
)}
|
||||
></ha-entity-toggle>
|
||||
|
||||
<mwc-button
|
||||
@click=${this._runActions}
|
||||
.stateObj=${stateObj}
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.card.automation.trigger"
|
||||
)}
|
||||
</mwc-button>
|
||||
</div>
|
||||
`
|
||||
: ""}
|
||||
</ha-card>
|
||||
`
|
||||
: ``}
|
||||
<ha-yaml-editor
|
||||
.defaultValue=${this._preprocessYaml()}
|
||||
@value-changed=${this._yamlChanged}
|
||||
></ha-yaml-editor>
|
||||
<ha-card
|
||||
><div class="card-actions">
|
||||
<mwc-button @click=${this._copyYaml}>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.copy_to_clipboard"
|
||||
)}
|
||||
</mwc-button>
|
||||
</div>
|
||||
</ha-card>
|
||||
`
|
||||
: ``}
|
||||
</div>
|
||||
@@ -531,6 +535,22 @@ export class HaAutomationEditor extends KeyboardShortcutMixin(LitElement) {
|
||||
.content {
|
||||
padding-bottom: 20px;
|
||||
}
|
||||
.yaml-mode {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
ha-yaml-editor {
|
||||
flex-grow: 1;
|
||||
--code-mirror-height: 100%;
|
||||
min-height: 0;
|
||||
}
|
||||
.yaml-mode ha-card {
|
||||
overflow: initial;
|
||||
--ha-card-border-radius: 0;
|
||||
border-bottom: 1px solid var(--divider-color);
|
||||
}
|
||||
p {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
@@ -136,7 +136,11 @@ export default class HaAutomationTriggerRow extends LitElement {
|
||||
)}
|
||||
`
|
||||
: ""}
|
||||
<h2>Edit in YAML</h2>
|
||||
<h2>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.edit_yaml"
|
||||
)}
|
||||
</h2>
|
||||
<ha-yaml-editor
|
||||
.defaultValue=${this.trigger}
|
||||
@value-changed=${this._onYamlChange}
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
import "@material/mwc-icon-button";
|
||||
import { mdiDelete, mdiDownload, mdiHelpCircle, mdiRobot } from "@mdi/js";
|
||||
import {
|
||||
mdiDelete,
|
||||
mdiDownload,
|
||||
mdiHelpCircle,
|
||||
mdiRobot,
|
||||
mdiShareVariant,
|
||||
} from "@mdi/js";
|
||||
import "@polymer/paper-tooltip/paper-tooltip";
|
||||
import {
|
||||
CSSResult,
|
||||
@@ -140,6 +146,24 @@ class HaBlueprintOverview extends LitElement {
|
||||
)}
|
||||
</mwc-button>`,
|
||||
},
|
||||
share: {
|
||||
title: "",
|
||||
type: "icon-button",
|
||||
template: (_, blueprint: any) =>
|
||||
blueprint.error
|
||||
? ""
|
||||
: html`<mwc-icon-button
|
||||
.blueprint=${blueprint}
|
||||
.disabled=${!blueprint.source_url}
|
||||
.label=${this.hass.localize(
|
||||
blueprint.source_url
|
||||
? "ui.panel.config.blueprint.overview.share_blueprint"
|
||||
: "ui.panel.config.blueprint.overview.share_blueprint_no_url"
|
||||
)}
|
||||
@click=${(ev) => this._share(ev)}
|
||||
><ha-svg-icon .path=${mdiShareVariant}></ha-svg-icon
|
||||
></mwc-icon-button>`,
|
||||
},
|
||||
delete: {
|
||||
title: "",
|
||||
type: "icon-button",
|
||||
@@ -262,6 +286,16 @@ class HaBlueprintOverview extends LitElement {
|
||||
createNewFunctions[blueprint.domain](this, blueprint);
|
||||
}
|
||||
|
||||
private _share(ev) {
|
||||
const blueprint = ev.currentTarget.blueprint;
|
||||
const params = new URLSearchParams();
|
||||
params.append("redirect", "blueprint_import");
|
||||
params.append("blueprint_url", blueprint.source_url);
|
||||
window.open(
|
||||
`https://my.home-assistant.io/create-link/?${params.toString()}`
|
||||
);
|
||||
}
|
||||
|
||||
private async _delete(ev) {
|
||||
const blueprint = ev.currentTarget.blueprint;
|
||||
if (
|
||||
|
||||
+10
@@ -21,6 +21,7 @@ import { haStyle } from "../../../../../../resources/styles";
|
||||
import { HomeAssistant } from "../../../../../../types";
|
||||
import { showZHAClusterDialog } from "../../../../integrations/integration-panels/zha/show-dialog-zha-cluster";
|
||||
import { showZHADeviceZigbeeInfoDialog } from "../../../../integrations/integration-panels/zha/show-dialog-zha-device-zigbee-info";
|
||||
import { showZHADeviceChildrenDialog } from "../../../../integrations/integration-panels/zha/show-dialog-zha-device-children";
|
||||
|
||||
@customElement("ha-device-actions-zha")
|
||||
export class HaDeviceActionsZha extends LitElement {
|
||||
@@ -65,6 +66,11 @@ export class HaDeviceActionsZha extends LitElement {
|
||||
<mwc-button @click=${this._onAddDevicesClick}>
|
||||
${this.hass!.localize("ui.dialogs.zha_device_info.buttons.add")}
|
||||
</mwc-button>
|
||||
<mwc-button @click=${this._handleDeviceChildrenClicked}>
|
||||
${this.hass!.localize(
|
||||
"ui.dialogs.zha_device_info.buttons.device_children"
|
||||
)}
|
||||
</mwc-button>
|
||||
`
|
||||
: ""}
|
||||
${this._zhaDevice.device_type !== "Coordinator"
|
||||
@@ -120,6 +126,10 @@ export class HaDeviceActionsZha extends LitElement {
|
||||
showZHADeviceZigbeeInfoDialog(this, { device: this._zhaDevice! });
|
||||
}
|
||||
|
||||
private async _handleDeviceChildrenClicked() {
|
||||
showZHADeviceChildrenDialog(this, { device: this._zhaDevice! });
|
||||
}
|
||||
|
||||
private async _removeDevice() {
|
||||
const confirmed = await showConfirmationDialog(this, {
|
||||
text: this.hass.localize(
|
||||
|
||||
@@ -161,8 +161,8 @@ export class HaConfigDevicePage extends LitElement {
|
||||
const batteryState = batteryEntity
|
||||
? this.hass.states[batteryEntity.entity_id]
|
||||
: undefined;
|
||||
const batteryIsBinary = batteryState
|
||||
&& computeStateDomain(batteryState) === "binary_sensor";
|
||||
const batteryIsBinary =
|
||||
batteryState && computeStateDomain(batteryState) === "binary_sensor";
|
||||
const batteryChargingState = batteryChargingEntity
|
||||
? this.hass.states[batteryChargingEntity.entity_id]
|
||||
: undefined;
|
||||
@@ -181,15 +181,17 @@ export class HaConfigDevicePage extends LitElement {
|
||||
<span slot="header">
|
||||
${computeDeviceName(device, this.hass)}
|
||||
</span>
|
||||
<ha-icon-button
|
||||
slot="toolbar-icon"
|
||||
icon="hass:pencil"
|
||||
@click=${this._showSettings}
|
||||
></ha-icon-button>
|
||||
`
|
||||
: ""
|
||||
}
|
||||
|
||||
<ha-icon-button
|
||||
slot="toolbar-icon"
|
||||
icon="hass:cog"
|
||||
@click=${this._showSettings}
|
||||
></ha-icon-button>
|
||||
|
||||
|
||||
|
||||
<div class="container">
|
||||
<div class="header fullwidth">
|
||||
@@ -197,19 +199,25 @@ export class HaConfigDevicePage extends LitElement {
|
||||
this.narrow
|
||||
? ""
|
||||
: html`
|
||||
<div>
|
||||
<h1>${computeDeviceName(device, this.hass)}</h1>
|
||||
${area
|
||||
? html`
|
||||
<a href="/config/areas/area/${area.area_id}"
|
||||
>${this.hass.localize(
|
||||
"ui.panel.config.integrations.config_entry.area",
|
||||
"area",
|
||||
area.name || "Unnamed Area"
|
||||
)}</a
|
||||
>
|
||||
`
|
||||
: ""}
|
||||
<div class="header-name">
|
||||
<div>
|
||||
<h1>${computeDeviceName(device, this.hass)}</h1>
|
||||
${area
|
||||
? html`
|
||||
<a href="/config/areas/area/${area.area_id}"
|
||||
>${this.hass.localize(
|
||||
"ui.panel.config.integrations.config_entry.area",
|
||||
"area",
|
||||
area.name || "Unnamed Area"
|
||||
)}</a
|
||||
>
|
||||
`
|
||||
: ""}
|
||||
</div>
|
||||
<ha-icon-button
|
||||
icon="hass:pencil"
|
||||
@click=${this._showSettings}
|
||||
></ha-icon-button>
|
||||
</div>
|
||||
`
|
||||
}
|
||||
@@ -218,7 +226,7 @@ export class HaConfigDevicePage extends LitElement {
|
||||
batteryState
|
||||
? html`
|
||||
<div class="battery">
|
||||
${batteryIsBinary ? "" : batteryState.state + "%"}
|
||||
${batteryIsBinary ? "" : batteryState.state + " %"}
|
||||
<ha-battery-icon
|
||||
.hass=${this.hass!}
|
||||
.batteryStateObj=${batteryState}
|
||||
@@ -780,6 +788,12 @@ export class HaConfigDevicePage extends LitElement {
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.header-name {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding-left: 8px;
|
||||
}
|
||||
|
||||
.column,
|
||||
.fullwidth {
|
||||
padding: 8px;
|
||||
|
||||
@@ -283,8 +283,8 @@ export class HaConfigDeviceDashboard extends LitElement {
|
||||
title: this.hass.localize("ui.panel.config.devices.data_table.battery"),
|
||||
sortable: true,
|
||||
type: "numeric",
|
||||
width: narrow ? "90px" : "15%",
|
||||
maxWidth: "90px",
|
||||
width: narrow ? "95px" : "15%",
|
||||
maxWidth: "95px",
|
||||
template: (batteryEntityPair: DeviceRowData["battery_entity"]) => {
|
||||
const battery =
|
||||
batteryEntityPair && batteryEntityPair[0]
|
||||
@@ -298,7 +298,7 @@ export class HaConfigDeviceDashboard extends LitElement {
|
||||
battery && computeStateDomain(battery) === "binary_sensor";
|
||||
return battery && (batteryIsBinary || !isNaN(battery.state as any))
|
||||
? html`
|
||||
${batteryIsBinary ? "" : battery.state + "%"}
|
||||
${batteryIsBinary ? "" : battery.state + " %"}
|
||||
<ha-battery-icon
|
||||
.hass=${this.hass!}
|
||||
.batteryStateObj=${battery}
|
||||
|
||||
@@ -0,0 +1,146 @@
|
||||
import {
|
||||
CSSResult,
|
||||
customElement,
|
||||
html,
|
||||
internalProperty,
|
||||
LitElement,
|
||||
property,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { computeRTLDirection } from "../../../../../common/util/compute_rtl";
|
||||
import "../../../../../components/ha-code-editor";
|
||||
import { createCloseHeading } from "../../../../../components/ha-dialog";
|
||||
import { haStyleDialog } from "../../../../../resources/styles";
|
||||
import { HomeAssistant } from "../../../../../types";
|
||||
import { ZHADeviceChildrenDialogParams } from "./show-dialog-zha-device-children";
|
||||
import "../../../../../components/data-table/ha-data-table";
|
||||
import type {
|
||||
DataTableColumnContainer,
|
||||
DataTableRowData,
|
||||
} from "../../../../../components/data-table/ha-data-table";
|
||||
import "../../../../../components/ha-circular-progress";
|
||||
import { fetchDevices, ZHADevice } from "../../../../../data/zha";
|
||||
import { fireEvent } from "../../../../../common/dom/fire_event";
|
||||
|
||||
export interface DeviceRowData extends DataTableRowData {
|
||||
id: string;
|
||||
name: string;
|
||||
lqi: number;
|
||||
}
|
||||
|
||||
@customElement("dialog-zha-device-children")
|
||||
class DialogZHADeviceChildren extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@internalProperty() private _device: ZHADevice | undefined;
|
||||
|
||||
@internalProperty() private _devices: Map<string, ZHADevice> | undefined;
|
||||
|
||||
private _deviceChildren = memoizeOne(
|
||||
(
|
||||
device: ZHADevice | undefined,
|
||||
devices: Map<string, ZHADevice> | undefined
|
||||
) => {
|
||||
const outputDevices: DeviceRowData[] = [];
|
||||
if (device && devices) {
|
||||
device.neighbors.forEach((child) => {
|
||||
const zhaDevice: ZHADevice | undefined = devices.get(child.ieee);
|
||||
if (zhaDevice) {
|
||||
outputDevices.push({
|
||||
name: zhaDevice.user_given_name || zhaDevice.name,
|
||||
id: zhaDevice.device_reg_id,
|
||||
lqi: child.lqi,
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
return outputDevices;
|
||||
}
|
||||
);
|
||||
|
||||
private _columns: DataTableColumnContainer = {
|
||||
name: {
|
||||
title: "Name",
|
||||
sortable: true,
|
||||
filterable: true,
|
||||
direction: "asc",
|
||||
grows: true,
|
||||
},
|
||||
lqi: {
|
||||
title: "LQI",
|
||||
sortable: true,
|
||||
filterable: true,
|
||||
direction: "asc",
|
||||
width: "75px",
|
||||
},
|
||||
};
|
||||
|
||||
public showDialog(
|
||||
params: ZHADeviceChildrenDialogParams
|
||||
): void {
|
||||
this._device = params.device;
|
||||
this._fetchData();
|
||||
}
|
||||
|
||||
public closeDialog(): void {
|
||||
this._device = undefined;
|
||||
this._devices = undefined;
|
||||
fireEvent(this, "dialog-closed", { dialog: this.localName });
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
if (!this._device) {
|
||||
return html``;
|
||||
}
|
||||
return html`
|
||||
<ha-dialog
|
||||
hideActions
|
||||
open
|
||||
@closed=${this.closeDialog}
|
||||
.heading=${createCloseHeading(
|
||||
this.hass,
|
||||
this.hass.localize(`ui.dialogs.zha_device_info.device_children`)
|
||||
)}
|
||||
>
|
||||
${!this._devices
|
||||
? html`<ha-circular-progress
|
||||
alt="Loading"
|
||||
size="large"
|
||||
active
|
||||
></ha-circular-progress>`
|
||||
: html`<ha-data-table
|
||||
.columns=${this._columns}
|
||||
.data=${this._deviceChildren(this._device, this._devices)}
|
||||
auto-height
|
||||
.dir=${computeRTLDirection(this.hass)}
|
||||
.searchLabel=${this.hass.localize(
|
||||
"ui.components.data-table.search"
|
||||
)}
|
||||
.noDataText=${this.hass.localize(
|
||||
"ui.components.data-table.no-data"
|
||||
)}
|
||||
></ha-data-table>`}
|
||||
</ha-dialog>
|
||||
`;
|
||||
}
|
||||
|
||||
private async _fetchData(): Promise<void> {
|
||||
if (this._device && this.hass) {
|
||||
const devices = await fetchDevices(this.hass!);
|
||||
this._devices = new Map(
|
||||
devices.map((device: ZHADevice) => [device.ieee, device])
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
static get styles(): CSSResult {
|
||||
return haStyleDialog;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"dialog-zha-device-children": DialogZHADeviceChildren;
|
||||
}
|
||||
}
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
import { fireEvent } from "../../../../../common/dom/fire_event";
|
||||
import { ZHADevice } from "../../../../../data/zha";
|
||||
|
||||
export interface ZHADeviceChildrenDialogParams {
|
||||
device: ZHADevice;
|
||||
}
|
||||
|
||||
export const loadZHADeviceChildrenDialog = () =>
|
||||
import("./dialog-zha-device-children");
|
||||
|
||||
export const showZHADeviceChildrenDialog = (
|
||||
element: HTMLElement,
|
||||
zhaDeviceChildrenParams: ZHADeviceChildrenDialogParams
|
||||
): void => {
|
||||
fireEvent(element, "show-dialog", {
|
||||
dialogTag: "dialog-zha-device-children",
|
||||
dialogImport: loadZHADeviceChildrenDialog,
|
||||
dialogParams: zhaDeviceChildrenParams,
|
||||
});
|
||||
};
|
||||
+27
@@ -27,6 +27,8 @@ import "../../../../../components/ha-svg-icon";
|
||||
import { PolymerChangedEvent } from "../../../../../polymer-types";
|
||||
import { formatAsPaddedHex } from "./functions";
|
||||
import { DeviceRegistryEntry } from "../../../../../data/device_registry";
|
||||
import "../../../../../components/ha-checkbox";
|
||||
import type { HaCheckbox } from "../../../../../components/ha-checkbox";
|
||||
|
||||
@customElement("zha-network-visualization-page")
|
||||
export class ZHANetworkVisualizationPage extends LitElement {
|
||||
@@ -55,11 +57,15 @@ export class ZHANetworkVisualizationPage extends LitElement {
|
||||
@internalProperty()
|
||||
private _filter?: string;
|
||||
|
||||
private _autoZoom = true;
|
||||
|
||||
protected firstUpdated(changedProperties: PropertyValues): void {
|
||||
super.firstUpdated(changedProperties);
|
||||
|
||||
if (this.hass) {
|
||||
this._fetchData();
|
||||
}
|
||||
|
||||
this._network = new Network(
|
||||
this._visualization!,
|
||||
{},
|
||||
@@ -92,6 +98,7 @@ export class ZHANetworkVisualizationPage extends LitElement {
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
this._network.on("doubleClick", (properties) => {
|
||||
const ieee = properties.nodes[0];
|
||||
if (ieee) {
|
||||
@@ -106,6 +113,17 @@ export class ZHANetworkVisualizationPage extends LitElement {
|
||||
}
|
||||
});
|
||||
|
||||
this._network.on("click", (properties) => {
|
||||
const ieee = properties.nodes[0];
|
||||
if (ieee) {
|
||||
const device = this._devices.get(ieee);
|
||||
if (device && this._autoZoom) {
|
||||
this.zoomedDeviceId = device.device_reg_id;
|
||||
this._zoomToDevice();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
this._network.on("stabilized", () => {
|
||||
if (this.zoomedDeviceId) {
|
||||
this._zoomToDevice();
|
||||
@@ -141,6 +159,11 @@ export class ZHANetworkVisualizationPage extends LitElement {
|
||||
.deviceFilter=${(device) => this._filterDevices(device)}
|
||||
@value-changed=${this._onZoomToDevice}
|
||||
></ha-device-picker>
|
||||
<ha-checkbox
|
||||
@change=${this._handleCheckboxChange}
|
||||
.checked=${this._autoZoom}
|
||||
></ha-checkbox
|
||||
>${this.hass!.localize("ui.panel.config.zha.visualization.auto_zoom")}
|
||||
<mwc-button @click=${this._refreshTopology}
|
||||
>${this.hass!.localize(
|
||||
"ui.panel.config.zha.visualization.refresh_topology"
|
||||
@@ -325,6 +348,10 @@ export class ZHANetworkVisualizationPage extends LitElement {
|
||||
return false;
|
||||
}
|
||||
|
||||
private _handleCheckboxChange(ev: Event) {
|
||||
this._autoZoom = (ev.target as HaCheckbox).checked;
|
||||
}
|
||||
|
||||
static get styles(): CSSResult[] {
|
||||
return [
|
||||
css`
|
||||
|
||||
@@ -72,11 +72,7 @@ class DialogSystemLogDetail extends LitElement {
|
||||
<ha-svg-icon .path=${mdiClose}></ha-svg-icon>
|
||||
</mwc-icon-button>
|
||||
<span slot="title">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.logs.details",
|
||||
"level",
|
||||
item.level
|
||||
)}
|
||||
${this.hass.localize("ui.panel.config.logs.details", "level", html`<span class="${item.level.toLowerCase()}">${item.level}</span>`)}
|
||||
</span>
|
||||
<mwc-icon-button id="copy" @click=${this._copyLog} slot="actionItems">
|
||||
<ha-svg-icon .path=${mdiContentCopy}></ha-svg-icon>
|
||||
@@ -176,6 +172,12 @@ class DialogSystemLogDetail extends LitElement {
|
||||
margin-bottom: 0;
|
||||
font-family: var(--code-font-family, monospace);
|
||||
}
|
||||
.error {
|
||||
color: var(--error-color);
|
||||
}
|
||||
.warning {
|
||||
color: var(--warning-color);
|
||||
}
|
||||
|
||||
ha-header-bar {
|
||||
--mdc-theme-on-primary: var(--primary-text-color);
|
||||
|
||||
@@ -71,15 +71,15 @@ export class SystemLogCard extends LitElement {
|
||||
this.hass!.language
|
||||
)}
|
||||
–
|
||||
${html`(<span class="${item.level.toLowerCase()}"
|
||||
>${item.level}</span
|
||||
>) `}
|
||||
${integrations[idx]
|
||||
? domainToName(
|
||||
this.hass!.localize,
|
||||
integrations[idx]!
|
||||
)
|
||||
: item.source[0]}
|
||||
${html`(<span class="${item.level.toLowerCase()}"
|
||||
>${item.level}</span
|
||||
>)`}
|
||||
${item.count > 1
|
||||
? html`
|
||||
-
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import "@material/mwc-button";
|
||||
import {
|
||||
mdiInformationOutline,
|
||||
mdiClipboardTextMultipleOutline
|
||||
mdiClipboardTextMultipleOutline,
|
||||
} from "@mdi/js";
|
||||
import "@polymer/paper-checkbox/paper-checkbox";
|
||||
import "@polymer/paper-input/paper-input";
|
||||
@@ -169,7 +169,10 @@ class HaPanelDevState extends EventsMixin(LocalizeMixin(PolymerElement)) {
|
||||
<th>[[localize('ui.panel.developer-tools.tabs.states.state')]]</th>
|
||||
<th hidden$="[[narrow]]">
|
||||
[[localize('ui.panel.developer-tools.tabs.states.attributes')]]
|
||||
<paper-checkbox checked="{{_showAttributes}}" on-change="{{saveAttributeCheckboxState}}"></paper-checkbox>
|
||||
<paper-checkbox
|
||||
checked="{{_showAttributes}}"
|
||||
on-change="saveAttributeCheckboxState"
|
||||
></paper-checkbox>
|
||||
</th>
|
||||
</tr>
|
||||
<tr>
|
||||
@@ -285,7 +288,9 @@ class HaPanelDevState extends EventsMixin(LocalizeMixin(PolymerElement)) {
|
||||
|
||||
_showAttributes: {
|
||||
type: Boolean,
|
||||
value: JSON.parse(localStorage.getItem("devToolsShowAttributes") || true),
|
||||
value: JSON.parse(
|
||||
localStorage.getItem("devToolsShowAttributes") || true
|
||||
),
|
||||
},
|
||||
|
||||
_entities: {
|
||||
|
||||
@@ -191,6 +191,9 @@ class HuiAlarmPanelCard extends LitElement implements LovelaceCard {
|
||||
id="alarmCode"
|
||||
.label=${this.hass.localize("ui.card.alarm_control_panel.code")}
|
||||
type="password"
|
||||
.inputmode=${stateObj.attributes.code_format === FORMAT_NUMBER
|
||||
? "numeric"
|
||||
: "text"}
|
||||
></paper-input>
|
||||
`}
|
||||
${stateObj.attributes.code_format !== FORMAT_NUMBER
|
||||
|
||||
@@ -272,7 +272,10 @@ export class HuiButtonCard extends LitElement implements LovelaceCard {
|
||||
height: auto;
|
||||
color: var(--paper-item-icon-color, #44739e);
|
||||
--mdc-icon-size: 100%;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
ha-icon + span {
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
ha-icon,
|
||||
|
||||
@@ -199,7 +199,10 @@ class HuiPictureEntityCard extends LitElement implements LovelaceCard {
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: var(--ha-picture-card-background-color, rgba(0, 0, 0, 0.3));
|
||||
background-color: var(
|
||||
--ha-picture-card-background-color,
|
||||
rgba(0, 0, 0, 0.3)
|
||||
);
|
||||
padding: 16px;
|
||||
font-size: 16px;
|
||||
line-height: 16px;
|
||||
|
||||
@@ -314,7 +314,10 @@ class HuiPictureGlanceCard extends LitElement implements LovelaceCard {
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: var(--ha-picture-card-background-color, rgba(0, 0, 0, 0.3));
|
||||
background-color: var(
|
||||
--ha-picture-card-background-color,
|
||||
rgba(0, 0, 0, 0.3)
|
||||
);
|
||||
padding: 4px 8px;
|
||||
font-size: 16px;
|
||||
line-height: 40px;
|
||||
|
||||
@@ -129,7 +129,7 @@ class HuiGenericEntityRow extends LitElement {
|
||||
stateObj.attributes.brightness
|
||||
? html`${Math.round(
|
||||
(stateObj.attributes.brightness / 255) * 100
|
||||
)}%`
|
||||
)} %`
|
||||
: "")}
|
||||
</div>
|
||||
`
|
||||
|
||||
@@ -234,9 +234,13 @@ export abstract class HuiElementEditor<T> extends LitElement {
|
||||
<div class="warning">
|
||||
${this.hass.localize("ui.errors.config.editor_not_supported")}:
|
||||
<br />
|
||||
<ul>
|
||||
${this._warnings!.map((warning) => html`<li>${warning}</li>`)}
|
||||
</ul>
|
||||
${this._warnings!.length > 0 && this._warnings![0] !== undefined
|
||||
? html` <ul>
|
||||
${this._warnings!.map(
|
||||
(warning) => html`<li>${warning}</li>`
|
||||
)}
|
||||
</ul>`
|
||||
: ""}
|
||||
${this.hass.localize("ui.errors.config.edit_in_yaml_supported")}
|
||||
</div>
|
||||
`
|
||||
@@ -359,6 +363,9 @@ export abstract class HuiElementEditor<T> extends LitElement {
|
||||
.yaml-editor {
|
||||
padding: 8px 0px;
|
||||
}
|
||||
ha-code-editor {
|
||||
--code-mirror-max-height: calc(100vh - 245px);
|
||||
}
|
||||
.error,
|
||||
.warning,
|
||||
.info {
|
||||
|
||||
@@ -52,7 +52,7 @@ class HuiHumidifierEntityRow extends LitElement implements LovelaceRow {
|
||||
.config=${this._config}
|
||||
.secondaryText=${stateObj.attributes.humidity
|
||||
? `${this.hass!.localize("ui.card.humidifier.humidity")}:
|
||||
${stateObj.attributes.humidity}%${
|
||||
${stateObj.attributes.humidity} %${
|
||||
stateObj.attributes.mode
|
||||
? ` (${
|
||||
this.hass!.localize(
|
||||
|
||||
@@ -81,7 +81,7 @@ class LovelaceFullConfigEditor extends LitElement {
|
||||
</div>
|
||||
<mwc-button
|
||||
raised
|
||||
@click="${this._handleSave}"
|
||||
@click=${this._handleSave}
|
||||
.disabled=${!this._changed}
|
||||
>${this.hass!.localize(
|
||||
"ui.panel.lovelace.editor.raw_editor.save"
|
||||
@@ -95,8 +95,8 @@ class LovelaceFullConfigEditor extends LitElement {
|
||||
autofocus
|
||||
.rtl=${computeRTL(this.hass)}
|
||||
.hass=${this.hass}
|
||||
@value-changed="${this._yamlChanged}"
|
||||
@editor-save="${this._handleSave}"
|
||||
@value-changed=${this._yamlChanged}
|
||||
@editor-save=${this._handleSave}
|
||||
>
|
||||
</ha-code-editor>
|
||||
</div>
|
||||
@@ -112,6 +112,7 @@ class LovelaceFullConfigEditor extends LitElement {
|
||||
protected updated(changedProps: PropertyValues) {
|
||||
const oldLovelace = changedProps.get("lovelace") as Lovelace | undefined;
|
||||
if (
|
||||
!this._saving &&
|
||||
oldLovelace &&
|
||||
this.lovelace &&
|
||||
oldLovelace.config !== this.lovelace.config &&
|
||||
@@ -247,7 +248,7 @@ class LovelaceFullConfigEditor extends LitElement {
|
||||
return;
|
||||
}
|
||||
|
||||
if (/^#|\s#/gm.test(value)) {
|
||||
if (this.yamlEditor.hasComments) {
|
||||
if (
|
||||
!confirm(
|
||||
this.hass.localize(
|
||||
@@ -305,8 +306,8 @@ class LovelaceFullConfigEditor extends LitElement {
|
||||
});
|
||||
}
|
||||
window.onbeforeunload = null;
|
||||
this._saving = false;
|
||||
this._changed = false;
|
||||
this._saving = false;
|
||||
}
|
||||
|
||||
private get yamlEditor(): HaCodeEditor {
|
||||
|
||||
+46
-27
@@ -1,23 +1,33 @@
|
||||
import { HighlightStyle, tags } from "@codemirror/highlight";
|
||||
import { EditorView as CMEditorView, KeyBinding } from "@codemirror/view";
|
||||
import { EditorView, KeyBinding } from "@codemirror/view";
|
||||
import { StreamLanguage } from "@codemirror/stream-parser";
|
||||
import { jinja2 } from "@codemirror/legacy-modes/mode/jinja2";
|
||||
import { yaml } from "@codemirror/legacy-modes/mode/yaml";
|
||||
import { indentLess, indentMore } from "@codemirror/commands";
|
||||
import { Compartment } from "@codemirror/state";
|
||||
|
||||
export { keymap } from "@codemirror/view";
|
||||
export { CMEditorView as EditorView };
|
||||
export { EditorState, Prec, tagExtension } from "@codemirror/state";
|
||||
export {
|
||||
keymap,
|
||||
highlightActiveLine,
|
||||
drawSelection,
|
||||
EditorView,
|
||||
} from "@codemirror/view";
|
||||
export { EditorState, Prec } from "@codemirror/state";
|
||||
export { defaultKeymap } from "@codemirror/commands";
|
||||
export { lineNumbers } from "@codemirror/gutter";
|
||||
export { searchKeymap, highlightSelectionMatches } from "@codemirror/search";
|
||||
export { history, historyKeymap } from "@codemirror/history";
|
||||
export { rectangularSelection } from "@codemirror/rectangular-selection";
|
||||
export { HighlightStyle, tags } from "@codemirror/highlight";
|
||||
|
||||
export const langs = {
|
||||
jinja2: StreamLanguage.define(jinja2),
|
||||
yaml: StreamLanguage.define(yaml),
|
||||
};
|
||||
|
||||
export const langCompartment = new Compartment();
|
||||
export const readonlyCompartment = new Compartment();
|
||||
|
||||
export const tabKeyBindings: KeyBinding[] = [
|
||||
{ key: "Tab", run: indentMore },
|
||||
{
|
||||
@@ -26,36 +36,45 @@ export const tabKeyBindings: KeyBinding[] = [
|
||||
},
|
||||
];
|
||||
|
||||
export const theme = CMEditorView.theme({
|
||||
$: {
|
||||
export const theme = EditorView.theme({
|
||||
"&": {
|
||||
color: "var(--primary-text-color)",
|
||||
backgroundColor:
|
||||
"var(--code-editor-background-color, var(--card-background-color))",
|
||||
"& ::selection": { backgroundColor: "rgba(var(--rgb-primary-color), 0.3)" },
|
||||
caretColor: "var(--secondary-text-color)",
|
||||
height: "var(--code-mirror-height, auto)",
|
||||
maxHeight: "var(--code-mirror-max-height, unset)",
|
||||
},
|
||||
|
||||
$scroller: { outline: "none" },
|
||||
"&.cm-focused": { outline: "none" },
|
||||
|
||||
$content: { caretColor: "var(--secondary-text-color)" },
|
||||
"&.cm-focused .cm-cursor": {
|
||||
borderLeftColor: "var(--secondary-text-color)",
|
||||
},
|
||||
|
||||
$$focused: { outline: "none" },
|
||||
|
||||
"$$focused $cursor": { borderLeftColor: "#var(--secondary-text-color)" },
|
||||
"$$focused $selectionBackground, $selectionBackground": {
|
||||
"&.cm-focused .cm-selectionBackground, .cm-selectionBackground": {
|
||||
backgroundColor: "rgba(var(--rgb-primary-color), 0.3)",
|
||||
},
|
||||
|
||||
$panels: {
|
||||
".cm-activeLine": {
|
||||
backgroundColor: "rgba(var(--rgb-secondary-text-color), 0.1)",
|
||||
},
|
||||
|
||||
".cm-scroller": { outline: "none" },
|
||||
|
||||
".cm-content": { caretColor: "var(--secondary-text-color)" },
|
||||
|
||||
".cm-panels": {
|
||||
backgroundColor: "var(--primary-background-color)",
|
||||
color: "var(--primary-text-color)",
|
||||
},
|
||||
"$panels.top": { borderBottom: "1px solid var(--divider-color)" },
|
||||
"$panels.bottom": { borderTop: "1px solid var(--divider-color)" },
|
||||
".cm-panels.top": { borderBottom: "1px solid var(--divider-color)" },
|
||||
".cm-panels.bottom": { borderTop: "1px solid var(--divider-color)" },
|
||||
|
||||
"$panel.search input": { margin: "4px 4px 0" },
|
||||
".cm-panel.search input": { margin: "4px 4px 0" },
|
||||
|
||||
$button: {
|
||||
".cm-button": {
|
||||
border: "1px solid var(--primary-color)",
|
||||
padding: "0px 16px",
|
||||
textTransform: "uppercase",
|
||||
@@ -71,7 +90,7 @@ export const theme = CMEditorView.theme({
|
||||
letterSpacing: "var(--mdc-typography-button-letter-spacing, 0.0892857em)",
|
||||
},
|
||||
|
||||
$textfield: {
|
||||
".cm-textfield": {
|
||||
padding: "4px 0px 5px",
|
||||
borderRadius: "0",
|
||||
fontSize: "16px",
|
||||
@@ -92,20 +111,20 @@ export const theme = CMEditorView.theme({
|
||||
},
|
||||
},
|
||||
|
||||
$selectionMatch: {
|
||||
".cm-selectionMatch": {
|
||||
backgroundColor: "rgba(var(--rgb-primary-color), 0.1)",
|
||||
},
|
||||
|
||||
$searchMatch: {
|
||||
".cm-searchMatch": {
|
||||
backgroundColor: "rgba(var(--rgb-accent-color), .2)",
|
||||
outline: "1px solid rgba(var(--rgb-accent-color), .4)",
|
||||
},
|
||||
"$searchMatch.selected": {
|
||||
".cm-searchMatch.selected": {
|
||||
backgroundColor: "rgba(var(--rgb-accent-color), .4)",
|
||||
outline: "1px solid var(--accent-color)",
|
||||
},
|
||||
|
||||
$gutters: {
|
||||
".cm-gutters": {
|
||||
backgroundColor:
|
||||
"var(--paper-dialog-background-color, var(--primary-background-color))",
|
||||
color: "var(--paper-dialog-color, var(--secondary-text-color))",
|
||||
@@ -114,15 +133,15 @@ export const theme = CMEditorView.theme({
|
||||
"1px solid var(--paper-input-container-color, var(--secondary-text-color))",
|
||||
paddingRight: "1px",
|
||||
},
|
||||
"$$focused $gutters": {
|
||||
"&.cm-focused cm-gutters": {
|
||||
borderRight:
|
||||
"2px solid var(--paper-input-container-focus-color, var(--primary-color))",
|
||||
paddingRight: "0",
|
||||
},
|
||||
"$gutterElementags.lineNumber": { color: "inherit" },
|
||||
".cm-gutterElement.lineNumber": { color: "inherit" },
|
||||
});
|
||||
|
||||
export const highlightStyle = HighlightStyle.define(
|
||||
export const highlightStyle = HighlightStyle.define([
|
||||
{ tag: tags.keyword, color: "var(--codemirror-keyword, #6262FF)" },
|
||||
{
|
||||
tag: [
|
||||
@@ -193,5 +212,5 @@ export const highlightStyle = HighlightStyle.define(
|
||||
{ tag: tags.processingInstruction, color: "var(--secondary-text-color)" },
|
||||
{ tag: tags.string, color: "var(--codemirror-string, #07a)" },
|
||||
{ tag: tags.inserted, color: "var(--codemirror-string2, #07a)" },
|
||||
{ tag: tags.invalid, color: "var(--error-color)" }
|
||||
);
|
||||
{ tag: tags.invalid, color: "var(--error-color)" },
|
||||
]);
|
||||
|
||||
@@ -12,8 +12,8 @@ import { translationMetadata } from "../resources/translations-metadata";
|
||||
import { Constructor, HomeAssistant } from "../types";
|
||||
import { storeState } from "../util/ha-pref-storage";
|
||||
import {
|
||||
getLocalLanguage,
|
||||
getTranslation,
|
||||
getLocalLanguage,
|
||||
getUserLanguage,
|
||||
} from "../util/hass-translation";
|
||||
import { HassBaseEl } from "./hass-base-mixin";
|
||||
@@ -230,7 +230,7 @@ export default <T extends Constructor<HassBaseEl>>(superClass: T) =>
|
||||
}
|
||||
this.__loadedFragmetTranslations.add(fragment);
|
||||
const result = await getTranslation(fragment, language);
|
||||
this._updateResources(result.language, result.data);
|
||||
await this._updateResources(result.language, result.data);
|
||||
}
|
||||
|
||||
private async _loadCoreTranslations(language: string) {
|
||||
|
||||
+433
-15
@@ -611,6 +611,9 @@
|
||||
"updater": {
|
||||
"title": "Update Instructions"
|
||||
},
|
||||
"remote": {
|
||||
"activity": "Current activity"
|
||||
},
|
||||
"restored": {
|
||||
"not_provided": "This entity is currently unavailable and is an orphan to a removed, changed or dysfunctional integration or device.",
|
||||
"remove_intro": "If the entity is no longer in use, you can clean it up by removing it.",
|
||||
@@ -740,12 +743,14 @@
|
||||
"manuf": "by {manufacturer}",
|
||||
"no_area": "No Area",
|
||||
"device_signature": "Zigbee device signature",
|
||||
"device_children": "Zigbee device children",
|
||||
"buttons": {
|
||||
"add": "Add Devices via this device",
|
||||
"remove": "Remove Device",
|
||||
"clusters": "Manage Clusters",
|
||||
"reconfigure": "Reconfigure Device",
|
||||
"zigbee_information": "Zigbee device signature",
|
||||
"device_children": "View Children",
|
||||
"view_in_visualization": "View in Visualization"
|
||||
},
|
||||
"services": {
|
||||
@@ -797,7 +802,8 @@
|
||||
"edit_in_yaml_supported": "You can still edit your config in YAML.",
|
||||
"key_missing": "Required key \"{key}\" is missing.",
|
||||
"key_not_expected": "Key \"{key}\" is not expected or not supported by the visual editor.",
|
||||
"key_wrong_type": "The provided value for \"{key}\" is not supported by the visual editor. We support ({type_correct}) but received ({type_wrong})."
|
||||
"key_wrong_type": "The provided value for \"{key}\" is not supported by the visual editor. We support ({type_correct}) but received ({type_wrong}).",
|
||||
"no_template_editor_support": "Templates not supported in visual editor"
|
||||
}
|
||||
},
|
||||
"login-form": {
|
||||
@@ -832,7 +838,7 @@
|
||||
"no_supervisor": "This redirect is not supported by your Home Assistant installation. It needs either the Home Assistant Operating System or Home Assistant Supervised installation method. For more information, see the {docs_link}.",
|
||||
"documentation": "documentation",
|
||||
"faq_link": "My Home Assistant FAQ",
|
||||
"error": "An unknown error occured"
|
||||
"error": "An unknown error occurred"
|
||||
},
|
||||
"config": {
|
||||
"header": "Configure Home Assistant",
|
||||
@@ -1232,8 +1238,8 @@
|
||||
"queued": "Queue length",
|
||||
"parallel": "Max number of parallel runs"
|
||||
},
|
||||
"edit_yaml": "Edit as YAML",
|
||||
"edit_ui": "Edit with UI",
|
||||
"edit_yaml": "Edit in YAML",
|
||||
"edit_ui": "Edit in visual editor",
|
||||
"copy_to_clipboard": "Copy to Clipboard",
|
||||
"triggers": {
|
||||
"name": "Trigger",
|
||||
@@ -1244,7 +1250,7 @@
|
||||
"duplicate": "Duplicate",
|
||||
"delete": "[%key:ui::panel::mailbox::delete_button%]",
|
||||
"delete_confirm": "Are you sure you want to delete this?",
|
||||
"unsupported_platform": "No UI support for platform: {platform}",
|
||||
"unsupported_platform": "No visual editor support for platform: {platform}",
|
||||
"type_select": "Trigger type",
|
||||
"type": {
|
||||
"device": {
|
||||
@@ -1346,7 +1352,7 @@
|
||||
"duplicate": "[%key:ui::panel::config::automation::editor::triggers::duplicate%]",
|
||||
"delete": "[%key:ui::panel::mailbox::delete_button%]",
|
||||
"delete_confirm": "[%key:ui::panel::config::automation::editor::triggers::delete_confirm%]",
|
||||
"unsupported_condition": "No UI support for condition: {condition}",
|
||||
"unsupported_condition": "No visual editor support for condition: {condition}",
|
||||
"type_select": "Condition type",
|
||||
"type": {
|
||||
"and": {
|
||||
@@ -1422,7 +1428,7 @@
|
||||
"duplicate": "[%key:ui::panel::config::automation::editor::triggers::duplicate%]",
|
||||
"delete": "[%key:ui::panel::mailbox::delete_button%]",
|
||||
"delete_confirm": "[%key:ui::panel::config::automation::editor::triggers::delete_confirm%]",
|
||||
"unsupported_action": "No UI support for action: {action}",
|
||||
"unsupported_action": "No visual editor support for action: {action}",
|
||||
"type_select": "Action type",
|
||||
"type": {
|
||||
"service": {
|
||||
@@ -1528,6 +1534,8 @@
|
||||
"add_blueprint": "Import blueprint",
|
||||
"use_blueprint": "Create automation",
|
||||
"delete_blueprint": "Delete blueprint",
|
||||
"share_blueprint": "Share blueprint",
|
||||
"share_blueprint_no_url": "Unable to share blueprint: no source url",
|
||||
"discover_more": "Discover more blueprints"
|
||||
},
|
||||
"add": {
|
||||
@@ -2032,7 +2040,7 @@
|
||||
"new": "Set up a new integration",
|
||||
"confirm_new": "Do you want to set up {integration}?",
|
||||
"add_integration": "Add integration",
|
||||
"no_integrations": "Seems like you don't have any integations configured yet. Click on the button below to add your first integration!",
|
||||
"no_integrations": "Seems like you don't have any integrations configured yet. Click on the button below to add your first integration!",
|
||||
"note_about_integrations": "Not all integrations can be configured via the UI yet.",
|
||||
"note_about_website_reference": "More are available on the ",
|
||||
"home_assistant_website": "Home Assistant website",
|
||||
@@ -2094,7 +2102,7 @@
|
||||
"integration": "integration",
|
||||
"device": "device"
|
||||
},
|
||||
"disable_confirm": "Are you sure you want to disable this config entry? It's devices and entities will be disabled."
|
||||
"disable_confirm": "Are you sure you want to disable this config entry? Its devices and entities will be disabled."
|
||||
}
|
||||
},
|
||||
"config_flow": {
|
||||
@@ -2199,7 +2207,7 @@
|
||||
"node_query_stages": {
|
||||
"protocolinfo": "Obtaining basic Z-Wave capabilities of this node from the controller",
|
||||
"probe": "Checking if the node is awake/alive",
|
||||
"wakeup": "Setting up support for wakeup queues and messages",
|
||||
"wakeup": "Setting up support for wake-up queues and messages",
|
||||
"manufacturerspecific1": "Obtaining manufacturer and product ID codes from the node",
|
||||
"nodeinfo": "Obtaining supported command classes from the node",
|
||||
"nodeplusinfo": "Obtaining Z-Wave+ information from the node",
|
||||
@@ -2221,8 +2229,8 @@
|
||||
"complete": "Node Refresh Complete",
|
||||
"description": "This will tell OpenZWave to re-interview a node and update the node's command classes, capabilities, and values.",
|
||||
"battery_note": "If the node is battery powered, be sure to wake it before proceeding",
|
||||
"wakeup_header": "Wakeup Instructions for",
|
||||
"wakeup_instructions_source": "Wakeup instructions are sourced from the OpenZWave community device database.",
|
||||
"wakeup_header": "Wake-up Instructions for",
|
||||
"wakeup_instructions_source": "Wake-up instructions are sourced from the OpenZWave community device database.",
|
||||
"start_refresh_button": "Start Refresh",
|
||||
"refreshing_description": "Refreshing node information...",
|
||||
"node_status": "Node Status",
|
||||
@@ -2305,7 +2313,7 @@
|
||||
"spinner": "Searching for ZHA Zigbee devices...",
|
||||
"pairing_mode": "Make sure your devices are in pairing mode. Check the instructions of your device on how to do this.",
|
||||
"discovered_text": "Devices will show up here once discovered.",
|
||||
"no_devices_found": "No devices were found, make sure they are in paring mode and keep them awake while discovering is running.",
|
||||
"no_devices_found": "No devices were found, make sure they are in pairing mode and keep them awake while discovering is running.",
|
||||
"search_again": "Search Again"
|
||||
},
|
||||
"add_device": "Add Device",
|
||||
@@ -2367,6 +2375,7 @@
|
||||
"caption": "Visualization",
|
||||
"highlight_label": "Highlight Devices",
|
||||
"zoom_label": "Zoom To Device",
|
||||
"auto_zoom": "Auto Zoom",
|
||||
"refresh_topology": "Refresh Topology"
|
||||
},
|
||||
"group_binding": {
|
||||
@@ -2390,7 +2399,7 @@
|
||||
"instance": "Instance",
|
||||
"index": "Index",
|
||||
"unknown": "unknown",
|
||||
"wakeup_interval": "Wakeup Interval"
|
||||
"wakeup_interval": "Wake-up Interval"
|
||||
},
|
||||
"migration": {
|
||||
"ozw": {
|
||||
@@ -2441,7 +2450,7 @@
|
||||
"node_config": {
|
||||
"header": "Node Configuration Options",
|
||||
"seconds": "seconds",
|
||||
"set_wakeup": "Set Wakeup Interval",
|
||||
"set_wakeup": "Set Wake-up Interval",
|
||||
"config_parameter": "Configuration Parameter",
|
||||
"config_value": "Configuration Value",
|
||||
"true": "True",
|
||||
@@ -3417,5 +3426,414 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"supervisor": {
|
||||
"addon": {
|
||||
"failed_to_reset": "Failed to reset add-on configuration, {error}",
|
||||
"failed_to_save": "Failed to save add-on configuration, {error}",
|
||||
|
||||
"state": {
|
||||
"installed": "Add-on is installed",
|
||||
"not_installed": "Add-on is not installed",
|
||||
"not_available": "Add-on is not available on your system"
|
||||
},
|
||||
"panel": {
|
||||
"configuration": "Configuration",
|
||||
"documentation": "Documentation",
|
||||
"info": "Info",
|
||||
"log": "Log"
|
||||
},
|
||||
"configuration": {
|
||||
"no_configuration": "This add-on does not expose configuration for you to mess with...",
|
||||
"audio": {
|
||||
"header": "Audio",
|
||||
"default": "Default",
|
||||
"input": "Input",
|
||||
"output": "Output"
|
||||
},
|
||||
"options": {
|
||||
"header": "Options",
|
||||
"edit_in_ui": "Edit in UI",
|
||||
"edit_in_yaml": "Edit in YAML",
|
||||
"invalid_yaml": "Invalid YAML",
|
||||
"show_unused_optional": "Show unused optional configuration options"
|
||||
},
|
||||
"network": {
|
||||
"container": "Container",
|
||||
"disabled": "Disabled",
|
||||
"header": "Network",
|
||||
"host": "Host"
|
||||
}
|
||||
},
|
||||
"dashboard": {
|
||||
"changelog": "Changelog",
|
||||
"cpu_usage": "Add-on CPU Usage",
|
||||
"ram_usage": "Add-on RAM Usage",
|
||||
"hostname": "Hostname",
|
||||
"new_update_available": "{name} {version} is available",
|
||||
"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.",
|
||||
"not_available_version": "You are running Home Assistant {core_version_installed}, to update to this version of the add-on you need at least version {core_version_needed} of Home Assistant",
|
||||
"visit_addon_page": "Visit the {name} page for more details",
|
||||
"restart": "restart",
|
||||
"start": "start",
|
||||
"stop": "stop",
|
||||
"install": "install",
|
||||
"uninstall": "uninstall",
|
||||
"rebuild": "rebuild",
|
||||
"open_web_ui": "Open web UI",
|
||||
|
||||
"protection_mode": {
|
||||
"title": "Warning: Protection mode is disabled!",
|
||||
"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.",
|
||||
"enable": "Enable Protection mode"
|
||||
},
|
||||
|
||||
"capability": {
|
||||
"stage": {
|
||||
"title": "Add-on Stage",
|
||||
"description": "Add-ons can have one of three stages:\n\n{icon_stable} **Stable**: These are add-ons ready to be used in production.\n\n{icon_experimental} **Experimental**: These may contain bugs, and may be unfinished.\n\n{icon_deprecated} **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 add-on 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."
|
||||
},
|
||||
"label": {
|
||||
"stage": "stage",
|
||||
"rating": "rating",
|
||||
"hardware": "hardware",
|
||||
"host": "host",
|
||||
"hass": "hass",
|
||||
"hassio": "hassio",
|
||||
"docker": "docker",
|
||||
"host_pid": "host pid",
|
||||
"apparmor": "apparmor",
|
||||
"auth": "auth",
|
||||
"ingress": "ingress"
|
||||
},
|
||||
"role": {
|
||||
"manager": "manager",
|
||||
"default": "default",
|
||||
"homeassistant": "homeassistant",
|
||||
"backup": "backup",
|
||||
"admin": "admin"
|
||||
}
|
||||
},
|
||||
|
||||
"option": {
|
||||
"boot": {
|
||||
"title": "Start on boot",
|
||||
"description": "Make the add-on start during a system boot"
|
||||
},
|
||||
"watchdog": {
|
||||
"title": "Watchdog",
|
||||
"description": "This will start the add-on if it crashes"
|
||||
},
|
||||
"auto_update": {
|
||||
"title": "Auto update",
|
||||
"description": "Auto update the add-on when there is a new version available"
|
||||
},
|
||||
"ingress_panel": {
|
||||
"title": "Show in sidebar",
|
||||
"description": "Add this add-on to your sidebar"
|
||||
},
|
||||
"protected": {
|
||||
"title": "Protection mode",
|
||||
"description": "Blocks elevated system access from the add-on"
|
||||
}
|
||||
},
|
||||
|
||||
"action_error": {
|
||||
"uninstall": "Failed to uninstall add-on",
|
||||
"install": "Failed to install add-on",
|
||||
"stop": "Failed to stop add-on",
|
||||
"restart": "Failed to restart add-on",
|
||||
"start": "Failed to start add-on",
|
||||
"go_to_config": "Failed to start add-on - configuration validation failed!",
|
||||
"start_invalid_config": "Go to configuration",
|
||||
"validate_config": "Failed to validate add-on configuration",
|
||||
"get_changelog": "Failed to get add-on changelog"
|
||||
}
|
||||
},
|
||||
"documentation": {
|
||||
"get_documentation": "Failed to get add-on documentation, {error}"
|
||||
},
|
||||
"logs": {
|
||||
"get_logs": "Failed to get add-on logs, {error}"
|
||||
}
|
||||
},
|
||||
"common": {
|
||||
"cancel": "[%key:ui::common::cancel%]",
|
||||
"yes": "[%key:ui::common::yes%]",
|
||||
"no": "[%key:ui::common::no%]",
|
||||
"description": "Description",
|
||||
"failed_to_restart_name": "Failed to restart {name}",
|
||||
"failed_to_update_name": "Failed to update {name}",
|
||||
"learn_more": "Learn more",
|
||||
"new_version_available": "New version available",
|
||||
"newest_version": "Newest Version",
|
||||
"refresh": "[%key:ui::common::refresh%]",
|
||||
"release_notes": "Release notes",
|
||||
"reload": "Reload",
|
||||
"reset_defaults": "Reset to defaults",
|
||||
"reset_options": "Reset options",
|
||||
"restart_name": "Restart {name}",
|
||||
"restart": "Restart",
|
||||
"running_version": "You are currently running version {version}",
|
||||
"save": "[%key:ui::common::save%]",
|
||||
"close": "[%key:ui::common::close%]",
|
||||
"show_more": "Show more information about this",
|
||||
"update_available": "{count, plural,\n one {Update}\n other {{count} Updates}\n} pending",
|
||||
"update": "Update",
|
||||
"version": "Version",
|
||||
|
||||
"error": {
|
||||
"unknown": "Unknown error",
|
||||
"update_failed": "Update failed"
|
||||
}
|
||||
},
|
||||
"confirm": {
|
||||
"update": {
|
||||
"title": "Update {name}",
|
||||
"text": "Are you sure you want to update {name} to version {version}?"
|
||||
},
|
||||
"restart": {
|
||||
"title": "[%key:supervisor::common::restart_name%]",
|
||||
"text": "Are you sure you want to restart {name}?"
|
||||
},
|
||||
"reset_options": {
|
||||
"title": "Reset options",
|
||||
"text": "Are you sure you want to reset all your options?"
|
||||
}
|
||||
},
|
||||
"dashboard": {
|
||||
"addon_new_version": "New version available",
|
||||
"addon_running": "Add-on is running",
|
||||
"addon_stopped": "Add-on is stopped",
|
||||
"addons": "Add-ons",
|
||||
"no_addons": "You don't have any add-ons installed yet. Head over to the add-on store to get started!"
|
||||
},
|
||||
"store": {
|
||||
"missing_addons": "Missing add-ons? Enable advanced mode in your user profile page",
|
||||
"no_results_found": "No results found in {repository}.",
|
||||
"registries": "Registries",
|
||||
"repositories": "Repositories"
|
||||
},
|
||||
"panel": {
|
||||
"dashboard": "Dashboard",
|
||||
"snapshots": "Snapshots",
|
||||
"store": "Add-on Store",
|
||||
"system": "System"
|
||||
},
|
||||
"my": {
|
||||
"not_supported": "[%key:ui::panel::my::not_supported%]",
|
||||
"faq_link": "[%key:ui::panel::my::faq_link%]",
|
||||
"error": "[%key:ui::panel::my::error%]",
|
||||
"error_addon_not_found": "Add-on not found"
|
||||
},
|
||||
"system": {
|
||||
"system_checks": "System checks",
|
||||
"log": {
|
||||
"log_provider": "Log Provider",
|
||||
"get_logs": "Failed to get {provider} logs, {error}"
|
||||
},
|
||||
"supervisor": {
|
||||
"cpu_usage": "Supervisor CPU Usage",
|
||||
"ram_usage": "Supervisor RAM Usage",
|
||||
"failed_to_set_option": "Failed to set Supervisor option",
|
||||
"failed_to_reload": "Failed to reload the Supervisor",
|
||||
"failed_to_update": "Failed to update the Supervisor",
|
||||
"unsupported_title": "You are running an unsupported installation",
|
||||
"unsupported_description": "Below is a list of issues found with your installation, click on the links to learn how you can resolve the issues.",
|
||||
"unhealthy_title": "Your installation is unhealthy",
|
||||
"unhealthy_description": "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.",
|
||||
"update_supervisor": "Update the Supervisor",
|
||||
"channel": "Channel",
|
||||
"leave_beta_action": "Leave beta channel",
|
||||
"leave_beta_description": "Get stable updates for Home Assistant, Supervisor and host",
|
||||
"join_beta_action": "Join beta channel",
|
||||
"join_beta_description": "Get beta updates for Home Assistant (RCs), Supervisor and host",
|
||||
"share_diagnostics": "Share Diagnostics",
|
||||
"share_diagnostics_description": "Share crash reports and diagnostic information.",
|
||||
"reload_supervisor": "Reload Supervisor",
|
||||
"warning": "WARNING",
|
||||
"beta_warning": "Beta releases are for testers and early adopters and can contain unstable code changes",
|
||||
"beta_backup": "Make sure you have backups of your data before you activate this feature.",
|
||||
"beta_release_items": "This includes beta releases for:",
|
||||
"beta_join_confirm": "Do you want to join the beta channel?",
|
||||
"share_diagonstics_title": "Help Improve Home Assistant",
|
||||
"share_diagonstics_description": "Would you want to automatically share crash reports and diagnostic information when the Supervisor encounters unexpected errors? {line_break} 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.{line_break} The data does not include any private/sensitive information and you can disable this in settings at any time you want.",
|
||||
"unsupported_reason": {
|
||||
"container": "Containers known to cause issues",
|
||||
"dbus": "DBUS",
|
||||
"docker_configuration": "Docker Configuration",
|
||||
"docker_version": "Docker Version",
|
||||
"job_conditions": "Ignored job conditions",
|
||||
"lxc": "LXC",
|
||||
"network_manager": "Network Manager",
|
||||
"os": "Operating System",
|
||||
"privileged": "Supervisor is not privileged",
|
||||
"systemd": "Systemd"
|
||||
},
|
||||
"unhealthy_reason": {
|
||||
"privileged": "Supervisor is not privileged",
|
||||
"supervisor": "Supervisor was not able to update",
|
||||
"setup": "Setup of the Supervisor failed",
|
||||
"docker": "The Docker environment is not working properly"
|
||||
}
|
||||
},
|
||||
"host": {
|
||||
"failed_to_get_hardware_list": "Failed to get hardware list",
|
||||
"failed_to_reboot": "Failed to reboot the host",
|
||||
"failed_to_shutdown": "Failed to shutdown the host",
|
||||
"failed_to_set_hostname": "Setting hostname failed",
|
||||
"failed_to_import_from_usb": "Failed to import from USB",
|
||||
"used_space": "Used space",
|
||||
"hostname": "Hostname",
|
||||
"change_hostname": "Change Hostname",
|
||||
"new_hostname": "Please enter a new hostname:",
|
||||
"ip_address": "IP Address",
|
||||
"change": "Change",
|
||||
"operating_system": "Operating System",
|
||||
"docker_version": "Docker version",
|
||||
"deployment": "Deployment",
|
||||
"emmc_lifetime_used": "eMMC Lifetime Used",
|
||||
"reboot_host": "Reboot host",
|
||||
"confirm_reboot": "Are you sure you want to reboot the host?",
|
||||
"confirm_shutdown": "Are you sure you want to shutdown the host?",
|
||||
"shutdown_host": "Shutdown host",
|
||||
"hardware": "Hardware",
|
||||
"import_from_usb": "Import from USB"
|
||||
},
|
||||
"core": {
|
||||
"cpu_usage": "Core CPU Usage",
|
||||
"ram_usage": "Core RAM Usage"
|
||||
}
|
||||
},
|
||||
"snapshot": {
|
||||
"description": "Snapshots allow you to easily backup and restore all data of your Home Assistant instance.",
|
||||
"available_snapshots": "Available Snapshots",
|
||||
"no_snapshots": "You don't have any snapshots yet.",
|
||||
"create_blocked_not_running": "Creating a snapshot is not possible right now because the system is in {state} state.",
|
||||
"could_not_create": "Could not create snapshot",
|
||||
"upload_snapshot": "Upload snapshot",
|
||||
"create_snapshot": "Create snapshot",
|
||||
"create": "Create",
|
||||
"name": "Name",
|
||||
"type": "Type",
|
||||
"security": "Security",
|
||||
"full_snapshot": "Full snapshot",
|
||||
"partial_snapshot": "Partial snapshot",
|
||||
"addons": "Add-ons",
|
||||
"folders": "Folders",
|
||||
"password": "Password",
|
||||
"password_protection": "Password protection",
|
||||
"password_protected": "password protected",
|
||||
"enter_password": "Please enter a password.",
|
||||
|
||||
"folder": {
|
||||
"homeassistant": "Home Assistant configuration",
|
||||
"ssl": "SSL",
|
||||
"share": "Share",
|
||||
"media": "Media",
|
||||
"addons/local": "Local add-ons"
|
||||
}
|
||||
},
|
||||
"dialog": {
|
||||
"network": {
|
||||
"title": "Network settings",
|
||||
"connected_to": "Connected to {ssid}",
|
||||
"scan_ap": "Scan for access points",
|
||||
"open": "Open",
|
||||
"wep": "WEP",
|
||||
"wpa": "wpa-psk",
|
||||
"warning": "If you are changing the Wi-Fi, IP or gateway addresses, you might lose the connection!",
|
||||
"static": "Static",
|
||||
"dhcp": "DHCP",
|
||||
"disabled": "Disabled",
|
||||
"ip_netmask": "IP address/Netmask",
|
||||
"gateway": "Gateway address",
|
||||
"dns_servers": "DNS Servers",
|
||||
"unsaved": "You have unsaved changes, these will get lost if you change tabs, do you want to continue?",
|
||||
"failed_to_change": "Failed to change network settings"
|
||||
},
|
||||
"registries": {
|
||||
"title_add": "Add New Container Registry",
|
||||
"title_manage": "Manage Container Registries",
|
||||
"registry": "Registry",
|
||||
"username": "Username",
|
||||
"password": "Password",
|
||||
"no_registries": "No registries configured",
|
||||
"add_registry": "Add registry",
|
||||
"add_new_registry": "Add new registry",
|
||||
"remove": "Remove",
|
||||
"failed_to_add": "Failed to add registry",
|
||||
"failed_to_remove": "Failed to remove registry"
|
||||
},
|
||||
"repositories": {
|
||||
"title": "Manage add-on repositories",
|
||||
"add": "Add",
|
||||
"remove": "Remove"
|
||||
},
|
||||
"restart_addon": {
|
||||
"confirm_text": "Restart add-on",
|
||||
"text": "Do you want to restart the add-on with your changes?"
|
||||
},
|
||||
"update": {
|
||||
"snapshot": "Snapshot",
|
||||
"create_snapshot": "Create a snapshot of {name} before updating",
|
||||
"updating": "Updating {name} to version {version}",
|
||||
"snapshotting": "Creating snapshot of {name}"
|
||||
},
|
||||
"system_check": {
|
||||
"title": "Manage system checks",
|
||||
"failed_to_set_option": "Failed to set check options",
|
||||
"check": {
|
||||
"addon_pwned": {
|
||||
"title": "PWNED Add-ons",
|
||||
"description": "Monitor secrets and passwords used in add-on configuration against HIBP"
|
||||
},
|
||||
"free_space": {
|
||||
"title": "Free space",
|
||||
"description": "Monitor free space left on the device"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
import { translationMetadata } from "../resources/translations-metadata";
|
||||
|
||||
const DEFAULT_BASE_URL = "/static/translations";
|
||||
|
||||
// Store loaded translations in memory so translations are available immediately
|
||||
// when DOM is created in Polymer. Even a cache lookup creates noticeable latency.
|
||||
const translations = {};
|
||||
|
||||
async function fetchTranslation(fingerprint: string, base_url: string) {
|
||||
const response = await fetch(`${base_url}/${fingerprint}`, {
|
||||
credentials: "same-origin",
|
||||
});
|
||||
if (!response.ok) {
|
||||
throw new Error(
|
||||
`Fail to fetch translation ${fingerprint}: HTTP response status is ${response.status}`
|
||||
);
|
||||
}
|
||||
return response.json();
|
||||
}
|
||||
|
||||
export async function getTranslation(
|
||||
fragment: string | null,
|
||||
language: string,
|
||||
base_url?: string
|
||||
) {
|
||||
const metadata = translationMetadata.translations[language];
|
||||
if (!metadata) {
|
||||
if (language !== "en") {
|
||||
return getTranslation(fragment, "en", base_url);
|
||||
}
|
||||
throw new Error("Language en is not found in metadata");
|
||||
}
|
||||
|
||||
// nl-abcd.jon or logbook/nl-abcd.json
|
||||
const fingerprint = `${fragment ? fragment + "/" : ""}${language}-${
|
||||
metadata.hash
|
||||
}.json`;
|
||||
|
||||
// Fetch translation from the server
|
||||
if (!translations[fingerprint]) {
|
||||
translations[fingerprint] = fetchTranslation(
|
||||
fingerprint,
|
||||
base_url || DEFAULT_BASE_URL
|
||||
)
|
||||
.then((data) => ({ language, data }))
|
||||
.catch((error) => {
|
||||
delete translations[fingerprint];
|
||||
if (language !== "en") {
|
||||
// Couldn't load selected translation. Try a fall back to en before failing.
|
||||
return getTranslation(fragment, "en", base_url);
|
||||
}
|
||||
return Promise.reject(error);
|
||||
});
|
||||
}
|
||||
return translations[fingerprint];
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user