mirror of
https://github.com/chillpadclub/bedolaga-cabinet.git
synced 2026-07-30 02:23:47 +00:00
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:
@@ -308,9 +308,23 @@ export interface RecentPaymentsResponse {
|
|||||||
total_week_kopeks: number;
|
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 ============
|
// ============ Dashboard Stats API ============
|
||||||
|
|
||||||
export const statsApi = {
|
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
|
// Get complete dashboard stats
|
||||||
getDashboardStats: async (): Promise<DashboardStats> => {
|
getDashboardStats: async (): Promise<DashboardStats> => {
|
||||||
const response = await apiClient.get('/cabinet/admin/stats/dashboard');
|
const response = await apiClient.get('/cabinet/admin/stats/dashboard');
|
||||||
|
|||||||
@@ -2205,6 +2205,15 @@
|
|||||||
"purchasedWeek": "Week",
|
"purchasedWeek": "Week",
|
||||||
"purchasedMonth": "Month",
|
"purchasedMonth": "Month",
|
||||||
"noTariffs": "No tariffs"
|
"noTariffs": "No tariffs"
|
||||||
|
},
|
||||||
|
"systemInfo": {
|
||||||
|
"title": "System",
|
||||||
|
"cabinet": "Cabinet",
|
||||||
|
"bot": "Bot",
|
||||||
|
"python": "Python",
|
||||||
|
"uptime": "Uptime",
|
||||||
|
"users": "Users",
|
||||||
|
"activeSubs": "Active Subs"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"banSystem": {
|
"banSystem": {
|
||||||
|
|||||||
@@ -2008,6 +2008,15 @@
|
|||||||
"amount": "مبلغ",
|
"amount": "مبلغ",
|
||||||
"method": "روش",
|
"method": "روش",
|
||||||
"date": "تاریخ"
|
"date": "تاریخ"
|
||||||
|
},
|
||||||
|
"systemInfo": {
|
||||||
|
"title": "سیستم",
|
||||||
|
"cabinet": "کابینت",
|
||||||
|
"bot": "ربات",
|
||||||
|
"python": "Python",
|
||||||
|
"uptime": "آپتایم",
|
||||||
|
"users": "کاربران",
|
||||||
|
"activeSubs": "اشتراکها"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"profile": {
|
"profile": {
|
||||||
|
|||||||
@@ -2756,6 +2756,15 @@
|
|||||||
"amount": "Сумма",
|
"amount": "Сумма",
|
||||||
"method": "Метод",
|
"method": "Метод",
|
||||||
"date": "Дата"
|
"date": "Дата"
|
||||||
|
},
|
||||||
|
"systemInfo": {
|
||||||
|
"title": "Система",
|
||||||
|
"cabinet": "Кабинет",
|
||||||
|
"bot": "Бот",
|
||||||
|
"python": "Python",
|
||||||
|
"uptime": "Аптайм",
|
||||||
|
"users": "Юзеров",
|
||||||
|
"activeSubs": "Подписок"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"banSystem": {
|
"banSystem": {
|
||||||
|
|||||||
@@ -2007,6 +2007,15 @@
|
|||||||
"amount": "金额",
|
"amount": "金额",
|
||||||
"method": "方法",
|
"method": "方法",
|
||||||
"date": "日期"
|
"date": "日期"
|
||||||
|
},
|
||||||
|
"systemInfo": {
|
||||||
|
"title": "系统",
|
||||||
|
"cabinet": "面板",
|
||||||
|
"bot": "机器人",
|
||||||
|
"python": "Python",
|
||||||
|
"uptime": "运行时间",
|
||||||
|
"users": "用户",
|
||||||
|
"activeSubs": "订阅"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"profile": {
|
"profile": {
|
||||||
|
|||||||
@@ -5,10 +5,13 @@ import {
|
|||||||
statsApi,
|
statsApi,
|
||||||
type DashboardStats,
|
type DashboardStats,
|
||||||
type NodeStatus,
|
type NodeStatus,
|
||||||
|
type SystemInfo,
|
||||||
type TopReferrersResponse,
|
type TopReferrersResponse,
|
||||||
type TopCampaignsResponse,
|
type TopCampaignsResponse,
|
||||||
type RecentPaymentsResponse,
|
type RecentPaymentsResponse,
|
||||||
} from '../api/admin';
|
} from '../api/admin';
|
||||||
|
|
||||||
|
const CABINET_VERSION = __APP_VERSION__;
|
||||||
import { useCurrency } from '../hooks/useCurrency';
|
import { useCurrency } from '../hooks/useCurrency';
|
||||||
import { usePlatform } from '../platform/hooks/usePlatform';
|
import { usePlatform } from '../platform/hooks/usePlatform';
|
||||||
|
|
||||||
@@ -375,6 +378,7 @@ export default function AdminDashboard() {
|
|||||||
const [campaigns, setCampaigns] = useState<TopCampaignsResponse | null>(null);
|
const [campaigns, setCampaigns] = useState<TopCampaignsResponse | null>(null);
|
||||||
const [payments, setPayments] = useState<RecentPaymentsResponse | null>(null);
|
const [payments, setPayments] = useState<RecentPaymentsResponse | null>(null);
|
||||||
const [referrersTab, setReferrersTab] = useState<'earnings' | 'invited'>('earnings');
|
const [referrersTab, setReferrersTab] = useState<'earnings' | 'invited'>('earnings');
|
||||||
|
const [systemInfo, setSystemInfo] = useState<SystemInfo | null>(null);
|
||||||
|
|
||||||
const fetchStats = useCallback(async () => {
|
const fetchStats = useCallback(async () => {
|
||||||
try {
|
try {
|
||||||
@@ -392,14 +396,16 @@ export default function AdminDashboard() {
|
|||||||
|
|
||||||
const fetchExtendedStats = useCallback(async () => {
|
const fetchExtendedStats = useCallback(async () => {
|
||||||
try {
|
try {
|
||||||
const [referrersData, campaignsData, paymentsData] = await Promise.all([
|
const [referrersData, campaignsData, paymentsData, sysInfoData] = await Promise.all([
|
||||||
statsApi.getTopReferrers(10),
|
statsApi.getTopReferrers(10),
|
||||||
statsApi.getTopCampaigns(10),
|
statsApi.getTopCampaigns(10),
|
||||||
statsApi.getRecentPayments(20),
|
statsApi.getRecentPayments(20),
|
||||||
|
statsApi.getSystemInfo(),
|
||||||
]);
|
]);
|
||||||
setReferrers(referrersData);
|
setReferrers(referrersData);
|
||||||
setCampaigns(campaignsData);
|
setCampaigns(campaignsData);
|
||||||
setPayments(paymentsData);
|
setPayments(paymentsData);
|
||||||
|
setSystemInfo(sysInfoData);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Failed to load extended stats:', err);
|
console.error('Failed to load extended stats:', err);
|
||||||
}
|
}
|
||||||
@@ -1137,6 +1143,49 @@ export default function AdminDashboard() {
|
|||||||
</div>
|
</div>
|
||||||
</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>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
2
src/vite-env.d.ts
vendored
2
src/vite-env.d.ts
vendored
@@ -1,5 +1,7 @@
|
|||||||
/// <reference types="vite/client" />
|
/// <reference types="vite/client" />
|
||||||
|
|
||||||
|
declare const __APP_VERSION__: string;
|
||||||
|
|
||||||
interface ImportMetaEnv {
|
interface ImportMetaEnv {
|
||||||
readonly VITE_API_URL: string;
|
readonly VITE_API_URL: string;
|
||||||
readonly VITE_TELEGRAM_BOT_USERNAME?: string;
|
readonly VITE_TELEGRAM_BOT_USERNAME?: string;
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
"module": "ESNext",
|
"module": "ESNext",
|
||||||
"moduleResolution": "bundler",
|
"moduleResolution": "bundler",
|
||||||
"allowSyntheticDefaultImports": true,
|
"allowSyntheticDefaultImports": true,
|
||||||
|
"resolveJsonModule": true,
|
||||||
"types": ["node"]
|
"types": ["node"]
|
||||||
},
|
},
|
||||||
"include": ["vite.config.ts"]
|
"include": ["vite.config.ts"]
|
||||||
|
|||||||
@@ -1,10 +1,14 @@
|
|||||||
import { defineConfig } from 'vite';
|
import { defineConfig } from 'vite';
|
||||||
import react from '@vitejs/plugin-react';
|
import react from '@vitejs/plugin-react';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
|
import packageJson from './package.json';
|
||||||
|
|
||||||
// https://vitejs.dev/config/
|
// https://vitejs.dev/config/
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
plugins: [react()],
|
plugins: [react()],
|
||||||
|
define: {
|
||||||
|
__APP_VERSION__: JSON.stringify(packageJson.version),
|
||||||
|
},
|
||||||
resolve: {
|
resolve: {
|
||||||
alias: {
|
alias: {
|
||||||
'@': path.resolve(__dirname, 'src'),
|
'@': path.resolve(__dirname, 'src'),
|
||||||
|
|||||||
Reference in New Issue
Block a user