diff --git a/config-ui/src/App.tsx b/config-ui/src/App.tsx index 11757250e680..433c7a16fd81 100644 --- a/config-ui/src/App.tsx +++ b/config-ui/src/App.tsx @@ -16,8 +16,11 @@ * */ +import { useEffect } from 'react'; import { createBrowserRouter, Navigate, RouterProvider, json } from 'react-router-dom'; +import { useAppDispatch, useAppSelector } from '@/app/hook'; +import { init, selectStatus } from '@/features'; import { PageLoading } from '@/components'; import { ConnectionHomePage, @@ -100,4 +103,17 @@ const router = createBrowserRouter([ }, ]); -export const App = () => } />; +export const App = () => { + const dispatch = useAppDispatch(); + const status = useAppSelector(selectStatus); + + useEffect(() => { + dispatch(init()); + }, []); + + if (['idle', 'loading'].includes(status)) { + return ; + } + + return } />; +}; diff --git a/config-ui/src/features/connections/slice.ts b/config-ui/src/features/connections/slice.ts index 66b534d1f617..f0494f929aaf 100644 --- a/config-ui/src/features/connections/slice.ts +++ b/config-ui/src/features/connections/slice.ts @@ -20,7 +20,6 @@ import { createSlice, createAsyncThunk } from '@reduxjs/toolkit'; import { flatten } from 'lodash'; import API from '@/api'; -import type { ConnectionForm } from '@/api/connection/types'; import { RootState } from '@/app/store'; import { PluginConfig } from '@/plugins'; import { IConnection, IConnectionStatus } from '@/types'; @@ -29,8 +28,10 @@ import { transformConnection } from './utils'; const initialState: { connections: IConnection[]; + status: 'idle' | 'loading' | 'success' | 'failed'; } = { connections: [], + status: 'idle', }; export const init = createAsyncThunk('connections/init', async () => { @@ -77,7 +78,21 @@ export const addConnection = createAsyncThunk('connections/addConnection', async return transformConnection(plugin, connection); }); -export const updateConnection = createAsyncThunk('connections/updateConnection', async (payload: ConnectionForm) => {}); +export const updateConnection = createAsyncThunk( + 'connections/updateConnection', + async ({ plugin, connectionId, ...payload }: any) => { + const connection = await API.connection.update(plugin, connectionId, payload); + return transformConnection(plugin, connection); + }, +); + +export const removeConnection = createAsyncThunk( + 'connections/removeConnection', + async ({ plugin, connectionId }: any) => { + await API.connection.remove(plugin, connectionId); + return `${plugin}-${connectionId}`; + }, +); export const slice = createSlice({ name: 'connections', @@ -85,8 +100,12 @@ export const slice = createSlice({ reducers: {}, extraReducers(builder) { builder + .addCase(init.pending, (state) => { + state.status = 'loading'; + }) .addCase(init.fulfilled, (state, action) => { state.connections = action.payload; + state.status = 'success'; }) .addCase(fetchConnections.fulfilled, (state, action) => { state.connections = state.connections.concat(action.payload.connections); @@ -94,6 +113,17 @@ export const slice = createSlice({ .addCase(addConnection.fulfilled, (state, action) => { state.connections.push(action.payload); }) + .addCase(updateConnection.fulfilled, (state, action) => { + state.connections = state.connections.map((cs) => { + if (cs.unique === action.payload.unique) { + return action.payload; + } + return cs; + }); + }) + .addCase(removeConnection.fulfilled, (state, action) => { + state.connections = state.connections.filter((cs) => cs.unique !== action.payload); + }) .addCase(testConnection.pending, (state, action) => { const existingConnection = state.connections.find((cs) => cs.unique === action.meta.arg.unique); if (existingConnection) { @@ -113,6 +143,8 @@ export const {} = slice.actions; export default slice.reducer; +export const selectStatus = (state: RootState) => state.connections.status; + export const selectAllConnections = (state: RootState) => state.connections.connections; export const selectConnections = (state: RootState, plugin: string) => diff --git a/config-ui/src/pages/connection/detail/index.tsx b/config-ui/src/pages/connection/detail/index.tsx index 2ce5e207ed31..e040c82ca367 100644 --- a/config-ui/src/pages/connection/detail/index.tsx +++ b/config-ui/src/pages/connection/detail/index.tsx @@ -21,9 +21,9 @@ import { useParams, useNavigate, Link } from 'react-router-dom'; import { Button, Intent } from '@blueprintjs/core'; import API from '@/api'; -import { useAppSelector } from '@/app/hook'; +import { useAppDispatch, useAppSelector } from '@/app/hook'; import { PageHeader, Buttons, Dialog, IconButton, Table, Message, toast } from '@/components'; -import { selectConnection } from '@/features'; +import { selectConnection, removeConnection } from '@/features'; import { useTips, useRefreshData } from '@/hooks'; import ClearImg from '@/images/icons/clear.svg'; import { @@ -64,7 +64,9 @@ export const ConnectionDetailPage = () => { const { plugin, id } = useParams() as { plugin: string; id: string }; const connectionId = +id; + const dispatch = useAppDispatch(); const connection = useAppSelector((state) => selectConnection(state, `${plugin}-${connectionId}`)) as IConnection; + const navigate = useNavigate(); const { setTips } = useTips(); const { ready, data } = useRefreshData( @@ -102,7 +104,7 @@ export const ConnectionDetailPage = () => { const [, res] = await operator( async () => { try { - await API.connection.remove(plugin, connectionId); + await dispatch(removeConnection({ plugin, connectionId })); return { status: 'success' }; } catch (err: any) { const { status, data } = err.response; diff --git a/config-ui/src/plugins/components/connection-form/index.tsx b/config-ui/src/plugins/components/connection-form/index.tsx index cb7ffa549153..1a6d93a65265 100644 --- a/config-ui/src/plugins/components/connection-form/index.tsx +++ b/config-ui/src/plugins/components/connection-form/index.tsx @@ -23,6 +23,7 @@ import { pick } from 'lodash'; import API from '@/api'; import { useAppDispatch, useAppSelector } from '@/app/hook'; import { ExternalLink, Buttons } from '@/components'; +import { addConnection, updateConnection } from '@/features'; import { selectConnection } from '@/features/connections'; import { PluginConfig, PluginConfigType } from '@/plugins'; import { operator } from '@/utils'; @@ -82,7 +83,9 @@ export const ConnectionForm = ({ plugin, connectionId, onSuccess }: Props) => { const handleSave = async () => { const [success, res] = await operator( () => - !connectionId ? API.connection.create(plugin, values) : API.connection.update(plugin, connectionId, values), + !connectionId + ? dispatch(addConnection({ plugin, ...values })).unwrap() + : dispatch(updateConnection({ plugin, connectionId, ...values })).unwrap(), { setOperating, formatMessage: () => (!connectionId ? 'Create a New Connection Successful.' : 'Update Connection Successful.'), diff --git a/config-ui/src/routes/layout/layout.tsx b/config-ui/src/routes/layout/layout.tsx index cc5b4b260e45..de136ae4d404 100644 --- a/config-ui/src/routes/layout/layout.tsx +++ b/config-ui/src/routes/layout/layout.tsx @@ -16,14 +16,12 @@ * */ -import { useEffect, useRef } from 'react'; +import { useRef } from 'react'; import { useLoaderData, Outlet, useNavigate, useLocation } from 'react-router-dom'; import { CSSTransition } from 'react-transition-group'; import { Menu, MenuItem, Navbar, Alignment } from '@blueprintjs/core'; -import { useAppDispatch } from '@/app/hook'; import { Logo, ExternalLink, IconButton } from '@/components'; -import { init } from '@/features'; import { DOC_URL } from '@/release'; import { TipsContextProvider, TipsContextConsumer } from '@/store'; @@ -41,12 +39,6 @@ import './tips-transition.css'; export const Layout = () => { const { version } = useLoaderData() as Awaited>; - const dispatch = useAppDispatch(); - - useEffect(() => { - dispatch(init()); - }, []); - const navigate = useNavigate(); const { pathname } = useLocation();