소스 검색

feat: 增加LDAP-未完成

tuonian 4 달 전
부모
커밋
949cd9d200
94개의 변경된 파일1776개의 추가작업 그리고 268개의 파일을 삭제
  1. 1 1
      .gitignore
  2. 7 0
      conf/app.conf
  3. 3 2
      frontend/package.json
  4. 3 3
      frontend/src/App.tsx
  5. 43 0
      frontend/src/api/ldap.ts
  6. 26 1
      frontend/src/api/request.ts
  7. 15 3
      frontend/src/api/user.ts
  8. 10 0
      frontend/src/components/curd/index.less
  9. 70 0
      frontend/src/components/curd/index.tsx
  10. 0 2
      frontend/src/config/nginx_form.json
  11. 4 1
      frontend/src/config/nginx_template.json
  12. 7 0
      frontend/src/models/api.ts
  13. 1 1
      frontend/src/models/nginx.ts
  14. 66 0
      frontend/src/pages/layout/MainLayout.tsx
  15. 9 0
      frontend/src/pages/layout/layout.less
  16. 36 0
      frontend/src/pages/ldap/layout.tsx
  17. 29 0
      frontend/src/pages/ldap/server/index.tsx
  18. 10 0
      frontend/src/pages/ldap/user/index.less
  19. 79 0
      frontend/src/pages/ldap/user/index.tsx
  20. 7 0
      frontend/src/pages/ldap/user/list/index.tsx
  21. 1 1
      frontend/src/pages/login/index.tsx
  22. 2 2
      frontend/src/pages/login/sso.tsx
  23. 1 1
      frontend/src/pages/nginx/certs/index.tsx
  24. 1 1
      frontend/src/pages/nginx/components/EditNginxBtn.tsx
  25. 1 1
      frontend/src/pages/nginx/components/StopStartButton.tsx
  26. 1 1
      frontend/src/pages/nginx/components/access/index.tsx
  27. 1 1
      frontend/src/pages/nginx/components/auth/index.tsx
  28. 2 2
      frontend/src/pages/nginx/components/basic/index.tsx
  29. 1 1
      frontend/src/pages/nginx/components/certs/index.tsx
  30. 1 1
      frontend/src/pages/nginx/components/cors/index.tsx
  31. 1 1
      frontend/src/pages/nginx/components/error/index.tsx
  32. 1 1
      frontend/src/pages/nginx/components/fastcgi/index.tsx
  33. 2 2
      frontend/src/pages/nginx/components/gzip/index.tsx
  34. 1 1
      frontend/src/pages/nginx/components/input.ts
  35. 1 1
      frontend/src/pages/nginx/components/location/index.tsx
  36. 1 1
      frontend/src/pages/nginx/components/location/utils.ts
  37. 1 1
      frontend/src/pages/nginx/components/log/index.tsx
  38. 1 1
      frontend/src/pages/nginx/components/proxy/index.tsx
  39. 1 1
      frontend/src/pages/nginx/components/proxy/utils.ts
  40. 1 1
      frontend/src/pages/nginx/components/proxypass/index.tsx
  41. 1 1
      frontend/src/pages/nginx/components/proxypass/stream.tsx
  42. 1 1
      frontend/src/pages/nginx/components/site/components/Dragger.tsx
  43. 1 1
      frontend/src/pages/nginx/components/site/index.tsx
  44. 1 1
      frontend/src/pages/nginx/components/utils/index.ts
  45. 1 1
      frontend/src/pages/nginx/http/components/HttpConfSync.tsx
  46. 5 3
      frontend/src/pages/nginx/http/index.tsx
  47. 1 1
      frontend/src/pages/nginx/http/utils.ts
  48. 1 0
      frontend/src/pages/nginx/index.less
  49. 1 1
      frontend/src/pages/nginx/index.tsx
  50. 98 0
      frontend/src/pages/nginx/layout.tsx
  51. 1 1
      frontend/src/pages/nginx/list.tsx
  52. 1 1
      frontend/src/pages/nginx/location/index.tsx
  53. 1 1
      frontend/src/pages/nginx/server/components/SyncButton.tsx
  54. 1 1
      frontend/src/pages/nginx/server/components/preview.tsx
  55. 1 1
      frontend/src/pages/nginx/server/index.tsx
  56. 1 1
      frontend/src/pages/nginx/server/new.tsx
  57. 1 1
      frontend/src/pages/nginx/settings/index.tsx
  58. 1 1
      frontend/src/pages/nginx/stream/index.tsx
  59. 1 1
      frontend/src/pages/nginx/upstream/components/ConfigSync.tsx
  60. 1 1
      frontend/src/pages/nginx/upstream/tab.tsx
  61. 1 1
      frontend/src/pages/nginx/utils/index.ts
  62. 1 1
      frontend/src/pages/nginx/utils/nginx.ts
  63. 2 2
      frontend/src/pages/signup/index.tsx
  64. 49 98
      frontend/src/routes/index.tsx
  65. 2 0
      frontend/src/store/root.ts
  66. 40 0
      frontend/src/store/slice/route.ts
  67. 1 1
      frontend/src/styles/planning.less
  68. 191 23
      frontend/yarn.lock
  69. 9 5
      go.mod
  70. 72 11
      go.sum
  71. 30 0
      server/config/config.go
  72. 9 9
      server/controllers/certificate.go
  73. 3 3
      server/controllers/check.go
  74. 24 11
      server/controllers/default.go
  75. 2 2
      server/controllers/file.go
  76. 1 1
      server/controllers/logger.go
  77. 19 19
      server/controllers/nginx.go
  78. 11 11
      server/controllers/oauth2.go
  79. 4 4
      server/controllers/server.go
  80. 3 3
      server/controllers/user.go
  81. 16 2
      server/db/db.go
  82. 2 0
      server/middleware/auth.go
  83. 34 0
      server/models/ldap.go
  84. 8 4
      server/models/user.go
  85. 170 0
      server/modules/ldap/client.go
  86. 28 0
      server/modules/ldap/client_test.go
  87. 22 0
      server/modules/ldap/router.go
  88. 90 0
      server/modules/ldap/server_controller.go
  89. 209 0
      server/modules/ldap/service.go
  90. 89 0
      server/modules/ldap/user_controller.go
  91. 13 0
      server/modules/ldap/vo.go
  92. 3 0
      server/routers/router.go
  93. 26 0
      server/vo/base.go
  94. 22 0
      yarn.lock

+ 1 - 1
.gitignore

@@ -33,7 +33,7 @@ server/data/files
 
 nginx-ui.tar.gz
 
-server/conf/app.local.conf
+conf/app.local.conf
 server/data/sessions
 
 /data/sessions

+ 7 - 0
conf/app.conf

@@ -10,6 +10,13 @@ datadir = ./data
 dbdir = ./data/db
 nginxPath = /usr/sbin/nginx
 nginxDir = /etc/nginx
+# 支持sqlite,MySQL
+db_type = sqlite
+db_host = 10.10.0.x
+db_port = 3306
+db_user = nginx
+db_password = nginx
+db_name = nginx_ui
 
 admin_password = qwer@1234
 reset_admin_password = true

+ 3 - 2
frontend/package.json

@@ -24,6 +24,7 @@
     "@reduxjs/toolkit": "^1.9.5",
     "antd": "4.x",
     "artt-template": "^4.13.6",
+    "auto-antd": "^0.1.9",
     "classnames": "^2.3.2",
     "dayjs": "^1.11.9",
     "events": "^3.3.0",
@@ -33,14 +34,14 @@
     "less": "^4.1.3",
     "lodash": "^4.17.21",
     "npm": "^9.8.0",
-    "planning-tools": "^0.1.6",
     "query-string": "^8.1.0",
     "react": "^18.2.0",
     "react-dom": "^18.2.0",
     "react-redux": "^8.1.1",
     "react-router": "^6.14.0",
     "react-router-dom": "^6.14.0",
