Browse Source

Merge pull request #82 from 0xJacky/refactor-obtain-cert

Refactored obtain certifications module
Jacky 2 years ago
parent
commit
a63ed74eca
49 changed files with 1411 additions and 603 deletions
  1. 2 2
      .air.toml
  2. 27 0
      dev.Dockerfile
  3. 1 1
      frontend/package.json
  4. 2 2
      frontend/src/api/domain.ts
  5. 1 1
      frontend/src/components/StdDataDisplay/StdTable.vue
  6. 61 31
      frontend/src/language/en/app.po
  7. 57 30
      frontend/src/language/messages.pot
  8. 0 0
      frontend/src/language/translations.json
  9. BIN
      frontend/src/language/zh_CN/app.mo
  10. 62 31
      frontend/src/language/zh_CN/app.po
  11. BIN
      frontend/src/language/zh_TW/app.mo
  12. 62 31
      frontend/src/language/zh_TW/app.po
  13. 1 1
      frontend/src/version.json
  14. 32 14
      frontend/src/views/cert/Cert.vue
  15. 1 5
      frontend/src/views/dashboard/DashBoard.vue
  16. 1 1
      frontend/src/views/domain/DomainEdit.vue
  17. 2 1
      frontend/src/views/domain/cert/Cert.vue
  18. 7 7
      frontend/src/views/domain/cert/IssueCert.vue
  19. 1 0
      frontend/src/views/domain/ngx_conf/NgxConfigEditor.vue
  20. 1 1
      frontend/version.json
  21. 1 1
      frontend/vite.config.ts
  22. 1 0
      go.mod
  23. 2 0
      go.sum
  24. 1 1
      main.go
  25. 1 1
      resources/demo/app.ini
  26. 13 0
      resources/development/entrypoint.sh
  27. 25 0
      resources/development/nginx/fastcgi_params
  28. 99 0
      resources/development/nginx/mime.types
  29. 1 0
      resources/development/nginx/modules
  30. 32 0
      resources/development/nginx/nginx.conf
  31. 17 0
      resources/development/nginx/scgi_params
  32. 28 0
      resources/development/nginx/sites-available/amstourship.jackyu.cn
  33. 84 0
      resources/development/nginx/sites-available/homework.jackyu.cn
  34. 27 0
      resources/development/nginx/sites-available/nginx.jackyu.cn
  35. 26 0
      resources/development/nginx/sites-available/qi.jackyu.cn
  36. 1 0
      resources/development/nginx/sites-enabled/amstourship.jackyu.cn
  37. 64 0
      resources/development/nginx/ssl/amstourship.jackyu.cn/fullchain.cer
  38. 27 0
      resources/development/nginx/ssl/amstourship.jackyu.cn/private.key
  39. 64 0
      resources/development/nginx/ssl/amstourship.jackyu.cn_t.jackyu.cn/fullchain.cer
  40. 27 0
      resources/development/nginx/ssl/amstourship.jackyu.cn_t.jackyu.cn/private.key
  41. 17 0
      resources/development/nginx/uwsgi_params
  42. 9 0
      resources/development/sources.list
  43. 305 307
      server/api/cert.go
  44. 23 18
      server/api/domain.go
  45. 31 14
      server/model/cert.go
  46. 114 63
      server/pkg/cert/auto_cert.go
  47. 14 5
      server/pkg/cert/cert.go
  48. 33 30
      server/pkg/nginx/type.go
  49. 3 4
      server/router/routers.go

+ 2 - 2
.air.toml

@@ -7,13 +7,13 @@ tmp_dir = "tmp"
 
 [build]
 # Just plain old shell command. You could use `make` as well.
-cmd = "go build -ldflags=\"-X 'github.com/0xJacky/Nginx-UI/server/settings.buildTime=$(date +%s)'\" -o ./tmp/main ."
+cmd = "CGO_ENABLED=1 go build -ldflags=\"-X 'github.com/0xJacky/Nginx-UI/server/settings.buildTime=$(date +%s)'\" -o ./tmp/main ."
 # Binary file yields from `cmd`.
 bin = "tmp/main"
 # Customize binary.
 full_bin = "APP_ENV=dev APP_USER=air ./tmp/main"
 # Watch these filename extensions.
-include_ext = ["go", "tpl", "tmpl", "html", "conf"]
+include_ext = ["go", "tpl", "tmpl", "html", "conf", "ini", "toml"]
 # Ignore these filename extensions or directories.
 exclude_dir = ["assets", "tmp", "vendor", "frontend/node_modules", "upload"]
 # Watch these directories if you specified.

+ 27 - 0
dev.Dockerfile

@@ -0,0 +1,27 @@
+FROM --platform=linux/amd64 ubuntu:latest
+
+WORKDIR /app
+EXPOSE 80 443
+
+COPY resources/development/sources.list /etc/apt/sources.list
+
+RUN set -x \
+# create nginx user/group first, to be consistent throughout docker variants
+    && addgroup --system --gid 101 nginx \
+    && adduser --system --disabled-login --ingroup nginx --no-create-home --home /nonexistent --gecos "nginx user" --shell /bin/false --uid 101 nginx \
+    && apt update && apt install -y wget nginx gcc curl
+
+RUN wget https://go.dev/dl/go1.20.linux-amd64.tar.gz && \
+    rm -rf /usr/local/go && tar -C /usr/local -xzf go1.20.linux-amd64.tar.gz && rm -f go1.20.linux-amd64.tar.gz
+
+ENV PATH="${PATH}:/usr/local/go/bin"
+
+RUN go install github.com/cosmtrek/air@latest
+
+COPY resources/development/entrypoint.sh /entrypoint.sh
+
+RUN chmod a+x /entrypoint.sh  \
+    && rm -f /etc/nginx/conf.d/default.conf  \
+    && rm -f /usr/etc/nginx/conf.d/default.conf
+
+CMD ["/entrypoint.sh"]

+ 1 - 1
frontend/package.json

