@grifhinz/logics-manager 2.8.0 → 2.8.1
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/README.md +22 -7
- package/VERSION +1 -1
- package/clients/viewer/browser-host.js +421 -33
- package/clients/viewer/index.html +1 -0
- package/clients/viewer/viewer.css +251 -0
- package/logics_manager/cli.py +17 -17
- package/logics_manager/viewer.py +251 -22
- package/package.json +2 -1
- package/pyproject.toml +1 -1
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
(() => {
|
|
2
2
|
const stateKey = "logics.localViewer.state";
|
|
3
|
+
const preferenceKey = "logics.localViewer.preferences.v1";
|
|
4
|
+
const preferenceVersion = 1;
|
|
3
5
|
const meta = () => document.getElementById("viewer-meta");
|
|
4
6
|
const documentPanel = () => document.getElementById("viewer-document");
|
|
5
7
|
const documentTitle = () => document.getElementById("viewer-document-title");
|
|
@@ -16,6 +18,7 @@
|
|
|
16
18
|
const projectMenu = () => document.getElementById("viewer-project-menu");
|
|
17
19
|
const repoGithubLink = () => document.getElementById("viewer-repo-github");
|
|
18
20
|
const repoFolderButton = () => document.getElementById("viewer-repo-folder");
|
|
21
|
+
const workspaceButton = () => document.getElementById("viewer-workspace");
|
|
19
22
|
const ciButton = () => document.getElementById("viewer-ci");
|
|
20
23
|
const autoRefreshControl = () => document.getElementById("viewer-auto-refresh");
|
|
21
24
|
const refreshIntervalControl = () => document.getElementById("viewer-refresh-interval");
|
|
@@ -72,10 +75,27 @@
|
|
|
72
75
|
let latestViewerStateSignature = "";
|
|
73
76
|
let latestGitStatusSignature = "";
|
|
74
77
|
let latestCdxStatusSignature = "";
|
|
78
|
+
let latestCdxStatusPayload = null;
|
|
75
79
|
let latestCiStatusSignature = "";
|
|
76
80
|
let primaryActionBusyKey = "";
|
|
77
81
|
let cdxMissionBusyKey = "";
|
|
78
82
|
let cdxCloseTarget = null;
|
|
83
|
+
let viewerPreferences = readViewerPreferences();
|
|
84
|
+
let autoRefreshIntervalForcedByLaunch = false;
|
|
85
|
+
const cdxStatusColumns = [
|
|
86
|
+
{ id: "session", label: "SESSION" },
|
|
87
|
+
{ id: "provider", label: "PROV." },
|
|
88
|
+
{ id: "status", label: "STATUS" },
|
|
89
|
+
{ id: "auth", label: "AUTH" },
|
|
90
|
+
{ id: "ok", label: "OK" },
|
|
91
|
+
{ id: "remaining5h", label: "5H" },
|
|
92
|
+
{ id: "remainingWeek", label: "WEEK" },
|
|
93
|
+
{ id: "block", label: "BLOCK", defaultVisible: false },
|
|
94
|
+
{ id: "credits", label: "CR", defaultVisible: false },
|
|
95
|
+
{ id: "reset5h", label: "RESET 5H" },
|
|
96
|
+
{ id: "resetWeek", label: "RESET WEEK" },
|
|
97
|
+
{ id: "updated", label: "UPDATED" }
|
|
98
|
+
];
|
|
79
99
|
|
|
80
100
|
function readStoredState() {
|
|
81
101
|
try {
|
|
@@ -85,6 +105,83 @@
|
|
|
85
105
|
}
|
|
86
106
|
}
|
|
87
107
|
|
|
108
|
+
function readViewerPreferences() {
|
|
109
|
+
try {
|
|
110
|
+
const value = JSON.parse(window.localStorage.getItem(preferenceKey) || "null");
|
|
111
|
+
if (!value || typeof value !== "object" || value.version !== preferenceVersion) {
|
|
112
|
+
return { version: preferenceVersion };
|
|
113
|
+
}
|
|
114
|
+
return { ...value, version: preferenceVersion };
|
|
115
|
+
} catch {
|
|
116
|
+
return { version: preferenceVersion };
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function writeViewerPreferences(nextPreferences) {
|
|
121
|
+
viewerPreferences = { ...nextPreferences, version: preferenceVersion };
|
|
122
|
+
try {
|
|
123
|
+
window.localStorage.setItem(preferenceKey, JSON.stringify(viewerPreferences));
|
|
124
|
+
} catch {
|
|
125
|
+
// Keep the in-memory preference for this session when browser storage is unavailable.
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function updateViewerPreferences(patch) {
|
|
130
|
+
writeViewerPreferences({ ...viewerPreferences, ...patch });
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function preferredAutoRefreshIntervalSeconds() {
|
|
134
|
+
const seconds = Number(viewerPreferences.autoRefreshIntervalSeconds);
|
|
135
|
+
return Number.isFinite(seconds) && seconds > 0 ? normalizeAutoRefreshIntervalSeconds(seconds) : null;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function cdxColumnVisibilityPreference() {
|
|
139
|
+
const stored = viewerPreferences.cdxStatusColumns;
|
|
140
|
+
const storedVisibility = stored && typeof stored === "object" ? stored.visibility : null;
|
|
141
|
+
const visibility = {};
|
|
142
|
+
cdxStatusColumns.forEach((column) => {
|
|
143
|
+
visibility[column.id] = column.defaultVisible !== false;
|
|
144
|
+
if (storedVisibility && typeof storedVisibility[column.id] === "boolean") {
|
|
145
|
+
visibility[column.id] = storedVisibility[column.id];
|
|
146
|
+
}
|
|
147
|
+
});
|
|
148
|
+
return visibility;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function persistCdxColumnVisibility(columnId, visible) {
|
|
152
|
+
const current = cdxColumnVisibilityPreference();
|
|
153
|
+
if (!cdxStatusColumns.some((column) => column.id === columnId)) {
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
updateViewerPreferences({
|
|
157
|
+
cdxStatusColumns: {
|
|
158
|
+
visibility: { ...current, [columnId]: Boolean(visible) }
|
|
159
|
+
}
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function cdxProviderFilterPreference() {
|
|
164
|
+
const stored = viewerPreferences.cdxStatusProviders;
|
|
165
|
+
if (!stored || typeof stored !== "object" || stored.mode !== "subset") {
|
|
166
|
+
return { mode: "all", selected: [] };
|
|
167
|
+
}
|
|
168
|
+
const selected = Array.isArray(stored.selected)
|
|
169
|
+
? stored.selected.map((entry) => String(entry || "").trim()).filter(Boolean)
|
|
170
|
+
: [];
|
|
171
|
+
return selected.length ? { mode: "subset", selected: Array.from(new Set(selected)) } : { mode: "all", selected: [] };
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function persistCdxProviderFilter(nextFilter) {
|
|
175
|
+
const selected = Array.isArray(nextFilter?.selected)
|
|
176
|
+
? nextFilter.selected.map((entry) => String(entry || "").trim()).filter(Boolean)
|
|
177
|
+
: [];
|
|
178
|
+
updateViewerPreferences({
|
|
179
|
+
cdxStatusProviders: selected.length
|
|
180
|
+
? { mode: "subset", selected: Array.from(new Set(selected)).sort() }
|
|
181
|
+
: { mode: "all", selected: [] }
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
|
|
88
185
|
function sanitizeViewerFilterState(value) {
|
|
89
186
|
const nextState = { ...defaultFilterState };
|
|
90
187
|
if (!value || typeof value !== "object") {
|
|
@@ -155,6 +252,7 @@
|
|
|
155
252
|
return Array.from(document.querySelectorAll([
|
|
156
253
|
"#viewer-insights",
|
|
157
254
|
"#viewer-health",
|
|
255
|
+
"#viewer-workspace",
|
|
158
256
|
"#viewer-git",
|
|
159
257
|
"#viewer-ci",
|
|
160
258
|
"#viewer-cdx",
|
|
@@ -452,6 +550,7 @@
|
|
|
452
550
|
autoRefreshIntervalMs = normalizeAutoRefreshIntervalSeconds(value) * 1000;
|
|
453
551
|
if (options.user) {
|
|
454
552
|
autoRefreshIntervalTouched = true;
|
|
553
|
+
updateViewerPreferences({ autoRefreshIntervalSeconds: Math.round(autoRefreshIntervalMs / 1000) });
|
|
455
554
|
}
|
|
456
555
|
updateRefreshIntervalControl();
|
|
457
556
|
scheduleNextAutoRefresh();
|
|
@@ -586,6 +685,7 @@
|
|
|
586
685
|
const capabilities = payload?.capabilities && typeof payload.capabilities === "object" ? payload.capabilities : {};
|
|
587
686
|
return {
|
|
588
687
|
logics: capabilities.logics || { state: "ready", available: true, message: "" },
|
|
688
|
+
workspace: capabilities.workspace || { state: "ready", available: true, message: "" },
|
|
589
689
|
git: capabilities.git || { state: "ready", available: true, message: "" },
|
|
590
690
|
ci: capabilities.ci || { state: "ready", available: true, message: "" },
|
|
591
691
|
cdx: capabilities.cdx || { state: "ready", available: true, message: "" },
|
|
@@ -624,6 +724,16 @@
|
|
|
624
724
|
}
|
|
625
725
|
|
|
626
726
|
function updateCapabilityControls() {
|
|
727
|
+
const workspace = workspaceButton();
|
|
728
|
+
if (workspace instanceof HTMLElement) {
|
|
729
|
+
workspace.hidden = !isCapabilityAvailable("workspace");
|
|
730
|
+
if (isCapabilityAvailable("workspace")) {
|
|
731
|
+
setButtonAvailable(workspace, "Show file explorer");
|
|
732
|
+
} else {
|
|
733
|
+
setButtonUnavailable(workspace, capabilityMessage("workspace", "Explorer is not available for this project."));
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
|
|
627
737
|
const gitButton = document.getElementById("viewer-git");
|
|
628
738
|
if (gitButton instanceof HTMLElement) {
|
|
629
739
|
gitButton.hidden = !isCapabilityAvailable("git");
|
|
@@ -1225,7 +1335,13 @@
|
|
|
1225
1335
|
latestViewerStateSignature = nextSignature;
|
|
1226
1336
|
latestItems = updateStoredActivity(Array.isArray(payload.items) ? payload.items : []);
|
|
1227
1337
|
if (!autoRefreshIntervalTouched) {
|
|
1228
|
-
|
|
1338
|
+
const launchSeconds = Number(payload.autoRefreshIntervalSeconds);
|
|
1339
|
+
const preferredSeconds = preferredAutoRefreshIntervalSeconds();
|
|
1340
|
+
autoRefreshIntervalForcedByLaunch = Boolean(payload.autoRefreshIntervalForced);
|
|
1341
|
+
const nextSeconds = autoRefreshIntervalForcedByLaunch || preferredSeconds === null
|
|
1342
|
+
? launchSeconds
|
|
1343
|
+
: preferredSeconds;
|
|
1344
|
+
autoRefreshIntervalMs = normalizeAutoRefreshIntervalSeconds(nextSeconds) * 1000;
|
|
1229
1345
|
updateRefreshIntervalControl();
|
|
1230
1346
|
}
|
|
1231
1347
|
updateRepositoryIdentity(payload);
|
|
@@ -1302,6 +1418,12 @@
|
|
|
1302
1418
|
return Boolean(panel && !panel.hidden && title && title.textContent === "Git status");
|
|
1303
1419
|
}
|
|
1304
1420
|
|
|
1421
|
+
function isWorkspaceOpen() {
|
|
1422
|
+
const panel = documentPanel();
|
|
1423
|
+
const title = documentTitle();
|
|
1424
|
+
return Boolean(panel && !panel.hidden && title && title.textContent === "Explorer");
|
|
1425
|
+
}
|
|
1426
|
+
|
|
1305
1427
|
function isCdxStatusOpen() {
|
|
1306
1428
|
const panel = documentPanel();
|
|
1307
1429
|
const title = documentTitle();
|
|
@@ -1328,7 +1450,11 @@
|
|
|
1328
1450
|
|
|
1329
1451
|
async function refreshViewer(method = "POST", options = {}) {
|
|
1330
1452
|
const changed = await loadItems(method, options);
|
|
1331
|
-
if (
|
|
1453
|
+
if (isWorkspaceOpen()) {
|
|
1454
|
+
if (changed || options.force) {
|
|
1455
|
+
await showWorkspace({ silent: Boolean(options.silent) });
|
|
1456
|
+
}
|
|
1457
|
+
} else if (isGitStatusOpen()) {
|
|
1332
1458
|
await showGitStatus({ preserve: true, silent: Boolean(options.silent), skipUnchanged: !changed && !options.force, force: Boolean(options.force) });
|
|
1333
1459
|
} else if (isCiStatusOpen()) {
|
|
1334
1460
|
await showCiStatus({ silent: Boolean(options.silent), skipUnchanged: !changed && !options.force, force: Boolean(options.force) });
|
|
@@ -2045,6 +2171,145 @@
|
|
|
2045
2171
|
setMeta("Health loaded.");
|
|
2046
2172
|
}
|
|
2047
2173
|
|
|
2174
|
+
function workspaceParentPath(path) {
|
|
2175
|
+
const parts = String(path || "").split("/").filter(Boolean);
|
|
2176
|
+
parts.pop();
|
|
2177
|
+
return parts.join("/");
|
|
2178
|
+
}
|
|
2179
|
+
|
|
2180
|
+
function renderWorkspaceTree(treePayload, selectedPath = "") {
|
|
2181
|
+
if (!treePayload || treePayload.state !== "ok") {
|
|
2182
|
+
return `<div class="viewer-workspace__empty">${escapeHtml(treePayload?.message || "Workspace tree is unavailable.")}</div>`;
|
|
2183
|
+
}
|
|
2184
|
+
const currentPath = String(treePayload.path || "");
|
|
2185
|
+
const parentPath = workspaceParentPath(currentPath);
|
|
2186
|
+
const upButton = currentPath
|
|
2187
|
+
? `<button class="viewer-workspace__item viewer-workspace__item--up" type="button" data-viewer-workspace-tree="${escapeHtml(parentPath)}">..</button>`
|
|
2188
|
+
: "";
|
|
2189
|
+
const rows = (Array.isArray(treePayload.entries) ? treePayload.entries : []).map((entry) => {
|
|
2190
|
+
const path = String(entry.path || "");
|
|
2191
|
+
const kind = String(entry.kind || "file");
|
|
2192
|
+
const ignored = Boolean(entry.ignored);
|
|
2193
|
+
const selected = path === selectedPath;
|
|
2194
|
+
const actionAttr = kind === "directory" && !ignored
|
|
2195
|
+
? `data-viewer-workspace-tree="${escapeHtml(path)}"`
|
|
2196
|
+
: `data-viewer-workspace-preview="${escapeHtml(path)}"`;
|
|
2197
|
+
const icon = kind === "directory" ? (ignored ? "x" : ">") : "-";
|
|
2198
|
+
return `
|
|
2199
|
+
<button class="viewer-workspace__item${selected ? " is-selected" : ""}${ignored ? " is-muted" : ""}" type="button" ${actionAttr} title="${escapeHtml(path)}">
|
|
2200
|
+
<span class="viewer-workspace__item-icon" aria-hidden="true">${escapeHtml(icon)}</span>
|
|
2201
|
+
<span class="viewer-workspace__item-name">${escapeHtml(entry.name || path || "/")}</span>
|
|
2202
|
+
</button>
|
|
2203
|
+
`;
|
|
2204
|
+
}).join("");
|
|
2205
|
+
return `
|
|
2206
|
+
<div class="viewer-workspace__tree-header">
|
|
2207
|
+
<span>${escapeHtml(currentPath || "/")}</span>
|
|
2208
|
+
</div>
|
|
2209
|
+
<div class="viewer-workspace__tree-list">
|
|
2210
|
+
${upButton}
|
|
2211
|
+
${rows || '<div class="viewer-workspace__empty">Directory is empty.</div>'}
|
|
2212
|
+
</div>
|
|
2213
|
+
${treePayload.truncated ? '<div class="viewer-workspace__empty">Directory listing truncated.</div>' : ""}
|
|
2214
|
+
`;
|
|
2215
|
+
}
|
|
2216
|
+
|
|
2217
|
+
function renderWorkspacePreview(previewPayload) {
|
|
2218
|
+
if (!previewPayload) {
|
|
2219
|
+
return '<div class="viewer-workspace__empty">Select a file or directory.</div>';
|
|
2220
|
+
}
|
|
2221
|
+
const path = previewPayload.path || "/";
|
|
2222
|
+
const name = previewPayload.name || path || "/";
|
|
2223
|
+
const state = previewPayload.state || "unknown";
|
|
2224
|
+
if (state === "ok") {
|
|
2225
|
+
return `
|
|
2226
|
+
<div class="viewer-workspace__preview-header">
|
|
2227
|
+
<div><strong>${escapeHtml(name)}</strong><span>${escapeHtml(path)}</span></div>
|
|
2228
|
+
<em>${escapeHtml(previewPayload.truncated ? "truncated" : `${previewPayload.size || 0} bytes`)}</em>
|
|
2229
|
+
</div>
|
|
2230
|
+
${previewPayload.truncated ? '<div class="viewer-cdx__state viewer-cdx__state--warn">Preview truncated.</div>' : ""}
|
|
2231
|
+
<pre class="viewer-workspace__code">${escapeHtml(previewPayload.content || "")}</pre>
|
|
2232
|
+
`;
|
|
2233
|
+
}
|
|
2234
|
+
if (state === "image") {
|
|
2235
|
+
return `
|
|
2236
|
+
<div class="viewer-workspace__preview-header">
|
|
2237
|
+
<div><strong>${escapeHtml(name)}</strong><span>${escapeHtml(path)}</span></div>
|
|
2238
|
+
<em>${escapeHtml(previewPayload.contentType || "image")}</em>
|
|
2239
|
+
</div>
|
|
2240
|
+
<img class="viewer-workspace__image" src="/api/workspace-file?path=${encodeURIComponent(path)}" alt="${escapeHtml(name)}">
|
|
2241
|
+
`;
|
|
2242
|
+
}
|
|
2243
|
+
return `
|
|
2244
|
+
<div class="viewer-workspace__preview-header">
|
|
2245
|
+
<div><strong>${escapeHtml(name)}</strong><span>${escapeHtml(path)}</span></div>
|
|
2246
|
+
<em>${escapeHtml(state)}</em>
|
|
2247
|
+
</div>
|
|
2248
|
+
<div class="viewer-workspace__empty">${escapeHtml(previewPayload.message || "No preview is available.")}</div>
|
|
2249
|
+
`;
|
|
2250
|
+
}
|
|
2251
|
+
|
|
2252
|
+
function renderWorkspace(treePayload, previewPayload) {
|
|
2253
|
+
const selectedPath = previewPayload?.path || "";
|
|
2254
|
+
return `
|
|
2255
|
+
<div class="viewer-workspace">
|
|
2256
|
+
<aside class="viewer-workspace__tree" aria-label="Workspace files">
|
|
2257
|
+
${renderWorkspaceTree(treePayload, selectedPath)}
|
|
2258
|
+
</aside>
|
|
2259
|
+
<section class="viewer-workspace__preview" aria-label="Workspace preview">
|
|
2260
|
+
${renderWorkspacePreview(previewPayload)}
|
|
2261
|
+
</section>
|
|
2262
|
+
</div>
|
|
2263
|
+
`;
|
|
2264
|
+
}
|
|
2265
|
+
|
|
2266
|
+
async function fetchWorkspaceTree(path = "") {
|
|
2267
|
+
const response = await fetch(`/api/workspace-tree?path=${encodeURIComponent(path)}`);
|
|
2268
|
+
const data = await response.json();
|
|
2269
|
+
if (!response.ok || !data.ok) {
|
|
2270
|
+
throw new Error(data.error || "Unable to load workspace tree.");
|
|
2271
|
+
}
|
|
2272
|
+
return data.payload;
|
|
2273
|
+
}
|
|
2274
|
+
|
|
2275
|
+
async function fetchWorkspacePreview(path = "") {
|
|
2276
|
+
const response = await fetch(`/api/workspace-preview?path=${encodeURIComponent(path)}`);
|
|
2277
|
+
const data = await response.json();
|
|
2278
|
+
if (!response.ok || !data.ok) {
|
|
2279
|
+
throw new Error(data.error || "Unable to load workspace preview.");
|
|
2280
|
+
}
|
|
2281
|
+
return data.payload;
|
|
2282
|
+
}
|
|
2283
|
+
|
|
2284
|
+
async function showWorkspace(options = {}) {
|
|
2285
|
+
if (!isCapabilityAvailable("workspace")) {
|
|
2286
|
+
const message = capabilityMessage("workspace", "Explorer is not available for this project.");
|
|
2287
|
+
setDocument("Explorer", renderWorkspace({ state: "unavailable", message }, { state: "unavailable", message }));
|
|
2288
|
+
setMeta(message);
|
|
2289
|
+
return;
|
|
2290
|
+
}
|
|
2291
|
+
if (!options.silent) {
|
|
2292
|
+
setMeta("Loading workspace...");
|
|
2293
|
+
}
|
|
2294
|
+
const tree = await fetchWorkspaceTree("");
|
|
2295
|
+
const preview = await fetchWorkspacePreview("");
|
|
2296
|
+
setDocument("Explorer", renderWorkspace(tree, preview));
|
|
2297
|
+
setMeta(options.silent ? "Explorer refreshed." : "Explorer loaded.");
|
|
2298
|
+
}
|
|
2299
|
+
|
|
2300
|
+
async function openWorkspaceTree(path) {
|
|
2301
|
+
const [tree, preview] = await Promise.all([fetchWorkspaceTree(path), fetchWorkspacePreview(path)]);
|
|
2302
|
+
setDocument("Explorer", renderWorkspace(tree, preview));
|
|
2303
|
+
setMeta(path ? `Explorer folder ${path}` : "Explorer root.");
|
|
2304
|
+
}
|
|
2305
|
+
|
|
2306
|
+
async function openWorkspacePreview(path) {
|
|
2307
|
+
const treePath = workspaceParentPath(path);
|
|
2308
|
+
const [tree, preview] = await Promise.all([fetchWorkspaceTree(treePath), fetchWorkspacePreview(path)]);
|
|
2309
|
+
setDocument("Explorer", renderWorkspace(tree, preview));
|
|
2310
|
+
setMeta(`Previewing ${path || "workspace root"}.`);
|
|
2311
|
+
}
|
|
2312
|
+
|
|
2048
2313
|
function objectEntries(value) {
|
|
2049
2314
|
return value && typeof value === "object" && !Array.isArray(value) ? Object.entries(value) : [];
|
|
2050
2315
|
}
|
|
@@ -2398,31 +2663,108 @@
|
|
|
2398
2663
|
return explicit === true ? "YES" : "-";
|
|
2399
2664
|
}
|
|
2400
2665
|
|
|
2666
|
+
function cdxProviderName(item) {
|
|
2667
|
+
return String(cdxField(item, ["provider", "name"], "unknown") || "unknown");
|
|
2668
|
+
}
|
|
2669
|
+
|
|
2670
|
+
function cdxKnownProviders(status, providers, sessions) {
|
|
2671
|
+
const names = new Set();
|
|
2672
|
+
providers.forEach((provider) => {
|
|
2673
|
+
const name = cdxProviderName(provider);
|
|
2674
|
+
if (name) {
|
|
2675
|
+
names.add(name);
|
|
2676
|
+
}
|
|
2677
|
+
});
|
|
2678
|
+
sessions.forEach((session) => {
|
|
2679
|
+
const name = cdxProviderName(session);
|
|
2680
|
+
if (name) {
|
|
2681
|
+
names.add(name);
|
|
2682
|
+
}
|
|
2683
|
+
});
|
|
2684
|
+
pickFirstArray(status, ["providers", "providerStatus", "provider_status"]).forEach((provider) => {
|
|
2685
|
+
const name = cdxProviderName(provider);
|
|
2686
|
+
if (name) {
|
|
2687
|
+
names.add(name);
|
|
2688
|
+
}
|
|
2689
|
+
});
|
|
2690
|
+
return Array.from(names).sort((left, right) => left.localeCompare(right));
|
|
2691
|
+
}
|
|
2692
|
+
|
|
2693
|
+
function filterCdxEntriesByProvider(entries, providerFilter) {
|
|
2694
|
+
if (providerFilter.mode !== "subset" || !providerFilter.selected.length) {
|
|
2695
|
+
return entries;
|
|
2696
|
+
}
|
|
2697
|
+
const selected = new Set(providerFilter.selected);
|
|
2698
|
+
return entries.filter((entry) => selected.has(cdxProviderName(entry)));
|
|
2699
|
+
}
|
|
2700
|
+
|
|
2701
|
+
function renderCdxStatusControls(knownProviders, visibleColumns, providerFilter) {
|
|
2702
|
+
const columnRows = cdxStatusColumns.map((column) => `
|
|
2703
|
+
<label class="viewer-cdx__menu-check">
|
|
2704
|
+
<input type="checkbox" data-viewer-cdx-column="${escapeHtml(column.id)}"${visibleColumns[column.id] ? " checked" : ""}>
|
|
2705
|
+
<span>${escapeHtml(column.label)}</span>
|
|
2706
|
+
</label>
|
|
2707
|
+
`).join("");
|
|
2708
|
+
const selected = new Set(providerFilter.mode === "subset" ? providerFilter.selected : knownProviders);
|
|
2709
|
+
const providerRows = knownProviders.map((provider) => `
|
|
2710
|
+
<label class="viewer-cdx__menu-check">
|
|
2711
|
+
<input type="checkbox" data-viewer-cdx-provider="${escapeHtml(provider)}"${selected.has(provider) ? " checked" : ""}>
|
|
2712
|
+
<span>${escapeHtml(provider)}</span>
|
|
2713
|
+
</label>
|
|
2714
|
+
`).join("");
|
|
2715
|
+
const providerSummary = providerFilter.mode === "subset" && providerFilter.selected.length
|
|
2716
|
+
? `${providerFilter.selected.length}/${knownProviders.length || providerFilter.selected.length}`
|
|
2717
|
+
: "All";
|
|
2718
|
+
return `
|
|
2719
|
+
<div class="viewer-cdx__controls" aria-label="CDX status table controls">
|
|
2720
|
+
<details class="viewer-cdx__menu">
|
|
2721
|
+
<summary class="viewer-cdx__icon-button" title="Configure status columns" aria-label="Configure status columns">
|
|
2722
|
+
<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="M12 8.5a3.5 3.5 0 1 0 0 7 3.5 3.5 0 0 0 0-7Z" fill="none" stroke="currentColor" stroke-width="1.8"/><path d="M19.4 15a1.7 1.7 0 0 0 .3 1.9l.1.1-2 3.4-.2-.1a1.7 1.7 0 0 0-2 .1 1.7 1.7 0 0 0-.8 1.7v.2H9.2v-.2a1.7 1.7 0 0 0-.8-1.7 1.7 1.7 0 0 0-2-.1l-.2.1-2-3.4.1-.1a1.7 1.7 0 0 0 .3-1.9 1.7 1.7 0 0 0-1.5-1.1H3v-3.8h.1A1.7 1.7 0 0 0 4.6 9a1.7 1.7 0 0 0-.3-1.9l-.1-.1 2-3.4.2.1a1.7 1.7 0 0 0 2-.1 1.7 1.7 0 0 0 .8-1.7v-.2h5.6v.2a1.7 1.7 0 0 0 .8 1.7 1.7 1.7 0 0 0 2 .1l.2-.1 2 3.4-.1.1a1.7 1.7 0 0 0-.3 1.9 1.7 1.7 0 0 0 1.5 1.1h.1v3.8h-.1a1.7 1.7 0 0 0-1.5 1.1Z" fill="none" stroke="currentColor" stroke-width="1.4" stroke-linejoin="round"/></svg>
|
|
2723
|
+
</summary>
|
|
2724
|
+
<div class="viewer-cdx__menu-panel" role="menu" aria-label="CDX status columns">${columnRows}</div>
|
|
2725
|
+
</details>
|
|
2726
|
+
<details class="viewer-cdx__menu">
|
|
2727
|
+
<summary class="viewer-cdx__icon-button" title="Filter status providers" aria-label="Filter status providers">
|
|
2728
|
+
<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="M4 6h16l-6.5 7.2V19l-3 1.5v-7.3z" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linejoin="round" stroke-linecap="round"/></svg>
|
|
2729
|
+
<span class="viewer-cdx__icon-count">${escapeHtml(providerSummary)}</span>
|
|
2730
|
+
</summary>
|
|
2731
|
+
<div class="viewer-cdx__menu-panel" role="menu" aria-label="CDX provider filter">
|
|
2732
|
+
<button class="viewer-cdx__menu-action" type="button" data-viewer-cdx-provider-all>All providers</button>
|
|
2733
|
+
${providerRows || '<div class="viewer-cdx__empty">No providers reported.</div>'}
|
|
2734
|
+
</div>
|
|
2735
|
+
</details>
|
|
2736
|
+
</div>
|
|
2737
|
+
`;
|
|
2738
|
+
}
|
|
2739
|
+
|
|
2401
2740
|
function renderCdxSessionTable(sessions, emptyText) {
|
|
2402
2741
|
if (!sessions.length) {
|
|
2403
2742
|
return `<div class="viewer-cdx__empty">${escapeHtml(emptyText)}</div>`;
|
|
2404
2743
|
}
|
|
2744
|
+
const visibleColumns = cdxColumnVisibilityPreference();
|
|
2745
|
+
const cellRenderers = {
|
|
2746
|
+
session: (item) => {
|
|
2747
|
+
const name = cdxField(item, ["session_name", "name", "id", "value"]);
|
|
2748
|
+
return `<td class="viewer-cdx__session-name">${escapeHtml(`${name}${item.active ? "*" : ""}`)}</td>`;
|
|
2749
|
+
},
|
|
2750
|
+
provider: (item) => `<td>${escapeHtml(cdxField(item, ["provider"], "-"))}</td>`,
|
|
2751
|
+
status: (item) => `<td>${renderCdxBadge(cdxField(item, ["status", "state"]))}</td>`,
|
|
2752
|
+
auth: (item) => `<td>${escapeHtml(String(cdxField(item, ["auth_status", "authStatus"], "-")).replace("authenticated", "logged"))}</td>`,
|
|
2753
|
+
ok: (item) => `<td>${renderCdxRemainingPill(item) || escapeHtml(cdxPct(cdxField(item, ["available_pct", "availablePct"], NaN)))}</td>`,
|
|
2754
|
+
remaining5h: (item) => `<td>${escapeHtml(cdxPct(cdxField(item, ["remaining_5h_pct", "remaining5hPct"], NaN)))}</td>`,
|
|
2755
|
+
remainingWeek: (item) => `<td>${escapeHtml(cdxPct(cdxField(item, ["remaining_week_pct", "remainingWeekPct"], NaN)))}</td>`,
|
|
2756
|
+
block: (item) => `<td>${escapeHtml(cdxSessionBlock(item))}</td>`,
|
|
2757
|
+
credits: (item) => `<td>${escapeHtml(formatCdxCredits(cdxField(item, ["credits", "cr"], "-")))}</td>`,
|
|
2758
|
+
reset5h: (item) => `<td>${escapeHtml(formatCdxResetAt(cdxField(item, ["reset_5h_at", "reset5hAt", "reset_at", "resetAt"], "")))}</td>`,
|
|
2759
|
+
resetWeek: (item) => `<td>${escapeHtml(formatCdxResetAt(cdxField(item, ["reset_week_at", "resetWeekAt", "reset_at", "resetAt"], "")))}</td>`,
|
|
2760
|
+
updated: (item) => `<td>${escapeHtml(formatCdxResetAt(cdxField(item, ["updated_at", "updatedAt"], "")))}</td>`
|
|
2761
|
+
};
|
|
2762
|
+
const activeColumns = cdxStatusColumns.filter((column) => visibleColumns[column.id]);
|
|
2405
2763
|
const rows = sessions.slice(0, 24).map((entry) => {
|
|
2406
2764
|
const item = entry && typeof entry === "object" ? entry : { value: entry };
|
|
2407
|
-
const name = cdxField(item, ["session_name", "name", "id", "value"]);
|
|
2408
|
-
const sessionName = `${name}${item.active ? "*" : ""}`;
|
|
2409
|
-
const status = cdxField(item, ["status", "state"]);
|
|
2410
|
-
const auth = String(cdxField(item, ["auth_status", "authStatus"], "-")).replace("authenticated", "logged");
|
|
2411
|
-
const block = cdxSessionBlock(item);
|
|
2412
2765
|
return `
|
|
2413
2766
|
<tr>
|
|
2414
|
-
|
|
2415
|
-
<td>${escapeHtml(cdxField(item, ["provider"], "-"))}</td>
|
|
2416
|
-
<td>${renderCdxBadge(status)}</td>
|
|
2417
|
-
<td>${escapeHtml(auth)}</td>
|
|
2418
|
-
<td>${renderCdxRemainingPill(item) || escapeHtml(cdxPct(cdxField(item, ["available_pct", "availablePct"], NaN)))}</td>
|
|
2419
|
-
<td>${escapeHtml(cdxPct(cdxField(item, ["remaining_5h_pct", "remaining5hPct"], NaN)))}</td>
|
|
2420
|
-
<td>${escapeHtml(cdxPct(cdxField(item, ["remaining_week_pct", "remainingWeekPct"], NaN)))}</td>
|
|
2421
|
-
<td>${escapeHtml(block)}</td>
|
|
2422
|
-
<td>${escapeHtml(formatCdxCredits(cdxField(item, ["credits", "cr"], "-")))}</td>
|
|
2423
|
-
<td>${escapeHtml(formatCdxResetAt(cdxField(item, ["reset_5h_at", "reset5hAt", "reset_at", "resetAt"], "")))}</td>
|
|
2424
|
-
<td>${escapeHtml(formatCdxResetAt(cdxField(item, ["reset_week_at", "resetWeekAt", "reset_at", "resetAt"], "")))}</td>
|
|
2425
|
-
<td>${escapeHtml(formatCdxResetAt(cdxField(item, ["updated_at", "updatedAt"], "")))}</td>
|
|
2767
|
+
${activeColumns.map((column) => cellRenderers[column.id](item)).join("")}
|
|
2426
2768
|
</tr>
|
|
2427
2769
|
`;
|
|
2428
2770
|
}).join("");
|
|
@@ -2431,18 +2773,7 @@
|
|
|
2431
2773
|
<table class="viewer-cdx__table">
|
|
2432
2774
|
<thead>
|
|
2433
2775
|
<tr>
|
|
2434
|
-
|
|
2435
|
-
<th>PROV.</th>
|
|
2436
|
-
<th>STATUS</th>
|
|
2437
|
-
<th>AUTH</th>
|
|
2438
|
-
<th>OK</th>
|
|
2439
|
-
<th>5H</th>
|
|
2440
|
-
<th>WEEK</th>
|
|
2441
|
-
<th>BLOCK</th>
|
|
2442
|
-
<th>CR</th>
|
|
2443
|
-
<th>RESET 5H</th>
|
|
2444
|
-
<th>RESET WEEK</th>
|
|
2445
|
-
<th>UPDATED</th>
|
|
2776
|
+
${activeColumns.map((column) => `<th>${escapeHtml(column.label)}</th>`).join("")}
|
|
2446
2777
|
</tr>
|
|
2447
2778
|
</thead>
|
|
2448
2779
|
<tbody>${rows}</tbody>
|
|
@@ -2718,8 +3049,12 @@
|
|
|
2718
3049
|
`;
|
|
2719
3050
|
}
|
|
2720
3051
|
const status = payload.status || {};
|
|
2721
|
-
const
|
|
2722
|
-
const
|
|
3052
|
+
const allProviders = cdxProviders(status);
|
|
3053
|
+
const allSessions = cdxSessions(status);
|
|
3054
|
+
const providerFilter = cdxProviderFilterPreference();
|
|
3055
|
+
const knownProviders = cdxKnownProviders(status, allProviders, allSessions);
|
|
3056
|
+
const providers = filterCdxEntriesByProvider(allProviders, providerFilter);
|
|
3057
|
+
const sessions = filterCdxEntriesByProvider(allSessions, providerFilter);
|
|
2723
3058
|
const readiness = cdxReadiness(status);
|
|
2724
3059
|
const commands = pickFirstArray(status, ["nextCommands", "next_commands", "safeCommands", "safe_commands", "commands"])
|
|
2725
3060
|
.map((entry) => typeof entry === "string" ? entry : (entry.command || entry.value || entry.name || ""))
|
|
@@ -2750,6 +3085,7 @@
|
|
|
2750
3085
|
<div class="viewer-cdx">
|
|
2751
3086
|
${renderCdxModeSwitcher("status")}
|
|
2752
3087
|
<div class="viewer-cdx__summary">${cards}</div>
|
|
3088
|
+
${renderCdxStatusControls(knownProviders, cdxColumnVisibilityPreference(), providerFilter)}
|
|
2753
3089
|
<div class="viewer-cdx__workspace">
|
|
2754
3090
|
<div class="viewer-cdx__stack">
|
|
2755
3091
|
<section class="viewer-cdx__section">
|
|
@@ -2776,6 +3112,12 @@
|
|
|
2776
3112
|
`;
|
|
2777
3113
|
}
|
|
2778
3114
|
|
|
3115
|
+
function rerenderCdxStatusFromPreferences() {
|
|
3116
|
+
if (isCdxStatusOpen() && latestCdxStatusPayload) {
|
|
3117
|
+
setDocument("CDX status", renderCdxStatus(latestCdxStatusPayload));
|
|
3118
|
+
}
|
|
3119
|
+
}
|
|
3120
|
+
|
|
2779
3121
|
function renderCdxRuns(payload) {
|
|
2780
3122
|
if (!payload || payload.state !== "ok") {
|
|
2781
3123
|
return `
|
|
@@ -3106,6 +3448,7 @@
|
|
|
3106
3448
|
return;
|
|
3107
3449
|
}
|
|
3108
3450
|
latestCdxStatusSignature = nextCdxSignature;
|
|
3451
|
+
latestCdxStatusPayload = data.payload;
|
|
3109
3452
|
updateMainCdxBadge(data.payload);
|
|
3110
3453
|
setDocument("CDX status", renderCdxStatus(data.payload));
|
|
3111
3454
|
setMeta(options.silent ? "CDX status refreshed." : "CDX status loaded.");
|
|
@@ -3820,6 +4163,12 @@
|
|
|
3820
4163
|
withPrimaryAction("insights", "Loading insights", showCorpusInsights);
|
|
3821
4164
|
});
|
|
3822
4165
|
});
|
|
4166
|
+
[workspaceButton()].forEach((button) => {
|
|
4167
|
+
button?.addEventListener("click", () => {
|
|
4168
|
+
setRefreshMenuOpen(false);
|
|
4169
|
+
withPrimaryAction("workspace", "Loading Explorer", showWorkspace);
|
|
4170
|
+
});
|
|
4171
|
+
});
|
|
3823
4172
|
const autoControl = autoRefreshControl();
|
|
3824
4173
|
if (autoControl instanceof HTMLInputElement) {
|
|
3825
4174
|
autoControl.addEventListener("change", () => {
|
|
@@ -3914,6 +4263,8 @@
|
|
|
3914
4263
|
document.addEventListener("change", (event) => {
|
|
3915
4264
|
const sessionTarget = event.target instanceof Element ? event.target.closest("[data-viewer-cdx-session]") : null;
|
|
3916
4265
|
const cdxInputTarget = event.target instanceof Element ? event.target.closest("[data-viewer-cdx-input]") : null;
|
|
4266
|
+
const cdxColumnTarget = event.target instanceof Element ? event.target.closest("[data-viewer-cdx-column]") : null;
|
|
4267
|
+
const cdxProviderTarget = event.target instanceof Element ? event.target.closest("[data-viewer-cdx-provider]") : null;
|
|
3917
4268
|
if (sessionTarget instanceof HTMLSelectElement) {
|
|
3918
4269
|
latestCdxMissionState.sessionId = sessionTarget.value || "";
|
|
3919
4270
|
latestCdxMissionState.planPayload = null;
|
|
@@ -3930,6 +4281,25 @@
|
|
|
3930
4281
|
latestCdxMissionState.applyPayload = null;
|
|
3931
4282
|
}
|
|
3932
4283
|
}
|
|
4284
|
+
if (cdxColumnTarget instanceof HTMLInputElement) {
|
|
4285
|
+
persistCdxColumnVisibility(cdxColumnTarget.getAttribute("data-viewer-cdx-column") || "", cdxColumnTarget.checked);
|
|
4286
|
+
rerenderCdxStatusFromPreferences();
|
|
4287
|
+
}
|
|
4288
|
+
if (cdxProviderTarget instanceof HTMLInputElement) {
|
|
4289
|
+
const provider = cdxProviderTarget.getAttribute("data-viewer-cdx-provider") || "";
|
|
4290
|
+
const status = latestCdxStatusPayload?.status || {};
|
|
4291
|
+
const allProviders = cdxKnownProviders(status, cdxProviders(status), cdxSessions(status));
|
|
4292
|
+
const current = cdxProviderFilterPreference();
|
|
4293
|
+
const selected = new Set(current.mode === "subset" ? current.selected : allProviders);
|
|
4294
|
+
if (cdxProviderTarget.checked) {
|
|
4295
|
+
selected.add(provider);
|
|
4296
|
+
} else {
|
|
4297
|
+
selected.delete(provider);
|
|
4298
|
+
}
|
|
4299
|
+
const nextSelected = Array.from(selected).filter((entry) => allProviders.includes(entry));
|
|
4300
|
+
persistCdxProviderFilter(nextSelected.length === allProviders.length ? { mode: "all", selected: [] } : { mode: "subset", selected: nextSelected });
|
|
4301
|
+
rerenderCdxStatusFromPreferences();
|
|
4302
|
+
}
|
|
3933
4303
|
});
|
|
3934
4304
|
document.addEventListener("click", (event) => {
|
|
3935
4305
|
window.setTimeout(() => applyLocalViewerChrome(), 0);
|
|
@@ -3940,12 +4310,15 @@
|
|
|
3940
4310
|
const gitHistoryRevealTarget = event.target instanceof Element ? event.target.closest("[data-viewer-git-history-reveal]") : null;
|
|
3941
4311
|
const gitDomainTarget = event.target instanceof Element ? event.target.closest(".viewer-git__domain[data-viewer-git-domain]") : null;
|
|
3942
4312
|
const gitFileTarget = event.target instanceof Element ? event.target.closest("[data-viewer-git-file]") : null;
|
|
4313
|
+
const workspaceTreeTarget = event.target instanceof Element ? event.target.closest("[data-viewer-workspace-tree]") : null;
|
|
4314
|
+
const workspacePreviewTarget = event.target instanceof Element ? event.target.closest("[data-viewer-workspace-preview]") : null;
|
|
3943
4315
|
const projectSwitcherTarget = event.target instanceof Element ? event.target.closest("#viewer-repo-pill") : null;
|
|
3944
4316
|
const projectTarget = event.target instanceof Element ? event.target.closest("[data-viewer-project-id]") : null;
|
|
3945
4317
|
const cdxModeTarget = event.target instanceof Element ? event.target.closest("[data-viewer-cdx-mode]") : null;
|
|
3946
4318
|
const cdxBackRunsTarget = event.target instanceof Element ? event.target.closest("[data-viewer-cdx-back-runs]") : null;
|
|
3947
4319
|
const cdxReportTarget = event.target instanceof Element ? event.target.closest("[data-viewer-cdx-report]") : null;
|
|
3948
4320
|
const cdxArtifactTarget = event.target instanceof Element ? event.target.closest("[data-viewer-cdx-artifact-path]") : null;
|
|
4321
|
+
const cdxProviderAllTarget = event.target instanceof Element ? event.target.closest("[data-viewer-cdx-provider-all]") : null;
|
|
3949
4322
|
const cdxCreateRequestTarget = event.target instanceof Element ? event.target.closest("[data-viewer-cdx-create-request]") : null;
|
|
3950
4323
|
const cdxMissionTarget = event.target instanceof Element ? event.target.closest("[data-viewer-cdx-mission]") : null;
|
|
3951
4324
|
const cdxStrengthTarget = event.target instanceof Element ? event.target.closest("[data-viewer-cdx-strength]") : null;
|
|
@@ -3981,6 +4354,11 @@
|
|
|
3981
4354
|
withCdxMissionAction("cdx-apply-plan", "Applying CDX mission plan", applyCdxMissionPlan);
|
|
3982
4355
|
return;
|
|
3983
4356
|
}
|
|
4357
|
+
if (cdxProviderAllTarget instanceof HTMLElement) {
|
|
4358
|
+
persistCdxProviderFilter({ mode: "all", selected: [] });
|
|
4359
|
+
rerenderCdxStatusFromPreferences();
|
|
4360
|
+
return;
|
|
4361
|
+
}
|
|
3984
4362
|
if (cdxBackRunsTarget instanceof HTMLElement) {
|
|
3985
4363
|
withPrimaryAction("cdx-runs", "Loading CDX runs", showCdxRuns);
|
|
3986
4364
|
return;
|
|
@@ -4008,6 +4386,16 @@
|
|
|
4008
4386
|
}
|
|
4009
4387
|
return;
|
|
4010
4388
|
}
|
|
4389
|
+
if (workspaceTreeTarget instanceof HTMLElement) {
|
|
4390
|
+
event.preventDefault();
|
|
4391
|
+
withPrimaryAction("workspace-tree", "Loading Explorer folder", () => openWorkspaceTree(workspaceTreeTarget.getAttribute("data-viewer-workspace-tree") || ""));
|
|
4392
|
+
return;
|
|
4393
|
+
}
|
|
4394
|
+
if (workspacePreviewTarget instanceof HTMLElement) {
|
|
4395
|
+
event.preventDefault();
|
|
4396
|
+
withPrimaryAction("workspace-preview", "Loading Explorer preview", () => openWorkspacePreview(workspacePreviewTarget.getAttribute("data-viewer-workspace-preview") || ""));
|
|
4397
|
+
return;
|
|
4398
|
+
}
|
|
4011
4399
|
if (projectSwitcherTarget instanceof HTMLElement) {
|
|
4012
4400
|
const menu = projectMenu();
|
|
4013
4401
|
setProjectMenuOpen(Boolean(menu?.hidden));
|
|
@@ -37,6 +37,7 @@
|
|
|
37
37
|
<div class="viewer-topbar__meta" id="viewer-meta">Read-only local viewer</div>
|
|
38
38
|
</div>
|
|
39
39
|
<div class="viewer-topbar__actions">
|
|
40
|
+
<button class="btn" id="viewer-workspace" type="button" title="Show file explorer" hidden>Explorer</button>
|
|
40
41
|
<button class="btn" id="viewer-git" type="button" title="Show Git status">Git</button>
|
|
41
42
|
<button class="btn" id="viewer-ci" type="button" title="Show GitHub Actions CI status" hidden>CI</button>
|
|
42
43
|
<button class="btn" id="viewer-cdx" type="button" title="Show CDX status">CDX</button>
|