Skip to content

Commit d55075c

Browse files
committed
feat: view and download Computed Config Profile (with resolved snippets)
1 parent d74d8f1 commit d55075c

File tree

5 files changed

+139
-8
lines changed

5 files changed

+139
-8
lines changed

package-lock.json

Lines changed: 4 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@
5959
"@monaco-editor/react": "^4.7.0",
6060
"@noble/post-quantum": "^0.5.2",
6161
"@paralleldrive/cuid2": "2.2.2",
62-
"@remnawave/backend-contract": "2.2.27",
62+
"@remnawave/backend-contract": "2.2.28",
6363
"@simplewebauthn/browser": "^13.2.2",
6464
"@stablelib/base64": "^2.0.1",
6565
"@stablelib/x25519": "^2.0.1",

public/locales/en/remnawave.json

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,10 @@
4949
"save-changes": "Save changes",
5050
"remove": "Remove",
5151
"add": "Add",
52-
"select": "Select"
52+
"select": "Select",
53+
"loading": "Loading",
54+
"success": "Success",
55+
"download": "Download"
5356
},
5457
"bandwidth-metrics": {
5558
"current-year": "Current year",
@@ -1219,6 +1222,12 @@
12191222
"config-profile-card": {
12201223
"shared": {
12211224
"active-on-nodes": "Active on nodes"
1225+
},
1226+
"widget": {
1227+
"loading-computed-config-profile": "Loading computed config profile...",
1228+
"computed-config-profile-loaded-successfully": "Computed config profile loaded successfully",
1229+
"the-computed-config-profile-description": "The computed config profile shows the final configuration after all snippets have been applied.",
1230+
"view-computed": "View Computed"
12221231
}
12231232
},
12241233
"flat-inbound-checkbox-card": {
@@ -1744,4 +1753,4 @@
17441753
"token-name": "Token Name"
17451754
}
17461755
}
1747-
}
1756+
}

src/shared/api/hooks/config-profiles/config-profiles.query.hooks.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import {
2+
GetComputedConfigProfileByUuidCommand,
23
GetConfigProfileByUuidCommand,
34
GetConfigProfilesCommand,
45
GetInboundsByProfileUuidCommand
@@ -17,6 +18,9 @@ export const configProfilesQueryKeys = createQueryKeys('configProfiles', {
1718
getConfigProfile: (route: GetConfigProfileByUuidCommand.Request) => ({
1819
queryKey: [route]
1920
}),
21+
getComputedConfigProfile: (route: GetComputedConfigProfileByUuidCommand.Request) => ({
22+
queryKey: [route]
23+
}),
2024
getConfigProfileInbounds: (route: GetConfigProfileByUuidCommand.Request) => ({
2125
queryKey: [route]
2226
})
@@ -74,3 +78,20 @@ export const useGetConfigProfileInbounds = createGetQueryHook({
7478
})
7579
}
7680
})
81+
82+
export const useGetComputedConfigProfile = createGetQueryHook({
83+
endpoint: GetComputedConfigProfileByUuidCommand.TSQ_url,
84+
responseSchema: GetComputedConfigProfileByUuidCommand.ResponseSchema,
85+
routeParamsSchema: GetComputedConfigProfileByUuidCommand.RequestSchema,
86+
getQueryKey: ({ route }) => configProfilesQueryKeys.getComputedConfigProfile(route!).queryKey,
87+
rQueryParams: {
88+
enabled: false
89+
},
90+
errorHandler: (error) => {
91+
notifications.show({
92+
title: `Get Computed Config Profile`,
93+
message: error instanceof Error ? error.message : `Request failed with unknown error.`,
94+
color: 'red'
95+
})
96+
}
97+
})

src/widgets/dashboard/config-profiles/config-profile-card/config-profile-card.widget.tsx

Lines changed: 102 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,23 +5,27 @@ import {
55
Button,
66
Card,
77
CopyButton,
8+
Divider,
89
Group,
10+
Loader,
911
Menu,
1012
Stack,
1113
Text,
1214
Tooltip
1315
} from '@mantine/core'
16+
import { TbCheck, TbChevronDown, TbCpu2, TbDownload, TbEdit, TbEye } from 'react-icons/tb'
1417
import { PiCheck, PiCopy, PiCpu, PiPencil, PiTag, PiTrashDuotone } from 'react-icons/pi'
15-
import { TbChevronDown, TbDownload, TbEdit, TbEye } from 'react-icons/tb'
1618
import { GetConfigProfilesCommand } from '@remnawave/backend-contract'
1719
import { githubDarkTheme, JsonEditor } from 'json-edit-react'
1820
import { generatePath, useNavigate } from 'react-router-dom'
21+
import { notifications } from '@mantine/notifications'
1922
import { useDisclosure } from '@mantine/hooks'
2023
import { useTranslation } from 'react-i18next'
2124
import { modals } from '@mantine/modals'
2225
import { motion } from 'framer-motion'
2326
import clsx from 'clsx'
2427

