Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions frontend/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -534,6 +534,9 @@ function updateUnlockState(): void {
document.querySelectorAll<HTMLButtonElement>('[data-key]').forEach((button) => {
button.disabled = !targetReady(button.dataset.shell || '');
});
document.querySelectorAll<HTMLButtonElement>('[data-stop]').forEach((button) => {
button.disabled = !targetReady(button.dataset.stop || '');
});
document.querySelectorAll<HTMLTextAreaElement>('[data-command]').forEach((input) => {
input.disabled = !targetReady(input.dataset.command || '');
});
Expand Down
7 changes: 7 additions & 0 deletions frontend/events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ document.addEventListener('click', async (event: MouseEvent) => {
if (!target) return;
const copyButton = target.closest<HTMLElement>('[data-copy]');
const startButton = target.closest<HTMLButtonElement>('[data-start]');
const stopButton = target.closest<HTMLButtonElement>('[data-stop]');
const restartButton = target.closest<HTMLButtonElement>('[data-restart]');
const keyButton = target.closest<HTMLButtonElement>('[data-key]');
const sendButton = target.closest<HTMLButtonElement>('[data-send-shell]');
Expand Down Expand Up @@ -91,6 +92,12 @@ document.addEventListener('click', async (event: MouseEvent) => {
await sessionAction('/api/start', startButton.dataset.start || '');
return selectSession(startButton.dataset.start);
}
if (stopButton && !stopButton.disabled) {
const stopped = stopButton.dataset.stop || '';
await sessionAction('/api/stop', stopped);
if (sessionByName(stopped)) selectSession(stopped);
return;
}
if (restartButton && !restartButton.disabled) {
await sessionAction('/api/restart', restartButton.dataset.restart || '');
return selectSession(restartButton.dataset.restart);
Expand Down
5 changes: 4 additions & 1 deletion frontend/render.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ function createShellCard(shell: ShellPreview): HTMLElement {
<button class="iconly" type="button" data-add-image title="Attach an image; inserts its saved path" aria-label="Attach image">${icon('image')}</button>
<button class="iconly" type="button" data-key="enter" title="Press Enter in the shell" aria-label="Press Enter">${icon('enter')}</button>
<button class="warn" type="button" data-key="interrupt" title="Interrupt the running command (Ctrl-C)">${icon('stop')}<span>Ctrl-C</span></button>
<button class="warn iconly" type="button" data-stop title="Stop this tmux session" aria-label="Stop tmux session">${icon('stop')}</button>
<button class="iconly" type="button" data-history title="Cycle previous inputs (or ↑ / ↓ in the box)" aria-label="Input history">${icon('clock')}</button>
<button class="iconly" type="button" data-key="clear" title="Clear the shell screen" aria-label="Clear screen">${icon('eraser')}</button>
<button class="iconly" type="button" data-copy-output title="Copy the pane output" aria-label="Copy output">${icon('copy')}</button>
Expand All @@ -51,6 +52,7 @@ function createShellCard(shell: ShellPreview): HTMLElement {
article.querySelector<HTMLButtonElement>('[data-reset-preview]')!.dataset.resetPreview = shell.name;
article.querySelector<HTMLButtonElement>('[data-shellin]')!.dataset.shellin = shell.name;
article.querySelector<HTMLButtonElement>('[data-resume]')!.dataset.resume = shell.name;
article.querySelector<HTMLButtonElement>('[data-stop]')!.dataset.stop = shell.name;
article.querySelectorAll<HTMLButtonElement>('[data-key]').forEach((button) => {
button.dataset.shell = shell.name;
});
Expand Down Expand Up @@ -238,13 +240,14 @@ function renderSelectedSessionActions(): void {
const state = sessionRuntime(selected);
const startDisabled = selected.running || selected.family === 'custom' || !shellUnlocked ? 'disabled' : '';
const restartDisabled = selected.family === 'custom' || !shellUnlocked ? 'disabled' : '';
const stopDisabled = !selected.running || !shellUnlocked ? 'disabled' : '';
const attached = selected.attached > 0 ? `<span class="session-chip">${selected.attached} attached</span>` : '';
const displayLabel = shellDisplayLabel(selected.name, selected.label);
const sshButton = selected.sshCommand
? `<button type="button" data-copy="${escapeHtml(selected.sshCommand)}" title="Copy SSH command to attach to this tmux session from another machine">${icon('terminal')}<span>SSH</span></button>`
: '';
el.hidden = false;
el.innerHTML = `<div class="session-action-meta"><span class="badge">${escapeHtml(selected.badge)}</span><div><b>${escapeHtml(displayLabel)}</b><small><i class="dot ${state.dotClass}"></i>${escapeHtml(state.label)} · <span data-act-epoch="${selected.activity ?? ''}">${escapeHtml(fmtTime(selected.activity))}</span>${attached}</small></div></div><div class="session-action-buttons"><button type="button" ${startDisabled} data-start="${escapeHtml(selected.name)}">${icon('play')}<span>Start</span></button><button class="warn" type="button" ${restartDisabled} data-restart="${escapeHtml(selected.name)}">${icon('restart')}<span>Restart</span></button><button type="button" data-copy="${escapeHtml(selected.command)}" title="Copy tmux attach command">${icon('help')}<span>Attach</span></button>${sshButton}</div>`;
el.innerHTML = `<div class="session-action-meta"><span class="badge">${escapeHtml(selected.badge)}</span><div><b>${escapeHtml(displayLabel)}</b><small><i class="dot ${state.dotClass}"></i>${escapeHtml(state.label)} · <span data-act-epoch="${selected.activity ?? ''}">${escapeHtml(fmtTime(selected.activity))}</span>${attached}</small></div></div><div class="session-action-buttons"><button type="button" ${startDisabled} data-start="${escapeHtml(selected.name)}">${icon('play')}<span>Start</span></button><button class="warn" type="button" ${stopDisabled} data-stop="${escapeHtml(selected.name)}" title="Kill this tmux session">${icon('stop')}<span>Stop</span></button><button class="warn" type="button" ${restartDisabled} data-restart="${escapeHtml(selected.name)}">${icon('restart')}<span>Restart</span></button><button type="button" data-copy="${escapeHtml(selected.command)}" title="Copy tmux attach command">${icon('help')}<span>Attach</span></button>${sshButton}</div>`;
}

let shellTabsSignature = '';
Expand Down
7 changes: 4 additions & 3 deletions public/app.css
Original file line number Diff line number Diff line change
Expand Up @@ -499,11 +499,12 @@ body.shells-locked .container-actions{display:none}
/* Compact (remote) rows: float the action buttons at the right, revealed on hover/focus, so they
never crowd the line or wrap at narrow sidebar widths. */
.container-item.compact{position:relative}
.container-item.compact .container-actions{position:absolute;top:50%;right:6px;transform:translateY(-50%);margin:0;opacity:0;pointer-events:none;transition:opacity .12s ease;background:rgba(5,11,17,.92);border:1px solid rgba(139,246,255,.18);border-radius:7px;padding:2px;gap:4px}
.container-item.compact .container-actions{position:absolute;top:6px;right:6px;transform:none;margin:0;opacity:0;pointer-events:none;transition:opacity .12s ease;background:rgba(5,11,17,.92);border:1px solid rgba(139,246,255,.18);border-radius:7px;padding:2px;gap:4px}
.container-item.compact:hover .container-actions,.container-item.compact:focus-within .container-actions{opacity:1;pointer-events:auto}
.container-item.compact:hover .ci-cpu,.container-item.compact:focus-within .ci-cpu,.container-item.compact:hover .container-age,.container-item.compact:focus-within .container-age{opacity:0}
.container-item.compact .container-action{padding:2px 7px}
/* No hover on touch — keep the buttons in flow on their own line there. */
@media (hover:none){.container-item.compact .container-actions{position:static;transform:none;opacity:1;pointer-events:auto;background:none;border:0;justify-content:flex-end;margin-top:3px}}
/* No hover on touch — keep the buttons visible in the same reserved top-right space. */
@media (hover:none){.container-item.compact .container-actions{top:6px;right:6px;transform:none;opacity:1;pointer-events:auto}.container-item.compact .ci-cpu,.container-item.compact .container-age{opacity:0}}
.container-item .ci-row1{align-items:flex-start}
/* Condensed 2-line container row for remote host cards. */
.container-item.compact{padding:6px 9px;gap:2px}
Expand Down
3 changes: 3 additions & 0 deletions public/core.js
Original file line number Diff line number Diff line change
Expand Up @@ -436,6 +436,9 @@ function updateUnlockState() {
document.querySelectorAll('[data-key]').forEach((button) => {
button.disabled = !targetReady(button.dataset.shell || '');
});
document.querySelectorAll('[data-stop]').forEach((button) => {
button.disabled = !targetReady(button.dataset.stop || '');
});
document.querySelectorAll('[data-command]').forEach((input) => {
input.disabled = !targetReady(input.dataset.command || '');
});
Expand Down
8 changes: 8 additions & 0 deletions public/events.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ document.addEventListener('click', async (event) => {
return;
const copyButton = target.closest('[data-copy]');
const startButton = target.closest('[data-start]');
const stopButton = target.closest('[data-stop]');
const restartButton = target.closest('[data-restart]');
const keyButton = target.closest('[data-key]');
const sendButton = target.closest('[data-send-shell]');
Expand Down Expand Up @@ -103,6 +104,13 @@ document.addEventListener('click', async (event) => {
await sessionAction('/api/start', startButton.dataset.start || '');
return selectSession(startButton.dataset.start);
}
if (stopButton && !stopButton.disabled) {
const stopped = stopButton.dataset.stop || '';
await sessionAction('/api/stop', stopped);
if (sessionByName(stopped))
selectSession(stopped);
return;
}
if (restartButton && !restartButton.disabled) {
await sessionAction('/api/restart', restartButton.dataset.restart || '');
return selectSession(restartButton.dataset.restart);
Expand Down
5 changes: 4 additions & 1 deletion public/render.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ function createShellCard(shell) {
<button class="iconly" type="button" data-add-image title="Attach an image; inserts its saved path" aria-label="Attach image">${icon('image')}</button>
<button class="iconly" type="button" data-key="enter" title="Press Enter in the shell" aria-label="Press Enter">${icon('enter')}</button>
<button class="warn" type="button" data-key="interrupt" title="Interrupt the running command (Ctrl-C)">${icon('stop')}<span>Ctrl-C</span></button>
<button class="warn iconly" type="button" data-stop title="Stop this tmux session" aria-label="Stop tmux session">${icon('stop')}</button>
<button class="iconly" type="button" data-history title="Cycle previous inputs (or ↑ / ↓ in the box)" aria-label="Input history">${icon('clock')}</button>
<button class="iconly" type="button" data-key="clear" title="Clear the shell screen" aria-label="Clear screen">${icon('eraser')}</button>
<button class="iconly" type="button" data-copy-output title="Copy the pane output" aria-label="Copy output">${icon('copy')}</button>
Expand All @@ -51,6 +52,7 @@ function createShellCard(shell) {
article.querySelector('[data-reset-preview]').dataset.resetPreview = shell.name;
article.querySelector('[data-shellin]').dataset.shellin = shell.name;
article.querySelector('[data-resume]').dataset.resume = shell.name;
article.querySelector('[data-stop]').dataset.stop = shell.name;
article.querySelectorAll('[data-key]').forEach((button) => {
button.dataset.shell = shell.name;
});
Expand Down Expand Up @@ -245,13 +247,14 @@ function renderSelectedSessionActions() {
const state = sessionRuntime(selected);
const startDisabled = selected.running || selected.family === 'custom' || !shellUnlocked ? 'disabled' : '';
const restartDisabled = selected.family === 'custom' || !shellUnlocked ? 'disabled' : '';
const stopDisabled = !selected.running || !shellUnlocked ? 'disabled' : '';
const attached = selected.attached > 0 ? `<span class="session-chip">${selected.attached} attached</span>` : '';
const displayLabel = shellDisplayLabel(selected.name, selected.label);
const sshButton = selected.sshCommand
? `<button type="button" data-copy="${escapeHtml(selected.sshCommand)}" title="Copy SSH command to attach to this tmux session from another machine">${icon('terminal')}<span>SSH</span></button>`
: '';
el.hidden = false;
el.innerHTML = `<div class="session-action-meta"><span class="badge">${escapeHtml(selected.badge)}</span><div><b>${escapeHtml(displayLabel)}</b><small><i class="dot ${state.dotClass}"></i>${escapeHtml(state.label)} · <span data-act-epoch="${selected.activity ?? ''}">${escapeHtml(fmtTime(selected.activity))}</span>${attached}</small></div></div><div class="session-action-buttons"><button type="button" ${startDisabled} data-start="${escapeHtml(selected.name)}">${icon('play')}<span>Start</span></button><button class="warn" type="button" ${restartDisabled} data-restart="${escapeHtml(selected.name)}">${icon('restart')}<span>Restart</span></button><button type="button" data-copy="${escapeHtml(selected.command)}" title="Copy tmux attach command">${icon('help')}<span>Attach</span></button>${sshButton}</div>`;
el.innerHTML = `<div class="session-action-meta"><span class="badge">${escapeHtml(selected.badge)}</span><div><b>${escapeHtml(displayLabel)}</b><small><i class="dot ${state.dotClass}"></i>${escapeHtml(state.label)} · <span data-act-epoch="${selected.activity ?? ''}">${escapeHtml(fmtTime(selected.activity))}</span>${attached}</small></div></div><div class="session-action-buttons"><button type="button" ${startDisabled} data-start="${escapeHtml(selected.name)}">${icon('play')}<span>Start</span></button><button class="warn" type="button" ${stopDisabled} data-stop="${escapeHtml(selected.name)}" title="Kill this tmux session">${icon('stop')}<span>Stop</span></button><button class="warn" type="button" ${restartDisabled} data-restart="${escapeHtml(selected.name)}">${icon('restart')}<span>Restart</span></button><button type="button" data-copy="${escapeHtml(selected.command)}" title="Copy tmux attach command">${icon('help')}<span>Attach</span></button>${sshButton}</div>`;
}
let shellTabsSignature = '';
function shellbarSummaryMoving(text) {
Expand Down
Loading
Loading