diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/PortsList/PortsList.tsx b/apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/PortsList/PortsList.tsx
index 12813863547..956490ba274 100644
--- a/apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/PortsList/PortsList.tsx
+++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/PortsList/PortsList.tsx
@@ -1,7 +1,14 @@
import { COMPANY } from "@superset/shared/constants";
import { Tooltip, TooltipContent, TooltipTrigger } from "@superset/ui/tooltip";
-import { LuChevronRight, LuCircleHelp, LuRadioTower } from "react-icons/lu";
+import { useCallback, useRef } from "react";
+import {
+ LuChevronRight,
+ LuCircleHelp,
+ LuFilter,
+ LuRadioTower,
+} from "react-icons/lu";
import { usePortsStore } from "renderer/stores";
+import { MIN_LIST_HEIGHT, MAX_LIST_HEIGHT } from "renderer/stores/ports/store";
import { STROKE_WIDTH } from "../constants";
import { WorkspacePortGroup } from "./components/WorkspacePortGroup";
import { usePortsData } from "./hooks/usePortsData";
@@ -11,10 +18,43 @@ const PORTS_DOCS_URL = `${COMPANY.DOCS_URL}/ports`;
export function PortsList() {
const isCollapsed = usePortsStore((s) => s.isListCollapsed);
const toggleCollapsed = usePortsStore((s) => s.toggleListCollapsed);
+ const listHeight = usePortsStore((s) => s.listHeight);
+ const setListHeight = usePortsStore((s) => s.setListHeight);
+ const showConfiguredOnly = usePortsStore((s) => s.showConfiguredOnly);
+ const setShowConfiguredOnly = usePortsStore((s) => s.setShowConfiguredOnly);
const { workspacePortGroups, totalPortCount } = usePortsData();
- if (totalPortCount === 0) {
+ // --- Drag-to-resize handle ---
+ const isDragging = useRef(false);
+ const startY = useRef(0);
+ const startHeight = useRef(0);
+
+ const handleMouseDown = useCallback(
+ (e: React.MouseEvent) => {
+ e.preventDefault();
+ isDragging.current = true;
+ startY.current = e.clientY;
+ startHeight.current = listHeight;
+
+ const handleMouseMove = (ev: MouseEvent) => {
+ if (!isDragging.current) return;
+ // Dragging UP increases height (handle is at top of list)
+ const delta = startY.current - ev.clientY;
+ setListHeight(startHeight.current + delta);
+ };
+ const handleMouseUp = () => {
+ isDragging.current = false;
+ document.removeEventListener("mousemove", handleMouseMove);
+ document.removeEventListener("mouseup", handleMouseUp);
+ };
+ document.addEventListener("mousemove", handleMouseMove);
+ document.addEventListener("mouseup", handleMouseUp);
+ },
+ [listHeight, setListHeight],
+ );
+
+ if (totalPortCount === 0 && !showConfiguredOnly) {
return null;
}
@@ -24,7 +64,16 @@ export function PortsList() {
};
return (
-
+
+ {/* Resize handle */}
+ {!isCollapsed && (
+
+ )}
{!isCollapsed && (
-
- {workspacePortGroups.map((group) => (
-
- ))}
+
+ {workspacePortGroups.length > 0 ? (
+ workspacePortGroups.map((group) => (
+
+ ))
+ ) : showConfiguredOnly ? (
+
+ No ports defined in ports.json
+
+ ) : null}
)}
diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/PortsList/hooks/usePortsData.ts b/apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/PortsList/hooks/usePortsData.ts
index 4e3e926b9fb..d795fe76ffe 100644
--- a/apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/PortsList/hooks/usePortsData.ts
+++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/PortsList/hooks/usePortsData.ts
@@ -1,5 +1,6 @@
import { useMemo } from "react";
import { electronTrpc } from "renderer/lib/electron-trpc";
+import { usePortsStore } from "renderer/stores";
import type { EnrichedPort } from "shared/types";
const PORTS_FALLBACK_REFETCH_INTERVAL_MS = 10_000;
@@ -29,7 +30,13 @@ export function usePortsData() {
},
});
- const ports = detectedPorts ?? [];
+ const showConfiguredOnly = usePortsStore((s) => s.showConfiguredOnly);
+
+ const ports = useMemo(() => {
+ const all = detectedPorts ?? [];
+ if (!showConfiguredOnly) return all;
+ return all.filter((p) => p.label != null);
+ }, [detectedPorts, showConfiguredOnly]);
const workspaceNames = useMemo(() => {
if (!allWorkspaces) return {};
diff --git a/apps/desktop/src/renderer/stores/ports/store.ts b/apps/desktop/src/renderer/stores/ports/store.ts
index 2081bfebba1..814f7c7559b 100644
--- a/apps/desktop/src/renderer/stores/ports/store.ts
+++ b/apps/desktop/src/renderer/stores/ports/store.ts
@@ -1,28 +1,55 @@
import { create } from "zustand";
import { devtools, persist } from "zustand/middleware";
+const DEFAULT_LIST_HEIGHT = 288; // 18rem (max-h-72)
+const MIN_LIST_HEIGHT = 80;
+const MAX_LIST_HEIGHT = 600;
+
interface PortsState {
isListCollapsed: boolean;
+ /** Height of the ports list in pixels (user-resizable). */
+ listHeight: number;
+ /** When true, only ports with a label from ports.json are shown. */
+ showConfiguredOnly: boolean;
setListCollapsed: (collapsed: boolean) => void;
toggleListCollapsed: () => void;
+ setListHeight: (height: number) => void;
+ setShowConfiguredOnly: (value: boolean) => void;
}
+export { MIN_LIST_HEIGHT, MAX_LIST_HEIGHT };
+
export const usePortsStore = create
()(
devtools(
persist(
(set, get) => ({
isListCollapsed: false,
+ listHeight: DEFAULT_LIST_HEIGHT,
+ showConfiguredOnly: false,
setListCollapsed: (collapsed) => set({ isListCollapsed: collapsed }),
toggleListCollapsed: () =>
set({ isListCollapsed: !get().isListCollapsed }),
+
+ setListHeight: (height) =>
+ set({
+ listHeight: Math.max(
+ MIN_LIST_HEIGHT,
+ Math.min(MAX_LIST_HEIGHT, height),
+ ),
+ }),
+
+ setShowConfiguredOnly: (value) =>
+ set({ showConfiguredOnly: value }),
}),
{
name: "ports-store",
partialize: (state) => ({
isListCollapsed: state.isListCollapsed,
+ listHeight: state.listHeight,
+ showConfiguredOnly: state.showConfiguredOnly,
}),
},
),