AuthForm.tsx 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354
  1. import React, { useContext, useMemo, useState } from 'react';
  2. import { makeStyles, Theme, Typography, Menu } from '@material-ui/core';
  3. import { useTranslation } from 'react-i18next';
  4. import CustomButton from '@/components/customButton/CustomButton';
  5. import CustomInput from '@/components/customInput/CustomInput';
  6. import { useFormValidation } from '@/hooks';
  7. import { formatForm } from '@/utils';
  8. import { useNavigate } from 'react-router-dom';
  9. import { rootContext, authContext, dataContext } from '@/context';
  10. import { MILVUS_CLIENT_ID } from '@/consts';
  11. import { CustomRadio } from '@/components/customRadio/CustomRadio';
  12. import Icons from '@/components/icons/Icons';
  13. import CustomToolTip from '@/components/customToolTip/CustomToolTip';
  14. import ConnectHistory from './ConnectHistory';
  15. import CustomIconButton from '@/components/customButton/CustomIconButton';
  16. const useStyles = makeStyles((theme: Theme) => ({
  17. wrapper: {
  18. display: 'flex',
  19. flexDirection: 'column',
  20. padding: theme.spacing(0, 3),
  21. position: 'relative',
  22. },
  23. titleWrapper: {
  24. textAlign: 'left',
  25. alignSelf: 'flex-start',
  26. padding: theme.spacing(3, 0),
  27. '& svg': {
  28. fontSize: 15,
  29. marginLeft: theme.spacing(0.5),
  30. },
  31. },
  32. input: {
  33. margin: theme.spacing(0.5, 0, 0),
  34. },
  35. toggle: {
  36. display: 'flex',
  37. width: '100%',
  38. justifyContent: 'flex-start',
  39. },
  40. star: {
  41. position: 'absolute',
  42. top: -48,
  43. right: -8,
  44. marginTop: theme.spacing(1),
  45. alignItems: 'center',
  46. height: '32px',
  47. lineHeight: '32px',
  48. color: '#333',
  49. background: '#f1f1f1',
  50. padding: theme.spacing(0.5, 0, 0.5, 1),
  51. fontSize: 13,
  52. display: 'block',
  53. width: '132px',
  54. textDecoration: 'none',
  55. marginRight: theme.spacing(1),
  56. fontWeight: 500,
  57. '&:hover': {
  58. fontWeight: 'bold',
  59. },
  60. },
  61. menu: {
  62. '& ul': {
  63. padding: '0',
  64. maxHeight: '400px',
  65. overflowY: 'auto',
  66. },
  67. },
  68. icon: {
  69. verticalAlign: '-5px',
  70. marginRight: theme.spacing(1),
  71. },
  72. }));
  73. export const AuthForm = (props: any) => {
  74. // styles
  75. const classes = useStyles();
  76. // context
  77. const { openSnackBar } = useContext(rootContext);
  78. const { authReq, setAuthReq, login } = useContext(authContext);
  79. const { setDatabase } = useContext(dataContext);
  80. // i18n
  81. const { t: commonTrans } = useTranslation();
  82. const attuTrans = commonTrans('attu');
  83. const { t: btnTrans } = useTranslation('btn');
  84. const { t: warningTrans } = useTranslation('warning');
  85. const { t: successTrans } = useTranslation('success');
  86. const { t: dbTrans } = useTranslation('database');
  87. // hooks
  88. const navigate = useNavigate();
  89. // UI states
  90. const [withPass, setWithPass] = useState(authReq.username.length > 0);
  91. const [anchorEl, setAnchorEl] = React.useState<null | HTMLElement>(null);
  92. // form validation
  93. const checkedForm = useMemo(() => {
  94. return formatForm(authReq);
  95. }, [authReq]);
  96. const { validation, checkIsValid } = useFormValidation(checkedForm);
  97. // handle input change
  98. const handleInputChange = (
  99. key: 'address' | 'username' | 'password' | 'database' | 'token',
  100. value: string | boolean
  101. ) => {
  102. setAuthReq(v => ({ ...v, [key]: value }));
  103. };
  104. // UI handlers
  105. const handleClick = (event: React.MouseEvent<HTMLButtonElement>) => {
  106. setAnchorEl(event.currentTarget);
  107. };
  108. //
  109. const handleMenuClose = () => {
  110. setAnchorEl(null);
  111. };
  112. // handle auth toggle
  113. const handleEnableAuth = (val: boolean) => {
  114. setWithPass(val);
  115. };
  116. // connect
  117. const handleConnect = async (event: React.FormEvent) => {
  118. event.preventDefault();
  119. try {
  120. // login
  121. const result = await login(authReq);
  122. // set database
  123. setDatabase(authReq.database);
  124. // success message
  125. openSnackBar(successTrans('connect'));
  126. // save clientId to local storage
  127. window.localStorage.setItem(MILVUS_CLIENT_ID, result.clientId);
  128. // redirect to homepage
  129. navigate('/');
  130. } catch (error: any) {
  131. // if not authorized, show auth inputs
  132. if (error.response.data.message.includes('UNAUTHENTICATED')) {
  133. handleEnableAuth(true);
  134. }
  135. }
  136. };
  137. // connect history clicked
  138. const onConnectHistoryClicked = (connection: any) => {
  139. console.log('connection', connection);
  140. handleMenuClose();
  141. };
  142. // is button should be disabled
  143. const btnDisabled = useMemo(() => {
  144. return authReq.address.trim().length === 0;
  145. }, [authReq.address]);
  146. return (
  147. <form onSubmit={handleConnect}>
  148. <section className={classes.wrapper}>
  149. <div className={classes.titleWrapper}>
  150. <Typography variant="h4" component="h4">
  151. {attuTrans.connectTitle}
  152. <CustomToolTip title={attuTrans.connectionTip}>
  153. <Icons.info />
  154. </CustomToolTip>
  155. </Typography>
  156. </div>
  157. {/* address */}
  158. <CustomInput
  159. type="text"
  160. textConfig={{
  161. label: attuTrans.address,
  162. key: 'address',
  163. onChange: (val: string) => handleInputChange('address', val),
  164. variant: 'filled',
  165. className: classes.input,
  166. placeholder: attuTrans.address,
  167. fullWidth: true,
  168. InputProps: {
  169. endAdornment: (
  170. <CustomIconButton onClick={handleClick}>
  171. <Icons.dropdown />
  172. </CustomIconButton>
  173. ),
  174. },
  175. validations: [
  176. {
  177. rule: 'require',
  178. errorText: warningTrans('required', {
  179. name: attuTrans.address,
  180. }),
  181. },
  182. ],
  183. defaultValue: authReq.address,
  184. }}
  185. checkValid={checkIsValid}
  186. validInfo={validation}
  187. key={attuTrans.address}
  188. />
  189. {/* db */}
  190. <CustomInput
  191. type="text"
  192. textConfig={{
  193. label: `Milvus ${dbTrans('database')} ${attuTrans.optional}`,
  194. key: 'database',
  195. onChange: (value: string) => handleInputChange('database', value),
  196. variant: 'filled',
  197. className: classes.input,
  198. placeholder: dbTrans('database'),
  199. fullWidth: true,
  200. defaultValue: authReq.database,
  201. }}
  202. checkValid={checkIsValid}
  203. validInfo={validation}
  204. key={attuTrans.database}
  205. />
  206. {/* toggle auth */}
  207. <div className={classes.toggle}>
  208. <CustomRadio
  209. checked={withPass}
  210. label={attuTrans.authentication}
  211. handleChange={handleEnableAuth}
  212. />
  213. </div>
  214. {/* token */}
  215. {withPass && (
  216. <>
  217. <CustomInput
  218. type="text"
  219. textConfig={{
  220. label: `${attuTrans.token} ${attuTrans.optional} `,
  221. key: 'token',
  222. onChange: (val: string) => handleInputChange('token', val),
  223. variant: 'filled',
  224. className: classes.input,
  225. placeholder: attuTrans.token,
  226. fullWidth: true,
  227. defaultValue: authReq.token,
  228. }}
  229. checkValid={checkIsValid}
  230. validInfo={validation}
  231. key={attuTrans.token}
  232. />
  233. {/* user */}
  234. <CustomInput
  235. type="text"
  236. textConfig={{
  237. label: `${attuTrans.username} ${attuTrans.optional}`,
  238. key: 'username',
  239. onChange: (value: string) =>
  240. handleInputChange('username', value),
  241. variant: 'filled',
  242. className: classes.input,
  243. placeholder: attuTrans.username,
  244. fullWidth: true,
  245. defaultValue: authReq.username,
  246. }}
  247. checkValid={checkIsValid}
  248. validInfo={validation}
  249. key={attuTrans.username}
  250. />
  251. {/* pass */}
  252. <CustomInput
  253. type="text"
  254. textConfig={{
  255. label: `${attuTrans.password} ${attuTrans.optional}`,
  256. key: 'password',
  257. onChange: (value: string) =>
  258. handleInputChange('password', value),
  259. variant: 'filled',
  260. className: classes.input,
  261. placeholder: attuTrans.password,
  262. fullWidth: true,
  263. type: 'password',
  264. defaultValue: authReq.password,
  265. }}
  266. checkValid={checkIsValid}
  267. validInfo={validation}
  268. key={attuTrans.password}
  269. />
  270. </>
  271. )}
  272. {/* <div className={classes.toggle}>
  273. <CustomRadio
  274. defaultChecked={withPrometheus}
  275. label={attuTrans.prometheus}
  276. handleChange={setWithPrometheus}
  277. />
  278. </div>
  279. {withPrometheus &&
  280. prometheusConfigs.map(v => (
  281. <CustomInput
  282. type="text"
  283. textConfig={v}
  284. checkValid={checkIsValid}
  285. validInfo={validation}
  286. key={v.label}
  287. />
  288. ))} */}
  289. <CustomButton type="submit" variant="contained" disabled={btnDisabled}>
  290. {btnTrans('connect')}
  291. </CustomButton>
  292. </section>
  293. <Menu
  294. anchorEl={anchorEl}
  295. keepMounted
  296. className={classes.menu}
  297. anchorOrigin={{
  298. vertical: 'bottom',
  299. horizontal: 'right',
  300. }}
  301. transformOrigin={{
  302. vertical: 'top',
  303. horizontal: 'right',
  304. }}
  305. open={Boolean(anchorEl)}
  306. onClose={handleMenuClose}
  307. getContentAnchorEl={null}
  308. >
  309. <ConnectHistory
  310. data={{
  311. url: 'http://102.232.234.234:19121',
  312. database: 'default',
  313. time: '2021-09-09 12:00:00',
  314. }}
  315. onClick={onConnectHistoryClicked}
  316. />
  317. <ConnectHistory
  318. data={{
  319. url: 'http://102.232.234.234:19121',
  320. database: 'default2',
  321. time: '2021-09-09 12:00:00',
  322. }}
  323. onClick={onConnectHistoryClicked}
  324. />
  325. </Menu>
  326. </form>
  327. );
  328. };