feat: display raw subscription

This commit is contained in:
kastov 2026-07-06 08:37:08 +03:00
parent e4c0004edf
commit bcb9bd8a62
No known key found for this signature in database
GPG key ID: 1B27BE29057F4C90
5 changed files with 127 additions and 12 deletions

View file

@ -1,4 +1,6 @@
import { CodeHighlight } from '@mantine/code-highlight'
import { Button, Card, Group, Paper, Stack, Switch, Text, Transition } from '@mantine/core'
import { modals } from '@mantine/modals'
import { notifications } from '@mantine/notifications'
import { GetExternalSquadByUuidCommand } from '@remnawave/backend-contract'
import { RemarksManager } from '@widgets/dashboard/subscription-settings/settings/cards/managers/remarks-manager.widget'
@ -9,6 +11,7 @@ import { TbDeviceFloppy, TbDevices2, TbListLetters, TbX } from 'react-icons/tb'
import { queryClient } from '@shared/api'
import { QueryKeys, useUpdateExternalSquad } from '@shared/api/hooks'
import { BaseOverlayHeader } from '@shared/ui/overlays/base-overlay-header'
interface IProps {
externalSquad: GetExternalSquadByUuidCommand.Response['response']
@ -108,6 +111,23 @@ export const ExternalSquadsCustomRemarksTabWidget = (props: IProps) => {
data
)
}
},
onError: (error) => {
if ((error.cause as unknown as { errorCode: string })?.errorCode === 'A231') {
modals.open({
centered: true,
size: 'auto',
title: (
<BaseOverlayHeader
iconColor="red"
IconComponent={TbX}
iconVariant="soft"
title={t('subscription-settings.widget.validation-error')}
/>
),
children: <CodeHighlight language="json" code={error.message} />
})
}
}
}
})

View file

