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 53410229..b06aaefe 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'; @@ -25,12 +26,20 @@ type ExistingTunnelOption = { 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'; export default function AppParametersSection({ @@ -102,8 +111,9 @@ 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(compareTunnelStatusAndAlias); setExistingTunnels(tunnels); return tunnels; @@ -240,6 +250,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 +270,7 @@ export default function AppParametersSection({ key={tunnel.id} textValue={tunnel.isCustom ? tunnel.alias : `${tunnel.alias} | ${tunnel.url}`} > -
-
{tunnel.alias}
-
{tunnel.url}
-
+ ); }} @@ -313,3 +331,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}
+
+
+ ); +} 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, +};