From ee9bf31738ec2357821c49c22f69cd2bb2705f60 Mon Sep 17 00:00:00 2001 From: Wes Date: Tue, 3 Sep 2024 13:52:29 -0700 Subject: [PATCH] feat: add infrastructure tabs and content --- frontend/console/src/components/List.tsx | 24 +++++++++ .../src/components/StatusIndicator.tsx | 24 +++++++++ .../infrastructure/ControllersList.tsx | 30 ++++++++++- .../infrastructure/DeploymentsList.tsx | 39 ++++++++++++++- .../infrastructure/InfrastructurePage.tsx | 24 +++++---- .../features/infrastructure/RoutesList.tsx | 28 ++++++++++- .../features/infrastructure/RunnersList.tsx | 50 ++++++++++++++++++- .../infrastructure/infrastructure.utils.ts | 18 +++++++ .../src/providers/routing-provider.tsx | 3 +- 9 files changed, 220 insertions(+), 20 deletions(-) create mode 100644 frontend/console/src/components/List.tsx create mode 100644 frontend/console/src/components/StatusIndicator.tsx create mode 100644 frontend/console/src/features/infrastructure/infrastructure.utils.ts diff --git a/frontend/console/src/components/List.tsx b/frontend/console/src/components/List.tsx new file mode 100644 index 0000000000..8733d182f6 --- /dev/null +++ b/frontend/console/src/components/List.tsx @@ -0,0 +1,24 @@ +import { classNames } from '../utils' + +type ListProps = { + items: T[] + renderItem: (item: T) => React.ReactNode + onClick?: (item: T) => void + className?: string +} + +export const List = ({ items, renderItem, onClick, className }: ListProps) => { + return ( +
    + {items.map((item, index) => ( +
  • onClick(item) : undefined} + > + {renderItem(item)} +
  • + ))} +
+ ) +} diff --git a/frontend/console/src/components/StatusIndicator.tsx b/frontend/console/src/components/StatusIndicator.tsx new file mode 100644 index 0000000000..05736f24b3 --- /dev/null +++ b/frontend/console/src/components/StatusIndicator.tsx @@ -0,0 +1,24 @@ +import type React from 'react' +import { classNames } from '../utils' + +type StatusIndicatorProps = { + state: 'success' | 'error' | 'idle' + text?: string +} + +export const StatusIndicator: React.FC = ({ state, text }) => { + const backgrounds = { + idle: 'text-gray-500 bg-gray-100/10', + success: 'text-green-400 bg-green-400/10', + error: 'text-rose-400 bg-rose-400/10', + } + + return ( +
+
+
+
+ {text &&

{text}

} +
+ ) +} diff --git a/frontend/console/src/features/infrastructure/ControllersList.tsx b/frontend/console/src/features/infrastructure/ControllersList.tsx index 299186833f..c947714ecc 100644 --- a/frontend/console/src/features/infrastructure/ControllersList.tsx +++ b/frontend/console/src/features/infrastructure/ControllersList.tsx @@ -1,3 +1,29 @@ -export const ControllersList = () => { - return <>Controllers Content +import { Badge } from '../../components/Badge' +import { List } from '../../components/List' +import type { StatusResponse_Controller } from '../../protos/xyz/block/ftl/v1/ftl_pb' + +export const ControllersList = ({ controllers }: { controllers: StatusResponse_Controller[] }) => { + return ( + ( + <> +
+
+

+ + {controller.key} +

+

{controller.endpoint}

+
+
+
+
+ +
+
+ + )} + /> + ) } diff --git a/frontend/console/src/features/infrastructure/DeploymentsList.tsx b/frontend/console/src/features/infrastructure/DeploymentsList.tsx index cd0e9296e0..2545e9ff38 100644 --- a/frontend/console/src/features/infrastructure/DeploymentsList.tsx +++ b/frontend/console/src/features/infrastructure/DeploymentsList.tsx @@ -1,3 +1,38 @@ -export const DeploymentsList = () => { - return <>Deployments Content +import { AttributeBadge } from '../../components' +import { Badge } from '../../components/Badge' +import { List } from '../../components/List' +import type { StatusResponse_Deployment } from '../../protos/xyz/block/ftl/v1/ftl_pb' +import { classNames } from '../../utils' +import { deploymentTextColor } from '../deployments/deployment.utils' +import { renderValue } from './infrastructure.utils' + +export const DeploymentsList = ({ deployments }: { deployments: StatusResponse_Deployment[] }) => { + return ( + ( +
+
+
+
+

{deployment.name}

+ +
+ +

{deployment.key}

+
+
+
+
+ + + {Object.entries(deployment.labels?.fields || {}).map(([key, value]) => ( + + ))} +
+
+
+ )} + /> + ) } diff --git a/frontend/console/src/features/infrastructure/InfrastructurePage.tsx b/frontend/console/src/features/infrastructure/InfrastructurePage.tsx index e031445c62..2c652c96a6 100644 --- a/frontend/console/src/features/infrastructure/InfrastructurePage.tsx +++ b/frontend/console/src/features/infrastructure/InfrastructurePage.tsx @@ -1,5 +1,5 @@ import { useEffect, useState } from 'react' -import { useSearchParams } from 'react-router-dom' +import { useLocation, useNavigate } from 'react-router-dom' import { useStatus } from '../../api/status/use-status' import { Tabs } from '../../components/Tabs' import { ControllersList } from './ControllersList' @@ -9,7 +9,8 @@ import { RunnersList } from './RunnersList' export const InfrastructurePage = () => { const status = useStatus() - const [searchParams, setSearchParams] = useSearchParams() + const navigate = useNavigate() + const location = useLocation() const [tabs, setTabs] = useState([ { name: 'Controllers', id: 'controllers' }, @@ -41,30 +42,31 @@ export const InfrastructurePage = () => { ) }, [status.data]) - const handleTabClick = (tabId: string) => { - setSearchParams({ tab: tabId }) - } + const currentTab = location.pathname.split('/').pop() - const currentTab = searchParams.get('tab') || tabs[0].id const renderTabContent = () => { switch (currentTab) { case 'controllers': - return + return case 'runners': - return + return case 'deployments': - return + return case 'routes': - return + return default: return <> } } + const handleTabClick = (tabId: string) => { + navigate(`/infrastructure/${tabId}`) + } + return (
-
{renderTabContent()}
+
{renderTabContent()}
) } diff --git a/frontend/console/src/features/infrastructure/RoutesList.tsx b/frontend/console/src/features/infrastructure/RoutesList.tsx index eea618d767..7ed5f528b2 100644 --- a/frontend/console/src/features/infrastructure/RoutesList.tsx +++ b/frontend/console/src/features/infrastructure/RoutesList.tsx @@ -1,3 +1,27 @@ -export const RoutesList = () => { - return <>Routes Content +import { AttributeBadge } from '../../components' +import { List } from '../../components/List' +import type { StatusResponse_Route } from '../../protos/xyz/block/ftl/v1/ftl_pb' + +export const RoutesList = ({ routes }: { routes: StatusResponse_Route[] }) => { + return ( + ( +
+
+
+
{route.module}
+

{route.endpoint}

+
+
+
+
+ + +
+
+
+ )} + /> + ) } diff --git a/frontend/console/src/features/infrastructure/RunnersList.tsx b/frontend/console/src/features/infrastructure/RunnersList.tsx index 3daf525342..7b443581a6 100644 --- a/frontend/console/src/features/infrastructure/RunnersList.tsx +++ b/frontend/console/src/features/infrastructure/RunnersList.tsx @@ -1,3 +1,49 @@ -export const RunnersList = () => { - return <>Runners Content +import { AttributeBadge } from '../../components' +import { List } from '../../components/List' +import { StatusIndicator } from '../../components/StatusIndicator' +import { RunnerState, type StatusResponse_Runner } from '../../protos/xyz/block/ftl/v1/ftl_pb' +import { classNames } from '../../utils' +import { deploymentTextColor } from '../deployments/deployment.utils' +import { renderValue } from './infrastructure.utils' + +export const RunnersList = ({ runners }: { runners: StatusResponse_Runner[] }) => { + return ( + ( + <> +
+
+

{runner.key}

+

{runner.endpoint}

+
+ {status(runner.state)} + {runner.deployment &&

{runner.deployment}

} +
+
+
+
+
+ {Object.entries(runner.labels?.fields || {}).map(([key, value]) => ( + + ))} +
+
+ + )} + /> + ) +} + +const status = (state: RunnerState) => { + switch (state) { + case RunnerState.RUNNER_ASSIGNED: + return + case RunnerState.RUNNER_RESERVED: + return + case RunnerState.RUNNER_DEAD: + return + case RunnerState.RUNNER_IDLE: + return + } } diff --git a/frontend/console/src/features/infrastructure/infrastructure.utils.ts b/frontend/console/src/features/infrastructure/infrastructure.utils.ts new file mode 100644 index 0000000000..1ebecb4b33 --- /dev/null +++ b/frontend/console/src/features/infrastructure/infrastructure.utils.ts @@ -0,0 +1,18 @@ +import type { Value } from '@bufbuild/protobuf' + +export const renderValue = (value: Value): string => { + switch (value.kind?.case) { + case 'numberValue': + return value.kind.value.toString() + case 'stringValue': + return value.kind.value + case 'boolValue': + return value.kind.value ? 'true' : 'false' + case 'structValue': + return value.kind.value.toJsonString() + case 'listValue': + return value.kind.value.values.map(renderValue).join(', ') + default: + return '' + } +} diff --git a/frontend/console/src/providers/routing-provider.tsx b/frontend/console/src/providers/routing-provider.tsx index 0eb55bfc44..445bb4df3d 100644 --- a/frontend/console/src/providers/routing-provider.tsx +++ b/frontend/console/src/providers/routing-provider.tsx @@ -24,7 +24,8 @@ const router = createBrowserRouter( } /> } /> } /> - } /> + } /> + } /> } />