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 @@
+
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 {{username}}?",
"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": "از حذف کاربر {{username}} مطمئن هستید?",
"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 (
-
-
+
+
+ }
+ />
+
+
+
+
+
+
+
+
+
+
+
+ {pages.map((route: PageRoute) =>
+
+ )}
+
+
+
+
+
+
);
}
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 = (props) => {
const { version } = useDashboard();
return (
-
+
= (props) => {
fontSize="xs"
>
- Marzban
+ Marzneshin
{version ? ` (v${version}), ` : ', '}
Made with ❤️ in{' '}
- Gozargah
+ khodedawosh
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 = () => {
onResetAllUsage,
onEditingNodes,
onShowingNodesUsage,
+ activePage,
} = useDashboard();
const { t } = useTranslation();
const { colorMode, toggleColorMode } = useColorMode();
@@ -110,8 +112,8 @@ export const Header: FC = () => {
}}
position="relative"
>
-
- {t('users')}
+
+ {t(pages[activePage]?.name)}
{showDonationNotif && (
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 = ({ 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 (
+ { activatePage(index) }}>
+
+
+
+
+ {t(name)}
+
+
+ )
+}
+
+export const Sidebar = () => {
+
+ return (
+
+
+
+ {pages.map((item: SideBarItemProps, index: number) =>
+ )}
+
+
+ )
+}
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: ,
- errorElement: ,
- loader: fetchAdminLoader,
- },
- {
- path: '/login/',
- element: ,
- },
- ],
- { 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: ,
+ errorElement:
+ },
+ {
+ name: 'pageHeaderTitle.Users',
+ path: '/users',
+ itemIcon: UsersSidebarItemIcon,
+ loader: fetchAdminLoader,
+ element: ,
+ errorElement:
+ },
+ {
+ name: 'pageHeaderTitle.Services',
+ path: '/services',
+ itemIcon: ServicesSidebarItemIcon,
+ element: ,
+ loader: fetchAdminLoader, errorElement:
+ },
+ {
+ name: 'pageHeaderTitle.Nodes',
+ path: '/nodes',
+ itemIcon: NodesSidebarItemIcon,
+ element: ,
+ loader: fetchAdminLoader,
+ errorElement:
+ },
+ {
+ name: 'pageHeaderTitle.Host',
+ path: '/host',
+ itemIcon: HostSidebarItemIcon,
+ element: ,
+ loader: fetchAdminLoader,
+ errorElement:
+ },
+];
+
+export type PageRoute = Pick
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;
+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(
-
-
-
-
-
+
+
+
+
+
+
+
);
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 (
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
);
};
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 (
+
+
+
+
+
+ )
+}
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 (
+
+
+
+
+
+
+
+ )
+}
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 Serviced;
+}
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 (
+
+
+
+
+
+
+
+
+
+
+
+
+ )
+}
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;
+ itemIcon: ChakraIcon;
+}