From 772b0138d94e3c2436f21c72cb97bb8183f4b688 Mon Sep 17 00:00:00 2001 From: Wes Date: Thu, 29 Aug 2024 12:21:52 -0700 Subject: [PATCH] feat: add infrastructure tabs and state --- frontend/e2e/infrastructure.spec.ts | 19 +++++ frontend/src/components/Pill.tsx | 14 ++++ frontend/src/components/Tabs.tsx | 61 ++++++++++++++++ .../infrastructure/ControllersList.tsx | 3 + .../infrastructure/DeploymentsList.tsx | 3 + .../infrastructure/InfrastructurePage.tsx | 69 ++++++++++++++++++- .../features/infrastructure/RoutesList.tsx | 3 + .../features/infrastructure/RunnersList.tsx | 3 + frontend/src/layout/navigation/Navigation.tsx | 2 +- frontend/src/stories/Pill.stories.ts | 16 +++++ frontend/src/stories/Tabs.stories.ts | 22 ++++++ 11 files changed, 213 insertions(+), 2 deletions(-) create mode 100644 frontend/e2e/infrastructure.spec.ts create mode 100644 frontend/src/components/Pill.tsx create mode 100644 frontend/src/components/Tabs.tsx create mode 100644 frontend/src/features/infrastructure/ControllersList.tsx create mode 100644 frontend/src/features/infrastructure/DeploymentsList.tsx create mode 100644 frontend/src/features/infrastructure/RoutesList.tsx create mode 100644 frontend/src/features/infrastructure/RunnersList.tsx create mode 100644 frontend/src/stories/Pill.stories.ts create mode 100644 frontend/src/stories/Tabs.stories.ts diff --git a/frontend/e2e/infrastructure.spec.ts b/frontend/e2e/infrastructure.spec.ts new file mode 100644 index 0000000000..1e02a6f098 --- /dev/null +++ b/frontend/e2e/infrastructure.spec.ts @@ -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(); +}) diff --git a/frontend/src/components/Pill.tsx b/frontend/src/components/Pill.tsx new file mode 100644 index 0000000000..b134d66bca --- /dev/null +++ b/frontend/src/components/Pill.tsx @@ -0,0 +1,14 @@ +import { classNames } from '../utils' + +export const Pill = ({ text, className }: { text: string; className?: string }) => { + return ( + + {text} + + ) +} diff --git a/frontend/src/components/Tabs.tsx b/frontend/src/components/Tabs.tsx new file mode 100644 index 0000000000..c05a081366 --- /dev/null +++ b/frontend/src/components/Tabs.tsx @@ -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 = ({ tabs, initialTabId, onTabClick }) => { + const [selectedTabId, setSelectedTabId] = useState(initialTabId || tabs[0]?.id) + + useEffect(() => { + if (initialTabId) { + setSelectedTabId(initialTabId) + } + }, [initialTabId]) + + const handleTabClick = (tabId: string) => { + setSelectedTabId(tabId) + if (onTabClick) { + onTabClick(tabId) + } + } + + return ( +
+ +
+ ) +} diff --git a/frontend/src/features/infrastructure/ControllersList.tsx b/frontend/src/features/infrastructure/ControllersList.tsx new file mode 100644 index 0000000000..299186833f --- /dev/null +++ b/frontend/src/features/infrastructure/ControllersList.tsx @@ -0,0 +1,3 @@ +export const ControllersList = () => { + return <>Controllers Content +} diff --git a/frontend/src/features/infrastructure/DeploymentsList.tsx b/frontend/src/features/infrastructure/DeploymentsList.tsx new file mode 100644 index 0000000000..cd0e9296e0 --- /dev/null +++ b/frontend/src/features/infrastructure/DeploymentsList.tsx @@ -0,0 +1,3 @@ +export const DeploymentsList = () => { + return <>Deployments Content +} diff --git a/frontend/src/features/infrastructure/InfrastructurePage.tsx b/frontend/src/features/infrastructure/InfrastructurePage.tsx index 3a71d5d211..e031445c62 100644 --- a/frontend/src/features/infrastructure/InfrastructurePage.tsx +++ b/frontend/src/features/infrastructure/InfrastructurePage.tsx @@ -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 + case 'runners': + return + case 'deployments': + return + case 'routes': + return + default: + return <> + } + } + + return ( +
+ +
{renderTabContent()}
+
+ ) } diff --git a/frontend/src/features/infrastructure/RoutesList.tsx b/frontend/src/features/infrastructure/RoutesList.tsx new file mode 100644 index 0000000000..eea618d767 --- /dev/null +++ b/frontend/src/features/infrastructure/RoutesList.tsx @@ -0,0 +1,3 @@ +export const RoutesList = () => { + return <>Routes Content +} diff --git a/frontend/src/features/infrastructure/RunnersList.tsx b/frontend/src/features/infrastructure/RunnersList.tsx new file mode 100644 index 0000000000..3daf525342 --- /dev/null +++ b/frontend/src/features/infrastructure/RunnersList.tsx @@ -0,0 +1,3 @@ +export const RunnersList = () => { + return <>Runners Content +} diff --git a/frontend/src/layout/navigation/Navigation.tsx b/frontend/src/layout/navigation/Navigation.tsx index 9ff869f383..8c4121f13a 100644 --- a/frontend/src/layout/navigation/Navigation.tsx +++ b/frontend/src/layout/navigation/Navigation.tsx @@ -15,7 +15,7 @@ const navigation = [ export const Navigation = ({ version }: { version?: string }) => { return (