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
202 changes: 35 additions & 167 deletions apps/desktop/src/lib/trpc/routers/ports/ports.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,9 @@ import { workspaces } from "@superset/local-db";
import { observable } from "@trpc/server/observable";
import { eq } from "drizzle-orm";
import { localDb } from "main/lib/local-db";
import {
hasStaticPortsConfig,
loadStaticPorts,
staticPortsWatcher,
} from "main/lib/static-ports";
import { loadStaticPorts } from "main/lib/static-ports";
import { portManager } from "main/lib/terminal/port-manager";
import type { DetectedPort, StaticPort } from "shared/types";
import type { DetectedPort, EnrichedPort } from "shared/types";
import { z } from "zod";
import { publicProcedure, router } from "../..";
import { getWorkspacePath } from "../workspaces/utils/worktree";
Expand All @@ -17,10 +13,41 @@ type PortEvent =
| { type: "add"; port: DetectedPort }
| { type: "remove"; port: DetectedPort };

function getLabelsForPath(worktreePath: string): Map<number, string> | null {
const result = loadStaticPorts(worktreePath);
if (!result.exists || result.error || !result.ports) return null;

const labels = new Map<number, string>();
for (const p of result.ports) {
labels.set(p.port, p.label);
}
return labels;
}

export const createPortsRouter = () => {
return router({
getAll: publicProcedure.query(() => {
return portManager.getAllPorts();
getAll: publicProcedure.query((): EnrichedPort[] => {
const detectedPorts = portManager.getAllPorts();

const labelCache = new Map<string, Map<number, string> | null>();

return detectedPorts.map((port) => {
if (!labelCache.has(port.workspaceId)) {
const ws = localDb
.select()
.from(workspaces)
.where(eq(workspaces.id, port.workspaceId))
.get();
const wsPath = ws ? getWorkspacePath(ws) : null;
labelCache.set(
port.workspaceId,
wsPath ? getLabelsForPath(wsPath) : null,
);
}

const labels = labelCache.get(port.workspaceId);
return { ...port, label: labels?.get(port.port) ?? null };
});
}),

subscribe: publicProcedure.subscription(() => {
Expand Down Expand Up @@ -55,164 +82,5 @@ export const createPortsRouter = () => {
return portManager.killPort(input);
},
),

hasStaticConfig: publicProcedure
.input(z.object({ workspaceId: z.string() }))
.query(({ input }): { hasStatic: boolean } => {
const workspace = localDb
.select()
.from(workspaces)
.where(eq(workspaces.id, input.workspaceId))
.get();

if (!workspace) {
return { hasStatic: false };
}

const workspacePath = getWorkspacePath(workspace);
if (!workspacePath) {
return { hasStatic: false };
}

return { hasStatic: hasStaticPortsConfig(workspacePath) };
}),

getStatic: publicProcedure
.input(z.object({ workspaceId: z.string() }))
.query(
({ input }): { ports: StaticPort[] | null; error: string | null } => {
const workspace = localDb
.select()
.from(workspaces)
.where(eq(workspaces.id, input.workspaceId))
.get();

if (!workspace) {
return { ports: null, error: "Workspace not found" };
}

const workspacePath = getWorkspacePath(workspace);
if (!workspacePath) {
return { ports: null, error: "Workspace path not found" };
}

const result = loadStaticPorts(workspacePath);

if (!result.exists) {
return { ports: null, error: null };
}

if (result.error) {
return { ports: null, error: result.error };
}

const portsWithWorkspace: StaticPort[] =
result.ports?.map((p) => ({
...p,
workspaceId: input.workspaceId,
})) ?? [];

return { ports: portsWithWorkspace, error: null };
},
),

getAllStatic: publicProcedure.query(
(): {
ports: StaticPort[];
errors: Array<{ workspaceId: string; error: string }>;
} => {
const allWorkspaces = localDb.select().from(workspaces).all();
const allPorts: StaticPort[] = [];
const errors: Array<{ workspaceId: string; error: string }> = [];

for (const workspace of allWorkspaces) {
const workspacePath = getWorkspacePath(workspace);
if (!workspacePath) continue;

const result = loadStaticPorts(workspacePath);

if (!result.exists) continue;

if (result.error) {
errors.push({ workspaceId: workspace.id, error: result.error });
continue;
}

if (result.ports) {
const portsWithWorkspace = result.ports.map((p) => ({
...p,
workspaceId: workspace.id,
}));
allPorts.push(...portsWithWorkspace);
}
}

return { ports: allPorts, errors };
},
),

subscribeStatic: publicProcedure
.input(z.object({ workspaceId: z.string() }))
.subscription(({ input }) => {
return observable<{ type: "change" }>((emit) => {
const workspace = localDb
.select()
.from(workspaces)
.where(eq(workspaces.id, input.workspaceId))
.get();

if (!workspace) {
return () => {};
}

const workspacePath = getWorkspacePath(workspace);
if (!workspacePath) {
return () => {};
}

staticPortsWatcher.watch(input.workspaceId, workspacePath);

const onChange = (changedWorkspaceId: string) => {
if (changedWorkspaceId === input.workspaceId) {
emit.next({ type: "change" });
}
};

staticPortsWatcher.on("change", onChange);

return () => {
staticPortsWatcher.off("change", onChange);
staticPortsWatcher.unwatch(input.workspaceId);
};
});
}),

subscribeAllStatic: publicProcedure.subscription(() => {
return observable<{ type: "change"; workspaceId: string }>((emit) => {
const allWorkspaces = localDb.select().from(workspaces).all();
const watchedIds: string[] = [];

for (const workspace of allWorkspaces) {
const workspacePath = getWorkspacePath(workspace);
if (!workspacePath) continue;

staticPortsWatcher.watch(workspace.id, workspacePath);
watchedIds.push(workspace.id);
}

const onChange = (changedWorkspaceId: string) => {
emit.next({ type: "change", workspaceId: changedWorkspaceId });
};

staticPortsWatcher.on("change", onChange);

return () => {
staticPortsWatcher.off("change", onChange);
for (const id of watchedIds) {
staticPortsWatcher.unwatch(id);
}
};
});
}),
});
};
Submodule .test-origin-head-1771066473561-mrg02kb762j added at 308fdf
3 changes: 1 addition & 2 deletions apps/desktop/src/main/lib/static-ports/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1 @@
export { hasStaticPortsConfig, loadStaticPorts } from "./loader";
export { staticPortsWatcher } from "./watcher";
export { loadStaticPorts } from "./loader";
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@ import { useNavigate } from "@tanstack/react-router";
import { LuExternalLink, LuX } from "react-icons/lu";
import { navigateToWorkspace } from "renderer/routes/_authenticated/_dashboard/utils/workspace-navigation";
import { useTabsStore } from "renderer/stores/tabs/store";
import type { MergedPort } from "shared/types";
import type { EnrichedPort } from "shared/types";
import { STROKE_WIDTH } from "../../../constants";
import { useKillPort } from "../../hooks/useKillPort";

interface MergedPortBadgeProps {
port: MergedPort;
port: EnrichedPort;
}

export function MergedPortBadge({ port }: MergedPortBadgeProps) {
Expand All @@ -17,30 +17,25 @@ export function MergedPortBadge({ port }: MergedPortBadgeProps) {
const setFocusedPane = useTabsStore((s) => s.setFocusedPane);
const { killPort } = useKillPort();

const portNumberColor = port.isActive
? "text-muted-foreground"
: "text-muted-foreground/80";

const displayContent = port.label ? (
<>
{port.label}{" "}
<span className={`font-mono font-normal ${portNumberColor}`}>
<span className="font-mono font-normal text-muted-foreground">
{port.port}
</span>
</>
) : (
<span className={`font-mono ${portNumberColor}`}>{port.port}</span>
<span className="font-mono text-muted-foreground">{port.port}</span>
);

const canJumpToTerminal = port.isActive && port.paneId;
const canJumpToTerminal = !!port.paneId;

const handleClick = () => {
if (!canJumpToTerminal || !port.paneId) return;
if (!port.paneId) return;

const pane = useTabsStore.getState().panes[port.paneId];
if (!pane) return;

// Navigate to workspace, then focus the pane
navigateToWorkspace(port.workspaceId, navigate);
setActiveTab(port.workspaceId, pane.tabId);
setFocusedPane(pane.tabId, port.paneId);
Expand All @@ -54,8 +49,6 @@ export function MergedPortBadge({ port }: MergedPortBadgeProps) {
killPort(port);
};

const canClose = port.isActive && port.paneId != null;

return (
<Tooltip>
<TooltipTrigger asChild>
Expand All @@ -76,16 +69,14 @@ export function MergedPortBadge({ port }: MergedPortBadgeProps) {
>
<LuExternalLink className="size-3.5" strokeWidth={STROKE_WIDTH} />
</button>
{canClose && (
<button
type="button"
onClick={handleClose}
aria-label={`Close ${port.label || `port ${port.port}`}`}
className="opacity-0 group-hover:opacity-100 pr-1 transition-opacity text-muted-foreground hover:text-primary focus-visible:opacity-100 focus-visible:outline-none"
>
<LuX className="size-3.5" strokeWidth={STROKE_WIDTH} />
</button>
)}
<button
type="button"
onClick={handleClose}
aria-label={`Close ${port.label || `port ${port.port}`}`}
className="opacity-0 group-hover:opacity-100 pr-1 transition-opacity text-muted-foreground hover:text-primary focus-visible:opacity-100 focus-visible:outline-none"
>
<LuX className="size-3.5" strokeWidth={STROKE_WIDTH} />
</button>
</div>
</TooltipTrigger>
<TooltipContent side="top" sideOffset={6} showArrow={false}>
Expand All @@ -96,20 +87,16 @@ export function MergedPortBadge({ port }: MergedPortBadgeProps) {
>
localhost:{port.port}
</div>
{port.isActive && (
<>
{(port.processName || port.pid != null) && (
<div className="text-muted-foreground">
{port.processName}
{port.pid != null && ` (pid ${port.pid})`}
</div>
)}
{canJumpToTerminal && (
<div className="text-muted-foreground/70 text-[10px]">
Click to open workspace
</div>
)}
</>
{(port.processName || port.pid != null) && (
<div className="text-muted-foreground">
{port.processName}
{port.pid != null && ` (pid ${port.pid})`}
</div>
)}
{canJumpToTerminal && (
<div className="text-muted-foreground/70 text-[10px]">
Click to open workspace
</div>
)}
</div>
</TooltipContent>
Expand Down
Loading
Loading