-    "redux-persist": "^6.0.0"
+    "redux-persist": "^6.0.0",
+    "sass": "^1.80.3"
   },
   "devDependencies": {
     "@types/events": "^3.0.0",

+ 3 - 3
frontend/src/App.tsx

@@ -1,12 +1,12 @@
 import './App.css'
-import {MyRouter} from "./routes";
 import { Provider } from 'react-redux';
 import { persistor, store } from './store';
 import {PersistGate} from "redux-persist/integration/react";
 import {ConfigProvider} from "antd";
 import zhCN from 'antd/lib/locale/zh_CN';
 import 'antd/dist/antd.css'
-import 'planning-tools/dist/umd/planning-tools.min.css'
+import 'auto-antd/dist/umd/auto-antd.min.css'
+import {MyRouter} from "./routes";
 
 function App() {
     return (
@@ -14,7 +14,7 @@ function App() {
       <Provider store={store}>
         <PersistGate loading persistor={persistor}>
           <ConfigProvider locale={zhCN}>
-            <MyRouter />
+              <MyRouter />
           </ConfigProvider>
         </PersistGate>
       </Provider>

+ 43 - 0
frontend/src/api/ldap.ts

@@ -0,0 +1,43 @@
+import {LoginReq} from "./user.ts";
+import request from "./request.ts";
+import {PageResp} from "../models/api.ts";
+
+
+// eslint-disable-next-line @typescript-eslint/no-namespace
+export namespace LDAP {
+
+    export type Server = {
+        id: number
+        key: string
+        url: string
+        admin: string
+        baseDN: string
+        filter?: string
+    }
+
+}
+
+/**
+ * LDAP相关的API
+ */
+export const LDAPApis = {
+
+    login: (data: LoginReq & { serverKey: string }) => {
+        return request.post('/ldap/login', data)
+    },
+    /**
+     * 加锁,避免多次访问,多次弹窗
+     */
+    getServer: (id: any) => {
+       return request.get(`/ldap/server/detail`, { params: { id }})
+    },
+    getServerList: (query: any) => {
+        return request.post<PageResp<LDAP.Server>>(`/ldap/server/list`, query)
+    },
+    /**
+     * 获取当前激活的server
+     */
+    getActiveServer: () => {
+        return request.get(`/ldap/server/active`)
+    }
+}

+ 26 - 1
frontend/src/api/request.ts

@@ -1,6 +1,6 @@
 import axios, {AxiosResponse} from 'axios';
 import {BaseResp} from "../models/api.ts";
-import {Message, Notify} from "planning-tools";
+import {Message, Notify} from "auto-antd";
 import {store} from "../store";
 import {UserActions} from "../store/slice/user.ts";
 // import {checkDesktopApi} from "./desktop.api.ts";
@@ -13,6 +13,27 @@ if (!CONFIG.baseApi){
   CONFIG.baseApi = '/api'
 }
 
+let blockPromise:  Promise<any> | null = null
+
+
+export const getLockRequest = () => {
+  return blockPromise
+}
+
+
+export const lockRequest = (promise: Promise<any>) => {
+  if (blockPromise) {
+    return false
+  }
+  blockPromise = promise
+  return true
+}
+
+export const unlockRequest = () => {
+  blockPromise = null
+}
+
+
 /**
  * 支持网络请求
  * @type {AxiosInstance}
@@ -30,6 +51,10 @@ request.interceptors.request.use(
       config.headers = {}
     }
     config.headers["Authorization"] = "token"
+    if (config.url && blockPromise && !['/user/info'].includes(config.url)){
+      console.log('blockPromise', blockPromise, config.url);
+      return blockPromise.then(() => config)
+    }
     return config;
   },
   (error) => {

+ 15 - 3
frontend/src/api/user.ts

@@ -1,4 +1,4 @@
-import request from "./request.ts";
+import request, {getLockRequest, lockRequest, unlockRequest} from "./request.ts";
 
 export type LoginReq = {
     account: string
@@ -24,10 +24,22 @@ export const LoginApis = {
         return request.post('/user/login', data)
     },
     signUp: (data: RegisterReq) => request.post('/user/register', data),
+    /**
+     * 加锁,避免多次访问,多次弹窗
+     */
     userinfo: () => {
-        return request.get('/user/info', { disableErrorMsg: true } as never)
+        let promise = getLockRequest()
+        if (promise){
+            return promise
+        }
+        promise = request.get('/user/info', { disableErrorMsg: true } as never)
+        const block = lockRequest(promise);
+        if (block){
+            promise.finally(()=>unlockRequest())
+        }
+        return promise;
     },
     oauth2Url: ()=> request.get('/oauth2'),
     oauth2Callback: (data: SSOReq) => request.post('/oauth2/callback', data, { disableErrorMsg: true } as never)
 
-}
+}

+ 10 - 0
frontend/src/components/curd/index.less

@@ -0,0 +1,10 @@
+.curd{
+
+  &-header{}
+
+  &-body{}
+
+  &-footer{
+
+  }
+}

+ 70 - 0
frontend/src/components/curd/index.tsx

@@ -0,0 +1,70 @@
+import {AutoColumn, Message} from "auto-antd";
+import {Pagination, Table} from "antd";
+import {useEffect, useMemo, useState} from "react";
+import {ColumnsType} from "antd/lib/table";
+
+
+export type CurdColumn = AutoColumn & {
+    search?: boolean
+    editable?: boolean
+    hidden?: boolean
+}
+
+type PageResp<T> = { total: number, list: T[], current: number, pageSize: number }
+
+type IProps<T> = {
+    columns: CurdColumn[],
+    getList: (query: any) => Promise<PageResp<T> | undefined>,
+}
+
+/**
+ * 一个完成的增删查改界面
+ * @constructor
+ */
+export function CurdPage<T>({columns, getList}: IProps<T>) {
+
+    const [list, setList] = useState<T[]>([]);
+    const [total, setTotal] = useState<number>(0)
+    const [query, setQuery] = useState<any>({current: 1, pageSize: 10});
+
+    const tableColumns = useMemo(() => {
+        return columns.filter(item => !item.hidden)
+            .map((column: CurdColumn) => {
+                return {
+                    dataIndex: column.key,
+                    title: column.title,
+                    width: column.width,
+                }
+            }) as ColumnsType<T>
+
+    }, [columns]);
+
+
+    useEffect(() => {
+        getList(query).then(res => {
+            if (res){
+                setList(res.list)
+                setTotal(res.total)
+            }else {
+                Message.warning('无数据!')
+            }
+        })
+    }, [getList, query]);
+
+    const onPageChange = (current: number, pageSize: number) => {
+        setQuery((q: any) => ({...q, current, pageSize}))
+    }
+
+    return <div className="curd">
+        <div className="curd-header"></div>
+        <div className="curd-body">
+            <Table columns={tableColumns as any} dataSource={list as any}/>
+            <Pagination total={total} pageSize={query.pageSize} current={query.current} onChange={onPageChange}/>
+        </div>
+        <div className="curd-footer">
+
+        </div>
+
+    </div>
+
+}

+ 0 - 2
frontend/src/config/nginx_form.json

@@ -404,7 +404,6 @@
             {
               "type": "textarea",
               "key": "name",
-              "value": "main",
               "title": "格式名称",
               "rows": 4,
               "placeholder": "日志格式名称,eg. main compression",
@@ -416,7 +415,6 @@
             {
               "type": "textarea",
               "key": "content",
-              "value": "",
               "title": "日志格式",
               "required": true,
               "rows": 4,

+ 4 - 1
frontend/src/config/nginx_template.json

@@ -36,7 +36,10 @@
       "name": "tcp_format",
       "path": "/var/log/nginx/access_stream.log"
     },
-    "stream.error_log": "/var/log/nginx/error_stream.log"
+    "stream.error_log": {
+      "path": "/var/log/nginx/error_stream.log",
+      "level": "warn"
+    }
   },
   "server": {
     "port": 80,

+ 7 - 0
frontend/src/models/api.ts

@@ -7,6 +7,13 @@ export type BaseResp<T =any> = {
   data?: T
 }
 
+export type PageResp<T=any> = BaseResp<{
+  current: number
+  total: number
+  pageSize: number
+  list: T[]
+}>
+
 /**
  * 虚拟主机,后端,跟前端不一致
  */

+ 1 - 1
frontend/src/models/nginx.ts

@@ -1,4 +1,4 @@
-import {FormColumnType} from "planning-tools";
+import {FormColumnType} from "auto-antd";
 import {NgxModuleData} from "../pages/nginx/components/input.ts";
 
 export type INginx = {

+ 66 - 0
frontend/src/pages/layout/MainLayout.tsx

@@ -0,0 +1,66 @@
+import {Breadcrumb, Layout, Menu} from 'antd';
+import './layout.less'
+import {useAppDispatch, useAppSelector} from "../../store";
+import {routeActions} from "../../store/slice/route.ts";
+import {Outlet, useMatches, useNavigate} from "react-router";
+import {useEffect} from "react";
+import {Link} from "react-router-dom";
+
+const BreadcrumbItem = Breadcrumb.Item
+
+const {Header, Content} = Layout;
+
+
+export const MainLayout = () => {
+
+    const navList = useAppSelector(state => state.route.navList) || []
+    const nav = useAppSelector(state => state.route.nav)
+
+    const dispatch = useAppDispatch()
+    const navigate = useNavigate()
+
+    const matches = useMatches()
+
+    const crumbs = matches
+        .filter(match => Boolean((match.handle as any)?.crumb))
+        .reverse()
+
+    useEffect(() => {
+        console.log('matches', matches)
+    }, [matches]);
+
+    const setNav = (key: string) => {
+        dispatch(routeActions.setNav(key))
+        navigate(key)
+    }
+
+    return (
+        <Layout className="customLayout">
+            <Header style={{display: 'flex', alignItems: 'center'}}>
+                <div className="demo-logo"/>
+                <Menu
+                    theme="dark"
+                    mode="horizontal"
+                    defaultSelectedKeys={[nav]}
+                    items={navList}
+                    style={{flex: 1, minWidth: 0}}
+                    onClick={event => setNav(event.key)}
+                />
+            </Header>
+            <Layout>
+                {
+                    crumbs.length ? (<Breadcrumb
+                        style={{margin: '16px 0'}}
+                    >
+                        {crumbs.map((item) => (<BreadcrumbItem>
+                            <Link to={item.pathname}>{(item.handle as any)?.label || ''}</Link>
+                        </BreadcrumbItem>))}
+                    </Breadcrumb>) : null
+                }
+                <Content>
+                    <Outlet/>
+                </Content>
+            </Layout>
+        </Layout>
+    );
+};

+ 9 - 0
frontend/src/pages/layout/layout.less

@@ -0,0 +1,9 @@
+
+.customLayout{
+  height: 100%;
+  overflow: hidden;
+}
+.ant-layout.customLayout{
+  height: 100%;
+  overflow: hidden;
+}

+ 36 - 0
frontend/src/pages/ldap/layout.tsx

@@ -0,0 +1,36 @@
+import {useRoutes} from "react-router-dom";
+
+import type {RouteObject} from "react-router/dist/lib/context";
+import {Server} from "./server";
+import {UserLayout} from "./user";
+import {List} from "./user/list";
+import {LDAPApis} from "../../api/ldap.ts";
+
+
+
+
+const ldapRoutes: RouteObject[] = [
+    {
+        path: "",
+        element: <Server />,
+        index: true,
+    },
+    {
+        path: 'server/:id',
+        Component: UserLayout,
+        loader: (params: any) => LDAPApis.getServer(params.id),
+        children: [
+            {
+                index: true,
+                path: 'user',
+                Component: List
+            },
+        ]
+    },
+]
+
+export const Layout = () => {
+    return useRoutes(ldapRoutes)
+}
+
+

+ 29 - 0
frontend/src/pages/ldap/server/index.tsx

@@ -0,0 +1,29 @@
+import {CurdColumn, CurdPage} from "../../../components/curd";
+import {useCallback} from "react";
+import {LDAPApis} from "../../../api/ldap.ts";
+
+const columns: CurdColumn[] = [
+    {
+        key: 'id',
+        title: 'ID',
+        type: 'string',
+    }
+]
+
+
+export const Server = () => {
+
+    const getList = useCallback((query: any)=>{
+        return LDAPApis.getServerList(query).then(res=>{
+            console.log('server', res)
+            return res.data.data
+        })
+
+    },[])
+
+
+
+    return (<>
+        <CurdPage columns={columns} getList={getList} />
+    </>)
+}

+ 10 - 0
frontend/src/pages/ldap/user/index.less

@@ -0,0 +1,10 @@
+.ldap-container{
+
+  &-header{
+
+  }
+
+  &-content{}
+
+  &-wrap{}
+}

+ 79 - 0
frontend/src/pages/ldap/user/index.tsx

@@ -0,0 +1,79 @@
+import {Outlet, useLoaderData, useNavigate} from "react-router";
+import {useAppDispatch} from "../../../store";
+import {useEffect, useMemo, useState} from "react";
+import {Menu, MenuProps} from "antd";
+import {BackButton} from "../../../components/BackButton.tsx";
+import {LDAP} from "../../../api/ldap.ts";
+import './index.less'
+
+
+export const UserLayout = () => {
+
+    const server = useLoaderData() as LDAP.Server;
+    const navigate = useNavigate()
+
+
+    const [activeKey,setActiveKey] = useState<string>('settings')
+    const [openKeys,setOpenKeys] = useState<string[]>([])
+
+    const dispatch = useAppDispatch();
+
+
+    useEffect(()=>{
+        setActiveKey(location.pathname)
+        console.log('location changed ', location)
+    },[location.pathname])
+
+
+    const onClick: MenuProps['onClick'] = (e) => {
+        setActiveKey(e.key);
+        navigate(e.key, {
+            replace: true,
+        })
+
+        console.log('click ', e);
+    };
+
+    /**
+     * 仅允许一个打开
+     * @param e
+     */
+    const onOpenChange: MenuProps['onOpenChange'] = (e)=>{
+        console.log('onOpenChange', e)
+        setOpenKeys(e)
+    }
+
+    const menuItems: MenuProps['items'] = useMemo(()=>{
+        return [
+            {
+                key: ``,
+                title: 'Settings',
+            }
+        ] as MenuProps['items']
+    },[server])
+
+    return (<div className="ldap-container">
+        <div className="ldap-container-header">
+            <BackButton />
+            <div>LDAP:{server.url}</div>
+            <div style={{flex: 1}} />
+            <a target="_blank" style={{fontSize: 14,marginLeft: 10}} href="https://nginx.org/en/docs/">参考文档</a>
+        </div>
+        <div className="ldap-container-content">
+            <Menu
+                onClick={onClick}
+                style={{ width: 300 }}
+                mode="inline"
+                items={menuItems}
+                activeKey={activeKey}
+                selectedKeys={[activeKey]}
+                onOpenChange={onOpenChange}
+                openKeys={openKeys}
+            />
+            <div className="ldap-container-wrap">
+                <Outlet />
+            </div>
+        </div>
+
+    </div>)
+}

+ 7 - 0
frontend/src/pages/ldap/user/list/index.tsx

@@ -0,0 +1,7 @@
+
+
+export const List = () => {
+    return (<>
+
+        </>)
+}

+ 1 - 1
frontend/src/pages/login/index.tsx

@@ -7,7 +7,7 @@ import {LoginApis, LoginReq } from "../../api/user.ts";
 import { useState} from "react";
 import {useAppDispatch} from "../../store";
 import {UserActions} from "../../store/slice/user.ts";
-import {Message} from "planning-tools";
+import {Message} from "auto-antd";
 import {useNavigate } from "react-router";
 import {cacheTo, parseQuery, useQuery} from "../../utils";
 

+ 2 - 2
frontend/src/pages/login/sso.tsx

@@ -1,6 +1,6 @@
 import {LoginApis, SSOReq} from "../../api/user.ts";
 import {useEffect, useState} from "react";
-import {Message} from "planning-tools";
+import {Message} from "auto-antd";
 import {getFirst, getLastTo, parseQuery} from "../../utils";
 import {EmptyLoading} from "../../components/empty";
 
@@ -49,4 +49,4 @@ export const SSOWrapper = ({children}: any)=>{
    return (<EmptyLoading />)
   }
   return children
-}
+}

+ 1 - 1
frontend/src/pages/nginx/certs/index.tsx

@@ -14,7 +14,7 @@ import {
 import './index.less'
 import {INginxCerts} from "../../../models/api.ts";
 import {RcFile} from "antd/es/upload";
-import {isNull, Message} from "planning-tools";
+import {isNull, Message} from "auto-antd";
 import {ModalStaticFunctions} from "antd/es/modal/confirm";
 
 /**

+ 1 - 1
frontend/src/pages/nginx/components/EditNginxBtn.tsx

@@ -8,7 +8,7 @@ import {useState} from "react";
 import {EditOutlined} from "@ant-design/icons";
 import {Button} from "antd";
 import {NginxApis} from "../../../api/nginx.ts";
-import {Notify} from "planning-tools";
+import {Notify} from "auto-antd";
 import {useAppDispatch} from "../../../store";
 import {useNavigate} from "react-router";
 import {nginxPrefix} from "../../../routes/routes.tsx";

+ 1 - 1
frontend/src/pages/nginx/components/StopStartButton.tsx

@@ -1,6 +1,6 @@
 import {useEffect, useState} from "react";
 import {Button, Modal, Tag} from "antd";
-import {isNull, Message} from "planning-tools";
+import {isNull, Message} from "auto-antd";
 import {NginxApis} from "../../../api/nginx.ts";
 import {useAppSelector} from "../../../store";
 

+ 1 - 1
frontend/src/pages/nginx/components/access/index.tsx

@@ -3,7 +3,7 @@
  * @date 2023/7/31
  */
 import {IContentProps, NgxBasicInput, registerInput} from "../basic";
-import {AutoTypeInputProps} from "planning-tools";
+import {AutoTypeInputProps} from "auto-antd";
 
 import CFG from './config.json'
 import {Input, Popover} from "antd";

+ 1 - 1
frontend/src/pages/nginx/components/auth/index.tsx

@@ -2,7 +2,7 @@
  * @author tuonian
  * @date 2023/7/5
  */
-import {AutoTypeInputProps} from 'planning-tools'
+import {AutoTypeInputProps} from 'auto-antd'
 import './index.less'
 import config from './config.json'
 import {IContentProps, NgxBasicInput, registerInput} from "../basic";

+ 2 - 2
frontend/src/pages/nginx/components/basic/index.tsx

@@ -2,11 +2,11 @@
  * @author tuonian
  * @date 2023/7/5
  */
-import {AdvanceInputConfigs, AutoForm, AutoTypeInputProps, FormColumnType} from 'planning-tools'
+import {AdvanceInputConfigs, AutoForm, AutoTypeInputProps, FormColumnType} from 'auto-antd'
 import {Button, Drawer, FormInstance, Popover} from "antd";
 import {EditOutlined} from "@ant-design/icons";
 import React, {useEffect, useState} from "react";
-import {AutoFormFooterProps} from "planning-tools/dist/esm/Components/AutoForm/form";
+import {AutoFormFooterProps} from "auto-antd/dist/esm/Components/AutoForm/form";
 import {NgxModuleData} from "../input.ts";
 import './index.less'
 import {DrawerProps} from "antd/lib/drawer";

+ 1 - 1
frontend/src/pages/nginx/components/certs/index.tsx

@@ -1,4 +1,4 @@
-import {AdvanceInputConfigs, AutoTypeInputProps} from "planning-tools";
+import {AdvanceInputConfigs, AutoTypeInputProps} from "auto-antd";
 import {Select} from "antd";
 import {useEffect, useState} from "react";
 import {NginxApis} from "../../../../api/nginx.ts";

+ 1 - 1
frontend/src/pages/nginx/components/cors/index.tsx

@@ -2,7 +2,7 @@
  * @author tuonian
  * @date 2023/7/5
  */
-import {AutoTypeInputProps, isNull, uniqueKey} from 'planning-tools'
+import {AutoTypeInputProps, isNull, uniqueKey} from 'auto-antd'
 import './index.less'
 import config from './config.json'
 import {IContentProps, NgxBasicInput, registerInput} from "../basic";

+ 1 - 1
frontend/src/pages/nginx/components/error/index.tsx

@@ -2,7 +2,7 @@
  * @author tuonian
  * @date 2023/7/5
  */
-import {AutoTypeInputProps} from 'planning-tools'
+import {AutoTypeInputProps} from 'auto-antd'
 import './index.less'
 import config from './config.json'
 import {IContentProps, NgxBasicInput, registerInput} from "../basic";

+ 1 - 1
frontend/src/pages/nginx/components/fastcgi/index.tsx

@@ -3,7 +3,7 @@
  * @date 2023/7/31
  */
 import {IContentProps, NgxBasicInput, registerInput} from "../basic";
-import {AutoTypeInputProps} from "planning-tools";
+import {AutoTypeInputProps} from "auto-antd";
 
 import CFG from './config.json'
 import {Input, Popover} from "antd";

+ 2 - 2
frontend/src/pages/nginx/components/gzip/index.tsx

@@ -2,13 +2,13 @@
  * @author tuonian
  * @date 2023/7/5
  */
-import {AdvanceInputConfigs, AutoForm, AutoTypeInputProps, isNull} from 'planning-tools'
+import {AdvanceInputConfigs, AutoForm, AutoTypeInputProps, isNull} from 'auto-antd'
 import {Button, FormInstance, Popover, Switch} from "antd";
 import './index.less'
 import {EditOutlined} from "@ant-design/icons";
 import {useEffect, useState} from "react";
 import config from './config.json'
-import {AutoFormFooterProps} from "planning-tools/dist/esm/Components/AutoForm/form";
+import {AutoFormFooterProps} from "auto-antd/dist/esm/Components/AutoForm/form";
 import {isBoolean} from "lodash";
 import {NgxModuleData} from "../input.ts";
 

+ 1 - 1
frontend/src/pages/nginx/components/input.ts

@@ -1,4 +1,4 @@
-import {isObject} from "planning-tools";
+import {isObject} from "auto-antd";
 
 /**
  * 自定义的模块化输入框的数据格式

+ 1 - 1
frontend/src/pages/nginx/components/location/index.tsx

@@ -8,7 +8,7 @@ import {
     isNull,
     Message,
     uniqueKey
-} from "planning-tools";
+} from "auto-antd";
 import {useEffect, useRef, useState} from "react";
 import {CopyOutlined, DeleteOutlined, EditOutlined, PlusOutlined} from "@ant-design/icons";
 import {INginxLocation} from "../../../../models/nginx.ts";

+ 1 - 1
frontend/src/pages/nginx/components/location/utils.ts

@@ -1,7 +1,7 @@
 import {INginxLocation} from "../../../../models/nginx.ts";
 import {cloneDeep} from "lodash";
 import {isNgxModuleValue, NgxModuleData} from "../input.ts";
-import {isBasicData} from "planning-tools";
+import {isBasicData} from "auto-antd";
 
 /**
  * 临时数据,不渲染

+ 1 - 1
frontend/src/pages/nginx/components/log/index.tsx

@@ -11,7 +11,7 @@ import {
   ObjectInput,
   DataValidatorConfig,
   AutoColumn, isNull
-} from "planning-tools";
+} from "auto-antd";
 import {Button, Tooltip} from "antd";
 import CONFIG from './config.json'
 import './index.less'

+ 1 - 1
frontend/src/pages/nginx/components/proxy/index.tsx

@@ -3,7 +3,7 @@
  * @date 2023/7/5
  */
 import {Button, Drawer, Input, Popover, Tooltip} from "antd";
-import {AdvanceInputConfigs, AutoForm, AutoFormInstance, AutoTypeInputProps, isObject} from 'planning-tools'
+import {AdvanceInputConfigs, AutoForm, AutoFormInstance, AutoTypeInputProps, isObject} from 'auto-antd'
 
 import './index.less'
 import {useEffect, useRef, useState} from "react";

+ 1 - 1
frontend/src/pages/nginx/components/proxy/utils.ts

@@ -1,6 +1,6 @@
 import {cloneDeep, isBoolean} from "lodash";
 import {INginx, KeyValue} from "../../../../models/nginx.ts";
-import {isFalse, isNull} from "planning-tools";
+import {isFalse, isNull} from "auto-antd";
 
 /**
  * 渲染代理配置

+ 1 - 1
frontend/src/pages/nginx/components/proxypass/index.tsx

@@ -3,7 +3,7 @@
  * @author tuonian
  * @date 2023/7/4
  */
-import {AutoTypeInputProps, AdvanceInputConfigs} from "planning-tools";
+import {AutoTypeInputProps, AdvanceInputConfigs} from "auto-antd";
 import {Input, Select} from "antd";
 import {useAppSelector} from "../../../../store";
 import {ChangeEvent, useEffect, useMemo, useState} from "react";

+ 1 - 1
frontend/src/pages/nginx/components/proxypass/stream.tsx

@@ -3,7 +3,7 @@
  * @author tuonian
  * @date 2023/7/4
  */
-import {AutoTypeInputProps, AdvanceInputConfigs} from "planning-tools";
+import {AutoTypeInputProps, AdvanceInputConfigs} from "auto-antd";
 import {Button, Input, Popover, Select} from "antd";
 import {useAppSelector} from "../../../../store";
 import {ChangeEvent, useEffect, useMemo, useState} from "react";

+ 1 - 1
frontend/src/pages/nginx/components/site/components/Dragger.tsx

@@ -1,7 +1,7 @@
 import React, {useCallback, useEffect, useRef, useState} from "react";
 
 import './dragger.less'
-import {uniqueKey} from "planning-tools";
+import {uniqueKey} from "auto-antd";
 import {Button, Tree} from "antd";
 import {
     DeleteOutlined,

+ 1 - 1
frontend/src/pages/nginx/components/site/index.tsx

@@ -1,7 +1,7 @@
 import {Button, Drawer, Form, Input, Modal, Switch} from "antd";
 import {
     Message
-} from "planning-tools";
+} from "auto-antd";
 import { useState} from "react";
 import {CloudUploadOutlined} from "@ant-design/icons";
 

+ 1 - 1
frontend/src/pages/nginx/components/utils/index.ts

@@ -1,4 +1,4 @@
-import {isBasicData, isNull} from "planning-tools";
+import {isBasicData, isNull} from "auto-antd";
 import {IRenderProcessor, isNameValue, isNgxModuleValue, NgxModuleData, KeyValue, ProcessorData} from "../input.ts";
 import {isBoolean} from "lodash";
 

+ 1 - 1
frontend/src/pages/nginx/http/components/HttpConfSync.tsx

@@ -10,7 +10,7 @@ import {SyncOutlined} from "@ant-design/icons";
 import {NginxApis} from "../../../../api/nginx.ts";
 import {useAppDispatch} from "../../../../store";
 import {NginxActions} from "../../../../store/slice/nginx.ts";
-import {Message} from "planning-tools";
+import {Message} from "auto-antd";
 import {toNginxConf} from "../utils.ts";
 
 type IProps = {

+ 5 - 3
frontend/src/pages/nginx/http/index.tsx

@@ -3,7 +3,7 @@
  * @date 2023/6/29
  */
 import {useFormConfig, useFormTemplate} from "../config.tsx";
-import {AutoForm, AutoFormInstance, Message} from "planning-tools";
+import {AutoForm, AutoFormInstance, Message} from "auto-antd";
 import {useEffect, useRef, useState} from "react";
 import {Button, Tooltip} from "antd";
 import {useAppDispatch, useAppSelector} from "../../../store";
@@ -57,7 +57,6 @@ export const NginxHttp = () => {
         })
   }
 
-
   /**
    * 将配置文件同步到服务器
    */
@@ -88,12 +87,15 @@ export const NginxHttp = () => {
       const curData = nginx?.httpData ? JSON.parse(nginx.httpData) : {};
       const updateData = { ...formTemplate.nginxConf, ...curData };
       setData(updateData)
-      formRef.current?.setData(updateData);
     }catch (e){
       console.log('parse httpData fail',e)
     }
   },[nginx,formTemplate])
 
