From a15b3d410157f916c6008f7dbbe24b1284d3d595 Mon Sep 17 00:00:00 2001 From: Fringg Date: Sun, 8 Feb 2026 23:20:49 +0300 Subject: [PATCH] feat: add admin updates page with release history Shows bot and cabinet releases with version badges, changelogs, and update detection via __APP_VERSION__. --- src/App.tsx | 11 ++ src/api/adminUpdates.ts | 32 ++++ src/locales/en.json | 19 ++- src/locales/fa.json | 19 ++- src/locales/ru.json | 19 ++- src/locales/zh.json | 19 ++- src/pages/AdminPanel.tsx | 8 +- src/pages/AdminUpdates.tsx | 310 +++++++++++++++++++++++++++++++++++++ 8 files changed, 428 insertions(+), 9 deletions(-) create mode 100644 src/api/adminUpdates.ts create mode 100644 src/pages/AdminUpdates.tsx diff --git a/src/App.tsx b/src/App.tsx index 0e5031a..121f49b 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -70,6 +70,7 @@ const AdminRemnawave = lazy(() => import('./pages/AdminRemnawave')); const AdminRemnawaveSquadDetail = lazy(() => import('./pages/AdminRemnawaveSquadDetail')); const AdminEmailTemplates = lazy(() => import('./pages/AdminEmailTemplates')); const AdminTrafficUsage = lazy(() => import('./pages/AdminTrafficUsage')); +const AdminUpdates = lazy(() => import('./pages/AdminUpdates')); const AdminUserDetail = lazy(() => import('./pages/AdminUserDetail')); const AdminBroadcastDetail = lazy(() => import('./pages/AdminBroadcastDetail')); const AdminEmailTemplatePreview = lazy(() => import('./pages/AdminEmailTemplatePreview')); @@ -656,6 +657,16 @@ function App() { } /> + + + + + + } + /> => { + const response = await apiClient.get('/cabinet/admin/updates/releases'); + return response.data; + }, +}; diff --git a/src/locales/en.json b/src/locales/en.json index afd36c6..df1bd61 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -762,7 +762,8 @@ "promoOffers": "Promo Offers", "promocodes": "Promo Codes", "promoGroups": "Discount Groups", - "trafficUsage": "Traffic Usage" + "trafficUsage": "Traffic Usage", + "updates": "Updates" }, "panel": { "title": "Admin Panel", @@ -785,7 +786,8 @@ "promoOffersDesc": "Personal discounts", "promocodesDesc": "Manage promo codes", "promoGroupsDesc": "Discount groups for users", - "trafficUsageDesc": "Per-user traffic by nodes" + "trafficUsageDesc": "Per-user traffic by nodes", + "updatesDesc": "Bot & cabinet versions" }, "trafficUsage": { "title": "Traffic Usage", @@ -2113,6 +2115,19 @@ } } }, + "adminUpdates": { + "title": "Updates", + "subtitle": "Bot and cabinet release history", + "bot": "Bot", + "cabinet": "Cabinet", + "currentVersion": "Current version", + "upToDate": "Up to date", + "updateAvailable": "Update available", + "published": "Published", + "prerelease": "Pre-release", + "refresh": "Check for updates", + "noReleases": "No release data" + }, "adminDashboard": { "title": "Statistics Dashboard", "subtitle": "Real-time system overview", diff --git a/src/locales/fa.json b/src/locales/fa.json index f5e4d25..c1be32b 100644 --- a/src/locales/fa.json +++ b/src/locales/fa.json @@ -643,7 +643,8 @@ "promoGroups": "گروه‌های تخفیف", "remnawave": "RemnaWave", "users": "کاربران", - "trafficUsage": "مصرف ترافیک" + "trafficUsage": "مصرف ترافیک", + "updates": "به‌روزرسانی‌ها" }, "panel": { "title": "پنل مدیریت", @@ -666,7 +667,8 @@ "promoGroupsDesc": "گروه‌های تخفیف برای کاربران", "remnawaveDesc": "مدیریت پنل و آمار", "usersDesc": "مدیریت کاربران ربات", - "trafficUsageDesc": "ترافیک هر کاربر بر اساس نود" + "trafficUsageDesc": "ترافیک هر کاربر بر اساس نود", + "updatesDesc": "نسخه‌های ربات و کابینت" }, "trafficUsage": { "title": "مصرف ترافیک", @@ -1928,6 +1930,19 @@ "saturation": "اشباع" } }, + "adminUpdates": { + "title": "به‌روزرسانی‌ها", + "subtitle": "تاریخچه انتشار ربات و کابینت", + "bot": "ربات", + "cabinet": "کابینت", + "currentVersion": "نسخه فعلی", + "upToDate": "به‌روز است", + "updateAvailable": "به‌روزرسانی موجود", + "published": "منتشر شده", + "prerelease": "پیش‌انتشار", + "refresh": "بررسی به‌روزرسانی", + "noReleases": "داده‌ای موجود نیست" + }, "adminDashboard": { "title": "پنل آمار", "subtitle": "نمای کلی سیستم در لحظه", diff --git a/src/locales/ru.json b/src/locales/ru.json index ce8de31..bd2f537 100644 --- a/src/locales/ru.json +++ b/src/locales/ru.json @@ -783,7 +783,8 @@ "promoOffers": "Промопредложения", "promocodes": "Промокоды", "promoGroups": "Группы скидок", - "trafficUsage": "Расход трафика" + "trafficUsage": "Расход трафика", + "updates": "Обновления" }, "panel": { "title": "Панель администратора", @@ -806,7 +807,8 @@ "promoOffersDesc": "Персональные скидки", "promocodesDesc": "Управление промокодами", "promoGroupsDesc": "Группы скидок для пользователей", - "trafficUsageDesc": "Трафик пользователей по нодам" + "trafficUsageDesc": "Трафик пользователей по нодам", + "updatesDesc": "Версии бота и кабинета" }, "trafficUsage": { "title": "Расход трафика", @@ -2659,6 +2661,19 @@ "saturation": "Насыщенность" } }, + "adminUpdates": { + "title": "Обновления", + "subtitle": "История релизов бота и кабинета", + "bot": "Бот", + "cabinet": "Кабинет", + "currentVersion": "Текущая версия", + "upToDate": "Актуальная версия", + "updateAvailable": "Доступно обновление", + "published": "Опубликовано", + "prerelease": "Предрелиз", + "refresh": "Проверить обновления", + "noReleases": "Нет данных о релизах" + }, "adminDashboard": { "title": "Панель статистики", "subtitle": "Обзор системы в реальном времени", diff --git a/src/locales/zh.json b/src/locales/zh.json index 9a56aec..3fc8ca5 100644 --- a/src/locales/zh.json +++ b/src/locales/zh.json @@ -643,7 +643,8 @@ "promoGroups": "折扣组", "remnawave": "RemnaWave", "users": "用户", - "trafficUsage": "流量使用" + "trafficUsage": "流量使用", + "updates": "更新" }, "panel": { "title": "管理面板", @@ -666,7 +667,8 @@ "promoGroupsDesc": "用户折扣组", "remnawaveDesc": "面板管理和统计", "usersDesc": "管理机器人用户", - "trafficUsageDesc": "按节点统计用户流量" + "trafficUsageDesc": "按节点统计用户流量", + "updatesDesc": "机器人和面板版本" }, "trafficUsage": { "title": "流量使用", @@ -1927,6 +1929,19 @@ } } }, + "adminUpdates": { + "title": "更新", + "subtitle": "机器人和面板发布历史", + "bot": "机器人", + "cabinet": "面板", + "currentVersion": "当前版本", + "upToDate": "已是最新", + "updateAvailable": "有可用更新", + "published": "发布于", + "prerelease": "预发布", + "refresh": "检查更新", + "noReleases": "没有发布数据" + }, "adminDashboard": { "title": "统计面板", "subtitle": "实时系统概览", diff --git a/src/pages/AdminPanel.tsx b/src/pages/AdminPanel.tsx index ff86da8..5cbd872 100644 --- a/src/pages/AdminPanel.tsx +++ b/src/pages/AdminPanel.tsx @@ -1,6 +1,6 @@ import { Link } from 'react-router'; import { useTranslation } from 'react-i18next'; -import { RemnawaveIcon } from '../components/icons'; +import { RemnawaveIcon, ArrowPathIcon } from '../components/icons'; // Group header icons const AnalyticsGroupIcon = () => ( @@ -473,6 +473,12 @@ export default function AdminPanel() { title: t('admin.nav.emailTemplates'), description: t('admin.panel.emailTemplatesDesc'), }, + { + to: '/admin/updates', + icon: , + title: t('admin.nav.updates'), + description: t('admin.panel.updatesDesc'), + }, ], }, ]; diff --git a/src/pages/AdminUpdates.tsx b/src/pages/AdminUpdates.tsx new file mode 100644 index 0000000..e598096 --- /dev/null +++ b/src/pages/AdminUpdates.tsx @@ -0,0 +1,310 @@ +import { useNavigate } from 'react-router'; +import { useQuery } from '@tanstack/react-query'; +import { useTranslation } from 'react-i18next'; +import { adminUpdatesApi, ReleaseItem, ProjectReleasesInfo } from '../api/adminUpdates'; + +declare const __APP_VERSION__: string; + +// ============ Icons ============ + +const BackIcon = () => ( + + + +); + +const RefreshIcon = () => ( + + + +); + +const BotIcon = () => ( + + + +); + +const CabinetIcon = () => ( + + + +); + +const TagIcon = () => ( + + + + +); + +const CalendarIcon = () => ( + + + +); + +const ExternalLinkIcon = () => ( + + + +); + +// ============ Helpers ============ + +function formatDate(iso: string): string { + try { + const date = new Date(iso); + return date.toLocaleDateString(undefined, { year: 'numeric', month: 'short', day: 'numeric' }); + } catch { + return iso; + } +} + +function stripVPrefix(tag: string): string { + return tag.replace(/^v/, ''); +} + +// ============ Components ============ + +function VersionBadge({ hasUpdate }: { hasUpdate: boolean }) { + const { t } = useTranslation(); + + if (hasUpdate) { + return ( + + + {t('adminUpdates.updateAvailable')} + + ); + } + + return ( + + + {t('adminUpdates.upToDate')} + + ); +} + +function ReleaseCard({ release }: { release: ReleaseItem }) { + const { t } = useTranslation(); + + return ( +
+
+
+ + {release.tag_name} +
+ {release.name !== release.tag_name && ( + {release.name} + )} + {release.prerelease && ( + + {t('adminUpdates.prerelease')} + + )} +
+ + {formatDate(release.published_at)} +
+
+ {release.body ? ( +
+          {release.body}
+        
+ ) : null} +
+ ); +} + +function ProjectSection({ + icon, + title, + info, + currentVersion, + hasUpdate, + repoUrl, +}: { + icon: React.ReactNode; + title: string; + info: ProjectReleasesInfo; + currentVersion: string; + hasUpdate: boolean; + repoUrl: string; +}) { + const { t } = useTranslation(); + + return ( +
+ {/* Header */} +
+
+ {icon} +
+
+
+

{title}

+ +
+

+ {t('adminUpdates.currentVersion')}:{' '} + {currentVersion || '—'} +

+
+ + GitHub + + +
+ + {/* Releases list */} + {info.releases.length > 0 ? ( +
+ {info.releases.map((release) => ( + + ))} +
+ ) : ( +
+ {t('adminUpdates.noReleases')} +
+ )} +
+ ); +} + +// ============ Page ============ + +export default function AdminUpdates() { + const { t } = useTranslation(); + const navigate = useNavigate(); + + const { data, isLoading, refetch, isFetching } = useQuery({ + queryKey: ['admin', 'releases'], + queryFn: adminUpdatesApi.getReleases, + staleTime: 60_000, + }); + + // Cabinet has_updates: compare __APP_VERSION__ with latest release + const cabinetHasUpdate = (() => { + if (!data?.cabinet.releases.length) return false; + try { + const latestTag = data.cabinet.releases.find((r) => !r.prerelease)?.tag_name; + if (!latestTag) return false; + return stripVPrefix(latestTag) !== stripVPrefix(__APP_VERSION__); + } catch { + return false; + } + })(); + + return ( +
+ {/* Top bar */} +
+ +
+

{t('adminUpdates.title')}

+

{t('adminUpdates.subtitle')}

+
+ +
+ + {/* Loading skeleton */} + {isLoading && ( +
+ {[0, 1].map((i) => ( +
+ ))} +
+ )} + + {/* Content */} + {data && ( +
+ } + title={t('adminUpdates.bot')} + info={data.bot} + currentVersion={data.bot.current_version} + hasUpdate={data.bot.has_updates} + repoUrl={data.bot.repo_url} + /> + } + title={t('adminUpdates.cabinet')} + info={data.cabinet} + currentVersion={__APP_VERSION__} + hasUpdate={cabinetHasUpdate} + repoUrl={data.cabinet.repo_url} + /> +
+ )} +
+ ); +}