diff --git a/app/dashboard/chakra.config.ts b/app/dashboard/chakra.config.ts
@@ -6,6 +6,7 @@ export const theme = extendTheme({
},
colors: {
'light-border': '#d2d2d4',
+ 'dark-bg': '#1A202C',
primary: {
50: '#9cb7f2',
100: '#88a9ef',
diff --git a/app/dashboard/src/components/delete-modal/index.tsx b/app/dashboard/src/components/delete-modal/index.tsx
@@ -14,7 +14,7 @@ import {
} from '@chakra-ui/react';
import { FC } from 'react';
import { Trans, useTranslation } from 'react-i18next';
-import { DeleteIcon } from 'components/dialog/Icons';
+import { DeleteIcon } from 'components/dialog';
import { Icon } from 'components/icon';
type DeleteModalProps = {
diff --git a/app/dashboard/src/components/dialog/Icons.tsx b/app/dashboard/src/components/dialog/icons.tsx
rename from app/dashboard/src/components/dialog/Icons.tsx
rename to app/dashboard/src/components/dialog/icons.tsx
diff --git a/app/dashboard/src/components/dialog/index.ts b/app/dashboard/src/components/dialog/index.ts
@@ -0,0 +1,3 @@
+export * from './modal-footer';
+export * from './modal-header';
+export * from './icons';
diff --git a/app/dashboard/src/components/dialog/ModalFooter.tsx b/app/dashboard/src/components/dialog/modal-footer.tsx
rename from app/dashboard/src/components/dialog/ModalFooter.tsx
rename to app/dashboard/src/components/dialog/modal-footer.tsx
diff --git a/app/dashboard/src/components/dialog/ModalHeader.tsx b/app/dashboard/src/components/dialog/modal-header.tsx
rename from app/dashboard/src/components/dialog/ModalHeader.tsx
rename to app/dashboard/src/components/dialog/modal-header.tsx
diff --git a/app/dashboard/src/components/Footer.tsx b/app/dashboard/src/components/footer/index.tsx
rename from app/dashboard/src/components/Footer.tsx
rename to app/dashboard/src/components/footer/index.tsx
diff --git a/app/dashboard/src/components/header/github-button.tsx b/app/dashboard/src/components/header/github-button.tsx
@@ -0,0 +1,20 @@
+
+import { REPO_URL } from 'constants/Project';
+import { useColorMode } from '@chakra-ui/react';
+import GitHubButton from 'react-github-btn';
+
+export const GithubMenuButton = () => {
+ const { colorMode } = useColorMode();
+ const gBtnColor = colorMode === 'dark' ? 'dark_dimmed' : colorMode;
+ return (
+ <GitHubButton
+ href={REPO_URL}
+ data-color-scheme={`no-preference: ${gBtnColor}; light: ${gBtnColor}; dark: ${gBtnColor};`}
+ data-size="large"
+ data-show-count="true"
+ aria-label="Star Marzneshin on GitHub"
+ >
+ Star
+ </GitHubButton>
+ )
+}
diff --git a/app/dashboard/src/components/header/index.tsx b/app/dashboard/src/components/header/index.tsx
@@ -1,90 +1,24 @@
import {
Box,
- chakra,
HStack,
- IconButton,
- Menu,
- MenuButton,
- MenuItem,
- MenuList,
Text,
- useColorMode,
} from '@chakra-ui/react';
-import {
- ArrowLeftStartOnRectangleIcon,
- Bars3Icon,
- Cog6ToothIcon,
- CurrencyDollarIcon,
-} from '@heroicons/react/24/outline';
-import { pages } from 'constants/Pages';
-import { DONATION_URL, REPO_URL } from 'constants/Project';
-import { useNodes, useDashboard } from 'stores';
-import differenceInDays from 'date-fns/differenceInDays';
-import isValid from 'date-fns/isValid';
-import { FC, ReactNode, useState } from 'react';
-import GitHubButton from 'react-github-btn';
+import { useDashboard } from 'stores';
+import { FC, ReactNode } from 'react';
import { useTranslation } from 'react-i18next';
-import { Link } from 'react-router-dom';
-import { Language } from '../Language';
-import { ThemeToggle } from '../theme-toggler';
+import { Language } from 'components/language';
+import { ThemeToggle } from 'components/theme-toggler';
type HeaderProps = {
actions?: ReactNode;
};
-const iconProps = {
- baseStyle: {
- w: 4,
- h: 4,
- },
-};
-
-const CoreSettingsIcon = chakra(Cog6ToothIcon, iconProps);
-const SettingsIcon = chakra(Bars3Icon, iconProps);
-const LogoutIcon = chakra(ArrowLeftStartOnRectangleIcon, iconProps);
-const DonationIcon = chakra(CurrencyDollarIcon, iconProps);
-const NotificationCircle = chakra(Box, {
- baseStyle: {
- bg: 'yellow.500',
- w: '2',
- h: '2',
- rounded: 'full',
- position: 'absolute',
- },
-});
-const NOTIFICATION_KEY = 'marzban-menu-notification';
-
-export const shouldShowDonation = (): boolean => {
- const date = localStorage.getItem(NOTIFICATION_KEY);
- if (!date) return true;
- try {
- if (date && isValid(parseInt(date))) {
- if (differenceInDays(new Date(), new Date(parseInt(date))) >= 7)
- return true;
- return false;
- }
- return true;
- } catch (err) {
- return true;
- }
-};
+import { GithubMenuButton } from './github-button';
+import { HeaderMenu } from './menu';
export const Header: FC<HeaderProps> = () => {
- const {
- activePage,
- } = useDashboard();
+ const { activePage, } = useDashboard();
const { t } = useTranslation();
- const { colorMode } = useColorMode();
- const [showDonationNotif, setShowDonationNotif] = useState(
- shouldShowDonation()
- );
- const gBtnColor = colorMode === 'dark' ? 'dark_dimmed' : colorMode;
-
- const handleOnClose = () => {
- localStorage.setItem(NOTIFICATION_KEY, new Date().getTime().toString());
- setShowDonationNotif(false);
- };
-
return (
<HStack
gap={2}
@@ -94,93 +28,23 @@ export const Header: FC<HeaderProps> = () => {
direction: 'ltr',
},
}}
- position="relative"
+ bg='white'
+ _dark={{ bg: 'dark-bg' }}
+ p="6"
+ borderBottom={'1px solid gray.600'}
+ position="sticky"
+ zIndex={999}
+ top={0}
>
<Text as="h1" fontWeight="bold" fontSize="2xl" m="0">
- {t(pages[activePage]?.name)}
+ {t(activePage.name)}
</Text>
- {showDonationNotif && (
- <NotificationCircle top="0" right="0" zIndex={9999} />
- )}
- <Box overflow="auto" css={{ direction: 'rtl' }}>
+ <Box css={{ direction: 'rtl' }}>
<HStack alignItems="center">
- <Menu>
- <MenuButton
- as={IconButton}
- size="sm"
- variant="outline"
- icon={
- <>
- <SettingsIcon />
- </>
- }
- position="relative"
- ></MenuButton>
- <MenuList minW="170px" zIndex={99999} className="menuList">
- {/* NOTE: Host settings trigger */}
- {/* <MenuItem */}
- {/* maxW="170px" */}
- {/* fontSize="sm" */}
- {/* icon={<HostsIcon />} */}
- {/* onClick={onEditingHosts.bind(null, true)} */}
- {/* > */}
- {/* {t('header.hostSettings')} */}
- {/* </MenuItem> */}
- <Link to={DONATION_URL} target="_blank">
- <MenuItem
- maxW="170px"
- fontSize="sm"
- icon={<DonationIcon />}
- position="relative"
- onClick={handleOnClose}
- >
- {t('header.donation')}{' '}
- {showDonationNotif && (
- <NotificationCircle top="3" right="2" />
- )}
- </MenuItem>
- </Link>
- <Link to="/login">
- <MenuItem maxW="170px" fontSize="sm" icon={<LogoutIcon />}>
- {t('header.logout')}
- </MenuItem>
- </Link>
- </MenuList>
- </Menu>
-
- <IconButton
- size="sm"
- variant="outline"
- aria-label="core settings"
- onClick={() => {
- useNodes.setState({ isEditingCore: true });
- }}
- >
- <CoreSettingsIcon />
- </IconButton>
+ <HeaderMenu />
<Language />
<ThemeToggle />
- <Box
- css={{ direction: 'ltr' }}
- display="flex"
- alignItems="center"
- pr="2"
- __css={{
- '& span': {
- display: 'inline-flex',
- },
- }}
- >
- <GitHubButton
- href={REPO_URL}
- data-color-scheme={`no-preference: ${gBtnColor}; light: ${gBtnColor}; dark: ${gBtnColor};`}
- data-size="large"
- data-show-count="true"
- aria-label="Star Marzneshin on GitHub"
- >
- Star
- </GitHubButton>
- </Box>
+ <GithubMenuButton />
</HStack>
</Box>
</HStack>
diff --git a/app/dashboard/src/components/header/menu.tsx b/app/dashboard/src/components/header/menu.tsx
@@ -0,0 +1,62 @@
+import {
+ IconButton,
+ Menu,
+ MenuButton,
+ MenuItem,
+ chakra,
+ MenuList,
+} from '@chakra-ui/react';
+import { DONATION_URL } from 'constants/Project'
+import {
+ ArrowLeftStartOnRectangleIcon,
+ Bars3Icon,
+ CurrencyDollarIcon,
+} from '@heroicons/react/24/outline';
+import { FC } from 'react';
+import { Link } from 'react-router-dom';
+import { useTranslation } from 'react-i18next';
+
+const iconProps = {
+ baseStyle: {
+ w: 4,
+ h: 4,
+ },
+};
+
+const SettingsIcon = chakra(Bars3Icon, iconProps);
+const LogoutIcon = chakra(ArrowLeftStartOnRectangleIcon, iconProps);
+const DonationIcon = chakra(CurrencyDollarIcon, iconProps);
+
+type HeaderMenuProps = {};
+
+export const HeaderMenu: FC<HeaderMenuProps> = () => {
+ const { t } = useTranslation('header');
+ return (
+ <Menu>
+ <MenuButton
+ as={IconButton}
+ size="sm"
+ variant="outline"
+ icon={<SettingsIcon />}
+ position="relative"
+ ></MenuButton>
+ <MenuList minW="170px" zIndex={99999} className="menuList">
+ <Link to={DONATION_URL} target="_blank">
+ <MenuItem
+ maxW="170px"
+ fontSize="sm"
+ icon={<DonationIcon />}
+ position="relative"
+ >
+ {t('donation')}{' '}
+ </MenuItem>
+ </Link>
+ <Link to="/login">
+ <MenuItem maxW="170px" fontSize="sm" icon={<LogoutIcon />}>
+ {t('logout')}
+ </MenuItem>
+ </Link>
+ </MenuList>
+ </Menu>
+ )
+}
diff --git a/app/dashboard/src/components/Input.tsx b/app/dashboard/src/components/input/index.tsx
rename from app/dashboard/src/components/Input.tsx
rename to app/dashboard/src/components/input/index.tsx
diff --git a/app/dashboard/src/components/Language.tsx b/app/dashboard/src/components/language/index.tsx
rename from app/dashboard/src/components/Language.tsx
rename to app/dashboard/src/components/language/index.tsx
diff --git a/app/dashboard/src/components/OnlineBadge.tsx b/app/dashboard/src/components/online-badge/index.tsx
rename from app/dashboard/src/components/OnlineBadge.tsx
rename to app/dashboard/src/components/online-badge/index.tsx
diff --git a/app/dashboard/src/components/OnlineStatus.tsx b/app/dashboard/src/components/online-status/index.tsx
rename from app/dashboard/src/components/OnlineStatus.tsx
rename to app/dashboard/src/components/online-status/index.tsx
diff --git a/app/dashboard/src/components/page/index.tsx b/app/dashboard/src/components/page/index.tsx
@@ -1,7 +1,21 @@
import { Box, VStack } from '@chakra-ui/react';
-import { FC, PropsWithChildren } from 'react';
+import { FC, PropsWithChildren, useEffect } from 'react';
+import { useDashboard } from 'stores';
+import { PageType } from 'types';
+
+const usePage = (page: PageType) => {
+ useEffect(() => {
+ useDashboard.getState().activatePage(page);
+ }, []);
+};
+
+type PageProp = {
+ page: PageType;
+}
+
+export const Page: FC<PropsWithChildren<PageProp>> = ({ page, children }) => {
+ usePage(page);
-export const Page: FC<PropsWithChildren> = ({ children }) => {
return (
<VStack justifyContent="space-between" mt={4} rowGap={4}>
<Box w="full">
diff --git a/app/dashboard/src/components/sidebar/index.tsx b/app/dashboard/src/components/sidebar/index.tsx
@@ -1,10 +1,10 @@
import { HamburgerIcon } from '@chakra-ui/icons';
import { chakra, Flex } from '@chakra-ui/react'
-import { pages } from 'constants/Pages';
+import { pages } from 'stores';
import { FC } from 'react';
import { Logo } from './logo';
import { SidebarGroup } from './sidebar-group';
-import { SidebarItem, SideBarItemProps } from './sidebar-item';
+import { SidebarItem } from './sidebar-item';
export const SidebarMenuIcon = chakra(HamburgerIcon, {
baseStyle: {
@@ -30,8 +30,12 @@ export const Sidebar: FC = () => {
>
<Logo />
<SidebarGroup titleT={'sidebar.groups.management'}>
- {pages.map((item: SideBarItemProps, index: number) =>
- <SidebarItem path={item.path} name={item.name} itemIcon={item.itemIcon} index={index} key={index} />)}
+ {Object.entries(pages).map(([pageKey, page]) =>
+ <SidebarItem
+ page={page}
+ key={pageKey}
+ />
+ )}
</SidebarGroup>
</Flex>
</aside>
diff --git a/app/dashboard/src/components/sidebar/sidebar-item.tsx b/app/dashboard/src/components/sidebar/sidebar-item.tsx
@@ -8,19 +8,16 @@ import { FC, useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { PageType } from 'types';
-export type SideBarItemProps = Pick<
- PageType,
- | 'name'
- | 'path'
- | 'itemIcon'
->
+export type SideBarItemProps = {
+ page: PageType,
+}
-export const SidebarItem: FC<SideBarItemProps & { index: number }> = ({ itemIcon: ItemIcon, name, path, index }) => {
+export const SidebarItem: FC<SideBarItemProps> = ({ page }) => {
const { t } = useTranslation();
const { activePage, activatePage, isCollapsed } = useDashboard();
- const [active, activate] = useState(index === activePage);
+ const [active, activate] = useState(page === activePage);
- useEffect(() => { activate(index === activePage) }, [active, activePage]);
+ useEffect(() => { activate(page === activePage) }, [active, activePage]);
const darkStyle = { bg: active ? 'primary.900' : 'transparent' };
const hoverStyle = {
@@ -31,21 +28,20 @@ export const SidebarItem: FC<SideBarItemProps & { index: number }> = ({ itemIcon
},
transition: 'background-color 0.9s',
};
-
+ const ItemIcon = page.itemIcon;
return (
<ChakraLink
as={ReactRouterLink}
borderRadius={10}
bg={active ? 'primary.500' : 'transparent'}
border="4px"
- // ps="4px"
borderColor="transparent"
_dark={darkStyle}
- to={path}
+ to={page.path}
w="85%"
_hover={hoverStyle}
onClick={() => {
- activatePage(index);
+ activatePage(page);
}}
>
<Flex flexDir="row" alignItems="center" justifyContent={isCollapsed ? 'center' : undefined} >
@@ -56,7 +52,7 @@ export const SidebarItem: FC<SideBarItemProps & { index: number }> = ({ itemIcon
color={active ? 'gray.100' : 'gray.700'}
_dark={{ color: 'gray.100' }} fontSize="xl"
display={isCollapsed ? 'none' : 'block'}>
- {t(name)}
+ {t(page.name)}
</Text>
</Flex>
</ChakraLink>
diff --git a/app/dashboard/src/components/StatusBadge.tsx b/app/dashboard/src/components/status-badge/index.tsx
rename from app/dashboard/src/components/StatusBadge.tsx
rename to app/dashboard/src/components/status-badge/index.tsx
diff --git a/app/dashboard/src/components/Textarea.tsx b/app/dashboard/src/components/textarea/index.tsx
rename from app/dashboard/src/components/Textarea.tsx
rename to app/dashboard/src/components/textarea/index.tsx
diff --git a/app/dashboard/src/components/UsageFilter.tsx b/app/dashboard/src/components/usage-filter/index.tsx
rename from app/dashboard/src/components/UsageFilter.tsx
rename to app/dashboard/src/components/usage-filter/index.tsx
diff --git a/app/dashboard/src/constants/Pages.tsx b/app/dashboard/src/constants/Pages.tsx
@@ -1,38 +0,0 @@
-import { PageType } from 'types';
-import { chakra } from '@chakra-ui/react';
-import { CubeIcon, HomeIcon, ServerIcon, ServerStackIcon, UsersIcon } from '@heroicons/react/24/outline';
-import { IconBaseStyle } from 'constants/IconBaseStyle';
-
-const UsersSidebarItemIcon = chakra(UsersIcon, IconBaseStyle);
-const ServicesSidebarItemIcon = chakra(ServerStackIcon, IconBaseStyle);
-const NodesSidebarItemIcon = chakra(CubeIcon, IconBaseStyle);
-const HostSidebarItemIcon = chakra(ServerIcon, IconBaseStyle);
-const HomeSidebarItemIcon = chakra(HomeIcon, IconBaseStyle);
-
-export const pages: PageType[] = [
- {
- name: 'home',
- path: '/',
- itemIcon: HomeSidebarItemIcon,
- },
- {
- name: 'users',
- path: '/users',
- itemIcon: UsersSidebarItemIcon,
- },
- {
- name: 'services',
- path: '/services',
- itemIcon: ServicesSidebarItemIcon,
- },
- {
- name: 'nodes',
- path: '/nodes',
- itemIcon: NodesSidebarItemIcon,
- },
- {
- name: 'hosts',
- path: '/hosts',
- itemIcon: HostSidebarItemIcon,
- },
-];
diff --git a/app/dashboard/src/layouts/console-layout.tsx b/app/dashboard/src/layouts/console-layout.tsx
@@ -1,6 +1,6 @@
import { Box } from '@chakra-ui/react';
import { PanelGroup, Panel, PanelResizeHandle, ImperativePanelHandle } from 'react-resizable-panels';
-import { Footer } from 'components/Footer';
+import { Footer } from 'components/footer';
import { Header } from 'components/header';
import { Sidebar } from 'components/sidebar';
import { Suspense, useRef } from 'react';
@@ -18,9 +18,9 @@ export const ConsoleLayout = () => {
</Panel>
<PanelResizeHandle />
<Panel minSize={30} >
- <Box overflowY="auto" p="6" >
+ <Box overflowY="auto" h="100vh" >
<Header />
- <Box >
+ <Box p="6">
<Suspense>
<Outlet />
</Suspense>
diff --git a/app/dashboard/src/pages/home/index.tsx b/app/dashboard/src/pages/home/index.tsx
@@ -2,10 +2,11 @@
import Page from 'components/page';
import { FC } from 'react';
import { Statistics } from './statistics';
+import { pages } from 'stores';
export const Home: FC = () => {
return (
- <Page>
+ <Page page={pages.home}>
<Statistics mt="4" />
</Page>
);
diff --git a/app/dashboard/src/pages/hosts/dialog/index.tsx b/app/dashboard/src/pages/hosts/dialog/index.tsx
@@ -36,9 +36,8 @@ import { getDefaultValues } from './default-values';
import { useForm } from 'react-hook-form';
import 'slick-carousel/slick/slick-theme.css';
import 'slick-carousel/slick/slick.css';
-import { AddIcon, DeleteIcon } from 'components/dialog/Icons';
-import { Input as CustomInput } from 'components/Input';
-import { DialogModalHeader, } from 'components/dialog/ModalHeader';
+import { DialogModalHeader, AddIcon, DeleteIcon } from 'components/dialog';
+import { Input as CustomInput } from 'components/input';
import { EditIcon } from 'components/table';
import { InfoIcon } from './icons';
import {
diff --git a/app/dashboard/src/pages/hosts/index.tsx b/app/dashboard/src/pages/hosts/index.tsx
@@ -1,9 +1,10 @@
import { HostsDialog } from './dialog';
import Page from 'components/page';
+import { pages } from 'stores';
const HostsPage = () => {
return (
- <Page>
+ <Page page={pages.hosts}>
<HostsDialog />
</Page>
)
diff --git a/app/dashboard/src/pages/login/index.tsx b/app/dashboard/src/pages/login/index.tsx
@@ -16,12 +16,12 @@ import { FC, useEffect, useState } from 'react';
import { FieldValues, useForm } from 'react-hook-form';
import { useLocation, useNavigate } from 'react-router-dom';
import { z } from 'zod';
-import { Footer } from 'components/Footer';
-import { Input } from 'components/Input';
+import { Footer } from 'components/footer';
+import { Input } from 'components/input';
import { fetch } from 'service/http';
import { ReactComponent as Logo } from 'assets/logo.svg';
import { useTranslation } from 'react-i18next';
-import { Language } from 'components/Language';
+import { Language } from 'components/language';
import { useAdmin } from 'stores';
const schema = z.object({
diff --git a/app/dashboard/src/pages/nodes/dialog.tsx b/app/dashboard/src/pages/nodes/dialog.tsx
@@ -28,9 +28,8 @@ import { Controller, useForm } from 'react-hook-form';
import { useTranslation } from 'react-i18next';
import 'slick-carousel/slick/slick-theme.css';
import 'slick-carousel/slick/slick.css';
-import { DeleteIcon, AddIcon, EditIcon } from 'components/dialog/Icons';
-import { Input } from 'components/Input';
-import { DialogModalHeader } from 'components/dialog/ModalHeader';
+import { DialogModalHeader, DeleteIcon, AddIcon, EditIcon } from 'components/dialog';
+import { Input } from 'components/input';
const CustomInput = chakra(Input, {
baseStyle: {
diff --git a/app/dashboard/src/pages/nodes/index.tsx b/app/dashboard/src/pages/nodes/index.tsx
@@ -1,23 +1,17 @@
-import { FC, useEffect } from 'react';
+import { FC } from 'react';
import { CoreSettingsModal } from './modals/core-settings-modal';
import { NodesDialog } from './dialog';
import { NodesUsage } from './usage';
import { NodesTable } from './table';
import { NodesFilters } from './filters';
import { DeleteNodeModal } from './modals/delete-modal';
-import { useNodes, useDashboard } from 'stores';
-import { pages } from 'constants/Pages';
+import { pages } from 'stores';
import Page from 'components/page';
const NodesPage: FC = () => {
- useEffect(() => {
- useDashboard.getState().activatePage(pages.findIndex((page) => page.path === '/nodes'));
- useNodes.getState().refetchNodes();
- useNodes.getState().refetchCertificate();
- }, []);
return (
- <Page>
+ <Page page={pages.nodes}>
<NodesUsage />
<NodesFilters />
<NodesTable />
diff --git a/app/dashboard/src/pages/nodes/table.tsx b/app/dashboard/src/pages/nodes/table.tsx
@@ -23,9 +23,9 @@ import {
import classNames from 'classnames';
import { t } from 'i18next';
-import { FC, Fragment, useEffect, useState } from 'react';
+import { FC, Fragment, useState } from 'react';
import { useTranslation } from 'react-i18next';
-import { StatusBadge } from 'components/StatusBadge';
+import { StatusBadge } from 'components/status-badge';
import {
Pagination,
EmptySection,
@@ -43,6 +43,7 @@ const iconProps = {
h: 4,
},
};
+
const CoreSettingsIcon = chakra(Cog6ToothIcon, iconProps);
type ExpandedIndex = number | number[];
@@ -53,9 +54,10 @@ export const NodesTable: FC<NodesTableProps> = (props) => {
const {
nodesFilters: filters,
nodes,
- onEditingNode: onEditingNodes,
- onAddingNode: onCreateNodes,
+ onEditingNode,
+ onAddingNode,
onFilterChange,
+ refetchNodes,
} = useNodes();
const total = nodes.length;
@@ -64,18 +66,8 @@ export const NodesTable: FC<NodesTableProps> = (props) => {
const [selectedRow, setSelectedRow] = useState<ExpandedIndex | undefined>(
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 useTable = useBreakpointValue({ base: false, md: true });
// TODO: Find a different mechancism to to detect ftiler
// const isFiltered = users.length !== total;
@@ -106,8 +98,15 @@ export const NodesTable: FC<NodesTableProps> = (props) => {
setSelectedRow(index === selectedRow ? undefined : index);
};
- const StatusSortSelectNodes = () => (<StatusSortSelect filters={filters} options={['healthy', 'unhealthy', 'disabled']} handleStatusFilter={handleStatusFilter} />);
+ const StatusSortSelectNodes = () => (
+ <StatusSortSelect
+ filters={filters}
+ options={['healthy', 'unhealthy', 'disabled']}
+ handleStatusFilter={handleStatusFilter}
+ />
+ );
+ refetchNodes();
return (
<Box id="node-table" overflowX={{ base: 'unset', md: 'unset' }} >
<Accordion
@@ -119,8 +118,7 @@ export const NodesTable: FC<NodesTableProps> = (props) => {
<Thead zIndex="docked" position="relative">
<Tr>
<Th
- position="sticky"
- top={top}
+ position="fixed"
minW="120px"
pl={4}
pr={4}
@@ -133,8 +131,7 @@ export const NodesTable: FC<NodesTableProps> = (props) => {
</HStack>
</Th>
<Th
- position="sticky"
- top={top}
+ position="fixed"
minW="50px"
pl={0}
pr={0}
@@ -162,8 +159,7 @@ export const NodesTable: FC<NodesTableProps> = (props) => {
</HStack>
</Th>
<Th
- position="sticky"
- top={top}
+ position="fixed"
minW="100px"
cursor={'pointer'}
pr={0}
@@ -175,8 +171,7 @@ export const NodesTable: FC<NodesTableProps> = (props) => {
</HStack>
</Th>
<Th
- position="sticky"
- top={top}
+ position="fixed"
minW="100px"
cursor={'pointer'}
pr={0}
@@ -185,8 +180,7 @@ export const NodesTable: FC<NodesTableProps> = (props) => {
</Th>
{/* Action buttons (certificate, core settings)*/}
<Th
- position="sticky"
- top={top}
+ position="fixed"
minW="32px"
w="32px"
p={0}
@@ -285,7 +279,7 @@ export const NodesTable: FC<NodesTableProps> = (props) => {
}}
onClick={(e) => {
e.stopPropagation();
- onEditingNodes(node);
+ onEditingNode(node);
}}
>
<EditIcon />
@@ -312,8 +306,6 @@ export const NodesTable: FC<NodesTableProps> = (props) => {
<Thead zIndex="docked" position="relative">
<Tr>
<Th
- position="sticky"
- top={{ base: 'unset', md: top }}
minW="140px"
cursor={'pointer'}
onClick={handleSort.bind(null, 'name')}
@@ -324,8 +316,6 @@ export const NodesTable: FC<NodesTableProps> = (props) => {
</HStack>
</Th>
<Th
- position="sticky"
- top={{ base: 'unset', md: top }}
width="400px"
minW="150px"
cursor={'pointer'}
@@ -351,8 +341,6 @@ export const NodesTable: FC<NodesTableProps> = (props) => {
</HStack>
</Th>
<Th
- position="sticky"
- top={{ base: 'unset', md: top }}
width="350px"
minW="230px"
cursor={'pointer'}
@@ -364,8 +352,6 @@ export const NodesTable: FC<NodesTableProps> = (props) => {
</HStack>
</Th>
<Th
- position="sticky"
- top={{ base: 'unset', md: top }}
width="350px"
minW="230px"
cursor={'pointer'}
@@ -377,14 +363,12 @@ export const NodesTable: FC<NodesTableProps> = (props) => {
</HStack>
</Th>
<Th
- position="sticky"
- top={{ base: 'unset', md: top }}
width="200px"
minW="180px"
/>
</Tr>
</Thead>
- <Tbody>
+ <Tbody overflowY="auto">
{useTable &&
nodes?.map((node, i) => {
return (
@@ -393,7 +377,7 @@ export const NodesTable: FC<NodesTableProps> = (props) => {
className={classNames('interactive', {
'last-row': i === nodes.length - 1,
})}
- onClick={() => onEditingNodes(node)}
+ onClick={() => onEditingNode(node)}
>
<Td minW="140px">
<div className="flex-status">
@@ -425,7 +409,7 @@ export const NodesTable: FC<NodesTableProps> = (props) => {
noObjectT="nodesTable.noNode"
noObjectMatchedT='nodesTable.noNodeMatched'
createObjectT="addNode"
- onCreateObject={onCreateNodes}
+ onCreateObject={onAddingNode}
/>
</Td>
</Tr>
diff --git a/app/dashboard/src/pages/nodes/usage.tsx b/app/dashboard/src/pages/nodes/usage.tsx
@@ -13,7 +13,7 @@ import { FC, Suspense, useEffect, useState } from 'react';
import ReactApexChart from 'react-apexcharts';
import { useTranslation } from 'react-i18next';
import { Icon } from 'components/icon';
-import { UsageFilter, createUsageConfig } from 'components/UsageFilter';
+import { UsageFilter, createUsageConfig } from 'components/usage-filter';
import { StatisticsCardWide } from 'components/statistics-card';
const UsageIcon = chakra(ChartPieIcon, {
diff --git a/app/dashboard/src/pages/routes.tsx b/app/dashboard/src/pages/routes.tsx
@@ -1,6 +1,7 @@
import { lazy } from 'react';
import { createHashRouter } from 'react-router-dom';
import { fetchAdminLoader } from 'service/http';
+import { pages } from 'stores';
const ConsoleLayout = lazy(() => import('layouts/console-layout'));
const Home = lazy(() => import('./home'));
@@ -18,23 +19,23 @@ export const router = createHashRouter([
element: <ConsoleLayout />,
children: [
{
- path: '/',
+ path: pages.home.path,
element: <Home />,
},
{
- path: '/users',
+ path: pages.users.path,
element: <Users />,
},
{
- path: '/services',
+ path: pages.services.path,
element: <Services />,
},
{
- path: '/nodes',
+ path: pages.nodes.path,
element: <Nodes />,
},
{
- path: '/hosts',
+ path: pages.hosts.path,
element: <Hosts />,
},
],
diff --git a/app/dashboard/src/pages/services/dialog/index.tsx b/app/dashboard/src/pages/services/dialog/index.tsx
@@ -20,9 +20,13 @@ import { zodResolver } from '@hookform/resolvers/zod';
import { FC, useEffect, useState } from 'react';
import { FormProvider, useForm } from 'react-hook-form';
import { useTranslation } from 'react-i18next';
-import { AddIcon, DeleteIcon, EditIcon } from 'components/dialog/Icons';
-import { DialogModalHeader } from 'components/dialog/ModalHeader';
-import { DialogModalFooter } from 'components/dialog/ModalFooter';
+import {
+ AddIcon,
+ DialogModalHeader,
+ DialogModalFooter,
+ DeleteIcon,
+ EditIcon
+} from 'components/dialog';
import { getDefaultValues } from './default-values';
import { schema, FormType } from './form-schema';
import { InboundsField } from './inbounds-field';
diff --git a/app/dashboard/src/pages/services/index.tsx b/app/dashboard/src/pages/services/index.tsx
@@ -1,21 +1,16 @@
-import { FC, useEffect } from 'react';
+import { FC } from 'react';
import { ServicesFilters } from './filters';
import { ServicesTable } from './table';
-import { pages } from 'constants/Pages';
+import { pages } from 'stores';
import { ServiceDialog } from './dialog';
-import { useDashboard, useServices } from 'stores';
import { DeleteServiceModal } from './modals/delete-modal';
import Page from 'components/page';
const ServicesPage: FC = () => {
- useEffect(() => {
- useDashboard.getState().activatePage(pages.findIndex((page) => page.path === '/services'));
- useServices.getState().refetchServices();
- }, []);
return (
- <Page>
+ <Page page={pages.services}>
<ServiceDialog />
<DeleteServiceModal />
<ServicesFilters />
diff --git a/app/dashboard/src/pages/services/table.tsx b/app/dashboard/src/pages/services/table.tsx
@@ -31,6 +31,7 @@ export const ServicesTable: FC<ServicesTableProps> = (props) => {
onEditingService,
onCreateService,
onFilterChange,
+ refetchServices,
} = useServices();
const { t } = useTranslation();
@@ -62,8 +63,9 @@ export const ServicesTable: FC<ServicesTableProps> = (props) => {
});
};
+ refetchServices();
return (
- <Box id="services-table" overflowX={{ base: 'unset', md: 'unset' }} >
+ <Box id="services-table" h="45vh" overflowX={{ base: 'unset', md: 'unset' }} >
<Table orientation="vertical" zIndex="docked" {...props}>
<Thead zIndex="docked" position="relative">
<Tr>
diff --git a/app/dashboard/src/pages/users/dialog/data-limit-field.tsx b/app/dashboard/src/pages/users/dialog/data-limit-field.tsx
@@ -1,5 +1,5 @@
import { FormControl, FormLabel } from '@chakra-ui/react';
-import { Input } from 'components/Input';
+import { Input } from 'components/input';
import { TFunction } from 'i18next';
import { Controller } from 'react-hook-form';
diff --git a/app/dashboard/src/pages/users/dialog/expire-date-field.tsx b/app/dashboard/src/pages/users/dialog/expire-date-field.tsx
@@ -8,7 +8,7 @@ import { i18n, TFunction } from 'i18next';
import ReactDatePicker from 'react-datepicker';
import { Controller } from 'react-hook-form';
import { relativeExpiryDate } from 'utils/dateFormatter';
-import { Input } from 'components/Input';
+import { Input } from 'components/input';
interface ExpireDateFieldProps {
form: any;
diff --git a/app/dashboard/src/pages/users/dialog/index.tsx b/app/dashboard/src/pages/users/dialog/index.tsx
@@ -32,11 +32,14 @@ import {
UserCreate,
Service
} from 'types';
-import { UsageFilter, createUsageConfig } from 'components/UsageFilter';
-import { AddIcon, EditIcon } from 'components/dialog/Icons';
-import { DialogModalHeader } from 'components/dialog/ModalHeader';
-import { DialogModalFooter } from 'components/dialog/ModalFooter';
-import { DeleteIcon } from 'components/dialog/Icons';
+import { UsageFilter, createUsageConfig } from 'components/usage-filter';
+import {
+ AddIcon,
+ EditIcon,
+ DialogModalHeader,
+ DialogModalFooter,
+ DeleteIcon
+} from 'components/dialog';
import { UserUsageIcon } from './icons';
import { getDefaultValues } from './default-values';
import { schema, FormType } from './form-schema';
diff --git a/app/dashboard/src/pages/users/index.tsx b/app/dashboard/src/pages/users/index.tsx
@@ -1,7 +1,5 @@
import { UserDialog } from './dialog';
import { UsersTable } from './table';
-import { useUsers, useDashboard } from 'stores';
-import { useEffect } from 'react';
import { UsersFilters } from './filters';
import {
ResetAllUsageModal,
@@ -10,17 +8,12 @@ import {
ResetUserUsageModal,
RevokeSubscriptionModal
} from './modals';
-import { pages } from 'constants/Pages';
+import { pages } from 'stores';
import Page from 'components/page';
-
const UsersPage = () => {
- useEffect(() => {
- useDashboard.getState().activatePage(pages.findIndex((page) => page.path === '/users'));
- useUsers.getState().refetchUsers();
- }, []);
return (
- <Page>
+ <Page page={pages.users}>
<UsersFilters />
<UsersTable />
<UserDialog />
diff --git a/app/dashboard/src/pages/users/table/index.tsx b/app/dashboard/src/pages/users/table/index.tsx
@@ -27,9 +27,9 @@ import { FC, Fragment, useEffect, useState } from 'react';
import CopyToClipboard from 'react-copy-to-clipboard';
import { useTranslation } from 'react-i18next';
import { User } from 'types';
-import { OnlineBadge } from 'components/OnlineBadge';
-import { OnlineStatus } from 'components/OnlineStatus';
-import { StatusBadge } from 'components/StatusBadge';
+import { OnlineBadge } from 'components/online-badge';
+import { OnlineStatus } from 'components/online-status';
+import { StatusBadge } from 'components/status-badge';
import {
Pagination,
EmptySection,
@@ -54,6 +54,7 @@ export const UsersTable: FC<UsersTableProps> = (props) => {
usersFilters: filters,
users: { users },
users: { total },
+ refetchUsers,
onEditingUser,
onCreateUser,
onFilterChange,
@@ -103,7 +104,7 @@ export const UsersTable: FC<UsersTableProps> = (props) => {
const toggleAccordion = (index: number) => {
setSelectedRow(index === selectedRow ? undefined : index);
};
-
+ refetchUsers();
return (
<Box id="users-table" overflowX={{ base: 'unset', md: 'unset' }}>
<Accordion
diff --git a/app/dashboard/src/stores/dashboard.store.ts b/app/dashboard/src/stores/dashboard.store.ts
@@ -1,23 +1,60 @@
import { create } from 'zustand';
import { subscribeWithSelector } from 'zustand/middleware';
+import { PageType, Pages } from 'types';
+import { chakra } from '@chakra-ui/react';
+import { CubeIcon, HomeIcon, ServerIcon, ServerStackIcon, UsersIcon } from '@heroicons/react/24/outline';
+import { IconBaseStyle } from 'constants/IconBaseStyle';
-type PageId = number;
+const UsersSidebarItemIcon = chakra(UsersIcon, IconBaseStyle);
+const ServicesSidebarItemIcon = chakra(ServerStackIcon, IconBaseStyle);
+const NodesSidebarItemIcon = chakra(CubeIcon, IconBaseStyle);
+const HostSidebarItemIcon = chakra(ServerIcon, IconBaseStyle);
+const HomeSidebarItemIcon = chakra(HomeIcon, IconBaseStyle);
+
+
+export const pages: Pages = {
+ home: {
+ name: 'home',
+ path: '/',
+ itemIcon: HomeSidebarItemIcon,
+ },
+ users: {
+ name: 'users',
+ path: '/users',
+ itemIcon: UsersSidebarItemIcon,
+ },
+ services: {
+ name: 'services',
+ path: '/services',
+ itemIcon: ServicesSidebarItemIcon,
+ },
+ nodes: {
+ name: 'nodes',
+ path: '/nodes',
+ itemIcon: NodesSidebarItemIcon,
+ },
+ hosts: {
+ name: 'hosts',
+ path: '/hosts',
+ itemIcon: HostSidebarItemIcon,
+ },
+};
type DashboardStateType = {
version: string | null;
loading: boolean;
- activePage: number;
+ activePage: PageType;
isCollapsed: boolean;
collapseSidebar: () => void;
expandSidebar: () => void;
- activatePage: (pageId: PageId) => void;
+ activatePage: (pageId: PageType) => void;
};
export const useDashboard = create(
subscribeWithSelector<DashboardStateType>((set,) => ({
version: null,
loading: true,
- activePage: 0,
+ activePage: pages.home,
isCollapsed: false,
expandSidebar: () => {
set({ isCollapsed: false })
@@ -25,8 +62,8 @@ export const useDashboard = create(
collapseSidebar: () => {
set({ isCollapsed: true })
},
- activatePage: (pageId: number) => {
- set({ activePage: pageId });
+ activatePage: (page) => {
+ set({ activePage: page });
},
}))
);
diff --git a/app/dashboard/src/types/page.ts b/app/dashboard/src/types/page.ts
@@ -1,7 +1,15 @@
import { ChakraIcon } from 'types'
-export interface PageType {
+export type PageType = {
path: string;
name: string;
itemIcon: ChakraIcon;
}
+
+export interface Pages {
+ home: PageType;
+ users: PageType;
+ services: PageType;
+ nodes: PageType;
+ hosts: PageType;
+}