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,9 @@
using PnvPanel.Application.Common.Messaging;
using PnvPanel.Application.Common.Models;
namespace PnvPanel.Application.Configs.GetConfigLink;
public sealed record GetConfigLinkQuery(Guid ConfigId) : IQuery<Result<ConfigLinkDto>>;
/// <summary>SubscriptionToken — Api-слой строит из него абсолютный URL (знает scheme/host запроса).</summary>
public sealed record ConfigLinkDto(string ConnectionString, string SubscriptionToken);
@@ -0,0 +1,38 @@
using Microsoft.EntityFrameworkCore;
using PnvPanel.Application.Auth;
using PnvPanel.Application.Common.Interfaces;
using PnvPanel.Application.Common.Messaging;
using PnvPanel.Application.Common.Models;
namespace PnvPanel.Application.Configs.GetConfigLink;
public sealed class GetConfigLinkQueryHandler(IAppDbContext dbContext, IXuiPanelGateway gateway, ICurrentUser currentUser)
: IQueryHandler<GetConfigLinkQuery, Result<ConfigLinkDto>>
{
public async Task<Result<ConfigLinkDto>> Handle(GetConfigLinkQuery query, CancellationToken cancellationToken)
{
if (currentUser.UserId is not { } userId)
return Result.Failure<ConfigLinkDto>(AuthErrors.Unauthorized);
var config = await dbContext.VpnConfigs.AsNoTracking()
.FirstOrDefaultAsync(c => c.Id == query.ConfigId && c.UserId == userId, cancellationToken);
if (config is null)
return Result.Failure<ConfigLinkDto>(ConfigErrors.NotFound);
var inbound = await dbContext.Inbounds.AsNoTracking().FirstOrDefaultAsync(i => i.Id == config.InboundId, cancellationToken);
if (inbound is null)
return Result.Failure<ConfigLinkDto>(ConfigErrors.InboundNotAvailable);
var node = await dbContext.Nodes.AsNoTracking().FirstOrDefaultAsync(n => n.Id == inbound.NodeId, cancellationToken);
if (node is null)
return Result.Failure<ConfigLinkDto>(ConfigErrors.NodeDisabled);
var linkResult = await gateway.BuildConnectionStringAsync(
node, inbound, config.ClientExternalId, config.Label ?? config.ClientEmail, node.BaseAddress.Host, cancellationToken);
if (!linkResult.IsSuccess)
return Result.Failure<ConfigLinkDto>(linkResult.Error);
return Result.Success(new ConfigLinkDto(linkResult.Value, config.SubscriptionToken));
}
}