diff --git a/src/client/components/CommonList.tsx b/src/client/components/CommonList.tsx index 38bd795..6d9f0be 100644 --- a/src/client/components/CommonList.tsx +++ b/src/client/components/CommonList.tsx @@ -46,7 +46,7 @@ export const CommonList: React.FC = React.memo((props) => { const finalList = searchResult ?? props.items; return ( - <> +
{props.hasSearch && (
@@ -63,7 +63,7 @@ export const CommonList: React.FC = React.memo((props) => {
)} - +
{finalList.map((item) => { const isSelected = item.href === location.pathname; @@ -105,7 +105,7 @@ export const CommonList: React.FC = React.memo((props) => { })}
- +
); }); CommonList.displayName = 'CommonList'; diff --git a/src/client/components/CommonSidebar.tsx b/src/client/components/CommonSidebar.tsx new file mode 100644 index 0000000..633f586 --- /dev/null +++ b/src/client/components/CommonSidebar.tsx @@ -0,0 +1,22 @@ +import React from 'react'; +import { Separator } from './ui/separator'; + +interface CommonSidebarProps extends React.PropsWithChildren { + header: React.ReactNode; +} +export const CommonSidebar: React.FC = React.memo( + (props) => { + return ( +
+
+ {props.header} +
+ + + +
{props.children}
+
+ ); + } +); +CommonSidebar.displayName = 'CommonSidebar'; diff --git a/src/client/components/website/AddWebsiteBtn.tsx b/src/client/components/website/AddWebsiteBtn.tsx index 45a8e4d..4bd6c1e 100644 --- a/src/client/components/website/AddWebsiteBtn.tsx +++ b/src/client/components/website/AddWebsiteBtn.tsx @@ -24,7 +24,7 @@ import { FormLabel, FormMessage, } from '@/components/ui/form'; -import { useEvent, useEventWithLoading } from '@/hooks/useEvent'; +import { useEvent } from '@/hooks/useEvent'; import { Input } from '../ui/input'; import { preventDefault } from '@/utils/dom'; import { trpc } from '@/api/trpc'; diff --git a/src/client/pages/LayoutV2.tsx b/src/client/pages/LayoutV2.tsx index 7083024..9b8d3ad 100644 --- a/src/client/pages/LayoutV2.tsx +++ b/src/client/pages/LayoutV2.tsx @@ -17,14 +17,10 @@ import { cn } from '@/utils/style'; import { Separator } from '@/components/ui/separator'; import { Nav } from './Layout/Nav'; import { WorkspaceSwitcher } from '@/components/WorkspaceSwitcher'; -import { ColorSchemeSwitcher } from '@/components/ColorSchemeSwitcher'; -import { LanguageSelector } from '@/components/LanguageSelector'; -import { Avatar, AvatarFallback } from '@/components/ui/avatar'; -import { useUserInfo } from '@/store/user'; -import { Button } from '@/components/ui/button'; import { UserConfig } from './Layout/UserConfig'; import { Outlet } from '@tanstack/react-router'; -import { CommonList, CommonListItem } from '@/components/CommonList'; +import { trpc } from '@/api/trpc'; +import { useCurrentWorkspaceId } from '@/store/user'; const defaultLayout: [number, number, number] = [265, 440, 655]; @@ -41,6 +37,10 @@ export const LayoutV2: React.FC<{ defaultValue: false, } ); + const workspaceId = useCurrentWorkspaceId(); + const { data: serviceCount } = trpc.workspace.getServiceCount.useQuery({ + workspaceId, + }); return ( @@ -83,13 +83,13 @@ export const LayoutV2: React.FC<{ links={[ { title: 'Website', - label: '128', + label: String(serviceCount?.website ?? ''), icon: LuAreaChart, variant: 'default', }, { title: 'Monitor', - label: '9', + label: String(serviceCount?.monitor ?? ''), icon: LuMonitorDot, variant: 'ghost', }, @@ -101,13 +101,13 @@ export const LayoutV2: React.FC<{ }, { title: 'Telemetry', - label: '', + label: String(serviceCount?.telemetry ?? ''), icon: LuWifi, variant: 'ghost', }, { title: 'Pages', - label: '', + label: String(serviceCount?.page ?? ''), icon: LuFilePieChart, variant: 'ghost', }, @@ -121,7 +121,7 @@ export const LayoutV2: React.FC<{ -
{props.list}
+
{props.list}
diff --git a/src/client/routes/website.tsx b/src/client/routes/website.tsx index 76ae923..dded314 100644 --- a/src/client/routes/website.tsx +++ b/src/client/routes/website.tsx @@ -1,5 +1,6 @@ import { trpc } from '@/api/trpc'; import { CommonList } from '@/components/CommonList'; +import { CommonSidebar } from '@/components/CommonSidebar'; import { Separator } from '@/components/ui/separator'; import { AddWebsiteBtn } from '@/components/website/AddWebsiteBtn'; import { useDataReady } from '@/hooks/useDataReady'; @@ -54,18 +55,19 @@ function WebsiteComponent() { return ( -
-

{t('Website')}

- -
- -
-
- + +

{t('Website')}

+
+ +
+ + } + > - +
} /> ); diff --git a/src/server/trpc/routers/website.ts b/src/server/trpc/routers/website.ts index 9bb705d..1db3175 100644 --- a/src/server/trpc/routers/website.ts +++ b/src/server/trpc/routers/website.ts @@ -64,7 +64,7 @@ export const websiteRouter = router({ .meta({ openapi: { method: 'GET', - path: `/workspace/{workspaceId}/website/all`, + path: '/workspace/{workspaceId}/website/all', tags: [OPENAPI_TAG.WEBSITE], protect: true, }, diff --git a/src/server/trpc/routers/workspace.ts b/src/server/trpc/routers/workspace.ts index 1c11b20..5eea5ad 100644 --- a/src/server/trpc/routers/workspace.ts +++ b/src/server/trpc/routers/workspace.ts @@ -1,8 +1,16 @@ -import { publicProcedure, router, workspaceOwnerProcedure } from '../trpc'; +import { + OpenApiMetaInfo, + publicProcedure, + router, + workspaceOwnerProcedure, + workspaceProcedure, +} from '../trpc'; import { z } from 'zod'; import { prisma } from '../../model/_client'; import { workspaceDashboardLayoutSchema } from '../../model/_schema'; import { Prisma } from '@prisma/client'; +import { OPENAPI_TAG } from '../../utils/const'; +import { OpenApiMeta } from 'trpc-openapi'; export const workspaceRouter = router({ getUserWorkspaceRole: publicProcedure @@ -27,6 +35,54 @@ export const workspaceRouter = router({ return relation?.role ?? null; }), + getServiceCount: workspaceProcedure + .meta( + buildWorkspaceOpenapi({ + method: 'GET', + path: '/getServiceCount', + }) + ) + .output( + z.object({ + website: z.number(), + monitor: z.number(), + telemetry: z.number(), + page: z.number(), + }) + ) + .query(async ({ input }) => { + const { workspaceId } = input; + + const [website, monitor, telemetry, page] = await Promise.all([ + prisma.website.count({ + where: { + workspaceId, + }, + }), + prisma.monitor.count({ + where: { + workspaceId, + }, + }), + prisma.telemetry.count({ + where: { + workspaceId, + }, + }), + prisma.monitorStatusPage.count({ + where: { + workspaceId, + }, + }), + ]); + + return { + website, + monitor, + telemetry, + page, + }; + }), updateDashboardOrder: workspaceOwnerProcedure .input( z.object({ @@ -64,3 +120,14 @@ export const workspaceRouter = router({ }); }), }); + +function buildWorkspaceOpenapi(meta: OpenApiMetaInfo): OpenApiMeta { + return { + openapi: { + tags: [OPENAPI_TAG.WORKSPACE], + protect: true, + ...meta, + path: `/workspace/{workspaceId}${meta.path}`, + }, + }; +}