Enhance VpnConfigDto to include ClientEmail and update related components
CI / Backend (build + test) (push) Successful in 1m17s
CI / Frontend (lint + typecheck + build) (push) Successful in 32s

- Added ClientEmail property to VpnConfigDto for better client identification.
- Updated ConfigCard to display the ClientEmail in the UI for improved user information.
- Modified API schema and types to accommodate the new ClientEmail field.
- Enhanced localization for displaying the ClientEmail in both Russian and English.
This commit is contained in:
Leonid Pershin
2026-07-13 15:59:09 +03:00
parent 24d9ea1099
commit 7fce5ef181
5 changed files with 12 additions and 3 deletions
@@ -5,13 +5,15 @@ namespace PnvPanel.Application.Configs;
/// <summary>
/// Пользователю показываем только DisplayName + протокол инбаунда — адрес/хост ноды и прочие
/// детали 3x-ui в этот DTO не попадают (см. domain-model.md).
/// детали 3x-ui в этот DTO не попадают (см. domain-model.md). Исключение — ClientEmail: это не
/// секрет и не адрес ноды, а просто ключ клиента в 3x-ui, полезный владельцу для диагностики
/// (сверка конфига с записью в панели у админа).
/// </summary>
public sealed record VpnConfigDto(
Guid Id, string? Label, VpnProtocol Protocol, string Location,
Guid Id, string? Label, string ClientEmail, 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.Id, config.Label, config.ClientEmail, config.Protocol, inbound.DisplayName ?? inbound.Remark,
config.UsedUpBytes, config.UsedDownBytes, config.ExpiresAt, config.Status, config.CreatedAt);
}
@@ -80,6 +80,9 @@ export function ConfigCard({ config }: { config: VpnConfigDto }) {
<div className="text-sm text-muted-foreground">
{formatBytes(config.usedUpBytes)} {formatBytes(config.usedDownBytes)}
</div>
<p className="truncate font-mono text-xs text-muted-foreground/60">
{t('configs.panelEmail', { email: config.clientEmail })}
</p>
<div className="flex flex-wrap gap-2">
<Button size="sm" variant="outline" onClick={() => setDetailsOpen(true)}>
<QrCode className="h-4 w-4" />
+1
View File
@@ -2071,6 +2071,7 @@ export interface components {
/** Format: uuid */
id: string;
label: null | string;
clientEmail: string;
protocol: components["schemas"]["VpnProtocol"];
location: string;
/** Format: int64 */
+1
View File
@@ -49,6 +49,7 @@ export type ActivationStatusDto = {
export type VpnConfigDto = {
id: string
label: string | null
clientEmail: string
protocol: VpnProtocol
location: string
usedUpBytes: number
+2
View File
@@ -87,6 +87,7 @@ const resources = {
copyLink: 'Скопировать ссылку',
loadingLink: 'Загрузка ссылки…',
subscriptionLink: 'Ссылка-подписка (для клиента):',
panelEmail: 'Email в панели: {{email}}',
aggregatedSubscription: 'Общая подписка',
aggregatedSubscriptionHint: 'Одна ссылка/QR со всеми активными конфигами — удобно добавить один раз в клиент.',
status: {
@@ -379,6 +380,7 @@ const resources = {
copyLink: 'Copy link',
loadingLink: 'Loading link…',
subscriptionLink: 'Subscription link (for the client app):',
panelEmail: 'Panel email: {{email}}',
aggregatedSubscription: 'Aggregated subscription',
aggregatedSubscriptionHint: 'One link/QR with all active configs — add it once to your client.',
status: {