Data.tsx 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493
  1. import {
  2. createContext,
  3. useCallback,
  4. useContext,
  5. useEffect,
  6. useState,
  7. useRef,
  8. } from 'react';
  9. import { io, Socket } from 'socket.io-client';
  10. import { authContext } from '@/context';
  11. import {
  12. CollectionService,
  13. MilvusService,
  14. DatabaseService,
  15. isElectron,
  16. url,
  17. } from '@/http';
  18. import {
  19. IndexCreateParam,
  20. IndexManageParam,
  21. } from '@/pages/databases/collections/overview/Types';
  22. import { DataContextType } from './Types';
  23. import {
  24. CollectionObject,
  25. CollectionFullObject,
  26. DatabaseObject,
  27. } from '@server/types';
  28. import { WS_EVENTS, WS_EVENTS_TYPE, LOADING_STATE } from '@server/utils/Const';
  29. import { DEFAULT_TREE_WIDTH, ATTU_UI_TREE_WIDTH } from '@/consts';
  30. import { checkIndexing, checkLoading } from '@server/utils/Shared';
  31. export const dataContext = createContext<DataContextType>({
  32. loading: true,
  33. loadingDatabases: true,
  34. collections: [],
  35. setCollections: () => {},
  36. database: '',
  37. setDatabase: () => {},
  38. databases: [],
  39. setDatabaseList: () => {},
  40. createDatabase: async () => {},
  41. dropDatabase: async () => {},
  42. fetchDatabases: async () => {
  43. return [];
  44. },
  45. fetchCollections: async () => {},
  46. fetchCollection: async () => {
  47. return {} as CollectionFullObject;
  48. },
  49. createCollection: async () => {
  50. return {} as CollectionFullObject;
  51. },
  52. loadCollection: async () => {},
  53. releaseCollection: async () => {},
  54. renameCollection: async () => {
  55. return {} as CollectionFullObject;
  56. },
  57. duplicateCollection: async () => {
  58. return {} as CollectionFullObject;
  59. },
  60. dropCollection: async () => {},
  61. createIndex: async () => {
  62. return {} as CollectionFullObject;
  63. },
  64. dropIndex: async () => {
  65. return {} as CollectionFullObject;
  66. },
  67. createAlias: async () => {
  68. return {} as CollectionFullObject;
  69. },
  70. dropAlias: async () => {
  71. return {} as CollectionFullObject;
  72. },
  73. setProperty: async () => {
  74. return {} as CollectionFullObject;
  75. },
  76. ui: {
  77. tree: {
  78. width: DEFAULT_TREE_WIDTH,
  79. },
  80. },
  81. setUIPref: () => {},
  82. });
  83. const { Provider } = dataContext;
  84. export const DataProvider = (props: { children: React.ReactNode }) => {
  85. // auth context
  86. const { authReq, isAuth, clientId, logout, setAuthReq } =
  87. useContext(authContext);
  88. // UI preferences
  89. const [ui, setUI] = useState({
  90. tree: {
  91. width: DEFAULT_TREE_WIDTH,
  92. },
  93. });
  94. // local data state
  95. const [collections, setCollections] = useState<CollectionObject[]>([]);
  96. const [connected, setConnected] = useState(false);
  97. const [loading, setLoading] = useState(true);
  98. const [loadingDatabases, setLoadingDatabases] = useState(true);
  99. const [database, setDatabase] = useState<string>(authReq.database);
  100. const [databases, setDatabases] = useState<DatabaseObject[]>([]);
  101. // socket ref
  102. const socket = useRef<Socket | null>(null);
  103. // collection state test
  104. const detectLoadingIndexing = useCallback(
  105. (collections: CollectionObject[]) => {
  106. const LoadingOrBuildingCollections = collections.filter(v => {
  107. const isLoading = checkLoading(v);
  108. const isBuildingIndex = checkIndexing(v);
  109. return isLoading || isBuildingIndex;
  110. });
  111. // trigger cron if it has to
  112. if (LoadingOrBuildingCollections.length > 0) {
  113. MilvusService.triggerCron({
  114. name: WS_EVENTS.COLLECTION_UPDATE,
  115. type: WS_EVENTS_TYPE.START,
  116. payload: {
  117. database,
  118. collections: LoadingOrBuildingCollections.map(
  119. c => c.collection_name
  120. ),
  121. },
  122. });
  123. }
  124. },
  125. [database]
  126. );
  127. // Websocket Callback: update single collection
  128. const updateCollections = useCallback(
  129. (updateCollections: CollectionFullObject[]) => {
  130. // check state to see if it is loading or building index, if so, start server cron job
  131. detectLoadingIndexing(updateCollections);
  132. // update single collection
  133. setCollections(prev => {
  134. // update exist collection
  135. const newCollections = prev.map(v => {
  136. const collectionToUpdate = updateCollections.find(c => c.id === v.id);
  137. if (collectionToUpdate) {
  138. return collectionToUpdate;
  139. }
  140. return v;
  141. });
  142. return newCollections;
  143. });
  144. },
  145. [database]
  146. );
  147. // API: fetch databases
  148. const fetchDatabases = async (updateLoading?: boolean) => {
  149. try {
  150. updateLoading && setLoadingDatabases(true);
  151. const newDatabases = await DatabaseService.listDatabases();
  152. // if no database, logout
  153. if (newDatabases.length === 0) {
  154. logout();
  155. }
  156. setDatabases(newDatabases);
  157. return newDatabases;
  158. } finally {
  159. updateLoading && setLoadingDatabases(false);
  160. }
  161. };
  162. // API: create database
  163. const createDatabase = async (params: { db_name: string }) => {
  164. await DatabaseService.createDatabase(params);
  165. await fetchDatabases();
  166. };
  167. // API: delete database
  168. const dropDatabase = async (params: { db_name: string }) => {
  169. await DatabaseService.dropDatabase(params);
  170. const newDatabases = await fetchDatabases();
  171. setDatabase(newDatabases[0].name);
  172. };
  173. // API:fetch collections
  174. const fetchCollections = async () => {
  175. try {
  176. // set loading true
  177. setLoading(true);
  178. // set collections
  179. setCollections([]);
  180. // fetch collections
  181. const res = await CollectionService.getCollections();
  182. // check state
  183. detectLoadingIndexing(res);
  184. // set collections
  185. setCollections(res);
  186. // set loading false
  187. setLoading(false);
  188. } finally {
  189. setLoading(false);
  190. }
  191. };
  192. // API: fetch single collection
  193. const fetchCollection = async (name: string) => {
  194. // fetch collections
  195. const res = await CollectionService.getCollection(name);
  196. // update collection
  197. updateCollections([res]);
  198. return res;
  199. };
  200. // API: create collection
  201. const createCollection = async (data: any) => {
  202. // create collection
  203. const newCollection = await CollectionService.createCollection(data);
  204. // combine new collection with old collections
  205. // sort state by createdTime.
  206. const newCollections = collections.concat(newCollection).sort((a, b) => {
  207. if (a.loadedPercentage === b.loadedPercentage && a.schema && b.schema) {
  208. if (a.schema.hasVectorIndex === b.schema.hasVectorIndex) {
  209. return b.createdTime - a.createdTime;
  210. }
  211. return a.schema.hasVectorIndex ? -1 : 1;
  212. }
  213. return (b.loadedPercentage || 0) - (a.loadedPercentage || 0);
  214. });
  215. // update collection
  216. setCollections(newCollections);
  217. return newCollection;
  218. };
  219. // API: load collection
  220. const loadCollection = async (name: string, param?: any) => {
  221. // load collection
  222. await CollectionService.loadCollection(name, param);
  223. // find the collection in the collections
  224. const collection = collections.find(
  225. v => v.collection_name === name
  226. ) as CollectionFullObject;
  227. // update collection infomation
  228. if (collection) {
  229. collection.loadedPercentage = 0;
  230. collection.loaded = false;
  231. collection.status = LOADING_STATE.LOADING;
  232. }
  233. // update collection, and trigger cron job
  234. updateCollections([collection]);
  235. };
  236. // API: release collection
  237. const releaseCollection = async (name: string) => {
  238. // release collection
  239. await CollectionService.releaseCollection(name);
  240. };
  241. // API: rename collection
  242. const renameCollection = async (name: string, newName: string) => {
  243. // rename collection
  244. const newCollection = await CollectionService.renameCollection(name, {
  245. new_collection_name: newName,
  246. });
  247. updateCollections([newCollection]);
  248. return newCollection;
  249. };
  250. // API: duplicate collection
  251. const duplicateCollection = async (name: string, newName: string) => {
  252. // duplicate collection
  253. const newCollection = await CollectionService.duplicateCollection(name, {
  254. new_collection_name: newName,
  255. });
  256. // inset collection to state
  257. setCollections(prev => [...prev, newCollection]);
  258. return newCollection;
  259. };
  260. // API: drop collection
  261. const dropCollection = async (name: string) => {
  262. // drop collection
  263. const dropped = await CollectionService.dropCollection(name);
  264. if (dropped.data.error_code === 'Success') {
  265. // remove collection from state
  266. setCollections(prev => prev.filter(v => v.collection_name !== name));
  267. }
  268. };
  269. // API: create index
  270. const createIndex = async (param: IndexCreateParam) => {
  271. // create index
  272. const newCollection = await CollectionService.createIndex(param);
  273. // update collection
  274. updateCollections([newCollection]);
  275. return newCollection;
  276. };
  277. // API: drop index
  278. const dropIndex = async (params: IndexManageParam) => {
  279. // drop index
  280. const { data } = await CollectionService.dropIndex(params);
  281. // update collection
  282. updateCollections([data]);
  283. return data;
  284. };
  285. // API: create alias
  286. const createAlias = async (collectionName: string, alias: string) => {
  287. // create alias
  288. const newCollection = await CollectionService.createAlias(collectionName, {
  289. alias,
  290. });
  291. // update collection
  292. updateCollections([newCollection]);
  293. return newCollection;
  294. };
  295. // API: drop alias
  296. const dropAlias = async (collectionName: string, alias: string) => {
  297. // drop alias
  298. const { data } = await CollectionService.dropAlias(collectionName, {
  299. alias,
  300. });
  301. // update collection
  302. updateCollections([data]);
  303. return data;
  304. };
  305. // API: set property
  306. const setProperty = async (
  307. collectionName: string,
  308. key: string,
  309. value: any
  310. ) => {
  311. // set property
  312. const newCollection = await CollectionService.setProperty(collectionName, {
  313. [key]: value,
  314. });
  315. // update existing collection
  316. updateCollections([newCollection]);
  317. return newCollection;
  318. };
  319. // set UI preferences
  320. const setUIPref = (pref: DataContextType['ui']) => {
  321. setUI(pref);
  322. localStorage.setItem(ATTU_UI_TREE_WIDTH, String(pref.tree.width));
  323. };
  324. // load UI preferences
  325. useEffect(() => {
  326. const storedWidth = Number(localStorage.getItem(ATTU_UI_TREE_WIDTH));
  327. if (storedWidth) {
  328. setUI(prevUI => ({
  329. ...prevUI,
  330. tree: {
  331. ...prevUI.tree,
  332. width: storedWidth,
  333. },
  334. }));
  335. }
  336. }, []);
  337. useEffect(() => {
  338. const clear = () => {
  339. // clear collections
  340. setCollections([]);
  341. // clear database
  342. setDatabases([]);
  343. // set connected to false
  344. setConnected(false);
  345. // remove all listeners when component unmount
  346. socket.current?.offAny();
  347. socket.current?.disconnect();
  348. };
  349. if (isAuth) {
  350. // update database get from auth
  351. setDatabase(authReq.database);
  352. // create socket
  353. socket.current = isElectron ? io(url as string) : io();
  354. // register client
  355. socket.current.emit(WS_EVENTS.REGISTER, clientId);
  356. socket.current.on('connect', async () => {
  357. // console.info('--- ws connected ---', clientId);
  358. // fetch db
  359. await fetchDatabases(true);
  360. // set connected to trues
  361. setConnected(true);
  362. });
  363. // handle disconnect
  364. socket.current.on('disconnect', () => {
  365. // Set connected to false
  366. setConnected(false);
  367. });
  368. // handle error
  369. socket.current.on('error', error => {
  370. socket.current?.disconnect();
  371. });
  372. } else {
  373. clear();
  374. }
  375. return () => {
  376. clear();
  377. };
  378. }, [isAuth]);
  379. useEffect(() => {
  380. if (connected) {
  381. // clear data
  382. setCollections([]);
  383. // remove all listeners
  384. socket.current?.offAny();
  385. // listen to backend collection event
  386. socket.current?.on(WS_EVENTS.COLLECTION_UPDATE, updateCollections);
  387. // fetch db
  388. fetchCollections();
  389. }
  390. return () => {
  391. // remove all listeners when component unmount
  392. socket.current?.offAny();
  393. };
  394. }, [updateCollections, connected]);
  395. useEffect(() => {
  396. setAuthReq({ ...authReq, database });
  397. }, [database]);
  398. return (
  399. <Provider
  400. value={{
  401. loading,
  402. loadingDatabases,
  403. collections,
  404. setCollections,
  405. database,
  406. databases,
  407. setDatabase,
  408. setDatabaseList: setDatabases,
  409. createDatabase,
  410. dropDatabase,
  411. fetchDatabases,
  412. fetchCollections,
  413. fetchCollection,
  414. createCollection,
  415. loadCollection,
  416. releaseCollection,
  417. renameCollection,
  418. duplicateCollection,
  419. dropCollection,
  420. createIndex,
  421. dropIndex,
  422. createAlias,
  423. dropAlias,
  424. setProperty,
  425. ui,
  426. setUIPref,
  427. }}
  428. >
  429. {props.children}
  430. </Provider>
  431. );
  432. };