@@ -371,7 +386,7 @@ class HassioAddonInfo extends EventsMixin(PolymerElement) {
Open web UI
+
+ Open web UI
+
@@ -448,8 +473,16 @@ class HassioAddonInfo extends EventsMixin(PolymerElement) {
return webui && webui.replace("[HOST]", document.location.hostname);
}
- computeShowWebUI(webui, isRunning) {
- return webui && isRunning;
+ computeShowWebUI(ingress, webui, isRunning) {
+ return !ingress && webui && isRunning;
+ }
+
+ openIngress() {
+ navigate(this, `/hassio/ingress/${this.addon.slug}`);
+ }
+
+ computeShowIngressUI(ingress, isRunning) {
+ return ingress && isRunning;
}
computeStartOnBoot(state) {
diff --git a/hassio/src/entrypoint.js b/hassio/src/entrypoint.js
index 2e91bbd4bc..3567516f58 100644
--- a/hassio/src/entrypoint.js
+++ b/hassio/src/entrypoint.js
@@ -2,4 +2,16 @@ window.loadES5Adapter().then(() => {
import(/* webpackChunkName: "hassio-icons" */ "./resources/hassio-icons.js");
import(/* webpackChunkName: "hassio-main" */ "./hassio-main.js");
});
-document.body.style.height = "100%";
+const styleEl = document.createElement("style");
+styleEl.innerHTML = `
+body {
+ font-family: Roboto, sans-serif;
+ -moz-osx-font-smoothing: grayscale;
+ -webkit-font-smoothing: antialiased;
+ font-weight: 400;
+ margin: 0;
+ padding: 0;
+ height: 100vh;
+}
+`;
+document.head.appendChild(styleEl);
diff --git a/hassio/src/hassio-main.js b/hassio/src/hassio-main.js
index df4d57b985..dbc78e2952 100644
--- a/hassio/src/hassio-main.js
+++ b/hassio/src/hassio-main.js
@@ -4,9 +4,11 @@ import { PolymerElement } from "@polymer/polymer/polymer-element";
import "../../src/layouts/hass-loading-screen";
import "./addon-view/hassio-addon-view";
+import "./ingress-view/hassio-ingress-view";
import "./hassio-data";
import "./hassio-pages-with-tabs";
+import "../../src/resources/ha-style";
import applyThemesOnElement from "../../src/common/dom/apply_themes_on_element";
import EventsMixin from "../../src/mixins/events-mixin";
import NavigateMixin from "../../src/mixins/navigate-mixin";
@@ -33,7 +35,7 @@ class HassioMain extends EventsMixin(NavigateMixin(PolymerElement)) {
-
+
+
+
+
`;
}
@@ -137,6 +145,14 @@ class HassioMain extends EventsMixin(NavigateMixin(PolymerElement)) {
equalsAddon(page) {
return page && page === "addon";
}
+
+ equalsDashboard(page) {
+ return !page || !["addon", "ingress"].includes(page);
+ }
+
+ equalsIngress(page) {
+ return page && page === "ingress";
+ }
}
customElements.define("hassio-main", HassioMain);
diff --git a/hassio/src/ingress-view/hassio-ingress-view.ts b/hassio/src/ingress-view/hassio-ingress-view.ts
new file mode 100644
index 0000000000..0d93fb8b19
--- /dev/null
+++ b/hassio/src/ingress-view/hassio-ingress-view.ts
@@ -0,0 +1,113 @@
+import {
+ LitElement,
+ customElement,
+ property,
+ TemplateResult,
+ html,
+ PropertyValues,
+ CSSResult,
+ css,
+} from "lit-element";
+import { HomeAssistant, Route } from "../../../src/types";
+import {
+ createHassioSession,
+ HassioAddon,
+ fetchHassioAddonInfo,
+} from "../../../src/data/hassio";
+import "../../../src/layouts/hass-loading-screen";
+import "../../../src/layouts/hass-subpage";
+
+const extractAddon = (path: string) => {
+ path = path.substr("/ingress".length);
+ const subpathStart = path.indexOf("/", 1);
+ return subpathStart === -1
+ ? path.substr(1)
+ : path.substr(1, subpathStart - 1);
+};
+
+@customElement("hassio-ingress-view")
+class HassioIngressView extends LitElement {
+ @property() public hass!: HomeAssistant;
+ @property() public route!: Route & { slug: string };
+ @property() private _hasSession = false;
+ @property() private _addon?: HassioAddon;
+
+ protected render(): TemplateResult | void {
+ if (!this._hasSession || !this._addon) {
+ return html`
+
+ `;
+ }
+ return html`
+
+
+
+
+
+
+ `;
+ }
+
+ protected updated(changedProps: PropertyValues) {
+ super.firstUpdated(changedProps);
+
+ if (!changedProps.has("route")) {
+ return;
+ }
+
+ const addon = extractAddon(this.route.path);
+
+ const oldRoute = changedProps.get("route") as this["route"] | undefined;
+ const oldAddon = oldRoute ? extractAddon(oldRoute.path) : undefined;
+
+ if (addon !== oldAddon) {
+ this._createSession();
+ this._fetchAddonInfo(addon);
+ }
+ }
+
+ private async _fetchAddonInfo(addonSlug: string) {
+ try {
+ const addon = await fetchHassioAddonInfo(this.hass, addonSlug);
+ if (addon.ingress) {
+ this._addon = addon;
+ } else {
+ alert("This add-on does not support ingress.");
+ history.back();
+ }
+ } catch (err) {
+ alert("Failed to fetch add-on info");
+ history.back();
+ }
+ }
+
+ private async _createSession() {
+ try {
+ await createHassioSession(this.hass);
+ this._hasSession = true;
+ } catch (err) {
+ alert("Failed to generate a session");
+ history.back();
+ }
+ }
+
+ static get styles(): CSSResult {
+ return css`
+ iframe {
+ display: block;
+ width: 100%;
+ height: 100%;
+ border: 0;
+ }
+ paper-icon-button {
+ color: var(--text-primary-color);
+ }
+ `;
+ }
+}
+
+declare global {
+ interface HTMLElementTagNameMap {
+ "hassio-ingress-view": HassioIngressView;
+ }
+}
diff --git a/src/components/ha-paper-icon-button-arrow-prev.ts b/src/components/ha-paper-icon-button-arrow-prev.ts
index acac478b8f..de512993f8 100644
--- a/src/components/ha-paper-icon-button-arrow-prev.ts
+++ b/src/components/ha-paper-icon-button-arrow-prev.ts
@@ -9,10 +9,16 @@ const paperIconButtonClass = customElements.get(
) as Constructor;
export class HaPaperIconButtonArrowPrev extends paperIconButtonClass {
+ public hassio?: boolean;
+
public connectedCallback() {
this.icon =
window.getComputedStyle(this).direction === "ltr"
- ? "hass:arrow-left"
+ ? this.hassio
+ ? "hassio:arrow-left"
+ : "hass:arrow-left"
+ : this.hassio
+ ? "hassio:arrow-right"
: "hass:arrow-right";
// calling super after setting icon to have it consistently show the icon (otherwise not always shown)
diff --git a/src/data/hassio.ts b/src/data/hassio.ts
new file mode 100644
index 0000000000..25a83afb83
--- /dev/null
+++ b/src/data/hassio.ts
@@ -0,0 +1,86 @@
+import { HomeAssistant } from "../types";
+
+interface HassioResponse {
+ data: T;
+ result: "ok";
+}
+
+interface CreateSessionResponse {
+ session: string;
+}
+
+export interface HassioAddon {
+ name: string;
+ slug: string;
+ description: string;
+ long_description: null | string;
+ auto_update: boolean;
+ url: null | string;
+ detached: boolean;
+ available: boolean;
+ arch: "armhf" | "aarch64" | "i386" | "amd64";
+ machine: any;
+ homeassistant: string;
+ repository: null | string;
+ version: null | string;
+ last_version: string;
+ state: "none" | "started" | "stopped";
+ boot: "auto" | "manual";
+ build: boolean;
+ options: object;
+ network: null | object;
+ host_network: boolean;
+ host_pid: boolean;
+ host_ipc: boolean;
+ host_dbus: boolean;
+ privileged: any;
+ apparmor: "disable" | "default" | "profile";
+ devices: string[];
+ auto_uart: boolean;
+ icon: boolean;
+ logo: boolean;
+ changelog: boolean;
+ hassio_api: boolean;
+ hassio_role: "default" | "homeassistant" | "manager" | "admin";
+ homeassistant_api: boolean;
+ auth_api: boolean;
+ full_access: boolean;
+ protected: boolean;
+ rating: "1-6";
+ stdin: boolean;
+ webui: null | string;
+ gpio: boolean;
+ kernel_modules: boolean;
+ devicetree: boolean;
+ docker_api: boolean;
+ audio: boolean;
+ audio_input: null | string;
+ audio_output: null | string;
+ services_role: string[];
+ discovery: string[];
+ ip_address: string;
+ ingress: boolean;
+ ingress_entry: null | string;
+ ingress_url: null | string;
+}
+
+const hassioApiResultExtractor = (response: HassioResponse) =>
+ response.data;
+
+export const createHassioSession = async (hass: HomeAssistant) => {
+ const response = await hass.callApi>(
+ "POST",
+ "hassio/ingress/session"
+ );
+ document.cookie = `ingress_session=${
+ response.data.session
+ };path=/api/hassio_ingress/`;
+};
+
+export const fetchHassioAddonInfo = async (
+ hass: HomeAssistant,
+ addon: string
+) =>
+ hass
+ .callApi>("GET", `hassio/addons/${addon}/info`)
+ .then(hassioApiResultExtractor);
diff --git a/src/layouts/hass-subpage.ts b/src/layouts/hass-subpage.ts
index 8dae9e7125..950e9637a7 100644
--- a/src/layouts/hass-subpage.ts
+++ b/src/layouts/hass-subpage.ts
@@ -7,6 +7,7 @@ import {
css,
CSSResult,
} from "lit-element";
+import "../components/ha-menu-button";
import "../components/ha-paper-icon-button-arrow-prev";
@customElement("hass-subpage")
@@ -14,12 +15,26 @@ class HassSubpage extends LitElement {
@property()
public header?: string;
+ @property({ type: Boolean })
+ public root = false;
+
+ @property({ type: Boolean })
+ public hassio = false;
+
protected render(): TemplateResult | void {
return html`
@@ -51,6 +66,7 @@ class HassSubpage extends LitElement {
color: var(--text-primary-color, white);
}
+ ha-menu-button,
ha-paper-icon-button-arrow-prev,
::slotted([slot="toolbar-icon"]) {
pointer-events: auto;