diff --git a/app/dashboard/src/components/header/github-button.tsx b/app/dashboard/src/components/header/github-button.tsx index 55d1547..516bcf6 100644 --- a/app/dashboard/src/components/header/github-button.tsx +++ b/app/dashboard/src/components/header/github-button.tsx @@ -14,7 +14,7 @@ export const GithubMenuButton = () => { data-show-count="true" aria-label="Star Marzneshin on GitHub" > - Star + Star ) } diff --git a/app/dashboard/src/components/header/index.tsx b/app/dashboard/src/components/header/index.tsx index 7ecc824..6c3b624 100644 --- a/app/dashboard/src/components/header/index.tsx +++ b/app/dashboard/src/components/header/index.tsx @@ -33,18 +33,19 @@ export const Header: FC = () => { p="6" borderBottom={'1px solid gray.600'} position="sticky" + alignItems="center" zIndex={999} top={0} > {t(activePage.name)} - + - + - + diff --git a/app/dashboard/src/components/table/handle-sort.ts b/app/dashboard/src/components/table/handle-sort.ts new file mode 100644 index 0000000..b985dad --- /dev/null +++ b/app/dashboard/src/components/table/handle-sort.ts @@ -0,0 +1,22 @@ + +import { FilterType } from 'types'; + +export const handleSort = ( + filters: FilterType, + column: string, + onFilterChange: (filters: Partial) => void +) => { + let newSort = filters.sort; + if (newSort.includes(column)) { + if (newSort.startsWith('-')) { + newSort = '-created_at'; + } else { + newSort = '-' + column; + } + } else { + newSort = column; + } + onFilterChange({ + sort: newSort, + }); +}; diff --git a/app/dashboard/src/components/table/index.ts b/app/dashboard/src/components/table/index.tsx similarity index 82% rename from app/dashboard/src/components/table/index.ts rename to app/dashboard/src/components/table/index.tsx index 959892b..4575876 100644 --- a/app/dashboard/src/components/table/index.ts +++ b/app/dashboard/src/components/table/index.tsx @@ -1,4 +1,5 @@ export * from './icons'; +export * from './handle-sort'; export * from './sort'; export * from './pagination'; export * from './status-sort-select'; diff --git a/app/dashboard/src/pages/nodes/table/action-buttons.tsx b/app/dashboard/src/pages/nodes/table/action-buttons.tsx new file mode 100644 index 0000000..7133093 --- /dev/null +++ b/app/dashboard/src/pages/nodes/table/action-buttons.tsx @@ -0,0 +1,51 @@ + +import { t } from 'i18next'; +import { Cog6ToothIcon } from '@heroicons/react/24/outline'; +import { chakra, HStack, IconButton, Tooltip } from '@chakra-ui/react'; +import { FC } from 'react'; + +const iconProps = { + baseStyle: { + w: 4, + h: 4, + }, +}; +const CoreSettingsIcon = chakra(Cog6ToothIcon, iconProps); +type ActionButtonsProps = {}; + +export const ActionButtons: FC = () => { + return ( + { + e.preventDefault(); + e.stopPropagation(); + }} + > + + + + + + + ); +}; + diff --git a/app/dashboard/src/pages/nodes/table.tsx b/app/dashboard/src/pages/nodes/table/index.tsx similarity index 86% rename from app/dashboard/src/pages/nodes/table.tsx rename to app/dashboard/src/pages/nodes/table/index.tsx index c18e1ff..310d7dc 100644 --- a/app/dashboard/src/pages/nodes/table.tsx +++ b/app/dashboard/src/pages/nodes/table/index.tsx @@ -5,7 +5,6 @@ import { AccordionItem, AccordionPanel, Box, - chakra, HStack, IconButton, Table, @@ -21,8 +20,8 @@ import { VStack, } from '@chakra-ui/react'; import classNames from 'classnames'; +import { ActionButtons } from './action-buttons'; -import { t } from 'i18next'; import { FC, Fragment, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { StatusBadge } from 'components/status-badge'; @@ -33,18 +32,9 @@ import { EditIcon, Sort, StatusSortSelect, + handleSort, } from 'components/table'; import { useNodes } from 'stores'; -import { Cog6ToothIcon } from '@heroicons/react/24/outline'; - -const iconProps = { - baseStyle: { - w: 4, - h: 4, - }, -}; - -const CoreSettingsIcon = chakra(Cog6ToothIcon, iconProps); type ExpandedIndex = number | number[]; @@ -57,7 +47,6 @@ export const NodesTable: FC = (props) => { onEditingNode, onAddingNode, onFilterChange, - refetchNodes, } = useNodes(); const total = nodes.length; @@ -72,22 +61,6 @@ export const NodesTable: FC = (props) => { // TODO: Find a different mechancism to to detect ftiler // const isFiltered = users.length !== total; - const handleSort = (column: string) => { - let newSort = filters.sort; - if (newSort.includes(column)) { - if (newSort.startsWith('-')) { - newSort = '-created_at'; - } else { - newSort = '-' + column; - } - } else { - newSort = column; - } - onFilterChange({ - sort: newSort, - }); - }; - const handleStatusFilter = (e: any) => { onFilterChange({ status: e.target.value.length > 0 ? e.target.value : undefined, @@ -106,7 +79,6 @@ export const NodesTable: FC = (props) => { /> ); - refetchNodes(); return ( = (props) => { pl={4} pr={4} cursor={'pointer'} - onClick={handleSort.bind(null, 'name')} + onClick={handleSort.bind(null, filters, 'name', onFilterChange)} > {t('name')} @@ -163,7 +135,7 @@ export const NodesTable: FC = (props) => { minW="100px" cursor={'pointer'} pr={0} - onClick={handleSort.bind(null, 'xray_version')} + onClick={handleSort.bind(null, filters, 'xray_version', onFilterChange)} > {t('nodesTable.xrayVersion')} @@ -306,9 +278,9 @@ export const NodesTable: FC = (props) => { {t('name')} @@ -316,8 +288,8 @@ export const NodesTable: FC = (props) => { @@ -341,10 +313,10 @@ export const NodesTable: FC = (props) => { {t('nodesTable.xrayVersion')} @@ -352,10 +324,10 @@ export const NodesTable: FC = (props) => { {t('nodesTable.ipPort')} @@ -363,8 +335,8 @@ export const NodesTable: FC = (props) => { @@ -420,41 +392,3 @@ export const NodesTable: FC = (props) => { ); }; - -type ActionButtonsProps = {}; - -const ActionButtons: FC = () => { - return ( - { - e.preventDefault(); - e.stopPropagation(); - }} - > - - - - - - - ); -}; diff --git a/app/dashboard/src/pages/services/table.tsx b/app/dashboard/src/pages/services/table.tsx index fc81ba9..f9c6198 100644 --- a/app/dashboard/src/pages/services/table.tsx +++ b/app/dashboard/src/pages/services/table.tsx @@ -1,5 +1,6 @@ import { + Accordion, Box, HStack, Table, @@ -13,15 +14,17 @@ import { } from '@chakra-ui/react'; import classNames from 'classnames'; -import { FC, Fragment, useEffect, useState } from 'react'; +import { FC, useState, } from 'react'; import { useTranslation } from 'react-i18next'; import { EmptySection, + handleSort, Pagination, Sort } from 'components/table'; import { useServices } from 'stores'; +type ExpandedIndex = number | number[]; type ServicesTableProps = {} & TableProps; export const ServicesTable: FC = (props) => { @@ -31,52 +34,104 @@ export const ServicesTable: FC = (props) => { onEditingService, onCreateService, onFilterChange, - refetchServices, } = useServices(); - + const useTable = useBreakpointValue({ base: false, md: true }); const { t } = useTranslation(); - const marginTop = useBreakpointValue({ base: 120, lg: 72 }) || 72; - const [top, setTop] = useState(`${marginTop}px`); - - useEffect(() => { - const calcTop = () => { - const el = document.querySelectorAll('#filters')[0] as HTMLElement; - setTop(`${el.offsetHeight}px`); - }; - window.addEventListener('scroll', calcTop); - () => window.removeEventListener('scroll', calcTop); - }, []); - - const handleSort = (column: string) => { - let newSort = filters.sort; - if (newSort.includes(column)) { - if (newSort.startsWith('-')) { - newSort = '-created_at'; - } else { - newSort = '-' + column; - } - } else { - newSort = column; - } - onFilterChange({ - sort: newSort, - }); - }; + const [selectedRow,] = useState( + undefined + ); - refetchServices(); return ( + + + + + + + + + + + + + {!useTable && + services?.map((service, i) => { + return ( + { + e.stopPropagation(); + onEditingService(service); + }} + > + + + + + ); + })} + +
+ + {t('services')} + + + + + {t('servicesTable.usersNumber')} + + + + + {t('servicesTable.inboundsNumber')} + + +
+ {service.name} + + {service.users.length} + + {service.inbounds.length} +
+
- {services.length !== 0 ? services.map((service, i) => { - return ( - + {useTable && + services?.map((service, i) => { + return ( = (props) => { {service.inbounds.length} - - ); - }) : + ); + })} + {services.length == 0 && ( - } + )}
{t('services')} @@ -85,11 +140,10 @@ export const ServicesTable: FC = (props) => { {t('servicesTable.usersNumber')} @@ -98,11 +152,10 @@ export const ServicesTable: FC = (props) => { {t('servicesTable.inboundsNumber')} @@ -112,9 +165,9 @@ export const ServicesTable: FC = (props) => {
= (props) => { />
diff --git a/app/dashboard/src/pages/users/table/action-buttons.tsx b/app/dashboard/src/pages/users/table/action-buttons.tsx new file mode 100644 index 0000000..b44941d --- /dev/null +++ b/app/dashboard/src/pages/users/table/action-buttons.tsx @@ -0,0 +1,144 @@ + +import { t } from 'i18next'; +import CopyToClipboard from 'react-copy-to-clipboard'; +import { User } from 'types'; +import { + CopiedIcon, + CopyIcon, + QRIcon, + SubscriptionLinkIcon +} from 'components/table'; +import { + HStack, + IconButton, + Tooltip, +} from '@chakra-ui/react'; +import { FC, useEffect, useState } from 'react'; +import { useUsers } from 'stores'; + +type ActionButtonsProps = { + user: User; +}; + +export const ActionButtons: FC = ({ user }) => { + const { setQRCode, setSubLink } = useUsers(); + + const proxyLinks = user.links.join('\r\n'); + + const [copied, setCopied] = useState([-1, false]); + useEffect(() => { + if (copied[1]) { + setTimeout(() => { + setCopied([-1, false]); + }, 1000); + } + }, [copied]); + return ( + { + e.preventDefault(); + e.stopPropagation(); + }} + > + { + setCopied([0, true]); + }} + > +
+ + + {copied[0] == 0 && copied[1] ? ( + + ) : ( + + )} + + +
+
+ { + setCopied([1, true]); + }} + > +
+ + + {copied[0] == 1 && copied[1] ? : } + + +
+
+ + { + setQRCode(user.links); + setSubLink(user.subscription_url); + }} + > + + + +
+ ); +}; diff --git a/app/dashboard/src/pages/users/table/index.tsx b/app/dashboard/src/pages/users/table/index.tsx index 257ebbe..f050737 100644 --- a/app/dashboard/src/pages/users/table/index.tsx +++ b/app/dashboard/src/pages/users/table/index.tsx @@ -20,13 +20,9 @@ import { VStack, } from '@chakra-ui/react'; import classNames from 'classnames'; - import { statusColors } from 'constants/Settings'; -import { t } from 'i18next'; -import { FC, Fragment, useEffect, useState } from 'react'; -import CopyToClipboard from 'react-copy-to-clipboard'; +import { FC, Fragment, useState } from 'react'; import { useTranslation } from 'react-i18next'; -import { User } from 'types'; import { OnlineBadge } from 'components/online-badge'; import { OnlineStatus } from 'components/online-status'; import { StatusBadge } from 'components/status-badge'; @@ -34,16 +30,14 @@ import { Pagination, EmptySection, AccordionArrowIcon, + handleSort, EditIcon, - CopiedIcon, - CopyIcon, StatusSortSelect, Sort, - QRIcon, - SubscriptionLinkIcon } from 'components/table'; import { UsageSlider, UsageSliderCompact } from './usage-slider'; import { useUsers } from 'stores'; +import { ActionButtons } from './action-buttons'; type ExpandedIndex = number | number[]; @@ -54,7 +48,6 @@ export const UsersTable: FC = (props) => { usersFilters: filters, users: { users }, users: { total }, - refetchUsers, onEditingUser, onCreateUser, onFilterChange, @@ -64,37 +57,10 @@ export const UsersTable: FC = (props) => { const [selectedRow, setSelectedRow] = useState( undefined ); - const marginTop = useBreakpointValue({ base: 120, lg: 72 }) || 72; - const [top, setTop] = useState(`${marginTop}px`); const useTable = useBreakpointValue({ base: false, md: true }); - useEffect(() => { - const calcTop = () => { - const el = document.querySelectorAll('#filters')[0] as HTMLElement; - setTop(`${el.offsetHeight}px`); - }; - window.addEventListener('scroll', calcTop); - () => window.removeEventListener('scroll', calcTop); - }, []); - const isFiltered = users.length !== total; - const handleSort = (column: string) => { - let newSort = filters.sort; - if (newSort.includes(column)) { - if (newSort.startsWith('-')) { - newSort = '-created_at'; - } else { - newSort = '-' + column; - } - } else { - newSort = column; - } - onFilterChange({ - sort: newSort, - }); - }; - const handleStatusFilter = (e: any) => { onFilterChange({ status: e.target.value.length > 0 ? e.target.value : undefined, @@ -104,7 +70,6 @@ export const UsersTable: FC = (props) => { const toggleAccordion = (index: number) => { setSelectedRow(index === selectedRow ? undefined : index); }; - refetchUsers(); return ( = (props) => { {t('users')} @@ -131,7 +95,6 @@ export const UsersTable: FC = (props) => { = (props) => { {t('usersTable.dataUsage')} @@ -173,7 +135,6 @@ export const UsersTable: FC = (props) => { = (props) => { {t('username')} @@ -344,7 +304,6 @@ export const UsersTable: FC = (props) => { = (props) => { {t('usersTable.dataUsage')} @@ -384,7 +342,6 @@ export const UsersTable: FC = (props) => { @@ -448,130 +405,3 @@ export const UsersTable: FC = (props) => { ); }; - -type ActionButtonsProps = { - user: User; -}; - -const ActionButtons: FC = ({ user }) => { - const { setQRCode, setSubLink } = useUsers(); - - const proxyLinks = user.links.join('\r\n'); - - const [copied, setCopied] = useState([-1, false]); - useEffect(() => { - if (copied[1]) { - setTimeout(() => { - setCopied([-1, false]); - }, 1000); - } - }, [copied]); - return ( - { - e.preventDefault(); - e.stopPropagation(); - }} - > - { - setCopied([0, true]); - }} - > -
- - - {copied[0] == 0 && copied[1] ? ( - - ) : ( - - )} - - -
-
- { - setCopied([1, true]); - }} - > -
- - - {copied[0] == 1 && copied[1] ? : } - - -
-
- - { - setQRCode(user.links); - setSubLink(user.subscription_url); - }} - > - - - -
- ); -};