Skip to content

Commit

Permalink
feat: add infrastructure tabs and content
Browse files Browse the repository at this point in the history
  • Loading branch information
wesbillman committed Sep 3, 2024
1 parent 4d31576 commit ee9bf31
Show file tree
Hide file tree
Showing 9 changed files with 220 additions and 20 deletions.
24 changes: 24 additions & 0 deletions frontend/console/src/components/List.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { classNames } from '../utils'

type ListProps<T> = {
items: T[]
renderItem: (item: T) => React.ReactNode
onClick?: (item: T) => void
className?: string
}

export const List = <T,>({ items, renderItem, onClick, className }: ListProps<T>) => {
return (
<ul className={classNames('divide-y divide-gray-100 dark:divide-gray-700 overflow-hidden', className)}>
{items.map((item, index) => (
<li
key={index}
className={`relative flex justify-between gap-x-6 p-4 ${onClick ? 'cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-700' : ''}`}
onClick={onClick ? () => onClick(item) : undefined}
>
{renderItem(item)}
</li>
))}
</ul>
)
}
24 changes: 24 additions & 0 deletions frontend/console/src/components/StatusIndicator.tsx
Original file line number Diff line number Diff line change
@@ -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<StatusIndicatorProps> = ({ 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 (
<div className='flex items-center gap-x-1.5'>
<div className={classNames(backgrounds[state], 'flex-none rounded-full p-1')}>
<div className='h-1.5 w-1.5 rounded-full bg-current' />
</div>
{text && <p className='text-xs leading-5 text-gray-500'>{text}</p>}
</div>
)
}
30 changes: 28 additions & 2 deletions frontend/console/src/features/infrastructure/ControllersList.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<List
items={controllers}
renderItem={(controller) => (
<>
<div className='flex min-w-0 gap-x-4'>
<div className='min-w-0 flex-auto'>
<p className='text-sm font-semibold leading-6'>
<span className='absolute inset-x-0 -top-px bottom-0' />
{controller.key}
</p>
<p className='mt-1 flex text-xs leading-5 text-gray-400 font-roboto-mono'>{controller.endpoint}</p>
</div>
</div>
<div className='flex shrink-0 items-center gap-x-4'>
<div className='hidden sm:flex sm:flex-col sm:items-end'>
<Badge name={controller.version} />
</div>
</div>
</>
)}
/>
)
}
39 changes: 37 additions & 2 deletions frontend/console/src/features/infrastructure/DeploymentsList.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<List
items={deployments}
renderItem={(deployment) => (
<div className='flex w-full'>
<div className='flex gap-x-4 items-center w-1/2'>
<div className='whitespace-nowrap'>
<div className='flex gap-x-2 items-center'>
<p>{deployment.name}</p>
<Badge name={deployment.language} />
</div>

<p className={classNames(deploymentTextColor(deployment.key), 'text-sm font-semibold leading-6')}>{deployment.key}</p>
</div>
</div>
<div className='flex gap-x-4 items-center w-1/2 justify-end'>
<div className='flex flex-wrap gap-1'>
<AttributeBadge key='replicas' name='replicas' value={deployment.replicas.toString()} />
<AttributeBadge key='min_replicas' name='min_replicas' value={deployment.minReplicas.toString()} />
{Object.entries(deployment.labels?.fields || {}).map(([key, value]) => (
<AttributeBadge key={key} name={key} value={renderValue(value)} />
))}
</div>
</div>
</div>
)}
/>
)
}
24 changes: 13 additions & 11 deletions frontend/console/src/features/infrastructure/InfrastructurePage.tsx
Original file line number Diff line number Diff line change
@@ -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'
Expand All @@ -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' },
Expand Down Expand Up @@ -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 <ControllersList />
return <ControllersList controllers={status.data?.controllers || []} />
case 'runners':
return <RunnersList />
return <RunnersList runners={status.data?.runners || []} />
case 'deployments':
return <DeploymentsList />
return <DeploymentsList deployments={status.data?.deployments || []} />
case 'routes':
return <RoutesList />
return <RoutesList routes={status.data?.routes || []} />
default:
return <></>
}
}

const handleTabClick = (tabId: string) => {
navigate(`/infrastructure/${tabId}`)
}

return (
<div className='px-6'>
<Tabs tabs={tabs} initialTabId={currentTab} onTabClick={handleTabClick} />
<div className='mt-4'>{renderTabContent()}</div>
<div className='mt-2'>{renderTabContent()}</div>
</div>
)
}
28 changes: 26 additions & 2 deletions frontend/console/src/features/infrastructure/RoutesList.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<List
items={routes}
renderItem={(route) => (
<div className='flex w-full'>
<div className='flex gap-x-4 items-center w-1/2'>
<div className='whitespace-nowrap'>
<div className='flex gap-x-2 items-center'>{route.module}</div>
<p className='mt-1 flex text-xs leading-5 text-gray-400 font-roboto-mono'>{route.endpoint}</p>
</div>
</div>
<div className='flex gap-x-4 items-center w-1/2 justify-end'>
<div className='flex flex-wrap gap-1 justify-end'>
<AttributeBadge name='deployment' value={route.deployment} />
<AttributeBadge name='runner' value={route.runner} />
</div>
</div>
</div>
)}
/>
)
}
50 changes: 48 additions & 2 deletions frontend/console/src/features/infrastructure/RunnersList.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<List
items={runners}
renderItem={(runner) => (
<>
<div className='flex gap-x-4 items-center'>
<div className='whitespace-nowrap'>
<p className='text-sm font-semibold leading-6'>{runner.key}</p>
<p className='mt-1 flex text-xs leading-5 text-gray-400 font-roboto-mono'>{runner.endpoint}</p>
<div className='mt-1 flex gap-x-2 items-center'>
{status(runner.state)}
{runner.deployment && <p className={classNames(deploymentTextColor(runner.deployment), 'text-xs')}>{runner.deployment}</p>}
</div>
</div>
</div>
<div className='flex gap-x-4 items-center w-1/2'>
<div className='flex flex-wrap gap-1 justify-end'>
{Object.entries(runner.labels?.fields || {}).map(([key, value]) => (
<AttributeBadge key={key} name={key} value={renderValue(value)} />
))}
</div>
</div>
</>
)}
/>
)
}

const status = (state: RunnerState) => {
switch (state) {
case RunnerState.RUNNER_ASSIGNED:
return <StatusIndicator state='success' text='Assigned' />
case RunnerState.RUNNER_RESERVED:
return <StatusIndicator state='success' text='Reserved' />
case RunnerState.RUNNER_DEAD:
return <StatusIndicator state='error' text='Dead' />
case RunnerState.RUNNER_IDLE:
return <StatusIndicator state='idle' text='Idle' />
}
}
Original file line number Diff line number Diff line change
@@ -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 ''
}
}
3 changes: 2 additions & 1 deletion frontend/console/src/providers/routing-provider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ const router = createBrowserRouter(
<Route path='deployments/:deploymentKey' element={<DeploymentPage />} />
<Route path='deployments/:deploymentKey/verbs/:verbName' element={<VerbPage />} />
<Route path='graph' element={<ConsolePage />} />
<Route path='infrastructure' element={<InfrastructurePage />} />
<Route path='infrastructure' element={<Navigate to='infrastructure/controllers' replace />} />
<Route path='infrastructure/*' element={<InfrastructurePage />} />
</Route>

<Route path='*' element={<NotFoundPage />} />
Expand Down

0 comments on commit ee9bf31

Please sign in to comment.