Refactor VPN configuration handling to remove device limit management
CI / Backend (build + test) (push) Successful in 1m24s
CI / Frontend (lint + typecheck + build) (push) Successful in 30s

- Updated the VPN configuration commands and handlers to eliminate the device limit parameter, simplifying the configuration process.
- Adjusted related API documentation to reflect the removal of device limit management, clarifying that this setting is now handled directly in the 3x-ui by node administrators.
- Enhanced the overall codebase by removing unnecessary device limit references across various components, ensuring a cleaner and more maintainable code structure.
This commit is contained in:
Leonid Pershin
2026-07-02 23:21:26 +03:00
parent 05d49a8cbd
commit bea2b5fcf7
43 changed files with 933 additions and 157 deletions
@@ -3,4 +3,4 @@ using PnvPanel.Application.Common.Models;
namespace PnvPanel.Application.Configs.Create;
public sealed record CreateVpnConfigCommand(Guid InboundId, string? Label, int? DeviceLimit) : ICommand<Result<VpnConfigDto>>;
public sealed record CreateVpnConfigCommand(Guid InboundId, string? Label) : ICommand<Result<VpnConfigDto>>;
@@ -36,7 +36,7 @@ public sealed class CreateVpnConfigCommandHandler(
if (node is null || !node.IsEnabled)
return Result.Failure<VpnConfigDto>(ConfigErrors.NodeDisabled);
var config = VpnConfig.Create(userId, inbound.Id, inbound.Protocol, command.Label, command.DeviceLimit ?? 0);
var config = VpnConfig.Create(userId, inbound.Id, inbound.Protocol, command.Label);
var reserveResult = await ReserveQuotaSlotAsync(userId, profile.MaxConfigs, config, cancellationToken);
if (!reserveResult.IsSuccess)
@@ -44,7 +44,7 @@ public sealed class CreateVpnConfigCommandHandler(
var addResult = await gateway.AddClientAsync(
node, inbound.RemoteInboundId, inbound.Protocol, config.ClientEmail,
config.Label ?? config.ClientEmail, config.DeviceLimit, cancellationToken);
config.Label ?? config.ClientEmail, cancellationToken);
if (!addResult.IsSuccess)
{
@@ -8,6 +8,5 @@ public sealed class CreateVpnConfigCommandValidator : AbstractValidator<CreateVp
{
RuleFor(x => x.InboundId).NotEmpty();
RuleFor(x => x.Label).MaximumLength(100);
RuleFor(x => x.DeviceLimit).GreaterThanOrEqualTo(0).When(x => x.DeviceLimit.HasValue);
}
}
@@ -3,4 +3,4 @@ using PnvPanel.Application.Common.Models;
namespace PnvPanel.Application.Configs.Edit;
public sealed record EditVpnConfigCommand(Guid ConfigId, string? Label, int? DeviceLimit) : ICommand<Result<VpnConfigDto>>;
public sealed record EditVpnConfigCommand(Guid ConfigId, string? Label) : ICommand<Result<VpnConfigDto>>;
@@ -25,18 +25,15 @@ public sealed class EditVpnConfigCommandHandler(IAppDbContext dbContext, IXuiPan
return Result.Failure<VpnConfigDto>(ConfigErrors.InboundNotAvailable);
if (command.Label is not null)
config.Rename(command.Label);
if (command.DeviceLimit is { } deviceLimit)
{
config.SetDeviceLimit(deviceLimit);
config.Rename(command.Label);
var node = await dbContext.Nodes.AsNoTracking().FirstOrDefaultAsync(n => n.Id == inbound.NodeId, cancellationToken);
if (node is not null)
{
await gateway.UpdateClientAsync(
node, inbound.RemoteInboundId, config.ClientExternalId, config.Protocol,
config.Label ?? config.ClientEmail, deviceLimit, enable: true, cancellationToken);
config.Label ?? config.ClientEmail, enable: true, cancellationToken);
}
}
@@ -7,6 +7,5 @@ public sealed class EditVpnConfigCommandValidator : AbstractValidator<EditVpnCon
public EditVpnConfigCommandValidator()
{
RuleFor(x => x.Label).MaximumLength(100);
RuleFor(x => x.DeviceLimit).GreaterThanOrEqualTo(0).When(x => x.DeviceLimit.HasValue);
}
}
@@ -35,7 +35,7 @@ public sealed class RotateVpnConfigCommandHandler(IAppDbContext dbContext, IXuiP
var newClientEmail = VpnConfig.GenerateClientEmail(userId);
var addResult = await gateway.AddClientAsync(
node, inbound.RemoteInboundId, config.Protocol, newClientEmail,
config.Label ?? newClientEmail, config.DeviceLimit, cancellationToken);
config.Label ?? newClientEmail, cancellationToken);
if (!addResult.IsSuccess)
return Result.Failure<VpnConfigDto>(addResult.Error);
@@ -8,10 +8,10 @@ namespace PnvPanel.Application.Configs;
/// детали 3x-ui в этот DTO не попадают (см. domain-model.md).
/// </summary>
public sealed record VpnConfigDto(
Guid Id, string? Label, VpnProtocol Protocol, string Location, int DeviceLimit,
Guid Id, string? Label, VpnProtocol Protocol, string Location,
long UsedUpBytes, long UsedDownBytes, DateTimeOffset? ExpiresAt, ConfigStatus Status, DateTimeOffset CreatedAt)
{
public static VpnConfigDto FromDomain(VpnConfig config, Inbound inbound) => new(
config.Id, config.Label, config.Protocol, inbound.DisplayName ?? inbound.Remark, config.DeviceLimit,
config.Id, config.Label, config.Protocol, inbound.DisplayName ?? inbound.Remark,
config.UsedUpBytes, config.UsedDownBytes, config.ExpiresAt, config.Status, config.CreatedAt);
}