@@ -1,7 +1,7 @@
 {
     "name": "nginx-ui-frontend-next",
     "private": true,
-    "version": "1.7.5",
+    "version": "1.7.6",
     "type": "commonjs",
     "scripts": {
         "dev": "vite",

+ 2 - 2
frontend/src/api/domain.ts

@@ -14,8 +14,8 @@ class Domain extends Curd {
         return http.get('template')
     }
 
-    add_auto_cert(domain: string) {
-        return http.post('auto_cert/' + domain)
+    add_auto_cert(domain: string, data: any) {
+        return http.post('auto_cert/' + domain, data)
     }
 
     remove_auto_cert(domain: string) {

+ 1 - 1
frontend/src/components/StdDataDisplay/StdTable.vue

@@ -525,7 +525,7 @@ function initSortable() {
                             :okText="$gettext('OK')"
                             :title="$gettext('Are you sure you want to delete?')"
                             @confirm="destroy(record[rowKey])">
-                            <a-button type="link" size="small" v-translate>Delete</a-button>
+                            <a-button type="link" size="small">{{ $gettext('Delete') }}</a-button>
                         </a-popconfirm>
                     </template>
                 </template>

+ 61 - 31
frontend/src/language/en/app.po

@@ -17,8 +17,8 @@ msgstr "About"
 msgid "Access Logs"
 msgstr ""
 
-#: src/views/cert/Cert.vue:78 src/views/config/config.ts:36
-#: src/views/domain/DomainList.vue:47 src/views/user/User.vue:43
+#: src/views/cert/Cert.vue:74 src/views/config/config.ts:36
+#: src/views/domain/DomainList.vue:48 src/views/user/User.vue:43
 msgid "Action"
 msgstr "Action"
 
@@ -85,11 +85,11 @@ msgstr ""
 msgid "Auto"
 msgstr ""
 
-#: src/views/cert/Cert.vue:41 src/views/domain/cert/ChangeCert.vue:35
+#: src/views/cert/Cert.vue:37 src/views/domain/cert/ChangeCert.vue:35
 msgid "Auto Cert"
 msgstr ""
 
-#: src/views/cert/Cert.vue:8
+#: src/views/cert/Cert.vue:9
 msgid "Auto cert is enabled, please do not modify this certification."
 msgstr ""
 
@@ -97,6 +97,10 @@ msgstr ""
 msgid "Auto Refresh"
 msgstr ""
 
+#: src/views/cert/Cert.vue:27
+msgid "Auto-Cert Log"
+msgstr ""
+
 #: src/views/domain/cert/IssueCert.vue:71
 msgid "Auto-renewal disabled for %{name}"
 msgstr "Auto-renewal disabled for %{name}"
@@ -152,7 +156,7 @@ msgstr "Certificate has expired"
 msgid "Certificate is valid"
 msgstr "Certificate is valid"
 
-#: src/views/cert/Cert.vue:12 src/views/domain/cert/Cert.vue:35
+#: src/views/cert/Cert.vue:34 src/views/domain/cert/Cert.vue:35
 msgid "Certificate Status"
 msgstr "Certificate Status"
 
@@ -176,10 +180,15 @@ msgstr ""
 #: src/views/domain/ngx_conf/directive/DirectiveEditorItem.vue:34
 #: src/views/domain/ngx_conf/LocationEditor.vue:35
 #: src/views/domain/ngx_conf/LocationEditor.vue:52
-#: src/views/domain/ngx_conf/NgxConfigEditor.vue:180
+#: src/views/domain/ngx_conf/NgxConfigEditor.vue:181
 msgid "Comments"
 msgstr "Comments"
 
+#: src/views/cert/Cert.vue:32
+#, fuzzy
+msgid "Config Name"
+msgstr "Configuration Name"
+
 #: src/views/domain/ngx_conf/ConfigTemplate.vue:61
 #, fuzzy
 msgid "Config Templates"
@@ -258,7 +267,13 @@ msgstr "Dashboard"
 msgid "Database (Optional, default: database)"
 msgstr "Database (Optional, default: database)"
 
-#: src/components/StdDataDisplay/StdTable.vue:528
+#: src/components/StdDataDisplay/StdTable.vue:31
+#: src/components/StdDataDisplay/StdTable.vue:32
+#: src/components/StdDataDisplay/StdTable.vue:37
+#: src/components/StdDataDisplay/StdTable.vue:50
+#: src/components/StdDataDisplay/StdTable.vue:52
+#: src/components/StdDataDisplay/StdTable.vue:53
+#: src/components/StdDataDisplay/StdTable.vue:57
 #: src/views/domain/DomainList.vue:19 src/views/domain/DomainList.vue:20
 #: src/views/domain/DomainList.vue:21 src/views/domain/DomainList.vue:28
 #: src/views/domain/DomainList.vue:32
@@ -269,7 +284,7 @@ msgstr ""
 msgid "Delete ID: %{id}"
 msgstr ""
 
-#: src/views/domain/DomainList.vue:81
+#: src/views/domain/DomainList.vue:82
 msgid "Delete site: %{site_name}"
 msgstr ""
 
@@ -304,15 +319,15 @@ msgstr "Directives"
 msgid "Disable auto-renewal failed for %{name}"
 msgstr "Disable auto-renewal failed for %{name}"
 
-#: src/views/cert/Cert.vue:51 src/views/domain/cert/ChangeCert.vue:45
+#: src/views/cert/Cert.vue:47 src/views/domain/cert/ChangeCert.vue:45
 #: src/views/domain/DomainEdit.vue:10 src/views/domain/DomainEdit.vue:9
-#: src/views/domain/DomainList.vue:16 src/views/domain/DomainList.vue:34
+#: src/views/domain/DomainList.vue:16 src/views/domain/DomainList.vue:35
 #: src/views/domain/DomainList.vue:7 src/views/domain/DomainList.vue:8
 #: src/views/domain/DomainList.vue:9
 msgid "Disabled"
 msgstr "Disabled"
 
-#: src/views/domain/DomainEdit.vue:146 src/views/domain/DomainList.vue:69
+#: src/views/domain/DomainEdit.vue:146 src/views/domain/DomainList.vue:70
 msgid "Disabled successfully"
 msgstr "Disabled successfully"
 
@@ -320,14 +335,14 @@ msgstr "Disabled successfully"
 msgid "Disk IO"
 msgstr "Disk IO"
 
-#: src/views/cert/Cert.vue:32
-msgid "Domain"
-msgstr ""
-
 #: src/views/domain/DomainAdd.vue:58
 msgid "Domain Config Created Successfully"
 msgstr "Domain Config Created Successfully"
 
+#: src/views/cert/Cert.vue:21
+msgid "Domains list is empty, try to reopen auto-cert for %{config}"
+msgstr ""
+
 #: src/language/constants.ts:26
 msgid "Download latest release error"
 msgstr ""
@@ -338,9 +353,15 @@ msgstr ""
 
 #: src/views/domain/DomainList.vue:14 src/views/domain/DomainList.vue:15
 #: src/views/domain/DomainList.vue:16 src/views/domain/DomainList.vue:23
+#: src/views/domain/SiteDuplicate.vue:2
 msgid "Duplicate"
 msgstr ""
 
+#: src/views/domain/SiteDuplicate.vue:43
+#, fuzzy
+msgid "Duplicated successfully"
+msgstr "Saved successfully"
+
 #: src/views/domain/DomainEdit.vue:4 src/views/domain/DomainEdit.vue:5
 msgid "Edit %{n}"
 msgstr "Edit %{n}"
@@ -369,16 +390,16 @@ msgstr "Enable failed"
 msgid "Enable TLS"
 msgstr "Enable TLS"
 
-#: src/views/cert/Cert.vue:48 src/views/domain/cert/ChangeCert.vue:42
+#: src/views/cert/Cert.vue:44 src/views/domain/cert/ChangeCert.vue:42
 #: src/views/domain/DomainEdit.vue:43 src/views/domain/DomainEdit.vue:6
 #: src/views/domain/DomainEdit.vue:7 src/views/domain/DomainList.vue:10
 #: src/views/domain/DomainList.vue:11 src/views/domain/DomainList.vue:12
-#: src/views/domain/DomainList.vue:19 src/views/domain/DomainList.vue:31
+#: src/views/domain/DomainList.vue:19 src/views/domain/DomainList.vue:32
 msgid "Enabled"
 msgstr "Enabled"
 
 #: src/views/domain/DomainAdd.vue:47 src/views/domain/DomainEdit.vue:137
-#: src/views/domain/DomainList.vue:59
+#: src/views/domain/DomainList.vue:60
 msgid "Enabled successfully"
 msgstr "Enabled successfully"
 
@@ -410,11 +431,11 @@ msgstr "Expiration Date: %{date}"
 msgid "Export"
 msgstr ""
 
-#: src/views/domain/DomainEdit.vue:149 src/views/domain/DomainList.vue:73
+#: src/views/domain/DomainEdit.vue:149 src/views/domain/DomainList.vue:74
 msgid "Failed to disable %{msg}"
 msgstr "Failed to disable %{msg}"
 
-#: src/views/domain/DomainEdit.vue:140 src/views/domain/DomainList.vue:63
+#: src/views/domain/DomainEdit.vue:140 src/views/domain/DomainList.vue:64
 msgid "Failed to enable %{msg}"
 msgstr "Failed to enable %{msg}"
 
@@ -609,7 +630,7 @@ msgstr "Single Directive"
 
 #: src/views/cert/Cert.vue:16 src/views/config/config.ts:9
 #: src/views/domain/cert/ChangeCert.vue:19 src/views/domain/DomainEdit.vue:46
-#: src/views/domain/DomainList.vue:15
+#: src/views/domain/DomainList.vue:16 src/views/domain/SiteDuplicate.vue:5
 msgid "Name"
 msgstr "Name"
 
@@ -736,6 +757,12 @@ msgstr ""
 msgid "Performing core upgrade"
 msgstr ""
 
+#: src/views/domain/SiteDuplicate.vue:28
+msgid ""
+"Please input name, this will be used as the filename of the new "
+"configuration!"
+msgstr ""
+
 #: src/views/other/Install.vue:36
 msgid "Please input your E-mail!"
 msgstr "Please input your E-mail!"
@@ -885,9 +912,8 @@ msgstr "Send"
 #: src/components/StdDataDisplay/StdTable.vue:168
 #: src/components/StdDataDisplay/StdTable.vue:343
 #: src/components/StdDataDisplay/StdTable.vue:463
-#: src/views/config/ConfigEdit.vue:32 src/views/domain/DomainEdit.vue:87
-#: src/views/domain/DomainList.vue:83 src/views/other/Install.vue:71
-#: src/views/preference/Preference.vue:41
+#: src/views/config/ConfigEdit.vue:32 src/views/domain/DomainList.vue:84
+#: src/views/other/Install.vue:71 src/views/preference/Preference.vue:41
 msgid "Server error"
 msgstr "Server error"
 
@@ -917,27 +943,27 @@ msgstr "Sites List"
 msgid "Sites List"
 msgstr "Sites List"
 
-#: src/views/cert/Cert.vue:65
+#: src/views/cert/Cert.vue:61
 #, fuzzy
 msgid "SSL Certificate Key Path"
 msgstr "Certificate Status"
 
-#: src/views/cert/Cert.vue:58
+#: src/views/cert/Cert.vue:54
 #, fuzzy
 msgid "SSL Certificate Path"
 msgstr "Certificate Status"
 
-#: src/views/cert/Cert.vue:19
+#: src/views/cert/Cert.vue:41
 #, fuzzy
 msgid "SSL Certification Content"
 msgstr "Certificate Status"
 
-#: src/views/cert/Cert.vue:22
+#: src/views/cert/Cert.vue:44
 #, fuzzy
 msgid "SSL Certification Key Content"
 msgstr "Certificate Status"
 
-#: src/views/domain/DomainList.vue:24
+#: src/views/domain/DomainList.vue:25
 msgid "Status"
 msgstr "Status"
 
@@ -1003,12 +1029,16 @@ msgstr ""
 msgid "Theme"
 msgstr ""
 
+#: src/views/cert/Cert.vue:15
+msgid "This auto-cert item is invalid, please remove it."
+msgstr ""
+
 #: src/views/config/config.ts:14
 msgid "Type"
 msgstr ""
 
-#: src/views/cert/Cert.vue:72 src/views/config/config.ts:29
-#: src/views/domain/DomainList.vue:41 src/views/user/User.vue:37
+#: src/views/cert/Cert.vue:68 src/views/config/config.ts:29
+#: src/views/domain/DomainList.vue:42 src/views/user/User.vue:37
 msgid "Updated at"
 msgstr "Updated at"
 

+ 57 - 30
frontend/src/language/messages.pot

@@ -11,9 +11,9 @@ msgstr ""
 msgid "Access Logs"
 msgstr ""
 
-#: src/views/cert/Cert.vue:78
+#: src/views/cert/Cert.vue:74
 #: src/views/config/config.ts:36
-#: src/views/domain/DomainList.vue:47
+#: src/views/domain/DomainList.vue:48
 #: src/views/user/User.vue:43
 msgid "Action"
 msgstr ""
@@ -83,12 +83,12 @@ msgstr ""
 msgid "Auto"
 msgstr ""
 
-#: src/views/cert/Cert.vue:41
+#: src/views/cert/Cert.vue:37
 #: src/views/domain/cert/ChangeCert.vue:35
 msgid "Auto Cert"
 msgstr ""
 
-#: src/views/cert/Cert.vue:8
+#: src/views/cert/Cert.vue:9
 msgid "Auto cert is enabled, please do not modify this certification."
 msgstr ""
 
@@ -96,6 +96,10 @@ msgstr ""
 msgid "Auto Refresh"
 msgstr ""
 
+#: src/views/cert/Cert.vue:27
+msgid "Auto-Cert Log"
+msgstr ""
+
 #: src/views/domain/cert/IssueCert.vue:71
 msgid "Auto-renewal disabled for %{name}"
 msgstr ""
@@ -153,7 +157,7 @@ msgstr ""
 msgid "Certificate is valid"
 msgstr ""
 
-#: src/views/cert/Cert.vue:12
+#: src/views/cert/Cert.vue:34
 #: src/views/domain/cert/Cert.vue:35
 msgid "Certificate Status"
 msgstr ""
@@ -179,10 +183,14 @@ msgstr ""
 #: src/views/domain/ngx_conf/directive/DirectiveEditorItem.vue:34
 #: src/views/domain/ngx_conf/LocationEditor.vue:35
 #: src/views/domain/ngx_conf/LocationEditor.vue:52
-#: src/views/domain/ngx_conf/NgxConfigEditor.vue:180
+#: src/views/domain/ngx_conf/NgxConfigEditor.vue:181
 msgid "Comments"
 msgstr ""
 
+#: src/views/cert/Cert.vue:32
+msgid "Config Name"
+msgstr ""
+
 #: src/views/domain/ngx_conf/ConfigTemplate.vue:61
 msgid "Config Templates"
 msgstr ""
@@ -263,7 +271,13 @@ msgstr ""
 msgid "Database (Optional, default: database)"
 msgstr ""
 
-#: src/components/StdDataDisplay/StdTable.vue:528
+#: src/components/StdDataDisplay/StdTable.vue:31
+#: src/components/StdDataDisplay/StdTable.vue:32
+#: src/components/StdDataDisplay/StdTable.vue:37
+#: src/components/StdDataDisplay/StdTable.vue:50
+#: src/components/StdDataDisplay/StdTable.vue:52
+#: src/components/StdDataDisplay/StdTable.vue:53
+#: src/components/StdDataDisplay/StdTable.vue:57
 #: src/views/domain/DomainList.vue:19
 #: src/views/domain/DomainList.vue:20
 #: src/views/domain/DomainList.vue:21
@@ -276,7 +290,7 @@ msgstr ""
 msgid "Delete ID: %{id}"
 msgstr ""
 
-#: src/views/domain/DomainList.vue:81
+#: src/views/domain/DomainList.vue:82
 msgid "Delete site: %{site_name}"
 msgstr ""
 
@@ -312,12 +326,12 @@ msgstr ""
 msgid "Disable auto-renewal failed for %{name}"
 msgstr ""
 
-#: src/views/cert/Cert.vue:51
+#: src/views/cert/Cert.vue:47
 #: src/views/domain/cert/ChangeCert.vue:45
 #: src/views/domain/DomainEdit.vue:10
 #: src/views/domain/DomainEdit.vue:9
 #: src/views/domain/DomainList.vue:16
-#: src/views/domain/DomainList.vue:34
+#: src/views/domain/DomainList.vue:35
 #: src/views/domain/DomainList.vue:7
 #: src/views/domain/DomainList.vue:8
 #: src/views/domain/DomainList.vue:9
@@ -325,7 +339,7 @@ msgid "Disabled"
 msgstr ""
 
 #: src/views/domain/DomainEdit.vue:146
-#: src/views/domain/DomainList.vue:69
+#: src/views/domain/DomainList.vue:70
 msgid "Disabled successfully"
 msgstr ""
 
@@ -333,14 +347,14 @@ msgstr ""
 msgid "Disk IO"
 msgstr ""
 
-#: src/views/cert/Cert.vue:32
-msgid "Domain"
-msgstr ""
-
 #: src/views/domain/DomainAdd.vue:58
 msgid "Domain Config Created Successfully"
 msgstr ""
 
+#: src/views/cert/Cert.vue:21
+msgid "Domains list is empty, try to reopen auto-cert for %{config}"
+msgstr ""
+
 #: src/language/constants.ts:26
 msgid "Download latest release error"
 msgstr ""
@@ -353,9 +367,14 @@ msgstr ""
 #: src/views/domain/DomainList.vue:15
 #: src/views/domain/DomainList.vue:16
 #: src/views/domain/DomainList.vue:23
+#: src/views/domain/SiteDuplicate.vue:2
 msgid "Duplicate"
 msgstr ""
 
+#: src/views/domain/SiteDuplicate.vue:43
+msgid "Duplicated successfully"
+msgstr ""
+
 #: src/views/domain/DomainEdit.vue:4
 #: src/views/domain/DomainEdit.vue:5
 msgid "Edit %{n}"
@@ -386,7 +405,7 @@ msgstr ""
 msgid "Enable TLS"
 msgstr ""
 
-#: src/views/cert/Cert.vue:48
+#: src/views/cert/Cert.vue:44
 #: src/views/domain/cert/ChangeCert.vue:42
 #: src/views/domain/DomainEdit.vue:43
 #: src/views/domain/DomainEdit.vue:6
@@ -395,13 +414,13 @@ msgstr ""
 #: src/views/domain/DomainList.vue:11
 #: src/views/domain/DomainList.vue:12
 #: src/views/domain/DomainList.vue:19
-#: src/views/domain/DomainList.vue:31
+#: src/views/domain/DomainList.vue:32
 msgid "Enabled"
 msgstr ""
 
 #: src/views/domain/DomainAdd.vue:47
 #: src/views/domain/DomainEdit.vue:137
-#: src/views/domain/DomainList.vue:59
+#: src/views/domain/DomainList.vue:60
 msgid "Enabled successfully"
 msgstr ""
 
@@ -437,12 +456,12 @@ msgid "Export"
 msgstr ""
 
 #: src/views/domain/DomainEdit.vue:149
-#: src/views/domain/DomainList.vue:73
+#: src/views/domain/DomainList.vue:74
 msgid "Failed to disable %{msg}"
 msgstr ""
 
 #: src/views/domain/DomainEdit.vue:140
-#: src/views/domain/DomainList.vue:63
+#: src/views/domain/DomainList.vue:64
 msgid "Failed to enable %{msg}"
 msgstr ""
 
@@ -636,7 +655,8 @@ msgstr ""
 #: src/views/config/config.ts:9
 #: src/views/domain/cert/ChangeCert.vue:19
 #: src/views/domain/DomainEdit.vue:46
-#: src/views/domain/DomainList.vue:15
+#: src/views/domain/DomainList.vue:16
+#: src/views/domain/SiteDuplicate.vue:5
 msgid "Name"
 msgstr ""
 
@@ -765,6 +785,10 @@ msgstr ""
 msgid "Performing core upgrade"
 msgstr ""
 
+#: src/views/domain/SiteDuplicate.vue:28
+msgid "Please input name, this will be used as the filename of the new configuration!"
+msgstr ""
+
 #: src/views/other/Install.vue:36
 msgid "Please input your E-mail!"
 msgstr ""
@@ -924,8 +948,7 @@ msgstr ""
 #: src/components/StdDataDisplay/StdTable.vue:343
 #: src/components/StdDataDisplay/StdTable.vue:463
 #: src/views/config/ConfigEdit.vue:32
-#: src/views/domain/DomainEdit.vue:87
-#: src/views/domain/DomainList.vue:83
+#: src/views/domain/DomainList.vue:84
 #: src/views/other/Install.vue:71
 #: src/views/preference/Preference.vue:41
 msgid "Server error"
@@ -957,23 +980,23 @@ msgstr ""
 msgid "Sites List"
 msgstr ""
 
-#: src/views/cert/Cert.vue:65
+#: src/views/cert/Cert.vue:61
 msgid "SSL Certificate Key Path"
 msgstr ""
 
-#: src/views/cert/Cert.vue:58
+#: src/views/cert/Cert.vue:54
 msgid "SSL Certificate Path"
 msgstr ""
 
-#: src/views/cert/Cert.vue:19
+#: src/views/cert/Cert.vue:41
 msgid "SSL Certification Content"
 msgstr ""
 
-#: src/views/cert/Cert.vue:22
+#: src/views/cert/Cert.vue:44
 msgid "SSL Certification Key Content"
 msgstr ""
 
-#: src/views/domain/DomainList.vue:24
+#: src/views/domain/DomainList.vue:25
 msgid "Status"
 msgstr ""
 
@@ -1030,13 +1053,17 @@ msgstr ""
 msgid "Theme"
 msgstr ""
 
+#: src/views/cert/Cert.vue:15
+msgid "This auto-cert item is invalid, please remove it."
+msgstr ""
+
 #: src/views/config/config.ts:14
 msgid "Type"
 msgstr ""
 
-#: src/views/cert/Cert.vue:72
+#: src/views/cert/Cert.vue:68
 #: src/views/config/config.ts:29
-#: src/views/domain/DomainList.vue:41
+#: src/views/domain/DomainList.vue:42
 #: src/views/user/User.vue:37
 msgid "Updated at"
 msgstr ""

File diff suppressed because it is too large
+ 0 - 0
frontend/src/language/translations.json


BIN
frontend/src/language/zh_CN/app.mo


+ 62 - 31
frontend/src/language/zh_CN/app.po

@@ -20,8 +20,8 @@ msgstr "关于"
 msgid "Access Logs"
 msgstr "访问日志"
 
-#: src/views/cert/Cert.vue:78 src/views/config/config.ts:36
-#: src/views/domain/DomainList.vue:47 src/views/user/User.vue:43
+#: src/views/cert/Cert.vue:74 src/views/config/config.ts:36
+#: src/views/domain/DomainList.vue:48 src/views/user/User.vue:43
 msgid "Action"
 msgstr "操作"
 
@@ -86,11 +86,11 @@ msgstr "作者"
 msgid "Auto"
 msgstr "自动"
 
-#: src/views/cert/Cert.vue:41 src/views/domain/cert/ChangeCert.vue:35
+#: src/views/cert/Cert.vue:37 src/views/domain/cert/ChangeCert.vue:35
 msgid "Auto Cert"
 msgstr "自动更新"
 
-#: src/views/cert/Cert.vue:8
+#: src/views/cert/Cert.vue:9
 msgid "Auto cert is enabled, please do not modify this certification."
 msgstr "自动更新已启用,请勿修改此证书配置。"
 
@@ -98,6 +98,10 @@ msgstr "自动更新已启用,请勿修改此证书配置。"
 msgid "Auto Refresh"
 msgstr "自动刷新"
 
+#: src/views/cert/Cert.vue:27
+msgid "Auto-Cert Log"
+msgstr "证书自动续期日志"
+
 #: src/views/domain/cert/IssueCert.vue:71
 msgid "Auto-renewal disabled for %{name}"
 msgstr "成功关闭 %{name} 自动续签"
@@ -151,7 +155,7 @@ msgstr "此证书已过期"
 msgid "Certificate is valid"
 msgstr "此证书有效"
 
-#: src/views/cert/Cert.vue:12 src/views/domain/cert/Cert.vue:35
+#: src/views/cert/Cert.vue:34 src/views/domain/cert/Cert.vue:35
 msgid "Certificate Status"
 msgstr "证书状态"
 
@@ -173,10 +177,14 @@ msgstr "重新检查"
 #: src/views/domain/ngx_conf/directive/DirectiveEditorItem.vue:34
 #: src/views/domain/ngx_conf/LocationEditor.vue:35
 #: src/views/domain/ngx_conf/LocationEditor.vue:52
-#: src/views/domain/ngx_conf/NgxConfigEditor.vue:180
+#: src/views/domain/ngx_conf/NgxConfigEditor.vue:181
 msgid "Comments"
 msgstr "注释"
 
+#: src/views/cert/Cert.vue:32
+msgid "Config Name"
+msgstr "配置文件名称"
+
 #: src/views/domain/ngx_conf/ConfigTemplate.vue:61
 msgid "Config Templates"
 msgstr "配置"
@@ -254,7 +262,13 @@ msgstr "仪表盘"
 msgid "Database (Optional, default: database)"
 msgstr "数据库 (可选,默认: database)"
 
-#: src/components/StdDataDisplay/StdTable.vue:528
+#: src/components/StdDataDisplay/StdTable.vue:31
+#: src/components/StdDataDisplay/StdTable.vue:32
+#: src/components/StdDataDisplay/StdTable.vue:37
+#: src/components/StdDataDisplay/StdTable.vue:50
+#: src/components/StdDataDisplay/StdTable.vue:52
+#: src/components/StdDataDisplay/StdTable.vue:53
+#: src/components/StdDataDisplay/StdTable.vue:57
 #: src/views/domain/DomainList.vue:19 src/views/domain/DomainList.vue:20
 #: src/views/domain/DomainList.vue:21 src/views/domain/DomainList.vue:28
 #: src/views/domain/DomainList.vue:32
@@ -265,7 +279,7 @@ msgstr "删除"
 msgid "Delete ID: %{id}"
 msgstr "删除 ID: %{id}"
 
-#: src/views/domain/DomainList.vue:81
+#: src/views/domain/DomainList.vue:82
 msgid "Delete site: %{site_name}"
 msgstr "删除站点: %{site_name}"
 
@@ -300,15 +314,15 @@ msgstr "指令"
 msgid "Disable auto-renewal failed for %{name}"
 msgstr "关闭 %{name} 自动续签失败"
 
-#: src/views/cert/Cert.vue:51 src/views/domain/cert/ChangeCert.vue:45
+#: src/views/cert/Cert.vue:47 src/views/domain/cert/ChangeCert.vue:45
 #: src/views/domain/DomainEdit.vue:10 src/views/domain/DomainEdit.vue:9
-#: src/views/domain/DomainList.vue:16 src/views/domain/DomainList.vue:34
+#: src/views/domain/DomainList.vue:16 src/views/domain/DomainList.vue:35
 #: src/views/domain/DomainList.vue:7 src/views/domain/DomainList.vue:8
 #: src/views/domain/DomainList.vue:9
 msgid "Disabled"
 msgstr "禁用"
 
-#: src/views/domain/DomainEdit.vue:146 src/views/domain/DomainList.vue:69
+#: src/views/domain/DomainEdit.vue:146 src/views/domain/DomainList.vue:70
 msgid "Disabled successfully"
 msgstr "禁用成功"
 
@@ -316,14 +330,14 @@ msgstr "禁用成功"
 msgid "Disk IO"
 msgstr "磁盘 IO"
 
-#: src/views/cert/Cert.vue:32
-msgid "Domain"
-msgstr "域名"
-
 #: src/views/domain/DomainAdd.vue:58
 msgid "Domain Config Created Successfully"
 msgstr "域名配置文件创建成功"
 
+#: src/views/cert/Cert.vue:21
+msgid "Domains list is empty, try to reopen auto-cert for %{config}"
+msgstr "域名列表为空,请尝试为%{config}重新打开证书自动续期。"
+
 #: src/language/constants.ts:26
 msgid "Download latest release error"
 msgstr "下载最新版本错误"
@@ -334,9 +348,14 @@ msgstr "下载最新版本"
 
 #: src/views/domain/DomainList.vue:14 src/views/domain/DomainList.vue:15
 #: src/views/domain/DomainList.vue:16 src/views/domain/DomainList.vue:23
+#: src/views/domain/SiteDuplicate.vue:2
 msgid "Duplicate"
 msgstr "复制"
 
+#: src/views/domain/SiteDuplicate.vue:43
+msgid "Duplicated successfully"
+msgstr "复制成功"
+
 #: src/views/domain/DomainEdit.vue:4 src/views/domain/DomainEdit.vue:5
 msgid "Edit %{n}"
 msgstr "编辑 %{n}"
@@ -365,16 +384,16 @@ msgstr "启用失败"
 msgid "Enable TLS"
 msgstr "启用 TLS"
 
-#: src/views/cert/Cert.vue:48 src/views/domain/cert/ChangeCert.vue:42
+#: src/views/cert/Cert.vue:44 src/views/domain/cert/ChangeCert.vue:42
 #: src/views/domain/DomainEdit.vue:43 src/views/domain/DomainEdit.vue:6
 #: src/views/domain/DomainEdit.vue:7 src/views/domain/DomainList.vue:10
 #: src/views/domain/DomainList.vue:11 src/views/domain/DomainList.vue:12
-#: src/views/domain/DomainList.vue:19 src/views/domain/DomainList.vue:31
+#: src/views/domain/DomainList.vue:19 src/views/domain/DomainList.vue:32
 msgid "Enabled"
 msgstr "启用"
 
 #: src/views/domain/DomainAdd.vue:47 src/views/domain/DomainEdit.vue:137
-#: src/views/domain/DomainList.vue:59
+#: src/views/domain/DomainList.vue:60
 msgid "Enabled successfully"
 msgstr "启用成功"
 
@@ -406,11 +425,11 @@ msgstr "过期时间: %{date}"
 msgid "Export"
 msgstr "导出"
 
-#: src/views/domain/DomainEdit.vue:149 src/views/domain/DomainList.vue:73
+#: src/views/domain/DomainEdit.vue:149 src/views/domain/DomainList.vue:74
 msgid "Failed to disable %{msg}"
 msgstr "禁用失败 %{msg}"
 
-#: src/views/domain/DomainEdit.vue:140 src/views/domain/DomainList.vue:63
+#: src/views/domain/DomainEdit.vue:140 src/views/domain/DomainList.vue:64
 msgid "Failed to enable %{msg}"
 msgstr "启用失败 %{msg}"
 
@@ -598,7 +617,7 @@ msgstr "单行指令"
 
 #: src/views/cert/Cert.vue:16 src/views/config/config.ts:9
 #: src/views/domain/cert/ChangeCert.vue:19 src/views/domain/DomainEdit.vue:46
-#: src/views/domain/DomainList.vue:15
+#: src/views/domain/DomainList.vue:16 src/views/domain/SiteDuplicate.vue:5
 msgid "Name"
 msgstr "名称"
 
@@ -721,6 +740,12 @@ msgstr "执行核心升级错误"
 msgid "Performing core upgrade"
 msgstr "正在进行核心升级"
 
+#: src/views/domain/SiteDuplicate.vue:28
+msgid ""
+"Please input name, this will be used as the filename of the new "
+"configuration!"
+msgstr "请输入名称,这将被用作新配置的文件名。"
+
 #: src/views/other/Install.vue:36
 msgid "Please input your E-mail!"
 msgstr "请输入您的邮箱!"
@@ -865,9 +890,8 @@ msgstr "上传"
 #: src/components/StdDataDisplay/StdTable.vue:168
 #: src/components/StdDataDisplay/StdTable.vue:343
 #: src/components/StdDataDisplay/StdTable.vue:463
-#: src/views/config/ConfigEdit.vue:32 src/views/domain/DomainEdit.vue:87
-#: src/views/domain/DomainList.vue:83 src/views/other/Install.vue:71
-#: src/views/preference/Preference.vue:41
+#: src/views/config/ConfigEdit.vue:32 src/views/domain/DomainList.vue:84
+#: src/views/other/Install.vue:71 src/views/preference/Preference.vue:41
 msgid "Server error"
 msgstr "服务器错误"
 
@@ -896,23 +920,23 @@ msgstr "站点列表"
 msgid "Sites List"
 msgstr "站点列表"
 
-#: src/views/cert/Cert.vue:65
+#: src/views/cert/Cert.vue:61
 msgid "SSL Certificate Key Path"
 msgstr "SSL证书密钥路径"
 
-#: src/views/cert/Cert.vue:58
+#: src/views/cert/Cert.vue:54
 msgid "SSL Certificate Path"
 msgstr "SSL证书路径"
 
-#: src/views/cert/Cert.vue:19
+#: src/views/cert/Cert.vue:41
 msgid "SSL Certification Content"
 msgstr "SSL证书内容"
 
-#: src/views/cert/Cert.vue:22
+#: src/views/cert/Cert.vue:44
 msgid "SSL Certification Key Content"
 msgstr "SSL证书密钥内容"
 
-#: src/views/domain/DomainList.vue:24
+#: src/views/domain/DomainList.vue:25
 msgid "Status"
 msgstr "状态"
 
@@ -973,12 +997,16 @@ msgstr "用户名或密码错误"
 msgid "Theme"
 msgstr "主题"
 
+#: src/views/cert/Cert.vue:15
+msgid "This auto-cert item is invalid, please remove it."
+msgstr "这个证书自动续期项目是无效的,请删除。"
+
 #: src/views/config/config.ts:14
 msgid "Type"
 msgstr "类型"
 
-#: src/views/cert/Cert.vue:72 src/views/config/config.ts:29
-#: src/views/domain/DomainList.vue:41 src/views/user/User.vue:37
+#: src/views/cert/Cert.vue:68 src/views/config/config.ts:29
+#: src/views/domain/DomainList.vue:42 src/views/user/User.vue:37
 msgid "Updated at"
 msgstr "修改时间"
 
@@ -1061,6 +1089,9 @@ msgctxt "Project"
 msgid "License"
 msgstr "开源许可"
 
+#~ msgid "Domain"
+#~ msgstr "域名"
+
 #~ msgid "Do you want to reload Nginx?"
 #~ msgstr "你需要重载 Nginx 吗?"
 

BIN
frontend/src/language/zh_TW/app.mo


+ 62 - 31
frontend/src/language/zh_TW/app.po

@@ -21,8 +21,8 @@ msgstr "關於"
 msgid "Access Logs"
 msgstr "訪問日誌"
 
-#: src/views/cert/Cert.vue:78 src/views/config/config.ts:36
-#: src/views/domain/DomainList.vue:47 src/views/user/User.vue:43
+#: src/views/cert/Cert.vue:74 src/views/config/config.ts:36
+#: src/views/domain/DomainList.vue:48 src/views/user/User.vue:43
 msgid "Action"
 msgstr "操作"
 
@@ -87,11 +87,11 @@ msgstr "作者"
 msgid "Auto"
 msgstr "自動"
 
-#: src/views/cert/Cert.vue:41 src/views/domain/cert/ChangeCert.vue:35
+#: src/views/cert/Cert.vue:37 src/views/domain/cert/ChangeCert.vue:35
 msgid "Auto Cert"
 msgstr "自動更新"
 
-#: src/views/cert/Cert.vue:8
+#: src/views/cert/Cert.vue:9
 msgid "Auto cert is enabled, please do not modify this certification."
 msgstr "自動證書已啟用,請不要修改此證書。"
 
@@ -99,6 +99,10 @@ msgstr "自動證書已啟用,請不要修改此證書。"
 msgid "Auto Refresh"
 msgstr "自動刷新"
 
+#: src/views/cert/Cert.vue:27
+msgid "Auto-Cert Log"
+msgstr "自動證書日誌"
+
 #: src/views/domain/cert/IssueCert.vue:71
 msgid "Auto-renewal disabled for %{name}"
 msgstr "已關閉 %{name} 自動續簽"
@@ -152,7 +156,7 @@ msgstr "此憑證已過期"
 msgid "Certificate is valid"
 msgstr "此憑證有效"
 
-#: src/views/cert/Cert.vue:12 src/views/domain/cert/Cert.vue:35
+#: src/views/cert/Cert.vue:34 src/views/domain/cert/Cert.vue:35
 msgid "Certificate Status"
 msgstr "憑證狀態"
 
@@ -174,10 +178,14 @@ msgstr "再次檢查"
 #: src/views/domain/ngx_conf/directive/DirectiveEditorItem.vue:34
 #: src/views/domain/ngx_conf/LocationEditor.vue:35
 #: src/views/domain/ngx_conf/LocationEditor.vue:52
-#: src/views/domain/ngx_conf/NgxConfigEditor.vue:180
+#: src/views/domain/ngx_conf/NgxConfigEditor.vue:181
 msgid "Comments"
 msgstr "註釋"
 
+#: src/views/cert/Cert.vue:32
+msgid "Config Name"
+msgstr "配置名稱"
+
 #: src/views/domain/ngx_conf/ConfigTemplate.vue:61
 msgid "Config Templates"
 msgstr "配置模板"
@@ -255,7 +263,13 @@ msgstr "儀表盤"
 msgid "Database (Optional, default: database)"
 msgstr "資料庫 (可選,預設: database)"
 
-#: src/components/StdDataDisplay/StdTable.vue:528
+#: src/components/StdDataDisplay/StdTable.vue:31
+#: src/components/StdDataDisplay/StdTable.vue:32
+#: src/components/StdDataDisplay/StdTable.vue:37
+#: src/components/StdDataDisplay/StdTable.vue:50
+#: src/components/StdDataDisplay/StdTable.vue:52
+#: src/components/StdDataDisplay/StdTable.vue:53
+#: src/components/StdDataDisplay/StdTable.vue:57
 #: src/views/domain/DomainList.vue:19 src/views/domain/DomainList.vue:20
 #: src/views/domain/DomainList.vue:21 src/views/domain/DomainList.vue:28
 #: src/views/domain/DomainList.vue:32
@@ -266,7 +280,7 @@ msgstr "刪除"
 msgid "Delete ID: %{id}"
 msgstr "刪除 ID: %{id}"
 
-#: src/views/domain/DomainList.vue:81
+#: src/views/domain/DomainList.vue:82
 msgid "Delete site: %{site_name}"
 msgstr "刪除站點:%{site_name}"
 
@@ -301,15 +315,15 @@ msgstr "指令"
 msgid "Disable auto-renewal failed for %{name}"
 msgstr "關閉 %{name} 自動續簽失敗"
 
-#: src/views/cert/Cert.vue:51 src/views/domain/cert/ChangeCert.vue:45
+#: src/views/cert/Cert.vue:47 src/views/domain/cert/ChangeCert.vue:45
 #: src/views/domain/DomainEdit.vue:10 src/views/domain/DomainEdit.vue:9
-#: src/views/domain/DomainList.vue:16 src/views/domain/DomainList.vue:34
+#: src/views/domain/DomainList.vue:16 src/views/domain/DomainList.vue:35
 #: src/views/domain/DomainList.vue:7 src/views/domain/DomainList.vue:8
 #: src/views/domain/DomainList.vue:9
 msgid "Disabled"
 msgstr "禁用"
 
-#: src/views/domain/DomainEdit.vue:146 src/views/domain/DomainList.vue:69
+#: src/views/domain/DomainEdit.vue:146 src/views/domain/DomainList.vue:70
 msgid "Disabled successfully"
 msgstr "禁用成功"
 
@@ -317,14 +331,14 @@ msgstr "禁用成功"
 msgid "Disk IO"
 msgstr "磁碟 IO"
 
-#: src/views/cert/Cert.vue:32
-msgid "Domain"
-msgstr "網域"
-
 #: src/views/domain/DomainAdd.vue:58
 msgid "Domain Config Created Successfully"
 msgstr "域名配置文件創建成功"
 
+#: src/views/cert/Cert.vue:21
+msgid "Domains list is empty, try to reopen auto-cert for %{config}"
+msgstr "域列表為空,請嘗試重新打開 %{config} 的自動證書"
+
 #: src/language/constants.ts:26
 msgid "Download latest release error"
 msgstr "下載最新版本錯誤"
@@ -335,9 +349,14 @@ msgstr "正在下載最新版本"
 
 #: src/views/domain/DomainList.vue:14 src/views/domain/DomainList.vue:15
 #: src/views/domain/DomainList.vue:16 src/views/domain/DomainList.vue:23
+#: src/views/domain/SiteDuplicate.vue:2
 msgid "Duplicate"
 msgstr "複製"
 
+#: src/views/domain/SiteDuplicate.vue:43
+msgid "Duplicated successfully"
+msgstr "複製成功"
+
 #: src/views/domain/DomainEdit.vue:4 src/views/domain/DomainEdit.vue:5
 msgid "Edit %{n}"
 msgstr "編輯 %{n}"
@@ -366,16 +385,16 @@ msgstr "啟用失敗"
 msgid "Enable TLS"
 msgstr "啟用 TLS"
 
-#: src/views/cert/Cert.vue:48 src/views/domain/cert/ChangeCert.vue:42
+#: src/views/cert/Cert.vue:44 src/views/domain/cert/ChangeCert.vue:42
 #: src/views/domain/DomainEdit.vue:43 src/views/domain/DomainEdit.vue:6
 #: src/views/domain/DomainEdit.vue:7 src/views/domain/DomainList.vue:10
 #: src/views/domain/DomainList.vue:11 src/views/domain/DomainList.vue:12
-#: src/views/domain/DomainList.vue:19 src/views/domain/DomainList.vue:31
+#: src/views/domain/DomainList.vue:19 src/views/domain/DomainList.vue:32
 msgid "Enabled"
 msgstr "啟用"
 
 #: src/views/domain/DomainAdd.vue:47 src/views/domain/DomainEdit.vue:137
-#: src/views/domain/DomainList.vue:59
+#: src/views/domain/DomainList.vue:60
 msgid "Enabled successfully"
 msgstr "啟用成功"
 
@@ -407,11 +426,11 @@ msgstr "過期時間: %{date}"
 msgid "Export"
 msgstr "導出"
 
-#: src/views/domain/DomainEdit.vue:149 src/views/domain/DomainList.vue:73
+#: src/views/domain/DomainEdit.vue:149 src/views/domain/DomainList.vue:74
 msgid "Failed to disable %{msg}"
 msgstr "禁用失敗 %{msg}"
 
-#: src/views/domain/DomainEdit.vue:140 src/views/domain/DomainList.vue:63
+#: src/views/domain/DomainEdit.vue:140 src/views/domain/DomainList.vue:64
 msgid "Failed to enable %{msg}"
 msgstr "啟用失敗 %{msg}"
 
@@ -599,7 +618,7 @@ msgstr "多行指令"
 
 #: src/views/cert/Cert.vue:16 src/views/config/config.ts:9
 #: src/views/domain/cert/ChangeCert.vue:19 src/views/domain/DomainEdit.vue:46
-#: src/views/domain/DomainList.vue:15
+#: src/views/domain/DomainList.vue:16 src/views/domain/SiteDuplicate.vue:5
 msgid "Name"
 msgstr "名稱"
 
@@ -722,6 +741,12 @@ msgstr "執行核心升級錯誤"
 msgid "Performing core upgrade"
 msgstr "正在執行核心升級"
 
+#: src/views/domain/SiteDuplicate.vue:28
+msgid ""
+"Please input name, this will be used as the filename of the new "
+"configuration!"
+msgstr "請輸入名稱,這將作為新配置的文件名!"
+
 #: src/views/other/Install.vue:36
 msgid "Please input your E-mail!"
 msgstr "請輸入您的郵箱!"
@@ -866,9 +891,8 @@ msgstr "上傳"
 #: src/components/StdDataDisplay/StdTable.vue:168
 #: src/components/StdDataDisplay/StdTable.vue:343
 #: src/components/StdDataDisplay/StdTable.vue:463
-#: src/views/config/ConfigEdit.vue:32 src/views/domain/DomainEdit.vue:87
-#: src/views/domain/DomainList.vue:83 src/views/other/Install.vue:71
-#: src/views/preference/Preference.vue:41
+#: src/views/config/ConfigEdit.vue:32 src/views/domain/DomainList.vue:84
+#: src/views/other/Install.vue:71 src/views/preference/Preference.vue:41
 msgid "Server error"
 msgstr "伺服器錯誤"
 
@@ -897,23 +921,23 @@ msgstr "網站日誌"
 msgid "Sites List"
 msgstr "站點列表"
 
-#: src/views/cert/Cert.vue:65
+#: src/views/cert/Cert.vue:61
 msgid "SSL Certificate Key Path"
 msgstr "SSL 證書密鑰路徑"
 
-#: src/views/cert/Cert.vue:58
+#: src/views/cert/Cert.vue:54
 msgid "SSL Certificate Path"
 msgstr "SSL證書路徑"
 
-#: src/views/cert/Cert.vue:19
+#: src/views/cert/Cert.vue:41
 msgid "SSL Certification Content"
 msgstr "SSL認證內容"
 
-#: src/views/cert/Cert.vue:22
+#: src/views/cert/Cert.vue:44
 msgid "SSL Certification Key Content"
 msgstr "SSL 證書密鑰內容"
 
-#: src/views/domain/DomainList.vue:24
+#: src/views/domain/DomainList.vue:25
 msgid "Status"
 msgstr "狀態"
 
@@ -975,12 +999,16 @@ msgstr "用戶名或密碼不正確"
 msgid "Theme"
 msgstr "外觀樣式"
 
+#: src/views/cert/Cert.vue:15
+msgid "This auto-cert item is invalid, please remove it."
+msgstr "此自動證書項無效,請將其刪除。"
+
 #: src/views/config/config.ts:14
 msgid "Type"
 msgstr "類型"
 
-#: src/views/cert/Cert.vue:72 src/views/config/config.ts:29
-#: src/views/domain/DomainList.vue:41 src/views/user/User.vue:37
+#: src/views/cert/Cert.vue:68 src/views/config/config.ts:29
+#: src/views/domain/DomainList.vue:42 src/views/user/User.vue:37
 msgid "Updated at"
 msgstr "修改時間"
 
@@ -1063,6 +1091,9 @@ msgctxt "Project"
 msgid "License"
 msgstr "開源軟體授權條款"
 
+#~ msgid "Domain"
+#~ msgstr "網域"
+
 #~ msgid "Do you want to reload Nginx?"
 #~ msgstr "你想重載 Nginx 嗎?"
 

+ 1 - 1
frontend/src/version.json

@@ -1 +1 @@
-{"version":"1.7.5","build_id":81,"total_build":151}
+{"version":"1.7.6","build_id":82,"total_build":152}

+ 32 - 14
frontend/src/views/cert/Cert.vue

@@ -2,15 +2,15 @@
 import {useGettext} from 'vue3-gettext'
 import {input} from '@/components/StdDataEntry'
 import {customRender, datetime} from '@/components/StdDataDisplay/StdTableTransformer'
-import {h} from 'vue'
 import {Badge} from 'ant-design-vue'
 import cert from '@/api/cert'
 import StdCurd from '@/components/StdDataDisplay/StdCurd.vue'
 import Template from '@/views/template/Template.vue'
 import CodeEditor from '@/components/CodeEditor/CodeEditor.vue'
 import CertInfo from '@/views/domain/cert/CertInfo.vue'
+import {h} from 'vue'
 
-const {$gettext} = useGettext()
+const {$gettext, interpolate} = useGettext()
 
 const columns = [{
     title: () => $gettext('Name'),
@@ -29,14 +29,10 @@ const columns = [{
     },
     search: true
 }, {
-    title: () => $gettext('Domain'),
-    dataIndex: 'domain',
+    title: () => $gettext('Config Name'),
+    dataIndex: 'filename',
     sorter: true,
-    pithy: true,
-    edit: {
-        type: input
-    },
-    search: true
+    pithy: true
 }, {
     title: () => $gettext('Auto Cert'),
     dataIndex: 'auto_cert',
@@ -85,11 +81,33 @@ const columns = [{
               row-key="name"
     >
         <template #beforeEdit="{data}">
-            <div v-if="data.auto_cert===1" style="margin-bottom: 15px">
-                <a-alert
-                    :message="$gettext('Auto cert is enabled, please do not modify this certification.')" type="info"
-                    show-icon/>
-            </div>
+            <template v-if="data.auto_cert===1">
+                <div style="margin-bottom: 15px">
+                    <a-alert
+                        :message="$gettext('Auto cert is enabled, please do not modify this certification.')"
+                        type="info"
+                        show-icon/>
+                </div>
+                <div v-if="!data.filename" style="margin-bottom: 15px">
+                    <a-alert
+                        :message="$gettext('This auto-cert item is invalid, please remove it.')"
+                        type="error"
+                        show-icon/>
+                </div>
+                <div v-else-if="!data.domains" style="margin-bottom: 15px">
+                    <a-alert
+                        :message="interpolate($gettext('Domains list is empty, try to reopen auto-cert for %{config}'), {config: data.filename})"
+                        type="error"
+                        show-icon/>
+                </div>
+                <div v-if="data.log" style="margin-bottom: 15px">
+                    <a-form layout="vertical">
+                        <a-form-item :label="$gettext('Auto-Cert Log')">
+                            <p>{{ data.log }}</p>
+                        </a-form-item>
+                    </a-form>
+                </div>
+            </template>
             <a-form layout="vertical" v-if="data.certificate_info">
                 <a-form-item :label="$gettext('Certificate Status')">
                     <cert-info :cert="data.certificate_info"/>

+ 1 - 5
frontend/src/views/dashboard/DashBoard.vue

@@ -164,7 +164,7 @@ function wsOnMessage(m: { data: any }) {
                     </p>
                     <p v-if="cpu_info">
                         {{ $gettext('CPU:') + ' ' }}
-                        <span class="cpu-model">{{ cpu_info[0]?.modelName }}</span>
+                        <span class="cpu-model">{{ cpu_info[0]?.modelName || 'core' }}</span>
                         <span class="cpu-mhz">{{ (cpu_info[0]?.mhz / 1000).toFixed(2) + 'GHz' }}</span>
                         * {{ cpu_info.length }}
                     </p>
@@ -303,10 +303,6 @@ function wsOnMessage(m: { data: any }) {
     }
 }
 
-.os-platform {
-    text-transform: capitalize;
-}
-
 .load-avg-describe {
     @media (max-width: 1600px) and (min-width: 1200px) {
         display: none;

+ 1 - 1
frontend/src/views/domain/DomainEdit.vue

@@ -25,7 +25,7 @@ watch(route, () => {
 const update = ref(0)
 
 const ngx_config = reactive({
-    filename: '',
+    name: '',
     upstreams: [],
     servers: []
 })

+ 2 - 1
frontend/src/views/domain/cert/Cert.vue

@@ -7,7 +7,7 @@ import ChangeCert from '@/views/domain/cert/ChangeCert.vue'
 
 const {$gettext} = useGettext()
 
-const props = defineProps(['directivesMap', 'current_server_directives', 'enabled', 'cert_info'])
+const props = defineProps(['config_name', 'directivesMap', 'current_server_directives', 'enabled', 'cert_info'])
 
 const emit = defineEmits(['callback', 'update:enabled'])
 
@@ -38,6 +38,7 @@ const enabled = computed({
         <change-cert :directives-map="props.directivesMap"/>
 
         <issue-cert
+            :config_name="config_name"
             :current_server_directives="props.current_server_directives"
             :directives-map="props.directivesMap"
             v-model:enabled="enabled"

+ 7 - 7
frontend/src/views/domain/cert/IssueCert.vue

@@ -1,6 +1,6 @@
 <script setup lang="ts">
 import {useGettext} from 'vue3-gettext'
-import {computed, h, nextTick, onMounted, ref, VNode, watch} from 'vue'
+import {computed, nextTick, ref, watch} from 'vue'
 import {message} from 'ant-design-vue'
 import domain from '@/api/domain'
 import websocket from '@/lib/websocket'
@@ -8,7 +8,7 @@ import Template from '@/views/template/Template.vue'
 
 const {$gettext, interpolate} = useGettext()
 
-const props = defineProps(['directivesMap', 'current_server_directives', 'enabled'])
+const props = defineProps(['config_name', 'directivesMap', 'current_server_directives', 'enabled'])
 
 const emit = defineEmits(['changeEnabled', 'callback', 'update:enabled'])
 
@@ -50,7 +50,7 @@ function job() {
             })
         }
     }).then(() => {
-        issue_cert(name.value, callback)
+        issue_cert(props.config_name, name.value, callback)
     })
 }
 
@@ -61,13 +61,13 @@ function callback(ssl_certificate: string, ssl_certificate_key: string) {
 
 function change_auto_cert(r: boolean) {
     if (r) {
-        domain.add_auto_cert(name.value).then(() => {
+        domain.add_auto_cert(props.config_name, {domains: name.value.trim().split(' ')}).then(() => {
             message.success(interpolate($gettext('Auto-renewal enabled for %{name}'), {name: name.value}))
         }).catch(e => {
             message.error(e.message ?? interpolate($gettext('Enable auto-renewal failed for %{name}'), {name: name.value}))
         })
     } else {
-        domain.remove_auto_cert(name.value).then(() => {
+        domain.remove_auto_cert(props.config_name).then(() => {
             message.success(interpolate($gettext('Auto-renewal disabled for %{name}'), {name: name.value}))
         }).catch(e => {
             message.error(e.message ?? interpolate($gettext('Disable auto-renewal failed for %{name}'), {name: name.value}))
@@ -86,7 +86,7 @@ function log(msg: string) {
     (logContainer.value as any as Element).scroll({top: 320, left: 0, behavior: 'smooth'})
 }
 
-const issue_cert = async (server_name: string, callback: Function) => {
+const issue_cert = async (config_name: string, server_name: string, callback: Function) => {
     progressStatus.value = 'active'
     modalClosable.value = false
     modalVisible.value = true
@@ -95,7 +95,7 @@ const issue_cert = async (server_name: string, callback: Function) => {
 
     log($gettext('Getting the certificate, please wait...'))
 
-    const ws = websocket('/api/cert/issue', false)
+    const ws = websocket(`/api/domain/${config_name}/cert`, false)
 
     ws.onopen = () => {
         ws.send(JSON.stringify({

+ 1 - 0
frontend/src/views/domain/ngx_conf/NgxConfigEditor.vue

@@ -168,6 +168,7 @@ watch(current_server_index, () => {
                     <template v-if="current_support_ssl&&enabled">
                         <cert
                             v-if="current_support_ssl"
+                            :config_name="ngx_config.name"
                             :cert_info="props.cert_info?.[k]"
                             :current_server_directives="current_server_directives"
                             :directives-map="directivesMap"

+ 1 - 1
frontend/version.json

@@ -1 +1 @@
-{"version":"1.7.5","build_id":81,"total_build":151}
+{"version":"1.7.6","build_id":82,"total_build":152}

+ 1 - 1
frontend/vite.config.ts

@@ -66,7 +66,7 @@ export default defineConfig({
     server: {
         proxy: {
             '/api': {
-                target: 'https://nginx.jackyu.cn/',
+                target: 'http://127.0.0.1:9001/',
                 changeOrigin: true,
                 secure: false,
                 ws: true

+ 1 - 0
go.mod

@@ -39,6 +39,7 @@ require (
 	github.com/jpillora/s3 v1.1.4 // indirect
 	github.com/json-iterator/go v1.1.9 // indirect
 	github.com/leodido/go-urn v1.2.0 // indirect
+	github.com/lib/pq v1.10.7 // indirect
 	github.com/mattn/go-isatty v0.0.12 // indirect
 	github.com/mattn/go-sqlite3 v1.14.5 // indirect
 	github.com/miekg/dns v1.1.40 // indirect

+ 2 - 0
go.sum

@@ -280,6 +280,8 @@ github.com/labstack/echo/v4 v4.1.11/go.mod h1:i541M3Fj6f76NZtHSj7TXnyM8n2gaodfvf
 github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k=
 github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y=
 github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
+github.com/lib/pq v1.10.7 h1:p7ZhMD+KsSRozJr34udlUrhboJwWAgCg34+/ZZNvZZw=
+github.com/lib/pq v1.10.7/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
 github.com/linode/linodego v0.25.3/go.mod h1:GSBKPpjoQfxEfryoCRcgkuUOCuVtGHWhzI8OMdycNTE=
 github.com/liquidweb/go-lwApi v0.0.0-20190605172801-52a4864d2738/go.mod h1:0sYF9rMXb0vlG+4SzdiGMXHheCZxjguMq+Zb4S2BfBs=
 github.com/liquidweb/go-lwApi v0.0.5/go.mod h1:0sYF9rMXb0vlG+4SzdiGMXHheCZxjguMq+Zb4S2BfBs=

+ 1 - 1
main.go

@@ -54,7 +54,7 @@ func prog(state overseer.State) {
 	}
 
 	s := gocron.NewScheduler(time.UTC)
-	job, err := s.Every(1).Hour().SingletonMode().Do(cert.AutoCert)
+	job, err := s.Every(1).Minute().SingletonMode().Do(cert.AutoObtain)
 
 	if err != nil {
 		log.Fatalf("AutoCert Job: %v, Err: %v\n", job, err)

+ 1 - 1
resources/demo/app.ini

@@ -1,6 +1,6 @@
 [server]
 HttpPort = 9000
-RunMode = debug
+RunMode = release
 JwtSecret = 2E1CE615-BB15-44F5-B5BE-6B5DA3581D0F
 Email = test@jackyu.cn
 HTTPChallengePort = 9180

+ 13 - 0
resources/development/entrypoint.sh

@@ -0,0 +1,13 @@
+#!/bin/bash
+
+if [ "$(ls -A /etc/nginx)" = "" ]; then
+    echo "Initialing Nginx config dir"
+    cp -rp /usr/etc/nginx/* /etc/nginx/
+    echo "Initialed Nginx config dir"
+fi
+
+echo "export PATH=$PATH:/usr/local/go/bin:$(go env GOPATH)/bin" >> ~/.profile
+source ~/.profile
+
+nginx
+cd /app && air

+ 25 - 0
resources/development/nginx/fastcgi_params

@@ -0,0 +1,25 @@
+
+fastcgi_param  QUERY_STRING       $query_string;
+fastcgi_param  REQUEST_METHOD     $request_method;
+fastcgi_param  CONTENT_TYPE       $content_type;
+fastcgi_param  CONTENT_LENGTH     $content_length;
+
+fastcgi_param  SCRIPT_NAME        $fastcgi_script_name;
+fastcgi_param  REQUEST_URI        $request_uri;
+fastcgi_param  DOCUMENT_URI       $document_uri;
+fastcgi_param  DOCUMENT_ROOT      $document_root;
+fastcgi_param  SERVER_PROTOCOL    $server_protocol;
+fastcgi_param  REQUEST_SCHEME     $scheme;
+fastcgi_param  HTTPS              $https if_not_empty;
+
+fastcgi_param  GATEWAY_INTERFACE  CGI/1.1;
+fastcgi_param  SERVER_SOFTWARE    nginx/$nginx_version;
+
+fastcgi_param  REMOTE_ADDR        $remote_addr;
+fastcgi_param  REMOTE_PORT        $remote_port;
+fastcgi_param  SERVER_ADDR        $server_addr;
+fastcgi_param  SERVER_PORT        $server_port;
+fastcgi_param  SERVER_NAME        $server_name;
+
+# PHP only, required if PHP was built with --enable-force-cgi-redirect
+fastcgi_param  REDIRECT_STATUS    200;

+ 99 - 0
resources/development/nginx/mime.types

@@ -0,0 +1,99 @@
+
+types {
+    text/html                                        html htm shtml;
+    text/css                                         css;
+    text/xml                                         xml;
+    image/gif                                        gif;
+    image/jpeg                                       jpeg jpg;
+    application/javascript                           js;
+    application/atom+xml                             atom;
+    application/rss+xml                              rss;
+
+    text/mathml                                      mml;
+    text/plain                                       txt;
+    text/vnd.sun.j2me.app-descriptor                 jad;
+    text/vnd.wap.wml                                 wml;
+    text/x-component                                 htc;
+
+    image/avif                                       avif;
+    image/png                                        png;
+    image/svg+xml                                    svg svgz;
+    image/tiff                                       tif tiff;
+    image/vnd.wap.wbmp                               wbmp;
+    image/webp                                       webp;
+    image/x-icon                                     ico;
+    image/x-jng                                      jng;
+    image/x-ms-bmp                                   bmp;
+
+    font/woff                                        woff;
+    font/woff2                                       woff2;
+
+    application/java-archive                         jar war ear;
+    application/json                                 json;
+    application/mac-binhex40                         hqx;
+    application/msword                               doc;
+    application/pdf                                  pdf;
+    application/postscript                           ps eps ai;
+    application/rtf                                  rtf;
+    application/vnd.apple.mpegurl                    m3u8;
+    application/vnd.google-earth.kml+xml             kml;
+    application/vnd.google-earth.kmz                 kmz;
+    application/vnd.ms-excel                         xls;
+    application/vnd.ms-fontobject                    eot;
+    application/vnd.ms-powerpoint                    ppt;
+    application/vnd.oasis.opendocument.graphics      odg;
+    application/vnd.oasis.opendocument.presentation  odp;
+    application/vnd.oasis.opendocument.spreadsheet   ods;
+    application/vnd.oasis.opendocument.text          odt;
+    application/vnd.openxmlformats-officedocument.presentationml.presentation
+                                                     pptx;
+    application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
+                                                     xlsx;
+    application/vnd.openxmlformats-officedocument.wordprocessingml.document
+                                                     docx;
+    application/vnd.wap.wmlc                         wmlc;
+    application/wasm                                 wasm;
+    application/x-7z-compressed                      7z;
+    application/x-cocoa                              cco;
+    application/x-java-archive-diff                  jardiff;
+    application/x-java-jnlp-file                     jnlp;
+    application/x-makeself                           run;
+    application/x-perl                               pl pm;
+    application/x-pilot                              prc pdb;
+    application/x-rar-compressed                     rar;
+    application/x-redhat-package-manager             rpm;
+    application/x-sea                                sea;
+    application/x-shockwave-flash                    swf;
+    application/x-stuffit                            sit;
+    application/x-tcl                                tcl tk;
+    application/x-x509-ca-cert                       der pem crt;
+    application/x-xpinstall                          xpi;
+    application/xhtml+xml                            xhtml;
+    application/xspf+xml                             xspf;
+    application/zip                                  zip;
+
+    application/octet-stream                         bin exe dll;
+    application/octet-stream                         deb;
+    application/octet-stream                         dmg;
+    application/octet-stream                         iso img;
+    application/octet-stream                         msi msp msm;
+
+    audio/midi                                       mid midi kar;
+    audio/mpeg                                       mp3;
+    audio/ogg                                        ogg;
+    audio/x-m4a                                      m4a;
+    audio/x-realaudio                                ra;
+
+    video/3gpp                                       3gpp 3gp;
+    video/mp2t                                       ts;
+    video/mp4                                        mp4;
+    video/mpeg                                       mpeg mpg;
+    video/quicktime                                  mov;
+    video/webm                                       webm;
+    video/x-flv                                      flv;
+    video/x-m4v                                      m4v;
+    video/x-mng                                      mng;
+    video/x-ms-asf                                   asx asf;
+    video/x-ms-wmv                                   wmv;
+    video/x-msvideo                                  avi;
+}

+ 1 - 0
resources/development/nginx/modules

@@ -0,0 +1 @@
+/usr/lib/nginx/modules

+ 32 - 0
resources/development/nginx/nginx.conf

@@ -0,0 +1,32 @@
+
+user  nginx;
+worker_processes  auto;
+
+error_log  /var/log/nginx/error.log notice;
+pid        /var/run/nginx.pid;
+
+
+events {
+    worker_connections  1024;
+}
+
+
+http {
+    include       /etc/nginx/mime.types;
+    default_type  application/octet-stream;
+
+    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
+                      '$status $body_bytes_sent "$http_referer" '
+                      '"$http_user_agent" "$http_x_forwarded_for"';
+
+    access_log  /var/log/nginx/access.log  main;
+
+    sendfile        on;
+    #tcp_nopush     on;
+
+    keepalive_timeout  65;
+
+    #gzip  on;
+
+    include /etc/nginx/sites-enabled/*;
+}

+ 17 - 0
resources/development/nginx/scgi_params

@@ -0,0 +1,17 @@
+
+scgi_param  REQUEST_METHOD     $request_method;
+scgi_param  REQUEST_URI        $request_uri;
+scgi_param  QUERY_STRING       $query_string;
+scgi_param  CONTENT_TYPE       $content_type;
+
+scgi_param  DOCUMENT_URI       $document_uri;
+scgi_param  DOCUMENT_ROOT      $document_root;
+scgi_param  SCGI               1;
+scgi_param  SERVER_PROTOCOL    $server_protocol;
+scgi_param  REQUEST_SCHEME     $scheme;
+scgi_param  HTTPS              $https if_not_empty;
+
+scgi_param  REMOTE_ADDR        $remote_addr;
+scgi_param  REMOTE_PORT        $remote_port;
+scgi_param  SERVER_PORT        $server_port;
+scgi_param  SERVER_NAME        $server_name;

+ 28 - 0
resources/development/nginx/sites-available/amstourship.jackyu.cn

@@ -0,0 +1,28 @@
+server {
+    listen 80;
+    listen [::]:80;
+    server_name amstourship.jackyu.cn t.jackyu.cn;
+    root /var/www/amstourship;
+    index index.html;
+    location /.well-known/acme-challenge {
+        proxy_set_header Host $host;
+        proxy_set_header X-Real_IP $remote_addr;
+        proxy_set_header X-Forwarded-For $remote_addr:$remote_port;
+        proxy_pass http://127.0.0.1:5002;
+    }
+}
+server {
+    listen 443 ssl http2;
+    listen [::]:443 ssl http2;
+    server_name amstourship.jackyu.cn t.jackyu.cn;
+    ssl_certificate /etc/nginx/ssl/amstourship.jackyu.cn_t.jackyu.cn/fullchain.cer;
+    ssl_certificate_key /etc/nginx/ssl/amstourship.jackyu.cn_t.jackyu.cn/private.key;
+    root /var/www/amstourship;
+    index index.html;
+    location /.well-known/acme-challenge {
+        proxy_set_header Host $host;
+        proxy_set_header X-Real_IP $remote_addr;
+        proxy_set_header X-Forwarded-For $remote_addr:$remote_port;
+        proxy_pass http://127.0.0.1:5002;
+    }
+}

+ 84 - 0
resources/development/nginx/sites-available/homework.jackyu.cn

@@ -0,0 +1,84 @@
+server {
+	listen	80;
+	listen	[::]:80;
+
+	server_name	homework.jackyu.cn;
+  	# rewrite ^(.*)$  https://$host$1 permanent;
+  	return 307 https://$server_name$request_uri;
+}
+
+server {
+	listen	443 ssl http2;
+	listen	[::]:443 ssl http2;
+
+	server_name	homework.jackyu.cn;
+
+	ssl_certificate	/etc/nginx/ssl/jackyu.cn/alpha/jackyu.cn_server_cert.pem;
+  	ssl_certificate_key	/etc/nginx/ssl/jackyu.cn/alpha/jackyu.cn_key.pem;
+
+	root	/var/www/homework/frontend;
+
+	# Add index.php to the list if you are using PHP
+	index	index.html;
+
+	location / {
+		# First attempt to serve request as file, then
+		# as directory, then fall back to displaying a 404.
+                index index.html;
+		try_files $uri $uri/ /index.html;
+	}
+    
+    location /student {
+      index manage.html;
+      try_files $uri $uri/ /student.html;
+	}
+    
+    location /teacher {
+      index manage.html;
+      try_files $uri $uri/ /teacher.html;
+	}
+    
+    location /admin {
+      index admin.html;
+      try_files $uri $uri/ /admin.html;
+	}
+            
+    location ^~/upload/ {
+		alias /var/www/homework/api/upload/;
+	}
+	include			error_json;
+	location /api/	 {
+    	proxy_http_version 1.1;
+        proxy_set_header Upgrade $http_upgrade;
+        proxy_set_header Connection upgrade;
+    
+		proxy_pass         http://127.0.0.1:9008/;
+		proxy_redirect     off;
+
+		proxy_set_header   Host                 $host;
+		proxy_set_header   X-Real-IP            $remote_addr;
+		proxy_set_header   X-Forwarded-For      $proxy_add_x_forwarded_for;
+		proxy_set_header   X-Forwarded-Proto    $scheme;
+        client_max_body_size 1000m;
+	}
+    
+    location /zigbee-pi	 {
+    	alias /var/www/zigbee-pi/frontend/;
+    	index index.html;
+	}
+    
+    location /zigbee-pi/api/	 {
+    	proxy_http_version 1.1;
+        proxy_set_header Upgrade $http_upgrade;
+        proxy_set_header Connection upgrade;
+    
+		proxy_pass         http://127.0.0.1:9200/;
+		proxy_redirect     off;
+
+		proxy_set_header   Host                 $host;
+		proxy_set_header   X-Real-IP            $remote_addr;
+		proxy_set_header   X-Forwarded-For      $proxy_add_x_forwarded_for;
+		proxy_set_header   X-Forwarded-Proto    $scheme;
+        client_max_body_size 1000m;
+	}
+}

+ 27 - 0
resources/development/nginx/sites-available/nginx.jackyu.cn

@@ -0,0 +1,27 @@
+map $http_upgrade $connection_upgrade {
+    default upgrade;
+    '' close;
+}
+server {
+    listen 80;
+    listen [::]:80;
+    server_name nginx.jackyu.cn;
+    rewrite ^(.*)$ https://$host$1 permanent;
+}
+server {
+    listen 443 ssl http2;
+    listen [::]:443 ssl http2;
+    server_name nginx.jackyu.cn;
+    ssl_certificate /etc/nginx/ssl/jackyu.cn/alpha/jackyu.cn_server_cert.pem;
+    ssl_certificate_key /etc/nginx/ssl/jackyu.cn/alpha/jackyu.cn_key.pem;
+    location / {
+        proxy_set_header Host $host;
+        proxy_set_header X-Real-IP $remote_addr;
+        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+        proxy_set_header X-Forwarded-Proto $scheme;
+        proxy_http_version 1.1;
+        proxy_set_header Upgrade $http_upgrade;
+        proxy_set_header Connection $connection_upgrade;
+        proxy_pass http://127.0.0.1:9000/;
+    }
+}

+ 26 - 0
resources/development/nginx/sites-available/qi.jackyu.cn

@@ -0,0 +1,26 @@
+server {
+	listen	80;
+	listen	[::]:80;
+	server_name	qi.jackyu.cn;
+	rewrite ^(.*)$  https://$host$1 permanent;
+
+}
+
+server {
+	server_name	qi.jackyu.cn;
+	ssl_certificate	/etc/nginx/ssl/jackyu.cn/alpha/jackyu.cn_server_cert.pem;
+	ssl_certificate_key	/etc/nginx/ssl/jackyu.cn/alpha/jackyu.cn_key.pem;
+	listen	443 ssl;
+	listen	[::]:443 ssl;
+
+	location / {
+		proxy_pass         http://127.0.0.1:5001/;
+		proxy_redirect     off;
+		proxy_set_header   Host                 $host;
+		proxy_set_header   X-Real-IP            $remote_addr;
+		proxy_set_header   X-Forwarded-For      $proxy_add_x_forwarded_for;
+		proxy_set_header   X-Forwarded-Proto    $scheme;
+	}
+
+}
+

+ 1 - 0
resources/development/nginx/sites-enabled/amstourship.jackyu.cn

@@ -0,0 +1 @@
+/etc/nginx/sites-available/amstourship.jackyu.cn

+ 64 - 0
resources/development/nginx/ssl/amstourship.jackyu.cn/fullchain.cer

@@ -0,0 +1,64 @@
+-----BEGIN CERTIFICATE-----
+MIIFhjCCBG6gAwIBAgITAP/nxy+CumZyaQpnT/Ii3HgcgTANBgkqhkiG9w0BAQsF
+ADBDMQswCQYDVQQGEwJVUzESMBAGA1UEChMJZ29vZCBndXlzMSAwHgYDVQQDExdD
+QSBpbnRlcm1lZGlhdGUgKFJTQSkgQTAeFw0yMzAyMTUwMDUxMDhaFw0yMzA1MTYw
+MDUxMDdaMCAxHjAcBgNVBAMTFWFtc3RvdXJzaGlwLmphY2t5dS5jbjCCASIwDQYJ
+KoZIhvcNAQEBBQADggEPADCCAQoCggEBAM8xJTQQiLnb9BPViM8eihPlkJD472vV
+g6u5W6NpmGkvqOmKFcC3IY8D77ATa5IlKeVtgomWAtzQDbQ3+Q8p5gqhotQjLzpp
+xjfK+B9zsrILqDGFpw1n8WKGf/QG6/EsOdecO0ntuhwG80OpsQIDMwjchszC4BPc
+vheCAsRV8pflzEqEqbOzeKcPHa6hDO2Bs70k2zP5SuqTl+GjNzeLKaFpJa5F+rrk
++o4uO8pQOvEGaYW/0i9cN3YKmzfp2yuJY63OsbQ8idSXWTCuSayJhnAjYjgkgcRz
+PWiTNsaQgnPATiZqN81hxq63ISb+kLieq+yMkNAO7xVuItOPDIgawRsCAwEAAaOC
+ApQwggKQMA4GA1UdDwEB/wQEAwIFoDAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYB
+BQUHAwIwDAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQU2rpC6XfeKM4RRGOOIsXp5MxO
+7yEwKwYDVR0jBCQwIoAgYjWGqKXC1CgUyRtbS1bZxpMqaNdKnoY33nyaZdVEQ/Iw
+cQYIKwYBBQUHAQEEZTBjMCIGCCsGAQUFBzABhhZodHRwOi8vMTI3LjAuMC4xOjQw
+MDIvMD0GCCsGAQUFBzAChjFodHRwOi8vMTI3LjAuMC4xOjQwMDEvYWlhL2lzc3Vl
+ci82NjA1NDQwNDk4MzY5NzQxMCAGA1UdEQQZMBeCFWFtc3RvdXJzaGlwLmphY2t5
+dS5jbjAnBgNVHR8EIDAeMBygGqAYhhZodHRwOi8vZXhhbXBsZS5jb20vY3JsMEAG
+A1UdIAQ5MDcwCAYGZ4EMAQIBMCsGAyoDBDAkMCIGCCsGAQUFBwIBFhZodHRwOi8v
+ZXhhbXBsZS5jb20vY3BzMIIBAwYKKwYBBAHWeQIEAgSB9ASB8QDvAHYAOJiMlNA1
+mMOTLd/pI7q68npCDrlsQeFaqAwasPwEvQMAAAGGUsSzKQAABAMARzBFAiEAslVV
+aGQEFOseeWMIcWStkdY5fpgfc/mU8MNr2l2GjRQCIHu/O4lsj4LBupnVN8jwDBZn
+fta/KSsrQ7LvC0SfE5NJAHUAe90gTyc4KkZpHv2pgeSOS224Md6/21UmWIxRF9mX
+veIAAAGGUsSzKgAABAMARjBEAiAOOoRiMeKGRpQckOp04laE6GxN+h+FKeKRCwZ8
+wrWm3QIgCUFPPuJswg1VumJIsLKGdFPgCG8gJM4jVVFdbO/1+5YwDQYJKoZIhvcN
+AQELBQADggEBAEwax/yM6yfUW/ctPvMeiGh4bSaO9xWbQbKrOw5/+K+upqgXUSh0
+Qo0WzcrLZzT9ecp3K6KTbph58IrPGxWmPZo92W9sm2tAR3NIOtSwjCyfeBou0TCO
+yBeHwiJv2JbEZndUAqaz2cd2PfpPbNOWTS9ag6nWRDPPW42VoSYU0EFwzvGfFswA
+vww+gC4kzHjsZuR7T/0qsnvJ/1IHH8KDfzki0TnZ9PAhUvPFBAgxwDElLqUFkSmA
+hc5BJfFrGInnKe1s+DL5oL6oc9RmDdlLV8v0Ita39GlaOs3rdvt611qON9S1Ea6z
+lkGadI0bxxv5GlN+UhNRxyTKseyAsBdzY64=
+-----END CERTIFICATE-----
+
+-----BEGIN CERTIFICATE-----
+MIIFZzCCA0+gAwIBAgIQHPTBy0utaJ82mHJs9V3u8zANBgkqhkiG9w0BAQsFADA5
+MQswCQYDVQQGEwJVUzESMBAGA1UEChMJZ29vZCBndXlzMRYwFAYDVQQDEw1DQSBy
+b290IChSU0EpMB4XDTIwMDEwMTEyMDAwMFoXDTQwMDEwMTEyMDAwMFowQzELMAkG
+A1UEBhMCVVMxEjAQBgNVBAoTCWdvb2QgZ3V5czEgMB4GA1UEAxMXQ0EgaW50ZXJt
+ZWRpYXRlIChSU0EpIEEwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCk
+f1rlGJeno27J8UAltWo8PopRsTP93Il6+L+SScaOUQsM+TCbTO5EJ4xC+d3Unp8v
+iZRLAB1/lGhHh/Uifzov2ux2sa9J4kxlfCxVaaCx6maOs9KnfGUug2hcUCh1oUVv
+zD9X9VkWdBdR+kKJvYqWJlU/EJxEa5ERjFp591LQBpR7ksZrsbLvXeywqVS3ek8s
+d7w+ZqpYOfo6DNLl5aEJlk6F6CiSjmT352n8dnsOEIEL+bOusLhP5F8pED85geU5
+rijc38fZ+gfZAVVenz7kqBh7ld6qT5inIM4uQa7oCuFX2dZ0jqm5TFBBtQp9dkFv
+WFz9kEb/CVJr1IsTdp1PAgMBAAGjggFfMIIBWzAOBgNVHQ8BAf8EBAMCAYYwHQYD
+VR0lBBYwFAYIKwYBBQUHAwIGCCsGAQUFBwMBMBIGA1UdEwEB/wQIMAYBAf8CAQAw
+KQYDVR0OBCIEIGI1hqilwtQoFMkbW0tW2caTKmjXSp6GN958mmXVREPyMCsGA1Ud
+IwQkMCKAINmvCHaWHo5MD5lKL52otAsr/TsCX5Hwfy5euQ6zIAWEMFgGCCsGAQUF
+BwEBBEwwSjAjBggrBgEFBQcwAYYXaHR0cDovL2V4YW1wbGUuY29tL29jc3AwIwYI
+KwYBBQUHMAKGF2h0dHA6Ly9leGFtcGxlLmNvbS9yb290MCcGA1UdHwQgMB4wHKAa
+oBiGFmh0dHA6Ly9leGFtcGxlLmNvbS9jcmwwOwYDVR0gBDQwMjAEBgIqAzAqBgIt
+BjAkMCIGCCsGAQUFBwIBFhZodHRwOi8vZXhhbXBsZS5jb20vY3BzMA0GCSqGSIb3
+DQEBCwUAA4ICAQCTLNQlCzHynESAvtPRV1FPaOQhx01RofwS/0Zg3IH5oXxSC98C
+n2L0xHN1gCaJai9XutrFtMCjeBmese48QoPa8MxrB1UpmZ1AuFOQAfHWJZbYPp0V
+PxgY34W9Onb+JPnKTbL9ofKUV0aX67eJ5KKFD1G2z+y9Lz1oA3yJpGzqOY/JCWYz
+q46ik0bmgcGfol6F/T5hoE8pZk8Wr+nNUpSuOSNp7c/g2/pKDRWK8trTrG3owtaJ
+LbQc+W4e97AtTg6DGvR5gftar/+4g2o0xhKSnep+s/bf5NFXVDCTvCmemrbR8Hr7
+NLDKXWuGMoMKIxhyPX6ttpU2Um3rQ1rCQbJ5yWIREZvbdaeK8HSRE3GYE71Z3n/0
+0Kmtg2BKGkrJzcqUSG4o+9mdSjhJ65J76ri5tVQby7Ai7W2KlNjpdI6GYtejUAlf
+vZz5N0e2X36XLPZ8tz04Ix9KLHXMEuA7w/aOglH1Lei+PPp7kBjvXAL66soqCTqu
+49yNbPPGIjGO453+jNzxhbeimh6a5/Fwd4SjsdSBe8AwIGGZTzLiIzCNM5OmcoUf
+Tl5RrVXau4DvX5KvfwOLusl/uJH+7oETJlbi8+fNn2ioYfHg5/Tu3zKZw/Y+6wSA
+LsOIJrFmJEgIBUnWp/B1ZC6TeIokmw5FeJsY1UnFDWsPVcax2T/tg6BZ5Q==
+-----END CERTIFICATE-----

+ 27 - 0
resources/development/nginx/ssl/amstourship.jackyu.cn/private.key

@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEoAIBAAKCAQEAzzElNBCIudv0E9WIzx6KE+WQkPjva9WDq7lbo2mYaS+o6YoV
+wLchjwPvsBNrkiUp5W2CiZYC3NANtDf5DynmCqGi1CMvOmnGN8r4H3OysguoMYWn
+DWfxYoZ/9Abr8Sw515w7Se26HAbzQ6mxAgMzCNyGzMLgE9y+F4ICxFXyl+XMSoSp
+s7N4pw8drqEM7YGzvSTbM/lK6pOX4aM3N4spoWklrkX6uuT6ji47ylA68QZphb/S
+L1w3dgqbN+nbK4ljrc6xtDyJ1JdZMK5JrImGcCNiOCSBxHM9aJM2xpCCc8BOJmo3
+zWHGrrchJv6QuJ6r7IyQ0A7vFW4i048MiBrBGwIDAQABAoIBAFjb3/PLX4gugCh9
+fRYvJ9LOssiqKqyZvsXIUjfsUrRR/2Rhl2C2acsXShW+znS4QZam98QgNQwUorv3
+AXbyZjmLBvVqPZgUwzEseGusyCe5/iF3C6UhPhIeUwQKb+hYKlqBI6BaN2ZLyMrt
+HQAa9SlIwvtQXJ/IHTKhzKP9pHRmETVQCBtJBEl/rxXAZ+Uq+qQde8PJ/UYMTu3f
+Q+r2Rr+pfITJcHBBvB8w03u0hsCpLyN5tJzSKjAUF86lHYEEUh/KM6PrMerYHvsF
+h60VrusUF/jzankkeQFJKsmY043aI3aYjx9+ghj6DOXLhAX52ubB4etsALYpbpTn
+CF6dySkCgYEA66OCWsFl9UrhkAPT+LuKzCs7li+LMCjGjMhXAj0DxVUceG6ZT32H
+rDsEpSPHeL72vAaZIxcnroAJBkz39vYfr49hHUo//GFELiJTlP5S0joZeJvDYhF2
+D1NKzDvLvJq/HkkJor5DGMfTXYbSS8w3tAxacs6o59I1pseOxK333gUCgYEA4Rhf
+73IYw2A44OSBVuy1PxH1zam1AELpUKDHNo1gT9CJhFLiPYuL4Y6jdQl3PVgD23fW
+uTxyu9rC7a/s6HFTx3tIGvUBFKsTLTagHvmZBnx9SKTT//PZELPoObKesNHy3cIS
+NcUoHnRtbvoTIfZXGBOFiByKjz6JrRKNpO4zLJ8Cf1wLgt/wq+MlXPbkh+ihWZZn
+TN9dswEc7HIRz+sP6mkq1fQ1P63NWzHr2+SmFUUVU0wfR7JtWRLQ8LWFW1cczUPG
+viiy2Eu9suRShFOvBKsaBtkV/zxPlc5mutEMiokh6YXrAdiQeaU0aVwfTgZNv3SV
+Xr95+oCTnGGJkjtBuZECgYAXy4vYtCcKAFIC9CT4G23NjQh/BUGtFWW0sEpe6fMU
+fOcMpfZkqyvOXFXLOYTxZyyBNtnsGwfF9ApHNiHFMLW6kL2+m8fRd4Q/c1bZ72jN
+1rsnwsTmedCog1w5PTipWIhkCPAD3yOulIA9CxKatH/ge6/SA7JiEipbsWpn1QZz
+4QKBgCqLsJMIcgZJcJUqor/ta45CppMfM3FXvWyX6tnTK/WyHUHQPN+wfGVB9lNa
+KEHYbF3CfAy2F/RS+aQn8WLCXaurZWsH9spUOPCugj8xtYQ5GFDNrWK9fJclsXFl
+YH0dpcI88pAaKl0ymrjyVp0kFUiaf5JQFKit5UGwGZTtWyvC
+-----END RSA PRIVATE KEY-----

+ 64 - 0
resources/development/nginx/ssl/amstourship.jackyu.cn_t.jackyu.cn/fullchain.cer

@@ -0,0 +1,64 @@
+-----BEGIN CERTIFICATE-----
+MIIFkzCCBHugAwIBAgITAP9BpIHmYRZFpLiNFNwEvnS6lTANBgkqhkiG9w0BAQsF
+ADBDMQswCQYDVQQGEwJVUzESMBAGA1UEChMJZ29vZCBndXlzMSAwHgYDVQQDExdD
+QSBpbnRlcm1lZGlhdGUgKFJTQSkgQTAeFw0yMzAyMTUwMzM5NDhaFw0yMzA1MTYw
+MzM5NDdaMCAxHjAcBgNVBAMTFWFtc3RvdXJzaGlwLmphY2t5dS5jbjCCASIwDQYJ
+KoZIhvcNAQEBBQADggEPADCCAQoCggEBAM31HTeIJ6CcbK1Y1MKb1Pylt2/mXvt8
+M4quWNJjJWWelaRCpe/BD/mBCM9RsQUnf5F8m5OW6QAesoM/QiewTAjf06o5WHfX
+SinH9yMKqXMny4nUBI7U5jJEXbiV82HBsuieU5YldeBCMilefjIG3UNfwNqcA40E
+Miq9xZAGQx9lmabno0iyQlltFyYb1l+4CX0SBm5ygBOyyhb4tReZEB1Sn54n5gtZ
+bK4ZvYgPvKg4wHT6f9A+D4GqE1kZPuGqAKkvAmt8whlEUfXL2zlfNOFfuReQNujs
+FrwnJgSVHa0UPhQEB3zowMjmBSqGZnJap9zXS0W8Eu+D5EWFKUVuRy0CAwEAAaOC
+AqEwggKdMA4GA1UdDwEB/wQEAwIFoDAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYB
+BQUHAwIwDAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQUxKOLPUnFetKJF7xySU+hY/32
+3FwwKwYDVR0jBCQwIoAgYjWGqKXC1CgUyRtbS1bZxpMqaNdKnoY33nyaZdVEQ/Iw
+cQYIKwYBBQUHAQEEZTBjMCIGCCsGAQUFBzABhhZodHRwOi8vMTI3LjAuMC4xOjQw
+MDIvMD0GCCsGAQUFBzAChjFodHRwOi8vMTI3LjAuMC4xOjQwMDEvYWlhL2lzc3Vl
+ci82NjA1NDQwNDk4MzY5NzQxMC0GA1UdEQQmMCSCFWFtc3RvdXJzaGlwLmphY2t5
+dS5jboILdC5qYWNreXUuY24wJwYDVR0fBCAwHjAcoBqgGIYWaHR0cDovL2V4YW1w
+bGUuY29tL2NybDBABgNVHSAEOTA3MAgGBmeBDAECATArBgMqAwQwJDAiBggrBgEF
+BQcCARYWaHR0cDovL2V4YW1wbGUuY29tL2NwczCCAQMGCisGAQQB1nkCBAIEgfQE
+gfEA7wB2AFLU6MpxhMjJJFwzEHovC5eeKDMFhyNCIngxCrNdv03eAAABhlNfHncA
+AAQDAEcwRQIhAL84cecGwG4bYGHcCxGVwaLPgISazBGaIcOP/11sY78gAiAA+1/3
+XSk0hPTv5zWYwqJIcI0ajGOeiIfaLwpFxnW+DwB1ADiYjJTQNZjDky3f6SO6uvJ6
+Qg65bEHhWqgMGrD8BL0DAAABhlNfHncAAAQDAEYwRAIgS3rH0/r0zBshQN9LwmWv
+JadxbPEJtQuWjhyH/5gln4cCICZlS/B2qYkOZJzQQkjRgnfHrmUc1vRHFNBEGuRR
+HGX2MA0GCSqGSIb3DQEBCwUAA4IBAQBLP7i7PPn3mUtmsYoguW07lQa8abjsHirs
+r5TgfOpWLVFQ8ASWuIu/OTLdKrbfTXseZibLKlPU+Zoz+HF8V3lnCmgXbnlQo/ex
++uEDPkLYyXuWe96nssiVgtUAmkSWQOEwhIz0xtWNgskgRVt2c4CihYbqBB3uXLL1
+TubIFHAizRKcQ/JUDfSnieN4R5tX1MIw/TnUmNxj3KMtF1OHsqGo9Pt2z8oRWGf5
+kx2HtgFyIigkTIlUB5TFmlv5HLAtE4H3cc2NSYZ397WXhil2mTqPTBLRXQJwzQ7C
+5tenpmzazqPOOu23QJaA94a7UeQtowcEDkSMoCe/G31leeEUbRjt
+-----END CERTIFICATE-----
+
+-----BEGIN CERTIFICATE-----
+MIIFZzCCA0+gAwIBAgIQHPTBy0utaJ82mHJs9V3u8zANBgkqhkiG9w0BAQsFADA5
+MQswCQYDVQQGEwJVUzESMBAGA1UEChMJZ29vZCBndXlzMRYwFAYDVQQDEw1DQSBy
+b290IChSU0EpMB4XDTIwMDEwMTEyMDAwMFoXDTQwMDEwMTEyMDAwMFowQzELMAkG
+A1UEBhMCVVMxEjAQBgNVBAoTCWdvb2QgZ3V5czEgMB4GA1UEAxMXQ0EgaW50ZXJt
+ZWRpYXRlIChSU0EpIEEwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCk
+f1rlGJeno27J8UAltWo8PopRsTP93Il6+L+SScaOUQsM+TCbTO5EJ4xC+d3Unp8v
+iZRLAB1/lGhHh/Uifzov2ux2sa9J4kxlfCxVaaCx6maOs9KnfGUug2hcUCh1oUVv
+zD9X9VkWdBdR+kKJvYqWJlU/EJxEa5ERjFp591LQBpR7ksZrsbLvXeywqVS3ek8s
+d7w+ZqpYOfo6DNLl5aEJlk6F6CiSjmT352n8dnsOEIEL+bOusLhP5F8pED85geU5
+rijc38fZ+gfZAVVenz7kqBh7ld6qT5inIM4uQa7oCuFX2dZ0jqm5TFBBtQp9dkFv
+WFz9kEb/CVJr1IsTdp1PAgMBAAGjggFfMIIBWzAOBgNVHQ8BAf8EBAMCAYYwHQYD
+VR0lBBYwFAYIKwYBBQUHAwIGCCsGAQUFBwMBMBIGA1UdEwEB/wQIMAYBAf8CAQAw
+KQYDVR0OBCIEIGI1hqilwtQoFMkbW0tW2caTKmjXSp6GN958mmXVREPyMCsGA1Ud
+IwQkMCKAINmvCHaWHo5MD5lKL52otAsr/TsCX5Hwfy5euQ6zIAWEMFgGCCsGAQUF
+BwEBBEwwSjAjBggrBgEFBQcwAYYXaHR0cDovL2V4YW1wbGUuY29tL29jc3AwIwYI
+KwYBBQUHMAKGF2h0dHA6Ly9leGFtcGxlLmNvbS9yb290MCcGA1UdHwQgMB4wHKAa
+oBiGFmh0dHA6Ly9leGFtcGxlLmNvbS9jcmwwOwYDVR0gBDQwMjAEBgIqAzAqBgIt
+BjAkMCIGCCsGAQUFBwIBFhZodHRwOi8vZXhhbXBsZS5jb20vY3BzMA0GCSqGSIb3
+DQEBCwUAA4ICAQCTLNQlCzHynESAvtPRV1FPaOQhx01RofwS/0Zg3IH5oXxSC98C
+n2L0xHN1gCaJai9XutrFtMCjeBmese48QoPa8MxrB1UpmZ1AuFOQAfHWJZbYPp0V
+PxgY34W9Onb+JPnKTbL9ofKUV0aX67eJ5KKFD1G2z+y9Lz1oA3yJpGzqOY/JCWYz
+q46ik0bmgcGfol6F/T5hoE8pZk8Wr+nNUpSuOSNp7c/g2/pKDRWK8trTrG3owtaJ
+LbQc+W4e97AtTg6DGvR5gftar/+4g2o0xhKSnep+s/bf5NFXVDCTvCmemrbR8Hr7
+NLDKXWuGMoMKIxhyPX6ttpU2Um3rQ1rCQbJ5yWIREZvbdaeK8HSRE3GYE71Z3n/0
+0Kmtg2BKGkrJzcqUSG4o+9mdSjhJ65J76ri5tVQby7Ai7W2KlNjpdI6GYtejUAlf
+vZz5N0e2X36XLPZ8tz04Ix9KLHXMEuA7w/aOglH1Lei+PPp7kBjvXAL66soqCTqu
+49yNbPPGIjGO453+jNzxhbeimh6a5/Fwd4SjsdSBe8AwIGGZTzLiIzCNM5OmcoUf
+Tl5RrVXau4DvX5KvfwOLusl/uJH+7oETJlbi8+fNn2ioYfHg5/Tu3zKZw/Y+6wSA
+LsOIJrFmJEgIBUnWp/B1ZC6TeIokmw5FeJsY1UnFDWsPVcax2T/tg6BZ5Q==
+-----END CERTIFICATE-----

+ 27 - 0
resources/development/nginx/ssl/amstourship.jackyu.cn_t.jackyu.cn/private.key

@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEowIBAAKCAQEAzfUdN4gnoJxsrVjUwpvU/KW3b+Ze+3wziq5Y0mMlZZ6VpEKl
+78EP+YEIz1GxBSd/kXybk5bpAB6ygz9CJ7BMCN/TqjlYd9dKKcf3IwqpcyfLidQE
+jtTmMkRduJXzYcGy6J5TliV14EIyKV5+MgbdQ1/A2pwDjQQyKr3FkAZDH2WZpuej
+SLJCWW0XJhvWX7gJfRIGbnKAE7LKFvi1F5kQHVKfnifmC1lsrhm9iA+8qDjAdPp/
+0D4PgaoTWRk+4aoAqS8Ca3zCGURR9cvbOV804V+5F5A26OwWvCcmBJUdrRQ+FAQH
+fOjAyOYFKoZmclqn3NdLRbwS74PkRYUpRW5HLQIDAQABAoIBAGCuuRlxfBDEfave
+cHouxwwXCwanoVzzEAsBD0csLckHaf3jH5xuB/67WRRhp/Tgdt0oHqxpAlYBExHT
+p02UUA02bVmSc/pGAVWdvmEfxy38t2qrMbyPKsTcHRbipY71a/QRJvHsAerViFCt
+QBZh7IqNL64v4ObY2mgAD/ctSWM56gqkFR70X7HpMKQ9Z8Q9iT5IKeFdVtfOcdSq
+1HuAiz4p8v8IOzLHTKruY5OTYM9uyr02FtPoWsoZSyzSjaC8BpO+wnFlVsDXDBNv
+/Kgqyetj6Iqo6cInj8dExpdnCZXqO5j4FXgHABLgjgiIJV+L3F0KY1PbO9NejMEh
+TvKgZiECgYEA6aIGaUtdoPXWY1o4sEjHy8rwQnBs25JPx+4+HOC5wHrsn03yTmXC
+cjKapIySQhasekQZGQk++H4cRyToqR0izIfkqEmOLfec2kKy7FgcSXzs2PKH66om
+4mvYSWVQj0r3SwYyG6UHJJCeY8i+RCLMkEXSkNPvhEQXg7zCI2oMpSUCgYEA4azO
+yfHvUKnJbGW+r3ujbszn7IlsobVTL0Rf4nHLSuKrW0v/OFg9lsjsm6IJyWV1H5KO
+NNNVzpubAESvPujAwiYfSeFLjK67hbEVSaVLp+5ubsGKf+0BDPKh3+yVJ6abHmFo
+lqzqUlZhZteMp8BN+n3fwR/W5RJFAju9o3F972kCgYBq3IlCMA6rSMa+us2jFCcO
+t8wdF38zD0EemYIfg0pzF8aTNvvVkAXYZf0FtqZPRD+vzOYN5YS/9C7K+77PW1xH
+YQDdWIeHzvIXgtqD7+lAU7uhn4075Z/TgLB1IbovUIK79iGFM36I4v0PdwpP7/rR
+Ip6lT8sGHH8E6pmByUfvYQKBgQDhG1r6HZY1w+bjdWoL6SxQ7Zu6Wio9830SfQWX
+/yJlhEyhOOFP9tUYfztk0vEoL0fxQmMPVm8VNCoczmZwPgNoplY3f7+4iOMMrGMr
+nvIkhLUrTWs1x9dwbuYBUyBE+O9qEogdJEZn8zodN41aF3yxDLYREg1tWhfz7ltv
+mVmhYQKBgBFtHgvmlw/fA5P4dfn/xnOFYwz2v+f5dxcD9DXP9Hu53m7mVPTEUC1t
+2SiBaaUMqTiOflW8Aq9qHHvOPNoO1GrGt4oUbhrAzKwxmkiCUyspYTRBOWHbLjW0
+07Mu40Y8I0WqEVenAGIsHfrsXzvdzat0bijsAt+P1LMliRjK7qhh
+-----END RSA PRIVATE KEY-----

+ 17 - 0
resources/development/nginx/uwsgi_params

@@ -0,0 +1,17 @@
+
+uwsgi_param  QUERY_STRING       $query_string;
+uwsgi_param  REQUEST_METHOD     $request_method;
+uwsgi_param  CONTENT_TYPE       $content_type;
+uwsgi_param  CONTENT_LENGTH     $content_length;
+
+uwsgi_param  REQUEST_URI        $request_uri;
+uwsgi_param  PATH_INFO          $document_uri;
+uwsgi_param  DOCUMENT_ROOT      $document_root;
+uwsgi_param  SERVER_PROTOCOL    $server_protocol;
+uwsgi_param  REQUEST_SCHEME     $scheme;
+uwsgi_param  HTTPS              $https if_not_empty;
+
+uwsgi_param  REMOTE_ADDR        $remote_addr;
+uwsgi_param  REMOTE_PORT        $remote_port;
+uwsgi_param  SERVER_PORT        $server_port;
+uwsgi_param  SERVER_NAME        $server_name;

+ 9 - 0
resources/development/sources.list

@@ -0,0 +1,9 @@
+# 默认注释了源码镜像以提高 apt update 速度,如有需要可自行取消注释
+deb http://mirrors.tuna.tsinghua.edu.cn/ubuntu/ jammy main restricted universe multiverse
+# deb-src https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ jammy main restricted universe multiverse
+deb http://mirrors.tuna.tsinghua.edu.cn/ubuntu/ jammy-updates main restricted universe multiverse
+# deb-src https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ jammy-updates main restricted universe multiverse
+deb http://mirrors.tuna.tsinghua.edu.cn/ubuntu/ jammy-backports main restricted universe multiverse
+# deb-src https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ jammy-backports main restricted universe multiverse
+deb http://mirrors.tuna.tsinghua.edu.cn/ubuntu/ jammy-security main restricted universe multiverse
+# deb-src https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ jammy-security main restricted universe multiverse

+ 305 - 307
server/api/cert.go

@@ -1,352 +1,350 @@
 package api
 
 import (
-	"github.com/0xJacky/Nginx-UI/server/model"
-	"github.com/0xJacky/Nginx-UI/server/pkg/cert"
-	"github.com/0xJacky/Nginx-UI/server/pkg/nginx"
-	"github.com/gin-gonic/gin"
-	"github.com/gorilla/websocket"
-	"github.com/spf13/cast"
-	"log"
-	"net/http"
-	"os"
-	"path/filepath"
-	"strings"
+    "github.com/0xJacky/Nginx-UI/server/model"
+    "github.com/0xJacky/Nginx-UI/server/pkg/cert"
+    "github.com/0xJacky/Nginx-UI/server/pkg/nginx"
+    "github.com/gin-gonic/gin"
+    "github.com/gorilla/websocket"
+    "github.com/spf13/cast"
+    "log"
+    "net/http"
+    "os"
+    "path/filepath"
+    "strings"
 )
 
 const (
-	Success = "success"
-	Info    = "info"
-	Error   = "error"
+    Success = "success"
+    Info    = "info"
+    Error   = "error"
 )
 
 type IssueCertResponse struct {
-	Status            string `json:"status"`
-	Message           string `json:"message"`
-	SSLCertificate    string `json:"ssl_certificate,omitempty"`
-	SSLCertificateKey string `json:"ssl_certificate_key,omitempty"`
+    Status            string `json:"status"`
+    Message           string `json:"message"`
+    SSLCertificate    string `json:"ssl_certificate,omitempty"`
+    SSLCertificateKey string `json:"ssl_certificate_key,omitempty"`
 }
 
 func handleIssueCertLogChan(conn *websocket.Conn, logChan chan string) {
-	defer func() {
-		if err := recover(); err != nil {
-			log.Println("api.handleIssueCertLogChan recover", err)
-		}
-	}()
+    defer func() {
+        if err := recover(); err != nil {
+            log.Println("api.handleIssueCertLogChan recover", err)
+        }
+    }()
 
-	for logString := range logChan {
+    for logString := range logChan {
 
-		err := conn.WriteJSON(IssueCertResponse{
-			Status:  Info,
-			Message: logString,
-		})
+        err := conn.WriteJSON(IssueCertResponse{
+            Status:  Info,
+            Message: logString,
+        })
 
-		if err != nil {
-			log.Println("Error handleIssueCertLogChan", err)
-			return
-		}
+        if err != nil {
+            log.Println("Error handleIssueCertLogChan", err)
+            return
+        }
 
-	}
+    }
 }
 
 func IssueCert(c *gin.Context) {
-	var upGrader = websocket.Upgrader{
-		CheckOrigin: func(r *http.Request) bool {
-			return true
-		},
-	}
-
-	// upgrade http to websocket
-	ws, err := upGrader.Upgrade(c.Writer, c.Request, nil)
-	if err != nil {
-		log.Println(err)
-		return
-	}
-
-	defer func(ws *websocket.Conn) {
-		err := ws.Close()
-		if err != nil {
-			log.Println("defer websocket close err", err)
-		}
-	}(ws)
-
-	// read
-	var buffer struct {
-		ServerName []string `json:"server_name"`
-	}
-
-	err = ws.ReadJSON(&buffer)
-
-	if err != nil {
-		log.Println(err)
-		return
-	}
-
-	logChan := make(chan string, 1)
-	errChan := make(chan error, 1)
-
-	go cert.IssueCert(buffer.ServerName, logChan, errChan)
-
-	domain := strings.Join(buffer.ServerName, "_")
-
-	go handleIssueCertLogChan(ws, logChan)
-
-	// block, unless errChan closed
-	for err = range errChan {
-		log.Println("Error cert.IssueCert", err)
-
-		err = ws.WriteJSON(IssueCertResponse{
-			Status:  Error,
-			Message: err.Error(),
-		})
-
-		if err != nil {
-			log.Println("Error WriteJSON", err)
-			return
-		}
-
-		return
-	}
-
-	close(logChan)
-
-	sslCertificatePath := nginx.GetConfPath("ssl", domain, "fullchain.cer")
-	sslCertificateKeyPath := nginx.GetConfPath("ssl", domain, "private.key")
-
-	certModel, err := model.FirstOrCreateCert(domain)
-
-	if err != nil {
-		log.Println(err)
-	}
-
-	err = certModel.Updates(&model.Cert{
-		SSLCertificatePath:    sslCertificatePath,
-		SSLCertificateKeyPath: sslCertificateKeyPath,
-	})
-
-	if err != nil {
-		log.Println(err)
-	}
-
-	err = ws.WriteJSON(IssueCertResponse{
-		Status:            Success,
-		Message:           "Issued certificate successfully",
-		SSLCertificate:    sslCertificatePath,
-		SSLCertificateKey: sslCertificateKeyPath,
-	})
-
-	if err != nil {
-		log.Println(err)
-		return
-	}
+    var upGrader = websocket.Upgrader{
+        CheckOrigin: func(r *http.Request) bool {
+            return true
+        },
+    }
+
+    // upgrade http to websocket
+    ws, err := upGrader.Upgrade(c.Writer, c.Request, nil)
+    if err != nil {
+        log.Println(err)
+        return
+    }
+
+    defer func(ws *websocket.Conn) {
+        err := ws.Close()
+        if err != nil {
+            log.Println("defer websocket close err", err)
+        }
+    }(ws)
+
+    // read
+    var buffer struct {
+        ServerName []string `json:"server_name"`
+    }
+
+    err = ws.ReadJSON(&buffer)
+
+    if err != nil {
+        log.Println(err)
+        return
+    }
+
+    certModel, err := model.FirstOrCreateCert(c.Param("name"))
+
+    if err != nil {
+        log.Println(err)
+    }
+
+    logChan := make(chan string, 1)
+    errChan := make(chan error, 1)
+
+    go cert.IssueCert(buffer.ServerName, logChan, errChan)
+
+    go handleIssueCertLogChan(ws, logChan)
+
+    // block, until errChan closes
+    for err = range errChan {
+        errLog := &cert.AutoCertErrorLog{}
+        errLog.SetCertModel(&certModel)
+        errLog.Exit("issue cert", err)
+
+        err = ws.WriteJSON(IssueCertResponse{
+            Status:  Error,
+            Message: err.Error(),
+        })
+
+        if err != nil {
+            log.Println("Error WriteJSON", err)
+            return
+        }
+
+        return
+    }
+
+    certDirName := strings.Join(buffer.ServerName, "_")
+    sslCertificatePath := nginx.GetConfPath("ssl", certDirName, "fullchain.cer")
+    sslCertificateKeyPath := nginx.GetConfPath("ssl", certDirName, "private.key")
+
+    err = certModel.Updates(&model.Cert{
+        Domains:               buffer.ServerName,
+        SSLCertificatePath:    sslCertificatePath,
+        SSLCertificateKeyPath: sslCertificateKeyPath,
+    })
+
+    if err != nil {
+        log.Println(err)
+        err = ws.WriteJSON(IssueCertResponse{
+            Status:  Error,
+            Message: err.Error(),
+        })
+        return
+    }
+
+    certModel.ClearLog()
+
+    err = ws.WriteJSON(IssueCertResponse{
+        Status:            Success,
+        Message:           "Issued certificate successfully",
+        SSLCertificate:    sslCertificatePath,
+        SSLCertificateKey: sslCertificateKeyPath,
+    })
+
+    if err != nil {
+        log.Println(err)
+        return
+    }
 
 }
 
 func GetCertList(c *gin.Context) {
-	certList := model.GetCertList(c.Query("name"), c.Query("domain"))
+    certList := model.GetCertList(c.Query("name"), c.Query("domain"))
 
-	c.JSON(http.StatusOK, gin.H{
-		"data": certList,
-	})
+    c.JSON(http.StatusOK, gin.H{
+        "data": certList,
+    })
 }
 
-func getCert(c *gin.Context, certModel model.Cert) {
-	type resp struct {
-		model.Cert
-		SSLCertification    string           `json:"ssl_certification"`
-		SSLCertificationKey string           `json:"ssl_certification_key"`
-		CertificateInfo     *CertificateInfo `json:"certificate_info,omitempty"`
-	}
-
-	var sslCertificationBytes, sslCertificationKeyBytes []byte
-	var certificateInfo *CertificateInfo
-	if certModel.SSLCertificatePath != "" {
-		if _, err := os.Stat(certModel.SSLCertificatePath); err == nil {
-			sslCertificationBytes, _ = os.ReadFile(certModel.SSLCertificatePath)
-		}
-
-		pubKey, err := cert.GetCertInfo(certModel.SSLCertificatePath)
-
-		if err != nil {
-			ErrHandler(c, err)
-			return
-		}
-
-		certificateInfo = &CertificateInfo{
-			SubjectName: pubKey.Subject.CommonName,
-			IssuerName:  pubKey.Issuer.CommonName,
-			NotAfter:    pubKey.NotAfter,
-			NotBefore:   pubKey.NotBefore,
-		}
-	}
-
-	if certModel.SSLCertificateKeyPath != "" {
-		if _, err := os.Stat(certModel.SSLCertificateKeyPath); err == nil {
-			sslCertificationKeyBytes, _ = os.ReadFile(certModel.SSLCertificateKeyPath)
-		}
-	}
-
-	c.JSON(http.StatusOK, resp{
-		certModel,
-		string(sslCertificationBytes),
-		string(sslCertificationKeyBytes),
-		certificateInfo,
-	})
+func getCert(c *gin.Context, certModel *model.Cert) {
+    type resp struct {
+        *model.Cert
+        SSLCertification    string           `json:"ssl_certification"`
+        SSLCertificationKey string           `json:"ssl_certification_key"`
+        CertificateInfo     *CertificateInfo `json:"certificate_info,omitempty"`
+    }
+
+    var sslCertificationBytes, sslCertificationKeyBytes []byte
+    var certificateInfo *CertificateInfo
+    if certModel.SSLCertificatePath != "" {
+        if _, err := os.Stat(certModel.SSLCertificatePath); err == nil {
+            sslCertificationBytes, _ = os.ReadFile(certModel.SSLCertificatePath)
+        }
+
+        pubKey, err := cert.GetCertInfo(certModel.SSLCertificatePath)
+
+        if err != nil {
+            ErrHandler(c, err)
+            return
+        }
+
+        certificateInfo = &CertificateInfo{
+            SubjectName: pubKey.Subject.CommonName,
+            IssuerName:  pubKey.Issuer.CommonName,
+            NotAfter:    pubKey.NotAfter,
+            NotBefore:   pubKey.NotBefore,
+        }
+    }
+
+    if certModel.SSLCertificateKeyPath != "" {
+        if _, err := os.Stat(certModel.SSLCertificateKeyPath); err == nil {
+            sslCertificationKeyBytes, _ = os.ReadFile(certModel.SSLCertificateKeyPath)
+        }
+    }
+
+    c.JSON(http.StatusOK, resp{
+        certModel,
+        string(sslCertificationBytes),
+        string(sslCertificationKeyBytes),
+        certificateInfo,
+    })
 }
 
 func GetCert(c *gin.Context) {
-	certModel, err := model.FirstCertByID(cast.ToInt(c.Param("id")))
+    certModel, err := model.FirstCertByID(cast.ToInt(c.Param("id")))
 
-	if err != nil {
-		ErrHandler(c, err)
-		return
-	}
+    if err != nil {
+        ErrHandler(c, err)
+        return
+    }
 
-	getCert(c, certModel)
+    getCert(c, &certModel)
 }
 
 func AddCert(c *gin.Context) {
-	var json struct {
-		Name                  string `json:"name"`
-		Domain                string `json:"domain" binding:"required"`
-		SSLCertificatePath    string `json:"ssl_certificate_path" binding:"required"`
-		SSLCertificateKeyPath string `json:"ssl_certificate_key_path" binding:"required"`
-		SSLCertification      string `json:"ssl_certification"`
-		SSLCertificationKey   string `json:"ssl_certification_key"`
-	}
-	if !BindAndValid(c, &json) {
-		return
-	}
-	certModel, err := model.FirstOrCreateCert(json.Domain)
-
-	if err != nil {
-		ErrHandler(c, err)
-		return
-	}
-
-	err = certModel.Updates(&model.Cert{
-		Name:                  json.Name,
-		Domain:                json.Domain,
-		SSLCertificatePath:    json.SSLCertificatePath,
-		SSLCertificateKeyPath: json.SSLCertificateKeyPath,
-	})
-
-	if err != nil {
-		ErrHandler(c, err)
-		return
-	}
-
-	err = os.MkdirAll(filepath.Dir(json.SSLCertificatePath), 0644)
-	if err != nil {
-		ErrHandler(c, err)
-		return
-	}
-
-	err = os.MkdirAll(filepath.Dir(json.SSLCertificateKeyPath), 0644)
-	if err != nil {
-		ErrHandler(c, err)
-		return
-	}
-
-	if json.SSLCertification != "" {
-		err = os.WriteFile(json.SSLCertificatePath, []byte(json.SSLCertification), 0644)
-		if err != nil {
-			ErrHandler(c, err)
-			return
-		}
-	}
-
-	if json.SSLCertificationKey != "" {
-		err = os.WriteFile(json.SSLCertificateKeyPath, []byte(json.SSLCertificationKey), 0644)
-		if err != nil {
-			ErrHandler(c, err)
-			return
-		}
-	}
-
-	getCert(c, certModel)
+    var json struct {
+        Name                  string `json:"name"`
+        SSLCertificatePath    string `json:"ssl_certificate_path" binding:"required"`
+        SSLCertificateKeyPath string `json:"ssl_certificate_key_path" binding:"required"`
+        SSLCertification      string `json:"ssl_certification"`
+        SSLCertificationKey   string `json:"ssl_certification_key"`
+    }
+    if !BindAndValid(c, &json) {
+        return
+    }
+    certModel := &model.Cert{
+        Name:                  json.Name,
+        SSLCertificatePath:    json.SSLCertificatePath,
+        SSLCertificateKeyPath: json.SSLCertificateKeyPath,
+    }
+
+    err := certModel.Insert()
+
+    if err != nil {
+        ErrHandler(c, err)
+        return
+    }
+
+    err = os.MkdirAll(filepath.Dir(json.SSLCertificatePath), 0644)
+    if err != nil {
+        ErrHandler(c, err)
+        return
+    }
+
+    err = os.MkdirAll(filepath.Dir(json.SSLCertificateKeyPath), 0644)
+    if err != nil {
+        ErrHandler(c, err)
+        return
+    }
+
+    if json.SSLCertification != "" {
+        err = os.WriteFile(json.SSLCertificatePath, []byte(json.SSLCertification), 0644)
+        if err != nil {
+            ErrHandler(c, err)
+            return
+        }
+    }
+
+    if json.SSLCertificationKey != "" {
+        err = os.WriteFile(json.SSLCertificateKeyPath, []byte(json.SSLCertificationKey), 0644)
+        if err != nil {
+            ErrHandler(c, err)
+            return
+        }
+    }
+
+    getCert(c, certModel)
 }
 
 func ModifyCert(c *gin.Context) {
-	id := cast.ToInt(c.Param("id"))
-	certModel, err := model.FirstCertByID(id)
-
-	var json struct {
-		Name                  string `json:"name"`
-		Domain                string `json:"domain" binding:"required"`
-		SSLCertificatePath    string `json:"ssl_certificate_path" binding:"required"`
-		SSLCertificateKeyPath string `json:"ssl_certificate_key_path" binding:"required"`
-		SSLCertification      string `json:"ssl_certification"`
-		SSLCertificationKey   string `json:"ssl_certification_key"`
-	}
-
-	if !BindAndValid(c, &json) {
-		return
-	}
-
-	if err != nil {
-		ErrHandler(c, err)
-		return
-	}
-
-	err = certModel.Updates(&model.Cert{
-		Name:                  json.Name,
-		Domain:                json.Domain,
-		SSLCertificatePath:    json.SSLCertificatePath,
-		SSLCertificateKeyPath: json.SSLCertificateKeyPath,
-	})
-
-	if err != nil {
-		ErrHandler(c, err)
-		return
-	}
-
-	err = os.MkdirAll(filepath.Dir(json.SSLCertificatePath), 0644)
-	if err != nil {
-		ErrHandler(c, err)
-		return
-	}
-
-	err = os.MkdirAll(filepath.Dir(json.SSLCertificateKeyPath), 0644)
-	if err != nil {
-		ErrHandler(c, err)
-		return
-	}
-
-	if json.SSLCertification != "" {
-		err = os.WriteFile(json.SSLCertificatePath, []byte(json.SSLCertification), 0644)
-		if err != nil {
-			ErrHandler(c, err)
-			return
-		}
-	}
-
-	if json.SSLCertificationKey != "" {
-		err = os.WriteFile(json.SSLCertificateKeyPath, []byte(json.SSLCertificationKey), 0644)
-		if err != nil {
-			ErrHandler(c, err)
-			return
-		}
-	}
-
-	GetCert(c)
+    id := cast.ToInt(c.Param("id"))
+    certModel, err := model.FirstCertByID(id)
+
+    var json struct {
+        Name                  string `json:"name"`
+        SSLCertificatePath    string `json:"ssl_certificate_path" binding:"required"`
+        SSLCertificateKeyPath string `json:"ssl_certificate_key_path" binding:"required"`
+        SSLCertification      string `json:"ssl_certification"`
+        SSLCertificationKey   string `json:"ssl_certification_key"`
+    }
+
+    if !BindAndValid(c, &json) {
+        return
+    }
+
+    if err != nil {
+        ErrHandler(c, err)
+        return
+    }
+
+    err = certModel.Updates(&model.Cert{
+        Name:                  json.Name,
+        SSLCertificatePath:    json.SSLCertificatePath,
+        SSLCertificateKeyPath: json.SSLCertificateKeyPath,
+    })
+
+    if err != nil {
+        ErrHandler(c, err)
+        return
+    }
+
+    err = os.MkdirAll(filepath.Dir(json.SSLCertificatePath), 0644)
+    if err != nil {
+        ErrHandler(c, err)
+        return
+    }
+
+    err = os.MkdirAll(filepath.Dir(json.SSLCertificateKeyPath), 0644)
+    if err != nil {
+        ErrHandler(c, err)
+        return
+    }
+
+    if json.SSLCertification != "" {
+        err = os.WriteFile(json.SSLCertificatePath, []byte(json.SSLCertification), 0644)
+        if err != nil {
+            ErrHandler(c, err)
+            return
+        }
+    }
+
+    if json.SSLCertificationKey != "" {
+        err = os.WriteFile(json.SSLCertificateKeyPath, []byte(json.SSLCertificationKey), 0644)
+        if err != nil {
+            ErrHandler(c, err)
+            return
+        }
+    }
+
+    GetCert(c)
 }
 
 func RemoveCert(c *gin.Context) {
-	id := cast.ToInt(c.Param("id"))
-	certModel, err := model.FirstCertByID(id)
+    id := cast.ToInt(c.Param("id"))
+    certModel, err := model.FirstCertByID(id)
 
-	if err != nil {
-		ErrHandler(c, err)
-		return
-	}
+    if err != nil {
+        ErrHandler(c, err)
+        return
+    }
 
-	err = certModel.Remove()
+    err = certModel.Remove()
 
-	if err != nil {
-		ErrHandler(c, err)
-		return
-	}
+    if err != nil {
+        ErrHandler(c, err)
+        return
+    }
 
-	c.JSON(http.StatusNoContent, nil)
+    c.JSON(http.StatusNoContent, nil)
 }

+ 23 - 18
server/api/domain.go

@@ -104,15 +104,8 @@ func GetDomain(c *gin.Context) {
     c.Set("maybe_error", "")
 
     certInfoMap := make(map[int]CertificateInfo)
-    var serverName string
     for serverIdx, server := range config.Servers {
         for _, directive := range server.Directives {
-
-            if directive.Directive == "server_name" {
-                serverName = strings.ReplaceAll(directive.Params, " ", "_")
-                continue
-            }
-
             if directive.Directive == "ssl_certificate" {
 
                 pubKey, err := cert.GetCertInfo(directive.Params)
@@ -134,7 +127,7 @@ func GetDomain(c *gin.Context) {
         }
     }
 
-    certModel, _ := model.FirstCert(serverName)
+    certModel, _ := model.FirstCert(name)
 
     c.Set("maybe_error", "nginx_config_syntax_error")
 
@@ -304,7 +297,7 @@ func DisableDomain(c *gin.Context) {
     }
 
     // delete auto cert record
-    certModel := model.Cert{Domain: c.Param("name")}
+    certModel := model.Cert{Filename: c.Param("name")}
     err = certModel.Remove()
     if err != nil {
         ErrHandler(c, err)
@@ -345,7 +338,7 @@ func DeleteDomain(c *gin.Context) {
         return
     }
 
-    certModel := model.Cert{Domain: name}
+    certModel := model.Cert{Filename: name}
     _ = certModel.Remove()
 
     err = os.Remove(availablePath)
@@ -362,9 +355,17 @@ func DeleteDomain(c *gin.Context) {
 }
 
 func AddDomainToAutoCert(c *gin.Context) {
-    domain := c.Param("domain")
-    domain = strings.ReplaceAll(domain, " ", "_")
-    certModel, err := model.FirstOrCreateCert(domain)
+    name := c.Param("name")
+
+    var json struct {
+        Domains []string `json:"domains"`
+    }
+
+    if !BindAndValid(c, &json) {
+        return
+    }
+
+    certModel, err := model.FirstOrCreateCert(name)
 
     if err != nil {
         ErrHandler(c, err)
@@ -372,6 +373,8 @@ func AddDomainToAutoCert(c *gin.Context) {
     }
 
     err = certModel.Updates(&model.Cert{
+        Name:     name,
+        Domains:  json.Domains,
         AutoCert: model.AutoCertEnabled,
     })
 
@@ -384,13 +387,15 @@ func AddDomainToAutoCert(c *gin.Context) {
 }
 
 func RemoveDomainFromAutoCert(c *gin.Context) {
-    domain := c.Param("domain")
-    domain = strings.ReplaceAll(domain, " ", "_")
-    certModel := model.Cert{
-        Domain: domain,
+    name := c.Param("name")
+    certModel, err := model.FirstCert(name)
+
+    if err != nil {
+        ErrHandler(c, err)
+        return
     }
 
-    err := certModel.Updates(&model.Cert{
+    err = certModel.Updates(&model.Cert{
         AutoCert: model.AutoCertDisabled,
     })
 

+ 31 - 14
server/model/cert.go

@@ -2,6 +2,7 @@ package model
 
 import (
 	"github.com/0xJacky/Nginx-UI/server/pkg/nginx"
+	"github.com/lib/pq"
 	"os"
 )
 
@@ -10,30 +11,38 @@ const (
 	AutoCertDisabled = -1
 )
 
+type CertDomains []string
+
 type Cert struct {
 	Model
-	Name                  string `json:"name"`
-	Domain                string `json:"domain"`
-	SSLCertificatePath    string `json:"ssl_certificate_path"`
-	SSLCertificateKeyPath string `json:"ssl_certificate_key_path"`
-	AutoCert              int    `json:"auto_cert"`
+	Name                  string         `json:"name"`
+	Domains               pq.StringArray `json:"domains" gorm:"type:text[]"`
+	Filename              string         `json:"filename"`
+	SSLCertificatePath    string         `json:"ssl_certificate_path"`
+	SSLCertificateKeyPath string         `json:"ssl_certificate_key_path"`
+	AutoCert              int            `json:"auto_cert"`
+	Log                   string         `json:"log"`
 }
 
-func FirstCert(domain string) (c Cert, err error) {
+func FirstCert(confName string) (c Cert, err error) {
 	err = db.First(&c, &Cert{
-		Domain: domain,
+		Filename: confName,
 	}).Error
 
 	return
 }
 
-func FirstOrCreateCert(domain string) (c Cert, err error) {
-	err = db.FirstOrCreate(&c, &Cert{Domain: domain}).Error
+func FirstOrCreateCert(confName string) (c Cert, err error) {
+	err = db.FirstOrCreate(&c, &Cert{Filename: confName}).Error
 	return
 }
 
-func GetAutoCertList() (c []Cert) {
-	var t []Cert
+func (c *Cert) Insert() error {
+	return db.Create(c).Error
+}
+
+func GetAutoCertList() (c []*Cert) {
+	var t []*Cert
 	db.Where("auto_cert", AutoCertEnabled).Find(&t)
 
 	// check if this domain is enabled
@@ -49,7 +58,7 @@ func GetAutoCertList() (c []Cert) {
 	}
 
 	for _, v := range t {
-		if enabledConfigMap[v.Domain] == true {
+		if enabledConfigMap[v.Filename] == true {
 			c = append(c, v)
 		}
 	}
@@ -76,9 +85,17 @@ func FirstCertByID(id int) (c Cert, err error) {
 }
 
 func (c *Cert) Updates(n *Cert) error {
-	return db.Model(c).Updates(n).Error
+	return db.Model(&Cert{}).Where("id", c.ID).Updates(n).Error
+}
+
+func (c *Cert) ClearLog() {
+	db.Model(&Cert{}).Where("id", c.ID).Update("log", "")
 }
 
 func (c *Cert) Remove() error {
-	return db.Where("domain", c.Domain).Delete(c).Error
+	if c.Filename == "" {
+		return db.Delete(c).Error
+	}
+
+	return db.Where("filename", c.Filename).Delete(c).Error
 }

+ 114 - 63
server/pkg/cert/auto_cert.go

@@ -1,72 +1,123 @@
 package cert
 
 import (
-	"github.com/0xJacky/Nginx-UI/server/model"
-	"log"
-	"strings"
-	"time"
+    "fmt"
+    "github.com/0xJacky/Nginx-UI/server/model"
+    "github.com/pkg/errors"
+    "log"
+    "time"
 )
 
 func handleIssueCertLogChan(logChan chan string) {
-	defer func() {
-		if err := recover(); err != nil {
-			log.Println("[Auto Cert] handleIssueCertLogChan", err)
-		}
-	}()
-
-	for logString := range logChan {
-		log.Println("[Auto Cert] Info", logString)
-	}
+    defer func() {
+        if err := recover(); err != nil {
+            log.Println("[Auto Cert] handleIssueCertLogChan", err)
+        }
+    }()
+
+    for logString := range logChan {
+        log.Println("[Auto Cert] Info", logString)
+    }
+}
+
+type AutoCertErrorLog struct {
+    buffer []string
+    cert   *model.Cert
+}
+
+func (t *AutoCertErrorLog) SetCertModel(cert *model.Cert) {
+    t.cert = cert
+}
+
+func (t *AutoCertErrorLog) Push(text string, err error) {
+    t.buffer = append(t.buffer, text+" "+err.Error())
+    log.Println("[AutoCert Error]", text, err)
+}
+
+func (t *AutoCertErrorLog) Exit(text string, err error) {
+    t.buffer = append(t.buffer, text+" "+err.Error())
+    log.Println("[AutoCert Error]", text, err)
+
+    if t.cert == nil {
+        return
+    }
+
+    _ = t.cert.Updates(&model.Cert{
+        Log: t.ToString(),
+    })
+}
+
+func (t *AutoCertErrorLog) ToString() (content string) {
+
+    for _, v := range t.buffer {
+        content += fmt.Sprintf("[AutoCert Error] %s\n", v)
+    }
+
+    return
 }
 
-func AutoCert() {
-	defer func() {
-		if err := recover(); err != nil {
-			log.Println("[AutoCert] Recover", err)
-		}
-	}()
-	log.Println("[AutoCert] Start")
-	autoCertList := model.GetAutoCertList()
-	for i := range autoCertList {
-		domain := autoCertList[i].Domain
-
-		certModel, err := model.FirstCert(domain)
-
-		if err != nil {
-			log.Println("[AutoCert] Error get certificate from database", err)
-			continue
-		}
-
-		if certModel.SSLCertificatePath == "" {
-			log.Println("[AutoCert] Error ssl_certificate_path is empty, " +
-				"try to reopen auto-cert for this domain:" + domain)
-			continue
-		}
-
-		cert, err := GetCertInfo(certModel.SSLCertificatePath)
-		if err != nil {
-			log.Println("GetCertInfo Err", err)
-			// Get certificate info error, ignore this domain
-			continue
-		}
-		// before 1 mo
-		if time.Now().Before(cert.NotBefore.AddDate(0, 1, 0)) {
-			continue
-		}
-		// after 1 mo, reissue certificate
-		logChan := make(chan string, 1)
-		errChan := make(chan error, 1)
-
-		// support SAN certification
-		go IssueCert(strings.Split(domain, "_"), logChan, errChan)
-
-		go handleIssueCertLogChan(logChan)
-
-		// block, unless errChan closed
-		for err = range errChan {
-			log.Println("Error cert.IssueCert", err)
-		}
-
-		close(logChan)
-	}
+func AutoObtain() {
+    defer func() {
+        if err := recover(); err != nil {
+            log.Println("[AutoCert] Recover", err)
+        }
+    }()
+    log.Println("[AutoCert] Start")
+    autoCertList := model.GetAutoCertList()
+    for _, certModel := range autoCertList {
+        confName := certModel.Filename
+
+        errLog := &AutoCertErrorLog{}
+        errLog.SetCertModel(certModel)
+
+        if len(certModel.Filename) == 0 {
+            errLog.Exit("", errors.New("filename is empty"))
+            continue
+        }
+
+        if len(certModel.Domains) == 0 {
+            errLog.Exit(confName, errors.New("domains list is empty, "+
+                "try to reopen auto-cert for this config:"+confName))
+            continue
+        }
+
+        if certModel.SSLCertificatePath != "" {
+            cert, err := GetCertInfo(certModel.SSLCertificatePath)
+            if err != nil {
+                errLog.Push("get cert info", err)
+                // Get certificate info error, ignore this domain
+                continue
+            }
+            // every week
+            if time.Now().Sub(cert.NotBefore).Hours()/24 < 7 {
+                continue
+            }
+        }
+        // after 1 mo, reissue certificate
+        logChan := make(chan string, 1)
+        errChan := make(chan error, 1)
+
+        // support SAN certification
+        go IssueCert(certModel.Domains, logChan, errChan)
+
+        go handleIssueCertLogChan(logChan)
+
+        // block, unless errChan closed
+        for err := range errChan {
+            errLog.Push("issue cert", err)
+        }
+
+        logStr := errLog.ToString()
+        if logStr != "" {
+            // store error log to db
+            _ = certModel.Updates(&model.Cert{
+                Log: errLog.ToString(),
+            })
+        } else {
+            certModel.ClearLog()
+        }
+
+        close(logChan)
+    }
+    log.Println("[AutoCert] End")
 }

+ 14 - 5
server/pkg/cert/cert.go

@@ -5,6 +5,7 @@ import (
 	"crypto/ecdsa"
 	"crypto/elliptic"
 	"crypto/rand"
+	"crypto/tls"
 	"github.com/0xJacky/Nginx-UI/server/pkg/nginx"
 	"github.com/0xJacky/Nginx-UI/server/settings"
 	"github.com/go-acme/lego/v4/certcrypto"
@@ -14,6 +15,7 @@ import (
 	"github.com/go-acme/lego/v4/registration"
 	"github.com/pkg/errors"
 	"log"
+	"net/http"
 	"os"
 	"path/filepath"
 	"strings"
@@ -65,6 +67,11 @@ func IssueCert(domain []string, logChan chan string, errChan chan error) {
 
 	if settings.ServerSettings.CADir != "" {
 		config.CADirURL = settings.ServerSettings.CADir
+		if config.HTTPClient != nil {
+			config.HTTPClient.Transport = &http.Transport{
+				TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
+			}
+		}
 	}
 
 	config.Certificate.KeyType = certcrypto.RSA2048
@@ -85,7 +92,7 @@ func IssueCert(domain []string, logChan chan string, errChan chan error) {
 	)
 
 	if err != nil {
-		errChan <- errors.Wrap(err, "issue cert challenge fail")
+		errChan <- errors.Wrap(err, "fail to challenge")
 		return
 	}
 
@@ -93,7 +100,7 @@ func IssueCert(domain []string, logChan chan string, errChan chan error) {
 	logChan <- "Registering user"
 	reg, err := client.Registration.Register(registration.RegisterOptions{TermsOfServiceAgreed: true})
 	if err != nil {
-		errChan <- errors.Wrap(err, "issue cert register fail")
+		errChan <- errors.Wrap(err, "fail to register")
 		return
 	}
 	myUser.Registration = reg
@@ -106,7 +113,7 @@ func IssueCert(domain []string, logChan chan string, errChan chan error) {
 	logChan <- "Obtaining certificate"
 	certificates, err := client.Certificate.Obtain(request)
 	if err != nil {
-		errChan <- errors.Wrap(err, "issue cert fail to obtain")
+		errChan <- errors.Wrap(err, "fail to obtain")
 		return
 	}
 	name := strings.Join(domain, "_")
@@ -114,7 +121,7 @@ func IssueCert(domain []string, logChan chan string, errChan chan error) {
 	if _, err = os.Stat(saveDir); os.IsNotExist(err) {
 		err = os.MkdirAll(saveDir, 0755)
 		if err != nil {
-			errChan <- errors.Wrap(err, "issue cert fail to create")
+			errChan <- errors.Wrap(err, "fail to mkdir")
 			return
 		}
 	}
@@ -135,7 +142,7 @@ func IssueCert(domain []string, logChan chan string, errChan chan error) {
 		certificates.PrivateKey, 0644)
 
 	if err != nil {
-		errChan <- errors.Wrap(err, "error issue cert write key")
+		errChan <- errors.Wrap(err, "fail to write key")
 		return
 	}
 
@@ -145,4 +152,6 @@ func IssueCert(domain []string, logChan chan string, errChan chan error) {
 	nginx.Reload()
 
 	logChan <- "Finished"
+
+	close(logChan)
 }

+ 33 - 30
server/pkg/nginx/type.go

@@ -1,61 +1,64 @@
 package nginx
 
 import (
-	"github.com/tufanbarisyildirim/gonginx"
-	"strings"
+    "github.com/tufanbarisyildirim/gonginx"
+    "path"
+    "strings"
 )
 
 type NgxConfig struct {
-	FileName  string         `json:"file_name"`
-	Upstreams []*NgxUpstream `json:"upstreams"`
-	Servers   []*NgxServer   `json:"servers"`
-	Custom    string         `json:"custom"`
-	c         *gonginx.Config
+    FileName  string         `json:"file_name"`
+    Name      string         `json:"name"`
+    Upstreams []*NgxUpstream `json:"upstreams"`
+    Servers   []*NgxServer   `json:"servers"`
+    Custom    string         `json:"custom"`
+    c         *gonginx.Config
 }
 
 type NgxServer struct {
-	Directives []*NgxDirective `json:"directives"`
-	Locations  []*NgxLocation  `json:"locations"`
-	Comments   string          `json:"comments"`
+    Directives []*NgxDirective `json:"directives"`
+    Locations  []*NgxLocation  `json:"locations"`
+    Comments   string          `json:"comments"`
 }
 
 type NgxUpstream struct {
-	Name       string          `json:"name"`
-	Directives []*NgxDirective `json:"directives"`
-	Comments   string          `json:"comments"`
+    Name       string          `json:"name"`
+    Directives []*NgxDirective `json:"directives"`
+    Comments   string          `json:"comments"`
 }
 
 type NgxDirective struct {
-	Directive string `json:"directive"`
-	Params    string `json:"params"`
-	Comments  string `json:"comments"`
+    Directive string `json:"directive"`
+    Params    string `json:"params"`
+    Comments  string `json:"comments"`
 }
 
 type NgxLocation struct {
-	Path     string `json:"path"`
-	Content  string `json:"content"`
-	Comments string `json:"comments"`
+    Path     string `json:"path"`
+    Content  string `json:"content"`
+    Comments string `json:"comments"`
 }
 
 func (d *NgxDirective) Orig() string {
-	return d.Directive + " " + d.Params
+    return d.Directive + " " + d.Params
 }
 
 func (d *NgxDirective) TrimParams() {
-	d.Params = strings.TrimRight(strings.TrimSpace(d.Params), ";")
-	return
+    d.Params = strings.TrimRight(strings.TrimSpace(d.Params), ";")
+    return
 }
 
 func NewNgxServer() *NgxServer {
-	return &NgxServer{
-		Locations:  make([]*NgxLocation, 0),
-		Directives: make([]*NgxDirective, 0),
-	}
+    return &NgxServer{
+        Locations:  make([]*NgxLocation, 0),
+        Directives: make([]*NgxDirective, 0),
+    }
 }
 
 func NewNgxConfig(filename string) *NgxConfig {
-	return &NgxConfig{
-		FileName:  filename,
-		Upstreams: make([]*NgxUpstream, 0),
-	}
+    return &NgxConfig{
+        FileName:  filename,
+        Upstreams: make([]*NgxUpstream, 0),
+        Name:      path.Base(filename),
+    }
 }

+ 3 - 4
server/router/routers.go

@@ -76,6 +76,7 @@ func InitRouter() *gin.Engine {
 			g.DELETE("domain/:name", api.DeleteDomain)
 			// duplicate site
 			g.POST("domain/:name/duplicate", api.DuplicateSite)
+			g.GET("domain/:name/cert", api.IssueCert)
 
 			g.GET("configs", api.GetConfigs)
 			g.GET("config/*name", api.GetConfig)
@@ -90,17 +91,15 @@ func InitRouter() *gin.Engine {
 			g.GET("template/blocks", api.GetTemplateBlockList)
 			g.GET("template/block/:name", api.GetTemplateBlock)
 
-			g.GET("cert/issue", api.IssueCert)
-
 			g.GET("certs", api.GetCertList)
 			g.GET("cert/:id", api.GetCert)
 			g.POST("cert", api.AddCert)
 			g.POST("cert/:id", api.ModifyCert)
 			g.DELETE("cert/:id", api.RemoveCert)
 			// Add domain to auto-renew cert list
-			g.POST("auto_cert/:domain", api.AddDomainToAutoCert)
+			g.POST("auto_cert/:name", api.AddDomainToAutoCert)
 			// Delete domain from auto-renew cert list
-			g.DELETE("auto_cert/:domain", api.RemoveDomainFromAutoCert)
+			g.DELETE("auto_cert/:name", api.RemoveDomainFromAutoCert)
 
 			// pty
 			g.GET("pty", api.Pty)

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