Browse Source

new connect page (#446)

* connect page part1

Signed-off-by: ruiyi.jiang <ruiyi.jiang@zilliz.com>

* support show attu version on connect page

Signed-off-by: ruiyi.jiang <ruiyi.jiang@zilliz.com>

* adjust star button pos

Signed-off-by: ruiyi.jiang <ruiyi.jiang@zilliz.com>

* add token in connect page, UI only

Signed-off-by: ruiyi.jiang <ruiyi.jiang@zilliz.com>

* support token part1

Signed-off-by: ruiyi.jiang <ruiyi.jiang@zilliz.com>

* update connect feature

Signed-off-by: ryjiang <jiangruiyi@gmail.com>

* new connect page part2

Signed-off-by: ryjiang <jiangruiyi@gmail.com>

---------

Signed-off-by: ruiyi.jiang <ruiyi.jiang@zilliz.com>
Signed-off-by: ryjiang <jiangruiyi@gmail.com>
ryjiang 1 year ago
parent
commit
c7951c76f7

+ 0 - 1
client/src/components/customButton/CustomButton.tsx

@@ -7,7 +7,6 @@ const buttonStyle = makeStyles(theme => ({
     fontWeight: 'bold',
     fontWeight: 'bold',
   },
   },
   textBtn: {
   textBtn: {
-    color: theme.palette.primary.main,
     padding: theme.spacing(1),
     padding: theme.spacing(1),
 
 
     '&:hover': {
     '&:hover': {

+ 4 - 7
client/src/components/customRadio/CustomRadio.tsx

@@ -3,10 +3,11 @@ import { FormGroup, FormControlLabel, Switch } from '@material-ui/core';
 
 
 export const CustomRadio = (props: {
 export const CustomRadio = (props: {
   label: string;
   label: string;
-  defaultChecked?: boolean;
+  checked?: boolean;
   handleChange: (checked: boolean) => void;
   handleChange: (checked: boolean) => void;
 }) => {
 }) => {
-  const { label, defaultChecked = false, handleChange } = props;
+  const { label, checked, handleChange } = props;
+
   const onChange = (
   const onChange = (
     e: React.ChangeEvent<HTMLInputElement>,
     e: React.ChangeEvent<HTMLInputElement>,
     checked: boolean
     checked: boolean
@@ -17,11 +18,7 @@ export const CustomRadio = (props: {
     <FormGroup>
     <FormGroup>
       <FormControlLabel
       <FormControlLabel
         control={
         control={
-          <Switch
-            defaultChecked={defaultChecked}
-            onChange={onChange}
-            color="primary"
-          />
+          <Switch checked={checked} onChange={onChange} color="primary" />
         }
         }
         label={label}
         label={label}
       />
       />

File diff suppressed because it is too large
+ 12 - 0
client/src/components/icons/Icons.tsx


+ 3 - 1
client/src/components/icons/Types.ts

@@ -48,4 +48,6 @@ export type IconsType =
   | 'expand'
   | 'expand'
   | 'github'
   | 'github'
   | 'question'
   | 'question'
-  | 'check';
+  | 'check'
+  | 'discord'
+  | 'star';

+ 2 - 1
client/src/components/layout/Header.tsx

@@ -72,7 +72,8 @@ const Header: FC = () => {
   const classes = useStyles();
   const classes = useStyles();
   const { navInfo } = useContext(navContext);
   const { navInfo } = useContext(navContext);
   const { database, databases, setDatabase, loading } = useContext(dataContext);
   const { database, databases, setDatabase, loading } = useContext(dataContext);
-  const { address, username, logout } = useContext(authContext);
+  const { authReq, logout } = useContext(authContext);
+  const { address, username } = authReq;
   const navigate = useNavigate();
   const navigate = useNavigate();
 
 
   const { t: commonTrans } = useTranslation();
   const { t: commonTrans } = useTranslation();

+ 1 - 4
client/src/consts/Localstorage.ts

@@ -1,8 +1,5 @@
-export const SESSION = 'CLOUD_SESSION';
 export const MILVUS_CLIENT_ID = 'milvus-client-id';
 export const MILVUS_CLIENT_ID = 'milvus-client-id';
-export const LOGIN_USERNAME = 'login-username';
-export const LAST_TIME_ADDRESS = 'last-time-address';
-export const LAST_TIME_DATABASE = 'last-time-database';
+export const ATTU_AUTH_REQ = 'attu-auth-req';
 
 
 export const LAST_TIME_WITH_PROMETHEUS = 'last-time-with-prometheus';
 export const LAST_TIME_WITH_PROMETHEUS = 'last-time-with-prometheus';
 export const LAST_TIME_PROMETHEUS_ADDRESS = 'last-time-prometheus-address';
 export const LAST_TIME_PROMETHEUS_ADDRESS = 'last-time-prometheus-address';

+ 65 - 38
client/src/context/Auth.tsx

@@ -1,69 +1,96 @@
 import { createContext, useEffect, useState } from 'react';
 import { createContext, useEffect, useState } from 'react';
-import { MILVUS_CLIENT_ID, LOGIN_USERNAME, LAST_TIME_ADDRESS } from '@/consts';
 import { AuthContextType } from './Types';
 import { AuthContextType } from './Types';
+import { MilvusService } from '@/http';
+import { AuthReq } from '@server/types';
+import {
+  MILVUS_CLIENT_ID,
+  MILVUS_URL,
+  MILVUS_DATABASE,
+  ATTU_AUTH_REQ,
+} from '@/consts';
 
 
 export const authContext = createContext<AuthContextType>({
 export const authContext = createContext<AuthContextType>({
-  isAuth: false,
   clientId: '',
   clientId: '',
-  address: '',
-  username: '',
+  authReq: {
+    username: '',
+    password: '',
+    address: '',
+    token: '',
+    database: '',
+  },
+  setAuthReq: () => {},
   isManaged: false,
   isManaged: false,
+  isAuth: false,
+  login: async () => {
+    return { clientId: '', database: '' };
+  },
   logout: () => {},
   logout: () => {},
-  setAddress: () => {},
-  setUsername: () => {},
-  setIsAuth: () => {},
-  setClientId: () => {},
 });
 });
 
 
 const { Provider } = authContext;
 const { Provider } = authContext;
 export const AuthProvider = (props: { children: React.ReactNode }) => {
 export const AuthProvider = (props: { children: React.ReactNode }) => {
-  // get milvus address from local storage
-  const [address, setAddress] = useState<string>(
-    window.localStorage.getItem(LAST_TIME_ADDRESS) || ''
-  );
-  // get login username from local storage
-  const [username, setUsername] = useState<string>(
-    window.localStorage.getItem(LOGIN_USERNAME) || ''
+  // get data from local storage
+  const localAuthReq = JSON.parse(
+    window.localStorage.getItem(ATTU_AUTH_REQ) ||
+      JSON.stringify({
+        username: '',
+        password: '',
+        address: '' || MILVUS_URL,
+        token: '',
+        database: '' || MILVUS_DATABASE,
+      })
   );
   );
-
-  // get milvus address from local storage
+  // state
+  const [authReq, setAuthReq] = useState<AuthReq>(localAuthReq);
   const [clientId, setClientId] = useState<string>(
   const [clientId, setClientId] = useState<string>(
     window.localStorage.getItem(MILVUS_CLIENT_ID) || ''
     window.localStorage.getItem(MILVUS_CLIENT_ID) || ''
   );
   );
-  const [isAuth, setIsAuth] = useState<boolean>(clientId !== '');
-  // const isAuth = useMemo(() => !!address, [address]);
 
 
+  // update title when address changes
   useEffect(() => {
   useEffect(() => {
-    document.title = address ? `${address} - Attu` : 'Attu';
+    document.title = authReq.address ? `${authReq.address} - Attu` : 'Attu';
     return () => {
     return () => {
       document.title = 'Attu';
       document.title = 'Attu';
-    }
-  }, [address, username]);
+    };
+  }, [authReq.address]);
+
+  // update local storage when authReq changes
+  useEffect(() => {
+    // store auth request in local storage
+    window.localStorage.setItem(
+      ATTU_AUTH_REQ,
+      JSON.stringify({ ...authReq, password: '', token: '' })
+    );
+  }, [authReq]);
 
 
+  // login API
+  const login = async (params: AuthReq) => {
+    // connect to Milvus
+    const res = await MilvusService.connect(params);
+    // update auth request
+    setAuthReq({ ...params, database: res.database, password: '', token: '' });
+    setClientId(res.clientId);
+
+    return res;
+  };
+  // logout API
   const logout = () => {
   const logout = () => {
-    // remove user data from local storage
+    // clear client id
+    setClientId('');
+    // remove client id from local storage
     window.localStorage.removeItem(MILVUS_CLIENT_ID);
     window.localStorage.removeItem(MILVUS_CLIENT_ID);
-    window.localStorage.removeItem(LOGIN_USERNAME);
-
-    // update state
-    setAddress('');
-    setUsername('');
-    setIsAuth(false);
   };
   };
 
 
   return (
   return (
     <Provider
     <Provider
       value={{
       value={{
-        isAuth,
-        clientId,
-        address,
-        username,
-        setAddress,
-        setUsername,
-        setIsAuth,
-        setClientId,
+        authReq,
+        setAuthReq,
+        login,
         logout,
         logout,
-        isManaged: address.includes('zilliz'),
+        clientId,
+        isAuth: !!clientId,
+        isManaged: authReq.address.includes('zilliz'),
       }}
       }}
     >
     >
       {props.children}
       {props.children}

+ 12 - 14
client/src/context/Data.tsx

@@ -9,10 +9,11 @@ import {
 import { io, Socket } from 'socket.io-client';
 import { io, Socket } from 'socket.io-client';
 import { authContext } from '@/context';
 import { authContext } from '@/context';
 import { url, CollectionService, MilvusService, DatabaseService } from '@/http';
 import { url, CollectionService, MilvusService, DatabaseService } from '@/http';
-import { IndexCreateParam, IndexManageParam } from '@/pages/databases/collections/overview/Types';
-import { getDbValueFromUrl } from '@/utils';
+import {
+  IndexCreateParam,
+  IndexManageParam,
+} from '@/pages/databases/collections/overview/Types';
 import { DataContextType } from './Types';
 import { DataContextType } from './Types';
-import { LAST_TIME_DATABASE } from '@/consts';
 import {
 import {
   CollectionObject,
   CollectionObject,
   CollectionFullObject,
   CollectionFullObject,
@@ -72,22 +73,17 @@ export const dataContext = createContext<DataContextType>({
 const { Provider } = dataContext;
 const { Provider } = dataContext;
 
 
 export const DataProvider = (props: { children: React.ReactNode }) => {
 export const DataProvider = (props: { children: React.ReactNode }) => {
-  // get database name from url
-  const currentUrl = window.location.href;
-  const initialDatabase = getDbValueFromUrl(currentUrl);
+  // auth context
+  const { authReq, isAuth, clientId, logout } = useContext(authContext);
+
   // local data state
   // local data state
   const [collections, setCollections] = useState<CollectionObject[]>([]);
   const [collections, setCollections] = useState<CollectionObject[]>([]);
   const [connected, setConnected] = useState(false);
   const [connected, setConnected] = useState(false);
   const [loading, setLoading] = useState(true);
   const [loading, setLoading] = useState(true);
   const [loadingDatabases, setLoadingDatabases] = useState(true);
   const [loadingDatabases, setLoadingDatabases] = useState(true);
-  const defaultDb =
-    initialDatabase ||
-    window.localStorage.getItem(LAST_TIME_DATABASE) ||
-    'default';
-  const [database, setDatabase] = useState<string>(defaultDb);
+  const [database, setDatabase] = useState<string>(authReq.database);
+
   const [databases, setDatabases] = useState<DatabaseObject[]>([]);
   const [databases, setDatabases] = useState<DatabaseObject[]>([]);
-  // auth context
-  const { isAuth, clientId, logout } = useContext(authContext);
   // socket ref
   // socket ref
   const socket = useRef<Socket | null>(null);
   const socket = useRef<Socket | null>(null);
 
 
@@ -323,6 +319,8 @@ export const DataProvider = (props: { children: React.ReactNode }) => {
 
 
   useEffect(() => {
   useEffect(() => {
     if (isAuth) {
     if (isAuth) {
+      // update database get from auth
+      setDatabase(authReq.database);
       // connect to socket server
       // connect to socket server
       socket.current = io(url as string);
       socket.current = io(url as string);
       // register client
       // register client
@@ -344,7 +342,7 @@ export const DataProvider = (props: { children: React.ReactNode }) => {
       // set connected to false
       // set connected to false
       setConnected(false);
       setConnected(false);
     }
     }
-  }, [isAuth]);
+  }, [isAuth, authReq]);
 
 
   useEffect(() => {
   useEffect(() => {
     if (connected) {
     if (connected) {

+ 11 - 9
client/src/context/Types.ts

@@ -3,9 +3,14 @@ import {
   CollectionObject,
   CollectionObject,
   CollectionFullObject,
   CollectionFullObject,
   DatabaseObject,
   DatabaseObject,
+  AuthReq,
 } from '@server/types';
 } from '@server/types';
 import { NavInfo } from '@/router/Types';
 import { NavInfo } from '@/router/Types';
-import { IndexCreateParam, IndexManageParam } from '@/pages/databases/collections/overview/Types';
+import {
+  IndexCreateParam,
+  IndexManageParam,
+} from '@/pages/databases/collections/overview/Types';
+import { AuthObject } from '@server/types';
 
 
 export type RootContextType = {
 export type RootContextType = {
   openSnackBar: OpenSnackBarType;
   openSnackBar: OpenSnackBarType;
@@ -60,16 +65,13 @@ export type OpenSnackBarType = (
 ) => void;
 ) => void;
 
 
 export type AuthContextType = {
 export type AuthContextType = {
-  isAuth: boolean;
+  authReq: AuthReq;
+  setAuthReq: Dispatch<SetStateAction<AuthReq>>;
   clientId: string;
   clientId: string;
-  address: string;
-  username: string;
   isManaged: boolean;
   isManaged: boolean;
-  logout: Function;
-  setAddress: Dispatch<SetStateAction<string>>;
-  setUsername: Dispatch<SetStateAction<string>>;
-  setIsAuth: Dispatch<SetStateAction<boolean>>;
-  setClientId: Dispatch<SetStateAction<string>>;
+  isAuth: boolean;
+  logout: () => void;
+  login: (params: AuthReq) => Promise<AuthObject>;
 };
 };
 
 
 export type SystemContextType = {
 export type SystemContextType = {

+ 6 - 11
client/src/http/Milvus.service.ts

@@ -1,18 +1,13 @@
 import BaseModel from './BaseModel';
 import BaseModel from './BaseModel';
 import { CronJobObject } from '@server/types';
 import { CronJobObject } from '@server/types';
+import { AuthReq, AuthObject } from '@server/types';
 
 
 export class MilvusService extends BaseModel {
 export class MilvusService extends BaseModel {
-  static connect(data: {
-    address: string;
-    username?: string;
-    password?: string;
-    database?: string;
-  }) {
-    return super.create({ path: '/milvus/connect', data }) as Promise<{
-      address: string;
-      database: string;
-      clientId: string;
-    }>;
+  static connect(data: AuthReq) {
+    return super.create({
+      path: '/milvus/connect',
+      data,
+    }) as Promise<AuthObject>;
   }
   }
 
 
   static closeConnection() {
   static closeConnection() {

+ 5 - 0
client/src/i18n/cn/common.ts

@@ -2,6 +2,11 @@ const commonTrans = {
   attu: {
   attu: {
     admin: 'Attu',
     admin: 'Attu',
     address: 'Milvus地址',
     address: 'Milvus地址',
+    fileIssue: '提交问题',
+    discord: 'Discord',
+    token: 'token',
+    authentication: '认证',
+    connectTitle: '连接到Milvus服务器',
     unAuth: '用户名或密码不正确',
     unAuth: '用户名或密码不正确',
     username: '用户名',
     username: '用户名',
     password: '密码',
     password: '密码',

+ 7 - 2
client/src/i18n/en/common.ts

@@ -2,9 +2,14 @@ const commonTrans = {
   attu: {
   attu: {
     admin: 'Attu',
     admin: 'Attu',
     address: 'Milvus Address',
     address: 'Milvus Address',
+    fileIssue: 'Open an Issue',
+    discord: 'Discord',
+    token: 'token',
+    authentication: 'Authentication',
+    connectTitle: 'Connect to Milvus Server',
     unAuth: 'Username or password is not correct',
     unAuth: 'Username or password is not correct',
-    username: 'Username',
-    password: 'Password',
+    username: 'username',
+    password: 'password',
     optional: '(optional)',
     optional: '(optional)',
     prometheus: 'Prometheus',
     prometheus: 'Prometheus',
     prometheusAddress: 'Prometheus Address',
     prometheusAddress: 'Prometheus Address',

+ 206 - 190
client/src/pages/connect/AuthForm.tsx

@@ -3,26 +3,11 @@ import { makeStyles, Theme, Typography } from '@material-ui/core';
 import { useTranslation } from 'react-i18next';
 import { useTranslation } from 'react-i18next';
 import CustomButton from '@/components/customButton/CustomButton';
 import CustomButton from '@/components/customButton/CustomButton';
 import CustomInput from '@/components/customInput/CustomInput';
 import CustomInput from '@/components/customInput/CustomInput';
-import icons from '@/components/icons/Icons';
-import { ITextfieldConfig } from '@/components/customInput/Types';
 import { useFormValidation } from '@/hooks';
 import { useFormValidation } from '@/hooks';
 import { formatForm } from '@/utils';
 import { formatForm } from '@/utils';
-import { MilvusService } from '@/http';
 import { useNavigate } from 'react-router-dom';
 import { useNavigate } from 'react-router-dom';
-import {
-  rootContext,
-  authContext,
-  prometheusContext,
-  dataContext,
-} from '@/context';
-import {
-  MILVUS_CLIENT_ID,
-  LOGIN_USERNAME,
-  LAST_TIME_ADDRESS,
-  MILVUS_URL,
-  LAST_TIME_DATABASE,
-  MILVUS_DATABASE,
-} from '@/consts';
+import { rootContext, authContext, dataContext } from '@/context';
+import { MILVUS_CLIENT_ID } from '@/consts';
 import { CustomRadio } from '@/components/customRadio/CustomRadio';
 import { CustomRadio } from '@/components/customRadio/CustomRadio';
 
 
 const useStyles = makeStyles((theme: Theme) => ({
 const useStyles = makeStyles((theme: Theme) => ({
@@ -34,27 +19,14 @@ const useStyles = makeStyles((theme: Theme) => ({
     position: 'relative',
     position: 'relative',
   },
   },
   titleWrapper: {
   titleWrapper: {
-    display: 'flex',
-    alignItems: 'center',
-    padding: theme.spacing(3),
-    margin: '0 auto',
-    flexDirection: 'column',
-    '& .title': {
-      margin: 0,
-      color: '#323232',
-      fontWeight: 'bold',
-    },
-  },
-  logo: {
-    width: '42px',
-    height: 'auto',
-    marginBottom: theme.spacing(1),
-    display: 'block',
+    textAlign: 'left',
+    alignSelf: 'flex-start',
+    padding: theme.spacing(3, 0),
   },
   },
   input: {
   input: {
     margin: theme.spacing(0.5, 0, 0),
     margin: theme.spacing(0.5, 0, 0),
   },
   },
-  sslWrapper: {
+  toggle: {
     display: 'flex',
     display: 'flex',
     width: '100%',
     width: '100%',
     justifyContent: 'flex-start',
     justifyContent: 'flex-start',
@@ -87,193 +59,246 @@ const useStyles = makeStyles((theme: Theme) => ({
 }));
 }));
 
 
 export const AuthForm = (props: any) => {
 export const AuthForm = (props: any) => {
-  const navigate = useNavigate();
+  // styles
   const classes = useStyles();
   const classes = useStyles();
 
 
+  // context
   const { openSnackBar } = useContext(rootContext);
   const { openSnackBar } = useContext(rootContext);
-  const { setAddress, setUsername, setIsAuth, setClientId } =
-    useContext(authContext);
+  const { authReq, setAuthReq, login } = useContext(authContext);
   const { setDatabase } = useContext(dataContext);
   const { setDatabase } = useContext(dataContext);
 
 
-  const Logo = icons.attu;
-  const GithubIcon = icons.github;
+  // i18n
   const { t: commonTrans } = useTranslation();
   const { t: commonTrans } = useTranslation();
   const attuTrans = commonTrans('attu');
   const attuTrans = commonTrans('attu');
   const { t: btnTrans } = useTranslation('btn');
   const { t: btnTrans } = useTranslation('btn');
   const { t: warningTrans } = useTranslation('warning');
   const { t: warningTrans } = useTranslation('warning');
   const { t: successTrans } = useTranslation('success');
   const { t: successTrans } = useTranslation('success');
   const { t: dbTrans } = useTranslation('database');
   const { t: dbTrans } = useTranslation('database');
+  // hooks
+  const navigate = useNavigate();
+
+  // UI states
+  const [withPass, setWithPass] = useState(authReq.username.length > 0);
 
 
-  const [form, setForm] = useState({
-    address: window.localStorage.getItem(LAST_TIME_ADDRESS) || MILVUS_URL,
-    username: '',
-    password: '',
-    database:
-      window.localStorage.getItem(LAST_TIME_DATABASE) || MILVUS_DATABASE,
-    ssl: false,
-  });
+  // form validation
   const checkedForm = useMemo(() => {
   const checkedForm = useMemo(() => {
-    return formatForm(form);
-  }, [form]);
+    return formatForm(authReq);
+  }, [authReq]);
   const { validation, checkIsValid } = useFormValidation(checkedForm);
   const { validation, checkIsValid } = useFormValidation(checkedForm);
 
 
+  // handle input change
   const handleInputChange = (
   const handleInputChange = (
-    key: 'address' | 'username' | 'password' | 'database' | 'ssl',
+    key: 'address' | 'username' | 'password' | 'database' | 'token',
     value: string | boolean
     value: string | boolean
   ) => {
   ) => {
-    setForm(v => ({ ...v, [key]: value }));
+    setAuthReq(v => ({ ...v, [key]: value }));
   };
   };
 
 
-  const inputConfigs: ITextfieldConfig[] = useMemo(() => {
-    const noAuthConfigs: ITextfieldConfig[] = [
-      {
-        label: attuTrans.address,
-        key: 'address',
-        onChange: (val: string) => handleInputChange('address', val),
-        variant: 'filled',
-        className: classes.input,
-        placeholder: attuTrans.address,
-        fullWidth: true,
-        validations: [
-          {
-            rule: 'require',
-            errorText: warningTrans('required', { name: attuTrans.address }),
-          },
-        ],
-        defaultValue: form.address,
-      },
-    ];
-    return [
-      ...noAuthConfigs,
-      {
-        label: `Milvus ${dbTrans('database')} ${attuTrans.optional}`,
-        key: 'database',
-        onChange: (value: string) => handleInputChange('database', value),
-        variant: 'filled',
-        className: classes.input,
-        placeholder: dbTrans('database'),
-        fullWidth: true,
-        defaultValue: form.database,
-      },
-      {
-        label: `Milvus ${attuTrans.username} ${attuTrans.optional}`,
-        key: 'username',
-        onChange: (value: string) => handleInputChange('username', value),
-        variant: 'filled',
-        className: classes.input,
-        placeholder: attuTrans.username,
-        fullWidth: true,
-        defaultValue: form.username,
-      },
-      {
-        label: `Milvus ${attuTrans.password} ${attuTrans.optional}`,
-        key: 'password',
-        onChange: (value: string) => handleInputChange('password', value),
-        variant: 'filled',
-        className: classes.input,
-        placeholder: attuTrans.password,
-        fullWidth: true,
-        type: 'password',
+  // const {
+  //   withPrometheus,
+  //   setWithPrometheus,
+  //   prometheusAddress,
+  //   prometheusInstance,
+  //   prometheusNamespace,
+  //   setPrometheusAddress,
+  //   setPrometheusInstance,
+  //   setPrometheusNamespace,
+  // } = useContext(prometheusContext);
 
 
-        defaultValue: form.username,
-      },
-    ];
-  }, [form, attuTrans, warningTrans, classes.input]);
+  // const prometheusConfigs: ITextfieldConfig[] = useMemo(
+  //   () => [
+  //     {
+  //       label: `${attuTrans.prometheusAddress}`,
+  //       key: 'prometheus_address',
+  //       onChange: setPrometheusAddress,
+  //       variant: 'filled',
+  //       className: classes.input,
+  //       placeholder: attuTrans.prometheusAddress,
+  //       fullWidth: true,
 
 
-  const {
-    withPrometheus,
-    setWithPrometheus,
-    prometheusAddress,
-    prometheusInstance,
-    prometheusNamespace,
-    setPrometheusAddress,
-    setPrometheusInstance,
-    setPrometheusNamespace,
-  } = useContext(prometheusContext);
+  //       defaultValue: prometheusAddress,
+  //     },
+  //     {
+  //       label: `${attuTrans.prometheusNamespace}`,
+  //       key: 'prometheus_namespace',
+  //       onChange: setPrometheusNamespace,
+  //       variant: 'filled',
+  //       className: classes.input,
+  //       placeholder: attuTrans.prometheusNamespace,
+  //       fullWidth: true,
 
 
-  const prometheusConfigs: ITextfieldConfig[] = useMemo(
-    () => [
-      {
-        label: `${attuTrans.prometheusAddress}`,
-        key: 'prometheus_address',
-        onChange: setPrometheusAddress,
-        variant: 'filled',
-        className: classes.input,
-        placeholder: attuTrans.prometheusAddress,
-        fullWidth: true,
+  //       defaultValue: prometheusNamespace,
+  //     },
+  //     {
+  //       label: `${attuTrans.prometheusInstance}`,
+  //       key: 'prometheus_instance',
+  //       onChange: setPrometheusInstance,
+  //       variant: 'filled',
+  //       className: classes.input,
+  //       placeholder: attuTrans.prometheusInstance,
+  //       fullWidth: true,
 
 
-        defaultValue: prometheusAddress,
-      },
-      {
-        label: `${attuTrans.prometheusNamespace}`,
-        key: 'prometheus_namespace',
-        onChange: setPrometheusNamespace,
-        variant: 'filled',
-        className: classes.input,
-        placeholder: attuTrans.prometheusNamespace,
-        fullWidth: true,
-
-        defaultValue: prometheusNamespace,
-      },
-      {
-        label: `${attuTrans.prometheusInstance}`,
-        key: 'prometheus_instance',
-        onChange: setPrometheusInstance,
-        variant: 'filled',
-        className: classes.input,
-        placeholder: attuTrans.prometheusInstance,
-        fullWidth: true,
-
-        defaultValue: prometheusInstance,
-      },
-    ],
-    []
-  );
+  //       defaultValue: prometheusInstance,
+  //     },
+  //   ],
+  //   []
+  // );
 
 
   const handleConnect = async (event: React.FormEvent) => {
   const handleConnect = async (event: React.FormEvent) => {
     event.preventDefault();
     event.preventDefault();
-    const result = await MilvusService.connect(form);
 
 
-    setIsAuth(true);
-    setClientId(result.clientId);
-    setAddress(form.address);
-    setUsername(form.username);
-    setDatabase(result.database);
+    try {
+      // login
+      const result = await login(authReq);
 
 
-    openSnackBar(successTrans('connect'));
-    window.localStorage.setItem(MILVUS_CLIENT_ID, result.clientId);
-    window.localStorage.setItem(LOGIN_USERNAME, form.username);
-    // store address for next time using
-    window.localStorage.setItem(LAST_TIME_ADDRESS, form.address);
-    window.localStorage.setItem(LAST_TIME_DATABASE, result.database);
+      // set database
+      setDatabase(authReq.database);
+      // success message
+      openSnackBar(successTrans('connect'));
+      // save clientId to local storage
+      window.localStorage.setItem(MILVUS_CLIENT_ID, result.clientId);
 
 
-    // redirect to homepage
-    navigate('/');
+      // redirect to homepage
+      navigate('/');
+    } catch (error: any) {
+      // if not authorized, show auth inputs
+      if (error.response.data.message.includes('UNAUTHENTICATED')) {
+        handleEnableAuth(true);
+      }
+    }
   };
   };
 
 
   const btnDisabled = useMemo(() => {
   const btnDisabled = useMemo(() => {
-    return form.address.trim().length === 0;
-  }, [form.address]);
+    return authReq.address.trim().length === 0;
+  }, [authReq.address]);
+
+  // handle auth toggle
+  const handleEnableAuth = (val: boolean) => {
+    setWithPass(val);
+  };
 
 
   return (
   return (
     <form onSubmit={handleConnect}>
     <form onSubmit={handleConnect}>
       <section className={classes.wrapper}>
       <section className={classes.wrapper}>
         <div className={classes.titleWrapper}>
         <div className={classes.titleWrapper}>
-          <Logo classes={{ root: classes.logo }} />
-          <Typography variant="h2" className="title">
-            {attuTrans.admin}
+          <Typography variant="h4" component="h4">
+            {attuTrans.connectTitle}
           </Typography>
           </Typography>
         </div>
         </div>
-        {inputConfigs.map(v => (
-          <CustomInput
-            type="text"
-            textConfig={v}
-            checkValid={checkIsValid}
-            validInfo={validation}
-            key={v.label}
+        {/* address  */}
+        <CustomInput
+          type="text"
+          textConfig={{
+            label: attuTrans.address,
+            key: 'address',
+            onChange: (val: string) => handleInputChange('address', val),
+            variant: 'filled',
+            className: classes.input,
+            placeholder: attuTrans.address,
+            fullWidth: true,
+            validations: [
+              {
+                rule: 'require',
+                errorText: warningTrans('required', {
+                  name: attuTrans.address,
+                }),
+              },
+            ],
+            defaultValue: authReq.address,
+          }}
+          checkValid={checkIsValid}
+          validInfo={validation}
+          key={attuTrans.address}
+        />
+        {/* db  */}
+        <CustomInput
+          type="text"
+          textConfig={{
+            label: `Milvus ${dbTrans('database')} ${attuTrans.optional}`,
+            key: 'database',
+            onChange: (value: string) => handleInputChange('database', value),
+            variant: 'filled',
+            className: classes.input,
+            placeholder: dbTrans('database'),
+            fullWidth: true,
+            defaultValue: authReq.database,
+          }}
+          checkValid={checkIsValid}
+          validInfo={validation}
+          key={attuTrans.database}
+        />
+
+        {/* toggle auth */}
+        <div className={classes.toggle}>
+          <CustomRadio
+            checked={withPass}
+            label={attuTrans.authentication}
+            handleChange={handleEnableAuth}
           />
           />
-        ))}
-        <div className={classes.sslWrapper}>
+        </div>
+
+        {/* token  */}
+        {withPass && (
+          <>
+            <CustomInput
+              type="text"
+              textConfig={{
+                label: `${attuTrans.token} ${attuTrans.optional} `,
+                key: 'token',
+                onChange: (val: string) => handleInputChange('token', val),
+                variant: 'filled',
+                className: classes.input,
+                placeholder: attuTrans.token,
+                fullWidth: true,
+                defaultValue: authReq.token,
+              }}
+              checkValid={checkIsValid}
+              validInfo={validation}
+              key={attuTrans.token}
+            />
+
+            {/* user  */}
+            <CustomInput
+              type="text"
+              textConfig={{
+                label: `${attuTrans.username} ${attuTrans.optional}`,
+                key: 'username',
+                onChange: (value: string) =>
+                  handleInputChange('username', value),
+                variant: 'filled',
+                className: classes.input,
+                placeholder: attuTrans.username,
+                fullWidth: true,
+                defaultValue: authReq.username,
+              }}
+              checkValid={checkIsValid}
+              validInfo={validation}
+              key={attuTrans.username}
+            />
+
+            {/* pass  */}
+            <CustomInput
+              type="text"
+              textConfig={{
+                label: `${attuTrans.password} ${attuTrans.optional}`,
+                key: 'password',
+                onChange: (value: string) =>
+                  handleInputChange('password', value),
+                variant: 'filled',
+                className: classes.input,
+                placeholder: attuTrans.password,
+                fullWidth: true,
+                type: 'password',
+                defaultValue: authReq.password,
+              }}
+              checkValid={checkIsValid}
+              validInfo={validation}
+              key={attuTrans.password}
+            />
+          </>
+        )}
+
+        {/* <div className={classes.toggle}>
           <CustomRadio
           <CustomRadio
             defaultChecked={withPrometheus}
             defaultChecked={withPrometheus}
             label={attuTrans.prometheus}
             label={attuTrans.prometheus}
@@ -289,20 +314,11 @@ export const AuthForm = (props: any) => {
               validInfo={validation}
               validInfo={validation}
               key={v.label}
               key={v.label}
             />
             />
-          ))}
+          ))} */}
 
 
         <CustomButton type="submit" variant="contained" disabled={btnDisabled}>
         <CustomButton type="submit" variant="contained" disabled={btnDisabled}>
           {btnTrans('connect')}
           {btnTrans('connect')}
         </CustomButton>
         </CustomButton>
-
-        <a
-          href="https://github.com/zilliztech/attu"
-          target="_blank"
-          className={classes.star}
-        >
-          <GithubIcon className={classes.icon} />
-          {btnTrans('star')}
-        </a>
       </section>
       </section>
     </form>
     </form>
   );
   );

+ 1 - 4
client/src/pages/connect/Connect.tsx

@@ -1,7 +1,6 @@
 import { useContext } from 'react';
 import { useContext } from 'react';
 import { Navigate } from 'react-router-dom';
 import { Navigate } from 'react-router-dom';
 import ConnectContainer from './ConnectContainer';
 import ConnectContainer from './ConnectContainer';
-import { AuthForm } from './AuthForm';
 import { authContext } from '@/context/Auth';
 import { authContext } from '@/context/Auth';
 import GlobalEffect from '@/components/layout/GlobalEffect';
 import GlobalEffect from '@/components/layout/GlobalEffect';
 
 
@@ -12,9 +11,7 @@ const Connect = () => {
     <>
     <>
       {isAuth && <Navigate to="/" replace={true} />}
       {isAuth && <Navigate to="/" replace={true} />}
       <GlobalEffect>
       <GlobalEffect>
-        <ConnectContainer>
-          <AuthForm />
-        </ConnectContainer>
+        <ConnectContainer />
       </GlobalEffect>
       </GlobalEffect>
     </>
     </>
   );
   );

+ 111 - 9
client/src/pages/connect/ConnectContainer.tsx

@@ -1,28 +1,130 @@
-import { makeStyles, Theme } from '@material-ui/core';
-import { ReactElement } from 'react';
+import { useEffect, useState } from 'react';
+import { makeStyles, Theme, Typography } from '@material-ui/core';
+import { useTranslation } from 'react-i18next';
+import Icons from '@/components/icons/Icons';
+import { AuthForm } from './AuthForm';
+import CustomButton from '@/components/customButton/CustomButton';
+import { MilvusService } from '@/http';
 
 
 const getContainerStyles = makeStyles((theme: Theme) => ({
 const getContainerStyles = makeStyles((theme: Theme) => ({
   wrapper: {
   wrapper: {
     width: '100%',
     width: '100%',
     height: '100vh',
     height: '100vh',
-    backgroundRepeat: 'no-repeat',
-    backgroundSize: 'cover',
   },
   },
-  card: {
-    width: '480px',
+  box: {
+    display: 'flex',
+    flexDirection: 'row',
     backgroundColor: '#fff',
     backgroundColor: '#fff',
-    boxShadow: '0px 10px 30px rgba(0, 0, 0, 0.1)',
+    border: '1px solid #E0E0E0',
+    boxShadow: '0px 6px 30px rgba(0, 0, 0, 0.1)',
+    minHeight: 644,
+  },
+  logo: {
+    width: 64,
+    height: 'auto',
+    marginBottom: theme.spacing(1),
+    display: 'block',
+  },
+
+  links: {
+    marginTop: theme.spacing(4),
+    display: 'flex',
+    flexDirection: 'column',
+    alignItems: 'center',
+    justifyContent: 'center',
+    width: '100%',
+    padding: theme.spacing(2, 0),
+    '& button': {
+      border: 'none',
+    },
+  },
+
+  attu: {
+    width: 299,
+    display: 'flex',
+    flexDirection: 'column',
+    padding: theme.spacing(0, 3),
+    backgroundColor: '#f5f5f5',
+  },
+  form: {
+    width: 481,
     padding: theme.spacing(5, 0),
     padding: theme.spacing(5, 0),
   },
   },
+  sub: {
+    marginTop: theme.spacing(1),
+    fontSize: 12,
+    color: '#666',
+  },
 }));
 }));
 
 
 // used for user connect process
 // used for user connect process
-const ConnectContainer = ({ children }: { children: ReactElement }) => {
+const ConnectContainer = () => {
+  const [version, setVersion] = useState('');
   const classes = getContainerStyles();
   const classes = getContainerStyles();
+  const { t: commonTrans } = useTranslation();
+  const attuTrans = commonTrans('attu');
+  const { t: btnTrans } = useTranslation('btn');
+
+  useEffect(() => {
+    MilvusService.getVersion().then((res: any) => {
+      setVersion(res.attu);
+    });
+  }, []);
 
 
   return (
   return (
     <main className={`flex-center ${classes.wrapper}`}>
     <main className={`flex-center ${classes.wrapper}`}>
-      <section className={classes.card}>{children}</section>
+      <section className={classes.box}>
+        <section className={`flex-center ${classes.attu}`}>
+          <Icons.attu classes={{ root: classes.logo }} />
+          <Typography variant="h2" className="title">
+            {attuTrans.admin}
+          </Typography>
+          {version && (
+            <Typography component="sub" className={classes.sub}>
+              {attuTrans.version} {version}
+            </Typography>
+          )}
+
+          <div className={classes.links}>
+            <CustomButton
+              startIcon={<Icons.star />}
+              fullWidth={true}
+              variant="outlined"
+              onClick={() =>
+                window.open('https://github.com/zilliztech/attu', '_blank')
+              }
+            >
+              {btnTrans('star')}
+            </CustomButton>
+
+            <CustomButton
+              startIcon={<Icons.github />}
+              fullWidth={true}
+              variant="outlined"
+              onClick={() =>
+                window.open(
+                  'https://github.com/zilliztech/attu/issues',
+                  '_blank'
+                )
+              }
+            >
+              {attuTrans.fileIssue}
+            </CustomButton>
+
+            <CustomButton
+              startIcon={<Icons.discord />}
+              variant="outlined"
+              onClick={() => window.open('https://milvus.io/discord', '_blank')}
+              fullWidth={true}
+            >
+              {attuTrans.discord}
+            </CustomButton>
+          </div>
+        </section>
+        <section className={classes.form}>
+          <AuthForm />
+        </section>
+      </section>
     </main>
     </main>
   );
   );
 };
 };

+ 0 - 8
client/src/utils/Validation.ts

@@ -283,11 +283,3 @@ export const checkIndexBuilding = (v: CollectionObject): boolean => {
       v.schema?.fields.some(field => field.index?.state === 'InProgress')
       v.schema?.fields.some(field => field.index?.state === 'InProgress')
   );
   );
 };
 };
-
-// get database name from url
-export const getDbValueFromUrl = (currentUrl: string) => {
-  const url = new URL(currentUrl);
-  const pathname = url.hash;
-  const match = pathname.match(/\/databases\/([^/]+)/);
-  return match ? match[1] : null;
-};

+ 2 - 2
server/src/middleware/index.ts

@@ -24,10 +24,10 @@ export const ReqHeaderMiddleware = (
   const milvusClientId = (req.headers[MILVUS_CLIENT_ID] as string) || '';
   const milvusClientId = (req.headers[MILVUS_CLIENT_ID] as string) || '';
   req.clientId = req.headers[MILVUS_CLIENT_ID] as string;
   req.clientId = req.headers[MILVUS_CLIENT_ID] as string;
 
 
-  const CONNECT_URL = `/api/v1/milvus/connect`;
+  const bypassURLs = [`/api/v1/milvus/connect`, `/api/v1/milvus/version`];
 
 
   if (
   if (
-    req.url !== CONNECT_URL &&
+    bypassURLs.indexOf(req.url) === -1 &&
     milvusClientId &&
     milvusClientId &&
     !clientCache.get(milvusClientId)
     !clientCache.get(milvusClientId)
   ) {
   ) {

+ 2 - 2
server/src/milvus/milvus.controller.ts

@@ -41,10 +41,11 @@ export class MilvusController {
   }
   }
 
 
   async connectMilvus(req: Request, res: Response, next: NextFunction) {
   async connectMilvus(req: Request, res: Response, next: NextFunction) {
-    const { address, username, password, database } = req.body;
+    const { address, username, password, database, token } = req.body;
     try {
     try {
       const result = await this.milvusService.connectMilvus({
       const result = await this.milvusService.connectMilvus({
         address,
         address,
+        token,
         username,
         username,
         password,
         password,
         database,
         database,
@@ -52,7 +53,6 @@ export class MilvusController {
 
 
       res.send(result);
       res.send(result);
     } catch (error) {
     } catch (error) {
-      console.log(error);
       next(error);
       next(error);
     }
     }
   }
   }

+ 11 - 21
server/src/milvus/milvus.service.ts

@@ -8,7 +8,7 @@ import { LRUCache } from 'lru-cache';
 import { DEFAULT_MILVUS_PORT, INDEX_TTL, SimpleQueue } from '../utils';
 import { DEFAULT_MILVUS_PORT, INDEX_TTL, SimpleQueue } from '../utils';
 import { connectivityState } from '@grpc/grpc-js';
 import { connectivityState } from '@grpc/grpc-js';
 import { clientCache } from '../app';
 import { clientCache } from '../app';
-import { DescribeIndexRes } from '../types';
+import { DescribeIndexRes, AuthReq, AuthObject } from '../types';
 
 
 export class MilvusService {
 export class MilvusService {
   private DEFAULT_DATABASE = 'default';
   private DEFAULT_DATABASE = 'default';
@@ -23,50 +23,41 @@ export class MilvusService {
     return ip.includes(':') ? ip : `${ip}:${DEFAULT_MILVUS_PORT}`;
     return ip.includes(':') ? ip : `${ip}:${DEFAULT_MILVUS_PORT}`;
   }
   }
 
 
-  async connectMilvus(data: {
-    address: string;
-    username?: string;
-    password?: string;
-    database?: string;
-  }) {
+  async connectMilvus(data: AuthReq): Promise<AuthObject> {
     // Destructure the data object to get the connection details
     // Destructure the data object to get the connection details
-    const {
-      address,
-      username,
-      password,
-      database = this.DEFAULT_DATABASE,
-    } = data;
+    const { address, token, username, password, database } = data;
     // Format the address to remove the http prefix
     // Format the address to remove the http prefix
     const milvusAddress = MilvusService.formatAddress(address);
     const milvusAddress = MilvusService.formatAddress(address);
 
 
     try {
     try {
       // Create a new Milvus client with the provided connection details
       // Create a new Milvus client with the provided connection details
-      const clientOptions: ClientConfig = {
+      const clientConfig: ClientConfig = {
         address: milvusAddress,
         address: milvusAddress,
+        token,
         username,
         username,
         password,
         password,
         logLevel: process.env.ATTU_LOG_LEVEL || 'info',
         logLevel: process.env.ATTU_LOG_LEVEL || 'info',
       };
       };
 
 
       if (process.env.ROOT_CERT_PATH) {
       if (process.env.ROOT_CERT_PATH) {
-        clientOptions.tls = {
+        clientConfig.tls = {
           rootCertPath: process.env.ROOT_CERT_PATH,
           rootCertPath: process.env.ROOT_CERT_PATH,
         };
         };
 
 
         if (process.env.PRIVATE_KEY_PATH) {
         if (process.env.PRIVATE_KEY_PATH) {
-          clientOptions.tls.privateKeyPath = process.env.PRIVATE_KEY_PATH;
+          clientConfig.tls.privateKeyPath = process.env.PRIVATE_KEY_PATH;
         }
         }
 
 
         if (process.env.CERT_CHAIN_PATH) {
         if (process.env.CERT_CHAIN_PATH) {
-          clientOptions.tls.certChainPath = process.env.CERT_CHAIN_PATH;
+          clientConfig.tls.certChainPath = process.env.CERT_CHAIN_PATH;
         }
         }
 
 
         if (process.env.SERVER_NAME) {
         if (process.env.SERVER_NAME) {
-          clientOptions.tls.serverName = process.env.SERVER_NAME;
+          clientConfig.tls.serverName = process.env.SERVER_NAME;
         }
         }
       }
       }
       // create the client
       // create the client
-      const milvusClient: MilvusClient = new MilvusClient(clientOptions);
+      const milvusClient: MilvusClient = new MilvusClient(clientConfig);
 
 
       try {
       try {
         // Attempt to connect to the Milvus server
         // Attempt to connect to the Milvus server
@@ -114,9 +105,8 @@ export class MilvusService {
 
 
       // Return the address and the database (if it exists, otherwise return 'default')
       // Return the address and the database (if it exists, otherwise return 'default')
       return {
       return {
-        address,
-        database: db,
         clientId: milvusClient.clientId,
         clientId: milvusClient.clientId,
+        database: db,
       };
       };
     } catch (error) {
     } catch (error) {
       // If any error occurs, clear the cache and throw the error
       // If any error occurs, clear the cache and throw the error

+ 13 - 0
server/src/types/index.ts

@@ -8,3 +8,16 @@ export {
 export * from './collections.type';
 export * from './collections.type';
 export * from './partitions.type';
 export * from './partitions.type';
 export * from './users.type';
 export * from './users.type';
+
+export type AuthReq = {
+  username: string;
+  password: string;
+  address: string;
+  token: string;
+  database: string;
+};
+
+export type AuthObject = {
+  clientId: string;
+  database: string;
+};

Some files were not shown because too many files changed in this diff