28+
import { useGetComputedConfigProfile } from '@shared/api/hooks/config-profiles/config-profiles.query.hooks'
2529
import { MODALS, useModalsStoreOpenWithData } from '@entities/dashboard/modal-store'
2630
import { formatInt } from '@shared/utils/misc'
2731
import { XrayLogo } from '@shared/ui/logos'
@@ -58,6 +62,87 @@ export function ConfigProfileCardWidget(props: IProps) {
5862
)
5963
}
6064

65+
const { refetch: refetchComputedConfigProfile, isLoading: isLoadingComputedConfigProfile } =
66+
useGetComputedConfigProfile({
67+
route: {
68+
uuid: configProfile.uuid
69+
}
70+
})
71+
72+
const handleViewComputedConfigProfile = async () => {
73+
notifications.show({
74+
id: 'view-computed-config-profile',
75+
loading: true,
76+
title: t('common.loading'),
77+
message: t('config-profile-card.widget.loading-computed-config-profile'),
78+
autoClose: false,
79+
withCloseButton: false
80+
})
81+
82+
const computedConfigProfile = await refetchComputedConfigProfile()
83+
84+
if (computedConfigProfile && computedConfigProfile.data) {
85+
notifications.update({
86+
id: 'view-computed-config-profile',
87+
loading: false,
88+
title: t('common.success'),
89+
message: t(
90+
'config-profile-card.widget.computed-config-profile-loaded-successfully'
91+
),
92+
icon: <TbCheck size={18} />,
93+
autoClose: 3000
94+
})
95+
96+
modals.openConfirmModal({
97+
children: (
98+
<>
99+
<Text size="sm">
100+
{t(
101+
'config-profile-card.widget.the-computed-config-profile-description'
102+
)}
103+
</Text>
104+
<Divider my="md" />
105+
<JsonEditor
106+
data={computedConfigProfile.data.config as object}
107+
indent={4}
108+
maxWidth="100%"
109+
rootName=""
110+
theme={githubDarkTheme}
111+
viewOnly
112+
/>
113+
</>
114+
),
115+
cancelProps: {
116+
variant: 'subtle',
117+
color: 'gray'
118+
},
119+
confirmProps: {
120+
color: 'teal'
121+
},
122+
labels: {
123+
confirm: t('common.download'),
124+
cancel: t('common.cancel')
125+
},
126+
size: 'xl',
127+
title: computedConfigProfile.data.name,
128+
onConfirm: () => {
129+
const jsonString = JSON.stringify(computedConfigProfile.data.config, null, 2)
130+
const blob = new Blob([jsonString], {
131+
type: 'application/json'
132+
})
133+
const url = URL.createObjectURL(blob)
134+
const a = document.createElement('a')
135+
a.href = url
136+
a.download = `${computedConfigProfile.data.name}.json`
137+
document.body.appendChild(a)
138+
a.click()
139+
document.body.removeChild(a)
140+
URL.revokeObjectURL(url)
141+
}
142+
})
143+
}
144+
}
145+
61146
return (
62147
<motion.div
63148
animate={{ opacity: 1, y: 0 }}
@@ -225,6 +310,22 @@ export function ConfigProfileCardWidget(props: IProps) {
225310
{t('config-profiles-grid.widget.quick-view')}
226311
</Menu.Item>
227312

313+
<Menu.Item
314+
leftSection={
315+
isLoadingComputedConfigProfile ? (
316+
<Loader size={18} />
317+
) : (
318+
<TbCpu2 size={18} />
319+
)
320+
}
321+
onClick={(e) => {
322+
e.stopPropagation()
323+
handleViewComputedConfigProfile()
324+
}}
325+
>
326+
{t('config-profile-card.widget.view-computed')}
327+
</Menu.Item>
328+
228329
<Menu.Item
229330
leftSection={<TbDownload size={18} />}
230331
onClick={(e) => {

0 commit comments

Comments
 (0)