feat: add system info card to admin dashboard

Shows cabinet version, bot version, Python version, uptime, users and
active subscriptions at the bottom of the dashboard page.
This commit is contained in:
Fringg
2026-02-08 22:52:14 +03:00
parent 5678dfd558
commit ab0270ac58
9 changed files with 107 additions and 1 deletions

View File

@@ -308,9 +308,23 @@ export interface RecentPaymentsResponse {
total_week_kopeks: number;
}
export interface SystemInfo {
bot_version: string;
python_version: string;
uptime_seconds: number;
users_total: number;
subscriptions_active: number;
}
// ============ Dashboard Stats API ============
export const statsApi = {
// Get system info
getSystemInfo: async (): Promise<SystemInfo> => {
const response = await apiClient.get('/cabinet/admin/stats/system-info');
return response.data;
},
// Get complete dashboard stats
getDashboardStats: async (): Promise<DashboardStats> => {
const response = await apiClient.get('/cabinet/admin/stats/dashboard');

View File

@@ -2205,6 +2205,15 @@
"purchasedWeek": "Week",
"purchasedMonth": "Month",
"noTariffs": "No tariffs"
},
"systemInfo": {
"title": "System",
"cabinet": "Cabinet",
"bot": "Bot",
"python": "Python",
"uptime": "Uptime",
"users": "Users",
"activeSubs": "Active Subs"
}
},
"banSystem": {

View File

@@ -2008,6 +2008,15 @@
"amount": "مبلغ",
"method": "روش",
"date": "تاریخ"
},
"systemInfo": {
"title": "سیستم",
"cabinet": "کابینت",
"bot": "ربات",
"python": "Python",
"uptime": "آپتایم",
"users": "کاربران",
"activeSubs": "اشتراک‌ها"
}
},
"profile": {

View File

@@ -2756,6 +2756,15 @@
"amount": "Сумма",
"method": "Метод",
"date": "Дата"
},
"systemInfo": {
"title": "Система",
"cabinet": "Кабинет",
"bot": "Бот",
"python": "Python",
"uptime": "Аптайм",
"users": "Юзеров",
"activeSubs": "Подписок"
}
},
"banSystem": {

View File

@@ -2007,6 +2007,15 @@
"amount": "金额",
"method": "方法",
"date": "日期"
},
"systemInfo": {
"title": "系统",
"cabinet": "面板",
"bot": "机器人",
"python": "Python",
"uptime": "运行时间",
"users": "用户",
"activeSubs": "订阅"
}
},
"profile": {

View File

@@ -5,10 +5,13 @@ import {
statsApi,
type DashboardStats,
type NodeStatus,
type SystemInfo,
type TopReferrersResponse,
type TopCampaignsResponse,
type RecentPaymentsResponse,
} from '../api/admin';
const CABINET_VERSION = __APP_VERSION__;
import { useCurrency } from '../hooks/useCurrency';
import { usePlatform } from '../platform/hooks/usePlatform';
@@ -375,6 +378,7 @@ export default function AdminDashboard() {
const [campaigns, setCampaigns] = useState<TopCampaignsResponse | null>(null);
const [payments, setPayments] = useState<RecentPaymentsResponse | null>(null);
const [referrersTab, setReferrersTab] = useState<'earnings' | 'invited'>('earnings');
const [systemInfo, setSystemInfo] = useState<SystemInfo | null>(null);
const fetchStats = useCallback(async () => {
try {
@@ -392,14 +396,16 @@ export default function AdminDashboard() {
const fetchExtendedStats = useCallback(async () => {
try {
const [referrersData, campaignsData, paymentsData] = await Promise.all([
const [referrersData, campaignsData, paymentsData, sysInfoData] = await Promise.all([
statsApi.getTopReferrers(10),
statsApi.getTopCampaigns(10),
statsApi.getRecentPayments(20),
statsApi.getSystemInfo(),
]);
setReferrers(referrersData);
setCampaigns(campaignsData);
setPayments(paymentsData);
setSystemInfo(sysInfoData);
} catch (err) {
console.error('Failed to load extended stats:', err);
}
@@ -1137,6 +1143,49 @@ export default function AdminDashboard() {
</div>
</div>
)}
{/* System Info */}
{systemInfo && (
<div className="rounded-xl border border-dark-700 bg-dark-800 p-4">
<h3 className="mb-3 text-sm font-semibold text-dark-300">
{t('adminDashboard.systemInfo.title')}
</h3>
<div className="flex flex-wrap gap-x-6 gap-y-2 text-sm">
<div>
<span className="text-dark-500">{t('adminDashboard.systemInfo.cabinet')}: </span>
<span className="font-medium text-dark-200">v{CABINET_VERSION}</span>
</div>
<div>
<span className="text-dark-500">{t('adminDashboard.systemInfo.bot')}: </span>
<span className="font-medium text-dark-200">v{systemInfo.bot_version}</span>
</div>
<div>
<span className="text-dark-500">{t('adminDashboard.systemInfo.python')}: </span>
<span className="font-medium text-dark-200">{systemInfo.python_version}</span>
</div>
<div>
<span className="text-dark-500">{t('adminDashboard.systemInfo.uptime')}: </span>
<span className="font-medium text-dark-200">
{(() => {
const s = systemInfo.uptime_seconds;
const d = Math.floor(s / 86400);
const h = Math.floor((s % 86400) / 3600);
const m = Math.floor((s % 3600) / 60);
return [d > 0 && `${d}d`, h > 0 && `${h}h`, `${m}m`].filter(Boolean).join(' ');
})()}
</span>
</div>
<div>
<span className="text-dark-500">{t('adminDashboard.systemInfo.users')}: </span>
<span className="font-medium text-dark-200">{systemInfo.users_total}</span>
</div>
<div>
<span className="text-dark-500">{t('adminDashboard.systemInfo.activeSubs')}: </span>
<span className="font-medium text-dark-200">{systemInfo.subscriptions_active}</span>
</div>
</div>
</div>
)}
</div>
);
}

2
src/vite-env.d.ts vendored
View File

@@ -1,5 +1,7 @@
/// <reference types="vite/client" />
declare const __APP_VERSION__: string;
interface ImportMetaEnv {
readonly VITE_API_URL: string;
readonly VITE_TELEGRAM_BOT_USERNAME?: string;

View File

@@ -5,6 +5,7 @@
"module": "ESNext",
"moduleResolution": "bundler",
"allowSyntheticDefaultImports": true,
"resolveJsonModule": true,
"types": ["node"]
},
"include": ["vite.config.ts"]

View File

@@ -1,10 +1,14 @@
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import path from 'path';
import packageJson from './package.json';
// https://vitejs.dev/config/
export default defineConfig({
plugins: [react()],
define: {
__APP_VERSION__: JSON.stringify(packageJson.version),
},
resolve: {
alias: {
'@': path.resolve(__dirname, 'src'),