@gaia-ai/addon-gaia-ui 0.14.1 → 0.14.2
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/Entity/collection.d.ts +15 -0
- package/dist/src/Entity/collection.js +26 -5
- package/dist/src/Entity/conductor/Data/detail.d.ts +39 -0
- package/dist/src/Entity/conductor/Data/detail.js +69 -0
- package/dist/src/Entity/conductor/Data/log-block.d.ts +18 -0
- package/dist/src/Entity/conductor/Data/log-block.js +32 -0
- package/dist/src/Entity/conductor/Screen/conductor-log.d.ts +36 -0
- package/dist/src/Entity/conductor/Screen/conductor-log.js +46 -0
- package/dist/src/Entity/conductor/Screen/conductor-overview.d.ts +40 -0
- package/dist/src/Entity/conductor/Screen/conductor-overview.js +61 -0
- package/dist/src/Entity/conductor/Screen/conductor-runs.d.ts +90 -0
- package/dist/src/Entity/conductor/Screen/conductor-runs.js +128 -0
- package/dist/src/Entity/conductor/Screen/conductors.d.ts +6 -13
- package/dist/src/Entity/conductor/Screen/conductors.js +9 -60
- package/dist/src/Entity/conductor/Screen/project-conductors.d.ts +6 -13
- package/dist/src/Entity/conductor/Screen/project-conductors.js +9 -57
- package/dist/src/Entity/project/Data/with-tickets.js +1 -0
- package/dist/src/Entity/run/Data/query.d.ts +12 -0
- package/dist/src/Entity/run/Data/query.js +22 -1
- package/dist/src/Entity/run/Data/runs.d.ts +99 -0
- package/dist/src/Entity/run/Data/runs.js +37 -0
- package/dist/src/Entity/run/Form/edit.d.ts +29 -0
- package/dist/src/Entity/run/Form/edit.js +23 -0
- package/dist/src/Entity/run/Screen/edit-run-form.d.ts +26 -0
- package/dist/src/Entity/run/Screen/edit-run-form.js +117 -0
- package/dist/src/Entity/run/Screen/run-detail.d.ts +24 -3
- package/dist/src/Entity/run/Screen/run-detail.js +55 -3
- package/dist/src/Entity/ticket/Component/ticket-content/model.js +9 -1
- package/dist/src/Entity/ticket/Component/ticket-detail/index.d.ts +6 -3
- package/dist/src/Entity/ticket/Component/ticket-detail/index.js +8 -3
- package/dist/src/Entity/ticket/Data/parents.js +2 -0
- package/dist/src/Entity/ticket/Data/query.d.ts +17 -0
- package/dist/src/Entity/ticket/Data/query.js +29 -1
- package/dist/src/Entity/ticket/Data/store.js +16 -0
- package/dist/src/Entity/ticket/Data/tickets.d.ts +115 -1
- package/dist/src/Entity/ticket/Data/tickets.js +38 -0
- package/dist/src/Entity/ticket/Form/who-filter.d.ts +8 -0
- package/dist/src/Entity/ticket/Screen/conductor-tickets.d.ts +85 -0
- package/dist/src/Entity/ticket/Screen/conductor-tickets.js +111 -0
- package/dist/src/Entity/ticket/Screen/ticket-runs.js +9 -0
- package/dist/src/Entity/ticket/Screen/tickets.d.ts +8 -0
- package/dist/src/Entity/ticket/Screen/tickets.js +27 -0
- package/dist/src/Entity/ticket/Screen/user-tickets.d.ts +8 -0
- package/dist/src/Entity/ticket/Screen/who-filter-form.d.ts +7 -0
- package/dist/src/Entity/ticket/Screen/who-filter-form.js +20 -6
- package/dist/src/Kernel/braid.d.ts +4 -0
- package/dist/src/Kernel/braid.js +10 -0
- package/dist/src/Kernel/chrome.d.ts +1 -1
- package/dist/src/Kernel/chrome.js +4 -4
- package/dist/src/Kernel/splash.js +10 -3
- package/dist/src/Routing/registry.js +1 -0
- package/dist/src/Routing/routes.js +10 -0
- package/dist/src/Screen/async-react-screen.d.ts +4 -2
- package/dist/src/Screen/async-react-screen.js +26 -1
- package/dist/src/Statistics/figures.d.ts +1 -2
- package/dist/src/Statistics/figures.js +72 -29
- package/dist/src/Statistics/presentation.d.ts +8 -0
- package/dist/src/Statistics/presentation.js +23 -0
- package/dist/src/Statistics/screen.d.ts +21 -14
- package/dist/src/Statistics/screen.js +126 -27
- package/dist/src/Vendor/termcn/ui/dither-chart.js +1 -2
- package/dist/src/lib/theme.d.ts +5 -0
- package/dist/src/lib/theme.js +5 -0
- package/package.json +2 -2
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import type { VNode } from '@opentui/core';
|
|
2
1
|
import type { ListFilterBadge } from '../../../Component/list-chrome/index.js';
|
|
3
2
|
import type { FullScreenChrome } from '../../../Screen/full-screen.js';
|
|
4
3
|
import { ListScreen } from '../../../Screen/list-screen.js';
|
|
@@ -88,7 +87,6 @@ export declare class ConductorsScreen extends ListScreen<ConductorRow, Conductor
|
|
|
88
87
|
readonly get: (row: ConductorRow) => string;
|
|
89
88
|
}];
|
|
90
89
|
};
|
|
91
|
-
private logFor;
|
|
92
90
|
private readonly busyIds;
|
|
93
91
|
/**
|
|
94
92
|
* GAIA-460 review — how `mine` failed to resolve, not an action's outcome.
|
|
@@ -113,7 +111,6 @@ export declare class ConductorsScreen extends ListScreen<ConductorRow, Conductor
|
|
|
113
111
|
protected loadRows(context: ScreenContext, page: number): Promise<Page<ConductorRow>>;
|
|
114
112
|
protected badges(): readonly ListFilterBadge[];
|
|
115
113
|
protected renderLoading(): string[];
|
|
116
|
-
protected renderReady(value: Page<ConductorRow>, context: ScreenContext): VNode;
|
|
117
114
|
protected chrome(_context: ScreenContext): FullScreenChrome;
|
|
118
115
|
/**
|
|
119
116
|
* GAIA-450 review B9 — `s`/`x` are advertised only when the SELECTED row can
|
|
@@ -121,9 +118,9 @@ export declare class ConductorsScreen extends ListScreen<ConductorRow, Conductor
|
|
|
121
118
|
* filesystem check, so a foreign conductor whose workspace happens to exist
|
|
122
119
|
* on this disk (GAIA-281 Finding 2) is correctly excluded too.
|
|
123
120
|
*
|
|
124
|
-
*
|
|
125
|
-
*
|
|
126
|
-
*
|
|
121
|
+
* `Enter open` is no longer gated on `mine`: it opens the conductor detail
|
|
122
|
+
* scope for every row, and the log tab inside it is the one place that still
|
|
123
|
+
* withholds a foreign conductor's log (its `mine` derivation lives there).
|
|
127
124
|
*/
|
|
128
125
|
protected actions(): import("../../../Component/footer-actions/index.js").ActionItem[];
|
|
129
126
|
/**
|
|
@@ -136,13 +133,9 @@ export declare class ConductorsScreen extends ListScreen<ConductorRow, Conductor
|
|
|
136
133
|
protected notice(): string | undefined;
|
|
137
134
|
select(index: number, context: ScreenContext): boolean;
|
|
138
135
|
/**
|
|
139
|
-
*
|
|
140
|
-
*
|
|
141
|
-
*
|
|
142
|
-
* (`Kernel/regions.ts`'s `rows.activate(index)`) both reach this method
|
|
143
|
-
* directly, bypassing `handleInput` entirely. Refusing here is therefore the
|
|
144
|
-
* one place that actually withholds the log on a row that is not ours,
|
|
145
|
-
* whichever path invoked it.
|
|
136
|
+
* The `mine` gate that used to sit here moved onto the log tab: a row on
|
|
137
|
+
* another machine still opens, and its Log tab is where "no log here" is
|
|
138
|
+
* now said (see `Entity/conductor/Data/log-block.ts`).
|
|
146
139
|
*/
|
|
147
140
|
activate(index: number, context: ScreenContext): boolean;
|
|
148
141
|
handleInput(input: string, context: ScreenContext): boolean;
|
|
@@ -1,44 +1,11 @@
|
|
|
1
1
|
import { existsSync } from 'node:fs';
|
|
2
|
-
import { configHomeOf } from '@gaia-ai/core';
|
|
3
2
|
import { actionItem } from '../../../Component/footer-actions/index.js';
|
|
4
|
-
import { columnNode, textNode } from '../../../lib/vnode.js';
|
|
5
3
|
import { ListScreen } from '../../../Screen/list-screen.js';
|
|
6
4
|
import { runRowAction } from '../../../Screen/row-action.js';
|
|
7
5
|
import { resolveWhoCandidate } from '../../user/Data/users.js';
|
|
8
6
|
import { CONDUCTOR_TABLE, countConductors, listConductorRowPage, listProjectNames, } from '../Data/conductors.js';
|
|
9
|
-
import { tailConductorLog } from '../Data/log-tail.js';
|
|
10
7
|
import { DEFAULT_CONDUCTOR_MINE, hiddenNotice, mineBadges, OwnerIdentity, } from '../Data/own-rows.js';
|
|
11
8
|
import { createConductorProcessHost } from '../Data/process.js';
|
|
12
|
-
/**
|
|
13
|
-
* The open row's log block: where it is, and its tail.
|
|
14
|
-
*
|
|
15
|
-
* A conductor on another machine has a workspace this machine cannot read, so
|
|
16
|
-
* the block states that instead of showing an empty tail — the same rule the
|
|
17
|
-
* row's `elsewhere` mark states for its actions.
|
|
18
|
-
*
|
|
19
|
-
* GAIA-450 review B12: keyed on `mine`, not `isLocal`. `isLocal` is a
|
|
20
|
-
* filesystem existence check on `workspace_root`, so a FOREIGN conductor that
|
|
21
|
-
* happens to share this machine's directory layout (GAIA-281 Finding 2 — two
|
|
22
|
-
* `gaia_conductor` records naming one checkout under different machines) still
|
|
23
|
-
* has `isLocal === true`. Keying the log on it tailed THIS machine's own
|
|
24
|
-
* `log.txt` under the foreign conductor's heading, while the footer
|
|
25
|
-
* simultaneously read "view-only". `mine` is the identity gate `actions()` and
|
|
26
|
-
* `act()` already use; the log now agrees with them.
|
|
27
|
-
*/
|
|
28
|
-
const logFor = (row) => {
|
|
29
|
-
// GAIA-450 D4: workspace_root is the checkout root; the log lives under its
|
|
30
|
-
// own .gaia dir, not directly at the root.
|
|
31
|
-
const logPath = `${configHomeOf(row.workspaceRoot)}/log.txt`;
|
|
32
|
-
return row.mine
|
|
33
|
-
? {
|
|
34
|
-
heading: `log — ${logPath}`,
|
|
35
|
-
lines: tailConductorLog(logPath, { maxLines: 40 }).lines,
|
|
36
|
-
}
|
|
37
|
-
: {
|
|
38
|
-
heading: `log — ${row.label} runs on another machine; no log here`,
|
|
39
|
-
lines: [],
|
|
40
|
-
};
|
|
41
|
-
};
|
|
42
9
|
/**
|
|
43
10
|
* Conductors list — an ordinary {@link ListScreen}, paged and filtered to the
|
|
44
11
|
* operator's own conductors by default (GAIA-460 D1).
|
|
@@ -56,7 +23,6 @@ export class ConductorsScreen extends ListScreen {
|
|
|
56
23
|
id = ConductorsScreen.route.id;
|
|
57
24
|
frameId = 'conductors';
|
|
58
25
|
table = CONDUCTOR_TABLE;
|
|
59
|
-
logFor = null;
|
|
60
26
|
busyIds = new Set();
|
|
61
27
|
/**
|
|
62
28
|
* GAIA-460 review — how `mine` failed to resolve, not an action's outcome.
|
|
@@ -129,17 +95,6 @@ export class ConductorsScreen extends ListScreen {
|
|
|
129
95
|
renderLoading() {
|
|
130
96
|
return ['Loading conductors…'];
|
|
131
97
|
}
|
|
132
|
-
renderReady(value, context) {
|
|
133
|
-
const open = value.rows.find((row) => row.id === this.logFor);
|
|
134
|
-
const log = open ? logFor(open) : undefined;
|
|
135
|
-
return columnNode('conductor-list', super.renderReady(value, context), ...(log
|
|
136
|
-
? [
|
|
137
|
-
textNode({ id: 'conductor-log-gap', content: '' }),
|
|
138
|
-
textNode({ id: 'conductor-log-heading', content: log.heading }),
|
|
139
|
-
...log.lines.map((line, index) => textNode({ id: `conductor-log-${index}`, content: line })),
|
|
140
|
-
]
|
|
141
|
-
: []));
|
|
142
|
-
}
|
|
143
98
|
chrome(_context) {
|
|
144
99
|
return {
|
|
145
100
|
selectable: this.rows.length,
|
|
@@ -161,9 +116,9 @@ export class ConductorsScreen extends ListScreen {
|
|
|
161
116
|
* filesystem check, so a foreign conductor whose workspace happens to exist
|
|
162
117
|
* on this disk (GAIA-281 Finding 2) is correctly excluded too.
|
|
163
118
|
*
|
|
164
|
-
*
|
|
165
|
-
*
|
|
166
|
-
*
|
|
119
|
+
* `Enter open` is no longer gated on `mine`: it opens the conductor detail
|
|
120
|
+
* scope for every row, and the log tab inside it is the one place that still
|
|
121
|
+
* withholds a foreign conductor's log (its `mine` derivation lives there).
|
|
167
122
|
*/
|
|
168
123
|
actions() {
|
|
169
124
|
const canAct = this.rows[this.selectedIndex]?.mine ?? false;
|
|
@@ -172,9 +127,9 @@ export class ConductorsScreen extends ListScreen {
|
|
|
172
127
|
? [
|
|
173
128
|
actionItem('conductors-start', 's', 'start'),
|
|
174
129
|
actionItem('conductors-stop', 'x', 'stop'),
|
|
175
|
-
actionItem('conductors-enter', 'Enter', 'log'),
|
|
176
130
|
]
|
|
177
131
|
: []),
|
|
132
|
+
actionItem('conductors-enter', 'Enter', 'open'),
|
|
178
133
|
actionItem('conductors-tab', 'Tab', 'tabs'),
|
|
179
134
|
...this.pagingActionItems(),
|
|
180
135
|
];
|
|
@@ -203,20 +158,15 @@ export class ConductorsScreen extends ListScreen {
|
|
|
203
158
|
return true;
|
|
204
159
|
}
|
|
205
160
|
/**
|
|
206
|
-
*
|
|
207
|
-
*
|
|
208
|
-
*
|
|
209
|
-
* (`Kernel/regions.ts`'s `rows.activate(index)`) both reach this method
|
|
210
|
-
* directly, bypassing `handleInput` entirely. Refusing here is therefore the
|
|
211
|
-
* one place that actually withholds the log on a row that is not ours,
|
|
212
|
-
* whichever path invoked it.
|
|
161
|
+
* The `mine` gate that used to sit here moved onto the log tab: a row on
|
|
162
|
+
* another machine still opens, and its Log tab is where "no log here" is
|
|
163
|
+
* now said (see `Entity/conductor/Data/log-block.ts`).
|
|
213
164
|
*/
|
|
214
165
|
activate(index, context) {
|
|
215
166
|
const row = this.rows[index];
|
|
216
|
-
if (!row?.
|
|
167
|
+
if (!row?.id || !context.navigate)
|
|
217
168
|
return false;
|
|
218
|
-
|
|
219
|
-
context.requestDraw();
|
|
169
|
+
context.navigate('conductor.overview', { conductorId: row.id });
|
|
220
170
|
return true;
|
|
221
171
|
}
|
|
222
172
|
handleInput(input, context) {
|
|
@@ -256,7 +206,6 @@ export class ConductorsScreen extends ListScreen {
|
|
|
256
206
|
this.identity.reset();
|
|
257
207
|
this.ownerId = undefined;
|
|
258
208
|
this.hidden = null;
|
|
259
|
-
this.logFor = null;
|
|
260
209
|
this.busyIds.clear();
|
|
261
210
|
this.identityReason = '';
|
|
262
211
|
super.reset();
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import type { VNode } from '@opentui/core';
|
|
2
1
|
import type { ListFilterBadge } from '../../../Component/list-chrome/index.js';
|
|
3
2
|
import type { FullScreenChrome } from '../../../Screen/full-screen.js';
|
|
4
3
|
import { ListScreen } from '../../../Screen/list-screen.js';
|
|
@@ -83,7 +82,6 @@ export declare class ProjectConductorsScreen extends ListScreen<ConductorRow, Co
|
|
|
83
82
|
readonly noun: "conductor";
|
|
84
83
|
readonly emptyMessage: "No conductors on this control plane.";
|
|
85
84
|
};
|
|
86
|
-
private logFor;
|
|
87
85
|
private readonly busyIds;
|
|
88
86
|
/**
|
|
89
87
|
* GAIA-460 review — how `mine` failed to resolve, not an action's outcome.
|
|
@@ -107,7 +105,6 @@ export declare class ProjectConductorsScreen extends ListScreen<ConductorRow, Co
|
|
|
107
105
|
protected loadRows(context: ScreenContext, page: number): Promise<Page<ConductorRow>>;
|
|
108
106
|
protected badges(): readonly ListFilterBadge[];
|
|
109
107
|
protected renderLoading(): string[];
|
|
110
|
-
protected renderReady(value: Page<ConductorRow>, context: ScreenContext): VNode;
|
|
111
108
|
protected chrome(context: ScreenContext): FullScreenChrome;
|
|
112
109
|
/**
|
|
113
110
|
* GAIA-450 review B9 — `s`/`x` are advertised only when the SELECTED row can
|
|
@@ -115,9 +112,9 @@ export declare class ProjectConductorsScreen extends ListScreen<ConductorRow, Co
|
|
|
115
112
|
* filesystem check, so a foreign conductor whose workspace happens to exist
|
|
116
113
|
* on this disk (GAIA-281 Finding 2) is correctly excluded too.
|
|
117
114
|
*
|
|
118
|
-
*
|
|
119
|
-
*
|
|
120
|
-
*
|
|
115
|
+
* `Enter open` is no longer gated on `mine`: it opens the conductor detail
|
|
116
|
+
* scope for every row; its Log tab is the one place that still withholds a
|
|
117
|
+
* foreign conductor's log.
|
|
121
118
|
*/
|
|
122
119
|
protected actions(): import("../../../Component/footer-actions/index.js").ActionItem[];
|
|
123
120
|
/**
|
|
@@ -130,13 +127,9 @@ export declare class ProjectConductorsScreen extends ListScreen<ConductorRow, Co
|
|
|
130
127
|
protected notice(): string | undefined;
|
|
131
128
|
select(index: number, context: ScreenContext): boolean;
|
|
132
129
|
/**
|
|
133
|
-
*
|
|
134
|
-
*
|
|
135
|
-
*
|
|
136
|
-
* (`Kernel/regions.ts`'s `rows.activate(index)`) both reach this method
|
|
137
|
-
* directly, bypassing `handleInput` entirely. Refusing here is therefore the
|
|
138
|
-
* one place that actually withholds the log on a row that is not ours,
|
|
139
|
-
* whichever path invoked it.
|
|
130
|
+
* The `mine` gate that used to sit here moved onto the log tab: a row on
|
|
131
|
+
* another machine still opens, and its Log tab is where "no log here" is
|
|
132
|
+
* now said (see `Entity/conductor/Data/log-block.ts`).
|
|
140
133
|
*/
|
|
141
134
|
activate(index: number, context: ScreenContext): boolean;
|
|
142
135
|
handleInput(input: string, context: ScreenContext): boolean;
|
|
@@ -1,38 +1,11 @@
|
|
|
1
1
|
import { existsSync } from 'node:fs';
|
|
2
|
-
import { configHomeOf } from '@gaia-ai/core';
|
|
3
2
|
import { actionItem } from '../../../Component/footer-actions/index.js';
|
|
4
|
-
import { columnNode, textNode } from '../../../lib/vnode.js';
|
|
5
3
|
import { ListScreen } from '../../../Screen/list-screen.js';
|
|
6
4
|
import { runRowAction } from '../../../Screen/row-action.js';
|
|
7
5
|
import { resolveWhoCandidate } from '../../user/Data/users.js';
|
|
8
6
|
import { countConductors, listConductorRowPage, PROJECT_CONDUCTOR_TABLE, } from '../Data/conductors.js';
|
|
9
|
-
import { tailConductorLog } from '../Data/log-tail.js';
|
|
10
7
|
import { DEFAULT_CONDUCTOR_MINE, hiddenNotice, mineBadges, OwnerIdentity, } from '../Data/own-rows.js';
|
|
11
8
|
import { createConductorProcessHost } from '../Data/process.js';
|
|
12
|
-
/**
|
|
13
|
-
* The open row's log block: where it is, and its tail.
|
|
14
|
-
*
|
|
15
|
-
* A conductor on another machine has a workspace this machine cannot read, so
|
|
16
|
-
* the block states that instead of showing an empty tail — the same rule the
|
|
17
|
-
* row's `elsewhere` mark states for its actions.
|
|
18
|
-
*
|
|
19
|
-
* GAIA-450 review B12: keyed on `mine`, not `isLocal` — see the root screen's
|
|
20
|
-
* copy of this comment for the GAIA-281 Finding 2 shape this closes.
|
|
21
|
-
*/
|
|
22
|
-
const logFor = (row) => {
|
|
23
|
-
// GAIA-450 D4: workspace_root is the checkout root; the log lives under its
|
|
24
|
-
// own .gaia dir, not directly at the root.
|
|
25
|
-
const logPath = `${configHomeOf(row.workspaceRoot)}/log.txt`;
|
|
26
|
-
return row.mine
|
|
27
|
-
? {
|
|
28
|
-
heading: `log — ${logPath}`,
|
|
29
|
-
lines: tailConductorLog(logPath, { maxLines: 40 }).lines,
|
|
30
|
-
}
|
|
31
|
-
: {
|
|
32
|
-
heading: `log — ${row.label} runs on another machine; no log here`,
|
|
33
|
-
lines: [],
|
|
34
|
-
};
|
|
35
|
-
};
|
|
36
9
|
/**
|
|
37
10
|
* The project-scoped Conductors tab — an ordinary {@link ListScreen}, paged
|
|
38
11
|
* and filtered to the operator's own conductors by default (GAIA-460 D1),
|
|
@@ -57,7 +30,6 @@ export class ProjectConductorsScreen extends ListScreen {
|
|
|
57
30
|
id = ProjectConductorsScreen.route.id;
|
|
58
31
|
frameId = 'project-conductors';
|
|
59
32
|
table = PROJECT_CONDUCTOR_TABLE;
|
|
60
|
-
logFor = null;
|
|
61
33
|
busyIds = new Set();
|
|
62
34
|
/**
|
|
63
35
|
* GAIA-460 review — how `mine` failed to resolve, not an action's outcome.
|
|
@@ -126,20 +98,6 @@ export class ProjectConductorsScreen extends ListScreen {
|
|
|
126
98
|
renderLoading() {
|
|
127
99
|
return ['Loading conductors…'];
|
|
128
100
|
}
|
|
129
|
-
renderReady(value, context) {
|
|
130
|
-
const open = value.rows.find((row) => row.id === this.logFor);
|
|
131
|
-
const log = open ? logFor(open) : undefined;
|
|
132
|
-
return columnNode('project-conductor-list', super.renderReady(value, context), ...(log
|
|
133
|
-
? [
|
|
134
|
-
textNode({ id: 'project-conductor-log-gap', content: '' }),
|
|
135
|
-
textNode({
|
|
136
|
-
id: 'project-conductor-log-heading',
|
|
137
|
-
content: log.heading,
|
|
138
|
-
}),
|
|
139
|
-
...log.lines.map((line, index) => textNode({ id: `project-conductor-log-${index}`, content: line })),
|
|
140
|
-
]
|
|
141
|
-
: []));
|
|
142
|
-
}
|
|
143
101
|
chrome(context) {
|
|
144
102
|
return {
|
|
145
103
|
selectable: this.rows.length,
|
|
@@ -161,9 +119,9 @@ export class ProjectConductorsScreen extends ListScreen {
|
|
|
161
119
|
* filesystem check, so a foreign conductor whose workspace happens to exist
|
|
162
120
|
* on this disk (GAIA-281 Finding 2) is correctly excluded too.
|
|
163
121
|
*
|
|
164
|
-
*
|
|
165
|
-
*
|
|
166
|
-
*
|
|
122
|
+
* `Enter open` is no longer gated on `mine`: it opens the conductor detail
|
|
123
|
+
* scope for every row; its Log tab is the one place that still withholds a
|
|
124
|
+
* foreign conductor's log.
|
|
167
125
|
*/
|
|
168
126
|
actions() {
|
|
169
127
|
const canAct = this.rows[this.selectedIndex]?.mine ?? false;
|
|
@@ -172,9 +130,9 @@ export class ProjectConductorsScreen extends ListScreen {
|
|
|
172
130
|
? [
|
|
173
131
|
actionItem('project-conductors-start', 's', 'start'),
|
|
174
132
|
actionItem('project-conductors-stop', 'x', 'stop'),
|
|
175
|
-
actionItem('project-conductors-enter', 'Enter', 'log'),
|
|
176
133
|
]
|
|
177
134
|
: []),
|
|
135
|
+
actionItem('project-conductors-enter', 'Enter', 'open'),
|
|
178
136
|
actionItem('project-conductors-tab', 'Tab', 'tabs'),
|
|
179
137
|
...this.pagingActionItems(),
|
|
180
138
|
];
|
|
@@ -203,20 +161,15 @@ export class ProjectConductorsScreen extends ListScreen {
|
|
|
203
161
|
return true;
|
|
204
162
|
}
|
|
205
163
|
/**
|
|
206
|
-
*
|
|
207
|
-
*
|
|
208
|
-
*
|
|
209
|
-
* (`Kernel/regions.ts`'s `rows.activate(index)`) both reach this method
|
|
210
|
-
* directly, bypassing `handleInput` entirely. Refusing here is therefore the
|
|
211
|
-
* one place that actually withholds the log on a row that is not ours,
|
|
212
|
-
* whichever path invoked it.
|
|
164
|
+
* The `mine` gate that used to sit here moved onto the log tab: a row on
|
|
165
|
+
* another machine still opens, and its Log tab is where "no log here" is
|
|
166
|
+
* now said (see `Entity/conductor/Data/log-block.ts`).
|
|
213
167
|
*/
|
|
214
168
|
activate(index, context) {
|
|
215
169
|
const row = this.rows[index];
|
|
216
|
-
if (!row?.
|
|
170
|
+
if (!row?.id || !context.navigate)
|
|
217
171
|
return false;
|
|
218
|
-
|
|
219
|
-
context.requestDraw();
|
|
172
|
+
context.navigate('conductor.overview', { conductorId: row.id });
|
|
220
173
|
return true;
|
|
221
174
|
}
|
|
222
175
|
handleInput(input, context) {
|
|
@@ -256,7 +209,6 @@ export class ProjectConductorsScreen extends ListScreen {
|
|
|
256
209
|
this.identity.reset();
|
|
257
210
|
this.ownerId = undefined;
|
|
258
211
|
this.hidden = null;
|
|
259
|
-
this.logFor = null;
|
|
260
212
|
this.busyIds.clear();
|
|
261
213
|
this.identityReason = '';
|
|
262
214
|
super.reset();
|
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
import { DrupalJsonApiParams } from 'drupal-jsonapi-params';
|
|
2
|
+
/** Non-terminal run states — the ONLY states "currently held work" means.
|
|
3
|
+
* Never `closed`, which tracks something else entirely. */
|
|
4
|
+
export declare const HELD_RUN_STATES: readonly ["claimed", "running"];
|
|
2
5
|
/** The active filter criteria for the project run list. */
|
|
3
6
|
export interface RunCriteria {
|
|
4
7
|
/** false: open only (the default) · true: closed only · null: all. */
|
|
@@ -36,3 +39,12 @@ export declare function buildTeamMemberRunQueryParams(userId: string, projectIds
|
|
|
36
39
|
size: number;
|
|
37
40
|
index: number;
|
|
38
41
|
}): DrupalJsonApiParams;
|
|
42
|
+
/**
|
|
43
|
+
* Build the JSON:API query for one conductor's held work: runs whose
|
|
44
|
+
* `conductor_id` matches and whose state is still non-terminal
|
|
45
|
+
* (claimed/running), newest-started first. Never filters on `closed`.
|
|
46
|
+
*/
|
|
47
|
+
export declare function buildConductorRunQueryParams(conductorId: string, page?: {
|
|
48
|
+
size: number;
|
|
49
|
+
index: number;
|
|
50
|
+
}): DrupalJsonApiParams;
|
|
@@ -3,7 +3,10 @@
|
|
|
3
3
|
// Runs offer only the closed cycle; search/state are not part of GAIA-282.
|
|
4
4
|
import { DrupalJsonApiParams } from 'drupal-jsonapi-params';
|
|
5
5
|
import { LIST_PAGE_SIZE } from '../../cycle.js';
|
|
6
|
-
import { RUN_LIST_FIELDS, RUN_TICKET_INCLUDE_FIELDS } from './runs.js';
|
|
6
|
+
import { CONDUCTOR_RUN_TICKET_INCLUDE_FIELDS, RUN_LIST_FIELDS, RUN_TICKET_INCLUDE_FIELDS, } from './runs.js';
|
|
7
|
+
/** Non-terminal run states — the ONLY states "currently held work" means.
|
|
8
|
+
* Never `closed`, which tracks something else entirely. */
|
|
9
|
+
export const HELD_RUN_STATES = ['claimed', 'running'];
|
|
7
10
|
/** The shipped default: closed runs hidden (GAIA-282 D1). */
|
|
8
11
|
export const DEFAULT_RUN_CRITERIA = { closed: false };
|
|
9
12
|
/** "Active" means the view is narrowed relative to showing every row —
|
|
@@ -49,6 +52,24 @@ export function buildTeamMemberRunQueryParams(userId, projectIds, criteria, page
|
|
|
49
52
|
applyRunCriteria(params, criteria, page);
|
|
50
53
|
return params;
|
|
51
54
|
}
|
|
55
|
+
/**
|
|
56
|
+
* Build the JSON:API query for one conductor's held work: runs whose
|
|
57
|
+
* `conductor_id` matches and whose state is still non-terminal
|
|
58
|
+
* (claimed/running), newest-started first. Never filters on `closed`.
|
|
59
|
+
*/
|
|
60
|
+
export function buildConductorRunQueryParams(conductorId, page = { size: LIST_PAGE_SIZE, index: 0 }) {
|
|
61
|
+
const params = new DrupalJsonApiParams();
|
|
62
|
+
params.addFilter('conductor_id.id', conductorId);
|
|
63
|
+
params.addFilter('state', [...HELD_RUN_STATES], 'IN');
|
|
64
|
+
params.addInclude(['ticket_id']);
|
|
65
|
+
params.addFields('gaia_ticket--gaia_ticket', CONDUCTOR_RUN_TICKET_INCLUDE_FIELDS);
|
|
66
|
+
params.addFields('gaia_run--gaia_run', RUN_LIST_FIELDS);
|
|
67
|
+
params.addSort('started_at', 'DESC');
|
|
68
|
+
params.addPageLimit(page.size);
|
|
69
|
+
if (page.index > 0)
|
|
70
|
+
params.addPageOffset(page.index * page.size);
|
|
71
|
+
return params;
|
|
72
|
+
}
|
|
52
73
|
function applyRunCriteria(params, criteria, page) {
|
|
53
74
|
if (criteria.closed !== null)
|
|
54
75
|
params.addFilter('closed', criteria.closed ? '1' : '0');
|
|
@@ -10,6 +10,9 @@ export interface RunRow {
|
|
|
10
10
|
ticketId: string | null;
|
|
11
11
|
/** The ticket's `identifier` from the sideloaded include, or '—'. */
|
|
12
12
|
ticketIdentifier: string;
|
|
13
|
+
/** The ticket's `title` from the sideloaded include, or '' when the
|
|
14
|
+
* include did not carry one (only the conductor-scoped query asks). */
|
|
15
|
+
ticketTitle: string;
|
|
13
16
|
/** Authoritative run state (`claimed|running|done|expired|failed`). */
|
|
14
17
|
state: string;
|
|
15
18
|
/** The ticket state the run was dispatched for (`state_at_start`). */
|
|
@@ -199,6 +202,102 @@ export declare const RUN_LIST_FIELDS: string[];
|
|
|
199
202
|
* out of 254.7 KB total, and took 4732 ms.
|
|
200
203
|
*/
|
|
201
204
|
export declare const RUN_TICKET_INCLUDE_FIELDS: string[];
|
|
205
|
+
/**
|
|
206
|
+
* The conductor-scoped held-work table also names the ticket's title, so its
|
|
207
|
+
* ticket include fieldset is wider than the project run list's — declared
|
|
208
|
+
* separately so that list's own payload does not widen.
|
|
209
|
+
*/
|
|
210
|
+
export declare const CONDUCTOR_RUN_TICKET_INCLUDE_FIELDS: string[];
|
|
211
|
+
/**
|
|
212
|
+
* The held-work table's columns: RUN, TICKET, TITLE, STATE, PHASE, STARTED.
|
|
213
|
+
* AGENT is dropped — the conductor is constant on this page, and so is its
|
|
214
|
+
* agent-per-run detail of little use here.
|
|
215
|
+
*/
|
|
216
|
+
export declare const CONDUCTOR_RUN_COLUMNS: readonly [{
|
|
217
|
+
readonly key: "run";
|
|
218
|
+
readonly label: "RUN";
|
|
219
|
+
readonly width: 8;
|
|
220
|
+
readonly fields: readonly ["drupal_internal__id"];
|
|
221
|
+
readonly get: (r: RunRow) => string;
|
|
222
|
+
}, {
|
|
223
|
+
readonly key: "ticket";
|
|
224
|
+
readonly label: "TICKET";
|
|
225
|
+
readonly width: 10;
|
|
226
|
+
readonly fields: readonly ["ticket_id"];
|
|
227
|
+
readonly get: (r: RunRow) => string;
|
|
228
|
+
}, {
|
|
229
|
+
readonly key: "title";
|
|
230
|
+
readonly label: "TITLE";
|
|
231
|
+
readonly width: 0;
|
|
232
|
+
readonly grow: 2;
|
|
233
|
+
readonly min: 24;
|
|
234
|
+
readonly fields: readonly ["ticket_id"];
|
|
235
|
+
readonly get: (r: RunRow) => string;
|
|
236
|
+
}, {
|
|
237
|
+
readonly key: "state";
|
|
238
|
+
readonly label: "STATE";
|
|
239
|
+
readonly width: 8;
|
|
240
|
+
readonly fields: readonly ["state", "heartbeat", "claim_expires_at"];
|
|
241
|
+
readonly get: (r: RunRow, nowSeconds: number) => string;
|
|
242
|
+
}, {
|
|
243
|
+
readonly key: "phase";
|
|
244
|
+
readonly label: "PHASE";
|
|
245
|
+
readonly width: 13;
|
|
246
|
+
readonly fields: readonly ["state_at_start"];
|
|
247
|
+
readonly get: (r: RunRow) => string;
|
|
248
|
+
}, {
|
|
249
|
+
readonly key: "started";
|
|
250
|
+
readonly label: "STARTED";
|
|
251
|
+
readonly width: 0;
|
|
252
|
+
readonly grow: 1;
|
|
253
|
+
readonly fields: readonly ["started_at", "created"];
|
|
254
|
+
readonly get: (r: RunRow, nowSeconds: number) => string;
|
|
255
|
+
}];
|
|
256
|
+
/** Held work: one conductor's non-terminal runs (GAIA-282-style table). */
|
|
257
|
+
export declare const CONDUCTOR_RUN_TABLE: {
|
|
258
|
+
readonly noun: "run";
|
|
259
|
+
readonly columns: readonly [{
|
|
260
|
+
readonly key: "run";
|
|
261
|
+
readonly label: "RUN";
|
|
262
|
+
readonly width: 8;
|
|
263
|
+
readonly fields: readonly ["drupal_internal__id"];
|
|
264
|
+
readonly get: (r: RunRow) => string;
|
|
265
|
+
}, {
|
|
266
|
+
readonly key: "ticket";
|
|
267
|
+
readonly label: "TICKET";
|
|
268
|
+
readonly width: 10;
|
|
269
|
+
readonly fields: readonly ["ticket_id"];
|
|
270
|
+
readonly get: (r: RunRow) => string;
|
|
271
|
+
}, {
|
|
272
|
+
readonly key: "title";
|
|
273
|
+
readonly label: "TITLE";
|
|
274
|
+
readonly width: 0;
|
|
275
|
+
readonly grow: 2;
|
|
276
|
+
readonly min: 24;
|
|
277
|
+
readonly fields: readonly ["ticket_id"];
|
|
278
|
+
readonly get: (r: RunRow) => string;
|
|
279
|
+
}, {
|
|
280
|
+
readonly key: "state";
|
|
281
|
+
readonly label: "STATE";
|
|
282
|
+
readonly width: 8;
|
|
283
|
+
readonly fields: readonly ["state", "heartbeat", "claim_expires_at"];
|
|
284
|
+
readonly get: (r: RunRow, nowSeconds: number) => string;
|
|
285
|
+
}, {
|
|
286
|
+
readonly key: "phase";
|
|
287
|
+
readonly label: "PHASE";
|
|
288
|
+
readonly width: 13;
|
|
289
|
+
readonly fields: readonly ["state_at_start"];
|
|
290
|
+
readonly get: (r: RunRow) => string;
|
|
291
|
+
}, {
|
|
292
|
+
readonly key: "started";
|
|
293
|
+
readonly label: "STARTED";
|
|
294
|
+
readonly width: 0;
|
|
295
|
+
readonly grow: 1;
|
|
296
|
+
readonly fields: readonly ["started_at", "created"];
|
|
297
|
+
readonly get: (r: RunRow, nowSeconds: number) => string;
|
|
298
|
+
}];
|
|
299
|
+
readonly emptyMessage: "This conductor holds no run.";
|
|
300
|
+
};
|
|
202
301
|
/**
|
|
203
302
|
* Map a RAW JSON:API `data[]` entry (from `client.get`) to a run row, resolving
|
|
204
303
|
* the ticket identifier from the sideloaded includes indexed by id. Mirrors
|
|
@@ -154,6 +154,42 @@ export const RUN_LIST_FIELDS = [
|
|
|
154
154
|
* out of 254.7 KB total, and took 4732 ms.
|
|
155
155
|
*/
|
|
156
156
|
export const RUN_TICKET_INCLUDE_FIELDS = ['identifier'];
|
|
157
|
+
/**
|
|
158
|
+
* The conductor-scoped held-work table also names the ticket's title, so its
|
|
159
|
+
* ticket include fieldset is wider than the project run list's — declared
|
|
160
|
+
* separately so that list's own payload does not widen.
|
|
161
|
+
*/
|
|
162
|
+
export const CONDUCTOR_RUN_TICKET_INCLUDE_FIELDS = [
|
|
163
|
+
'identifier',
|
|
164
|
+
'title',
|
|
165
|
+
];
|
|
166
|
+
/**
|
|
167
|
+
* The held-work table's columns: RUN, TICKET, TITLE, STATE, PHASE, STARTED.
|
|
168
|
+
* AGENT is dropped — the conductor is constant on this page, and so is its
|
|
169
|
+
* agent-per-run detail of little use here.
|
|
170
|
+
*/
|
|
171
|
+
export const CONDUCTOR_RUN_COLUMNS = [
|
|
172
|
+
RUN_COLUMNS[0], // RUN
|
|
173
|
+
RUN_COLUMNS[1], // TICKET
|
|
174
|
+
{
|
|
175
|
+
key: 'title',
|
|
176
|
+
label: 'TITLE',
|
|
177
|
+
width: 0,
|
|
178
|
+
grow: 2,
|
|
179
|
+
min: 24,
|
|
180
|
+
fields: ['ticket_id'],
|
|
181
|
+
get: (r) => r.ticketTitle,
|
|
182
|
+
},
|
|
183
|
+
RUN_COLUMNS[2], // STATE
|
|
184
|
+
RUN_COLUMNS[3], // PHASE
|
|
185
|
+
RUN_COLUMNS[5], // STARTED
|
|
186
|
+
];
|
|
187
|
+
/** Held work: one conductor's non-terminal runs (GAIA-282-style table). */
|
|
188
|
+
export const CONDUCTOR_RUN_TABLE = {
|
|
189
|
+
noun: 'run',
|
|
190
|
+
columns: CONDUCTOR_RUN_COLUMNS,
|
|
191
|
+
emptyMessage: 'This conductor holds no run.',
|
|
192
|
+
};
|
|
157
193
|
/**
|
|
158
194
|
* Map a RAW JSON:API `data[]` entry (from `client.get`) to a run row, resolving
|
|
159
195
|
* the ticket identifier from the sideloaded includes indexed by id. Mirrors
|
|
@@ -181,6 +217,7 @@ export function mapRunData(item, ticketsById) {
|
|
|
181
217
|
drupalId,
|
|
182
218
|
ticketId,
|
|
183
219
|
ticketIdentifier: str(ticket?.attributes?.identifier) ?? (ticketId ? ticketId : '—'),
|
|
220
|
+
ticketTitle: str(ticket?.attributes?.title) ?? '',
|
|
184
221
|
state: text('state') ?? 'unknown',
|
|
185
222
|
phase: text('state_at_start') ?? '—',
|
|
186
223
|
agent: text('agent') ?? '—',
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export declare const CANCELLED_RUN_STATE = "cancelled";
|
|
2
|
+
/**
|
|
3
|
+
* The run states `cancel` leaves, mirroring `gaia_run.transitions.cancel.from`
|
|
4
|
+
* in `gaia_core.workflows.yml` by hand.
|
|
5
|
+
*
|
|
6
|
+
* No served fact carries workflow transition edges, so widen this set together
|
|
7
|
+
* with the YAML transition whenever its source states change.
|
|
8
|
+
*/
|
|
9
|
+
export declare const CANCELLABLE_RUN_STATES: ReadonlySet<string>;
|
|
10
|
+
export declare const CANCEL_RUN_ACTION_ID = "cancelRun";
|
|
11
|
+
export type RunEditFieldId = typeof CANCEL_RUN_ACTION_ID;
|
|
12
|
+
export interface CancelRunPayload {
|
|
13
|
+
attributes: {
|
|
14
|
+
state: typeof CANCELLED_RUN_STATE;
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
export declare const cancelRunPayload: () => CancelRunPayload;
|
|
18
|
+
declare const EDIT_FORM: {
|
|
19
|
+
fields: import("../../../Form/field.js").FieldDecl[];
|
|
20
|
+
activeField: (s: import("../../../Form/form.js").FormState<Record<string, never>>) => import("../../../Form/field.js").FieldDecl;
|
|
21
|
+
move: (s: import("../../../Form/form.js").FormState<Record<string, never>>, d: number) => import("../../../Form/form.js").FormState<Record<string, never>>;
|
|
22
|
+
initial(options: Record<string, never>, prefill?: Partial<Record<string, never>>): import("../../../Form/form.js").FormState<Record<string, never>>;
|
|
23
|
+
apply(s: import("../../../Form/form.js").FormState<Record<string, never>>, options: Record<string, never>, intent: import("../../../Form/form.js").Intent): import("../../../Form/form.js").FormState<Record<string, never>>;
|
|
24
|
+
toObject: (s: import("../../../Form/form.js").FormState<Record<string, never>>, options: Record<string, never>) => CancelRunPayload;
|
|
25
|
+
};
|
|
26
|
+
export type EditRunFormDeclaration = typeof EDIT_FORM;
|
|
27
|
+
export declare const isRunCancellable: (state: string) => boolean;
|
|
28
|
+
export declare const editRunFormFor: (runState: string) => EditRunFormDeclaration | undefined;
|
|
29
|
+
export {};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { declareForm } from '../../../Form/index.js';
|
|
2
|
+
export const CANCELLED_RUN_STATE = 'cancelled';
|
|
3
|
+
/**
|
|
4
|
+
* The run states `cancel` leaves, mirroring `gaia_run.transitions.cancel.from`
|
|
5
|
+
* in `gaia_core.workflows.yml` by hand.
|
|
6
|
+
*
|
|
7
|
+
* No served fact carries workflow transition edges, so widen this set together
|
|
8
|
+
* with the YAML transition whenever its source states change.
|
|
9
|
+
*/
|
|
10
|
+
export const CANCELLABLE_RUN_STATES = new Set([
|
|
11
|
+
'claimed',
|
|
12
|
+
'running',
|
|
13
|
+
]);
|
|
14
|
+
export const CANCEL_RUN_ACTION_ID = 'cancelRun';
|
|
15
|
+
export const cancelRunPayload = () => ({
|
|
16
|
+
attributes: { state: CANCELLED_RUN_STATE },
|
|
17
|
+
});
|
|
18
|
+
const EDIT_FORM = declareForm({
|
|
19
|
+
fields: (f) => [f.action('Cancel run', CANCEL_RUN_ACTION_ID)],
|
|
20
|
+
toObject: () => cancelRunPayload(),
|
|
21
|
+
});
|
|
22
|
+
export const isRunCancellable = (state) => CANCELLABLE_RUN_STATES.has(state);
|
|
23
|
+
export const editRunFormFor = (runState) => isRunCancellable(runState) ? EDIT_FORM : undefined;
|