diff --git a/app/dashboard/index.html b/app/dashboard/index.html
@@ -1,41 +1,26 @@
<!DOCTYPE html>
-<html lang="en">
- <head>
- <meta charset="UTF-8" />
- <meta name="viewport" content="width=device-width, initial-scale=1.0" />
- <title>Marzban</title>
- <link
- rel="apple-touch-icon"
- sizes="180x180"
- href="/favicon/apple-touch-icon.png"
- />
- <link
- rel="icon"
- type="image/png"
- sizes="32x32"
- href="/favicon/favicon-32x32.png"
- />
- <link
- rel="icon"
- type="image/png"
- sizes="16x16"
- href="/favicon/favicon-16x16.png"
- />
- <link rel="manifest" href="/favicon/site.webmanifest" />
- <link
- rel="mask-icon"
- href="/favicon/safari-pinned-tab.svg"
- color="#5bbad5"
- />
- <link rel="shortcut icon" href="/favicon/favicon.ico" />
- <meta name="apple-mobile-web-app-title" content="Marzban" />
- <meta name="application-name" content="Marzban" />
- <meta name="msapplication-TileColor" content="#2b5797" />
- <meta name="msapplication-config" content="/favicon/browserconfig.xml" />
- <meta name="theme-color" content="#3B81F6" />
- </head>
- <body>
- <div id="root"></div>
- <script type="module" src="/src/index.tsx"></script>
- </body>
+<html lang="en-us">
+
+<head>
+ <meta charset="UTF-8" />
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+ <title>Marzneshin</title>
+ <link rel="apple-touch-icon" sizes="180x180" href="/favicon/apple-touch-icon.png" />
+ <link rel="icon" type="image/png" sizes="32x32" href="/favicon/favicon-32x32.png" />
+ <link rel="icon" type="image/png" sizes="16x16" href="/favicon/favicon-16x16.png" />
+ <link rel="manifest" href="/favicon/site.webmanifest" />
+ <link rel="mask-icon" href="/favicon/safari-pinned-tab.svg" color="#5bbad5" />
+ <link rel="shortcut icon" href="/favicon/favicon.ico" />
+ <meta name="apple-mobile-web-app-title" content="Marzban" />
+ <meta name="application-name" content="Marzban" />
+ <meta name="msapplication-TileColor" content="#2b5797" />
+ <meta name="msapplication-config" content="/favicon/browserconfig.xml" />
+ <meta name="theme-color" content="#3B81F6" />
+</head>
+
+<body>
+ <div id="root"></div>
+ <script type="module" src="/src/index.tsx"></script>
+</body>
+
</html>
diff --git a/app/dashboard/public/locales/en.json b/app/dashboard/public/locales/en.json
@@ -86,6 +86,10 @@
"port": "Port",
"total": "Total"
},
+ "noInboundsCallout": {
+ "header": "Configure Nodes!",
+ "body": "Please configure nodes to access inbounds hosts"
+ },
"userDialog": {
"dataLimit": "Data Limit",
"periodicUsageReset": "Periodic Usage Reset",
@@ -204,12 +208,16 @@
"host.wildcard": "Use <badge>*</badge> to generate a random string (works for wildcard domain names)"
},
"revoke": "Revoke",
- "deleteNode.title": "Delete Node",
- "deleteNode.prompt": "Are you sure you want to delete the <b>{{name}}</b> node?",
- "deleteNode.deleteSuccess": "Node {{name}} removed successfully",
- "deleteService.title": "Delete Service",
- "deleteService.prompt": "Are you sure you want to delete the <b>{{name}}</b> node?",
- "deleteService.deleteSuccess": "Service {{name}} removed successfully",
+ "deleteNode": {
+ "title": "Delete Node",
+ "prompt": "Are you sure you want to delete the <b>{{name}}</b> node?",
+ "deleteSuccess": "Node {{name}} removed successfully"
+ },
+ "deleteService": {
+ "title": "Delete Service",
+ "prompt": "Are you sure you want to delete the <b>{{name}}</b> node?",
+ "deleteSuccess": "Service {{name}} removed successfully"
+ },
"users": "Users",
"active": "active",
"disabled": "disabled",
diff --git a/app/dashboard/src/components/callout/index.tsx b/app/dashboard/src/components/callout/index.tsx
@@ -0,0 +1,20 @@
+
+
+import { Box, ChakraProps, Text } from '@chakra-ui/react'
+import { FC } from 'react'
+
+type CalloutProps = {
+ header: string,
+ body: string,
+};
+
+export const Callout: FC<CalloutProps & ChakraProps> = ({ header, body, ...props }) => {
+ return (
+ <Box bg="gray.50" _dark={{ bg: 'gray.700' }} borderRadius="10" borderColor="red.500" border="2px" p="4" {...props}>
+ <Text fontSize="xl" fontWeight="bold">
+ {header}
+ </Text>
+ {body}
+ </Box>
+ )
+}
diff --git a/app/dashboard/src/pages/hosts/container.tsx b/app/dashboard/src/pages/hosts/container.tsx
@@ -5,6 +5,8 @@ import { queryIds } from 'constants/query-ids';
import { useQuery } from '@tanstack/react-query';
import { fetchInbounds, fetchInboundHosts, useInbounds } from 'stores';
import { Grid, GridItem, } from '@chakra-ui/react';
+import { useTranslation } from 'react-i18next';
+import { Callout } from 'components/callout';
export const InboundsHostsManager = () => {
const { selectedInbound } = useInbounds();
@@ -25,21 +27,26 @@ export const InboundsHostsManager = () => {
}
});
- return (
- <>
- <InboundsHostsFilters />
- <Grid
- templateColumns='repeat(4, 1fr)'
- gap="3"
- h="10rem"
- >
- <GridItem colSpan={1}>
- <InboundsSidebar inbounds={inbounds} />
- </GridItem>
- <GridItem colSpan={3}>
- <HostsTable hosts={hosts} />
- </GridItem>
- </Grid>
- </>
- )
+ const { t } = useTranslation('noInboundsCallout');
+
+ return inbounds.length === 0 ?
+ <Callout header={t('noInboundsCallout.header')} body={t('noInboundsCallout.body')} />
+ : (
+ <>
+ <InboundsHostsFilters />
+ <Grid
+ templateColumns='repeat(4, 1fr)'
+ gap="3"
+ h="10rem"
+ >
+ <GridItem colSpan={1}>
+ <InboundsSidebar inbounds={inbounds} />
+ </GridItem>
+ <GridItem colSpan={3}>
+ <HostsTable hosts={hosts} />
+ </GridItem>
+ </Grid>
+ </>
+
+ )
}
diff --git a/app/dashboard/src/pages/hosts/dialog/default-values.ts b/app/dashboard/src/pages/hosts/dialog/default-values.ts
@@ -3,7 +3,7 @@ export const getDefaultValues = (): HostSchema => {
return {
host: '',
sni: '',
- port: 1,
+ port: null,
path: '',
address: '',
remark: '',
diff --git a/app/dashboard/src/pages/hosts/dialog/index.tsx b/app/dashboard/src/pages/hosts/dialog/index.tsx
@@ -104,7 +104,7 @@ export const HostsDialog: FC = () => {
.then(() => {
toast({
title: t(
- isEditingHost ? 'hostsDialog.editHost' : 'hostsDialog.createNewHost',
+ isEditingHost ? 'hostsDialog.editHost' : 'hostsDialog.createHost',
{ hoshtName: values.host }
),
status: 'success',
diff --git a/app/dashboard/src/pages/hosts/inbounds-sidebar/index.tsx b/app/dashboard/src/pages/hosts/inbounds-sidebar/index.tsx
@@ -4,6 +4,7 @@ import { InboundCard } from 'components/inbounds-card';
import { handleSort } from 'components/table';
import { FC } from 'react';
import { useTranslation } from 'react-i18next';
+import { useEffect } from 'react';
import { useInbounds } from 'stores';
import { InboundType } from 'types';
@@ -22,6 +23,9 @@ export const InboundsSidebar: FC<InboundsSidebarProps> = ({
refetchHosts,
} = useInbounds();
+ useEffect(() => {
+ selectedInbound === undefined && selectInbound(inbounds[0]);
+ }, [selectedInbound, inbounds]);
const { t } = useTranslation();
diff --git a/app/dashboard/src/pages/nodes/dialog.tsx b/app/dashboard/src/pages/nodes/dialog.tsx
@@ -210,7 +210,6 @@ export const NodesDialog: FC = () => {
aria-label="delete node"
onClick={() => {
if (editingNode !== null) {
- console.log('deleting')
onDeletingNode(editingNode);
onClose();
}
diff --git a/app/dashboard/src/stores/inbounds.store.ts b/app/dashboard/src/stores/inbounds.store.ts
@@ -14,7 +14,8 @@ export const hostSchema = z.object({
address: z.string().min(1, 'Address is required'),
port: z.coerce.number()
.gte(1, 'Port must be more than 1')
- .lte(65535, 'Port can not be more than 65535'),
+ .lte(65535, 'Port can not be more than 65535')
+ .or(z.string().nullable()),
path: z.string(),
sni: z.string(),
host: z.string(),
@@ -136,6 +137,7 @@ export const useInbounds = create(
.catch(() => false)
.finally(() => {
useDashboard.setState({ loading: false });
+ get().refetchHosts();
});
}
},
@@ -150,6 +152,7 @@ export const useInbounds = create(
.catch(() => false)
.finally(() => {
useDashboard.setState({ loading: false });
+ get().refetchHosts();
});
}
},
@@ -159,7 +162,10 @@ export const useInbounds = create(
deleteHost: async (hostId: number): Promise<boolean> => {
useDashboard.setState({ loading: true });
return fetch(`/inbounds/hosts/${hostId}`, { method: 'DELETE' })
- .then(() => true)
+ .then(() => {
+ get().refetchHosts();
+ return true;
+ })
.catch(() => false)
.finally(() => {
useDashboard.setState({ loading: false });
diff --git a/makefile b/makefile
@@ -14,16 +14,15 @@ dashboard-dev:
VITE_BASE_API=http://0.0.0.0:8000/api/ npm run dev \
--prefix './app/dashboard/' \
-- --host 0.0.0.0 \
- --base ./app/dashboard/ \
+ --base /dashboard/ \
--clearScreen false
dashboard-preview:
VITE_BASE_API=http://0.0.0.0:8000/api/ npm run preview \
--prefix './app/dashboard/' \
-- --host 0.0.0.0 \
- --base ./app/dashboard/ \
+ --base /dashboard/ \
--clearScreen false
-
dashboard-cleanup:
rm -rf app/dashboard/node_modules/