Skip to content

Commit

Permalink
feat: add infrastructure tabs and state
Browse files Browse the repository at this point in the history
  • Loading branch information
wesbillman committed Aug 29, 2024
1 parent cc2294f commit 772b013
Show file tree
Hide file tree
Showing 11 changed files with 213 additions and 2 deletions.
19 changes: 19 additions & 0 deletions frontend/e2e/infrastructure.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { expect, ftlTest } from './ftl-test'

ftlTest('shows infrastructure', async ({ page }) => {
const infrastructureNavItem = page.getByRole('link', { name: 'Infrastructure' })
await infrastructureNavItem.click()
await expect(page).toHaveURL(/\/infrastructure$/)

const controllersTab = await page.getByRole('button', { name: 'Controllers' });
await expect(controllersTab).toBeVisible();

const runnersTab = await page.getByRole('button', { name: 'Runners' });
await expect(runnersTab).toBeVisible();

const deploymentsTab = await page.getByRole('button', { name: 'Deployments' });
await expect(deploymentsTab).toBeVisible();

const routesTab = await page.getByRole('button', { name: 'Routes' });
await expect(routesTab).toBeVisible();
})
14 changes: 14 additions & 0 deletions frontend/src/components/Pill.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { classNames } from '../utils'

export const Pill = ({ text, className }: { text: string; className?: string }) => {
return (
<span
className={classNames(
'bg-gray-100 text-gray-500 dark:text-gray-400 dark:bg-gray-700 rounded-full px-2.5 py-0.5 text-xs font-medium inline-block',
className,
)}
>
{text}
</span>
)
}
61 changes: 61 additions & 0 deletions frontend/src/components/Tabs.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { type FC, useEffect, useState } from 'react'
import { classNames } from '../utils'
import { Pill } from './Pill'

interface Tab {
name: string
id: string
count?: number
}

interface TabsProps {
tabs: Tab[]
initialTabId?: string
onTabClick?: (tabId: string) => void
}

export const Tabs: FC<TabsProps> = ({ tabs, initialTabId, onTabClick }) => {
const [selectedTabId, setSelectedTabId] = useState<string | undefined>(initialTabId || tabs[0]?.id)

useEffect(() => {
if (initialTabId) {
setSelectedTabId(initialTabId)
}
}, [initialTabId])

const handleTabClick = (tabId: string) => {
setSelectedTabId(tabId)
if (onTabClick) {
onTabClick(tabId)
}
}

return (
<div className='border-b border-gray-200 dark:border-white/10'>
<nav aria-label='Tabs' className='-mb-px flex space-x-8'>
{tabs.map((tab) => (
<button
key={tab.id}
type='button'
aria-current={selectedTabId === tab.id ? 'page' : undefined}
onClick={() => handleTabClick(tab.id)}
className={classNames(
selectedTabId === tab.id
? 'border-indigo-500 text-indigo-500'
: 'border-transparent text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-indigo-400 hover:border-gray-200 dark:hover:border-indigo-400 ',
'flex whitespace-nowrap border-b-2 px-1 py-4 text-sm font-medium',
)}
>
{tab.name}
{tab.count && (
<Pill
text={`${tab.count}`}
className={classNames(selectedTabId === tab.id ? 'bg-indigo-100 text-indigo-600 dark:bg-indigo-500 dark:text-indigo-100' : '', 'ml-2')}
/>
)}
</button>
))}
</nav>
</div>
)
}
3 changes: 3 additions & 0 deletions frontend/src/features/infrastructure/ControllersList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export const ControllersList = () => {
return <>Controllers Content</>
}
3 changes: 3 additions & 0 deletions frontend/src/features/infrastructure/DeploymentsList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export const DeploymentsList = () => {
return <>Deployments Content</>
}
69 changes: 68 additions & 1 deletion frontend/src/features/infrastructure/InfrastructurePage.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,70 @@
import { useEffect, useState } from 'react'
import { useSearchParams } from 'react-router-dom'
import { useStatus } from '../../api/status/use-status'
import { Tabs } from '../../components/Tabs'
import { ControllersList } from './ControllersList'
import { DeploymentsList } from './DeploymentsList'
import { RoutesList } from './RoutesList'
import { RunnersList } from './RunnersList'

export const InfrastructurePage = () => {
return <>Infrastructure</>
const status = useStatus()
const [searchParams, setSearchParams] = useSearchParams()

const [tabs, setTabs] = useState([
{ name: 'Controllers', id: 'controllers' },
{ name: 'Runners', id: 'runners' },
{ name: 'Deployments', id: 'deployments' },
{ name: 'Routes', id: 'routes' },
])

useEffect(() => {
if (!status.data) {
return
}

setTabs((prevTabs) =>
prevTabs.map((tab) => {
switch (tab.id) {
case 'controllers':
return { ...tab, count: status.data.controllers.length }
case 'runners':
return { ...tab, count: status.data.runners.length }
case 'deployments':
return { ...tab, count: status.data.deployments.length }
case 'routes':
return { ...tab, count: status.data.routes.length }
default:
return tab
}
}),
)
}, [status.data])

const handleTabClick = (tabId: string) => {
setSearchParams({ tab: tabId })
}

const currentTab = searchParams.get('tab') || tabs[0].id
const renderTabContent = () => {
switch (currentTab) {
case 'controllers':
return <ControllersList />
case 'runners':
return <RunnersList />
case 'deployments':
return <DeploymentsList />
case 'routes':
return <RoutesList />
default:
return <></>
}
}

return (
<div className='px-6'>
<Tabs tabs={tabs} initialTabId={currentTab} onTabClick={handleTabClick} />
<div className='mt-4'>{renderTabContent()}</div>
</div>
)
}
3 changes: 3 additions & 0 deletions frontend/src/features/infrastructure/RoutesList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export const RoutesList = () => {
return <>Routes Content</>
}
3 changes: 3 additions & 0 deletions frontend/src/features/infrastructure/RunnersList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export const RunnersList = () => {
return <>Runners Content</>
}
2 changes: 1 addition & 1 deletion frontend/src/layout/navigation/Navigation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ const navigation = [
export const Navigation = ({ version }: { version?: string }) => {
return (
<nav className='bg-indigo-600'>
<div className='mx-auto px-4 sm:px-6'>
<div className='mx-auto pl-3 pr-4'>
<div className='flex h-16 items-center justify-between'>
<div className='flex items-center'>
<div>
Expand Down
16 changes: 16 additions & 0 deletions frontend/src/stories/Pill.stories.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import type { StoryObj } from '@storybook/react/*'
import { Pill } from '../components/Pill'

const meta = {
title: 'Components/Pill',
component: Pill,
}

export default meta
type Story = StoryObj<typeof meta>

export const Primary: Story = {
args: {
text: 'name',
},
}
22 changes: 22 additions & 0 deletions frontend/src/stories/Tabs.stories.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import type { StoryObj } from '@storybook/react/*'
import { Tabs } from '../components/Tabs'

const meta = {
title: 'Components/Tabs',
component: Tabs,
}

export default meta
type Story = StoryObj<typeof meta>

export const Primary: Story = {
args: {
tabs: [
{ name: 'First Tab', id: 'first', count: 3 },
{ name: 'Second Tab', id: 'second', count: 1 },
{ name: 'Third Tab', id: 'third' },
],
initialTabId: 'second',
onTabClick: (tabId: string) => console.log(tabId),
},
}

0 comments on commit 772b013

Please sign in to comment.