mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-10-03 14:09:42 +02:00
feat: improve Monitor panel UX and preserve session names
- Monitor panel now opens by default on webapp start - Replace "Reconcile" button with refresh icon (↻) - Change close button to toggle (▼/▲) for collapse/expand - Preserve session display names when saving screen info - Use stored name when restoring sessions (no more "Restored:" prefix) - Show session name in Monitor panel instead of screen name - Kill screen session when closing a tab Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
+14
-2
@@ -48,7 +48,7 @@ export class ScreenManager extends EventEmitter {
|
||||
}
|
||||
|
||||
// Create a new GNU screen session
|
||||
async createScreen(sessionId: string, workingDir: string, mode: 'claude' | 'shell'): Promise<ScreenSession> {
|
||||
async createScreen(sessionId: string, workingDir: string, mode: 'claude' | 'shell', name?: string): Promise<ScreenSession> {
|
||||
const screenName = `claudeman-${sessionId.slice(0, 8)}`;
|
||||
|
||||
// Create screen in detached mode with the appropriate command
|
||||
@@ -84,7 +84,8 @@ export class ScreenManager extends EventEmitter {
|
||||
createdAt: Date.now(),
|
||||
workingDir,
|
||||
mode,
|
||||
attached: false
|
||||
attached: false,
|
||||
name
|
||||
};
|
||||
|
||||
this.screens.set(sessionId, screen);
|
||||
@@ -150,6 +151,17 @@ export class ScreenManager extends EventEmitter {
|
||||
return this.screens.get(sessionId);
|
||||
}
|
||||
|
||||
// Update screen display name
|
||||
updateScreenName(sessionId: string, name: string): boolean {
|
||||
const screen = this.screens.get(sessionId);
|
||||
if (!screen) {
|
||||
return false;
|
||||
}
|
||||
screen.name = name;
|
||||
this.saveScreens();
|
||||
return true;
|
||||
}
|
||||
|
||||
// Reconcile screens - find orphaned/dead screens
|
||||
async reconcileScreens(): Promise<{ alive: string[]; dead: string[] }> {
|
||||
const alive: string[] = [];
|
||||
|
||||
+13
-2
@@ -306,7 +306,7 @@ export class Session extends EventEmitter {
|
||||
// If screen wrapping is enabled, create a screen session first
|
||||
if (this._useScreen && this._screenManager) {
|
||||
try {
|
||||
this._screenSession = await this._screenManager.createScreen(this.id, this.workingDir, 'claude');
|
||||
this._screenSession = await this._screenManager.createScreen(this.id, this.workingDir, 'claude', this._name);
|
||||
console.log('[Session] Created screen session:', this._screenSession.screenName);
|
||||
|
||||
// Wait a moment for screen to fully start
|
||||
@@ -424,7 +424,7 @@ export class Session extends EventEmitter {
|
||||
// If screen wrapping is enabled, create a screen session first
|
||||
if (this._useScreen && this._screenManager) {
|
||||
try {
|
||||
this._screenSession = await this._screenManager.createScreen(this.id, this.workingDir, 'shell');
|
||||
this._screenSession = await this._screenManager.createScreen(this.id, this.workingDir, 'shell', this._name);
|
||||
console.log('[Session] Created screen session:', this._screenSession.screenName);
|
||||
|
||||
// Wait a moment for screen to fully start
|
||||
@@ -767,6 +767,17 @@ export class Session extends EventEmitter {
|
||||
this._status = 'stopped';
|
||||
this._currentTaskId = null;
|
||||
|
||||
// Kill the associated screen session if any
|
||||
if (this._screenSession && this._screenManager) {
|
||||
try {
|
||||
await this._screenManager.killScreen(this.id);
|
||||
console.log('[Session] Killed screen session:', this._screenSession.screenName);
|
||||
} catch (err) {
|
||||
console.error('[Session] Failed to kill screen session:', err);
|
||||
}
|
||||
this._screenSession = null;
|
||||
}
|
||||
|
||||
if (this.rejectPromise) {
|
||||
this.rejectPromise(new Error('Session stopped'));
|
||||
this.resolvePromise = null;
|
||||
|
||||
@@ -214,6 +214,7 @@ export interface ScreenSession {
|
||||
workingDir: string;
|
||||
mode: 'claude' | 'shell';
|
||||
attached: boolean; // Whether webserver is attached
|
||||
name?: string; // Session display name (tab name)
|
||||
}
|
||||
|
||||
export interface ProcessStats {
|
||||
|
||||
@@ -30,6 +30,8 @@ class ClaudemanApp {
|
||||
this.loadState();
|
||||
this.loadQuickStartCases();
|
||||
this.setupEventListeners();
|
||||
// Show monitor panel by default
|
||||
this.toggleMonitorPanel();
|
||||
}
|
||||
|
||||
initTerminal() {
|
||||
@@ -1361,6 +1363,7 @@ class ClaudemanApp {
|
||||
|
||||
async toggleMonitorPanel() {
|
||||
const panel = document.getElementById('monitorPanel');
|
||||
const toggleBtn = document.getElementById('monitorToggleBtn');
|
||||
panel.classList.toggle('open');
|
||||
|
||||
if (panel.classList.contains('open')) {
|
||||
@@ -1368,9 +1371,11 @@ class ClaudemanApp {
|
||||
await this.loadScreens();
|
||||
await fetch('/api/screens/stats/start', { method: 'POST' });
|
||||
this.renderTaskPanel();
|
||||
if (toggleBtn) toggleBtn.innerHTML = '▼'; // Down arrow when open
|
||||
} else {
|
||||
// Stop stats collection when panel is closed
|
||||
await fetch('/api/screens/stats/stop', { method: 'POST' });
|
||||
if (toggleBtn) toggleBtn.innerHTML = '▲'; // Up arrow when closed
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1487,7 +1492,7 @@ class ClaudemanApp {
|
||||
<div class="process-item">
|
||||
<span class="process-mode ${modeClass}">${screen.mode}</span>
|
||||
<div class="process-info">
|
||||
<div class="process-name">${this.escapeHtml(screen.screenName)}</div>
|
||||
<div class="process-name">${this.escapeHtml(screen.name || screen.screenName)}</div>
|
||||
<div class="process-meta">
|
||||
<span class="process-stat memory">${stats.memoryMB}MB</span>
|
||||
<span class="process-stat cpu">${stats.cpuPercent}%</span>
|
||||
|
||||
@@ -135,8 +135,8 @@
|
||||
<div class="monitor-panel-header">
|
||||
<span>Monitor</span>
|
||||
<div class="monitor-panel-actions">
|
||||
<button class="btn-toolbar btn-sm" onclick="app.reconcileScreens()" title="Check for dead screens">Reconcile</button>
|
||||
<button class="btn-close" onclick="app.toggleMonitorPanel()">×</button>
|
||||
<button class="btn-icon-sm" onclick="app.reconcileScreens()" title="Refresh screens">↻</button>
|
||||
<button class="btn-icon-sm" onclick="app.toggleMonitorPanel()" title="Toggle panel" id="monitorToggleBtn">▼</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="monitor-panel-body">
|
||||
|
||||
@@ -661,6 +661,22 @@ body {
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
.btn-icon-sm {
|
||||
background: transparent;
|
||||
border: none;
|
||||
color: var(--text-dim);
|
||||
font-size: 1rem;
|
||||
cursor: pointer;
|
||||
padding: 0.25rem;
|
||||
line-height: 1;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.btn-icon-sm:hover {
|
||||
color: var(--text);
|
||||
background: var(--bg-hover);
|
||||
}
|
||||
|
||||
.form-row {
|
||||
margin-bottom: 0.75rem;
|
||||
}
|
||||
|
||||
+3
-1
@@ -141,6 +141,8 @@ export class WebServer extends EventEmitter {
|
||||
}
|
||||
|
||||
session.name = body.name || '';
|
||||
// Also update the screen name if this session has a screen
|
||||
this.screenManager.updateScreenName(id, session.name);
|
||||
this.broadcast('session:updated', session.toDetailedState());
|
||||
return { success: true, name: session.name };
|
||||
});
|
||||
@@ -1063,7 +1065,7 @@ export class WebServer extends EventEmitter {
|
||||
id: screen.sessionId, // Preserve the original session ID
|
||||
workingDir: screen.workingDir,
|
||||
mode: screen.mode,
|
||||
name: `Restored: ${screen.screenName}`,
|
||||
name: screen.name || screen.screenName,
|
||||
screenManager: this.screenManager,
|
||||
useScreen: true
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user