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 ( + + + marzneshin + {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 ( - + -
- - - - - - - - - - - - -