diff --git a/src/api/adminRemnawave.ts b/src/api/adminRemnawave.ts
index c9e383b..9e86819 100644
--- a/src/api/adminRemnawave.ts
+++ b/src/api/adminRemnawave.ts
@@ -134,6 +134,8 @@ export interface NodeInfo {
created_at?: string;
updated_at?: string;
provider_uuid?: string;
+ provider_name?: string | null;
+ provider_favicon?: string | null;
versions?: { xray: string; node: string } | null;
system?: {
info: {
diff --git a/src/pages/AdminRemnawave.tsx b/src/pages/AdminRemnawave.tsx
index b34308e..ed0cff7 100644
--- a/src/pages/AdminRemnawave.tsx
+++ b/src/pages/AdminRemnawave.tsx
@@ -23,6 +23,19 @@ import {
ServerIcon,
ChartIcon,
GlobeIcon,
+ HeartbeatIcon,
+ PowerIcon,
+ WarningCircleIcon,
+ CalendarIcon,
+ CalendarBlankIcon,
+ CalendarStarIcon,
+ ChartPieIcon,
+ ChartDonutIcon,
+ CpuIcon,
+ MemoryIcon,
+ PulseIcon,
+ DevicesIcon,
+ StatUptimeIcon,
UsersIcon,
SyncIcon,
RefreshIcon,
@@ -36,7 +49,6 @@ import {
BackIcon,
ChevronRightIcon,
} from '../components/icons';
-import { PiCpu, PiMemory, PiTimer, PiDevices, PiPulse } from 'react-icons/pi';
const formatBytes = (bytes: number): string => {
if (bytes === 0) return '0 B';
@@ -128,6 +140,9 @@ function NodeCard({ node, providerName, onAction, isLoading }: NodeCardProps) {
const limit = node.traffic_limit_bytes ?? 0;
const trafficPct = limit > 0 ? Math.min(100, (used / limit) * 100) : null;
+ // Provider name: realtime metrics first, fall back to the node's own provider.
+ const providerLabel = providerName || node.provider_name;
+
return (
{/* Identity + actions */}
@@ -145,9 +160,19 @@ function NodeCard({ node, providerName, onAction, isLoading }: NodeCardProps) {
{getCountryFlag(node.country_code)}
{node.name}
- {providerName && (
-
- {providerName}
+ {(providerLabel || node.provider_favicon) && (
+
+ {node.provider_favicon && (
+
{
+ e.currentTarget.style.display = 'none';
+ }}
+ />
+ )}
+ {providerLabel && {providerLabel}}
)}
@@ -186,9 +211,9 @@ function NodeCard({ node, providerName, onAction, isLoading }: NodeCardProps) {
{/* Address + traffic + uptime */}
-
-
- {node.address}
+
+
+ {node.address}
{formatBytes(used)}
@@ -200,11 +225,11 @@ function NodeCard({ node, providerName, onAction, isLoading }: NodeCardProps) {
/>
)}
- {limit > 0 ? formatBytes(limit) : '∞'}
+ / {limit > 0 ? formatBytes(limit) : '∞'}
{node.xray_uptime > 0 && (
-
+
{formatUptime(node.xray_uptime)}
)}
@@ -246,7 +271,7 @@ function NodeCard({ node, providerName, onAction, isLoading }: NodeCardProps) {
{(node.versions?.node || node.versions?.xray) && (
-
+
{node.versions?.node && {node.versions.node}}
{node.versions?.xray && xray {node.versions.xray}}
@@ -527,20 +552,20 @@ function OverviewTab({
}
+ icon={}
color="accent"
/>
}
+ icon={}
color={memoryUsedPercent > 80 ? 'red' : memoryUsedPercent > 60 ? 'orange' : 'green'}
/>
}
+ icon={}
color="blue"
/>
@@ -556,31 +581,31 @@ function OverviewTab({
}
+ icon={}
color="accent"
/>
}
+ icon={}
color="blue"
/>
}
+ icon={}
color="green"
/>
}
+ icon={}
color="purple"
/>
}
+ icon={}
color="orange"
/>
@@ -650,7 +675,7 @@ function OverviewTab({
{devicesStats && (devicesStats.by_platform.length > 0 || devicesStats.by_app.length > 0) && (
-
+
{t('admin.remnawave.overview.devices', 'Устройства')} ·{' '}
{devicesStats.total_hwid_devices} ({devicesStats.average_devices_per_user.toFixed(1)}/
{t('admin.remnawave.overview.perUser', 'юзер')})
@@ -717,7 +742,7 @@ function OverviewTab({
}
+ icon={}
color="blue"
/>
}
+ icon={}
color={health.event_loop_p99_ms > 50 ? 'red' : 'green'}
/>
}
+ icon={}
color="accent"
/>
@@ -809,25 +834,25 @@ function NodesTab({
}
+ icon={}
color="accent"
/>
}
+ icon={}
color="green"
/>
}
+ icon={}
color="red"
/>
}
+ icon={}
color="accent"
/>