diff --git a/backend/Directory.Packages.props b/backend/Directory.Packages.props
index 64504a9..109e280 100644
--- a/backend/Directory.Packages.props
+++ b/backend/Directory.Packages.props
@@ -28,7 +28,7 @@
-
+
diff --git a/backend/src/PnvPanel.Application/Common/Interfaces/IXuiPanelGateway.cs b/backend/src/PnvPanel.Application/Common/Interfaces/IXuiPanelGateway.cs
index 351c02f..f2e271d 100644
--- a/backend/src/PnvPanel.Application/Common/Interfaces/IXuiPanelGateway.cs
+++ b/backend/src/PnvPanel.Application/Common/Interfaces/IXuiPanelGateway.cs
@@ -112,8 +112,11 @@ public interface IXuiPanelGateway
);
///
- /// Трафик по клиентам инбаунда, ключ — ClientEmail. ThreeXui.Net не даёт типизированного метода
- /// для этого — извлекается из сырого clientStats[] в RawInboundJson (стандартное поле 3x-ui API).
+ /// Трафик по клиентам инбаунда, ключ — ClientEmail. Реализация — через
+ /// ThreeXui.Net's IXuiClient.GetInboundClientTrafficAsync (1.0.3+), а не
+ /// GetInboundAsync: на части версий 3x-ui (подтверждено на v2.8.11) одиночный
+ /// эндпоинт инбаунда не подгружает clientStats, из-за чего трафик молча не
+ /// синхронизировался бы никогда.
///
Task>> GetClientTrafficAsync(
Node node,
diff --git a/backend/src/PnvPanel.Infrastructure/Xui/XuiPanelGateway.cs b/backend/src/PnvPanel.Infrastructure/Xui/XuiPanelGateway.cs
index 1cb160e..8a62c98 100644
--- a/backend/src/PnvPanel.Infrastructure/Xui/XuiPanelGateway.cs
+++ b/backend/src/PnvPanel.Infrastructure/Xui/XuiPanelGateway.cs
@@ -294,15 +294,20 @@ internal sealed class XuiPanelGateway(
try
{
var client = GetClient(node);
- var remoteInbound = await client.GetInboundAsync(inboundRemoteId, cancellationToken);
- if (remoteInbound is null)
- {
- return Result.Failure>(
- Error.Failure("Xui.InboundNotFound", "Inbound не найден на панели.")
- );
- }
+ // GetInboundClientTrafficAsync (не GetInboundAsync!) — на 3x-ui v2.8.11 одиночный
+ // /panel/api/inbounds/get/{id} не подгружает clientStats (нет Preload на этом пути в
+ // самой панели), из-за чего трафик молча не синхронизировался бы вообще. Список
+ // инбаундов подгружает clientStats всегда — см. ThreeXui.Net CHANGELOG 1.0.3.
+ var traffic = await client.GetInboundClientTrafficAsync(
+ inboundRemoteId,
+ cancellationToken
+ );
- return Result.Success(ParseClientStats(remoteInbound.RawInboundJson));
+ var result = new Dictionary(traffic.Count);
+ foreach (var entry in traffic)
+ result[entry.Email] = new ClientTrafficInfo(entry.UpBytes, entry.DownBytes);
+
+ return Result.Success>(result);
}
catch (Exception ex)
{
@@ -312,53 +317,6 @@ internal sealed class XuiPanelGateway(
}
}
- ///
- /// ThreeXui.Net типизированно не отдаёт трафик по клиентам — достаём его из сырого JSON инбаунда:
- /// стандартное поле 3x-ui API "clientStats": [{ "email": "...", "up": N, "down": N }, ...].
- /// Формат форка может отличаться — при ошибке парсинга просто возвращаем пусто, не валим синхронизацию.
- ///
- private static IReadOnlyDictionary ParseClientStats(
- string? rawInboundJson
- )
- {
- var result = new Dictionary();
- if (string.IsNullOrWhiteSpace(rawInboundJson))
- return result;
-
- try
- {
- using var doc = JsonDocument.Parse(rawInboundJson);
- if (
- !doc.RootElement.TryGetProperty("clientStats", out var clientStats)
- || clientStats.ValueKind != JsonValueKind.Array
- )
- return result;
-
- foreach (var stat in clientStats.EnumerateArray())
- {
- if (
- !stat.TryGetProperty("email", out var emailProp)
- || emailProp.ValueKind != JsonValueKind.String
- )
- continue;
-
- var email = emailProp.GetString();
- if (string.IsNullOrEmpty(email))
- continue;
-
- var up = stat.TryGetProperty("up", out var upProp) ? upProp.GetInt64() : 0;
- var down = stat.TryGetProperty("down", out var downProp) ? downProp.GetInt64() : 0;
- result[email] = new ClientTrafficInfo(up, down);
- }
- }
- catch (JsonException)
- {
- // Не удалось распарсить — возвращаем пусто, вызывающий код просто пропустит синк для этого инбаунда.
- }
-
- return result;
- }
-
public void InvalidateClient(Guid nodeId)
{
if (_clients.TryRemove(nodeId, out var lazy) && lazy.IsValueCreated)