feat(booterm): add PTY session registry + listing endpoint
In-memory SessionMeta registry tracks active terminal sessions with paneId, sessionId, projectPath, title, createdAt, lastActivityAt. GET /api/term/sessions returns all active sessions as JSON array. Registry is updated on WS attach and cleaned up on disconnect.
This commit is contained in:
@@ -4,6 +4,7 @@ import { loadConfig } from './config.js';
|
|||||||
import { getPool, closeDb } from './db.js';
|
import { getPool, closeDb } from './db.js';
|
||||||
import { registerHealthRoutes } from './routes/health.js';
|
import { registerHealthRoutes } from './routes/health.js';
|
||||||
import { registerTerminalRoutes } from './routes/terminals.js';
|
import { registerTerminalRoutes } from './routes/terminals.js';
|
||||||
|
import { registerSessionRoutes } from './routes/sessions.js';
|
||||||
import { registerWsAttachRoute } from './ws/attach.js';
|
import { registerWsAttachRoute } from './ws/attach.js';
|
||||||
|
|
||||||
async function main(): Promise<void> {
|
async function main(): Promise<void> {
|
||||||
@@ -33,6 +34,7 @@ async function main(): Promise<void> {
|
|||||||
|
|
||||||
registerHealthRoutes(app);
|
registerHealthRoutes(app);
|
||||||
registerTerminalRoutes(app, config.TMUX_CONF_PATH);
|
registerTerminalRoutes(app, config.TMUX_CONF_PATH);
|
||||||
|
registerSessionRoutes(app);
|
||||||
registerWsAttachRoute(app, config.TMUX_CONF_PATH);
|
registerWsAttachRoute(app, config.TMUX_CONF_PATH);
|
||||||
|
|
||||||
const shutdown = async (signal: string) => {
|
const shutdown = async (signal: string) => {
|
||||||
|
|||||||
44
apps/booterm/src/pty/registry.ts
Normal file
44
apps/booterm/src/pty/registry.ts
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
export interface SessionMeta {
|
||||||
|
paneId: string;
|
||||||
|
sessionId: string;
|
||||||
|
projectPath: string;
|
||||||
|
title?: string;
|
||||||
|
createdAt: Date;
|
||||||
|
lastActivityAt: Date;
|
||||||
|
}
|
||||||
|
|
||||||
|
const sessions = new Map<string, SessionMeta>();
|
||||||
|
|
||||||
|
export function register(
|
||||||
|
sessionId: string,
|
||||||
|
paneId: string,
|
||||||
|
projectPath: string,
|
||||||
|
title?: string,
|
||||||
|
): void {
|
||||||
|
const now = new Date();
|
||||||
|
const existing = sessions.get(paneId);
|
||||||
|
if (existing) {
|
||||||
|
existing.lastActivityAt = now;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
sessions.set(paneId, {
|
||||||
|
paneId,
|
||||||
|
sessionId,
|
||||||
|
projectPath,
|
||||||
|
title,
|
||||||
|
createdAt: now,
|
||||||
|
lastActivityAt: now,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function unregister(paneId: string): void {
|
||||||
|
sessions.delete(paneId);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function list(): SessionMeta[] {
|
||||||
|
return Array.from(sessions.values());
|
||||||
|
}
|
||||||
|
|
||||||
|
export function get(paneId: string): SessionMeta | undefined {
|
||||||
|
return sessions.get(paneId);
|
||||||
|
}
|
||||||
18
apps/booterm/src/routes/sessions.ts
Normal file
18
apps/booterm/src/routes/sessions.ts
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
import type { FastifyInstance } from 'fastify';
|
||||||
|
import { list } from '../pty/registry.js';
|
||||||
|
|
||||||
|
export function registerSessionRoutes(app: FastifyInstance): void {
|
||||||
|
app.get('/api/term/sessions', async (_req, reply) => {
|
||||||
|
const active = list();
|
||||||
|
return reply.code(200).send({
|
||||||
|
sessions: active.map((s) => ({
|
||||||
|
paneId: s.paneId,
|
||||||
|
sessionId: s.sessionId,
|
||||||
|
projectPath: s.projectPath,
|
||||||
|
title: s.title ?? null,
|
||||||
|
createdAt: s.createdAt.toISOString(),
|
||||||
|
lastActivityAt: s.lastActivityAt.toISOString(),
|
||||||
|
})),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -9,6 +9,7 @@ import {
|
|||||||
} from '../pty/manager.js';
|
} from '../pty/manager.js';
|
||||||
import { attachPty } from '../pty/pty.js';
|
import { attachPty } from '../pty/pty.js';
|
||||||
import { getUser } from '../auth.js';
|
import { getUser } from '../auth.js';
|
||||||
|
import { register, unregister } from '../pty/registry.js';
|
||||||
|
|
||||||
export function registerWsAttachRoute(app: FastifyInstance, tmuxConfPath: string): void {
|
export function registerWsAttachRoute(app: FastifyInstance, tmuxConfPath: string): void {
|
||||||
app.get<{
|
app.get<{
|
||||||
@@ -57,6 +58,8 @@ export function registerWsAttachRoute(app: FastifyInstance, tmuxConfPath: string
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
register(sid, pid, session.project_path);
|
||||||
|
|
||||||
let handle: IPty;
|
let handle: IPty;
|
||||||
try {
|
try {
|
||||||
handle = attachPty({
|
handle = attachPty({
|
||||||
@@ -157,6 +160,7 @@ export function registerWsAttachRoute(app: FastifyInstance, tmuxConfPath: string
|
|||||||
// teardown happens via the /kill route called from the frontend when the
|
// teardown happens via the /kill route called from the frontend when the
|
||||||
// user closes the pane.
|
// user closes the pane.
|
||||||
socket.on('close', () => {
|
socket.on('close', () => {
|
||||||
|
unregister(pid);
|
||||||
try {
|
try {
|
||||||
handle.kill();
|
handle.kill();
|
||||||
} catch {
|
} catch {
|
||||||
|
|||||||
Reference in New Issue
Block a user