mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-08-24 08:01:38 +00:00
feat: add open in Cursor button with SSH remote support
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -178,6 +178,7 @@ async function handleApi(req: Request, url: URL): Promise<Response> {
|
||||
]);
|
||||
return {
|
||||
...wt,
|
||||
dir: wtDir ?? null,
|
||||
status: st?.status ?? "",
|
||||
elapsed: st?.elapsed ?? "",
|
||||
title: st?.title ?? "",
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
import Terminal from "./lib/Terminal.svelte";
|
||||
import ConfirmDialog from "./lib/ConfirmDialog.svelte";
|
||||
import CreateWorktreeDialog from "./lib/CreateWorktreeDialog.svelte";
|
||||
import SettingsDialog from "./lib/SettingsDialog.svelte";
|
||||
import type { WorktreeInfo } from "./lib/types";
|
||||
import type { Profile, Agent } from "./lib/api";
|
||||
import * as api from "./lib/api";
|
||||
@@ -13,8 +14,11 @@
|
||||
let selectedBranch = $state<string | null>(null);
|
||||
let removeBranch = $state<string | null>(null);
|
||||
let removingBranches = $state<Set<string>>(new Set());
|
||||
const SSH_STORAGE_KEY = "wt-ssh-host";
|
||||
let showCreateDialog = $state(false);
|
||||
let showSettingsDialog = $state(false);
|
||||
let creating = $state(false);
|
||||
let sshHost = $state(localStorage.getItem(SSH_STORAGE_KEY) ?? "");
|
||||
|
||||
let visibleWorktrees = $derived(
|
||||
worktrees.filter((w) => w.path === "(here)" || w.branch === "main" || w.mux === "✓")
|
||||
@@ -145,6 +149,10 @@
|
||||
<div class="flex justify-between"><span>Navigate</span><kbd class="opacity-60">Cmd+Up/Down</kbd></div>
|
||||
<div class="flex justify-between"><span>New worktree</span><kbd class="opacity-60">Cmd+K</kbd></div>
|
||||
<div class="flex justify-between"><span>Remove</span><kbd class="opacity-60">Cmd+D</kbd></div>
|
||||
<button
|
||||
class="mt-1 text-[11px] text-muted hover:text-accent cursor-pointer text-left bg-transparent border-none p-0"
|
||||
onclick={() => (showSettingsDialog = true)}
|
||||
>Settings</button>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
@@ -152,6 +160,7 @@
|
||||
<TopBar
|
||||
name={selectedBranch}
|
||||
worktree={selectedWorktree}
|
||||
{sshHost}
|
||||
onremove={() => { if (selectedBranch) removeBranch = selectedBranch; }}
|
||||
/>
|
||||
|
||||
@@ -188,3 +197,10 @@
|
||||
oncancel={() => (removeBranch = null)}
|
||||
/>
|
||||
{/if}
|
||||
|
||||
{#if showSettingsDialog}
|
||||
<SettingsDialog
|
||||
onsave={(host) => { sshHost = host; showSettingsDialog = false; }}
|
||||
onclose={() => (showSettingsDialog = false)}
|
||||
/>
|
||||
{/if}
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
<script lang="ts">
|
||||
const STORAGE_KEY = "wt-ssh-host";
|
||||
|
||||
let { onsave, onclose }: {
|
||||
onsave: (sshHost: string) => void;
|
||||
onclose: () => void;
|
||||
} = $props();
|
||||
|
||||
let sshHost = $state(localStorage.getItem(STORAGE_KEY) ?? "");
|
||||
let dialogEl: HTMLDialogElement;
|
||||
|
||||
$effect(() => {
|
||||
dialogEl?.showModal();
|
||||
});
|
||||
|
||||
function handleSave() {
|
||||
const trimmed = sshHost.trim();
|
||||
if (trimmed) {
|
||||
localStorage.setItem(STORAGE_KEY, trimmed);
|
||||
} else {
|
||||
localStorage.removeItem(STORAGE_KEY);
|
||||
}
|
||||
onsave(trimmed);
|
||||
}
|
||||
|
||||
const btn = "px-3 py-1.5 rounded-md border border-edge bg-surface text-primary text-xs cursor-pointer hover:bg-hover";
|
||||
</script>
|
||||
|
||||
<dialog bind:this={dialogEl} onclose={onclose} class="bg-sidebar text-primary border border-edge rounded-xl p-6 max-w-[380px] w-[90%]">
|
||||
<form method="dialog" onsubmit={(e) => { e.preventDefault(); handleSave(); }}>
|
||||
<h2 class="text-base mb-4">Settings</h2>
|
||||
<div class="mb-4">
|
||||
<label class="block text-xs text-muted mb-1.5" for="ssh-host">
|
||||
SSH Host <span class="opacity-60">(for "Open in Cursor")</span>
|
||||
</label>
|
||||
<input
|
||||
id="ssh-host"
|
||||
type="text"
|
||||
class="w-full px-2.5 py-1.5 rounded-md border border-edge bg-surface text-primary text-[13px] placeholder:text-muted/50 outline-none focus:border-accent"
|
||||
placeholder="e.g. devbox or 10.0.0.5"
|
||||
bind:value={sshHost}
|
||||
autofocus
|
||||
/>
|
||||
<p class="text-[11px] text-muted mt-1.5">
|
||||
Must match an entry in your local <code class="text-accent/80">~/.ssh/config</code>. Leave empty for local mode.
|
||||
</p>
|
||||
</div>
|
||||
<div class="flex justify-end gap-2">
|
||||
<button type="button" class={btn} onclick={onclose}>Cancel</button>
|
||||
<button
|
||||
type="submit"
|
||||
class="{btn} !bg-accent !text-white !border-accent hover:!opacity-90"
|
||||
>Save</button>
|
||||
</div>
|
||||
</form>
|
||||
</dialog>
|
||||
@@ -1,12 +1,22 @@
|
||||
<script lang="ts">
|
||||
import type { WorktreeInfo } from "./types";
|
||||
|
||||
let { name, worktree, onremove }: {
|
||||
let { name, worktree, sshHost, onremove }: {
|
||||
name: string | null;
|
||||
worktree: WorktreeInfo | undefined;
|
||||
sshHost: string;
|
||||
onremove: () => void;
|
||||
} = $props();
|
||||
|
||||
let cursorUrl = $derived.by(() => {
|
||||
const dir = worktree?.dir;
|
||||
if (!dir) return null;
|
||||
if (sshHost) {
|
||||
return `cursor://vscode-remote/ssh-remote+${sshHost}${dir}`;
|
||||
}
|
||||
return `cursor://file${dir}`;
|
||||
});
|
||||
|
||||
const btn = "px-3 py-1.5 rounded-md border border-edge bg-surface text-primary text-xs cursor-pointer hover:bg-hover";
|
||||
</script>
|
||||
|
||||
@@ -29,6 +39,13 @@
|
||||
class="text-[11px] px-1.5 py-0.5 rounded border font-mono no-underline hover:opacity-80 {worktree.frontendRunning ? 'text-success border-success/40' : 'text-muted border-edge pointer-events-none'}"
|
||||
>FE :{worktree.frontendPort}</a>
|
||||
{/if}
|
||||
{#if cursorUrl}
|
||||
<a
|
||||
href={cursorUrl}
|
||||
class="text-[11px] px-1.5 py-0.5 rounded border border-accent/40 text-accent font-mono no-underline hover:opacity-80"
|
||||
title="Open in Cursor"
|
||||
>Cursor</a>
|
||||
{/if}
|
||||
</div>
|
||||
{#if name}
|
||||
<div class="flex gap-2 items-center">
|
||||
|
||||
@@ -3,6 +3,7 @@ export interface WorktreeInfo {
|
||||
agent: string;
|
||||
mux: string;
|
||||
path: string;
|
||||
dir: string | null;
|
||||
status: string;
|
||||
elapsed: string;
|
||||
title: string;
|
||||
|
||||
Reference in New Issue
Block a user