Add Clean Exited button and improve button row styling

- Added handleCleanExited method to delete all exited sessions in batch
- Added cleaningExited state to track cleaning progress
- Added Clean Exited button with proper styling and disabled state
- Improved button row layout with grouped buttons on left and checkbox on right
- Added transition-colors for smooth hover effects
- Button automatically disables when no exited sessions exist

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Mario Zechner 2025-06-16 04:30:39 +02:00
parent 5ffc2dca8d
commit 6e22984ee8
3 changed files with 133 additions and 13 deletions

View file

@ -17,6 +17,7 @@ let SessionList = class SessionList extends LitElement {
this.loadingSnapshots = new Set();
this.hideExited = true;
this.showCreateModal = false;
this.cleaningExited = false;
}
// Disable shadow DOM to use Tailwind
createRenderRoot() {
@ -157,6 +158,49 @@ let SessionList = class SessionList extends LitElement {
detail: e.detail
}));
}
async handleCleanExited() {
const exitedSessions = this.sessions.filter(session => session.status === 'exited');
if (exitedSessions.length === 0) {
this.dispatchEvent(new CustomEvent('error', {
detail: 'No exited sessions to clean'
}));
return;
}
if (!confirm(`Are you sure you want to delete ${exitedSessions.length} exited session${exitedSessions.length > 1 ? 's' : ''}?`)) {
return;
}
this.cleaningExited = true;
this.requestUpdate();
try {
const deletePromises = exitedSessions.map(async (session) => {
const response = await fetch(`/api/sessions/${session.id}`, {
method: 'DELETE'
});
if (!response.ok) {
throw new Error(`Failed to delete session ${session.id}`);
}
return session.id;
});
await Promise.all(deletePromises);
this.dispatchEvent(new CustomEvent('error', {
detail: `Successfully cleaned ${exitedSessions.length} exited session${exitedSessions.length > 1 ? 's' : ''}`
}));
// Refresh the list after cleanup
setTimeout(() => {
this.handleRefresh();
}, 500);
}
catch (error) {
console.error('Error cleaning exited sessions:', error);
this.dispatchEvent(new CustomEvent('error', {
detail: 'Failed to clean some sessions'
}));
}
finally {
this.cleaningExited = false;
this.requestUpdate();
}
}
get filteredSessions() {
return this.hideExited
? this.sessions.filter(session => session.status === 'running')
@ -168,12 +212,22 @@ let SessionList = class SessionList extends LitElement {
<div class="font-mono text-sm p-4">
<!-- Controls -->
<div class="mb-4 flex items-center justify-between">
<button
class="bg-vs-user text-vs-text hover:bg-vs-accent font-mono px-4 py-2 border-none rounded"
@click=${() => this.showCreateModal = true}
>
Create Session
</button>
<div class="flex items-center gap-3">
<button
class="bg-vs-user text-vs-text hover:bg-vs-accent font-mono px-4 py-2 border-none rounded transition-colors"
@click=${() => this.showCreateModal = true}
>
Create Session
</button>
<button
class="bg-vs-warning text-vs-bg hover:bg-vs-highlight font-mono px-4 py-2 border-none rounded transition-colors disabled:opacity-50"
@click=${this.handleCleanExited}
?disabled=${this.cleaningExited || this.sessions.filter(s => s.status === 'exited').length === 0}
>
${this.cleaningExited ? 'Cleaning...' : 'Clean Exited'}
</button>
</div>
<label class="flex items-center gap-2 text-vs-text text-sm cursor-pointer hover:text-vs-accent transition-colors">
<div class="relative">
@ -275,6 +329,9 @@ __decorate([
__decorate([
state()
], SessionList.prototype, "showCreateModal", void 0);
__decorate([
state()
], SessionList.prototype, "cleaningExited", void 0);
SessionList = __decorate([
customElement('session-list')
], SessionList);

File diff suppressed because one or more lines are too long

View file

@ -28,6 +28,7 @@ export class SessionList extends LitElement {
@state() private loadingSnapshots = new Set<string>();
@state() private hideExited = true;
@state() private showCreateModal = false;
@state() private cleaningExited = false;
private handleRefresh() {
this.dispatchEvent(new CustomEvent('refresh'));
@ -180,6 +181,58 @@ export class SessionList extends LitElement {
}));
}
private async handleCleanExited() {
const exitedSessions = this.sessions.filter(session => session.status === 'exited');
if (exitedSessions.length === 0) {
this.dispatchEvent(new CustomEvent('error', {
detail: 'No exited sessions to clean'
}));
return;
}
if (!confirm(`Are you sure you want to delete ${exitedSessions.length} exited session${exitedSessions.length > 1 ? 's' : ''}?`)) {
return;
}
this.cleaningExited = true;
this.requestUpdate();
try {
const deletePromises = exitedSessions.map(async (session) => {
const response = await fetch(`/api/sessions/${session.id}`, {
method: 'DELETE'
});
if (!response.ok) {
throw new Error(`Failed to delete session ${session.id}`);
}
return session.id;
});
await Promise.all(deletePromises);
this.dispatchEvent(new CustomEvent('error', {
detail: `Successfully cleaned ${exitedSessions.length} exited session${exitedSessions.length > 1 ? 's' : ''}`
}));
// Refresh the list after cleanup
setTimeout(() => {
this.handleRefresh();
}, 500);
} catch (error) {
console.error('Error cleaning exited sessions:', error);
this.dispatchEvent(new CustomEvent('error', {
detail: 'Failed to clean some sessions'
}));
} finally {
this.cleaningExited = false;
this.requestUpdate();
}
}
private get filteredSessions() {
return this.hideExited
? this.sessions.filter(session => session.status === 'running')
@ -193,12 +246,22 @@ export class SessionList extends LitElement {
<div class="font-mono text-sm p-4">
<!-- Controls -->
<div class="mb-4 flex items-center justify-between">
<button
class="bg-vs-user text-vs-text hover:bg-vs-accent font-mono px-4 py-2 border-none rounded"
@click=${() => this.showCreateModal = true}
>
Create Session
</button>
<div class="flex items-center gap-3">
<button
class="bg-vs-user text-vs-text hover:bg-vs-accent font-mono px-4 py-2 border-none rounded transition-colors"
@click=${() => this.showCreateModal = true}
>
Create Session
</button>
<button
class="bg-vs-warning text-vs-bg hover:bg-vs-highlight font-mono px-4 py-2 border-none rounded transition-colors disabled:opacity-50"
@click=${this.handleCleanExited}
?disabled=${this.cleaningExited || this.sessions.filter(s => s.status === 'exited').length === 0}
>
${this.cleaningExited ? 'Cleaning...' : 'Clean Exited'}
</button>
</div>
<label class="flex items-center gap-2 text-vs-text text-sm cursor-pointer hover:text-vs-accent transition-colors">
<div class="relative">