feat: add alpha mode and usage page

This commit is contained in:
moonrailgun 2024-02-17 11:45:11 +08:00
parent 4b35cc0588
commit d3df3f2692
18 changed files with 187 additions and 30 deletions

View File

@ -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

View File

@ -2,6 +2,7 @@ import { AppRouterOutput, trpc } from '../api/trpc';
const defaultGlobalConfig: AppRouterOutput['global']['config'] = {
allowRegister: false,
alphaMode: false,
};
/**

View File

@ -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",

View 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';

View File

@ -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,8 +8,22 @@ import { NotificationList } from './NotificationList';
import { Profile } from './Profile';
import { AuditLog } from './AuditLog';
import { Trans } from '@i18next-toolkit/react';
import { compact } from 'lodash-es';
import { useGlobalConfig } from '../../hooks/useConfig';
import { Usage } from './Usage';
const items: MenuProps['items'] = [
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>,
@ -26,15 +40,13 @@ const items: MenuProps['items'] = [
key: 'profile',
label: <Trans>Profile</Trans>,
},
];
export const SettingsPage: React.FC = React.memo(() => {
const navigate = useNavigate();
const { pathname } = useLocation();
const onClick: MenuProps['onClick'] = useEvent((e) => {
navigate(`/settings/${e.key}`);
});
alphaMode && {
key: 'usage',
label: <Trans>Usage</Trans>,
},
]),
[alphaMode]
);
const selectedKey =
(items.find((item) => pathname.startsWith(`/settings/${item?.key}`))
@ -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>

View File

@ -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",

View File

@ -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",

View File

@ -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",

View File

@ -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",

View File

@ -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}}мс",

View File

@ -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}}毫秒",

View File

@ -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',
},
})

View 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,
};
}),
});

View File

@ -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';
}

View File

@ -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;

View File

@ -107,4 +107,6 @@ export enum OPENAPI_TAG {
USER = 'User',
WEBSITE = 'Website',
MONITOR = 'Monitor',
AUDIT_LOG = 'AuditLog',
BILLING = 'Billing',
}

View File

@ -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 {