feat: add telemetry list
This commit is contained in:
parent
dd0ad8c5de
commit
5e720abb11
@ -9,7 +9,7 @@ import { Register } from './pages/Register';
|
|||||||
import { QueryClientProvider } from '@tanstack/react-query';
|
import { QueryClientProvider } from '@tanstack/react-query';
|
||||||
import { queryClient } from './api/cache';
|
import { queryClient } from './api/cache';
|
||||||
import { TokenLoginContainer } from './components/TokenLoginContainer';
|
import { TokenLoginContainer } from './components/TokenLoginContainer';
|
||||||
import React, { Suspense, useRef } from 'react';
|
import React, { useRef } from 'react';
|
||||||
import { trpc, trpcClient } from './api/trpc';
|
import { trpc, trpcClient } from './api/trpc';
|
||||||
import { MonitorPage } from './pages/Monitor';
|
import { MonitorPage } from './pages/Monitor';
|
||||||
import { WebsitePage } from './pages/Website';
|
import { WebsitePage } from './pages/Website';
|
||||||
@ -19,7 +19,7 @@ import { ConfigProvider, theme } from 'antd';
|
|||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
import { useSettingsStore } from './store/settings';
|
import { useSettingsStore } from './store/settings';
|
||||||
import { StatusPage } from './pages/Status';
|
import { StatusPage } from './pages/Status';
|
||||||
import { Loading } from './components/Loading';
|
import { TelemetryPage } from './pages/Telemetry';
|
||||||
|
|
||||||
export const AppRoutes: React.FC = React.memo(() => {
|
export const AppRoutes: React.FC = React.memo(() => {
|
||||||
const { info } = useUserStore();
|
const { info } = useUserStore();
|
||||||
@ -35,6 +35,7 @@ export const AppRoutes: React.FC = React.memo(() => {
|
|||||||
<Route path="/monitor/*" element={<MonitorPage />} />
|
<Route path="/monitor/*" element={<MonitorPage />} />
|
||||||
<Route path="/website/*" element={<WebsitePage />} />
|
<Route path="/website/*" element={<WebsitePage />} />
|
||||||
<Route path="/servers" element={<Servers />} />
|
<Route path="/servers" element={<Servers />} />
|
||||||
|
<Route path="/telemetry/*" element={<TelemetryPage />} />
|
||||||
<Route path="/settings/*" element={<SettingsPage />} />
|
<Route path="/settings/*" element={<SettingsPage />} />
|
||||||
</Route>
|
</Route>
|
||||||
) : (
|
) : (
|
||||||
|
146
src/client/components/telemetry/TelemetryList.tsx
Normal file
146
src/client/components/telemetry/TelemetryList.tsx
Normal file
@ -0,0 +1,146 @@
|
|||||||
|
import { t } from '@i18next-toolkit/react';
|
||||||
|
import { Button, Form, Input, Modal, Table } from 'antd';
|
||||||
|
import React, { useMemo, useState } from 'react';
|
||||||
|
import { AppRouterOutput, trpc } from '../../api/trpc';
|
||||||
|
import { useCurrentWorkspaceId } from '../../store/user';
|
||||||
|
import { type ColumnsType } from 'antd/es/table/interface';
|
||||||
|
import {
|
||||||
|
BarChartOutlined,
|
||||||
|
EditOutlined,
|
||||||
|
PlusOutlined,
|
||||||
|
} from '@ant-design/icons';
|
||||||
|
import { useNavigate } from 'react-router';
|
||||||
|
import { PageHeader } from '../PageHeader';
|
||||||
|
import { useEvent } from '../../hooks/useEvent';
|
||||||
|
|
||||||
|
type TelemetryInfo = AppRouterOutput['telemetry']['all'][number];
|
||||||
|
|
||||||
|
export const TelemetryList: React.FC = React.memo(() => {
|
||||||
|
const workspaceId = useCurrentWorkspaceId();
|
||||||
|
const [isModalOpen, setIsModalOpen] = useState(false);
|
||||||
|
const [form] = Form.useForm<{ id?: string; name: string }>();
|
||||||
|
const upsertTelemetryMutation = trpc.telemetry.upsert.useMutation();
|
||||||
|
const utils = trpc.useUtils();
|
||||||
|
|
||||||
|
const handleAddTelemetry = useEvent(async () => {
|
||||||
|
await form.validateFields();
|
||||||
|
const values = form.getFieldsValue();
|
||||||
|
|
||||||
|
await upsertTelemetryMutation.mutateAsync({
|
||||||
|
telemetryId: values.id,
|
||||||
|
workspaceId,
|
||||||
|
name: values.name,
|
||||||
|
});
|
||||||
|
|
||||||
|
utils.telemetry.all.refetch();
|
||||||
|
|
||||||
|
setIsModalOpen(false);
|
||||||
|
|
||||||
|
form.resetFields();
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleEditTelemetry = useEvent(async (info: TelemetryInfo) => {
|
||||||
|
setIsModalOpen(true);
|
||||||
|
form.setFieldsValue({
|
||||||
|
id: info.id,
|
||||||
|
name: info.name,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<PageHeader
|
||||||
|
title={t('Telemetry')}
|
||||||
|
action={
|
||||||
|
<div>
|
||||||
|
<Button
|
||||||
|
type="primary"
|
||||||
|
icon={<PlusOutlined />}
|
||||||
|
size="large"
|
||||||
|
onClick={() => setIsModalOpen(true)}
|
||||||
|
>
|
||||||
|
{t('Add Telemetry')}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<TelemetryListTable onEdit={handleEditTelemetry} />
|
||||||
|
|
||||||
|
<Modal
|
||||||
|
title={t('Add Telemetry')}
|
||||||
|
open={isModalOpen}
|
||||||
|
okButtonProps={{
|
||||||
|
loading: upsertTelemetryMutation.isLoading,
|
||||||
|
}}
|
||||||
|
onOk={() => handleAddTelemetry()}
|
||||||
|
onCancel={() => setIsModalOpen(false)}
|
||||||
|
>
|
||||||
|
<Form layout="vertical" form={form}>
|
||||||
|
<Form.Item name="id" hidden={true} />
|
||||||
|
<Form.Item
|
||||||
|
label={t('Telemetry Name')}
|
||||||
|
name="name"
|
||||||
|
tooltip={t('Telemetry Name to Display')}
|
||||||
|
rules={[{ required: true }]}
|
||||||
|
>
|
||||||
|
<Input />
|
||||||
|
</Form.Item>
|
||||||
|
</Form>
|
||||||
|
</Modal>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
TelemetryList.displayName = 'TelemetryList';
|
||||||
|
|
||||||
|
const TelemetryListTable: React.FC<{
|
||||||
|
onEdit: (info: TelemetryInfo) => void;
|
||||||
|
}> = React.memo((props) => {
|
||||||
|
const workspaceId = useCurrentWorkspaceId();
|
||||||
|
const { data = [], isLoading } = trpc.telemetry.all.useQuery({
|
||||||
|
workspaceId,
|
||||||
|
});
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
const columns = useMemo((): ColumnsType<TelemetryInfo> => {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
dataIndex: 'name',
|
||||||
|
title: t('Name'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'action',
|
||||||
|
render: (_, record) => {
|
||||||
|
return (
|
||||||
|
<div className="flex gap-2 justify-end">
|
||||||
|
<Button
|
||||||
|
icon={<EditOutlined />}
|
||||||
|
onClick={() => props.onEdit(record)}
|
||||||
|
>
|
||||||
|
{t('Edit')}
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
icon={<BarChartOutlined />}
|
||||||
|
onClick={() => {
|
||||||
|
navigate(`/telemetry/${record.id}`);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t('View')}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
] as ColumnsType<TelemetryInfo>;
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Table
|
||||||
|
loading={isLoading}
|
||||||
|
dataSource={data}
|
||||||
|
columns={columns}
|
||||||
|
rowKey="id"
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
TelemetryListTable.displayName = 'TelemetryListTable';
|
@ -12,6 +12,7 @@ import { useIsMobile } from '../hooks/useIsMobile';
|
|||||||
import { RiMenuUnfoldLine } from 'react-icons/ri';
|
import { RiMenuUnfoldLine } from 'react-icons/ri';
|
||||||
import { useTranslation } from '@i18next-toolkit/react';
|
import { useTranslation } from '@i18next-toolkit/react';
|
||||||
import { LanguageSelector } from '../components/LanguageSelector';
|
import { LanguageSelector } from '../components/LanguageSelector';
|
||||||
|
import { useGlobalConfig } from '../hooks/useConfig';
|
||||||
|
|
||||||
export const Layout: React.FC = React.memo(() => {
|
export const Layout: React.FC = React.memo(() => {
|
||||||
const [params] = useSearchParams();
|
const [params] = useSearchParams();
|
||||||
@ -34,6 +35,7 @@ export const Layout: React.FC = React.memo(() => {
|
|||||||
const showHeader = !params.has('hideHeader');
|
const showHeader = !params.has('hideHeader');
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
const { alphaMode } = useGlobalConfig();
|
||||||
|
|
||||||
const accountEl = (
|
const accountEl = (
|
||||||
<Dropdown
|
<Dropdown
|
||||||
@ -117,6 +119,14 @@ export const Layout: React.FC = React.memo(() => {
|
|||||||
label={t('Servers')}
|
label={t('Servers')}
|
||||||
onClick={() => setOpenDraw(false)}
|
onClick={() => setOpenDraw(false)}
|
||||||
/>
|
/>
|
||||||
|
{alphaMode && (
|
||||||
|
<MobileNavItem
|
||||||
|
to="/telemetry"
|
||||||
|
label={t('Telemetry')}
|
||||||
|
onClick={() => setOpenDraw(false)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
<MobileNavItem
|
<MobileNavItem
|
||||||
to="/settings"
|
to="/settings"
|
||||||
label={t('Settings')}
|
label={t('Settings')}
|
||||||
@ -147,6 +157,7 @@ export const Layout: React.FC = React.memo(() => {
|
|||||||
<NavItem to="/monitor" label={t('Monitor')} />
|
<NavItem to="/monitor" label={t('Monitor')} />
|
||||||
<NavItem to="/website" label={t('Website')} />
|
<NavItem to="/website" label={t('Website')} />
|
||||||
<NavItem to="/servers" label={t('Servers')} />
|
<NavItem to="/servers" label={t('Servers')} />
|
||||||
|
<NavItem to="/telemetry" label={t('Telemetry')} />
|
||||||
<NavItem to="/settings" label={t('Settings')} />
|
<NavItem to="/settings" label={t('Settings')} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
7
src/client/pages/Telemetry/index.tsx
Normal file
7
src/client/pages/Telemetry/index.tsx
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { TelemetryList } from '../../components/telemetry/TelemetryList';
|
||||||
|
|
||||||
|
export const TelemetryPage: React.FC = React.memo(() => {
|
||||||
|
return <TelemetryList />;
|
||||||
|
});
|
||||||
|
TelemetryPage.displayName = 'TelemetryPage';
|
@ -0,0 +1,35 @@
|
|||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "TelemetryEvent" ADD COLUMN "telemetryId" VARCHAR(30);
|
||||||
|
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "TelemetrySession" ADD COLUMN "telemetryId" VARCHAR(30);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "Telemetry" (
|
||||||
|
"id" VARCHAR(30) NOT NULL,
|
||||||
|
"workspaceId" VARCHAR(30) NOT NULL,
|
||||||
|
"name" VARCHAR(100) NOT NULL,
|
||||||
|
"createdAt" TIMESTAMPTZ(6) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"updatedAt" TIMESTAMPTZ(6) NOT NULL,
|
||||||
|
"deletedAt" TIMESTAMPTZ(6),
|
||||||
|
|
||||||
|
CONSTRAINT "Telemetry_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE UNIQUE INDEX "Telemetry_id_key" ON "Telemetry"("id");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE INDEX "Telemetry_workspaceId_idx" ON "Telemetry"("workspaceId");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE INDEX "Telemetry_createdAt_idx" ON "Telemetry"("createdAt");
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "Telemetry" ADD CONSTRAINT "Telemetry_workspaceId_fkey" FOREIGN KEY ("workspaceId") REFERENCES "Workspace"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "TelemetrySession" ADD CONSTRAINT "TelemetrySession_telemetryId_fkey" FOREIGN KEY ("telemetryId") REFERENCES "Telemetry"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "TelemetryEvent" ADD CONSTRAINT "TelemetryEvent_telemetryId_fkey" FOREIGN KEY ("telemetryId") REFERENCES "Telemetry"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
@ -49,6 +49,7 @@ model Workspace {
|
|||||||
notifications Notification[]
|
notifications Notification[]
|
||||||
monitors Monitor[]
|
monitors Monitor[]
|
||||||
monitorStatusPages MonitorStatusPage[]
|
monitorStatusPages MonitorStatusPage[]
|
||||||
|
telemetryList Telemetry[]
|
||||||
|
|
||||||
// for user currentWorkspace
|
// for user currentWorkspace
|
||||||
selectedUsers User[] // user list who select this workspace, not use in most of case
|
selectedUsers User[] // user list who select this workspace, not use in most of case
|
||||||
@ -201,9 +202,27 @@ model WebsiteSessionData {
|
|||||||
@@index([sessionId])
|
@@index([sessionId])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
model Telemetry {
|
||||||
|
id String @id @unique @default(cuid()) @db.VarChar(30)
|
||||||
|
workspaceId String @db.VarChar(30)
|
||||||
|
name String @db.VarChar(100)
|
||||||
|
createdAt DateTime @default(now()) @db.Timestamptz(6)
|
||||||
|
updatedAt DateTime @updatedAt @db.Timestamptz(6)
|
||||||
|
deletedAt DateTime? @db.Timestamptz(6)
|
||||||
|
|
||||||
|
workspace Workspace @relation(fields: [workspaceId], references: [id], onUpdate: Cascade, onDelete: Cascade)
|
||||||
|
|
||||||
|
sessions TelemetrySession[]
|
||||||
|
events TelemetryEvent[]
|
||||||
|
|
||||||
|
@@index([workspaceId])
|
||||||
|
@@index([createdAt])
|
||||||
|
}
|
||||||
|
|
||||||
model TelemetrySession {
|
model TelemetrySession {
|
||||||
id String @id @unique @db.Uuid
|
id String @id @unique @db.Uuid
|
||||||
workspaceId String @db.VarChar(30)
|
workspaceId String @db.VarChar(30)
|
||||||
|
telemetryId String? @db.VarChar(30) // if null, means Default
|
||||||
hostname String? @db.VarChar(100)
|
hostname String? @db.VarChar(100)
|
||||||
browser String? @db.VarChar(20)
|
browser String? @db.VarChar(20)
|
||||||
os String? @db.VarChar(20)
|
os String? @db.VarChar(20)
|
||||||
@ -217,6 +236,7 @@ model TelemetrySession {
|
|||||||
accuracyRadius Int?
|
accuracyRadius Int?
|
||||||
createdAt DateTime @default(now()) @db.Timestamptz(6)
|
createdAt DateTime @default(now()) @db.Timestamptz(6)
|
||||||
|
|
||||||
|
telemetry Telemetry? @relation(fields: [telemetryId], references: [id], onUpdate: Cascade, onDelete: Cascade)
|
||||||
telemetryEvent TelemetryEvent[]
|
telemetryEvent TelemetryEvent[]
|
||||||
|
|
||||||
@@index([createdAt])
|
@@index([createdAt])
|
||||||
@ -225,8 +245,9 @@ model TelemetrySession {
|
|||||||
|
|
||||||
model TelemetryEvent {
|
model TelemetryEvent {
|
||||||
id String @id() @default(cuid()) @db.VarChar(30)
|
id String @id() @default(cuid()) @db.VarChar(30)
|
||||||
sessionId String @db.Uuid
|
|
||||||
workspaceId String @db.VarChar(30)
|
workspaceId String @db.VarChar(30)
|
||||||
|
telemetryId String? @db.VarChar(30) // if null, means Default
|
||||||
|
sessionId String @db.Uuid
|
||||||
eventName String? @db.VarChar(100)
|
eventName String? @db.VarChar(100)
|
||||||
urlOrigin String @db.VarChar(500)
|
urlOrigin String @db.VarChar(500)
|
||||||
urlPath String @db.VarChar(500)
|
urlPath String @db.VarChar(500)
|
||||||
@ -236,7 +257,8 @@ model TelemetryEvent {
|
|||||||
payload Json? @db.Json // Other payload info get from query params, should be a object
|
payload Json? @db.Json // Other payload info get from query params, should be a object
|
||||||
createdAt DateTime @default(now()) @db.Timestamptz(6)
|
createdAt DateTime @default(now()) @db.Timestamptz(6)
|
||||||
|
|
||||||
session TelemetrySession @relation(fields: [sessionId], references: [id], onUpdate: Cascade, onDelete: Cascade)
|
telemetry Telemetry? @relation(fields: [telemetryId], references: [id], onUpdate: Cascade, onDelete: Cascade)
|
||||||
|
session TelemetrySession @relation(fields: [sessionId], references: [id], onUpdate: Cascade, onDelete: Cascade)
|
||||||
|
|
||||||
@@index([createdAt])
|
@@index([createdAt])
|
||||||
@@index([sessionId])
|
@@index([sessionId])
|
||||||
|
@ -6,6 +6,7 @@ export * from "./websitesession"
|
|||||||
export * from "./websiteevent"
|
export * from "./websiteevent"
|
||||||
export * from "./websiteeventdata"
|
export * from "./websiteeventdata"
|
||||||
export * from "./websitesessiondata"
|
export * from "./websitesessiondata"
|
||||||
|
export * from "./telemetry"
|
||||||
export * from "./telemetrysession"
|
export * from "./telemetrysession"
|
||||||
export * from "./telemetryevent"
|
export * from "./telemetryevent"
|
||||||
export * from "./notification"
|
export * from "./notification"
|
||||||
|
29
src/server/prisma/zod/telemetry.ts
Normal file
29
src/server/prisma/zod/telemetry.ts
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
import * as z from "zod"
|
||||||
|
import * as imports from "./schemas"
|
||||||
|
import { CompleteWorkspace, RelatedWorkspaceModelSchema, CompleteTelemetrySession, RelatedTelemetrySessionModelSchema, CompleteTelemetryEvent, RelatedTelemetryEventModelSchema } from "./index"
|
||||||
|
|
||||||
|
export const TelemetryModelSchema = z.object({
|
||||||
|
id: z.string(),
|
||||||
|
workspaceId: z.string(),
|
||||||
|
name: z.string(),
|
||||||
|
createdAt: z.date(),
|
||||||
|
updatedAt: z.date(),
|
||||||
|
deletedAt: z.date().nullish(),
|
||||||
|
})
|
||||||
|
|
||||||
|
export interface CompleteTelemetry extends z.infer<typeof TelemetryModelSchema> {
|
||||||
|
workspace: CompleteWorkspace
|
||||||
|
sessions: CompleteTelemetrySession[]
|
||||||
|
events: CompleteTelemetryEvent[]
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* RelatedTelemetryModelSchema contains all relations on your model in addition to the scalars
|
||||||
|
*
|
||||||
|
* NOTE: Lazy required in case of potential circular dependencies within schema
|
||||||
|
*/
|
||||||
|
export const RelatedTelemetryModelSchema: z.ZodSchema<CompleteTelemetry> = z.lazy(() => TelemetryModelSchema.extend({
|
||||||
|
workspace: RelatedWorkspaceModelSchema,
|
||||||
|
sessions: RelatedTelemetrySessionModelSchema.array(),
|
||||||
|
events: RelatedTelemetryEventModelSchema.array(),
|
||||||
|
}))
|
@ -1,6 +1,6 @@
|
|||||||
import * as z from "zod"
|
import * as z from "zod"
|
||||||
import * as imports from "./schemas"
|
import * as imports from "./schemas"
|
||||||
import { CompleteTelemetrySession, RelatedTelemetrySessionModelSchema } from "./index"
|
import { CompleteTelemetry, RelatedTelemetryModelSchema, CompleteTelemetrySession, RelatedTelemetrySessionModelSchema } from "./index"
|
||||||
|
|
||||||
// Helper schema for JSON fields
|
// Helper schema for JSON fields
|
||||||
type Literal = boolean | number | string
|
type Literal = boolean | number | string
|
||||||
@ -10,8 +10,9 @@ const jsonSchema: z.ZodSchema<Json> = z.lazy(() => z.union([literalSchema, z.arr
|
|||||||
|
|
||||||
export const TelemetryEventModelSchema = z.object({
|
export const TelemetryEventModelSchema = z.object({
|
||||||
id: z.string(),
|
id: z.string(),
|
||||||
sessionId: z.string(),
|
|
||||||
workspaceId: z.string(),
|
workspaceId: z.string(),
|
||||||
|
telemetryId: z.string().nullish(),
|
||||||
|
sessionId: z.string(),
|
||||||
eventName: z.string().nullish(),
|
eventName: z.string().nullish(),
|
||||||
urlOrigin: z.string(),
|
urlOrigin: z.string(),
|
||||||
urlPath: z.string(),
|
urlPath: z.string(),
|
||||||
@ -23,6 +24,7 @@ export const TelemetryEventModelSchema = z.object({
|
|||||||
})
|
})
|
||||||
|
|
||||||
export interface CompleteTelemetryEvent extends z.infer<typeof TelemetryEventModelSchema> {
|
export interface CompleteTelemetryEvent extends z.infer<typeof TelemetryEventModelSchema> {
|
||||||
|
telemetry?: CompleteTelemetry | null
|
||||||
session: CompleteTelemetrySession
|
session: CompleteTelemetrySession
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -32,5 +34,6 @@ export interface CompleteTelemetryEvent extends z.infer<typeof TelemetryEventMod
|
|||||||
* NOTE: Lazy required in case of potential circular dependencies within schema
|
* NOTE: Lazy required in case of potential circular dependencies within schema
|
||||||
*/
|
*/
|
||||||
export const RelatedTelemetryEventModelSchema: z.ZodSchema<CompleteTelemetryEvent> = z.lazy(() => TelemetryEventModelSchema.extend({
|
export const RelatedTelemetryEventModelSchema: z.ZodSchema<CompleteTelemetryEvent> = z.lazy(() => TelemetryEventModelSchema.extend({
|
||||||
|
telemetry: RelatedTelemetryModelSchema.nullish(),
|
||||||
session: RelatedTelemetrySessionModelSchema,
|
session: RelatedTelemetrySessionModelSchema,
|
||||||
}))
|
}))
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
import * as z from "zod"
|
import * as z from "zod"
|
||||||
import * as imports from "./schemas"
|
import * as imports from "./schemas"
|
||||||
import { CompleteTelemetryEvent, RelatedTelemetryEventModelSchema } from "./index"
|
import { CompleteTelemetry, RelatedTelemetryModelSchema, CompleteTelemetryEvent, RelatedTelemetryEventModelSchema } from "./index"
|
||||||
|
|
||||||
export const TelemetrySessionModelSchema = z.object({
|
export const TelemetrySessionModelSchema = z.object({
|
||||||
id: z.string(),
|
id: z.string(),
|
||||||
workspaceId: z.string(),
|
workspaceId: z.string(),
|
||||||
|
telemetryId: z.string().nullish(),
|
||||||
hostname: z.string().nullish(),
|
hostname: z.string().nullish(),
|
||||||
browser: z.string().nullish(),
|
browser: z.string().nullish(),
|
||||||
os: z.string().nullish(),
|
os: z.string().nullish(),
|
||||||
@ -20,6 +21,7 @@ export const TelemetrySessionModelSchema = z.object({
|
|||||||
})
|
})
|
||||||
|
|
||||||
export interface CompleteTelemetrySession extends z.infer<typeof TelemetrySessionModelSchema> {
|
export interface CompleteTelemetrySession extends z.infer<typeof TelemetrySessionModelSchema> {
|
||||||
|
telemetry?: CompleteTelemetry | null
|
||||||
telemetryEvent: CompleteTelemetryEvent[]
|
telemetryEvent: CompleteTelemetryEvent[]
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -29,5 +31,6 @@ export interface CompleteTelemetrySession extends z.infer<typeof TelemetrySessio
|
|||||||
* NOTE: Lazy required in case of potential circular dependencies within schema
|
* NOTE: Lazy required in case of potential circular dependencies within schema
|
||||||
*/
|
*/
|
||||||
export const RelatedTelemetrySessionModelSchema: z.ZodSchema<CompleteTelemetrySession> = z.lazy(() => TelemetrySessionModelSchema.extend({
|
export const RelatedTelemetrySessionModelSchema: z.ZodSchema<CompleteTelemetrySession> = z.lazy(() => TelemetrySessionModelSchema.extend({
|
||||||
|
telemetry: RelatedTelemetryModelSchema.nullish(),
|
||||||
telemetryEvent: RelatedTelemetryEventModelSchema.array(),
|
telemetryEvent: RelatedTelemetryEventModelSchema.array(),
|
||||||
}))
|
}))
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import * as z from "zod"
|
import * as z from "zod"
|
||||||
import * as imports from "./schemas"
|
import * as imports from "./schemas"
|
||||||
import { CompleteWorkspacesOnUsers, RelatedWorkspacesOnUsersModelSchema, CompleteWebsite, RelatedWebsiteModelSchema, CompleteNotification, RelatedNotificationModelSchema, CompleteMonitor, RelatedMonitorModelSchema, CompleteMonitorStatusPage, RelatedMonitorStatusPageModelSchema, CompleteUser, RelatedUserModelSchema, CompleteWorkspaceDailyUsage, RelatedWorkspaceDailyUsageModelSchema, CompleteWorkspaceAuditLog, RelatedWorkspaceAuditLogModelSchema } from "./index"
|
import { CompleteWorkspacesOnUsers, RelatedWorkspacesOnUsersModelSchema, CompleteWebsite, RelatedWebsiteModelSchema, CompleteNotification, RelatedNotificationModelSchema, CompleteMonitor, RelatedMonitorModelSchema, CompleteMonitorStatusPage, RelatedMonitorStatusPageModelSchema, CompleteTelemetry, RelatedTelemetryModelSchema, CompleteUser, RelatedUserModelSchema, CompleteWorkspaceDailyUsage, RelatedWorkspaceDailyUsageModelSchema, CompleteWorkspaceAuditLog, RelatedWorkspaceAuditLogModelSchema } from "./index"
|
||||||
|
|
||||||
// Helper schema for JSON fields
|
// Helper schema for JSON fields
|
||||||
type Literal = boolean | number | string
|
type Literal = boolean | number | string
|
||||||
@ -30,6 +30,7 @@ export interface CompleteWorkspace extends z.infer<typeof WorkspaceModelSchema>
|
|||||||
notifications: CompleteNotification[]
|
notifications: CompleteNotification[]
|
||||||
monitors: CompleteMonitor[]
|
monitors: CompleteMonitor[]
|
||||||
monitorStatusPages: CompleteMonitorStatusPage[]
|
monitorStatusPages: CompleteMonitorStatusPage[]
|
||||||
|
telemetryList: CompleteTelemetry[]
|
||||||
selectedUsers: CompleteUser[]
|
selectedUsers: CompleteUser[]
|
||||||
workspaceDailyUsage: CompleteWorkspaceDailyUsage[]
|
workspaceDailyUsage: CompleteWorkspaceDailyUsage[]
|
||||||
workspaceAuditLog: CompleteWorkspaceAuditLog[]
|
workspaceAuditLog: CompleteWorkspaceAuditLog[]
|
||||||
@ -46,6 +47,7 @@ export const RelatedWorkspaceModelSchema: z.ZodSchema<CompleteWorkspace> = z.laz
|
|||||||
notifications: RelatedNotificationModelSchema.array(),
|
notifications: RelatedNotificationModelSchema.array(),
|
||||||
monitors: RelatedMonitorModelSchema.array(),
|
monitors: RelatedMonitorModelSchema.array(),
|
||||||
monitorStatusPages: RelatedMonitorStatusPageModelSchema.array(),
|
monitorStatusPages: RelatedMonitorStatusPageModelSchema.array(),
|
||||||
|
telemetryList: RelatedTelemetryModelSchema.array(),
|
||||||
selectedUsers: RelatedUserModelSchema.array(),
|
selectedUsers: RelatedUserModelSchema.array(),
|
||||||
workspaceDailyUsage: RelatedWorkspaceDailyUsageModelSchema.array(),
|
workspaceDailyUsage: RelatedWorkspaceDailyUsageModelSchema.array(),
|
||||||
workspaceAuditLog: RelatedWorkspaceAuditLogModelSchema.array(),
|
workspaceAuditLog: RelatedWorkspaceAuditLogModelSchema.array(),
|
||||||
|
@ -1,20 +1,20 @@
|
|||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
import { router, workspaceProcedure } from '../trpc';
|
import { OpenApiMetaInfo, router, workspaceProcedure } from '../trpc';
|
||||||
import { OPENAPI_TAG } from '../../utils/const';
|
import { OPENAPI_TAG } from '../../utils/const';
|
||||||
import { WorkspaceAuditLogModelSchema } from '../../prisma/zod';
|
import { WorkspaceAuditLogModelSchema } from '../../prisma/zod';
|
||||||
import { prisma } from '../../model/_client';
|
import { prisma } from '../../model/_client';
|
||||||
import { fetchDataByCursor } from '../../utils/prisma';
|
import { fetchDataByCursor } from '../../utils/prisma';
|
||||||
|
import { OpenApiMeta } from 'trpc-openapi';
|
||||||
|
|
||||||
export const auditLogRouter = router({
|
export const auditLogRouter = router({
|
||||||
fetchByCursor: workspaceProcedure
|
fetchByCursor: workspaceProcedure
|
||||||
.meta({
|
.meta(
|
||||||
openapi: {
|
buildAuditLogOpenapi({
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
path: '/fetchByCursor',
|
path: '/fetchByCursor',
|
||||||
tags: [OPENAPI_TAG.AUDIT_LOG],
|
|
||||||
description: 'Fetch workspace audit log',
|
description: 'Fetch workspace audit log',
|
||||||
},
|
})
|
||||||
})
|
)
|
||||||
.input(
|
.input(
|
||||||
z.object({
|
z.object({
|
||||||
limit: z.number().min(1).max(100).default(50),
|
limit: z.number().min(1).max(100).default(50),
|
||||||
@ -47,3 +47,14 @@ export const auditLogRouter = router({
|
|||||||
};
|
};
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function buildAuditLogOpenapi(meta: OpenApiMetaInfo): OpenApiMeta {
|
||||||
|
return {
|
||||||
|
openapi: {
|
||||||
|
tags: [OPENAPI_TAG.AUDIT_LOG],
|
||||||
|
protect: true,
|
||||||
|
...meta,
|
||||||
|
path: `/audit${meta.path}`,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
@ -1,18 +1,18 @@
|
|||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
import { router, workspaceProcedure } from '../trpc';
|
import { OpenApiMetaInfo, router, workspaceProcedure } from '../trpc';
|
||||||
import { OPENAPI_TAG } from '../../utils/const';
|
import { OPENAPI_TAG } from '../../utils/const';
|
||||||
import { prisma } from '../../model/_client';
|
import { prisma } from '../../model/_client';
|
||||||
|
import { OpenApiMeta } from 'trpc-openapi';
|
||||||
|
|
||||||
export const billingRouter = router({
|
export const billingRouter = router({
|
||||||
usage: workspaceProcedure
|
usage: workspaceProcedure
|
||||||
.meta({
|
.meta(
|
||||||
openapi: {
|
buildBillingOpenapi({
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
path: '/usage',
|
path: '/usage',
|
||||||
tags: [OPENAPI_TAG.BILLING],
|
|
||||||
description: 'get workspace usage',
|
description: 'get workspace usage',
|
||||||
},
|
})
|
||||||
})
|
)
|
||||||
.input(
|
.input(
|
||||||
z.object({
|
z.object({
|
||||||
startAt: z.number(),
|
startAt: z.number(),
|
||||||
@ -51,3 +51,14 @@ export const billingRouter = router({
|
|||||||
};
|
};
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function buildBillingOpenapi(meta: OpenApiMetaInfo): OpenApiMeta {
|
||||||
|
return {
|
||||||
|
openapi: {
|
||||||
|
tags: [OPENAPI_TAG.BILLING],
|
||||||
|
protect: true,
|
||||||
|
...meta,
|
||||||
|
path: `/billing${meta.path}`,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
@ -8,6 +8,7 @@ import { globalRouter } from './global';
|
|||||||
import { serverStatusRouter } from './serverStatus';
|
import { serverStatusRouter } from './serverStatus';
|
||||||
import { auditLogRouter } from './auditLog';
|
import { auditLogRouter } from './auditLog';
|
||||||
import { billingRouter } from './billing';
|
import { billingRouter } from './billing';
|
||||||
|
import { telemetryRouter } from './telemetry';
|
||||||
|
|
||||||
export const appRouter = router({
|
export const appRouter = router({
|
||||||
global: globalRouter,
|
global: globalRouter,
|
||||||
@ -16,6 +17,7 @@ export const appRouter = router({
|
|||||||
website: websiteRouter,
|
website: websiteRouter,
|
||||||
notification: notificationRouter,
|
notification: notificationRouter,
|
||||||
monitor: monitorRouter,
|
monitor: monitorRouter,
|
||||||
|
telemetry: telemetryRouter,
|
||||||
serverStatus: serverStatusRouter,
|
serverStatus: serverStatusRouter,
|
||||||
auditLog: auditLogRouter,
|
auditLog: auditLogRouter,
|
||||||
billing: billingRouter,
|
billing: billingRouter,
|
||||||
|
83
src/server/trpc/routers/telemetry.ts
Normal file
83
src/server/trpc/routers/telemetry.ts
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
import { z } from 'zod';
|
||||||
|
import {
|
||||||
|
OpenApiMetaInfo,
|
||||||
|
router,
|
||||||
|
workspaceOwnerProcedure,
|
||||||
|
workspaceProcedure,
|
||||||
|
} from '../trpc';
|
||||||
|
import { OPENAPI_TAG } from '../../utils/const';
|
||||||
|
import { prisma } from '../../model/_client';
|
||||||
|
import { TelemetryModelSchema } from '../../prisma/zod';
|
||||||
|
import { OpenApiMeta } from 'trpc-openapi';
|
||||||
|
|
||||||
|
export const telemetryRouter = router({
|
||||||
|
all: workspaceProcedure
|
||||||
|
.meta(
|
||||||
|
buildTelemetryOpenapi({
|
||||||
|
method: 'GET',
|
||||||
|
path: '/all',
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.output(z.array(TelemetryModelSchema))
|
||||||
|
.query(async ({ input }) => {
|
||||||
|
const { workspaceId } = input;
|
||||||
|
|
||||||
|
const res = await prisma.telemetry.findMany({
|
||||||
|
where: {
|
||||||
|
workspaceId,
|
||||||
|
},
|
||||||
|
orderBy: {
|
||||||
|
updatedAt: 'desc',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}),
|
||||||
|
upsert: workspaceOwnerProcedure
|
||||||
|
.meta(
|
||||||
|
buildTelemetryOpenapi({
|
||||||
|
method: 'POST',
|
||||||
|
path: '/upsert',
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.input(
|
||||||
|
z.object({
|
||||||
|
telemetryId: z.string().optional(),
|
||||||
|
name: z.string(),
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.output(TelemetryModelSchema)
|
||||||
|
.mutation(async ({ input }) => {
|
||||||
|
const { workspaceId, telemetryId, name } = input;
|
||||||
|
|
||||||
|
if (telemetryId) {
|
||||||
|
return prisma.telemetry.update({
|
||||||
|
where: {
|
||||||
|
id: telemetryId,
|
||||||
|
workspaceId,
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
name,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
return prisma.telemetry.create({
|
||||||
|
data: {
|
||||||
|
workspaceId,
|
||||||
|
name,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
function buildTelemetryOpenapi(meta: OpenApiMetaInfo): OpenApiMeta {
|
||||||
|
return {
|
||||||
|
openapi: {
|
||||||
|
tags: [OPENAPI_TAG.TELEMETRY],
|
||||||
|
protect: true,
|
||||||
|
...meta,
|
||||||
|
path: `/workspace/{workspaceId}${meta.path}`,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
@ -109,4 +109,5 @@ export enum OPENAPI_TAG {
|
|||||||
MONITOR = 'Monitor',
|
MONITOR = 'Monitor',
|
||||||
AUDIT_LOG = 'AuditLog',
|
AUDIT_LOG = 'AuditLog',
|
||||||
BILLING = 'Billing',
|
BILLING = 'Billing',
|
||||||
|
TELEMETRY = 'Telemetry',
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user