create-interview-cockpit 0.18.0 → 0.20.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/package.json +1 -1
- package/template/client/src/api.ts +101 -0
- package/template/client/src/components/GhaHistoryPanel.tsx +194 -0
- package/template/client/src/components/GhaJobsPanel.tsx +432 -0
- package/template/client/src/components/GithubActionsLabModal.tsx +583 -76
- package/template/client/src/components/LabsPanel.tsx +11 -1
- package/template/client/src/components/Sidebar.tsx +216 -59
- package/template/client/src/githubActionsLab.ts +239 -2
- package/template/client/src/store.ts +47 -0
- package/template/client/src/types.ts +6 -0
- package/template/client/tsconfig.tsbuildinfo +1 -1
- package/template/cockpit.json +1 -1
- package/template/server/src/gha-runner.ts +327 -1
- package/template/server/src/google-drive.ts +507 -125
- package/template/server/src/index.ts +87 -1
|
@@ -36,7 +36,7 @@ import {
|
|
|
36
36
|
runInfraAction,
|
|
37
37
|
streamInfraCommand,
|
|
38
38
|
} from "./infra-runner.js";
|
|
39
|
-
import { streamGhaCommand } from "./gha-runner.js";
|
|
39
|
+
import { streamGhaCommand, listGhaRuns, getGhaRun } from "./gha-runner.js";
|
|
40
40
|
|
|
41
41
|
const app = express();
|
|
42
42
|
app.use(cors());
|
|
@@ -373,6 +373,39 @@ app.post("/api/workspaces/:id/sync", async (req, res) => {
|
|
|
373
373
|
}
|
|
374
374
|
});
|
|
375
375
|
|
|
376
|
+
app.post("/api/workspaces/:id/topics/:topicId/sync", async (req, res) => {
|
|
377
|
+
try {
|
|
378
|
+
// Pull only this topic's Drive folder into the selected workspace.
|
|
379
|
+
storage.setActiveWorkspaceId(req.params.id);
|
|
380
|
+
const result = await googleDrive.syncTopic(
|
|
381
|
+
req.params.id,
|
|
382
|
+
req.params.topicId,
|
|
383
|
+
extractText,
|
|
384
|
+
);
|
|
385
|
+
if (result.errors.some((error) => /\b403\b/.test(error))) {
|
|
386
|
+
return res.json({
|
|
387
|
+
...result,
|
|
388
|
+
needsAuth: true,
|
|
389
|
+
authUrl: googleDrive.getExportAuthUrl(),
|
|
390
|
+
});
|
|
391
|
+
}
|
|
392
|
+
res.json(result);
|
|
393
|
+
} catch (err: any) {
|
|
394
|
+
if (err?.needsReauth || err?.message === "NEEDS_REAUTH") {
|
|
395
|
+
return res.json({
|
|
396
|
+
needsAuth: true,
|
|
397
|
+
authUrl: googleDrive.getExportAuthUrl(),
|
|
398
|
+
topicsUpserted: 0,
|
|
399
|
+
filesImported: 0,
|
|
400
|
+
filesSkipped: 0,
|
|
401
|
+
errors: ["Google Drive authorization is required to pull this topic."],
|
|
402
|
+
});
|
|
403
|
+
}
|
|
404
|
+
console.error("[sync-topic]", err);
|
|
405
|
+
res.status(500).json({ error: err?.message || "Topic sync failed" });
|
|
406
|
+
}
|
|
407
|
+
});
|
|
408
|
+
|
|
376
409
|
app.get("/api/workspaces/:id/drive-subfolders", async (req, res) => {
|
|
377
410
|
try {
|
|
378
411
|
const folders = await googleDrive.listDriveSubfolders(req.params.id);
|
|
@@ -452,6 +485,30 @@ app.post("/api/workspaces/:id/export-drive", async (req, res) => {
|
|
|
452
485
|
}
|
|
453
486
|
});
|
|
454
487
|
|
|
488
|
+
app.post(
|
|
489
|
+
"/api/workspaces/:id/topics/:topicId/export-drive",
|
|
490
|
+
async (req, res) => {
|
|
491
|
+
try {
|
|
492
|
+
if (!(await googleDrive.isExportAuthed())) {
|
|
493
|
+
return res.json({
|
|
494
|
+
needsAuth: true,
|
|
495
|
+
authUrl: googleDrive.getExportAuthUrl(),
|
|
496
|
+
});
|
|
497
|
+
}
|
|
498
|
+
const { targetFolderId } = req.body as { targetFolderId?: string };
|
|
499
|
+
const result = await googleDrive.exportTopic(
|
|
500
|
+
req.params.id,
|
|
501
|
+
req.params.topicId,
|
|
502
|
+
targetFolderId,
|
|
503
|
+
);
|
|
504
|
+
res.json(result);
|
|
505
|
+
} catch (err: any) {
|
|
506
|
+
console.error("[export-topic-drive]", err);
|
|
507
|
+
res.status(500).json({ error: err?.message || "Topic export failed" });
|
|
508
|
+
}
|
|
509
|
+
},
|
|
510
|
+
);
|
|
511
|
+
|
|
455
512
|
app.post("/api/drive/repair-permissions", async (req, res) => {
|
|
456
513
|
try {
|
|
457
514
|
const { folderId } = req.body as { folderId: string };
|
|
@@ -1518,6 +1575,35 @@ app.post("/api/gha/run-stream", async (req, res) => {
|
|
|
1518
1575
|
res.end();
|
|
1519
1576
|
});
|
|
1520
1577
|
|
|
1578
|
+
// List historical act runs, optionally scoped to a lab file or question.
|
|
1579
|
+
app.get("/api/gha/runs", async (req, res) => {
|
|
1580
|
+
try {
|
|
1581
|
+
const { questionId, fileId, limit } = req.query as {
|
|
1582
|
+
questionId?: string;
|
|
1583
|
+
fileId?: string;
|
|
1584
|
+
limit?: string;
|
|
1585
|
+
};
|
|
1586
|
+
const parsedLimit = limit ? Number(limit) : undefined;
|
|
1587
|
+
const runs = await listGhaRuns({
|
|
1588
|
+
questionId,
|
|
1589
|
+
fileId,
|
|
1590
|
+
...(Number.isFinite(parsedLimit) ? { limit: parsedLimit } : {}),
|
|
1591
|
+
});
|
|
1592
|
+
res.json(runs);
|
|
1593
|
+
} catch (err: any) {
|
|
1594
|
+
res.status(500).json({ error: err?.message || "Failed to list gha runs" });
|
|
1595
|
+
}
|
|
1596
|
+
});
|
|
1597
|
+
|
|
1598
|
+
app.get("/api/gha/runs/:runId", async (req, res) => {
|
|
1599
|
+
try {
|
|
1600
|
+
const run = await getGhaRun(req.params.runId);
|
|
1601
|
+
res.json(run);
|
|
1602
|
+
} catch {
|
|
1603
|
+
res.status(404).json({ error: "GHA run not found" });
|
|
1604
|
+
}
|
|
1605
|
+
});
|
|
1606
|
+
|
|
1521
1607
|
// Link an existing file to a topic without re-uploading
|
|
1522
1608
|
app.post("/api/topics/:topicId/context-files/link", async (req, res) => {
|
|
1523
1609
|
const { fileId, originalName } = req.body as {
|