feat: add alpha mode and usage page
This commit is contained in:
parent
4b35cc0588
commit
d3df3f2692
@ -73,8 +73,8 @@ importers:
|
||||
specifier: ^1.4.13
|
||||
version: 1.4.13(@antv/l7@2.20.14)(react-dom@18.2.0)(react@18.2.0)
|
||||
'@i18next-toolkit/react':
|
||||
specifier: ^1.0.4
|
||||
version: 1.0.4(@types/react@18.2.21)(react-dom@18.2.0)(react@18.2.0)
|
||||
specifier: ^1.0.5
|
||||
version: 1.0.5(@types/react@18.2.21)(react-dom@18.2.0)(react@18.2.0)
|
||||
'@loadable/component':
|
||||
specifier: ^5.16.3
|
||||
version: 5.16.3(react@18.2.0)
|
||||
@ -5913,8 +5913,8 @@ packages:
|
||||
- buffer
|
||||
dev: true
|
||||
|
||||
/@i18next-toolkit/react@1.0.4(@types/react@18.2.21)(react-dom@18.2.0)(react@18.2.0):
|
||||
resolution: {integrity: sha512-ZhVIgath7x++jeBe83XRZReHp2VP6tLapVY3zRM0oWnUDaewcmIdKsemorzojqbw0jLorP8gKq4UNR4tMoiVTQ==}
|
||||
/@i18next-toolkit/react@1.0.5(@types/react@18.2.21)(react-dom@18.2.0)(react@18.2.0):
|
||||
resolution: {integrity: sha512-Hc34LF6oT602YG/1orQhZhUyZcAr5pznx0oUPQ+5frI0Hg8eE2ahpY0G7XxDZ7+0D0Bcr3E5n2uNEdzg8IDm5A==}
|
||||
peerDependencies:
|
||||
'@types/react': ^18.2.55
|
||||
react: ^18.2.0
|
||||
|
@ -2,6 +2,7 @@ import { AppRouterOutput, trpc } from '../api/trpc';
|
||||
|
||||
const defaultGlobalConfig: AppRouterOutput['global']['config'] = {
|
||||
allowRegister: false,
|
||||
alphaMode: false,
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -18,7 +18,7 @@
|
||||
"@ant-design/icons": "^5.2.6",
|
||||
"@antv/l7": "^2.20.14",
|
||||
"@antv/larkmap": "^1.4.13",
|
||||
"@i18next-toolkit/react": "^1.0.4",
|
||||
"@i18next-toolkit/react": "^1.0.5",
|
||||
"@loadable/component": "^5.16.3",
|
||||
"@monaco-editor/react": "^4.6.0",
|
||||
"@tanstack/react-query": "4.33.0",
|
||||
|
62
src/client/pages/Settings/Usage.tsx
Normal file
62
src/client/pages/Settings/Usage.tsx
Normal file
@ -0,0 +1,62 @@
|
||||
import { Card, Statistic } from 'antd';
|
||||
import React, { useMemo } from 'react';
|
||||
import { PageHeader } from '../../components/PageHeader';
|
||||
import { useTranslation } from '@i18next-toolkit/react';
|
||||
import { trpc } from '../../api/trpc';
|
||||
import { useCurrentWorkspaceId } from '../../store/user';
|
||||
import dayjs from 'dayjs';
|
||||
import { formatNumber } from '../../utils/common';
|
||||
|
||||
export const Usage: React.FC = React.memo(() => {
|
||||
const { t } = useTranslation();
|
||||
const workspaceId = useCurrentWorkspaceId();
|
||||
const [startDate, endDate] = useMemo(
|
||||
() => [dayjs().startOf('month'), dayjs().endOf('day')],
|
||||
[]
|
||||
);
|
||||
|
||||
const { data } = trpc.billing.usage.useQuery({
|
||||
workspaceId,
|
||||
startAt: startDate.valueOf(),
|
||||
endAt: endDate.valueOf(),
|
||||
});
|
||||
|
||||
return (
|
||||
<div>
|
||||
<PageHeader title={t('Usage')} />
|
||||
|
||||
<Card>
|
||||
<div className="text-lg mb-2">
|
||||
{t('Statistic Date')}:
|
||||
<span className="font-bold ml-2">
|
||||
{startDate.format('YYYY/MM/DD')} - {endDate.format('YYYY/MM/DD')}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="flex gap-2">
|
||||
<Card className="flex-1">
|
||||
<Statistic
|
||||
title={t('Website Accepted Count')}
|
||||
value={formatNumber(data?.websiteAcceptedCount ?? 0)}
|
||||
/>
|
||||
</Card>
|
||||
|
||||
<Card className="flex-1">
|
||||
<Statistic
|
||||
title={t('Website Event Count')}
|
||||
value={formatNumber(data?.websiteEventCount ?? 0)}
|
||||
/>
|
||||
</Card>
|
||||
|
||||
<Card className="flex-1">
|
||||
<Statistic
|
||||
title={t('Monitor Execution Count')}
|
||||
value={formatNumber(data?.monitorExecutionCount ?? 0)}
|
||||
/>
|
||||
</Card>
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
Usage.displayName = 'Usage';
|
@ -1,5 +1,5 @@
|
||||
import { Menu, MenuProps } from 'antd';
|
||||
import React from 'react';
|
||||
import React, { useMemo } from 'react';
|
||||
import { Routes, Route, useLocation, useNavigate } from 'react-router-dom';
|
||||
import { WebsiteInfo } from '../../components/website/WebsiteInfo';
|
||||
import { WebsiteList } from '../../components/website/WebsiteList';
|
||||
@ -8,34 +8,46 @@ import { NotificationList } from './NotificationList';
|
||||
import { Profile } from './Profile';
|
||||
import { AuditLog } from './AuditLog';
|
||||
import { Trans } from '@i18next-toolkit/react';
|
||||
|
||||
const items: MenuProps['items'] = [
|
||||
{
|
||||
key: 'websites',
|
||||
label: <Trans>Websites</Trans>,
|
||||
},
|
||||
{
|
||||
key: 'notifications',
|
||||
label: <Trans>Notifications</Trans>,
|
||||
},
|
||||
{
|
||||
key: 'auditLog',
|
||||
label: <Trans>Audit Log</Trans>,
|
||||
},
|
||||
{
|
||||
key: 'profile',
|
||||
label: <Trans>Profile</Trans>,
|
||||
},
|
||||
];
|
||||
import { compact } from 'lodash-es';
|
||||
import { useGlobalConfig } from '../../hooks/useConfig';
|
||||
import { Usage } from './Usage';
|
||||
|
||||
export const SettingsPage: React.FC = React.memo(() => {
|
||||
const navigate = useNavigate();
|
||||
const { pathname } = useLocation();
|
||||
const { alphaMode } = useGlobalConfig();
|
||||
|
||||
const onClick: MenuProps['onClick'] = useEvent((e) => {
|
||||
navigate(`/settings/${e.key}`);
|
||||
});
|
||||
|
||||
const items: MenuProps['items'] = useMemo(
|
||||
() =>
|
||||
compact([
|
||||
{
|
||||
key: 'websites',
|
||||
label: <Trans>Websites</Trans>,
|
||||
},
|
||||
{
|
||||
key: 'notifications',
|
||||
label: <Trans>Notifications</Trans>,
|
||||
},
|
||||
{
|
||||
key: 'auditLog',
|
||||
label: <Trans>Audit Log</Trans>,
|
||||
},
|
||||
{
|
||||
key: 'profile',
|
||||
label: <Trans>Profile</Trans>,
|
||||
},
|
||||
alphaMode && {
|
||||
key: 'usage',
|
||||
label: <Trans>Usage</Trans>,
|
||||
},
|
||||
]),
|
||||
[alphaMode]
|
||||
);
|
||||
|
||||
const selectedKey =
|
||||
(items.find((item) => pathname.startsWith(`/settings/${item?.key}`))
|
||||
?.key as string) ?? 'websites';
|
||||
@ -59,6 +71,8 @@ export const SettingsPage: React.FC = React.memo(() => {
|
||||
<Route path="/notifications" element={<NotificationList />} />
|
||||
<Route path="/auditLog" element={<AuditLog />} />
|
||||
<Route path="/profile" element={<Profile />} />
|
||||
|
||||
{alphaMode && <Route path="/usage" element={<Usage />} />}
|
||||
</Routes>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -46,6 +46,7 @@
|
||||
"k3dbe79b1": "Löschen",
|
||||
"k3de768a1": "Passwort ändern",
|
||||
"k3e757ddf": "Hier gibt es noch keinen Monitor.",
|
||||
"k42347b91": "Website Event Count",
|
||||
"k43e21ee9": "Client-Reporter herunterladen",
|
||||
"k44cad477": "(Aktuell)",
|
||||
"k46f0adde": "Besucherkarte",
|
||||
@ -103,6 +104,7 @@
|
||||
"k88a9bf01": "Abzeichen anzeigen",
|
||||
"k88d2647b": "Website",
|
||||
"k89056082": "(30 Tage)",
|
||||
"k89d54f7a": "Monitor Execution Count",
|
||||
"k8a44833f": "Dienste",
|
||||
"k8bac6ae0": "6h",
|
||||
"k8ef56a20": "Maximale Wiederholungen, bevor der Dienst als ausgefallen markiert wird und eine Benachrichtigung gesendet wird",
|
||||
@ -113,6 +115,7 @@
|
||||
"k90a82c67": "Konto registrieren",
|
||||
"k90b668e5": "Letzte 24 Stunden",
|
||||
"k93374bc9": "Website löschen",
|
||||
"k951a939a": "Website Accepted Count",
|
||||
"k97ddb155": "Aktuelle Antwort anzeigen",
|
||||
"k98f433ee": "Reporter herunterladen von",
|
||||
"k9a272ecf": "Sind das Ihre Server?",
|
||||
@ -146,6 +149,7 @@
|
||||
"kb5673707": "Letzte 7 Tage",
|
||||
"kb659c1bc": "Zert. Ablauf",
|
||||
"kb8de8c50": "BCC",
|
||||
"kbb31d3db": "Statistic Date",
|
||||
"kbb58c99c": "Erfolgreich gelöscht",
|
||||
"kbcf67f53": "Neues Passwort",
|
||||
"kbd1e7dee": "Nutzung: {{usage}}ms",
|
||||
|
@ -46,6 +46,7 @@
|
||||
"k3dbe79b1": "Delete",
|
||||
"k3de768a1": "Change password",
|
||||
"k3e757ddf": "Here is no monitor yet.",
|
||||
"k42347b91": "Website Event Count",
|
||||
"k43e21ee9": "Download Client Reportor",
|
||||
"k44cad477": "(Current)",
|
||||
"k46f0adde": "Visitor Map",
|
||||
@ -103,6 +104,7 @@
|
||||
"k88a9bf01": "Show Badge",
|
||||
"k88d2647b": "Website",
|
||||
"k89056082": "(30 days)",
|
||||
"k89d54f7a": "Monitor Execution Count",
|
||||
"k8a44833f": "Services",
|
||||
"k8bac6ae0": "6h",
|
||||
"k8ef56a20": "Maximum retries before the service is marked as down and a notification is sent",
|
||||
@ -113,6 +115,7 @@
|
||||
"k90a82c67": "Register Account",
|
||||
"k90b668e5": "Last 24 Hours",
|
||||
"k93374bc9": "Delete Website",
|
||||
"k951a939a": "Website Accepted Count",
|
||||
"k97ddb155": "Show Current Response",
|
||||
"k98f433ee": "Download reporter from",
|
||||
"k9a272ecf": "Is this your servers?",
|
||||
@ -146,6 +149,7 @@
|
||||
"kb5673707": "Last 7 days",
|
||||
"kb659c1bc": "Cert Exp.",
|
||||
"kb8de8c50": "BCC",
|
||||
"kbb31d3db": "Statistic Date",
|
||||
"kbb58c99c": "Delete Success",
|
||||
"kbcf67f53": "New Password",
|
||||
"kbd1e7dee": "Usage: {{usage}}ms",
|
||||
|
@ -46,6 +46,7 @@
|
||||
"k3dbe79b1": "Supprimer",
|
||||
"k3de768a1": "Changer le mot de passe",
|
||||
"k3e757ddf": "Il n'y a pas encore de moniteur ici.",
|
||||
"k42347b91": "Website Event Count",
|
||||
"k43e21ee9": "Télécharger le rapporteur client",
|
||||
"k44cad477": "(Actuel)",
|
||||
"k46f0adde": "Carte des visiteurs",
|
||||
@ -103,6 +104,7 @@
|
||||
"k88a9bf01": "Afficher le badge",
|
||||
"k88d2647b": "Site Web",
|
||||
"k89056082": "(30 jours)",
|
||||
"k89d54f7a": "Monitor Execution Count",
|
||||
"k8a44833f": "Services",
|
||||
"k8bac6ae0": "6h",
|
||||
"k8ef56a20": "Nombre maximal de tentatives avant que le service ne soit marqué comme étant en panne et qu'une notification soit envoyée",
|
||||
@ -113,6 +115,7 @@
|
||||
"k90a82c67": "Créer un compte",
|
||||
"k90b668e5": "24 dernières heures",
|
||||
"k93374bc9": "Supprimer le site Web",
|
||||
"k951a939a": "Website Accepted Count",
|
||||
"k97ddb155": "Afficher la réponse actuelle",
|
||||
"k98f433ee": "Télécharger le rapporteur de",
|
||||
"k9a272ecf": "S'agit-il de vos serveurs ?",
|
||||
@ -146,6 +149,7 @@
|
||||
"kb5673707": "7 derniers jours",
|
||||
"kb659c1bc": "Expiration du cert.",
|
||||
"kb8de8c50": "CCI",
|
||||
"kbb31d3db": "Statistic Date",
|
||||
"kbb58c99c": "Suppression réussie",
|
||||
"kbcf67f53": "Nouveau mot de passe",
|
||||
"kbd1e7dee": "Utilisation : {{usage}}ms",
|
||||
|
@ -46,6 +46,7 @@
|
||||
"k3dbe79b1": "削除",
|
||||
"k3de768a1": "パスワードを変更",
|
||||
"k3e757ddf": "モニターがまだありません。",
|
||||
"k42347b91": "Website Event Count",
|
||||
"k43e21ee9": "クライアントレポーターをダウンロード",
|
||||
"k44cad477": "(現在)",
|
||||
"k46f0adde": "訪問者マップ",
|
||||
@ -103,6 +104,7 @@
|
||||
"k88a9bf01": "バッジを表示",
|
||||
"k88d2647b": "ウェブサイト",
|
||||
"k89056082": "(30日間)",
|
||||
"k89d54f7a": "Monitor Execution Count",
|
||||
"k8a44833f": "サービス",
|
||||
"k8bac6ae0": "6時間",
|
||||
"k8ef56a20": "サービスがダウンとマークされ、通知が送信される前の最大リトライ回数",
|
||||
@ -113,6 +115,7 @@
|
||||
"k90a82c67": "アカウントを登録",
|
||||
"k90b668e5": "過去24時間",
|
||||
"k93374bc9": "ウェブサイトを削除",
|
||||
"k951a939a": "Website Accepted Count",
|
||||
"k97ddb155": "現在の応答を表示",
|
||||
"k98f433ee": "からレポーターをダウンロード",
|
||||
"k9a272ecf": "これはあなたのサーバーですか?",
|
||||
@ -146,6 +149,7 @@
|
||||
"kb5673707": "過去7日間",
|
||||
"kb659c1bc": "証明書の有効期限",
|
||||
"kb8de8c50": "BCC",
|
||||
"kbb31d3db": "Statistic Date",
|
||||
"kbb58c99c": "削除に成功しました",
|
||||
"kbcf67f53": "新しいパスワード",
|
||||
"kbd1e7dee": "使用量:{{usage}}ms",
|
||||
|
@ -46,6 +46,7 @@
|
||||
"k3dbe79b1": "Удалить",
|
||||
"k3de768a1": "Сменить пароль",
|
||||
"k3e757ddf": "Здесь пока нет мониторов.",
|
||||
"k42347b91": "Website Event Count",
|
||||
"k43e21ee9": "Скачать клиентский отчет",
|
||||
"k44cad477": "(Текущий)",
|
||||
"k46f0adde": "Карта посетителей",
|
||||
@ -103,6 +104,7 @@
|
||||
"k88a9bf01": "Показать значок",
|
||||
"k88d2647b": "Веб-сайт",
|
||||
"k89056082": "(30 дней)",
|
||||
"k89d54f7a": "Monitor Execution Count",
|
||||
"k8a44833f": "Сервисы",
|
||||
"k8bac6ae0": "6ч",
|
||||
"k8ef56a20": "Максимальное количество попыток перед тем, как сервис будет помечен как недоступный и будет отправлено уведомление",
|
||||
@ -113,6 +115,7 @@
|
||||
"k90a82c67": "Зарегистрировать аккаунт",
|
||||
"k90b668e5": "Последние 24 часа",
|
||||
"k93374bc9": "Удалить веб-сайт",
|
||||
"k951a939a": "Website Accepted Count",
|
||||
"k97ddb155": "Показать текущий ответ",
|
||||
"k98f433ee": "Скачать репортер с",
|
||||
"k9a272ecf": "Это ваши серверы?",
|
||||
@ -146,6 +149,7 @@
|
||||
"kb5673707": "Последние 7 дней",
|
||||
"kb659c1bc": "Истечение серт.",
|
||||
"kb8de8c50": "Скрытая копия",
|
||||
"kbb31d3db": "Statistic Date",
|
||||
"kbb58c99c": "Успешно удалено",
|
||||
"kbcf67f53": "Новый пароль",
|
||||
"kbd1e7dee": "Использование: {{usage}}мс",
|
||||
|
@ -46,6 +46,7 @@
|
||||
"k3dbe79b1": "删除",
|
||||
"k3de768a1": "更改密码",
|
||||
"k3e757ddf": "这里还没有监控器。",
|
||||
"k42347b91": "Website Event Count",
|
||||
"k43e21ee9": "下载客户端报告器",
|
||||
"k44cad477": "(当前)",
|
||||
"k46f0adde": "访问者地图",
|
||||
@ -103,6 +104,7 @@
|
||||
"k88a9bf01": "显示徽章",
|
||||
"k88d2647b": "网站",
|
||||
"k89056082": "(30天)",
|
||||
"k89d54f7a": "Monitor Execution Count",
|
||||
"k8a44833f": "服务",
|
||||
"k8bac6ae0": "6小时",
|
||||
"k8ef56a20": "服务被标记为下线并发送通知前的最大重试次数",
|
||||
@ -113,6 +115,7 @@
|
||||
"k90a82c67": "注册账户",
|
||||
"k90b668e5": "最近24小时",
|
||||
"k93374bc9": "删除网站",
|
||||
"k951a939a": "Website Accepted Count",
|
||||
"k97ddb155": "显示当前响应",
|
||||
"k98f433ee": "从这里下载报告器",
|
||||
"k9a272ecf": "这是您的服务器吗?",
|
||||
@ -146,6 +149,7 @@
|
||||
"kb5673707": "最近7天",
|
||||
"kb659c1bc": "证书到期",
|
||||
"kb8de8c50": "密送",
|
||||
"kbb31d3db": "Statistic Date",
|
||||
"kbb58c99c": "删除成功",
|
||||
"kbcf67f53": "新密码",
|
||||
"kbd1e7dee": "使用量:{{usage}}毫秒",
|
||||
|
@ -11,7 +11,7 @@ export const auditLogRouter = router({
|
||||
openapi: {
|
||||
method: 'GET',
|
||||
path: '/fetchByCursor',
|
||||
tags: [OPENAPI_TAG.WORKSPACE],
|
||||
tags: [OPENAPI_TAG.AUDIT_LOG],
|
||||
description: 'Fetch workspace audit log',
|
||||
},
|
||||
})
|
||||
|
53
src/server/trpc/routers/billing.ts
Normal file
53
src/server/trpc/routers/billing.ts
Normal file
@ -0,0 +1,53 @@
|
||||
import { z } from 'zod';
|
||||
import { router, workspaceProcedure } from '../trpc';
|
||||
import { OPENAPI_TAG } from '../../utils/const';
|
||||
import { prisma } from '../../model/_client';
|
||||
|
||||
export const billingRouter = router({
|
||||
usage: workspaceProcedure
|
||||
.meta({
|
||||
openapi: {
|
||||
method: 'GET',
|
||||
path: '/usage',
|
||||
tags: [OPENAPI_TAG.BILLING],
|
||||
description: 'get workspace usage',
|
||||
},
|
||||
})
|
||||
.input(
|
||||
z.object({
|
||||
startAt: z.number(),
|
||||
endAt: z.number(),
|
||||
})
|
||||
)
|
||||
.output(
|
||||
z.object({
|
||||
websiteAcceptedCount: z.number(),
|
||||
websiteEventCount: z.number(),
|
||||
monitorExecutionCount: z.number(),
|
||||
})
|
||||
)
|
||||
.query(async ({ input }) => {
|
||||
const { workspaceId, startAt, endAt } = input;
|
||||
|
||||
const res = await prisma.workspaceDailyUsage.aggregate({
|
||||
where: {
|
||||
workspaceId,
|
||||
date: {
|
||||
gte: new Date(startAt),
|
||||
lte: new Date(endAt),
|
||||
},
|
||||
},
|
||||
_sum: {
|
||||
websiteAcceptedCount: true,
|
||||
websiteEventCount: true,
|
||||
monitorExecutionCount: true,
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
websiteAcceptedCount: res._sum.websiteAcceptedCount ?? 0,
|
||||
websiteEventCount: res._sum.websiteEventCount ?? 0,
|
||||
monitorExecutionCount: res._sum.monitorExecutionCount ?? 0,
|
||||
};
|
||||
}),
|
||||
});
|
@ -20,6 +20,7 @@ export const globalRouter = router({
|
||||
websiteId: z.string().optional(),
|
||||
amapToken: z.string().optional(),
|
||||
mapboxToken: z.string().optional(),
|
||||
alphaMode: z.boolean(),
|
||||
})
|
||||
)
|
||||
.query(async ({ input }) => {
|
||||
@ -28,10 +29,7 @@ export const globalRouter = router({
|
||||
websiteId: env.websiteId,
|
||||
amapToken: env.amapToken,
|
||||
mapboxToken: env.mapboxToken,
|
||||
alphaMode: env.alphaMode,
|
||||
};
|
||||
}),
|
||||
});
|
||||
|
||||
function checkEnvTrusty(env: string | undefined): boolean {
|
||||
return env === '1' || env === 'true';
|
||||
}
|
||||
|
@ -7,6 +7,7 @@ import { workspaceRouter } from './workspace';
|
||||
import { globalRouter } from './global';
|
||||
import { serverStatusRouter } from './serverStatus';
|
||||
import { auditLogRouter } from './auditLog';
|
||||
import { billingRouter } from './billing';
|
||||
|
||||
export const appRouter = router({
|
||||
global: globalRouter,
|
||||
@ -17,6 +18,7 @@ export const appRouter = router({
|
||||
monitor: monitorRouter,
|
||||
serverStatus: serverStatusRouter,
|
||||
auditLog: auditLogRouter,
|
||||
billing: billingRouter,
|
||||
});
|
||||
|
||||
export type AppRouter = typeof appRouter;
|
||||
|
@ -107,4 +107,6 @@ export enum OPENAPI_TAG {
|
||||
USER = 'User',
|
||||
WEBSITE = 'Website',
|
||||
MONITOR = 'Monitor',
|
||||
AUDIT_LOG = 'AuditLog',
|
||||
BILLING = 'Billing',
|
||||
}
|
||||
|
@ -10,6 +10,7 @@ export const env = {
|
||||
dbDebug: checkEnvTrusty(process.env.DB_DEBUG),
|
||||
amapToken: process.env.AMAP_TOKEN,
|
||||
mapboxToken: process.env.MAPBOX_TOKEN,
|
||||
alphaMode: checkEnvTrusty(process.env.ALPHA_MODE),
|
||||
};
|
||||
|
||||
export function checkEnvTrusty(env: string | undefined): boolean {
|
||||
|
Loading…
Reference in New Issue
Block a user