@hawtio/react
Version:
A Hawtio reimplementation based on TypeScript + React.
1,393 lines (1,338 loc) • 144 kB
JavaScript
"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("<", "<").replace(">", ">");
}
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