UNPKG

@hawtio/react

Version:

A Hawtio reimplementation based on TypeScript + React.

1,393 lines (1,338 loc) 144 kB
"use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } async function _asyncNullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return await rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; } var _class; var _class2; var _class3; var _class4; var _class5; var _class6; var _class7; var _class8; var _class9; var _chunkZYPGXT7Qjs = require('./chunk-ZYPGXT7Q.js'); // src/plugins/shared/HawtioEmptyCard.tsx var _reactcore = require('@patternfly/react-core'); var _infocircleicon = require('@patternfly/react-icons/dist/esm/icons/info-circle-icon'); var _jsxruntime = require('react/jsx-runtime'); var HawtioEmptyCard = ({ title, message, testid = "empty" }) => /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, _reactcore.Card, { children: [ title && /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _reactcore.CardTitle, { children: title }), /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _reactcore.CardBody, { children: /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, _reactcore.Text, { "data-testid": testid, component: "p", children: [ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _infocircleicon.InfoCircleIcon, {}), " ", message ] }) }) ] }); // src/plugins/shared/HawtioLoadingCard.tsx var HawtioLoadingCard = ({ message = "Loading...", testid = "loading" }) => /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _reactcore.Card, { children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _reactcore.CardBody, { children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _reactcore.Skeleton, { "data-testid": testid, screenreaderText: message }) }) }); // src/plugins/context.ts var _react = require('react'); var _react2 = _interopRequireDefault(_react); function usePluginNodeSelected() { const [selectedNode, setSelectedNode] = _react.useState.call(void 0, null); return { selectedNode, setSelectedNode }; } var PluginNodeSelectionContext = _react.createContext.call(void 0, { selectedNode: null, setSelectedNode: () => { } }); // src/plugins/shared/JmxContentMBeans.tsx var _reacttable = require('@patternfly/react-table'); var JmxContentMBeans = () => { const { selectedNode, setSelectedNode } = _react.useContext.call(void 0, PluginNodeSelectionContext); if (!selectedNode) { return null; } const rows = (selectedNode.children || []).map((node) => ({ name: node.name, objectName: _nullishCoalesce(node.objectName, () => ( "-")) })); if (rows.length === 0) { return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _reactcore.Card, { children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _reactcore.CardBody, { children: /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, _reactcore.Text, { component: _reactcore.TextVariants.p, children: [ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _infocircleicon.InfoCircleIcon, {}), " This node has no MBeans." ] }) }) }); } const selectChild = (clicked) => { const child = _optionalChain([selectedNode, 'access', _2 => _2.children, 'optionalAccess', _3 => _3.find, 'call', _4 => _4((c) => c.name === clicked)]); if (child) { setSelectedNode(child); } }; return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _reactcore.Panel, { children: /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, _reacttable.Table, { "aria-label": "MBeans", variant: "compact", children: [ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _reacttable.Thead, { children: /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, _reacttable.Tr, { children: [ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _reacttable.Th, { width: 50, children: "MBean" }), /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _reacttable.Th, { width: 50, children: "Object Name" }) ] }) }), /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _reacttable.Tbody, { className: "jmx-table-body", children: rows.map((r) => /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, _reacttable.Tr, { onClick: () => selectChild(r.name), children: [ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _reacttable.Td, { children: r.name }), /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _reacttable.Td, { children: r.objectName }) ] }, "row-" + r.name)) }) ] }) }); }; // src/plugins/shared/PluginTreeViewToolbar.tsx var PluginTreeViewToolbar = (props) => { const [expanded, setExpanded] = _react.useState.call(void 0, false); const onSearch = (event) => { if (props.onSearch) { props.onSearch(event); } }; const toggleExpanded = () => { const newExpanded = !expanded; setExpanded(newExpanded); if (props.onSetExpanded) { props.onSetExpanded(newExpanded); } }; return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _reactcore.Toolbar, { style: { padding: 0 }, children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _reactcore.ToolbarContent, { style: { padding: 0 }, children: /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, _reactcore.ToolbarGroup, { variant: "filter-group", children: [ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _reactcore.ToolbarItem, { variant: "search-filter", widths: { default: "100%" }, children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _reactcore.TreeViewSearch, { onSearch, id: "input-search", name: "search-input", "aria-label": "Search input example" } ) }), /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _reactcore.ToolbarItem, { variant: "expand-all", children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _reactcore.Button, { variant: "link", "aria-label": "Expand Collapse", onClick: toggleExpanded, children: expanded ? "Collapse all" : "Expand all" }) }) ] }) }) }); }; // src/util/crypto.ts var _thumbmarkjs = require('@thumbmarkjs/thumbmarkjs'); async function generateKey(salt) { const fingerprint = await _thumbmarkjs.getFingerprint.call(void 0, ); const data = new TextEncoder().encode(fingerprint); const key = await window.crypto.subtle.importKey("raw", data, { name: "PBKDF2" }, false, ["deriveKey"]); const algorithm = { name: "PBKDF2", salt, iterations: 1e5, hash: "SHA-256" }; const keyType = { name: "AES-GCM", length: 256 }; return window.crypto.subtle.deriveKey(algorithm, key, keyType, true, ["encrypt", "decrypt"]); } function toBase64(data) { return window.btoa(String.fromCharCode(...Array.from(data))); } function toByteArray(data) { return new Uint8Array(Array.from(window.atob(data)).map((c) => c.charCodeAt(0))); } async function encrypt(key, data) { const iv = window.crypto.getRandomValues(new Uint8Array(12)); const encodedData = new TextEncoder().encode(data); const encrypted = await window.crypto.subtle.encrypt({ name: "AES-GCM", iv }, key, encodedData); return toBase64(iv) + "." + toBase64(new Uint8Array(encrypted)); } async function decrypt(key, data) { const iv = toByteArray(_nullishCoalesce(data.split(".")[0], () => ( ""))); const encrypted = toByteArray(_nullishCoalesce(data.split(".")[1], () => ( ""))); const decrypted = await window.crypto.subtle.decrypt({ name: "AES-GCM", iv }, key, encrypted); return new TextDecoder("utf-8").decode(new Uint8Array(decrypted)); } // src/util/https.ts function basicAuthHeaderValue(username, password) { const base64UserPass = window.btoa(`${username}:${password}`); return `Basic ${base64UserPass}`; } function getCookie(name) { if (name == null) { return null; } const cookies = document.cookie.split(";"); const cookie = cookies.map((cookie2) => cookie2.split("=")).find((cookie2) => cookie2.length > 1 && cookie2[0] === name); return _nullishCoalesce(_optionalChain([cookie, 'optionalAccess', _5 => _5[1]]), () => ( null)); } // src/util/urls.ts function joinPaths(...paths) { const tmp = []; paths.forEach((path, index) => { if (_chunkZYPGXT7Qjs.isBlank.call(void 0, path)) { return; } if (path === "/") { tmp.push(""); return; } if (index !== 0 && path.match(/^\//)) { path = path.slice(1); } if (index < paths.length - 1 && path.match(/\/$/)) { path = path.slice(0, path.length - 1); } if (!_chunkZYPGXT7Qjs.isBlank.call(void 0, path)) { tmp.push(path); } }); return tmp.join("/"); } function getQueryParameterValue(url, parameterName) { const query = _nullishCoalesce(url.split("?")[1], () => ( "")); const params = _nullishCoalesce(query.split("&"), () => ( [])); const value = _optionalChain([params, 'access', _6 => _6.map, 'call', _7 => _7((param) => param.split("=")), 'access', _8 => _8.find, 'call', _9 => _9(([key, _]) => key && decodeURIComponent(key) === parameterName), 'optionalAccess', _10 => _10[1]]); return value ? decodeURIComponent(value) : null; } // src/plugins/shared/connect-service.ts var _simple = require('@jolokia.js/simple'); var _simple2 = _interopRequireDefault(_simple); // src/plugins/shared/globals.ts var pluginName = "hawtio-shared"; var log = _chunkZYPGXT7Qjs.Logger.get(pluginName); // src/plugins/connect/globals.ts var pluginId = "connect"; var statusPluginId = "connectStatus"; var pluginTitle = "Connect"; var pluginPath = "/connect"; var pluginName2 = "hawtio-connect"; var log2 = _chunkZYPGXT7Qjs.Logger.get(pluginName2); var PATH_PROXY_ENABLED = "proxy/enabled"; // src/plugins/connect/init.ts async function isActive() { const proxyEnabled2 = await isProxyEnabled(); if (!proxyEnabled2) { return false; } return connectService.getCurrentConnectionId() === null || isConnectLogin(); } async function isConnectionStatusActive() { const proxyEnabled2 = await isProxyEnabled(); if (!proxyEnabled2) { return false; } return connectService.getCurrentConnectionId() !== null; } var proxyEnabled = null; async function isProxyEnabled() { if (proxyEnabled != null) { return proxyEnabled; } try { _chunkZYPGXT7Qjs.configManager.initItem("Checking proxy", 0 /* started */, "config"); const res = await fetch(PATH_PROXY_ENABLED); if (!res.ok) { _chunkZYPGXT7Qjs.configManager.initItem("Checking proxy", 1 /* skipped */, "config"); log2.debug("Failed to fetch", PATH_PROXY_ENABLED, ":", res.status, res.statusText); proxyEnabled = false; } else { const data = await res.text(); proxyEnabled = data.trim() !== "false"; _chunkZYPGXT7Qjs.configManager.initItem("Checking proxy", proxyEnabled ? 2 /* finished */ : 1 /* skipped */, "config"); log2.debug("Proxy enabled:", proxyEnabled); } } catch (err) { _chunkZYPGXT7Qjs.configManager.initItem("Checking proxy", 1 /* skipped */, "config"); log2.debug("Failed to fetch", PATH_PROXY_ENABLED, ":", err); proxyEnabled = false; } return proxyEnabled; } function isConnectLogin() { const url = new URL(window.location.href); return url.pathname === connectService.getLoginPath(); } function registerUserHooks() { const credPromise = connectService.getCurrentCredentials(); _chunkZYPGXT7Qjs.userService.addLogoutHook("connect", async () => { const credentials = await credPromise; if (!credentials) { return false; } window.close(); return true; }); } // src/plugins/shared/connect-service.ts var INITIAL_CONNECTION = { id: "", name: "", scheme: "https", host: "localhost", port: 8080, path: "/hawtio/jolokia" }; var STORAGE_KEY_CONNECTIONS = "connect.connections"; var SESSION_KEY_SALT = "connect.salt"; var SESSION_KEY_CREDENTIALS = "connect.credentials"; var SESSION_KEY_CURRENT_CONNECTION = "connect.currentConnection"; var PARAM_KEY_CONNECTION = "con"; var PARAM_KEY_REDIRECT = "redirect"; var PATH_LOGIN = "/connect/login"; var PATH_PRESET_CONNECTIONS = "/preset-connections"; var ConnectService = class { constructor() { this.currentConnectionId = this.initCurrentConnectionId(); isActive(); } /** * The precedence of the current connection is as follows: * 1. URL query parameter: {@link PARAM_KEY_CONNECTION} * 2. Session storage: {@link SESSION_KEY_CURRENT_CONNECTION} * 3. Preset connections from the backend API: {@link PATH_PRESET_CONNECTIONS} * (after page reload or new tabs opened) */ initCurrentConnectionId() { const url = new URL(window.location.href); const searchParams = url.searchParams; log.debug("Checking search params:", searchParams.toString()); const idOrName = searchParams.get(PARAM_KEY_CONNECTION); if (idOrName) { const connId2 = this.resolveConnectionId(idOrName); if (connId2) { sessionStorage.setItem(SESSION_KEY_CURRENT_CONNECTION, JSON.stringify(connId2)); } searchParams.delete(PARAM_KEY_CONNECTION, idOrName); url.search = searchParams.toString(); window.history.replaceState(null, "", url); return connId2; } const connId = sessionStorage.getItem(SESSION_KEY_CURRENT_CONNECTION); if (connId) { return JSON.parse(connId); } this.loadPresetConnections(); return null; } resolveConnectionId(idOrName) { const conns = this.loadConnections(); if (conns[idOrName]) { return idOrName; } return _nullishCoalesce(_optionalChain([Object, 'access', _11 => _11.values, 'call', _12 => _12(conns), 'access', _13 => _13.find, 'call', _14 => _14((c) => c.name === idOrName), 'optionalAccess', _15 => _15.id]), () => ( null)); } /** * See: https://github.com/hawtio/hawtio/issues/3731 */ async loadPresetConnections() { try { _chunkZYPGXT7Qjs.configManager.initItem("Checking preset connections", 0 /* started */, "config"); const path = this.getPresetConnectionsPath(); const res = await fetch(path); if (!res.ok) { _chunkZYPGXT7Qjs.configManager.initItem("Checking preset connections", 1 /* skipped */, "config"); log.debug("Failed to load preset connections:", res.status, res.statusText); return; } const preset = await res.json(); log.debug("Preset connections:", preset); const connections = this.loadConnections(); const toOpen = []; preset.forEach(({ name, scheme, host, port, path: path2 }) => { if (!name) { return; } let conn = Object.values(connections).find((c) => c.name === name); if (scheme && host && port && path2) { if (port < 0) { port = scheme === "https" ? 443 : 80; } if (!conn) { conn = { id: "", name, scheme, host, port, path: path2 }; this.generateId(conn, connections); connections[conn.id] = conn; } else { conn.scheme = scheme; conn.host = host; conn.port = port; conn.path = path2; } toOpen.push(conn); } else if (conn) { toOpen.push(conn); } }); this.saveConnections(connections); _chunkZYPGXT7Qjs.configManager.initItem("Checking preset connections", 2 /* finished */, "config"); const first = toOpen.shift(); toOpen.forEach((c) => this.connect(c)); if (first) { this.connect(first, true); } } catch (err) { _chunkZYPGXT7Qjs.configManager.initItem("Checking preset connections", 1 /* skipped */, "config"); log.debug("Error loading preset connections:", err); } } getPresetConnectionsPath() { const basePath = _chunkZYPGXT7Qjs.hawtio.getBasePath(); return basePath ? `${basePath}${PATH_PRESET_CONNECTIONS}` : PATH_PRESET_CONNECTIONS; } getCurrentConnectionId() { return this.currentConnectionId; } getCurrentConnectionName() { const id = this.currentConnectionId; if (!id) { return null; } const connection = this.getConnection(id); return _nullishCoalesce(_optionalChain([connection, 'optionalAccess', _16 => _16.name]), () => ( null)); } async getCurrentConnection() { const id = this.currentConnectionId; const conn = id ? this.getConnection(id) : null; if (!conn) { return null; } const credentials = await this.getCurrentCredentials(); if (!credentials) { return conn; } conn.username = credentials.username; conn.password = credentials.password; this.clearCredentialsOnLogout(); return conn; } setCurrentConnection(connection) { const connectionId = connection.id; if (!this.resolveConnectionId(connectionId)) { log.warn("Cannot resolve connection Id in saved connections"); return; } sessionStorage.setItem(SESSION_KEY_CURRENT_CONNECTION, JSON.stringify(connectionId)); this.currentConnectionId = connectionId; } clearCredentialsOnLogout() { _chunkZYPGXT7Qjs.eventService.onLogout(() => sessionStorage.clear()); } async getCurrentCredentials() { if (!window.isSecureContext) { return null; } const saltItem = sessionStorage.getItem(SESSION_KEY_SALT); if (!saltItem) { return null; } const salt = toByteArray(saltItem); const credItem = sessionStorage.getItem(SESSION_KEY_CREDENTIALS); if (!credItem) { return null; } const key = await generateKey(salt); return JSON.parse(await decrypt(key, credItem)); } async setCurrentCredentials(credentials) { const salt = window.crypto.getRandomValues(new Uint8Array(16)); sessionStorage.setItem(SESSION_KEY_SALT, toBase64(salt)); const key = await generateKey(salt); const encrypted = await encrypt(key, JSON.stringify(credentials)); sessionStorage.setItem(SESSION_KEY_CREDENTIALS, encrypted); } loadConnections() { const item = localStorage.getItem(STORAGE_KEY_CONNECTIONS); if (!item) { return {}; } const conns = JSON.parse(item); Object.values(conns).forEach((conn) => { if (conn.scheme !== "http" && conn.scheme !== "https") { log.warn("Invalid scheme for connection:", conn); conn.scheme = "http"; } if (!conn.id) { this.generateId(conn, conns); } }); return conns; } saveConnections(connections) { localStorage.setItem(STORAGE_KEY_CONNECTIONS, JSON.stringify(connections)); } generateId(connection, connections) { for (; ; ) { if (!connection.id) { connection.id = "" + Math.floor(Math.random() * 1e6); connection.id = "c" + connection.id.padStart(6, "0") + "-" + Date.now(); } let exists = false; for (const c in connections) { if (c === connection.id) { exists = true; } if (exists) { connection.id = ""; } } if (!exists) { break; } } } getConnection(id) { const connections = this.loadConnections(); return _nullishCoalesce(connections[id], () => ( null)); } connectionToUrl(connection) { let url = `${connection.scheme}://${connection.host}:${connection.port}`; if (!connection.path.startsWith("/")) { url += "/"; } url += connection.path; return url; } async checkReachable(connection) { try { const result = await this.testConnection(connection); return result.status; } catch (error) { return "not-reachable"; } } testConnection(connection) { log.debug("Testing connection:", _chunkZYPGXT7Qjs.toString.call(void 0, connection)); return new Promise((resolve, reject) => { try { const xsrfToken = getCookie("XSRF-TOKEN"); const headers = {}; if (connection.username && connection.password) { headers["X-Jolokia-Authorization"] = basicAuthHeaderValue(connection.username, connection.password); } if (xsrfToken) { headers["X-XSRF-TOKEN"] = xsrfToken; } fetch(this.getJolokiaUrl(connection), { method: "post", // with application/json, I'm getting "CanceledError: Request stream has been aborted" when running // via hawtioMiddleware... headers: { ...headers, "Content-Type": "text/json" }, credentials: "same-origin", body: JSON.stringify({ type: "version" }) }).then((response) => { if (response.ok) { resolve({ status: "reachable", message: "Connection successful" }); } else if (response.status === 401) { resolve({ status: window.isSecureContext ? "reachable" : "not-reachable-securely", message: window.isSecureContext ? "Connection successful (auth needed)" : "Connection failed (insecure context)" }); } else if (response.status === 403) { this.forbiddenReasonMatches(response, "HOST_NOT_ALLOWED").then((matches) => { if (matches) { resolve({ status: "not-reachable", message: "Host not allowlisted" }); } else { resolve({ status: window.isSecureContext ? "reachable" : "not-reachable-securely", message: window.isSecureContext ? "Connection successful (auth failed)" : "Connection failed (insecure context)" }); } }); } else { resolve({ status: "not-reachable", message: "Connection failed" }); } }).catch((error) => { log.error("Exception", error); reject(error); }); } catch (error) { log.error(error); reject(error); } }); } async forbiddenReasonMatches(response, reason) { return response.text().then((txt) => { try { const json = JSON.parse(txt); return json["reason"] === reason; } catch (e) { return response.headers.get("Hawtio-Forbidden-Reason") === reason; } }); } connect(connection, current = false) { log.debug("Connecting with options:", _chunkZYPGXT7Qjs.toString.call(void 0, connection)); const basepath = _nullishCoalesce(_chunkZYPGXT7Qjs.hawtio.getBasePath(), () => ( "")); const url = `${basepath}/?${PARAM_KEY_CONNECTION}=${connection.id}`; if (current) { log.debug("Redirecting to URL:", url); window.location.href = url; } else { log.debug("Opening URL:", url); window.open(url, connection.id); } } /** * Log in to the current connection. */ async login(username, password) { const connection = await this.getCurrentConnection(); if (!connection) { return { type: "failure" }; } const result = await new Promise((resolve) => { const headers = { "X-Jolokia-Authorization": basicAuthHeaderValue(username, password) }; const token = getCookie("XSRF-TOKEN"); if (token) { headers["X-XSRF-TOKEN"] = token; } this.createJolokia(connection, true).request( { type: "version" }, { success: () => resolve({ type: "success" }), // this handles Jolokia error (HTTP status = 200, Jolokia status != 200) - unlikely for "version" request error: () => resolve({ type: "failure" }), // this handles HTTP status != 200 or other communication error (like connection refused) fetchError: (response, error) => { if (response) { log.debug("Login error:", response.status, response.statusText); if (response.status === 429) { const retryAfter = parseInt(_nullishCoalesce(response.headers.get("Retry-After"), () => ( "0"))); resolve({ type: "throttled", retryAfter }); return; } if (response.status === 403 && "SESSION_EXPIRED" === response.headers.get("Hawtio-Forbidden-Reason")) { resolve({ type: "session-expired" }); return; } } else { log.debug("Login error:", error); } resolve({ type: "failure" }); }, headers } ); }); if (result.type !== "success") { return result; } if (window.isSecureContext) { await this.setCurrentCredentials({ username, password }); } this.clearCredentialsOnLogout(); return result; } /** * Redirect to the URL specified in the query parameter {@link PARAM_KEY_REDIRECT}. */ redirect() { const url = new URL(window.location.href); let redirect = _nullishCoalesce(url.searchParams.get(PARAM_KEY_REDIRECT), () => ( "/")); let safeRedirect = false; try { const { hostname, port, protocol, searchParams } = new URL(redirect); let connectionKey = _nullishCoalesce(searchParams.get(PARAM_KEY_CONNECTION), () => ( "")); if (connectionKey === "") { connectionKey = _nullishCoalesce(sessionStorage.getItem(SESSION_KEY_CURRENT_CONNECTION), () => ( "")); } safeRedirect = hostname === url.hostname && port === url.port && ["http:", "https:"].includes(protocol) && connectionKey !== "" && connectionKey === this.currentConnectionId; } catch (_e) { log.error("Invalid URL"); _chunkZYPGXT7Qjs.eventService.notify({ type: "danger", message: "Redirect parameter was modified" }); } if (!safeRedirect) { redirect = _nullishCoalesce(_chunkZYPGXT7Qjs.hawtio.getBasePath(), () => ( "/")); } log.debug("Redirect to:", redirect); window.location.href = encodeURI(redirect); } /** * Create a Jolokia instance with the given connection. */ createJolokia(connection, checkCredentials = false) { if (checkCredentials) { return new (0, _simple2.default)({ url: this.getJolokiaUrl(connection), method: "post", mimeType: "application/json", username: connection.username, password: connection.password }); } return new (0, _simple2.default)({ url: this.getJolokiaUrl(connection), method: "post", mimeType: "application/json" }); } /** * Get the Jolokia URL for the given connection. */ getJolokiaUrl(connection) { log.debug("Connect to server with connection:", _chunkZYPGXT7Qjs.toString.call(void 0, connection)); if (connection.jolokiaUrl) { log.debug("Using provided URL:", connection.jolokiaUrl); return connection.jolokiaUrl; } const url = joinPaths( _nullishCoalesce(_chunkZYPGXT7Qjs.hawtio.getBasePath(), () => ( "")), "/proxy", _nullishCoalesce(connection.scheme, () => ( "http")), _nullishCoalesce(connection.host, () => ( "localhost")), String(_nullishCoalesce(connection.port, () => ( 80))), connection.path ); log.debug("Using URL:", url); return url; } /** * Get the Jolokia URL for the given connection ID. */ getJolokiaUrlFromId(id) { const connection = this.getConnection(id); return connection ? this.getJolokiaUrl(connection) : null; } getLoginPath() { const basePath = _chunkZYPGXT7Qjs.hawtio.getBasePath(); return basePath ? `${basePath}${PATH_LOGIN}` : PATH_LOGIN; } export(connections) { const content = JSON.stringify(Object.values(connections), null, " "); const url = URL.createObjectURL(new Blob([content], { type: "application/json" })); const link = document.createElement("a"); link.href = url; link.download = `hawtio-connections-${Date.now()}.json`; document.body.appendChild(link); link.click(); document.body.removeChild(link); URL.revokeObjectURL(url); } }; var connectService = new ConnectService(); // src/util/htmls.ts function escapeTags(text) { return text.replace("<", "&lt;").replace(">", "&gt;"); } function escapeHtmlId(text) { return text.replace(/\s/g, ""); } // src/util/objects.ts function isObject(value) { const type2 = typeof value; return value != null && (type2 === "object" || type2 === "function"); } function cloneObject(value) { return structuredClone(value); } function isEmpty(obj) { return Object.keys(obj).length === 0; } function isString(value) { return typeof value === "string" || value instanceof String; } function isNumber(value) { return typeof value === "number" && !Number.isNaN(value); } function isTranslatableToNumber(value) { return ["number", "string"].includes(typeof value) && value !== "" && !Number.isNaN(Number(value)); } function roundNumber(value, decimals) { return value && isTranslatableToNumber(value) ? +Number(value).toFixed(decimals || 0) : value; } function objectSorter(aValue, bValue, sortDesc) { if (isNumber(aValue)) { if (!sortDesc) { return aValue - bValue; } return bValue - aValue; } else { return _chunkZYPGXT7Qjs.stringSorter.call(void 0, aValue, bValue, sortDesc); } } // src/plugins/shared/tree/node.ts var _cubeicon = require('@patternfly/react-icons/dist/esm/icons/cube-icon'); var _foldericon = require('@patternfly/react-icons/dist/esm/icons/folder-icon'); var _folderopenicon = require('@patternfly/react-icons/dist/esm/icons/folder-open-icon'); var _lockicon = require('@patternfly/react-icons/dist/esm/icons/lock-icon'); var _superstruct = require('superstruct'); // src/plugins/shared/tree/globals.ts var log3 = _chunkZYPGXT7Qjs.Logger.get(`${pluginName}-tree`); // src/plugins/shared/tree/node.ts var Icons = { folder: _react2.default.createElement(_foldericon.FolderIcon), folderOpen: _react2.default.createElement(_folderopenicon.FolderOpenIcon), mbean: _react2.default.createElement(_cubeicon.CubeIcon), locked: _react2.default.createElement(_lockicon.LockIcon) }; function isJmxDomains(value) { return _superstruct.is.call(void 0, value, _superstruct.record.call(void 0, _superstruct.string.call(void 0, ), _superstruct.define.call(void 0, "JmxDomain", isJmxDomain))); } function isJmxDomain(value) { const isMBeanInfoOrError = (value2) => isMBeanInfo(value2) || isMBeanInfoError(value2); return _superstruct.is.call(void 0, value, _superstruct.record.call(void 0, _superstruct.string.call(void 0, ), _superstruct.define.call(void 0, "MBeanInfo", isMBeanInfoOrError))); } function isMBeanInfo(value) { return _superstruct.is.call(void 0, value, _superstruct.type.call(void 0, { desc: _superstruct.optional.call(void 0, _superstruct.nullable.call(void 0, _superstruct.string.call(void 0, ))), class: _superstruct.optional.call(void 0, _superstruct.string.call(void 0, )), attr: _superstruct.optional.call(void 0, _superstruct.record.call(void 0, _superstruct.string.call(void 0, ), _superstruct.object.call(void 0, ))), op: _superstruct.optional.call(void 0, _superstruct.record.call(void 0, _superstruct.string.call(void 0, ), _superstruct.union.call(void 0, [_superstruct.object.call(void 0, ), _superstruct.array.call(void 0, _superstruct.object.call(void 0, ))]))), notif: _superstruct.optional.call(void 0, _superstruct.record.call(void 0, _superstruct.string.call(void 0, ), _superstruct.object.call(void 0, ))) }) ); } function isMBeanInfoError(value) { return _superstruct.is.call(void 0, value, _superstruct.type.call(void 0, { error: _superstruct.string.call(void 0, ) })); } var MBEAN_NODE_ID_SEPARATOR = "-"; var MBeanNode = (_class = class _MBeanNode { /** * A new node * @constructor * @param {MBeanNode|null} parent - The parent of the new node. Otherwise, for a singleton node use null. * @param {string} name - The name of the new node. * @param {boolean} folder - Whether this new node is a folder, ie. has children */ constructor(parent, name, folder) {;_class.prototype.__init.call(this); this.parent = parent; this.name = name; this.folder = folder; if (this === parent) throw new Error("Node cannot be its own parent"); if (folder) { this.icon = Icons.folder; this.expandedIcon = Icons.folderOpen; this.children = []; } else { this.icon = Icons.mbean; } this.id = this.generateId(folder); } /** * ID of the tree view item in HTML. */ /** * Various metadata that can be attached to the node for processing it in the MBean tree. */ __init() {this.metadata = {}} // MBean info // TreeViewDataItem properties generateId(folder) { const idPrefix = this.parent ? this.parent.id + MBEAN_NODE_ID_SEPARATOR : ""; const idPostfix = folder ? "-folder" : ""; let id = idPrefix + escapeHtmlId(this.name) + idPostfix; if (this.parent) { this.parent.getChildren().forEach((child) => { if (child === this) return; if (child.id === id) id = id + "-" + Math.floor(Math.random() * 100); }); } return id; } initId(recursive) { this.id = this.generateId(this.children !== void 0); if (recursive) { _optionalChain([this, 'access', _17 => _17.children, 'optionalAccess', _18 => _18.forEach, 'call', _19 => _19((c) => c.initId(recursive))]); } } populateMBean(propList, mbean) { log3.debug(" JMX tree mbean:", propList); const props = new PropertyList(this, propList); this.createMBeanNode(props.getPaths(), props, mbean); } createMBeanNode(paths, props, mbean) { log3.debug(" JMX tree property:", paths[0]); if (paths.length === 1) { const path2 = paths[0]; if (!path2) { log3.error("Failed to process MBean. Malformed ObjectName:", `"${props.objectName()}"`); return; } const mbeanNode = this.create(path2, false); mbeanNode.configureMBean(props, mbean); return; } const path = paths.shift(); if (path === void 0) { log3.error("Failed to process MBean. Malformed ObjectName:", `"${props.objectName()}"`); return; } const child = this.getOrCreate(path, true); child.createMBeanNode(paths, props, mbean); } configureMBean(propList, mbean) { this.objectName = propList.objectName(); this.mbean = mbean; this.propertyList = propList; this.applyCanInvoke(); } applyCanInvoke() { if (!this.mbean) { return; } if (this.mbean.canInvoke !== void 0 && !this.mbean.canInvoke) { this.icon = Icons.locked; } } /** * Copy the node to a new node with the given name, transferring the icons, children, * metadata, and MBean info. */ copyTo(name) { const copy = new _MBeanNode(null, name, this.folder); copy.icon = this.icon; copy.expandedIcon = this.expandedIcon; copy.children = this.children; copy.metadata = this.metadata; copy.objectName = this.objectName; copy.mbean = this.mbean; copy.propertyList = this.propertyList; return copy; } /** * Find children with the given name. There can be at most two nodes with the * same name, one as an MBean and the other as a folder. * * Think about the following case: * - MBean1: 'com.example:type=Example,name=App' * - MBean2: 'com.example:type=Example,name=App,sub=Part1' * - MBean3: 'com.example:type=Example,name=App,sub=Part2' * In this case, there can be two nodes with the same name 'App', one is MBean1, * and the other is the folder that contains MBean2 and MBean3. */ findChildren(name) { return _nullishCoalesce(_optionalChain([this, 'access', _20 => _20.children, 'optionalAccess', _21 => _21.filter, 'call', _22 => _22((node) => node.name === name)]), () => ( [])); } /** * Return a child node with the given name or null. The 'folder' parameter is * required to identify a single node, as there can be at most two nodes with * the same name, one as an MBean and the other as a folder. * * See the JSDoc comment for the findChildren(name: string) method for more detail. */ get(name, folder) { const candidates = this.findChildren(name); return _nullishCoalesce(candidates.find((node) => node.folder === folder), () => ( null)); } getIndex(index) { return _nullishCoalesce(_optionalChain([this, 'access', _23 => _23.children, 'optionalAccess', _24 => _24[index]]), () => ( null)); } getChildren() { return _nullishCoalesce(this.children, () => ( [])); } create(name, folder) { if (this.children === void 0) { this.icon = Icons.folder; this.expandedIcon = Icons.folderOpen; this.children = []; } const newChild = new _MBeanNode(this, name, folder); this.children.push(newChild); return newChild; } getOrCreate(name, folder) { const node = this.get(name, folder); if (node) { return node; } return this.create(name, folder); } removeChildren() { if (!this.children) return []; const remove = this.children; this.children = []; for (const r of remove) { r.parent = null; } return remove; } removeChild(child) { if (!this.children || !child) return null; const index = this.children.indexOf(child); if (index === -1) return null; const removed = _nullishCoalesce(this.children.splice(index, 1)[0], () => ( null)); if (removed) { removed.parent = null; } return removed; } childCount() { return this.children ? this.children.length : 0; } getType() { return this.getMetadata("type"); } setType(type2) { this.addMetadata("type", type2); } getMetadata(key) { return this.metadata[key]; } addMetadata(key, value) { this.metadata[key] = value; } getProperty(key) { return _optionalChain([this, 'access', _25 => _25.propertyList, 'optionalAccess', _26 => _26.get, 'call', _27 => _27(key)]); } static sorter(a, b) { const res = _chunkZYPGXT7Qjs.stringSorter.call(void 0, a.name, b.name); if (res !== 0) { return res; } return Number(b.folder) - Number(a.folder); } sort(recursive) { if (!this.children) return; _optionalChain([this, 'access', _28 => _28.children, 'optionalAccess', _29 => _29.sort, 'call', _30 => _30(_MBeanNode.sorter)]); if (recursive) { _optionalChain([this, 'access', _31 => _31.children, 'optionalAccess', _32 => _32.forEach, 'call', _33 => _33((child) => child.sort(recursive))]); } } path() { const path = [this.name]; let p = this.parent; while (p) { path.unshift(p.name); p = p.parent; } return path; } navigate(...namePath) { if (namePath.length === 0) return this; const name = namePath[0]; if (!name) return null; const child = this.findByNamePattern(name); return _nullishCoalesce(_optionalChain([child, 'optionalAccess', _34 => _34.navigate, 'call', _35 => _35(...namePath.slice(1))]), () => ( null)); } /** * Perform a function on each node in the given path * where the namePath drills down to descendants from * this node */ forEach(namePath, eachFn) { if (namePath.length === 0) return; const name = namePath[0]; if (!name) return; const child = this.findByNamePattern(name); if (!child) return; eachFn(child); child.forEach(namePath.slice(1), eachFn); } /** * Searches this node and all its descendants for the first node to match the filter. */ find(filter) { if (filter(this)) { return this; } return _nullishCoalesce(_optionalChain([this, 'access', _36 => _36.children, 'optionalAccess', _37 => _37.map, 'call', _38 => _38((child) => child.find(filter)), 'access', _39 => _39.find, 'call', _40 => _40((node) => node !== null)]), () => ( null)); } findByNamePattern(name) { return this.find((node) => _chunkZYPGXT7Qjs.matchWithWildcard.call(void 0, node.name, name)); } /** * Finds MBeans in this node and all its descendants based on the properties. */ findMBeans(properties) { const mbeans = this.match(properties) ? [this] : []; _optionalChain([this, 'access', _41 => _41.children, 'optionalAccess', _42 => _42.forEach, 'call', _43 => _43((child) => mbeans.push(...child.findMBeans(properties)))]); return mbeans; } /** * Matches the node with the given MBean properties. * Since only MBean node holds the properties, this method always returns false * when invoked on a folder node. */ match(properties) { return _nullishCoalesce(_optionalChain([this, 'access', _44 => _44.propertyList, 'optionalAccess', _45 => _45.match, 'call', _46 => _46(properties)]), () => ( false)); } /** * Returns the chain of nodes forming the tree branch of ancestors * @method findAncestors * @for Node * @return {MBeanNode[]} */ findAncestors() { const chain = []; let ancestor = this.parent; while (ancestor !== null) { chain.unshift(ancestor); ancestor = ancestor.parent; } return chain; } /** * Returns the first node in the tree branch of ancestors that satisfies the given filter * @method findAncestor * @for Node * @return {MBeanNode} */ findAncestor(filter) { let ancestor = this.parent; while (ancestor !== null) { if (filter(ancestor)) return ancestor; ancestor = ancestor.parent; } return null; } filterClone(filter) { const copyChildren = []; if (this.children) { this.children.forEach((child) => { const childCopy = child.filterClone(filter); if (childCopy) { copyChildren.push(childCopy); } }); } if (copyChildren.length === 0 && !filter(this)) { return null; } const copy = new _MBeanNode(this, this.name, copyChildren.length > 0); if (copyChildren.length > 0) { copy.children = copyChildren; } copy.icon = this.icon; copy.expandedIcon = this.expandedIcon; return copy; } adopt(child) { if (!this.children) { this.children = []; } if (this === child) throw new Error("Node cannot be its own child"); if (child.parent) child.parent.removeChild(child); child.parent = this; this.children.push(child); } /** * Recursive method to flatten MBeans. */ flatten(mbeans) { if (this.objectName) { mbeans[this.objectName] = this; } _optionalChain([this, 'access', _47 => _47.children, 'optionalAccess', _48 => _48.forEach, 'call', _49 => _49((child) => child.flatten(mbeans))]); } /** * Returns true if RBACDecorator has been already applied to this node at server side. * If the node doesn't have mbean or mbean.op, it always returns true. * https://github.com/hawtio/hawtio/blob/main/platforms/hawtio-osgi-jmx/src/main/java/io/hawt/osgi/jmx/RBACDecorator.java */ isRBACDecorated() { if (!this.mbean || !this.mbean.op || isEmpty(this.mbean.op)) { return true; } return this.mbean.opByString !== void 0 && !isEmpty(this.mbean.opByString); } updateCanInvoke(canInvoke) { if (!this.mbean) { return; } this.mbean.canInvoke = canInvoke; this.applyCanInvoke(); } setIcons(icon, expandedIcon = icon) { this.icon = icon; this.expandedIcon = expandedIcon; } /** * Returns true only if all the given operations exist in this MBean node. */ hasOperations(...names) { if (!this.mbean || !this.mbean.op) { return false; } const operations = this.mbean.op; return names.every((name) => operations[name] !== void 0); } /** * Returns true only if all the given methods can be invoked. */ hasInvokeRights(...methods) { const mbean = this.mbean; if (!mbean) return true; let canInvoke = _nullishCoalesce(mbean.canInvoke, () => ( true)); if (canInvoke && methods && methods.length > 0) { const opsByString = mbean.opByString; const ops = mbean.op; if (opsByString && ops) { canInvoke = this.resolveCanInvokeInOps(ops, opsByString, methods); } } return canInvoke; } /** * Returns true only if all relevant operations can be invoked. */ resolveCanInvokeInOps(ops, opsByString, methods) { let canInvoke = true; methods.forEach((method) => { if (!canInvoke) { return; } let op = null; if (method.endsWith(")")) { op = opsByString[method]; } else { op = ops[method]; } if (!op) { log3.debug("Could not find method:", method, "to check permissions, skipping"); return; } canInvoke = this.resolveCanInvoke(op); }); return canInvoke; } resolveCanInvoke(op) { if (!Array.isArray(op)) { return _nullishCoalesce(op.canInvoke, () => ( true)); } const cantInvoke = op.find((o) => o.canInvoke !== void 0 && !o.canInvoke); return cantInvoke === void 0; } }, _class); var PropertyList = (_class2 = class { constructor(domain, propList) {;_class2.prototype.__init2.call(this);_class2.prototype.__init3.call(this);_class2.prototype.__init4.call(this); this.domain = domain; this.propList = propList; this.parse(propList); } __init2() {this.properties = {}} __init3() {this.paths = []} // TODO: typeName needed? // TODO: serviceName needed? __init4() {this.propRegex = new RegExp( `(([^=,]+)=(\\\\"[^"]+\\\\"|\\\\'[^']+\\\\'|"[^"]+"|'[^']+'|[^,]+))|([^=,]+)`, "g" )} parse(propList) { let match; while (match = this.propRegex.exec(propList)) { const [propKey, propValue] = this.parseProperty(match[0]); this.properties[propKey] = propValue; let index = -1; const lowerKey = propKey.toLowerCase(); const path = { key: lowerKey, value: propValue }; switch (lowerKey) { case "type": this.typeName = propValue; if (this.domain.findChildren(propValue).length > 0) { index = 0; } else if (this.properties["name"]) { index = this.paths.findIndex((p) => p.key === "name"); } break; case "service": this.serviceName = propValue; break; } if (index >= 0) { this.paths.splice(index, 0, path); } else { this.paths.push(path); } } this.maybeReorderPaths(); } parseProperty(property) { let key = property; let value = property; const pos = property.indexOf("="); if (pos > 0) { key = property.substring(0, pos); value = property.substring(pos + 1); } value = escapeTags(_chunkZYPGXT7Qjs.trimQuotes.call(void 0, value || key)); return [key, value]; } /** * Reorders paths when they aren't in the correct order. */ maybeReorderPaths() { switch (this.domain.name) { case "osgi.compendium": reorderObjects(this.paths, "key", ["service", "version", "framework"]); break; case "osgi.core": reorderObjects(this.paths, "key", ["type", "version", "framework"]); break; } } get(key) { return this.properties[key]; } match(properties) { return Object.entries(properties).every(([key, value]) => { const thisValue = this.properties[key]; return thisValue && _chunkZYPGXT7Qjs.matchWithWildcard.call(void 0, thisValue, value); }); } getPaths() { return this.paths.map((p) => p.value); } objectName() { return `${this.domain.name}:${this.propList}`; } }, _class2); function reorderObjects(objs, key, order) { if (!checkReorderNeeded(objs, key, order)) { return; } const objKey = key; order.reverse().forEach((value) => { const index = objs.findIndex((o) => o[objKey] === value); if (index >= 0) { const obj = objs.splice(index, 1)[0]; if (obj) { objs.unshift(obj); } } }); } function checkReorderNeeded(objs, key, order) { if (objs.length === 0 || order.length === 0) { return false; } const objKey = key; if (objs.length < order.length) { return objs.some((o, i) => o[objKey] !== order[i]); } return order.some((v, i) => v !== _optionalChain([objs, 'access', _50 => _50[i], 'optionalAccess', _51 => _51[objKey]])); } // src/plugins/shared/tree/processor-registry.ts var TreeProcessorRegistry = (_class3 = class {constructor() { _class3.prototype.__init5.call(this); } __init5() {this.processors = {}} add(name, processor) { this.processors[name] = processor; } async process(tree) { log3.debug("Apply processors to tree:", this.processors); for (const [name, processor] of Object.entries(this.processors)) { log3.debug("Apply processor:", name); await processor(tree); } } getProcessors() { return this.processors; } reset() { this.processors = {}; } }, _class3); var treeProcessorRegistry = new TreeProcessorRegistry(); // src/plugins/shared/tree/tree.ts var MBeanTree = (_class4 = class _MBeanTree { constructor(id) {;_class4.prototype.__init6.call(this); this.id = id; } __init6() {this.tree = []} static createEmpty(id) { return new _MBeanTree(id); } static async createFromDomains(id, domains) { const mBeanTree = new _MBeanTree(id); await mBeanTree.populate(domains); return mBeanTree; } static createFromNodes(id, nodes) { const mBeanTree = new _MBeanTree(id); mBeanTree.tree = nodes; return mBeanTree; } static filter(originalTree, filter) { if (!originalTree || _optionalChain([originalTree, 'optionalAccess', _52 => _52.length]) === 0) return []; let results = []; for (const parentNode of originalTree) { if (filter(parentNode)) { results = results.concat(parentNode); } else { const resultsInSubtree = _MBeanTree.filter(parentNode.children || [], filter); if (resultsInSubtree.length !== 0) { const parentNodeCloned = Object.assign({}, parentNode); parentN