@artemis-studio/plugin-sdk 2026.9.37 → 2026.9.39
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/index.js +4 -0
- package/package.json +1 -1
- package/types/kernel/actions/ActionHost.d.ts +14 -0
- package/types/kernel/actions/hostContext.d.ts +4 -0
- package/types/kernel/actions/types.d.ts +130 -0
- package/types/kernel/api/schema.d.ts +42 -0
- package/types/kernel/feature.d.ts +10 -0
- package/types/kernel/keyboard/ShortcutsHelp.d.ts +6 -0
- package/types/kernel/keyboard/filterShortcut.d.ts +5 -0
- package/types/kernel/keyboard/keys.d.ts +12 -0
- package/types/kernel/keyboard/shortcuts.d.ts +5 -0
- package/types/kernel/keyboard/useKeySequences.d.ts +16 -0
- package/types/kernel/nav/currentView.d.ts +21 -0
- package/types/kernel/shell/Breadcrumb.d.ts +5 -0
- package/types/kernel/shell/ClusterLayout.d.ts +1 -1
- package/types/kernel/shell/CommandPalette.d.ts +8 -3
- package/types/kernel/shell/pageTitle.d.ts +13 -0
- package/types/kernel/shell/recents.d.ts +16 -0
- package/types/kernel/slots.d.ts +23 -0
- package/types/kernel/time/ranges.d.ts +32 -0
- package/types/sdk/index.d.ts +4 -0
- package/types/ui/ActionMenuItem.d.ts +27 -0
- package/types/ui/AnchoredMenu.d.ts +20 -0
- package/types/ui/CapabilityGate.d.ts +22 -0
- package/types/ui/CapabilityReason.d.ts +11 -0
- package/types/ui/VirtualTable.d.ts +30 -1
- package/types/ui/capabilityGate.d.ts +28 -0
- package/types/ui/menuAnchor.d.ts +12 -0
- package/types/ui/rovingGrid.d.ts +36 -0
package/index.js
CHANGED
|
@@ -3,8 +3,11 @@
|
|
|
3
3
|
const outsideStudio = (name) => () => {
|
|
4
4
|
throw new Error(`${name}: @artemis-studio/plugin-sdk is provided by Artemis Studio at runtime; load this plugin in Studio.`);
|
|
5
5
|
};
|
|
6
|
+
export const ACTION_SECTIONS = outsideStudio('ACTION_SECTIONS');
|
|
7
|
+
export const ActionMenuItem = outsideStudio('ActionMenuItem');
|
|
6
8
|
export const ApiError = outsideStudio('ApiError');
|
|
7
9
|
export const CONTRACT = outsideStudio('CONTRACT');
|
|
10
|
+
export const CapabilityGate = outsideStudio('CapabilityGate');
|
|
8
11
|
export const ConfirmByTyping = outsideStudio('ConfirmByTyping');
|
|
9
12
|
export const NAV_GROUPS = outsideStudio('NAV_GROUPS');
|
|
10
13
|
export const NodeOutcomeSummary = outsideStudio('NodeOutcomeSummary');
|
|
@@ -15,6 +18,7 @@ export const VirtualTable = outsideStudio('VirtualTable');
|
|
|
15
18
|
export const clusterKey = outsideStudio('clusterKey');
|
|
16
19
|
export const clusterRoute = outsideStudio('clusterRoute');
|
|
17
20
|
export const definePlugin = outsideStudio('definePlugin');
|
|
21
|
+
export const gateFor = outsideStudio('gateFor');
|
|
18
22
|
export const notify = outsideStudio('notify');
|
|
19
23
|
export const pluginApi = outsideStudio('pluginApi');
|
|
20
24
|
export const pluginPath = outsideStudio('pluginPath');
|
package/package.json
CHANGED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { type ReactNode } from 'react';
|
|
2
|
+
/**
|
|
3
|
+
* Hosts the dialogs row actions open (ADR-0107), outside any grid: a dialog mounted in a
|
|
4
|
+
* virtualized row is unmounted when the row scrolls away or when the refresh its own success
|
|
5
|
+
* triggers removes the row, and its outcome goes with it.
|
|
6
|
+
*
|
|
7
|
+
* <p>A dialog is mounted closed and opened on the next frame. Dialogs take their dry-run preview
|
|
8
|
+
* in `onEnterTransitionEnd`, which a modal mounted already open never fires. On close it is kept
|
|
9
|
+
* for its exit transition, then removed, and focus goes back to what opened it — unless the dialog
|
|
10
|
+
* navigated away, where moving focus would be a jump the operator did not ask for.
|
|
11
|
+
*/
|
|
12
|
+
export declare function ActionHostProvider({ children }: {
|
|
13
|
+
children: ReactNode;
|
|
14
|
+
}): import("react").JSX.Element;
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import type { ComponentType, ReactNode } from 'react';
|
|
2
|
+
import type { components } from '../api/schema.d.ts';
|
|
3
|
+
import type { GateVerdict } from '../../ui/capabilityGate.ts';
|
|
4
|
+
type Schemas = components['schemas'];
|
|
5
|
+
/**
|
|
6
|
+
* The sections of a row's action menu, in the order it shows them (ADR-0107). The list is closed,
|
|
7
|
+
* like the navigation groups: an action names one, and adding one is a kernel change.
|
|
8
|
+
*/
|
|
9
|
+
export declare const ACTION_SECTIONS: readonly [{
|
|
10
|
+
readonly id: "open";
|
|
11
|
+
readonly label: "Open";
|
|
12
|
+
}, {
|
|
13
|
+
readonly id: "copy";
|
|
14
|
+
readonly label: "Copy";
|
|
15
|
+
}, {
|
|
16
|
+
readonly id: "operate";
|
|
17
|
+
readonly label: "Operate";
|
|
18
|
+
}, {
|
|
19
|
+
readonly id: "destroy";
|
|
20
|
+
readonly label: "Destroy";
|
|
21
|
+
}];
|
|
22
|
+
export type ActionSection = (typeof ACTION_SECTIONS)[number]['id'];
|
|
23
|
+
/**
|
|
24
|
+
* `act` offers everything; `navigate` only the Open and Copy sections, for a view that must never
|
|
25
|
+
* change the broker (Flow).
|
|
26
|
+
*/
|
|
27
|
+
export type ActionMode = 'act' | 'navigate';
|
|
28
|
+
export interface QueueTarget {
|
|
29
|
+
queueName: string;
|
|
30
|
+
address?: string | null;
|
|
31
|
+
snapshot?: Schemas['QueueView'];
|
|
32
|
+
}
|
|
33
|
+
export interface AddressTarget {
|
|
34
|
+
address: string;
|
|
35
|
+
snapshot?: Schemas['AddressView'];
|
|
36
|
+
}
|
|
37
|
+
/** Connections, sessions, consumers and producers are per node; a close needs to know which. */
|
|
38
|
+
interface NodeScoped {
|
|
39
|
+
nodeId: string;
|
|
40
|
+
nodeName: string;
|
|
41
|
+
}
|
|
42
|
+
export interface ConnectionTarget extends NodeScoped {
|
|
43
|
+
connectionId: string;
|
|
44
|
+
snapshot?: Schemas['ConnectionView'];
|
|
45
|
+
}
|
|
46
|
+
export interface SessionTarget extends NodeScoped {
|
|
47
|
+
sessionId: string;
|
|
48
|
+
connectionId?: string | null;
|
|
49
|
+
snapshot?: Schemas['SessionView'];
|
|
50
|
+
}
|
|
51
|
+
export interface ConsumerTarget extends NodeScoped {
|
|
52
|
+
consumerId: string;
|
|
53
|
+
queueName?: string | null;
|
|
54
|
+
sessionId?: string | null;
|
|
55
|
+
snapshot?: Schemas['ConsumerView'];
|
|
56
|
+
}
|
|
57
|
+
export interface ProducerTarget extends NodeScoped {
|
|
58
|
+
producerId: string;
|
|
59
|
+
address?: string | null;
|
|
60
|
+
sessionId?: string | null;
|
|
61
|
+
snapshot?: Schemas['ProducerView'];
|
|
62
|
+
}
|
|
63
|
+
export interface MessageTarget {
|
|
64
|
+
queueName: string;
|
|
65
|
+
messageId: number;
|
|
66
|
+
/** The node the message was browsed on; absent for the live node. */
|
|
67
|
+
node?: string;
|
|
68
|
+
}
|
|
69
|
+
export interface DivertTarget {
|
|
70
|
+
name: string;
|
|
71
|
+
snapshot?: Schemas['DivertView'];
|
|
72
|
+
}
|
|
73
|
+
/** A client of the flow view: an identity that may stand for many connections. */
|
|
74
|
+
export interface ClientTarget {
|
|
75
|
+
label: string;
|
|
76
|
+
}
|
|
77
|
+
/** Each resource kind a row menu or a link can be about, with its target. */
|
|
78
|
+
export interface ActionTargets {
|
|
79
|
+
queue: QueueTarget;
|
|
80
|
+
address: AddressTarget;
|
|
81
|
+
connection: ConnectionTarget;
|
|
82
|
+
session: SessionTarget;
|
|
83
|
+
consumer: ConsumerTarget;
|
|
84
|
+
producer: ProducerTarget;
|
|
85
|
+
message: MessageTarget;
|
|
86
|
+
divert: DivertTarget;
|
|
87
|
+
client: ClientTarget;
|
|
88
|
+
}
|
|
89
|
+
export type ActionKind = keyof ActionTargets;
|
|
90
|
+
/** What the action host gives a dialog it hosts. */
|
|
91
|
+
export interface HostedDialogProps {
|
|
92
|
+
opened: boolean;
|
|
93
|
+
onClose: () => void;
|
|
94
|
+
}
|
|
95
|
+
type Blocked = Extract<GateVerdict, {
|
|
96
|
+
kind: 'blocked';
|
|
97
|
+
}>;
|
|
98
|
+
/**
|
|
99
|
+
* Where an action's dialog lives (ADR-0107): outside the grid, so it outlives the row that opened
|
|
100
|
+
* it, the menu that offered it, and the refresh that removes the row when the action succeeds.
|
|
101
|
+
*/
|
|
102
|
+
export interface ActionHost {
|
|
103
|
+
/**
|
|
104
|
+
* Opens `Dialog` with `props`, plus `opened` and `onClose`. On close, focus goes back through
|
|
105
|
+
* `restoreFocus` — unless the dialog navigated away.
|
|
106
|
+
*/
|
|
107
|
+
open<P extends HostedDialogProps>(Dialog: ComponentType<P>, props: NoInfer<Omit<P, keyof HostedDialogProps>>, options?: {
|
|
108
|
+
restoreFocus?: () => void;
|
|
109
|
+
}): void;
|
|
110
|
+
/** Explains why an action is unavailable, in full. */
|
|
111
|
+
explain(verdict: Blocked, what: string, options?: {
|
|
112
|
+
restoreFocus?: () => void;
|
|
113
|
+
}): void;
|
|
114
|
+
/** Copies `text` and says so; `what` names it ("queue name"). */
|
|
115
|
+
copy(text: string, what: string): void;
|
|
116
|
+
}
|
|
117
|
+
/** What a row-action contribution is given. */
|
|
118
|
+
export interface ActionProps<T> {
|
|
119
|
+
clusterId: string;
|
|
120
|
+
target: T;
|
|
121
|
+
host: ActionHost;
|
|
122
|
+
mode: ActionMode;
|
|
123
|
+
}
|
|
124
|
+
/** What a link contribution is given: it wraps `children` (the name) in a link to the resource. */
|
|
125
|
+
export interface LinkProps<T> {
|
|
126
|
+
clusterId: string;
|
|
127
|
+
target: T;
|
|
128
|
+
children: ReactNode;
|
|
129
|
+
}
|
|
130
|
+
export {};
|
|
@@ -4568,6 +4568,12 @@ export interface components {
|
|
|
4568
4568
|
matchesAvailable: number;
|
|
4569
4569
|
note?: string | null;
|
|
4570
4570
|
};
|
|
4571
|
+
MetricNodeSeries: {
|
|
4572
|
+
nodeId: string;
|
|
4573
|
+
nodeName: string;
|
|
4574
|
+
sampled: boolean;
|
|
4575
|
+
series: components["schemas"]["MetricSeries"][];
|
|
4576
|
+
};
|
|
4571
4577
|
MetricPoint: {
|
|
4572
4578
|
/** Format: date-time */
|
|
4573
4579
|
ts: string;
|
|
@@ -4590,6 +4596,8 @@ export interface components {
|
|
|
4590
4596
|
step: string;
|
|
4591
4597
|
truncated: boolean;
|
|
4592
4598
|
series: components["schemas"]["MetricSeries"][];
|
|
4599
|
+
splitBy?: string | null;
|
|
4600
|
+
byNode?: components["schemas"]["MetricNodeSeries"][] | null;
|
|
4593
4601
|
};
|
|
4594
4602
|
FlowBrokerNodeView: {
|
|
4595
4603
|
nodeId?: string;
|
|
@@ -4609,6 +4617,14 @@ export interface components {
|
|
|
4609
4617
|
consumersTotal: number;
|
|
4610
4618
|
truncated: boolean;
|
|
4611
4619
|
brokerXmlSnippet?: string;
|
|
4620
|
+
/** Format: int64 */
|
|
4621
|
+
backlog?: number | null;
|
|
4622
|
+
/** Format: int64 */
|
|
4623
|
+
consumers?: number | null;
|
|
4624
|
+
/** Format: double */
|
|
4625
|
+
inRate?: number | null;
|
|
4626
|
+
/** Format: double */
|
|
4627
|
+
outRate?: number | null;
|
|
4612
4628
|
};
|
|
4613
4629
|
FlowEdgeView: {
|
|
4614
4630
|
id?: string;
|
|
@@ -4639,6 +4655,7 @@ export interface components {
|
|
|
4639
4655
|
presentOf?: number;
|
|
4640
4656
|
studio: boolean;
|
|
4641
4657
|
faults?: ("NO_CONSUMER" | "STALLED" | "BRIDGE_DOWN" | "PARTIAL_PRESENCE")[];
|
|
4658
|
+
byNode?: components["schemas"]["FlowNodeRate"][] | null;
|
|
4642
4659
|
};
|
|
4643
4660
|
FlowFocusView: {
|
|
4644
4661
|
kind?: string;
|
|
@@ -4674,6 +4691,28 @@ export interface components {
|
|
|
4674
4691
|
/** Format: int32 */
|
|
4675
4692
|
faults: number;
|
|
4676
4693
|
};
|
|
4694
|
+
FlowNodeRate: {
|
|
4695
|
+
nodeId: string;
|
|
4696
|
+
node: string;
|
|
4697
|
+
/** Format: double */
|
|
4698
|
+
rate?: number | null;
|
|
4699
|
+
/** Format: date-time */
|
|
4700
|
+
asOf?: string | null;
|
|
4701
|
+
stale: boolean;
|
|
4702
|
+
};
|
|
4703
|
+
FlowNodeShare: {
|
|
4704
|
+
nodeId: string;
|
|
4705
|
+
node: string;
|
|
4706
|
+
/** Format: int64 */
|
|
4707
|
+
messageCount?: number | null;
|
|
4708
|
+
/** Format: int64 */
|
|
4709
|
+
consumerCount?: number | null;
|
|
4710
|
+
/** Format: double */
|
|
4711
|
+
inRate?: number | null;
|
|
4712
|
+
/** Format: double */
|
|
4713
|
+
outRate?: number | null;
|
|
4714
|
+
stale: boolean;
|
|
4715
|
+
};
|
|
4677
4716
|
FlowNodeView: {
|
|
4678
4717
|
id?: string;
|
|
4679
4718
|
/** @enum {string} */
|
|
@@ -4693,6 +4732,7 @@ export interface components {
|
|
|
4693
4732
|
users?: string[];
|
|
4694
4733
|
brokerNodes?: string[];
|
|
4695
4734
|
faults?: ("NO_CONSUMER" | "STALLED" | "BRIDGE_DOWN" | "PARTIAL_PRESENCE")[];
|
|
4735
|
+
byNode?: components["schemas"]["FlowNodeShare"][] | null;
|
|
4696
4736
|
};
|
|
4697
4737
|
FlowTotals: {
|
|
4698
4738
|
/** Format: int32 */
|
|
@@ -8481,6 +8521,7 @@ export interface operations {
|
|
|
8481
8521
|
from?: string;
|
|
8482
8522
|
to?: string;
|
|
8483
8523
|
step?: string;
|
|
8524
|
+
splitBy?: string;
|
|
8484
8525
|
};
|
|
8485
8526
|
header?: never;
|
|
8486
8527
|
path: {
|
|
@@ -8532,6 +8573,7 @@ export interface operations {
|
|
|
8532
8573
|
limit?: number;
|
|
8533
8574
|
groupBy?: "CLIENT_ID" | "USER" | "HOST";
|
|
8534
8575
|
layers?: string;
|
|
8576
|
+
byNode?: boolean;
|
|
8535
8577
|
};
|
|
8536
8578
|
header?: never;
|
|
8537
8579
|
path: {
|
|
@@ -45,13 +45,23 @@ export interface NavContribution {
|
|
|
45
45
|
Badge?: ComponentType<{
|
|
46
46
|
clusterId: string;
|
|
47
47
|
}>;
|
|
48
|
+
/**
|
|
49
|
+
* The letter that, after `g`, goes to this view (ADR-0109). Unique among the built-in views; a
|
|
50
|
+
* plugin's is ignored, so a plugin can never take a letter an operator already relies on.
|
|
51
|
+
*/
|
|
52
|
+
hotkey?: string;
|
|
48
53
|
}
|
|
49
54
|
/**
|
|
50
55
|
* A feature's command-palette groups. It is rendered inside the palette, so it may use hooks, and calls
|
|
51
56
|
* `report` whenever its groups change; `clusterId` is the cluster in view, if there is one.
|
|
57
|
+
*
|
|
58
|
+
* `query` is what the operator has typed (debounced) and `opened` whether the palette is open: a source
|
|
59
|
+
* that searches fetches only while it is open, never on every keystroke of a broker (ADR-0109).
|
|
52
60
|
*/
|
|
53
61
|
export type PaletteSource = ComponentType<{
|
|
54
62
|
clusterId?: string;
|
|
63
|
+
query: string;
|
|
64
|
+
opened: boolean;
|
|
55
65
|
report: (groups: SpotlightActionGroupData[]) => void;
|
|
56
66
|
}>;
|
|
57
67
|
/** Handles one frame of a stream topic the feature owns (ADR-0070). */
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Every keyboard shortcut, in one place (ADR-0109), with the switch that turns the single-key ones
|
|
3
|
+
* off: a header button and the popover it opens, like the refresh control beside it. `?` opens the
|
|
4
|
+
* same popover; the button keeps it reachable when the single-key shortcuts are off.
|
|
5
|
+
*/
|
|
6
|
+
export declare function ShortcutsHelp(): import("react").JSX.Element;
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { type RefObject } from 'react';
|
|
2
|
+
/** Registers a view's filter field for `/` while the view is mounted. */
|
|
3
|
+
export declare function useFilterShortcut(ref: RefObject<HTMLInputElement | null>): void;
|
|
4
|
+
/** Focuses the registered filter field; false when the view has none, so the key is left alone. */
|
|
5
|
+
export declare function focusFilter(): boolean;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The printable key an event stands for, in the Latin layout the shortcuts are named in. On a
|
|
3
|
+
* non-Latin layout (Persian, Russian, Greek) `event.key` is that script's letter, so the physical
|
|
4
|
+
* key decides instead: an operator does not switch layout to press `g q`.
|
|
5
|
+
*/
|
|
6
|
+
export declare function latinKey(event: KeyboardEvent): string;
|
|
7
|
+
/**
|
|
8
|
+
* Whether a key press belongs to something else and must not be taken as a shortcut (ADR-0109):
|
|
9
|
+
* typing into a field or an editor, a modified key, an IME composition, a key already handled, and
|
|
10
|
+
* anything inside a dialog or a menu — a confirmation is never navigated away from by a stray key.
|
|
11
|
+
*/
|
|
12
|
+
export declare function ignoredForShortcuts(event: KeyboardEvent): boolean;
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export declare function singleKeyShortcutsEnabled(): boolean;
|
|
2
|
+
export declare function setSingleKeyShortcuts(next: boolean): void;
|
|
3
|
+
export declare function useSingleKeyShortcuts(): [boolean, (next: boolean) => void];
|
|
4
|
+
export declare function setShortcutsHelpOpen(next: boolean): void;
|
|
5
|
+
export declare function useShortcutsHelpOpen(): boolean;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { type NavContribution } from '../feature.ts';
|
|
2
|
+
/**
|
|
3
|
+
* The views that have a `g` letter: built-in views only — a plugin's letter is ignored, so a plugin
|
|
4
|
+
* can never take a letter an operator already relies on (ADR-0109).
|
|
5
|
+
*/
|
|
6
|
+
export declare function viewHotkeys(features: {
|
|
7
|
+
id: string;
|
|
8
|
+
nav?: NavContribution[];
|
|
9
|
+
}[]): Map<string, NavContribution>;
|
|
10
|
+
/**
|
|
11
|
+
* The single-key shortcuts (ADR-0109), as one document listener mounted by the shell:
|
|
12
|
+
* `g` then a view's letter goes to that view of the open cluster, `?` opens the list of shortcuts,
|
|
13
|
+
* and `/` focuses the view's filter when it has one. All of them are off when the operator turned
|
|
14
|
+
* single-key shortcuts off, and ignored while typing, with a modifier, and inside dialogs and menus.
|
|
15
|
+
*/
|
|
16
|
+
export declare function useKeySequences(): void;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { NavContribution, StudioFeature } from '../feature.ts';
|
|
2
|
+
export interface CurrentView {
|
|
3
|
+
clusterId: string;
|
|
4
|
+
/** The navigation entry the address is under, when there is one. */
|
|
5
|
+
item?: NavContribution;
|
|
6
|
+
/** Its group's label. */
|
|
7
|
+
groupLabel?: string;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Which cluster view an address is under (ADR-0109): the navigation entry whose path is the longest
|
|
11
|
+
* prefix of the path after `/clusters/<id>/`, so a queue's message browser is under Queues and a
|
|
12
|
+
* bulk run under Bulk runs. Null outside any cluster.
|
|
13
|
+
*/
|
|
14
|
+
export declare function matchView(pathname: string, features: StudioFeature[]): CurrentView | null;
|
|
15
|
+
export declare function useCurrentView(): CurrentView | null;
|
|
16
|
+
/**
|
|
17
|
+
* The address of the same view on another cluster (ADR-0109): an operator comparing two clusters
|
|
18
|
+
* stays on Queues. The view's search is not carried — its filter and its open resource name things
|
|
19
|
+
* that may not exist there. Without a current view, the cluster's landing page.
|
|
20
|
+
*/
|
|
21
|
+
export declare function sameViewOn(clusterId: string, view: CurrentView | null): string;
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Where the operator is inside a cluster (ADR-0109): cluster › group › view › open resource. The
|
|
3
|
+
* last crumb is the current page; the ones before it that are places are links back to them.
|
|
4
|
+
*/
|
|
5
|
+
export declare function Breadcrumb(): import("react").JSX.Element | null;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* One cluster's screen: the header its features contribute, then the routed view.
|
|
3
3
|
*
|
|
4
|
-
* It mounts the cluster's one SSE stream, subscribed to the topics of every enabled feature
|
|
4
|
+
* It hosts the dialogs row actions open (ADR-0107), and mounts the cluster's one SSE stream, subscribed to the topics of every enabled feature
|
|
5
5
|
* (ADR-0018, ADR-0070). A view never opens a second one for a topic a feature handles: a second
|
|
6
6
|
* `EventSource` is a second connection, and it fights over the shared stream-status store. The
|
|
7
7
|
* stream reconnects indefinitely and reports its state to the header's freshness indicator
|
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* ⌘K navigation across the console: jump to a view,
|
|
3
|
-
*
|
|
4
|
-
* {@link Spotlight}.
|
|
2
|
+
* ⌘K navigation across the console: jump to a view, a recent place, a cluster, or a queue by name, or
|
|
3
|
+
* search a live view (ADR-0109). Mounted once in the root layout; the shortcut is registered by
|
|
4
|
+
* {@link Spotlight}, and the header's Search button opens it too.
|
|
5
|
+
*
|
|
6
|
+
* The typed query reaches the features' sources, debounced, together with whether the palette is
|
|
7
|
+
* open, so a source searches only while someone is looking — and only what Studio already holds.
|
|
8
|
+
* A view the operator may not open is still listed, disabled, with the reason: the rail shows it the
|
|
9
|
+
* same way.
|
|
5
10
|
*
|
|
6
11
|
* Refresh and pause live here rather than on a hotkey: the browser owns both
|
|
7
12
|
* shortcuts an operator would reach for (⌘R and ⇧⌘R), and taking either would be
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The parts of "where the operator is" that the shell cannot derive from the address itself
|
|
3
|
+
* (ADR-0109): the cluster's name, which only the clusters feature knows, and the open resource,
|
|
4
|
+
* which only its view knows. The shell combines them with the view it matches from the address into
|
|
5
|
+
* the document title and the breadcrumb.
|
|
6
|
+
*/
|
|
7
|
+
export interface TitleParts {
|
|
8
|
+
cluster?: string;
|
|
9
|
+
resource?: string;
|
|
10
|
+
}
|
|
11
|
+
export declare function useTitleParts(): TitleParts;
|
|
12
|
+
/** Declares one part of the title while the calling component is mounted. */
|
|
13
|
+
export declare function useTitlePart(part: keyof TitleParts, value: string | null | undefined): void;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The views and resources an operator opened most recently on a cluster, for the palette's Recent
|
|
3
|
+
* group (ADR-0109). Browser-local and per viewer: a convenience that is fine to lose with site data,
|
|
4
|
+
* never state anything depends on.
|
|
5
|
+
*/
|
|
6
|
+
export interface Recent {
|
|
7
|
+
label: string;
|
|
8
|
+
/** Where it is: the pathname, and the search it was opened with. */
|
|
9
|
+
to: string;
|
|
10
|
+
search: Record<string, unknown>;
|
|
11
|
+
/** What kind of place it is, shown as the entry's description. */
|
|
12
|
+
kind: string;
|
|
13
|
+
}
|
|
14
|
+
export declare function readRecents(clusterId: string): Recent[];
|
|
15
|
+
/** Records a visit, most recent first, one entry per label. */
|
|
16
|
+
export declare function recordRecent(clusterId: string, recent: Recent): void;
|
package/types/kernel/slots.d.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import type { ComponentType } from 'react';
|
|
2
|
+
import type { ActionProps, ActionSection, AddressTarget, ClientTarget, ConnectionTarget, ConsumerTarget, DivertTarget, LinkProps, MessageTarget, ProducerTarget, QueueTarget, SessionTarget } from './actions/types.ts';
|
|
3
|
+
import type { MetricRange } from './time/ranges.ts';
|
|
2
4
|
/** Queues picked by name, or every queue matching the queues screen's filter (`q`, blank for all). */
|
|
3
5
|
export type QueueSelection = {
|
|
4
6
|
kind: 'names';
|
|
@@ -74,6 +76,12 @@ export interface SlotProps {
|
|
|
74
76
|
'metrics.panels': {
|
|
75
77
|
clusterId: string;
|
|
76
78
|
};
|
|
79
|
+
/** In the flow view's monitoring pane, when a queue is selected: its history over `range`, per node. */
|
|
80
|
+
'flow.selection.panels': {
|
|
81
|
+
clusterId: string;
|
|
82
|
+
queueName: string;
|
|
83
|
+
range: MetricRange;
|
|
84
|
+
};
|
|
77
85
|
/** Inside a box on the topology graph, after its name. `nodeIds` are the broker endpoints the box stands for. */
|
|
78
86
|
'topology.node.marks': {
|
|
79
87
|
clusterId: string;
|
|
@@ -91,6 +99,19 @@ export interface SlotProps {
|
|
|
91
99
|
'admin.tabs': object;
|
|
92
100
|
/** A section of the signed-in user's Account page, under the contribution's title. */
|
|
93
101
|
'account.sections': object;
|
|
102
|
+
'queue.actions': ActionProps<QueueTarget>;
|
|
103
|
+
'address.actions': ActionProps<AddressTarget>;
|
|
104
|
+
'connection.actions': ActionProps<ConnectionTarget>;
|
|
105
|
+
'session.actions': ActionProps<SessionTarget>;
|
|
106
|
+
'consumer.actions': ActionProps<ConsumerTarget>;
|
|
107
|
+
'producer.actions': ActionProps<ProducerTarget>;
|
|
108
|
+
'message.actions': ActionProps<MessageTarget>;
|
|
109
|
+
'divert.actions': ActionProps<DivertTarget>;
|
|
110
|
+
'client.actions': ActionProps<ClientTarget>;
|
|
111
|
+
'queue.link': LinkProps<QueueTarget>;
|
|
112
|
+
'address.link': LinkProps<AddressTarget>;
|
|
113
|
+
'connection.link': LinkProps<ConnectionTarget>;
|
|
114
|
+
'session.link': LinkProps<SessionTarget>;
|
|
94
115
|
}
|
|
95
116
|
export type SlotName = keyof SlotProps;
|
|
96
117
|
/**
|
|
@@ -121,6 +142,8 @@ export interface SlotContribution<P> {
|
|
|
121
142
|
title?: string;
|
|
122
143
|
/** `settings.sections` only: the heading its tab sits under. Without one it is listed under Plugins. */
|
|
123
144
|
group?: SettingsGroupId;
|
|
145
|
+
/** `*.actions` only: the menu section the item is listed under. */
|
|
146
|
+
section?: ActionSection;
|
|
124
147
|
Component: ComponentType<P>;
|
|
125
148
|
}
|
|
126
149
|
export type SlotContributions = {
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Preset relative metric windows, shared by every view that charts history (metrics, and the
|
|
3
|
+
* Flow pane that shows its panels). Kept out of `router.tsx` so importing it (a
|
|
4
|
+
* component like `RangePicker` needs the runtime array, not just the type)
|
|
5
|
+
* never has to evaluate the whole route tree — that module calls
|
|
6
|
+
* `createRootRoute`/`createRoute` at import time, which only a real router
|
|
7
|
+
* context (or a full mock of it) can satisfy.
|
|
8
|
+
*/
|
|
9
|
+
export declare const METRIC_RANGES: readonly ["15m", "1h", "6h", "24h", "7d"];
|
|
10
|
+
export type MetricRange = (typeof METRIC_RANGES)[number];
|
|
11
|
+
/**
|
|
12
|
+
* Each range's width and the bucket it asks the server for, in one place.
|
|
13
|
+
*
|
|
14
|
+
* The two travel together on purpose: the window advances in whole buckets
|
|
15
|
+
* (ADR-0055), the axis chooses its tick format from the range, and the poll
|
|
16
|
+
* cadence is derived from both. Splitting them across three modules is how they
|
|
17
|
+
* drift.
|
|
18
|
+
*
|
|
19
|
+
* `step` is a hint. The server clamps it to the fastest sampling tier and widens
|
|
20
|
+
* it to keep a response under its point cap, and reports what it actually used;
|
|
21
|
+
* nothing here assumes the hint was honoured.
|
|
22
|
+
*/
|
|
23
|
+
export interface RangeSpec {
|
|
24
|
+
/** Window width. */
|
|
25
|
+
windowMs: number;
|
|
26
|
+
/** Requested bucket width, as the ISO-8601 duration the endpoint takes. */
|
|
27
|
+
step: string;
|
|
28
|
+
/** The same bucket width in ms — what the window quantizes to. */
|
|
29
|
+
stepMs: number;
|
|
30
|
+
}
|
|
31
|
+
export declare const RANGE_SPEC: Record<MetricRange, RangeSpec>;
|
|
32
|
+
export declare function rangeSpec(range: MetricRange): RangeSpec;
|
package/types/sdk/index.d.ts
CHANGED
|
@@ -13,6 +13,10 @@ export { NAV_GROUPS, type NavGroupId } from '../kernel/nav/groups.ts';
|
|
|
13
13
|
export { clusterRoute, rootRoute } from '../kernel/routing/roots.ts';
|
|
14
14
|
export { ApiError, clusterKey, request } from '../kernel/api/request.ts';
|
|
15
15
|
export { useCan } from '../kernel/auth/useCan.ts';
|
|
16
|
+
export { ACTION_SECTIONS, type ActionHost, type ActionMode, type ActionProps, type ActionSection, type ActionTargets, type AddressTarget, type ConnectionTarget, type ConsumerTarget, type DivertTarget, type HostedDialogProps, type MessageTarget, type ProducerTarget, type QueueTarget, type SessionTarget, } from '../kernel/actions/types.ts';
|
|
17
|
+
export { ActionMenuItem } from '../ui/ActionMenuItem.tsx';
|
|
18
|
+
export { CapabilityGate } from '../ui/CapabilityGate.tsx';
|
|
19
|
+
export { gateFor, type GateVerdict } from '../ui/capabilityGate.ts';
|
|
16
20
|
export { useMe } from '../kernel/auth/api.ts';
|
|
17
21
|
export { ConfirmByTyping } from '../ui/ConfirmByTyping.tsx';
|
|
18
22
|
export { NodeOutcomeSummary, OutcomeSummary, type OutcomeRow } from '../ui/NodeOutcomeSummary.tsx';
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { type ReactNode } from 'react';
|
|
2
|
+
import type { GateVerdict } from './capabilityGate.ts';
|
|
3
|
+
type Blocked = Extract<GateVerdict, {
|
|
4
|
+
kind: 'blocked';
|
|
5
|
+
}>;
|
|
6
|
+
/**
|
|
7
|
+
* One item of a row's action menu (ADR-0107).
|
|
8
|
+
*
|
|
9
|
+
* <p>An item the operator may not use is never removed and never `disabled`: Mantine skips disabled
|
|
10
|
+
* items in keyboard navigation, which would leave a keyboard user no way to learn why. It is
|
|
11
|
+
* `aria-disabled`, states the first sentence of its reason, and activating it hands the full reason
|
|
12
|
+
* (with its `broker.xml`) to `onExplain`.
|
|
13
|
+
*
|
|
14
|
+
* <p>A navigation item takes `href`, so it can be opened in a new tab; a plain activation still goes
|
|
15
|
+
* through `onSelect`, which navigates inside the app.
|
|
16
|
+
*/
|
|
17
|
+
export declare function ActionMenuItem({ label, icon, verdict, tone, href, onSelect, onExplain, }: {
|
|
18
|
+
label: string;
|
|
19
|
+
icon?: ReactNode;
|
|
20
|
+
verdict?: GateVerdict;
|
|
21
|
+
/** `danger` for an action that destroys something; the word still carries it. */
|
|
22
|
+
tone?: 'danger';
|
|
23
|
+
href?: string;
|
|
24
|
+
onSelect: () => void;
|
|
25
|
+
onExplain?: (verdict: Blocked) => void;
|
|
26
|
+
}): import("react").JSX.Element;
|
|
27
|
+
export {};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { type ReactNode } from 'react';
|
|
2
|
+
import type { MenuAnchor } from './menuAnchor.ts';
|
|
3
|
+
/**
|
|
4
|
+
* One controlled menu opened at a point rather than from a trigger it wraps (ADR-0107). A grid
|
|
5
|
+
* renders one of these for all of its rows: the items of a closed menu are not mounted, so a page
|
|
6
|
+
* of rows costs one menu, not one per row.
|
|
7
|
+
*
|
|
8
|
+
* <p>The anchor is a clipped point element, portalled so that no transformed ancestor (virtualized
|
|
9
|
+
* rows are placed by `transform`) can shift it. It carries `label`, which is what names the menu.
|
|
10
|
+
* Focus is not returned by the menu itself: only the caller knows what opened it, and whether a
|
|
11
|
+
* dialog has taken over.
|
|
12
|
+
*/
|
|
13
|
+
export declare function AnchoredMenu({ opened, anchor, label, onClose, children, }: {
|
|
14
|
+
opened: boolean;
|
|
15
|
+
anchor: MenuAnchor | null;
|
|
16
|
+
/** The menu's accessible name, e.g. "Actions for orders". */
|
|
17
|
+
label: string;
|
|
18
|
+
onClose: () => void;
|
|
19
|
+
children: ReactNode;
|
|
20
|
+
}): import("react").JSX.Element | null;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
import type { GateVerdict } from './capabilityGate.ts';
|
|
3
|
+
/**
|
|
4
|
+
* Wraps a control that may be unavailable, keeping it visible and explaining
|
|
5
|
+
* itself in place (non-negotiable #5). A silently missing button teaches the
|
|
6
|
+
* operator that the product cannot do something, when the truth is that this
|
|
7
|
+
* connection is not configured for it.
|
|
8
|
+
*
|
|
9
|
+
* <p>The explanation is a popover on a real focusable button, not a `title` or a
|
|
10
|
+
* hover tooltip, because a disabled control takes no focus and a keyboard user
|
|
11
|
+
* would otherwise have no way to reach the reason at all.
|
|
12
|
+
*
|
|
13
|
+
* <p>`what` names the control being explained. A screen with several gated
|
|
14
|
+
* controls otherwise announces the same "Why this is unavailable" for each of
|
|
15
|
+
* them, which tells a screen-reader user nothing about which one it belongs to.
|
|
16
|
+
*/
|
|
17
|
+
export declare function CapabilityGate({ verdict, what, children, }: {
|
|
18
|
+
verdict: GateVerdict;
|
|
19
|
+
/** "applying address setting orders.#"; defaults to "this". */
|
|
20
|
+
what?: string;
|
|
21
|
+
children: ReactNode;
|
|
22
|
+
}): import("react").JSX.Element;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Why a control is unavailable, in full: the reason, and the `broker.xml` that enables it where one
|
|
3
|
+
* exists (non-negotiable #5). The one rendering of it, shared by the {@link CapabilityGate} popover
|
|
4
|
+
* and the explanation a blocked menu item opens, so both say the same thing the same way.
|
|
5
|
+
*/
|
|
6
|
+
export declare function CapabilityReason({ reason, snippet, size, }: {
|
|
7
|
+
reason: string;
|
|
8
|
+
snippet?: string | null;
|
|
9
|
+
/** `xs` in a popover; `sm` where the explanation is the whole dialog. */
|
|
10
|
+
size?: 'xs' | 'sm';
|
|
11
|
+
}): import("react").JSX.Element;
|
|
@@ -14,11 +14,29 @@ export interface GridColumn<T> {
|
|
|
14
14
|
*/
|
|
15
15
|
width?: number;
|
|
16
16
|
}
|
|
17
|
+
/** What a row menu's items get: a way to close the menu, and to put focus back on the row. */
|
|
18
|
+
export interface RowMenuContext {
|
|
19
|
+
close: () => void;
|
|
20
|
+
/**
|
|
21
|
+
* Puts focus back on the row's Actions control, scrolling it into view if it has left; on the
|
|
22
|
+
* grid when the row is gone. For a dialog opened from the menu to call when it closes.
|
|
23
|
+
*/
|
|
24
|
+
restoreFocus: () => void;
|
|
25
|
+
}
|
|
26
|
+
/** A per-row action menu (ADR-0107): its items are rendered only while it is open. */
|
|
27
|
+
export interface RowMenu<T> {
|
|
28
|
+
/** Names the row in "Actions for <label>". */
|
|
29
|
+
label: (row: T) => string;
|
|
30
|
+
render: (row: T, context: RowMenuContext) => React.ReactNode;
|
|
31
|
+
}
|
|
17
32
|
interface VirtualTableProps<T> {
|
|
18
33
|
columns: GridColumn<T>[];
|
|
19
34
|
data: T[];
|
|
35
|
+
/** What the grid lists, as its accessible name: "Queues", "Connections". */
|
|
36
|
+
label?: string;
|
|
20
37
|
sort?: string;
|
|
21
38
|
onSortChange?: (sort: string | undefined) => void;
|
|
39
|
+
/** Activating a row, by click or by Enter on any of its cells. */
|
|
22
40
|
onRowClick?: (row: T) => void;
|
|
23
41
|
rowKey: (row: T) => string;
|
|
24
42
|
emptyLabel?: React.ReactNode;
|
|
@@ -36,6 +54,10 @@ interface VirtualTableProps<T> {
|
|
|
36
54
|
* the caller needs to know in order to hold new rows back.
|
|
37
55
|
*/
|
|
38
56
|
onAtTopChange?: (atTop: boolean) => void;
|
|
57
|
+
/** A per-row action menu, opened by right-click, by the row's Actions control, or by Shift+F10. */
|
|
58
|
+
rowMenu?: RowMenu<T>;
|
|
59
|
+
/** Sized to its rows, up to a short cap, instead of to the viewport: a handful of rows in a pane. */
|
|
60
|
+
compact?: boolean;
|
|
39
61
|
}
|
|
40
62
|
/**
|
|
41
63
|
* A virtualized data grid: one CSS grid track list, declared once and shared by
|
|
@@ -51,6 +73,13 @@ interface VirtualTableProps<T> {
|
|
|
51
73
|
* <p>Sorting is a URL round-trip, not local state: the header carries
|
|
52
74
|
* `aria-sort` from the current `sort` param and clicking it navigates. Row
|
|
53
75
|
* selection is opt-in (`selectable`) and its state lives with the caller.
|
|
76
|
+
*
|
|
77
|
+
* <p>The keyboard model is the WAI-ARIA grid (ADR-0108): the grid is one tab
|
|
78
|
+
* stop, the arrow keys move a roving focus between cells (the header row
|
|
79
|
+
* included), Enter activates a row, Space selects it, Shift+F10 opens its menu,
|
|
80
|
+
* and Ctrl/Cmd+C copies a focused cell. The focused cell is remembered by row
|
|
81
|
+
* key, so a refresh or a re-sort does not move it to another row, and its row is
|
|
82
|
+
* always rendered however far it is scrolled.
|
|
54
83
|
*/
|
|
55
|
-
export declare function VirtualTable<T>({ columns, data, sort, onSortChange, onRowClick, rowKey, emptyLabel, rowClassName, selectable, selected, onToggleRow, onToggleAll, onAtTopChange, }: VirtualTableProps<T>): import("react").JSX.Element;
|
|
84
|
+
export declare function VirtualTable<T>({ columns, data, label, sort, onSortChange, onRowClick, rowKey, emptyLabel, rowClassName, selectable, selected, onToggleRow, onToggleAll, onAtTopChange, rowMenu, compact, }: VirtualTableProps<T>): import("react").JSX.Element;
|
|
56
85
|
export {};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { components } from '../kernel/api/schema.d.ts';
|
|
2
|
+
type CapabilityView = components['schemas']['CapabilityView'];
|
|
3
|
+
export type GateVerdict = {
|
|
4
|
+
kind: 'allowed';
|
|
5
|
+
uncertain: boolean;
|
|
6
|
+
} | {
|
|
7
|
+
kind: 'blocked';
|
|
8
|
+
reason: string;
|
|
9
|
+
snippet?: string | null;
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* Whether a lifecycle control may act, and why not when it may not.
|
|
13
|
+
*
|
|
14
|
+
* <p>Two independent gates, in the order the operator can do something about:
|
|
15
|
+
*
|
|
16
|
+
* <ul>
|
|
17
|
+
* <li><b>Permission</b> — the caller's own grants. The server is the
|
|
18
|
+
* enforcement point; this only explains, and never hides.
|
|
19
|
+
* <li><b>Capability</b> — whether the broker connection can write at all. Only
|
|
20
|
+
* a known refusal blocks. Since ADR-0049 D5 an unproven capability is
|
|
21
|
+
* reported as unknown, and blocking on the absence of evidence would stop
|
|
22
|
+
* an operator using a broker that works perfectly well.
|
|
23
|
+
* </ul>
|
|
24
|
+
*/
|
|
25
|
+
export declare function gateFor(permitted: boolean, permissionLabel: string, capability: CapabilityView | undefined,
|
|
26
|
+
/** True while the caller's grants are still being fetched. */
|
|
27
|
+
loading?: boolean): GateVerdict;
|
|
28
|
+
export {};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/** A viewport point the menu opens from: the pointer, or just under the control that opened it. */
|
|
2
|
+
export interface MenuAnchor {
|
|
3
|
+
x: number;
|
|
4
|
+
y: number;
|
|
5
|
+
}
|
|
6
|
+
/** The anchor just under an element, at its inline start, for a menu opened from the keyboard. */
|
|
7
|
+
export declare function anchorBelow(element: Element): MenuAnchor;
|
|
8
|
+
/**
|
|
9
|
+
* Keeps an anchor inside the viewport. A point outside it counts as a hidden reference, and the
|
|
10
|
+
* positioning engine then hides the menu outright rather than showing it at the edge.
|
|
11
|
+
*/
|
|
12
|
+
export declare function clampToViewport(anchor: MenuAnchor): MenuAnchor;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The keyboard model of a data grid (ADR-0108): one tab stop, and the WAI-ARIA APG grid keys to
|
|
3
|
+
* move between cells. Pure, so the moves are tested apart from any rendering.
|
|
4
|
+
*
|
|
5
|
+
* <p>Row 0 is the header row; body rows are 1…`rows`. Columns are 0…`cols - 1` in visual order.
|
|
6
|
+
*/
|
|
7
|
+
export interface GridPos {
|
|
8
|
+
row: number;
|
|
9
|
+
col: number;
|
|
10
|
+
}
|
|
11
|
+
export interface GridShape {
|
|
12
|
+
/** Body rows; the header row is added on top as row 0. */
|
|
13
|
+
rows: number;
|
|
14
|
+
cols: number;
|
|
15
|
+
/** Rows a Page Up / Page Down moves by. */
|
|
16
|
+
page: number;
|
|
17
|
+
/** In a right-to-left page, ArrowLeft moves toward the end of the row. */
|
|
18
|
+
rtl?: boolean;
|
|
19
|
+
}
|
|
20
|
+
export interface GridKey {
|
|
21
|
+
key: string;
|
|
22
|
+
ctrlKey?: boolean;
|
|
23
|
+
metaKey?: boolean;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Where a key moves focus from `pos`, or null when the key is not a movement key. A move past an
|
|
27
|
+
* edge stays at the edge rather than wrapping: a grid is not a ring, and wrapping from the last
|
|
28
|
+
* row to the header would read as the view having jumped.
|
|
29
|
+
*/
|
|
30
|
+
export declare function nextCell(pos: GridPos, input: GridKey, shape: GridShape): GridPos | null;
|
|
31
|
+
/**
|
|
32
|
+
* The row index a remembered row key resolves to after the data changed: its new index when it is
|
|
33
|
+
* still there, otherwise the nearest surviving neighbour of where it was, so that removing the
|
|
34
|
+
* focused row hands focus to the row that took its place.
|
|
35
|
+
*/
|
|
36
|
+
export declare function resolveRow(keys: readonly string[], key: string | null, lastIndex: number): number;
|