Browse Source

1、feat: 增加wails 打包成桌面应用,探索下

tuon 1 year ago
parent
commit
6451c6740b
100 changed files with 5766 additions and 5570 deletions
  1. 7 130
      README.md
  2. 18 18
      build-server.sh
  3. BIN
      build/appicon.png
  4. BIN
      build/bin/nginx-ui-desktop-dev.exe
  5. BIN
      build/bin/nginx-ui-dev.exe
  6. BIN
      build/windows/icon.ico
  7. 15 0
      build/windows/info.json
  8. 15 0
      build/windows/wails.exe.manifest
  9. BIN
      data/db/sqlite.db
  10. 90 0
      desktop/api.go
  11. 16 0
      desktop/session.go
  12. 1 1
      docker/Dockerfile
  13. 0 0
      frontend/.env
  14. 0 0
      frontend/.env.production
  15. 16 16
      frontend/.eslintrc.cjs
  16. 21 21
      frontend/LICENSE
  17. 136 0
      frontend/README.md
  18. 0 0
      frontend/dist/assets/index-154a1b67.js
  19. 0 0
      frontend/dist/assets/index-49502c45.css
  20. 5 5
      frontend/dist/config.js
  21. 1 1
      frontend/dist/index.html
  22. 0 0
      frontend/dist/vite.svg
  23. 14 14
      frontend/index.html
  24. 66 66
      frontend/package.json
  25. 1 0
      frontend/package.json.md5
  26. 5 5
      frontend/public/config.js
  27. 0 0
      frontend/public/vite.svg
  28. 41 41
      frontend/src/App.css
  29. 25 25
      frontend/src/App.tsx
  30. 27 27
      frontend/src/adapter/index.js
  31. 27 0
      frontend/src/api/desktop.api.ts
  32. 127 127
      frontend/src/api/nginx.ts
  33. 77 74
      frontend/src/api/request.ts
  34. 32 28
      frontend/src/api/user.ts
  35. 0 0
      frontend/src/assets/react.svg
  36. 29 29
      frontend/src/components/BackButton.tsx
  37. 14 14
      frontend/src/components/empty/index.less
  38. 11 11
      frontend/src/components/empty/index.tsx
  39. 12 0
      frontend/src/config/consts.ts
  40. 723 723
      frontend/src/config/nginx_form.json
  41. 68 68
      frontend/src/config/nginx_template.json
  42. 75 75
      frontend/src/index.css
  43. 56 56
      frontend/src/main.tsx
  44. 41 41
      frontend/src/models/api.ts
  45. 284 284
      frontend/src/models/nginx.ts
  46. 13 13
      frontend/src/models/user.ts
  47. 35 35
      frontend/src/pages/login/index.less
  48. 117 117
      frontend/src/pages/login/index.tsx
  49. 0 0
      frontend/src/pages/login/sso.tsx
  50. 52 52
      frontend/src/pages/nginx/certs/index.less
  51. 249 249
      frontend/src/pages/nginx/certs/index.tsx
  52. 56 56
      frontend/src/pages/nginx/components/EditNginxBtn.tsx
  53. 97 97
      frontend/src/pages/nginx/components/StopStartButton.tsx
  54. 0 0
      frontend/src/pages/nginx/components/access/config.json
  55. 0 0
      frontend/src/pages/nginx/components/access/index.less
  56. 0 0
      frontend/src/pages/nginx/components/access/index.tsx
  57. 42 42
      frontend/src/pages/nginx/components/auth/config.json
  58. 10 10
      frontend/src/pages/nginx/components/auth/index.less
  59. 40 40
      frontend/src/pages/nginx/components/auth/index.tsx
  60. 37 37
      frontend/src/pages/nginx/components/basic/index.less
  61. 133 133
      frontend/src/pages/nginx/components/basic/index.tsx
  62. 52 52
      frontend/src/pages/nginx/components/certs/index.tsx
  63. 53 53
      frontend/src/pages/nginx/components/cors/config.json
  64. 39 39
      frontend/src/pages/nginx/components/cors/index.less
  65. 110 110
      frontend/src/pages/nginx/components/cors/index.tsx
  66. 32 32
      frontend/src/pages/nginx/components/error/config.json
  67. 2 2
      frontend/src/pages/nginx/components/error/index.less
  68. 73 73
      frontend/src/pages/nginx/components/error/index.tsx
  69. 0 0
      frontend/src/pages/nginx/components/fastcgi/config.json
  70. 0 0
      frontend/src/pages/nginx/components/fastcgi/index.less
  71. 0 0
      frontend/src/pages/nginx/components/fastcgi/index.tsx
  72. 64 64
      frontend/src/pages/nginx/components/gzip/config.json
  73. 10 10
      frontend/src/pages/nginx/components/gzip/index.less
  74. 97 97
      frontend/src/pages/nginx/components/gzip/index.tsx
  75. 12 12
      frontend/src/pages/nginx/components/index.ts
  76. 56 56
      frontend/src/pages/nginx/components/input.ts
  77. 301 301
      frontend/src/pages/nginx/components/location/config.json
  78. 41 41
      frontend/src/pages/nginx/components/location/index.less
  79. 296 296
      frontend/src/pages/nginx/components/location/index.tsx
  80. 108 108
      frontend/src/pages/nginx/components/location/utils.ts
  81. 0 0
      frontend/src/pages/nginx/components/log/config.json
  82. 0 0
      frontend/src/pages/nginx/components/log/index.less
  83. 0 0
      frontend/src/pages/nginx/components/log/index.tsx
  84. 199 199
      frontend/src/pages/nginx/components/proxy/config.json
  85. 29 29
      frontend/src/pages/nginx/components/proxy/index.less
  86. 87 87
      frontend/src/pages/nginx/components/proxy/index.tsx
  87. 70 70
      frontend/src/pages/nginx/components/proxy/utils.ts
  88. 30 30
      frontend/src/pages/nginx/components/proxypass/index.less
  89. 111 111
      frontend/src/pages/nginx/components/proxypass/index.tsx
  90. 70 70
      frontend/src/pages/nginx/components/proxypass/stream.tsx
  91. 352 352
      frontend/src/pages/nginx/components/site/components/Dragger.tsx
  92. 59 59
      frontend/src/pages/nginx/components/site/components/dragger.less
  93. 25 25
      frontend/src/pages/nginx/components/site/index.less
  94. 159 159
      frontend/src/pages/nginx/components/site/index.tsx
  95. 0 0
      frontend/src/pages/nginx/components/utils/index.ts
  96. 42 42
      frontend/src/pages/nginx/config.tsx
  97. 63 63
      frontend/src/pages/nginx/help/args.mdx
  98. 22 22
      frontend/src/pages/nginx/help/index.less
  99. 25 25
      frontend/src/pages/nginx/help/index.tsx
  100. 100 100
      frontend/src/pages/nginx/http/components/HttpConfSync.tsx

+ 7 - 130
README.md

@@ -1,136 +1,13 @@
-# nginx 可视化界面
+# nginx-ui desktop
-项目的主要功能未nginx的配置管理,通过可视化的界面去配置nginx,所有的配置渲染逻辑都在前端进行,通过后台服务渲染到部署nginx的服务器上;\
-由于nginx的配置实在是太多了,只是可视化了部分常用的功能;\
-可以用于开发环境,需要经常变动一些配置信息的场景.
 
 
-- 项目的web前端基于react开发,使用vite构建工具;
+参考文档: https://wails.io/zh-Hans/docs/reference/project-config
-- 后端使用golang语言开发(菜鸟学习中)
 
 
-## demos and docs
+## 开发
-[在线demo](http://demos.tonyandmoney.cn/nginx-ui/#/) \
-账号: demo \
-密码: demo
-
-[在线文档](https://portal.tonyandmoney.cn/common/notes/html/pages/list?type=nginx-ui)
-
-## 快速部署
-
-- docker-compose
-```yaml
-
-version: "3"
-
-services:
-  nginx-with-ui:
-    image: registry.cn-hangzhou.aliyuncs.com/tuon-pub/nginx-with-ui:latest
-    restart: always
-    ports:
-      - 8080:8080
-    #    network_mode: host
-    volumes:
-      - ./data:/app/data
-      - ./data/conf:/app/conf
-
-```
-- docker快速启动
-
-```shell
-docker run -itd --name nginx-ui -p8080:8080 -v {datadir}:/app/data -v {confdir}:/app/conf registry.cn-hangzhou.aliyuncs.com/tuon-pub/nginx-with-ui:latest
-```
-
-- 说明
-  - 8080为nginx-ui的服务端口,其余则为nginx代理端口,自定义或者直接使用 network_mode: host 模式即可
-  - 启动成功后,在登录界面,先注册账号后,使用注册的账号登录即可使用,管理员账号可以查看docker的启动日志查看
-  - 配置文件参考[在线文档](https://portal.tonyandmoney.cn/common/notes/html/pages/list?type=nginx-ui)
-
-
-## 构建
-项目构建基于docker-compose, 分为两种情况
-- 基础镜像为nginx
-  容器自带nginx,启用web服务
-    ```
-  docker-compose -f ./docker-compose-dev.yaml build
-  ```
-- 基础镜像为debian:sid-slim
-  镜像不包含nginx,仅启动web服务
-    ```
-  docker-compose -f ./docker-compose.yaml build
-  ```
-前端在本地构建,以上构建方式不包含前端
-
-## 部署
-### 使用docker部署
-- 将docker-compose.yaml 或者 docker-compose-dev.yaml 复制到自己的文件夹下
-修改编排中的volumes,更改目录映射,容器内 /app/data 为目录持久化数据所在目录。
 ```shell
 ```shell
-docker-compose -f ./docker-compose.yaml up -d
+wails dev
-```
-使用IP:8080端口访问
-或者
-```shell
-docker run -itd -v ./data/:/app/data --network host --name registry.cn-hangzhou.aliyuncs.com/tuon-pub/nginx-with-ui:latest
-# or
-docker run -itd -v ./data/:/app/data -p8080:8080 --name registry.cn-hangzhou.aliyuncs.com/tuon-pub/nginx-ui
 ```
 ```
 
 
-### 本地部署
+## 打包
 ```shell
 ```shell
-# 下载构建产物,解压
+wails build
-
+```
-```
-
-## 截图
-  ![实例列表](./docs/images/list.png)
-
-  ![实例信息](./docs/images/dashboard.png)
-
-  ![负载均衡](./docs/images/upstream.png)
-
-  ![虚拟主机](./docs/images/server.png)
-
-## 参考文档
-配置部分参考一下文档:
-
-- [Nginx Rewrite](https://blog.csdn.net/qq1356059950/article/details/125014248)
-- [nginx负载均衡](https://zhuanlan.zhihu.com/p/557994010?utm_id=0)
-
-
-## 构建部署
-以下操作进入到项目根目录执行
-### 构建
- docker-compose build 或者执行脚本sh build.sh
-
-
-
-## nginx-ui优化点
-- [x] nginx实例列表界面,添加完实例之后,弹窗没有关闭,且没有自动刷新当前界面
-- [x] server或者location的rewrite是否配置的判定问题
-- [x] nginx 实例设置时,将需要的目录文件创建好
-- [x] 新增虚拟主机或者其它新增界面,无法重置当前的表单,需要结合planning-tools,增加重置功能
-- [x] 证书管理,将证书信息保存到数据库,方便做nginx服务前移,不能直接写文件,可以增加从文件夹同步的功能
-- [x] 后端docker启动时,默认启动本地的NGINX,docker镜像问题
-- [ ] 很多界面的默认值问题,优化初始值
-- [x] nginx.conf默认值问题,每次容器重启都会被重置 
-- [x] 增强证书管理:添加时间,有效期,域名
-- [x] 增加一些快捷按钮,比如转发真实IP,支持websocket,跨域设置等
-- [x] 考虑是否增加静态站点的文件上传功能
-- [ ] 考虑多租户的功能,目前前端使用的是nginx auth的授权认证,该方式是否能传递用户id作为查询数据的条件
-- [ ] 考虑增加jwt,basic授权
-
-### 2023-07-06
-- [ ] [ngx_http_auth_request_module](https://nginx.org/en/docs/http/ngx_http_auth_request_module.html)
-    鉴权模块的实现
-
-## 更新日志
-- 20230710:修复return 语句未渲染的问题
-- 20230719: 修复return语句在代理或者静态站点的情况下依然渲染的问题
-
-## git代理
-git config --global http.proxy http://127.0.0.1:{port}
-
-git config --global https.proxy  http://127.0.0.1:{port}
-
-
-git config --global --unset http.proxy
-
-git config --global --unset https.proxy

+ 18 - 18
build-server.sh

@@ -1,18 +1,18 @@
-#!/bin/bash
+#!/bin/bash
-CURRENT_DIR=$(cd $(dirname $0); pwd)
+CURRENT_DIR=$(cd $(dirname $0); pwd)
-
+
-mkdir -p ./local
+mkdir -p ./local
-mkdir -p ./local/data/db
+mkdir -p ./local/data/db
-cp -rf ./server/conf ./local
+cp -rf ./server/conf ./local
-cp -rf ./server/static ./local
+cp -rf ./server/static ./local
-
+
-cd ./server
+cd ./server
-export GOODS=linux
+export GOODS=linux
-export GOARCH=amd64
+export GOARCH=amd64
-go build -o ../local/server
+go build server/main.go -o ../local/server
-cd $CURRENT_DIR
+cd $CURRENT_DIR
-cp -rf ./dist/* ./local/static/web/
+cp -rf ./dist/* ./local/static/web/
-rm -rf ./local/static/web/config.js
+rm -rf ./local/static/web/config.js
-chmod +x ./local/server
+chmod +x ./local/server
-
+
-tar -czf nginx-ui.tar.gz ./local
+tar -czf nginx-ui.tar.gz ./local

BIN
build/appicon.png


BIN
build/bin/nginx-ui-desktop-dev.exe


BIN
build/bin/nginx-ui-dev.exe


BIN
build/windows/icon.ico


+ 15 - 0
build/windows/info.json

@@ -0,0 +1,15 @@
+{
+	"fixed": {
+		"file_version": "{{.Info.ProductVersion}}"
+	},
+	"info": {
+		"0000": {
+			"ProductVersion": "{{.Info.ProductVersion}}",
+			"CompanyName": "{{.Info.CompanyName}}",
+			"FileDescription": "{{.Info.ProductName}}",
+			"LegalCopyright": "{{.Info.Copyright}}",
+			"ProductName": "{{.Info.ProductName}}",
+			"Comments": "{{.Info.Comments}}"
+		}
+	}
+}

+ 15 - 0
build/windows/wails.exe.manifest

@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1" xmlns:asmv3="urn:schemas-microsoft-com:asm.v3">
+    <assemblyIdentity type="win32" name="com.wails.{{.Name}}" version="{{.Info.ProductVersion}}.0" processorArchitecture="*"/>
+    <dependency>
+        <dependentAssembly>
+            <assemblyIdentity type="win32" name="Microsoft.Windows.Common-Controls" version="6.0.0.0" processorArchitecture="*" publicKeyToken="6595b64144ccf1df" language="*"/>
+        </dependentAssembly>
+    </dependency>
+    <asmv3:application>
+        <asmv3:windowsSettings>
+            <dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true/pm</dpiAware> <!-- fallback for Windows 7 and 8 -->
+            <dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">permonitorv2,permonitor</dpiAwareness> <!-- falls back to per-monitor if per-monitor v2 is not supported -->
+        </asmv3:windowsSettings>
+    </asmv3:application>
+</assembly>

BIN
data/db/sqlite.db


+ 90 - 0
desktop/api.go

@@ -0,0 +1,90 @@
+package desktop
+
+import (
+	"context"
+	"encoding/json"
+	"github.com/astaxie/beego/logs"
+	"nginx-ui/server/models"
+	"nginx-ui/server/service"
+)
+
+var ApiSession = Session{}
+
+var userService = service.NewUserService()
+
+// Api struct
+type Api struct {
+	ctx context.Context
+}
+
+// NewApi NewApp creates a new App application struct
+func NewApi() *Api {
+	return &Api{}
+}
+
+// Startup is called when the app starts. The context is saved
+// so we can call the runtime methods
+func (a *Api) Startup(ctx context.Context) {
+	ApiSession.ctx = ctx
+	a.ctx = ctx
+
+}
+
+type ApiResp struct {
+	Data models.RespData `json:"data"`
+}
+
+// PostApi 做一个统一的适配层
+func (a *Api) PostApi(path string, req string) ApiResp {
+	logs.Info("path: %s, data: %s", path, req)
+	var data models.RespData
+	switch path {
+	case "/user/login":
+		var user models.User
+		err := json.Unmarshal([]byte(req), &user)
+		if err != nil {
+			logs.Error(err, req)
+			data = models.NewErrorResp(err)
+		}
+		data = userService.Login(user)
+		break
+	case "/user/info":
+		break
+	}
+	return ApiResp{Data: data}
+}
+
+func (a *Api) GetApi(path string, req string) ApiResp {
+	logs.Info("path: %s, data: %s", path, req)
+	var data models.RespData
+	switch path {
+	case "/user/info":
+		data = models.SuccessResp(ApiSession.user)
+		break
+	}
+	return ApiResp{Data: data}
+}
+
+func (a *Api) DeleteApi(path string, req string) ApiResp {
+	logs.Info("path: %s, data: %s", path, req)
+	switch path {
+
+	case "/user/login":
+		break
+	case "/user/info":
+		break
+	}
+	return ApiResp{}
+}
+
+func (a *Api) PutApi(path string, req string) ApiResp {
+	logs.Info("path: %s, data: %s", path, req)
+	switch path {
+
+	case "/user/login":
+		break
+	case "/user/info":
+		break
+	}
+	return ApiResp{}
+}

+ 16 - 0
desktop/session.go

@@ -0,0 +1,16 @@
+package desktop
+
+import (
+	"context"
+	"nginx-ui/server/models"
+)
+
+type Session struct {
+	ctx context.Context
+	//缓存当前的用户信息
+	user *models.User
+}
+
+func (s *Session) SetUser(user *models.User) {
+	s.user = user
+}

+ 1 - 1
docker/Dockerfile

@@ -16,7 +16,7 @@ COPY --from=builder /app/app /app
 COPY server/conf /app/conf
 COPY server/conf /app/conf
 COPY server/data  /app/data
 COPY server/data  /app/data
 #COPY ../server/static  /app/static
 #COPY ../server/static  /app/static
-COPY dist  /app/static/web
+COPY frontend/dist  /app/static/web
 
 
 RUN chmod +x /app/app
 RUN chmod +x /app/app
 
 

+ 0 - 0
.env → frontend/.env


+ 0 - 0
.env.production → frontend/.env.production


+ 16 - 16
.eslintrc.cjs → frontend/.eslintrc.cjs

@@ -1,16 +1,16 @@
-module.exports = {
+module.exports = {
-  env: { browser: true, es2020: true },
+  env: { browser: true, es2020: true },
-  extends: [
+  extends: [
-    'eslint:recommended',
+    'eslint:recommended',
-    'plugin:@typescript-eslint/recommended',
+    'plugin:@typescript-eslint/recommended',
-    'plugin:react-hooks/recommended',
+    'plugin:react-hooks/recommended',
-  ],
+  ],
-  parser: '@typescript-eslint/parser',
+  parser: '@typescript-eslint/parser',
-  parserOptions: { ecmaVersion: 'latest', sourceType: 'module' },
+  parserOptions: { ecmaVersion: 'latest', sourceType: 'module' },
-  plugins: ['react-refresh'],
+  plugins: ['react-refresh'],
-  rules: {
+  rules: {
-    'react-refresh/only-export-components': 'warn',
+    'react-refresh/only-export-components': 'warn',
-      '@typescript-eslint/ban-ts-comment':"warn",
+      '@typescript-eslint/ban-ts-comment':"warn",
-      '@typescript-eslint/no-explicit-any': 'off'
+      '@typescript-eslint/no-explicit-any': 'off'
-  },
+  },
-}
+}

+ 21 - 21
LICENSE → frontend/LICENSE

@@ -1,21 +1,21 @@
-MIT License
+MIT License
-
+
-Copyright (c) 2023 niantuo
+Copyright (c) 2023 niantuo
-
+
-Permission is hereby granted, free of charge, to any person obtaining a copy
+Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
+of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
+in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
+copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
+furnished to do so, subject to the following conditions:
-
+
-The above copyright notice and this permission notice shall be included in all
+The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
+copies or substantial portions of the Software.
-
+
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE.
+SOFTWARE.

+ 136 - 0
frontend/README.md

@@ -0,0 +1,136 @@
+# nginx 可视化界面
+项目的主要功能未nginx的配置管理,通过可视化的界面去配置nginx,所有的配置渲染逻辑都在前端进行,通过后台服务渲染到部署nginx的服务器上;\
+由于nginx的配置实在是太多了,只是可视化了部分常用的功能;\
+可以用于开发环境,需要经常变动一些配置信息的场景.
+
+- 项目的web前端基于react开发,使用vite构建工具;
+- 后端使用golang语言开发(菜鸟学习中)
+
+## demos and docs
+[在线demo](http://demos.tonyandmoney.cn/nginx-ui/#/) \
+账号: demo \
+密码: demo
+
+[在线文档](https://portal.tonyandmoney.cn/common/notes/html/pages/list?type=nginx-ui)
+
+## 快速部署
+
+- docker-compose
+```yaml
+
+version: "3"
+
+services:
+  nginx-with-ui:
+    image: registry.cn-hangzhou.aliyuncs.com/tuon-pub/nginx-with-ui:latest
+    restart: always
+    ports:
+      - 8080:8080
+    #    network_mode: host
+    volumes:
+      - ./data:/app/data
+      - ./data/conf:/app/conf
+
+```
+- docker快速启动
+
+```shell
+docker run -itd --name nginx-ui -p8080:8080 -v {datadir}:/app/data -v {confdir}:/app/conf registry.cn-hangzhou.aliyuncs.com/tuon-pub/nginx-with-ui:latest
+```
+
+- 说明
+  - 8080为nginx-ui的服务端口,其余则为nginx代理端口,自定义或者直接使用 network_mode: host 模式即可
+  - 启动成功后,在登录界面,先注册账号后,使用注册的账号登录即可使用,管理员账号可以查看docker的启动日志查看
+  - 配置文件参考[在线文档](https://portal.tonyandmoney.cn/common/notes/html/pages/list?type=nginx-ui)
+
+
+## 构建
+项目构建基于docker-compose, 分为两种情况
+- 基础镜像为nginx
+  容器自带nginx,启用web服务
+    ```
+  docker-compose -f ./docker-compose-dev.yaml build
+  ```
+- 基础镜像为debian:sid-slim
+  镜像不包含nginx,仅启动web服务
+    ```
+  docker-compose -f ./docker-compose.yaml build
+  ```
+前端在本地构建,以上构建方式不包含前端
+
+## 部署
+### 使用docker部署
+- 将docker-compose.yaml 或者 docker-compose-dev.yaml 复制到自己的文件夹下
+修改编排中的volumes,更改目录映射,容器内 /app/data 为目录持久化数据所在目录。
+```shell
+docker-compose -f ./docker-compose.yaml up -d
+```
+使用IP:8080端口访问
+或者
+```shell
+docker run -itd -v ./data/:/app/data --network host --name registry.cn-hangzhou.aliyuncs.com/tuon-pub/nginx-with-ui:latest
+# or
+docker run -itd -v ./data/:/app/data -p8080:8080 --name registry.cn-hangzhou.aliyuncs.com/tuon-pub/nginx-ui
+```
+
+### 本地部署
+```shell
+# 下载构建产物,解压
+
+```
+
+## 截图
+  ![实例列表](./docs/images/list.png)
+
+  ![实例信息](./docs/images/dashboard.png)
+
+  ![负载均衡](./docs/images/upstream.png)
+
+  ![虚拟主机](./docs/images/server.png)
+
+## 参考文档
+配置部分参考一下文档:
+
+- [Nginx Rewrite](https://blog.csdn.net/qq1356059950/article/details/125014248)
+- [nginx负载均衡](https://zhuanlan.zhihu.com/p/557994010?utm_id=0)
+
+
+## 构建部署
+以下操作进入到项目根目录执行
+### 构建
+ docker-compose build 或者执行脚本sh build.sh
+
+
+
+## nginx-ui优化点
+- [x] nginx实例列表界面,添加完实例之后,弹窗没有关闭,且没有自动刷新当前界面
+- [x] server或者location的rewrite是否配置的判定问题
+- [x] nginx 实例设置时,将需要的目录文件创建好
+- [x] 新增虚拟主机或者其它新增界面,无法重置当前的表单,需要结合planning-tools,增加重置功能
+- [x] 证书管理,将证书信息保存到数据库,方便做nginx服务前移,不能直接写文件,可以增加从文件夹同步的功能
+- [x] 后端docker启动时,默认启动本地的NGINX,docker镜像问题
+- [ ] 很多界面的默认值问题,优化初始值
+- [x] nginx.conf默认值问题,每次容器重启都会被重置 
+- [x] 增强证书管理:添加时间,有效期,域名
+- [x] 增加一些快捷按钮,比如转发真实IP,支持websocket,跨域设置等
+- [x] 考虑是否增加静态站点的文件上传功能
+- [ ] 考虑多租户的功能,目前前端使用的是nginx auth的授权认证,该方式是否能传递用户id作为查询数据的条件
+- [ ] 考虑增加jwt,basic授权
+
+### 2023-07-06
+- [ ] [ngx_http_auth_request_module](https://nginx.org/en/docs/http/ngx_http_auth_request_module.html)
+    鉴权模块的实现
+
+## 更新日志
+- 20230710:修复return 语句未渲染的问题
+- 20230719: 修复return语句在代理或者静态站点的情况下依然渲染的问题
+
+## git代理
+git config --global http.proxy http://127.0.0.1:{port}
+
+git config --global https.proxy  http://127.0.0.1:{port}
+
+
+git config --global --unset http.proxy
+
+git config --global --unset https.proxy

File diff suppressed because it is too large
+ 0 - 0
frontend/dist/assets/index-154a1b67.js


+ 0 - 0
dist/assets/index-49502c45.css → frontend/dist/assets/index-49502c45.css


+ 5 - 5
public/config.js → frontend/dist/config.js

@@ -1,5 +1,5 @@
-// config.js
+// config.js
-window.CONFIG = {
+window.CONFIG = {
-    baseApi: '/api/nginx-ui/api',
+    baseApi: '/api/nginx-ui/api',
-    SSO: true
+    SSO: true
-}
+}

+ 1 - 1
dist/index.html → frontend/dist/index.html

@@ -4,7 +4,7 @@
     <meta name="viewport" content="width=device-width, initial-scale=1.0">
     <meta name="viewport" content="width=device-width, initial-scale=1.0">
     <title>NginxUI</title>
     <title>NginxUI</title>
     <script type="application/javascript" src="./config.js"></script>
     <script type="application/javascript" src="./config.js"></script>
-    <script crossorigin="">import('/nginx-ui/assets/index-49244724.js').finally(() => {
+    <script crossorigin="">import('/nginx-ui/assets/index-154a1b67.js').finally(() => {
             
             
     const qiankunLifeCycle = window.moudleQiankunAppLifeCycles && window.moudleQiankunAppLifeCycles['nginx-ui'];
     const qiankunLifeCycle = window.moudleQiankunAppLifeCycles && window.moudleQiankunAppLifeCycles['nginx-ui'];
     if (qiankunLifeCycle) {
     if (qiankunLifeCycle) {

+ 0 - 0
dist/vite.svg → frontend/dist/vite.svg


+ 14 - 14
index.html → frontend/index.html

@@ -1,14 +1,14 @@
-<!DOCTYPE html>
+<!DOCTYPE html>
-<html lang="en">
+<html lang="en">
-  <head>
+  <head>
-    <meta charset="UTF-8" />
+    <meta charset="UTF-8" />
-    <link rel="icon" type="image/svg+xml" href="/vite.svg" />
+    <link rel="icon" type="image/svg+xml" href="/vite.svg" />
-    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
-    <title>NginxUI</title>
+    <title>NginxUI</title>
-    <script type="application/javascript" src="./config.js"></script>
+    <script type="application/javascript" src="./config.js"></script>
-  </head>
+  </head>
-  <body>
+  <body>
-    <div id="nginx_ui_root"></div>
+    <div id="nginx_ui_root"></div>
-    <script type="module" src="/src/main.tsx"></script>
+    <script type="module" src="/src/main.tsx"></script>
-  </body>
+  </body>
-</html>
+</html>

+ 66 - 66
package.json → frontend/package.json

@@ -1,66 +1,66 @@
-{
+{
-  "name": "nginx-ui-react",
+  "name": "nginx-ui-react",
-  "private": false,
+  "private": false,
-  "version": "0.1.0",
+  "version": "0.1.0",
-  "license": "MIT",
+  "license": "MIT",
-  "author": {
+  "author": {
-    "email": "976056042@qq.com",
+    "email": "976056042@qq.com",
-    "name": "tuonina"
+    "name": "tuonina"
-  },
+  },
-  "type": "module",
+  "type": "module",
-  "scripts": {
+  "scripts": {
-    "dev": "vite",
+    "dev": "vite",
-    "build": "tsc && vite build --base=/nginx-ui/",
+    "build": "tsc && vite build --base=/nginx-ui/",
-    "lint": "eslint src --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
+    "lint": "eslint src --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
-    "preview": "vite preview"
+    "preview": "vite preview"
-  },
+  },
-  "dependencies": {
+  "dependencies": {
-    "@ant-design/icons": "^5.1.4",
+    "@ant-design/icons": "^5.1.4",
-    "@mdx-js/mdx": "^2.0.0-rc.2",
+    "@mdx-js/mdx": "^2.0.0-rc.2",
-    "@mdx-js/react": "^2.3.0",
+    "@mdx-js/react": "^2.3.0",
-    "@mdx-js/rollup": "^2.3.0",
+    "@mdx-js/rollup": "^2.3.0",
-    "@reduxjs/toolkit": "^1.9.5",
+    "@reduxjs/toolkit": "^1.9.5",
-    "antd": "4.x",
+    "antd": "4.x",
-    "artt-template": "^4.13.6",
+    "artt-template": "^4.13.6",
-    "classnames": "^2.3.2",
+    "classnames": "^2.3.2",
-    "dayjs": "^1.11.9",
+    "dayjs": "^1.11.9",
-    "events": "^3.3.0",
+    "events": "^3.3.0",
-    "history": "^5.3.0",
+    "history": "^5.3.0",
-    "install": "^0.13.0",
+    "install": "^0.13.0",
-    "jszip": "^3.10.1",
+    "jszip": "^3.10.1",
-    "less": "^4.1.3",
+    "less": "^4.1.3",
-    "lodash": "^4.17.21",
+    "lodash": "^4.17.21",
-    "npm": "^9.8.0",
+    "npm": "^9.8.0",
-    "planning-tools": "^0.1.6",
+    "planning-tools": "^0.1.6",
-    "query-string": "^8.1.0",
+    "query-string": "^8.1.0",
-    "react": "^18.2.0",
+    "react": "^18.2.0",
-    "react-dom": "^18.2.0",
+    "react-dom": "^18.2.0",
-    "react-redux": "^8.1.1",
+    "react-redux": "^8.1.1",
-    "react-router": "^6.14.0",
+    "react-router": "^6.14.0",
-    "react-router-dom": "^6.14.0",
+    "react-router-dom": "^6.14.0",
-    "redux-persist": "^6.0.0"
+    "redux-persist": "^6.0.0"
-  },
+  },
-  "devDependencies": {
+  "devDependencies": {
-    "@types/events": "^3.0.0",
+    "@types/events": "^3.0.0",
-    "@types/lodash": "^4.14.195",
+    "@types/lodash": "^4.14.195",
-    "@types/react": "^18.0.37",
+    "@types/react": "^18.0.37",
-    "@types/react-dom": "^18.0.11",
+    "@types/react-dom": "^18.0.11",
-    "@typescript-eslint/eslint-plugin": "^5.59.0",
+    "@typescript-eslint/eslint-plugin": "^5.59.0",
-    "@typescript-eslint/parser": "^5.59.0",
+    "@typescript-eslint/parser": "^5.59.0",
-    "@vitejs/plugin-react-swc": "^3.0.0",
+    "@vitejs/plugin-react-swc": "^3.0.0",
-    "consola": "^3.1.0",
+    "consola": "^3.1.0",
-    "eslint": "^8.38.0",
+    "eslint": "^8.38.0",
-    "eslint-plugin-react-hooks": "^4.6.0",
+    "eslint-plugin-react-hooks": "^4.6.0",
-    "eslint-plugin-react-refresh": "^0.3.4",
+    "eslint-plugin-react-refresh": "^0.3.4",
-    "typescript": "^5.0.2",
+    "typescript": "^5.0.2",
-    "vite": "^4.3.9",
+    "vite": "^4.3.9",
-    "vite-plugin-md": "^0.21.5",
+    "vite-plugin-md": "^0.21.5",
-    "vite-plugin-mdx": "^3.5.11",
+    "vite-plugin-mdx": "^3.5.11",
-    "vite-plugin-qiankun": "^1.0.15",
+    "vite-plugin-qiankun": "^1.0.15",
-    "vite-plugin-require-transform": "^1.0.20",
+    "vite-plugin-require-transform": "^1.0.20",
-    "vite-plugin-style-import": "^2.0.0"
+    "vite-plugin-style-import": "^2.0.0"
-  },
+  },
-  "peerDependencies": {
+  "peerDependencies": {
-    "artt-template": "^4.13.6"
+    "artt-template": "^4.13.6"
-  }
+  }
-}
+}

+ 1 - 0
frontend/package.json.md5

@@ -0,0 +1 @@
+7deb3a4e37e6d19d18e2428677450a23

+ 5 - 5
dist/config.js → frontend/public/config.js

@@ -1,5 +1,5 @@
-// config.js
+// config.js
-window.CONFIG = {
+window.CONFIG = {
-    baseApi: '/api/nginx-ui/api',
+    baseApi: '/api/nginx-ui/api',
-    SSO: true
+    SSO: true
-}
+}

+ 0 - 0
public/vite.svg → frontend/public/vite.svg


+ 41 - 41
src/App.css → frontend/src/App.css

@@ -1,41 +1,41 @@
-#nginx_ui_root {
+#nginx_ui_root {
-    height: 100%;
+    height: 100%;
-    width: 100%;
+    width: 100%;
-    overflow: hidden;
+    overflow: hidden;
-}
+}
-
+
-.logo {
+.logo {
-  height: 6em;
+  height: 6em;
-  padding: 1.5em;
+  padding: 1.5em;
-  will-change: filter;
+  will-change: filter;
-  transition: filter 300ms;
+  transition: filter 300ms;
-}
+}
-.logo:hover {
+.logo:hover {
-  filter: drop-shadow(0 0 2em #646cffaa);
+  filter: drop-shadow(0 0 2em #646cffaa);
-}
+}
-.logo.react:hover {
+.logo.react:hover {
-  filter: drop-shadow(0 0 2em #61dafbaa);
+  filter: drop-shadow(0 0 2em #61dafbaa);
-}
+}
-
+
-@keyframes logo-spin {
+@keyframes logo-spin {
-  from {
+  from {
-    transform: rotate(0deg);
+    transform: rotate(0deg);
-  }
+  }
-  to {
+  to {
-    transform: rotate(360deg);
+    transform: rotate(360deg);
-  }
+  }
-}
+}
-
+
-@media (prefers-reduced-motion: no-preference) {
+@media (prefers-reduced-motion: no-preference) {
-  a:nth-of-type(2) .logo {
+  a:nth-of-type(2) .logo {
-    animation: logo-spin infinite 20s linear;
+    animation: logo-spin infinite 20s linear;
-  }
+  }
-}
+}
-
+
-.card {
+.card {
-  padding: 2em;
+  padding: 2em;
-}
+}
-
+
-.read-the-docs {
+.read-the-docs {
-  color: #888;
+  color: #888;
-}
+}

+ 25 - 25
src/App.tsx → frontend/src/App.tsx

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

+ 27 - 27
src/adapter/index.js → frontend/src/adapter/index.js

@@ -1,27 +1,27 @@
-/**
+/**
- * 神奇了,这个东西居然没有了,可能跟打包工具有关系
+ * 神奇了,这个东西居然没有了,可能跟打包工具有关系
- */
+ */
-if (!window.caches) {
+if (!window.caches) {
-    window.caches = {
+    window.caches = {
-        delete(cacheName) {
+        delete(cacheName) {
-            return false
+            return false
-        },
+        },
-        has(cacheName) {
+        has(cacheName) {
-            return false
+            return false
-        },
+        },
-        keys() {
+        keys() {
-            return []
+            return []
-        },
+        },
-        /**
+        /**
-         *
+         *
-         * @param request RequestInfo | URL
+         * @param request RequestInfo | URL
-         * @param options MultiCacheQueryOptions
+         * @param options MultiCacheQueryOptions
-         */
+         */
-        match(request, options) {
+        match(request, options) {
-
+
-        },
+        },
-        open(cacheName) {
+        open(cacheName) {
-
+
-        }
+        }
-    }
+    }
-}
+}

+ 27 - 0
frontend/src/api/desktop.api.ts

@@ -0,0 +1,27 @@
+import {AxiosInstance, AxiosRequestConfig} from 'axios'
+import {isDesktop} from "../config/consts.ts";
+import * as DesktopApi from "../../wailsjs/go/desktop/Api";
+
+// @ts-ignore
+export const checkDesktopApi = (request: AxiosInstance)=>{
+    if (!isDesktop){
+        return
+    }
+    // @ts-ignore
+    request.get = (url: string, config: AxiosRequestConfig<any>)=> {
+        const data = config?.params ? JSON.stringify(config.params) : "{}"
+        return DesktopApi.GetApi(url, data)
+    }
+    // @ts-ignore
+    request.post = (url: string, data?: any, config?: AxiosRequestConfig<any>) =>{
+        return DesktopApi.PostApi(url, data? JSON.stringify(data): "{}")
+    }
+    // @ts-ignore
+    request.delete = (url: string, config?: AxiosRequestConfig<any>) => {
+        return DesktopApi.DeleteApi(url, config?.data?JSON.stringify(config.data):"{}")
+    }
+    // @ts-ignore
+    request.put = (url: string, data?: any, config?: AxiosRequestConfig<any>) => {
+        return DesktopApi.PutApi(url, data ? JSON.stringify(data): "{}")
+    }
+}

+ 127 - 127
src/api/nginx.ts → frontend/src/api/nginx.ts

@@ -1,127 +1,127 @@
-import request from "./request.ts";
+import request from "./request.ts";
-import {BaseResp, INginxCerts, IServerHost} from "../models/api.ts";
+import {BaseResp, INginxCerts, IServerHost} from "../models/api.ts";
-import {INginx, INginxServer} from "../models/nginx.ts";
+import {INginx, INginxServer} from "../models/nginx.ts";
-import {createServer, createServerHost} from "../pages/nginx/utils/nginx.ts";
+import {createServer, createServerHost} from "../pages/nginx/utils/nginx.ts";
-
+
-
+
-type RefreshHttpData = {
+type RefreshHttpData = {
-  id: number
+  id: number
-  httpConf: string
+  httpConf: string
-  httpData: string
+  httpData: string
-}
+}
-
+
-
+
-export const NginxApis= {
+export const NginxApis= {
-
+
-  findAll: () => request.get<BaseResp<INginx[]>>('/nginx'),
+  findAll: () => request.get<BaseResp<INginx[]>>('/nginx'),
-  // eslint-disable-next-line @typescript-eslint/ban-ts-comment
+  // eslint-disable-next-line @typescript-eslint/ban-ts-comment
-  // @ts-ignore
+  // @ts-ignore
-  updateOrAdd: (data: Partial<INginx>) => {
+  updateOrAdd: (data: Partial<INginx>) => {
-    if (data.id){
+    if (data.id){
-      return request.post<BaseResp<INginx>>(`/nginx/${data.id}`, data, { disableErrorMsg: true, timeout: 60000 } as any)
+      return request.post<BaseResp<INginx>>(`/nginx/${data.id}`, data, { disableErrorMsg: true, timeout: 60000 } as any)
-    }else {
+    }else {
-      return request.post<BaseResp<INginx>>('/nginx', data, { disableErrorMsg: true, timeout: 60000 } as any)
+      return request.post<BaseResp<INginx>>('/nginx', data, { disableErrorMsg: true, timeout: 60000 } as any)
-    }
+    }
-  },
+  },
-  /**
+  /**
-   * 同步配置文件到本地,仅需要传递id  和httpConf, httpData 三个参数
+   * 同步配置文件到本地,仅需要传递id  和httpConf, httpData 三个参数
-   * @param nginx
+   * @param nginx
-   */
+   */
-  refreshHttp: (nginx: RefreshHttpData) => {
+  refreshHttp: (nginx: RefreshHttpData) => {
-    return request.post(`/nginx/${nginx.id}/http/refresh`, nginx, { timeout: 60000 })
+    return request.post(`/nginx/${nginx.id}/http/refresh`, nginx, { timeout: 60000 })
-  },
+  },
-  getNginx: (id:number | string) => request.get<BaseResp<{nginx: INginx, servers: IServerHost[]}>>(`/nginx/${id}`),
+  getNginx: (id:number | string) => request.get<BaseResp<{nginx: INginx, servers: IServerHost[]}>>(`/nginx/${id}`),
-  delNginx: (id:number) => request.delete(`/nginx/${id}`),
+  delNginx: (id:number) => request.delete(`/nginx/${id}`),
-  status: (id:number) => request.post(`/nginx/${id}/status`, { }, { timeout: 60000 }),
+  status: (id:number) => request.post(`/nginx/${id}/status`, { }, { timeout: 60000 }),
-  startNginx: (id:number) => request.post(`/nginx/${id}/start`, { }, { timeout: 60000 }),
+  startNginx: (id:number) => request.post(`/nginx/${id}/start`, { }, { timeout: 60000 }),
-  stopNginx: (id:number) => request.post(`/nginx/${id}/stop`, { }, { timeout: 60000 }),
+  stopNginx: (id:number) => request.post(`/nginx/${id}/stop`, { }, { timeout: 60000 }),
-
+
-  /**
+  /**
-   * 不更改配置文件,仅保存数据,方便某些特殊情况,一直手动修改配置文件
+   * 不更改配置文件,仅保存数据,方便某些特殊情况,一直手动修改配置文件
-   * @param nginx
+   * @param nginx
-   * @param server
+   * @param server
-   */
+   */
-  // add or update
+  // add or update
-  updateServer: (nginx: INginx,server: Partial<INginxServer>) => {
+  updateServer: (nginx: INginx,server: Partial<INginxServer>) => {
-    const serverHost: Partial<IServerHost> = createServerHost(nginx,server);
+    const serverHost: Partial<IServerHost> = createServerHost(nginx,server);
-    // eslint-disable-next-line @typescript-eslint/ban-ts-comment
+    // eslint-disable-next-line @typescript-eslint/ban-ts-comment
-    // @ts-ignore
+    // @ts-ignore
-    return request.post<BaseResp<IServerHost>>(`/nginx/${nginx.id}/server`,serverHost, { disableErrorMsg: true } as any)
+    return request.post<BaseResp<IServerHost>>(`/nginx/${nginx.id}/server`,serverHost, { disableErrorMsg: true } as any)
-        .then(({data})=>{
+        .then(({data})=>{
-          if (data.data){
+          if (data.data){
-            return createServer(data.data)
+            return createServer(data.data)
-          }
+          }
-          return Promise.reject(data)
+          return Promise.reject(data)
-        })
+        })
-  },
+  },
-  /**
+  /**
-   * 更改配置文件,保存数据
+   * 更改配置文件,保存数据
-   * @param nginx
+   * @param nginx
-   * @param server
+   * @param server
-   */
+   */
-  refreshServer: (server: Partial<IServerHost>) => {
+  refreshServer: (server: Partial<IServerHost>) => {
-    return request.post(`/nginx/${server.nginxId}/server/refresh`, server, { timeout: 60000 })
+    return request.post(`/nginx/${server.nginxId}/server/refresh`, server, { timeout: 60000 })
-  },
+  },
-  deleteServer: (nginxId: number,server: INginxServer) => request.delete(`/nginx/${nginxId}/server`,{ data: { id: server.id}}),
+  deleteServer: (nginxId: number,server: INginxServer) => request.delete(`/nginx/${nginxId}/server`,{ data: { id: server.id}}),
-  /**
+  /**
-   * 获取证书信息,不传name,则返回所有证书文件信息,传了name,则返回该证书的内容
+   * 获取证书信息,不传name,则返回所有证书文件信息,传了name,则返回该证书的内容
-   * @param id
+   * @param id
-   * @param name
+   * @param name
-   */
+   */
-  getCerts: (id: number,name?: string) => request.get(`/nginx/${id}/certs`, { params: { name }}),
+  getCerts: (id: number,name?: string) => request.get(`/nginx/${id}/certs`, { params: { name }}),
-  /**
+  /**
-   * 保存证书信息
+   * 保存证书信息
-   * @param id
+   * @param id
-   * @param data
+   * @param data
-   */
+   */
-  saveCerts: (id: number,data: INginxCerts) => request.post(`/nginx/${id}/certs`, data),
+  saveCerts: (id: number,data: INginxCerts) => request.post(`/nginx/${id}/certs`, data),
-  delCerts: (nginxId: number,id: number) => request.delete(`/nginx/${nginxId}/certs`, { params: { id } }),
+  delCerts: (nginxId: number,id: number) => request.delete(`/nginx/${nginxId}/certs`, { params: { id } }),
-  /**
+  /**
-   * 从配置的数据目录中同步
+   * 从配置的数据目录中同步
-   * @param id
+   * @param id
-   * @param name
+   * @param name
-   */
+   */
-  syncCerts: (id: number) => request.post(`/nginx/${id}/certs/sync`),
+  syncCerts: (id: number) => request.post(`/nginx/${id}/certs/sync`),
-
+
-}
+}
-
+
-
+
-export type IDeployReq  ={
+export type IDeployReq  ={
-  key: string
+  key: string
-  nginxId: number
+  nginxId: number
-  /**
+  /**
-   * 部署目录,资源部署目录,一般是root+name 或者是alias
+   * 部署目录,资源部署目录,一般是root+name 或者是alias
-   */
+   */
-  dir: string
+  dir: string
-  /**
+  /**
-   * 是否清空文件夹再部署
+   * 是否清空文件夹再部署
-   */
+   */
-  clear?: boolean
+  clear?: boolean
-  cmd?: string
+  cmd?: string
-}
+}
-/**
+/**
- * 文件上传
+ * 文件上传
- */
+ */
-export const uploadApis = {
+export const uploadApis = {
-  uploadFile: (entry: FileSystemFileEntry, id: string) => {
+  uploadFile: (entry: FileSystemFileEntry, id: string) => {
-    return new Promise<File>((resolve, reject) => {
+    return new Promise<File>((resolve, reject) => {
-      entry.file(function (f){
+      entry.file(function (f){
-        resolve(f)
+        resolve(f)
-      },function (err){
+      },function (err){
-        reject(err)
+        reject(err)
-      })
+      })
-    }).then(file=>{
+    }).then(file=>{
-      const formData = new FormData()
+      const formData = new FormData()
-      formData.append("file", file)
+      formData.append("file", file)
-      formData.append("Path", entry.fullPath)
+      formData.append("Path", entry.fullPath)
-      formData.append("Key", id)
+      formData.append("Key", id)
-      return request.post('/file',formData, {
+      return request.post('/file',formData, {
-        withCredentials: true,
+        withCredentials: true,
-        headers: {
+        headers: {
-          'Content-type' : 'multipart/form-data'
+          'Content-type' : 'multipart/form-data'
-        }
+        }
-      })
+      })
-    })
+    })
-  },
+  },
-  deploy:(data: IDeployReq)=>request.post(`/nginx/${data.nginxId}/file/deploy`, data, {timeout: 120000}),
+  deploy:(data: IDeployReq)=>request.post(`/nginx/${data.nginxId}/file/deploy`, data, {timeout: 120000}),
-}
+}

+ 77 - 74
src/api/request.ts → frontend/src/api/request.ts

@@ -1,74 +1,77 @@
-import axios, {AxiosResponse} from 'axios';
+import axios, {AxiosResponse} from 'axios';
-import {BaseResp} from "../models/api.ts";
+import {BaseResp} from "../models/api.ts";
-import {Message, Notify} from "planning-tools";
+import {Message, Notify} from "planning-tools";
-import {store} from "../store";
+import {store} from "../store";
-import {UserActions} from "../store/slice/user.ts";
+import {UserActions} from "../store/slice/user.ts";
-console.log('env', import.meta.env)
+import {checkDesktopApi} from "./desktop.api.ts";
-
+console.log('env', import.meta.env)
-// eslint-disable-next-line @typescript-eslint/ban-ts-comment
+
-// @ts-ignore
+// eslint-disable-next-line @typescript-eslint/ban-ts-comment
-const CONFIG = window.CONFIG;
+// @ts-ignore
-if (!CONFIG.baseApi){
+const CONFIG = window.CONFIG;
-  CONFIG.baseApi = '/api'
+if (!CONFIG.baseApi){
-}
+  CONFIG.baseApi = '/api'
-
+}
-/**
+
- * 支持网络请求
+/**
- * @type {AxiosInstance}
+ * 支持网络请求
- */
+ * @type {AxiosInstance}
-// create an axios instance
+ */
-const request = axios.create({
+// create an axios instance
-  baseURL: CONFIG.baseApi,
+const request = axios.create({
-  withCredentials: true, // send cookies when cross-domain requests
+  baseURL: CONFIG.baseApi,
-  timeout: 10000, // request timeout
+  withCredentials: true, // send cookies when cross-domain requests
-});
+  timeout: 10000, // request timeout
-
+});
-request.interceptors.request.use(
+
-  (config) => {
+request.interceptors.request.use(
-    if (!config.headers){
+  (config) => {
-      config.headers = {}
+    if (!config.headers){
-    }
+      config.headers = {}
-    config.headers["Authorization"] = "token"
+    }
-    return config;
+    config.headers["Authorization"] = "token"
-  },
+    return config;
-  (error) => {
+  },
-    // do something with request error
+  (error) => {
-    console.log(error); // for debug
+    // do something with request error
-    return Promise.reject(error);
+    console.log(error); // for debug
-  },
+    return Promise.reject(error);
-);
+  },
-
+);
-request.interceptors.response.use((resp: AxiosResponse<BaseResp>)=>{
+
-  const disableErrorMsg = (resp.config as any)['disableErrorMsg']
+request.interceptors.response.use((resp: AxiosResponse<BaseResp>)=>{
-  if (resp.data && resp.data.code == 0){
+  const disableErrorMsg = (resp.config as any)['disableErrorMsg']
-    return resp
+  if (resp.data && resp.data.code == 0){
-  }else if (resp.data) {
+    return resp
-    (!disableErrorMsg) && Notify.warn(resp.data.msg)
+  }else if (resp.data) {
-    return Promise.reject(resp.data)
+    (!disableErrorMsg) && Notify.warn(resp.data.msg)
-  }else {
+    return Promise.reject(resp.data)
-    (!disableErrorMsg) && Notify.warn("请求错误")
+  }else {
-    return resp
+    (!disableErrorMsg) && Notify.warn("请求错误")
-  }
+    return resp
-},error => {
+  }
-  let errData: any = {
+},error => {
-    code: 10
+  let errData: any = {
-  }
+    code: 10
-  const disableErrorMsg = (error.request?.config as any)?.['disableErrorMsg']
+  }
-  if (error.response && error.response.data){
+  const disableErrorMsg = (error.request?.config as any)?.['disableErrorMsg']
-    errData = error.response.data
+  if (error.response && error.response.data){
-  }else if (error.message){
+    errData = error.response.data
-    errData.msg = error.message;
+  }else if (error.message){
-  }
+    errData.msg = error.message;
-  if (!errData.code){
+  }
-    errData.msg = 'request fail'
+  if (!errData.code){
-  }
+    errData.msg = 'request fail'
-  (!disableErrorMsg)&& Message.error(errData.msg)
+  }
-  console.log('status', error.response?.status)
+  (!disableErrorMsg)&& Message.error(errData.msg)
-  if (error.response.status == 401){
+  console.log('status', error.response?.status)
-    store.dispatch(UserActions.clearUser())
+  if (error.response.status == 401){
-  }
+    store.dispatch(UserActions.clearUser())
-  return Promise.reject(errData)
+  }
-})
+  return Promise.reject(errData)
-
+})
-
+
-export default request
+checkDesktopApi(request)
+
+
+export default request

+ 32 - 28
src/api/user.ts → frontend/src/api/user.ts

@@ -1,29 +1,33 @@
-import request from "./request.ts";
+import request from "./request.ts";
-
+
-export type LoginReq = {
+export type LoginReq = {
-    account: string
+    account: string
-    password: string
+    password: string
-}
+}
-
+
-export type RegisterReq = LoginReq & {
+export type RegisterReq = LoginReq & {
-    nickname?: string
+    nickname?: string
-}
+}
-
+
-export type SSOReq = {
+export type SSOReq = {
-    code: string
+    code: string
-    scope: string
+    scope: string
-    state: string
+    state: string
-}
+}
-
+
-/**
+/**
- * 登录相关的API
+ * 登录相关的API
- */
+ */
-export const LoginApis = {
+export const LoginApis = {
-
+
-    login: (data: LoginReq) => request.post('/user/login', data),
+    login: (data: LoginReq) => {
-    signUp: (data: RegisterReq) => request.post('/user/register', data),
+        return request.post('/user/login', data)
-    userinfo: () => request.get('/user/info', { disableErrorMsg: true } as never),
+    },
-    oauth2Url: ()=> request.get('/oauth2'),
+    signUp: (data: RegisterReq) => request.post('/user/register', data),
-    oauth2Callback: (data: SSOReq) => request.post('/oauth2/callback', data, { disableErrorMsg: true } as never)
+    userinfo: () => {
-
+        return request.get('/user/info', { disableErrorMsg: true } as never)
+    },
+    oauth2Url: ()=> request.get('/oauth2'),
+    oauth2Callback: (data: SSOReq) => request.post('/oauth2/callback', data, { disableErrorMsg: true } as never)
+
 }
 }

+ 0 - 0
src/assets/react.svg → frontend/src/assets/react.svg


+ 29 - 29
src/components/BackButton.tsx → frontend/src/components/BackButton.tsx

@@ -1,29 +1,29 @@
-/**
+/**
- * @author tuonian
+ * @author tuonian
- * @date 2023/6/30
+ * @date 2023/6/30
- */
+ */
-import {Button} from "antd";
+import {Button} from "antd";
-import {ArrowLeftOutlined} from "@ant-design/icons";
+import {ArrowLeftOutlined} from "@ant-design/icons";
-import {useNavigate} from "react-router";
+import {useNavigate} from "react-router";
-
+
-
+
-type IProps = {
+type IProps = {
-  to?: string
+  to?: string
-}
+}
-export const BackButton = ({to}: IProps) => {
+export const BackButton = ({to}: IProps) => {
-
+
-  const navigate = useNavigate()
+  const navigate = useNavigate()
-
+
-  const goBack = ()=>{
+  const goBack = ()=>{
-    if (to){
+    if (to){
-      navigate(to, { replace: true })
+      navigate(to, { replace: true })
-    }else {
+    }else {
-      navigate(-1)
+      navigate(-1)
-    }
+    }
-  }
+  }
-
+
-  return (<Button
+  return (<Button
-    ghost style={{color: '#1890ff',marginRight: 10}}
+    ghost style={{color: '#1890ff',marginRight: 10}}
-                  onClick={goBack}
+                  onClick={goBack}
-                  icon={<ArrowLeftOutlined />} /> )
+                  icon={<ArrowLeftOutlined />} /> )
-}
+}

+ 14 - 14
src/components/empty/index.less → frontend/src/components/empty/index.less

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

+ 11 - 11
src/components/empty/index.tsx → frontend/src/components/empty/index.tsx

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

+ 12 - 0
frontend/src/config/consts.ts

@@ -0,0 +1,12 @@
+/**
+ * 是否为桌面应用
+ */
+let isDesktop = false;
+// @ts-ignore
+if (window.go){
+    isDesktop = true
+}
+
+export {
+    isDesktop
+}

+ 723 - 723
src/config/nginx_form.json → frontend/src/config/nginx_form.json

@@ -1,723 +1,723 @@
-{
+{
-  "server": [
+  "server": [
-    {
+    {
-      "key": "server_name",
+      "key": "server_name",
-      "title": "域名",
+      "title": "域名",
-      "type": "string",
+      "type": "string",
-      "ruleType": "",
+      "ruleType": "",
-      "pattern": "^((?!-)[A-Za-z0-9-]{1,63}(?<!-)\\.)+[A-Za-z]{2,6}$",
+      "pattern": "^((?!-)[A-Za-z0-9-]{1,63}(?<!-)\\.)+[A-Za-z]{2,6}$",
-      "placeholder": "请填写域名",
+      "placeholder": "请填写域名",
-      "description": "eg. demo.domain.cn",
+      "description": "eg. demo.domain.cn",
-      "width": 300
+      "width": 300
-    },
+    },
-    {
+    {
-      "key": "listen",
+      "key": "listen",
-      "type": "int",
+      "type": "int",
-      "title": "监听",
+      "title": "监听",
-      "min": 0,
+      "min": 0,
-      "max": 65535,
+      "max": 65535,
-      "width": 300
+      "width": 300
-    },
+    },
-    {
+    {
-      "key": "enable",
+      "key": "enable",
-      "title": "启用",
+      "title": "启用",
-      "type": "switch",
+      "type": "switch",
-      "description": "是否启用,如果不启用,将不会渲染该配置"
+      "description": "是否启用,如果不启用,将不会渲染该配置"
-    },
+    },
-    {
+    {
-      "key": "ssl",
+      "key": "ssl",
-      "title": "https",
+      "title": "https",
-      "type": "switch",
+      "type": "switch",
-      "cascade": {
+      "cascade": {
-        "true": [
+        "true": [
-          {
+          {
-            "key": "certName",
+            "key": "certName",
-            "type": "certs",
+            "type": "certs",
-            "placeholder": "选择SSL证书",
+            "placeholder": "选择SSL证书",
-            "title": "SSL证书",
+            "title": "SSL证书",
-            "description": "选择SSL证书,如果没有,请填到“SSL证书”管理界面添加证书"
+            "description": "选择SSL证书,如果没有,请填到“SSL证书”管理界面添加证书"
-          }
+          }
-        ]
+        ]
-      }
+      }
-    },
+    },
-    {
+    {
-      "key": "http2",
+      "key": "http2",
-      "title": "http2",
+      "title": "http2",
-      "type": "switch",
+      "type": "switch",
-      "cascade": {
+      "cascade": {
-        "true": [
+        "true": [
-          {
+          {
-            "key": "http2_max_concurrent_streams",
+            "key": "http2_max_concurrent_streams",
-            "value": 1024,
+            "value": 1024,
-            "title": "最大并发流",
+            "title": "最大并发流",
-            "type": "int",
+            "type": "int",
-            "placeholder": "http2_max_concurrent_streams",
+            "placeholder": "http2_max_concurrent_streams",
-            "description": "http2_max_concurrent_streams",
+            "description": "http2_max_concurrent_streams",
-            "width": 300
+            "width": 300
-          }
+          }
-        ]
+        ]
-      }
+      }
-    },
+    },
-    {
+    {
-      "key": "access_log",
+      "key": "access_log",
-      "title": "访问日志",
+      "title": "访问日志",
-      "type": "access_log",
+      "type": "access_log",
-      "required": false
+      "required": false
-    },
+    },
-    {
+    {
-      "key": "proxy_settings",
+      "key": "proxy_settings",
-      "title": "更多代理设置",
+      "title": "更多代理设置",
-      "type": "proxy_settings",
+      "type": "proxy_settings",
-      "required": false,
+      "required": false,
-      "description": "更多代理设置"
+      "description": "更多代理设置"
-    },
+    },
-    {
+    {
-      "key": "fastcgi",
+      "key": "fastcgi",
-      "title": "fastcgi",
+      "title": "fastcgi",
-      "type": "fastcgi",
+      "type": "fastcgi",
-      "required": false,
+      "required": false,
-      "description": "ngx_http_fastcgi_module,allows passing requests to a FastCGI server."
+      "description": "ngx_http_fastcgi_module,allows passing requests to a FastCGI server."
-    },
+    },
-    {
+    {
-      "type": "locations",
+      "type": "locations",
-      "title": "代理/站点",
+      "title": "代理/站点",
-      "key": "locations",
+      "key": "locations",
-      "required": false,
+      "required": false,
-      "description": "静态资源或者反向代理,路由规则"
+      "description": "静态资源或者反向代理,路由规则"
-    },
+    },
-    {
+    {
-      "type": "gzip",
+      "type": "gzip",
-      "title": "压缩配置",
+      "title": "压缩配置",
-      "key": "gzip",
+      "key": "gzip",
-      "required": false,
+      "required": false,
-      "description": "gzip"
+      "description": "gzip"
-    },
+    },
-    {
+    {
-      "title": "Access",
+      "title": "Access",
-      "key": "access",
+      "key": "access",
-      "type": "access",
+      "type": "access",
-      "required": false,
+      "required": false,
-      "description": "deny or allow,白名单或者黑名单访问限制"
+      "description": "deny or allow,白名单或者黑名单访问限制"
-    },
+    },
-    {
+    {
-      "type": "auth",
+      "type": "auth",
-      "title": "鉴权",
+      "title": "鉴权",
-      "key": "auth_request",
+      "key": "auth_request",
-      "required": false,
+      "required": false,
-      "description": "ngx_http_auth_request_module:实现了基于一子请求的结果的客户端的授权。如果子请求返回2xx响应码,则允许访问。如果它返回401或403,则访问被拒绝并显示相应的错误代码。子请求返回的任何其他响应代码都被认为是错误的"
+      "description": "ngx_http_auth_request_module:实现了基于一子请求的结果的客户端的授权。如果子请求返回2xx响应码,则允许访问。如果它返回401或403,则访问被拒绝并显示相应的错误代码。子请求返回的任何其他响应代码都被认为是错误的"
-    },
+    },
-    {
+    {
-      "key": "rewrite",
+      "key": "rewrite",
-      "type": "object",
+      "type": "object",
-      "title": "rewrite",
+      "title": "rewrite",
-      "required": false,
+      "required": false,
-      "hideHeader": true,
+      "hideHeader": true,
-      "description": "格式:rewrite < regex > < replacement > [flag]",
+      "description": "格式:rewrite < regex > < replacement > [flag]",
-      "items": [
+      "items": [
-        {
+        {
-          "key": "regex",
+          "key": "regex",
-          "title": "正则表达式",
+          "title": "正则表达式",
-          "type": "string",
+          "type": "string",
-          "width": 180,
+          "width": 180,
-          "placeholder": "eq. ^/(.*)",
+          "placeholder": "eq. ^/(.*)",
-          "description": "<regex> 正则匹配",
+          "description": "<regex> 正则匹配",
-          "required": false
+          "required": false
-        },
+        },
-        {
+        {
-          "key": "replacement",
+          "key": "replacement",
-          "title": "跳转路径",
+          "title": "跳转路径",
-          "type": "string",
+          "type": "string",
-          "placeholder": "eq. https://www.demo.com/$1",
+          "placeholder": "eq. https://www.demo.com/$1",
-          "width": 300,
+          "width": 300,
-          "required": false
+          "required": false
-        },
+        },
-        {
+        {
-          "key": "flag",
+          "key": "flag",
-          "title": "flag",
+          "title": "flag",
-          "type": "select",
+          "type": "select",
-          "option": ["last","break","redirect","permanent"],
+          "option": ["last","break","redirect","permanent"],
-          "width": 120,
+          "width": 120,
-          "placeholder": "[flag] 标记",
+          "placeholder": "[flag] 标记",
-          "description": "last: 相当于Apache的【L】标记,表示完成rewrite;\nbreak:本条规则匹配完成即终止,不在匹配后面的任何规则;\nredirect: 返回302临时重定向,浏览器地址栏会显示跳转后的URL地址,爬虫不会更新url;\npermanent:返回301永久重定向,浏览器地址栏会显示跳转后的URL地址,爬虫更新url;"
+          "description": "last: 相当于Apache的【L】标记,表示完成rewrite;\nbreak:本条规则匹配完成即终止,不在匹配后面的任何规则;\nredirect: 返回302临时重定向,浏览器地址栏会显示跳转后的URL地址,爬虫不会更新url;\npermanent:返回301永久重定向,浏览器地址栏会显示跳转后的URL地址,爬虫更新url;"
-        }
+        }
-      ]
+      ]
-    },
+    },
-    {
+    {
-      "key": "cors_setting",
+      "key": "cors_setting",
-      "title": "跨域配置",
+      "title": "跨域配置",
-      "type": "cors",
+      "type": "cors",
-      "description": "跨域配置,可以通过该配置项解决前端跨域问题",
+      "description": "跨域配置,可以通过该配置项解决前端跨域问题",
-      "required": false
+      "required": false
-    },
+    },
-    {
+    {
-      "key": "tmp_custom_config",
+      "key": "tmp_custom_config",
-      "title": "自定义配置",
+      "title": "自定义配置",
-      "type": "textarea",
+      "type": "textarea",
-      "hideHeader": true,
+      "hideHeader": true,
-      "description": "自定义配置,注意,每行结尾需要加“;”号,将会拼接在最后,不做任何修改,请注意格式",
+      "description": "自定义配置,注意,每行结尾需要加“;”号,将会拼接在最后,不做任何修改,请注意格式",
-      "required": false,
+      "required": false,
-      "trim": false,
+      "trim": false,
-      "width": 600
+      "width": 600
-    },
+    },
-    {
+    {
-      "type": "divider",
+      "type": "divider",
-      "key": "tmp_more_settings",
+      "key": "tmp_more_settings",
-      "collapsible": true,
+      "collapsible": true,
-      "value": false,
+      "value": false,
-      "title": "更多设置",
+      "title": "更多设置",
-      "items": [
+      "items": [
-        {
+        {
-          "key": "keepalive_timeout",
+          "key": "keepalive_timeout",
-          "type": "string",
+          "type": "string",
-          "required": false,
+          "required": false,
-          "title": "keepalive_timeout",
+          "title": "keepalive_timeout",
-          "description": "eg. 10s",
+          "description": "eg. 10s",
-          "min": 0,
+          "min": 0,
-          "ruleType": "reg",
+          "ruleType": "reg",
-          "pattern": "(\\d)(s|m|h)$"
+          "pattern": "(\\d)(s|m|h)$"
-        },
+        },
-        {
+        {
-          "key": "client_max_body_size",
+          "key": "client_max_body_size",
-          "type": "string",
+          "type": "string",
-          "placeholder": "请求体的最大大小",
+          "placeholder": "请求体的最大大小",
-          "title": "最大请求体大小",
+          "title": "最大请求体大小",
-          "description": "eg. 500m 20m",
+          "description": "eg. 500m 20m",
-          "required": false,
+          "required": false,
-          "value": "10m"
+          "value": "10m"
-        },
+        },
-        {
+        {
-          "key": "charset",
+          "key": "charset",
-          "title": "编码",
+          "title": "编码",
-          "required": false,
+          "required": false,
-          "description": "charset"
+          "description": "charset"
-        },
+        },
-        {
+        {
-          "key": "ssl_session_timeout",
+          "key": "ssl_session_timeout",
-          "type": "string",
+          "type": "string",
-          "placeholder": "ssl_session_timeout",
+          "placeholder": "ssl_session_timeout",
-          "title": "ssl_session_timeout",
+          "title": "ssl_session_timeout",
-          "description": "eg. 5m 60s",
+          "description": "eg. 5m 60s",
-          "required": false
+          "required": false
-        },
+        },
-        {
+        {
-          "key": "ssl_prefer_server_ciphers",
+          "key": "ssl_prefer_server_ciphers",
-          "type": "select",
+          "type": "select",
-          "placeholder": "ssl_prefer_server_ciphers",
+          "placeholder": "ssl_prefer_server_ciphers",
-          "title": "ssl_prefer_server_ciphers",
+          "title": "ssl_prefer_server_ciphers",
-          "option": ["on","off"],
+          "option": ["on","off"],
-          "value": "on",
+          "value": "on",
-          "required": false,
+          "required": false,
-          "width": 260
+          "width": 260
-        },
+        },
-        {
+        {
-          "key": "ssl_ciphers",
+          "key": "ssl_ciphers",
-          "type": "string",
+          "type": "string",
-          "placeholder": "ssl_ciphers",
+          "placeholder": "ssl_ciphers",
-          "title": "ssl_ciphers",
+          "title": "ssl_ciphers",
-          "description": "ECDHE-RSA-AES128-GCM-SHA256:ECDHE:ECDH:AES:HIGH:!NULL:!aNULL:!MD5:!ADH:!RC4",
+          "description": "ECDHE-RSA-AES128-GCM-SHA256:ECDHE:ECDH:AES:HIGH:!NULL:!aNULL:!MD5:!ADH:!RC4",
-          "required": false,
+          "required": false,
-          "width": 450
+          "width": 450
-        },
+        },
-        {
+        {
-          "key": "ssl_protocols",
+          "key": "ssl_protocols",
-          "type": "select",
+          "type": "select",
-          "mode": "multiple",
+          "mode": "multiple",
-          "placeholder": "ssl_protocols",
+          "placeholder": "ssl_protocols",
-          "title": "SSL协议",
+          "title": "SSL协议",
-          "option": ["TLSv1","TLSv1.1","TLSv1.2","TLSv2","TLSv3"],
+          "option": ["TLSv1","TLSv1.1","TLSv1.2","TLSv2","TLSv3"],
-          "required": false,
+          "required": false,
-          "width": 450
+          "width": 450
-        }
+        }
-      ]
+      ]
-    }
+    }
-  ],
+  ],
-  "addNginx": [
+  "addNginx": [
-    {
+    {
-      "type": "string",
+      "type": "string",
-      "key": "name",
+      "key": "name",
-      "title": "名称"
+      "title": "名称"
-    },
+    },
-    {
+    {
-      "key": "isLocal",
+      "key": "isLocal",
-      "type": "switch",
+      "type": "switch",
-      "description": "本地实例,直接在服务器上运行名称,非本地实例,需要配置SSH连接信息,使用SSH执行相关命令",
+      "description": "本地实例,直接在服务器上运行名称,非本地实例,需要配置SSH连接信息,使用SSH执行相关命令",
-      "value": true,
+      "value": true,
-      "title": "本地实例",
+      "title": "本地实例",
-      "cascade": {
+      "cascade": {
-        "false": [
+        "false": [
-          {
+          {
-            "type": "string",
+            "type": "string",
-            "key": "ipAddr",
+            "key": "ipAddr",
-            "title": "IP地址"
+            "title": "IP地址"
-          },
+          },
-          {
+          {
-            "type": "int",
+            "type": "int",
-            "key": "port",
+            "key": "port",
-            "title": "端口"
+            "title": "端口"
-          },
+          },
-          {
+          {
-            "type": "string",
+            "type": "string",
-            "key": "user",
+            "key": "user",
-            "title": "用户名"
+            "title": "用户名"
-          },
+          },
-          {
+          {
-            "type": "password",
+            "type": "password",
-            "key": "password",
+            "key": "password",
-            "title": "密码"
+            "title": "密码"
-          }
+          }
-        ]
+        ]
-      }
+      }
-    }
+    }
-  ],
+  ],
-  "nginxSettings": [
+  "nginxSettings": [
-    {
+    {
-      "key": "isServer",
+      "key": "isServer",
-      "title": "服务方式运行",
+      "title": "服务方式运行",
-      "type": "switch",
+      "type": "switch",
-      "description": "以服务方式运行,则使用service nginx start|stop|reload 等命令,否则使用nginx -s reload|stop 等命令"
+      "description": "以服务方式运行,则使用service nginx start|stop|reload 等命令,否则使用nginx -s reload|stop 等命令"
-    },
+    },
-    {
+    {
-      "key": "nginxPath",
+      "key": "nginxPath",
-      "title": "nginx位置",
+      "title": "nginx位置",
-      "type": "string",
+      "type": "string",
-      "description": "nginx的文件所在的绝对路径,默认为:/usr/sbin/nginx,可使用nginx -V 查看参数--sbin-path;",
+      "description": "nginx的文件所在的绝对路径,默认为:/usr/sbin/nginx,可使用nginx -V 查看参数--sbin-path;",
-      "value": "/usr/sbin/nginx"
+      "value": "/usr/sbin/nginx"
-    },
+    },
-    {
+    {
-      "key": "nginxDir",
+      "key": "nginxDir",
-      "title": "nginx配置目录",
+      "title": "nginx配置目录",
-      "type": "string",
+      "type": "string",
-      "description": "nginx的配置文件所在的目录,即nginx.conf所在的目录,一般为:/etc/nginx,可使用nginx -V 查看参数 --prefix",
+      "description": "nginx的配置文件所在的目录,即nginx.conf所在的目录,一般为:/etc/nginx,可使用nginx -V 查看参数 --prefix",
-      "value": "/etc/nginx"
+      "value": "/etc/nginx"
-    },
+    },
-    {
+    {
-      "key":"dataDir",
+      "key":"dataDir",
-      "type": "string",
+      "type": "string",
-      "title": "数据目录",
+      "title": "数据目录",
-      "description": "nginx的自定义配置文件所在目录,注意,是nginx的配置文件目录,包括配置文件,证书,备份文件都将保存到该目录下"
+      "description": "nginx的自定义配置文件所在目录,注意,是nginx的配置文件目录,包括配置文件,证书,备份文件都将保存到该目录下"
-    },
+    },
-    {
+    {
-      "key": "remark",
+      "key": "remark",
-      "title": "备注信息",
+      "title": "备注信息",
-      "placeholder": "输入备注",
+      "placeholder": "输入备注",
-      "type": "textarea",
+      "type": "textarea",
-      "required": false,
+      "required": false,
-      "trim": false
+      "trim": false
-    }
+    }
-  ],
+  ],
-  "nginxConf": [
+  "nginxConf": [
-    {
+    {
-      "key": "user",
+      "key": "user",
-      "value": "nginx",
+      "value": "nginx",
-      "title": "user",
+      "title": "user",
-      "placeholder": "nginx user"
+      "placeholder": "nginx user"
-    },
+    },
-    {
+    {
-      "key": "worker_processes",
+      "key": "worker_processes",
-      "title": "工作进程数量",
+      "title": "工作进程数量",
-      "type": "string",
+      "type": "string",
-      "ruleType": "reg",
+      "ruleType": "reg",
-      "pattern": "^(auto|\\d+)$",
+      "pattern": "^(auto|\\d+)$",
-      "description": "auto或者指定数量"
+      "description": "auto或者指定数量"
-    },
+    },
-    {
+    {
-      "key": "error_log",
+      "key": "error_log",
-      "title": "错误日志路径",
+      "title": "错误日志路径",
-      "type": "error_log",
+      "type": "error_log",
-      "value": {
+      "value": {
-        "path": "/var/log/nginx/error.log",
+        "path": "/var/log/nginx/error.log",
-        "level": "notice"
+        "level": "notice"
-      }
+      }
-    },
+    },
-    {
+    {
-      "key": "pid",
+      "key": "pid",
-      "title": "pid位置",
+      "title": "pid位置",
-      "type": "string",
+      "type": "string",
-      "value": "/var/run/nginx.pid",
+      "value": "/var/run/nginx.pid",
-      "description": "eg. /var/run/nginx.pid"
+      "description": "eg. /var/run/nginx.pid"
-    },
+    },
-    {
+    {
-      "key": "temp.events",
+      "key": "temp.events",
-      "type": "divider",
+      "type": "divider",
-      "title": "events配置",
+      "title": "events配置",
-      "description": "nginx events 模块主要是nginx 和用户交互网络连接优化的配置内容",
+      "description": "nginx events 模块主要是nginx 和用户交互网络连接优化的配置内容",
-      "items": [
+      "items": [
-        {
+        {
-          "key": "events.accept_mutex",
+          "key": "events.accept_mutex",
-          "title": "accept_mutex",
+          "title": "accept_mutex",
-          "type": "switch",
+          "type": "switch",
-          "value": true,
+          "value": true,
-          "required": false,
+          "required": false,
-          "description": "这个配置主要可以用来解决常说的\"惊群\"问题。大致意思是在某一个时刻,客户端发来一个请求连接,Nginx后台是以多进程的工作模式,也就是说有多个worker进程会被同时唤醒,但是最终只会有一个进程可以获取到连接,如果每次唤醒的进程数目太多,就会影响Nginx的整体性能。如果将上述值设置为on(开启状态),将会对多个Nginx进程接收连接进行序列号,一个个来唤醒接收,就防止了多个进程对连接的争抢"
+          "description": "这个配置主要可以用来解决常说的\"惊群\"问题。大致意思是在某一个时刻,客户端发来一个请求连接,Nginx后台是以多进程的工作模式,也就是说有多个worker进程会被同时唤醒,但是最终只会有一个进程可以获取到连接,如果每次唤醒的进程数目太多,就会影响Nginx的整体性能。如果将上述值设置为on(开启状态),将会对多个Nginx进程接收连接进行序列号,一个个来唤醒接收,就防止了多个进程对连接的争抢"
-        },
+        },
-        {
+        {
-          "key": "events.worker_connections",
+          "key": "events.worker_connections",
-          "type": "int",
+          "type": "int",
-          "layout": "form",
+          "layout": "form",
-          "title": "最大连接数",
+          "title": "最大连接数",
-          "description": "用来配置单个worker进程最大的连接数,nginx 默认连接数是1024",
+          "description": "用来配置单个worker进程最大的连接数,nginx 默认连接数是1024",
-          "min": 0,
+          "min": 0,
-          "max": 65536,
+          "max": 65536,
-          "value": 1024,
+          "value": 1024,
-          "required": false
+          "required": false
-        },
+        },
-        {
+        {
-          "key": "events.multi_accept",
+          "key": "events.multi_accept",
-          "title": "multi_accept",
+          "title": "multi_accept",
-          "description": "用来设置是否允许同时接收多个网络连接",
+          "description": "用来设置是否允许同时接收多个网络连接",
-          "type": "switch",
+          "type": "switch",
-          "value": false,
+          "value": false,
-          "required": false
+          "required": false
-        },
+        },
-        {
+        {
-          "key": "events.use",
+          "key": "events.use",
-          "title": "网络驱动",
+          "title": "网络驱动",
-          "description": "用来设置Nginx服务器选择哪种事件驱动来处理网络消息;另外这些值的选择,我们也可以在编译的时候使用:–with-select_module、–without-select_module、 --with-poll_module、–without-poll_module来设置是否需要将对应的事件驱动模块编译到Nginx的内核",
+          "description": "用来设置Nginx服务器选择哪种事件驱动来处理网络消息;另外这些值的选择,我们也可以在编译的时候使用:–with-select_module、–without-select_module、 --with-poll_module、–without-poll_module来设置是否需要将对应的事件驱动模块编译到Nginx的内核",
-          "type": "select",
+          "type": "select",
-          "option": ["select","poll","epoll","kqueue"],
+          "option": ["select","poll","epoll","kqueue"],
-          "required": false
+          "required": false
-        }
+        }
-      ]
+      ]
-    },
+    },
-    {
+    {
-      "key": "temp.http",
+      "key": "temp.http",
-      "title": "http配置",
+      "title": "http配置",
-      "type": "divider",
+      "type": "divider",
-      "items": [
+      "items": [
-        {
+        {
-          "key": "http.include",
+          "key": "http.include",
-          "type": "string",
+          "type": "string",
-          "value": "/etc/nginx/mime.types",
+          "value": "/etc/nginx/mime.types",
-          "title": "include"
+          "title": "include"
-        },
+        },
-        {
+        {
-          "key": "http.default_type",
+          "key": "http.default_type",
-          "type": "string",
+          "type": "string",
-          "value": "application/octet-stream",
+          "value": "application/octet-stream",
-          "title": "default_type"
+          "title": "default_type"
-        },
+        },
-        {
+        {
-          "key": "http.log_format",
+          "key": "http.log_format",
-          "title": "日志格式",
+          "title": "日志格式",
-          "type": "array",
+          "type": "array",
-          "items": [
+          "items": [
-            {
+            {
-              "type": "textarea",
+              "type": "textarea",
-              "key": "name",
+              "key": "name",
-              "value": "main",
+              "value": "main",
-              "title": "格式名称",
+              "title": "格式名称",
-              "rows": 4,
+              "rows": 4,
-              "placeholder": "日志格式名称,eg. main compression",
+              "placeholder": "日志格式名称,eg. main compression",
-              "width": 200,
+              "width": 200,
-              "description": "日志格式名称,eg. main compression log1 log2",
+              "description": "日志格式名称,eg. main compression log1 log2",
-              "trim": false,
+              "trim": false,
-              "required": true
+              "required": true
-            },
+            },
-            {
+            {
-              "type": "textarea",
+              "type": "textarea",
-              "key": "content",
+              "key": "content",
-              "value": "",
+              "value": "",
-              "title": "日志格式",
+              "title": "日志格式",
-              "required": true,
+              "required": true,
-              "rows": 4,
+              "rows": 4,
-              "width": 400,
+              "width": 400,
-              "trim": false,
+              "trim": false,
-              "placeholder": "'$remote_addr - $remote_user [$time_local] \"$request\" '\n                      '$status $body_bytes_sent \"$http_referer\" '\n                      '\"$http_user_agent\" \"$http_x_forwarded_for\"'",
+              "placeholder": "'$remote_addr - $remote_user [$time_local] \"$request\" '\n                      '$status $body_bytes_sent \"$http_referer\" '\n                      '\"$http_user_agent\" \"$http_x_forwarded_for\"'",
-              "description": "参数                      说明                                         示例\n$remote_addr             客户端地址                                    211.28.65.253\n$remote_user             客户端用户名称                                --\n$time_local              访问时间和时区                                18/Jul/2012:17:00:01 +0800\n$request                 请求的URI和HTTP协议                           \"GET /article-10000.html HTTP/1.1\"\n$http_host               请求地址,即浏览器中你输入的地址(IP或域名)     www.wang.com 192.168.100.100\n$status                  HTTP请求状态                                  200\n$upstream_status         upstream状态                                  200\n$body_bytes_sent         发送给客户端文件内容大小                        1547\n$http_referer            url跳转来源                                   https://www.baidu.com/\n$http_user_agent         用户终端浏览器等信息                           \"Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0; SV1; GTB7.0; .NET4.0C;\n$ssl_protocol            SSL协议版本                                   TLSv1\n$ssl_cipher              交换数据中的算法                               RC4-SHA\n$upstream_addr           后台upstream的地址,即真正提供服务的主机地址     10.10.10.100:80\n$request_time            整个请求的总时间                               0.205\n$upstream_response_time  请求过程中,upstream响应时间                    0.002\neg.'$remote_addr - $remote_user [$time_local] \"$request\" '\n                      '$status $body_bytes_sent \"$http_referer\" '\n                      '\"$http_user_agent\" \"$http_x_forwarded_for\"'"
+              "description": "参数                      说明                                         示例\n$remote_addr             客户端地址                                    211.28.65.253\n$remote_user             客户端用户名称                                --\n$time_local              访问时间和时区                                18/Jul/2012:17:00:01 +0800\n$request                 请求的URI和HTTP协议                           \"GET /article-10000.html HTTP/1.1\"\n$http_host               请求地址,即浏览器中你输入的地址(IP或域名)     www.wang.com 192.168.100.100\n$status                  HTTP请求状态                                  200\n$upstream_status         upstream状态                                  200\n$body_bytes_sent         发送给客户端文件内容大小                        1547\n$http_referer            url跳转来源                                   https://www.baidu.com/\n$http_user_agent         用户终端浏览器等信息                           \"Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0; SV1; GTB7.0; .NET4.0C;\n$ssl_protocol            SSL协议版本                                   TLSv1\n$ssl_cipher              交换数据中的算法                               RC4-SHA\n$upstream_addr           后台upstream的地址,即真正提供服务的主机地址     10.10.10.100:80\n$request_time            整个请求的总时间                               0.205\n$upstream_response_time  请求过程中,upstream响应时间                    0.002\neg.'$remote_addr - $remote_user [$time_local] \"$request\" '\n                      '$status $body_bytes_sent \"$http_referer\" '\n                      '\"$http_user_agent\" \"$http_x_forwarded_for\"'"
-            }
+            }
-          ]
+          ]
-        },
+        },
-        {
+        {
-          "key": "http.access_log",
+          "key": "http.access_log",
-          "title": "访问日志",
+          "title": "访问日志",
-          "type": "access_log",
+          "type": "access_log",
-          "required": false,
+          "required": false,
-          "stream": false
+          "stream": false
-        },
+        },
-        {
+        {
-          "key": "http.error_log",
+          "key": "http.error_log",
-          "title": "错误日志",
+          "title": "错误日志",
-          "type": "error_log",
+          "type": "error_log",
-          "required": false
+          "required": false
-        },
+        },
-        {
+        {
-          "key": "http.sendfile",
+          "key": "http.sendfile",
-          "type": "select",
+          "type": "select",
-          "required": false,
+          "required": false,
-          "option": ["on","off"],
+          "option": ["on","off"],
-          "title": "sendfile"
+          "title": "sendfile"
-        },
+        },
-        {
+        {
-          "key": "http.tcp_nopush",
+          "key": "http.tcp_nopush",
-          "type": "select",
+          "type": "select",
-          "required": false,
+          "required": false,
-          "option": ["on","off"],
+          "option": ["on","off"],
-          "title": "tcp_nopush"
+          "title": "tcp_nopush"
-        },
+        },
-        {
+        {
-          "type": "gzip",
+          "type": "gzip",
-          "title": "压缩配置",
+          "title": "压缩配置",
-          "key": "http.gzip",
+          "key": "http.gzip",
-          "required": false,
+          "required": false,
-          "description": "gzip"
+          "description": "gzip"
-        },
+        },
-        {
+        {
-          "key": "http.keepalive_timeout",
+          "key": "http.keepalive_timeout",
-          "type": "int",
+          "type": "int",
-          "required": false,
+          "required": false,
-          "title": "keepalive_timeout",
+          "title": "keepalive_timeout",
-          "description": "单位为秒(s), 0表示不限制",
+          "description": "单位为秒(s), 0表示不限制",
-          "min": 0
+          "min": 0
-        },
+        },
-        {
+        {
-          "title": "Access",
+          "title": "Access",
-          "key": "http.access",
+          "key": "http.access",
-          "type": "access",
+          "type": "access",
-          "required": false,
+          "required": false,
-          "description": "deny or allow,白名单或者黑名单访问限制"
+          "description": "deny or allow,白名单或者黑名单访问限制"
-        },
+        },
-        {
+        {
-          "key": "http.proxy_settings",
+          "key": "http.proxy_settings",
-          "title": "代理设置",
+          "title": "代理设置",
-          "type": "proxy_settings",
+          "type": "proxy_settings",
-          "required": false
+          "required": false
-        },
+        },
-        {
+        {
-          "key": "http.fastcgi",
+          "key": "http.fastcgi",
-          "title": "fastcgi",
+          "title": "fastcgi",
-          "type": "fastcgi",
+          "type": "fastcgi",
-          "required": false,
+          "required": false,
-          "description": "ngx_http_fastcgi_module,allows passing requests to a FastCGI server."
+          "description": "ngx_http_fastcgi_module,allows passing requests to a FastCGI server."
-        },
+        },
-        {
+        {
-          "key": "http.more",
+          "key": "http.more",
-          "type": "textarea",
+          "type": "textarea",
-          "required": false,
+          "required": false,
-          "title": "更多配置",
+          "title": "更多配置",
-          "description": "自定义配置,每行需要有分隔符号",
+          "description": "自定义配置,每行需要有分隔符号",
-          "trim": false
+          "trim": false
-        }
+        }
-      ]
+      ]
-    },
+    },
-    {
+    {
-      "key": "stream",
+      "key": "stream",
-      "type": "divider",
+      "type": "divider",
-      "collapsible": true,
+      "collapsible": true,
-      "title": "TCP/UDP配置",
+      "title": "TCP/UDP配置",
-      "description": "stream配置,需要注意安装的nginx版本是否支持;默认情况下,没有构建此模块。 -必须使用-with stream配置参数启用",
+      "description": "stream配置,需要注意安装的nginx版本是否支持;默认情况下,没有构建此模块。 -必须使用-with stream配置参数启用",
-      "items": [
+      "items": [
-        {
+        {
-          "key": "stream.log_format",
+          "key": "stream.log_format",
-          "title": "日志格式",
+          "title": "日志格式",
-          "type": "array",
+          "type": "array",
-          "items": [
+          "items": [
-            {
+            {
-              "type": "textarea",
+              "type": "textarea",
-              "key": "name",
+              "key": "name",
-              "value": "tcp_format",
+              "value": "tcp_format",
-              "title": "格式名称",
+              "title": "格式名称",
-              "rows": 4,
+              "rows": 4,
-              "placeholder": "日志格式名称,eg. tcp_format",
+              "placeholder": "日志格式名称,eg. tcp_format",
-              "width": 200,
+              "width": 200,
-              "description": "日志格式名称,eg. tcp_format",
+              "description": "日志格式名称,eg. tcp_format",
-              "trim": false
+              "trim": false
-            },
+            },
-            {
+            {
-              "type": "textarea",
+              "type": "textarea",
-              "key": "content",
+              "key": "content",
-              "value": "",
+              "value": "",
-              "title": "日志格式",
+              "title": "日志格式",
-              "rows": 4,
+              "rows": 4,
-              "width": 400,
+              "width": 400,
-              "trim": false,
+              "trim": false,
-              "description": "eg. '$time_local|$remote_addr|$protocol|$status|$bytes_sent|$bytes_received|$session_time|$upstream_addr|$upstream_bytes_sent|$upstream_bytes_received|$upstream_connect_time'",
+              "description": "eg. '$time_local|$remote_addr|$protocol|$status|$bytes_sent|$bytes_received|$session_time|$upstream_addr|$upstream_bytes_sent|$upstream_bytes_received|$upstream_connect_time'",
-              "placeholder": "'$time_local|$remote_addr|$protocol|$status|$bytes_sent|$bytes_received|$session_time|$upstream_addr|$upstream_bytes_sent|$upstream_bytes_received|$upstream_connect_time'"
+              "placeholder": "'$time_local|$remote_addr|$protocol|$status|$bytes_sent|$bytes_received|$session_time|$upstream_addr|$upstream_bytes_sent|$upstream_bytes_received|$upstream_connect_time'"
-            }
+            }
-          ]
+          ]
-        },
+        },
-        {
+        {
-          "key": "stream.access_log",
+          "key": "stream.access_log",
-          "title": "访问日志",
+          "title": "访问日志",
-          "type": "access_log",
+          "type": "access_log",
-          "value": {
+          "value": {
-            "level": "tcp_format",
+            "level": "tcp_format",
-            "path": "/var/log/nginx/access_stream.log"
+            "path": "/var/log/nginx/access_stream.log"
-          },
+          },
-          "stream": true
+          "stream": true
-        },
+        },
-        {
+        {
-          "key": "stream.error_log",
+          "key": "stream.error_log",
-          "title": "错误日志",
+          "title": "错误日志",
-          "type": "error_log",
+          "type": "error_log",
-          "stream": true,
+          "stream": true,
-          "value": {
+          "value": {
-            "path": "/var/log/nginx/error_stream.log"
+            "path": "/var/log/nginx/error_stream.log"
-          }
+          }
-        }
+        }
-      ]
+      ]
-    }
+    }
-  ],
+  ],
-  "upstream": [
+  "upstream": [
-    {
+    {
-      "title": "名称",
+      "title": "名称",
-      "key": "name",
+      "key": "name",
-      "width": 100,
+      "width": 100,
-      "description": "名称相同则为同一组负载均衡,只支持英文字母",
+      "description": "名称相同则为同一组负载均衡,只支持英文字母",
-      "type": "string"
+      "type": "string"
-    },
+    },
-    {
+    {
-      "title": "负载方式",
+      "title": "负载方式",
-      "key": "type",
+      "key": "type",
-      "type": "select",
+      "type": "select",
-      "option": ["ip_hash","weight"],
+      "option": ["ip_hash","weight"],
-      "width": 100,
+      "width": 100,
-      "required": false,
+      "required": false,
-      "description": "ip_hash: 每个请求按访问ip的hash结果分配,这样每个访客固定访问一个后端服务器,可以解决session不能跨服务器的问题。如果后端服务器down掉,要手工down掉;weight:指定轮询几率,weight和访问比率成正比,如果后端服务器down掉,能自动剔除。"
+      "description": "ip_hash: 每个请求按访问ip的hash结果分配,这样每个访客固定访问一个后端服务器,可以解决session不能跨服务器的问题。如果后端服务器down掉,要手工down掉;weight:指定轮询几率,weight和访问比率成正比,如果后端服务器down掉,能自动剔除。"
-    },
+    },
-    {
+    {
-      "title": "是否启用",
+      "title": "是否启用",
-      "key": "enable",
+      "key": "enable",
-      "type": "switch",
+      "type": "switch",
-      "value": true
+      "value": true
-    },
+    },
-    {
+    {
-      "title": "服务配置",
+      "title": "服务配置",
-      "type": "array",
+      "type": "array",
-      "key": "servers",
+      "key": "servers",
-      "items": [
+      "items": [
-        {
+        {
-          "title": "主机",
+          "title": "主机",
-          "type": "string",
+          "type": "string",
-          "key": "host",
+          "key": "host",
-          "description": "后端服务IP",
+          "description": "后端服务IP",
-          "width": 150
+          "width": 150
-        },
+        },
-        {
+        {
-          "title": "端口",
+          "title": "端口",
-          "type": "int",
+          "type": "int",
-          "key": "port",
+          "key": "port",
-          "description": "后端服务端口",
+          "description": "后端服务端口",
-          "width": 100
+          "width": 100
-        },
+        },
-        {
+        {
-          "title": "权重",
+          "title": "权重",
-          "type": "int",
+          "type": "int",
-          "key": "weight",
+          "key": "weight",
-          "description": "权重,ip_hash模式下不生效,数字越大,越高,为0将剔除",
+          "description": "权重,ip_hash模式下不生效,数字越大,越高,为0将剔除",
-          "width": 80,
+          "width": 80,
-          "min": 0,
+          "min": 0,
-          "value": 100
+          "value": 100
-        },
+        },
-        {
+        {
-          "title": "状态/角色",
+          "title": "状态/角色",
-          "type": "select",
+          "type": "select",
-          "key": "status",
+          "key": "status",
-          "option": ["normal","down","backup"],
+          "option": ["normal","down","backup"],
-          "description": "weight,backup 不能和 ip_hash 关键字一起使用;down:表示当前的server暂时不参与负载",
+          "description": "weight,backup 不能和 ip_hash 关键字一起使用;down:表示当前的server暂时不参与负载",
-          "width": 100,
+          "width": 100,
-          "required": false
+          "required": false
-        },
+        },
-        {
+        {
-          "title": "max_fails",
+          "title": "max_fails",
-          "type": "int",
+          "type": "int",
-          "key": "max_fails",
+          "key": "max_fails",
-          "required": false,
+          "required": false,
-          "description": "最大失败次数,也就是最多进行 3 次尝试,默认为1",
+          "description": "最大失败次数,也就是最多进行 3 次尝试,默认为1",
-          "width": 100
+          "width": 100
-        },
+        },
-        {
+        {
-          "title": "超时时间",
+          "title": "超时时间",
-          "type": "int",
+          "type": "int",
-          "key": "fail_timeout",
+          "key": "fail_timeout",
-          "required": false,
+          "required": false,
-          "description": "超时时间,单位秒,默认值是10s",
+          "description": "超时时间,单位秒,默认值是10s",
-          "width": 100
+          "width": 100
-        }
+        }
-      ]
+      ]
-    }
+    }
-  ],
+  ],
-  "stream": [
+  "stream": [
-    {
+    {
-      "key": "listen",
+      "key": "listen",
-      "type": "int",
+      "type": "int",
-      "value": 3306,
+      "value": 3306,
-      "title": "端口",
+      "title": "端口",
-      "width": 100
+      "width": 100
-    },
+    },
-    {
+    {
-      "key": "isUdp",
+      "key": "isUdp",
-      "type": "switch",
+      "type": "switch",
-      "value": false,
+      "value": false,
-      "title": "是否UDP"
+      "title": "是否UDP"
-    },
+    },
-    {
+    {
-      "key": "proxy_pass",
+      "key": "proxy_pass",
-      "type": "stream_proxy_pass",
+      "type": "stream_proxy_pass",
-      "title": "后端服务",
+      "title": "后端服务",
-      "description": "IP:PORT 或者upstream的名称",
+      "description": "IP:PORT 或者upstream的名称",
-      "width": 200
+      "width": 200
-    },
+    },
-    {
+    {
-      "key": "enable",
+      "key": "enable",
-      "type": "switch",
+      "type": "switch",
-      "title": "启用",
+      "title": "启用",
-      "description": "如果不启用,不会渲染该组配置信息",
+      "description": "如果不启用,不会渲染该组配置信息",
-      "required": false
+      "required": false
-    },
+    },
-    {
+    {
-      "key": "proxy_connect_timeout",
+      "key": "proxy_connect_timeout",
-      "type": "int",
+      "type": "int",
-      "value": 10,
+      "value": 10,
-      "title": "connect_timeout",
+      "title": "connect_timeout",
-      "placeholder": "与被代理服务器建立连接的超时时间,单位为s",
+      "placeholder": "与被代理服务器建立连接的超时时间,单位为s",
-      "required": false
+      "required": false
-    },
+    },
-    {
+    {
-      "key": "proxy_timeout",
+      "key": "proxy_timeout",
-      "type": "int",
+      "type": "int",
-      "value": 10,
+      "value": 10,
-      "title": "超时时间",
+      "title": "超时时间",
-      "placeholder": "获取被代理服务器的响应最大超时时间,单位为s",
+      "placeholder": "获取被代理服务器的响应最大超时时间,单位为s",
-      "required": false
+      "required": false
-    },
+    },
-    {
+    {
-      "key": "proxy_next_upstream",
+      "key": "proxy_next_upstream",
-      "type": "switch",
+      "type": "switch",
-      "value": true,
+      "value": true,
-      "title": "next_upstream",
+      "title": "next_upstream",
-      "description": "当被代理的服务器返回错误或超时时,将未返回响应的客户端连接请求传递给upstream中的下一个服务器",
+      "description": "当被代理的服务器返回错误或超时时,将未返回响应的客户端连接请求传递给upstream中的下一个服务器",
-      "required": false
+      "required": false
-    },
+    },
-    {
+    {
-      "key": "proxy_next_upstream_tries",
+      "key": "proxy_next_upstream_tries",
-      "type": "int",
+      "type": "int",
-      "value": 3,
+      "value": 3,
-      "title": "最大错误次数",
+      "title": "最大错误次数",
-      "description": "转发尝试请求最多3次",
+      "description": "转发尝试请求最多3次",
-      "required": false
+      "required": false
-    },
+    },
-    {
+    {
-      "key": "proxy_next_upstream_timeout",
+      "key": "proxy_next_upstream_timeout",
-      "type": "int",
+      "type": "int",
-      "value": 10,
+      "value": 10,
-      "title": "总尝试超时时间",
+      "title": "总尝试超时时间",
-      "description": "总尝试超时时间,单位为s",
+      "description": "总尝试超时时间,单位为s",
-      "required": false
+      "required": false
-    },
+    },
-    {
+    {
-      "key": "proxy_socket_keepalive",
+      "key": "proxy_socket_keepalive",
-      "type": "switch",
+      "type": "switch",
-      "value": true,
+      "value": true,
-      "title": "心跳",
+      "title": "心跳",
-      "description": "开启SO_KEEPALIVE选项进行心跳检测",
+      "description": "开启SO_KEEPALIVE选项进行心跳检测",
-      "required": false
+      "required": false
-    },
+    },
-    {
+    {
-      "key": "remark",
+      "key": "remark",
-      "type": "string",
+      "type": "string",
-      "placeholder": "备注信息",
+      "placeholder": "备注信息",
-      "required": false,
+      "required": false,
-      "title": "备注"
+      "title": "备注"
-    }
+    }
-  ]
+  ]
-}
+}

+ 68 - 68
src/config/nginx_template.json → frontend/src/config/nginx_template.json

@@ -1,68 +1,68 @@
-{
+{
-  "nginxConf": {
+  "nginxConf": {
-    "user": "nginx",
+    "user": "nginx",
-    "worker_processes": "auto",
+    "worker_processes": "auto",
-    "error_log": "/var/log/nginx/error.log notice",
+    "error_log": "/var/log/nginx/error.log notice",
-    "pid": "/var/run/nginx.pid",
+    "pid": "/var/run/nginx.pid",
-    "events.worker_connections": 1024,
+    "events.worker_connections": 1024,
-    "http.include": "/etc/nginx/mime.types",
+    "http.include": "/etc/nginx/mime.types",
-    "http.default_type": "application/octet-stream",
+    "http.default_type": "application/octet-stream",
-    "http.log_format": [
+    "http.log_format": [
-      {
+      {
-        "key": "http.log_formatXK1BCq0XKQCMEuV",
+        "key": "http.log_formatXK1BCq0XKQCMEuV",
-        "name": "main",
+        "name": "main",
-        "content": "'$remote_addr - $remote_user [$time_local] \"$request\" '\n                      '$status $body_bytes_sent \"$http_referer\" '\n                      '\"$http_user_agent\" \"$http_x_forwarded_for\"'"
+        "content": "'$remote_addr - $remote_user [$time_local] \"$request\" '\n                      '$status $body_bytes_sent \"$http_referer\" '\n                      '\"$http_user_agent\" \"$http_x_forwarded_for\"'"
-      }
+      }
-    ],
+    ],
-    "http.access_log": {
+    "http.access_log": {
-      "key": "http.access_logqwY8npz3ypNjgIA",
+      "key": "http.access_logqwY8npz3ypNjgIA",
-      "name": "main",
+      "name": "main",
-      "path": "/var/log/nginx/access.log"
+      "path": "/var/log/nginx/access.log"
-    },
+    },
-    "http.sendfile": "off",
+    "http.sendfile": "off",
-    "http.tcp_nopush": "off",
+    "http.tcp_nopush": "off",
-    "http.keepalive_timeout": 0,
+    "http.keepalive_timeout": 0,
-    "http.gzip": "off",
+    "http.gzip": "off",
-    "stream": true,
+    "stream": true,
-    "stream.log_format": [
+    "stream.log_format": [
-      {
+      {
-        "key": "stream.log_format8avSYRKrTjzgzcF",
+        "key": "stream.log_format8avSYRKrTjzgzcF",
-        "name": "tcp_format",
+        "name": "tcp_format",
-        "content": "'$time_local|$remote_addr|$protocol|$status|$bytes_sent|$bytes_received|$session_time|$upstream_addr|$upstream_bytes_sent|$upstream_bytes_received|$upstream_connect_time'"
+        "content": "'$time_local|$remote_addr|$protocol|$status|$bytes_sent|$bytes_received|$session_time|$upstream_addr|$upstream_bytes_sent|$upstream_bytes_received|$upstream_connect_time'"
-      }
+      }
-    ],
+    ],
-    "stream.access_log": {
+    "stream.access_log": {
-      "key": "stream.access_logpuBlWTJXD64QiOL",
+      "key": "stream.access_logpuBlWTJXD64QiOL",
-      "name": "tcp_format",
+      "name": "tcp_format",
-      "path": "/var/log/nginx/access_stream.log"
+      "path": "/var/log/nginx/access_stream.log"
-    },
+    },
-    "stream.error_log": "/var/log/nginx/error_stream.log"
+    "stream.error_log": "/var/log/nginx/error_stream.log"
-  },
+  },
-  "server": {
+  "server": {
-    "port": 80,
+    "port": 80,
-    "enable": true,
+    "enable": true,
-    "ssl_session_timeout": "5m",
+    "ssl_session_timeout": "5m",
-    "ssl_ciphers": "ECDHE-RSA-AES128-GCM-SHA256:ECDHE:ECDH:AES:HIGH:!NULL:!aNULL:!MD5:!ADH:!RC4",
+    "ssl_ciphers": "ECDHE-RSA-AES128-GCM-SHA256:ECDHE:ECDH:AES:HIGH:!NULL:!aNULL:!MD5:!ADH:!RC4",
-    "ssl_protocols": ["TLSv1","TLSv1.1","TLSv1.2"],
+    "ssl_protocols": ["TLSv1","TLSv1.1","TLSv1.2"],
-    "ssl_prefer_server_ciphers": "on",
+    "ssl_prefer_server_ciphers": "on",
-    "locations": [
+    "locations": [
-      {
+      {
-        "id": "default",
+        "id": "default",
-        "name": "默认",
+        "name": "默认",
-        "match": {
+        "match": {
-          "path": "/"
+          "path": "/"
-        },
+        },
-        "root": "/data/www",
+        "root": "/data/www",
-        "proxy_type": "static",
+        "proxy_type": "static",
-        "enable": true
+        "enable": true
-      }
+      }
-    ]
+    ]
-  },
+  },
-  "location": {
+  "location": {
-
+
-  },
+  },
-  "addNginx": {
+  "addNginx": {
-
+
-  },
+  },
-  "nginxSettings": {}
+  "nginxSettings": {}
-}
+}

+ 75 - 75
src/index.css → frontend/src/index.css

@@ -1,75 +1,75 @@
-:root {
+:root {
-  font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
+  font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
-  line-height: 1.5;
+  line-height: 1.5;
-  font-weight: 400;
+  font-weight: 400;
-
+
-  color-scheme: light dark;
+  color-scheme: light dark;
-  color: rgba(255, 255, 255, 0.87);
+  color: rgba(255, 255, 255, 0.87);
-  background-color: #242424;
+  background-color: #242424;
-
+
-  font-synthesis: none;
+  font-synthesis: none;
-  text-rendering: optimizeLegibility;
+  text-rendering: optimizeLegibility;
-  -webkit-font-smoothing: antialiased;
+  -webkit-font-smoothing: antialiased;
-  -moz-osx-font-smoothing: grayscale;
+  -moz-osx-font-smoothing: grayscale;
-  -webkit-text-size-adjust: 100%;
+  -webkit-text-size-adjust: 100%;
-}
+}
-
+
-html,body{
+html,body{
-    width: 100%;
+    width: 100%;
-    height: 100%;
+    height: 100%;
-    overflow: hidden;
+    overflow: hidden;
-}
+}
-
+
-a {
+a {
-  font-weight: 500;
+  font-weight: 500;
-  color: #646cff;
+  color: #646cff;
-  text-decoration: inherit;
+  text-decoration: inherit;
-}
+}
-a:hover {
+a:hover {
-  color: #535bf2;
+  color: #535bf2;
-}
+}
-
+
-body {
+body {
-  margin: 0;
+  margin: 0;
-  display: flex;
+  display: flex;
-  place-items: center;
+  place-items: center;
-  min-width: 320px;
+  min-width: 320px;
-  min-height: 100vh;
+  min-height: 100vh;
-}
+}
-
+
-h1 {
+h1 {
-  font-size: 3.2em;
+  font-size: 3.2em;
-  line-height: 1.1;
+  line-height: 1.1;
-}
+}
-
+
-button {
+button {
-  border-radius: 8px;
+  border-radius: 8px;
-  border: 1px solid transparent;
+  border: 1px solid transparent;
-  padding: 0.6em 1.2em;
+  padding: 0.6em 1.2em;
-  font-size: 1em;
+  font-size: 1em;
-  font-weight: 500;
+  font-weight: 500;
-  font-family: inherit;
+  font-family: inherit;
-  background-color: #1a1a1a;
+  background-color: #1a1a1a;
-  cursor: pointer;
+  cursor: pointer;
-  transition: border-color 0.25s;
+  transition: border-color 0.25s;
-}
+}
-button:hover {
+button:hover {
-  border-color: #646cff;
+  border-color: #646cff;
-}
+}
-button:focus,
+button:focus,
-button:focus-visible {
+button:focus-visible {
-  outline: 4px auto -webkit-focus-ring-color;
+  outline: 4px auto -webkit-focus-ring-color;
-}
+}
-
+
-@media (prefers-color-scheme: light) {
+@media (prefers-color-scheme: light) {
-  :root {
+  :root {
-    color: #213547;
+    color: #213547;
-    background-color: #ffffff;
+    background-color: #ffffff;
-  }
+  }
-  a:hover {
+  a:hover {
-    color: #747bff;
+    color: #747bff;
-  }
+  }
-  button {
+  button {
-    background-color: #f9f9f9;
+    background-color: #f9f9f9;
-  }
+  }
-}
+}

+ 56 - 56
src/main.tsx → frontend/src/main.tsx

@@ -1,56 +1,56 @@
-import React from 'react'
+import React from 'react'
-import ReactDOM, {Root} from 'react-dom/client'
+import ReactDOM, {Root} from 'react-dom/client'
-import './adapter/index.js'
+import './adapter/index.js'
-import App from './App.tsx'
+import App from './App.tsx'
-import './index.css'
+import './index.css'
-import './styles/index.less'
+import './styles/index.less'
-import renderWithQiankun from "vite-plugin-qiankun/es/helper";
+import renderWithQiankun from "vite-plugin-qiankun/es/helper";
-
+
-let root: Root | null
+let root: Root | null
-
+
-const render = (props: any ={}) => {
+const render = (props: any ={}) => {
-  console.log('[nginx-ui] render', props);
+  console.log('[nginx-ui] render', props);
-  const {container} = props;
+  const {container} = props;
-  const rootContainer = container ? (container as HTMLElement).querySelector('#nginx_ui_root') : document.getElementById('nginx_ui_root')
+  const rootContainer = container ? (container as HTMLElement).querySelector('#nginx_ui_root') : document.getElementById('nginx_ui_root')
-  root = ReactDOM.createRoot(rootContainer as never);
+  root = ReactDOM.createRoot(rootContainer as never);
-  root.render(
+  root.render(
-    <React.StrictMode>
+    <React.StrictMode>
-      <App />
+      <App />
-    </React.StrictMode>
+    </React.StrictMode>
-  )
+  )
-}
+}
-
+
-const initQianKun = ()=>{
+const initQianKun = ()=>{
-  renderWithQiankun({
+  renderWithQiankun({
-    bootstrap(){
+    bootstrap(){
-      console.log('bootstrap')
+      console.log('bootstrap')
-    },
+    },
-    mount(props){
+    mount(props){
-      console.log('[nginx-ui] mount', props)
+      console.log('[nginx-ui] mount', props)
-      render(props)
+      render(props)
-    },
+    },
-    unmount(){
+    unmount(){
-      console.log('unmount')
+      console.log('unmount')
-      if (!root){
+      if (!root){
-        return
+        return
-      }
+      }
-      try {
+      try {
-        root.unmount()
+        root.unmount()
-        root = null
+        root = null
-      }catch (e) {
+      }catch (e) {
-        console.log('[nginx-ui] unmount fail', e)
+        console.log('[nginx-ui] unmount fail', e)
-      }
+      }
-    },
+    },
-    update(props){
+    update(props){
-      console.log('update', props)
+      console.log('update', props)
-    }
+    }
-  })
+  })
-}
+}
-
+
-// eslint-disable-next-line @typescript-eslint/ban-ts-comment
+// eslint-disable-next-line @typescript-eslint/ban-ts-comment
-// @ts-ignore
+// @ts-ignore
-if (window.__POWERED_BY_QIANKUN__){
+if (window.__POWERED_BY_QIANKUN__){
-  initQianKun()
+  initQianKun()
-} else {
+} else {
-  render()
+  render()
-}
+}

+ 41 - 41
src/models/api.ts → frontend/src/models/api.ts

@@ -1,41 +1,41 @@
-/**
+/**
- * 后端返回数据的基本格式
+ * 后端返回数据的基本格式
- */
+ */
-export type BaseResp<T =any> = {
+export type BaseResp<T =any> = {
-  code: number
+  code: number
-  msg: string
+  msg: string
-  data?: T
+  data?: T
-}
+}
-
+
-/**
+/**
- * 虚拟主机,后端,跟前端不一致
+ * 虚拟主机,后端,跟前端不一致
- */
+ */
-export type IServerHost = {
+export type IServerHost = {
-  id: number
+  id: number
-  name: string
+  name: string
-  nginxId: number
+  nginxId: number
-  enable?: boolean
+  enable?: boolean
-  serverData: string
+  serverData: string
-  serverConf: string
+  serverConf: string
-  remark: string
+  remark: string
-  /**
+  /**
-   * 是否为TCP/UDP代理
+   * 是否为TCP/UDP代理
-   */
+   */
-  isStream?: boolean
+  isStream?: boolean
-}
+}
-
+
-/**
+/**
- * 证书信息
+ * 证书信息
- */
+ */
-export type INginxCerts = {
+export type INginxCerts = {
-  id: number
+  id: number
-  nginxId: number
+  nginxId: number
-  serviceName: string
+  serviceName: string
-  subjectName?: string
+  subjectName?: string
-  hintMsg?: string
+  hintMsg?: string
-  pem: string
+  pem: string
-  key: string
+  key: string
-  createdAt?: string
+  createdAt?: string
-  expiresAt?: string
+  expiresAt?: string
-  remark?: string
+  remark?: string
-}
+}

+ 284 - 284
src/models/nginx.ts → frontend/src/models/nginx.ts

@@ -1,284 +1,284 @@
-import {FormColumnType} from "planning-tools";
+import {FormColumnType} from "planning-tools";
-import {NgxModuleData} from "../pages/nginx/components/input.ts";
+import {NgxModuleData} from "../pages/nginx/components/input.ts";
-
+
-export type INginx = {
+export type INginx = {
-  id: number
+  id: number
-  name: string
+  name: string
-  uid: string
+  uid: string
-  /**
+  /**
-   * 数据目录,所有自定义配置文件都在里面
+   * 数据目录,所有自定义配置文件都在里面
-   * conf.d stream.d backup certs
+   * conf.d stream.d backup certs
-   */
+   */
-  dataDir: string
+  dataDir: string
-  /**
+  /**
-   * nginx的配置文件主目录,及nginx.conf 配置文件所在的目录
+   * nginx的配置文件主目录,及nginx.conf 配置文件所在的目录
-   */
+   */
-  nginxDir: string
+  nginxDir: string
-  /**
+  /**
-   * nginx可执行文件
+   * nginx可执行文件
-   */
+   */
-  nginxPath?:string
+  nginxPath?:string
-  isLocal: boolean
+  isLocal: boolean
-  ipAddr: string
+  ipAddr: string
-  port: number
+  port: number
-  user: string
+  user: string
-  password: string
+  password: string
-  httpData: string
+  httpData: string
-  httpConf: string
+  httpConf: string
-  remark: string
+  remark: string
-  /**
+  /**
-   * 版本信息
+   * 版本信息
-   */
+   */
-  versionInfo?: string
+  versionInfo?: string
-}
+}
-
+
-/**
+/**
- * 虚拟主机或者 TCP/UDP代理
+ * 虚拟主机或者 TCP/UDP代理
- */
+ */
-export type INginxServer = {
+export type INginxServer = {
-  /**
+  /**
-   * 唯一标识
+   * 唯一标识
-   */
+   */
-  id: number
+  id: number
-  /**
+  /**
-   * 是否是负载均衡
+   * 是否是负载均衡
-   */
+   */
-  isUpstream?: boolean
+  isUpstream?: boolean
-  /**
+  /**
-   * 是否为TCP/UDP代理
+   * 是否为TCP/UDP代理
-   */
+   */
-  isStream?: boolean
+  isStream?: boolean
-  nginxId: number
+  nginxId: number
-  enable?: boolean
+  enable?: boolean
-  http2?: boolean
+  http2?: boolean
-  /**
+  /**
-   * 配置文件,当前的配置文件
+   * 配置文件,当前的配置文件
-   */
+   */
-  confData?: string
+  confData?: string
-
+
-  server_name: string
+  server_name: string
-  listen: number
+  listen: number
-  ssl?: boolean
+  ssl?: boolean
-  charset?: string
+  charset?: string
-  access_log?: string
+  access_log?: string
-  error_log?: string
+  error_log?: string
-  /**
+  /**
-   * 客户端最大的请求体大小,500m 1g
+   * 客户端最大的请求体大小,500m 1g
-   */
+   */
-  client_max_body_size?: string
+  client_max_body_size?: string
-  /**
+  /**
-   * 证书名称,平台托管的证书名称
+   * 证书名称,平台托管的证书名称
-   */
+   */
-  certName?: string
+  certName?: string
-  ssl_certificate?: string
+  ssl_certificate?: string
-  ssl_certificate_key?: string
+  ssl_certificate_key?: string
-  /**
+  /**
-   * eg. 5m 1h
+   * eg. 5m 1h
-   */
+   */
-  ssl_session_timeout?: string
+  ssl_session_timeout?: string
-  // ECDHE-RSA-AES128-GCM-SHA256:ECDHE:ECDH:AES:HIGH:!NULL:!aNULL:!MD5:!ADH:!RC4
+  // ECDHE-RSA-AES128-GCM-SHA256:ECDHE:ECDH:AES:HIGH:!NULL:!aNULL:!MD5:!ADH:!RC4
-  ssl_ciphers?: string
+  ssl_ciphers?: string
-  // TLSv1 TLSv1.1 TLSv1.2
+  // TLSv1 TLSv1.1 TLSv1.2
-  ssl_protocols?: string[]
+  ssl_protocols?: string[]
-  ssl_prefer_server_ciphers?: 'on'|'off'
+  ssl_prefer_server_ciphers?: 'on'|'off'
-
+
-  locations?: INginxLocation[]
+  locations?: INginxLocation[]
-  upstreams?: IUpstream[]
+  upstreams?: IUpstream[]
-  streams?: INginxStream[]
+  streams?: INginxStream[]
-  rewrite?: IRewrite
+  rewrite?: IRewrite
-  remark?: string
+  remark?: string
-
+
-  proxy_settings?: NgxModuleData
+  proxy_settings?: NgxModuleData
-  tmp_custom_config?: string
+  tmp_custom_config?: string
-  gzip?: NgxModuleData
+  gzip?: NgxModuleData
-}
+}
-/**
+/**
- * 负载均衡,跟虚拟主机放在一起吧,方便
+ * 负载均衡,跟虚拟主机放在一起吧,方便
- */
+ */
-export type IUpstream = {
+export type IUpstream = {
-  name: string
+  name: string
-  /**
+  /**
-   * weight\backup 不能和 ip_hash 关键字一起使用。
+   * weight\backup 不能和 ip_hash 关键字一起使用。
-   * ip_hash 或者weight 轮训
+   * ip_hash 或者weight 轮训
-   */
+   */
-  type?:'ip_hash' | 'weight'
+  type?:'ip_hash' | 'weight'
-  /**
+  /**
-   * 是否启用
+   * 是否启用
-   */
+   */
-  enable?: boolean
+  enable?: boolean
-  servers: {
+  servers: {
-    host: string
+    host: string
-    port: number
+    port: number
-    weight?: number
+    weight?: number
-    /**
+    /**
-     * down:表示当前的server暂时不参与负载
+     * down:表示当前的server暂时不参与负载
-     * weight:默认为1.weight越大,负载的权重就越大。
+     * weight:默认为1.weight越大,负载的权重就越大。
-     * backup: 其它所有的非backup机器down或者忙的时候,请求backup机器。所以这台机器压力会最轻。
+     * backup: 其它所有的非backup机器down或者忙的时候,请求backup机器。所以这台机器压力会最轻。
-     */
+     */
-    status?: 'down' | 'backup' | 'normal'
+    status?: 'down' | 'backup' | 'normal'
-    /**
+    /**
-     * 最大失败次数,也就是最多进行 3 次尝试,默认为1
+     * 最大失败次数,也就是最多进行 3 次尝试,默认为1
-     */
+     */
-    max_fails?: number
+    max_fails?: number
-    /**
+    /**
-     * 超时时间,单位秒,默认值是10s
+     * 超时时间,单位秒,默认值是10s
-     */
+     */
-    fail_timeout?: number
+    fail_timeout?: number
-  }[]
+  }[]
-}
+}
-
+
-export type INginxStream = {
+export type INginxStream = {
-  /**
+  /**
-   * 唯一索引
+   * 唯一索引
-   */
+   */
-  key: string
+  key: string
-  listen: number
+  listen: number
-  /**
+  /**
-   * 与被代理服务器建立连接的超时时间为5s
+   * 与被代理服务器建立连接的超时时间为5s
-   */
+   */
-  proxy_connect_timeout?: number
+  proxy_connect_timeout?: number
-  /**
+  /**
-   * 获取被代理服务器的响应最大超时时间为10s
+   * 获取被代理服务器的响应最大超时时间为10s
-   */
+   */
-  proxy_timeout?: number
+  proxy_timeout?: number
-  /**
+  /**
-   * 当被代理的服务器返回错误或超时时,将未返回响应的客户端连接请求传递给upstream中的下一个服务器
+   * 当被代理的服务器返回错误或超时时,将未返回响应的客户端连接请求传递给upstream中的下一个服务器
-   */
+   */
-  proxy_next_upstream?: boolean
+  proxy_next_upstream?: boolean
-  /**
+  /**
-   * 总尝试超时时间为10s
+   * 总尝试超时时间为10s
-   */
+   */
-  proxy_next_upstream_tries?: number
+  proxy_next_upstream_tries?: number
-  /**
+  /**
-   *  总尝试超时时间为10s
+   *  总尝试超时时间为10s
-   */
+   */
-  proxy_next_upstream_timeout?: number
+  proxy_next_upstream_timeout?: number
-  /**
+  /**
-   * 开启SO_KEEPALIVE选项进行心跳检测
+   * 开启SO_KEEPALIVE选项进行心跳检测
-   */
+   */
-  proxy_socket_keepalive?: boolean
+  proxy_socket_keepalive?: boolean
-  /**
+  /**
-   * proxy_pass
+   * proxy_pass
-   */
+   */
-  proxy_pass: string
+  proxy_pass: string
-  /**
+  /**
-   * 是否启用
+   * 是否启用
-   */
+   */
-  enable?: boolean
+  enable?: boolean
-  /**
+  /**
-   * 是否监听TCP,但是isSteam=true时有效
+   * 是否监听TCP,但是isSteam=true时有效
-   */
+   */
-  isUdp?: boolean;
+  isUdp?: boolean;
-}
+}
-
+
-export type PNginxServer = Partial<INginxServer>
+export type PNginxServer = Partial<INginxServer>
-
+
-/**
+/**
- * 键值对
+ * 键值对
- */
+ */
-export type KeyValue = {
+export type KeyValue = {
-  name: string
+  name: string
-  value: string
+  value: string
-}
+}
-
+
-/**
+/**
- * nginx 的location配置
+ * nginx 的location配置
- */
+ */
-export type INginxLocation = Omit<NgxModuleData, "data"> & {
+export type INginxLocation = Omit<NgxModuleData, "data"> & {
-  /**
+  /**
-   * 唯一标识
+   * 唯一标识
-   */
+   */
-  id: string
+  id: string
-  /**
+  /**
-   * location的名称
+   * location的名称
-   */
+   */
-  name: string;
+  name: string;
-  /**
+  /**
-   * 匹配规则
+   * 匹配规则
-   */
+   */
-  match: {
+  match: {
-    path: string
+    path: string
-    regex?: string
+    regex?: string
-  }
+  }
-  index?: string
+  index?: string
-  root?: string
+  root?: string
-  alias?: string
+  alias?: string
-  proxy_set_header?: IProxyHeader[]
+  proxy_set_header?: IProxyHeader[]
-  add_header?: IProxyHeader[]
+  add_header?: IProxyHeader[]
-  proxy_pass?: string
+  proxy_pass?: string
-  // http_502 http_504 http_404 error timeout invalid_header
+  // http_502 http_504 http_404 error timeout invalid_header
-  proxy_next_upstream?: string[]
+  proxy_next_upstream?: string[]
-  //eg. 60s 1m
+  //eg. 60s 1m
-  proxy_connect_timeout?: string
+  proxy_connect_timeout?: string
-  // 1.1
+  // 1.1
-  proxy_http_version?: string
+  proxy_http_version?: string
-  rewrite?: IRewrite
+  rewrite?: IRewrite
-
+
-  proxy_settings?: NgxModuleData
+  proxy_settings?: NgxModuleData
-  gzip?: NgxModuleData
+  gzip?: NgxModuleData
-  tmp_custom_config?: string
+  tmp_custom_config?: string
-  proxy_type?: 'proxy'| 'static' | 'returnBody' | 'other'
+  proxy_type?: 'proxy'| 'static' | 'returnBody' | 'other'
-  /**
+  /**
-   * 是否为内部路由
+   * 是否为内部路由
-   */
+   */
-  internal?: boolean
+  internal?: boolean
-  return?: {
+  return?: {
-    code: number
+    code: number
-    content: string
+    content: string
-  }
+  }
-  /**
+  /**
-   * 临时数据,表示
+   * 临时数据,表示
-   */
+   */
-  __index__?: number
+  __index__?: number
-  __deploy__?: any
+  __deploy__?: any
-}
+}
-
+
-export type PLocation = Partial<INginxLocation>
+export type PLocation = Partial<INginxLocation>
-
+
-export type IProxyHeader = {
+export type IProxyHeader = {
-  name: string
+  name: string
-  value: string
+  value: string
-}
+}
-
+
-
+
-export type IRewrite = {
+export type IRewrite = {
-  /**
+  /**
-   * 正则表达式
+   * 正则表达式
-   */
+   */
-  regex: string
+  regex: string
-  /**
+  /**
-   * 跳转后的内容
+   * 跳转后的内容
-   */
+   */
-  replacement: string
+  replacement: string
-  /**
+  /**
-   * rewrite支持的flag标记
+   * rewrite支持的flag标记
-   */
+   */
-  flag: 'last' | 'break' | 'redirect' | 'permanent'
+  flag: 'last' | 'break' | 'redirect' | 'permanent'
-}
+}
-
+
-/**
+/**
- * nginx的自动化表单配置
+ * nginx的自动化表单配置
- */
+ */
-export type INginxFormConfig = {
+export type INginxFormConfig = {
-  server: FormColumnType[]
+  server: FormColumnType[]
-  location: FormColumnType[]
+  location: FormColumnType[]
-  addNginx: FormColumnType[]
+  addNginx: FormColumnType[]
-  nginxSettings: FormColumnType[]
+  nginxSettings: FormColumnType[]
-  nginxConf: FormColumnType[]
+  nginxConf: FormColumnType[]
-  /**
+  /**
-   * 负载均衡的
+   * 负载均衡的
-   */
+   */
-  upstream: FormColumnType[]
+  upstream: FormColumnType[]
-  stream: FormColumnType[]
+  stream: FormColumnType[]
-}
+}
-
+
-/**
+/**
- * 给定的初始值模板
+ * 给定的初始值模板
- */
+ */
-export type INginxFormTemplate = {
+export type INginxFormTemplate = {
-  server: Partial<INginxServer>,
+  server: Partial<INginxServer>,
-  location: any,
+  location: any,
-  addNginx: any,
+  addNginx: any,
-  nginxSettings: any,
+  nginxSettings: any,
-  nginxConf: any
+  nginxConf: any
-}
+}

+ 13 - 13
src/models/user.ts → frontend/src/models/user.ts

@@ -1,14 +1,14 @@
-/**
+/**
- * 用户
+ * 用户
- */
+ */
-export type User = {
+export type User = {
-    id: number
+    id: number
-    account: string
+    account: string
-    nickname: string
+    nickname: string
-    roles?: string
+    roles?: string
-    remark?: string
+    remark?: string
-    /**
+    /**
-     * 缓存时间
+     * 缓存时间
-     */
+     */
-    timestamp: number
+    timestamp: number
 }
 }

+ 35 - 35
src/pages/login/index.less → frontend/src/pages/login/index.less

@@ -1,36 +1,36 @@
-.login-page{
+.login-page{
-  width: 100%;
+  width: 100%;
-  height: 100%;
+  height: 100%;
-  display: flex;
+  display: flex;
-  flex-direction: column;
+  flex-direction: column;
-  align-items: center;
+  align-items: center;
-  justify-content: center;
+  justify-content: center;
-  background: #efefef;
+  background: #efefef;
-  .login-container{
+  .login-container{
-    width: 400px;
+    width: 400px;
-    max-width: 100%;
+    max-width: 100%;
-    min-height: 300px;
+    min-height: 300px;
-    background: white;
+    background: white;
-    padding: 10px 20px 20px;
+    padding: 10px 20px 20px;
-    border-radius: 10px;
+    border-radius: 10px;
-    .ant-form{
+    .ant-form{
-      margin-top: 15px;
+      margin-top: 15px;
-    }
+    }
-    .login-btn{
+    .login-btn{
-      text-align: center;
+      text-align: center;
-      display: flex;
+      display: flex;
-      flex-direction: row;
+      flex-direction: row;
-      align-items: flex-end;
+      align-items: flex-end;
-      padding-left: 60px;
+      padding-left: 60px;
-      .signup{
+      .signup{
-        font-size: 12px;
+        font-size: 12px;
-        line-height: 15px;
+        line-height: 15px;
-        color: #666666;
+        color: #666666;
-        margin-left: 10px;
+        margin-left: 10px;
-        a{
+        a{
-          color: #1890ff;
+          color: #1890ff;
-        }
+        }
-      }
+      }
-    }
+    }
-  }
+  }
 }
 }

+ 117 - 117
src/pages/login/index.tsx → frontend/src/pages/login/index.tsx

@@ -1,117 +1,117 @@
-
+
-import './index.less'
+import './index.less'
-import {Button, Form, Input, Spin, Tabs} from "antd";
+import {Button, Form, Input, Spin, Tabs} from "antd";
-import {TabsProps} from "antd/lib/tabs";
+import {TabsProps} from "antd/lib/tabs";
-import {Link} from "react-router-dom";
+import {Link} from "react-router-dom";
-import {LoginApis, LoginReq } from "../../api/user.ts";
+import {LoginApis, LoginReq } from "../../api/user.ts";
-import { useState} from "react";
+import { useState} from "react";
-import {useAppDispatch} from "../../store";
+import {useAppDispatch} from "../../store";
-import {UserActions} from "../../store/slice/user.ts";
+import {UserActions} from "../../store/slice/user.ts";
-import {Message} from "planning-tools";
+import {Message} from "planning-tools";
-import {useNavigate } from "react-router";
+import {useNavigate } from "react-router";
-import {cacheTo, parseQuery, useQuery} from "../../utils";
+import {cacheTo, parseQuery, useQuery} from "../../utils";
-
+
-const AccountPanel = ()=>{
+const AccountPanel = ()=>{
-
+
-    const [loading,setLoading] = useState(false)
+    const [loading,setLoading] = useState(false)
-    const dispatch = useAppDispatch()
+    const dispatch = useAppDispatch()
-    const navigate = useNavigate()
+    const navigate = useNavigate()
-    const query = useQuery<{to?: string}>()
+    const query = useQuery<{to?: string}>()
-
+
-    const onSubmit = (values: LoginReq)=>{
+    const onSubmit = (values: LoginReq)=>{
-        console.log('submit',values);
+        console.log('submit',values);
-        setLoading(true);
+        setLoading(true);
-        LoginApis.login(values)
+        LoginApis.login(values)
-            .then(({data})=>{
+            .then(({data})=>{
-                console.log('login resp',data)
+                console.log('login resp',data)
-                if (data.data){
+                if (data.data){
-                    dispatch(UserActions.setUser(data.data));
+                    dispatch(UserActions.setUser(data.data));
-                    navigate(query?.to || '/')
+                    navigate(query?.to || '/')
-                }
+                }
-                if (data.code == 0){
+                if (data.code == 0){
-                    Message.success(data.msg)
+                    Message.success(data.msg)
-                }else {
+                }else {
-                    Message.warning(data.msg)
+                    Message.warning(data.msg)
-                }
+                }
-            })
+            })
-            .finally(()=>{
+            .finally(()=>{
-                setLoading(false)
+                setLoading(false)
-            })
+            })
-    }
+    }
-
+
-    return (
+    return (
-        <Form onFinish={onSubmit} labelCol={{span: 4}}>
+        <Form onFinish={onSubmit} labelCol={{span: 4}}>
-            <Form.Item name="account" label="账号" rules={[{required: true,message: '请输入账号'}]}>
+            <Form.Item name="account" label="账号" rules={[{required: true,message: '请输入账号'}]}>
-                <Input placeholder="请输入账号" />
+                <Input placeholder="请输入账号" />
-            </Form.Item>
+            </Form.Item>
-            <Form.Item name="password" label="密码"  rules={[{required: true,message: '请输入密码'}]}>
+            <Form.Item name="password" label="密码"  rules={[{required: true,message: '请输入密码'}]}>
-                <Input.Password placeholder="请输入密码" />
+                <Input.Password placeholder="请输入密码" />
-            </Form.Item>
+            </Form.Item>
-            <div className="login-btn">
+            <div className="login-btn">
-                <Button loading={loading} htmlType="submit" type="primary">登录</Button>
+                <Button loading={loading} htmlType="submit" type="primary">登录</Button>
-                <span className="signup">没有账号?<Link to="/signup">去注册</Link></span>
+                <span className="signup">没有账号?<Link to="/signup">去注册</Link></span>
-            </div>
+            </div>
-        </Form>
+        </Form>
-    )
+    )
-}
+}
-
+
-
+
-export const LoginPage = ()=>{
+export const LoginPage = ()=>{
-
+
-    const [activeKey,setActiveKey] = useState('account')
+    const [activeKey,setActiveKey] = useState('account')
-    const [loading,setLoading] = useState(false)
+    const [loading,setLoading] = useState(false)
-    const { query } = parseQuery()
+    const { query } = parseQuery()
-    console.log('query', query)
+    console.log('query', query)
-
+
-    const fetchSSO = ()=>{
+    const fetchSSO = ()=>{
-        setLoading(true)
+        setLoading(true)
-
+
-        LoginApis.oauth2Url()
+        LoginApis.oauth2Url()
-            .then(({data})=>{
+            .then(({data})=>{
-                cacheTo(query?.to);
+                cacheTo(query?.to);
-                window.location.href = data.data.redirect_url
+                window.location.href = data.data.redirect_url
-            })
+            })
-            .catch(e=>{
+            .catch(e=>{
-                setActiveKey('account');
+                setActiveKey('account');
-                console.log('fetchSSO data fail', e)
+                console.log('fetchSSO data fail', e)
-            })
+            })
-            .finally(()=>{
+            .finally(()=>{
-                setLoading(false)
+                setLoading(false)
-            })
+            })
-    }
+    }
-
+
-
+
-    const onActiveKeyChange = (ak: string)=>{
+    const onActiveKeyChange = (ak: string)=>{
-        if (ak === 'sso'){
+        if (ak === 'sso'){
-            setActiveKey(ak);
+            setActiveKey(ak);
-            fetchSSO()
+            fetchSSO()
-        }else if (!loading){
+        }else if (!loading){
-            setActiveKey(ak);
+            setActiveKey(ak);
-        }
+        }
-    }
+    }
-
+
-
+
-    const tabItems:TabsProps["items"] = [
+    const tabItems:TabsProps["items"] = [
-        {
+        {
-            label: '账号密码',
+            label: '账号密码',
-            key: 'account',
+            key: 'account',
-            children: <AccountPanel />
+            children: <AccountPanel />
-        },
+        },
-    ]
+    ]
-    // @ts-ignore
+    // @ts-ignore
-    if (window.CONFIG.SSO){
+    if (window.CONFIG.SSO){
-        tabItems.push({
+        tabItems.push({
-            label: "SSO",
+            label: "SSO",
-            key: 'sso',
+            key: 'sso',
-            children: <Spin />
+            children: <Spin />
-        })
+        })
-    }
+    }
-
+
-    return (<div className="login-page">
+    return (<div className="login-page">
-        <div className="login-container">
+        <div className="login-container">
-            <Tabs activeKey={activeKey} onChange={onActiveKeyChange} items={tabItems}></Tabs>
+            <Tabs activeKey={activeKey} onChange={onActiveKeyChange} items={tabItems}></Tabs>
-        </div>
+        </div>
-    </div>)
+    </div>)
-}
+}
-
+
-
+

+ 0 - 0
src/pages/login/sso.tsx → frontend/src/pages/login/sso.tsx


+ 52 - 52
src/pages/nginx/certs/index.less → frontend/src/pages/nginx/certs/index.less

@@ -1,52 +1,52 @@
-.cert-page{
+.cert-page{
-  padding: 0 10px;
+  padding: 0 10px;
-  .cert-list{
+  .cert-list{
-    display: flex;
+    display: flex;
-    flex-direction: column;
+    flex-direction: column;
-  }
+  }
-  .cert-tags{
+  .cert-tags{
-    padding: 10px;
+    padding: 10px;
-    display: flex;
+    display: flex;
-    flex-direction: row;
+    flex-direction: row;
-    flex-wrap: wrap;
+    flex-wrap: wrap;
-    .ant-tag{
+    .ant-tag{
-      margin-bottom: 5px;
+      margin-bottom: 5px;
-      cursor: pointer;
+      cursor: pointer;
-    }
+    }
-  }
+  }
-
+
-}
+}
-.cert-edit-drawer{
+.cert-edit-drawer{
-  .ant-drawer-header{
+  .ant-drawer-header{
-    padding: 5px 10px;
+    padding: 5px 10px;
-  }
+  }
-  .ant-drawer-body{
+  .ant-drawer-body{
-    padding: 10px;
+    padding: 10px;
-
+
-    .ant-picker{
+    .ant-picker{
-      min-width: 60%;
+      min-width: 60%;
-    }
+    }
-  }
+  }
-
+
-  .cert-data{
+  .cert-data{
-    max-width: 900px;
+    max-width: 900px;
-    h5{
+    h5{
-      font-weight: bold;
+      font-weight: bold;
-      font-size: 16px;
+      font-size: 16px;
-    }
+    }
-  }
+  }
-  .inline-item{
+  .inline-item{
-    display: flex;
+    display: flex;
-    flex-direction: row;
+    flex-direction: row;
-    .ant-form-item{
+    .ant-form-item{
-      flex: 1;
+      flex: 1;
-      margin-right: 10px;
+      margin-right: 10px;
-    }
+    }
-  }
+  }
-  .footer-item{
+  .footer-item{
-    padding-left: 16%;
+    padding-left: 16%;
-    .ant-btn+.ant-btn{
+    .ant-btn+.ant-btn{
-      margin-left: 10px;
+      margin-left: 10px;
-    }
+    }
-  }
+  }
-}
+}

+ 249 - 249
src/pages/nginx/certs/index.tsx → frontend/src/pages/nginx/certs/index.tsx

@@ -1,249 +1,249 @@
-import {NginxApis} from "../../../api/nginx.ts";
+import {NginxApis} from "../../../api/nginx.ts";
-import {useAppSelector} from "../../../store";
+import {useAppSelector} from "../../../store";
-import {useEffect, useState} from "react";
+import {useEffect, useState} from "react";
-import {Button, Drawer, Form, Input, Modal, Table, Tooltip, Upload} from "antd";
+import {Button, Drawer, Form, Input, Modal, Table, Tooltip, Upload} from "antd";
-import {
+import {
-  DeleteOutlined,
+  DeleteOutlined,
-  EditOutlined,
+  EditOutlined,
-  ImportOutlined,
+  ImportOutlined,
-  PlusOutlined, QuestionOutlined,
+  PlusOutlined, QuestionOutlined,
-  SyncOutlined,
+  SyncOutlined,
-  UploadOutlined
+  UploadOutlined
-} from "@ant-design/icons";
+} from "@ant-design/icons";
-
+
-import './index.less'
+import './index.less'
-import {INginxCerts} from "../../../models/api.ts";
+import {INginxCerts} from "../../../models/api.ts";
-import {RcFile} from "antd/es/upload";
+import {RcFile} from "antd/es/upload";
-import {isNull, Message} from "planning-tools";
+import {isNull, Message} from "planning-tools";
-import {ModalStaticFunctions} from "antd/es/modal/confirm";
+import {ModalStaticFunctions} from "antd/es/modal/confirm";
-
+
-/**
+/**
- * 证书管理
+ * 证书管理
- * @constructor
+ * @constructor
- */
+ */
-export const NginxCerts = () => {
+export const NginxCerts = () => {
-    const nginx = useAppSelector(state => state.nginx.current)
+    const nginx = useAppSelector(state => state.nginx.current)
-
+
-    const [loading, setLoading] = useState(false)
+    const [loading, setLoading] = useState(false)
-    const [certs, setCerts] = useState<INginxCerts[]>([])
+    const [certs, setCerts] = useState<INginxCerts[]>([])
-    const [cert, setCert] = useState<Partial<INginxCerts>>()
+    const [cert, setCert] = useState<Partial<INginxCerts>>()
-    const [modal,contextHolder] = Modal.useModal()
+    const [modal,contextHolder] = Modal.useModal()
-
+
-
+
-  const [form] = Form.useForm()
+  const [form] = Form.useForm()
-
+
-    const onBeforeUpload = (name: 'pem' | 'key', file: RcFile) => {
+    const onBeforeUpload = (name: 'pem' | 'key', file: RcFile) => {
-        console.log('onBeforeUpload', name, file.name)
+        console.log('onBeforeUpload', name, file.name)
-        file.text().then(v=>{
+        file.text().then(v=>{
-            const data = {...cert, [name]: v };
+            const data = {...cert, [name]: v };
-            setCert(data as INginxCerts)
+            setCert(data as INginxCerts)
-            form.setFieldsValue(data)
+            form.setFieldsValue(data)
-        })
+        })
-
+
-        return false
+        return false
-    }
+    }
-
+
-    const fetchList = () => {
+    const fetchList = () => {
-        if (!nginx?.id) {
+        if (!nginx?.id) {
-            return
+            return
-        }
+        }
-        setLoading(true)
+        setLoading(true)
-        NginxApis.getCerts(nginx.id)
+        NginxApis.getCerts(nginx.id)
-            .then(({data}) => {
+            .then(({data}) => {
-                const content = data.data;
+                const content = data.data;
-                if (!content) {
+                if (!content) {
-                    setCerts([])
+                    setCerts([])
-                }else {
+                }else {
-                  setCerts(data.data)
+                  setCerts(data.data)
-                }
+                }
-            })
+            })
-            .finally(() => {
+            .finally(() => {
-                setLoading(false)
+                setLoading(false)
-            })
+            })
-    }
+    }
-
+
-    const syncFromDisk = ()=>{
+    const syncFromDisk = ()=>{
-      if (!nginx?.id){
+      if (!nginx?.id){
-        return
+        return
-      }
+      }
-      setLoading(true)
+      setLoading(true)
-      NginxApis.syncCerts(nginx.id)
+      NginxApis.syncCerts(nginx.id)
-        .then(() => {
+        .then(() => {
-          fetchList()
+          fetchList()
-        })
+        })
-        .finally(() => {
+        .finally(() => {
-          setLoading(false)
+          setLoading(false)
-        })
+        })
-    }
+    }
-
+
-    const onAddData = ()=>{
+    const onAddData = ()=>{
-        setCert({})
+        setCert({})
-        form.resetFields()
+        form.resetFields()
-    }
+    }
-
+
-  const onEditCert = (data: INginxCerts)=>{
+  const onEditCert = (data: INginxCerts)=>{
-    const fields = { ...data }
+    const fields = { ...data }
-    setCert(fields)
+    setCert(fields)
-    form.setFieldsValue(fields)
+    form.setFieldsValue(fields)
-  }
+  }
-
+
-    const onSubmitData = async ()=>{
+    const onSubmitData = async ()=>{
-        if (!nginx?.id){
+        if (!nginx?.id){
-          Message.warning('缓存数据异常,请退出到首页重新进去nginx实例配置页面。')
+          Message.warning('缓存数据异常,请退出到首页重新进去nginx实例配置页面。')
-            return
+            return
-        }
+        }
-        const values = await form.validateFields()
+        const values = await form.validateFields()
-        console.log('values',values);
+        console.log('values',values);
-        setLoading(true);
+        setLoading(true);
-        const postData = { ...cert, ...values}
+        const postData = { ...cert, ...values}
-        postData.nginxId = nginx.id
+        postData.nginxId = nginx.id
-        NginxApis.saveCerts(nginx.id, postData)
+        NginxApis.saveCerts(nginx.id, postData)
-            .then(({data})=>{
+            .then(({data})=>{
-                console.log('data',data);
+                console.log('data',data);
-                Message.success('保存成功!');
+                Message.success('保存成功!');
-                setCert(undefined)
+                setCert(undefined)
-                fetchList();
+                fetchList();
-            })
+            })
-            .finally(()=>{
+            .finally(()=>{
-                setLoading(false)
+                setLoading(false)
-            })
+            })
-    }
+    }
-
+
-
+
-    useEffect(() => {
+    useEffect(() => {
-        fetchList()
+        fetchList()
-    }, [])
+    }, [])
-
+
-    return (<div className="page cert-page">
+    return (<div className="page cert-page">
-        <div className="page-header">
+        <div className="page-header">
-            <Button type="primary" loading={loading} onClick={fetchList} icon={<SyncOutlined/>}/>
+            <Button type="primary" loading={loading} onClick={fetchList} icon={<SyncOutlined/>}/>
-            <Button onClick={onAddData} icon={<PlusOutlined/>}/>
+            <Button onClick={onAddData} icon={<PlusOutlined/>}/>
-            <div style={{flex:1}} />
+            <div style={{flex:1}} />
-          <Tooltip placement="left" title="从数据目录中导入,适用于初始化;如果数据库中已存在,会覆盖,请谨慎处理">
+          <Tooltip placement="left" title="从数据目录中导入,适用于初始化;如果数据库中已存在,会覆盖,请谨慎处理">
-            <Button danger loading={loading} onClick={syncFromDisk} icon={<ImportOutlined/>}></Button>
+            <Button danger loading={loading} onClick={syncFromDisk} icon={<ImportOutlined/>}></Button>
-          </Tooltip>
+          </Tooltip>
-        </div>
+        </div>
-        <div className="page-container cert-list">
+        <div className="page-container cert-list">
-          <Table rowKey="id" dataSource={certs} pagination={false}>
+          <Table rowKey="id" dataSource={certs} pagination={false}>
-            <Table.Column
+            <Table.Column
-              title="名称"
+              title="名称"
-              dataIndex="serviceName"
+              dataIndex="serviceName"
-            />
+            />
-            <Table.Column
+            <Table.Column
-              title="域名"
+              title="域名"
-              dataIndex="subjectName"
+              dataIndex="subjectName"
-              render={(v,data: INginxCerts)=>{
+              render={(v,data: INginxCerts)=>{
-                return (<>
+                return (<>
-                  {v || '--'}
+                  {v || '--'}
-                  { data.hintMsg ? <Tooltip title={data.hintMsg}><QuestionOutlined /></Tooltip> : null }
+                  { data.hintMsg ? <Tooltip title={data.hintMsg}><QuestionOutlined /></Tooltip> : null }
-                </>)
+                </>)
-              }}
+              }}
-            />
+            />
-            <Table.Column title="添加时间" dataIndex="createdAt" />
+            <Table.Column title="添加时间" dataIndex="createdAt" />
-            <Table.Column title="过期时间" dataIndex="expiresAt" />
+            <Table.Column title="过期时间" dataIndex="expiresAt" />
-            <Table.Column title="备注" dataIndex="remark" />
+            <Table.Column title="备注" dataIndex="remark" />
-            <Table.Column title="操作" dataIndex="ops"
+            <Table.Column title="操作" dataIndex="ops"
-                          width={120}
+                          width={120}
-                          render={(_,data: INginxCerts)=>(<>
+                          render={(_,data: INginxCerts)=>(<>
-                            <DelButton onRefresh={fetchList} cert={data} nginxId={nginx?.id || 0} modal={modal} />
+                            <DelButton onRefresh={fetchList} cert={data} nginxId={nginx?.id || 0} modal={modal} />
-                            <Button onClick={()=>onEditCert(data)} type="link" icon={<EditOutlined />}></Button>
+                            <Button onClick={()=>onEditCert(data)} type="link" icon={<EditOutlined />}></Button>
-            </>)} />
+            </>)} />
-          </Table>
+          </Table>
-          <Drawer open={!!cert}
+          <Drawer open={!!cert}
-                  width={750}
+                  width={750}
-                  className="cert-edit-drawer"
+                  className="cert-edit-drawer"
-                  onClose={()=>setCert(undefined)}
+                  onClose={()=>setCert(undefined)}
-                  destroyOnClose
+                  destroyOnClose
-                  title={isNull(cert?.id) ?'添加证书': '编辑证书'}>
+                  title={isNull(cert?.id) ?'添加证书': '编辑证书'}>
-
+
-            <div className="cert-data">
+            <div className="cert-data">
-              <Form form={form} initialValues={cert} labelCol={{span: 4}}>
+              <Form form={form} initialValues={cert} labelCol={{span: 4}}>
-                <Form.Item name="serviceName"
+                <Form.Item name="serviceName"
-                           rules={[{required: true, message: '请输入域名或者名称,唯一不可重复'}]}
+                           rules={[{required: true, message: '请输入域名或者名称,唯一不可重复'}]}
-                           label="域名">
+                           label="域名">
-                  <Input />
+                  <Input />
-                </Form.Item>
+                </Form.Item>
-                <div className="inline-item" >
+                <div className="inline-item" >
-                  <Form.Item name="pem"
+                  <Form.Item name="pem"
-                             rules={[{required: true, message: '请输入或者选择证书'}]}
+                             rules={[{required: true, message: '请输入或者选择证书'}]}
-                             label="pem证书">
+                             label="pem证书">
-                    <Input.TextArea rows={8}/>
+                    <Input.TextArea rows={8}/>
-
+
-                  </Form.Item>
+                  </Form.Item>
-                  <Upload beforeUpload={(file) => onBeforeUpload("pem", file)}
+                  <Upload beforeUpload={(file) => onBeforeUpload("pem", file)}
-                          showUploadList={false}
+                          showUploadList={false}
-                          accept=".pem">
+                          accept=".pem">
-                    <Button icon={<UploadOutlined/>}></Button>
+                    <Button icon={<UploadOutlined/>}></Button>
-                  </Upload>
+                  </Upload>
-                </div>
+                </div>
-                <div className="inline-item" >
+                <div className="inline-item" >
-                  <Form.Item name="key"
+                  <Form.Item name="key"
-                             rules={[{required: true, message: '请输入或者选择私钥'}]}
+                             rules={[{required: true, message: '请输入或者选择私钥'}]}
-                             label="私钥">
+                             label="私钥">
-                    <Input.TextArea rows={8} />
+                    <Input.TextArea rows={8} />
-
+
-                  </Form.Item>
+                  </Form.Item>
-                  <Upload beforeUpload={(file) => onBeforeUpload("key", file)}
+                  <Upload beforeUpload={(file) => onBeforeUpload("key", file)}
-                          showUploadList={false}
+                          showUploadList={false}
-                          accept=".key">
+                          accept=".key">
-                    <Button icon={<UploadOutlined/>}></Button>
+                    <Button icon={<UploadOutlined/>}></Button>
-                  </Upload>
+                  </Upload>
-                </div>
+                </div>
-
+
-                <Form.Item name="remark" label="备注">
+                <Form.Item name="remark" label="备注">
-                  <Input.TextArea />
+                  <Input.TextArea />
-                </Form.Item>
+                </Form.Item>
-                <Form.Item className="footer-item">
+                <Form.Item className="footer-item">
-                  <Button onClick={()=>setCert(undefined)}>取消</Button>
+                  <Button onClick={()=>setCert(undefined)}>取消</Button>
-                  <Button loading={loading} onClick={onSubmitData} type="primary">保存</Button>
+                  <Button loading={loading} onClick={onSubmitData} type="primary">保存</Button>
-                </Form.Item>
+                </Form.Item>
-              </Form>
+              </Form>
-            </div>
+            </div>
-          </Drawer>
+          </Drawer>
-        </div>
+        </div>
-      {contextHolder}
+      {contextHolder}
-
+
-    </div>)
+    </div>)
-}
+}
-type IProps = {
+type IProps = {
-    cert: INginxCerts
+    cert: INginxCerts
-    nginxId: number
+    nginxId: number
-    onRefresh: () => void
+    onRefresh: () => void
-  modal: Omit<ModalStaticFunctions, "warn">
+  modal: Omit<ModalStaticFunctions, "warn">
-}
+}
-const DelButton = ({cert, nginxId, onRefresh, modal}: IProps)=>{
+const DelButton = ({cert, nginxId, onRefresh, modal}: IProps)=>{
-
+
-    const [loading,setLoading] = useState(false)
+    const [loading,setLoading] = useState(false)
-
+
-    const onDel = (e: any)=>{
+    const onDel = (e: any)=>{
-        e.preventDefault()
+        e.preventDefault()
-        modal.confirm({
+        modal.confirm({
-            title: '警告',
+            title: '警告',
-            content: '您确定要删除该证书信息吗?删除操作不可恢复,请谨慎操作.',
+            content: '您确定要删除该证书信息吗?删除操作不可恢复,请谨慎操作.',
-            okType: 'danger',
+            okType: 'danger',
-            cancelText: '取消',
+            cancelText: '取消',
-            okText: '确定',
+            okText: '确定',
-            onOk: ()=>{
+            onOk: ()=>{
-                setLoading(true)
+                setLoading(true)
-                NginxApis.delCerts(nginxId, cert.id)
+                NginxApis.delCerts(nginxId, cert.id)
-                    .then(()=>{
+                    .then(()=>{
-                        onRefresh?.()
+                        onRefresh?.()
-                    })
+                    })
-                    .finally(()=>{
+                    .finally(()=>{
-                        setLoading(false)
+                        setLoading(false)
-                    })
+                    })
-            }
+            }
-        })
+        })
-    }
+    }
-
+
-
+
-
+
-    return <>
+    return <>
-        <Button onClick={onDel}
+        <Button onClick={onDel}
-                loading={loading}
+                loading={loading}
-                type="text"
+                type="text"
-                danger
+                danger
-                icon={<DeleteOutlined />}
+                icon={<DeleteOutlined />}
-        />
+        />
-    </>
+    </>
-}
+}

+ 56 - 56
src/pages/nginx/components/EditNginxBtn.tsx → frontend/src/pages/nginx/components/EditNginxBtn.tsx

@@ -1,56 +1,56 @@
-/**
+/**
- * @author tuonian
+ * @author tuonian
- * @date 2023/6/30
+ * @date 2023/6/30
- */
+ */
-import {INginx} from "../../../models/nginx.ts";
+import {INginx} from "../../../models/nginx.ts";
-import {NginxActions} from "../../../store/slice/nginx.ts";
+import {NginxActions} from "../../../store/slice/nginx.ts";
-import {useState} from "react";
+import {useState} from "react";
-import {EditOutlined} from "@ant-design/icons";
+import {EditOutlined} from "@ant-design/icons";
-import {Button} from "antd";
+import {Button} from "antd";
-import {NginxApis} from "../../../api/nginx.ts";
+import {NginxApis} from "../../../api/nginx.ts";
-import {Notify} from "planning-tools";
+import {Notify} from "planning-tools";
-import {useAppDispatch} from "../../../store";
+import {useAppDispatch} from "../../../store";
-import {useNavigate} from "react-router";
+import {useNavigate} from "react-router";
-import {nginxPrefix} from "../../../routes/routes.tsx";
+import {nginxPrefix} from "../../../routes/routes.tsx";
-
+
-type IProps = {
+type IProps = {
-  nginx: INginx
+  nginx: INginx
-}
+}
-
+
-
+
-export const EditNginxBtn = ({nginx}: IProps)=>{
+export const EditNginxBtn = ({nginx}: IProps)=>{
-
+
-  const [loading,setLoading] = useState(false)
+  const [loading,setLoading] = useState(false)
-  const dispatch = useAppDispatch()
+  const dispatch = useAppDispatch()
-  const navigate = useNavigate()
+  const navigate = useNavigate()
-
+
-  const toNginx = ()=>{
+  const toNginx = ()=>{
-    setLoading(true);
+    setLoading(true);
-    NginxApis.getNginx(nginx.id)
+    NginxApis.getNginx(nginx.id)
-      .then(({data})=>{
+      .then(({data})=>{
-        const respData = data.data;
+        const respData = data.data;
-        if (!respData){
+        if (!respData){
-          Notify.warn('查询失败,请重试!');
+          Notify.warn('查询失败,请重试!');
-          return
+          return
-        }
+        }
-        console.log('getNginx', data)
+        console.log('getNginx', data)
-        dispatch(NginxActions.setCurrent({
+        dispatch(NginxActions.setCurrent({
-            nginx,
+            nginx,
-            servers: respData.servers
+            servers: respData.servers
-        }))
+        }))
-        navigate(nginxPrefix(nginx.id))
+        navigate(nginxPrefix(nginx.id))
-      })
+      })
-      .catch(e=>{
+      .catch(e=>{
-        Notify.warn(e.msg || e.message)
+        Notify.warn(e.msg || e.message)
-      })
+      })
-      .finally(()=>{
+      .finally(()=>{
-        setLoading(false)
+        setLoading(false)
-      })
+      })
-
+
-
+
-  }
+  }
-
+
-  return (
+  return (
-    <Button loading={loading} onClick={()=>toNginx()} type="link" icon={<EditOutlined />} />
+    <Button loading={loading} onClick={()=>toNginx()} type="link" icon={<EditOutlined />} />
-  )
+  )
-}
+}

+ 97 - 97
src/pages/nginx/components/StopStartButton.tsx → frontend/src/pages/nginx/components/StopStartButton.tsx

@@ -1,97 +1,97 @@
-import {useEffect, useState} from "react";
+import {useEffect, useState} from "react";
-import {Button, Modal, Tag} from "antd";
+import {Button, Modal, Tag} from "antd";
-import {isNull, Message} from "planning-tools";
+import {isNull, Message} from "planning-tools";
-import {NginxApis} from "../../../api/nginx.ts";
+import {NginxApis} from "../../../api/nginx.ts";
-import {useAppSelector} from "../../../store";
+import {useAppSelector} from "../../../store";
-
+
-/**
+/**
- *
+ *
- * @constructor
+ * @constructor
- */
+ */
-export const StopStartButton = () => {
+export const StopStartButton = () => {
-
+
-    const [isRun,setIsRun] = useState<boolean>()
+    const [isRun,setIsRun] = useState<boolean>()
-    const [loading,setLoading] = useState(false)
+    const [loading,setLoading] = useState(false)
-    const [modal,contextHolder] = Modal.useModal()
+    const [modal,contextHolder] = Modal.useModal()
-
+
-    const nginx = useAppSelector(state => state.nginx.current)
+    const nginx = useAppSelector(state => state.nginx.current)
-
+
-    const fetchStatus = () => {
+    const fetchStatus = () => {
-        if (!nginx){
+        if (!nginx){
-            return
+            return
-        }
+        }
-        setLoading(true)
+        setLoading(true)
-        NginxApis.status(nginx.id)
+        NginxApis.status(nginx.id)
-            .then(({data})=>{
+            .then(({data})=>{
-                setIsRun(data.data)
+                setIsRun(data.data)
-                console.log('status', data)
+                console.log('status', data)
-                if (!data.msg){
+                if (!data.msg){
-                    return
+                    return
-                }
+                }
-                if (data.data){
+                if (data.data){
-                    Message.success(data.msg)
+                    Message.success(data.msg)
-                }else {
+                }else {
-                    Message.warning(data.msg)
+                    Message.warning(data.msg)
-                }
+                }
-            })
+            })
-            .finally(()=>{
+            .finally(()=>{
-                setLoading(false)
+                setLoading(false)
-            })
+            })
-    }
+    }
-
+
-
+
-    const postStartOrStopApi = ()=>{
+    const postStartOrStopApi = ()=>{
-        if (!nginx){
+        if (!nginx){
-            return
+            return
-        }
+        }
-        setLoading(true)
+        setLoading(true)
-        const request = isRun ? NginxApis.stopNginx(nginx.id) : NginxApis.startNginx(nginx.id);
+        const request = isRun ? NginxApis.stopNginx(nginx.id) : NginxApis.startNginx(nginx.id);
-        request.then(({data})=>{
+        request.then(({data})=>{
-            console.log('data', data);
+            console.log('data', data);
-            setIsRun(data.data);
+            setIsRun(data.data);
-            if (data.msg){
+            if (data.msg){
-                Message.warning(data.msg)
+                Message.warning(data.msg)
-            }
+            }
-        }).finally(()=>{
+        }).finally(()=>{
-            setLoading(false)
+            setLoading(false)
-        })
+        })
-    }
+    }
-    const onStartOrStop  = () => {
+    const onStartOrStop  = () => {
-        if (isNull(isRun)){
+        if (isNull(isRun)){
-            fetchStatus()
+            fetchStatus()
-            return
+            return
-        }
+        }
-        modal.confirm({
+        modal.confirm({
-            type: 'warning',
+            type: 'warning',
-            title: `您确定要${isRun ? '停止' : '启动'}nginx服务吗?`,
+            title: `您确定要${isRun ? '停止' : '启动'}nginx服务吗?`,
-            okType: 'danger',
+            okType: 'danger',
-            okText: '确定',
+            okText: '确定',
-            cancelText: '取消',
+            cancelText: '取消',
-            onOk: ()=>{
+            onOk: ()=>{
-                postStartOrStopApi()
+                postStartOrStopApi()
-            }
+            }
-        })
+        })
-    }
+    }
-
+
-    useEffect(()=>{
+    useEffect(()=>{
-        fetchStatus()
+        fetchStatus()
-    },[])
+    },[])
-
+
-    if (!nginx){
+    if (!nginx){
-        return null
+        return null
-    }
+    }
-
+
-    return (<>
+    return (<>
-      <span>Nginx:</span>
+      <span>Nginx:</span>
-      <Tag color={isNull(isRun) ? 'grey': isRun ? 'green': 'red'}>{isNull(isRun) ? '未知': isRun ? '运行中':'已停止'}</Tag>
+      <Tag color={isNull(isRun) ? 'grey': isRun ? 'green': 'red'}>{isNull(isRun) ? '未知': isRun ? '运行中':'已停止'}</Tag>
-        <Button type={ isRun?'default' : 'primary'}
+        <Button type={ isRun?'default' : 'primary'}
-                onClick={onStartOrStop}
+                onClick={onStartOrStop}
-                hidden={isNull(isRun)}
+                hidden={isNull(isRun)}
-                size="small"
+                size="small"
-                danger={isRun}
+                danger={isRun}
-                loading={loading}>
+                loading={loading}>
-            { isRun ? '停止':'启动'}
+            { isRun ? '停止':'启动'}
-        </Button>
+        </Button>
-        {contextHolder}
+        {contextHolder}
-        </>)
+        </>)
-}
+}

+ 0 - 0
src/pages/nginx/components/access/config.json → frontend/src/pages/nginx/components/access/config.json


+ 0 - 0
src/pages/nginx/components/access/index.less → frontend/src/pages/nginx/components/access/index.less


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


+ 42 - 42
src/pages/nginx/components/auth/config.json → frontend/src/pages/nginx/components/auth/config.json

@@ -1,42 +1,42 @@
-{
+{
-  "form": [
+  "form": [
-    {
+    {
-      "title": "是否启用",
+      "title": "是否启用",
-      "key": "auth_request_on",
+      "key": "auth_request_on",
-      "required": false,
+      "required": false,
-      "type": "switch",
+      "type": "switch",
-      "cascade": {
+      "cascade": {
-        "true": [
+        "true": [
-          {
+          {
-            "title": "auth_request_uri",
+            "title": "auth_request_uri",
-            "key": "auth_request_uri",
+            "key": "auth_request_uri",
-            "type": "string",
+            "type": "string",
-            "placeholder": "输入鉴权的路由",
+            "placeholder": "输入鉴权的路由",
-            "width": 382
+            "width": 382
-          }
+          }
-        ]
+        ]
-      }
+      }
-    },
+    },
-    {
+    {
-      "title": "auth_request_set",
+      "title": "auth_request_set",
-      "key": "auth_request_set",
+      "key": "auth_request_set",
-      "required": false,
+      "required": false,
-      "type": "array",
+      "type": "array",
-      "items": [
+      "items": [
-        {
+        {
-          "key": "name",
+          "key": "name",
-          "title": "变量",
+          "title": "变量",
-          "type": "string",
+          "type": "string",
-          "width": 120
+          "width": 120
-        },
+        },
-        {
+        {
-          "key": "value",
+          "key": "value",
-          "title": "变量值",
+          "title": "变量值",
-          "type": "string",
+          "type": "string",
-          "width": 200
+          "width": 200
-        }
+        }
-      ],
+      ],
-      "description": "Sets the request variable to the given value after the authorization request completes. The value may contain variables from the authorization request, such as $upstream_http_*."
+      "description": "Sets the request variable to the given value after the authorization request completes. The value may contain variables from the authorization request, such as $upstream_http_*."
-    }
+    }
-  ]
+  ]
-}
+}

+ 10 - 10
src/pages/nginx/components/auth/index.less → frontend/src/pages/nginx/components/auth/index.less

@@ -1,10 +1,10 @@
-.gzip-input{
+.gzip-input{
-  margin-right: 10px;
+  margin-right: 10px;
-}
+}
-
+
-.gzip-popover{
+.gzip-popover{
-  .auto-form{
+  .auto-form{
-    min-width: 450px;
+    min-width: 450px;
-    width: 550px;
+    width: 550px;
-  }
+  }
-}
+}

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

@@ -1,40 +1,40 @@
-/**
+/**
- * @author tuonian
+ * @author tuonian
- * @date 2023/7/5
+ * @date 2023/7/5
- */
+ */
-import {AutoTypeInputProps} from 'planning-tools'
+import {AutoTypeInputProps} from 'planning-tools'
-import './index.less'
+import './index.less'
-import config from './config.json'
+import config from './config.json'
-import {IContentProps, NgxBasicInput, registerInput} from "../basic";
+import {IContentProps, NgxBasicInput, registerInput} from "../basic";
-import {KeyValue} from "../../../../models/nginx.ts";
+import {KeyValue} from "../../../../models/nginx.ts";
-
+
-export const AuthInput = ({...props}: AutoTypeInputProps)=>{
+export const AuthInput = ({...props}: AutoTypeInputProps)=>{
-
+
-  const ShowContent = ({data}: IContentProps)=>{
+  const ShowContent = ({data}: IContentProps)=>{
-    return (<span>{data.auth_request_on ? data.auth_request_uri : '不启用'}</span>)
+    return (<span>{data.auth_request_on ? data.auth_request_uri : '不启用'}</span>)
-  }
+  }
-
+
-  const renderLines = (values: any)=>{
+  const renderLines = (values: any)=>{
-    const lines: string[] = []
+    const lines: string[] = []
-    if (!values.auth_request_on){
+    if (!values.auth_request_on){
-      return lines
+      return lines
-    }
+    }
-    lines.push(`auth_request  ${values.auth_request_uri};`)
+    lines.push(`auth_request  ${values.auth_request_uri};`)
-    if (Array.isArray(values.auth_request_set)){
+    if (Array.isArray(values.auth_request_set)){
-      values.auth_request_set.forEach((item: KeyValue)=>{
+      values.auth_request_set.forEach((item: KeyValue)=>{
-        lines.push(`auth_request_set  ${item.name}  ${item.value};`)
+        lines.push(`auth_request_set  ${item.name}  ${item.value};`)
-      })
+      })
-    }
+    }
-    return lines;
+    return lines;
-  }
+  }
-
+
-  return <NgxBasicInput
+  return <NgxBasicInput
-    {...props}
+    {...props}
-    columns={config.form}
+    columns={config.form}
-    renderLines={renderLines}
+    renderLines={renderLines}
-    content={ShowContent}
+    content={ShowContent}
-    />
+    />
-}
+}
-
+
-registerInput('auth', AuthInput)
+registerInput('auth', AuthInput)
-
+

+ 37 - 37
src/pages/nginx/components/basic/index.less → frontend/src/pages/nginx/components/basic/index.less

@@ -1,37 +1,37 @@
-.popover-input{
+.popover-input{
-  margin-right: 10px;
+  margin-right: 10px;
-  display: flex;
+  display: flex;
-  flex-direction: row;
+  flex-direction: row;
-  align-items: center;
+  align-items: center;
-}
+}
-
+
-.popover-popover{
+.popover-popover{
-  .auto-form{
+  .auto-form{
-    min-width: 450px;
+    min-width: 450px;
-    width: 550px;
+    width: 550px;
-  }
+  }
-  .form-btns{
+  .form-btns{
-    padding-left: 25%;
+    padding-left: 25%;
-    .ant-btn+.ant-btn{
+    .ant-btn+.ant-btn{
-      margin-left: 10px;
+      margin-left: 10px;
-    }
+    }
-  }
+  }
-}
+}
-
+
-.drawer-input{
+.drawer-input{
-  .ant-drawer-header{
+  .ant-drawer-header{
-    padding: 5px 15px;
+    padding: 5px 15px;
-  }
+  }
-  .ant-drawer-body{
+  .ant-drawer-body{
-    padding: 15px;
+    padding: 15px;
-    .form-btns{
+    .form-btns{
-      padding-left: 16%;
+      padding-left: 16%;
-      .ant-btn+.ant-btn{
+      .ant-btn+.ant-btn{
-        margin-left: 10px;
+        margin-left: 10px;
-      }
+      }
-    }
+    }
-  }
+  }
-  .ant-drawer-content-wrapper{
+  .ant-drawer-content-wrapper{
-
+
-  }
+  }
-}
+}

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

@@ -1,133 +1,133 @@
-/**
+/**
- * @author tuonian
+ * @author tuonian
- * @date 2023/7/5
+ * @date 2023/7/5
- */
+ */
-import {AdvanceInputConfigs, AutoForm, AutoTypeInputProps, FormColumnType} from 'planning-tools'
+import {AdvanceInputConfigs, AutoForm, AutoTypeInputProps, FormColumnType} from 'planning-tools'
-import {Button, Drawer, FormInstance, Popover} from "antd";
+import {Button, Drawer, FormInstance, Popover} from "antd";
-import {EditOutlined} from "@ant-design/icons";
+import {EditOutlined} from "@ant-design/icons";
-import React, {useEffect, useState} from "react";
+import React, {useEffect, useState} from "react";
-import {AutoFormFooterProps} from "planning-tools/dist/esm/Components/AutoForm/form";
+import {AutoFormFooterProps} from "planning-tools/dist/esm/Components/AutoForm/form";
-import {NgxModuleData} from "../input.ts";
+import {NgxModuleData} from "../input.ts";
-import './index.less'
+import './index.less'
-import {DrawerProps} from "antd/lib/drawer";
+import {DrawerProps} from "antd/lib/drawer";
-
+
-export type OnChange = (values: any) => void
+export type OnChange = (values: any) => void
-
+
-export type IContentProps<T = any> = {
+export type IContentProps<T = any> = {
-  data: T
+  data: T
-  onChange: OnChange
+  onChange: OnChange
-}
+}
-
+
-type IProps = {
+type IProps = {
-  content: React.FC<IContentProps>
+  content: React.FC<IContentProps>
-  columns?: FormColumnType[]
+  columns?: FormColumnType[]
-  renderLines: (values: any) => string[]
+  renderLines: (values: any) => string[]
-  renderHttpLines?: (values: any) =>string[]
+  renderHttpLines?: (values: any) =>string[]
-  overlayClassName?: string
+  overlayClassName?: string
-  labelCol?: number;
+  labelCol?: number;
-  drawer?: boolean;
+  drawer?: boolean;
-  drawerProps?: Partial<DrawerProps>
+  drawerProps?: Partial<DrawerProps>
-}
+}
-export const NgxBasicInput = (
+export const NgxBasicInput = (
-  {
+  {
-    content: ContentComp,
+    content: ContentComp,
-    columns = [],
+    columns = [],
-    renderLines,
+    renderLines,
-      renderHttpLines,
+      renderHttpLines,
-    overlayClassName,
+    overlayClassName,
-    labelCol = 6,
+    labelCol = 6,
-    value, onChange,
+    value, onChange,
-      drawer,
+      drawer,
-      drawerProps,
+      drawerProps,
-  }: IProps & AutoTypeInputProps) => {
+  }: IProps & AutoTypeInputProps) => {
-
+
-  const [data, setData] = useState<any>({})
+  const [data, setData] = useState<any>({})
-  const [open, setOpen] = useState(false)
+  const [open, setOpen] = useState(false)
-
+
-  useEffect(() => {
+  useEffect(() => {
-    if (value?.data) {
+    if (value?.data) {
-      setData(value.data || {})
+      setData(value.data || {})
-    }
+    }
-  }, [value])
+  }, [value])
-
+
-  const triggerChange = (values: any) => {
+  const triggerChange = (values: any) => {
-    const lines = renderLines(values)
+    const lines = renderLines(values)
-    onChange?.({
+    onChange?.({
-      data: values,
+      data: values,
-      lines: lines,
+      lines: lines,
-      http: renderHttpLines?.(values) || []
+      http: renderHttpLines?.(values) || []
-    } as NgxModuleData)
+    } as NgxModuleData)
-  }
+  }
-
+
-  const onValuesChange = (values: any) => {
+  const onValuesChange = (values: any) => {
-    const current = {...data, ...values}
+    const current = {...data, ...values}
-    setData(current)
+    setData(current)
-    setOpen(false)
+    setOpen(false)
-    triggerChange(current)
+    triggerChange(current)
-  }
+  }
-
+
-  const onSubmitData = async (form: FormInstance | null) => {
+  const onSubmitData = async (form: FormInstance | null) => {
-    if (!form) {
+    if (!form) {
-      return
+      return
-    }
+    }
-    const values = await form.validateFields();
+    const values = await form.validateFields();
-    console.log('values', values);
+    console.log('values', values);
-    onValuesChange(values)
+    onValuesChange(values)
-  }
+  }
-
+
-  const renderFormFooter = ({formRef}: AutoFormFooterProps) => {
+  const renderFormFooter = ({formRef}: AutoFormFooterProps) => {
-    return (<div className="form-btns">
+    return (<div className="form-btns">
-      <Button onClick={() => onSubmitData(formRef.current as never)} type="primary">保存</Button>
+      <Button onClick={() => onSubmitData(formRef.current as never)} type="primary">保存</Button>
-      <Button onClick={() => setOpen(false)}>取消</Button>
+      <Button onClick={() => setOpen(false)}>取消</Button>
-    </div>)
+    </div>)
-  }
+  }
-
+
-  const renderForm = () => {
+  const renderForm = () => {
-    return (<AutoForm columns={columns}
+    return (<AutoForm columns={columns}
-                      formProps={{
+                      formProps={{
-                        labelCol: {span: labelCol}
+                        labelCol: {span: labelCol}
-                      }}
+                      }}
-                      onlyFields={true}
+                      onlyFields={true}
-                      footer={renderFormFooter}
+                      footer={renderFormFooter}
-                      data={data}/>)
+                      data={data}/>)
-  }
+  }
-
+
-  const renderFormContainer = ()=>{
+  const renderFormContainer = ()=>{
-      if (drawer){
+      if (drawer){
-          return (<>
+          return (<>
-              <Button type="link" onClick={() => setOpen(true)} icon={<EditOutlined/>}/>
+              <Button type="link" onClick={() => setOpen(true)} icon={<EditOutlined/>}/>
-              <Drawer open={open}
+              <Drawer open={open}
-                      destroyOnClose
+                      destroyOnClose
-                      onClose={()=>setOpen(false)}
+                      onClose={()=>setOpen(false)}
-                      width={500}
+                      width={500}
-                      className="drawer-input"
+                      className="drawer-input"
-                      {...drawerProps}
+                      {...drawerProps}
-              >
+              >
-                  {renderForm()}
+                  {renderForm()}
-              </Drawer>
+              </Drawer>
-              </>)
+              </>)
-      }
+      }
-      return (
+      return (
-          <Popover destroyTooltipOnHide
+          <Popover destroyTooltipOnHide
-                   overlayClassName={`popover-popover ${overlayClassName || ''}`}
+                   overlayClassName={`popover-popover ${overlayClassName || ''}`}
-                   placement="right"
+                   placement="right"
-                   open={open}
+                   open={open}
-                   onOpenChange={o => setOpen(o)}
+                   onOpenChange={o => setOpen(o)}
-                   trigger="click" content={renderForm}>
+                   trigger="click" content={renderForm}>
-              <Button type="link" onClick={() => setOpen(true)} icon={<EditOutlined/>}/>
+              <Button type="link" onClick={() => setOpen(true)} icon={<EditOutlined/>}/>
-          </Popover>
+          </Popover>
-      )
+      )
-  }
+  }
-
+
-  return (<div className="popover-input">
+  return (<div className="popover-input">
-    <ContentComp data={data} onChange={onValuesChange}/>
+    <ContentComp data={data} onChange={onValuesChange}/>
-      {renderFormContainer()}
+      {renderFormContainer()}
-  </div>)
+  </div>)
-}
+}
-
+
-/**
+/**
- * 注册自定义的输入框
+ * 注册自定义的输入框
- * @param type
+ * @param type
- * @param Component
+ * @param Component
- */
+ */
-export const registerInput = (type: string, Component: React.FC<AutoTypeInputProps>) => AdvanceInputConfigs[type] = Component
+export const registerInput = (type: string, Component: React.FC<AutoTypeInputProps>) => AdvanceInputConfigs[type] = Component

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

@@ -1,52 +1,52 @@
-import {AdvanceInputConfigs, AutoTypeInputProps} from "planning-tools";
+import {AdvanceInputConfigs, AutoTypeInputProps} from "planning-tools";
-import {Select} from "antd";
+import {Select} from "antd";
-import {useEffect, useState} from "react";
+import {useEffect, useState} from "react";
-import {NginxApis} from "../../../../api/nginx.ts";
+import {NginxApis} from "../../../../api/nginx.ts";
-import {useAppSelector} from "../../../../store";
+import {useAppSelector} from "../../../../store";
-import {INginxCerts} from "../../../../models/api.ts";
+import {INginxCerts} from "../../../../models/api.ts";
-/**
+/**
- * @author tuonian
+ * @author tuonian
- * @date 2023/7/4
+ * @date 2023/7/4
- */
+ */
-
+
-export const CertsSelect = ({value, onChange }: AutoTypeInputProps)=>{
+export const CertsSelect = ({value, onChange }: AutoTypeInputProps)=>{
-
+
-  const [loading,setLoading] = useState(false)
+  const [loading,setLoading] = useState(false)
-  const [options,setOptions] = useState<any[]>([])
+  const [options,setOptions] = useState<any[]>([])
-
+
-  const nginx = useAppSelector(state => state.nginx.current)
+  const nginx = useAppSelector(state => state.nginx.current)
-
+
-  const fetchCerts = ()=>{
+  const fetchCerts = ()=>{
-    if (!nginx?.id){
+    if (!nginx?.id){
-      return
+      return
-    }
+    }
-    setLoading(true)
+    setLoading(true)
-    NginxApis.getCerts(nginx.id)
+    NginxApis.getCerts(nginx.id)
-      .then(({data})=>{
+      .then(({data})=>{
-        const list = Array.isArray(data.data) ?data.data.map((item: INginxCerts)=>{
+        const list = Array.isArray(data.data) ?data.data.map((item: INginxCerts)=>{
-          return {
+          return {
-            label: item.serviceName,
+            label: item.serviceName,
-            value: item.serviceName
+            value: item.serviceName
-          }
+          }
-        }): []
+        }): []
-        setOptions(list)
+        setOptions(list)
-      })
+      })
-      .finally(()=>{
+      .finally(()=>{
-        setLoading(false)
+        setLoading(false)
-      })
+      })
-
+
-  }
+  }
-  useEffect(()=>{
+  useEffect(()=>{
-    fetchCerts()
+    fetchCerts()
-  },[])
+  },[])
-
+
-
+
-  return (<Select loading={loading}
+  return (<Select loading={loading}
-                  value={value}
+                  value={value}
-                  onChange={onChange}
+                  onChange={onChange}
-                  options={options}
+                  options={options}
-                  style={{marginRight: 10}} />)
+                  style={{marginRight: 10}} />)
-}
+}
-
+
-
+
-AdvanceInputConfigs['certs'] = CertsSelect
+AdvanceInputConfigs['certs'] = CertsSelect

+ 53 - 53
src/pages/nginx/components/cors/config.json → frontend/src/pages/nginx/components/cors/config.json

@@ -1,53 +1,53 @@
-{
+{
-  "form": [
+  "form": [
-    {
+    {
-      "title": "域名",
+      "title": "域名",
-      "key": "origins",
+      "key": "origins",
-      "type": "select",
+      "type": "select",
-      "mode": "tags",
+      "mode": "tags",
-      "description": "配置允许跨域的域名,多个域名用map指令实现",
+      "description": "配置允许跨域的域名,多个域名用map指令实现",
-      "option": [],
+      "option": [],
-      "required": false
+      "required": false
-    },
+    },
-    {
+    {
-      "title": "请求方法",
+      "title": "请求方法",
-      "type": "select",
+      "type": "select",
-      "key": "methods",
+      "key": "methods",
-      "required": false,
+      "required": false,
-      "option": ["GET","POST","PUT","DELETE","OPTIONS"],
+      "option": ["GET","POST","PUT","DELETE","OPTIONS"],
-      "description": "配置允许跨域的请求方法",
+      "description": "配置允许跨域的请求方法",
-      "mode": "tags"
+      "mode": "tags"
-    },
+    },
-    {
+    {
-      "title": "请求头",
+      "title": "请求头",
-      "type": "select",
+      "type": "select",
-      "key": "headers",
+      "key": "headers",
-      "required": false,
+      "required": false,
-      "option": ["Authorization","Content-Type","Accept","Origin","Cache-Control","X-Requested-With"],
+      "option": ["Authorization","Content-Type","Accept","Origin","Cache-Control","X-Requested-With"],
-      "description": "配置允许跨域的请求头",
+      "description": "配置允许跨域的请求头",
-      "mode": "tags"
+      "mode": "tags"
-    },
+    },
-    {
+    {
-      "title": "拦截preflight",
+      "title": "拦截preflight",
-      "key": "preflight",
+      "key": "preflight",
-      "required": false,
+      "required": false,
-      "type": "switch",
+      "type": "switch",
-      "description": "拦截preflight的OPTION请求,返回跨域配置"
+      "description": "拦截preflight的OPTION请求,返回跨域配置"
-    },
+    },
-    {
+    {
-      "title": "允许发送凭据",
+      "title": "允许发送凭据",
-      "key": "credentials",
+      "key": "credentials",
-      "type": "switch",
+      "type": "switch",
-      "required": false,
+      "required": false,
-      "description": "可选字段,为true表示允许发送Cookie"
+      "description": "可选字段,为true表示允许发送Cookie"
-    },
+    },
-    {
+    {
-      "title": "跨域缓存",
+      "title": "跨域缓存",
-      "key": "maxAge",
+      "key": "maxAge",
-      "required": false,
+      "required": false,
-      "type": "int",
+      "type": "int",
-      "description": "配置 Access-Control-Max-Age,代表着在指定时间(秒)之内不用请求该地址的时候,不需要再进行预检请求,也就是跨域缓存。",
+      "description": "配置 Access-Control-Max-Age,代表着在指定时间(秒)之内不用请求该地址的时候,不需要再进行预检请求,也就是跨域缓存。",
-      "value": 86400
+      "value": 86400
-    }
+    }
-  ]
+  ]
-}
+}

+ 39 - 39
src/pages/nginx/components/cors/index.less → frontend/src/pages/nginx/components/cors/index.less

@@ -1,39 +1,39 @@
-.cors-page-overlay{
+.cors-page-overlay{
-  .ant-form-item-row{
+  .ant-form-item-row{
-    .ant-form-item-control{
+    .ant-form-item-control{
-      .ant-select{
+      .ant-select{
-        width: 100%;
+        width: 100%;
-        max-width: 100%;
+        max-width: 100%;
-        min-width: 250px;
+        min-width: 250px;
-      }
+      }
-    }
+    }
-    .auto-input-wrapper{
+    .auto-input-wrapper{
-      width: 100%;
+      width: 100%;
-    }
+    }
-  }
+  }
-  .form-btns{
+  .form-btns{
-    padding-left: 16%;
+    padding-left: 16%;
-  }
+  }
-}
+}
-
+
-.config-status{
+.config-status{
-  &.has-config{
+  &.has-config{
-    color: #1890ff;
+    color: #1890ff;
-  }
+  }
-}
+}
-
+
-.cors-config-overlay{
+.cors-config-overlay{
-  .ant-tooltip-inner{
+  .ant-tooltip-inner{
-    min-width: 600px;
+    min-width: 600px;
-    padding: 2px;
+    padding: 2px;
-    .ant-input{
+    .ant-input{
-      width: 100%;
+      width: 100%;
-      min-width: 400px;
+      min-width: 400px;
-      max-width: 100%;
+      max-width: 100%;
-      color: white;
+      color: white;
-      background: #000000;
+      background: #000000;
-      border: none;
+      border: none;
-    }
+    }
-  }
+  }
-
+
-}
+}

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

@@ -1,110 +1,110 @@
-/**
+/**
- * @author tuonian
+ * @author tuonian
- * @date 2023/7/5
+ * @date 2023/7/5
- */
+ */
-import {AutoTypeInputProps, isNull, uniqueKey} from 'planning-tools'
+import {AutoTypeInputProps, isNull, uniqueKey} from 'planning-tools'
-import './index.less'
+import './index.less'
-import config from './config.json'
+import config from './config.json'
-import {IContentProps, NgxBasicInput, registerInput} from "../basic";
+import {IContentProps, NgxBasicInput, registerInput} from "../basic";
-import {Input, Tooltip} from "antd";
+import {Input, Tooltip} from "antd";
-
+
-type DataType = {
+type DataType = {
-  /**
+  /**
-   * 多域名时的随机key
+   * 多域名时的随机key
-   */
+   */
-  key?: string
+  key?: string
-  origins?: string[]
+  origins?: string[]
-  methods?: string[]
+  methods?: string[]
-  headers?: string[]
+  headers?: string[]
-  preflight?: boolean
+  preflight?: boolean
-  credentials?: boolean
+  credentials?: boolean
-  maxAge?: number
+  maxAge?: number
-}
+}
-
+
-export const CorsInput = ({...props}: AutoTypeInputProps)=>{
+export const CorsInput = ({...props}: AutoTypeInputProps)=>{
-
+
-  const ShowContent = ({data}: IContentProps<DataType>)=>{
+  const ShowContent = ({data}: IContentProps<DataType>)=>{
-    if (data.origins?.length && data.methods?.length){
+    if (data.origins?.length && data.methods?.length){
-      const lines = renderLines(data);
+      const lines = renderLines(data);
-      const httpLines = renderHttpLines(data)
+      const httpLines = renderHttpLines(data)
-      let hint = ''
+      let hint = ''
-      if (httpLines.length){
+      if (httpLines.length){
-        hint = '# map \n'+ httpLines.join('\n') +'\n'
+        hint = '# map \n'+ httpLines.join('\n') +'\n'
-      }
+      }
-      hint +='# location\n'
+      hint +='# location\n'
-      hint += lines.join('\n')
+      hint += lines.join('\n')
-
+
-      return (<Tooltip
+      return (<Tooltip
-          destroyTooltipOnHide
+          destroyTooltipOnHide
-          overlayClassName="cors-config-overlay"
+          overlayClassName="cors-config-overlay"
-          trigger="click"
+          trigger="click"
-          placement="topLeft"
+          placement="topLeft"
-          autoAdjustOverflow
+          autoAdjustOverflow
-          title={<Input.TextArea disabled rows={Math.min(10,lines.length + httpLines.length + 3)} value={hint} />}>
+          title={<Input.TextArea disabled rows={Math.min(10,lines.length + httpLines.length + 3)} value={hint} />}>
-        <span className="config-status has-config">已配置</span>
+        <span className="config-status has-config">已配置</span>
-      </Tooltip>)
+      </Tooltip>)
-    }
+    }
-   return <span className="config-status">未完成配置</span>
+   return <span className="config-status">未完成配置</span>
-  }
+  }
-
+
-  const renderHttpLines = (values: DataType = {})=>{
+  const renderHttpLines = (values: DataType = {})=>{
-    const lines: string[] = []
+    const lines: string[] = []
-    if (!values.origins?.length){
+    if (!values.origins?.length){
-      return lines
+      return lines
-    }
+    }
-    if (values.origins.length < 2){
+    if (values.origins.length < 2){
-      return lines
+      return lines
-    }
+    }
-    lines.push(`map  $http_origin ${values.key}  {`)
+    lines.push(`map  $http_origin ${values.key}  {`)
-    lines.push(`    default 0;`)
+    lines.push(`    default 0;`)
-    values.origins.forEach(host=>{
+    values.origins.forEach(host=>{
-      lines.push(`    "~${host}"   ${host};`)
+      lines.push(`    "~${host}"   ${host};`)
-    })
+    })
-    lines.push(`}`)
+    lines.push(`}`)
-    return lines
+    return lines
-  }
+  }
-
+
-  const renderLines = (values: DataType = {})=>{
+  const renderLines = (values: DataType = {})=>{
-    const lines: string[] = []
+    const lines: string[] = []
-    if (!values.key){
+    if (!values.key){
-      values.key = `$cors_${uniqueKey(20)}`
+      values.key = `$cors_${uniqueKey(20)}`
-    }
+    }
-    if (!values.origins?.length){
+    if (!values.origins?.length){
-      return lines
+      return lines
-    }
+    }
-    if (values.origins.length === 1){
+    if (values.origins.length === 1){
-      lines.push(`add_header 'Access-Control-Allow-Origin' '${values.origins[0]}';`)
+      lines.push(`add_header 'Access-Control-Allow-Origin' '${values.origins[0]}';`)
-    }else {
+    }else {
-      lines.push(`add_header 'Access-Control-Allow-Origin' ${values.key};`)
+      lines.push(`add_header 'Access-Control-Allow-Origin' ${values.key};`)
-    }
+    }
-
+
-    if (values.methods?.length){
+    if (values.methods?.length){
-      lines.push(`add_header 'Access-Control-Allow-Methods' '${values.methods.join(',')}';`)
+      lines.push(`add_header 'Access-Control-Allow-Methods' '${values.methods.join(',')}';`)
-    }
+    }
-    if (values.headers?.length){
+    if (values.headers?.length){
-      lines.push(`add_header 'Access-Control-Allow-Headers' '${values.headers.join(',')}';`)
+      lines.push(`add_header 'Access-Control-Allow-Headers' '${values.headers.join(',')}';`)
-    }
+    }
-    if (!isNull(values.credentials)){
+    if (!isNull(values.credentials)){
-      lines.push(`add_header Access-Control-Allow-Credentials   '${values.credentials ? 'true': 'false'}';`)
+      lines.push(`add_header Access-Control-Allow-Credentials   '${values.credentials ? 'true': 'false'}';`)
-    }
+    }
-    if (!isNull(values.preflight)){
+    if (!isNull(values.preflight)){
-      lines.push(`if ($request_method = 'OPTIONS') {
+      lines.push(`if ($request_method = 'OPTIONS') {
-        return 204;
+        return 204;
- }`)
+ }`)
-    }
+    }
-    return lines;
+    return lines;
-  }
+  }
-
+
-  return <NgxBasicInput
+  return <NgxBasicInput
-    {...props}
+    {...props}
-    columns={config.form}
+    columns={config.form}
-    renderLines={renderLines}
+    renderLines={renderLines}
-    renderHttpLines={renderHttpLines}
+    renderHttpLines={renderHttpLines}
-    content={ShowContent}
+    content={ShowContent}
-    overlayClassName="cors-page-overlay"
+    overlayClassName="cors-page-overlay"
-    labelCol={4}
+    labelCol={4}
-    />
+    />
-}
+}
-
+
-registerInput('cors', CorsInput)
+registerInput('cors', CorsInput)
-
+

+ 32 - 32
src/pages/nginx/components/error/config.json → frontend/src/pages/nginx/components/error/config.json

@@ -1,32 +1,32 @@
-{
+{
-  "form": [
+  "form": [
-    {
+    {
-      "title": "错误页面",
+      "title": "错误页面",
-      "type": "array",
+      "type": "array",
-      "key": "error_pages",
+      "key": "error_pages",
-      "required": false,
+      "required": false,
-      "items": [
+      "items": [
-        {
+        {
-          "title": "错误码",
+          "title": "错误码",
-          "key": "codes",
+          "key": "codes",
-          "type": "select",
+          "type": "select",
-          "option": ["502","401","403","503"],
+          "option": ["502","401","403","503"],
-          "mode": "tags"
+          "mode": "tags"
-        },
+        },
-        {
+        {
-          "title": "响应码",
+          "title": "响应码",
-          "key": "respCode",
+          "key": "respCode",
-          "type": "int",
+          "type": "int",
-          "required": false,
+          "required": false,
-          "description": "重定向错误页面后,返回给前端的HTTP状态码,比如:404,200,301"
+          "description": "重定向错误页面后,返回给前端的HTTP状态码,比如:404,200,301"
-        },
+        },
-        {
+        {
-          "title": "跳转uri",
+          "title": "跳转uri",
-          "key": "uri",
+          "key": "uri",
-          "type": "string",
+          "type": "string",
-          "description": "当错误发生时,跳转的路由或者页面"
+          "description": "当错误发生时,跳转的路由或者页面"
-        }
+        }
-      ]
+      ]
-    }
+    }
-  ]
+  ]
-}
+}

+ 2 - 2
src/pages/nginx/components/error/index.less → frontend/src/pages/nginx/components/error/index.less

@@ -1,2 +1,2 @@
-.error-page-overlay{
+.error-page-overlay{
-}
+}

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

@@ -1,73 +1,73 @@
-/**
+/**
- * @author tuonian
+ * @author tuonian
- * @date 2023/7/5
+ * @date 2023/7/5
- */
+ */
-import {AutoTypeInputProps} from 'planning-tools'
+import {AutoTypeInputProps} from 'planning-tools'
-import './index.less'
+import './index.less'
-import config from './config.json'
+import config from './config.json'
-import {IContentProps, NgxBasicInput, registerInput} from "../basic";
+import {IContentProps, NgxBasicInput, registerInput} from "../basic";
-
+
-type ErrorPageData = {
+type ErrorPageData = {
-  error_pages?: {
+  error_pages?: {
-    /**
+    /**
-     * 处理的错误状态码
+     * 处理的错误状态码
-     */
+     */
-    codes: string[]
+    codes: string[]
-    /**
+    /**
-     * 响应状态码
+     * 响应状态码
-     */
+     */
-    respCode?: string
+    respCode?: string
-
+
-    /**
+    /**
-     * 错误路由,或者命名路由
+     * 错误路由,或者命名路由
-     */
+     */
-    uri: string
+    uri: string
-  }[]
+  }[]
-}
+}
-
+
-export const ErrorPageInput = ({...props}: AutoTypeInputProps)=>{
+export const ErrorPageInput = ({...props}: AutoTypeInputProps)=>{
-
+
-  const ShowContent = ({data}: IContentProps<ErrorPageData>)=>{
+  const ShowContent = ({data}: IContentProps<ErrorPageData>)=>{
-    const lines = renderLines(data);
+    const lines = renderLines(data);
-    return (<div className="error-pages">
+    return (<div className="error-pages">
-      {
+      {
-      lines.map((line,index)=>(<div className="error-page-item" key={index}>{line}</div>))
+      lines.map((line,index)=>(<div className="error-page-item" key={index}>{line}</div>))
-      }
+      }
-      {
+      {
-        lines.length ? null : '未配置'
+        lines.length ? null : '未配置'
-      }
+      }
-    </div>)
+    </div>)
-  }
+  }
-
+
-  const renderLines = (values: ErrorPageData = {})=>{
+  const renderLines = (values: ErrorPageData = {})=>{
-    const lines: string[] = []
+    const lines: string[] = []
-    if (!values.error_pages || !values.error_pages.length){
+    if (!values.error_pages || !values.error_pages.length){
-      return lines
+      return lines
-    }
+    }
-    values.error_pages.forEach(item=>{
+    values.error_pages.forEach(item=>{
-      if (!item.codes || item.codes.length ===0 || !item.uri){
+      if (!item.codes || item.codes.length ===0 || !item.uri){
-        lines.push(`#error_page code or uri is empty, skip`)
+        lines.push(`#error_page code or uri is empty, skip`)
-        return
+        return
-      }
+      }
-      let text = `error_page  ${item.codes.join(' ')}`
+      let text = `error_page  ${item.codes.join(' ')}`
-      if (item.respCode){
+      if (item.respCode){
-        text += `  =${item.respCode}`
+        text += `  =${item.respCode}`
-      }
+      }
-      text +=`   ${item.uri};`
+      text +=`   ${item.uri};`
-      lines.push(text)
+      lines.push(text)
-    })
+    })
-    return lines;
+    return lines;
-  }
+  }
-
+
-  return <NgxBasicInput
+  return <NgxBasicInput
-    {...props}
+    {...props}
-    columns={config.form}
+    columns={config.form}
-    renderLines={renderLines}
+    renderLines={renderLines}
-    content={ShowContent}
+    content={ShowContent}
-    overlayClassName="error-page-overlay"
+    overlayClassName="error-page-overlay"
-    labelCol={0}
+    labelCol={0}
-    />
+    />
-}
+}
-
+
-registerInput('error_page', ErrorPageInput)
+registerInput('error_page', ErrorPageInput)
-
+

+ 0 - 0
src/pages/nginx/components/fastcgi/config.json → frontend/src/pages/nginx/components/fastcgi/config.json


+ 0 - 0
src/pages/nginx/components/fastcgi/index.less → frontend/src/pages/nginx/components/fastcgi/index.less


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


+ 64 - 64
src/pages/nginx/components/gzip/config.json → frontend/src/pages/nginx/components/gzip/config.json

@@ -1,64 +1,64 @@
-{
+{
-  "form": [
+  "form": [
-    {
+    {
-      "title": "gzip_types",
+      "title": "gzip_types",
-      "key": "gzip_types",
+      "key": "gzip_types",
-      "required": false,
+      "required": false,
-      "type": "select",
+      "type": "select",
-      "mode": "tags",
+      "mode": "tags",
-      "option": ["text/html","application/javascript"],
+      "option": ["text/html","application/javascript"],
-      "description": "Syntax:\tgzip_types mime-type ...;\nDefault:\t\ngzip_types text/html;\nEnables gzipping of responses for the specified MIME types in addition to “text/html”. The special value “*” matches any MIME type (0.8.29). Responses with the “text/html” type are always compressed."
+      "description": "Syntax:\tgzip_types mime-type ...;\nDefault:\t\ngzip_types text/html;\nEnables gzipping of responses for the specified MIME types in addition to “text/html”. The special value “*” matches any MIME type (0.8.29). Responses with the “text/html” type are always compressed."
-    },
+    },
-    {
+    {
-      "title": "gzip_buffers",
+      "title": "gzip_buffers",
-      "key": "gzip_buffers",
+      "key": "gzip_buffers",
-      "required": false,
+      "required": false,
-      "type": "string",
+      "type": "string",
-      "description": "Syntax:gzip_buffers number size;\nDefault:gzip_buffers 32 4k|16 8k;\nSets the number and size of buffers used to compress a response. By default, the buffer size is equal to one memory page. This is either 4K or 8K, depending on a platform."
+      "description": "Syntax:gzip_buffers number size;\nDefault:gzip_buffers 32 4k|16 8k;\nSets the number and size of buffers used to compress a response. By default, the buffer size is equal to one memory page. This is either 4K or 8K, depending on a platform."
-    },
+    },
-    {
+    {
-      "title": "gzip_comp_level",
+      "title": "gzip_comp_level",
-      "key": "gzip_comp_level",
+      "key": "gzip_comp_level",
-      "required": false,
+      "required": false,
-      "type": "int",
+      "type": "int",
-      "description": "Syntax:gzip_comp_level level;\nDefault:\ngzip_comp_level 1;\nSets a gzip compression level of a response. Acceptable values are in the range from 1 to 9.\n"
+      "description": "Syntax:gzip_comp_level level;\nDefault:\ngzip_comp_level 1;\nSets a gzip compression level of a response. Acceptable values are in the range from 1 to 9.\n"
-    },
+    },
-    {
+    {
-      "title": "gzip_disable",
+      "title": "gzip_disable",
-      "key": "gzip_disable",
+      "key": "gzip_disable",
-      "required": false,
+      "required": false,
-      "type": "string",
+      "type": "string",
-      "description": "Syntax:\tgzip_disable regex ...;\nDisables gzipping of responses for requests with “User-Agent” header fields matching any of the specified regular expressions."
+      "description": "Syntax:\tgzip_disable regex ...;\nDisables gzipping of responses for requests with “User-Agent” header fields matching any of the specified regular expressions."
-    },
+    },
-    {
+    {
-      "title": "gzip_http_version",
+      "title": "gzip_http_version",
-      "key": "gzip_http_version",
+      "key": "gzip_http_version",
-      "required": false,
+      "required": false,
-      "type": "select",
+      "type": "select",
-      "option": ["1.0","1.1"],
+      "option": ["1.0","1.1"],
-      "description": "Syntax:\tgzip_http_version 1.0 | 1.1;\nDefault:\t\ngzip_http_version 1.1;"
+      "description": "Syntax:\tgzip_http_version 1.0 | 1.1;\nDefault:\t\ngzip_http_version 1.1;"
-    },
+    },
-    {
+    {
-      "title": "gzip_min_length",
+      "title": "gzip_min_length",
-      "key": "gzip_min_length",
+      "key": "gzip_min_length",
-      "required": false,
+      "required": false,
-      "type": "int",
+      "type": "int",
-      "description": "Syntax:\tgzip_min_length length;\nDefault:\t\ngzip_min_length 20;\nSets the minimum length of a response that will be gzipped. The length is determined only from the “Content-Length” response header field."
+      "description": "Syntax:\tgzip_min_length length;\nDefault:\t\ngzip_min_length 20;\nSets the minimum length of a response that will be gzipped. The length is determined only from the “Content-Length” response header field."
-    },
+    },
-    {
+    {
-      "title": "gzip_proxied",
+      "title": "gzip_proxied",
-      "key": "gzip_proxied",
+      "key": "gzip_proxied",
-      "required": false,
+      "required": false,
-      "type": "select",
+      "type": "select",
-      "option": ["off","expired","no-cache","no-store","private","no_last_modified","no_etag","auth","any"],
+      "option": ["off","expired","no-cache","no-store","private","no_last_modified","no_etag","auth","any"],
-      "description": "Syntax:\tgzip_proxied off | expired | no-cache | no-store | private | no_last_modified | no_etag | auth | any ...;\nDefault:\t\ngzip_proxied off;\nEnables or disables gzipping of responses for proxied requests depending on the request and response. The fact that the request is proxied is determined by the presence of the “Via” request header field. The directive accepts multiple parameters:off\ndisables compression for all proxied requests, ignoring other parameters;\nexpired\nenables compression if a response header includes the “Expires” field with a value that disables caching;\nno-cache\nenables compression if a response header includes the “Cache-Control” field with the “no-cache” parameter;\nno-store\nenables compression if a response header includes the “Cache-Control” field with the “no-store” parameter;\nprivate\nenables compression if a response header includes the “Cache-Control” field with the “private” parameter;\nno_last_modified\nenables compression if a response header does not include the “Last-Modified” field;\nno_etag\nenables compression if a response header does not include the “ETag” field;\nauth\nenables compression if a request header includes the “Authorization” field;\nany\nenables compression for all proxied requests."
+      "description": "Syntax:\tgzip_proxied off | expired | no-cache | no-store | private | no_last_modified | no_etag | auth | any ...;\nDefault:\t\ngzip_proxied off;\nEnables or disables gzipping of responses for proxied requests depending on the request and response. The fact that the request is proxied is determined by the presence of the “Via” request header field. The directive accepts multiple parameters:off\ndisables compression for all proxied requests, ignoring other parameters;\nexpired\nenables compression if a response header includes the “Expires” field with a value that disables caching;\nno-cache\nenables compression if a response header includes the “Cache-Control” field with the “no-cache” parameter;\nno-store\nenables compression if a response header includes the “Cache-Control” field with the “no-store” parameter;\nprivate\nenables compression if a response header includes the “Cache-Control” field with the “private” parameter;\nno_last_modified\nenables compression if a response header does not include the “Last-Modified” field;\nno_etag\nenables compression if a response header does not include the “ETag” field;\nauth\nenables compression if a request header includes the “Authorization” field;\nany\nenables compression for all proxied requests."
-    },
+    },
-    {
+    {
-      "title": "gzip_vary",
+      "title": "gzip_vary",
-      "key": "gzip_vary",
+      "key": "gzip_vary",
-      "required": false,
+      "required": false,
-      "type": "switch",
+      "type": "switch",
-      "description": "Syntax:\tgzip_vary on | off;\nDefault:\t\ngzip_vary off;\nEnables or disables inserting the “Vary: Accept-Encoding” response header field if the directives gzip, gzip_static, or gunzip are active."
+      "description": "Syntax:\tgzip_vary on | off;\nDefault:\t\ngzip_vary off;\nEnables or disables inserting the “Vary: Accept-Encoding” response header field if the directives gzip, gzip_static, or gunzip are active."
-    }
+    }
-  ]
+  ]
-}
+}

+ 10 - 10
src/pages/nginx/components/gzip/index.less → frontend/src/pages/nginx/components/gzip/index.less

@@ -1,10 +1,10 @@
-.gzip-input{
+.gzip-input{
-  margin-right: 10px;
+  margin-right: 10px;
-}
+}
-
+
-.gzip-popover{
+.gzip-popover{
-  .auto-form{
+  .auto-form{
-    min-width: 450px;
+    min-width: 450px;
-    width: 550px;
+    width: 550px;
-  }
+  }
-}
+}

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

@@ -1,97 +1,97 @@
-/**
+/**
- * @author tuonian
+ * @author tuonian
- * @date 2023/7/5
+ * @date 2023/7/5
- */
+ */
-import {AdvanceInputConfigs, AutoForm, AutoTypeInputProps, isNull} from 'planning-tools'
+import {AdvanceInputConfigs, AutoForm, AutoTypeInputProps, isNull} from 'planning-tools'
-import {Button, FormInstance, Popover, Switch} from "antd";
+import {Button, FormInstance, Popover, Switch} from "antd";
-import './index.less'
+import './index.less'
-import {EditOutlined} from "@ant-design/icons";
+import {EditOutlined} from "@ant-design/icons";
-import {useEffect, useState} from "react";
+import {useEffect, useState} from "react";
-import config from './config.json'
+import config from './config.json'
-import {AutoFormFooterProps} from "planning-tools/dist/esm/Components/AutoForm/form";
+import {AutoFormFooterProps} from "planning-tools/dist/esm/Components/AutoForm/form";
-import {isBoolean} from "lodash";
+import {isBoolean} from "lodash";
-import {NgxModuleData} from "../input.ts";
+import {NgxModuleData} from "../input.ts";
-
+
-export const GzipInput = ({value, onChange}:AutoTypeInputProps) => {
+export const GzipInput = ({value, onChange}:AutoTypeInputProps) => {
-
+
-  const [data,setData] = useState<any>({})
+  const [data,setData] = useState<any>({})
-  const [open,setOpen] = useState(false)
+  const [open,setOpen] = useState(false)
-
+
-  useEffect(()=>{
+  useEffect(()=>{
-    if (value?.data){
+    if (value?.data){
-      setData(value.data || {})
+      setData(value.data || {})
-    }
+    }
-  },[value])
+  },[value])
-
+
-  const onSwitch = (checked: boolean)=>{
+  const onSwitch = (checked: boolean)=>{
-    const values = { ...data,gzip: checked}
+    const values = { ...data,gzip: checked}
-    setData(values)
+    setData(values)
-    triggerChange(values)
+    triggerChange(values)
-  }
+  }
-
+
-  const triggerChange = (values: any)=>{
+  const triggerChange = (values: any)=>{
-    const lines:string[] = []
+    const lines:string[] = []
-    if (values?.gzip){
+    if (values?.gzip){
-      lines.push(`gzip      on;`)
+      lines.push(`gzip      on;`)
-      Object.keys(values).forEach(k=>{
+      Object.keys(values).forEach(k=>{
-        let v = values[k];
+        let v = values[k];
-        if (isNull(v) || k ==='gzip'){
+        if (isNull(v) || k ==='gzip'){
-          return
+          return
-        }
+        }
-        if (Array.isArray(v)){
+        if (Array.isArray(v)){
-          v = v.join(' ')
+          v = v.join(' ')
-        }else if (isBoolean(v)){
+        }else if (isBoolean(v)){
-          v = v ? 'on' : 'off'
+          v = v ? 'on' : 'off'
-        }
+        }
-        lines.push(`${k}  ${v};`)
+        lines.push(`${k}  ${v};`)
-      })
+      })
-    }
+    }
-    onChange?.({
+    onChange?.({
-      data: values,
+      data: values,
-      lines
+      lines
-    } as NgxModuleData)
+    } as NgxModuleData)
-  }
+  }
-
+
-  const onSubmitData =async (form: FormInstance | null)=>{
+  const onSubmitData =async (form: FormInstance | null)=>{
-    if (!form){
+    if (!form){
-      return
+      return
-    }
+    }
-    const values = await form.validateFields();
+    const values = await form.validateFields();
-    console.log('values', values);
+    console.log('values', values);
-    const current = { ...data, ...values }
+    const current = { ...data, ...values }
-    setData(current)
+    setData(current)
-    setOpen(false)
+    setOpen(false)
-    triggerChange(current)
+    triggerChange(current)
-  }
+  }
-
+
-  const renderFormFooter = ({formRef}:AutoFormFooterProps)=>{
+  const renderFormFooter = ({formRef}:AutoFormFooterProps)=>{
-    return (<div style={{textAlign: 'center'}}>
+    return (<div style={{textAlign: 'center'}}>
-      <Button onClick={()=>onSubmitData(formRef.current as never)} type="primary">保存</Button>
+      <Button onClick={()=>onSubmitData(formRef.current as never)} type="primary">保存</Button>
-      <Button onClick={()=>setOpen(false)}>取消</Button>
+      <Button onClick={()=>setOpen(false)}>取消</Button>
-    </div>)
+    </div>)
-  }
+  }
-
+
-  const renderForm = ()=>{
+  const renderForm = ()=>{
-    return (<AutoForm columns={config.form}
+    return (<AutoForm columns={config.form}
-                      formProps={{
+                      formProps={{
-                        labelCol: {span: 6}
+                        labelCol: {span: 6}
-                      }}
+                      }}
-                      onlyFields={true}
+                      onlyFields={true}
-                      footer={renderFormFooter}
+                      footer={renderFormFooter}
-                      data={data} />)
+                      data={data} />)
-  }
+  }
-
+
-  return (<div className="gzip-input">
+  return (<div className="gzip-input">
-    <Switch onChange={onSwitch} checked={data.gzip} />
+    <Switch onChange={onSwitch} checked={data.gzip} />
-    <Popover destroyTooltipOnHide overlayClassName="gzip-popover"
+    <Popover destroyTooltipOnHide overlayClassName="gzip-popover"
-             placement="right"
+             placement="right"
-             open={open}
+             open={open}
-             onOpenChange={o=>setOpen(o)}
+             onOpenChange={o=>setOpen(o)}
-             trigger="click" content={renderForm}>
+             trigger="click" content={renderForm}>
-      <Button onClick={()=>setOpen(true)} hidden={!data.gzip} type="link" icon={<EditOutlined />} />
+      <Button onClick={()=>setOpen(true)} hidden={!data.gzip} type="link" icon={<EditOutlined />} />
-    </Popover>
+    </Popover>
-
+
-  </div>)
+  </div>)
-}
+}
-
+
-AdvanceInputConfigs['gzip'] = GzipInput
+AdvanceInputConfigs['gzip'] = GzipInput

+ 12 - 12
src/pages/nginx/components/index.ts → frontend/src/pages/nginx/components/index.ts

@@ -1,12 +1,12 @@
-import './proxy/index.tsx'
+import './proxy/index.tsx'
-import './gzip/index.tsx'
+import './gzip/index.tsx'
-import './auth'
+import './auth'
-import './location'
+import './location'
-import './certs'
+import './certs'
-import './proxypass'
+import './proxypass'
-import './proxypass/stream.tsx'
+import './proxypass/stream.tsx'
-import './error'
+import './error'
-import './cors'
+import './cors'
-import './access'
+import './access'
-import './fastcgi'
+import './fastcgi'
-import './log'
+import './log'

+ 56 - 56
src/pages/nginx/components/input.ts → frontend/src/pages/nginx/components/input.ts

@@ -1,56 +1,56 @@
-import {isObject} from "planning-tools";
+import {isObject} from "planning-tools";
-
+
-/**
+/**
- * 自定义的模块化输入框的数据格式
+ * 自定义的模块化输入框的数据格式
- */
+ */
-export type NgxModuleData<D=any> = {
+export type NgxModuleData<D=any> = {
-  data: D
+  data: D
-  lines?: string[]
+  lines?: string[]
-  /**
+  /**
-   * 渲染到http模块,也就是跟server同级别,没想好怎么搞
+   * 渲染到http模块,也就是跟server同级别,没想好怎么搞
-   */
+   */
-  http?: string[]
+  http?: string[]
-  /**
+  /**
-   * 指定为false,则跳过渲染
+   * 指定为false,则跳过渲染
-   */
+   */
-  enable?: boolean
+  enable?: boolean
-}
+}
-
+
-/**
+/**
- * 是否是自定义的nginx的输入框
+ * 是否是自定义的nginx的输入框
- * @param value
+ * @param value
- */
+ */
-export const isNgxModuleValue = (value: any)=>{
+export const isNgxModuleValue = (value: any)=>{
-  if (!isObject(value)){
+  if (!isObject(value)){
-    return false
+    return false
-  }
+  }
-  return !!Array.isArray((value as NgxModuleData)?.lines);
+  return !!Array.isArray((value as NgxModuleData)?.lines);
-}
+}
-
+
-/**
+/**
- * 键值对
+ * 键值对
- */
+ */
-export type KeyValue = {
+export type KeyValue = {
-    name: string
+    name: string
-    value: string
+    value: string
-}
+}
-
+
-/**
+/**
- * 值是那种 ,{name: xxx, value: 123}的形式
+ * 值是那种 ,{name: xxx, value: 123}的形式
- * @param value
+ * @param value
- */
+ */
-export const isNameValue = (value: any)=>{
+export const isNameValue = (value: any)=>{
-    return value && value.name && value.value
+    return value && value.name && value.value
-}
+}
-
+
-export type ProcessorData = {
+export type ProcessorData = {
-    key: string,
+    key: string,
-    value: any,
+    value: any,
-    lines:string[],
+    lines:string[],
-    httpLines:string[]
+    httpLines:string[]
-}
+}
-
+
-/**
+/**
- * 返回true,表示已经处理完了,不继续后续的处理
+ * 返回true,表示已经处理完了,不继续后续的处理
- */
+ */
-export type IRenderProcessor = (data: ProcessorData) => boolean
+export type IRenderProcessor = (data: ProcessorData) => boolean

+ 301 - 301
src/pages/nginx/components/location/config.json → frontend/src/pages/nginx/components/location/config.json

@@ -1,301 +1,301 @@
-{
+{
-  "form": [
+  "form": [
-    {
+    {
-      "key": "name",
+      "key": "name",
-      "type": "string",
+      "type": "string",
-      "title": "名称",
+      "title": "名称",
-      "placeholder": "输入名称方便辨别",
+      "placeholder": "输入名称方便辨别",
-      "required": false
+      "required": false
-    },
+    },
-    {
+    {
-      "key": "match",
+      "key": "match",
-      "type": "object",
+      "type": "object",
-      "title": "匹配路径",
+      "title": "匹配路径",
-      "hideHeader": true,
+      "hideHeader": true,
-      "items": [
+      "items": [
-        {
+        {
-          "type": "select",
+          "type": "select",
-          "title": "匹配规则",
+          "title": "匹配规则",
-          "option": [
+          "option": [
-            {
+            {
-              "label": "精确匹配",
+              "label": "精确匹配",
-              "value": "="
+              "value": "="
-            },
+            },
-            {
+            {
-              "label": "以字符开头",
+              "label": "以字符开头",
-              "value": "^~"
+              "value": "^~"
-            },
+            },
-            {
+            {
-              "label": "正则(区分大小写)",
+              "label": "正则(区分大小写)",
-              "value": "~"
+              "value": "~"
-            },
+            },
-            {
+            {
-              "label": "正则(不区分大小写)",
+              "label": "正则(不区分大小写)",
-              "value": "~*"
+              "value": "~*"
-            },{
+            },{
-              "label": "默认",
+              "label": "默认",
-              "value": ""
+              "value": ""
-            }
+            }
-          ],
+          ],
-          "required": false,
+          "required": false,
-          "key": "regex",
+          "key": "regex",
-          "placeholder": "匹配规则,如=,~^",
+          "placeholder": "匹配规则,如=,~^",
-          "width": 180
+          "width": 180
-        },
+        },
-        {
+        {
-          "type": "string",
+          "type": "string",
-          "title": "路径",
+          "title": "路径",
-          "key": "path",
+          "key": "path",
-          "placeholder": "请输入路径",
+          "placeholder": "请输入路径",
-          "value": "/",
+          "value": "/",
-          "width": 300
+          "width": 300
-        }
+        }
-      ],
+      ],
-      "description": "优选级:精确匹配(=) > 完整路径 > 以字符开头(^~) > 正则顺序(~或者~*) > 部分起始路径(/xx/) > 默认路径(/);\n当有正则时,变量$1,$2为正则中匹配的()内的顺序内容,比如:~ \/(d+)\/([0-9]+),当访问/abc/123时,$1为abc,$2为123"
+      "description": "优选级:精确匹配(=) > 完整路径 > 以字符开头(^~) > 正则顺序(~或者~*) > 部分起始路径(/xx/) > 默认路径(/);\n当有正则时,变量$1,$2为正则中匹配的()内的顺序内容,比如:~ \/(d+)\/([0-9]+),当访问/abc/123时,$1为abc,$2为123"
-    },
+    },
-    {
+    {
-      "key": "enable",
+      "key": "enable",
-      "title": "启用",
+      "title": "启用",
-      "type": "switch",
+      "type": "switch",
-      "description": "是否启用,如果不启用,将不会渲染该配置"
+      "description": "是否启用,如果不启用,将不会渲染该配置"
-    },
+    },
-    {
+    {
-      "key": "proxy_type",
+      "key": "proxy_type",
-      "title": "代理类型",
+      "title": "代理类型",
-      "type": "select",
+      "type": "select",
-      "option": [
+      "option": [
-        {
+        {
-        "label": "反向代理",
+        "label": "反向代理",
-        "value": "proxy"
+        "value": "proxy"
-      },{
+      },{
-        "label": "静态资源",
+        "label": "静态资源",
-        "value": "static"
+        "value": "static"
-      },
+      },
-        {
+        {
-          "label": "fastcgi",
+          "label": "fastcgi",
-          "value": "fastcgi"
+          "value": "fastcgi"
-        },
+        },
-        {
+        {
-        "label": "其它",
+        "label": "其它",
-        "value": "other"
+        "value": "other"
-      }],
+      }],
-      "cascade": {
+      "cascade": {
-        "proxy": [
+        "proxy": [
-          {
+          {
-            "key": "proxy_pass",
+            "key": "proxy_pass",
-            "title": "代理地址",
+            "title": "代理地址",
-            "type": "proxy_pass"
+            "type": "proxy_pass"
-          },
+          },
-          {
+          {
-            "key": "proxy_settings",
+            "key": "proxy_settings",
-            "title": "更多代理设置",
+            "title": "更多代理设置",
-            "type": "proxy_settings",
+            "type": "proxy_settings",
-            "required": false,
+            "required": false,
-            "description": "更多代理设置"
+            "description": "更多代理设置"
-          }
+          }
-        ],
+        ],
-        "static": [
+        "static": [
-          {
+          {
-            "key": "index",
+            "key": "index",
-            "title": "首页",
+            "title": "首页",
-            "required": false,
+            "required": false,
-            "type": "select",
+            "type": "select",
-            "mode": "tags",
+            "mode": "tags",
-            "option": ["index.html","index.php","index.htm"],
+            "option": ["index.html","index.php","index.htm"],
-            "description": "nginx静态资源默认的首页文件名,比如index.html index.php"
+            "description": "nginx静态资源默认的首页文件名,比如index.html index.php"
-          },
+          },
-          {
+          {
-            "key": "root",
+            "key": "root",
-            "type": "string",
+            "type": "string",
-            "title": "根路径",
+            "title": "根路径",
-            "required": false,
+            "required": false,
-            "description": "静态资源的根路径,查找方式为直接拼接,比如:匹配路径为 /test/ ,root为/data/root,则查找资源的完整路径为:/data/root/test"
+            "description": "静态资源的根路径,查找方式为直接拼接,比如:匹配路径为 /test/ ,root为/data/root,则查找资源的完整路径为:/data/root/test"
-          },
+          },
-          {
+          {
-            "key": "alias",
+            "key": "alias",
-            "type": "string",
+            "type": "string",
-            "title": "路径别名",
+            "title": "路径别名",
-            "required": false,
+            "required": false,
-            "description": "alias和root二选一,注意与root的区别,比如:匹配路径为 /test/ ,alias为/data/root,则查找资源的完整路径为:/data/root/"
+            "description": "alias和root二选一,注意与root的区别,比如:匹配路径为 /test/ ,alias为/data/root,则查找资源的完整路径为:/data/root/"
-          },
+          },
-          {
+          {
-            "key": "try_files",
+            "key": "try_files",
-            "title": "try_files",
+            "title": "try_files",
-            "type": "select",
+            "type": "select",
-            "mode": "tags",
+            "mode": "tags",
-            "option": ["$uri","$uri/","/index.html"],
+            "option": ["$uri","$uri/","/index.html"],
-            "description": "",
+            "description": "",
-            "required": false,
+            "required": false,
-            "width": 450
+            "width": 450
-          }
+          }
-        ],
+        ],
-        "fastcgi": [
+        "fastcgi": [
-          {
+          {
-            "key": "fastcgi",
+            "key": "fastcgi",
-            "title": "fastcgi",
+            "title": "fastcgi",
-            "type": "fastcgi",
+            "type": "fastcgi",
-            "required": false,
+            "required": false,
-            "description": "ngx_http_fastcgi_module,allows passing requests to a FastCGI server."
+            "description": "ngx_http_fastcgi_module,allows passing requests to a FastCGI server."
-          }
+          }
-        ],
+        ],
-        "other": [
+        "other": [
-          {
+          {
-            "key": "return",
+            "key": "return",
-            "title": "return",
+            "title": "return",
-            "description": "直接返回固定内容",
+            "description": "直接返回固定内容",
-            "type": "object",
+            "type": "object",
-            "hideHeader": true,
+            "hideHeader": true,
-            "required": false,
+            "required": false,
-            "items": [
+            "items": [
-              {
+              {
-                "key": "code",
+                "key": "code",
-                "type": "int",
+                "type": "int",
-                "min": 200,
+                "min": 200,
-                "max": 600,
+                "max": 600,
-                "placeholder": "http状态码",
+                "placeholder": "http状态码",
-                "width": 120,
+                "width": 120,
-                "title": "状态码"
+                "title": "状态码"
-              },
+              },
-              {
+              {
-                "key": "content",
+                "key": "content",
-                "type": "textarea",
+                "type": "textarea",
-                "title": "内容",
+                "title": "内容",
-                "placeholder": "响应内容",
+                "placeholder": "响应内容",
-                "width": 360,
+                "width": 360,
-                "rows": 3
+                "rows": 3
-              }
+              }
-            ]
+            ]
-          }
+          }
-        ]
+        ]
-      }
+      }
-    },
+    },
-    {
+    {
-      "key": "cors_setting",
+      "key": "cors_setting",
-      "title": "跨域配置",
+      "title": "跨域配置",
-      "type": "cors",
+      "type": "cors",
-      "description": "跨域配置,可以通过该配置项解决前端跨域问题",
+      "description": "跨域配置,可以通过该配置项解决前端跨域问题",
-      "required": false
+      "required": false
-    },
+    },
-    {
+    {
-      "title": "Access",
+      "title": "Access",
-      "key": "access",
+      "key": "access",
-      "type": "access",
+      "type": "access",
-      "required": false,
+      "required": false,
-      "description": "deny or allow,白名单或者黑名单访问限制"
+      "description": "deny or allow,白名单或者黑名单访问限制"
-    },
+    },
-    {
+    {
-      "type": "auth",
+      "type": "auth",
-      "title": "鉴权",
+      "title": "鉴权",
-      "key": "auth_request",
+      "key": "auth_request",
-      "required": false,
+      "required": false,
-      "minimizeDesc": true,
+      "minimizeDesc": true,
-      "description": "ngx_http_auth_request_module:实现了基于一子请求的结果的客户端的授权。如果子请求返回2xx响应码,则允许访问。如果它返回401或403,则访问被拒绝并显示相应的错误代码。子请求返回的任何其他响应代码都被认为是错误的"
+      "description": "ngx_http_auth_request_module:实现了基于一子请求的结果的客户端的授权。如果子请求返回2xx响应码,则允许访问。如果它返回401或403,则访问被拒绝并显示相应的错误代码。子请求返回的任何其他响应代码都被认为是错误的"
-    },
+    },
-    {
+    {
-      "type": "gzip",
+      "type": "gzip",
-      "title": "压缩配置",
+      "title": "压缩配置",
-      "key": "gzip",
+      "key": "gzip",
-      "required": false,
+      "required": false,
-      "description": "gzip"
+      "description": "gzip"
-    },
+    },
-    {
+    {
-      "key": "add_header",
+      "key": "add_header",
-      "title": "添加响应头",
+      "title": "添加响应头",
-      "type": "array",
+      "type": "array",
-      "hideHeader": true,
+      "hideHeader": true,
-      "description": "添加http响应头",
+      "description": "添加http响应头",
-      "required": false,
+      "required": false,
-      "items": [
+      "items": [
-        {
+        {
-          "key": "name",
+          "key": "name",
-          "title": "header名称",
+          "title": "header名称",
-          "type": "string",
+          "type": "string",
-          "width": 180,
+          "width": 180,
-          "placeholder": "header名称"
+          "placeholder": "header名称"
-        },
+        },
-        {
+        {
-          "key": "value",
+          "key": "value",
-          "title": "header值",
+          "title": "header值",
-          "type": "string",
+          "type": "string",
-          "placeholder": "header值",
+          "placeholder": "header值",
-          "width": 300
+          "width": 300
-        }
+        }
-      ]
+      ]
-    },
+    },
-    {
+    {
-      "key": "rewrite",
+      "key": "rewrite",
-      "type": "object",
+      "type": "object",
-      "title": "rewrite",
+      "title": "rewrite",
-      "required": false,
+      "required": false,
-      "description": "rewrite:对访问路径进行,放在server{}, if{},location{}段中,rewrite < regex > < replacement > [flag], 必须填写正则表达式和跳转路径才能生效",
+      "description": "rewrite:对访问路径进行,放在server{}, if{},location{}段中,rewrite < regex > < replacement > [flag], 必须填写正则表达式和跳转路径才能生效",
-      "items": [
+      "items": [
-        {
+        {
-          "key": "regex",
+          "key": "regex",
-          "title": "正则表达式",
+          "title": "正则表达式",
-          "type": "string",
+          "type": "string",
-          "width": 180,
+          "width": 180,
-          "description": "跳转匹配的正则表达式",
+          "description": "跳转匹配的正则表达式",
-          "required": false,
+          "required": false,
-          "placeholder": "eq. ^/(.*)"
+          "placeholder": "eq. ^/(.*)"
-        },
+        },
-        {
+        {
-          "key": "replacement",
+          "key": "replacement",
-          "title": "跳转路径",
+          "title": "跳转路径",
-          "type": "string",
+          "type": "string",
-          "placeholder": "eq. https://www.demo.com/$1",
+          "placeholder": "eq. https://www.demo.com/$1",
-          "width": 300,
+          "width": 300,
-          "required": false
+          "required": false
-        },
+        },
-        {
+        {
-          "width": 120,
+          "width": 120,
-          "key": "flag",
+          "key": "flag",
-          "title": "flag",
+          "title": "flag",
-          "type": "select",
+          "type": "select",
-          "option": ["last","break","redirect","permanent"],
+          "option": ["last","break","redirect","permanent"],
-          "value": "permanent",
+          "value": "permanent",
-          "required": false,
+          "required": false,
-          "description": "last: 相当于Apache的【L】标记,表示完成rewrite;\nbreak:本条规则匹配完成即终止,不在匹配后面的任何规则;\nredirect: 返回302临时重定向,浏览器地址栏会显示跳转后的URL地址,爬虫不会更新url;\npermanent:返回301永久重定向,浏览器地址栏会显示跳转后的URL地址,爬虫更新url;"
+          "description": "last: 相当于Apache的【L】标记,表示完成rewrite;\nbreak:本条规则匹配完成即终止,不在匹配后面的任何规则;\nredirect: 返回302临时重定向,浏览器地址栏会显示跳转后的URL地址,爬虫不会更新url;\npermanent:返回301永久重定向,浏览器地址栏会显示跳转后的URL地址,爬虫更新url;"
-        }
+        }
-      ]
+      ]
-    },
+    },
-    {
+    {
-      "key": "tmp_custom_config",
+      "key": "tmp_custom_config",
-      "title": "自定义配置",
+      "title": "自定义配置",
-      "type": "textarea",
+      "type": "textarea",
-      "hideHeader": true,
+      "hideHeader": true,
-      "description": "自定义配置,注意,每行结尾需要加“;”号,将会拼接在最后",
+      "description": "自定义配置,注意,每行结尾需要加“;”号,将会拼接在最后",
-      "required": false,
+      "required": false,
-      "trim": false
+      "trim": false
-    },
+    },
-    {
+    {
-      "key": "internal",
+      "key": "internal",
-      "title": "内部路由",
+      "title": "内部路由",
-      "type": "switch",
+      "type": "switch",
-      "description": "内部路由:nginx内部访问,一旦出了这个配置文件,则失效"
+      "description": "内部路由:nginx内部访问,一旦出了这个配置文件,则失效"
-    },
+    },
-    {
+    {
-      "key": "error_page",
+      "key": "error_page",
-      "title": "错误页面",
+      "title": "错误页面",
-      "type": "error_page",
+      "type": "error_page",
-      "required": false,
+      "required": false,
-      "description": "错误页面配置"
+      "description": "错误页面配置"
-    },
+    },
-    {
+    {
-      "key": "default_type",
+      "key": "default_type",
-      "title": "默认内容类型",
+      "title": "默认内容类型",
-      "type": "string",
+      "type": "string",
-      "required": false,
+      "required": false,
-      "description": "default_type eg. text/plain application/json"
+      "description": "default_type eg. text/plain application/json"
-    },
+    },
-    {
+    {
-      "key": "remark",
+      "key": "remark",
-      "title": "备注信息",
+      "title": "备注信息",
-      "type": "textarea",
+      "type": "textarea",
-      "rows": 3,
+      "rows": 3,
-      "placeholder": "输入备注信息",
+      "placeholder": "输入备注信息",
-      "required": false,
+      "required": false,
-      "trim": false,
+      "trim": false,
-      "width": 600
+      "width": 600
-    }
+    }
-  ]
+  ]
-}
+}

+ 41 - 41
src/pages/nginx/components/location/index.less → frontend/src/pages/nginx/components/location/index.less

@@ -1,42 +1,42 @@
-.location-input{
+.location-input{
-
+
-  .ant-drawer-header{
+  .ant-drawer-header{
-    padding: 5px 10px;
+    padding: 5px 10px;
-  }
+  }
-  .ant-drawer-body{
+  .ant-drawer-body{
-    padding: 10px;
+    padding: 10px;
-  }
+  }
-}
+}
-
+
-.location-table{
+.location-table{
-  .ant-table-cell{
+  .ant-table-cell{
-    padding: 10px;
+    padding: 10px;
-  }
+  }
-  .location-btns{
+  .location-btns{
-    .ant-btn+.ant-btn{
+    .ant-btn+.ant-btn{
-      margin-left: 5px;
+      margin-left: 5px;
-    }
+    }
-    .ant-btn{
+    .ant-btn{
-      padding: 2.4px 0;
+      padding: 2.4px 0;
-    }
+    }
-    .ant-btn-link{
+    .ant-btn-link{
-      font-size: 13px;
+      font-size: 13px;
-    }
+    }
-  }
+  }
-
+
-}
+}
-.location-table ~ .description{
+.location-table ~ .description{
-  display: block;
+  display: block;
-  width: 100%;
+  width: 100%;
-  box-sizing: border-box;
+  box-sizing: border-box;
-  padding-right: 20px;
+  padding-right: 20px;
-}
+}
-
+
-.location-conf-preview{
+.location-conf-preview{
-  width: 520px;
+  width: 520px;
-  min-height: 100px;
+  min-height: 100px;
-  .ant-input[disabled]{
+  .ant-input[disabled]{
-    color: #333333;
+    color: #333333;
-    background: none;
+    background: none;
-  }
+  }
 }
 }

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

@@ -1,296 +1,296 @@
-import {Button, Drawer, Input, Modal, Popover, Space, Switch, Table} from "antd";
+import {Button, Drawer, Input, Modal, Popover, Space, Switch, Table} from "antd";
-import {ColumnsType} from "antd/es/table";
+import {ColumnsType} from "antd/es/table";
-import {
+import {
-    AdvanceInputConfigs,
+    AdvanceInputConfigs,
-    AutoForm,
+    AutoForm,
-    AutoFormInstance,
+    AutoFormInstance,
-    AutoTypeInputProps,
+    AutoTypeInputProps,
-    isNull,
+    isNull,
-    Message,
+    Message,
-    uniqueKey
+    uniqueKey
-} from "planning-tools";
+} from "planning-tools";
-import {useEffect, useRef, useState} from "react";
+import {useEffect, useRef, useState} from "react";
-import {CopyOutlined, DeleteOutlined, EditOutlined, PlusOutlined} from "@ant-design/icons";
+import {CopyOutlined, DeleteOutlined, EditOutlined, PlusOutlined} from "@ant-design/icons";
-import {INginxLocation} from "../../../../models/nginx.ts";
+import {INginxLocation} from "../../../../models/nginx.ts";
-import {cloneDeep} from "lodash";
+import {cloneDeep} from "lodash";
-import FormConfig from './config.json'
+import FormConfig from './config.json'
-
+
-import './index.less'
+import './index.less'
-import {SiteInput} from "../site";
+import {SiteInput} from "../site";
-import {renderLocation} from "./utils.ts";
+import {renderLocation} from "./utils.ts";
-
+
-/**
+/**
- * 部分的重要信息
+ * 部分的重要信息
- * @param data
+ * @param data
- * @param onChange
+ * @param onChange
- * @constructor
+ * @constructor
- */
+ */
-const LocationInfo = ({data, onChange}:{ data: INginxLocation, onChange?: (data: INginxLocation) => void})=>{
+const LocationInfo = ({data, onChange}:{ data: INginxLocation, onChange?: (data: INginxLocation) => void})=>{
-
+
-    const rootDir = ()=>{
+    const rootDir = ()=>{
-        if (data.alias){
+        if (data.alias){
-            return `alias: ${data.alias}`
+            return `alias: ${data.alias}`
-        }
+        }
-        return `root: ${data.root || '--'}`
+        return `root: ${data.root || '--'}`
-    }
+    }
-
+
-
+
-    return (<div>
+    return (<div>
-        {
+        {
-            data.proxy_type === 'proxy' ? <div>{`proxy: ${data.proxy_pass}`}</div> : null
+            data.proxy_type === 'proxy' ? <div>{`proxy: ${data.proxy_pass}`}</div> : null
-        }
+        }
-        {
+        {
-            data.proxy_type === 'static' ? <div>{rootDir()}<SiteInput onChange={onChange} location={data} /></div>:null
+            data.proxy_type === 'static' ? <div>{rootDir()}<SiteInput onChange={onChange} location={data} /></div>:null
-        }
+        }
-        <div>
+        <div>
-            {
+            {
-                data.rewrite?.regex && data.rewrite?.replacement ? `${data.rewrite.regex} ${data.rewrite.replacement}` : ''
+                data.rewrite?.regex && data.rewrite?.replacement ? `${data.rewrite.regex} ${data.rewrite.replacement}` : ''
-            }
+            }
-        </div>
+        </div>
-    </div>)
+    </div>)
-}
+}
-
+
-/**
+/**
- * 路由,站点,规则编辑
+ * 路由,站点,规则编辑
- * @param value
+ * @param value
- * @param onChange
+ * @param onChange
- * @param column
+ * @param column
- * @constructor
+ * @constructor
- */
+ */
-export const LocationInput = ({value, onChange }: AutoTypeInputProps) => {
+export const LocationInput = ({value, onChange }: AutoTypeInputProps) => {
-
+
-    const [locations, setLocations] = useState<INginxLocation[]>([])
+    const [locations, setLocations] = useState<INginxLocation[]>([])
-
+
-    const [editData, setEditData] = useState<INginxLocation>()
+    const [editData, setEditData] = useState<INginxLocation>()
-    const isAddRef = useRef(false)
+    const isAddRef = useRef(false)
-
+
-    const [modal,contextHolder] = Modal.useModal()
+    const [modal,contextHolder] = Modal.useModal()
-
+
-    const formRef = useRef<AutoFormInstance>()
+    const formRef = useRef<AutoFormInstance>()
-
+
-    useEffect(() => {
+    useEffect(() => {
-        if (Array.isArray(value)) {
+        if (Array.isArray(value)) {
-            setLocations(value.map((item: INginxLocation) => {
+            setLocations(value.map((item: INginxLocation) => {
-                if (!item.id) {
+                if (!item.id) {
-                    item.id = uniqueKey(20)
+                    item.id = uniqueKey(20)
-                }
+                }
-                if (!item.lines){
+                if (!item.lines){
-                    renderLocation(item)
+                    renderLocation(item)
-                }
+                }
-                return item
+                return item
-            }))
+            }))
-        }
+        }
-
+
-    }, [value])
+    }, [value])
-
+
-    const onEditRow = (data: INginxLocation) => {
+    const onEditRow = (data: INginxLocation) => {
-        isAddRef.current = false
+        isAddRef.current = false
-        setEditData(cloneDeep(data))
+        setEditData(cloneDeep(data))
-    }
+    }
-
+
-    const onAddData = (data?: INginxLocation, index?: number)=>{
+    const onAddData = (data?: INginxLocation, index?: number)=>{
-        isAddRef.current = true
+        isAddRef.current = true
-        setEditData({ ...data,id: uniqueKey(20),__index__: index} as never)
+        setEditData({ ...data,id: uniqueKey(20),__index__: index} as never)
-    }
+    }
-
+
-    const onRemoveData = (data: INginxLocation)=>{
+    const onRemoveData = (data: INginxLocation)=>{
-        const onOk = ()=>{
+        const onOk = ()=>{
-            const list = locations.filter(item=>item.id !== data.id);
+            const list = locations.filter(item=>item.id !== data.id);
-            onChange?.(list)
+            onChange?.(list)
-        }
+        }
-        modal.confirm({
+        modal.confirm({
-            title: '提示',
+            title: '提示',
-            type: 'warning',
+            type: 'warning',
-            content: '您确定要删除该代理/站点吗?删除操作不可恢复,请谨慎操作!',
+            content: '您确定要删除该代理/站点吗?删除操作不可恢复,请谨慎操作!',
-            okType: 'danger',
+            okType: 'danger',
-            okText: '仍要删除',
+            okText: '仍要删除',
-            cancelText: '先不了',
+            cancelText: '先不了',
-            onOk,
+            onOk,
-        })
+        })
-    }
+    }
-
+
-    const onQuickChangeStatus = (data: INginxLocation, enable: boolean) => {
+    const onQuickChangeStatus = (data: INginxLocation, enable: boolean) => {
-        const list = locations.map(item=>{
+        const list = locations.map(item=>{
-            if (item.id === data.id){
+            if (item.id === data.id){
-                return { ...item, enable }
+                return { ...item, enable }
-            }
+            }
-            return item
+            return item
-        })
+        })
-        onChange?.(list)
+        onChange?.(list)
-    }
+    }
-
+
-    const onSubmitData = async () => {
+    const onSubmitData = async () => {
-        const values = await formRef.current?.onSyncSubmit(true);
+        const values = await formRef.current?.onSyncSubmit(true);
-        const newData = {...editData, ...values} as INginxLocation;
+        const newData = {...editData, ...values} as INginxLocation;
-        console.log('newLocation', newData);
+        console.log('newLocation', newData);
-        if (!editData) {
+        if (!editData) {
-            console.warn('editData is null ,skip ');
+            console.warn('editData is null ,skip ');
-            return
+            return
-        }
+        }
-        renderLocation(newData)
+        renderLocation(newData)
-        let list: INginxLocation[]
+        let list: INginxLocation[]
-        if (isAddRef.current){
+        if (isAddRef.current){
-            const index = newData.__index__ || locations.length;
+            const index = newData.__index__ || locations.length;
-            delete newData.__index__;
+            delete newData.__index__;
-            let exist = locations.find(item=>item.name == newData.name);
+            let exist = locations.find(item=>item.name == newData.name);
-            if (exist){
+            if (exist){
-                Message.warning('名称不能相同,请修改后再保存!');
+                Message.warning('名称不能相同,请修改后再保存!');
-                return
+                return
-            }
+            }
-            exist = locations.find(item=> {
+            exist = locations.find(item=> {
-                if (item.match && newData.match){
+                if (item.match && newData.match){
-                    return item.match.regex === newData.match.regex && item.match.path === newData.match.path
+                    return item.match.regex === newData.match.regex && item.match.path === newData.match.path
-                }
+                }
-                return false
+                return false
-            });
+            });
-            if (exist){
+            if (exist){
-                Message.warning('匹配规则不能完全一样,请修改后重新添加!');
+                Message.warning('匹配规则不能完全一样,请修改后重新添加!');
-                return
+                return
-            }
+            }
-            renderLocation(newData);
+            renderLocation(newData);
-            if (isNull(index) || index < 0 || index >= locations.length-1){
+            if (isNull(index) || index < 0 || index >= locations.length-1){
-                list = locations.concat([newData])
+                list = locations.concat([newData])
-            }else {
+            }else {
-                list = []
+                list = []
-                locations.forEach((item,idx)=>{
+                locations.forEach((item,idx)=>{
-                    if (idx === index){
+                    if (idx === index){
-                        list.push(item)
+                        list.push(item)
-                        list.push(newData)
+                        list.push(newData)
-                    }else {
+                    }else {
-                        list.push(item)
+                        list.push(item)
-                    }
+                    }
-                })
+                })
-            }
+            }
-        }else {
+        }else {
-            renderLocation(newData);
+            renderLocation(newData);
-            list = locations.map(item => {
+            list = locations.map(item => {
-                if (item.id === newData.id) {
+                if (item.id === newData.id) {
-                    return {...item, ...newData}
+                    return {...item, ...newData}
-                }
+                }
-                return item
+                return item
-            })
+            })
-        }
+        }
-        onChange?.(list)
+        onChange?.(list)
-        setEditData(undefined)
+        setEditData(undefined)
-    }
+    }
-
+
-  /**
+  /**
-   * 部署数据变化,不重新渲染
+   * 部署数据变化,不重新渲染
-   * @param data
+   * @param data
-   */
+   */
-  const onDeployDataChange = (data: INginxLocation) => {
+  const onDeployDataChange = (data: INginxLocation) => {
-    const newList = locations.map(item=>{
+    const newList = locations.map(item=>{
-      if (item.id === data.id){
+      if (item.id === data.id){
-        return { ...item, ...data}
+        return { ...item, ...data}
-      }
+      }
-      return item;
+      return item;
-    });
+    });
-    onChange?.(newList)
+    onChange?.(newList)
-  }
+  }
-
+
-    const renderPreview = (data: INginxLocation)=>{
+    const renderPreview = (data: INginxLocation)=>{
-        let content ='';
+        let content ='';
-        let rows = 0;
+        let rows = 0;
-        if (data.http?.length){
+        if (data.http?.length){
-           content = data.http.join('\n') + '\n';
+           content = data.http.join('\n') + '\n';
-           rows = data.http.length;
+           rows = data.http.length;
-        }
+        }
-        if (data.lines){
+        if (data.lines){
-            content = content+ data.lines.join('\n')
+            content = content+ data.lines.join('\n')
-            rows +=data.lines.length
+            rows +=data.lines.length
-        }
+        }
-        return (<div className="location-conf-preview">
+        return (<div className="location-conf-preview">
-            <Input.TextArea rows={Math.max(Math.min(10,rows),5)} disabled value={content} />
+            <Input.TextArea rows={Math.max(Math.min(10,rows),5)} disabled value={content} />
-        </div>)
+        </div>)
-    }
+    }
-
+
-    const renderOps = (_: never, data: INginxLocation, index: number) => {
+    const renderOps = (_: never, data: INginxLocation, index: number) => {
-        return (
+        return (
-            <div className="location-btns">
+            <div className="location-btns">
-                <Button onClick={() => onRemoveData(data)} type="text" danger icon={<DeleteOutlined/>}/>
+                <Button onClick={() => onRemoveData(data)} type="text" danger icon={<DeleteOutlined/>}/>
-                <Button onClick={() => onEditRow(data)} type="link" icon={<EditOutlined/>}/>
+                <Button onClick={() => onEditRow(data)} type="link" icon={<EditOutlined/>}/>
-                <Button onClick={()=>onAddData(data, index)} type="link" icon={<CopyOutlined/>}/>
+                <Button onClick={()=>onAddData(data, index)} type="link" icon={<CopyOutlined/>}/>
-                <Popover trigger="click" destroyTooltipOnHide
+                <Popover trigger="click" destroyTooltipOnHide
-                         placement="top"
+                         placement="top"
-                         content={()=>renderPreview(data as never)} >
+                         content={()=>renderPreview(data as never)} >
-                    <Button type="link">预览</Button>
+                    <Button type="link">预览</Button>
-                </Popover>
+                </Popover>
-            </div>
+            </div>
-        )
+        )
-    }
+    }
-
+
-    const columns: ColumnsType = [
+    const columns: ColumnsType = [
-        {
+        {
-            dataIndex: 'name',
+            dataIndex: 'name',
-            title: '路由名称',
+            title: '路由名称',
-            width: 120
+            width: 120
-        },
+        },
-        {
+        {
-            dataIndex: 'match',
+            dataIndex: 'match',
-            title: "规则",
+            title: "规则",
-            render: (value) => <span>{`${value.regex || ''} ${value.path}`}</span>
+            render: (value) => <span>{`${value.regex || ''} ${value.path}`}</span>
-        },
+        },
-        {
+        {
-            dataIndex: 'enable',
+            dataIndex: 'enable',
-            title: '状态',
+            title: '状态',
-            render: (value,record) => <Switch onChange={c=>onQuickChangeStatus(record as never,c)} checked={value}/>
+            render: (value,record) => <Switch onChange={c=>onQuickChangeStatus(record as never,c)} checked={value}/>
-        },
+        },
-        {
+        {
-            dataIndex: 'proxy_pass',
+            dataIndex: 'proxy_pass',
-            title: '代理或路径',
+            title: '代理或路径',
-            render: (_,record: any)=>{
+            render: (_,record: any)=>{
-                return (<LocationInfo onChange={onDeployDataChange} data={record} />)
+                return (<LocationInfo onChange={onDeployDataChange} data={record} />)
-            }
+            }
-        },
+        },
-        {
+        {
-          dataIndex: 'remark',
+          dataIndex: 'remark',
-          title:"备注",
+          title:"备注",
-        },
+        },
-        {
+        {
-            title: '操作',
+            title: '操作',
-            render: renderOps as never,
+            render: renderOps as never,
-            width: 180,
+            width: 180,
-            fixed: 'right'
+            fixed: 'right'
-        }
+        }
-    ]
+    ]
-
+
-    return (
+    return (
-        <>
+        <>
-
+
-            {
+            {
-                locations.length ? (<Table pagination={false}
+                locations.length ? (<Table pagination={false}
-                                           style={{marginRight: 5}}
+                                           style={{marginRight: 5}}
-                                           rowKey="id"
+                                           rowKey="id"
-                                           columns={columns as never}
+                                           columns={columns as never}
-                                           className="location-table"
+                                           className="location-table"
-                                           dataSource={locations}>
+                                           dataSource={locations}>
-                    <div>Empty</div>
+                    <div>Empty</div>
-                </Table>) : (
+                </Table>) : (
-                    <>
+                    <>
-                        <Button onClick={()=>onAddData()} className="add-btn" type="link" icon={<PlusOutlined/>}/>
+                        <Button onClick={()=>onAddData()} className="add-btn" type="link" icon={<PlusOutlined/>}/>
-                    </>
+                    </>
-                )
+                )
-            }
+            }
-            <Drawer title={isAddRef.current? '新增' : '编辑'}
+            <Drawer title={isAddRef.current? '新增' : '编辑'}
-                    placement="right"
+                    placement="right"
-                    open={!!editData}
+                    open={!!editData}
-                    onClose={() => setEditData(undefined)}
+                    onClose={() => setEditData(undefined)}
-                    destroyOnClose
+                    destroyOnClose
-                    width={900}
+                    width={900}
-                    className="location-input"
+                    className="location-input"
-                    extra={<Space>
+                    extra={<Space>
-                        <Button onClick={onSubmitData} ghost type="primary">保存</Button>
+                        <Button onClick={onSubmitData} ghost type="primary">保存</Button>
-                    </Space>}
+                    </Space>}
-            >
+            >
-                <AutoForm
+                <AutoForm
-                    columns={FormConfig.form}
+                    columns={FormConfig.form}
-                    ref={formRef as never}
+                    ref={formRef as never}
-                    data={editData}/>
+                    data={editData}/>
-            </Drawer>
+            </Drawer>
-            {contextHolder}
+            {contextHolder}
-        </>
+        </>
-    )
+    )
-}
+}
-
+
-
+
-AdvanceInputConfigs['locations'] = LocationInput
+AdvanceInputConfigs['locations'] = LocationInput

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

@@ -1,108 +1,108 @@
-import {INginxLocation} from "../../../../models/nginx.ts";
+import {INginxLocation} from "../../../../models/nginx.ts";
-import {cloneDeep} from "lodash";
+import {cloneDeep} from "lodash";
-import {isNgxModuleValue, NgxModuleData} from "../input.ts";
+import {isNgxModuleValue, NgxModuleData} from "../input.ts";
-import {isBasicData} from "planning-tools";
+import {isBasicData} from "planning-tools";
-
+
-/**
+/**
- * 临时数据,不渲染
+ * 临时数据,不渲染
- */
+ */
-const blacklist: { [key:string]:boolean } = {
+const blacklist: { [key:string]:boolean } = {
-    'rewrite': true,
+    'rewrite': true,
-    'add_header': true,
+    'add_header': true,
-    'id': true,
+    'id': true,
-    'name': true,
+    'name': true,
-    'proxy_type': true,
+    'proxy_type': true,
-    'enable': true,
+    'enable': true,
-    "remark": true,
+    "remark": true,
-    "nginxId": true,
+    "nginxId": true,
-    "proxy_set_header": true,
+    "proxy_set_header": true,
-    "__index__": true,
+    "__index__": true,
-    "internal": true,
+    "internal": true,
-    "lines": true,
+    "lines": true,
-    http:true,
+    http:true,
-    data: true
+    data: true
-}
+}
-
+
-/**
+/**
- * 这里暂时要考虑下
+ * 这里暂时要考虑下
- * @param origin
+ * @param origin
- * @param httpLines
+ * @param httpLines
- */
+ */
-export const renderLocation = (origin: INginxLocation) => {
+export const renderLocation = (origin: INginxLocation) => {
-    const loc = cloneDeep(origin)
+    const loc = cloneDeep(origin)
-    const lines: string[] = [];
+    const lines: string[] = [];
-    const httpLines: string[] = [];
+    const httpLines: string[] = [];
-    origin.lines = lines;
+    origin.lines = lines;
-    origin.http = httpLines;
+    origin.http = httpLines;
-
+
-    lines.push('')
+    lines.push('')
-    lines.push(`####   ${loc.name || loc.id} start...`)
+    lines.push(`####   ${loc.name || loc.id} start...`)
-    lines.push(`location ${loc.match.regex || ''} ${loc.match.path || '/'} {`)
+    lines.push(`location ${loc.match.regex || ''} ${loc.match.path || '/'} {`)
-
+
-    if (loc.rewrite && loc.rewrite.replacement && loc.rewrite.regex){
+    if (loc.rewrite && loc.rewrite.replacement && loc.rewrite.regex){
-        lines.push(`    rewrite ${loc.rewrite.regex} ${loc.rewrite.replacement} ${loc.rewrite.flag || 'permanent'};`)
+        lines.push(`    rewrite ${loc.rewrite.regex} ${loc.rewrite.replacement} ${loc.rewrite.flag || 'permanent'};`)
-    }
+    }
-
+
-    (loc.add_header || []).forEach(h=>{
+    (loc.add_header || []).forEach(h=>{
-        lines.push(`    add_header ${h.name} ${h.value};`)
+        lines.push(`    add_header ${h.name} ${h.value};`)
-    })
+    })
-
+
-    if (loc.proxy_type !=='proxy'){
+    if (loc.proxy_type !=='proxy'){
-        delete loc.proxy_settings
+        delete loc.proxy_settings
-        delete loc.proxy_pass
+        delete loc.proxy_pass
-        delete loc.proxy_set_header
+        delete loc.proxy_set_header
-        console.log('loc', loc)
+        console.log('loc', loc)
-    }
+    }
-
+
-    if (loc.proxy_type !== 'static'){
+    if (loc.proxy_type !== 'static'){
-        delete loc.root
+        delete loc.root
-        delete loc.alias
+        delete loc.alias
-    }
+    }
-    if (loc.proxy_type !== 'other'){
+    if (loc.proxy_type !== 'other'){
-        delete loc.return
+        delete loc.return
-    }
+    }
-
+
-    if (loc.internal){
+    if (loc.internal){
-        lines.push('    internal;');
+        lines.push('    internal;');
-    }
+    }
-    delete loc.internal;
+    delete loc.internal;
-
+
-    Object.keys(loc).forEach(k=>{
+    Object.keys(loc).forEach(k=>{
-        if (blacklist[k]){
+        if (blacklist[k]){
-            return;
+            return;
-        }
+        }
-        if (k.startsWith("tmp" || k.startsWith("temp")) || k.startsWith("__")){
+        if (k.startsWith("tmp" || k.startsWith("temp")) || k.startsWith("__")){
-            return;
+            return;
-        }
+        }
-        let value = (loc as any)[k];
+        let value = (loc as any)[k];
-        if (Array.isArray(value)){
+        if (Array.isArray(value)){
-            value = value.join(' ')
+            value = value.join(' ')
-        }else if (isNgxModuleValue(value)){
+        }else if (isNgxModuleValue(value)){
-            value.lines.forEach((line: string)=>{
+            value.lines.forEach((line: string)=>{
-                lines.push(`    ${line}`)
+                lines.push(`    ${line}`)
-            });
+            });
-            (value as NgxModuleData).http?.forEach(l=>httpLines.push(l))
+            (value as NgxModuleData).http?.forEach(l=>httpLines.push(l))
-            value = '';
+            value = '';
-        } else if (!isBasicData(value)){
+        } else if (!isBasicData(value)){
-            console.log('[render] skip',k, value)
+            console.log('[render] skip',k, value)
-            value = ''
+            value = ''
-        }
+        }
-        if (value){
+        if (value){
-            lines.push(`    ${k}      ${value};`)
+            lines.push(`    ${k}      ${value};`)
-        }
+        }
-    })
+    })
-
+
-    if (loc.tmp_custom_config){
+    if (loc.tmp_custom_config){
-        loc.tmp_custom_config.split('\n').forEach(line=>{
+        loc.tmp_custom_config.split('\n').forEach(line=>{
-            lines.push(`    ${line}`)
+            lines.push(`    ${line}`)
-        })
+        })
-    }
+    }
-    if (loc.return && loc.return.code){
+    if (loc.return && loc.return.code){
-        lines.push(`    return  ${loc.return.code}  ${loc.return.content};`)
+        lines.push(`    return  ${loc.return.code}  ${loc.return.content};`)
-    }
+    }
-
+
-    lines.push('}')
+    lines.push('}')
-    lines.push(`####   ${loc.name || loc.id} end...`)
+    lines.push(`####   ${loc.name || loc.id} end...`)
-    lines.push('')
+    lines.push('')
-    return lines
+    return lines
-}
+}

+ 0 - 0
src/pages/nginx/components/log/config.json → frontend/src/pages/nginx/components/log/config.json


+ 0 - 0
src/pages/nginx/components/log/index.less → frontend/src/pages/nginx/components/log/index.less


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


+ 199 - 199
src/pages/nginx/components/proxy/config.json → frontend/src/pages/nginx/components/proxy/config.json

@@ -1,199 +1,199 @@
-{
+{
-  "form": [
+  "form": [
-    {
+    {
-      "key": "tmp_trans_ip",
+      "key": "tmp_trans_ip",
-      "title": "透传客户端IP",
+      "title": "透传客户端IP",
-      "type": "switch",
+      "type": "switch",
-      "required": false,
+      "required": false,
-      "description": "添加:proxy_set_header X-Real_IP $remote_addr,X-Forwarded-For $proxy_add_x_forwarded_for",
+      "description": "添加:proxy_set_header X-Real_IP $remote_addr,X-Forwarded-For $proxy_add_x_forwarded_for",
-      "minimizeDesc": true
+      "minimizeDesc": true
-    },
+    },
-    {
+    {
-      "key": "tmp_trans_host",
+      "key": "tmp_trans_host",
-      "title": "改写访问域名",
+      "title": "改写访问域名",
-      "type": "switch",
+      "type": "switch",
-      "required": false,
+      "required": false,
-      "description": "添加:proxy_set_header Host $host"
+      "description": "添加:proxy_set_header Host $host"
-    },
+    },
-    {
+    {
-      "key": "tmp_support_ws",
+      "key": "tmp_support_ws",
-      "title": "支持Websocket",
+      "title": "支持Websocket",
-      "type": "switch",
+      "type": "switch",
-      "required": false,
+      "required": false,
-      "description": "添加websocket代理需要的请求头"
+      "description": "添加websocket代理需要的请求头"
-    },
+    },
-    {
+    {
-      "key": "proxy_connect_timeout",
+      "key": "proxy_connect_timeout",
-      "title": "连接超时时间",
+      "title": "连接超时时间",
-      "type": "string",
+      "type": "string",
-      "required": false,
+      "required": false,
-      "ruleType": "reg",
+      "ruleType": "reg",
-      "pattern": "^(\\d+(s|m|h))?$",
+      "pattern": "^(\\d+(s|m|h))?$",
-      "description": "eg. 60s 5m 1h"
+      "description": "eg. 60s 5m 1h"
-    },
+    },
-    {
+    {
-      "key": "proxy_read_timeout",
+      "key": "proxy_read_timeout",
-      "title": "读超时时间",
+      "title": "读超时时间",
-      "type": "string",
+      "type": "string",
-      "ruleType": "reg",
+      "ruleType": "reg",
-      "pattern": "[\\d+](s|m|h)$",
+      "pattern": "[\\d+](s|m|h)$",
-      "required": false,
+      "required": false,
-      "description": "proxy_read_timeout"
+      "description": "proxy_read_timeout"
-    },
+    },
-    {
+    {
-      "key": "proxy_http_version",
+      "key": "proxy_http_version",
-      "title": "代理http版本",
+      "title": "代理http版本",
-      "type": "select",
+      "type": "select",
-      "option": ["1.0","1.1"],
+      "option": ["1.0","1.1"],
-      "required": false
+      "required": false
-    },
+    },
-    {
+    {
-      "key": "proxy_set_header",
+      "key": "proxy_set_header",
-      "title": "代理请求头",
+      "title": "代理请求头",
-      "type": "array",
+      "type": "array",
-      "items": [
+      "items": [
-        {
+        {
-          "key": "name",
+          "key": "name",
-          "type": "string",
+          "type": "string",
-          "placeholder": "请求头名称",
+          "placeholder": "请求头名称",
-          "title": "请求头名称",
+          "title": "请求头名称",
-          "mode": "tags",
+          "mode": "tags",
-          "option": ["Host","X-Real-IP","X-Forwarded-For","Upgrade","Connection"],
+          "option": ["Host","X-Real-IP","X-Forwarded-For","Upgrade","Connection"],
-          "description": "Host,X-Real-IP,X-Forwarded-For,Upgrade,Connection",
+          "description": "Host,X-Real-IP,X-Forwarded-For,Upgrade,Connection",
-          "width": 180
+          "width": 180
-        },
+        },
-        {
+        {
-          "key": "value",
+          "key": "value",
-          "type": "string",
+          "type": "string",
-          "mode": "tags",
+          "mode": "tags",
-          "placeholder": "请求头值",
+          "placeholder": "请求头值",
-          "title": "请求头值",
+          "title": "请求头值",
-          "option": ["$host","$remote_addr","$proxy_add_x_forwarded_for","$http_upgrade","upgrade"],
+          "option": ["$host","$remote_addr","$proxy_add_x_forwarded_for","$http_upgrade","upgrade"],
-          "description": "如:$host,$remote_addr,$proxy_add_x_forwarded_for,$http_upgrade,upgrade",
+          "description": "如:$host,$remote_addr,$proxy_add_x_forwarded_for,$http_upgrade,upgrade",
-          "width": 180
+          "width": 180
-        }
+        }
-      ],
+      ],
-      "required": false
+      "required": false
-    },
+    },
-    {
+    {
-      "key": "proxy_next_upstream",
+      "key": "proxy_next_upstream",
-      "title": "proxy_next_upstream",
+      "title": "proxy_next_upstream",
-      "type": "select",
+      "type": "select",
-      "option": ["error","timeout","invalid_header","http_500","http_502","http_503","http_504","http_403","http_404","http_429","non_idempotent","off"],
+      "option": ["error","timeout","invalid_header","http_500","http_502","http_503","http_504","http_403","http_404","http_429","non_idempotent","off"],
-      "mode": "multiple",
+      "mode": "multiple",
-      "placeholder": "default: proxy_next_upstream error timeout",
+      "placeholder": "default: proxy_next_upstream error timeout",
-      "required": false,
+      "required": false,
-      "width": 425
+      "width": 425
-    },
+    },
-    {
+    {
-      "key": "proxy_next_upstream_timeout",
+      "key": "proxy_next_upstream_timeout",
-      "title": "next_upstream_timeout",
+      "title": "next_upstream_timeout",
-      "type": "string",
+      "type": "string",
-      "required": false,
+      "required": false,
-      "placeholder": "eg. 60s 5m"
+      "placeholder": "eg. 60s 5m"
-    },
+    },
-    {
+    {
-      "key": "proxy_next_upstream_tries",
+      "key": "proxy_next_upstream_tries",
-      "title": "proxy_next_upstream_tries",
+      "title": "proxy_next_upstream_tries",
-      "type": "int",
+      "type": "int",
-      "required": false,
+      "required": false,
-      "placeholder": "proxy_next_upstream_tries,default is 0"
+      "placeholder": "proxy_next_upstream_tries,default is 0"
-    },
+    },
-    {
+    {
-      "key": "proxy_custom_config",
+      "key": "proxy_custom_config",
-      "title": "自定义配置",
+      "title": "自定义配置",
-      "type": "textarea",
+      "type": "textarea",
-      "hideHeader": true,
+      "hideHeader": true,
-      "description": "参考文档: https://nginx.org/en/docs/http/ngx_http_proxy_module.html",
+      "description": "参考文档: https://nginx.org/en/docs/http/ngx_http_proxy_module.html",
-      "required": false,
+      "required": false,
-      "placeholder": "将会拼接到http的配置文件后,请注意格式",
+      "placeholder": "将会拼接到http的配置文件后,请注意格式",
-      "width": 425
+      "width": 425
-    },
+    },
-    {
+    {
-      "key": "tmp_proxy_more",
+      "key": "tmp_proxy_more",
-      "title": "更多设置",
+      "title": "更多设置",
-      "collapsible": true,
+      "collapsible": true,
-      "type": "divider",
+      "type": "divider",
-      "items": [
+      "items": [
-        {
+        {
-          "key": "proxy_send_timeout",
+          "key": "proxy_send_timeout",
-          "title": "proxy_send_timeout",
+          "title": "proxy_send_timeout",
-          "type": "string",
+          "type": "string",
-          "ruleType": "reg",
+          "ruleType": "reg",
-          "pattern": "[\\d+](s|m|h)$",
+          "pattern": "[\\d+](s|m|h)$",
-          "required": false
+          "required": false
-        },
+        },
-        {
+        {
-          "key": "proxy_redirect",
+          "key": "proxy_redirect",
-          "title": "重定向(proxy_redirect)",
+          "title": "重定向(proxy_redirect)",
-          "type": "string",
+          "type": "string",
-          "required": false,
+          "required": false,
-          "description": "请输入 default 或者off 或者 redirect replacement,eg. http://upstream:port/two/ /one/"
+          "description": "请输入 default 或者off 或者 redirect replacement,eg. http://upstream:port/two/ /one/"
-        },
+        },
-        {
+        {
-          "key": "proxy_pass_request_body",
+          "key": "proxy_pass_request_body",
-          "title": "发送请求数据",
+          "title": "发送请求数据",
-          "description": "proxy_pass_request_body: 特定场景,不需要将数据转发到服务端,默认发送;关闭会同时添加 proxy_set_header Content-Length \"\" ",
+          "description": "proxy_pass_request_body: 特定场景,不需要将数据转发到服务端,默认发送;关闭会同时添加 proxy_set_header Content-Length \"\" ",
-          "type": "switch",
+          "type": "switch",
-          "value": true,
+          "value": true,
-          "required": false
+          "required": false
-        },
+        },
-        {
+        {
-          "key": "ssl_certificate",
+          "key": "ssl_certificate",
-          "title": "SSL证书",
+          "title": "SSL证书",
-          "type": "certs",
+          "type": "certs",
-          "required": false
+          "required": false
-        },
+        },
-        {
+        {
-          "key": "proxy_ssl_ciphers",
+          "key": "proxy_ssl_ciphers",
-          "title": "proxy_ssl_ciphers",
+          "title": "proxy_ssl_ciphers",
-          "type": "string",
+          "type": "string",
-          "required": false
+          "required": false
-        },
+        },
-        {
+        {
-          "key": "proxy_ssl_protocols",
+          "key": "proxy_ssl_protocols",
-          "title": "proxy_ssl_protocols",
+          "title": "proxy_ssl_protocols",
-          "type": "select",
+          "type": "select",
-          "option": ["SSLv2","SSLv3","SSLv1","SSLv1.1","SSLv1.2","SSLv1.3"],
+          "option": ["SSLv2","SSLv3","SSLv1","SSLv1.1","SSLv1.2","SSLv1.3"],
-          "mode": "multiple",
+          "mode": "multiple",
-          "required": false
+          "required": false
-        },
+        },
-        {
+        {
-          "key": "proxy_ssl_verify",
+          "key": "proxy_ssl_verify",
-          "title": "SSL证书校验",
+          "title": "SSL证书校验",
-          "type": "switch",
+          "type": "switch",
-          "required": false,
+          "required": false,
-          "description": "proxy_ssl_verify on|off"
+          "description": "proxy_ssl_verify on|off"
-        },
+        },
-        {
+        {
-          "key": "proxy_store",
+          "key": "proxy_store",
-          "title": "proxy_store",
+          "title": "proxy_store",
-          "type": "string",
+          "type": "string",
-          "required": false,
+          "required": false,
-          "description": "Enables saving of files to a disk. The on parameter saves files with paths corresponding to the directives alias or root. The off parameter disables saving of files. In addition, the file name can be set explicitly using the string with variables:\n\nproxy_store /data/www$original_uri;"
+          "description": "Enables saving of files to a disk. The on parameter saves files with paths corresponding to the directives alias or root. The off parameter disables saving of files. In addition, the file name can be set explicitly using the string with variables:\n\nproxy_store /data/www$original_uri;"
-        },
+        },
-        {
+        {
-          "key": "proxy_store_access",
+          "key": "proxy_store_access",
-          "title": "proxy_store_access",
+          "title": "proxy_store_access",
-          "type": "string",
+          "type": "string",
-          "required": false,
+          "required": false,
-          "description": "Sets access permissions for newly created files and directories, e.g.:\n\nproxy_store_access user:rw group:rw all:r;\nIf any group or all access permissions are specified then user permissions may be omitted:\n\nproxy_store_access group:rw all:r;"
+          "description": "Sets access permissions for newly created files and directories, e.g.:\n\nproxy_store_access user:rw group:rw all:r;\nIf any group or all access permissions are specified then user permissions may be omitted:\n\nproxy_store_access group:rw all:r;"
-        },
+        },
-        {
+        {
-          "key": "proxy_temp_file_write_size",
+          "key": "proxy_temp_file_write_size",
-          "title": "proxy_temp_file_write_size",
+          "title": "proxy_temp_file_write_size",
-          "type": "string",
+          "type": "string",
-          "required": false,
+          "required": false,
-          "description": "Default:\t\nproxy_temp_file_write_size 8k|16k;"
+          "description": "Default:\t\nproxy_temp_file_write_size 8k|16k;"
-        },
+        },
-        {
+        {
-          "key": "proxy_temp_path",
+          "key": "proxy_temp_path",
-          "title": "proxy_temp_path",
+          "title": "proxy_temp_path",
-          "type": "string",
+          "type": "string",
-          "required": false,
+          "required": false,
-          "description": "Syntax: proxy_temp_path path [level1 [level2 [level3]]];eg. proxy_temp_path /spool/nginx/proxy_temp 1 2;"
+          "description": "Syntax: proxy_temp_path path [level1 [level2 [level3]]];eg. proxy_temp_path /spool/nginx/proxy_temp 1 2;"
-        }
+        }
-      ]
+      ]
-    }
+    }
-  ]
+  ]
-}
+}

+ 29 - 29
src/pages/nginx/components/proxy/index.less → frontend/src/pages/nginx/components/proxy/index.less

@@ -1,29 +1,29 @@
-
+
-
+
-.more-conf-popover{
+.more-conf-popover{
-  .ant-popover-inner-content{
+  .ant-popover-inner-content{
-    width: 100%;
+    width: 100%;
-    padding: 5px;
+    padding: 5px;
-  }
+  }
-  .more-values.ant-input{
+  .more-values.ant-input{
-    min-width: 650px;
+    min-width: 650px;
-    border: none;
+    border: none;
-    color: #333333;
+    color: #333333;
-  }
+  }
-
+
-}
+}
-
+
-.proxy-settings-input{
+.proxy-settings-input{
-  display: flex;
+  display: flex;
-  flex-direction: row;
+  flex-direction: row;
-  align-items: center;
+  align-items: center;
-  .less-values{
+  .less-values{
-    max-width: 250px;
+    max-width: 250px;
-    overflow: hidden;
+    overflow: hidden;
-    display: inline-block;
+    display: inline-block;
-    line-height: 32px;
+    line-height: 32px;
-    height: 32px;
+    height: 32px;
-    text-overflow: ellipsis;
+    text-overflow: ellipsis;
-    white-space: nowrap;
+    white-space: nowrap;
-  }
+  }
-}
+}

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

@@ -1,87 +1,87 @@
-/**
+/**
- * @author tuonian
+ * @author tuonian
- * @date 2023/7/5
+ * @date 2023/7/5
- */
+ */
-import {Button, Drawer, Input, Popover, Tooltip} from "antd";
+import {Button, Drawer, Input, Popover, Tooltip} from "antd";
-import {AdvanceInputConfigs, AutoForm, AutoFormInstance, AutoTypeInputProps, isObject} from 'planning-tools'
+import {AdvanceInputConfigs, AutoForm, AutoFormInstance, AutoTypeInputProps, isObject} from 'planning-tools'
-
+
-import './index.less'
+import './index.less'
-import {useEffect, useRef, useState} from "react";
+import {useEffect, useRef, useState} from "react";
-import {EditOutlined} from "@ant-design/icons";
+import {EditOutlined} from "@ant-design/icons";
-import {renderProxy} from "./utils.ts";
+import {renderProxy} from "./utils.ts";
-import {useAppSelector} from "../../../../store";
+import {useAppSelector} from "../../../../store";
-import FormConfig from './config.json'
+import FormConfig from './config.json'
-
+
-
+
-export const ProxySettings = ({value, onChange}: AutoTypeInputProps) => {
+export const ProxySettings = ({value, onChange}: AutoTypeInputProps) => {
-
+
-  const nginx = useAppSelector(state => state.nginx.current)
+  const nginx = useAppSelector(state => state.nginx.current)
-
+
-  const [data,setData] = useState<any>()
+  const [data,setData] = useState<any>()
-  const [lines,setLines] = useState<string[]>([])
+  const [lines,setLines] = useState<string[]>([])
-  const [open,setOpen] = useState(false)
+  const [open,setOpen] = useState(false)
-
+
-  const formRef = useRef<AutoFormInstance>()
+  const formRef = useRef<AutoFormInstance>()
-
+
-  useEffect(()=>{
+  useEffect(()=>{
-    if (isObject(value)){
+    if (isObject(value)){
-      setData(value.data)
+      setData(value.data)
-      setLines(value.lines || [])
+      setLines(value.lines || [])
-    }
+    }
-    console.log('value change', value)
+    console.log('value change', value)
-  },[value])
+  },[value])
-
+
-  const onSubmitData = async ()=>{
+  const onSubmitData = async ()=>{
-    if (!nginx?.id){
+    if (!nginx?.id){
-      return
+      return
-    }
+    }
-    const values = await formRef.current?.onSyncSubmit(true);
+    const values = await formRef.current?.onSyncSubmit(true);
-    const lines = renderProxy(values, nginx)
+    const lines = renderProxy(values, nginx)
-    const postData = {
+    const postData = {
-      lines: lines,
+      lines: lines,
-      data:  values
+      data:  values
-    }
+    }
-    onChange?.(postData)
+    onChange?.(postData)
-    setOpen(false)
+    setOpen(false)
-  }
+  }
-
+
-  const renderMoreContent = ()=>{
+  const renderMoreContent = ()=>{
-    if (!lines?.length){
+    if (!lines?.length){
-      return <span>无配置,点击编辑按钮编辑代理设置</span>
+      return <span>无配置,点击编辑按钮编辑代理设置</span>
-    }
+    }
-    return (<Input.TextArea className="more-values"
+    return (<Input.TextArea className="more-values"
-                            rows={Math.min(10,lines.length)}
+                            rows={Math.min(10,lines.length)}
-                            disabled value={lines.join('\n')} />)
+                            disabled value={lines.join('\n')} />)
-  }
+  }
-
+
-  return (<div className="proxy-settings-input">
+  return (<div className="proxy-settings-input">
-    <Popover
+    <Popover
-             overlayClassName="more-conf-popover"
+             overlayClassName="more-conf-popover"
-             destroyTooltipOnHide content={renderMoreContent}>
+             destroyTooltipOnHide content={renderMoreContent}>
-      <span className="less-values">{lines.length ? lines.join(' ; ') : '无配置'}</span>
+      <span className="less-values">{lines.length ? lines.join(' ; ') : '无配置'}</span>
-    </Popover>
+    </Popover>
-    <Button onClick={()=>setOpen(true)} type="link" icon={<EditOutlined />} />
+    <Button onClick={()=>setOpen(true)} type="link" icon={<EditOutlined />} />
-    <Drawer title="代理设置"
+    <Drawer title="代理设置"
-            open={open}
+            open={open}
-            width={700}
+            width={700}
-            onClose={()=>setOpen(false)}
+            onClose={()=>setOpen(false)}
-            rootClassName="proxy-drawer"
+            rootClassName="proxy-drawer"
-            extra={<>
+            extra={<>
-              <Tooltip placement="rightBottom" title="提交后,请点击界面保存按钮,保存到服务器">
+              <Tooltip placement="rightBottom" title="提交后,请点击界面保存按钮,保存到服务器">
-                <Button onClick={onSubmitData} type="primary">提交</Button>
+                <Button onClick={onSubmitData} type="primary">提交</Button>
-              </Tooltip>
+              </Tooltip>
-
+
-               </>}
+               </>}
-            >
+            >
-
+
-      <AutoForm ref={formRef as never}
+      <AutoForm ref={formRef as never}
-                formProps={{
+                formProps={{
-                    labelCol:{span: 7}
+                    labelCol:{span: 7}
-                }}
+                }}
-                data={data}
+                data={data}
-                columns={FormConfig.form} />
+                columns={FormConfig.form} />
-    </Drawer>
+    </Drawer>
-    </div>)
+    </div>)
-}
+}
-
+
-AdvanceInputConfigs["proxy_settings"] =ProxySettings
+AdvanceInputConfigs["proxy_settings"] =ProxySettings

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

@@ -1,70 +1,70 @@
-import {cloneDeep, isBoolean} from "lodash";
+import {cloneDeep, isBoolean} from "lodash";
-import {INginx, KeyValue} from "../../../../models/nginx.ts";
+import {INginx, KeyValue} from "../../../../models/nginx.ts";
-import {isFalse, isNull} from "planning-tools";
+import {isFalse, isNull} from "planning-tools";
-
+
-/**
+/**
- * 渲染代理配置
+ * 渲染代理配置
- * @param data
+ * @param data
- * @param nginx
+ * @param nginx
- */
+ */
-export const renderProxy = (data: any, nginx: INginx)=>{
+export const renderProxy = (data: any, nginx: INginx)=>{
-  const lines: string[] = []
+  const lines: string[] = []
-  const values = cloneDeep(data);
+  const values = cloneDeep(data);
-  if (values.proxy_custom_config){
+  if (values.proxy_custom_config){
-    lines.push(values.proxy_custom_config)
+    lines.push(values.proxy_custom_config)
-  }
+  }
-  delete values.proxy_custom_config
+  delete values.proxy_custom_config
-
+
-  if (Array.isArray(values.proxy_set_header)){
+  if (Array.isArray(values.proxy_set_header)){
-    values.proxy_set_header.forEach((item: KeyValue)=>{
+    values.proxy_set_header.forEach((item: KeyValue)=>{
-      if (isNull(item.value) || isNull(item.name)){
+      if (isNull(item.value) || isNull(item.name)){
-        return
+        return
-      }
+      }
-      lines.push(`proxy_set_header    ${item.name}  ${item.value};`)
+      lines.push(`proxy_set_header    ${item.name}  ${item.value};`)
-    })
+    })
-  }
+  }
-  delete values.proxy_set_header
+  delete values.proxy_set_header
-
+
-  if (values.ssl_certificate){
+  if (values.ssl_certificate){
-    values.proxy_ssl_certificate = `${nginx.dataDir}/certs/${values.ssl_certificate}.pem`
+    values.proxy_ssl_certificate = `${nginx.dataDir}/certs/${values.ssl_certificate}.pem`
-    values.proxy_ssl_certificate_key = `${nginx.dataDir}/certs/${values.ssl_certificate}.key`
+    values.proxy_ssl_certificate_key = `${nginx.dataDir}/certs/${values.ssl_certificate}.key`
-    delete values.ssl_certificate
+    delete values.ssl_certificate
-  }
+  }
-
+
-  if (values.tmp_trans_ip){
+  if (values.tmp_trans_ip){
-    lines.push(`proxy_set_header X-Real-IP $remote_addr;`)
+    lines.push(`proxy_set_header X-Real-IP $remote_addr;`)
-    lines.push(`proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;`)
+    lines.push(`proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;`)
-  }
+  }
-  delete values.tmp_trans_ip
+  delete values.tmp_trans_ip
-  if (values.tmp_trans_host){
+  if (values.tmp_trans_host){
-    lines.push(`proxy_set_header Host $host;`)
+    lines.push(`proxy_set_header Host $host;`)
-  }
+  }
-  delete values.tmp_trans_host
+  delete values.tmp_trans_host
-  if (values.tmp_support_ws){
+  if (values.tmp_support_ws){
-    lines.push(`proxy_set_header Upgrade $http_upgrade;`)
+    lines.push(`proxy_set_header Upgrade $http_upgrade;`)
-    lines.push(`proxy_set_header Connection "upgrade";`)
+    lines.push(`proxy_set_header Connection "upgrade";`)
-  }
+  }
-  delete values.tmp_support_ws
+  delete values.tmp_support_ws
-
+
-  delete values.tmp_proxy_more
+  delete values.tmp_proxy_more
-
+
-  if (isFalse(values.proxy_pass_request_body)){
+  if (isFalse(values.proxy_pass_request_body)){
-    lines.push(`proxy_pass_request_body   off;`)
+    lines.push(`proxy_pass_request_body   off;`)
-    lines.push(`proxy_set_header  Content-Length    "";`)
+    lines.push(`proxy_set_header  Content-Length    "";`)
-  }
+  }
-  delete values.proxy_pass_request_body
+  delete values.proxy_pass_request_body
-
+
-  Object.keys(values).forEach(k=>{
+  Object.keys(values).forEach(k=>{
-    let v = values[k];
+    let v = values[k];
-    if (isNull(v)){
+    if (isNull(v)){
-      return
+      return
-    }
+    }
-    if (isBoolean(v)){
+    if (isBoolean(v)){
-      v = v ? 'on':'off'
+      v = v ? 'on':'off'
-    }else if (Array.isArray(v)){
+    }else if (Array.isArray(v)){
-      v = v.join(' ')
+      v = v.join(' ')
-    }
+    }
-    lines.push(`${k}  ${v};`)
+    lines.push(`${k}  ${v};`)
-  })
+  })
-  return lines
+  return lines
-}
+}

+ 30 - 30
src/pages/nginx/components/proxypass/index.less → frontend/src/pages/nginx/components/proxypass/index.less

@@ -1,30 +1,30 @@
-.proxy-pass-input{
+.proxy-pass-input{
-  display: flex;
+  display: flex;
-  flex-direction: row;
+  flex-direction: row;
-  align-items: center;
+  align-items: center;
-  justify-content: flex-start;
+  justify-content: flex-start;
-  .protocol.ant-select{
+  .protocol.ant-select{
-    min-width: 80px;
+    min-width: 80px;
-    width: 80px;
+    width: 80px;
-    max-width: 80px;
+    max-width: 80px;
-  }
+  }
-  .or{
+  .or{
-    margin-left: 5px;
+    margin-left: 5px;
-    margin-right: 5px;
+    margin-right: 5px;
-  }
+  }
-  .service-host-input{
+  .service-host-input{
-    .ant-input{
+    .ant-input{
-      min-width: 210px;
+      min-width: 210px;
-    }
+    }
-  }
+  }
-  .upstream-select.ant-select{
+  .upstream-select.ant-select{
-    min-width: 200px;
+    min-width: 200px;
-    width: auto;
+    width: auto;
-  }
+  }
-}
+}
-
+
-.proxy-pass-popover{
+.proxy-pass-popover{
-  display: flex;
+  display: flex;
-  flex-direction: row;
+  flex-direction: row;
-  min-width: 400px;
+  min-width: 400px;
-}
+}

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

@@ -1,111 +1,111 @@
-/**
+/**
- * 必须要兼容,手动输入,以及选择负载均衡名字
+ * 必须要兼容,手动输入,以及选择负载均衡名字
- * @author tuonian
+ * @author tuonian
- * @date 2023/7/4
+ * @date 2023/7/4
- */
+ */
-import {AutoTypeInputProps, AdvanceInputConfigs} from "planning-tools";
+import {AutoTypeInputProps, AdvanceInputConfigs} from "planning-tools";
-import {Input, Select} from "antd";
+import {Input, Select} from "antd";
-import {useAppSelector} from "../../../../store";
+import {useAppSelector} from "../../../../store";
-import {ChangeEvent, useEffect, useMemo, useState} from "react";
+import {ChangeEvent, useEffect, useMemo, useState} from "react";
-import './index.less'
+import './index.less'
-
+
-const protocols = [{value:"http", label:"http"},{ value: 'https',label: 'https'}]
+const protocols = [{value:"http", label:"http"},{ value: 'https',label: 'https'}]
-
+
-/**
+/**
- * proxy_pass 或者fastcgi_pass .后者没有协议
+ * proxy_pass 或者fastcgi_pass .后者没有协议
- * @param value
+ * @param value
- * @param onChange
+ * @param onChange
- * @param column
+ * @param column
- * @constructor
+ * @constructor
- */
+ */
-export const ProxyPassInput = ({value, onChange, column}: AutoTypeInputProps)=>{
+export const ProxyPassInput = ({value, onChange, column}: AutoTypeInputProps)=>{
-
+
-  const upstreamServer = useAppSelector(state => state.nginx.upstream);
+  const upstreamServer = useAppSelector(state => state.nginx.upstream);
-
+
-  const [data,setData] = useState<string>()
+  const [data,setData] = useState<string>()
-  const [protocol,setProtocol] = useState<string>("http")
+  const [protocol,setProtocol] = useState<string>("http")
-
+
-    const hideProtocol = useMemo(()=>{
+    const hideProtocol = useMemo(()=>{
-        return (column as any).hideProtocol;
+        return (column as any).hideProtocol;
-    },[column])
+    },[column])
-
+
-  const options = useMemo(()=>{
+  const options = useMemo(()=>{
-    let list:any[] = []
+    let list:any[] = []
-    if (upstreamServer?.upstreams){
+    if (upstreamServer?.upstreams){
-      list =  upstreamServer.upstreams.map(item=>{
+      list =  upstreamServer.upstreams.map(item=>{
-        return {
+        return {
-          label: item.name,
+          label: item.name,
-          value: item.name
+          value: item.name
-        }
+        }
-      })
+      })
-    }
+    }
-    return list
+    return list
-  },[upstreamServer])
+  },[upstreamServer])
-
+
-  useEffect(()=>{
+  useEffect(()=>{
-    if (!value || typeof value !=='string'){
+    if (!value || typeof value !=='string'){
-      setData(undefined)
+      setData(undefined)
-      return
+      return
-    }
+    }
-    if (value.startsWith('https://')){
+    if (value.startsWith('https://')){
-      setProtocol('https')
+      setProtocol('https')
-    }else if (value.startsWith('http://')){
+    }else if (value.startsWith('http://')){
-      setProtocol('http')
+      setProtocol('http')
-    }
+    }
-    const pass = value.replace(/http(s)?:\/\//,'');
+    const pass = value.replace(/http(s)?:\/\//,'');
-    if (pass){
+    if (pass){
-      setData(pass)
+      setData(pass)
-    }
+    }
-  },[value])
+  },[value])
-
+
-  const triggerChange = (pro: string,host?: string)=>{
+  const triggerChange = (pro: string,host?: string)=>{
-    if (!pro || !host){
+    if (!pro || !host){
-      return
+      return
-    }
+    }
-    if (hideProtocol){
+    if (hideProtocol){
-        onChange?.(host)
+        onChange?.(host)
-    }else {
+    }else {
-        const pass = `${pro}://${host}`
+        const pass = `${pro}://${host}`
-        onChange?.(pass)
+        onChange?.(pass)
-    }
+    }
-  }
+  }
-
+
-  const onProtocolChange = (pro: string)=>{
+  const onProtocolChange = (pro: string)=>{
-    setProtocol(pro)
+    setProtocol(pro)
-    triggerChange(pro,data)
+    triggerChange(pro,data)
-  }
+  }
-
+
-  const onSelectUpstream = (v?:string)=>{
+  const onSelectUpstream = (v?:string)=>{
-    if (v){
+    if (v){
-      const val = v + (hideProtocol ? '': '/')
+      const val = v + (hideProtocol ? '': '/')
-      setData(val)
+      setData(val)
-      triggerChange(protocol,val)
+      triggerChange(protocol,val)
-    }
+    }
-  }
+  }
-
+
-  const userInputChange = (e: ChangeEvent<any>) =>{
+  const userInputChange = (e: ChangeEvent<any>) =>{
-    const v = e.currentTarget.value;
+    const v = e.currentTarget.value;
-    setData(v);
+    setData(v);
-    triggerChange(protocol,v)
+    triggerChange(protocol,v)
-  }
+  }
-
+
-
+
-
+
-  return (<div className="proxy-pass-input">
+  return (<div className="proxy-pass-input">
-      {
+      {
-          hideProtocol ? null: (
+          hideProtocol ? null: (
-              <Select value={protocol}
+              <Select value={protocol}
-                      onChange={onProtocolChange}
+                      onChange={onProtocolChange}
-                      className="protocol" options={protocols} />
+                      className="protocol" options={protocols} />
-          )
+          )
-      }
+      }
-    <Input className="service-host-input" onChange={userInputChange} value={data} allowClear/>
+    <Input className="service-host-input" onChange={userInputChange} value={data} allowClear/>
-    <Select onChange={onSelectUpstream}
+    <Select onChange={onSelectUpstream}
-            placeholder="选择负载均衡"
+            placeholder="选择负载均衡"
-            allowClear
+            allowClear
-            className="upstream-select"
+            className="upstream-select"
-            options={options} />
+            options={options} />
-    </div>)
+    </div>)
-}
+}
-
+
-AdvanceInputConfigs['proxy_pass'] = ProxyPassInput
+AdvanceInputConfigs['proxy_pass'] = ProxyPassInput

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

@@ -1,70 +1,70 @@
-/**
+/**
- * 必须要兼容,手动输入,以及选择负载均衡名字
+ * 必须要兼容,手动输入,以及选择负载均衡名字
- * @author tuonian
+ * @author tuonian
- * @date 2023/7/4
+ * @date 2023/7/4
- */
+ */
-import {AutoTypeInputProps, AdvanceInputConfigs} from "planning-tools";
+import {AutoTypeInputProps, AdvanceInputConfigs} from "planning-tools";
-import {Button, Input, Popover, Select} from "antd";
+import {Button, Input, Popover, Select} from "antd";
-import {useAppSelector} from "../../../../store";
+import {useAppSelector} from "../../../../store";
-import {ChangeEvent, useEffect, useMemo, useState} from "react";
+import {ChangeEvent, useEffect, useMemo, useState} from "react";
-import {EditOutlined} from "@ant-design/icons";
+import {EditOutlined} from "@ant-design/icons";
-
+
-import './index.less'
+import './index.less'
-
+
-export const StreamProxyPassInput = ({value, onChange}: AutoTypeInputProps)=>{
+export const StreamProxyPassInput = ({value, onChange}: AutoTypeInputProps)=>{
-
+
-  const upstreamServer = useAppSelector(state => state.nginx.streamUpstream);
+  const upstreamServer = useAppSelector(state => state.nginx.streamUpstream);
-  const [data,setData] = useState<string>()
+  const [data,setData] = useState<string>()
-
+
-
+
-  const options = useMemo(()=>{
+  const options = useMemo(()=>{
-    let list:any[] = []
+    let list:any[] = []
-    if (upstreamServer?.upstreams){
+    if (upstreamServer?.upstreams){
-      list =  upstreamServer.upstreams.map(item=>{
+      list =  upstreamServer.upstreams.map(item=>{
-        return {
+        return {
-          label: item.name,
+          label: item.name,
-          value: item.name
+          value: item.name
-        }
+        }
-      })
+      })
-    }
+    }
-    return list
+    return list
-  },[upstreamServer])
+  },[upstreamServer])
-
+
-  useEffect(()=>{
+  useEffect(()=>{
-   setData(value)
+   setData(value)
-  },[value])
+  },[value])
-
+
-  const onSelectUpstream = (v?:string)=>{
+  const onSelectUpstream = (v?:string)=>{
-    if (v){
+    if (v){
-      setData(v)
+      setData(v)
-      onChange?.(v)
+      onChange?.(v)
-    }
+    }
-  }
+  }
-
+
-  const onUserInput = (e:ChangeEvent<any>)=>{
+  const onUserInput = (e:ChangeEvent<any>)=>{
-    setData(e.currentTarget.value)
+    setData(e.currentTarget.value)
-    onChange?.(e.currentTarget.value)
+    onChange?.(e.currentTarget.value)
-  }
+  }
-
+
-
+
-
+
-  const renderInput = ()=>{
+  const renderInput = ()=>{
-    return (<div className="proxy-pass-popover">
+    return (<div className="proxy-pass-popover">
-      <Input placeholder="输入后端服务的IP:PORT或者复制均衡名称" value={data} onChange={onUserInput} />
+      <Input placeholder="输入后端服务的IP:PORT或者复制均衡名称" value={data} onChange={onUserInput} />
-      <Select onChange={onSelectUpstream}
+      <Select onChange={onSelectUpstream}
-              placeholder="选择负载均衡"
+              placeholder="选择负载均衡"
-              allowClear
+              allowClear
-              className="upstream-select"
+              className="upstream-select"
-              options={options} />
+              options={options} />
-    </div>)
+    </div>)
-  }
+  }
-
+
-  return (<>
+  return (<>
-    {data}
+    {data}
-    <Popover trigger="click" destroyTooltipOnHide content={renderInput}>
+    <Popover trigger="click" destroyTooltipOnHide content={renderInput}>
-      <Button type="link" icon={<EditOutlined />} />
+      <Button type="link" icon={<EditOutlined />} />
-    </Popover>
+    </Popover>
-  </>)
+  </>)
-}
+}
-
+
-AdvanceInputConfigs['stream_proxy_pass'] = StreamProxyPassInput
+AdvanceInputConfigs['stream_proxy_pass'] = StreamProxyPassInput

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

@@ -1,352 +1,352 @@
-import React, {useCallback, useEffect, useRef, useState} from "react";
+import React, {useCallback, useEffect, useRef, useState} from "react";
-
+
-import './dragger.less'
+import './dragger.less'
-import {uniqueKey} from "planning-tools";
+import {uniqueKey} from "planning-tools";
-import {Button, Tree} from "antd";
+import {Button, Tree} from "antd";
-import {
+import {
-    DeleteOutlined,
+    DeleteOutlined,
-    DownOutlined,
+    DownOutlined,
-    FileOutlined,
+    FileOutlined,
-    FolderOutlined,
+    FolderOutlined,
-    InboxOutlined,
+    InboxOutlined,
-    LoadingOutlined,
+    LoadingOutlined,
-    UploadOutlined
+    UploadOutlined
-} from "@ant-design/icons";
+} from "@ant-design/icons";
-import type {DataNode} from 'antd/es/tree';
+import type {DataNode} from 'antd/es/tree';
-import {uploadApis} from "../../../../../api/nginx.ts";
+import {uploadApis} from "../../../../../api/nginx.ts";
-import classNames from "classnames";
+import classNames from "classnames";
-
+
-/**
+/**
- * 文件上传
+ * 文件上传
- */
+ */
-type MyFile = {
+type MyFile = {
-
+
-    children?: MyFile[]
+    children?: MyFile[]
-    /**
+    /**
-     * 是否为文件夹
+     * 是否为文件夹
-     */
+     */
-    isDirectory?: boolean
+    isDirectory?: boolean
-    title: string
+    title: string
-    key: string
+    key: string
-    item: FileSystemEntry
+    item: FileSystemEntry
-    pKey?: string
+    pKey?: string
-
+
-} & DataNode
+} & DataNode
-
+
-type FileStatus = {
+type FileStatus = {
-    success?: boolean
+    success?: boolean
-    error?: boolean
+    error?: boolean
-    loading?: boolean
+    loading?: boolean
-    message?: string
+    message?: string
-    key: string
+    key: string
-}
+}
-
+
-type StatusMap = {
+type StatusMap = {
-    [key:string]: FileStatus
+    [key:string]: FileStatus
-}
+}
-
+
-type TreeNodeProps = {
+type TreeNodeProps = {
-    data: MyFile,
+    data: MyFile,
-    onDelete: (data: MyFile)=>void,
+    onDelete: (data: MyFile)=>void,
-    onUpload: (data: MyFile) =>void
+    onUpload: (data: MyFile) =>void
-    statusMap: StatusMap
+    statusMap: StatusMap
-}
+}
-
+
-
+
-const TreeNode = ({data, onDelete, onUpload, statusMap}: TreeNodeProps)=>{
+const TreeNode = ({data, onDelete, onUpload, statusMap}: TreeNodeProps)=>{
-    const [status,setStatus] = useState<FileStatus>({key: data.key})
+    const [status,setStatus] = useState<FileStatus>({key: data.key})
-
+
-    useEffect(()=>{
+    useEffect(()=>{
-        const cur = statusMap[data.key];
+        const cur = statusMap[data.key];
-        cur && (setStatus(cur))
+        cur && (setStatus(cur))
-    },[statusMap])
+    },[statusMap])
-
+
-    return (
+    return (
-        <div className={classNames('node-data',{ 'error': status.error}, { success: status.success})}>
+        <div className={classNames('node-data',{ 'error': status.error}, { success: status.success})}>
-            <span className="tree-node-title-item">{data.title}</span>
+            <span className="tree-node-title-item">{data.title}</span>
-            <div style={{flex: 1}} />
+            <div style={{flex: 1}} />
-            <LoadingOutlined hidden={!status.loading} />
+            <LoadingOutlined hidden={!status.loading} />
-            <Button onClick={()=>onUpload(data)}
+            <Button onClick={()=>onUpload(data)}
-                    size="small"
+                    size="small"
-                    type="link"
+                    type="link"
-                    hidden={status.success || data.isDirectory}
+                    hidden={status.success || data.isDirectory}
-                    icon={<UploadOutlined />}
+                    icon={<UploadOutlined />}
-                    />
+                    />
-            <Button onClick={()=>onDelete(data)}
+            <Button onClick={()=>onDelete(data)}
-                    className="delete-btn"
+                    className="delete-btn"
-                    size="small" type="text"
+                    size="small" type="text"
-                    danger icon={<DeleteOutlined />} />
+                    danger icon={<DeleteOutlined />} />
-        </div>
+        </div>
-    )
+    )
-}
+}
-type IProps  = {
+type IProps  = {
-    onComplete?: (key: string, ok?: boolean)=>void
+    onComplete?: (key: string, ok?: boolean)=>void
-}
+}
-export const Dragger:React.FC<IProps> = ({onComplete}: IProps) => {
+export const Dragger:React.FC<IProps> = ({onComplete}: IProps) => {
-
+
-    const [ fileList,setFileList] = useState<MyFile[]>([])
+    const [ fileList,setFileList] = useState<MyFile[]>([])
-    const [loading,setLoading] = useState(false)
+    const [loading,setLoading] = useState(false)
-    const [statusMap,setStatusMap] = useState<StatusMap>({})
+    const [statusMap,setStatusMap] = useState<StatusMap>({})
-    const filesRef = useRef<MyFile[]>([])
+    const filesRef = useRef<MyFile[]>([])
-
+
-    const dropRef = useRef<HTMLDivElement>()
+    const dropRef = useRef<HTMLDivElement>()
-    const inputRef = useRef<HTMLInputElement>()
+    const inputRef = useRef<HTMLInputElement>()
-    const fileMapRef = useRef<{[key:string]: MyFile}>({})
+    const fileMapRef = useRef<{[key:string]: MyFile}>({})
-    const fileListRef = useRef(fileList)
+    const fileListRef = useRef(fileList)
-
+
-    const batchId = useRef<string>(uniqueKey(15))
+    const batchId = useRef<string>(uniqueKey(15))
-
+
-
+
-    const onReset = ()=>{
+    const onReset = ()=>{
-        setFileList([])
+        setFileList([])
-        batchId.current = uniqueKey(15)
+        batchId.current = uniqueKey(15)
-        setStatusMap({})
+        setStatusMap({})
-        fileMapRef.current = {}
+        fileMapRef.current = {}
-    }
+    }
-
+
-    useEffect(()=>{
+    useEffect(()=>{
-        if (!filesRef.current?.length){
+        if (!filesRef.current?.length){
-            return
+            return
-        }
+        }
-       const okList = filesRef.current.filter(item=>statusMap[item.key]?.success)
+       const okList = filesRef.current.filter(item=>statusMap[item.key]?.success)
-        if (okList.length === filesRef.current.length){
+        if (okList.length === filesRef.current.length){
-            onComplete?.(batchId.current, true)
+            onComplete?.(batchId.current, true)
-        }else {
+        }else {
-            onComplete?.(batchId.current, false)
+            onComplete?.(batchId.current, false)
-        }
+        }
-    },[statusMap])
+    },[statusMap])
-
+
-    useEffect(()=>{
+    useEffect(()=>{
-        fileListRef.current = fileList || []
+        fileListRef.current = fileList || []
-    },[fileList])
+    },[fileList])
-
+
-
+
-    const  onUploadFile = (data: MyFile)=>{
+    const  onUploadFile = (data: MyFile)=>{
-        if (data.isDirectory){
+        if (data.isDirectory){
-            return Promise.resolve()
+            return Promise.resolve()
-        }
+        }
-        const status = {
+        const status = {
-            key: data.key,
+            key: data.key,
-            message: '',
+            message: '',
-            success: true,
+            success: true,
-            error: false,
+            error: false,
-            loading: true
+            loading: true
-        }
+        }
-        setStatusMap(map=>({...map,[data.key]: status}))
+        setStatusMap(map=>({...map,[data.key]: status}))
-        return uploadApis.uploadFile(data.item as FileSystemFileEntry , batchId.current)
+        return uploadApis.uploadFile(data.item as FileSystemFileEntry , batchId.current)
-            .then(({data: resp})=>{
+            .then(({data: resp})=>{
-                const status = {
+                const status = {
-                    key: data.key,
+                    key: data.key,
-                    message: '',
+                    message: '',
-                    success: true,
+                    success: true,
-                    error: false,
+                    error: false,
-                    loading: false
+                    loading: false
-                }
+                }
-                setStatusMap(map=>({...map,[data.key]: status}))
+                setStatusMap(map=>({...map,[data.key]: status}))
-                return resp
+                return resp
-            })
+            })
-            .catch(e=>{
+            .catch(e=>{
-                const status = {
+                const status = {
-                    key: data.key,
+                    key: data.key,
-                    message: e.message || '上传失败',
+                    message: e.message || '上传失败',
-                    success: false,
+                    success: false,
-                    error: true,
+                    error: true,
-                    loading: false
+                    loading: false
-                }
+                }
-                setStatusMap(map=>({...map,[data.key]: status}))
+                setStatusMap(map=>({...map,[data.key]: status}))
-            })
+            })
-    }
+    }
-    const onUpload = async ()=>{
+    const onUpload = async ()=>{
-        setLoading(true)
+        setLoading(true)
-        const allList = Object.values(fileMapRef.current)
+        const allList = Object.values(fileMapRef.current)
-            .filter(item=>!item.isDirectory)
+            .filter(item=>!item.isDirectory)
-            .map(item=> ({...item}))
+            .map(item=> ({...item}))
-        filesRef.current = [...allList]
+        filesRef.current = [...allList]
-        const batchUpload = ()=>{
+        const batchUpload = ()=>{
-            const tasks: Promise<any>[] = []
+            const tasks: Promise<any>[] = []
-            for (let i =0;i<4;i++){
+            for (let i =0;i<4;i++){
-                const data = allList.pop()
+                const data = allList.pop()
-                if (!data){
+                if (!data){
-                    break
+                    break
-                }
+                }
-                tasks.push(new Promise((resolve,reject) => {
+                tasks.push(new Promise((resolve,reject) => {
-                    onUploadFile(data)
+                    onUploadFile(data)
-                        .then(resp=>resolve(resp))
+                        .then(resp=>resolve(resp))
-                        .catch(e=>reject(e))
+                        .catch(e=>reject(e))
-                }))
+                }))
-            }
+            }
-             Promise.all(tasks)
+             Promise.all(tasks)
-                .then((data)=>{
+                .then((data)=>{
-                    console.log('batch result', data)
+                    console.log('batch result', data)
-                })
+                })
-                 .catch(e=>{
+                 .catch(e=>{
-                     console.log('batch result fail', e)
+                     console.log('batch result fail', e)
-                 })
+                 })
-                 .finally(()=>{
+                 .finally(()=>{
-                     if (allList.length){
+                     if (allList.length){
-                         batchUpload()
+                         batchUpload()
-                     }else {
+                     }else {
-                         setLoading(false)
+                         setLoading(false)
-                         onComplete?.(batchId.current)
+                         onComplete?.(batchId.current)
-                     }
+                     }
-                 })
+                 })
-        }
+        }
-
+
-        batchUpload()
+        batchUpload()
-    }
+    }
-
+
-    const onDelete =(file: MyFile) => {
+    const onDelete =(file: MyFile) => {
-        delete fileMapRef.current[file.key];
+        delete fileMapRef.current[file.key];
-        if (!file.pKey){
+        if (!file.pKey){
-            const list = fileList.filter(item=>item.key !== file.key);
+            const list = fileList.filter(item=>item.key !== file.key);
-            setFileList(list)
+            setFileList(list)
-            return
+            return
-        }
+        }
-        const parent = fileMapRef.current[file.pKey];
+        const parent = fileMapRef.current[file.pKey];
-        if (!parent || !parent.children){
+        if (!parent || !parent.children){
-            return;
+            return;
-        }
+        }
-        parent.children = parent.children.filter(item=>item.key !== file.key)
+        parent.children = parent.children.filter(item=>item.key !== file.key)
-        if (!parent.children?.length){
+        if (!parent.children?.length){
-            const list = fileList.filter(item=>item.key !== parent.key);
+            const list = fileList.filter(item=>item.key !== parent.key);
-            setFileList(list);
+            setFileList(list);
-        }else {
+        }else {
-            setFileList([...fileList])
+            setFileList([...fileList])
-        }
+        }
-        console.log('file',file, fileMapRef.current)
+        console.log('file',file, fileMapRef.current)
-    }
+    }
-    const appendFiles = (items: DataTransferItemList)=>{
+    const appendFiles = (items: DataTransferItemList)=>{
-        const files: MyFile[] = [...fileListRef.current]
+        const files: MyFile[] = [...fileListRef.current]
-        console.log('current files', fileListRef.current, fileMapRef.current)
+        console.log('current files', fileListRef.current, fileMapRef.current)
-        const fileMap: {[key:string]:MyFile} = fileMapRef.current
+        const fileMap: {[key:string]:MyFile} = fileMapRef.current
-        const id = batchId.current;
+        const id = batchId.current;
-        const createKey = ()=>{
+        const createKey = ()=>{
-            return id + '_' + uniqueKey(10)
+            return id + '_' + uniqueKey(10)
-        }
+        }
-
+
-        const scanFiles = (item: FileSystemEntry, parent?: MyFile)=>{
+        const scanFiles = (item: FileSystemEntry, parent?: MyFile)=>{
-            let pList = parent ? parent.children : files;
+            let pList = parent ? parent.children : files;
-            if (!pList){
+            if (!pList){
-                pList = []
+                pList = []
-            }
+            }
-            if (parent){
+            if (parent){
-                parent.children = pList
+                parent.children = pList
-                fileMap[parent.key] = parent
+                fileMap[parent.key] = parent
-            }
+            }
-            if (item.isDirectory){
+            if (item.isDirectory){
-                const myFile: MyFile = {
+                const myFile: MyFile = {
-                    title: item.name,
+                    title: item.name,
-                    key: createKey(),
+                    key: createKey(),
-                    children: [],
+                    children: [],
-                    isDirectory: true,
+                    isDirectory: true,
-                    icon: <FolderOutlined />,
+                    icon: <FolderOutlined />,
-                    item,
+                    item,
-                    isLeaf: false,
+                    isLeaf: false,
-                    pKey: parent?.key
+                    pKey: parent?.key
-                }
+                }
-                pList.push(myFile)
+                pList.push(myFile)
-                const reader = (item as FileSystemDirectoryEntry).createReader();
+                const reader = (item as FileSystemDirectoryEntry).createReader();
-
+
-                let maxTimes = 5;
+                let maxTimes = 5;
-                const readEntries = ()=> {
+                const readEntries = ()=> {
-                  maxTimes--;
+                  maxTimes--;
-                  reader.readEntries(function (entries){
+                  reader.readEntries(function (entries){
-                    entries.forEach(entry=>{
+                    entries.forEach(entry=>{
-                      scanFiles(entry, myFile)
+                      scanFiles(entry, myFile)
-                    });
+                    });
-                    if (entries.length > 0 && maxTimes > 0){
+                    if (entries.length > 0 && maxTimes > 0){
-                      readEntries();
+                      readEntries();
-                    }
+                    }
-                  })
+                  })
-                }
+                }
-                readEntries();
+                readEntries();
-
+
-
+
-            }else{
+            }else{
-                const myFile = {
+                const myFile = {
-                    title: item.name,
+                    title: item.name,
-                    key: createKey(),
+                    key: createKey(),
-                    item,
+                    item,
-                    icon: <FileOutlined />,
+                    icon: <FileOutlined />,
-                    isLeaf: true,
+                    isLeaf: true,
-                    pKey: parent?.key,
+                    pKey: parent?.key,
-                }
+                }
-                pList.push(myFile)
+                pList.push(myFile)
-                fileMap[myFile.key] = myFile
+                fileMap[myFile.key] = myFile
-            }
+            }
-        }
+        }
-
+
-        for (let i=0;i<items.length;i++){
+        for (let i=0;i<items.length;i++){
-            const item = items[i].webkitGetAsEntry();
+            const item = items[i].webkitGetAsEntry();
-            if (!item){
+            if (!item){
-                continue
+                continue
-            }
+            }
-            scanFiles(item)
+            scanFiles(item)
-        }
+        }
-        setFileList(files)
+        setFileList(files)
-        console.log('files', files)
+        console.log('files', files)
-    }
+    }
-
+
-    const onDragOver = useCallback((evt: DragEvent)=>{
+    const onDragOver = useCallback((evt: DragEvent)=>{
-        evt.preventDefault()
+        evt.preventDefault()
-    },[])
+    },[])
-
+
-    const onDropEvent = (evt: DragEvent)=>{
+    const onDropEvent = (evt: DragEvent)=>{
-        evt.preventDefault()
+        evt.preventDefault()
-        if (!evt.dataTransfer){
+        if (!evt.dataTransfer){
-            console.log('onDropEvent dataTransfer is null')
+            console.log('onDropEvent dataTransfer is null')
-            return
+            return
-        }
+        }
-        const items = evt.dataTransfer.items;
+        const items = evt.dataTransfer.items;
-        // const files = evt.dataTransfer.files;
+        // const files = evt.dataTransfer.files;
-        appendFiles(items)
+        appendFiles(items)
-        console.log('items', items)
+        console.log('items', items)
-        // console.log('files', files)
+        // console.log('files', files)
-    }
+    }
-
+
-    useEffect(()=>{
+    useEffect(()=>{
-        const container = dropRef.current;
+        const container = dropRef.current;
-        if (!container){
+        if (!container){
-            return
+            return
-        }
+        }
-        container.addEventListener("dragover",onDragOver)
+        container.addEventListener("dragover",onDragOver)
-        container.addEventListener("drop", onDropEvent)
+        container.addEventListener("drop", onDropEvent)
-
+
-        return ()=>{
+        return ()=>{
-            container.removeEventListener("dragover", onDragOver)
+            container.removeEventListener("dragover", onDragOver)
-            container.removeEventListener("drop", onDropEvent)
+            container.removeEventListener("drop", onDropEvent)
-        }
+        }
-
+
-    },[])
+    },[])
-
+
-    return (<div className="dragger-input">
+    return (<div className="dragger-input">
-        <div ref={dropRef as never} className="dragger">
+        <div ref={dropRef as never} className="dragger">
-            <p className="ant-upload-drag-icon">
+            <p className="ant-upload-drag-icon">
-                <InboxOutlined />
+                <InboxOutlined />
-            </p>
+            </p>
-            <p className="ant-upload-text">拖拽文件添加上传的文件</p>
+            <p className="ant-upload-text">拖拽文件添加上传的文件</p>
-            <p className="ant-upload-hint">
+            <p className="ant-upload-hint">
-               支持单个文件,或者多个文件,将会压缩成tar.gz文件上传到服务端;更新不会备份Nginx服务器上已有的文件,如果初次使用该功能,请注意文件备份,上传的文件在服务端和nginx的服务器均有备份
+               支持单个文件,或者多个文件,将会压缩成tar.gz文件上传到服务端;更新不会备份Nginx服务器上已有的文件,如果初次使用该功能,请注意文件备份,上传的文件在服务端和nginx的服务器均有备份
-            </p>
+            </p>
-        </div>
+        </div>
-        <input hidden ref={inputRef as never} type="file"/>
+        <input hidden ref={inputRef as never} type="file"/>
-        <div className="file-tree-title btn-list">
+        <div className="file-tree-title btn-list">
-            <span className="list-name">文件列表</span>
+            <span className="list-name">文件列表</span>
-            <Button danger type="link" size="small" disabled={loading} hidden={!fileList.length} onClick={onReset}>清空</Button>
+            <Button danger type="link" size="small" disabled={loading} hidden={!fileList.length} onClick={onReset}>清空</Button>
-            <Button type="link" size="small" loading={loading} hidden={!fileList.length} onClick={onUpload} >上传</Button>
+            <Button type="link" size="small" loading={loading} hidden={!fileList.length} onClick={onUpload} >上传</Button>
-        </div>
+        </div>
-        <Tree
+        <Tree
-            showIcon
+            showIcon
-            showLine
+            showLine
-            blockNode
+            blockNode
-            switcherIcon={<DownOutlined />}
+            switcherIcon={<DownOutlined />}
-            autoExpandParent
+            autoExpandParent
-            defaultExpandAll
+            defaultExpandAll
-            titleRender={(data)=>(<TreeNode onUpload={onUploadFile}
+            titleRender={(data)=>(<TreeNode onUpload={onUploadFile}
-                                            statusMap={statusMap}
+                                            statusMap={statusMap}
-                                            data={data}
+                                            data={data}
-                                            onDelete={onDelete} />)}
+                                            onDelete={onDelete} />)}
-            treeData={fileList} />
+            treeData={fileList} />
-
+
-    </div>)
+    </div>)
-}
+}

+ 59 - 59
src/pages/nginx/components/site/components/dragger.less → frontend/src/pages/nginx/components/site/components/dragger.less

@@ -1,59 +1,59 @@
-.dragger-input{
+.dragger-input{
-
+
-  .dragger{
+  .dragger{
-    border: dashed #efefef 1px;
+    border: dashed #efefef 1px;
-    padding: 10px 20px;
+    padding: 10px 20px;
-    background: #f6f5f5;
+    background: #f6f5f5;
-    cursor: pointer;
+    cursor: pointer;
-    text-align: center;
+    text-align: center;
-    .ant-upload-drag-icon .anticon{
+    .ant-upload-drag-icon .anticon{
-      font-size: 35px;
+      font-size: 35px;
-      color: #1e88c7;
+      color: #1e88c7;
-    }
+    }
-  }
+  }
-  .ant-tree-list{
+  .ant-tree-list{
-    margin-top: 10px;
+    margin-top: 10px;
-    display: flex;
+    display: flex;
-    flex-direction: column;
+    flex-direction: column;
-    flex-wrap: nowrap;
+    flex-wrap: nowrap;
-    .ant-tree-node-content-wrapper{
+    .ant-tree-node-content-wrapper{
-      display: flex;
+      display: flex;
-      .ant-tree-title{
+      .ant-tree-title{
-        flex: 1;
+        flex: 1;
-        .node-data{
+        .node-data{
-          width: 100%;
+          width: 100%;
-          display: flex;
+          display: flex;
-          flex-direction: row;
+          flex-direction: row;
-          align-items: center;
+          align-items: center;
-          flex-wrap: nowrap;
+          flex-wrap: nowrap;
-          .tree-node-title-item{
+          .tree-node-title-item{
-            display: block;
+            display: block;
-          }
+          }
-        }
+        }
-      }
+      }
-    }
+    }
-  }
+  }
-
+
-  .node-data.success{
+  .node-data.success{
-    color: #1e88c7;
+    color: #1e88c7;
-  }
+  }
-  .node-data.error{
+  .node-data.error{
-    color: red;
+    color: red;
-  }
+  }
-  .btn-list{
+  .btn-list{
-    margin-top: 10px;
+    margin-top: 10px;
-    .ant-btn+.ant-btn{
+    .ant-btn+.ant-btn{
-      margin-left: 10px;
+      margin-left: 10px;
-    }
+    }
-  }
+  }
-  .file-tree-title{
+  .file-tree-title{
-    .list-name{
+    .list-name{
-      font-size: 14px;
+      font-size: 14px;
-      font-weight: bold;
+      font-weight: bold;
-      margin-right: 10px;
+      margin-right: 10px;
-    }
+    }
-    border-bottom: solid 1px #efefef;
+    border-bottom: solid 1px #efefef;
-    padding-bottom: 5px;
+    padding-bottom: 5px;
-  }
+  }
-}
+}
-
+

+ 25 - 25
src/pages/nginx/components/site/index.less → frontend/src/pages/nginx/components/site/index.less

@@ -1,25 +1,25 @@
-.site-deploy{
+.site-deploy{
-
+
-  .ant-drawer-header{
+  .ant-drawer-header{
-    padding: 5px 10px;
+    padding: 5px 10px;
-  }
+  }
-  .ant-drawer-body{
+  .ant-drawer-body{
-    padding: 10px 15px;
+    padding: 10px 15px;
-  }
+  }
-  .btn-list{
+  .btn-list{
-    .ant-btn+.ant-btn{
+    .ant-btn+.ant-btn{
-      margin-left: 10px;
+      margin-left: 10px;
-    }
+    }
-  }
+  }
-  .inline-item{
+  .inline-item{
-    .ant-form-item-row{
+    .ant-form-item-row{
-      display: flex;
+      display: flex;
-      flex-direction: row;
+      flex-direction: row;
-      align-items: center;
+      align-items: center;
-      .ant-form-item-label{
+      .ant-form-item-label{
-        margin-bottom: 0;
+        margin-bottom: 0;
-        padding-bottom: 0;
+        padding-bottom: 0;
-      }
+      }
-    }
+    }
-  }
+  }
-}
+}

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

@@ -1,159 +1,159 @@
-import {Button, Drawer, Form, Input, Modal, Switch} from "antd";
+import {Button, Drawer, Form, Input, Modal, Switch} from "antd";
-import {
+import {
-    Message
+    Message
-} from "planning-tools";
+} from "planning-tools";
-import { useState} from "react";
+import { useState} from "react";
-import {CloudUploadOutlined} from "@ant-design/icons";
+import {CloudUploadOutlined} from "@ant-design/icons";
-
+
-
+
-import './index.less'
+import './index.less'
-import {INginxLocation} from "../../../../models/nginx.ts";
+import {INginxLocation} from "../../../../models/nginx.ts";
-import {Dragger} from "./components/Dragger.tsx";
+import {Dragger} from "./components/Dragger.tsx";
-import {IDeployReq, uploadApis} from "../../../../api/nginx.ts";
+import {IDeployReq, uploadApis} from "../../../../api/nginx.ts";
-import {useAppSelector} from "../../../../store";
+import {useAppSelector} from "../../../../store";
-
+
-type IProps = {
+type IProps = {
-    location: INginxLocation
+    location: INginxLocation
-  onChange?: (location: INginxLocation) => void
+  onChange?: (location: INginxLocation) => void
-}
+}
-
+
-/**
+/**
- * 路由,站点,规则编辑
+ * 路由,站点,规则编辑
- * @param value
+ * @param value
- * @param onChange
+ * @param onChange
- * @param column
+ * @param column
- * @constructor
+ * @constructor
- */
+ */
-export const SiteInput = ({ location, onChange }: IProps) => {
+export const SiteInput = ({ location, onChange }: IProps) => {
-
+
-    const [editData,setEditData] = useState<Partial<IDeployReq>>()
+    const [editData,setEditData] = useState<Partial<IDeployReq>>()
-
+
-    const [complete,setComplete] = useState(false)
+    const [complete,setComplete] = useState(false)
-    const [loading,setLoading] = useState(false)
+    const [loading,setLoading] = useState(false)
-    const [form] = Form.useForm();
+    const [form] = Form.useForm();
-    const [modal,modalContextHolder] = Modal.useModal();
+    const [modal,modalContextHolder] = Modal.useModal();
-
+
-    const nginx = useAppSelector(state => state.nginx.current);
+    const nginx = useAppSelector(state => state.nginx.current);
-
+
-
+
-    const onAddData = ()=>{
+    const onAddData = ()=>{
-        if (!location || !nginx?.id){
+        if (!location || !nginx?.id){
-            return
+            return
-        }
+        }
-        if (location.proxy_type!=='static'){
+        if (location.proxy_type!=='static'){
-            Message.warning('只支持静态站点的部署!')
+            Message.warning('只支持静态站点的部署!')
-            return;
+            return;
-        }
+        }
-        const initialData :Partial<IDeployReq> = {
+        const initialData :Partial<IDeployReq> = {
-          ...location.__deploy__ as any,
+          ...location.__deploy__ as any,
-            nginxId: nginx.id,
+            nginxId: nginx.id,
-          clear: false,
+          clear: false,
-        }
+        }
-        if (!initialData.dir){
+        if (!initialData.dir){
-          if (location.alias){
+          if (location.alias){
-            initialData.dir = location.alias
+            initialData.dir = location.alias
-          }else if (location.root){
+          }else if (location.root){
-            initialData.dir = location.root
+            initialData.dir = location.root
-          }
+          }
-        }
+        }
-        setEditData(initialData)
+        setEditData(initialData)
-    }
+    }
-
+
-    const updateLocation = (deployData: IDeployReq) => {
+    const updateLocation = (deployData: IDeployReq) => {
-      const cacheData = {
+      const cacheData = {
-        cmd: deployData.cmd,
+        cmd: deployData.cmd,
-        dir: deployData.dir,
+        dir: deployData.dir,
-      };
+      };
-      onChange?.({ ...location, __deploy__: cacheData})
+      onChange?.({ ...location, __deploy__: cacheData})
-    }
+    }
-
+
-    const onRequest = (postData: IDeployReq)=>{
+    const onRequest = (postData: IDeployReq)=>{
-      setLoading(true)
+      setLoading(true)
-
+
-      uploadApis.deploy(postData)
+      uploadApis.deploy(postData)
-          .then(()=>{
+          .then(()=>{
-            Message.success('部署成功!');
+            Message.success('部署成功!');
-            updateLocation(postData);
+            updateLocation(postData);
-          })
+          })
-          .finally(()=>{
+          .finally(()=>{
-            setLoading(false)
+            setLoading(false)
-          })
+          })
-    }
+    }
-
+
-    const onSubmitData = async ()=>{
+    const onSubmitData = async ()=>{
-      if (!nginx?.id){
+      if (!nginx?.id){
-          return
+          return
-      }
+      }
-      const values = await form.validateFields()
+      const values = await form.validateFields()
-      const postData: IDeployReq = {
+      const postData: IDeployReq = {
-        key: "",
+        key: "",
-        dir:"",
+        dir:"",
-        ...editData,
+        ...editData,
-        ...values,
+        ...values,
-        nginxId: nginx.id,
+        nginxId: nginx.id,
-      }
+      }
-        console.log('onSubmitData',postData);
+        console.log('onSubmitData',postData);
-      if (postData.clear){
+      if (postData.clear){
-        modal.confirm({
+        modal.confirm({
-          title: '警告',
+          title: '警告',
-          content: '您确定要全量部署吗?改操作将会删除部署目录下的所有问题,请谨慎选择!',
+          content: '您确定要全量部署吗?改操作将会删除部署目录下的所有问题,请谨慎选择!',
-          cancelText: '取消全量部署',
+          cancelText: '取消全量部署',
-          okText: '仍然继续',
+          okText: '仍然继续',
-          onCancel: ()=>{
+          onCancel: ()=>{
-            postData.clear = false;
+            postData.clear = false;
-            onRequest(postData)
+            onRequest(postData)
-          },
+          },
-          onOk: ()=>onRequest(postData),
+          onOk: ()=>onRequest(postData),
-        })
+        })
-        return;
+        return;
-      }
+      }
-      await onRequest(postData);
+      await onRequest(postData);
-    }
+    }
-
+
-    /**
+    /**
-     * 文件上传完成的回调
+     * 文件上传完成的回调
-     * @param batchId
+     * @param batchId
-     * @param finish 是否全部上传完成
+     * @param finish 是否全部上传完成
-     */
+     */
-    const onUploadComplete =(batchId: string, finish?: boolean)=>{
+    const onUploadComplete =(batchId: string, finish?: boolean)=>{
-        setEditData(data=>({...data,key: batchId}))
+        setEditData(data=>({...data,key: batchId}))
-        setComplete(!!finish)
+        setComplete(!!finish)
-    }
+    }
-
+
-    return (
+    return (
-        <>
+        <>
-            <Button onClick={()=>onAddData()} className="add-btn" type="link" icon={<CloudUploadOutlined/>}/>
+            <Button onClick={()=>onAddData()} className="add-btn" type="link" icon={<CloudUploadOutlined/>}/>
-            <Drawer title={"静态资源部署"}
+            <Drawer title={"静态资源部署"}
-                    placement="right"
+                    placement="right"
-                    open={!!editData}
+                    open={!!editData}
-                    onClose={() => setEditData(undefined)}
+                    onClose={() => setEditData(undefined)}
-                    destroyOnClose
+                    destroyOnClose
-                    width={650}
+                    width={650}
-                    className="site-deploy"
+                    className="site-deploy"
-            >
+            >
-                <Form form={form} layout="vertical" initialValues={editData}>
+                <Form form={form} layout="vertical" initialValues={editData}>
-                    <Form.Item name="dir" label="部署目录" rules={[{required: true,message: '请完善部署目录',validateTrigger: 'blur'}]}>
+                    <Form.Item name="dir" label="部署目录" rules={[{required: true,message: '请完善部署目录',validateTrigger: 'blur'}]}>
-                        <Input />
+                        <Input />
-                    </Form.Item>
+                    </Form.Item>
-                    <Form.Item className="inline-item"
+                    <Form.Item className="inline-item"
-                               labelCol={{span: 4}}
+                               labelCol={{span: 4}}
-                               wrapperCol={{span: 18}}
+                               wrapperCol={{span: 18}}
-                               name="clear" label="全量部署" tooltip={{title: "全量部署会删除已有的文件,请注意"}}>
+                               name="clear" label="全量部署" tooltip={{title: "全量部署会删除已有的文件,请注意"}}>
-                        <Switch />
+                        <Switch />
-                    </Form.Item>
+                    </Form.Item>
-                    <Form.Item label="部署命令" name="cmd" tooltip="文件更新后执行该命令,谨慎操作">
+                    <Form.Item label="部署命令" name="cmd" tooltip="文件更新后执行该命令,谨慎操作">
-                        <Input.TextArea rows={1}/>
+                        <Input.TextArea rows={1}/>
-                    </Form.Item>
+                    </Form.Item>
-                    <Form.Item name="files" label="资源更新">
+                    <Form.Item name="files" label="资源更新">
-                        <Dragger onComplete={onUploadComplete}/>
+                        <Dragger onComplete={onUploadComplete}/>
-                    </Form.Item>
+                    </Form.Item>
-                    <div className="btn-list">
+                    <div className="btn-list">
-                        <Button disabled={loading} onClick={()=>setEditData(undefined)}>取消</Button>
+                        <Button disabled={loading} onClick={()=>setEditData(undefined)}>取消</Button>
-                        <Button loading={loading} onClick={onSubmitData} danger disabled={!complete}>部署</Button>
+                        <Button loading={loading} onClick={onSubmitData} danger disabled={!complete}>部署</Button>
-                    </div>
+                    </div>
-                </Form>
+                </Form>
-            </Drawer>
+            </Drawer>
-          {modalContextHolder}
+          {modalContextHolder}
-        </>
+        </>
-    )
+    )
-}
+}

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


+ 42 - 42
src/pages/nginx/config.tsx → frontend/src/pages/nginx/config.tsx

@@ -1,42 +1,42 @@
-/**
+/**
- * @author tuonian
+ * @author tuonian
- * @date 2023/6/29
+ * @date 2023/6/29
- */
+ */
-import {useAppSelector} from "../../store";
+import {useAppSelector} from "../../store";
-import {useMemo} from "react";
+import {useMemo} from "react";
-import formConfig from '../../config/nginx_form.json'
+import formConfig from '../../config/nginx_form.json'
-import {INginxFormConfig, INginxFormTemplate} from "../../models/nginx.ts";
+import {INginxFormConfig, INginxFormTemplate} from "../../models/nginx.ts";
-import formTemplate from '../../config/nginx_template.json'
+import formTemplate from '../../config/nginx_template.json'
-import {cloneDeep} from "lodash";
+import {cloneDeep} from "lodash";
-
+
-// eslint-disable-next-line react-refresh/only-export-components
+// eslint-disable-next-line react-refresh/only-export-components
-const EmptyConfig: INginxFormConfig = {
+const EmptyConfig: INginxFormConfig = {
-  server: [],
+  server: [],
-  location: [],
+  location: [],
-  addNginx: [],
+  addNginx: [],
-  nginxSettings: [],
+  nginxSettings: [],
-  nginxConf: [],
+  nginxConf: [],
-  upstream: [],
+  upstream: [],
-  stream: []
+  stream: []
-}
+}
-
+
-
+
-export const useFormConfig = () => {
+export const useFormConfig = () => {
-  const fixConfig = useAppSelector(state => state.nginx.formConfig);
+  const fixConfig = useAppSelector(state => state.nginx.formConfig);
-  return  useMemo(()=>{
+  return  useMemo(()=>{
-    const config = fixConfig || formConfig;
+    const config = fixConfig || formConfig;
-    if (!config){
+    if (!config){
-      return EmptyConfig
+      return EmptyConfig
-    }
+    }
-    return config as INginxFormConfig
+    return config as INginxFormConfig
-  },[formConfig, fixConfig])
+  },[formConfig, fixConfig])
-}
+}
-
+
-/**
+/**
- * 数据模板
+ * 数据模板
- */
+ */
-export const useFormTemplate = ()=>{
+export const useFormTemplate = ()=>{
-  return useMemo(()=>{
+  return useMemo(()=>{
-    return cloneDeep(formTemplate) as INginxFormTemplate
+    return cloneDeep(formTemplate) as INginxFormTemplate
-  },[])
+  },[])
-}
+}

+ 63 - 63
src/pages/nginx/help/args.mdx → frontend/src/pages/nginx/help/args.mdx

@@ -1,63 +1,63 @@
-```nginx中可用的变量```\
+```nginx中可用的变量```\
-$args                    #请求中的参数值\
+$args                    #请求中的参数值\
-$query_string            #同 $args\
+$query_string            #同 $args\
-$arg_NAME                #GET请求中NAME的值\
+$arg_NAME                #GET请求中NAME的值\
-$is_args                 #如果请求中有参数,值为"?",否则为空字符串\
+$is_args                 #如果请求中有参数,值为"?",否则为空字符串\
-$uri                     #请求中的当前URI(不带请求参数,参数位于$args),可以不同于浏览器传递的$request_uri的值,它可以通过内部重定向,或者使用index指令进行修改,$uri不包含主机名,如"/foo/bar.html"。\
+$uri                     #请求中的当前URI(不带请求参数,参数位于$args),可以不同于浏览器传递的$request_uri的值,它可以通过内部重定向,或者使用index指令进行修改,$uri不包含主机名,如"/foo/bar.html"。\
-$document_uri            #同 $uri\
+$document_uri            #同 $uri\
-$document_root           #当前请求的文档根目录或别名\
+$document_root           #当前请求的文档根目录或别名\
-$host                    #优先级:HTTP请求行的主机名>"HOST"请求头字段>符合请求的服务器名.请求中的主机头字段,如果请求中的主机头不可用,则为服务器处理请求的服务器名称\
+$host                    #优先级:HTTP请求行的主机名>"HOST"请求头字段>符合请求的服务器名.请求中的主机头字段,如果请求中的主机头不可用,则为服务器处理请求的服务器名称\
-$hostname                #主机名\
+$hostname                #主机名\
-$https                   #如果开启了SSL安全模式,值为"on",否则为空字符串。\
+$https                   #如果开启了SSL安全模式,值为"on",否则为空字符串。\
-$binary_remote_addr      #客户端地址的二进制形式,固定长度为4个字节\
+$binary_remote_addr      #客户端地址的二进制形式,固定长度为4个字节\
-$body_bytes_sent         #传输给客户端的字节数,响应头不计算在内;这个变量和Apache的mod_log_config模块中的"%B"参数保持兼容\
+$body_bytes_sent         #传输给客户端的字节数,响应头不计算在内;这个变量和Apache的mod_log_config模块中的"%B"参数保持兼容\
-$bytes_sent              #传输给客户端的字节数\
+$bytes_sent              #传输给客户端的字节数\
-$connection              #TCP连接的序列号\
+$connection              #TCP连接的序列号\
-$connection_requests     #TCP连接当前的请求数量\
+$connection_requests     #TCP连接当前的请求数量\
-$content_length          #"Content-Length" 请求头字段\
+$content_length          #"Content-Length" 请求头字段\
-$content_type            #"Content-Type" 请求头字段\
+$content_type            #"Content-Type" 请求头字段\
-$cookie_name             #cookie名称\
+$cookie_name             #cookie名称\
-$limit_rate              #用于设置响应的速度限制\
+$limit_rate              #用于设置响应的速度限制\
-$msec                    #当前的Unix时间戳\
+$msec                    #当前的Unix时间戳\
-$nginx_version           #nginx版本\
+$nginx_version           #nginx版本\
-$pid                     #工作进程的PID\
+$pid                     #工作进程的PID\
-$pipe                    #如果请求来自管道通信,值为"p",否则为"."\
+$pipe                    #如果请求来自管道通信,值为"p",否则为"."\
-$proxy_protocol_addr     #获取代理访问服务器的客户端地址,如果是直接访问,该值为空字符串\
+$proxy_protocol_addr     #获取代理访问服务器的客户端地址,如果是直接访问,该值为空字符串\
-$realpath_root           #当前请求的文档根目录或别名的真实路径,会将所有符号连接转换为真实路径\
+$realpath_root           #当前请求的文档根目录或别名的真实路径,会将所有符号连接转换为真实路径\
-$remote_addr             #客户端地址\
+$remote_addr             #客户端地址\
-$remote_port             #客户端端口\
+$remote_port             #客户端端口\
-$remote_user             #用于HTTP基础认证服务的用户名\
+$remote_user             #用于HTTP基础认证服务的用户名\
-$request                 #代表客户端的请求地址\
+$request                 #代表客户端的请求地址\
-$request_body            #客户端的请求主体:此变量可在location中使用,将请求主体通过proxy_pass,fastcgi_pass,uwsgi_pass和scgi_pass传递给下一级的代理服务器\
+$request_body            #客户端的请求主体:此变量可在location中使用,将请求主体通过proxy_pass,fastcgi_pass,uwsgi_pass和scgi_pass传递给下一级的代理服务器\
-$request_body_file       #将客户端请求主体保存在临时文件中。文件处理结束后,此文件需删除。如果需要之一开启此功能,需要设置client_body_in_file_only。如果将次文件传 递给后端的代理服务器,需要禁用request body,即设置proxy_pass_request_body off,fastcgi_pass_request_body off,uwsgi_pass_request_body off,or scgi_pass_request_body off\
+$request_body_file       #将客户端请求主体保存在临时文件中。文件处理结束后,此文件需删除。如果需要之一开启此功能,需要设置client_body_in_file_only。如果将次文件传 递给后端的代理服务器,需要禁用request body,即设置proxy_pass_request_body off,fastcgi_pass_request_body off,uwsgi_pass_request_body off,or scgi_pass_request_body off\
-$request_completion      #如果请求成功,值为"OK",如果请求未完成或者请求不是一个范围请求的最后一部分,则为空\
+$request_completion      #如果请求成功,值为"OK",如果请求未完成或者请求不是一个范围请求的最后一部分,则为空\
-$request_filename        #当前连接请求的文件路径,由root或alias指令与URI请求生成\
+$request_filename        #当前连接请求的文件路径,由root或alias指令与URI请求生成\
-$request_length          #请求的长度 (包括请求的地址,http请求头和请求主体)\
+$request_length          #请求的长度 (包括请求的地址,http请求头和请求主体)\
-$request_method          #HTTP请求方法,通常为"GET"或"POST"\
+$request_method          #HTTP请求方法,通常为"GET"或"POST"\
-$request_time            #处理客户端请求使用的时间,单位为秒,精度毫秒; 从读入客户端的第一个字节开始,直到把最后一个字符发送给客户端后进行日志写入为止。\
+$request_time            #处理客户端请求使用的时间,单位为秒,精度毫秒; 从读入客户端的第一个字节开始,直到把最后一个字符发送给客户端后进行日志写入为止。\
-$request_uri             #这个变量等于包含一些客户端请求参数的原始URI,它无法修改,请查看$uri更改或重写URI,不包含主机名,例如:"/cnphp/test.php?arg=freemouse"\
+$request_uri             #这个变量等于包含一些客户端请求参数的原始URI,它无法修改,请查看$uri更改或重写URI,不包含主机名,例如:"/cnphp/test.php?arg=freemouse"\
-$scheme                  #请求使用的Web协议,"http" 或 "https"\
+$scheme                  #请求使用的Web协议,"http" 或 "https"\
-$server_addr             #服务器端地址,需要注意的是:为了避免访问linux系统内核,应将ip地址提前设置在配置文件中\
+$server_addr             #服务器端地址,需要注意的是:为了避免访问linux系统内核,应将ip地址提前设置在配置文件中\
-$server_name             #服务器名\
+$server_name             #服务器名\
-$server_port             #服务器端口\
+$server_port             #服务器端口\
-$server_protocol         #服务器的HTTP版本,通常为 "HTTP/1.0" 或 "HTTP/1.1"\
+$server_protocol         #服务器的HTTP版本,通常为 "HTTP/1.0" 或 "HTTP/1.1"\
-$status                  #HTTP响应代码\
+$status                  #HTTP响应代码\
-$time_iso8601            #服务器时间的ISO 8610格式\
+$time_iso8601            #服务器时间的ISO 8610格式\
-$time_local              #服务器时间(LOG Format 格式)\
+$time_local              #服务器时间(LOG Format 格式)\
-$cookie_NAME             #客户端请求Header头中的cookie变量,前缀"$cookie_"加上cookie名称的变量,该变量的值即为cookie名称的值\
+$cookie_NAME             #客户端请求Header头中的cookie变量,前缀"$cookie_"加上cookie名称的变量,该变量的值即为cookie名称的值\
-$http_NAME               #匹配任意请求头字段;变量名中的后半部分NAME可以替换成任意请求头字段,如在配置文件中需要获取http请求头:"Accept-Language",$http_accept_language即可\
+$http_NAME               #匹配任意请求头字段;变量名中的后半部分NAME可以替换成任意请求头字段,如在配置文件中需要获取http请求头:"Accept-Language",$http_accept_language即可\
-$http_cookie                                    \
+$http_cookie                                    \
-$http_host               #请求地址,即浏览器中你输入的地址(IP或域名)\
+$http_host               #请求地址,即浏览器中你输入的地址(IP或域名)\
-$http_referer            #url跳转来源,用来记录从那个页面链接访问过来的\
+$http_referer            #url跳转来源,用来记录从那个页面链接访问过来的\
-$http_user_agent         #用户终端浏览器等信息\
+$http_user_agent         #用户终端浏览器等信息\
-$http_x_forwarded_for                                                           \
+$http_x_forwarded_for                                                           \
-$sent_http_NAME          #可以设置任意http响应头字段;变量名中的后半部分NAME可以替换成任意响应头字段,如需要设置响应头Content-length,$sent_http_content_length即可\
+$sent_http_NAME          #可以设置任意http响应头字段;变量名中的后半部分NAME可以替换成任意响应头字段,如需要设置响应头Content-length,$sent_http_content_length即可\
-$sent_http_cache_control                    \
+$sent_http_cache_control                    \
-$sent_http_connection                      \                                 
+$sent_http_connection                      \                                 
-$sent_http_content_type          \
+$sent_http_content_type          \
-$sent_http_keep_alive           \
+$sent_http_keep_alive           \
-$sent_http_last_modified            \
+$sent_http_last_modified            \
-$sent_http_location             \
+$sent_http_location             \
-$sent_http_transfer_encoding            \
+$sent_http_transfer_encoding            \
-
+
-来源于[博客]()
+来源于[博客]()

+ 22 - 22
src/pages/nginx/help/index.less → frontend/src/pages/nginx/help/index.less

@@ -1,22 +1,22 @@
-.help-container{
+.help-container{
-  padding: 0 15px;
+  padding: 0 15px;
-  overflow: hidden;
+  overflow: hidden;
-  height: 100%;
+  height: 100%;
-  .ant-tabs {
+  .ant-tabs {
-    height: 100%;
+    height: 100%;
-    overflow: hidden;
+    overflow: hidden;
-    display: flex;
+    display: flex;
-    flex-direction: column;
+    flex-direction: column;
-    .ant-tabs-nav{
+    .ant-tabs-nav{
-      margin-bottom: 0;
+      margin-bottom: 0;
-    }
+    }
-    .ant-tabs-content-holder{
+    .ant-tabs-content-holder{
-      flex: 1;
+      flex: 1;
-      overflow: hidden;
+      overflow: hidden;
-      .ant-tabs-content,.ant-tabs-tabpane{
+      .ant-tabs-content,.ant-tabs-tabpane{
-        height: 100%;
+        height: 100%;
-        overflow: auto;
+        overflow: auto;
-      }
+      }
-    }
+    }
-  }
+  }
-}
+}

+ 25 - 25
src/pages/nginx/help/index.tsx → frontend/src/pages/nginx/help/index.tsx

@@ -1,25 +1,25 @@
-/**
+/**
- * @author tuonian
+ * @author tuonian
- * @date 2023/7/10
+ * @date 2023/7/10
- */
+ */
-
+
-import Args from './args.mdx'
+import Args from './args.mdx'
-
+
-import './index.less'
+import './index.less'
-import {Tabs} from "antd";
+import {Tabs} from "antd";
-
+
-export const HelpPage = ()=>{
+export const HelpPage = ()=>{
-
+
-  const items =[{
+  const items =[{
-    key: 'args',
+    key: 'args',
-    label: '变量',
+    label: '变量',
-    children: <Args />
+    children: <Args />
-  }]
+  }]
-
+
-
+
-  return (<div className="help-container">
+  return (<div className="help-container">
-    <Tabs
+    <Tabs
-      items={items}
+      items={items}
-      />
+      />
-  </div>)
+  </div>)
-}
+}

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

@@ -1,100 +1,100 @@
-/**
+/**
- * @author tuonian
+ * @author tuonian
- * @date 2023/7/6
+ * @date 2023/7/6
- */
+ */
-import {INginx} from "../../../../models/nginx.ts";
+import {INginx} from "../../../../models/nginx.ts";
-import {Button, Drawer, Form, Input, Space, Switch, Tooltip} from "antd";
+import {Button, Drawer, Form, Input, Space, Switch, Tooltip} from "antd";
-import {ChangeEvent, useEffect, useState} from "react";
+import {ChangeEvent, useEffect, useState} from "react";
-import './index.less'
+import './index.less'
-import {SyncOutlined} from "@ant-design/icons";
+import {SyncOutlined} from "@ant-design/icons";
-import {NginxApis} from "../../../../api/nginx.ts";
+import {NginxApis} from "../../../../api/nginx.ts";
-import {useAppDispatch} from "../../../../store";
+import {useAppDispatch} from "../../../../store";
-import {NginxActions} from "../../../../store/slice/nginx.ts";
+import {NginxActions} from "../../../../store/slice/nginx.ts";
-import {Message} from "planning-tools";
+import {Message} from "planning-tools";
-import {toNginxConf} from "../utils.ts";
+import {toNginxConf} from "../utils.ts";
-
+
-type IProps = {
+type IProps = {
-    nginx?: INginx
+    nginx?: INginx
-    getRealData: () => Promise<any>
+    getRealData: () => Promise<any>
-}
+}
-export const HttpConfSync = ({nginx, getRealData}: IProps) => {
+export const HttpConfSync = ({nginx, getRealData}: IProps) => {
-
+
-    const [value, setValue] = useState<string>()
+    const [value, setValue] = useState<string>()
-    const [open, setOpen] = useState(false)
+    const [open, setOpen] = useState(false)
-    const [loading, setLoading] = useState(false)
+    const [loading, setLoading] = useState(false)
-    const [realtime, setRealtime] = useState(false)
+    const [realtime, setRealtime] = useState(false)
-
+
-    const dispatch = useAppDispatch()
+    const dispatch = useAppDispatch()
-
+
-    const onSetRealtime = async (checked: boolean)=>{
+    const onSetRealtime = async (checked: boolean)=>{
-        if (!checked){
+        if (!checked){
-            setValue(nginx?.httpConf);
+            setValue(nginx?.httpConf);
-            setRealtime(false)
+            setRealtime(false)
-            return
+            return
-        }
+        }
-        if (!nginx){
+        if (!nginx){
-            return
+            return
-        }
+        }
-        getRealData().then(data=>{
+        getRealData().then(data=>{
-            const conf = toNginxConf(nginx,data)
+            const conf = toNginxConf(nginx,data)
-            setValue(conf);
+            setValue(conf);
-            setRealtime(true)
+            setRealtime(true)
-        }).catch(()=>{
+        }).catch(()=>{
-            Message.warning('渲染失败,请检查配置文件是否存在错误提示!')
+            Message.warning('渲染失败,请检查配置文件是否存在错误提示!')
-        })
+        })
-    }
+    }
-
+
-
+
-    useEffect(() => {
+    useEffect(() => {
-        setValue(nginx?.httpConf)
+        setValue(nginx?.httpConf)
-    }, [nginx])
+    }, [nginx])
-
+
-    const onChange = (evt: ChangeEvent<HTMLTextAreaElement>) => {
+    const onChange = (evt: ChangeEvent<HTMLTextAreaElement>) => {
-        setValue(evt.currentTarget.value)
+        setValue(evt.currentTarget.value)
-    }
+    }
-
+
-    const onSubmitData = () => {
+    const onSubmitData = () => {
-        if (!nginx?.id) {
+        if (!nginx?.id) {
-            return
+            return
-        }
+        }
-        setLoading(true);
+        setLoading(true);
-        NginxApis.refreshHttp({id: nginx.id, httpConf: value || '', httpData: nginx.httpData})
+        NginxApis.refreshHttp({id: nginx.id, httpConf: value || '', httpData: nginx.httpData})
-            .then(() => {
+            .then(() => {
-                dispatch(NginxActions.updateNginx({...nginx, httpConf: value}))
+                dispatch(NginxActions.updateNginx({...nginx, httpConf: value}))
-                Message.success("success")
+                Message.success("success")
-            })
+            })
-            .finally(() => {
+            .finally(() => {
-                setLoading(false)
+                setLoading(false)
-            })
+            })
-    }
+    }
-
+
-    if (!nginx?.id) {
+    if (!nginx?.id) {
-        return null
+        return null
-    }
+    }
-
+
-    return (<>
+    return (<>
-        <Button onClick={() => setOpen(true)}>配置文件</Button>
+        <Button onClick={() => setOpen(true)}>配置文件</Button>
-        <Drawer title="nginx.conf"
+        <Drawer title="nginx.conf"
-                open={open}
+                open={open}
-                destroyOnClose
+                destroyOnClose
-                onClose={() => setOpen(false)}
+                onClose={() => setOpen(false)}
-                width={750}
+                width={750}
-                className="nginx-conf-drawer"
+                className="nginx-conf-drawer"
-                extra={<>
+                extra={<>
-                <Space>
+                <Space>
-                    <Form.Item tooltip="渲染实时数据" style={{marginBottom: 0}} label="实时">
+                    <Form.Item tooltip="渲染实时数据" style={{marginBottom: 0}} label="实时">
-                        <Switch onChange={checked=>onSetRealtime(checked)}  checked={realtime}/>
+                        <Switch onChange={checked=>onSetRealtime(checked)}  checked={realtime}/>
-                    </Form.Item>
+                    </Form.Item>
-                    <Tooltip placement="leftBottom"
+                    <Tooltip placement="leftBottom"
-                             title={`上传配置文件,注意:直接修改配置文件,将在界面操作“同步”功能后丢失`}>
+                             title={`上传配置文件,注意:直接修改配置文件,将在界面操作“同步”功能后丢失`}>
-                        <Button danger loading={loading} onClick={onSubmitData} icon={<SyncOutlined />}></Button>
+                        <Button danger loading={loading} onClick={onSubmitData} icon={<SyncOutlined />}></Button>
-                    </Tooltip>
+                    </Tooltip>
-                </Space>
+                </Space>
-                </>}
+                </>}
-        >
+        >
-            <Input.TextArea onChange={onChange} value={value}/>
+            <Input.TextArea onChange={onChange} value={value}/>
-        </Drawer>
+        </Drawer>
-    </>)
+    </>)
-
+
-
+
-}
+}

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