UNPKG

@hawtio/react

Version:

A Hawtio reimplementation based on TypeScript + React.

1,183 lines (1,126 loc) 44.4 kB
"use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { newObj[key] = obj[key]; } } } newObj.default = obj; return newObj; } } function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return 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 __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __reExport = (target, mod, secondTarget) => (__copyProps(target, mod, "default"), secondTarget && __copyProps(secondTarget, mod, "default")); // src/core/logging.ts var logging_exports = {}; __export(logging_exports, { Logger: () => Logger, STORAGE_KEY_CHILD_LOGGERS: () => STORAGE_KEY_CHILD_LOGGERS, STORAGE_KEY_LOG_LEVEL: () => STORAGE_KEY_LOG_LEVEL }); // src/util/strings.ts function isBlank(str) { if (str === void 0 || str === null) { return true; } if (typeof str !== "string") { return false; } return str.trim().length === 0; } function toString(obj) { if (!obj) { return "{}"; } const strs = Object.entries(obj).map(([key, value]) => { let obscured = value; if (key.toLowerCase() === "password") { obscured = obfuscate(value); } else if (typeof value === "object") { obscured = toString(obscured); } return `${key}: ${obscured}`; }); return `{ ${strs.join(", ")} }`; } function obfuscate(str) { if (typeof str !== "string") { return ""; } return str.split("").map((_) => "*").join(""); } function trimStart(text, chars) { return text.replace(new RegExp(`^[${chars}]+`, "g"), ""); } function trimEnd(text, chars) { return text.replace(new RegExp(`[${chars}]+$`, "g"), ""); } function trimQuotes(text) { if (text && text.length > 0) { const headTrimmed = trimStart(text, `'"`); if (headTrimmed.length < text.length) { return trimEnd(headTrimmed, `'"`); } } return text; } function stringSorter(a, b, sortDesc) { let res = a.localeCompare(b); if (sortDesc) { res *= -1; } return res; } function parseBoolean(value) { if (!value) return false; return /^true$/i.test(value) || parseInt(value) === 1; } function humanizeLabels(str) { return str.split("-").filter((str2) => !isBlank(str2)).map((str2) => str2.replace(/^./, (str3) => str3.toUpperCase())).join(" ").replace(/([a-z])([A-Z])/g, "$1 $2").replace(/\b([A-Z]+)([A-Z])([a-z])/, "$1 $2$3").replace("M Bean", "MBean").replace("Mbean", "MBean").replace(/^./, (str2) => str2.toUpperCase()).replace(/ +/, " ").trim(); } function matchWithWildcard(value, pattern) { if (!pattern.includes("*")) { return value === pattern; } const rule = pattern.split("*").map((s) => s.replace(/([.*+?^=!:${}()|[\]/\\])/g, "\\$1")).join(".*"); const regexp = new RegExp(`^${rule}$`, "i"); return value.match(regexp) !== null; } // src/core/logging.ts __reExport(logging_exports, js_logger_star); var _jslogger = require('js-logger'); var js_logger_star = _interopRequireWildcard(_jslogger); var _superstruct = require('superstruct'); var STORAGE_KEY_LOG_LEVEL = "core.logging.logLevel"; var STORAGE_KEY_CHILD_LOGGERS = "core.logging.childLoggers"; var LocalStorageHawtioLogger = (_class = class { __init() {this.TRACE = js_logger_star.default.TRACE} __init2() {this.DEBUG = js_logger_star.default.DEBUG} __init3() {this.INFO = js_logger_star.default.INFO} __init4() {this.TIME = js_logger_star.default.TIME} __init5() {this.WARN = js_logger_star.default.WARN} __init6() {this.ERROR = js_logger_star.default.ERROR} __init7() {this.OFF = js_logger_star.default.OFF} __init8() {this.trace = js_logger_star.default.trace} __init9() {this.debug = js_logger_star.default.debug} __init10() {this.info = js_logger_star.default.info} __init11() {this.log = js_logger_star.default.log} __init12() {this.warn = js_logger_star.default.warn} __init13() {this.error = js_logger_star.default.error} __init14() {this.time = js_logger_star.default.time} __init15() {this.timeEnd = js_logger_star.default.timeEnd} __init16() {this.getLevel = js_logger_star.default.getLevel} __init17() {this.enabledFor = js_logger_star.default.enabledFor} __init18() {this.useDefaults = js_logger_star.default.useDefaults} __init19() {this.setHandler = js_logger_star.default.setHandler} // 'typeof jsLogger.createDefaultHandler' is a hack as otherwise tsc complains TS4029 error __init20() {this.createDefaultHandler = js_logger_star.default.createDefaultHandler} __init21() {this.LOG_LEVEL_MAP = { TRACE: this.TRACE, DEBUG: this.DEBUG, INFO: this.INFO, TIME: this.TIME, WARN: this.WARN, ERROR: this.ERROR, OFF: this.OFF }} get(name) { let logger = this.loggers[name]; if (logger) { return logger; } logger = js_logger_star.default.get(name); this.loggers[name] = logger; return logger; } setLevel(level) { const logLevel = this.toLogLevel(level); js_logger_star.default.setLevel(logLevel); this.saveLogLevel(logLevel); } __init22() {this.loggers = {}} constructor() {;_class.prototype.__init.call(this);_class.prototype.__init2.call(this);_class.prototype.__init3.call(this);_class.prototype.__init4.call(this);_class.prototype.__init5.call(this);_class.prototype.__init6.call(this);_class.prototype.__init7.call(this);_class.prototype.__init8.call(this);_class.prototype.__init9.call(this);_class.prototype.__init10.call(this);_class.prototype.__init11.call(this);_class.prototype.__init12.call(this);_class.prototype.__init13.call(this);_class.prototype.__init14.call(this);_class.prototype.__init15.call(this);_class.prototype.__init16.call(this);_class.prototype.__init17.call(this);_class.prototype.__init18.call(this);_class.prototype.__init19.call(this);_class.prototype.__init20.call(this);_class.prototype.__init21.call(this);_class.prototype.__init22.call(this); try { const logLevel = this.loadLogLevel(); js_logger_star.default.setLevel(logLevel); } catch (e) { console.error("Failed to load log level from local storage:", e); } try { const childLoggers = this.loadChildLoggers(); childLoggers.forEach((logger) => this.get(logger.name).setLevel(logger.filterLevel)); } catch (e) { console.error("Failed to load child loggers from local storage:", e); } this.setHandler(this.createDefaultHandler()); } toLogLevel(level) { if (typeof level !== "string") { return level; } const logLevel = this.LOG_LEVEL_MAP[level]; if (!logLevel) { console.error("Unknown log level:", level); return this.INFO; } return logLevel; } loadLogLevel() { const logLevel = localStorage.getItem(STORAGE_KEY_LOG_LEVEL); return logLevel ? JSON.parse(logLevel) : this.INFO; } saveLogLevel(level) { localStorage.setItem(STORAGE_KEY_LOG_LEVEL, JSON.stringify(level)); } loadChildLoggers() { const childLoggers = localStorage.getItem(STORAGE_KEY_CHILD_LOGGERS); return childLoggers ? JSON.parse(childLoggers) : []; } saveChildLoggers(loggers) { localStorage.setItem(STORAGE_KEY_CHILD_LOGGERS, JSON.stringify(loggers)); } getChildLoggers() { const childLoggers = this.loadChildLoggers(); childLoggers.sort((a, b) => stringSorter(a.name, b.name)); return childLoggers; } getAvailableChildLoggers() { const allLoggers = []; Object.values(this.loggers).forEach((logger) => { if (_superstruct.is.call(void 0, logger, _superstruct.type.call(void 0, { context: _superstruct.object.call(void 0, ) }))) { allLoggers.push(logger.context); } else { console.error("Logger does not have context:", logger); } }); const childLoggers = this.getChildLoggers(); const availableLoggers = allLoggers.filter((logger) => !childLoggers.some((l) => l.name === logger.name)); availableLoggers.sort((a, b) => stringSorter(a.name, b.name)); return availableLoggers; } addChildLogger(logger) { const childLoggers = this.getChildLoggers(); childLoggers.push(logger); this.saveChildLoggers(childLoggers); this.get(logger.name).setLevel(logger.filterLevel); } updateChildLogger(name, level) { const logLevel = this.toLogLevel(level); const updated = this.getChildLoggers().map((logger) => { if (logger.name === name) { logger.filterLevel = logLevel; } return logger; }); this.saveChildLoggers(updated); this.get(name).setLevel(logLevel); } removeChildLogger(logger) { const removed = this.getChildLoggers().filter((l) => l.name !== logger.name); this.saveChildLoggers(removed); this.get(logger.name).setLevel(this.getLevel()); } }, _class); var Logger = new LocalStorageHawtioLogger(); // src/auth/globals.ts var moduleName = "hawtio-auth"; var log = Logger.get(moduleName); var PUBLIC_USER = "public"; var PATH_USER = "user"; var PATH_LOGIN = "auth/login"; var PATH_LOGOUT = "auth/logout"; // src/core/config-manager.ts var log2 = Logger.get("hawtio-core-config"); var DEFAULT_APP_NAME = "Hawtio Management Console"; var DEFAULT_LOGIN_TITLE = "Log in to your account"; var HAWTCONFIG_JSON = "hawtconfig.json"; var TaskState = /* @__PURE__ */ ((TaskState2) => { TaskState2[TaskState2["started"] = 0] = "started"; TaskState2[TaskState2["skipped"] = 1] = "skipped"; TaskState2[TaskState2["finished"] = 2] = "finished"; TaskState2[TaskState2["error"] = 3] = "error"; return TaskState2; })(TaskState || {}); var AuthenticationResult = /* @__PURE__ */ ((AuthenticationResult2) => { AuthenticationResult2[AuthenticationResult2["ok"] = 0] = "ok"; AuthenticationResult2[AuthenticationResult2["configuration_error"] = 1] = "configuration_error"; AuthenticationResult2[AuthenticationResult2["connect_error"] = 2] = "connect_error"; AuthenticationResult2[AuthenticationResult2["security_context_error"] = 3] = "security_context_error"; return AuthenticationResult2; })(AuthenticationResult || {}); var ConfigManager = (_class2 = class {constructor() { _class2.prototype.__init23.call(this);_class2.prototype.__init24.call(this);_class2.prototype.__init25.call(this);_class2.prototype.__init26.call(this);_class2.prototype.__init27.call(this); } /** Configuration object read from `hawtconfig.json` and/or built programmatically */ /** List of initialization tasks to be presented in `<HawtioInitialization>` component */ __init23() {this.initTasks = {}} /** Listeners notified about initialization task state changes */ __init24() {this.initListeners = []} /** Configuration of available authentication methods, used by login-related components */ __init25() {this.authenticationConfig = []} /** Resolution method for `authenticationConfigPromise` */ __init26() {this.authenticationConfigReady = () => true} /** Promise resolved when authentication config is already read from external source */ __init27() {this.authenticationConfigPromise = new Promise((resolve) => { this.authenticationConfigReady = resolve; })} // --- External Public API (IConfigManager) async addProductInfo(name, value) { const config = await this.getHawtconfig(); if (!config.about) { config.about = {}; } if (!config.about.productInfo) { config.about.productInfo = []; } config.about.productInfo.push({ name, value }); } initItem(item, ready, group) { this.initTasks[item] = { ready, group }; setTimeout(() => { for (const l of this.initListeners) { l(this.initTasks); } }, 0); } // --- Public API /** * This method is called by `hawtio.bootstrap()`, so we have a single point where global (not plugin-specific) * configuration is loaded */ async initialize() { this.initItem("Checking authentication providers", 0 /* started */, "config"); const defaultConfiguration = { method: "form", name: "Form Authentication", url: PATH_LOGIN, logoutUrl: PATH_LOGOUT, type: "json", userField: "username", passwordField: "password" }; this.authenticationConfig = await fetch("auth/config/login").then((response) => response.ok ? response.json() : []).then((json) => { if (Array.isArray(json)) { return json.length == 0 ? [defaultConfiguration] : json; } else { return [defaultConfiguration]; } }).catch((e) => { log2.debug("Problem fetching authentication providers", e); return [defaultConfiguration]; }); this.authenticationConfigReady(true); this.initItem("Checking authentication providers", 2 /* finished */, "config"); return true; } /** * Called by plugins to augment generic authentication method config. * Plugins may provide additional details, like location of OIDC provider. * @param config */ async configureAuthenticationMethod(config) { return this.authenticationConfigPromise.then(() => { for (const idx in this.authenticationConfig) { if (_optionalChain([this, 'access', _2 => _2.authenticationConfig, 'access', _3 => _3[idx], 'optionalAccess', _4 => _4.method]) === config.method) { this.authenticationConfig[idx] = { ...config, name: this.authenticationConfig[idx].name }; break; } } }); } /** * Get configured authentication methods - possibly augmented by plugins. This method should * be called from hooks and React components, so can be done only after hawtio.bootstrap() promise is * resolved. This ensures that plugins already finished their configuration. */ getAuthenticationConfig() { return this.authenticationConfig; } /** * Returns selected authentication method by name * @param method */ getAuthenticationMethod(method) { return this.authenticationConfig.find((am) => am.method === method); } /** * Set new `Hawtconfig` object as the configuration * @param config */ setHawtconfig(config) { this.config = Promise.resolve(config); } /** * Returns currently configured `Hawtconfig` object */ async getHawtconfig() { if (this.config) { return this.config; } this.config = this.loadConfig(); return this.config; } /** * Resets current `Hawtconfig` object to undefined state */ reset() { this.config = void 0; } /** * Loads configuration from `hawtconfig.json`. * @private */ async loadConfig() { log2.info("Loading", HAWTCONFIG_JSON); this.initItem("Loading " + HAWTCONFIG_JSON, 0 /* started */, "config"); try { const res = await fetch(HAWTCONFIG_JSON); if (!res.ok) { log2.error("Failed to fetch", HAWTCONFIG_JSON, "-", res.status, res.statusText); this.initItem("Loading " + HAWTCONFIG_JSON, 1 /* skipped */, "config"); return {}; } const config = await res.json(); log2.debug(HAWTCONFIG_JSON, "=", config); log2.info("Loaded", HAWTCONFIG_JSON); this.initItem("Loading " + HAWTCONFIG_JSON, 2 /* finished */, "config"); return config; } catch (err) { log2.error("Error fetching", HAWTCONFIG_JSON, "-", err); this.initItem("Loading " + HAWTCONFIG_JSON, 1 /* skipped */, "config"); return {}; } } /** * Plugins may use this method to change parts, or entire `hawtconfig.json` configuration. * @param configurer */ async configure(configurer) { const config = await this.getHawtconfig(); configurer(config); } /** * Apply loaded configuration to application (titles, links, icons). Should be called during Hawtio bootstrap. */ async applyBranding() { this.initItem("Applying branding", 0 /* started */, "config"); const { branding } = await this.getHawtconfig(); if (!branding) { this.initItem("Applying branding", 1 /* skipped */, "config"); return false; } log2.info("Apply branding", branding); let applied = false; if (branding.appName) { log2.info("Updating title -", branding.appName); document.title = branding.appName; applied = true; } if (branding.css) { this.updateHref("#branding", branding.css, true); applied = true; } if (branding.favicon) { this.updateHref("#favicon", branding.favicon); applied = true; } this.initItem("Applying branding", applied ? 2 /* finished */ : 1 /* skipped */, "config"); return applied; } /** * Alters `href` attribute of selected DOM element (for icons, styles) * @param id * @param path * @param moveToLast * @private */ updateHref(id, path, moveToLast = false) { log2.info("Updating href for", id, "-", path, moveToLast); const elm = document.querySelector(id); if (!elm) { return; } if ("disabled" in elm) { elm.disabled = true; } elm.setAttribute("href", path); if (moveToLast) { elm.remove(); _optionalChain([document, 'access', _5 => _5.querySelector, 'call', _6 => _6("head"), 'optionalAccess', _7 => _7.append, 'call', _8 => _8(elm)]); } if ("disabled" in elm) { elm.disabled = false; } } async isRouteEnabled(path) { const { disabledRoutes } = await this.getHawtconfig(); return !disabledRoutes || !disabledRoutes.includes(path); } /** * Filter loaded plugins, so only plugins which are not explicitly disabled in `hawtconfig.json` are used. * @param plugins */ async filterEnabledPlugins(plugins) { const { disabledRoutes } = await this.getHawtconfig(); const enabledPlugins = []; for (const plugin of plugins) { if (plugin.path == null && await plugin.isActive() || !disabledRoutes || !disabledRoutes.includes(plugin.path)) { enabledPlugins.push(plugin); } else { log2.debug(`Plugin "${plugin.id}" disabled by hawtconfig.json`); } } return enabledPlugins; } /** * Returns current state of initialization tasks */ getInitializationTasks() { return this.initTasks; } /** * Register a listener to be notified about state changes of initialization tasks * @param listener */ addInitListener(listener) { this.initListeners.push(listener); } /** * Unregister previously registered listener for state changes of initialization tasks * @param listener */ removeInitListener(listener) { this.initListeners.splice(this.initListeners.indexOf(listener), 1); } /** * Are all the initialization items completed? The returned promise will be asynchronously resolved when * initialization is finished. * * When the configuration is _ready_, Hawtio may proceed to rendering UI. */ async ready() { return new Promise((resolve, _reject) => { const h = setInterval(() => { const result = Object.values(this.initTasks).find((v) => v.ready == 0 /* started */ || v.ready == 3 /* error */) == void 0; if (result) { resolve(true); clearInterval(h); } }, 100); }); } }, _class2); var configManager = new ConfigManager(); // src/core/index.ts var core_exports = {}; __export(core_exports, { AuthenticationResult: () => AuthenticationResult, ConfigManager: () => ConfigManager, DEFAULT_APP_NAME: () => DEFAULT_APP_NAME, DEFAULT_LOGIN_TITLE: () => DEFAULT_LOGIN_TITLE, EVENT_LOGIN: () => EVENT_LOGIN, EVENT_LOGOUT: () => EVENT_LOGOUT, EVENT_NOTIFY: () => EVENT_NOTIFY, EVENT_PLUGINS_UPDATED: () => EVENT_PLUGINS_UPDATED, EVENT_REFRESH: () => EVENT_REFRESH, HAWTCONFIG_JSON: () => HAWTCONFIG_JSON, HawtioCore: () => HawtioCore, Logger: () => Logger, STORAGE_KEY_CHILD_LOGGERS: () => STORAGE_KEY_CHILD_LOGGERS, STORAGE_KEY_LOG_LEVEL: () => STORAGE_KEY_LOG_LEVEL, TaskState: () => TaskState, configManager: () => configManager, eventService: () => eventService, hawtio: () => hawtio, isUniversalHeaderItem: () => isUniversalHeaderItem, useHawtconfig: () => useHawtconfig, usePlugins: () => usePlugins }); // src/auth/hooks.ts var _react = require('react'); function useUser() { const [username, setUsername] = _react.useState.call(void 0, ""); const [isLogin, setIsLogin] = _react.useState.call(void 0, false); const [isLoginError, setIsLoginError] = _react.useState.call(void 0, false); const [loginError, setLoginError] = _react.useState.call(void 0, ""); const [userLoaded, setUserLoaded] = _react.useState.call(void 0, false); const [loginMethod, setLoginMethod] = _react.useState.call(void 0, ""); _react.useEffect.call(void 0, () => { let proceed = true; const isProceed = () => proceed; const fetchUser = async () => { await userService.fetchUser(false, () => isProceed()); const loginMethod2 = await userService.getLoginMethod(); const isLoginError2 = await userService.isLoginError(); const loginError2 = isLoginError2 ? await userService.loginError() : ""; const username2 = isLoginError2 ? "" : await userService.getUsername(); const isLogin2 = isLoginError2 ? false : await userService.isLogin(); if (isProceed()) { setUsername(_nullishCoalesce(username2, () => ( ""))); setIsLogin(isLogin2); setIsLoginError(isLoginError2); setLoginMethod(loginMethod2); setLoginError(loginError2); setUserLoaded(true); } }; fetchUser(); return () => { proceed = false; }; }, []); return { username, isLogin, userLoaded, loginMethod, isLoginError, loginError }; } // src/core/core.ts var _utilities = require('@module-federation/utilities'); // src/core/event-service.ts var _eventemitter3 = require('eventemitter3'); var _eventemitter32 = _interopRequireDefault(_eventemitter3); var log3 = Logger.get("hawtio-core-event"); var EVENT_NOTIFY = "notify"; var EVENT_LOGIN = "login"; var EVENT_LOGOUT = "logout"; var EVENT_REFRESH = "refresh"; var EVENT_PLUGINS_UPDATED = "pluginsUpdated"; var DEFAULT_DURATION = 8e3; var EventService = (_class3 = class {constructor() { _class3.prototype.__init28.call(this); } __init28() {this.eventEmitter = new (0, _eventemitter32.default)()} notify(notification) { if (!notification.duration) { notification.duration = DEFAULT_DURATION; } this.eventEmitter.emit(EVENT_NOTIFY, notification); } onNotify(listener) { this.eventEmitter.on(EVENT_NOTIFY, listener); log3.debug("Number of listeners on", EVENT_NOTIFY, "=", this.eventEmitter.listenerCount(EVENT_NOTIFY)); } login() { this.eventEmitter.emit(EVENT_LOGIN); } onLogin(listener) { this.eventEmitter.on(EVENT_LOGIN, listener); } logout() { this.eventEmitter.emit(EVENT_LOGOUT); } onLogout(listener) { this.eventEmitter.on(EVENT_LOGOUT, listener); } refresh() { this.eventEmitter.emit(EVENT_REFRESH); } onRefresh(listener) { this.eventEmitter.on(EVENT_REFRESH, listener); } pluginsUpdated() { this.eventEmitter.emit(EVENT_PLUGINS_UPDATED); } onPluginsUpdated(listener) { this.eventEmitter.on(EVENT_PLUGINS_UPDATED, listener); } removeListener(event, listener) { this.eventEmitter.removeListener(event, listener); } }, _class3); var eventService = new EventService(); // src/core/globals.ts var moduleName2 = "hawtio-core"; var log4 = Logger.get(moduleName2); // src/core/core.ts var DEFAULT_REMOTE_ENTRY_FILE = "remoteEntry.js"; var DEFAULT_PLUGIN_ORDER = 100; var DEFAULT_PLUGIN_ENTRY = "plugin"; var HAWTIO_DISABLE_THEME_LISTENER = "hawtio.disableThemeListener"; var PATTERNFLY_THEME_CLASS = "pf-v5-theme-dark"; function isUniversalHeaderItem(item) { return "component" in item && "universal" in item && typeof item.universal === "boolean"; } var HawtioCore = (_class4 = class {constructor() { _class4.prototype.__init29.call(this);_class4.prototype.__init30.call(this);_class4.prototype.__init31.call(this);_class4.prototype.__init32.call(this);_class4.prototype.__init33.call(this); } /** * Hawtio base path. */ /** * List of URLs that the plugin loader will try and discover plugins from. */ __init29() {this.urls = []} /** * Holds all of the Hawtio plugins that need to be bootstrapped. */ __init30() {this.plugins = {}} /** * Holds all of the Hawtio plugins which will be evaluated in deferred way. */ __init31() {this.deferredPlugins = {}} /** * The Window Theme Listener callback function */ __init32() {this.windowThemeListener = () => { this.updateFromTheme(); }} /** * Flag set once the window theme listener has been added */ __init33() {this.windowListenerAdded = false} // --- External Public API (IHawtio) addPlugin(plugin) { log4.info("Add plugin:", plugin.id); configManager.initItem("Registering plugin: " + plugin.id, 0 /* started */, "plugins"); if (this.plugins[plugin.id]) { throw new Error(`Plugin "${plugin.id}" already exists`); } this.plugins[plugin.id] = plugin; setTimeout(() => { configManager.initItem("Registering plugin: " + plugin.id, 2 /* finished */, "plugins"); }, 0); return this; } addDeferredPlugin(id, deferred) { log4.info("Add deferred plugin:", id); configManager.initItem("Registering deferred plugin: " + id, 0 /* started */, "plugins"); if (this.deferredPlugins[id]) { throw new Error(`Deferred plugin "${id}" already exists`); } if (this.plugins[id]) { throw new Error(`Conflict registering a deferred plugin with id="${id}". Plugin already registered.`); } this.deferredPlugins[id] = deferred; return this; } addUrl(url) { if (URL.canParse(url)) { } else { url = new URL(url, document.baseURI).href; } log4.info("Add URL:", url); this.urls.push(url); return this; } async bootstrap() { log4.info("Bootstrapping Hawtio..."); await configManager.initialize(); await this.loadPlugins(); const brandingApplied = await configManager.applyBranding(); log4.info("Branding applied:", brandingApplied); log4.info("Bootstrapped Hawtio"); configManager.initItem("Finish", 2 /* finished */, "finish"); return configManager.ready().then(() => { log4.debug("configManager.ready() resolved"); return true; }); } // --- Public API /** * Returns all plugins registered using different plugin registration methods. */ getPlugins() { return Object.values(this.plugins); } /** * Resolves which of registered plugins are active in current environment. * * There are two types of plugins: normal plugins and login plugins. * If it's normal, it's only resolved when the user is already logged in. * If it's login, it's only resolved when the user is not logged in yet, and thus * can only affects the login page. * * Therefore, this method depends on the login status provided by the `userService`. */ async resolvePlugins() { const userLoggedIn = await userService.isLogin(); log4.debug("Resolve plugins: login =", userLoggedIn); const resolved = []; for (const plugin of this.getPlugins()) { log4.debug("Resolve plugin:", plugin.id); if (userLoggedIn && plugin.isLogin || !userLoggedIn && !plugin.isLogin) { continue; } if (await plugin.isActive()) { resolved.push(plugin); } } log4.debug("Resolved plugins:", resolved); resolved.sort((a, b) => (_nullishCoalesce(a.order, () => ( DEFAULT_PLUGIN_ORDER))) - (_nullishCoalesce(b.order, () => ( DEFAULT_PLUGIN_ORDER)))); return resolved; } /** * Sets the base path of the Hawtio console. * If the given path includes trailing '/', it will be trimmed. */ setBasePath(path) { if (path.length > 1 && path.endsWith("/")) { this.basePath = path.slice(0, -1); } else { this.basePath = path; } } /** * Returns the base path of the Hawtio console without trailing '/'. */ getBasePath() { if (!this.basePath) { const basePath = this.documentBase(); log4.info("Base path from html head:", basePath); if (basePath && basePath.length > 1 && basePath.endsWith("/")) { this.basePath = basePath.slice(0, -1); } else { this.basePath = basePath; } } return this.basePath; } /** * Adds an event listener to the window theme to update * css values in the event of a change in theme */ addWindowThemeListener() { if (this.windowListenerAdded) { return; } const disableListener = _nullishCoalesce(localStorage.getItem(HAWTIO_DISABLE_THEME_LISTENER), () => ( "false")); if (disableListener === "true") { return; } this.updateFromTheme(); this.themeList().addEventListener("change", this.windowThemeListener); this.windowListenerAdded = true; } /** * Removes the event listener to the window theme */ removeWindowThemeListener() { this.themeList().removeEventListener("change", this.windowThemeListener); this.windowListenerAdded = false; } // --- private methods /** * Returns the base URL specified in html/head/base element, href attribute. It should end with trailing '/'. * Specified base affects how `fetch()` global function works. * @private */ documentBase() { const base = document.querySelector("head base"); if (base) { return _nullishCoalesce(base.getAttribute("href"), () => ( void 0)); } return void 0; } /** * Downloads plugins from any configured URLs and loads them. * It is invoked at Hawtio's bootstrapping. * * This plugin mechanism is implemented using [Webpack Module Federation](https://module-federation.github.io/). */ async loadPlugins() { if (this.urls.length === 0 && Object.entries(this.deferredPlugins).length === 0) { log4.info("No URLs provided to load external plugins and no deferred plugins registered"); return; } const numBefore = Object.keys(this.plugins).length; log4.info(numBefore, "plugins before loading:", { ...this.plugins }); await Promise.all(this.urls.map(this.loadExternalPlugins)); configManager.initItem("Loading deferred plugins", 0 /* started */, "plugins"); await this.loadDeferredPlugins(); configManager.initItem("Loading deferred plugins", 2 /* finished */, "plugins"); const numAfter = Object.keys(this.plugins).length; log4.info(numAfter, "plugins after loaded:", this.plugins); if (numBefore !== numAfter) { log4.debug("Notify plugins update"); eventService.pluginsUpdated(); } } /** * Loads external plugins from the given URL. The URL endpoint is expected to * return an array of HawtioRemote objects. These are not strictly "Hawtio plugins", but rather * "remote entry points" that when called may register Hawtio plugins (with `hawtio.addPlugin()` or * `hawtio.addDeferredPlugin()`). */ async loadExternalPlugins(url) { log4.debug("Trying url:", url); try { configManager.initItem("Loading plugins descriptor from URL " + url, 0 /* started */, "plugins"); const res = await fetch(url); if (!res.ok) { configManager.initItem("Loading plugins descriptor from URL " + url, 3 /* error */, "plugins"); log4.error("Failed to fetch url:", url, "-", res.status, res.statusText); return; } const remotes = await res.json(); log4.info("Loaded remotes from url:", url, "=", remotes); configManager.initItem("Loading plugins descriptor from URL " + url, 2 /* finished */, "plugins"); return Promise.all( remotes.map(async (remote) => { let url2; if (typeof remote.url === "function") { url2 = await remote.url(); } else { url2 = remote.url; } if (!url2.endsWith("/")) { url2 += "/"; } if (URL.canParse(url2)) { url2 += _nullishCoalesce(remote.remoteEntryFileName, () => ( DEFAULT_REMOTE_ENTRY_FILE)); } else { url2 = new URL(url2 + (_nullishCoalesce(remote.remoteEntryFileName, () => ( DEFAULT_REMOTE_ENTRY_FILE))), document.baseURI).href; } log4.info("Loading remote", remote); try { configManager.initItem("Importing plugin from: " + url2, 0 /* started */, "plugins"); const plugin = await _utilities.importRemote.call(void 0, remote); const entryFn = plugin[remote.pluginEntry || DEFAULT_PLUGIN_ENTRY]; if (!entryFn) { configManager.initItem("Importing plugin from: " + url2, 1 /* skipped */, "plugins"); log4.info("Ignored remote", remote, "(no entry point available)"); return Promise.resolve(true); } else { const result = entryFn(); let resultPromise; if (result instanceof Promise) { resultPromise = result; } else { resultPromise = Promise.resolve(result); } return resultPromise.then(() => { configManager.initItem("Importing plugin from: " + url2, 2 /* finished */, "plugins"); log4.info("Loaded remote", remote); }); } } catch (err) { configManager.initItem("Importing plugin from: " + url2, 3 /* error */, "plugins"); log4.error("Error loading remote:", remote, "-", err); return Promise.resolve(false); } }) ); } catch (err) { configManager.initItem("Loading plugins descriptor from URL " + url, 3 /* error */, "plugins"); log4.error("Error fetching url:", url, "-", err); return; } } /** * Evaluate all deferred plugins, so they are added to `plugins` array of available plugins * @private */ async loadDeferredPlugins() { log4.debug("Loading deferred plugins"); return Promise.all( Object.entries(this.deferredPlugins).map(async (e) => { const [id, deferred] = e; return deferred().then((result) => { let plugins; if (!Array.isArray(result)) { plugins = [result]; } else { plugins = result; } for (const plugin of plugins) { if (this.plugins[plugin.id]) { throw new Error(`Plugin "${plugin.id}" already exists`); } this.plugins[plugin.id] = plugin; } configManager.initItem("Registering deferred plugin: " + id, 2 /* finished */, "plugins"); }).catch((err) => { log4.error("Error registering deferred plugin:", id, "-", err); configManager.initItem("Registering deferred plugin: " + id, 3 /* error */, "plugins"); }); }) ); } // Actual window theme query themeList() { return window.matchMedia("(prefers-color-scheme: dark)"); } /** * Detect what theme the browser has been set to and * return 'dark' | 'light' */ windowTheme() { return this.themeList().matches ? "dark" : "light"; } /** * Update the document root with the PatternFly dark class * see https://www.patternfly.org/developer-resources/dark-theme-handbook */ updateFromTheme() { if (this.windowTheme() === "dark") { document.documentElement.classList.add(PATTERNFLY_THEME_CLASS); } else { document.documentElement.classList.remove(PATTERNFLY_THEME_CLASS); } } }, _class4); var hawtio = new HawtioCore(); // src/core/hooks.ts function usePlugins() { const [plugins, setPlugins] = _react.useState.call(void 0, []); const [pluginsLoaded, setPluginsLoaded] = _react.useState.call(void 0, false); log4.debug("usePlugins - Plugins:", hawtio.getPlugins()); _react.useEffect.call(void 0, () => { const loadPlugins = async () => { const activePlugins = await hawtio.resolvePlugins(); const enabledPlugins = await configManager.filterEnabledPlugins(activePlugins); setPlugins(enabledPlugins); setPluginsLoaded(true); }; loadPlugins(); eventService.onPluginsUpdated(loadPlugins); return () => eventService.removeListener(EVENT_PLUGINS_UPDATED, loadPlugins); }, []); return { plugins, pluginsLoaded }; } function useHawtconfig() { const [hawtconfig, setHawtconfig] = _react.useState.call(void 0, {}); const [hawtconfigLoaded, setHawtconfigLoaded] = _react.useState.call(void 0, false); _react.useEffect.call(void 0, () => { const loadHawtconfig = async () => { setHawtconfig(await configManager.getHawtconfig()); setHawtconfigLoaded(true); }; loadHawtconfig(); }, []); return { hawtconfig, hawtconfigLoaded }; } // src/core/index.ts __reExport(core_exports, logging_exports); // src/auth/user-service.ts var UserService = (_class5 = class { /** The main promise resolving to `User` instance. That's why we need full browser redirect on logout. */ /** user promise resolve method - to be called by registered auth plugins or default auth plugin */ __init34() {this.resolveUser = () => { }} /** Result of fetching user with plugins. When it indicates an error `user` promise won't be resolved */ __init35() {this.userAuthResult = null} /** Named authentication hooks used to fetch information about actual user logged into Hawtio. */ __init36() {this.fetchUserHooks = {}} /** Named authentication hooks used to logout the user. */ __init37() {this.logoutHooks = {}} /** Bearer Token to be used by Jolokia, set by plugins on successful authentication */ // TODO: Jolokia service should use auth plugins to configure the headers __init38() {this.token = null} constructor() {;_class5.prototype.__init34.call(this);_class5.prototype.__init35.call(this);_class5.prototype.__init36.call(this);_class5.prototype.__init37.call(this);_class5.prototype.__init38.call(this); this.user = new Promise((resolve) => { this.resolveUser = resolve; }); } addFetchUserHook(name, hook) { this.fetchUserHooks[name] = hook; configManager.initItem(`Registration of ${name} auth hook`, 2 /* finished */, "config"); } addLogoutHook(name, hook) { this.logoutHooks[name] = hook; } /** * Sync login status with the server by fetching login user. */ async fetchUser(retry = true, proceed) { for (const [name, fetchUser] of Object.entries(this.fetchUserHooks)) { const result = await fetchUser(this.resolveUser, proceed); if (proceed && !proceed()) { return; } this.userAuthResult = result; log.debug("Invoke fetch user hook", name, ": resolved =", result); if (!result.isIgnore) { if (!result.isError) { eventService.login(); } return; } } await this.defaultFetchUser(retry, proceed); } /** * Default user fetching logic that checks `/user` endpoint that returns json string value with named/logged-in user * @param retry * @param proceed * @private */ async defaultFetchUser(retry = true, proceed) { try { const res = await fetch(PATH_USER); if (!res.ok) { log.error("Failed to fetch user:", res.status, res.statusText); if (retry && res.status === 403) { await new Promise((resolve) => setTimeout(resolve, 1e3)); return this.defaultFetchUser(false, proceed); } this.resolveUser({ username: PUBLIC_USER, isLogin: false, loginMethod: "form" }); return; } const username = await res.json(); log.info("Logged in as:", username); this.resolveUser({ username, isLogin: true, loginMethod: "form" }); eventService.login(); } catch (err) { log.debug("Failed to get logged-in user from", PATH_USER, "-", err); this.resolveUser({ username: PUBLIC_USER, isLogin: false, loginMethod: "form" }); } } async getUsername() { return this.userAuthResult == null || !this.userAuthResult.isError ? (await this.user).username : null; } async isLogin() { return this.userAuthResult == null || !this.userAuthResult.isError ? (await this.user).isLogin : false; } async isLoginError() { return this.userAuthResult != null && this.userAuthResult.isError; } async loginError() { return this.userAuthResult != null && this.userAuthResult.isError ? this.userAuthResult.errorMessage : null; } async getLoginMethod() { return this.userAuthResult != null ? this.userAuthResult.loginMethod : (await this.user).loginMethod; } getToken() { return this.token; } setToken(token) { this.token = token; } async logout() { const login = await this.user; if (!login.isLogin) { log.debug("Not logged in"); return false; } log.info("Log out:", login.username, "Login method:", login.loginMethod); let attempted = false; let proceed = false; for (const [name, logout] of Object.entries(this.logoutHooks)) { if (name !== login.loginMethod) { continue; } attempted = true; const result = await logout(); log.debug("Invoke logout hook", name, ": result =", result); if (result) { proceed = true; break; } } if (attempted) { if (proceed) { eventService.logout(); return true; } else { return false; } } eventService.logout(); log.debug("Redirect to:", PATH_LOGOUT); window.location.href = PATH_LOGOUT; return true; } }, _class5); var userService = new UserService(); var __testing__ = { UserService }; exports.__export = __export; exports.__reExport = __reExport; exports.isBlank = isBlank; exports.toString = toString; exports.trimQuotes = trimQuotes; exports.stringSorter = stringSorter; exports.parseBoolean = parseBoolean; exports.humanizeLabels = humanizeLabels; exports.matchWithWildcard = matchWithWildcard; exports.STORAGE_KEY_LOG_LEVEL = STORAGE_KEY_LOG_LEVEL; exports.STORAGE_KEY_CHILD_LOGGERS = STORAGE_KEY_CHILD_LOGGERS; exports.Logger = Logger; exports.logging_exports = logging_exports; exports.PUBLIC_USER = PUBLIC_USER; exports.DEFAULT_APP_NAME = DEFAULT_APP_NAME; exports.DEFAULT_LOGIN_TITLE = DEFAULT_LOGIN_TITLE; exports.HAWTCONFIG_JSON = HAWTCONFIG_JSON; exports.TaskState = TaskState; exports.AuthenticationResult = AuthenticationResult; exports.ConfigManager = ConfigManager; exports.configManager = configManager; exports.userService = userService; exports.__testing__ = __testing__; exports.useUser = useUser; exports.EVENT_NOTIFY = EVENT_NOTIFY; exports.EVENT_LOGIN = EVENT_LOGIN; exports.EVENT_LOGOUT = EVENT_LOGOUT; exports.EVENT_REFRESH = EVENT_REFRESH; exports.EVENT_PLUGINS_UPDATED = EVENT_PLUGINS_UPDATED; exports.eventService = eventService; exports.isUniversalHeaderItem = isUniversalHeaderItem; exports.HawtioCore = HawtioCore; exports.hawtio = hawtio; exports.usePlugins = usePlugins; exports.useHawtconfig = useHawtconfig; exports.core_exports = core_exports; //# sourceMappingURL=chunk-ZYPGXT7Q.js.map