diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 9409323..f8552aa 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -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
diff --git a/src/client/hooks/useConfig.ts b/src/client/hooks/useConfig.ts
index af58930..210a1b0 100644
--- a/src/client/hooks/useConfig.ts
+++ b/src/client/hooks/useConfig.ts
@@ -2,6 +2,7 @@ import { AppRouterOutput, trpc } from '../api/trpc';
const defaultGlobalConfig: AppRouterOutput['global']['config'] = {
allowRegister: false,
+ alphaMode: false,
};
/**
diff --git a/src/client/i18next-toolkit.config.js b/src/client/i18next-toolkit.config.cjs
similarity index 100%
rename from src/client/i18next-toolkit.config.js
rename to src/client/i18next-toolkit.config.cjs
diff --git a/src/client/package.json b/src/client/package.json
index 30b3877..67e1e3e 100644
--- a/src/client/package.json
+++ b/src/client/package.json
@@ -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",
diff --git a/src/client/pages/Settings/Usage.tsx b/src/client/pages/Settings/Usage.tsx
new file mode 100644
index 0000000..2d9398d
--- /dev/null
+++ b/src/client/pages/Settings/Usage.tsx
@@ -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 (
+
+
+
+
+
+ {t('Statistic Date')}:
+
+ {startDate.format('YYYY/MM/DD')} - {endDate.format('YYYY/MM/DD')}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+});
+Usage.displayName = 'Usage';
diff --git a/src/client/pages/Settings/index.tsx b/src/client/pages/Settings/index.tsx
index 26a4253..3846d5b 100644
--- a/src/client/pages/Settings/index.tsx
+++ b/src/client/pages/Settings/index.tsx
@@ -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: Websites,
- },
- {
- key: 'notifications',
- label: Notifications,
- },
- {
- key: 'auditLog',
- label: Audit Log,
- },
- {
- key: 'profile',
- label: Profile,
- },
-];
+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: Websites,
+ },
+ {
+ key: 'notifications',
+ label: Notifications,
+ },
+ {
+ key: 'auditLog',
+ label: Audit Log,
+ },
+ {
+ key: 'profile',
+ label: Profile,
+ },
+ alphaMode && {
+ key: 'usage',
+ label: Usage,
+ },
+ ]),
+ [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(() => {
} />
} />
} />
+
+ {alphaMode && } />}
diff --git a/src/client/public/locales/de/translation.json b/src/client/public/locales/de/translation.json
index c27e6cc..b58687a 100644
--- a/src/client/public/locales/de/translation.json
+++ b/src/client/public/locales/de/translation.json
@@ -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",
diff --git a/src/client/public/locales/en/translation.json b/src/client/public/locales/en/translation.json
index a9e00b9..1610224 100644
--- a/src/client/public/locales/en/translation.json
+++ b/src/client/public/locales/en/translation.json
@@ -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",
diff --git a/src/client/public/locales/fr/translation.json b/src/client/public/locales/fr/translation.json
index 806f6c8..ad1989b 100644
--- a/src/client/public/locales/fr/translation.json
+++ b/src/client/public/locales/fr/translation.json
@@ -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",
diff --git a/src/client/public/locales/jp/translation.json b/src/client/public/locales/jp/translation.json
index d5f1fd2..98b25b2 100644
--- a/src/client/public/locales/jp/translation.json
+++ b/src/client/public/locales/jp/translation.json
@@ -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",
diff --git a/src/client/public/locales/ru/translation.json b/src/client/public/locales/ru/translation.json
index 97347b4..7af131d 100644
--- a/src/client/public/locales/ru/translation.json
+++ b/src/client/public/locales/ru/translation.json
@@ -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}}мс",
diff --git a/src/client/public/locales/zh/translation.json b/src/client/public/locales/zh/translation.json
index c534816..ed8adad 100644
--- a/src/client/public/locales/zh/translation.json
+++ b/src/client/public/locales/zh/translation.json
@@ -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}}毫秒",
diff --git a/src/server/trpc/routers/auditLog.ts b/src/server/trpc/routers/auditLog.ts
index 27642d2..de6efcd 100644
--- a/src/server/trpc/routers/auditLog.ts
+++ b/src/server/trpc/routers/auditLog.ts
@@ -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',
},
})
diff --git a/src/server/trpc/routers/billing.ts b/src/server/trpc/routers/billing.ts
new file mode 100644
index 0000000..07e35f0
--- /dev/null
+++ b/src/server/trpc/routers/billing.ts
@@ -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,
+ };
+ }),
+});
diff --git a/src/server/trpc/routers/global.ts b/src/server/trpc/routers/global.ts
index 4f091a8..e9f6baa 100644
--- a/src/server/trpc/routers/global.ts
+++ b/src/server/trpc/routers/global.ts
@@ -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';
-}
diff --git a/src/server/trpc/routers/index.ts b/src/server/trpc/routers/index.ts
index 33e04aa..8f5da64 100644
--- a/src/server/trpc/routers/index.ts
+++ b/src/server/trpc/routers/index.ts
@@ -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;
diff --git a/src/server/utils/const.ts b/src/server/utils/const.ts
index 2d2119e..8b72749 100644
--- a/src/server/utils/const.ts
+++ b/src/server/utils/const.ts
@@ -107,4 +107,6 @@ export enum OPENAPI_TAG {
USER = 'User',
WEBSITE = 'Website',
MONITOR = 'Monitor',
+ AUDIT_LOG = 'AuditLog',
+ BILLING = 'Billing',
}
diff --git a/src/server/utils/env.ts b/src/server/utils/env.ts
index ce583ef..e47cce2 100644
--- a/src/server/utils/env.ts
+++ b/src/server/utils/env.ts
@@ -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 {