refactor: update remnawave API types for v2.7.0

- ServerInfo: remove cpu_physical_cores, memory_available
- NodeInfo: replace cpu_count/cpu_model/total_ram with versions/system,
  xray_uptime changed to number, users_online now required
- NodeStatus: same field updates, remove dead uptime field
- Extract formatUptime to shared utils/format.ts
- Update AdminRemnawave and AdminDashboard components
This commit is contained in:
Fringg
2026-03-28 22:32:22 +03:00
parent 59e65283e1
commit a34a34204c
5 changed files with 56 additions and 39 deletions

View File

@@ -162,15 +162,11 @@ export interface NodeStatus {
is_disabled: boolean; is_disabled: boolean;
users_online: number; users_online: number;
traffic_used_bytes?: number; traffic_used_bytes?: number;
uptime?: string;
xray_version?: string;
node_version?: string;
last_status_message?: string; last_status_message?: string;
xray_uptime?: string; xray_uptime: number;
is_xray_running?: boolean; is_xray_running?: boolean;
cpu_count?: number; versions?: { xray: string; node: string } | null;
cpu_model?: string; system?: Record<string, unknown> | null;
total_ram?: string;
country_code?: string; country_code?: string;
} }

View File

@@ -29,11 +29,9 @@ export interface SystemSummary {
export interface ServerInfo { export interface ServerInfo {
cpu_cores: number; cpu_cores: number;
cpu_physical_cores: number;
memory_total: number; memory_total: number;
memory_used: number; memory_used: number;
memory_free: number; memory_free: number;
memory_available: number;
uptime_seconds: number; uptime_seconds: number;
} }
@@ -78,22 +76,47 @@ export interface NodeInfo {
is_disabled: boolean; is_disabled: boolean;
is_node_online: boolean; is_node_online: boolean;
is_xray_running: boolean; is_xray_running: boolean;
users_online?: number; users_online: number;
traffic_used_bytes?: number; traffic_used_bytes?: number;
traffic_limit_bytes?: number; traffic_limit_bytes?: number;
last_status_change?: string; last_status_change?: string;
last_status_message?: string; last_status_message?: string;
xray_uptime?: string; xray_uptime: number;
is_traffic_tracking_active: boolean; is_traffic_tracking_active: boolean;
traffic_reset_day?: number; traffic_reset_day?: number;
notify_percent?: number; notify_percent?: number;
consumption_multiplier: number; consumption_multiplier: number;
cpu_count?: number;
cpu_model?: string;
total_ram?: string;
created_at?: string; created_at?: string;
updated_at?: string; updated_at?: string;
provider_uuid?: string; provider_uuid?: string;
versions?: { xray: string; node: string } | null;
system?: {
info: {
arch: string;
cpus: number;
cpuModel: string;
memoryTotal: number;
hostname: string;
platform: string;
release: string;
type: string;
version: string;
networkInterfaces: string[];
};
stats: {
memoryFree: number;
memoryUsed: number;
uptime: number;
loadAvg: number[];
interface?: {
interface: string;
rxBytesPerSec: number;
txBytesPerSec: number;
rxTotal: number;
txTotal: number;
} | null;
};
} | null;
active_plugin_uuid?: string; active_plugin_uuid?: string;
config_profile?: { config_profile?: {
active_config_profile_uuid: string | null; active_config_profile_uuid: string | null;

View File

@@ -10,6 +10,7 @@ import {
type TopCampaignsResponse, type TopCampaignsResponse,
type RecentPaymentsResponse, type RecentPaymentsResponse,
} from '../api/admin'; } from '../api/admin';
import { formatUptime } from '../utils/format';
const CABINET_VERSION = __APP_VERSION__; const CABINET_VERSION = __APP_VERSION__;
import { useCurrency } from '../hooks/useCurrency'; import { useCurrency } from '../hooks/useCurrency';
@@ -254,14 +255,16 @@ function NodeCard({ node, onRestart, onToggle, isLoading }: NodeCardProps) {
</div> </div>
{/* Xray Version & Uptime */} {/* Xray Version & Uptime */}
{(node.xray_version || node.xray_uptime) && ( {(node.versions?.xray || node.xray_uptime > 0) && (
<div className="mb-3 flex items-center gap-3 text-xs"> <div className="mb-3 flex items-center gap-3 text-xs">
{node.xray_version && ( {node.versions?.xray && (
<span className="rounded bg-dark-700/50 px-2 py-1 text-dark-300"> <span className="rounded bg-dark-700/50 px-2 py-1 text-dark-300">
Xray {node.xray_version} Xray {node.versions.xray}
</span> </span>
)} )}
{node.xray_uptime && <span className="text-dark-500">Uptime: {node.xray_uptime}</span>} {node.xray_uptime > 0 && (
<span className="text-dark-500">Uptime: {formatUptime(node.xray_uptime)}</span>
)}
</div> </div>
)} )}

View File

@@ -10,6 +10,7 @@ import {
AutoSyncStatus, AutoSyncStatus,
} from '../api/adminRemnawave'; } from '../api/adminRemnawave';
import { usePlatform } from '../platform/hooks/usePlatform'; import { usePlatform } from '../platform/hooks/usePlatform';
import { formatUptime } from '../utils/format';
import { import {
ServerIcon, ServerIcon,
ChartIcon, ChartIcon,
@@ -43,16 +44,6 @@ const formatBytes = (bytes: number): string => {
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]; return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
}; };
const formatUptime = (seconds: number): string => {
const days = Math.floor(seconds / 86400);
const hours = Math.floor((seconds % 86400) / 3600);
const minutes = Math.floor((seconds % 3600) / 60);
if (days > 0) return `${days}d ${hours}h`;
if (hours > 0) return `${hours}h ${minutes}m`;
return `${minutes}m`;
};
const getCountryFlag = (code: string | null | undefined): string => { const getCountryFlag = (code: string | null | undefined): string => {
if (!code) return '🌍'; if (!code) return '🌍';
const codeMap: Record<string, string> = { const codeMap: Record<string, string> = {
@@ -175,11 +166,12 @@ function NodeCard({ node, onAction, isLoading }: NodeCardProps) {
{t('admin.remnawave.nodes.trafficUsed', 'used')} {t('admin.remnawave.nodes.trafficUsed', 'used')}
</span> </span>
)} )}
{node.xray_uptime && ( {node.xray_uptime > 0 && (
<span> <span>
{t('admin.remnawave.nodes.uptimeLabel', 'Uptime')}: {node.xray_uptime} {t('admin.remnawave.nodes.uptimeLabel', 'Uptime')}: {formatUptime(node.xray_uptime)}
</span> </span>
)} )}
{node.versions?.xray && <span>Xray {node.versions.xray}</span>}
</div> </div>
</div> </div>
@@ -355,15 +347,9 @@ function OverviewTab({ stats, isLoading, onRefresh }: OverviewTabProps) {
); );
} }
// Use (total - available) instead of raw "used" to exclude disk cache/buffers
// This matches what htop/free show as actual application memory usage
const memoryActualUsed =
stats.server_info.memory_available > 0
? stats.server_info.memory_total - stats.server_info.memory_available
: stats.server_info.memory_used;
const memoryUsedPercent = const memoryUsedPercent =
stats.server_info.memory_total > 0 stats.server_info.memory_total > 0
? Math.round((memoryActualUsed / stats.server_info.memory_total) * 100) ? Math.round((stats.server_info.memory_used / stats.server_info.memory_total) * 100)
: 0; : 0;
return ( return (
@@ -437,14 +423,14 @@ function OverviewTab({ stats, isLoading, onRefresh }: OverviewTabProps) {
<div className="grid grid-cols-2 gap-3 lg:grid-cols-3"> <div className="grid grid-cols-2 gap-3 lg:grid-cols-3">
<StatCard <StatCard
label={t('admin.remnawave.overview.cpu', 'CPU Cores')} label={t('admin.remnawave.overview.cpu', 'CPU Cores')}
value={`${stats.server_info.cpu_cores} (${stats.server_info.cpu_physical_cores} physical)`} value={stats.server_info.cpu_cores}
icon={<span className="text-lg"></span>} icon={<span className="text-lg"></span>}
color="accent" color="accent"
/> />
<StatCard <StatCard
label={t('admin.remnawave.overview.memory', 'Memory')} label={t('admin.remnawave.overview.memory', 'Memory')}
value={`${memoryUsedPercent}%`} value={`${memoryUsedPercent}%`}
subValue={`${formatBytes(memoryActualUsed)} / ${formatBytes(stats.server_info.memory_total)}`} subValue={`${formatBytes(stats.server_info.memory_used)} / ${formatBytes(stats.server_info.memory_total)}`}
icon={<span className="text-lg">💾</span>} icon={<span className="text-lg">💾</span>}
color={memoryUsedPercent > 80 ? 'red' : memoryUsedPercent > 60 ? 'orange' : 'green'} color={memoryUsedPercent > 80 ? 'red' : memoryUsedPercent > 60 ? 'orange' : 'green'}
/> />

View File

@@ -1,3 +1,12 @@
export function formatUptime(seconds: number): string {
const days = Math.floor(seconds / 86400);
const hours = Math.floor((seconds % 86400) / 3600);
const minutes = Math.floor((seconds % 3600) / 60);
if (days > 0) return `${days}d ${hours}h`;
if (hours > 0) return `${hours}h ${minutes}m`;
return `${minutes}m`;
}
export function formatPrice(kopeks: number): string { export function formatPrice(kopeks: number): string {
const rubles = kopeks / 100; const rubles = kopeks / 100;
return new Intl.NumberFormat('ru-RU', { return new Intl.NumberFormat('ru-RU', {