@gaia-ai/addon-gaia-ui 0.9.2 → 0.11.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/dist/src/Component/footer-actions/index.d.ts +58 -0
- package/dist/src/Component/footer-actions/index.js +111 -0
- package/dist/src/Component/list-chrome/index.d.ts +19 -0
- package/dist/src/Component/list-chrome/index.js +61 -0
- package/dist/src/Component/markdown/index.d.ts +68 -0
- package/dist/src/Component/markdown/index.js +133 -0
- package/dist/src/Component/section-list/index.d.ts +115 -23
- package/dist/src/Component/section-list/index.js +120 -34
- package/dist/src/Component/top-tabs/index.d.ts +110 -7
- package/dist/src/Component/top-tabs/index.js +88 -9
- package/dist/src/Entity/collection.js +1 -1
- package/dist/src/Entity/conductor/Data/conductors.d.ts +4 -3
- package/dist/src/Entity/conductor/Data/conductors.js +76 -10
- package/dist/src/Entity/conductor/Data/own-conductor.js +7 -1
- package/dist/src/Entity/conductor/Screen/conductors.d.ts +26 -41
- package/dist/src/Entity/conductor/Screen/conductors.js +65 -123
- package/dist/src/Entity/project/Screen/project-picker.d.ts +20 -31
- package/dist/src/Entity/project/Screen/project-picker.js +48 -58
- package/dist/src/Entity/project/Screen/projects-screen.d.ts +4 -2
- package/dist/src/Entity/project/Screen/projects-screen.js +11 -8
- package/dist/src/Entity/run/Component/run-detail/index.js +15 -7
- package/dist/src/Entity/run/Data/query.d.ts +10 -0
- package/dist/src/Entity/run/Data/query.js +16 -1
- package/dist/src/Entity/run/Data/runs.d.ts +2 -0
- package/dist/src/Entity/run/Data/runs.js +19 -1
- package/dist/src/Entity/run/Screen/run-detail.d.ts +16 -23
- package/dist/src/Entity/run/Screen/run-detail.js +27 -34
- package/dist/src/Entity/run/Screen/runs.d.ts +7 -3
- package/dist/src/Entity/run/Screen/runs.js +31 -48
- package/dist/src/Entity/run/Screen/user-runs.d.ts +79 -0
- package/dist/src/Entity/run/Screen/user-runs.js +98 -0
- package/dist/src/Entity/statistics/Data/statistics.d.ts +111 -9
- package/dist/src/Entity/statistics/Data/statistics.js +121 -9
- package/dist/src/Entity/statistics/Screen/dashboard.d.ts +10 -2
- package/dist/src/Entity/statistics/Screen/dashboard.js +127 -36
- package/dist/src/Entity/team/Data/members.d.ts +30 -0
- package/dist/src/Entity/team/Data/members.js +83 -0
- package/dist/src/Entity/team/Data/team.d.ts +22 -0
- package/dist/src/Entity/team/Data/team.js +38 -0
- package/dist/src/Entity/team/Data/teams.d.ts +223 -0
- package/dist/src/Entity/team/Data/teams.js +267 -0
- package/dist/src/Entity/team/Screen/team-users.d.ts +76 -0
- package/dist/src/Entity/team/Screen/team-users.js +215 -0
- package/dist/src/Entity/team/Screen/teams-screen.d.ts +116 -0
- package/dist/src/Entity/team/Screen/teams-screen.js +349 -0
- package/dist/src/Entity/ticket/Component/comment-item/index.d.ts +14 -1
- package/dist/src/Entity/ticket/Component/comment-item/index.js +15 -1
- package/dist/src/Entity/ticket/Component/edit-form/index.d.ts +8 -3
- package/dist/src/Entity/ticket/Component/edit-form/index.js +4 -6
- package/dist/src/Entity/ticket/Component/sections/index.js +16 -1
- package/dist/src/Entity/ticket/Component/ticket-detail/index.d.ts +102 -13
- package/dist/src/Entity/ticket/Component/ticket-detail/index.js +440 -72
- package/dist/src/Entity/ticket/Data/detail.d.ts +19 -3
- package/dist/src/Entity/ticket/Data/detail.js +20 -4
- package/dist/src/Entity/ticket/Data/query.d.ts +7 -0
- package/dist/src/Entity/ticket/Data/query.js +18 -2
- package/dist/src/Entity/ticket/Data/tickets.d.ts +24 -0
- package/dist/src/Entity/ticket/Data/tickets.js +41 -1
- package/dist/src/Entity/ticket/Screen/create-ticket-form.d.ts +10 -4
- package/dist/src/Entity/ticket/Screen/create-ticket-form.js +37 -16
- package/dist/src/Entity/ticket/Screen/edit-assignment-form.d.ts +6 -4
- package/dist/src/Entity/ticket/Screen/edit-assignment-form.js +16 -16
- package/dist/src/Entity/ticket/Screen/parent-filter-form.d.ts +6 -4
- package/dist/src/Entity/ticket/Screen/parent-filter-form.js +14 -11
- package/dist/src/Entity/ticket/Screen/ticket-agents.d.ts +5 -2
- package/dist/src/Entity/ticket/Screen/ticket-agents.js +14 -14
- package/dist/src/Entity/ticket/Screen/ticket-detail.d.ts +89 -4
- package/dist/src/Entity/ticket/Screen/ticket-detail.js +295 -74
- package/dist/src/Entity/ticket/Screen/tickets.d.ts +66 -3
- package/dist/src/Entity/ticket/Screen/tickets.js +140 -76
- package/dist/src/Entity/ticket/Screen/user-tickets.d.ts +91 -0
- package/dist/src/Entity/ticket/Screen/user-tickets.js +94 -0
- package/dist/src/Entity/ticket/Screen/who-filter-form.d.ts +6 -4
- package/dist/src/Entity/ticket/Screen/who-filter-form.js +14 -11
- package/dist/src/Entity/transport.d.ts +14 -4
- package/dist/src/Entity/transport.js +2 -1
- package/dist/src/Entity/user/Data/users.d.ts +87 -0
- package/dist/src/Entity/user/Data/users.js +60 -0
- package/dist/src/Entity/user/Screen/user-overview.d.ts +33 -0
- package/dist/src/Entity/user/Screen/user-overview.js +159 -0
- package/dist/src/Entity/user/Screen/users.d.ts +39 -0
- package/dist/src/Entity/user/Screen/users.js +136 -0
- package/dist/src/Extension/contract.d.ts +58 -0
- package/dist/src/Extension/contract.js +1 -0
- package/dist/src/Extension/load.d.ts +10 -0
- package/dist/src/Extension/load.js +21 -0
- package/dist/src/Extension/store.d.ts +11 -0
- package/dist/src/Extension/store.js +44 -0
- package/dist/src/Kernel/chrome.d.ts +49 -12
- package/dist/src/Kernel/chrome.js +150 -57
- package/dist/src/Kernel/key-input.js +1 -1
- package/dist/src/Kernel/mount.d.ts +1 -1
- package/dist/src/Kernel/mount.js +1 -1
- package/dist/src/Kernel/regions.d.ts +64 -0
- package/dist/src/Kernel/regions.js +328 -0
- package/dist/src/Kernel/renderer.d.ts +25 -0
- package/dist/src/Kernel/renderer.js +142 -0
- package/dist/src/Kernel/splash.d.ts +27 -0
- package/dist/src/Kernel/splash.js +72 -0
- package/dist/src/Kernel/tui-kernel.js +139 -435
- package/dist/src/Routing/context.d.ts +5 -0
- package/dist/src/Routing/context.js +6 -0
- package/dist/src/Routing/registry.js +21 -2
- package/dist/src/Routing/router.js +5 -1
- package/dist/src/Routing/routes.js +19 -0
- package/dist/src/Screen/async-modal-screen.d.ts +26 -0
- package/dist/src/Screen/async-modal-screen.js +44 -0
- package/dist/src/Screen/async-screen.d.ts +32 -40
- package/dist/src/Screen/async-screen.js +45 -41
- package/dist/src/Screen/full-screen.d.ts +1 -0
- package/dist/src/Screen/full-screen.js +3 -0
- package/dist/src/Screen/list-screen.d.ts +7 -1
- package/dist/src/Screen/list-screen.js +17 -6
- package/dist/src/Screen/modal-screen.d.ts +6 -0
- package/dist/src/Screen/modal-screen.js +2 -0
- package/dist/src/Screen/screen.d.ts +42 -6
- package/dist/src/Screen/screen.js +2 -2
- package/dist/src/Widget/textarea.js +10 -14
- package/dist/src/Widget/types.d.ts +1 -1
- package/dist/src/launcher.d.ts +24 -26
- package/dist/src/launcher.js +11 -11
- package/dist/src/lib/format.js +1 -1
- package/dist/src/lib/markdown.d.ts +53 -0
- package/dist/src/lib/markdown.js +522 -0
- package/dist/src/lib/refresh-signal.d.ts +56 -0
- package/dist/src/lib/refresh-signal.js +85 -0
- package/dist/src/lib/theme.d.ts +36 -0
- package/dist/src/lib/theme.js +40 -0
- package/dist/src/lib/version.d.ts +5 -0
- package/dist/src/lib/version.js +46 -0
- package/dist/src/lib/vnode.d.ts +86 -63
- package/dist/src/lib/vnode.js +112 -85
- package/dist/src/types.d.ts +47 -12
- package/dist/src/ui.d.ts +2 -0
- package/dist/src/ui.js +1 -0
- package/package.json +4 -3
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
import { cellsRow, textNode } from '../../lib/vnode.js';
|
|
1
|
+
import { cellsRow, textNode, } from '../../lib/vnode.js';
|
|
2
|
+
import { plainLine } from '../markdown/index.js';
|
|
3
|
+
/** Whether a line is columns rather than prose — `cells` is the discriminant. */
|
|
4
|
+
const isCells = (line) => typeof line !== 'string' && 'cells' in line;
|
|
2
5
|
/**
|
|
3
6
|
* A line as the text a surface with no layout prints.
|
|
4
7
|
*
|
|
@@ -6,29 +9,58 @@ import { cellsRow, textNode } from '../../lib/vnode.js';
|
|
|
6
9
|
* puts between two cells: the pipe and the tree then say the same thing about
|
|
7
10
|
* what separates two columns, and only the alignment inside a column is the
|
|
8
11
|
* renderer's own.
|
|
12
|
+
*
|
|
13
|
+
* GAIA-372 D4 — for a {@link StyledLine} this returns the RENDERED text, which is the
|
|
14
|
+
* whole of how parity is achieved: `SectionRow.text` carries it and the pipe
|
|
15
|
+
* prints exactly that, so `gaia ui <id> | cat` and the interactive tree are the
|
|
16
|
+
* same rendering by construction. The tree merely adds the attributes a pipe
|
|
17
|
+
* cannot carry. There is no raw-only pipe path left to disagree (AC-3).
|
|
9
18
|
*/
|
|
10
|
-
export const lineText = (line) =>
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
.
|
|
15
|
-
|
|
19
|
+
export const lineText = (line) => {
|
|
20
|
+
if (typeof line === 'string')
|
|
21
|
+
return line;
|
|
22
|
+
if (isCells(line)) {
|
|
23
|
+
return line.cells
|
|
24
|
+
.map((cell) => cell.text)
|
|
25
|
+
.join(' ')
|
|
26
|
+
.replace(/\s+$/, '');
|
|
27
|
+
}
|
|
28
|
+
return line.text;
|
|
29
|
+
};
|
|
16
30
|
/**
|
|
17
|
-
*
|
|
18
|
-
* the markers stripped — the same rule a section heading follows. Bullets and
|
|
19
|
-
* code fences stay raw by design (explicit v1 decision).
|
|
31
|
+
* Any body line as a RENDERED line — GAIA-372 D3/D8.
|
|
20
32
|
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
* make the same body look different depending on which renderer reached it.
|
|
33
|
+
* A plain or columned row is LIFTED (one unparsed span) rather than refused,
|
|
34
|
+
* because a consumer that needs rendered lines — the collapsible Description
|
|
35
|
+
* entry, whose extract reads `line.heading` — is handed a `Description` section
|
|
36
|
+
* whose empty state is the plain string `No description.` Lifting never parses,
|
|
37
|
+
* so nothing here can re-introduce a Markdown rule on the plain path.
|
|
27
38
|
*/
|
|
28
|
-
export
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
39
|
+
export const styledOf = (line) => typeof line === 'string' || isCells(line) ? plainLine(lineText(line)) : line;
|
|
40
|
+
/**
|
|
41
|
+
* One body line as the row it is — the ONE place D3's three variants are read
|
|
42
|
+
* apart, so a flat section and a collapsible entry cannot answer differently for
|
|
43
|
+
* the same line.
|
|
44
|
+
*/
|
|
45
|
+
const bodyRow = (key, line) => {
|
|
46
|
+
// Columns are never read as Markdown: a run number opens with `#`, and the ATX
|
|
47
|
+
// rule would both bold the row and eat the number.
|
|
48
|
+
if (isCells(line)) {
|
|
49
|
+
return { key, text: lineText(line), bold: false, cells: line.cells };
|
|
50
|
+
}
|
|
51
|
+
// A plain string is PLAIN. Nothing parses it (D3) — which is what puts
|
|
52
|
+
// `Assignment`, `Branch`, `Links` and `Labels` structurally out of reach.
|
|
53
|
+
if (typeof line === 'string')
|
|
54
|
+
return { key, text: line, bold: false };
|
|
55
|
+
return {
|
|
56
|
+
key,
|
|
57
|
+
text: line.text,
|
|
58
|
+
// An ATX heading inside a body is the ROW's weight, exactly as it was; what
|
|
59
|
+
// decides it is now the rendered line's own flag rather than a re-match.
|
|
60
|
+
bold: line.heading,
|
|
61
|
+
runs: line.runs,
|
|
62
|
+
};
|
|
63
|
+
};
|
|
32
64
|
/**
|
|
33
65
|
* A heading as an id fragment: `PER USER` → `per-user`.
|
|
34
66
|
*
|
|
@@ -41,6 +73,57 @@ export const sectionKey = (heading) => heading
|
|
|
41
73
|
.toLowerCase()
|
|
42
74
|
.replace(/[^a-z0-9]+/g, '-')
|
|
43
75
|
.replace(/^-|-$/g, '');
|
|
76
|
+
/**
|
|
77
|
+
* The same, for a heading that is DATA rather than one of the built-in set —
|
|
78
|
+
* GAIA-373 AC-10.
|
|
79
|
+
*
|
|
80
|
+
* {@link sectionKey} is LOSSY: it lowercases and collapses every run of
|
|
81
|
+
* non-alphanumeric characters onto one `-`. That is injective over the closed
|
|
82
|
+
* built-in headings (`Per user`, `Latest summary`), and it stops being injective
|
|
83
|
+
* the moment a contributed panel heads its sections with whatever it is
|
|
84
|
+
* listing. The shipped artifacts viewer heads them with paths under the ticket's
|
|
85
|
+
* tree, so `/`, `.` and `-` all land on the same character and two ordinary
|
|
86
|
+
* captures collapse onto one key:
|
|
87
|
+
*
|
|
88
|
+
* playwright/logs/walk.txt -> playwright-logs-walk-txt
|
|
89
|
+
* playwright/logs-walk.txt -> playwright-logs-walk-txt
|
|
90
|
+
*
|
|
91
|
+
* A key is an IDENTITY — `Kernel/mount.ts` refuses a repeated id, and it refuses
|
|
92
|
+
* it while MOUNTING, past the host's guard around `panel.sections()` and on a
|
|
93
|
+
* draw path with no `catch` — so a lossy key is not a cosmetic collision, it is
|
|
94
|
+
* the cockpit going down on a message naming neither the addon nor the file.
|
|
95
|
+
*
|
|
96
|
+
* SO THE SEPARATORS ARE ENCODED RATHER THAN COLLAPSED. Every UTF-8 byte outside
|
|
97
|
+
* `[a-z0-9]` becomes `_<hex>`, two digits, lower case. The escape has a fixed
|
|
98
|
+
* length and `_` is itself escaped (`_5f`), so the mapping is reversible and
|
|
99
|
+
* therefore injective — including over case (`README.txt` and `readme.txt` are
|
|
100
|
+
* two files on this filesystem) and over a heading that already looks like an
|
|
101
|
+
* escape.
|
|
102
|
+
*
|
|
103
|
+
* WHY THE OUTPUT CARRIES NO `-`, which is a second property and not a side
|
|
104
|
+
* effect. A key never becomes an id on its own: it is always suffixed
|
|
105
|
+
* (`-heading`, `-gap`, `-empty`, `-header`, `-<index>`). Injectivity of the key
|
|
106
|
+
* alone would not settle the id, because the suffix boundary would be
|
|
107
|
+
* ambiguous — with `-` allowed inside a key, section `a` at body index 12 and
|
|
108
|
+
* section `a-1` at body index 2 would both be `a-12`. With no `-` inside a key
|
|
109
|
+
* the first `-` after the screen's own prefix always ends the key, so a key and
|
|
110
|
+
* its suffix are both recovered from an id and two ids can only be equal when
|
|
111
|
+
* both halves are. Within one section the suffixes are distinct by construction:
|
|
112
|
+
* the indices are digits and the four literals are not.
|
|
113
|
+
*
|
|
114
|
+
* NOT THE BUILT-IN PATH. `sectionRows` and the ticket detail's tabs keep
|
|
115
|
+
* {@link sectionKey} as their default, so no built-in node id moves (AC-2) — a
|
|
116
|
+
* `_` in a key is exactly the mark of a heading that came from a contribution.
|
|
117
|
+
*
|
|
118
|
+
* WHAT THIS STILL DOES NOT FIX, said plainly: two panel sections carrying the
|
|
119
|
+
* SAME heading. No injective map over headings can separate two identical
|
|
120
|
+
* headings, so that case remains the loud refusal it is today. It is out of
|
|
121
|
+
* scope for GAIA-373 and belongs to whatever gives a panel section an identity
|
|
122
|
+
* distinct from its title.
|
|
123
|
+
*/
|
|
124
|
+
export const panelSectionKey = (heading) => Array.from(new TextEncoder().encode(heading), (byte) => (byte >= 0x30 && byte <= 0x39) || (byte >= 0x61 && byte <= 0x7a)
|
|
125
|
+
? String.fromCharCode(byte)
|
|
126
|
+
: `_${byte.toString(16).padStart(2, '0')}`).join('');
|
|
44
127
|
/**
|
|
45
128
|
* Flatten `{heading, lines}` sections into KEYED rows: the heading, its body,
|
|
46
129
|
* and the blank line that closes the section.
|
|
@@ -51,21 +134,18 @@ export const sectionKey = (heading) => heading
|
|
|
51
134
|
* property that lets the Dashboard hand over renderables while its pipe keeps
|
|
52
135
|
* emitting the bytes it always did (spec F5).
|
|
53
136
|
*/
|
|
54
|
-
export function sectionRows(sections
|
|
137
|
+
export function sectionRows(sections,
|
|
138
|
+
// GAIA-373 — HOW a heading becomes a key, as an argument. The built-in
|
|
139
|
+
// derivation stays the default so every existing caller keys exactly as it
|
|
140
|
+
// did (AC-2); a caller whose headings are data passes
|
|
141
|
+
// {@link panelSectionKey}, which is injective over them.
|
|
142
|
+
keyOf = sectionKey) {
|
|
55
143
|
const out = [];
|
|
56
144
|
for (const section of sections) {
|
|
57
|
-
const id =
|
|
145
|
+
const id = keyOf(section.heading);
|
|
58
146
|
out.push({ key: `${id}-heading`, text: section.heading, bold: true });
|
|
59
147
|
section.lines.forEach((line, index) => {
|
|
60
|
-
|
|
61
|
-
// Columns are never read as markdown: a run number opens with `#`, and the
|
|
62
|
-
// ATX rule would both bold the row and eat the number.
|
|
63
|
-
if (typeof line !== 'string') {
|
|
64
|
-
out.push({ key, text: lineText(line), bold: false, cells: line.cells });
|
|
65
|
-
return;
|
|
66
|
-
}
|
|
67
|
-
const { text, bold } = markdownRow(line);
|
|
68
|
-
out.push({ key, text, bold });
|
|
148
|
+
out.push(bodyRow(`${id}-${index}`, line));
|
|
69
149
|
});
|
|
70
150
|
out.push({ key: `${id}-gap`, text: '', bold: false });
|
|
71
151
|
}
|
|
@@ -85,10 +165,17 @@ export const sectionRowNode = (prefix, row, selected = false) => row.cells === u
|
|
|
85
165
|
content: row.text,
|
|
86
166
|
bold: row.bold,
|
|
87
167
|
selected,
|
|
168
|
+
// GAIA-372 D5 — spread rather than passed as `undefined`, for
|
|
169
|
+
// `exactOptionalPropertyTypes`: a row with no runs declares none, and
|
|
170
|
+
// `linked()` then returns the plain string it always did.
|
|
171
|
+
...(row.runs === undefined ? {} : { runs: row.runs }),
|
|
88
172
|
})
|
|
89
173
|
: cellsRow(`${prefix}-${row.key}`, row.cells, selected);
|
|
90
174
|
// The same rows as plain text plus the INDICES that are bold (section headings +
|
|
91
|
-
//
|
|
175
|
+
// a rendered body's own ATX headings) — the shape the row surface needs. The
|
|
176
|
+
// RUNS are dropped on this path by definition: a pipe carries no styling, and
|
|
177
|
+
// `row.text` is already the rendered text, so what it prints is what the tree
|
|
178
|
+
// draws minus the attributes (GAIA-372 D4). Headings carry NO
|
|
92
179
|
// ANSI here: the draw step applies bold after fit(), so sanitizeDisplay never
|
|
93
180
|
// sees (and never strips) the ESC.
|
|
94
181
|
export function flattenSections(sections) {
|
|
@@ -138,8 +225,7 @@ export function expandableRows(entries, { expanded = new Set(), emptyMessage, em
|
|
|
138
225
|
});
|
|
139
226
|
const body = expanded.has(i) ? entry.expanded : entry.collapsed;
|
|
140
227
|
body.forEach((line, index) => {
|
|
141
|
-
|
|
142
|
-
rows.push({ key: `${entry.key}-${index}`, text, bold });
|
|
228
|
+
rows.push(bodyRow(`${entry.key}-${index}`, line));
|
|
143
229
|
});
|
|
144
230
|
});
|
|
145
231
|
return { rows, headerRows, selectable: entries.length };
|
|
@@ -13,6 +13,9 @@ export declare const ROOT_TABS: readonly [{
|
|
|
13
13
|
}, {
|
|
14
14
|
readonly id: "conductors";
|
|
15
15
|
readonly label: "Conductors";
|
|
16
|
+
}, {
|
|
17
|
+
readonly id: "teams";
|
|
18
|
+
readonly label: "Teams";
|
|
16
19
|
}];
|
|
17
20
|
/** The project level: the tabs of an opened project. */
|
|
18
21
|
export declare const PROJECT_TABS: readonly [{
|
|
@@ -62,7 +65,54 @@ export declare const TICKET_TABS: readonly [{
|
|
|
62
65
|
readonly id: "runs";
|
|
63
66
|
readonly label: "Runs";
|
|
64
67
|
}];
|
|
65
|
-
/**
|
|
68
|
+
/**
|
|
69
|
+
* The team level: Overview · Users for one crew — GAIA-391 AC4, by operator
|
|
70
|
+
* ruling.
|
|
71
|
+
*
|
|
72
|
+
* The level the `users` root tab BECAME. It sits between root and user, so the
|
|
73
|
+
* navigation reads root → team → user: which crews are there, who is in this
|
|
74
|
+
* one, what has that person done. Destinations, not modes — each tab is its own
|
|
75
|
+
* screen, so Tab runs a real `Router.navigate` and `teamId` is preserved
|
|
76
|
+
* (`Routing/registry.ts` keeps the params for this scope for the same reason it
|
|
77
|
+
* keeps them for `project` and `user`).
|
|
78
|
+
*
|
|
79
|
+
* `overview` is also declared on the user level, which GAIA-256 D2 already made
|
|
80
|
+
* legal: ids are not globally unique, every function here is level-parameterised,
|
|
81
|
+
* and one bar is on screen at a time.
|
|
82
|
+
*/
|
|
83
|
+
export declare const TEAM_TABS: readonly [{
|
|
84
|
+
readonly id: "overview";
|
|
85
|
+
readonly label: "Overview";
|
|
86
|
+
}, {
|
|
87
|
+
readonly id: "users";
|
|
88
|
+
readonly label: "Users";
|
|
89
|
+
}];
|
|
90
|
+
/**
|
|
91
|
+
* The user level: Overview · Tickets · Runs for one person (GAIA-377 D1).
|
|
92
|
+
*
|
|
93
|
+
* Destinations, not modes — each tab is its own screen in the slice that owns
|
|
94
|
+
* its query (D2), so Tab runs a real `Router.navigate` and preserves `userId`.
|
|
95
|
+
*
|
|
96
|
+
* UNCHANGED by GAIA-391: the ruling moved this level's PARENT (it hangs off
|
|
97
|
+
* {@link TEAM_TABS} now instead of off a root `users` tab), not its contents.
|
|
98
|
+
*/
|
|
99
|
+
export declare const USER_TABS: readonly [{
|
|
100
|
+
readonly id: "overview";
|
|
101
|
+
readonly label: "Overview";
|
|
102
|
+
}, {
|
|
103
|
+
readonly id: "tickets";
|
|
104
|
+
readonly label: "Tickets";
|
|
105
|
+
}, {
|
|
106
|
+
readonly id: "runs";
|
|
107
|
+
readonly label: "Runs";
|
|
108
|
+
}];
|
|
109
|
+
/**
|
|
110
|
+
* The tab bars, keyed by level — the one place a level is named.
|
|
111
|
+
*
|
|
112
|
+
* Declared in DEPTH order (root → project → ticket, then team → user), which is
|
|
113
|
+
* documentation rather than mechanism: nothing reads this object in key order
|
|
114
|
+
* since GAIA-256 D2 deleted `locateTab`, the one function that did.
|
|
115
|
+
*/
|
|
66
116
|
export declare const TAB_LEVELS: {
|
|
67
117
|
readonly root: readonly [{
|
|
68
118
|
readonly id: "dashboard";
|
|
@@ -73,6 +123,9 @@ export declare const TAB_LEVELS: {
|
|
|
73
123
|
}, {
|
|
74
124
|
readonly id: "conductors";
|
|
75
125
|
readonly label: "Conductors";
|
|
126
|
+
}, {
|
|
127
|
+
readonly id: "teams";
|
|
128
|
+
readonly label: "Teams";
|
|
76
129
|
}];
|
|
77
130
|
readonly project: readonly [{
|
|
78
131
|
readonly id: "dashboard";
|
|
@@ -100,6 +153,23 @@ export declare const TAB_LEVELS: {
|
|
|
100
153
|
readonly id: "runs";
|
|
101
154
|
readonly label: "Runs";
|
|
102
155
|
}];
|
|
156
|
+
readonly team: readonly [{
|
|
157
|
+
readonly id: "overview";
|
|
158
|
+
readonly label: "Overview";
|
|
159
|
+
}, {
|
|
160
|
+
readonly id: "users";
|
|
161
|
+
readonly label: "Users";
|
|
162
|
+
}];
|
|
163
|
+
readonly user: readonly [{
|
|
164
|
+
readonly id: "overview";
|
|
165
|
+
readonly label: "Overview";
|
|
166
|
+
}, {
|
|
167
|
+
readonly id: "tickets";
|
|
168
|
+
readonly label: "Tickets";
|
|
169
|
+
}, {
|
|
170
|
+
readonly id: "runs";
|
|
171
|
+
readonly label: "Runs";
|
|
172
|
+
}];
|
|
103
173
|
};
|
|
104
174
|
/** The name of a level that carries a top-level tab bar. */
|
|
105
175
|
export type TabLevel = keyof typeof TAB_LEVELS;
|
|
@@ -117,13 +187,40 @@ export type TopTabId = {
|
|
|
117
187
|
* Where a screen sits in the navigation: which bar it draws, and which tab of
|
|
118
188
|
* that bar is its own. A discriminated union over the levels, so a location can
|
|
119
189
|
* never pair one level's name with another level's tab id.
|
|
190
|
+
*
|
|
191
|
+
* GAIA-373 D5/D6 — the `ticket` arm is the one that is NOT a literal union, and
|
|
192
|
+
* it is also the one that carries an optional bar of its own. Both halves have
|
|
193
|
+
* the same cause: the ticket level is not a DESTINATION level.
|
|
194
|
+
* `router.navigateTab` is never reached for it, because `selectTopTab` claims
|
|
195
|
+
* every ticket tab (GAIA-346 D4), so the literal union that protects root/project
|
|
196
|
+
* NAVIGATION has no target here — while a contributed section mode
|
|
197
|
+
* (`gaia/artifacts`) is data from the contribution store and cannot be a literal
|
|
198
|
+
* in this file at all. Root and project keep theirs intact, so
|
|
199
|
+
* `topTabBar('root', 'runs')` is still a TS2345.
|
|
200
|
+
*
|
|
201
|
+
* `tabs` is read by BOTH bar builders below and by the kernel's Tab cycle, so
|
|
202
|
+
* the bar drawn, the bar clicked and the bar cycled are three readings of one
|
|
203
|
+
* field rather than three answers.
|
|
120
204
|
*/
|
|
121
205
|
export type TabLocation = {
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
206
|
+
level: 'root';
|
|
207
|
+
tab: TabIdOf<'root'>;
|
|
208
|
+
} | {
|
|
209
|
+
level: 'project';
|
|
210
|
+
tab: TabIdOf<'project'>;
|
|
211
|
+
} | {
|
|
212
|
+
level: 'ticket';
|
|
213
|
+
tab: string;
|
|
214
|
+
tabs?: readonly TopTab[];
|
|
215
|
+
} | {
|
|
216
|
+
level: 'team';
|
|
217
|
+
tab: TabIdOf<'team'>;
|
|
218
|
+
} | {
|
|
219
|
+
level: 'user';
|
|
220
|
+
tab: TabIdOf<'user'>;
|
|
221
|
+
};
|
|
222
|
+
/** The entries a location draws: its own, else its level's registry. */
|
|
223
|
+
export declare const tabsOfLocation: (location: TabLocation) => readonly TopTab[];
|
|
127
224
|
/**
|
|
128
225
|
* The bar as the PIECES it is drawn from: one part per tab, carrying that tab's
|
|
129
226
|
* id, with the joins between them carrying none.
|
|
@@ -191,5 +288,11 @@ export declare function topTabParts<L extends TabLevel>(level: L, activeId: TabI
|
|
|
191
288
|
* companion of {@link tabBarFor}, and the pairing it needs was already checked
|
|
192
289
|
* when the location was built. Keeping the two in step is what makes "the bar you
|
|
193
290
|
* see is the bar you click" true by construction: both read `location`.
|
|
291
|
+
*
|
|
292
|
+
* GAIA-373 — `TabBarPart<string>` rather than the `TopTabId` default, and that
|
|
293
|
+
* is an honest widening rather than a lost check: a location may carry a bar of
|
|
294
|
+
* its own now, whose entries are CONTRIBUTED (`gaia/artifacts`) and by
|
|
295
|
+
* construction outside every union derived from `TAB_LEVELS`. The level-typed
|
|
296
|
+
* {@link topTabParts} keeps the narrow ids for callers that name a level.
|
|
194
297
|
*/
|
|
195
|
-
export declare function tabPartsFor(location: TabLocation): TabBarPart[];
|
|
298
|
+
export declare function tabPartsFor(location: TabLocation): TabBarPart<string>[];
|
|
@@ -1,13 +1,17 @@
|
|
|
1
1
|
// GAIA-239: THE registry — the single place the set, the order and the labels of
|
|
2
|
-
// the top-level tabs are written. Navigation has
|
|
2
|
+
// the top-level tabs are written. Navigation has FIVE levels, and every screen
|
|
3
3
|
// shows exactly ONE tab bar: the one of its own level.
|
|
4
4
|
//
|
|
5
|
-
// root dashboard, projects, project-picker → Dashboard · Projects
|
|
5
|
+
// root dashboard, projects, project-picker → Dashboard · Projects ·
|
|
6
|
+
// Conductors · Teams
|
|
6
7
|
// project dashboard, project, ticket-agents, → Dashboard · Tickets ·
|
|
7
8
|
// project-runs Ticket-Agents · Runs
|
|
8
9
|
// ticket overview, comments, related, runs → the sections of one open
|
|
9
10
|
// ticket, in the bar row every
|
|
10
11
|
// other level uses (GAIA-346)
|
|
12
|
+
// team one crew → Overview · Users (GAIA-391)
|
|
13
|
+
// user one person → Overview · Tickets · Runs
|
|
14
|
+
// (GAIA-377)
|
|
11
15
|
//
|
|
12
16
|
// `tickets` is the project/ticket drill-down; `ticket-agents` is the managed
|
|
13
17
|
// create-agent list (GAIA-190); `runs` is the project-scoped run overview
|
|
@@ -29,10 +33,12 @@
|
|
|
29
33
|
// level — the bar you Tab through or click IS the current level's bar — so it
|
|
30
34
|
// pairs the target with `here.level` directly and `locateTab` is deleted.
|
|
31
35
|
//
|
|
32
|
-
// Only tabs that are actually navigable are listed.
|
|
33
|
-
//
|
|
34
|
-
//
|
|
35
|
-
//
|
|
36
|
+
// Only tabs that are actually navigable are listed. GAIA-377 added `Users` with
|
|
37
|
+
// its screens; a tab drawn before its screen exists would advertise navigation
|
|
38
|
+
// the operator cannot take, which is the very defect GAIA-239 removes. The same
|
|
39
|
+
// rule runs the other way, and GAIA-391 is where it did: a LEVEL is declared
|
|
40
|
+
// with the screens that occupy it, so `team` arrives with the crew overview and
|
|
41
|
+
// the crew's Users list, not before them.
|
|
36
42
|
//
|
|
37
43
|
// GAIA-232 added `conductors`, and put it on the ROOT level although the note
|
|
38
44
|
// above once reserved it for the project level. A conductor is an operator
|
|
@@ -53,6 +59,28 @@ export const ROOT_TABS = [
|
|
|
53
59
|
{ id: 'dashboard', label: 'Dashboard' },
|
|
54
60
|
{ id: 'projects', label: 'Projects' },
|
|
55
61
|
{ id: 'conductors', label: 'Conductors' },
|
|
62
|
+
// GAIA-391 AC4 — `teams` is the LAST root tab, and there is deliberately no
|
|
63
|
+
// `users` beside it any more.
|
|
64
|
+
//
|
|
65
|
+
// ROOT level for teams, by GAIA-232's argument for Conductors: a team is the
|
|
66
|
+
// level that cuts ACROSS customer projects (GAIA-321), so "which crews are
|
|
67
|
+
// there" has to be answerable without first choosing a project.
|
|
68
|
+
//
|
|
69
|
+
// THIS REVISES AN INFORMATION-ARCHITECTURE DECISION THAT BELONGS TO GAIA-377,
|
|
70
|
+
// NOT TO GAIA-391. GAIA-377 D1 shipped `users` as a root tab and declared
|
|
71
|
+
// {@link USER_TABS} as the level below it; GAIA-391 moved that whole level
|
|
72
|
+
// under the team level by OPERATOR RULING, so the root bar reads
|
|
73
|
+
// `Dashboard · Projects · Conductors · Teams` and a person is reached through
|
|
74
|
+
// the crew they are in: Teams → one crew → Users → one person. Recorded here
|
|
75
|
+
// because a silent removal would later read as a regression against 377, and
|
|
76
|
+
// because the previous comment on this list argued the OPPOSITE order
|
|
77
|
+
// ("people, then the crews they are in") — an argument this ruling overturns
|
|
78
|
+
// rather than an accident.
|
|
79
|
+
//
|
|
80
|
+
// Users did NOT become unreachable, which is what makes the move a move: the
|
|
81
|
+
// team level carries the tab ({@link TEAM_TABS}), and the Dashboard's PER USER
|
|
82
|
+
// rows still open one person directly.
|
|
83
|
+
{ id: 'teams', label: 'Teams' },
|
|
56
84
|
];
|
|
57
85
|
/** The project level: the tabs of an opened project. */
|
|
58
86
|
export const PROJECT_TABS = [
|
|
@@ -88,12 +116,57 @@ export const TICKET_TABS = [
|
|
|
88
116
|
{ id: 'related', label: 'Related' },
|
|
89
117
|
{ id: 'runs', label: 'Runs' },
|
|
90
118
|
];
|
|
91
|
-
/**
|
|
119
|
+
/**
|
|
120
|
+
* The team level: Overview · Users for one crew — GAIA-391 AC4, by operator
|
|
121
|
+
* ruling.
|
|
122
|
+
*
|
|
123
|
+
* The level the `users` root tab BECAME. It sits between root and user, so the
|
|
124
|
+
* navigation reads root → team → user: which crews are there, who is in this
|
|
125
|
+
* one, what has that person done. Destinations, not modes — each tab is its own
|
|
126
|
+
* screen, so Tab runs a real `Router.navigate` and `teamId` is preserved
|
|
127
|
+
* (`Routing/registry.ts` keeps the params for this scope for the same reason it
|
|
128
|
+
* keeps them for `project` and `user`).
|
|
129
|
+
*
|
|
130
|
+
* `overview` is also declared on the user level, which GAIA-256 D2 already made
|
|
131
|
+
* legal: ids are not globally unique, every function here is level-parameterised,
|
|
132
|
+
* and one bar is on screen at a time.
|
|
133
|
+
*/
|
|
134
|
+
export const TEAM_TABS = [
|
|
135
|
+
{ id: 'overview', label: 'Overview' },
|
|
136
|
+
{ id: 'users', label: 'Users' },
|
|
137
|
+
];
|
|
138
|
+
/**
|
|
139
|
+
* The user level: Overview · Tickets · Runs for one person (GAIA-377 D1).
|
|
140
|
+
*
|
|
141
|
+
* Destinations, not modes — each tab is its own screen in the slice that owns
|
|
142
|
+
* its query (D2), so Tab runs a real `Router.navigate` and preserves `userId`.
|
|
143
|
+
*
|
|
144
|
+
* UNCHANGED by GAIA-391: the ruling moved this level's PARENT (it hangs off
|
|
145
|
+
* {@link TEAM_TABS} now instead of off a root `users` tab), not its contents.
|
|
146
|
+
*/
|
|
147
|
+
export const USER_TABS = [
|
|
148
|
+
{ id: 'overview', label: 'Overview' },
|
|
149
|
+
{ id: 'tickets', label: 'Tickets' },
|
|
150
|
+
{ id: 'runs', label: 'Runs' },
|
|
151
|
+
];
|
|
152
|
+
/**
|
|
153
|
+
* The tab bars, keyed by level — the one place a level is named.
|
|
154
|
+
*
|
|
155
|
+
* Declared in DEPTH order (root → project → ticket, then team → user), which is
|
|
156
|
+
* documentation rather than mechanism: nothing reads this object in key order
|
|
157
|
+
* since GAIA-256 D2 deleted `locateTab`, the one function that did.
|
|
158
|
+
*/
|
|
92
159
|
export const TAB_LEVELS = {
|
|
93
160
|
root: ROOT_TABS,
|
|
94
161
|
project: PROJECT_TABS,
|
|
95
162
|
ticket: TICKET_TABS,
|
|
163
|
+
team: TEAM_TABS,
|
|
164
|
+
user: USER_TABS,
|
|
96
165
|
};
|
|
166
|
+
/** The entries a location draws: its own, else its level's registry. */
|
|
167
|
+
export const tabsOfLocation = (location) => 'tabs' in location && location.tabs
|
|
168
|
+
? location.tabs
|
|
169
|
+
: TAB_LEVELS[location.level];
|
|
97
170
|
/** What joins two tabs in the rendered bar — part of neither tab's span. */
|
|
98
171
|
const SEPARATOR = ' ';
|
|
99
172
|
/** One tab's rendered segment: bracketed when active, space-padded otherwise. */
|
|
@@ -133,7 +206,7 @@ export function topTabBar(level, activeId) {
|
|
|
133
206
|
* not ask the compiler to correlate two independently narrowed properties.
|
|
134
207
|
*/
|
|
135
208
|
export function tabBarFor(location) {
|
|
136
|
-
return renderBar(
|
|
209
|
+
return renderBar(tabsOfLocation(location), location.tab);
|
|
137
210
|
}
|
|
138
211
|
// GAIA-256 spec D2 — `locateTab` USED TO LIVE HERE and is deliberately gone.
|
|
139
212
|
//
|
|
@@ -168,7 +241,13 @@ export function topTabParts(level, activeId) {
|
|
|
168
241
|
* companion of {@link tabBarFor}, and the pairing it needs was already checked
|
|
169
242
|
* when the location was built. Keeping the two in step is what makes "the bar you
|
|
170
243
|
* see is the bar you click" true by construction: both read `location`.
|
|
244
|
+
*
|
|
245
|
+
* GAIA-373 — `TabBarPart<string>` rather than the `TopTabId` default, and that
|
|
246
|
+
* is an honest widening rather than a lost check: a location may carry a bar of
|
|
247
|
+
* its own now, whose entries are CONTRIBUTED (`gaia/artifacts`) and by
|
|
248
|
+
* construction outside every union derived from `TAB_LEVELS`. The level-typed
|
|
249
|
+
* {@link topTabParts} keeps the narrow ids for callers that name a level.
|
|
171
250
|
*/
|
|
172
251
|
export function tabPartsFor(location) {
|
|
173
|
-
return renderParts(
|
|
252
|
+
return renderParts(tabsOfLocation(location), location.tab);
|
|
174
253
|
}
|
|
@@ -50,7 +50,7 @@ function readTotal(doc) {
|
|
|
50
50
|
* back is `pad`'s reason for existing: `cellFill` fills to a width the COLUMN
|
|
51
51
|
* DECLARES, never to one measured off the page.
|
|
52
52
|
* Reaching `lib/width.ts` for the measurement is a DELIBERATE decision on top of
|
|
53
|
-
* A2 already being reported as missed (`docs/evidence/GAIA-340/teardown-a2.txt
|
|
53
|
+
* A2 already being reported as missed (`docs/evidence/GAIA-340/teardown-a2.txt`, in git history since GAIA-371)
|
|
54
54
|
* — the module survives this ticket, so the alternative is not "no measurement"
|
|
55
55
|
* but "an illegible cut", and R5's rule against re-implementing it under another
|
|
56
56
|
* name is honoured by calling it rather than copying it.
|
|
@@ -223,9 +223,10 @@ export interface ConductorRowContext {
|
|
|
223
223
|
/**
|
|
224
224
|
* The conductor collection as cockpit rows, in server order.
|
|
225
225
|
*
|
|
226
|
-
*
|
|
227
|
-
*
|
|
228
|
-
*
|
|
226
|
+
* Sideloads `machine` so capacity (GAIA-353 D5/D10) comes from the host budget,
|
|
227
|
+
* not removed conductor fields. Swallows a read failure into an empty list on
|
|
228
|
+
* purpose: the Conductors screen re-reads every 5 s, so one failed poll must
|
|
229
|
+
* leave the screen navigable rather than replace it with an error.
|
|
229
230
|
*/
|
|
230
231
|
export declare const listConductorRows: (client: DropshClient, ctx: ConductorRowContext) => Promise<ConductorRow[]>;
|
|
231
232
|
/**
|
|
@@ -4,14 +4,19 @@
|
|
|
4
4
|
// service that applied them would be unusable for any other purpose. The two read
|
|
5
5
|
// strategies are both just reading, so both live here.
|
|
6
6
|
import { toUnixSeconds } from '@gaia-ai/core';
|
|
7
|
+
import { DrupalJsonApiParams } from 'drupal-jsonapi-params';
|
|
7
8
|
import { humanizeAge } from '../../../lib/format.js';
|
|
8
9
|
import { listOutcome, listPreferringWrite, } from '../../transport.js';
|
|
9
10
|
import { deriveConductorStatus } from './liveness.js';
|
|
10
11
|
const str = (v) => typeof v === 'string' && v.length > 0 ? v : undefined;
|
|
11
12
|
const toConductor = (r) => ({
|
|
12
13
|
id: r.id,
|
|
13
|
-
label: str(r.attr('label')) ??
|
|
14
|
-
|
|
14
|
+
label: str(r.attr('label')) ??
|
|
15
|
+
str(r.attr('conductor_id')) ??
|
|
16
|
+
str(r.attr('machine_id')) ??
|
|
17
|
+
r.id,
|
|
18
|
+
// Registration key (GAIA-353 renamed machine_id → conductor_id).
|
|
19
|
+
machineId: str(r.attr('conductor_id')) ?? str(r.attr('machine_id')),
|
|
15
20
|
status: str(r.attr('status')),
|
|
16
21
|
projectId: r.rel('project_id'),
|
|
17
22
|
});
|
|
@@ -242,19 +247,68 @@ export const CONDUCTOR_TABLE = {
|
|
|
242
247
|
],
|
|
243
248
|
};
|
|
244
249
|
const num = (v) => typeof v === 'number' && Number.isFinite(v) ? v : undefined;
|
|
245
|
-
|
|
250
|
+
/** Id of a single-valued relationship linkage (`data` object), or null. */
|
|
251
|
+
function singleRelId(rel) {
|
|
252
|
+
const data = rel?.data;
|
|
253
|
+
if (!data || Array.isArray(data))
|
|
254
|
+
return null;
|
|
255
|
+
return data.id ?? null;
|
|
256
|
+
}
|
|
257
|
+
/**
|
|
258
|
+
* Dropsh's collection path hands Resource accessors; the cockpit row path uses
|
|
259
|
+
* client.get so it can sideload `machine`. Map the raw JSON:API resource into
|
|
260
|
+
* the same attr/rel surface toConductor already speaks.
|
|
261
|
+
*/
|
|
262
|
+
function asResource(raw) {
|
|
263
|
+
return {
|
|
264
|
+
id: raw.id ?? '',
|
|
265
|
+
type: raw.type ?? '',
|
|
266
|
+
attr: (key) => raw.attributes?.[key],
|
|
267
|
+
rel: (key) => singleRelId(raw.relationships?.[key]),
|
|
268
|
+
rels: (key) => {
|
|
269
|
+
const data = raw.relationships?.[key]?.data;
|
|
270
|
+
if (!data)
|
|
271
|
+
return [];
|
|
272
|
+
if (Array.isArray(data)) {
|
|
273
|
+
return data
|
|
274
|
+
.map((d) => d.id)
|
|
275
|
+
.filter((id) => typeof id === 'string');
|
|
276
|
+
}
|
|
277
|
+
return data.id ? [data.id] : [];
|
|
278
|
+
},
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
/** Host capacity from the sideloaded machine, with cutover fallbacks (GAIA-353). */
|
|
282
|
+
function capacityOf(raw, machinesById) {
|
|
283
|
+
const machineId = singleRelId(raw.relationships?.machine);
|
|
284
|
+
const machine = machineId ? machinesById.get(machineId) : undefined;
|
|
285
|
+
const attrs = raw.attributes ?? {};
|
|
286
|
+
return {
|
|
287
|
+
// Prefer machine.current_load; process telemetry / legacy conductor attrs
|
|
288
|
+
// only when the host row is missing during cutover.
|
|
289
|
+
currentLoad: num(machine?.attributes?.current_load) ??
|
|
290
|
+
num(attrs.reported_load) ??
|
|
291
|
+
num(attrs.current_load) ??
|
|
292
|
+
0,
|
|
293
|
+
// max_parallel is machine-owned SoT; legacy conductor attr is last resort.
|
|
294
|
+
maxParallel: num(machine?.attributes?.max_parallel) ?? num(attrs.max_parallel) ?? 0,
|
|
295
|
+
};
|
|
296
|
+
}
|
|
297
|
+
const toConductorRow = (raw, ctx, machinesById) => {
|
|
298
|
+
const r = asResource(raw);
|
|
246
299
|
const workspaceRoot = str(r.attr('workspace_root')) ?? '';
|
|
247
300
|
// ISO-8601 over the wire, not the unix integer the field name implies — a
|
|
248
301
|
// `typeof === 'number'` guard here is what made a live conductor read `stale`.
|
|
249
302
|
const lastSeen = toUnixSeconds(r.attr('last_seen')) ?? 0;
|
|
250
303
|
const leaseExpiresAt = toUnixSeconds(r.attr('lease_expires_at')) ?? 0;
|
|
304
|
+
const { currentLoad, maxParallel } = capacityOf(raw, machinesById);
|
|
251
305
|
return {
|
|
252
306
|
...toConductor(r),
|
|
253
307
|
workspaceRoot,
|
|
254
308
|
lastSeen,
|
|
255
309
|
leaseExpiresAt,
|
|
256
|
-
currentLoad
|
|
257
|
-
maxParallel
|
|
310
|
+
currentLoad,
|
|
311
|
+
maxParallel,
|
|
258
312
|
lastPid: num(r.attr('last_pid')),
|
|
259
313
|
conductorName: str(r.attr('conductor_name')),
|
|
260
314
|
// `present: true` because the row IS a control-plane record — which is why
|
|
@@ -273,13 +327,25 @@ const toConductorRow = (r, ctx) => {
|
|
|
273
327
|
/**
|
|
274
328
|
* The conductor collection as cockpit rows, in server order.
|
|
275
329
|
*
|
|
276
|
-
*
|
|
277
|
-
*
|
|
278
|
-
*
|
|
330
|
+
* Sideloads `machine` so capacity (GAIA-353 D5/D10) comes from the host budget,
|
|
331
|
+
* not removed conductor fields. Swallows a read failure into an empty list on
|
|
332
|
+
* purpose: the Conductors screen re-reads every 5 s, so one failed poll must
|
|
333
|
+
* leave the screen navigable rather than replace it with an error.
|
|
279
334
|
*/
|
|
280
335
|
export const listConductorRows = async (client, ctx) => {
|
|
281
|
-
const
|
|
282
|
-
|
|
336
|
+
const params = new DrupalJsonApiParams().addInclude(['machine']);
|
|
337
|
+
try {
|
|
338
|
+
const doc = (await client.get('gaia_conductor/gaia_conductor', params));
|
|
339
|
+
const machinesById = new Map();
|
|
340
|
+
for (const inc of doc.included ?? []) {
|
|
341
|
+
if (inc.id)
|
|
342
|
+
machinesById.set(inc.id, inc);
|
|
343
|
+
}
|
|
344
|
+
return (doc.data ?? []).map((raw) => toConductorRow(raw, ctx, machinesById));
|
|
345
|
+
}
|
|
346
|
+
catch {
|
|
347
|
+
return [];
|
|
348
|
+
}
|
|
283
349
|
};
|
|
284
350
|
/**
|
|
285
351
|
* Project uuid → name, for the Conductors screen's project column.
|