Enhance documentation with new features: added dark/light/system theme support, instructions page, and application catalog. Updated API and domain model for app management and automatic migrations on startup. Improved frontend structure with new routes and features for user instructions and app management.

This commit is contained in:
Leonid Pershin
2026-07-01 22:38:01 +03:00
parent d8930409fe
commit 1a8d33efa3
229 changed files with 9226 additions and 20 deletions
@@ -0,0 +1,81 @@
using PnvPanel.Api.Common;
using PnvPanel.Application.Common.Messaging;
using PnvPanel.Application.Configs.Create;
using PnvPanel.Application.Configs.Edit;
using PnvPanel.Application.Configs.GetConfigLink;
using PnvPanel.Application.Configs.GetMyConfigs;
using PnvPanel.Application.Configs.ListAvailableInbounds;
using PnvPanel.Application.Configs.Revoke;
using PnvPanel.Application.Configs.Rotate;
namespace PnvPanel.Api.Endpoints;
public static class ConfigEndpoints
{
public static IEndpointRouteBuilder MapConfigEndpoints(this IEndpointRouteBuilder app)
{
var group = app.MapGroup("/api").WithTags("Configs").RequireAuthorization();
group.MapGet("/inbounds/available", ListAvailableInbounds);
group.MapGet("/configs", GetMyConfigs);
group.MapPost("/configs", CreateConfig);
group.MapPatch("/configs/{id:guid}", EditConfig);
group.MapPost("/configs/{id:guid}/rotate", RotateConfig);
group.MapDelete("/configs/{id:guid}", RevokeConfig);
group.MapGet("/configs/{id:guid}/link", GetConfigLink);
return app;
}
private static async Task<IResult> ListAvailableInbounds(ISender sender, CancellationToken cancellationToken)
{
var result = await sender.Send(new ListAvailableInboundsQuery(), cancellationToken);
return result.ToHttpResult();
}
private static async Task<IResult> GetMyConfigs(ISender sender, CancellationToken cancellationToken)
{
var result = await sender.Send(new GetMyConfigsQuery(), cancellationToken);
return result.ToHttpResult();
}
private static async Task<IResult> CreateConfig(CreateConfigBody body, ISender sender, CancellationToken cancellationToken)
{
var command = new CreateVpnConfigCommand(body.InboundId, body.Label, body.DeviceLimit);
var result = await sender.Send(command, cancellationToken);
return result.ToHttpResult();
}
private static async Task<IResult> EditConfig(Guid id, EditConfigBody body, ISender sender, CancellationToken cancellationToken)
{
var command = new EditVpnConfigCommand(id, body.Label, body.DeviceLimit);
var result = await sender.Send(command, cancellationToken);
return result.ToHttpResult();
}
private static async Task<IResult> RotateConfig(Guid id, ISender sender, CancellationToken cancellationToken)
{
var result = await sender.Send(new RotateVpnConfigCommand(id), cancellationToken);
return result.ToHttpResult();
}
private static async Task<IResult> RevokeConfig(Guid id, ISender sender, CancellationToken cancellationToken)
{
var result = await sender.Send(new RevokeVpnConfigCommand(id), cancellationToken);
return result.ToHttpResult();
}
private static async Task<IResult> GetConfigLink(Guid id, HttpRequest request, ISender sender, CancellationToken cancellationToken)
{
var result = await sender.Send(new GetConfigLinkQuery(id), cancellationToken);
if (!result.IsSuccess)
return result.ToHttpResult();
var subscriptionUrl = $"{request.Scheme}://{request.Host}/sub/{result.Value.SubscriptionToken}";
return Results.Ok(new { connectionString = result.Value.ConnectionString, subscriptionUrl });
}
}
public sealed record CreateConfigBody(Guid InboundId, string? Label, int? DeviceLimit);
public sealed record EditConfigBody(string? Label, int? DeviceLimit);