From 56c9025b804342b09e1aca7b53031d4bdf250b07 Mon Sep 17 00:00:00 2001 From: Alessandro Date: Wed, 11 Mar 2026 10:36:30 +0100 Subject: [PATCH 1/4] fix: prioritize active tunnels in selector --- .../sections/AppParametersSection.tsx | 80 +++++++++++++++++-- 1 file changed, 73 insertions(+), 7 deletions(-) diff --git a/src/components/create-job/sections/AppParametersSection.tsx b/src/components/create-job/sections/AppParametersSection.tsx index 53410229..1728cc52 100644 --- a/src/components/create-job/sections/AppParametersSection.tsx +++ b/src/components/create-job/sections/AppParametersSection.tsx @@ -20,19 +20,43 @@ type TunnelGenerationResult = { url?: string; }; +type TunnelStatus = 'inactive' | 'degraded' | 'healthy' | 'down'; + type ExistingTunnelOption = { id: string; alias: string; token: string; url: string; + status: TunnelStatus; + isCustom?: false; }; -type TunnelSelectOption = ExistingTunnelOption & { - isCustom?: boolean; +type CustomTunnelOption = { + id: string; + alias: string; + token: string; + url: string; + isCustom: true; }; +type TunnelSelectOption = ExistingTunnelOption | CustomTunnelOption; + const CUSTOM_TUNNEL_OPTION = 'custom'; +const tunnelStatusPriority: Record = { + healthy: 0, + degraded: 1, + inactive: 2, + down: 3, +}; + +const tunnelStatusColorClassName: Record = { + healthy: 'bg-emerald-500', + degraded: 'bg-yellow-500', + inactive: 'bg-slate-500', + down: 'bg-red-500', +}; + export default function AppParametersSection({ baseName = 'deployment', isCreatingTunnel = false, @@ -102,8 +126,17 @@ export default function AppParametersSection({ alias: (tunnel.metadata.alias || tunnel.metadata.dns_name) as string, token: tunnel.metadata.tunnel_token as string, url: tunnel.metadata.dns_name as string, + status: tunnel.status as TunnelStatus, })) - .sort((a, b) => a.alias.localeCompare(b.alias)); + .sort((a, b) => { + const statusPriorityDiff = tunnelStatusPriority[a.status] - tunnelStatusPriority[b.status]; + + if (statusPriorityDiff !== 0) { + return statusPriorityDiff; + } + + return a.alias.localeCompare(b.alias); + }); setExistingTunnels(tunnels); return tunnels; @@ -240,6 +273,17 @@ export default function AppParametersSection({ }} placeholder={isFetchingTunnels ? 'Loading tunnels...' : 'Select an existing tunnel'} isDisabled={isFetchingTunnels} + renderValue={(items) => { + return items.map((item) => { + const tunnel = item.data as TunnelSelectOption | undefined; + + if (!tunnel) { + return
{item.textValue}
; + } + + return ; + }); + }} > {(option: object) => { const tunnel = option as TunnelSelectOption; @@ -249,10 +293,7 @@ export default function AppParametersSection({ key={tunnel.id} textValue={tunnel.isCustom ? tunnel.alias : `${tunnel.alias} | ${tunnel.url}`} > -
-
{tunnel.alias}
-
{tunnel.url}
-
+ ); }} @@ -313,3 +354,28 @@ export default function AppParametersSection({ ); } + +function TunnelSelectOptionContent({ tunnel }: { tunnel: TunnelSelectOption }) { + if (tunnel.isCustom) { + return ( +
+
{tunnel.alias}
+
{tunnel.url}
+
+ ); + } + + return ( +
+
+
{tunnel.alias}
+
{tunnel.url}
+
+ +
+
+
{tunnel.status}
+
+
+ ); +} From 62e294ef704c5ad4b1116d3a58e43f9d18fd2a1e Mon Sep 17 00:00:00 2001 From: Alessandro Date: Wed, 11 Mar 2026 10:43:43 +0100 Subject: [PATCH 2/4] fix: reuse tunnel status mapping in selector and pages --- app/(protected)/tunnels/[id]/page.tsx | 21 +-------- .../sections/AppParametersSection.tsx | 29 ++----------- src/components/tunnels/TunnelCard.tsx | 11 +---- src/lib/tunnel-status.ts | 43 +++++++++++++++++++ 4 files changed, 50 insertions(+), 54 deletions(-) create mode 100644 src/lib/tunnel-status.ts diff --git a/app/(protected)/tunnels/[id]/page.tsx b/app/(protected)/tunnels/[id]/page.tsx index 9354e5cc..4122b722 100644 --- a/app/(protected)/tunnels/[id]/page.tsx +++ b/app/(protected)/tunnels/[id]/page.tsx @@ -13,6 +13,7 @@ import { import { InteractionContextType, useInteractionContext } from '@lib/contexts/interaction'; import { TunnelsContextType, useTunnelsContext } from '@lib/contexts/tunnels'; import { routePath } from '@lib/routes/route-paths'; +import { tunnelStatusTagVariantByStatus } from '@lib/tunnel-status'; import ActionButton from '@shared/ActionButton'; import { CompactCustomCard } from '@shared/cards/CompactCustomCard'; import { CopyableValue } from '@shared/CopyableValue'; @@ -266,24 +267,6 @@ export default function TunnelPage() { } }; - const getStatusTagVariant = () => { - if (!tunnel) { - return 'slate'; - } - - switch (tunnel.status) { - case 'healthy': - return 'green'; - case 'degraded': - return 'yellow'; - case 'down': - return 'red'; - - default: - return 'slate'; - } - }; - if (!tunnel) { return (
@@ -313,7 +296,7 @@ export default function TunnelPage() {
{tunnel.alias}
- +
{tunnel.status}
diff --git a/src/components/create-job/sections/AppParametersSection.tsx b/src/components/create-job/sections/AppParametersSection.tsx index 1728cc52..a5841457 100644 --- a/src/components/create-job/sections/AppParametersSection.tsx +++ b/src/components/create-job/sections/AppParametersSection.tsx @@ -5,6 +5,7 @@ import { SelectItem } from '@heroui/select'; import { BOOLEAN_TYPES } from '@data/booleanTypes'; import { getTunnels } from '@lib/api/tunnels'; import { TunnelsContextType, useTunnelsContext } from '@lib/contexts/tunnels'; +import { compareTunnelStatusAndAlias, tunnelStatusDotColorClassByStatus, type TunnelStatus } from '@lib/tunnel-status'; import InputWithLabel from '@shared/InputWithLabel'; import Label from '@shared/Label'; import DeeployInfoTag from '@shared/jobs/DeeployInfoTag'; @@ -20,8 +21,6 @@ type TunnelGenerationResult = { url?: string; }; -type TunnelStatus = 'inactive' | 'degraded' | 'healthy' | 'down'; - type ExistingTunnelOption = { id: string; alias: string; @@ -43,20 +42,6 @@ type TunnelSelectOption = ExistingTunnelOption | CustomTunnelOption; const CUSTOM_TUNNEL_OPTION = 'custom'; -const tunnelStatusPriority: Record = { - healthy: 0, - degraded: 1, - inactive: 2, - down: 3, -}; - -const tunnelStatusColorClassName: Record = { - healthy: 'bg-emerald-500', - degraded: 'bg-yellow-500', - inactive: 'bg-slate-500', - down: 'bg-red-500', -}; - export default function AppParametersSection({ baseName = 'deployment', isCreatingTunnel = false, @@ -128,15 +113,7 @@ export default function AppParametersSection({ url: tunnel.metadata.dns_name as string, status: tunnel.status as TunnelStatus, })) - .sort((a, b) => { - const statusPriorityDiff = tunnelStatusPriority[a.status] - tunnelStatusPriority[b.status]; - - if (statusPriorityDiff !== 0) { - return statusPriorityDiff; - } - - return a.alias.localeCompare(b.alias); - }); + .sort(compareTunnelStatusAndAlias); setExistingTunnels(tunnels); return tunnels; @@ -373,7 +350,7 @@ function TunnelSelectOptionContent({ tunnel }: { tunnel: TunnelSelectOption }) {
-
+
{tunnel.status}
diff --git a/src/components/tunnels/TunnelCard.tsx b/src/components/tunnels/TunnelCard.tsx index 9611293f..1e8e2e8e 100644 --- a/src/components/tunnels/TunnelCard.tsx +++ b/src/components/tunnels/TunnelCard.tsx @@ -4,12 +4,12 @@ import { deleteTunnel } from '@lib/api/tunnels'; import { InteractionContextType, useInteractionContext } from '@lib/contexts/interaction'; import { TunnelsContextType, useTunnelsContext } from '@lib/contexts/tunnels'; import { routePath } from '@lib/routes/route-paths'; +import { tunnelStatusDotColorClassByStatus } from '@lib/tunnel-status'; import { BorderedCard } from '@shared/cards/BorderedCard'; import ContextMenuWithTrigger from '@shared/ContextMenuWithTrigger'; import { CopyableValue } from '@shared/CopyableValue'; import { SmallTag } from '@shared/SmallTag'; import { Tunnel } from '@typedefs/tunnels'; -import clsx from 'clsx'; import Link from 'next/link'; import { useRouter } from 'next/navigation'; import toast from 'react-hot-toast'; @@ -56,14 +56,7 @@ export default function TunnelCard({ tunnel, fetchTunnels }: { tunnel: Tunnel; f
-
+
{tunnel.alias}
diff --git a/src/lib/tunnel-status.ts b/src/lib/tunnel-status.ts new file mode 100644 index 00000000..c1dbe53d --- /dev/null +++ b/src/lib/tunnel-status.ts @@ -0,0 +1,43 @@ +import type { Tunnel } from '@typedefs/tunnels'; + +type TunnelStatus = Tunnel['status']; +type TunnelStatusTagVariant = 'slate' | 'green' | 'yellow' | 'red'; + +const tunnelStatusDotColorClassByStatus: Record = { + healthy: 'bg-emerald-500', + degraded: 'bg-yellow-500', + down: 'bg-red-500', + inactive: 'bg-gray-500', +}; + +const tunnelStatusTagVariantByStatus: Record = { + healthy: 'green', + degraded: 'yellow', + down: 'red', + inactive: 'slate', +}; + +const tunnelStatusSortPriority: Record = { + inactive: 0, + down: 1, + degraded: 2, + healthy: 3, +}; + +function compareTunnelStatusAndAlias(a: T, b: T): number { + const statusPriorityDiff = tunnelStatusSortPriority[a.status] - tunnelStatusSortPriority[b.status]; + + if (statusPriorityDiff !== 0) { + return statusPriorityDiff; + } + + return a.alias.localeCompare(b.alias); +} + +export { + compareTunnelStatusAndAlias, + tunnelStatusDotColorClassByStatus, + tunnelStatusTagVariantByStatus, + tunnelStatusSortPriority, + type TunnelStatus, +}; From 0ad4cc3185ddfa643c252e100e50452d69339acb Mon Sep 17 00:00:00 2001 From: Alessandro Date: Wed, 11 Mar 2026 10:55:02 +0100 Subject: [PATCH 3/4] fix: align tunnel selector status column --- .../create-job/sections/AppParametersSection.tsx | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/components/create-job/sections/AppParametersSection.tsx b/src/components/create-job/sections/AppParametersSection.tsx index a5841457..8843368f 100644 --- a/src/components/create-job/sections/AppParametersSection.tsx +++ b/src/components/create-job/sections/AppParametersSection.tsx @@ -335,21 +335,21 @@ export default function AppParametersSection({ function TunnelSelectOptionContent({ tunnel }: { tunnel: TunnelSelectOption }) { if (tunnel.isCustom) { return ( -
-
{tunnel.alias}
-
{tunnel.url}
+
+
{tunnel.alias}
+
{tunnel.url}
); } return ( -
-
+
+
{tunnel.alias}
-
{tunnel.url}
+
{tunnel.url}
-
+
{tunnel.status}
From e8e492cb801731d3caad814ff33dda772425c4a9 Mon Sep 17 00:00:00 2001 From: Alessandro Date: Wed, 11 Mar 2026 10:58:12 +0100 Subject: [PATCH 4/4] fix: reduce tunnel selector spacing --- .../create-job/sections/AppParametersSection.tsx | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/components/create-job/sections/AppParametersSection.tsx b/src/components/create-job/sections/AppParametersSection.tsx index 8843368f..b06aaefe 100644 --- a/src/components/create-job/sections/AppParametersSection.tsx +++ b/src/components/create-job/sections/AppParametersSection.tsx @@ -335,19 +335,19 @@ export default function AppParametersSection({ function TunnelSelectOptionContent({ tunnel }: { tunnel: TunnelSelectOption }) { if (tunnel.isCustom) { return ( -
-
{tunnel.alias}
-
{tunnel.url}
+
+
{tunnel.alias}
+
{tunnel.url}
); } return ( -
-
-
{tunnel.alias}
+
+
+
{tunnel.alias}
+
{tunnel.url}
-
{tunnel.url}