Browse Source

feat: sso登录

tuonian 1 year ago
parent
commit
12cc535a14

File diff suppressed because it is too large
+ 1 - 1
dist/assets/index-553378ae.js


File diff suppressed because it is too large
+ 0 - 0
dist/assets/index-b9d29322.css


+ 2 - 2
dist/index.html

@@ -4,7 +4,7 @@
     <meta name="viewport" content="width=device-width, initial-scale=1.0">
     <title>NginxUI</title>
     <script type="application/javascript" src="./config.js"></script>
-    <script crossorigin="">import('/nginx-ui/assets/index-2d0a5350.js').finally(() => {
+    <script crossorigin="">import('/nginx-ui/assets/index-553378ae.js').finally(() => {
             
     const qiankunLifeCycle = window.moudleQiankunAppLifeCycles && window.moudleQiankunAppLifeCycles['nginx-ui'];
     if (qiankunLifeCycle) {
@@ -15,7 +15,7 @@
     }
   
           })</script>
-    <link rel="stylesheet" href="/nginx-ui/assets/index-649708a6.css">
+    <link rel="stylesheet" href="/nginx-ui/assets/index-b9d29322.css">
   </head>
   <body>
     <div id="nginx_ui_root"></div>

+ 1 - 0
package.json

@@ -23,6 +23,7 @@
     "antd": "4.x",
     "artt-template": "^4.13.6",
     "classnames": "^2.3.2",
+    "dayjs": "^1.11.9",
     "events": "^3.3.0",
     "history": "^5.3.0",
     "install": "^0.13.0",

+ 1 - 4
src/App.tsx

@@ -9,8 +9,7 @@ import 'antd/dist/antd.css'
 import 'planning-tools/dist/umd/planning-tools.min.css'
 
 function App() {
-
-  return (
+    return (
     <>
       <Provider store={store}>
         <PersistGate loading persistor={persistor}>
@@ -18,9 +17,7 @@ function App() {
             <MyRouter />
           </ConfigProvider>
         </PersistGate>
-
       </Provider>
-
     </>
   )
 }

+ 1 - 1
src/api/nginx.ts

@@ -30,7 +30,7 @@ export const NginxApis= {
   refreshHttp: (nginx: RefreshHttpData) => {
     return request.post(`/nginx/${nginx.id}/http/refresh`, nginx, { timeout: 60000 })
   },
-  getNginx: (id:number) => request.get<BaseResp<{nginx: INginx, servers: IServerHost[]}>>(`/nginx/${id}`),
+  getNginx: (id:number | string) => request.get<BaseResp<{nginx: INginx, servers: IServerHost[]}>>(`/nginx/${id}`),
   delNginx: (id:number) => request.delete(`/nginx/${id}`),
   status: (id:number) => request.post(`/nginx/${id}/status`, { }, { timeout: 60000 }),
   startNginx: (id:number) => request.post(`/nginx/${id}/start`, { }, { timeout: 60000 }),

+ 2 - 1
src/api/request.ts

@@ -63,7 +63,8 @@ request.interceptors.response.use((resp: AxiosResponse<BaseResp>)=>{
     errData.msg = 'request fail'
   }
   (!disableErrorMsg)&& Message.error(errData.msg)
-  if (error.response.statusCode == 401){
+  console.log('status', error.response?.status)
+  if (error.response.status == 401){
     store.dispatch(UserActions.clearUser())
   }
   return Promise.reject(errData)

+ 1 - 1
src/api/user.ts

@@ -24,6 +24,6 @@ export const LoginApis = {
     signUp: (data: RegisterReq) => request.post('/user/register', data),
     userinfo: () => request.get('/user/info', { disableErrorMsg: true } as never),
     oauth2Url: ()=> request.get('/oauth2'),
-    oauth2Callback: (data: SSOReq) => request.post('/oauth2/callback', data)
+    oauth2Callback: (data: SSOReq) => request.post('/oauth2/callback', data, { disableErrorMsg: true } as never)
 
 }

+ 15 - 0
src/components/empty/index.less

@@ -0,0 +1,15 @@
+.empty-loading{
+  height: 100%;
+  width: 100%;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  .ant-spin-dot{
+    font-size: 30px;
+  }
+  .hint-msg{
+    font-weight: lighter;
+    color: #666666;
+  }
+}

+ 12 - 0
src/components/empty/index.tsx

@@ -0,0 +1,12 @@
+import {Spin} from "antd";
+import './index.less'
+
+
+export const EmptyLoading = ()=>{
+    return (
+        <div className="empty-loading">
+            <Spin></Spin>
+            <div className="hint-msg">加载中,请稍等...</div>
+        </div>
+    )
+}

+ 4 - 0
src/models/user.ts

@@ -7,4 +7,8 @@ export type User = {
     nickname: string
     roles?: string
     remark?: string
+    /**
+     * 缓存时间
+     */
+    timestamp: number
 }

+ 15 - 8
src/pages/login/index.tsx

@@ -9,8 +9,7 @@ import {useAppDispatch} from "../../store";
 import {UserActions} from "../../store/slice/user.ts";
 import {Message} from "planning-tools";
 import {useNavigate } from "react-router";
-import {useQuery} from "../../utils";
-import {useSSOLogin} from "./sso.ts";
+import {cacheTo, parseQuery, useQuery} from "../../utils";
 
 const AccountPanel = ()=>{
 
@@ -60,16 +59,24 @@ const AccountPanel = ()=>{
 export const LoginPage = ()=>{
 
     const [activeKey,setActiveKey] = useState('account')
-    const [loading] = useSSOLogin()
+    const [loading,setLoading] = useState(false)
+    const { query } = parseQuery()
+    console.log('query', query)
 
     const fetchSSO = ()=>{
+        setLoading(true)
+
         LoginApis.oauth2Url()
             .then(({data})=>{
-                if (data.code == 0){
-                    window.location.href = data.data.redirect_url
-                }else {
-                    Message.warning(data.msg)
-                }
+                cacheTo(query?.to);
+                window.location.href = data.data.redirect_url
+            })
+            .catch(e=>{
+                setActiveKey('account');
+                console.log('fetchSSO data fail', e)
+            })
+            .finally(()=>{
+                setLoading(false)
             })
     }
 

+ 0 - 38
src/pages/login/sso.ts

@@ -1,38 +0,0 @@
-import {useQuery} from "../../utils";
-import {LoginApis, SSOReq} from "../../api/user.ts";
-import {useNavigate} from "react-router";
-import {useEffect, useState} from "react";
-import {Message} from "planning-tools";
-
-export const useSSOLogin = ()=>{
-  const ssoData = useQuery<SSOReq & {to?: string}>()
-  const navigate = useNavigate()
-  const [loading,setLoading] = useState(false)
-
-  const onSSOCallback = (data: SSOReq)=>{
-    setLoading(true);
-    LoginApis.oauth2Callback(data)
-      .then(({data})=>{
-        if (data.code === 0){
-          Message.success(data.msg);
-          navigate(ssoData?.to || '/')
-        }else {
-          Message.warning(data.msg)
-        }
-      })
-      .finally(()=>{
-        setLoading(false)
-      })
-  }
-
-
-  useEffect(()=>{
-    if (ssoData?.code){
-      onSSOCallback(ssoData)
-    }
-    console.log('ssoData', ssoData)
-  },[ssoData])
-
-  return [loading]
-
-}

+ 52 - 0
src/pages/login/sso.tsx

@@ -0,0 +1,52 @@
+import {LoginApis, SSOReq} from "../../api/user.ts";
+import {useEffect, useState} from "react";
+import {Message} from "planning-tools";
+import {getFirst, getLastTo, parseQuery} from "../../utils";
+import {EmptyLoading} from "../../components/empty";
+
+/**
+ * 对接SSO登录
+ * @param children
+ * @constructor
+ */
+export const SSOWrapper = ({children}: any)=>{
+  const [loading,setLoading] = useState(true)
+
+  const onSSOCallback = ()=>{
+    const { query, url} = parseQuery<SSOReq & {to?: string}>() ?? {}
+    if (!query?.code){
+      setLoading(false)
+      return
+    }
+    Object.keys(query).forEach(k=>{
+      query[k] = getFirst(query[k])
+    })
+    setLoading(true);
+    LoginApis.oauth2Callback(query)
+        .then(({data})=>{
+          if (data.code === 0){
+            Message.success(data.msg);
+          }else {
+            Message.warning(data.msg);
+          }
+        })
+        .catch(e=>{
+          console.log('oauth2Callback error',e)
+          Message.error('登录失败,请重新登录!');
+        })
+        .finally(()=>{
+          const lastTo = getLastTo();
+          window.location.href = `${url}#${lastTo}`;
+          setLoading(false)
+        })
+  }
+
+  useEffect(()=>{
+    onSSOCallback()
+  },[])
+
+  if (loading){
+   return (<EmptyLoading />)
+  }
+  return children
+}

+ 2 - 0
src/pages/nginx/components/EditNginxBtn.tsx

@@ -16,6 +16,8 @@ import {nginxPrefix} from "../../../routes/routes.tsx";
 type IProps = {
   nginx: INginx
 }
+
+
 export const EditNginxBtn = ({nginx}: IProps)=>{
 
   const [loading,setLoading] = useState(false)

+ 2 - 10
src/pages/nginx/index.tsx

@@ -16,6 +16,7 @@ import {NavLink} from "react-router-dom";
 import {BackButton} from "../../components/BackButton.tsx";
 import {StopStartButton} from "./components/StopStartButton.tsx";
 import './components'
+import {useNginx} from "./utils/nginx.ts";
 
 /**
  * nginx配置首页
@@ -27,7 +28,7 @@ export const Nginx = ({children}: any)=>{
   const location = useLocation();
   const params = useParams<NginxRouteParams>()
   const navigate = useNavigate()
-  const current = useAppSelector(state => state.nginx.current);
+  const [current] = useNginx(params.id);
   const server = useAppSelector(state => state.nginx.server);
   const servers = useAppSelector(state => state.nginx.servers)
 
@@ -36,17 +37,8 @@ export const Nginx = ({children}: any)=>{
 
   const dispatch = useAppDispatch();
 
-
   console.log('children',children,params)
 
-  useEffect(()=>{
-    if (!current || String(current.id) != params.id){
-      navigate(-1)
-    }else {
-      console.log('nginx data', current, params.id)
-    }
-  },[params.id])
-
   useEffect(()=>{
     setActiveKey(location.pathname)
     console.log('location changed ', location)

+ 3 - 3
src/pages/nginx/list.tsx

@@ -9,8 +9,8 @@ import {INginx} from "../../models/nginx.ts";
 import {NginxApis} from "../../api/nginx.ts";
 import {AutoForm, AutoFormInstance, Message, Notify} from "planning-tools";
 import {useFormConfig} from "./config.tsx";
-import {DeleteOutlined, PlusOutlined, SyncOutlined} from "@ant-design/icons";
-import {EditNginxBtn} from "./components/EditNginxBtn.tsx";
+import {DeleteOutlined, EditOutlined, PlusOutlined, SyncOutlined} from "@ant-design/icons";
+import {Link} from "react-router-dom";
 
 export const NginxList = ()=>{
 
@@ -94,7 +94,7 @@ export const NginxList = ()=>{
   const renderOperations = (data: INginx)=>{
 
     return (<>
-      <EditNginxBtn nginx={data} />
+      <Link to={`/nginx/${data.id}`}><EditOutlined /></Link>
       <Button onClick={()=>onRemoveNginx(data)} danger type="text" icon={<DeleteOutlined />}/>
     </>)
 

+ 47 - 0
src/pages/nginx/utils/nginx.ts

@@ -1,6 +1,12 @@
 import {IServerHost} from "../../../models/api.ts";
 import {INginx, INginxServer} from "../../../models/nginx.ts";
 import {renderServer} from "./index.ts";
+import {useAppDispatch, useAppSelector} from "../../../store";
+import {NginxApis} from "../../../api/nginx.ts";
+import {Notify} from "planning-tools";
+import {NginxActions} from "../../../store/slice/nginx.ts";
+import {useEffect, useState} from "react";
+import {useNavigate} from "react-router";
 
 export const createServer = (host: IServerHost)=>{
   const server = JSON.parse(host.serverData) as INginxServer;
@@ -22,4 +28,45 @@ export const createServerHost = (nginx: INginx, server: Partial<INginxServer>)=>
     remark: server.remark || '',
     isStream: server.isStream,
   }
+}
+
+export const useNginx = (id?: string)=>{
+  const current = useAppSelector(state => state.nginx.current);
+  const navigate = useNavigate();
+  const [loading,setLoading] = useState(false)
+  const dispatch = useAppDispatch()
+
+  const toNginx = ()=>{
+    if (!id){
+      return
+    }
+    setLoading(true);
+    NginxApis.getNginx(id)
+        .then(({data})=>{
+          const respData = data.data;
+          if (!respData){
+            Notify.warn('查询失败,请重试!');
+            navigate('/')
+            return
+          }
+          console.log('getNginx', data, loading)
+          dispatch(NginxActions.setCurrent({
+            nginx: respData.nginx,
+            servers: respData.servers
+          }))
+        })
+        .catch(e=>{
+          Notify.warn(e.msg || e.message);
+          navigate('/')
+        })
+        .finally(()=>{
+          setLoading(false)
+        })
+
+  }
+
+  useEffect(()=>{
+    toNginx()
+  },[id])
+  return [current]
 }

+ 0 - 15
src/routes/index.less

@@ -1,15 +0,0 @@
-.empty-loading{
-  height: 100%;
-  width: 100%;
-  display: flex;
-  flex-direction: column;
-  align-items: center;
-  justify-content: center;
-  .ant-spin-dot{
-    font-size: 30px;
-  }
-  .hint-msg{
-    font-weight: lighter;
-    color: #666666;
-  }
-}

+ 30 - 22
src/routes/index.tsx

@@ -21,6 +21,8 @@ import {useLocation, useNavigate} from "react-router";
 import {LoginPage} from "../pages/login";
 import {SignupPage} from "../pages/signup";
 import {UserActions} from "../store/slice/user.ts";
+import dayjs from "dayjs";
+import {SSOWrapper} from "../pages/login/sso.tsx";
 
 /**
  * @author tuonian
@@ -112,7 +114,13 @@ export const RouteWrapper = ({Component, ...props}: RouteWrapperProps) => {
     }
 
     useEffect(() => {
-        if (user?.account) {
+        if (!user?.account){
+            fetchUser()
+            return
+        }
+        const unix = dayjs().unix()
+        const cacheUnix = user.timestamp || 0;
+        if (unix - cacheUnix < 3600) {
             setLoading(false)
         } else {
             fetchUser()
@@ -130,28 +138,28 @@ export const RouteWrapper = ({Component, ...props}: RouteWrapperProps) => {
 }
 
 export const MyRouter = () => {
-
-
     return (
-        <HashRouter basename={'/'}>
-            <Routes>
-                <Route path='/' element={<RouteWrapper Component={NginxList}/>}/>
-                {
-                    nginxRoutes.map((r) => {
-                        return (
-                            <Route key={r.path} path={r.path} element={<RouteWrapper Component={r.component}/>}>
-                                {r.children?.map((c, cidx) => {
-                                    return (<Route key={r.path + cidx} index={c.index} path={c.path}
-                                                   element={<RouteWrapper Component={c.component}/>}/>)
-                                })}
-                            </Route>
-                        )
-                    })
-                }
-                <Route path="/login" Component={LoginPage} />
-              <Route path="/signup" Component={SignupPage} />
-            </Routes>
-        </HashRouter>
+       <SSOWrapper>
+           <HashRouter basename={'/'}>
+               <Routes>
+                   <Route path='/' element={<RouteWrapper Component={NginxList}/>}/>
+                   {
+                       nginxRoutes.map((r) => {
+                           return (
+                               <Route key={r.path} path={r.path} element={<RouteWrapper Component={r.component}/>}>
+                                   {r.children?.map((c, cidx) => {
+                                       return (<Route key={r.path + cidx} index={c.index} path={c.path}
+                                                      element={<RouteWrapper Component={c.component}/>}/>)
+                                   })}
+                               </Route>
+                           )
+                       })
+                   }
+                   <Route path="/login" Component={LoginPage} />
+                   <Route path="/signup" Component={SignupPage} />
+               </Routes>
+           </HashRouter>
+       </SSOWrapper>
     )
 }
 

+ 3 - 1
src/store/slice/user.ts

@@ -1,8 +1,9 @@
 import { createSlice, PayloadAction } from '@reduxjs/toolkit';
 import {User} from "../../models/user.ts";
+import dayjs from "dayjs";
 
 export type IUserState = {
-  user?: User;
+  user?: User & { timestamp: number };
   isLogin: boolean;
   isAdmin?: boolean;
 };
@@ -18,6 +19,7 @@ const userSlice = createSlice({
   reducers: {
     setUser(state, action: PayloadAction<User>) {
       state.user = action.payload;
+      state.user.timestamp = dayjs().unix()
       state.isLogin = true;
       const roles = state.user?.roles || '';
       state.isAdmin = roles.indexOf('ADMIN') > -1;

+ 44 - 17
src/utils/index.ts

@@ -12,35 +12,62 @@ export const getFirst = (item: string | null | Array<string | null>)=>{
     return item || ''
 }
 
+type ParseQueryType = {
+    url: string
+    query?: any
+    route?: string
+    fragmentIdentifier?: string
+}
+
+export function parseQuery<T>(): ParseQueryType{
+    const fullUrl= window.location.href;
+    const query =  querystring.parseUrl(fullUrl, { parseFragmentIdentifier: true }) as ParseQueryType;
+    if (query.fragmentIdentifier){
+        const subQuery = querystring.parseUrl(query.fragmentIdentifier);
+        Object.assign(query.query, subQuery.query);
+        query.route = subQuery.url;
+    }
+    console.log('parseQuery', query)
+    return query as {
+        query: T,
+        url: string
+    }
+}
+
+/**
+ * 解析route的参数
+ */
 export function useQuery<T>  (){
     const [query,setQuery] = useState<T>()
 
     const location = useLocation()
 
-  const parseQuery = ()=>{
-      console.log('location', location.search)
-    if (!location.search){
-      setQuery(undefined)
-      return
+    const parseRouteQuery = ()=>{
+        if (!location.search){
+            return
+        }
+        const q = querystring.parse(location.search);
+        setQuery(q as never)
     }
-    const query:querystring.ParsedQuery =  querystring.parse(location.search)
-    if (!query.code){
-      return;
-    }
-    const newQuery:any = {}
-    Object.keys(query).forEach(k=>{
-      newQuery[k] = getFirst(query[k])
-    })
-    setQuery(newQuery)
-  }
+
 
     useEffect(()=>{
-        parseQuery()
+        parseRouteQuery()
     },[location])
 
   useEffect(()=>{
-    parseQuery()
+      parseRouteQuery()
   },[])
 
     return query
 }
+
+
+export const cacheTo = (to='/')=>{
+    localStorage.setItem('redirect_to', to)
+}
+export const getLastTo = (remove=false)=>{
+    const to = localStorage.getItem(`redirect_to`);
+    remove && localStorage.removeItem('redirect_to');
+    return to ?? '/'
+}

+ 5 - 0
yarn.lock

@@ -1972,6 +1972,11 @@ dayjs@1.x:
   resolved "https://registry.npmmirror.com/dayjs/-/dayjs-1.11.8.tgz#4282f139c8c19dd6d0c7bd571e30c2d0ba7698ea"
   integrity sha512-LcgxzFoWMEPO7ggRv1Y2N31hUf2R0Vj7fuy/m+Bg1K8rr+KAs1AEy4y9jd5DXe8pbHgX+srkHNS7TH6Q6ZhYeQ==
 
+dayjs@^1.11.9:
+  version "1.11.9"
+  resolved "https://mirrors.tencent.com/npm/dayjs/-/dayjs-1.11.9.tgz#9ca491933fadd0a60a2c19f6c237c03517d71d1a"
+  integrity sha512-QvzAURSbQ0pKdIye2txOzNaHmxtUBXerpY0FJsFXUMKbIZeFm5ht1LS/jFsrncjnmtv8HsG0W2g6c0zUjZWmpA==
+
 debug@4, debug@^4.0.0, debug@^4.1.0, debug@^4.1.1, debug@^4.3.2, debug@^4.3.3, debug@^4.3.4:
   version "4.3.4"
   resolved "https://registry.npmmirror.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865"

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