chore: update @remnawave/backend-contract to version 2.6.23 and enhance node plugins functionality

This commit is contained in:
kastov 2026-02-26 21:29:39 +03:00
parent a47c497263
commit abb257217f
No known key found for this signature in database
GPG key ID: 1B27BE29057F4C90
8 changed files with 194 additions and 14 deletions

8
package-lock.json generated
View file

@ -35,7 +35,7 @@
"@monaco-editor/react": "^4.7.0",
"@noble/post-quantum": "^0.5.4",
"@paralleldrive/cuid2": "3.3.0",
"@remnawave/backend-contract": "2.6.22",
"@remnawave/backend-contract": "2.6.23",
"@remnawave/node-plugins": "^0.1.0",
"@remnawave/subscription-page-types": "0.4.0",
"@simplewebauthn/browser": "^13.2.2",
@ -2988,9 +2988,9 @@
}
},
"node_modules/@remnawave/backend-contract": {
"version": "2.6.22",
"resolved": "https://registry.npmjs.org/@remnawave/backend-contract/-/backend-contract-2.6.22.tgz",
"integrity": "sha512-1dZIX7/NSX/Y828oFpc5kBbrlccIyH3ZnmGMpV/B7dmFIi5/4Zu+5Ob3iCe9L2mRxzdK0OmzNIORaOogL6p/fA==",
"version": "2.6.23",
"resolved": "https://registry.npmjs.org/@remnawave/backend-contract/-/backend-contract-2.6.23.tgz",
"integrity": "sha512-aJr+At5vUP+nUShMEmJV3+lbiBfVRKtWIS53iKXJS/qxYR+8srg/fTfKJFvsWr/DtuBd0Jy6v+4l2a75ecL/FA==",
"license": "AGPL-3.0-only",
"dependencies": {
"zod": "3.25.76"

View file

@ -59,7 +59,7 @@
"@monaco-editor/react": "^4.7.0",
"@noble/post-quantum": "^0.5.4",
"@paralleldrive/cuid2": "3.3.0",
"@remnawave/backend-contract": "2.6.22",
"@remnawave/backend-contract": "2.6.23",
"@remnawave/node-plugins": "^0.1.0",
"@remnawave/subscription-page-types": "0.4.0",
"@simplewebauthn/browser": "^13.2.2",

View file

@ -1972,5 +1972,25 @@
"nothing-found": "Nothing found...",
"select-plugin": "Select plugin"
}
},
"node-plugin-card": {
"widget": {
"active-on-nodes": "Active on Nodes"
}
},
"adtive-on-nodes": {
"modal": {
"shared": {
"this-plugin-is-not-active-on-any-nodes": "This plugin is not active on any nodes."
}
}
},
"node-plugins-grid": {
"widget": {
"node-plugins-are-an-advanced-feature-please-review-the-documentation-before-use": "Node Plugins are an advanced feature. Please review the documentation before use.",
"warning": "Warning",
"no-node-plugins-yet": "No node plugins yet",
"create-a-plugin-to-extend-node-capabilities-with": "Create a plugin to extend node capabilities with"
}
}
}

View file

