Update ThreeXui.Net package version and enhance IXuiPanelGateway implementation
CI / Backend (build + test) (push) Successful in 1m21s
CI / Frontend (lint + typecheck + build) (push) Successful in 33s

- Updated the version of `ThreeXui.Net` to 1.0.3 in `Directory.Packages.props` for improved functionality.
- Modified the `IXuiPanelGateway` interface documentation to reflect changes in client traffic retrieval, specifying the use of `GetInboundClientTrafficAsync` method for better data synchronization.
- Refactored `XuiPanelGateway` to utilize the new method, ensuring accurate client traffic data retrieval and addressing issues with previous versions not loading client statistics correctly.
This commit is contained in:
Leonid Pershin
2026-07-23 01:30:54 +03:00
parent 99451a425a
commit c4bc6ff04b
3 changed files with 19 additions and 58 deletions
@@ -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<IReadOnlyDictionary<string, ClientTrafficInfo>>(
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<string, ClientTrafficInfo>(traffic.Count);
foreach (var entry in traffic)
result[entry.Email] = new ClientTrafficInfo(entry.UpBytes, entry.DownBytes);
return Result.Success<IReadOnlyDictionary<string, ClientTrafficInfo>>(result);
}
catch (Exception ex)
{
@@ -312,53 +317,6 @@ internal sealed class XuiPanelGateway(
}
}
/// <summary>
/// ThreeXui.Net типизированно не отдаёт трафик по клиентам — достаём его из сырого JSON инбаунда:
/// стандартное поле 3x-ui API "clientStats": [{ "email": "...", "up": N, "down": N }, ...].
/// Формат форка может отличаться — при ошибке парсинга просто возвращаем пусто, не валим синхронизацию.
/// </summary>
private static IReadOnlyDictionary<string, ClientTrafficInfo> ParseClientStats(
string? rawInboundJson
)
{
var result = new Dictionary<string, ClientTrafficInfo>();
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)