+  useEffect(() => {
+    formRef.current?.setData(data);
+  }, [data,formRef.current]);
+
 
   return (<div className="page">
     <div className="page-header">

+ 1 - 1
frontend/src/pages/nginx/http/utils.ts

@@ -1,6 +1,6 @@
 import {isBoolean, isNull, isObject} from "lodash";
 import {INginx} from "../../../models/nginx.ts";
-import {isBasicData} from "planning-tools";
+import {isBasicData} from "auto-antd";
 import {isNgxModuleValue, NgxModuleData} from "../components/input.ts";
 
 const excludeKeys =["other","tmp","temp","key","proxy_settings"]

+ 1 - 0
frontend/src/pages/nginx/index.less

@@ -28,6 +28,7 @@
     .nginx-routes{
       flex: 1;
       width: 100%;
+      background: white;
       overflow: hidden;
       .error{
         padding: 20px;

+ 1 - 1
frontend/src/pages/nginx/index.tsx

@@ -93,7 +93,7 @@ export const Nginx = ({children}: any)=>{
 
   return (<div className="nginx-container">
     <div className="nginx-header">
-      <BackButton to="/"/>
+      <BackButton />
       <div>Nginx实例配置:{current?.name}</div>
       <div>({current?.ipAddr || '--'})</div>
       <div style={{flex: 1}} />

+ 98 - 0
frontend/src/pages/nginx/layout.tsx

@@ -0,0 +1,98 @@
+import {useRoutes} from "react-router-dom";
+import {Nginx} from "./index.tsx";
+import {NginxSettings} from "./settings";
+import {NginxHttp} from "./http";
+import {NginxCerts} from "./certs";
+import {NginxUpstream} from "./upstream";
+import {NginxStream} from "./stream";
+import {NginxServer} from "./server";
+import {ServerLocation} from "./location";
+import {NewLocation} from "./location/new.tsx";
+import {NewServer} from "./server/new.tsx";
+import {HelpPage} from "./help";
+import React from "react";
+import type {RouteObject} from "react-router/dist/lib/context";
+import {NginxList} from "./list.tsx";
+
+type INginxRoute = {
+    path: string,
+    component: React.FC,
+    index?: boolean,
+    children?: INginxRoute[]
+}
+
+const nginxRoutes: INginxRoute[] = [
+    {
+        path: "",
+        component: NginxList,
+    },
+    {
+        path: ':id',
+        component: Nginx,
+        children: [
+            {
+                index: true,
+                path: '',
+                component: NginxSettings
+            },
+            {
+                path: 'http',
+                component: NginxHttp
+            },
+            {
+                path: 'certs',
+                component: NginxCerts
+            },
+            {
+                path: 'upstream',
+                component: NginxUpstream
+            },
+            {
+                path: 'stream',
+                component: NginxStream
+            },
+            {
+                path: 'server/:sid',
+                component: NginxServer
+            },
+            {
+                path: 'server/:sid/conf',
+                component: NginxServer
+            },
+            {
+                path: 'server/:sid/location/:locId',
+                component: ServerLocation
+            },
+            {
+                path: 'server/:sid/location-new',
+                component: NewLocation
+            },
+            {
+                path: 'server-new',
+                component: NewServer
+            },
+            {
+                path: 'help',
+                component: HelpPage
+            }
+        ]
+    },
+    {
+        path: 'help',
+        component: HelpPage
+    }
+]
+
+// @ts-ignore
+const buildRoutes = (r: INginxRoute) => {
+    return {
+        path: r.path,
+        Component: r.component,
+        children: (r.children || []).map(child => buildRoutes(child)),
+        index: r.index,
+    } as RouteObject
+}
+
+export const NginxLayout = () => {
+    return useRoutes(nginxRoutes.map(r => buildRoutes(r)))
+}

+ 1 - 1
frontend/src/pages/nginx/list.tsx

@@ -7,7 +7,7 @@ import {useEffect, useRef, useState} from "react";
 import NginxDemo from './nginx.json'
 import {INginx} from "../../models/nginx.ts";
 import {NginxApis} from "../../api/nginx.ts";
-import {AutoForm, AutoFormInstance, Message, Notify} from "planning-tools";
+import {AutoForm, AutoFormInstance, Message, Notify} from "auto-antd";
 import {useFormConfig} from "./config.tsx";
 import {DeleteOutlined, EditOutlined, PlusOutlined, SyncOutlined} from "@ant-design/icons";
 import {useNavigate} from "react-router";

+ 1 - 1
frontend/src/pages/nginx/location/index.tsx

@@ -7,7 +7,7 @@ import {NginxRouteParams} from "../types.ts";
 import {useAppDispatch, useAppSelector} from "../../../store";
 import {Button, Modal, Spin} from "antd";
 import {useEffect, useRef, useState} from "react";
-import {AutoForm, AutoFormInstance, Message, uniqueKey} from "planning-tools";
+import {AutoForm, AutoFormInstance, Message, uniqueKey} from "auto-antd";
 import {INginxServer, PLocation} from "../../../models/nginx.ts";
 import {useFormConfig} from "../config.tsx";
 import {NginxActions} from "../../../store/slice/nginx.ts";

+ 1 - 1
frontend/src/pages/nginx/server/components/SyncButton.tsx

@@ -2,7 +2,7 @@ import {Button, Tooltip} from "antd";
 import {useState} from "react";
 import {useAppDispatch, useAppSelector} from "../../../../store";
 import {NginxApis} from "../../../../api/nginx.ts";
-import {Message} from "planning-tools";
+import {Message} from "auto-antd";
 import {QuestionCircleOutlined} from "@ant-design/icons";
 import {createServerHost} from "../../utils/nginx.ts";
 import {NginxActions} from "../../../../store/slice/nginx.ts";

+ 1 - 1
frontend/src/pages/nginx/server/components/preview.tsx

@@ -6,7 +6,7 @@ import {INginxServer} from "../../../../models/nginx.ts";
 import {Alert, Button, Input, Modal} from "antd";
 import {useEffect, useState} from "react";
 import {renderServer} from "../../utils";
-import {Message, Notify} from "planning-tools";
+import {Message, Notify} from "auto-antd";
 
 import './preview.less'
 import {useAppSelector} from "../../../../store";

+ 1 - 1
frontend/src/pages/nginx/server/index.tsx

@@ -7,7 +7,7 @@ import {useEffect, useRef, useState} from "react";
 import {useAppDispatch, useAppSelector} from "../../../store";
 import './index.less'
 import {NginxActions} from "../../../store/slice/nginx.ts";
-import {AutoForm, AutoFormInstance, Message} from "planning-tools";
+import {AutoForm, AutoFormInstance, Message} from "auto-antd";
 import {cloneDeep} from "lodash";
 import {useNavigate} from "react-router";
 import {nginxPrefix} from "../../../routes/routes";

+ 1 - 1
frontend/src/pages/nginx/server/new.tsx

@@ -5,7 +5,7 @@
 import {useAppDispatch, useAppSelector} from "../../../store";
 import {useRef, useState} from "react";
 import {INginxServer, PNginxServer} from "../../../models/nginx.ts";
-import {AutoForm, AutoFormInstance, Message, Notify} from "planning-tools";
+import {AutoForm, AutoFormInstance, Message, Notify} from "auto-antd";
 import {Button} from "antd";
 
 import './new.less'

+ 1 - 1
frontend/src/pages/nginx/settings/index.tsx

@@ -3,7 +3,7 @@
  * @date 2023/6/29
  */
 import {useFormConfig} from "../config.tsx";
-import {AutoForm, AutoFormInstance, Message, Notify} from "planning-tools";
+import {AutoForm, AutoFormInstance, Message, Notify} from "auto-antd";
 import {useAppDispatch, useAppSelector} from "../../../store";
 import {useEffect, useRef, useState} from "react";
 import {INginx} from "../../../models/nginx.ts";

+ 1 - 1
frontend/src/pages/nginx/stream/index.tsx

@@ -3,7 +3,7 @@ import {useEffect, useRef, useState} from "react";
 import {INginxServer, INginxStream} from "../../../models/nginx.ts";
 import {cloneDeep} from "lodash";
 import {useFormConfig} from "../config.tsx";
-import { FormTable, FormTableInstance, Message} from "planning-tools";
+import { FormTable, FormTableInstance, Message} from "auto-antd";
 
 import './index.less'
 import {Button} from "antd";

+ 1 - 1
frontend/src/pages/nginx/upstream/components/ConfigSync.tsx

@@ -8,7 +8,7 @@ import {INginx, INginxServer} from "../../../../models/nginx.ts";
 import {createServerHost} from "../../utils/nginx.ts";
 import {useAppDispatch, useAppSelector} from "../../../../store";
 import {NginxApis} from "../../../../api/nginx.ts";
-import {Message} from "planning-tools";
+import {Message} from "auto-antd";
 import {cloneDeep} from "lodash";
 import {NginxActions} from "../../../../store/slice/nginx.ts";
 

+ 1 - 1
frontend/src/pages/nginx/upstream/tab.tsx

@@ -1,7 +1,7 @@
 import {forwardRef, useEffect, useImperativeHandle, useRef, useState} from "react";
 import {INginx, INginxServer, IUpstream} from "../../../models/nginx.ts";
 import {useAppDispatch, useAppSelector} from "../../../store";
-import {FormTable, FormTableInstance, Message, TableRowData, uniqueKey} from "planning-tools";
+import {FormTable, FormTableInstance, Message, TableRowData, uniqueKey} from "auto-antd";
 import {Alert} from "antd";
 import {NginxApis} from "../../../api/nginx.ts";
 import './tab.less'

+ 1 - 1
frontend/src/pages/nginx/utils/index.ts

@@ -1,5 +1,5 @@
 import {INginx, INginxServer, IUpstream} from "../../../models/nginx.ts";
-import {isBasicData, isFalse, isNull} from "planning-tools";
+import {isBasicData, isFalse, isNull} from "auto-antd";
 import {cloneDeep, isBoolean} from "lodash";
 import {isNgxModuleValue, NgxModuleData} from "../components/input.ts";
 

+ 1 - 1
frontend/src/pages/nginx/utils/nginx.ts

@@ -3,7 +3,7 @@ import {INginx, INginxServer} from "../../../models/nginx.ts";
 import {renderServer} from "./index.ts";
 import {useAppDispatch} from "../../../store";
 import {NginxApis} from "../../../api/nginx.ts";
-import {Notify} from "planning-tools";
+import {Notify} from "auto-antd";
 import {NginxActions} from "../../../store/slice/nginx.ts";
 import {useEffect, useState} from "react";
 import {useNavigate} from "react-router";

+ 2 - 2
frontend/src/pages/signup/index.tsx

@@ -4,7 +4,7 @@ import {Link} from "react-router-dom";
 import {useState} from "react";
 import {LoginApis} from "../../api/user.ts";
 import {useNavigate} from "react-router";
-import {Message} from "planning-tools";
+import {Message} from "auto-antd";
 
 export const SignupPage = ()=>{
 
@@ -47,4 +47,4 @@ export const SignupPage = ()=>{
             </Form>
         </div>
     </div>)
-}
+}

+ 49 - 98
frontend/src/routes/index.tsx

@@ -1,92 +1,26 @@
-import {Routes, Route, HashRouter} from 'react-router-dom';
-import {NginxList} from "../pages/nginx/list.tsx";
-import {Nginx} from "../pages/nginx";
-import {NginxSettings} from "../pages/nginx/settings";
-import {NginxHttp} from "../pages/nginx/http";
-import {NginxServer} from "../pages/nginx/server";
-import {ServerLocation} from "../pages/nginx/location";
-import {NewLocation} from "../pages/nginx/location/new.tsx";
-import {NewServer} from "../pages/nginx/server/new.tsx";
-import {NginxUpstream} from "../pages/nginx/upstream";
-import {NginxCerts} from "../pages/nginx/certs";
-import {NginxStream} from "../pages/nginx/stream";
-import {HelpPage} from "../pages/nginx/help";
+import {createHashRouter, RouterProvider,} from 'react-router-dom';
 import {LoginApis} from "../api/user.ts";
+import * as React from "react";
 import {useEffect, useState} from "react";
 import {useAppDispatch, useAppSelector} from "../store";
 import {Spin} from "antd";
-import * as React from "react";
 import './index.less'
 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";
+import {MainLayout} from "../pages/layout/MainLayout.tsx";
 import {ErrorPage} from "../pages/error";
-
+import {LoginPage} from "../pages/login";
+import {SignupPage} from "../pages/signup";
+import {NginxLayout} from "../pages/nginx/layout.tsx";
+import {Layout as LDAP } from '../pages/ldap/layout.tsx'
 /**
  * @author tuonian
  * @date 2023/6/26
  */
 
-const nginxRoutes = [
-    {
-        path: '/nginx/:id',
-        component: Nginx,
-        children: [
-            {
-                index: true,
-                path: '',
-                component: NginxSettings
-            },
-            {
-                path: 'http',
-                component: NginxHttp
-            },
-            {
-                path: 'certs',
-                component: NginxCerts
-            },
-            {
-                path: 'upstream',
-                component: NginxUpstream
-            },
-            {
-                path: 'stream',
-                component: NginxStream
-            },
-            {
-                path: 'server/:sid',
-                component: NginxServer
-            },
-            {
-                path: 'server/:sid/conf',
-                component: NginxServer
-            },
-            {
-                path: 'server/:sid/location/:locId',
-                component: ServerLocation
-            },
-            {
-                path: 'server/:sid/location-new',
-                component: NewLocation
-            },
-            {
-                path: 'server-new',
-                component: NewServer
-            },
-            {
-                path: 'help',
-                component: HelpPage
-            }
-        ]
-    },
-    {
-        path: '/nginx/help',
-        component: HelpPage
-    }
-]
+
 
 type RouteWrapperProps = {
     Component: React.ComponentType
@@ -103,7 +37,7 @@ export const RouteWrapper = ({Component, ...props}: RouteWrapperProps) => {
     const fetchUser = () => {
         setLoading(true);
         LoginApis.userinfo().then(({data}) => {
-          dispatch(UserActions.setUser(data.data))
+            dispatch(UserActions.setUser(data.data))
             console.log('fetchUser', data)
         }).catch(e => {
             console.warn('userinfo fail', e);
@@ -115,7 +49,7 @@ export const RouteWrapper = ({Component, ...props}: RouteWrapperProps) => {
     }
 
     useEffect(() => {
-        if (!user?.account){
+        if (!user?.account) {
             fetchUser()
             return
         }
@@ -138,30 +72,47 @@ export const RouteWrapper = ({Component, ...props}: RouteWrapperProps) => {
     return <Component {...props} />
 }
 
+
+const router = createHashRouter([
+    {
+        path: "/*",
+        element: <RouteWrapper Component={MainLayout} />,
+        children: [
+            {
+                path: 'nginx/*',
+                Component: NginxLayout,
+                index: true,
+                // handle: {
+                //     crumb: true,
+                //     label: 'Nginx列表'
+                // }
+            },
+            {
+                path: "ldap/*",
+                Component: LDAP,
+            }
+        ]
+    },
+    {
+        path: "/error",
+        element: <ErrorPage/>
+    },
+    {
+        path: "/login",
+        element: <LoginPage />
+    },
+    {
+        path: "/signup",
+        element: <SignupPage />
+    }
+])
+
 export const MyRouter = () => {
+
     return (
-       <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="/error" Component={ErrorPage} />
-                   <Route path="/login" Component={LoginPage} />
-                   <Route path="/signup" Component={SignupPage} />
-               </Routes>
-           </HashRouter>
-       </SSOWrapper>
+        <SSOWrapper>
+            <RouterProvider router={router} />
+        </SSOWrapper>
     )
 }
 

+ 2 - 0
frontend/src/store/root.ts

@@ -2,6 +2,7 @@ import { combineReducers } from '@reduxjs/toolkit';
 import { createSlice, PayloadAction } from '@reduxjs/toolkit';
 import userReducer from './slice/user.ts';
 import nginxReducer from './slice/nginx.ts'
+import routeReducer from './slice/route.ts';
 
 export type IAppState = {
   responsive: {
@@ -35,6 +36,7 @@ const rootReducer = combineReducers({
   app: appSlice.reducer,
   user: userReducer,
   nginx: nginxReducer,
+  route: routeReducer,
 });
 
 export default rootReducer;

+ 40 - 0
frontend/src/store/slice/route.ts

@@ -0,0 +1,40 @@
+import { createSlice, PayloadAction } from '@reduxjs/toolkit';
+import {MenuProps} from "antd";
+
+export type IRouteState = {
+  nav: string
+  navList: MenuProps['items'],
+  routes: {[key: string]: MenuProps['items']}
+};
+
+const initialState: IRouteState = {
+  nav: '/nginx',
+  navList: [
+    {
+      key: '/nginx',
+      label: 'Nginx管理'
+    },
+    {
+      key: '/ldap',
+      label: 'LDAP管理'
+    }
+  ],
+  routes: {
+    Nginx: [],
+    LDAP: []
+  },
+};
+
+const routeSlice = createSlice({
+  name: 'route',
+  initialState,
+  reducers: {
+    setNav(state, action: PayloadAction<string>) {
+      state.nav = action.payload;
+    },
+  },
+});
+
+export const { setNav } = routeSlice.actions;
+export default routeSlice.reducer;
+export const routeActions = routeSlice.actions;

+ 1 - 1
frontend/src/styles/planning.less

@@ -1,4 +1,4 @@
-// fix planning-tools的样式
+// fix auto-antd的样式
 
 .auto-form .auto-type-form-item .ant-select-multiple{
   min-width: 50px;

+ 191 - 23
frontend/yarn.lock

@@ -754,6 +754,89 @@
     read-package-json-fast "^3.0.0"
     which "^3.0.0"
 
+"@parcel/watcher-android-arm64@2.4.1":
+  version "2.4.1"
+  resolved "https://registry.npmmirror.com/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.4.1.tgz#c2c19a3c442313ff007d2d7a9c2c1dd3e1c9ca84"
+  integrity sha512-LOi/WTbbh3aTn2RYddrO8pnapixAziFl6SMxHM69r3tvdSm94JtCenaKgk1GRg5FJ5wpMCpHeW+7yqPlvZv7kg==
+
+"@parcel/watcher-darwin-arm64@2.4.1":
+  version "2.4.1"
+  resolved "https://registry.npmmirror.com/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.4.1.tgz#c817c7a3b4f3a79c1535bfe54a1c2818d9ffdc34"
+  integrity sha512-ln41eihm5YXIY043vBrrHfn94SIBlqOWmoROhsMVTSXGh0QahKGy77tfEywQ7v3NywyxBBkGIfrWRHm0hsKtzA==
+
+"@parcel/watcher-darwin-x64@2.4.1":
+  version "2.4.1"
+  resolved "https://registry.npmmirror.com/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.4.1.tgz#1a3f69d9323eae4f1c61a5f480a59c478d2cb020"
+  integrity sha512-yrw81BRLjjtHyDu7J61oPuSoeYWR3lDElcPGJyOvIXmor6DEo7/G2u1o7I38cwlcoBHQFULqF6nesIX3tsEXMg==
+
+"@parcel/watcher-freebsd-x64@2.4.1":
+  version "2.4.1"
+  resolved "https://registry.npmmirror.com/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.4.1.tgz#0d67fef1609f90ba6a8a662bc76a55fc93706fc8"
+  integrity sha512-TJa3Pex/gX3CWIx/Co8k+ykNdDCLx+TuZj3f3h7eOjgpdKM+Mnix37RYsYU4LHhiYJz3DK5nFCCra81p6g050w==
+
+"@parcel/watcher-linux-arm-glibc@2.4.1":
+  version "2.4.1"
+  resolved "https://registry.npmmirror.com/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.4.1.tgz#ce5b340da5829b8e546bd00f752ae5292e1c702d"
+  integrity sha512-4rVYDlsMEYfa537BRXxJ5UF4ddNwnr2/1O4MHM5PjI9cvV2qymvhwZSFgXqbS8YoTk5i/JR0L0JDs69BUn45YA==
+
+"@parcel/watcher-linux-arm64-glibc@2.4.1":
+  version "2.4.1"
+  resolved "https://registry.npmmirror.com/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.4.1.tgz#6d7c00dde6d40608f9554e73998db11b2b1ff7c7"
+  integrity sha512-BJ7mH985OADVLpbrzCLgrJ3TOpiZggE9FMblfO65PlOCdG++xJpKUJ0Aol74ZUIYfb8WsRlUdgrZxKkz3zXWYA==
+
+"@parcel/watcher-linux-arm64-musl@2.4.1":
+  version "2.4.1"
+  resolved "https://registry.npmmirror.com/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.4.1.tgz#bd39bc71015f08a4a31a47cd89c236b9d6a7f635"
+  integrity sha512-p4Xb7JGq3MLgAfYhslU2SjoV9G0kI0Xry0kuxeG/41UfpjHGOhv7UoUDAz/jb1u2elbhazy4rRBL8PegPJFBhA==
+
+"@parcel/watcher-linux-x64-glibc@2.4.1":
+  version "2.4.1"
+  resolved "https://registry.npmmirror.com/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.4.1.tgz#0ce29966b082fb6cdd3de44f2f74057eef2c9e39"
+  integrity sha512-s9O3fByZ/2pyYDPoLM6zt92yu6P4E39a03zvO0qCHOTjxmt3GHRMLuRZEWhWLASTMSrrnVNWdVI/+pUElJBBBg==
+
+"@parcel/watcher-linux-x64-musl@2.4.1":
+  version "2.4.1"
+  resolved "https://registry.npmmirror.com/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.4.1.tgz#d2ebbf60e407170bb647cd6e447f4f2bab19ad16"
+  integrity sha512-L2nZTYR1myLNST0O632g0Dx9LyMNHrn6TOt76sYxWLdff3cB22/GZX2UPtJnaqQPdCRoszoY5rcOj4oMTtp5fQ==
+
+"@parcel/watcher-win32-arm64@2.4.1":
+  version "2.4.1"
+  resolved "https://registry.npmmirror.com/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.4.1.tgz#eb4deef37e80f0b5e2f215dd6d7a6d40a85f8adc"
+  integrity sha512-Uq2BPp5GWhrq/lcuItCHoqxjULU1QYEcyjSO5jqqOK8RNFDBQnenMMx4gAl3v8GiWa59E9+uDM7yZ6LxwUIfRg==
+
+"@parcel/watcher-win32-ia32@2.4.1":
+  version "2.4.1"
+  resolved "https://registry.npmmirror.com/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.4.1.tgz#94fbd4b497be39fd5c8c71ba05436927842c9df7"
+  integrity sha512-maNRit5QQV2kgHFSYwftmPBxiuK5u4DXjbXx7q6eKjq5dsLXZ4FJiVvlcw35QXzk0KrUecJmuVFbj4uV9oYrcw==
+
+"@parcel/watcher-win32-x64@2.4.1":
+  version "2.4.1"
+  resolved "https://registry.npmmirror.com/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.4.1.tgz#4bf920912f67cae5f2d264f58df81abfea68dadf"
+  integrity sha512-+DvS92F9ezicfswqrvIRM2njcYJbd5mb9CUgtrHCHmvn7pPPa+nMDRu1o1bYYz/l5IB2NVGNJWiH7h1E58IF2A==
+
+"@parcel/watcher@^2.4.1":
+  version "2.4.1"
+  resolved "https://registry.npmmirror.com/@parcel/watcher/-/watcher-2.4.1.tgz#a50275151a1bb110879c6123589dba90c19f1bf8"
+  integrity sha512-HNjmfLQEVRZmHRET336f20H/8kOozUGwk7yajvsonjNxbj2wBTK1WsQuHkD5yYh9RxFGL2EyDHryOihOwUoKDA==
+  dependencies:
+    detect-libc "^1.0.3"
+    is-glob "^4.0.3"
+    micromatch "^4.0.5"
+    node-addon-api "^7.0.0"
+  optionalDependencies:
+    "@parcel/watcher-android-arm64" "2.4.1"
+    "@parcel/watcher-darwin-arm64" "2.4.1"
+    "@parcel/watcher-darwin-x64" "2.4.1"
+    "@parcel/watcher-freebsd-x64" "2.4.1"
+    "@parcel/watcher-linux-arm-glibc" "2.4.1"
+    "@parcel/watcher-linux-arm64-glibc" "2.4.1"
+    "@parcel/watcher-linux-arm64-musl" "2.4.1"
+    "@parcel/watcher-linux-x64-glibc" "2.4.1"
+    "@parcel/watcher-linux-x64-musl" "2.4.1"
+    "@parcel/watcher-win32-arm64" "2.4.1"
+    "@parcel/watcher-win32-ia32" "2.4.1"
+    "@parcel/watcher-win32-x64" "2.4.1"
+
 "@pkgjs/parseargs@^0.11.0":
   version "0.11.0"
   resolved "https://registry.npmmirror.com/@pkgjs/parseargs/-/parseargs-0.11.0.tgz#a77ea742fab25775145434eb1d2328cf5013ac33"
@@ -1454,6 +1537,27 @@ asynckit@^0.4.0:
   resolved "https://registry.npmmirror.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79"
   integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==
 
+auto-antd@^0.1.9:
+  version "0.1.9"
+  resolved "https://registry.npmmirror.com/auto-antd/-/auto-antd-0.1.9.tgz#a1d49caec90f78f41ed71f403d100ffa0b41289b"
+  integrity sha512-hukboyZDWlNA9wkkBNSRAh+31UaQDc5eDuSKNO4goNvTG/rrtukLEUVLu5gSjmvAkjSOCR+cOSYUB8nuUexufg==
+  dependencies:
+    "@ant-design/icons" "^5.0.1"
+    ace-builds "^1.16.0"
+    antd ">=4.0.4 && <=4.24.8"
+    artt-template "^4.13.6"
+    axios "^0.27.2"
+    camelcase "^5.3.1"
+    classnames "^2.3.2"
+    crypto-js "^4.1.1"
+    js-base64 "^3.7.2"
+    js-yaml "^4.1.0"
+    jszip "^3.5.0"
+    lodash-es "^4.17.21"
+    rc-collapse "^3.5.2"
+    react-ace "^10.1.0"
+    umbrella-storage "^1.0.2"
+
 axios@^0.27.2:
   version "0.27.2"
   resolved "https://registry.npmmirror.com/axios/-/axios-0.27.2.tgz#207658cc8621606e586c85db4b41a750e756d972"
@@ -1524,6 +1628,13 @@ braces@^3.0.2:
   dependencies:
     fill-range "^7.0.1"
 
+braces@^3.0.3:
+  version "3.0.3"
+  resolved "https://registry.npmmirror.com/braces/-/braces-3.0.3.tgz#490332f40919452272d55a8480adc0c441358789"
+  integrity sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==
+  dependencies:
+    fill-range "^7.1.1"
+
 brilliant-errors@^0.7.3:
   version "0.7.3"
   resolved "https://registry.npmmirror.com/brilliant-errors/-/brilliant-errors-0.7.3.tgz#bbfeb396619552af0b5dfa33d43c2c06c80877ca"
@@ -1737,6 +1848,13 @@ cheerio@^1.0.0-rc.10:
     parse5 "^7.0.0"
     parse5-htmlparser2-tree-adapter "^7.0.0"
 
+chokidar@^4.0.0:
+  version "4.0.1"
+  resolved "https://registry.npmmirror.com/chokidar/-/chokidar-4.0.1.tgz#4a6dff66798fb0f72a94f616abbd7e1a19f31d41"
+  integrity sha512-n8enUVCED/KVRQlab1hr3MVpcVMvxtZjmEa956u+4YijlmQED223XMSYj2tLuKvr4jcCTzNNMpQDUer72MMmzA==
+  dependencies:
+    readdirp "^4.0.1"
+
 chownr@^2.0.0:
   version "2.0.0"
   resolved "https://registry.npmmirror.com/chownr/-/chownr-2.0.0.tgz#15bfbe53d2eab4cf70f18a8cd68ebe5b3cb1dece"
@@ -2042,6 +2160,11 @@ dequal@^2.0.0:
   resolved "https://registry.npmmirror.com/dequal/-/dequal-2.0.3.tgz#2644214f1997d39ed0ee0ece72335490a7ac67be"
   integrity sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==
 
+detect-libc@^1.0.3:
+  version "1.0.3"
+  resolved "https://registry.npmmirror.com/detect-libc/-/detect-libc-1.0.3.tgz#fa137c4bd698edf55cd5cd02ac559f91a4c4ba9b"
+  integrity sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==
+
 diff-match-patch@^1.0.5:
   version "1.0.5"
   resolved "https://registry.npmmirror.com/diff-match-patch/-/diff-match-patch-1.0.5.tgz#abb584d5f10cd1196dfc55aa03701592ae3f7b37"
@@ -2591,6 +2714,13 @@ fill-range@^7.0.1:
   dependencies:
     to-regex-range "^5.0.1"
 
+fill-range@^7.1.1:
+  version "7.1.1"
+  resolved "https://registry.npmmirror.com/fill-range/-/fill-range-7.1.1.tgz#44265d3cac07e3ea7dc247516380643754a05292"
+  integrity sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==
+  dependencies:
+    to-regex-range "^5.0.1"
+
 filter-obj@^5.1.0:
   version "5.1.0"
   resolved "https://mirrors.tencent.com/npm/filter-obj/-/filter-obj-5.1.0.tgz#5bd89676000a713d7db2e197f660274428e524ed"
@@ -2977,6 +3107,11 @@ immer@^9.0.21:
   resolved "https://registry.npmmirror.com/immer/-/immer-9.0.21.tgz#1e025ea31a40f24fb064f1fef23e931496330176"
   integrity sha512-bc4NBHqOqSfRW7POMkHd51LvClaeMXpm8dx0e8oE2GORbq5aRK7Bxl4FyzVLdGtLmvLKL7BTDBG5ACQm4HWjTA==
 
+immutable@^4.0.0:
+  version "4.3.7"
+  resolved "https://registry.npmmirror.com/immutable/-/immutable-4.3.7.tgz#c70145fc90d89fb02021e65c84eb0226e4e5a381"
+  integrity sha512-1hqclzwYwjRDFLjcFxOM5AYkkG0rpFPpr1RLPMEuGczoS7YA8gLhy8SWXYRAA/XwfEHpfo3cw5JGioS32fnMRw==
+
 import-fresh@^3.0.0, import-fresh@^3.2.1:
   version "3.3.0"
   resolved "https://registry.npmmirror.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b"
@@ -3440,6 +3575,11 @@ locate-path@^6.0.0:
   dependencies:
     p-locate "^5.0.0"
 
+lodash-es@^4.17.21:
+  version "4.17.21"
+  resolved "https://registry.npmmirror.com/lodash-es/-/lodash-es-4.17.21.tgz#43e626c46e6591b7750beb2b50117390c609e3ee"
+  integrity sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==
+
 lodash.get@^4.4.2:
   version "4.4.2"
   resolved "https://registry.npmmirror.com/lodash.get/-/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99"
@@ -3987,6 +4127,14 @@ micromatch@^4.0.4:
     braces "^3.0.2"
     picomatch "^2.3.1"
 
+micromatch@^4.0.5:
+  version "4.0.8"
+  resolved "https://registry.npmmirror.com/micromatch/-/micromatch-4.0.8.tgz#d66fa18f3a47076789320b9b1af32bd86d9fa202"
+  integrity sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==
+  dependencies:
+    braces "^3.0.3"
+    picomatch "^2.3.1"
+
 mime-db@1.52.0:
   version "1.52.0"
   resolved "https://registry.npmmirror.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70"
@@ -4172,6 +4320,11 @@ no-case@^3.0.4:
     lower-case "^2.0.2"
     tslib "^2.0.3"
 
+node-addon-api@^7.0.0:
+  version "7.1.1"
+  resolved "https://registry.npmmirror.com/node-addon-api/-/node-addon-api-7.1.1.tgz#1aba6693b0f255258a049d621329329322aad558"
+  integrity sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==
+
 node-fetch@^2.x.x:
   version "2.6.12"
   resolved "https://registry.npmmirror.com/node-fetch/-/node-fetch-2.6.12.tgz#02eb8e22074018e3d5a83016649d04df0e348fba"
@@ -4618,27 +4771,6 @@ pify@^4.0.1:
   resolved "https://registry.npmmirror.com/pify/-/pify-4.0.1.tgz#4b2cd25c50d598735c50292224fd8c6df41e3231"
   integrity sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==
 
-planning-tools@^0.1.6:
-  version "0.1.6"
-  resolved "https://registry.yarnpkg.com/planning-tools/-/planning-tools-0.1.6.tgz#566e6c207795e6dae0677444fafb0a1dca5fbcf3"
-  integrity sha512-Gk0Zwncgv0xqY2PKPeid0viXz3hL/poax27o3fla1hTGN/IxxuKUFIQnulZEeww3dY2RP8G9sLjWcfqdn9xJhA==
-  dependencies:
-    "@ant-design/icons" "^5.0.1"
-    ace-builds "^1.16.0"
-    antd ">=4.0.4 && <=4.24.8"
-    artt-template "^4.13.6"
-    axios "^0.27.2"
-    camelcase "^5.3.1"
-    classnames "^2.3.2"
-    crypto-js "^4.1.1"
-    js-base64 "^3.7.2"
-    js-yaml "^4.1.0"
-    jszip "^3.5.0"
-    lodash "^4.17.21"
-    rc-collapse "^3.5.2"
-    react-ace "^10.1.0"
-    umbrella-storage "^1.0.2"
-
 postcss-selector-parser@^6.0.10:
   version "6.0.13"
   resolved "https://registry.npmmirror.com/postcss-selector-parser/-/postcss-selector-parser-6.0.13.tgz#d05d8d76b1e8e173257ef9d60b706a8e5e99bf1b"
@@ -5282,6 +5414,11 @@ readable-stream@~2.3.6:
     string_decoder "~1.1.1"
     util-deprecate "~1.0.1"
 
+readdirp@^4.0.1:
+  version "4.0.2"
+  resolved "https://registry.npmmirror.com/readdirp/-/readdirp-4.0.2.tgz#388fccb8b75665da3abffe2d8f8ed59fe74c230a"
+  integrity sha512-yDMz9g+VaZkqBYS/ozoBJwaBhTbZo3UNYQHNRw1D3UFQB8oHB4uS/tAODO+ZLjGWmUbKnIlOWO+aaIiAxrUWHA==
+
 redux-persist@^6.0.0:
   version "6.0.0"
   resolved "https://registry.npmmirror.com/redux-persist/-/redux-persist-6.0.0.tgz#b4d2972f9859597c130d40d4b146fecdab51b3a8"
@@ -5420,6 +5557,16 @@ safe-buffer@~5.2.0:
   resolved "https://registry.npmmirror.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
   integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==
 
+sass@^1.80.3:
+  version "1.80.3"
+  resolved "https://registry.npmmirror.com/sass/-/sass-1.80.3.tgz#3f63dd527647d2b3de35f36acb971bda80517423"
+  integrity sha512-ptDWyVmDMVielpz/oWy3YP3nfs7LpJTHIJZboMVs8GEC9eUmtZTZhMHlTW98wY4aEorDfjN38+Wr/XjskFWcfA==
+  dependencies:
+    "@parcel/watcher" "^2.4.1"
+    chokidar "^4.0.0"
+    immutable "^4.0.0"
+    source-map-js ">=0.6.2 <2.0.0"
+
 sax@^1.2.4:
   version "1.2.4"
   resolved "https://registry.npmmirror.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9"
@@ -5561,6 +5708,11 @@ socks@^2.6.2:
     ip "^2.0.0"
     smart-buffer "^4.2.0"
 
+"source-map-js@>=0.6.2 <2.0.0":
+  version "1.2.1"
+  resolved "https://registry.npmmirror.com/source-map-js/-/source-map-js-1.2.1.tgz#1ce5650fddd87abc099eda37dcff024c2667ae46"
+  integrity sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==
+
 source-map-js@^1.0.2:
   version "1.0.2"
   resolved "https://registry.npmmirror.com/source-map-js/-/source-map-js-1.0.2.tgz#adbc361d9c62df380125e7f161f71c826f1e490c"
@@ -5634,7 +5786,16 @@ string-convert@^0.2.0:
   resolved "https://registry.npmmirror.com/string-convert/-/string-convert-0.2.1.tgz#6982cc3049fbb4cd85f8b24568b9d9bf39eeff97"
   integrity sha512-u/1tdPl4yQnPBjnVrmdLo9gtuLvELKsAoRapekWggdiQNvvvum+jYF329d84NAa660KQw7pB2n36KrIKVoXa3A==
 
-"string-width-cjs@npm:string-width@^4.2.0", "string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
+"string-width-cjs@npm:string-width@^4.2.0":
+  version "4.2.3"
+  resolved "https://registry.npmmirror.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
+  integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
+  dependencies:
+    emoji-regex "^8.0.0"
+    is-fullwidth-code-point "^3.0.0"
+    strip-ansi "^6.0.1"
+
+"string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
   version "4.2.3"
   resolved "https://registry.npmmirror.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
   integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
@@ -5674,7 +5835,14 @@ stringify-entities@^4.0.0:
     character-entities-html4 "^2.0.0"
     character-entities-legacy "^3.0.0"
 
-"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1:
+"strip-ansi-cjs@npm:strip-ansi@^6.0.1":
+  version "6.0.1"
+  resolved "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
+  integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
+  dependencies:
+    ansi-regex "^5.0.1"
+
+strip-ansi@^6.0.0, strip-ansi@^6.0.1:
   version "6.0.1"
   resolved "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
   integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==

+ 9 - 5
go.mod

@@ -7,7 +7,7 @@ require github.com/wailsapp/wails/v2 v2.6.0
 require (
 	github.com/bep/debounce v1.2.1 // indirect
 	github.com/go-ole/go-ole v1.2.6 // indirect
-	github.com/google/uuid v1.3.0 // indirect
+	github.com/google/uuid v1.6.0 // indirect
 	github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e // indirect
 	github.com/labstack/echo/v4 v4.10.2 // indirect
 	github.com/labstack/gommon v0.4.0 // indirect
@@ -25,11 +25,11 @@ require (
 	github.com/valyala/fasttemplate v1.2.2 // indirect
 	github.com/wailsapp/go-webview2 v1.0.1 // indirect
 	github.com/wailsapp/mimetype v1.4.1 // indirect
-	golang.org/x/crypto v0.11.0
+	golang.org/x/crypto v0.21.0
 	golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 // indirect
-	golang.org/x/net v0.12.0 // indirect
-	golang.org/x/sys v0.10.0 // indirect
-	golang.org/x/text v0.11.0 // indirect
+	golang.org/x/net v0.22.0 // indirect
+	golang.org/x/sys v0.18.0 // indirect
+	golang.org/x/text v0.14.0 // indirect
 )
 
 // replace github.com/wailsapp/wails/v2 v2.6.0 => D:\CacheData\go\pkg\mod
@@ -38,6 +38,8 @@ require github.com/astaxie/beego v1.12.1
 
 require (
 	github.com/beego/beego/v2 v2.1.0
+	github.com/go-ldap/ldap/v3 v3.4.8
+	github.com/go-sql-driver/mysql v1.7.0
 	github.com/mattn/go-sqlite3 v1.14.17
 	github.com/mholt/archiver/v4 v4.0.0-alpha.8
 	github.com/pkg/sftp v1.13.5
@@ -46,12 +48,14 @@ require (
 )
 
 require (
+	github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 // indirect
 	github.com/andybalholm/brotli v1.0.4 // indirect
 	github.com/bodgit/plumbing v1.2.0 // indirect
 	github.com/bodgit/sevenzip v1.3.0 // indirect
 	github.com/bodgit/windows v1.0.0 // indirect
 	github.com/connesc/cipherio v0.2.1 // indirect
 	github.com/dsnet/compress v0.0.1 // indirect
+	github.com/go-asn1-ber/asn1-ber v1.5.5 // indirect
 	github.com/go-redis/redis/v7 v7.4.0 // indirect
 	github.com/golang/protobuf v1.5.3 // indirect
 	github.com/golang/snappy v0.0.4 // indirect

+ 72 - 11
go.sum

@@ -15,10 +15,14 @@ cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+
 cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
 cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
 dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
+github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 h1:mFRzDkZVAjdal+s7s0MwaRv9igoPqLRdzOLzw/8Xvq8=
+github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU=
 github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
 github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
 github.com/Knetic/govaluate v3.0.0+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0=
 github.com/OwnLocal/goes v1.0.0/go.mod h1:8rIFjBGTue3lCU0wplczcUgt9Gxgrkkrw7etMIcn8TM=
+github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa h1:LHTHcTQiSGT7VVbI0o4wBRNQIgn917usHWOd6VAffYI=
+github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa/go.mod h1:cEWa1LVoE5KvSD9ONXsZrj0z6KqySlCCNKHlLzbqAt4=
 github.com/andybalholm/brotli v1.0.4 h1:V7DdXeJtZscaqfNuAdSRuRFzuiKlHSC/Zh3zl9qY3JY=
 github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
 github.com/astaxie/beego v1.12.1 h1:dfpuoxpzLVgclveAXe4PyNKqkzgm5zF4tgF2B3kkM2I=
@@ -61,8 +65,12 @@ github.com/elazarl/go-bindata-assetfs v1.0.1 h1:m0kkaHRKEu7tUIUFVwhGGGYClXvyl4RE
 github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
 github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
 github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
+github.com/go-asn1-ber/asn1-ber v1.5.5 h1:MNHlNMBDgEKD4TcKr36vQN68BA00aDfjIt3/bD50WnA=
+github.com/go-asn1-ber/asn1-ber v1.5.5/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0=
 github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
 github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
+github.com/go-ldap/ldap/v3 v3.4.8 h1:loKJyspcRezt2Q3ZRMq2p/0v8iOurlmeXDPw6fikSvQ=
+github.com/go-ldap/ldap/v3 v3.4.8/go.mod h1:qS3Sjlu76eHfHGpUdWkAXQTw4beih+cHsco2jXlIXrk=
 github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
 github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
 github.com/go-redis/redis v6.14.2+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA=
@@ -70,6 +78,7 @@ github.com/go-redis/redis/v7 v7.4.0 h1:7obg6wUoj05T0EpY0o8B59S9w5yeMWql7sw2kwNW1
 github.com/go-redis/redis/v7 v7.4.0/go.mod h1:JDNMw23GTyLNC4GZu9njt15ctBQVn7xjRfnwdHj/Dcg=
 github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
 github.com/go-sql-driver/mysql v1.7.0 h1:ueSltNNllEqE3qcWBTD0iQd3IpL/6U+mJxLkazJ7YPc=
+github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
 github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
 github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
 github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
@@ -105,16 +114,21 @@ github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OI
 github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
 github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
 github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
-github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
-github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
+github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
 github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
 github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
 github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
 github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
+github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
+github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM=
 github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA=
 github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
 github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
 github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
+github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
+github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8=
+github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
 github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
 github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
 github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
@@ -122,6 +136,18 @@ github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpO
 github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
 github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e h1:Q3+PugElBCf4PFpxhErSzU3/PY5sFL5Z6rfv4AbGAck=
 github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e/go.mod h1:alcuEEnZsY1WQsagKhZDsoPCRoOijYqhZvPwLG0kzVs=
+github.com/jcmturner/aescts/v2 v2.0.0 h1:9YKLH6ey7H4eDBXW8khjYslgyqG2xZikXP0EQFKrle8=
+github.com/jcmturner/aescts/v2 v2.0.0/go.mod h1:AiaICIRyfYg35RUkr8yESTqvSy7csK90qZ5xfvvsoNs=
+github.com/jcmturner/dnsutils/v2 v2.0.0 h1:lltnkeZGL0wILNvrNiVCR6Ro5PGU/SeBvVO/8c/iPbo=
+github.com/jcmturner/dnsutils/v2 v2.0.0/go.mod h1:b0TnjGOvI/n42bZa+hmXL+kFJZsFT7G4t3HTlQ184QM=
+github.com/jcmturner/gofork v1.7.6 h1:QH0l3hzAU1tfT3rZCnW5zXl+orbkNMMRGJfdJjHVETg=
+github.com/jcmturner/gofork v1.7.6/go.mod h1:1622LH6i/EZqLloHfE7IeZ0uEJwMSUyQ/nDd82IeqRo=
+github.com/jcmturner/goidentity/v6 v6.0.1 h1:VKnZd2oEIMorCTsFBnJWbExfNN7yZr3EhJAxwOkZg6o=
+github.com/jcmturner/goidentity/v6 v6.0.1/go.mod h1:X1YW3bgtvwAXju7V3LCIMpY0Gbxyjn/mY9zx4tFonSg=
+github.com/jcmturner/gokrb5/v8 v8.4.4 h1:x1Sv4HaTpepFkXbt2IkL29DXRf8sOfZXo8eRKh687T8=
+github.com/jcmturner/gokrb5/v8 v8.4.4/go.mod h1:1btQEpgT6k+unzCwX1KdWMEwPPkkgBtP+F6aCACiMrs=
+github.com/jcmturner/rpc/v2 v2.0.3 h1:7FXXj8Ti1IaVFpSAziCZWNzbNuZmnvw/i6CqLNdWfZY=
+github.com/jcmturner/rpc/v2 v2.0.3/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc=
 github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
 github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
 github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
@@ -208,10 +234,14 @@ github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIK
 github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
 github.com/ssdb/gossdb v0.0.0-20180723034631-88f6b59b84ec/go.mod h1:QBvMkMya+gXctz3kmljlUCu/yB3GZ6oee+dUozsezQE=
 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
+github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
 github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
 github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
 github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
 github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
+github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
 github.com/syndtr/goleveldb v0.0.0-20181127023241-353a9fca669c/go.mod h1:Z4AUp2Km+PwemOoO/VB5AOx9XSsIItzFjoJlOSiYmn0=
 github.com/therootcompany/xz v1.0.1 h1:CmOtsn1CbtmyYiusbfmhmkpAAETj0wBIH6kCYaX+xzw=
 github.com/therootcompany/xz v1.0.1/go.mod h1:3K3UH1yCKgBneZYhuQUvJ9HPD19UEXEI0BWbMn8qNMY=
@@ -232,6 +262,7 @@ github.com/wailsapp/mimetype v1.4.1/go.mod h1:9aV5k31bBOv5z6u+QP8TltzvNGJPmNJD4X
 github.com/wailsapp/wails/v2 v2.6.0 h1:EyH0zR/EO6dDiqNy8qU5spaXDfkluiq77xrkabPYD4c=
 github.com/wailsapp/wails/v2 v2.6.0/go.mod h1:WBG9KKWuw0FKfoepBrr/vRlyTmHaMibWesK3yz6nNiM=
 github.com/wendal/errors v0.0.0-20130201093226-f66c77a7882b/go.mod h1:Q12BUT7DqIlHRmgv3RskH+UCM/4eqVMgI0EMmlSpAXc=
+github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
 go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
 go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
 go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
@@ -242,9 +273,12 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk
 golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
 golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
-golang.org/x/crypto v0.11.0 h1:6Ewdq3tDic1mg5xRO4milcWCfMVQhI4NkqWWvqejpuA=
-golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio=
+golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
+golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
+golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA=
+golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
 golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
 golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
 golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@@ -272,6 +306,8 @@ golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKG
 golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
 golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
 golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
+golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
 golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -286,12 +322,19 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL
 golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
 golang.org/x/net v0.0.0-20210505024714-0287a6fb4125/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
-golang.org/x/net v0.12.0 h1:cfawfvKITfUsFCeJIHJrbSxpeu/E81khclypR0GVT50=
-golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA=
+golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
+golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
+golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
+golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
+golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
+golang.org/x/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc=
+golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
 golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
 golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
 golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@@ -305,6 +348,8 @@ golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJ
 golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -330,19 +375,33 @@ golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBc
 golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20211103235746-7861aae1554b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA=
-golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
+golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
-golang.org/x/term v0.10.0 h1:3R7pNqamzBraeqj/Tj8qt1aQ2HpmlC+Cx/qL/7hn4/c=
+golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
+golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
+golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
+golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
+golang.org/x/term v0.18.0 h1:FcHjZXDMxI8mM3nwhX9HlKop4C0YQvCVCdwYl2wOtE8=
+golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58=
 golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
+golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
-golang.org/x/text v0.11.0 h1:LAntKIrcmeSKERyiOh0XMV39LXS8IE9UL2yP7+f5ij4=
-golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
+golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
+golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
+golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
+golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
+golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
 golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
@@ -371,6 +430,8 @@ golang.org/x/tools v0.0.0-20200117065230-39095c1d176c/go.mod h1:TB2adYChydJhpapK
 golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
 golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
 golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
+golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
 golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=

+ 30 - 0
server/config/config.go

@@ -8,12 +8,21 @@ import (
 	"github.com/astaxie/beego/logs"
 	"github.com/astaxie/beego/orm"
 	"golang.org/x/oauth2"
+	"log"
 	"nginx-ui/server/models"
 	"nginx-ui/server/utils"
 	"os"
 	"strings"
 )
 
+type DbConfig struct {
+	Host string `json:"host"`
+	Port string `json:"port"`
+	User string `json:"user"`
+	Pass string `json:"pass"`
+	Name string `json:"name"`
+}
+
 type AppConfig struct {
 	Port                 int
 	BaseApi              string
@@ -25,6 +34,8 @@ type AppConfig struct {
 	ThirdSession         bool
 	ThirdSessionName     string
 	ThirdSessionCheckUrl string
+	DbType               string
+	DbConfig             DbConfig
 }
 
 type CompleteOauth2Config struct {
@@ -53,6 +64,13 @@ func GetDataDir() string {
 }
 
 func init() {
+	localConf := "./conf/app.local.conf"
+	if utils.IsExist(localConf) {
+		err := beego.LoadAppConfig("ini", localConf)
+		if err != nil {
+			log.Panic(err)
+		}
+	}
 	beego.BConfig.CopyRequestBody = true
 	mode := beego.AppConfig.DefaultString("runmode", "prod")
 	beego.BConfig.RunMode = mode
@@ -68,6 +86,7 @@ func init() {
 	Config.ContextPath = strings.TrimSuffix(Config.ContextPath, "/")
 	Config.BaseApi = baseApi
 	Config.DataDir = beego.AppConfig.DefaultString("datadir", "./data")
+	// DB
 	Config.DBDir = beego.AppConfig.DefaultString("dbdir", "./data/db")
 	if exist := utils.IsExist(Config.DataDir); exist == false {
 		err := os.MkdirAll(Config.DataDir, 0777)
@@ -76,6 +95,17 @@ func init() {
 			panic(err)
 		}
 	}
+	dbType := beego.AppConfig.DefaultString("db_type", "sqlite")
+	Config.DbType = dbType
+	if dbType != "sqlite" {
+		Config.DbConfig = DbConfig{
+			Host: beego.AppConfig.DefaultString("db_host", "127.0.0.1"),
+			Port: beego.AppConfig.DefaultString("db_port", "3306"),
+			User: beego.AppConfig.DefaultString("db_user", "nginx"),
+			Pass: beego.AppConfig.DefaultString("db_password", "nginx"),
+			Name: beego.AppConfig.DefaultString("db_name", "nginx-ui"),
+		}
+	}
 
 	Config.NginxPath = beego.AppConfig.DefaultString("nginxPath", "/usr/sbin/nginx")
 	Config.NginxDir = beego.AppConfig.DefaultString("nginxDir", "/etc/nginx")

+ 9 - 9
server/controllers/certificate.go

@@ -41,7 +41,7 @@ func (c *CertController) Get() {
 		return
 	}
 	if nginx.DataDir == "" {
-		c.setCode(-1).setMsg("请先配置数据目录位置!").json()
+		c.SetCode(-1).SetMsg("请先配置数据目录位置!").Json()
 		return
 	}
 	o := orm.NewOrm()
@@ -51,7 +51,7 @@ func (c *CertController) Get() {
 		c.ErrorJson(err)
 		return
 	}
-	c.setData(list).json()
+	c.SetData(list).Json()
 }
 
 // Sync 从配置的证书路径同步证书到数据库
@@ -83,7 +83,7 @@ func (c *CertController) Sync() {
 			}
 		}
 	}
-	c.setData(true).json()
+	c.SetData(true).Json()
 }
 
 // Post save certs
@@ -101,7 +101,7 @@ func (c *CertController) Post() {
 		return
 	}
 	if cert.Pem == "" || cert.Key == "" {
-		c.setCode(-1).setMsg("请输入证书私钥和公钥内容!").json()
+		c.SetCode(-1).SetMsg("请输入证书私钥和公钥内容!").Json()
 		return
 	}
 
@@ -131,7 +131,7 @@ func (c *CertController) Post() {
 		c.ErrorJson(err)
 		return
 	}
-	c.json()
+	c.Json()
 }
 
 // Delete del certs
@@ -154,8 +154,8 @@ func (c *CertController) Delete() {
 
 	dirs := ins.CheckDirs()
 	if dirs.CertsDir == "" || dirs.CertsDir == "/" {
-		c.setCode(-1).setMsg("请先配置证书路径,不能为根路径。")
-		c.json()
+		c.SetCode(-1).SetMsg("请先配置证书路径,不能为根路径。")
+		c.Json()
 		return
 	}
 	o := orm.NewOrm()
@@ -165,7 +165,7 @@ func (c *CertController) Delete() {
 		c.ErrorJson(err)
 		return
 	} else if err != nil && err == orm.ErrNoRows {
-		c.json()
+		c.Json()
 		return
 	}
 
@@ -182,5 +182,5 @@ func (c *CertController) Delete() {
 		c.ErrorJson(err)
 		return
 	}
-	c.setData(resp).json()
+	c.SetData(resp).Json()
 }

+ 3 - 3
server/controllers/check.go

@@ -11,11 +11,11 @@ import (
 
 // CheckNginxPermission 从path中获取nginx的参数
 func (c *BaseController) CheckNginxPermission() (*models.Nginx, error) {
-	idStr := c.getParam(":id")
+	idStr := c.GetParam(":id")
 	id, err := strconv.Atoi(idStr)
 	if err != nil {
 		logs.Warn("strconv.Atoi(idStr) fail", idStr)
-		c.setCode(-1).setMsg("请传递正确的参数!").json()
+		c.SetCode(-1).SetMsg("请传递正确的参数!").Json()
 		return nil, err
 	}
 	return c.CheckNginxPermissionById(id)
@@ -29,7 +29,7 @@ func (c *BaseController) CheckNginxPermissionById(nginxId int) (*models.Nginx, e
 		return nil, errors.New("当前未登录,无法操作")
 	}
 	if nginxId < 1 {
-		c.setCode(-1).setMsg("Nginx ID must gt 0!").json()
+		c.SetCode(-1).SetMsg("Nginx ID must gt 0!").Json()
 		return nil, errors.New("nginx ID must gt 0!")
 	}
 	nginx := models.Nginx{Id: nginxId}

+ 24 - 11
server/controllers/default.go

@@ -1,6 +1,8 @@
 package controllers
 
 import (
+	"encoding/json"
+	"errors"
 	"github.com/astaxie/beego"
 	"github.com/astaxie/beego/logs"
 	"nginx-ui/server/middleware"
@@ -11,11 +13,11 @@ import (
 type BaseController struct {
 	beego.Controller
 	jsonData *models.RespData
-	// json real data
+	// Json real data
 	respData map[string]any
 }
 
-func (c *BaseController) json() {
+func (c *BaseController) Json() {
 	c.checkJsonData()
 	if c.respData != nil {
 		c.jsonData.Data = c.respData
@@ -24,13 +26,13 @@ func (c *BaseController) json() {
 	c.ServeJSON()
 }
 
-func (c *BaseController) postJson(json interface{}) {
+func (c *BaseController) PostJson(json interface{}) {
 	c.Data["json"] = json
 	c.ServeJSON()
 }
 
 func (c *BaseController) ErrorJson(error error) {
-	c.setCode(-1).setMsg(error.Error()).json()
+	c.SetCode(-1).SetMsg(error.Error()).Json()
 }
 
 func (c *BaseController) checkJsonData() *BaseController {
@@ -46,13 +48,13 @@ func (c *BaseController) checkJsonData() *BaseController {
 	return c
 }
 
-func (c *BaseController) setData(v interface{}) *BaseController {
+func (c *BaseController) SetData(v interface{}) *BaseController {
 	c.checkJsonData()
 	c.jsonData.Data = v
 	return c
 }
 
-func (c *BaseController) addRespData(k string, v interface{}) *BaseController {
+func (c *BaseController) AddRespData(k string, v interface{}) *BaseController {
 	c.checkJsonData()
 	if c.respData == nil {
 		c.respData = map[string]any{}
@@ -61,21 +63,21 @@ func (c *BaseController) addRespData(k string, v interface{}) *BaseController {
 	return c
 }
 
-func (c *BaseController) setCode(code int) *BaseController {
+func (c *BaseController) SetCode(code int) *BaseController {
 	c.checkJsonData()
 	c.jsonData.Code = code
 	return c
 }
-func (c *BaseController) setMsg(msg string) *BaseController {
+func (c *BaseController) SetMsg(msg string) *BaseController {
 	c.checkJsonData()
 	c.jsonData.Msg = msg
 	return c
 }
 
-func (c *BaseController) getParam(k string) string {
+func (c *BaseController) GetParam(k string) string {
 	return c.Ctx.Input.Param(k)
 }
-func (c *BaseController) getIntParam(k string) (int, error) {
+func (c *BaseController) GetIntParam(k string) (int, error) {
 	idStr := c.Ctx.Input.Param(k)
 	id, err := strconv.Atoi(idStr)
 	logs.Info("id", id)
@@ -102,6 +104,17 @@ func (c *BaseController) RequiredUser() *models.User {
 	return c.GetUser(true)
 }
 
+// ReadBody data 非指针
+func (c *BaseController) ReadBody(data any) bool {
+	err := json.Unmarshal(c.Ctx.Input.RequestBody, &data)
+	if err != nil {
+		logs.Error(err, string(c.Ctx.Input.RequestBody))
+		c.ErrorJson(errors.New("数据解析异常!"))
+		return false
+	}
+	return true
+}
+
 func (c *BaseController) Forbidden() {
-	c.setCode(403).setMsg("您没有权限操作该实例!").json()
+	c.SetCode(403).SetMsg("您没有权限操作该实例!").Json()
 }

+ 2 - 2
server/controllers/file.go

@@ -93,7 +93,7 @@ func (c *FileController) Post() {
 		c.ErrorJson(err)
 		return
 	}
-	c.setData(toFile).json()
+	c.SetData(toFile).Json()
 }
 
 // Deploy 部署到服务器
@@ -120,7 +120,7 @@ func (c *FileController) Deploy() {
 		c.ErrorJson(err)
 		return
 	}
-	c.json()
+	c.Json()
 }
 
 func HandleDeploy(req models.DeployReq) error {

+ 1 - 1
server/controllers/logger.go

@@ -48,5 +48,5 @@ func (c *LoggerController) Post() {
 		c.ErrorJson(err)
 		return
 	}
-	c.setData(resp).json()
+	c.SetData(resp).Json()
 }

+ 19 - 19
server/controllers/nginx.go

@@ -38,7 +38,7 @@ func (c *NginxController) Get() {
 		c.ErrorJson(err)
 		return
 	} else {
-		c.setData(list).json()
+		c.SetData(list).Json()
 	}
 }
 
@@ -76,19 +76,19 @@ func (c *NginxController) Post() {
 	instance := ngx.GetInstance(&nginx)
 	err = instance.Connect()
 	if err != nil {
-		c.setCode(1).setMsg(err.Error()).setData(nginx)
-		c.json()
+		c.SetCode(1).SetMsg(err.Error()).SetData(nginx)
+		c.Json()
 		return
 	}
 	out, err := instance.GetVersion()
 	if err != nil {
-		c.setCode(1).setMsg(err.Error()).setData(nginx)
-		c.json()
+		c.SetCode(1).SetMsg(err.Error()).SetData(nginx)
+		c.Json()
 		return
 	}
 	nginx.VersionInfo = out
 	_, _ = o.Update(&nginx, "VersionInfo")
-	c.setData(nginx).json()
+	c.SetData(nginx).Json()
 }
 
 // Update modify nginx instance
@@ -127,19 +127,19 @@ func (c *NginxController) Update() {
 	instance := ngx.GetInstance(&nginx)
 	err = instance.Connect()
 	if err != nil {
-		c.setCode(1).setMsg(err.Error()).setData(nginx)
-		c.json()
+		c.SetCode(1).SetMsg(err.Error()).SetData(nginx)
+		c.Json()
 		return
 	}
 	out, err := instance.GetVersion()
 	if err != nil {
-		c.setCode(1).setMsg(err.Error()).setData(nginx)
-		c.json()
+		c.SetCode(1).SetMsg(err.Error()).SetData(nginx)
+		c.Json()
 		return
 	}
 	nginx.VersionInfo = out
 	_, _ = o.Update(&nginx, "VersionInfo")
-	c.setData(nginx).json()
+	c.SetData(nginx).Json()
 }
 
 // StartNginx startNginx
@@ -154,7 +154,7 @@ func (c *NginxController) StartNginx() {
 	instance := ngx.GetInstance(nginx)
 	err = instance.Start()
 	isRun, msg := instance.Status()
-	c.setData(isRun).setMsg(msg).json()
+	c.SetData(isRun).SetMsg(msg).Json()
 }
 
 // StopNginx add nginx instance
@@ -167,7 +167,7 @@ func (c *NginxController) StopNginx() {
 	instance := ngx.GetInstance(nginx)
 	err = instance.Stop()
 	isRun, msg := instance.Status()
-	c.setData(isRun).setMsg(msg).json()
+	c.SetData(isRun).SetMsg(msg).Json()
 }
 
 // RefreshHttp nginx detail data
@@ -205,7 +205,7 @@ func (c *NginxController) RefreshHttp() {
 		c.ErrorJson(err)
 		return
 	}
-	c.json()
+	c.Json()
 }
 
 // GetNginx nginx detail data
@@ -218,7 +218,7 @@ func (c *NginxController) GetNginx() {
 	if nginx.Password != "" {
 		nginx.Password = ReplacePassword
 	}
-	c.addRespData("nginx", nginx)
+	c.AddRespData("nginx", nginx)
 
 	o := orm.NewOrm()
 
@@ -228,8 +228,8 @@ func (c *NginxController) GetNginx() {
 		c.ErrorJson(err)
 		return
 	}
-	c.addRespData("servers", servers)
-	c.json()
+	c.AddRespData("servers", servers)
+	c.Json()
 }
 
 // DelNginx delete a instance
@@ -244,7 +244,7 @@ func (c *NginxController) DelNginx() {
 	if err != nil {
 		c.ErrorJson(err)
 	} else {
-		c.setData(count).json()
+		c.SetData(count).Json()
 	}
 }
 
@@ -257,5 +257,5 @@ func (c *NginxController) StatusNginx() {
 	}
 	instance := ngx.GetInstance(nginx)
 	isRun, msg := instance.Status()
-	c.setData(isRun).setMsg(msg).json()
+	c.SetData(isRun).SetMsg(msg).Json()
 }

+ 11 - 11
server/controllers/oauth2.go

@@ -17,9 +17,9 @@ type Oauth2Controller struct {
 }
 
 type Oauth2SSOReq struct {
-	Code  string `json:"code"`
-	Scope string `json:"scope"`
-	State string `json:"state"`
+	Code  string `Json:"code"`
+	Scope string `Json:"scope"`
+	State string `Json:"state"`
 }
 
 // Get 获取oauth2.0的登录url
@@ -30,7 +30,7 @@ func (c *Oauth2Controller) Get() {
 		return
 	}
 	url := config.OauthConfig.AuthCodeURL(state)
-	c.addRespData("redirect_url", url).addRespData("state", state).json()
+	c.AddRespData("redirect_url", url).AddRespData("state", state).Json()
 }
 
 // Callback 用户注册
@@ -44,27 +44,27 @@ func (c *Oauth2Controller) Callback() {
 	}
 	oauth := config.OauthConfig
 	if len(ssoReq.Code) == 0 {
-		c.setCode(-1).setMsg("登录失败(Code):code is empty").json()
+		c.SetCode(-1).SetMsg("登录失败(Code):code is empty").Json()
 		return
 	}
 	token, err := oauth.Exchange(context.Background(), ssoReq.Code)
 	if err != nil {
 		logs.Error("ExchangeToken", err)
-		c.setCode(-1).setMsg("登录失败(Exchange):" + err.Error()).json()
+		c.SetCode(-1).SetMsg("登录失败(Exchange):" + err.Error()).Json()
 		return
 	}
 	client := oauth.Client(context.Background(), token)
 	resp, err := client.Get(oauth.Userinfo)
 	if err != nil {
 		logs.Error("GetUserinfo", err)
-		c.setCode(-1).setMsg(fmt.Sprintf("登录失败(Userinfo):%s", err.Error())).json()
+		c.SetCode(-1).SetMsg(fmt.Sprintf("登录失败(Userinfo):%s", err.Error())).Json()
 		return
 	}
 	defer resp.Body.Close()
 	content, err := io.ReadAll(resp.Body)
 	if err != nil {
 		logs.Error("GetUserinfo Read Body", err)
-		c.setCode(-1).setMsg(fmt.Sprintf("登录失败(Userinfo):%s", err.Error())).json()
+		c.SetCode(-1).SetMsg(fmt.Sprintf("登录失败(Userinfo):%s", err.Error())).Json()
 		return
 	}
 	user := models.User{}
@@ -73,7 +73,7 @@ func (c *Oauth2Controller) Callback() {
 		logs.Error("GetUserinfo Unmarshal", err)
 	}
 	if len(user.Account) == 0 {
-		c.setCode(-1).setMsg("登录失败,请确认userinfo接口返回了account字段").json()
+		c.SetCode(-1).SetMsg("登录失败,请确认userinfo接口返回了account字段").Json()
 		return
 	}
 	if len(user.Nickname) == 0 {
@@ -86,9 +86,9 @@ func (c *Oauth2Controller) Callback() {
 	}
 	user.Password = ""
 	if err != nil {
-		c.setCode(-1).setMsg(fmt.Sprintf("保存用户失败:%s", err.Error())).json()
+		c.SetCode(-1).SetMsg(fmt.Sprintf("保存用户失败:%s", err.Error())).Json()
 		return
 	}
 	c.SetSession("user", user)
-	c.setData(user).json()
+	c.SetData(user).Json()
 }

+ 4 - 4
server/controllers/server.go

@@ -32,7 +32,7 @@ func (c *ServerController) Get() {
 		c.ErrorJson(err)
 		return
 	}
-	c.setData(server).json()
+	c.SetData(server).Json()
 }
 
 // Post add or update nginx instance
@@ -67,7 +67,7 @@ func (c *ServerController) Post() {
 	if saveErr != nil {
 		c.ErrorJson(saveErr)
 	} else {
-		c.setData(server).json()
+		c.SetData(server).Json()
 	}
 }
 
@@ -104,7 +104,7 @@ func (c *ServerController) Delete() {
 		c.ErrorJson(err)
 		return
 	}
-	c.setData("success").json()
+	c.SetData("success").Json()
 }
 
 // Refresh check and refresh to disk
@@ -139,5 +139,5 @@ func (c *ServerController) Refresh() {
 	}
 	postData.LastName = postData.Name
 	_, _ = o.Update(&postData)
-	c.setData(true).json()
+	c.SetData(true).Json()
 }

+ 3 - 3
server/controllers/user.go

@@ -32,7 +32,7 @@ func (c *UserController) Login() {
 	if resp.Success() {
 		c.SetSession("user", user)
 	}
-	c.postJson(resp)
+	c.PostJson(resp)
 }
 
 func (c *UserController) User() {
@@ -40,11 +40,11 @@ func (c *UserController) User() {
 	if user == nil {
 		return
 	}
-	c.setData(user).json()
+	c.SetData(user).Json()
 }
 
 // Register 用户注册
 func (c *UserController) Register() {
 	resp := c.service.SignUp(c.Ctx.Input.RequestBody)
-	c.postJson(resp)
+	c.PostJson(resp)
 }

+ 16 - 2
server/db/db.go

@@ -1,7 +1,9 @@
 package db
 
 import (
+	"fmt"
 	"github.com/astaxie/beego/orm"
+	_ "github.com/go-sql-driver/mysql"
 	_ "github.com/mattn/go-sqlite3"
 	"nginx-ui/server/config"
 	"nginx-ui/server/models"
@@ -15,8 +17,17 @@ func Init() {
 	if !utils.IsExist(dir) {
 		os.MkdirAll(dir, 0777)
 	}
-	orm.RegisterDriver("sqlite3", orm.DRSqlite)
-	orm.RegisterDataBase("default", "sqlite3", dir+"/sqlite.db")
+
+	if config.Config.DbType == "sqlite" {
+		orm.RegisterDriver("sqlite3", orm.DRSqlite)
+		orm.RegisterDataBase("default", "sqlite3", dir+"/sqlite.db")
+	} else {
+		db := config.Config.DbConfig
+		orm.RegisterDriver("mysql", orm.DRMySQL)
+		url := fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?charset=utf8", db.User, db.Pass, db.Host, db.Port, db.Name)
+		orm.RegisterDataBase("default", "mysql", url)
+	}
+
 	orm.SetMaxIdleConns("default", 50)
 	orm.SetMaxOpenConns("default", 200)
 
@@ -28,6 +39,9 @@ func Init() {
 	orm.RegisterModel(new(models.NginxCerts))
 	orm.RegisterModel(new(models.User))
 
+	orm.RegisterModel(new(models.LdapServer))
+	orm.RegisterModel(new(models.LdapUser))
+
 	orm.RunSyncdb("default", false, true)
 
 }

+ 2 - 0
server/middleware/auth.go

@@ -19,6 +19,8 @@ var whitelist = map[string]bool{
 	"/user/register":   true,
 	"/oauth2":          true,
 	"/oauth2/callback": true,
+	"/ldap/login":      true,
+	"/ldap/server":     true,
 }
 
 var UnauthorizedResp = `{"code": 401, "msg":"未登录或者登录已过期!"}`

+ 34 - 0
server/models/ldap.go

@@ -0,0 +1,34 @@
+package models
+
+// LdapServer LDAP 服务配置表
+type LdapServer struct {
+	Id int `orm:"pk;auto" json:"id"`
+	// 用户账号,唯一标识, Uid
+	Uid      string `orm:"size(100)" json:"uid"`
+	Name     string `orm:"size(255)" json:"name"`
+	Key      string `orm:"unique" json:"key"`
+	Url      string `json:"dn"`
+	Active   bool   `json:"active"`   // 是否激活可用
+	UserName string `json:"userName"` // 直接存储JSON数据,数组格式,多个
+	Password string `json:"password"`
+	BaseDN   string `orm:"size(255);column(base_dn)" json:"baseDN"`
+	Filter   string `orm:"size(255)" json:"filter"`
+	Remark   string `json:"remark"`
+}
+
+// LdapUser User 用户表
+// https://blog.csdn.net/wzjking0929/article/details/81153206
+type LdapUser struct {
+	Id int `orm:"pk;auto" json:"id"`
+	// 用户账号,唯一标识, Uid
+	Uid        string `orm:"unique" json:"uid"`
+	Account    string `json:"account"` // 即DN,eg. cn=test,dc=xxxx,dc=cn
+	UserName   string `json:"userName"`
+	Mail       string `json:"mail"`
+	DN         string `orm:"column(db)" json:"dn"`
+	Attributes string `orm:"null;type(text)" json:"attributes"` // 直接存储JSON数据,数组格式,多个
+	SignType   string `json:"signType"`                         // 密码加密方式
+	Password   string `json:"password"`
+	Remark     string `json:"remark"`
+	ServerKey  string `json:"serverId"`
+}

+ 8 - 4
server/models/user.go

@@ -1,6 +1,9 @@
 package models
 
-import "strings"
+import (
+	"strings"
+	"time"
+)
 
 // User 用户表
 type User struct {
@@ -10,9 +13,10 @@ type User struct {
 	Nickname string `json:"nickname"`
 	// 用户角色,admin为管理员,多个使用逗号分割
 	// 现在没多大用
-	Roles    string `json:"roles"`
-	Password string `json:"password"`
-	Remark   string `json:"remark"`
+	Roles     string    `json:"roles"`
+	Password  string    `json:"password"`
+	Remark    string    `json:"remark"`
+	CreatedAt time.Time `orm:"auto_now_add;type(datetime)" json:"createdAt"`
 }
 
 func (u *User) IsAdmin() bool {

+ 170 - 0
server/modules/ldap/client.go

@@ -0,0 +1,170 @@
+package ldap
+
+import (
+	"crypto/md5"
+	"encoding/hex"
+	"encoding/json"
+	"errors"
+	"fmt"
+	"github.com/astaxie/beego/logs"
+	"github.com/go-ldap/ldap/v3"
+	"nginx-ui/server/models"
+	"strings"
+)
+
+type Client struct {
+	*ldap.Conn
+	Url       string
+	Connected bool
+	BaseDN    string
+	Admin     string
+	Password  string
+	ServerKey string
+}
+
+var ActiveClients = make(map[string]*Client)
+
+func Create(url string, baseDN string) *Client {
+	var client = &Client{
+		Url:    url,
+		BaseDN: baseDN,
+	}
+	conn, err := ldap.DialURL(client.Url)
+	if err != nil {
+		logs.Error("dialUrl fail: %v", err)
+		client.Connected = false
+	} else {
+		client.Conn = conn
+		client.Connected = true
+	}
+	return client
+}
+
+func GetActiveClient(server *models.LdapServer) (*Client, error) {
+	client := ActiveClients[server.Key]
+	if client == nil {
+		client = Create(server.Url, server.BaseDN)
+		err := client.Bind(server.UserName, server.Password, true)
+		if err != nil {
+			logs.Error("Bind fail: %v", err)
+			return nil, err
+		}
+		ActiveClients[server.Key] = client
+		client.ServerKey = server.Key
+	}
+	return client, nil
+}
+
+func (c *Client) Close() {
+	if c.Connected && c.Conn != nil {
+		c.Conn.Close()
+	}
+}
+
+// Bind 验证账号密码?
+func (c *Client) Bind(username string, password string, isAdmin bool) error {
+	err := c.Conn.Bind(username, password)
+	if err != nil {
+		logs.Error("GSSAPIBind failed, err:%v", err)
+		return err
+	}
+	if isAdmin {
+		c.Admin = username
+		c.Password = password
+	}
+	return nil
+}
+
+func createUser(entry *ldap.Entry) models.LdapUser {
+	user := models.LdapUser{
+		Account:  entry.GetAttributeValue("uid"),
+		DN:       entry.DN,
+		Password: entry.GetAttributeValue("userPassword"),
+		UserName: entry.GetAttributeValue("cn"),
+		Mail:     entry.GetAttributeValue("mail"),
+	}
+	jsonBytes, err := json.Marshal(entry.Attributes)
+	if err != nil {
+		logs.Error("marshal fail : %v", err)
+		user.Remark = "attributes marshal fail: " + err.Error()
+	} else {
+		user.Attributes = string(jsonBytes)
+	}
+	return user
+}
+
+// Search 搜索用户 eg. (&(objectClass=organizationalPerson))
+func (c *Client) Search(filter string) ([]models.LdapUser, error) {
+
+	if filter == "" {
+		filter = "(objectClass=*)"
+	}
+	searchRequest := ldap.NewSearchRequest(
+		c.BaseDN, // The base dn to search
+		ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false,
+		filter,        // The filter to apply
+		[]string{"*"}, // A list attributes to retrieve,"dn", "cn", "objectClass",
+		nil,
+	)
+
+	sr, err := c.Conn.Search(searchRequest)
+	if err != nil {
+		logs.Error("search fail : %v", err)
+		return nil, err
+	}
+	var users []models.LdapUser
+	for _, entry := range sr.Entries {
+		user := createUser(entry)
+		user.ServerKey = c.ServerKey
+		users = append(users, user)
+	}
+	return users, nil
+}
+
+func (c *Client) ModifyAdminPassword() error {
+	return nil
+}
+
+func (c *Client) ModifyPassword() error {
+	return nil
+}
+
+func (c *Client) Modify() error {
+	return nil
+}
+
+func (c *Client) Authentication(account string, password string) (*models.LdapUser, error) {
+	// The username and password we want to check
+	users, err := c.Search(fmt.Sprintf("(&(objectClass=*)(uid=%s))", ldap.EscapeFilter(account)))
+	if err != nil {
+		logs.Error("search fail: %v", err)
+		return nil, err
+	}
+	if len(users) != 1 {
+		logs.Error("User does not exist or too many entries returned")
+		return nil, errors.New("未找到用户或者账号重复!")
+	}
+
+	userDN := users[0].Account
+	userPassword := users[0].Password
+	if strings.HasPrefix(userPassword, "{MD5}") {
+		pass := md5.Sum([]byte(password))
+		password = hex.EncodeToString(pass[:])
+		if strings.TrimPrefix(userPassword, "{MD5}") != password {
+			return nil, errors.New("登录失败,账号或者密码不正确!")
+		}
+	} else {
+		client, err := ldap.DialURL(c.Url)
+		if err != nil {
+			logs.Error("DialURL failed, err:%v", err)
+			return nil, errors.New("服务连接异常!")
+		}
+		defer client.Close()
+		err = client.Bind(userDN, password)
+		if err != nil {
+			logs.Error("GSSAPIBind failed, err:%v", err)
+			return nil, errors.New("登录失败,账号或者密码不正确!")
+		}
+	}
+	return &users[0], nil
+}

+ 28 - 0
server/modules/ldap/client_test.go

@@ -0,0 +1,28 @@
+package ldap
+
+import (
+	"github.com/astaxie/beego/logs"
+	"log"
+	"testing"
+)
+
+func TestCreate(t *testing.T) {
+	client := Create("ldap://192.168.1.95:389", "dc=tonyandmoney,dc=cn")
+	if client.Connected == false {
+		log.Panic("connect fail")
+	}
+	err := client.Bind("cn=admin,dc=tonyandmoney,dc=cn", "TQ1312@kmlsx", true)
+	if err != nil {
+		logs.Error("Test error:", err)
+		return
+	}
+	logs.Info("Test: ok")
+	users, err := client.Search("")
+	if err != nil {
+		log.Panic(err)
+	}
+	for _, user := range users {
+		logs.Info(user)
+	}
+	client.Close()
+}

+ 22 - 0
server/modules/ldap/router.go

@@ -0,0 +1,22 @@
+package ldap
+
+import (
+	"github.com/astaxie/beego"
+)
+
+func InitRouter(prefix string) *beego.Namespace {
+
+	ns := beego.NewNamespace(prefix+"/ldap",
+		beego.NSRouter("/server/active", &ServerController{}, "get:GetServer"),
+		beego.NSRouter("/server/list", &ServerController{}, "post:GetServers"),
+		beego.NSRouter("/server", &ServerController{}, "post:Update"),
+		beego.NSRouter("/server/detail", &ServerController{}, "get:GetServerDetail"),
+
+		beego.NSRouter("/login", &UserController{}, "post:Login"),
+		beego.NSRouter("/user/sync", &UserController{}, "post:SyncUsers"),
+		beego.NSRouter("/user/updatePassword", &UserController{}, "post:UpdateUserPassword"),
+		beego.NSRouter("/user/update", &UserController{}, "post:UpdateUser"),
+		beego.NSRouter("/user/list", &UserController{}, "post:GetUsers"),
+	)
+	return ns
+}

+ 90 - 0
server/modules/ldap/server_controller.go

@@ -0,0 +1,90 @@
+package ldap
+
+import (
+	"errors"
+	"github.com/astaxie/beego/orm"
+	"nginx-ui/server/controllers"
+	"nginx-ui/server/models"
+	"nginx-ui/server/vo"
+)
+
+type ServerController struct {
+	controllers.BaseController
+}
+
+var ServiceInstance = new(Service)
+
+// GetServer 获取一个可用的LDAP 连接, 用于登录时获取服务信息
+func (c *ServerController) GetServer() {
+	server, err := ServiceInstance.GetServer()
+	if err != nil {
+		c.ErrorJson(errors.New("no server"))
+		return
+	}
+	resp := make(map[string]interface{})
+	resp["server"] = server.Key
+}
+
+// GetServerDetail 获取用户所有的LDAP连接
+// get /ldap/server
+func (c *ServerController) GetServerDetail() {
+	current := c.RequiredUser()
+	if current == nil {
+		return
+	}
+
+	id, err := c.GetIntParam("id")
+	if err != nil {
+		c.ErrorJson(err)
+		return
+	}
+	server := models.LdapServer{
+		Id: id,
+	}
+
+	o := orm.NewOrm()
+	err = o.Read(&server)
+	if err != nil {
+		c.ErrorJson(err)
+		return
+	}
+	server.Password = ""
+	c.SetData(server).Json()
+}
+
+// GetServers 获取用户所有的LDAP连接
+// get /ldap/server
+func (c *ServerController) GetServers() {
+	current := c.RequiredUser()
+	if current == nil {
+		return
+	}
+
+	req := vo.PageReq{}
+	if !c.ReadBody(req) {
+		return
+	}
+	resp, err := ServiceInstance.GetServers(current, &req)
+	if err != nil {
+		c.ErrorJson(err)
+		return
+	}
+	c.SetData(resp).Json()
+}
+
+// Update 保存或者修改
+// post /ldap/server
+func (c *ServerController) Update() {
+	if current := c.RequiredUser(); current != nil {
+		var body = models.LdapServer{}
+		if !c.ReadBody(body) {
+			return
+		}
+		user, err := ServiceInstance.Update(current, &body)
+		if err != nil {
+			c.ErrorJson(err)
+			return
+		}
+		c.SetData(user).Json()
+	}
+}

+ 209 - 0
server/modules/ldap/service.go

@@ -0,0 +1,209 @@
+package ldap
+
+import (
+	"crypto/md5"
+	"encoding/hex"
+	"errors"
+	"github.com/astaxie/beego/logs"
+	"github.com/astaxie/beego/orm"
+	"nginx-ui/server/models"
+	"nginx-ui/server/vo"
+)
+
+type Service struct {
+}
+
+// GetServer 获取一个可用的LDAP 连接, 用于登录时获取服务信息
+func (c *Service) GetServer() (*models.LdapServer, error) {
+	o := orm.NewOrm()
+	server := models.LdapServer{
+		Active: true,
+	}
+	err := o.Read(&server, "Active")
+	if err != nil {
+		return nil, err
+	}
+	return &server, nil
+}
+
+func (c *Service) Login(req *LDAPLoginReq) (*models.User, error) {
+
+	server := models.LdapServer{Key: req.Server}
+	o := orm.NewOrm()
+	err := o.Read(&server, "Key")
+
+	client, err := GetActiveClient(&server)
+	if err != nil {
+		return nil, err
+	}
+
+	ldapUser, err := client.Authentication(req.Account, req.Password)
+	if err != nil {
+		return nil, err
+	}
+	ldapUser.Uid = server.Uid
+	ldapUser.ServerKey = server.Key
+	_, err = o.InsertOrUpdate(ldapUser, "DN")
+	if err != nil {
+		return nil, err
+	}
+	var user = models.User{
+		Account: ldapUser.Account,
+	}
+	err = o.Read(&user, "Account")
+	if err != nil {
+		if !errors.Is(err, orm.ErrNoRows) {
+			return nil, err
+		}
+		user.Password = ldapUser.Password
+		user.Account = ldapUser.Account
+		user.Nickname = ldapUser.UserName
+		_, err := o.Insert(&user)
+		if err != nil {
+			return nil, err
+		}
+	}
+	user.Password = ""
+	return &user, nil
+}
+
+// GetServers 获取用户所有的LDAP连接
+// get /ldap/server
+func (c *Service) GetServers(current *models.User, req *vo.PageReq) (*vo.PageResp, error) {
+	o := orm.NewOrm()
+	req.Ensure()
+
+	qs := o.QueryTable(&models.LdapServer{})
+	if !current.IsAdmin() {
+		qs = qs.Filter("Uid", current.Account)
+	}
+
+	total, err := qs.Count()
+	if err != nil {
+		return nil, err
+	}
+
+	qs.OrderBy("Id")
+	qs.Offset(req.Offset)
+	qs.Limit(req.PageSize)
+	var list []*models.LdapServer
+	_, err = qs.All(&list)
+
+	if err != nil {
+		return nil, err
+	}
+	resp := vo.PageResp{
+		Current:  req.Current,
+		PageSize: req.PageSize,
+		Total:    total,
+		List:     list,
+	}
+	return &resp, err
+}
+
+// SyncUsers 同步用户信息
+// post /ldap/user/sync
+func (c *Service) SyncUsers(current *models.User, req *LDAPUserSyncReq) (int, error) {
+	server := models.LdapServer{Key: req.ServerKey}
+	o := orm.NewOrm()
+	err := o.Read(&server, "Key")
+	if err != nil {
+		return 0, err
+	}
+	client, err := GetActiveClient(&server)
+	if err != nil {
+		return 0, err
+	}
+
+	users, err := client.Search(req.Filter)
+	if err != nil {
+		return 0, err
+	}
+	for _, user := range users {
+		user.ServerKey = server.Key
+		user.Uid = string(rune(current.Id))
+		_, err := o.InsertOrUpdate(&user, "DN")
+		if err != nil {
+			logs.Error("save user fail: %v", err)
+		}
+	}
+	return len(users), nil
+}
+
+// Update 保存或者修改
+// post /ldap/server
+func (c *Service) Update(current *models.User, body *models.LdapServer) (*models.LdapServer, error) {
+
+	if body.Url == "" {
+		return nil, errors.New("请完成服务配置,缺少Url!")
+	}
+	if body.Key == "" {
+		key := md5.Sum([]byte(body.Url))
+		body.Key = hex.EncodeToString(key[:])
+	}
+	o := orm.NewOrm()
+	if body.Id == 0 {
+		exist := models.LdapServer{Key: body.Key}
+		err := o.Read(&exist, "Key")
+		if err != nil {
+			return nil, err
+		}
+		if exist.Id > 0 {
+			return nil, errors.New("该服务Url已存在!")
+		}
+	}
+	if body.Id > 0 {
+		_, err := o.Update(&body, "Id")
+		if err != nil {
+			return nil, err
+		}
+	} else {
+		id, err := o.Insert(&body)
+		if err != nil {
+			return nil, err
+		}
+		body.Id = int(id)
+	}
+	return body, nil
+}
+
+// UpdateUserPassword 更新用户密码
+// post /ldap/user/modifyPassword
+func (c *Service) UpdateUserPassword() {
+
+}
+
+// GetUsers 获取全部用户
+// get /ldap/users
+func (c *Service) GetUsers(current *models.User, req *vo.PageReq) (*vo.PageResp, error) {
+	req.Ensure()
+
+	o := orm.NewOrm()
+
+	qs := o.QueryTable(&models.LdapUser{})
+	if !current.IsAdmin() {
+		qs = qs.Filter("Uid", current.Account)
+	}
+
+	total, err := qs.Count()
+	if err != nil {
+		return nil, err
+	}
+
+	qs.OrderBy("Id")
+	qs.Offset(req.Offset)
+	qs.Limit(req.PageSize)
+	var list []*models.LdapUser
+	_, err = qs.All(&list)
+
+	if err != nil {
+		return nil, err
+	}
+	resp := vo.PageResp{
+		Current:  req.Current,
+		PageSize: req.PageSize,
+		Total:    total,
+		List:     list,
+	}
+	return &resp, nil
+}

+ 89 - 0
server/modules/ldap/user_controller.go

@@ -0,0 +1,89 @@
+package ldap
+
+import (
+	"nginx-ui/server/controllers"
+	"nginx-ui/server/models"
+	ngx "nginx-ui/server/nginx"
+	"nginx-ui/server/vo"
+)
+
+type UserController struct {
+	controllers.BaseController
+}
+
+func (c *UserController) Login() {
+
+	req := LDAPLoginReq{}
+	if c.ReadBody(req) == false {
+		return
+	}
+	user, err := ServiceInstance.Login(&req)
+	if err != nil {
+		c.ErrorJson(err)
+		return
+	}
+	user.Password = ""
+	c.SetSession("user", user)
+	c.PostJson(models.SuccessResp(user))
+}
+
+// SyncUsers 同步用户信息
+// post /ldap/user/sync
+func (c *UserController) SyncUsers() {
+	current := c.RequiredUser()
+	if current == nil {
+		return
+	}
+	req := LDAPUserSyncReq{}
+	if !c.ReadBody(req) {
+		return
+	}
+	count, err := ServiceInstance.SyncUsers(current, &req)
+	if err != nil {
+		c.ErrorJson(err)
+		return
+	}
+	resp := make(map[string]interface{})
+	resp["count"] = count
+	c.SetData(resp).Json()
+}
+
+// UpdateUserPassword 更新用户密码
+// post /ldap/user/modifyPassword
+func (c *UserController) UpdateUserPassword() {
+
+	nginx, err := c.CheckNginxPermission()
+	if err != nil {
+		return
+	}
+
+	instance := ngx.GetInstance(nginx)
+	err = instance.Start()
+	isRun, msg := instance.Status()
+	c.SetData(isRun).SetMsg(msg).Json()
+}
+
+// UpdateUser 更新用户信息或者新增用户
+// post /ldap/user/save
+func (c *UserController) UpdateUser() {
+
+}
+
+// GetUsers 获取全部用户
+// get /ldap/users
+func (c *UserController) GetUsers() {
+	current := c.RequiredUser()
+	if current == nil {
+		return
+	}
+	req := vo.PageReq{}
+	if !c.ReadBody(req) {
+		return
+	}
+	resp, err := ServiceInstance.GetUsers(current, &req)
+	if err != nil {
+		c.ErrorJson(err)
+		return
+	}
+	c.SetData(resp).Json()
+}

+ 13 - 0
server/modules/ldap/vo.go

@@ -0,0 +1,13 @@
+package ldap
+
+// LDAPLoginReq LDAP登录
+type LDAPLoginReq struct {
+	Server   string `json:"server"`
+	Account  string `json:"account"`
+	Password string `json:"password"`
+}
+
+type LDAPUserSyncReq struct {
+	Filter    string `json:"filter"`
+	ServerKey string `json:"serverKey"`
+}

+ 3 - 0
server/routers/router.go

@@ -11,6 +11,7 @@ import (
 	"nginx-ui/server/controllers"
 	"nginx-ui/server/middleware"
 	"nginx-ui/server/models"
+	"nginx-ui/server/modules/ldap"
 	"strings"
 )
 
@@ -55,6 +56,8 @@ func init() {
 		beego.NSRouter("/oauth2/callback", &controllers.Oauth2Controller{}, "post:Callback"),
 	)
 	beego.AddNamespace(ns)
+	// LDAP路由
+	beego.AddNamespace(ldap.InitRouter(config.BaseApi))
 
 	beego.InsertFilter(fmt.Sprintf("%s/**", config.BaseApi), beego.BeforeRouter, middleware.AuthFilter)
 

+ 26 - 0
server/vo/base.go

@@ -0,0 +1,26 @@
+package vo
+
+type PageReq struct {
+	Current  int `json:"current"`
+	PageSize int `json:"pageSize"`
+	Offset   int `json:"offset"`
+}
+
+func (r *PageReq) Ensure() {
+	if r.Current < 1 {
+		r.Current = 1
+	}
+	if r.PageSize < 1 {
+		r.PageSize = 10
+	}
+	if r.Offset == 0 {
+		r.Offset = (r.Current - 1) * r.PageSize
+	}
+}
+
+type PageResp struct {
+	Total    int64       `json:"total"`
+	List     interface{} `json:"list"`
+	Current  int         `json:"current"`
+	PageSize int         `json:"pageSize"`
+}

+ 22 - 0
yarn.lock

@@ -0,0 +1,22 @@
+# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
+# yarn lockfile v1
+
+
+"@babel/runtime@^7.5.5":
+  version "7.24.7"
+  resolved "https://registry.npmmirror.com/@babel/runtime/-/runtime-7.24.7.tgz#f4f0d5530e8dbdf59b3451b9b3e594b6ba082e12"
+  integrity sha512-UwgBRMjJP+xv857DCngvqXI3Iq6J4v0wXmwc6sapg+zyhbwmQX67LUEFrkK5tbyJ30jGuG3ZvWpBiB9LCy1kWw==
+  dependencies:
+    regenerator-runtime "^0.14.0"
+
+i18next-browser-languagedetector@5.x:
+  version "5.0.1"
+  resolved "https://registry.npmmirror.com/i18next-browser-languagedetector/-/i18next-browser-languagedetector-5.0.1.tgz#02a18f57f79b5de06279feb9a84c80ea4dde7da9"
+  integrity sha512-7K4A6DJ2rNz3Yd835Y493UgkzUxgpGsCeIMKLGkt6Ps0cbgSaJ+LdATFNFA+ujp2brmsUM9BeDThXKhabXUbUw==
+  dependencies:
+    "@babel/runtime" "^7.5.5"
+
+regenerator-runtime@^0.14.0:
+  version "0.14.1"
+  resolved "https://registry.npmmirror.com/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz#356ade10263f685dda125100cd862c1db895327f"
+  integrity sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==