@ -9,9 +9,13 @@ import {
Stack,
TextInput,
ThemeIcon,
Text
Text,
Box,
Button
} from '@mantine/core'
import { modals } from '@mantine/modals'
import { githubDarkTheme } from 'json-edit-react'
import { JsonEditor } from 'json-edit-react'
import { useTranslation } from 'react-i18next'
import {
PiEyeSlashDuotone,
@ -23,9 +27,10 @@ import {
PiEmptyDuotone,
PiQrCodeDuotone
} from 'react-icons/pi'
import { TbJson } from 'react-icons/tb'
import { useNiceMantineModal } from '@shared/_modals/use-nice-modal'
import { useGetConnectionKeysByUuid } from '@shared/api/hooks'
import { useGetConnectionKeysByUuid, useGetRawSubscription } from '@shared/api/hooks'
import { LoaderModalShared } from '@shared/ui/loader-modal'
import { BaseOverlayHeader } from '@shared/ui/overlays/base-overlay-header'
import { QrCodeBuilder } from '@shared/ui/qr-code-builder'
@ -34,10 +39,11 @@ import { SectionCardSection } from '@shared/ui/section-card/section-card.section
interface IProps {
userUuid: string
shortUuid: string
}
export const ConnectionKeysDrawer = NiceModal.create((props: IProps) => {
const { userUuid } = props
const { userUuid, shortUuid } = props
const { t } = useTranslation()
const modal = useModal()
@ -52,6 +58,10 @@ export const ConnectionKeysDrawer = NiceModal.create((props: IProps) => {
}
})
const { data: rawSubscription, isLoading: isRawSubscriptionLoading } = useGetRawSubscription({
route: { shortUuid }
})
const renderQrCode = (link: string, remark: string) => {
modals.open({
centered: true,
@ -169,6 +179,44 @@ export const ConnectionKeysDrawer = NiceModal.create((props: IProps) => {
)
}
const renderRawSubscription = () => {
modals.open({
centered: true,
size: 'xl',
title: (
<BaseOverlayHeader
iconColor="teal"
IconComponent={TbJson}
iconVariant="soft"
title="Raw Subscription"
/>
),
children: (
<Box>
<JsonEditor
collapse={({ path }) => {
if (path.length === 0) return false
if (path[0] !== 'resolvedProxyConfigs') return true
return path.length > 2
}}
data={JSON.parse(JSON.stringify(rawSubscription))}
indent={4}
keySort={([a], [b]) => {
if (a === 'resolvedProxyConfigs') return -1
if (b === 'resolvedProxyConfigs') return 1
return 0
}}
maxWidth="100%"
rootName=""
theme={githubDarkTheme}
viewOnly
jsonStringify={(data) => JSON.stringify(data)}
/>
</Box>
)
})
}
return (
<Drawer
{...modalProps}
@ -184,13 +232,24 @@ export const ConnectionKeysDrawer = NiceModal.create((props: IProps) => {
/>
}
>
{isLoading ? (
<LoaderModalShared
text={t('get-user-subscription-links.feature.loading-subscription-links')}
/>
) : (
<Stack>{renderLinks()}</Stack>
)}
<Stack>
<Button
fullWidth
onClick={renderRawSubscription}
leftSection={<TbJson size="16px" />}
variant="soft"
loading={isRawSubscriptionLoading}
>
Raw Subscription
</Button>
{isLoading ? (
<LoaderModalShared
text={t('get-user-subscription-links.feature.loading-subscription-links')}
/>
) : (
<Stack>{renderLinks()}</Stack>
)}
</Stack>
</Drawer>
)
})

View file

@ -6,7 +6,8 @@ import {
GetUserAccessibleNodesCommand,
GetUserByUuidCommand,
GetUserMetadataCommand,
GetUserSubscriptionRequestHistoryCommand
GetUserSubscriptionRequestHistoryCommand,
GetRawSubscriptionByShortUuidCommand
} from '@remnawave/backend-contract'
import { keepPreviousData } from '@tanstack/react-query'
@ -24,6 +25,9 @@ export const usersQueryKeys = createQueryKeys('users', {
getConnectionKeysByUuid: (route: GetConnectionKeysByUuidCommand.RequestParam) => ({
queryKey: [route]
}),
getRawSubscription: (route: GetRawSubscriptionByShortUuidCommand.RequestParam) => ({
queryKey: [route]
}),
getUserTags: {
queryKey: null
},
@ -119,3 +123,13 @@ export const useGetUserMetadata = createGetQueryHook({
staleTime: sToMs(60)
}
})
export const useGetRawSubscription = createGetQueryHook({
endpoint: GetRawSubscriptionByShortUuidCommand.TSQ_url,
responseSchema: GetRawSubscriptionByShortUuidCommand.ResponseSchema,
routeParamsSchema: GetRawSubscriptionByShortUuidCommand.RequestParamSchema,
getQueryKey: ({ route }) => usersQueryKeys.getRawSubscription(route!).queryKey,
rQueryParams: {
staleTime: sToMs(60)
}
})

View file

@ -181,7 +181,8 @@ export const UserIdentificationCard = memo((props: IProps) => {
color="teal"
onClick={() => {
showModal('users_connectionKeysDrawer', {
userUuid: user.uuid
userUuid: user.uuid,
shortUuid: user.shortUuid
})
}}
size="lg"

View file

@ -1,5 +1,7 @@
import { CodeHighlight } from '@mantine/code-highlight'
import { Button, Card, Group, Stack } from '@mantine/core'
import { useForm, schemaResolver } from '@mantine/form'
import { modals } from '@mantine/modals'
import { notifications } from '@mantine/notifications'
import { UpdateSubscriptionSettingsCommand } from '@remnawave/backend-contract'
import { useState } from 'react'
@ -10,6 +12,7 @@ import Masonry from 'react-layout-masonry'
import { queryClient } from '@shared/api'
import { QueryKeys, useUpdateSubscriptionSettings } from '@shared/api/hooks'
import { BaseOverlayHeader } from '@shared/ui/overlays/base-overlay-header'
import { SettingsCardShared } from '@shared/ui/settings-card'
import { handleFormErrors } from '@shared/utils/misc'
@ -77,6 +80,24 @@ export const SubscriptionUserRemarksCardWidget = (props: IProps) => {
},
onError(error) {
if ((error.cause as unknown as { errorCode: string })?.errorCode === 'A231') {
modals.open({
centered: true,
size: 'auto',
title: (
<BaseOverlayHeader
iconColor="red"
IconComponent={TbX}
iconVariant="soft"
title={t('subscription-settings.widget.validation-error')}
/>
),
children: <CodeHighlight language="json" code={error.message} />
})
return
}
handleFormErrors(form, error)
}
}