mirror of
https://github.com/chillpadclub/bedolaga-cabinet.git
synced 2026-07-28 09:33:46 +00:00
fix: add full i18n support for RemnaWave section and improve sync UI
This commit is contained in:
@@ -863,6 +863,11 @@
|
||||
"memory": "Memory",
|
||||
"uptime": "Uptime",
|
||||
"traffic": "Traffic Statistics",
|
||||
"traffic2days": "2 days",
|
||||
"traffic7days": "7 days",
|
||||
"traffic30days": "30 days",
|
||||
"trafficMonth": "Month",
|
||||
"trafficYear": "Year",
|
||||
"usersByStatus": "Users by Status"
|
||||
},
|
||||
"nodes": {
|
||||
@@ -874,29 +879,62 @@
|
||||
"enable": "Enable",
|
||||
"disable": "Disable",
|
||||
"restartAll": "Restart All",
|
||||
"confirmRestartAll": "Are you sure you want to restart all nodes?"
|
||||
"confirmRestartAll": "Are you sure you want to restart all nodes?",
|
||||
"usersOnline": "online",
|
||||
"trafficUsed": "used",
|
||||
"uptimeLabel": "Uptime",
|
||||
"stats": {
|
||||
"total": "Total",
|
||||
"online": "Online",
|
||||
"offline": "Offline",
|
||||
"disabled": "Disabled",
|
||||
"users": "Users"
|
||||
}
|
||||
},
|
||||
"squads": {
|
||||
"synced": "Synced",
|
||||
"notSynced": "Not synced",
|
||||
"noSquads": "No squads found",
|
||||
"syncServers": "Sync Servers"
|
||||
"syncServers": "Sync Servers",
|
||||
"members": "members",
|
||||
"inbounds": "inbounds",
|
||||
"available": "Available",
|
||||
"unavailable": "Unavailable",
|
||||
"stats": {
|
||||
"total": "Total",
|
||||
"synced": "Synced",
|
||||
"available": "Available",
|
||||
"members": "Members"
|
||||
},
|
||||
"detail": "Squad Details",
|
||||
"loadError": "Failed to load squad",
|
||||
"info": "Information",
|
||||
"originalName": "Original Name",
|
||||
"countryCode": "Country",
|
||||
"statsTitle": "Statistics",
|
||||
"inboundsList": "Inbounds",
|
||||
"localSettings": "Local Settings",
|
||||
"trialEligible": "Trial Eligible",
|
||||
"price": "Price",
|
||||
"users": "Users"
|
||||
},
|
||||
"sync": {
|
||||
"autoSync": "Auto Sync",
|
||||
"runNow": "Run Now",
|
||||
"manual": "Manual Sync",
|
||||
"fromPanel": "Sync from Panel",
|
||||
"fromPanelDesc": "Import users from RemnaWave panel to bot database",
|
||||
"toPanel": "Sync to Panel",
|
||||
"toPanelDesc": "Export users from bot database to RemnaWave panel",
|
||||
"subscriptions": "Subscriptions",
|
||||
"validate": "Validate",
|
||||
"validateDesc": "Check and fix subscription inconsistencies",
|
||||
"cleanup": "Cleanup",
|
||||
"cleanupDesc": "Remove orphaned subscriptions without users",
|
||||
"statuses": "Sync Statuses",
|
||||
"statusesDesc": "Synchronize subscription statuses with panel"
|
||||
"runNow": "Run",
|
||||
"run": "Run",
|
||||
"running": "Running...",
|
||||
"schedule": "Schedule",
|
||||
"status": "Status",
|
||||
"lastRun": "Last Run",
|
||||
"nextRun": "Next Run",
|
||||
"enabled": "Enabled",
|
||||
"disabled": "Disabled",
|
||||
"success": "Success",
|
||||
"runAutoSyncNow": "Run Auto Sync Now",
|
||||
"fromPanel": "From Panel",
|
||||
"fromPanelDesc": "Import users from RemnaWave to bot",
|
||||
"toPanel": "To Panel",
|
||||
"toPanelDesc": "Export users from bot to RemnaWave"
|
||||
}
|
||||
},
|
||||
"wheel": {
|
||||
|
||||
@@ -1702,7 +1702,17 @@
|
||||
"offline": "آفلاین",
|
||||
"online": "آنلاین",
|
||||
"restart": "راهاندازی مجدد",
|
||||
"restartAll": "راهاندازی مجدد همه"
|
||||
"restartAll": "راهاندازی مجدد همه",
|
||||
"usersOnline": "آنلاین",
|
||||
"trafficUsed": "استفاده شده",
|
||||
"uptimeLabel": "زمان فعالیت",
|
||||
"stats": {
|
||||
"total": "کل",
|
||||
"online": "آنلاین",
|
||||
"offline": "آفلاین",
|
||||
"disabled": "غیرفعال",
|
||||
"users": "کاربران"
|
||||
}
|
||||
},
|
||||
"overview": {
|
||||
"bandwidth": "پهنای باند لحظهای",
|
||||
@@ -1716,6 +1726,11 @@
|
||||
"total": "کل",
|
||||
"totalUsers": "کل کاربران",
|
||||
"traffic": "آمار ترافیک",
|
||||
"traffic2days": "۲ روز",
|
||||
"traffic7days": "۷ روز",
|
||||
"traffic30days": "۳۰ روز",
|
||||
"trafficMonth": "ماه",
|
||||
"trafficYear": "سال",
|
||||
"upload": "آپلود",
|
||||
"uptime": "زمان فعالیت",
|
||||
"usersByStatus": "کاربران بر اساس وضعیت",
|
||||
@@ -1725,23 +1740,46 @@
|
||||
"noSquads": "گروهی یافت نشد",
|
||||
"notSynced": "همگام نشده",
|
||||
"syncServers": "همگامسازی سرورها",
|
||||
"synced": "همگام شده"
|
||||
"synced": "همگام شده",
|
||||
"members": "اعضا",
|
||||
"inbounds": "ورودیها",
|
||||
"available": "در دسترس",
|
||||
"unavailable": "در دسترس نیست",
|
||||
"stats": {
|
||||
"total": "کل",
|
||||
"synced": "همگام",
|
||||
"available": "در دسترس",
|
||||
"members": "اعضا"
|
||||
},
|
||||
"detail": "جزئیات گروه",
|
||||
"loadError": "بارگذاری گروه ناموفق بود",
|
||||
"info": "اطلاعات",
|
||||
"originalName": "نام اصلی",
|
||||
"countryCode": "کشور",
|
||||
"statsTitle": "آمار",
|
||||
"inboundsList": "لیست ورودیها",
|
||||
"localSettings": "تنظیمات محلی",
|
||||
"trialEligible": "واجد شرایط آزمایشی",
|
||||
"price": "قیمت",
|
||||
"users": "کاربران"
|
||||
},
|
||||
"sync": {
|
||||
"autoSync": "همگامسازی خودکار",
|
||||
"cleanup": "پاکسازی",
|
||||
"cleanupDesc": "حذف اشتراکهای بدون کاربر",
|
||||
"fromPanel": "همگامسازی از پنل",
|
||||
"fromPanelDesc": "وارد کردن کاربران از پنل RemnaWave به پایگاه داده ربات",
|
||||
"manual": "همگامسازی دستی",
|
||||
"runNow": "اجرای فوری",
|
||||
"statuses": "همگامسازی وضعیتها",
|
||||
"statusesDesc": "همگامسازی وضعیت اشتراکها با پنل",
|
||||
"subscriptions": "اشتراکها",
|
||||
"toPanel": "همگامسازی به پنل",
|
||||
"toPanelDesc": "صادر کردن کاربران از پایگاه داده ربات به پنل RemnaWave",
|
||||
"validate": "اعتبارسنجی",
|
||||
"validateDesc": "بررسی و رفع ناسازگاریهای اشتراک"
|
||||
"runNow": "اجرا",
|
||||
"run": "اجرا",
|
||||
"running": "در حال اجرا...",
|
||||
"schedule": "برنامه",
|
||||
"status": "وضعیت",
|
||||
"lastRun": "آخرین اجرا",
|
||||
"nextRun": "اجرای بعدی",
|
||||
"enabled": "فعال",
|
||||
"disabled": "غیرفعال",
|
||||
"success": "موفق",
|
||||
"runAutoSyncNow": "اجرای همگامسازی خودکار",
|
||||
"fromPanel": "از پنل",
|
||||
"fromPanelDesc": "وارد کردن کاربران از RemnaWave به ربات",
|
||||
"toPanel": "به پنل",
|
||||
"toPanelDesc": "صادر کردن کاربران از ربات به RemnaWave"
|
||||
}
|
||||
},
|
||||
"theme": {
|
||||
|
||||
@@ -879,6 +879,11 @@
|
||||
"memory": "Память",
|
||||
"uptime": "Аптайм",
|
||||
"traffic": "Статистика трафика",
|
||||
"traffic2days": "2 дня",
|
||||
"traffic7days": "7 дней",
|
||||
"traffic30days": "30 дней",
|
||||
"trafficMonth": "Месяц",
|
||||
"trafficYear": "Год",
|
||||
"usersByStatus": "Пользователи по статусу"
|
||||
},
|
||||
"nodes": {
|
||||
@@ -890,29 +895,62 @@
|
||||
"enable": "Включить",
|
||||
"disable": "Отключить",
|
||||
"restartAll": "Перезапустить все",
|
||||
"confirmRestartAll": "Вы уверены, что хотите перезапустить все ноды?"
|
||||
"confirmRestartAll": "Вы уверены, что хотите перезапустить все ноды?",
|
||||
"usersOnline": "онлайн",
|
||||
"trafficUsed": "использовано",
|
||||
"uptimeLabel": "Аптайм",
|
||||
"stats": {
|
||||
"total": "Всего",
|
||||
"online": "Онлайн",
|
||||
"offline": "Офлайн",
|
||||
"disabled": "Отключены",
|
||||
"users": "Пользователей"
|
||||
}
|
||||
},
|
||||
"squads": {
|
||||
"synced": "Синхронизирован",
|
||||
"notSynced": "Не синхронизирован",
|
||||
"noSquads": "Сквады не найдены",
|
||||
"syncServers": "Синхронизировать серверы"
|
||||
"syncServers": "Синхронизировать серверы",
|
||||
"members": "участников",
|
||||
"inbounds": "inbounds",
|
||||
"available": "Доступен",
|
||||
"unavailable": "Недоступен",
|
||||
"stats": {
|
||||
"total": "Всего",
|
||||
"synced": "Синхр.",
|
||||
"available": "Доступно",
|
||||
"members": "Участников"
|
||||
},
|
||||
"detail": "Детали сквада",
|
||||
"loadError": "Не удалось загрузить сквад",
|
||||
"info": "Информация",
|
||||
"originalName": "Оригинальное имя",
|
||||
"countryCode": "Страна",
|
||||
"statsTitle": "Статистика",
|
||||
"inboundsList": "Inbounds",
|
||||
"localSettings": "Локальные настройки",
|
||||
"trialEligible": "Доступен триал",
|
||||
"price": "Цена",
|
||||
"users": "Пользователей"
|
||||
},
|
||||
"sync": {
|
||||
"autoSync": "Автосинхронизация",
|
||||
"runNow": "Запустить сейчас",
|
||||
"manual": "Ручная синхронизация",
|
||||
"fromPanel": "Синхронизация из панели",
|
||||
"fromPanelDesc": "Импорт пользователей из RemnaWave панели в базу бота",
|
||||
"toPanel": "Синхронизация в панель",
|
||||
"toPanelDesc": "Экспорт пользователей из базы бота в RemnaWave панель",
|
||||
"subscriptions": "Подписки",
|
||||
"validate": "Валидация",
|
||||
"validateDesc": "Проверка и исправление несоответствий подписок",
|
||||
"cleanup": "Очистка",
|
||||
"cleanupDesc": "Удаление orphaned подписок без пользователей",
|
||||
"statuses": "Синхронизация статусов",
|
||||
"statusesDesc": "Синхронизация статусов подписок с панелью"
|
||||
"runNow": "Запустить",
|
||||
"run": "Запустить",
|
||||
"running": "Выполняется...",
|
||||
"schedule": "Расписание",
|
||||
"status": "Статус",
|
||||
"lastRun": "Посл. запуск",
|
||||
"nextRun": "След. запуск",
|
||||
"enabled": "Включено",
|
||||
"disabled": "Выключено",
|
||||
"success": "Успешно",
|
||||
"runAutoSyncNow": "Запустить автосинхронизацию",
|
||||
"fromPanel": "Из панели",
|
||||
"fromPanelDesc": "Импорт пользователей из RemnaWave в бота",
|
||||
"toPanel": "В панель",
|
||||
"toPanelDesc": "Экспорт пользователей из бота в RemnaWave"
|
||||
}
|
||||
},
|
||||
"wheel": {
|
||||
|
||||
@@ -1710,7 +1710,17 @@
|
||||
"offline": "离线",
|
||||
"online": "在线",
|
||||
"restart": "重启",
|
||||
"restartAll": "全部重启"
|
||||
"restartAll": "全部重启",
|
||||
"usersOnline": "在线",
|
||||
"trafficUsed": "已使用",
|
||||
"uptimeLabel": "运行时间",
|
||||
"stats": {
|
||||
"total": "总计",
|
||||
"online": "在线",
|
||||
"offline": "离线",
|
||||
"disabled": "已禁用",
|
||||
"users": "用户"
|
||||
}
|
||||
},
|
||||
"overview": {
|
||||
"bandwidth": "实时带宽",
|
||||
@@ -1724,6 +1734,11 @@
|
||||
"total": "总计",
|
||||
"totalUsers": "总用户数",
|
||||
"traffic": "流量统计",
|
||||
"traffic2days": "2天",
|
||||
"traffic7days": "7天",
|
||||
"traffic30days": "30天",
|
||||
"trafficMonth": "本月",
|
||||
"trafficYear": "本年",
|
||||
"upload": "上传",
|
||||
"uptime": "运行时间",
|
||||
"usersByStatus": "按状态分类的用户",
|
||||
@@ -1733,23 +1748,46 @@
|
||||
"noSquads": "未找到小队",
|
||||
"notSynced": "未同步",
|
||||
"syncServers": "同步服务器",
|
||||
"synced": "已同步"
|
||||
"synced": "已同步",
|
||||
"members": "成员",
|
||||
"inbounds": "入站",
|
||||
"available": "可用",
|
||||
"unavailable": "不可用",
|
||||
"stats": {
|
||||
"total": "总计",
|
||||
"synced": "已同步",
|
||||
"available": "可用",
|
||||
"members": "成员"
|
||||
},
|
||||
"detail": "小队详情",
|
||||
"loadError": "加载小队失败",
|
||||
"info": "信息",
|
||||
"originalName": "原始名称",
|
||||
"countryCode": "国家",
|
||||
"statsTitle": "统计",
|
||||
"inboundsList": "入站列表",
|
||||
"localSettings": "本地设置",
|
||||
"trialEligible": "可试用",
|
||||
"price": "价格",
|
||||
"users": "用户"
|
||||
},
|
||||
"sync": {
|
||||
"autoSync": "自动同步",
|
||||
"cleanup": "清理",
|
||||
"cleanupDesc": "删除无用户的孤立订阅",
|
||||
"fromPanel": "从面板同步",
|
||||
"fromPanelDesc": "从RemnaWave面板导入用户到机器人数据库",
|
||||
"manual": "手动同步",
|
||||
"runNow": "立即运行",
|
||||
"statuses": "同步状态",
|
||||
"statusesDesc": "与面板同步订阅状态",
|
||||
"subscriptions": "订阅",
|
||||
"toPanel": "同步到面板",
|
||||
"toPanelDesc": "从机器人数据库导出用户到RemnaWave面板",
|
||||
"validate": "验证",
|
||||
"validateDesc": "检查并修复订阅不一致"
|
||||
"runNow": "运行",
|
||||
"run": "运行",
|
||||
"running": "运行中...",
|
||||
"schedule": "计划",
|
||||
"status": "状态",
|
||||
"lastRun": "上次运行",
|
||||
"nextRun": "下次运行",
|
||||
"enabled": "已启用",
|
||||
"disabled": "已禁用",
|
||||
"success": "成功",
|
||||
"runAutoSyncNow": "立即运行自动同步",
|
||||
"fromPanel": "从面板",
|
||||
"fromPanelDesc": "从RemnaWave导入用户到机器人",
|
||||
"toPanel": "到面板",
|
||||
"toPanelDesc": "从机器人导出用户到RemnaWave"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -157,12 +157,19 @@ function NodeCard({ node, onAction, isLoading }: NodeCardProps) {
|
||||
<div className="mt-3 flex flex-wrap gap-x-4 gap-y-1 text-xs text-dark-400">
|
||||
<span className="flex items-center gap-1">
|
||||
<UsersIcon className="h-3.5 w-3.5" />
|
||||
{node.users_online ?? 0} online
|
||||
{node.users_online ?? 0} {t('admin.remnawave.nodes.usersOnline', 'online')}
|
||||
</span>
|
||||
{node.traffic_used_bytes !== undefined && (
|
||||
<span>{formatBytes(node.traffic_used_bytes)} used</span>
|
||||
<span>
|
||||
{formatBytes(node.traffic_used_bytes)}{' '}
|
||||
{t('admin.remnawave.nodes.trafficUsed', 'used')}
|
||||
</span>
|
||||
)}
|
||||
{node.xray_uptime && (
|
||||
<span>
|
||||
{t('admin.remnawave.nodes.uptimeLabel', 'Uptime')}: {node.xray_uptime}
|
||||
</span>
|
||||
)}
|
||||
{node.xray_uptime && <span>Uptime: {node.xray_uptime}</span>}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -232,17 +239,21 @@ function SquadCard({ squad, onClick }: SquadCardProps) {
|
||||
<div className="mt-3 flex flex-wrap gap-x-4 gap-y-1 text-xs text-dark-400">
|
||||
<span className="flex items-center gap-1">
|
||||
<UsersIcon className="h-3.5 w-3.5" />
|
||||
{squad.members_count} members
|
||||
{squad.members_count} {t('admin.remnawave.squads.members', 'members')}
|
||||
</span>
|
||||
{squad.current_users !== undefined && (
|
||||
<span>
|
||||
{squad.current_users} / {squad.max_users ?? '∞'}
|
||||
</span>
|
||||
)}
|
||||
<span>{squad.inbounds_count} inbounds</span>
|
||||
<span>
|
||||
{squad.inbounds_count} {t('admin.remnawave.squads.inbounds', 'inbounds')}
|
||||
</span>
|
||||
{squad.is_available !== undefined && (
|
||||
<span className={squad.is_available ? 'text-success-400' : 'text-error-400'}>
|
||||
{squad.is_available ? '✓ Available' : '✗ Unavailable'}
|
||||
{squad.is_available
|
||||
? `✓ ${t('admin.remnawave.squads.available', 'Available')}`
|
||||
: `✗ ${t('admin.remnawave.squads.unavailable', 'Unavailable')}`}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
@@ -271,6 +282,8 @@ interface SyncCardProps {
|
||||
}
|
||||
|
||||
function SyncCard({ title, description, onAction, isLoading, lastResult }: SyncCardProps) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<div className="rounded-xl border border-dark-700 bg-dark-800/50 p-4">
|
||||
<div className="flex items-start justify-between gap-3">
|
||||
@@ -291,7 +304,9 @@ function SyncCard({ title, description, onAction, isLoading, lastResult }: SyncC
|
||||
className="flex shrink-0 items-center gap-2 rounded-lg bg-accent-500/20 px-3 py-1.5 text-accent-400 transition-colors hover:bg-accent-500/30 disabled:opacity-50"
|
||||
>
|
||||
<RefreshIcon spinning={isLoading} />
|
||||
{isLoading ? 'Running...' : 'Run'}
|
||||
{isLoading
|
||||
? t('admin.remnawave.sync.running', 'Running...')
|
||||
: t('admin.remnawave.sync.run', 'Run')}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -431,31 +446,31 @@ function OverviewTab({ stats, isLoading, onRefresh }: OverviewTabProps) {
|
||||
</h3>
|
||||
<div className="grid grid-cols-2 gap-3 lg:grid-cols-5">
|
||||
<StatCard
|
||||
label="2 days"
|
||||
label={t('admin.remnawave.overview.traffic2days', '2 days')}
|
||||
value={formatBytes(stats.traffic_periods.last_2_days.current)}
|
||||
icon={<span className="text-xs">📊</span>}
|
||||
color="accent"
|
||||
/>
|
||||
<StatCard
|
||||
label="7 days"
|
||||
label={t('admin.remnawave.overview.traffic7days', '7 days')}
|
||||
value={formatBytes(stats.traffic_periods.last_7_days.current)}
|
||||
icon={<span className="text-xs">📊</span>}
|
||||
color="blue"
|
||||
/>
|
||||
<StatCard
|
||||
label="30 days"
|
||||
label={t('admin.remnawave.overview.traffic30days', '30 days')}
|
||||
value={formatBytes(stats.traffic_periods.last_30_days.current)}
|
||||
icon={<span className="text-xs">📊</span>}
|
||||
color="green"
|
||||
/>
|
||||
<StatCard
|
||||
label="Month"
|
||||
label={t('admin.remnawave.overview.trafficMonth', 'Month')}
|
||||
value={formatBytes(stats.traffic_periods.current_month.current)}
|
||||
icon={<span className="text-xs">📊</span>}
|
||||
color="purple"
|
||||
/>
|
||||
<StatCard
|
||||
label="Year"
|
||||
label={t('admin.remnawave.overview.trafficYear', 'Year')}
|
||||
value={formatBytes(stats.traffic_periods.current_year.current)}
|
||||
icon={<span className="text-xs">📊</span>}
|
||||
color="orange"
|
||||
@@ -526,11 +541,36 @@ function NodesTab({
|
||||
<div className="space-y-4">
|
||||
{/* Stats */}
|
||||
<div className="grid grid-cols-2 gap-3 lg:grid-cols-5">
|
||||
<StatCard label="Total" value={stats.total} icon={<GlobeIcon />} color="accent" />
|
||||
<StatCard label="Online" value={stats.online} icon={<GlobeIcon />} color="green" />
|
||||
<StatCard label="Offline" value={stats.offline} icon={<GlobeIcon />} color="red" />
|
||||
<StatCard label="Disabled" value={stats.disabled} icon={<GlobeIcon />} color="accent" />
|
||||
<StatCard label="Users" value={stats.totalUsers} icon={<UsersIcon />} color="blue" />
|
||||
<StatCard
|
||||
label={t('admin.remnawave.nodes.stats.total', 'Total')}
|
||||
value={stats.total}
|
||||
icon={<GlobeIcon />}
|
||||
color="accent"
|
||||
/>
|
||||
<StatCard
|
||||
label={t('admin.remnawave.nodes.stats.online', 'Online')}
|
||||
value={stats.online}
|
||||
icon={<GlobeIcon />}
|
||||
color="green"
|
||||
/>
|
||||
<StatCard
|
||||
label={t('admin.remnawave.nodes.stats.offline', 'Offline')}
|
||||
value={stats.offline}
|
||||
icon={<GlobeIcon />}
|
||||
color="red"
|
||||
/>
|
||||
<StatCard
|
||||
label={t('admin.remnawave.nodes.stats.disabled', 'Disabled')}
|
||||
value={stats.disabled}
|
||||
icon={<GlobeIcon />}
|
||||
color="accent"
|
||||
/>
|
||||
<StatCard
|
||||
label={t('admin.remnawave.nodes.stats.users', 'Users')}
|
||||
value={stats.totalUsers}
|
||||
icon={<UsersIcon />}
|
||||
color="blue"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Actions */}
|
||||
@@ -608,19 +648,29 @@ function SquadsTab({
|
||||
{/* Stats */}
|
||||
<div className="grid grid-cols-2 gap-3 lg:grid-cols-4">
|
||||
<StatCard
|
||||
label="Total"
|
||||
label={t('admin.remnawave.squads.stats.total', 'Total')}
|
||||
value={stats.total}
|
||||
icon={<ServerIcon className="h-5 w-5" />}
|
||||
color="accent"
|
||||
/>
|
||||
<StatCard label="Synced" value={stats.synced} icon={<SyncIcon />} color="green" />
|
||||
<StatCard
|
||||
label="Available"
|
||||
label={t('admin.remnawave.squads.stats.synced', 'Synced')}
|
||||
value={stats.synced}
|
||||
icon={<SyncIcon />}
|
||||
color="green"
|
||||
/>
|
||||
<StatCard
|
||||
label={t('admin.remnawave.squads.stats.available', 'Available')}
|
||||
value={stats.available}
|
||||
icon={<ServerIcon className="h-5 w-5" />}
|
||||
color="blue"
|
||||
/>
|
||||
<StatCard label="Members" value={stats.totalMembers} icon={<UsersIcon />} color="purple" />
|
||||
<StatCard
|
||||
label={t('admin.remnawave.squads.stats.members', 'Members')}
|
||||
value={stats.totalMembers}
|
||||
icon={<UsersIcon />}
|
||||
color="purple"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Actions */}
|
||||
@@ -692,12 +742,11 @@ function SyncTab({
|
||||
{/* Auto Sync Status */}
|
||||
{autoSyncStatus && (
|
||||
<div className="rounded-xl border border-dark-700 bg-dark-800/50 p-4">
|
||||
<div className="mb-4 flex items-center justify-between">
|
||||
<div className="mb-3 flex items-center justify-between">
|
||||
<h3 className="flex items-center gap-2 font-medium text-dark-100">
|
||||
<SyncIcon />
|
||||
{t('admin.remnawave.sync.autoSync', 'Auto Sync')}
|
||||
</h3>
|
||||
<div className="flex items-center gap-2">
|
||||
<span
|
||||
className={`rounded-full px-2 py-0.5 text-xs ${
|
||||
autoSyncStatus.enabled
|
||||
@@ -705,20 +754,13 @@ function SyncTab({
|
||||
: 'bg-dark-600 text-dark-400'
|
||||
}`}
|
||||
>
|
||||
{autoSyncStatus.enabled ? 'Enabled' : 'Disabled'}
|
||||
{autoSyncStatus.enabled
|
||||
? t('admin.remnawave.sync.enabled', 'Enabled')
|
||||
: t('admin.remnawave.sync.disabled', 'Disabled')}
|
||||
</span>
|
||||
<button
|
||||
onClick={onRunAutoSync}
|
||||
disabled={loadingStates.autoSync || autoSyncStatus.is_running}
|
||||
className="flex items-center gap-1.5 rounded-lg bg-accent-500/20 px-3 py-1.5 text-sm text-accent-400 transition-colors hover:bg-accent-500/30 disabled:opacity-50"
|
||||
>
|
||||
<RefreshIcon spinning={loadingStates.autoSync || autoSyncStatus.is_running} />
|
||||
{t('admin.remnawave.sync.runNow', 'Run Now')}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-2 gap-3 text-sm lg:grid-cols-4">
|
||||
<div className="grid grid-cols-2 gap-3 text-sm">
|
||||
<div className="rounded-lg bg-dark-700/50 p-3">
|
||||
<p className="text-xs text-dark-500">
|
||||
{t('admin.remnawave.sync.schedule', 'Schedule')}
|
||||
@@ -727,24 +769,6 @@ function SyncTab({
|
||||
{autoSyncStatus.times.length > 0 ? autoSyncStatus.times.join(', ') : '—'}
|
||||
</p>
|
||||
</div>
|
||||
<div className="rounded-lg bg-dark-700/50 p-3">
|
||||
<p className="text-xs text-dark-500">
|
||||
{t('admin.remnawave.sync.nextRun', 'Next Run')}
|
||||
</p>
|
||||
<p className="mt-1 text-dark-200">
|
||||
{autoSyncStatus.next_run ? new Date(autoSyncStatus.next_run).toLocaleString() : '—'}
|
||||
</p>
|
||||
</div>
|
||||
<div className="rounded-lg bg-dark-700/50 p-3">
|
||||
<p className="text-xs text-dark-500">
|
||||
{t('admin.remnawave.sync.lastRun', 'Last Run')}
|
||||
</p>
|
||||
<p className="mt-1 text-dark-200">
|
||||
{autoSyncStatus.last_run_finished_at
|
||||
? new Date(autoSyncStatus.last_run_finished_at).toLocaleString()
|
||||
: '—'}
|
||||
</p>
|
||||
</div>
|
||||
<div className="rounded-lg bg-dark-700/50 p-3">
|
||||
<p className="text-xs text-dark-500">{t('admin.remnawave.sync.status', 'Status')}</p>
|
||||
<p
|
||||
@@ -763,7 +787,36 @@ function SyncTab({
|
||||
: autoSyncStatus.last_run_error || '—'}
|
||||
</p>
|
||||
</div>
|
||||
<div className="rounded-lg bg-dark-700/50 p-3">
|
||||
<p className="text-xs text-dark-500">
|
||||
{t('admin.remnawave.sync.lastRun', 'Last Run')}
|
||||
</p>
|
||||
<p className="mt-1 text-dark-200">
|
||||
{autoSyncStatus.last_run_finished_at
|
||||
? new Date(autoSyncStatus.last_run_finished_at).toLocaleString()
|
||||
: '—'}
|
||||
</p>
|
||||
</div>
|
||||
<div className="rounded-lg bg-dark-700/50 p-3">
|
||||
<p className="text-xs text-dark-500">
|
||||
{t('admin.remnawave.sync.nextRun', 'Next Run')}
|
||||
</p>
|
||||
<p className="mt-1 text-dark-200">
|
||||
{autoSyncStatus.next_run ? new Date(autoSyncStatus.next_run).toLocaleString() : '—'}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button
|
||||
onClick={onRunAutoSync}
|
||||
disabled={loadingStates.autoSync || autoSyncStatus.is_running}
|
||||
className="mt-4 flex w-full items-center justify-center gap-2 rounded-lg bg-accent-500/20 px-4 py-2.5 text-sm font-medium text-accent-400 transition-colors hover:bg-accent-500/30 disabled:opacity-50"
|
||||
>
|
||||
<RefreshIcon spinning={loadingStates.autoSync || autoSyncStatus.is_running} />
|
||||
{autoSyncStatus.is_running
|
||||
? t('admin.remnawave.sync.running', 'Running...')
|
||||
: t('admin.remnawave.sync.runAutoSyncNow', 'Run Auto Sync Now')}
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
@@ -158,7 +158,7 @@ export default function AdminRemnawaveSquadDetail() {
|
||||
{/* Stats */}
|
||||
<div className="card">
|
||||
<h3 className="mb-4 text-lg font-semibold text-dark-100">
|
||||
{t('admin.remnawave.squads.stats', 'Statistics')}
|
||||
{t('admin.remnawave.squads.statsTitle', 'Statistics')}
|
||||
</h3>
|
||||
<div className="grid grid-cols-2 gap-4 sm:grid-cols-4">
|
||||
<div className="rounded-lg bg-dark-700/50 p-4">
|
||||
|
||||
Reference in New Issue
Block a user