mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-10-08 08:29: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
|
// 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)}`;
|
const screenName = `claudeman-${sessionId.slice(0, 8)}`;
|
||||||
|
|
||||||
// Create screen in detached mode with the appropriate command
|
// Create screen in detached mode with the appropriate command
|
||||||
@@ -84,7 +84,8 @@ export class ScreenManager extends EventEmitter {
|
|||||||
createdAt: Date.now(),
|
createdAt: Date.now(),
|
||||||
workingDir,
|
workingDir,
|
||||||
mode,
|
mode,
|
||||||
attached: false
|
attached: false,
|
||||||
|
name
|
||||||
};
|
};
|
||||||
|
|
||||||
this.screens.set(sessionId, screen);
|
this.screens.set(sessionId, screen);
|
||||||
@@ -150,6 +151,17 @@ export class ScreenManager extends EventEmitter {
|
|||||||
return this.screens.get(sessionId);
|
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
|
// Reconcile screens - find orphaned/dead screens
|
||||||
async reconcileScreens(): Promise<{ alive: string[]; dead: string[] }> {
|
async reconcileScreens(): Promise<{ alive: string[]; dead: string[] }> {
|
||||||
const alive: 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 screen wrapping is enabled, create a screen session first
|
||||||
if (this._useScreen && this._screenManager) {
|
if (this._useScreen && this._screenManager) {
|
||||||
try {
|
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);
|
console.log('[Session] Created screen session:', this._screenSession.screenName);
|
||||||
|
|
||||||
// Wait a moment for screen to fully start
|
// 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 screen wrapping is enabled, create a screen session first
|
||||||
if (this._useScreen && this._screenManager) {
|
if (this._useScreen && this._screenManager) {
|
||||||
try {
|
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);
|
console.log('[Session] Created screen session:', this._screenSession.screenName);
|
||||||
|
|
||||||
// Wait a moment for screen to fully start
|
// Wait a moment for screen to fully start
|
||||||
@@ -767,6 +767,17 @@ export class Session extends EventEmitter {
|
|||||||
this._status = 'stopped';
|
this._status = 'stopped';
|
||||||
this._currentTaskId = null;
|
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) {
|
if (this.rejectPromise) {
|
||||||
this.rejectPromise(new Error('Session stopped'));
|
this.rejectPromise(new Error('Session stopped'));
|
||||||
this.resolvePromise = null;
|
this.resolvePromise = null;
|
||||||
|
|||||||
@@ -214,6 +214,7 @@ export interface ScreenSession {
|
|||||||
workingDir: string;
|
workingDir: string;
|
||||||
mode: 'claude' | 'shell';
|
mode: 'claude' | 'shell';
|
||||||
attached: boolean; // Whether webserver is attached
|
attached: boolean; // Whether webserver is attached
|
||||||
|
name?: string; // Session display name (tab name)
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ProcessStats {
|
export interface ProcessStats {
|
||||||
|
|||||||
@@ -30,6 +30,8 @@ class ClaudemanApp {
|
|||||||
this.loadState();
|
this.loadState();
|
||||||
this.loadQuickStartCases();
|
this.loadQuickStartCases();
|
||||||
this.setupEventListeners();
|
this.setupEventListeners();
|
||||||
|
// Show monitor panel by default
|
||||||
|
this.toggleMonitorPanel();
|
||||||
}
|
}
|
||||||
|
|
||||||
initTerminal() {
|
initTerminal() {
|
||||||
@@ -1361,6 +1363,7 @@ class ClaudemanApp {
|
|||||||
|
|
||||||
async toggleMonitorPanel() {
|
async toggleMonitorPanel() {
|
||||||
const panel = document.getElementById('monitorPanel');
|
const panel = document.getElementById('monitorPanel');
|
||||||
|
const toggleBtn = document.getElementById('monitorToggleBtn');
|
||||||
panel.classList.toggle('open');
|
panel.classList.toggle('open');
|
||||||
|
|
||||||
if (panel.classList.contains('open')) {
|
if (panel.classList.contains('open')) {
|
||||||
@@ -1368,9 +1371,11 @@ class ClaudemanApp {
|
|||||||
await this.loadScreens();
|
await this.loadScreens();
|
||||||
await fetch('/api/screens/stats/start', { method: 'POST' });
|
await fetch('/api/screens/stats/start', { method: 'POST' });
|
||||||
this.renderTaskPanel();
|
this.renderTaskPanel();
|
||||||
|
if (toggleBtn) toggleBtn.innerHTML = '▼'; // Down arrow when open
|
||||||
} else {
|
} else {
|
||||||
// Stop stats collection when panel is closed
|
// Stop stats collection when panel is closed
|
||||||
await fetch('/api/screens/stats/stop', { method: 'POST' });
|
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">
|
<div class="process-item">
|
||||||
<span class="process-mode ${modeClass}">${screen.mode}</span>
|
<span class="process-mode ${modeClass}">${screen.mode}</span>
|
||||||
<div class="process-info">
|
<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">
|
<div class="process-meta">
|
||||||
<span class="process-stat memory">${stats.memoryMB}MB</span>
|
<span class="process-stat memory">${stats.memoryMB}MB</span>
|
||||||
<span class="process-stat cpu">${stats.cpuPercent}%</span>
|
<span class="process-stat cpu">${stats.cpuPercent}%</span>
|
||||||
|
|||||||
@@ -135,8 +135,8 @@
|
|||||||
<div class="monitor-panel-header">
|
<div class="monitor-panel-header">
|
||||||
<span>Monitor</span>
|
<span>Monitor</span>
|
||||||
<div class="monitor-panel-actions">
|
<div class="monitor-panel-actions">
|
||||||
<button class="btn-toolbar btn-sm" onclick="app.reconcileScreens()" title="Check for dead screens">Reconcile</button>
|
<button class="btn-icon-sm" onclick="app.reconcileScreens()" title="Refresh screens">↻</button>
|
||||||
<button class="btn-close" onclick="app.toggleMonitorPanel()">×</button>
|
<button class="btn-icon-sm" onclick="app.toggleMonitorPanel()" title="Toggle panel" id="monitorToggleBtn">▼</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="monitor-panel-body">
|
<div class="monitor-panel-body">
|
||||||
|
|||||||
@@ -661,6 +661,22 @@ body {
|
|||||||
color: var(--text);
|
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 {
|
.form-row {
|
||||||
margin-bottom: 0.75rem;
|
margin-bottom: 0.75rem;
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-1
@@ -141,6 +141,8 @@ export class WebServer extends EventEmitter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
session.name = body.name || '';
|
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());
|
this.broadcast('session:updated', session.toDetailedState());
|
||||||
return { success: true, name: session.name };
|
return { success: true, name: session.name };
|
||||||
});
|
});
|
||||||
@@ -1063,7 +1065,7 @@ export class WebServer extends EventEmitter {
|
|||||||
id: screen.sessionId, // Preserve the original session ID
|
id: screen.sessionId, // Preserve the original session ID
|
||||||
workingDir: screen.workingDir,
|
workingDir: screen.workingDir,
|
||||||
mode: screen.mode,
|
mode: screen.mode,
|
||||||
name: `Restored: ${screen.screenName}`,
|
name: screen.name || screen.screenName,
|
||||||
screenManager: this.screenManager,
|
screenManager: this.screenManager,
|
||||||
useScreen: true
|
useScreen: true
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user