var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
    var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
    if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
    else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
    return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
import { Page, router } from "@openremote/or-app";
import { css, html, unsafeCSS } from "lit";
import { customElement, state } from "lit/decorators.js";
import { until } from "lit/directives/until.js";
import { when } from "lit/directives/when.js";
import { Task } from "@lit/task";
import { i18next } from "@openremote/or-translate";
import { DefaultColor3, manager } from "@openremote/core";
import { InputType } from "@openremote/or-mwc-components/or-mwc-input";
import { getAssetsRoute } from "../routes";
import { OrMwcDialog, showDialog, showOkCancelDialog } from "@openremote/or-mwc-components/or-mwc-dialog";
import { showSnackbar } from "@openremote/or-mwc-components/or-mwc-snackbar";
import moment from "moment";
export function pageGatewayTunnelProvider(store) {
    return {
        name: "gateway-tunnel",
        routes: [
            "gateway-tunnel"
        ],
        pageCreator: () => {
            return new PageGatewayTunnel(store);
        }
    };
}
const styling = css `
    #wrapper {
        height: 100%;
        width: 100%;
        display: flex;
        flex-direction: column;
    }

    #title {
        padding: 0 20px;
        font-size: 18px;
        font-weight: bold;
        width: calc(100% - 40px);
        max-width: 1360px;
        margin: 20px auto;
        align-items: center;
        display: flex;
        color: var(--or-app-color3, ${unsafeCSS(DefaultColor3)});
    }

    #title > or-icon {
        margin-right: 10px;
        margin-left: 14px;
    }

    .panel {
        flex: 0;
        width: 100%;
        box-sizing: border-box;
        max-width: 1360px;
        background-color: white;
        border: 1px solid #e5e5e5;
        border-radius: 5px;
        position: relative;
        margin: 0 auto 10px;
        padding: 12px 24px 24px;
        display: flex;
        flex-direction: column;
    }

    .panel-title {
        display: flex;
        text-transform: uppercase;
        font-weight: bolder;
        color: var(--or-app-color3, ${unsafeCSS(DefaultColor3)});
        line-height: 1em;
        margin-bottom: 10px;
        margin-top: 0;
        flex: 0 0 auto;
        letter-spacing: 0.025em;
        align-items: center;
        min-height: 36px;
    }

`;
let PageGatewayTunnel = class PageGatewayTunnel extends Page {
    constructor() {
        super(...arguments);
        // Static value with the asset types to be used
        this.GATEWAY_TUNNEL_TYPES = ["GatewayAsset"];
        // Static value with possible protocol types
        this.GATEWAY_TUNNEL_PROTOCOL_TYPES = ["HTTPS" /* GatewayTunnelInfoType.HTTPS */, "HTTP" /* GatewayTunnelInfoType.HTTP */, "TCP" /* GatewayTunnelInfoType.TCP */];
        this._realm = manager.displayRealm;
        this._loading = false;
        this._refreshTimer = undefined;
        /* ------------------------------------- */
        /**
         * Internal Lit {@link Task} meant for fetching the list of tunnels.
         * It acts as a reactive controller, and keeps its value cached.
         */
        this._fetchTunnelsTask = new Task(this, {
            task: (_a, _b) => __awaiter(this, [_a, _b], void 0, function* ([realm], { signal }) {
                return yield this._fetchTunnels(realm, signal);
            }),
            args: () => [this._realm]
        });
    }
    static get styles() {
        return [styling];
    }
    disconnectedCallback() {
        if (this._refreshTimer) {
            clearTimeout(this._refreshTimer);
        }
        super.disconnectedCallback();
    }
    get name() {
        return "gateway-tunnel";
    }
    stateChanged(state) {
        this._realm = state.app.realm || manager.displayRealm;
    }
    render() {
        return html `
            <div id="wrapper">
                <div id="title">
                    <or-icon icon="lan-connect"></or-icon>
                    ${i18next.t("gatewayTunnel")}
                </div>
                <div class="panel">
                    <div class="panel-title" style="justify-content: space-between;">
                        <or-translate value="tunnels"></or-translate>
                        <or-mwc-input style="margin: 0;" type="${InputType.BUTTON}" icon="plus"
                                      label="${i18next.t('add')} ${i18next.t("tunnel")}"
                                      @or-mwc-input-changed="${(ev) => this._onAddTunnelClick(ev)}"
                        ></or-mwc-input>
                    </div>
                    ${this._fetchTunnelsTask.render({
            pending: () => html `${i18next.t('loading')}`,
            complete: (tunnels) => until(this.getTunnelsTable(tunnels), html `${i18next.t('loading')}`),
            error: () => html `${i18next.t('errorOccurred')}`
        })}
                </div>
            </div>
        `;
    }
    /**
     * Function that returns an HTML {@link TemplateResult} with a table.
     * Displays the given {@link tunnels} parameter in a nice looking format.
     * Uses the {@link OrMwcTable} component for displaying
     */
    getTunnelsTable(tunnels) {
        return __awaiter(this, void 0, void 0, function* () {
            const columns = [
                { title: i18next.t("gatewayTunnels.id"), isSortable: true },
                { title: i18next.t("gatewayTunnels.gatewayId") },
                { title: i18next.t("gatewayTunnels.type"), isSortable: true },
                { title: i18next.t("gatewayTunnels.target"), isSortable: true, hideMobile: true },
                { title: i18next.t("gatewayTunnels.targetPort"), isSortable: true, hideMobile: true },
                { title: i18next.t("gatewayTunnels.assignedPort"), isSortable: true, hideMobile: true },
                { title: i18next.t("gatewayTunnels.closesAt"), isSortable: true, hideMobile: true },
                { title: "" }
            ];
            const rows = (tunnels === null || tunnels === void 0 ? void 0 : tunnels.map(tunnel => ({
                content: [
                    tunnel.id,
                    until(this._getTunnelGatewayIdTemplate(tunnel), html `${i18next.t("loading")}`),
                    tunnel.type,
                    tunnel.target,
                    tunnel.targetPort + "",
                    !tunnel.assignedPort ? "" : tunnel.assignedPort + "",
                    tunnel.autoCloseTime ? moment(tunnel.autoCloseTime).format('lll') : "",
                    until(this._getTunnelActionsTemplate(tunnel), html `${i18next.t("loading")}`)
                ],
                clickable: false,
            }))) || [];
            return html `
            <or-mwc-table .columns="${columns}" .rows="${rows}">

            </or-mwc-table>
        `;
        });
    }
    /**
     * Function that returns an HTML {@link TemplateResult} containing the gateway asset that is linked to the {@link tunnel}.
     */
    _getTunnelGatewayIdTemplate(tunnel) {
        return __awaiter(this, void 0, void 0, function* () {
            const onClick = () => {
                router.navigate(getAssetsRoute(false, tunnel.gatewayId));
            };
            return html `
            <div style="height: 100%; display: flex; align-items: center;">
                <span style="cursor: pointer;" @click="${() => onClick()}">${tunnel.gatewayId}</span>
            </div>
        `;
        });
    }
    /**
     * Function that returns an HTML {@link TemplateResult} containing the actions for the given {@link tunnel}.
     */
    _getTunnelActionsTemplate(tunnel) {
        return __awaiter(this, void 0, void 0, function* () {
            return html `
            <div style="display: flex; justify-content: end; align-items: center; gap: 12px;">
                <or-mwc-input .type="${InputType.BUTTON}" icon="stop" @or-mwc-input-changed="${(ev) => this._onStopTunnelClick(ev, tunnel)}"></or-mwc-input>
                ${when(tunnel.type === "TCP" /* GatewayTunnelInfoType.TCP */, () => html `
                    <or-mwc-input .type="${InputType.BUTTON}" outlined label="${i18next.t('gatewayTunnels.copyAddress')}" @or-mwc-input-changed="${(ev) => this._onCopyTunnelAddressClick(ev, tunnel)}"></or-mwc-input>
                `, () => html `
                    <or-mwc-input .type="${InputType.BUTTON}" outlined label="${i18next.t('gatewayTunnels.open')}" @or-mwc-input-changed="${(ev) => this._onOpenTunnelClick(ev, tunnel)}"></or-mwc-input>
                `)}
            </div>
        `;
        });
    }
    /**
     * HTML callback event of the 'stop' button in the tunnels table,
     * meant to stop and delete the constructed tunnel.
     */
    _onStopTunnelClick(ev, tunnel) {
        ev.stopPropagation();
        showOkCancelDialog(i18next.t("areYouSure"), i18next.t("gatewayTunnels.stopWarning"), i18next.t("gatewayTunnels.stop")).then((ok) => {
            if (ok) {
                this._stopTunnel(tunnel).then(success => {
                    if (success) {
                        showSnackbar(undefined, "gatewayTunnels.closeSuccessful");
                    }
                    else {
                        showSnackbar(undefined, "errorOccurred");
                    }
                }).catch(_error => {
                    showSnackbar(undefined, "errorOccurred");
                }).finally(() => {
                    this._fetchTunnelsTask.run();
                });
            }
        });
    }
    /**
     * Function that requests the manager to stop the {@link tunnel}.
     */
    _stopTunnel(tunnel) {
        return __awaiter(this, void 0, void 0, function* () {
            const response = yield manager.rest.api.GatewayServiceResource.stopTunnel(tunnel);
            return response.status === 204;
        });
    }
    /**
     * HTML callback event for the 'copy address' button in the tunnels table,
     * meant for TCP addresses to be copied to the browsers' clipboard.
     */
    _onCopyTunnelAddressClick(ev, tunnel) {
        const address = this._getTunnelAddress(tunnel);
        if (address) {
            navigator.clipboard.writeText(address).finally(() => {
                showSnackbar(undefined, i18next.t('gatewayTunnels.copySuccess'));
            });
        }
        else {
            console.warn("Could not copy tunnel address as it could not be found.");
            showSnackbar(undefined, i18next.t('errorOccurred'));
        }
    }
    /**
     * HTML callback event of the 'open' button in the tunnels table,
     * meant to start tunneling towards that instance
     */
    _onOpenTunnelClick(ev, tunnel) {
        this._navigateToTunnel(tunnel);
    }
    /**
     * HTML callback event of the 'add' button above the tunnels table,
     * meant to start a tunnel towards that instance.
     */
    _onAddTunnelClick(ev) {
        const tunnel = this._getDefaultTunnelToAdd();
        let dialog;
        const onAddClick = () => {
            this._tryStartTunnel(tunnel);
        };
        const updateActions = () => {
            dialog.setActions([
                { actionName: "cancel", content: "cancel" },
                { actionName: "add", disabled: (!tunnel.gatewayId || !tunnel.target || !tunnel.targetPort), content: "add", action: () => onAddClick() },
            ]);
        };
        const gatewayListTemplate = () => __awaiter(this, void 0, void 0, function* () {
            const gatewayAssets = yield this._fetchGatewayAssets();
            const items = gatewayAssets.map((g) => [g.id, g.name]);
            return html `
                <div style="display: flex; flex-direction: column; gap: 20px; width: 360px;">
                    <or-mwc-input .type="${InputType.SELECT}" .options="${items}" .value="${tunnel.gatewayId}" label="${i18next.t('gatewayTunnels.selectAsset')}" style="width: 100%;"
                                  @or-mwc-input-changed="${(ev) => {
                tunnel.gatewayId = ev.detail.value;
                updateActions();
            }}"
                    ></or-mwc-input>
                    <or-mwc-input .type="${InputType.SELECT}" label="${i18next.t('gatewayTunnels.protocol')}" .value="${tunnel.type}" .options="${this.GATEWAY_TUNNEL_PROTOCOL_TYPES}"
                                  style="width: 100%"
                                  @or-mwc-input-changed="${(ev) => {
                tunnel.type = ev.detail.value;
                updateActions();
            }}"
                    ></or-mwc-input>
                    <or-mwc-input .type="${InputType.TEXT}" label="${i18next.t('host')}" .value="${tunnel.target}" style="width: 100%;"
                                  @or-mwc-input-changed="${(ev) => {
                tunnel.target = ev.detail.value;
                updateActions();
            }}"
                    ></or-mwc-input>
                    <or-mwc-input .type="${InputType.NUMBER}" label="${i18next.t('port')}" .value="${tunnel.targetPort}" style="width: 100%"
                                  @or-mwc-input-changed="${(ev) => {
                tunnel.targetPort = ev.detail.value;
                updateActions();
            }}"
                    ></or-mwc-input>
                </div>
            `;
        });
        dialog = new OrMwcDialog()
            .setHeading(`${i18next.t("add")} ${i18next.t("tunnel")}`)
            .setContent(html `
                ${until(gatewayListTemplate(), html `${i18next.t("loading")}`)}
            `);
        updateActions();
        showDialog(dialog);
    }
    /**
     * Function that attempts to start a new {@link tunnel}, but requires checking of its fields beforehand,
     * making sure the correct details are sent towards the {@link _startTunnel()} method.
     */
    _tryStartTunnel(tunnel) {
        if (!tunnel) {
            console.warn("Tried to add tunnel, but failed.");
            return;
        }
        if (!tunnel.gatewayId || !tunnel.target || !tunnel.targetPort) {
            console.warn("Tried to add tunnel, but not all fields were filled in!");
            return;
        }
        if (!tunnel.realm) {
            tunnel.realm = manager.displayRealm;
        }
        this._startTunnel(tunnel)
            .catch(e => {
            console.error(e);
            showSnackbar(undefined, i18next.t("errorOccurred"));
        })
            .finally(() => {
            this._fetchTunnelsTask.run();
        });
    }
    /**
     * Function that requests the manager to start a new {@link tunnel}.
     */
    _startTunnel(tunnel) {
        return __awaiter(this, void 0, void 0, function* () {
            const response = yield manager.rest.api.GatewayServiceResource.startTunnel(tunnel);
            return (response === null || response === void 0 ? void 0 : response.status) === 200;
        });
    }
    /**
     * Function that opens a web page through the tunnel (in a new browser tab)
     */
    _navigateToTunnel(info) {
        var _a;
        if (!info.realm || !info.gatewayId || !info.target || !info.targetPort) {
            console.warn("Could not navigate to tunnel, as some provided information was not set.");
        }
        const address = this._getTunnelAddress(info);
        switch (info.type) {
            case "HTTPS" /* GatewayTunnelInfoType.HTTPS */:
            case "HTTP" /* GatewayTunnelInfoType.HTTP */:
                (_a = window.open(address)) === null || _a === void 0 ? void 0 : _a.focus();
                break;
            default:
                console.error("Unknown error when navigating to tunnel.");
                break;
        }
    }
    /**
     * Internal function to get the tunnel address based on {@link GatewayTunnelInfo}
     */
    _getTunnelAddress(info) {
        switch (info.type) {
            case "HTTPS" /* GatewayTunnelInfoType.HTTPS */:
            case "HTTP" /* GatewayTunnelInfoType.HTTP */:
                return "//" + info.id + "." + (info.hostname ? info.hostname : window.location.hostname);
            case "TCP" /* GatewayTunnelInfoType.TCP */:
                return (info.hostname ? info.hostname : window.location.hostname) + ":" + info.assignedPort;
        }
    }
    /**
     * Internal asynchronous function to fetch the list of tunnels.
     */
    _fetchTunnels() {
        return __awaiter(this, arguments, void 0, function* (realm = manager.displayRealm, _signal) {
            const response = yield manager.rest.api.GatewayServiceResource.getAllActiveTunnelInfos(realm);
            if (response.status !== 200) {
                throw new Error(response.statusText);
            }
            if (response.data.length > 0) {
                this._updateRefreshTimer(response.data);
                return response.data;
            }
            else {
                console.warn("No tunnels were received from the manager.");
            }
        });
    }
    /**
     * Sets a timeout to refresh the tunnels whenever the next tunnel is automatically closed.
     */
    _updateRefreshTimer(tunnels) {
        if (this._refreshTimer) {
            clearTimeout(this._refreshTimer);
        }
        if (tunnels && tunnels.length > 0) {
            const nextCloseTime = tunnels.map(t => t.autoCloseTime).filter(t => t).sort()[0];
            if (nextCloseTime) {
                const timeout = nextCloseTime - Date.now();
                if (timeout > 0) {
                    this._refreshTimer = window.setTimeout(() => this._fetchTunnelsTask.run(), timeout);
                }
            }
        }
    }
    /* ------------------------------ */
    /**
     * Utility function to fetch the available gateway assets to select from
     */
    _fetchGatewayAssets() {
        return __awaiter(this, arguments, void 0, function* (requireTunnelSupport = true) {
            const query = {
                realm: { name: manager.displayRealm },
                select: { attributes: ["tunnelingSupported"] },
                types: this.GATEWAY_TUNNEL_TYPES,
                attributes: requireTunnelSupport ? {
                    items: [
                        {
                            name: {
                                predicateType: "string",
                                value: "tunnelingSupported"
                            },
                            value: {
                                predicateType: "boolean",
                                value: true
                            },
                        },
                        {
                            name: {
                                predicateType: "string",
                                value: "gatewayStatus"
                            },
                            value: {
                                predicateType: "string",
                                value: "CONNECTED"
                            }
                        }
                    ]
                } : undefined
            };
            return (yield manager.rest.api.AssetResource.queryAssets(query)).data;
        });
    }
    /**
     * Utility function that returns the default values of a new tunnel.
     */
    _getDefaultTunnelToAdd() {
        var _a;
        return {
            target: "localhost",
            targetPort: 443,
            type: (_a = this.GATEWAY_TUNNEL_PROTOCOL_TYPES) === null || _a === void 0 ? void 0 : _a[0]
        };
    }
};
__decorate([
    state()
], PageGatewayTunnel.prototype, "_realm", void 0);
__decorate([
    state()
], PageGatewayTunnel.prototype, "_loading", void 0);
PageGatewayTunnel = __decorate([
    customElement("page-gateway-tunnel")
], PageGatewayTunnel);
export { PageGatewayTunnel };
//# sourceMappingURL=page-gateway-tunnel.js.map