@xh/hoist 86.1.0 → 86.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.gitattributes +4 -0
- package/CHANGELOG.md +104 -0
- package/admin/AdminJsonDisplay.ts +31 -0
- package/admin/App.scss +3 -11
- package/admin/AppModel.ts +3 -4
- package/admin/jsonsearch/impl/JsonSearchImplModel.ts +4 -4
- package/admin/tabs/activity/tracking/ActivityTrackingModel.ts +56 -6
- package/admin/tabs/activity/tracking/ActivityTrackingPanel.ts +9 -26
- package/admin/tabs/clients/ClientsModel.ts +4 -1
- package/admin/tabs/clients/activity/ClientDetailModel.ts +5 -1
- package/admin/tabs/cluster/instances/connpool/ConnPoolMonitorPanel.ts +3 -11
- package/admin/tabs/cluster/instances/services/DetailsPanel.ts +2 -13
- package/admin/tabs/cluster/objects/DetailPanel.ts +3 -12
- package/admin/tabs/userData/roles/RolePanel.ts +2 -2
- package/appcontainer/RouterModel.ts +6 -6
- package/build/types/admin/AdminJsonDisplay.d.ts +7 -0
- package/build/types/admin/AppModel.d.ts +3 -4
- package/build/types/admin/jsonsearch/impl/JsonSearchImplModel.d.ts +2 -2
- package/build/types/admin/tabs/activity/tracking/ActivityTrackingModel.d.ts +14 -3
- package/build/types/admin/tabs/clients/ClientsModel.d.ts +3 -0
- package/build/types/appcontainer/RouterModel.d.ts +5 -5
- package/build/types/cmp/grid/Grid.d.ts +8 -4
- package/build/types/cmp/grid/GridModel.d.ts +13 -2
- package/build/types/cmp/grid/Types.d.ts +8 -1
- package/build/types/cmp/grid/filter/GridFilterFieldSpec.d.ts +24 -6
- package/build/types/cmp/input/SegmentedControlOption.d.ts +12 -0
- package/build/types/core/HoistAppModel.d.ts +2 -3
- package/build/types/core/types/Types.d.ts +12 -0
- package/build/types/desktop/cmp/button/grid/ExpandToLevelButton.d.ts +2 -1
- package/build/types/desktop/cmp/filter/FilterChooser.d.ts +5 -0
- package/build/types/desktop/cmp/filter/PopoverFilterChooser.d.ts +3 -1
- package/build/types/desktop/cmp/grid/impl/colchooser/ColChooserModel.d.ts +2 -2
- package/build/types/desktop/cmp/grid/impl/filter/headerfilter/custom/CustomRowModel.d.ts +9 -1
- package/build/types/desktop/cmp/grid/impl/filter/headerfilter/custom/CustomTabModel.d.ts +8 -2
- package/build/types/desktop/cmp/input/CodeInput.d.ts +7 -0
- package/build/types/desktop/cmp/input/NumberInput.d.ts +1 -1
- package/build/types/desktop/cmp/input/Select.d.ts +8 -0
- package/build/types/desktop/cmp/input/impl/CalcWindowedMenuWidth.d.ts +20 -0
- package/build/types/desktop/cmp/rest/impl/RestFormModel.d.ts +1 -1
- package/build/types/format/FormatNumber.d.ts +11 -3
- package/build/types/icon/Icon.d.ts +4 -3
- package/build/types/kit/react-dropzone/index.d.ts +1 -0
- package/build/types/kit/swiper/index.d.ts +4 -3
- package/build/types/mobile/cmp/button/grid/ExpandToLevelButton.d.ts +2 -1
- package/build/types/mobile/cmp/grid/impl/ColChooserModel.d.ts +2 -2
- package/build/types/mobile/cmp/input/NumberInput.d.ts +1 -1
- package/build/types/mobile/cmp/input/Select.d.ts +8 -0
- package/cmp/grid/Grid.ts +27 -2
- package/cmp/grid/GridModel.ts +21 -8
- package/cmp/grid/Types.ts +9 -1
- package/cmp/grid/filter/GridFilterFieldSpec.ts +28 -5
- package/cmp/grid/impl/MenuSupport.ts +3 -5
- package/cmp/input/SegmentedControlOption.ts +14 -0
- package/cmp/store/impl/StoreFilterFieldImplModel.ts +5 -6
- package/core/HoistAppModel.ts +2 -3
- package/core/types/Types.ts +14 -5
- package/desktop/cmp/button/grid/ExpandToLevelButton.ts +4 -5
- package/desktop/cmp/filechooser/FileChooser.ts +6 -3
- package/desktop/cmp/filechooser/FileChooserModel.ts +12 -5
- package/desktop/cmp/filter/FilterChooser.scss +41 -0
- package/desktop/cmp/filter/FilterChooser.ts +147 -7
- package/desktop/cmp/filter/PopoverFilterChooser.ts +7 -84
- package/desktop/cmp/grid/editors/NumberEditor.ts +16 -17
- package/desktop/cmp/grid/editors/SelectEditor.ts +15 -10
- package/desktop/cmp/grid/editors/impl/InlineEditorModel.ts +6 -1
- package/desktop/cmp/grid/find/impl/GridFindFieldImplModel.ts +3 -5
- package/desktop/cmp/grid/impl/colchooser/ColChooserModel.ts +2 -2
- package/desktop/cmp/grid/impl/filter/GridFilterDialog.ts +2 -2
- package/desktop/cmp/grid/impl/filter/headerfilter/custom/CustomRow.ts +12 -12
- package/desktop/cmp/grid/impl/filter/headerfilter/custom/CustomRowModel.ts +18 -0
- package/desktop/cmp/grid/impl/filter/headerfilter/custom/CustomTab.scss +5 -0
- package/desktop/cmp/grid/impl/filter/headerfilter/custom/CustomTab.ts +13 -2
- package/desktop/cmp/grid/impl/filter/headerfilter/custom/CustomTabModel.ts +97 -14
- package/desktop/cmp/grid/impl/filter/headerfilter/values/ValuesTabModel.ts +46 -7
- package/desktop/cmp/input/CheckboxButton.ts +21 -1
- package/desktop/cmp/input/CodeInput.ts +20 -3
- package/desktop/cmp/input/NumberInput.ts +1 -1
- package/desktop/cmp/input/SegmentedControl.ts +7 -3
- package/desktop/cmp/input/Select.ts +37 -18
- package/desktop/cmp/input/impl/CalcWindowedMenuWidth.ts +106 -0
- package/desktop/cmp/rest/impl/RestFormField.ts +1 -1
- package/desktop/cmp/tab/Tabs.scss +72 -25
- package/docs/routing.md +4 -2
- package/docs/upgrade-notes/v86-upgrade-notes.md +73 -8
- package/format/FormatNumber.ts +69 -32
- package/icon/Icon.scss +13 -0
- package/icon/Icon.ts +4 -3
- package/icon/impl/IconHtml.ts +1 -1
- package/kit/react-dropzone/index.ts +1 -0
- package/mcp/README.md +12 -0
- package/mcp/data/ts-registry.spec.ts +156 -0
- package/mcp/data/ts-registry.ts +14 -7
- package/mcp/formatters/typescript.ts +7 -3
- package/mcp/util/paths.ts +21 -0
- package/mobile/cmp/button/grid/ExpandToLevelButton.ts +4 -5
- package/mobile/cmp/grid/impl/ColChooserModel.ts +2 -2
- package/mobile/cmp/input/CheckboxButton.ts +18 -1
- package/mobile/cmp/input/NumberInput.ts +1 -1
- package/mobile/cmp/input/SegmentedControl.ts +7 -3
- package/mobile/cmp/input/Select.ts +21 -15
- package/mobx/README.md +2 -1
- package/package.json +10 -10
- package/styles/vars.scss +15 -0
- package/desktop/cmp/filter/PopoverFilterChooser.scss +0 -42
package/.gitattributes
ADDED
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,109 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 86.3.0 - 2026-07-10
|
|
4
|
+
|
|
5
|
+
### 🎁 New Features
|
|
6
|
+
|
|
7
|
+
* `Select` now accepts a `generateOptionFn` prop to resolve an option for a selected value that is
|
|
8
|
+
not present in the current options list (e.g. with `queryFn`-based selects or readonly forms),
|
|
9
|
+
ensuring such values render with their proper label rather than falling back to the raw value.
|
|
10
|
+
* `SegmentedControl` options (desktop and mobile) now accept a `testId`, emitted on the option's
|
|
11
|
+
rendered button as `data-testid` for E2E targeting. If an option omits its own `testId` but the
|
|
12
|
+
control has one, an id is auto-derived as `${controlTestId}-${value}` - restoring parity with
|
|
13
|
+
the legacy `ButtonGroupInput` test-hook pattern for apps migrating between the two.
|
|
14
|
+
|
|
15
|
+
### 🐞 Bug Fixes
|
|
16
|
+
|
|
17
|
+
* Fixed grid columns configured as `hidden` becoming visible after being grouped and then
|
|
18
|
+
ungrouped. `GridModel` now re-asserts each column's configured visibility whenever `groupBy`
|
|
19
|
+
changes, keeping AG Grid's column state in sync with `columnState`.
|
|
20
|
+
* Fixed `StoreFilterField` and grid Find so an active quick-filter or find query no longer returns
|
|
21
|
+
different results when the grid's `groupBy` changes.
|
|
22
|
+
* Fixed inline grid cell editors to reliably commit their value when editing ends, including popup
|
|
23
|
+
editors (e.g. `textAreaEditor`) within a dialog, which previously dropped edits on Enter or
|
|
24
|
+
click-away.
|
|
25
|
+
* Fixed `Select` to correctly handle non-primitive (object) values: selected-option matching and
|
|
26
|
+
async query de-duplication now use deep equality, so object values no longer render as
|
|
27
|
+
`[object Object]` or collide with one another.
|
|
28
|
+
* Hardened the grid column filter's Custom tab against filters it previously mishandled -
|
|
29
|
+
multi-value clauses are now expanded into editable rows and recombined on commit, and filters it
|
|
30
|
+
cannot represent are left untouched rather than corrupted.
|
|
31
|
+
* Fixed `FilterChooser` popover mode (formerly `PopoverFilterChooser`) so its collapsed control no
|
|
32
|
+
longer disappears when opened - it now always occupies its place in the layout, so surrounding
|
|
33
|
+
elements no longer shift. Its clear and favorites controls also respond to a single click rather
|
|
34
|
+
than requiring the popover to be opened first. This mode is now enabled more naturally via
|
|
35
|
+
a new option `filterChooser({popover: true})`, deprecating `PopoverFilterChooser`, which remains
|
|
36
|
+
as a thin alias.
|
|
37
|
+
* Fixed "not a valid MIME type" console warnings from `FileChooser`. Accepted extensions are now
|
|
38
|
+
passed under a dummy MIME type key, silencing the warnings while continuing to filter selected
|
|
39
|
+
files by extension.
|
|
40
|
+
|
|
41
|
+
### ⚙️ Typescript API Adjustments
|
|
42
|
+
|
|
43
|
+
* Retyped `GridModel.colChooserModel` as the new cross-platform `IColChooserModel` interface,
|
|
44
|
+
replacing the bare `HoistModel` type and exposing `isOpen`, `open()`, and `close()` directly.
|
|
45
|
+
* Added the exported `HoistRoute` type - Router5's `Route` extended with Hoist's `omit` key - and
|
|
46
|
+
retyped `HoistAppModel.getRoutes()` to return it, so declarative route exclusion (e.g.
|
|
47
|
+
`omit: !XH.getUser().isHoistAdmin`) now type-checks without a cast.
|
|
48
|
+
|
|
49
|
+
### 🤖 AI Docs + Tooling
|
|
50
|
+
|
|
51
|
+
* Fixed the MCP server and `hoist-ts` CLI TypeScript symbol tools (`search`, `symbol`, `members`)
|
|
52
|
+
returning no results on Windows, where a path-separator mismatch left the symbol index empty.
|
|
53
|
+
Path handling is now normalized so the developer tools work on Windows as well as macOS/Linux.
|
|
54
|
+
|
|
55
|
+
### 📚 Libraries
|
|
56
|
+
|
|
57
|
+
* @auth0/auth0-spa-js `2.21 → 2.23`
|
|
58
|
+
* @azure/msal-browser `5.14 → 5.16`
|
|
59
|
+
* swiper `12.1 -> 14.0`
|
|
60
|
+
|
|
61
|
+
## 86.2.0 - 2026-06-25
|
|
62
|
+
|
|
63
|
+
### 🎁 New Features
|
|
64
|
+
|
|
65
|
+
* `CodeInput` / `JsonInput` now auto-format content for display via their configured `formatter`,
|
|
66
|
+
controlled by a new `autoFormat` prop. On editable inputs, content is tidied automatically on blur
|
|
67
|
+
(never mid-edit) - users get clean, consistently-formatted JSON without reaching for the format
|
|
68
|
+
button. For `readonly` inputs it defaults to true, so applications can bind directly to raw source
|
|
69
|
+
values and drop their pre-formatting logic, simplifying call sites substantially.
|
|
70
|
+
* Grid column filter specs now support a `sortValue` config, letting the Values tab of the filter
|
|
71
|
+
dialog sort its entries the same way the underlying grid column sorts them. When not provided,
|
|
72
|
+
the column's own `sortValue` is used.
|
|
73
|
+
* `GridModel.levelLabels` now accepts a partial array covering only the top levels of a tree or
|
|
74
|
+
grouped grid. The "Expand to..." menu and `ExpandToLevelButton` offer one entry per labelled
|
|
75
|
+
level, so deeper, unlabelled levels (e.g. system-managed) are no longer required and are omitted
|
|
76
|
+
as expand-to targets - previously a too-short array disabled the feature entirely.
|
|
77
|
+
* Added a `lossless` option to `fmtQuantity` to compact values to millions / billions units only
|
|
78
|
+
when doing so loses no precision, rendering the full value otherwise (e.g. `7,100,100` stays
|
|
79
|
+
`7,100,100` rather than collapsing to `7.10m`).
|
|
80
|
+
|
|
81
|
+
### 🐞 Bug Fixes
|
|
82
|
+
|
|
83
|
+
* Fixed grid `NumberEditor` to allow starting an edit by typing `-`, `+`, or `.` (e.g. to enter a
|
|
84
|
+
negative or decimal value), while reliably rejecting other non-numeric keypresses.
|
|
85
|
+
* `fmtNumber` now treats a `precision` of `null` as full, unrestricted precision rather than
|
|
86
|
+
`'auto'`, aligning a `NumberInput` with `precision: null` so its blurred display matches its
|
|
87
|
+
focused and committed (full-precision) value.
|
|
88
|
+
* Fixed inline grid editing not ending when clicking empty grid space to the right of the last
|
|
89
|
+
column or below the last row - such clicks now commit the active edit.
|
|
90
|
+
* Fixed `Icon.placeholder()` to render with the correct width.
|
|
91
|
+
* `CheckboxButton` no longer leaks `HoistInputProps` into underlying HTML `<button>` element.
|
|
92
|
+
* Fixed `Select` (and `SelectEditor`) dropdown menu sizing: windowed menus now auto-size to their
|
|
93
|
+
option labels instead of the control/cell width, and an explicit `menuWidth` is respected rather
|
|
94
|
+
than overridden by content auto-sizing.
|
|
95
|
+
|
|
96
|
+
### ⚙️ Typescript API Adjustments
|
|
97
|
+
|
|
98
|
+
* `GridFilterFieldSpec.renderer` is now typed as a pure value transform (`GridFilterRenderer`),
|
|
99
|
+
rather than a `ColumnRenderer`. This more accurately represents the existing run-time limitation
|
|
100
|
+
that a complex column renderer would throw.
|
|
101
|
+
|
|
102
|
+
### ✨ Styles
|
|
103
|
+
|
|
104
|
+
* Vertical (left/right) `TabContainer` switchers now render a modern rounded-pill treatment by
|
|
105
|
+
default, customizable via new `--xh-tab-switcher-vertical-*` CSS variables.
|
|
106
|
+
|
|
3
107
|
## 86.1.0 - 2026-06-22
|
|
4
108
|
|
|
5
109
|
### 🎁 New Features
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* This file belongs to Hoist, an application development toolkit
|
|
3
|
+
* developed by Extremely Heavy Industries (www.xh.io | info@xh.io)
|
|
4
|
+
*
|
|
5
|
+
* Copyright © 2026 Extremely Heavy Industries Inc.
|
|
6
|
+
*/
|
|
7
|
+
import {hoistCmp} from '@xh/hoist/core';
|
|
8
|
+
import {jsonInput, JsonInputProps} from '@xh/hoist/desktop/cmp/input';
|
|
9
|
+
import {fmtJson, timestampReplacer} from '@xh/hoist/format';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Readonly `jsonInput` preconfigured for Admin Console stat / config displays - sized to fill its
|
|
13
|
+
* container, without line numbers or a fullscreen button, and rendering timestamp-suffixed fields
|
|
14
|
+
* as readable dates. Pass the data via `value` (any object) or `bind`, plus any additional
|
|
15
|
+
* `jsonInput` props (e.g. `enableSearch`) to extend or override the defaults.
|
|
16
|
+
*/
|
|
17
|
+
export const adminJsonDisplay = hoistCmp.factory<JsonInputProps>({
|
|
18
|
+
displayName: 'AdminJsonDisplay',
|
|
19
|
+
render(props) {
|
|
20
|
+
return jsonInput({
|
|
21
|
+
readonly: true,
|
|
22
|
+
flex: 1,
|
|
23
|
+
width: '100%',
|
|
24
|
+
height: '100%',
|
|
25
|
+
showFullscreenButton: false,
|
|
26
|
+
lineNumbers: false,
|
|
27
|
+
formatter: v => fmtJson(v, {replacer: timestampReplacer()}),
|
|
28
|
+
...props
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
});
|
package/admin/App.scss
CHANGED
|
@@ -6,19 +6,11 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
.xh-admin-app {
|
|
9
|
+
// The admin console uses left side-tab rails throughout - give them a consistent minimum width.
|
|
10
|
+
// All other vertical-switcher styling is inherited from the framework default.
|
|
9
11
|
.xh-tab-switcher--left,
|
|
10
12
|
.xh-tab-switcher--right {
|
|
11
|
-
min-width: 160px;
|
|
12
|
-
background-color: var(--xh-grid-bg-odd);
|
|
13
|
-
border-right: var(--xh-border-solid);
|
|
14
|
-
|
|
15
|
-
.bp6-tab {
|
|
16
|
-
border-radius: 0 !important;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
.xh-tab-switcher__tab {
|
|
20
|
-
line-height: 40px;
|
|
21
|
-
}
|
|
13
|
+
--xh-tab-switcher-vertical-min-width: 160px;
|
|
22
14
|
}
|
|
23
15
|
|
|
24
16
|
&__editor-dialog {
|
package/admin/AppModel.ts
CHANGED
|
@@ -7,10 +7,9 @@
|
|
|
7
7
|
import {GridModel} from '@xh/hoist/cmp/grid';
|
|
8
8
|
import {TabConfig, TabContainerModel} from '@xh/hoist/cmp/tab';
|
|
9
9
|
import {ViewManagerModel} from '@xh/hoist/cmp/viewmanager';
|
|
10
|
-
import {HoistAppModel, InitContext, XH} from '@xh/hoist/core';
|
|
10
|
+
import {HoistAppModel, HoistRoute, InitContext, XH} from '@xh/hoist/core';
|
|
11
11
|
import {Icon} from '@xh/hoist/icon';
|
|
12
12
|
import {without} from 'lodash';
|
|
13
|
-
import {Route} from 'router5';
|
|
14
13
|
import {activityTrackingPanel} from './tabs/activity/tracking/ActivityTrackingPanel';
|
|
15
14
|
import {clientsPanel} from './tabs/clients/ClientsPanel';
|
|
16
15
|
import {monitorTab} from './tabs/monitor/MonitorTab';
|
|
@@ -49,7 +48,7 @@ export class AppModel extends HoistAppModel {
|
|
|
49
48
|
await super.initAsync(ctx);
|
|
50
49
|
}
|
|
51
50
|
|
|
52
|
-
override getRoutes():
|
|
51
|
+
override getRoutes(): HoistRoute[] {
|
|
53
52
|
return [
|
|
54
53
|
{
|
|
55
54
|
name: 'default',
|
|
@@ -66,7 +65,7 @@ export class AppModel extends HoistAppModel {
|
|
|
66
65
|
return [];
|
|
67
66
|
}
|
|
68
67
|
|
|
69
|
-
getTabRoutes():
|
|
68
|
+
getTabRoutes(): HoistRoute[] {
|
|
70
69
|
return [
|
|
71
70
|
{
|
|
72
71
|
name: 'general',
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import {GridConfig, GridModel} from '@xh/hoist/cmp/grid';
|
|
9
|
-
import {HoistModel, managed, TaskObserver, XH} from '@xh/hoist/core';
|
|
9
|
+
import {HoistModel, managed, PlainObject, TaskObserver, XH} from '@xh/hoist/core';
|
|
10
10
|
import {action, bindable, makeObservable, observable} from '@xh/hoist/mobx';
|
|
11
11
|
import {pluralize} from '@xh/hoist/utils/js';
|
|
12
12
|
import {isEmpty, zipWith} from 'lodash';
|
|
@@ -32,7 +32,7 @@ export class JsonSearchImplModel extends HoistModel {
|
|
|
32
32
|
@bindable path: string = '';
|
|
33
33
|
@bindable readerContentType: 'document' | 'matches' = 'matches';
|
|
34
34
|
@bindable pathFormat: 'XPath' | 'JSONPath' = 'XPath';
|
|
35
|
-
@bindable readerContent: string = '';
|
|
35
|
+
@bindable readerContent: string | PlainObject[] = '';
|
|
36
36
|
@bindable matchingNodeCount: number = 0;
|
|
37
37
|
|
|
38
38
|
get subjectName(): string {
|
|
@@ -137,7 +137,7 @@ export class JsonSearchImplModel extends HoistModel {
|
|
|
137
137
|
const {json} = this.selectedRecord.data;
|
|
138
138
|
|
|
139
139
|
if (this.readerContentType === 'document') {
|
|
140
|
-
this.readerContent =
|
|
140
|
+
this.readerContent = json;
|
|
141
141
|
return;
|
|
142
142
|
}
|
|
143
143
|
|
|
@@ -162,7 +162,7 @@ export class JsonSearchImplModel extends HoistModel {
|
|
|
162
162
|
value
|
|
163
163
|
};
|
|
164
164
|
});
|
|
165
|
-
this.readerContent =
|
|
165
|
+
this.readerContent = nodes;
|
|
166
166
|
});
|
|
167
167
|
}
|
|
168
168
|
|
|
@@ -15,7 +15,17 @@ import {FilterChooserModel} from '@xh/hoist/cmp/filter';
|
|
|
15
15
|
import {FormModel} from '@xh/hoist/cmp/form';
|
|
16
16
|
import {ColumnRenderer, ColumnSpec, GridModel, TreeStyle} from '@xh/hoist/cmp/grid';
|
|
17
17
|
import {GroupingChooserModel} from '@xh/hoist/cmp/grouping';
|
|
18
|
-
import {
|
|
18
|
+
import {
|
|
19
|
+
HoistModel,
|
|
20
|
+
LoadSpec,
|
|
21
|
+
LocalDateUnit,
|
|
22
|
+
managed,
|
|
23
|
+
PersistableState,
|
|
24
|
+
PersistenceProvider,
|
|
25
|
+
persistOptions,
|
|
26
|
+
PlainObject,
|
|
27
|
+
XH
|
|
28
|
+
} from '@xh/hoist/core';
|
|
19
29
|
import {Cube, CubeFieldSpec, FieldSpec, ViewRowData} from '@xh/hoist/data';
|
|
20
30
|
import {dateRenderer, dateTimeSecRenderer, numberRenderer} from '@xh/hoist/format';
|
|
21
31
|
import {action, computed, makeObservable, observable} from '@xh/hoist/mobx';
|
|
@@ -24,6 +34,23 @@ import {compact, get, isEmpty, isEqual, round} from 'lodash';
|
|
|
24
34
|
import moment from 'moment';
|
|
25
35
|
import {ActivityDetailProvider} from './detail/ActivityDetailModel';
|
|
26
36
|
|
|
37
|
+
/**
|
|
38
|
+
* A relative time interval (e.g. 7 days) ending on the current app day. Drives the quick-select
|
|
39
|
+
* buttons and the persisted time period, which rolls forward to end on today on restore.
|
|
40
|
+
*/
|
|
41
|
+
export interface ActivityInterval {
|
|
42
|
+
value: number;
|
|
43
|
+
unit: LocalDateUnit;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** Quick-select intervals offered in the toolbar - labels are derived as `${value}${unit[0]}`. */
|
|
47
|
+
export const INTERVALS: ActivityInterval[] = [
|
|
48
|
+
{value: 6, unit: 'months'},
|
|
49
|
+
{value: 1, unit: 'months'},
|
|
50
|
+
{value: 7, unit: 'days'},
|
|
51
|
+
{value: 1, unit: 'days'}
|
|
52
|
+
];
|
|
53
|
+
|
|
27
54
|
export class ActivityTrackingModel extends HoistModel implements ActivityDetailProvider {
|
|
28
55
|
override telemetryPrefix = 'xh.client.admin.tracking';
|
|
29
56
|
|
|
@@ -115,6 +142,7 @@ export class ActivityTrackingModel extends HoistModel implements ActivityDetailP
|
|
|
115
142
|
this.markPersist('showFilterChooser');
|
|
116
143
|
|
|
117
144
|
this.formModel = this.createQueryFormModel();
|
|
145
|
+
this.setupPeriodPersistence();
|
|
118
146
|
|
|
119
147
|
this.dataFieldsEditorModel = new DataFieldsEditorModel(this);
|
|
120
148
|
this.markPersist('dataFields');
|
|
@@ -203,17 +231,17 @@ export class ActivityTrackingModel extends HoistModel implements ActivityDetailP
|
|
|
203
231
|
endDay.setValue(newEnd);
|
|
204
232
|
}
|
|
205
233
|
|
|
206
|
-
// Set the start date by taking the end date and pushing back
|
|
234
|
+
// Set the start date by taking the end date and pushing back the interval - then pushing
|
|
207
235
|
// forward one day as the day range query is inclusive.
|
|
208
|
-
adjustStartDate(
|
|
236
|
+
adjustStartDate(interval: ActivityInterval) {
|
|
209
237
|
this.formModel.setValues({
|
|
210
|
-
startDay: this.endDay.subtract(value, unit).nextDay()
|
|
238
|
+
startDay: this.endDay.subtract(interval.value, interval.unit).nextDay()
|
|
211
239
|
});
|
|
212
240
|
}
|
|
213
241
|
|
|
214
|
-
isInterval(
|
|
242
|
+
isInterval(interval: ActivityInterval) {
|
|
215
243
|
const {startDay, endDay} = this.formModel.values;
|
|
216
|
-
return startDay === endDay.subtract(value, unit).nextDay();
|
|
244
|
+
return startDay === endDay.subtract(interval.value, interval.unit).nextDay();
|
|
217
245
|
}
|
|
218
246
|
|
|
219
247
|
getDisplayName(fieldName: string) {
|
|
@@ -234,6 +262,28 @@ export class ActivityTrackingModel extends HoistModel implements ActivityDetailP
|
|
|
234
262
|
});
|
|
235
263
|
}
|
|
236
264
|
|
|
265
|
+
// Persist the selected time period as part of the active view, if endDay is currentDay
|
|
266
|
+
private setupPeriodPersistence() {
|
|
267
|
+
PersistenceProvider.create<ActivityInterval>({
|
|
268
|
+
persistOptions: persistOptions({path: 'queryPeriod'}, this.persistWith),
|
|
269
|
+
owner: this,
|
|
270
|
+
target: {
|
|
271
|
+
getPersistableState: () => {
|
|
272
|
+
const currDay = this.endDay === LocalDate.currentAppDay(),
|
|
273
|
+
interval = currDay ? INTERVALS.find(it => this.isInterval(it)) : null;
|
|
274
|
+
return new PersistableState(interval ?? null);
|
|
275
|
+
},
|
|
276
|
+
setPersistableState: ({value}) => {
|
|
277
|
+
if (value) {
|
|
278
|
+
const endDay = LocalDate.currentAppDay();
|
|
279
|
+
this.formModel.setValues({endDay});
|
|
280
|
+
this.adjustStartDate(value);
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
|
|
237
287
|
private async loadGridAsync() {
|
|
238
288
|
const {cube, gridModel, dimensions} = this,
|
|
239
289
|
data = cube.executeQuery({
|
|
@@ -11,7 +11,7 @@ import {grid} from '@xh/hoist/cmp/grid';
|
|
|
11
11
|
import {div, filler, hframe} from '@xh/hoist/cmp/layout';
|
|
12
12
|
import {creates, hoistCmp} from '@xh/hoist/core';
|
|
13
13
|
import {button, buttonGroup, colChooserButton, exportButton} from '@xh/hoist/desktop/cmp/button';
|
|
14
|
-
import {
|
|
14
|
+
import {filterChooser} from '@xh/hoist/desktop/cmp/filter';
|
|
15
15
|
import {formField} from '@xh/hoist/desktop/cmp/form';
|
|
16
16
|
import {groupingChooser} from '@xh/hoist/desktop/cmp/grouping';
|
|
17
17
|
import {dateInput, DateInputProps, select} from '@xh/hoist/desktop/cmp/input';
|
|
@@ -20,7 +20,7 @@ import {toolbar, toolbarSep} from '@xh/hoist/desktop/cmp/toolbar';
|
|
|
20
20
|
import {viewManager} from '@xh/hoist/desktop/cmp/viewmanager';
|
|
21
21
|
import {Icon} from '@xh/hoist/icon';
|
|
22
22
|
import {LocalDate} from '@xh/hoist/utils/datetime';
|
|
23
|
-
import {ActivityTrackingModel} from './ActivityTrackingModel';
|
|
23
|
+
import {INTERVALS, ActivityTrackingModel} from './ActivityTrackingModel';
|
|
24
24
|
import {aggChartPanel} from '@xh/hoist/admin/tabs/activity/tracking/chart/AggChartPanel';
|
|
25
25
|
import {activityDetailView} from './detail/ActivityDetailView';
|
|
26
26
|
import './ActivityTracking.scss';
|
|
@@ -75,32 +75,14 @@ const tbar = hoistCmp.factory<ActivityTrackingModel>(({model}) => {
|
|
|
75
75
|
disabled: model.endDay >= LocalDate.currentAppDay()
|
|
76
76
|
}),
|
|
77
77
|
buttonGroup({
|
|
78
|
-
items:
|
|
78
|
+
items: INTERVALS.map(interval =>
|
|
79
79
|
button({
|
|
80
|
-
text:
|
|
81
|
-
onClick: () => model.adjustStartDate(
|
|
82
|
-
active: model.isInterval(
|
|
83
|
-
...dateBtn
|
|
84
|
-
}),
|
|
85
|
-
button({
|
|
86
|
-
text: '1m',
|
|
87
|
-
onClick: () => model.adjustStartDate(1, 'months'),
|
|
88
|
-
active: model.isInterval(1, 'months'),
|
|
89
|
-
...dateBtn
|
|
90
|
-
}),
|
|
91
|
-
button({
|
|
92
|
-
text: '7d',
|
|
93
|
-
onClick: () => model.adjustStartDate(7, 'days'),
|
|
94
|
-
active: model.isInterval(7, 'days'),
|
|
95
|
-
...dateBtn
|
|
96
|
-
}),
|
|
97
|
-
button({
|
|
98
|
-
text: '1d',
|
|
99
|
-
onClick: () => model.adjustStartDate(1, 'days'),
|
|
100
|
-
active: model.isInterval(1, 'days'),
|
|
80
|
+
text: `${interval.value}${interval.unit[0]}`,
|
|
81
|
+
onClick: () => model.adjustStartDate(interval),
|
|
82
|
+
active: model.isInterval(interval),
|
|
101
83
|
...dateBtn
|
|
102
84
|
})
|
|
103
|
-
|
|
85
|
+
)
|
|
104
86
|
}),
|
|
105
87
|
toolbarSep(),
|
|
106
88
|
filterChooserToggleButton(),
|
|
@@ -137,7 +119,8 @@ const filterChooserToggleButton = hoistCmp.factory<ActivityTrackingModel>(({mode
|
|
|
137
119
|
const filterBar = hoistCmp.factory<ActivityTrackingModel>(({model}) => {
|
|
138
120
|
return model.showFilterChooser
|
|
139
121
|
? toolbar(
|
|
140
|
-
|
|
122
|
+
filterChooser({
|
|
123
|
+
popover: true,
|
|
141
124
|
flex: 1,
|
|
142
125
|
enableClear: true
|
|
143
126
|
})
|
|
@@ -9,7 +9,7 @@ import {AppModel} from '@xh/hoist/admin/AppModel';
|
|
|
9
9
|
import * as Col from '@xh/hoist/admin/columns';
|
|
10
10
|
import {GridModel} from '@xh/hoist/cmp/grid';
|
|
11
11
|
import {div, p} from '@xh/hoist/cmp/layout';
|
|
12
|
-
import {LoadSpec, managed, XH} from '@xh/hoist/core';
|
|
12
|
+
import {LoadSpec, managed, persistOptions, XH} from '@xh/hoist/core';
|
|
13
13
|
import {RecordActionSpec, StoreRecord} from '@xh/hoist/data';
|
|
14
14
|
import {textInput} from '@xh/hoist/desktop/cmp/input';
|
|
15
15
|
import {Icon} from '@xh/hoist/icon';
|
|
@@ -23,6 +23,8 @@ import {BaseAdminTabModel} from '@xh/hoist/admin/tabs/BaseAdminTabModel';
|
|
|
23
23
|
export class ClientsModel extends BaseAdminTabModel {
|
|
24
24
|
override telemetryPrefix = 'xh.client.admin.clients';
|
|
25
25
|
|
|
26
|
+
override persistWith = {localStorageKey: 'xhAdminClientsState'};
|
|
27
|
+
|
|
26
28
|
@observable
|
|
27
29
|
lastRefresh: number;
|
|
28
30
|
|
|
@@ -150,6 +152,7 @@ export class ClientsModel extends BaseAdminTabModel {
|
|
|
150
152
|
return new GridModel({
|
|
151
153
|
emptyText: 'No clients connected.',
|
|
152
154
|
groupBy: this.groupBy,
|
|
155
|
+
persistWith: persistOptions(this.persistWith, {path: 'mainGrid'}),
|
|
153
156
|
colChooserModel: true,
|
|
154
157
|
enableExport: true,
|
|
155
158
|
selModel: 'multiple',
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import {ClientsModel} from '../ClientsModel';
|
|
8
8
|
import {ColumnSpec} from '@xh/hoist/cmp/grid';
|
|
9
|
-
import {HoistModel, LoadSpec, lookup, PlainObject, XH} from '@xh/hoist/core';
|
|
9
|
+
import {HoistModel, LoadSpec, lookup, persistOptions, PlainObject, XH} from '@xh/hoist/core';
|
|
10
10
|
import {StoreRecord} from '@xh/hoist/data';
|
|
11
11
|
import {bindable, computed, makeObservable} from '@xh/hoist/mobx';
|
|
12
12
|
import {ReactNode} from 'react';
|
|
@@ -53,6 +53,10 @@ export class ClientDetailModel extends HoistModel implements ActivityDetailProvi
|
|
|
53
53
|
override onLinked() {
|
|
54
54
|
super.onLinked();
|
|
55
55
|
|
|
56
|
+
this.persistWith = persistOptions(this.clientsModel.persistWith, {
|
|
57
|
+
pathPrefix: 'activityGrid'
|
|
58
|
+
});
|
|
59
|
+
|
|
56
60
|
this.addReaction(
|
|
57
61
|
{
|
|
58
62
|
track: () => this.clientsModel.gridModel.selectedRecord,
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
*
|
|
5
5
|
* Copyright © 2026 Extremely Heavy Industries Inc.
|
|
6
6
|
*/
|
|
7
|
+
import {adminJsonDisplay} from '@xh/hoist/admin/AdminJsonDisplay';
|
|
7
8
|
import {AppModel} from '@xh/hoist/admin/AppModel';
|
|
8
9
|
import {ConnPoolMonitorModel} from '@xh/hoist/admin/tabs/cluster/instances/connpool/ConnPoolMonitorModel';
|
|
9
10
|
import {chart} from '@xh/hoist/cmp/chart';
|
|
@@ -12,7 +13,6 @@ import {filler, hframe, vframe} from '@xh/hoist/cmp/layout';
|
|
|
12
13
|
import {creates, hoistCmp} from '@xh/hoist/core';
|
|
13
14
|
import {button, exportButton} from '@xh/hoist/desktop/cmp/button';
|
|
14
15
|
import {errorMessage} from '@xh/hoist/cmp/error';
|
|
15
|
-
import {jsonInput} from '@xh/hoist/desktop/cmp/input';
|
|
16
16
|
import {panel} from '@xh/hoist/desktop/cmp/panel';
|
|
17
17
|
import {toolbar} from '@xh/hoist/desktop/cmp/toolbar';
|
|
18
18
|
import {Icon} from '@xh/hoist/icon';
|
|
@@ -74,7 +74,7 @@ const bbar = hoistCmp.factory<ConnPoolMonitorModel>({
|
|
|
74
74
|
});
|
|
75
75
|
|
|
76
76
|
const poolConfigPanel = hoistCmp.factory<ConnPoolMonitorModel>({
|
|
77
|
-
render(
|
|
77
|
+
render() {
|
|
78
78
|
return panel({
|
|
79
79
|
title: 'Pool Configuration',
|
|
80
80
|
icon: Icon.info(),
|
|
@@ -84,15 +84,7 @@ const poolConfigPanel = hoistCmp.factory<ConnPoolMonitorModel>({
|
|
|
84
84
|
defaultSize: 450,
|
|
85
85
|
defaultCollapsed: true
|
|
86
86
|
},
|
|
87
|
-
item:
|
|
88
|
-
readonly: true,
|
|
89
|
-
height: '100%',
|
|
90
|
-
width: '100%',
|
|
91
|
-
enableSearch: true,
|
|
92
|
-
showFullscreenButton: false,
|
|
93
|
-
lineNumbers: false,
|
|
94
|
-
value: JSON.stringify(model.poolConfiguration, null, 2)
|
|
95
|
-
})
|
|
87
|
+
item: adminJsonDisplay({enableSearch: true, bind: 'poolConfiguration'})
|
|
96
88
|
});
|
|
97
89
|
}
|
|
98
90
|
});
|
|
@@ -4,15 +4,14 @@
|
|
|
4
4
|
*
|
|
5
5
|
* Copyright © 2026 Extremely Heavy Industries Inc.
|
|
6
6
|
*/
|
|
7
|
+
import {adminJsonDisplay} from '@xh/hoist/admin/AdminJsonDisplay';
|
|
7
8
|
import {DetailsModel} from '@xh/hoist/admin/tabs/cluster/instances/services/DetailsModel';
|
|
8
9
|
import {placeholder} from '@xh/hoist/cmp/layout';
|
|
9
10
|
import {creates, hoistCmp, XH} from '@xh/hoist/core';
|
|
10
11
|
import {errorMessage} from '@xh/hoist/cmp/error';
|
|
11
12
|
import {panel} from '@xh/hoist/desktop/cmp/panel';
|
|
12
|
-
import {jsonInput} from '@xh/hoist/desktop/cmp/input';
|
|
13
13
|
import {Icon} from '@xh/hoist/icon';
|
|
14
14
|
import {isEmpty} from 'lodash';
|
|
15
|
-
import {fmtJson, timestampReplacer} from '@xh/hoist/format';
|
|
16
15
|
|
|
17
16
|
export const detailsPanel = hoistCmp.factory({
|
|
18
17
|
model: creates(DetailsModel),
|
|
@@ -50,16 +49,6 @@ const stats = hoistCmp.factory<DetailsModel>({
|
|
|
50
49
|
? []
|
|
51
50
|
: [Icon.questionCircle(), 'This service does not report any admin stats.'])
|
|
52
51
|
)
|
|
53
|
-
: panel(
|
|
54
|
-
jsonInput({
|
|
55
|
-
readonly: true,
|
|
56
|
-
width: '100%',
|
|
57
|
-
height: '100%',
|
|
58
|
-
enableSearch: true,
|
|
59
|
-
showFullscreenButton: false,
|
|
60
|
-
lineNumbers: false,
|
|
61
|
-
value: fmtJson(stats, {replacer: timestampReplacer()})
|
|
62
|
-
})
|
|
63
|
-
);
|
|
52
|
+
: panel(adminJsonDisplay({enableSearch: true, bind: 'stats'}));
|
|
64
53
|
}
|
|
65
54
|
});
|
|
@@ -4,21 +4,20 @@
|
|
|
4
4
|
*
|
|
5
5
|
* Copyright © 2026 Extremely Heavy Industries Inc.
|
|
6
6
|
*/
|
|
7
|
+
import {adminJsonDisplay} from '@xh/hoist/admin/AdminJsonDisplay';
|
|
7
8
|
import {grid} from '@xh/hoist/cmp/grid';
|
|
8
9
|
import {placeholder} from '@xh/hoist/cmp/layout';
|
|
9
10
|
import {creates, hoistCmp} from '@xh/hoist/core';
|
|
10
|
-
import {jsonInput} from '@xh/hoist/desktop/cmp/input';
|
|
11
11
|
import {panel} from '@xh/hoist/desktop/cmp/panel';
|
|
12
12
|
import {Icon} from '@xh/hoist/icon';
|
|
13
13
|
import {DetailModel} from './DetailModel';
|
|
14
14
|
import './ClusterObjects.scss';
|
|
15
|
-
import {fmtJson, timestampReplacer} from '@xh/hoist/format';
|
|
16
15
|
|
|
17
16
|
export const detailPanel = hoistCmp.factory({
|
|
18
17
|
model: creates(DetailModel),
|
|
19
18
|
|
|
20
19
|
render({model}) {
|
|
21
|
-
const {instanceName,
|
|
20
|
+
const {instanceName, objectName, objectType} = model;
|
|
22
21
|
if (!objectName) return placeholder(Icon.grip(), 'Select an object to view details...');
|
|
23
22
|
|
|
24
23
|
return panel({
|
|
@@ -36,15 +35,7 @@ export const detailPanel = hoistCmp.factory({
|
|
|
36
35
|
defaultSize: '80%',
|
|
37
36
|
collapsible: false
|
|
38
37
|
},
|
|
39
|
-
item:
|
|
40
|
-
readonly: true,
|
|
41
|
-
flex: 1,
|
|
42
|
-
width: '100%',
|
|
43
|
-
height: '100%',
|
|
44
|
-
showFullscreenButton: false,
|
|
45
|
-
lineNumbers: false,
|
|
46
|
-
value: fmtJson(selectedAdminStats, {replacer: timestampReplacer()})
|
|
47
|
-
})
|
|
38
|
+
item: adminJsonDisplay({bind: 'selectedAdminStats'})
|
|
48
39
|
})
|
|
49
40
|
]
|
|
50
41
|
});
|
|
@@ -10,7 +10,7 @@ import {fragment, hframe, vframe} from '@xh/hoist/cmp/layout';
|
|
|
10
10
|
import {creates, hoistCmp} from '@xh/hoist/core';
|
|
11
11
|
import {button, colChooserButton} from '@xh/hoist/desktop/cmp/button';
|
|
12
12
|
import {errorMessage} from '@xh/hoist/cmp/error';
|
|
13
|
-
import {
|
|
13
|
+
import {filterChooser} from '@xh/hoist/desktop/cmp/filter';
|
|
14
14
|
import {switchInput} from '@xh/hoist/desktop/cmp/input';
|
|
15
15
|
import {panel} from '@xh/hoist/desktop/cmp/panel';
|
|
16
16
|
import {recordActionBar} from '@xh/hoist/desktop/cmp/record';
|
|
@@ -44,7 +44,7 @@ export const rolePanel = hoistCmp.factory({
|
|
|
44
44
|
selModel: gridModel.selModel
|
|
45
45
|
}),
|
|
46
46
|
'-',
|
|
47
|
-
|
|
47
|
+
filterChooser({popover: true, flex: 1}),
|
|
48
48
|
'-',
|
|
49
49
|
switchInput({
|
|
50
50
|
bind: 'showInGroups',
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
*
|
|
5
5
|
* Copyright © 2026 Extremely Heavy Industries Inc.
|
|
6
6
|
*/
|
|
7
|
-
import {HoistModel} from '../core';
|
|
7
|
+
import {HoistModel, HoistRoute} from '../core';
|
|
8
8
|
import {action, observable, makeObservable} from '@xh/hoist/mobx';
|
|
9
9
|
import {mergeDeep} from '@xh/hoist/utils/js';
|
|
10
10
|
import {isOmitted} from '@xh/hoist/utils/impl';
|
|
@@ -38,11 +38,11 @@ export class RouterModel extends HoistModel {
|
|
|
38
38
|
/**
|
|
39
39
|
* Add routes to the router.
|
|
40
40
|
*
|
|
41
|
-
* @param routes - collection of
|
|
42
|
-
*
|
|
43
|
-
*
|
|
41
|
+
* @param routes - collection of {@link HoistRoute} specs. In addition to the standard Router5
|
|
42
|
+
* route config, each spec supports an `omit` keyword to allow declarative exclusion.
|
|
43
|
+
* Otherwise these are Router5 configs to be passed directly to the Router5 API.
|
|
44
44
|
*/
|
|
45
|
-
addRoutes(routes:
|
|
45
|
+
addRoutes(routes: HoistRoute[]) {
|
|
46
46
|
this.router.add(this.preprocessRoutes(routes));
|
|
47
47
|
}
|
|
48
48
|
|
|
@@ -100,7 +100,7 @@ export class RouterModel extends HoistModel {
|
|
|
100
100
|
return ret;
|
|
101
101
|
}
|
|
102
102
|
|
|
103
|
-
private preprocessRoutes(routes) {
|
|
103
|
+
private preprocessRoutes(routes: HoistRoute[]): HoistRoute[] {
|
|
104
104
|
const ret = routes.filter(r => !isOmitted(r));
|
|
105
105
|
ret.forEach(r => {
|
|
106
106
|
if (r.children) r.children = this.preprocessRoutes(r.children);
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Readonly `jsonInput` preconfigured for Admin Console stat / config displays - sized to fill its
|
|
3
|
+
* container, without line numbers or a fullscreen button, and rendering timestamp-suffixed fields
|
|
4
|
+
* as readable dates. Pass the data via `value` (any object) or `bind`, plus any additional
|
|
5
|
+
* `jsonInput` props (e.g. `enableSearch`) to extend or override the defaults.
|
|
6
|
+
*/
|
|
7
|
+
export declare const adminJsonDisplay: import("@xh/hoist/core").ElementFactory<import("@xh/hoist/desktop/cmp/input").CodeInputProps>;
|
|
@@ -1,16 +1,15 @@
|
|
|
1
1
|
import { TabConfig, TabContainerModel } from '@xh/hoist/cmp/tab';
|
|
2
2
|
import { ViewManagerModel } from '@xh/hoist/cmp/viewmanager';
|
|
3
|
-
import { HoistAppModel, InitContext } from '@xh/hoist/core';
|
|
4
|
-
import { Route } from 'router5';
|
|
3
|
+
import { HoistAppModel, HoistRoute, InitContext } from '@xh/hoist/core';
|
|
5
4
|
export declare class AppModel extends HoistAppModel {
|
|
6
5
|
tabModel: TabContainerModel;
|
|
7
6
|
viewManagerModels: Record<string, ViewManagerModel>;
|
|
8
7
|
static get readonly(): boolean;
|
|
9
8
|
constructor();
|
|
10
9
|
initAsync(ctx: InitContext): Promise<void>;
|
|
11
|
-
getRoutes():
|
|
10
|
+
getRoutes(): HoistRoute[];
|
|
12
11
|
getAppMenuButtonExtraItems(): any[];
|
|
13
|
-
getTabRoutes():
|
|
12
|
+
getTabRoutes(): HoistRoute[];
|
|
14
13
|
createTabs(): TabConfig[];
|
|
15
14
|
/** Open the primary business-facing application, typically 'app'. */
|
|
16
15
|
openPrimaryApp(): void;
|