diff --git a/hassio/src/addon-view/hassio-addon-dashboard.ts b/hassio/src/addon-view/hassio-addon-dashboard.ts
index 4260ea4f92..528b46f50a 100644
--- a/hassio/src/addon-view/hassio-addon-dashboard.ts
+++ b/hassio/src/addon-view/hassio-addon-dashboard.ts
@@ -9,16 +9,21 @@ import {
CSSResult,
customElement,
html,
+ internalProperty,
LitElement,
property,
TemplateResult,
} from "lit-element";
import memoizeOne from "memoize-one";
+import { navigate } from "../../../src/common/navigate";
+import { extractSearchParam } from "../../../src/common/url/search-params";
import "../../../src/components/ha-circular-progress";
import {
fetchHassioAddonInfo,
HassioAddonDetails,
} from "../../../src/data/hassio/addon";
+import "../../../src/layouts/hass-loading-screen";
+import "../../../src/layouts/hass-error-screen";
import { Supervisor } from "../../../src/data/supervisor/supervisor";
import "../../../src/layouts/hass-tabs-subpage";
import type { PageNavigation } from "../../../src/layouts/hass-tabs-subpage";
@@ -31,6 +36,7 @@ import "./config/hassio-addon-network";
import "./hassio-addon-router";
import "./info/hassio-addon-info";
import "./log/hassio-addon-logs";
+import { extractApiErrorMessage } from "../../../src/data/hassio/common";
@customElement("hassio-addon-dashboard")
class HassioAddonDashboard extends LitElement {
@@ -44,6 +50,8 @@ class HassioAddonDashboard extends LitElement {
@property({ type: Boolean }) public narrow!: boolean;
+ @internalProperty() _error?: string;
+
private _computeTail = memoizeOne((route: Route) => {
const dividerPos = route.path.indexOf("/", 1);
return dividerPos === -1
@@ -58,8 +66,14 @@ class HassioAddonDashboard extends LitElement {
});
protected render(): TemplateResult {
+ if (this._error) {
+ return html``;
+ }
+
if (!this.addon) {
- return html``;
+ return html``;
}
const addonTabs: PageNavigation[] = [
@@ -156,7 +170,12 @@ class HassioAddonDashboard extends LitElement {
}
protected async firstUpdated(): Promise {
- await this._routeDataChanged(this.route);
+ if (this.route.path === "") {
+ const addon = extractSearchParam("addon");
+ if (addon) {
+ navigate(this, `/hassio/addon/${addon}`, true);
+ }
+ }
this.addEventListener("hass-api-called", (ev) => this._apiCalled(ev));
}
@@ -170,16 +189,26 @@ class HassioAddonDashboard extends LitElement {
if (path === "uninstall") {
history.back();
} else {
- await this._routeDataChanged(this.route);
+ await this._routeDataChanged();
}
}
- private async _routeDataChanged(routeData: Route): Promise {
- const addon = routeData.path.split("/")[1];
+ protected updated(changedProperties) {
+ if (changedProperties.has("route") && !this.addon) {
+ this._routeDataChanged();
+ }
+ }
+
+ private async _routeDataChanged(): Promise {
+ const addon = this.route.path.split("/")[1];
+ if (!addon) {
+ return;
+ }
try {
const addoninfo = await fetchHassioAddonInfo(this.hass, addon);
this.addon = addoninfo;
- } catch {
+ } catch (err) {
+ this._error = `Error fetching addon info: ${extractApiErrorMessage(err)}`;
this.addon = undefined;
}
}
diff --git a/hassio/src/hassio-my-redirect.ts b/hassio/src/hassio-my-redirect.ts
new file mode 100644
index 0000000000..9391da6914
--- /dev/null
+++ b/hassio/src/hassio-my-redirect.ts
@@ -0,0 +1,125 @@
+import {
+ customElement,
+ html,
+ internalProperty,
+ LitElement,
+ property,
+ TemplateResult,
+} from "lit-element";
+import { sanitizeUrl } from "@braintree/sanitize-url";
+import {
+ createSearchParam,
+ extractSearchParamsObject,
+} from "../../src/common/url/search-params";
+import "../../src/layouts/hass-error-screen";
+import {
+ ParamType,
+ Redirect,
+ Redirects,
+} from "../../src/panels/my/ha-panel-my";
+import { navigate } from "../../src/common/navigate";
+import { HomeAssistant, Route } from "../../src/types";
+
+const REDIRECTS: Redirects = {
+ supervisor_system: {
+ redirect: "/hassio/system",
+ },
+ supervisor_snapshots: {
+ redirect: "/hassio/snapshots",
+ },
+ supervisor_store: {
+ redirect: "/hassio/store",
+ },
+ supervisor: {
+ redirect: "/hassio/dashboard",
+ },
+ supervisor_addon: {
+ redirect: "/hassio/addon",
+ params: {
+ addon: "string",
+ },
+ },
+};
+
+@customElement("hassio-my-redirect")
+class HassioMyRedirect extends LitElement {
+ @property({ attribute: false }) public hass!: HomeAssistant;
+
+ @property() public route!: Route;
+
+ @internalProperty() public _error?: TemplateResult | string;
+
+ connectedCallback() {
+ super.connectedCallback();
+ const path = this.route.path.substr(1);
+ const redirect = REDIRECTS[path];
+
+ if (!redirect) {
+ this._error = html`This redirect is not supported by your Home Assistant
+ instance. Check the
+ My Home Assistant FAQ
+ for the supported redirects and the version they where introduced.`;
+ return;
+ }
+
+ let url: string;
+ try {
+ url = this._createRedirectUrl(redirect);
+ } catch (err) {
+ this._error = "An unknown error occured";
+ return;
+ }
+
+ navigate(this, url, true);
+ }
+
+ protected render(): TemplateResult {
+ if (this._error) {
+ return html``;
+ }
+ return html``;
+ }
+
+ private _createRedirectUrl(redirect: Redirect): string {
+ const params = this._createRedirectParams(redirect);
+ return `${redirect.redirect}${params}`;
+ }
+
+ private _createRedirectParams(redirect: Redirect): string {
+ const params = extractSearchParamsObject();
+ if (!redirect.params && !Object.keys(params).length) {
+ return "";
+ }
+ const resultParams = {};
+ Object.entries(redirect.params || {}).forEach(([key, type]) => {
+ if (!params[key] || !this._checkParamType(type, params[key])) {
+ throw Error();
+ }
+ resultParams[key] = params[key];
+ });
+ return `?${createSearchParam(resultParams)}`;
+ }
+
+ private _checkParamType(type: ParamType, value: string) {
+ if (type === "string") {
+ return true;
+ }
+ if (type === "url") {
+ return value && value === sanitizeUrl(value);
+ }
+ return false;
+ }
+}
+
+declare global {
+ interface HTMLElementTagNameMap {
+ "hassio-my-redirect": HassioMyRedirect;
+ }
+}
diff --git a/hassio/src/hassio-router.ts b/hassio/src/hassio-router.ts
index 50bd69cd3a..dbcbfd08ed 100644
--- a/hassio/src/hassio-router.ts
+++ b/hassio/src/hassio-router.ts
@@ -41,6 +41,10 @@ class HassioRouter extends HassRouterPage {
tag: "hassio-ingress-view",
load: () => import("./ingress-view/hassio-ingress-view"),
},
+ _my_redirect: {
+ tag: "hassio-my-redirect",
+ load: () => import("./hassio-my-redirect"),
+ },
},
};
diff --git a/src/dialogs/quick-bar/ha-quick-bar.ts b/src/dialogs/quick-bar/ha-quick-bar.ts
index 4db3b69260..9789157691 100644
--- a/src/dialogs/quick-bar/ha-quick-bar.ts
+++ b/src/dialogs/quick-bar/ha-quick-bar.ts
@@ -380,22 +380,24 @@ export class QuickBar extends LitElement {
QuickBarNavigationItem,
"action"
>[] {
- return Object.keys(this.hass.panels).map((panelKey) => {
- const panel = this.hass.panels[panelKey];
- const translationKey = getPanelNameTranslationKey(panel);
+ return Object.keys(this.hass.panels)
+ .filter((panelKey) => panelKey !== "_my_redirect")
+ .map((panelKey) => {
+ const panel = this.hass.panels[panelKey];
+ const translationKey = getPanelNameTranslationKey(panel);
- const text = this.hass.localize(
- "ui.dialogs.quick-bar.commands.navigation.navigate_to",
- "panel",
- this.hass.localize(translationKey) || panel.title || panel.url_path
- );
+ const text = this.hass.localize(
+ "ui.dialogs.quick-bar.commands.navigation.navigate_to",
+ "panel",
+ this.hass.localize(translationKey) || panel.title || panel.url_path
+ );
- return {
- text,
- icon: getPanelIcon(panel) || DEFAULT_NAVIGATION_ICON,
- path: `/${panel.url_path}`,
- };
- });
+ return {
+ text,
+ icon: getPanelIcon(panel) || DEFAULT_NAVIGATION_ICON,
+ path: `/${panel.url_path}`,
+ };
+ });
}
private _generateNavigationConfigSectionCommands(): Partial<
diff --git a/src/panels/my/ha-panel-my.ts b/src/panels/my/ha-panel-my.ts
index b9eaea9916..97f12ef6aa 100644
--- a/src/panels/my/ha-panel-my.ts
+++ b/src/panels/my/ha-panel-my.ts
@@ -13,8 +13,10 @@ import {
extractSearchParamsObject,
} from "../../common/url/search-params";
import "../../layouts/hass-error-screen";
+import { isComponentLoaded } from "../../common/config/is_component_loaded";
+import { domainToName } from "../../data/integration";
-const REDIRECTS = {
+const REDIRECTS: Redirects = {
info: {
redirect: "/config/info",
},
@@ -38,10 +40,12 @@ const REDIRECTS = {
},
};
-type ParamType = "url" | "string";
+export type ParamType = "url" | "string";
-interface Redirect {
+export type Redirects = { [key: string]: Redirect };
+export interface Redirect {
redirect: string;
+ component?: string;
params?: {
[key: string]: ParamType;
};
@@ -58,7 +62,25 @@ class HaPanelMy extends LitElement {
connectedCallback() {
super.connectedCallback();
const path = this.route.path.substr(1);
- const redirect: Redirect | undefined = REDIRECTS[path];
+
+ if (path.startsWith("supervisor")) {
+ if (!isComponentLoaded(this.hass, "hassio")) {
+ this._error = this.hass.localize(
+ "ui.panel.my.component_not_loaded",
+ "integration",
+ domainToName(this.hass.localize, "hassio")
+ );
+ return;
+ }
+ navigate(
+ this,
+ `/hassio/_my_redirect/${path}${window.location.search}`,
+ true
+ );
+ return;
+ }
+
+ const redirect = REDIRECTS[path];
if (!redirect) {
this._error = this.hass.localize(
@@ -74,6 +96,18 @@ class HaPanelMy extends LitElement {
return;
}
+ if (
+ redirect.component &&
+ !isComponentLoaded(this.hass, redirect.component)
+ ) {
+ this._error = this.hass.localize(
+ "ui.panel.my.component_not_loaded",
+ "integration",
+ domainToName(this.hass.localize, redirect.component)
+ );
+ return;
+ }
+
let url: string;
try {
url = this._createRedirectUrl(redirect);
diff --git a/src/translations/en.json b/src/translations/en.json
index 9a7cff23c4..ef30609c90 100755
--- a/src/translations/en.json
+++ b/src/translations/en.json
@@ -806,6 +806,7 @@
"panel": {
"my": {
"not_supported": "This redirect is not supported by your Home Assistant instance. Check the {link} for the supported redirects and the version they where introduced.",
+ "component_not_loaded": "This redirect is not supported by your Home Assistant instance. You need the integration {integration} to use this redirect.",
"faq_link": "My Home Assistant FAQ",
"error": "An unknown error occured"
},