gitscope
← marzneshin / Diff · 112aaed → 112aaed
Changes 112aaed..112aaed
Patch commit
diff --git a/app/dashboard/package.json b/app/dashboard/package.json
index 1069fb6..4d989ce 100644
--- a/app/dashboard/package.json
+++ b/app/dashboard/package.json
@@ -8,7 +8,8 @@
"preview": "vite preview --port 3000",
"gen:theme-typings": "chakra-cli tokens ./chakra.config.ts",
"postinstall": "npm run gen:theme-typings",
- "lint": "eslint --ignore-path .eslintignore . --ext ts --ext tsx --ext js --ext jsx"
+ "lint": "eslint --ignore-path .eslintignore . --ext ts --ext tsx --ext js --ext jsx",
+ "lint:fix": "eslint --ignore-path .eslintignore . --ext ts --ext tsx --ext js --ext jsx --fix"
},
"dependencies": {
"@chakra-ui/icons": "^2.1.1",
diff --git a/app/dashboard/public/favicon/logo.svg b/app/dashboard/public/favicon/logo.svg
new file mode 100644
index 0000000..6bf7ccf
--- /dev/null
+++ b/app/dashboard/public/favicon/logo.svg
@@ -0,0 +1,3 @@
+<svg viewBox="0 0 747 747" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M746.671 209.652V77.6572C746.671 72.9384 743.827 68.6832 739.468 66.8811C735.108 65.0738 730.093 66.0739 726.754 69.4072L579.354 216.807C577.453 202.386 570.203 189.203 559.037 179.87C547.875 170.542 533.615 165.744 519.083 166.432C504.551 167.12 490.807 173.24 480.573 183.583L414.375 249.776C412.417 235.391 405.141 222.25 393.985 212.959C382.823 203.662 368.594 198.88 354.083 199.552C339.578 200.224 325.849 206.302 315.594 216.588L274.343 257.828L19.9167 3.41515C16.5781 0.0818157 11.5625 -0.918184 7.20306 0.889149C2.84373 2.69648 0 6.94648 0 11.6651V143.649C0 146.743 1.22917 149.712 3.41667 151.899L191.843 340.339L3.41667 528.753C1.2292 530.94 0 533.909 0 537.003V668.997C0 673.716 2.84373 677.971 7.20306 679.773C11.5624 681.581 16.578 680.58 19.9167 677.247L167.317 529.847C169.285 544.233 176.561 557.358 187.718 566.65C198.869 575.947 213.098 580.728 227.603 580.066C242.103 579.404 255.838 573.342 266.098 563.071L332.295 496.874V496.879C334.259 511.264 341.535 524.395 352.691 533.686C363.853 542.977 378.082 547.759 392.587 547.092C407.093 546.421 420.822 540.348 431.077 530.066L472.327 488.826L726.754 743.239C728.941 745.427 731.91 746.656 735.004 746.656C736.535 746.661 738.056 746.359 739.473 745.77C743.832 743.963 746.671 739.708 746.671 734.989V602.994C746.671 599.901 745.442 596.932 743.254 594.744L554.827 406.318L743.254 217.904H743.259C745.447 215.717 746.676 212.748 746.676 209.654L746.671 209.652ZM23.3373 39.8118L257.844 274.318L208.328 323.818L23.3413 138.818L23.3373 39.8118ZM723.337 706.825L488.831 472.318L538.347 422.818L723.333 607.818L723.337 706.825ZM23.3373 541.825L332.097 233.078C338.764 226.719 347.624 223.172 356.843 223.172C366.056 223.172 374.916 226.719 381.588 233.078C388.151 239.641 391.838 248.548 391.838 257.828C391.838 267.114 388.151 276.016 381.588 282.577L23.3347 640.831L23.3373 541.825ZM249.604 546.575C240.755 555.419 227.864 558.872 215.781 555.638C203.703 552.398 194.265 542.961 191.027 530.883C187.792 518.8 191.245 505.91 200.089 497.06L497.076 200.074C505.92 191.23 518.811 187.772 530.893 191.011C542.971 194.246 552.409 203.678 555.648 215.76C558.888 227.839 555.434 240.73 546.591 249.572L249.604 546.575ZM723.337 204.815L414.577 513.562C407.911 519.926 399.051 523.473 389.832 523.473C380.618 523.473 371.753 519.926 365.087 513.562C358.529 506.999 354.842 498.098 354.842 488.816C354.842 479.535 358.529 470.634 365.087 464.071L723.34 105.818L723.337 204.815Z" fill="currentColor"/>
+</svg>
diff --git a/app/dashboard/public/locales/en.json b/app/dashboard/public/locales/en.json
index 4d8e08a..a92adb7 100644
--- a/app/dashboard/public/locales/en.json
+++ b/app/dashboard/public/locales/en.json
@@ -19,6 +19,11 @@
"header.nodesUsage": "Nodes Usage",
"header.donation": "Donation",
"header.logout": "Log out",
+ "pageHeaderTitle.Home": "Home",
+ "pageHeaderTitle.Users": "Users",
+ "pageHeaderTitle.Services": "Services",
+ "pageHeaderTitle.Host": "Host",
+ "pageHeaderTitle.Nodes": "Nodes",
"deleteUser.title": "Delete User",
"deleteUser.prompt": "Are you sure you want to delete <b>{{username}}</b>?",
"deleteUser.deleteSuccess": "{{username}} deleted successfully.",
diff --git a/app/dashboard/public/locales/fa.json b/app/dashboard/public/locales/fa.json
index 795257e..1e8c785 100644
--- a/app/dashboard/public/locales/fa.json
+++ b/app/dashboard/public/locales/fa.json
@@ -24,6 +24,11 @@
"header.nodesUsage": "نمودار گره‌ها",
"header.donation": "کمک مالی",
"header.logout": "خروج",
+ "pageHeaderTitle.Home": "صفحه اصلی",
+ "pageHeaderTitle.Users": "کاربر ها",
+ "pageHeaderTitle.Services": "سرویس ها",
+ "pageHeaderTitle.Host": "هاست",
+ "pageHeaderTitle.Nodes": "نود ها",
"deleteUser.title": "حذف کاربر",
"deleteUser.prompt": "از حذف کاربر <b>{{username}}</b> مطمئن هستید?",
"deleteUser.deleteSuccess": "{{username}} با موفقیت حذف شد.",
diff --git a/app/dashboard/src/App.tsx b/app/dashboard/src/App.tsx
index 6b34b9e..e610aa2 100644
--- a/app/dashboard/src/App.tsx
+++ b/app/dashboard/src/App.tsx
@@ -1,12 +1,48 @@
+import { Grid, GridItem } from '@chakra-ui/react';
+import { Footer } from 'components/Footer';
+import { Header } from 'components/Header';
+import { pages, PageRoute } from 'constants/Pages';
+import { Sidebar } from 'components/Sidebar';
+import Login from 'pages/Login';
import 'react-datepicker/dist/react-datepicker.css';
import 'react-loading-skeleton/dist/skeleton.css';
-import { RouterProvider } from 'react-router-dom';
-import { router } from './components/modules/Router';
+import { Route, Routes } from 'react-router-dom';
function App() {
return (
- <main className="p-8">
- <RouterProvider router={router} />
+ <main >
+ <Routes>
+ <Route
+ path="/login"
+ element={<Login />}
+ />
+ </Routes>
+
+ <Grid
+ h="100%"
+ templateAreas={`"nav header"
+ "nav main"
+ "nav footer"`}
+ gridTemplateRows={'20px 1fr 20px'}
+ gridTemplateColumns={'15rem 1fr'}
+ >
+ <GridItem area={'header'} my="12px" mx="30px">
+ <Header />
+ </GridItem>
+ <GridItem area={'nav'}>
+ <Sidebar />
+ </GridItem>
+ <GridItem area={'main'}>
+ <Routes>
+ {pages.map((route: PageRoute) =>
+ <Route {...route} />
+ )}
+ </Routes>
+ </GridItem>
+ <GridItem area={'footer'}>
+ <Footer />
+ </GridItem>
+ </Grid>
</main>
);
}
diff --git a/app/dashboard/src/components/Footer.tsx b/app/dashboard/src/components/Footer.tsx
index e31dc7d..e56037c 100644
--- a/app/dashboard/src/components/Footer.tsx
+++ b/app/dashboard/src/components/Footer.tsx
@@ -6,7 +6,7 @@ import { FC } from 'react';
export const Footer: FC<BoxProps> = (props) => {
const { version } = useDashboard();
return (
- <HStack w="full" py="0" position="relative" {...props}>
+ <HStack w="full" position="relative" {...props}>
<Text
display="inline-block"
flexGrow={1}
@@ -15,12 +15,12 @@ export const Footer: FC<BoxProps> = (props) => {
fontSize="xs"
>
<Link color="blue.400" href={REPO_URL}>
- Marzban
+ Marzneshin
</Link>
{version ? ` (v${version}), ` : ', '}
Made with ❤️ in{' '}
<Link color="blue.400" href={ORGANIZATION_URL}>
- Gozargah
+ khodedawosh
</Link>
</Text>
</HStack>
diff --git a/app/dashboard/src/components/Header.tsx b/app/dashboard/src/components/Header.tsx
index 3a11756..4fd7c42 100644
--- a/app/dashboard/src/components/Header.tsx
+++ b/app/dashboard/src/components/Header.tsx
@@ -11,7 +11,7 @@ import {
useColorMode,
} from '@chakra-ui/react';
import {
- ArrowLeftOnRectangleIcon,
+ ArrowLeftStartOnRectangleIcon,
Bars3Icon,
ChartPieIcon,
Cog6ToothIcon,
@@ -22,6 +22,7 @@ import {
SquaresPlusIcon,
SunIcon,
} from '@heroicons/react/24/outline';
+import { pages } from 'constants/Pages';
import { DONATION_URL, REPO_URL } from 'constants/Project';
import { useDashboard } from 'contexts/DashboardContext';
import differenceInDays from 'date-fns/differenceInDays';
@@ -47,7 +48,7 @@ const DarkIcon = chakra(MoonIcon, iconProps);
const LightIcon = chakra(SunIcon, iconProps);
const CoreSettingsIcon = chakra(Cog6ToothIcon, iconProps);
const SettingsIcon = chakra(Bars3Icon, iconProps);
-const LogoutIcon = chakra(ArrowLeftOnRectangleIcon, iconProps);
+const LogoutIcon = chakra(ArrowLeftStartOnRectangleIcon, iconProps);
const DonationIcon = chakra(CurrencyDollarIcon, iconProps);
const HostsIcon = chakra(LinkIcon, iconProps);
const NodesIcon = chakra(SquaresPlusIcon, iconProps);
@@ -86,6 +87,7 @@ export const Header: FC<HeaderProps> = () => {
onResetAllUsage,
onEditingNodes,
onShowingNodesUsage,
+ activePage,
} = useDashboard();
const { t } = useTranslation();
const { colorMode, toggleColorMode } = useColorMode();
@@ -110,8 +112,8 @@ export const Header: FC<HeaderProps> = () => {
}}
position="relative"
>
- <Text as="h1" fontWeight="semibold" fontSize="2xl">
- {t('users')}
+ <Text as="h1" fontWeight="bold" fontSize="2xl" m="0">
+ {t(pages[activePage]?.name)}
</Text>
{showDonationNotif && (
<NotificationCircle top="0" right="0" zIndex={9999} />
diff --git a/app/dashboard/src/components/Sidebar/index.tsx b/app/dashboard/src/components/Sidebar/index.tsx
new file mode 100644
index 0000000..863bc35
--- /dev/null
+++ b/app/dashboard/src/components/Sidebar/index.tsx
@@ -0,0 +1,75 @@
+import { HamburgerIcon } from '@chakra-ui/icons';
+import { chakra, Image, Flex, VStack, Text } from '@chakra-ui/react'
+import { useDashboard } from 'contexts/DashboardContext';
+import { Link as ReactRouterLink } from 'react-router-dom'
+import { Link as ChakraLink } from '@chakra-ui/react'
+import { pages } from 'constants/Pages';
+import { PageType } from 'types/Page';
+import { Icon } from 'components/Icon';
+import { FC, useEffect, useState } from 'react';
+import { useTranslation } from 'react-i18next';
+
+export type SideBarItemProps = Pick<
+ PageType,
+ | 'name'
+ | 'path'
+ | 'itemIcon'
+>
+
+export const SidebarMenuIcon = chakra(HamburgerIcon, {
+ baseStyle: {
+ w: 5,
+ h: 5,
+ },
+});
+
+export const SidebarItem: FC<SideBarItemProps & { index: number }> = ({ itemIcon: ItemIcon, name, path, index, }) => {
+ const hoverStyle = {
+ borderRadius: 4,
+ border: '4px',
+ m: '-2px',
+ borderColor: 'primary.100',
+ };
+ const activeStyle = {
+ bg: 'primary.500',
+ ...hoverStyle,
+ };
+ const { t } = useTranslation();
+ const { activePage, activatePage } = useDashboard();
+ const [active, activate] = useState(index === activePage)
+
+ useEffect(() => { activate(index === activePage) }, [active, activePage]);
+
+ return (
+ <ChakraLink as={ReactRouterLink} sx={active ? activeStyle : {}} to={path} w="13rem" _hover={hoverStyle} onClick={() => { activatePage(index) }}>
+ <Flex flexDir="row" alignItems="center">
+ <Icon color="primary.500">
+ <ItemIcon color={active ? 'white' : 'primary.500'} />
+ </Icon>
+ <Text color={active ? 'white' : 'black'} fontSize="xl">{t(name)}</Text>
+ </Flex>
+ </ChakraLink>
+ )
+}
+
+export const Sidebar = () => {
+
+ return (
+ <Flex
+ pos="sticky"
+ left="5"
+ borderRightWidth="4px"
+ borderRightColor="primary.100"
+ borderRightStyle="solid"
+ flexDir="column"
+ h="100vh"
+ bg="gray.50"
+ >
+ <VStack pt="30px" gap="6">
+ <Image src="favicon/logo.svg" alt="marzneshin" boxSize="50px" />
+ {pages.map((item: SideBarItemProps, index: number) =>
+ <SidebarItem path={item.path} name={item.name} itemIcon={item.itemIcon} index={index} />)}
+ </VStack>
+ </Flex>
+ )
+}
diff --git a/app/dashboard/src/components/UserDialog/ServicesSelectList.tsx b/app/dashboard/src/components/UserDialog/ServicesSelectList.tsx
index 22e4d08..4d0be9e 100644
--- a/app/dashboard/src/components/UserDialog/ServicesSelectList.tsx
+++ b/app/dashboard/src/components/UserDialog/ServicesSelectList.tsx
@@ -8,6 +8,7 @@ import {
Text,
} from '@chakra-ui/react';
import { ServerStackIcon, UsersIcon } from '@heroicons/react/24/outline';
+import { IconBaseStyle } from 'constants/IconBaseStyle';
import { forwardRef, Ref } from 'react';
import {
useFormContext,
@@ -15,19 +16,10 @@ import {
import { Service } from 'types/Service';
import { ServicePropertyStatus } from './ServicePropertyStatus';
-const ServiceIconBaseStyle = {
- baseStyle: {
- strokeWidth: '2px',
- color: 'primary.500',
- w: 5,
- h: 5,
- mx: '6px',
- },
-}
-const UsersServiceIcon = chakra(UsersIcon, ServiceIconBaseStyle);
+const UsersServiceIcon = chakra(UsersIcon, IconBaseStyle);
-const InboundsServiceIcon = chakra(ServerStackIcon, ServiceIconBaseStyle);
+const InboundsServiceIcon = chakra(ServerStackIcon, IconBaseStyle);
type CustomCheckboxProps = {
isChecked: boolean;
diff --git a/app/dashboard/src/components/modules/Router.tsx b/app/dashboard/src/components/modules/Router.tsx
index c38d2d1..e80b50f 100644
--- a/app/dashboard/src/components/modules/Router.tsx
+++ b/app/dashboard/src/components/modules/Router.tsx
@@ -1,29 +1,10 @@
-import { createBrowserRouter } from 'react-router-dom';
-import { Dashboard } from '../../pages/Dashboard';
-import { Login } from '../../pages/Login';
import { fetch } from '../../service/http';
import { getAuthToken } from '../../utils/authStorage';
-const fetchAdminLoader = () => {
+export const fetchAdminLoader = () => {
return fetch('/admin', {
headers: {
Authorization: `Bearer ${getAuthToken()}`,
},
});
};
-
-export const router = createBrowserRouter(
- [
- {
- path: '/',
- element: <Dashboard />,
- errorElement: <Login />,
- loader: fetchAdminLoader,
- },
- {
- path: '/login/',
- element: <Login />,
- },
- ],
- { basename: import.meta.env.BASE_URL }
-);
diff --git a/app/dashboard/src/constants/IconBaseStyle.ts b/app/dashboard/src/constants/IconBaseStyle.ts
new file mode 100644
index 0000000..32ba980
--- /dev/null
+++ b/app/dashboard/src/constants/IconBaseStyle.ts
@@ -0,0 +1,10 @@
+
+export const IconBaseStyle = {
+ baseStyle: {
+ strokeWidth: '2px',
+ color: 'primary.500',
+ w: 5,
+ h: 5,
+ mx: '6px',
+ },
+}
diff --git a/app/dashboard/src/constants/Pages.tsx b/app/dashboard/src/constants/Pages.tsx
new file mode 100644
index 0000000..a799b05
--- /dev/null
+++ b/app/dashboard/src/constants/Pages.tsx
@@ -0,0 +1,65 @@
+import { PageType } from 'types/Page';
+import { chakra } from '@chakra-ui/react';
+import { CubeIcon, HomeIcon, ServerIcon, ServerStackIcon, UsersIcon } from '@heroicons/react/24/outline';
+import { IconBaseStyle } from 'constants/IconBaseStyle';
+import { fetchAdminLoader } from 'components/modules/Router';
+import { UsersPage } from 'pages/Users';
+import { NodesPage } from 'pages/Nodes';
+import { Login } from 'pages/Login';
+import { ServicesPage } from 'pages/Services';
+import { HostPage } from 'pages/Host';
+import { Dashboard } from 'pages/Dashboard';
+
+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: 'pageHeaderTitle.Home',
+ path: '/',
+ itemIcon: HomeSidebarItemIcon,
+ loader: fetchAdminLoader,
+ element: <Dashboard />,
+ errorElement: <Login />
+ },
+ {
+ name: 'pageHeaderTitle.Users',
+ path: '/users',
+ itemIcon: UsersSidebarItemIcon,
+ loader: fetchAdminLoader,
+ element: <UsersPage />,
+ errorElement: <Login />
+ },
+ {
+ name: 'pageHeaderTitle.Services',
+ path: '/services',
+ itemIcon: ServicesSidebarItemIcon,
+ element: <ServicesPage />,
+ loader: fetchAdminLoader, errorElement: <Login />
+ },
+ {
+ name: 'pageHeaderTitle.Nodes',
+ path: '/nodes',
+ itemIcon: NodesSidebarItemIcon,
+ element: <NodesPage />,
+ loader: fetchAdminLoader,
+ errorElement: <Login />
+ },
+ {
+ name: 'pageHeaderTitle.Host',
+ path: '/host',
+ itemIcon: HostSidebarItemIcon,
+ element: <HostPage />,
+ loader: fetchAdminLoader,
+ errorElement: <Login />
+ },
+];
+
+export type PageRoute = Pick<PageType,
+ | 'path'
+ | 'loader'
+ | 'element'
+ | 'errorElement'>
diff --git a/app/dashboard/src/contexts/DashboardContext.tsx b/app/dashboard/src/contexts/DashboardContext.tsx
index f5d5416..ef99ccc 100644
--- a/app/dashboard/src/contexts/DashboardContext.tsx
+++ b/app/dashboard/src/contexts/DashboardContext.tsx
@@ -30,6 +30,7 @@ export type InboundType = {
};
export type Inbounds = Map<ProtocolType, InboundType[]>;
+type PageId = number;
type DashboardStateType = {
isCreatingNewUser: boolean;
@@ -53,6 +54,8 @@ type DashboardStateType = {
resetUsageUser: User | null;
revokeSubscriptionUser: User | null;
isEditingCore: boolean;
+ activePage: number;
+ activatePage: (pageId: PageId) => void;
onCreateUser: (isOpen: boolean) => void;
onEditingUser: (user: User | null) => void;
onDeletingUser: (user: User | null) => void;
@@ -143,6 +146,10 @@ export const useDashboard = create(
},
inbounds: new Map(),
isEditingCore: false,
+ activePage: 0,
+ activatePage: (pageId: number) => {
+ set({ activePage: pageId });
+ },
refetchServices: () => {
fetchServices();
},
diff --git a/app/dashboard/src/index.tsx b/app/dashboard/src/index.tsx
index 38e5cb1..28bcf5d 100644
--- a/app/dashboard/src/index.tsx
+++ b/app/dashboard/src/index.tsx
@@ -14,6 +14,7 @@ import { updateThemeColor } from 'utils/themeColor';
import { theme } from '../chakra.config';
import App from './App';
import 'index.scss';
+import { BrowserRouter } from 'react-router-dom';
dayjs.extend(Timezone);
dayjs.extend(LocalizedFormat);
@@ -25,10 +26,12 @@ updateThemeColor(localStorageManager.get() || 'light');
ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
<React.StrictMode>
- <ChakraProvider theme={theme}>
- <QueryClientProvider client={queryClient}>
- <App />
- </QueryClientProvider>
- </ChakraProvider>
+ <BrowserRouter basename={import.meta.env.BASE_URL}>
+ <ChakraProvider theme={theme}>
+ <QueryClientProvider client={queryClient}>
+ <App />
+ </QueryClientProvider>
+ </ChakraProvider>
+ </BrowserRouter>
</React.StrictMode>
);
diff --git a/app/dashboard/src/pages/Dashboard.tsx b/app/dashboard/src/pages/Dashboard.tsx
index bf9d754..cdd471e 100644
--- a/app/dashboard/src/pages/Dashboard.tsx
+++ b/app/dashboard/src/pages/Dashboard.tsx
@@ -1,46 +1,17 @@
import { Box, VStack } from '@chakra-ui/react';
-import { CoreSettingsModal } from 'components/CoreSettingsModal';
-import { DeleteUserModal } from 'components/DeleteUserModal';
-import { Filters } from 'components/Filters';
-import { Footer } from 'components/Footer';
-import { Header } from 'components/Header';
-import { HostsDialog } from 'components/HostsDialog';
-import { NodesDialog } from 'components/NodesModal';
-import { NodesUsage } from 'components/NodesUsage';
-import { QRCodeDialog } from 'components/QRCodeDialog';
-import { ResetAllUsageModal } from 'components/ResetAllUsageModal';
-import { ResetUserUsageModal } from 'components/ResetUserUsageModal';
-import { RevokeSubscriptionModal } from 'components/RevokeSubscriptionModal';
-import { UserDialog } from 'components/UserDialog';
-import { UsersTable } from 'components/UsersTable';
-import { fetchInbounds, useDashboard } from 'contexts/DashboardContext';
+import { useDashboard } from 'contexts/DashboardContext';
import { FC, useEffect } from 'react';
import { Statistics } from '../components/Statistics';
export const Dashboard: FC = () => {
useEffect(() => {
useDashboard.getState().refetchUsers();
- fetchInbounds();
}, []);
return (
- <VStack justifyContent="space-between" minH="100vh" p="6" rowGap={4}>
+ <VStack justifyContent="space-between" p="6" rowGap={4}>
<Box w="full">
- <Header />
<Statistics mt="4" />
- <Filters />
- <UsersTable />
- <UserDialog />
- <DeleteUserModal />
- <QRCodeDialog />
- <HostsDialog />
- <ResetUserUsageModal />
- <RevokeSubscriptionModal />
- <NodesDialog />
- <NodesUsage />
- <ResetAllUsageModal />
- <CoreSettingsModal />
</Box>
- <Footer />
</VStack>
);
};
diff --git a/app/dashboard/src/pages/Host.tsx b/app/dashboard/src/pages/Host.tsx
new file mode 100644
index 0000000..af5d828
--- /dev/null
+++ b/app/dashboard/src/pages/Host.tsx
@@ -0,0 +1,12 @@
+import { Box, VStack } from '@chakra-ui/react';
+import { HostsDialog } from 'components/HostsDialog';
+
+export const HostPage = () => {
+ return (
+ <VStack justifyContent="space-between" p="6" rowGap={4} >
+ <Box w="full" >
+ <HostsDialog />
+ </Box>
+ </VStack >
+ )
+}
diff --git a/app/dashboard/src/pages/Nodes.tsx b/app/dashboard/src/pages/Nodes.tsx
new file mode 100644
index 0000000..9afae43
--- /dev/null
+++ b/app/dashboard/src/pages/Nodes.tsx
@@ -0,0 +1,18 @@
+
+import { FC } from 'react';
+import { Box, VStack } from '@chakra-ui/react';
+import { CoreSettingsModal } from 'components/CoreSettingsModal';
+import { NodesDialog } from 'components/NodesModal';
+import { NodesUsage } from 'components/NodesUsage';
+
+export const NodesPage: FC = () => {
+ return (
+ <VStack justifyContent="space-between" p="6" rowGap={4}>
+ <Box>
+ <NodesDialog />
+ <NodesUsage />
+ <CoreSettingsModal />
+ </Box>
+ </VStack>
+ )
+}
diff --git a/app/dashboard/src/pages/Services.tsx b/app/dashboard/src/pages/Services.tsx
new file mode 100644
index 0000000..1422a06
--- /dev/null
+++ b/app/dashboard/src/pages/Services.tsx
@@ -0,0 +1,6 @@
+import { FC } from 'react';
+import { Text } from '@chakra-ui/react';
+
+export const ServicesPage: FC = () => {
+ return <Text>Serviced</Text>;
+}
diff --git a/app/dashboard/src/pages/Users.tsx b/app/dashboard/src/pages/Users.tsx
new file mode 100644
index 0000000..00cfab1
--- /dev/null
+++ b/app/dashboard/src/pages/Users.tsx
@@ -0,0 +1,33 @@
+
+import { ResetAllUsageModal } from 'components/ResetAllUsageModal';
+import { Box, VStack } from '@chakra-ui/react';
+import { DeleteUserModal } from 'components/DeleteUserModal';
+import { UserDialog } from 'components/UserDialog';
+import { UsersTable } from 'components/UsersTable';
+import { useDashboard } from 'contexts/DashboardContext';
+import { useEffect } from 'react';
+import { Filters } from 'components/Filters';
+import { QRCodeDialog } from 'components/QRCodeDialog';
+import { ResetUserUsageModal } from 'components/ResetUserUsageModal';
+import { RevokeSubscriptionModal } from 'components/RevokeSubscriptionModal';
+
+
+export const UsersPage = () => {
+ useEffect(() => {
+ useDashboard.getState().refetchUsers();
+ }, []);
+ return (
+ <VStack justifyContent="space-between" maxH="100vh" p="2rem" rowGap={4}>
+ <Box w="full" >
+ <Filters />
+ <UsersTable />
+ <UserDialog />
+ <QRCodeDialog />
+ <DeleteUserModal />
+ <RevokeSubscriptionModal />
+ <ResetAllUsageModal />
+ <ResetUserUsageModal />
+ </Box>
+ </VStack>
+ )
+}
diff --git a/app/dashboard/src/types/Page.ts b/app/dashboard/src/types/Page.ts
new file mode 100644
index 0000000..3549c39
--- /dev/null
+++ b/app/dashboard/src/types/Page.ts
@@ -0,0 +1,11 @@
+import { ReactElement } from 'react';
+import { ChakraIcon } from 'types/ChakraIcon'
+
+export interface PageType {
+ path: string;
+ name: string;
+ errorElement: ReactElement;
+ element: ReactElement;
+ loader: () => Promise<any>;
+ itemIcon: ChakraIcon;
+}