@ -1,4 +1,4 @@
import { GetNodePluginsCommand } from '@remnawave/backend-contract'
import { GetAllNodesCommand, GetNodePluginsCommand } from '@remnawave/backend-contract'
import { useTranslation } from 'react-i18next'
import { TbFile } from 'react-icons/tb'
import { motion } from 'motion/react'
@ -10,11 +10,12 @@ import { RenameModalShared } from '@shared/ui/modals/rename-modal.shared'
import { Page, PageHeaderShared } from '@shared/ui'
interface Props {
nodes: GetAllNodesCommand.Response['response']
plugins: GetNodePluginsCommand.Response['response']['nodePlugins']
}
export const NodePluginsBasePageComponent = (props: Props) => {
const { plugins } = props
const { nodes, plugins } = props
const { t } = useTranslation()
return (
@ -30,7 +31,7 @@ export const NodePluginsBasePageComponent = (props: Props) => {
initial={{ opacity: 0 }}
transition={{ duration: 0.5 }}
>
<NodePluginsGridWidget plugins={plugins} />
<NodePluginsGridWidget nodes={nodes} plugins={plugins} />
</motion.div>
<NodePluginsSpotlightWidget plugins={plugins} />

View file

@ -1,14 +1,15 @@
import { useGetNodePlugins } from '@shared/api/hooks'
import { useGetNodePlugins, useGetNodes } from '@shared/api/hooks'
import { LoadingScreen } from '@shared/ui'
import { NodePluginsBasePageComponent } from '../components/node-plugins-base-page.component'
export function NodePluginsBasePageConnector() {
const { data: plugins, isLoading: isPluginsLoading } = useGetNodePlugins({})
const { data: nodes, isLoading: isNodesLoading } = useGetNodes()
if (isPluginsLoading || !plugins) {
if (isPluginsLoading || isNodesLoading || !plugins || !nodes) {
return <LoadingScreen text="Loading node plugins..." />
}
return <NodePluginsBasePageComponent plugins={plugins.nodePlugins} />
return <NodePluginsBasePageComponent nodes={nodes} plugins={plugins.nodePlugins} />
}

View file

@ -0,0 +1,52 @@
import { GetAllNodesCommand } from '@remnawave/backend-contract'
import { Center, Stack, Text, ThemeIcon } from '@mantine/core'
import { useTranslation } from 'react-i18next'
import { TbServer } from 'react-icons/tb'
import { PiCpu } from 'react-icons/pi'
import { BaseOverlayHeader } from '@shared/ui/overlays/base-overlay-header'
import { SectionCard } from '@shared/ui/section-card'
interface IProps {
nodes: GetAllNodesCommand.Response['response']
}
export const ActivePluginsOnNodesModalShared = (props: IProps) => {
const { nodes } = props
const { t } = useTranslation()
if (nodes.length === 0) {
return (
<Center py="xl">
<Stack align="center" gap="sm">
<ThemeIcon size="xl" variant="gradient-gray">
<PiCpu size={24} />
</ThemeIcon>
<Text c="dimmed" size="sm" ta="center">
{t('adtive-on-nodes.modal.shared.this-plugin-is-not-active-on-any-nodes')}
</Text>
</Stack>
</Center>
)
}
return (
<Stack gap="md">
<SectionCard.Root gap="xs">
{nodes.map((node) => (
<SectionCard.Section key={node.uuid}>
<BaseOverlayHeader
countryCode={node.countryCode}
IconComponent={TbServer}
iconVariant="gradient-blue"
subtitle={node.address}
title={node.name}
titleOrder={5}
withCopy={true}
/>
</SectionCard.Section>
))}
</SectionCard.Root>
</Stack>
)
}

View file

@ -1,4 +1,4 @@
import { PiCheck, PiCopy, PiPencil, PiTrashDuotone } from 'react-icons/pi'
import { PiCheck, PiCopy, PiCpu, PiPencil, PiTrashDuotone } from 'react-icons/pi'
import { GetNodePluginsCommand } from '@remnawave/backend-contract'
import { TbCopyCheck, TbEdit, TbPlug } from 'react-icons/tb'
import { generatePath, useNavigate } from 'react-router-dom'
@ -13,6 +13,7 @@ import { ROUTES } from '@shared/constants'
interface IProps {
handleCloneNodePlugin: (nodePluginUuid: string) => void
handleDeleteNodePlugin: (nodePluginUuid: string) => void
handleShowActiveNodes: (nodePluginUuid: string) => void
isDragOverlay?: boolean
nodePlugin: GetNodePluginsCommand.Response['response']['nodePlugins'][number]
}
@ -22,6 +23,7 @@ export function NodePluginCardWidget(props: IProps) {
nodePlugin,
handleDeleteNodePlugin,
handleCloneNodePlugin,
handleShowActiveNodes,
isDragOverlay = false
} = props
@ -75,6 +77,13 @@ export function NodePluginCardWidget(props: IProps) {
)}
</CopyButton>
<Menu.Item
leftSection={<PiCpu size={18} />}
onClick={() => handleShowActiveNodes(nodePlugin.uuid)}
>
{t('node-plugin-card.widget.active-on-nodes')}
</Menu.Item>
<Menu.Item
leftSection={<PiPencil size={18} />}
onClick={() => {

View file

@ -1,4 +1,6 @@
import { GetNodePluginsCommand } from '@remnawave/backend-contract'
import { TbAlertTriangle, TbFlame, TbPlug, TbPlugConnectedX, TbShieldX } from 'react-icons/tb'
import { GetAllNodesCommand, GetNodePluginsCommand } from '@remnawave/backend-contract'
import { Badge, Center, Group, Stack, Text, ThemeIcon } from '@mantine/core'
import { useTranslation } from 'react-i18next'
import { modals } from '@mantine/modals'
@ -8,18 +10,22 @@ import {
useDeleteNodePlugin,
useReorderNodePlugins
} from '@shared/api/hooks'
import { BaseOverlayHeader } from '@shared/ui/overlays/base-overlay-header'
import { VirtualizedDndGrid } from '@shared/ui/virtualized-dnd-grid'
import { queryClient } from '@shared/api/query-client'
import { SectionCard } from '@shared/ui/section-card'
import { ActivePluginsOnNodesModalShared } from '../active-on-nodes-modal/adtive-on-nodes.modal.shared'
import { NodePluginCardWidget } from '../node-plugin-card/node-plugin-card.widget'
interface IProps {
nodes: GetAllNodesCommand.Response['response']
plugins: GetNodePluginsCommand.Response['response']['nodePlugins']
}
export function NodePluginsGridWidget(props: IProps) {
const { t } = useTranslation()
const { plugins } = props
const { nodes, plugins } = props
const { mutate: deleteNodePlugin } = useDeleteNodePlugin({
mutationFns: {
@ -27,6 +33,9 @@ export function NodePluginsGridWidget(props: IProps) {
queryClient.refetchQueries({
queryKey: QueryKeys.nodePlugins.getNodePlugins.queryKey
})
queryClient.refetchQueries({
queryKey: QueryKeys.nodes.getAllNodes.queryKey
})
}
}
})
@ -88,6 +97,92 @@ export function NodePluginsGridWidget(props: IProps) {
})
}
const handleShowActiveNodes = (nodePluginUuid: string) => {
const activeOnNodes = nodes.filter((node) => node.activePluginUuid === nodePluginUuid)
modals.open({
children: <ActivePluginsOnNodesModalShared nodes={activeOnNodes} />,
title: (
<BaseOverlayHeader
IconComponent={TbPlug}
iconVariant="gradient-teal"
title={t('node-plugin-card.widget.active-on-nodes')}
titleOrder={5}
/>
),
size: 'lg',
centered: true
})
}
if (!plugins || plugins.length === 0) {
return (
<SectionCard.Root p="xl">
<SectionCard.Section>
<BaseOverlayHeader
IconComponent={TbAlertTriangle}
iconVariant="gradient-orange"
subtitle={t(
'node-plugins-grid.widget.node-plugins-are-an-advanced-feature-please-review-the-documentation-before-use'
)}
title={t('node-plugins-grid.widget.warning')}
titleOrder={4}
/>
</SectionCard.Section>
<SectionCard.Section>
<Center py="xl">
<Stack align="center" gap="lg">
<ThemeIcon radius="xl" size={64} variant="gradient-gray">
<TbPlug size={32} />
</ThemeIcon>
<Stack align="center" gap="xs">
<Text fw={600} size="lg" ta="center">
{t('node-plugins-grid.widget.no-node-plugins-yet')}
</Text>
<Text c="dimmed" maw={400} size="sm" ta="center">
{t(
'node-plugins-grid.widget.create-a-plugin-to-extend-node-capabilities-with'
)}
</Text>
</Stack>
<Group gap="sm" justify="center">
<Badge
leftSection={<TbFlame size={12} />}
radius="md"
size="lg"
variant="light"
>
Torrent Blocker
</Badge>
<Badge
color="red"
leftSection={<TbShieldX size={12} />}
radius="md"
size="lg"
variant="light"
>
Blacklist
</Badge>
<Badge
color="violet"
leftSection={<TbPlugConnectedX size={12} />}
radius="md"
size="lg"
variant="light"
>
Connection Drop
</Badge>
</Group>
</Stack>
</Center>
</SectionCard.Section>
</SectionCard.Root>
)
}
return (
<VirtualizedDndGrid
enableDnd={true}
@ -98,6 +193,7 @@ export function NodePluginsGridWidget(props: IProps) {
<NodePluginCardWidget
handleCloneNodePlugin={handleCloneNodePlugin}
handleDeleteNodePlugin={handleDeleteNodePlugin}
handleShowActiveNodes={handleShowActiveNodes}
isDragOverlay
nodePlugin={nodePlugin}
/>
@ -106,6 +202,7 @@ export function NodePluginsGridWidget(props: IProps) {
<NodePluginCardWidget
handleCloneNodePlugin={handleCloneNodePlugin}
handleDeleteNodePlugin={handleDeleteNodePlugin}
handleShowActiveNodes={handleShowActiveNodes}
nodePlugin={nodePlugin}
/>
)}