소스 검색

wip: ssl manage panel #52, #29

0xJacky 2 년 전
부모
커밋
a5b9bb88d6

+ 5 - 0
frontend/src/api/cert.ts

@@ -0,0 +1,5 @@
+import Curd from '@/api/curd'
+
+const cert = new Curd('/cert')
+
+export default cert

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

@@ -13,13 +13,13 @@ class Domain extends Curd {
     get_template() {
         return http.get('template')
     }
-    
+
     add_auto_cert(domain: string) {
-        return http.post('cert/' + domain)
+        return http.post('auto_cert/' + domain)
     }
 
     remove_auto_cert(domain: string) {
-        return http.delete('cert/' + domain)
+        return http.delete('auto_cert/' + domain)
     }
 }
 

+ 18 - 3
frontend/src/language/en/app.po

@@ -17,7 +17,8 @@ msgstr "About"
 msgid "Access Logs"
 msgstr ""
 
-#: src/views/domain/DomainList.vue:47 src/views/user/User.vue:43
+#: src/views/config/config.ts:36 src/views/domain/DomainList.vue:47
+#: src/views/user/User.vue:43
 msgid "Action"
 msgstr "Action"
 
@@ -205,6 +206,10 @@ msgstr ""
 msgid "Development Mode"
 msgstr "Development Mode"
 
+#: src/views/config/config.ts:20
+msgid "Dir"
+msgstr ""
+
 #: src/views/domain/ngx_conf/directive/DirectiveAdd.vue:20
 msgid "Directive"
 msgstr "Directive"
@@ -308,6 +313,10 @@ msgstr "Failed to enable %{msg}"
 msgid "Failed to get certificate information"
 msgstr ""
 
+#: src/views/config/config.ts:22
+msgid "File"
+msgstr ""
+
 #: src/views/other/Error.vue:3 src/views/other/Error.vue:4
 msgid "File Not Found"
 msgstr "File Not Found"
@@ -444,7 +453,8 @@ msgstr "Modify Config"
 msgid "Modify Config"
 msgstr "Modify Config"
 
-#: src/views/domain/DomainEdit.vue:36 src/views/domain/DomainList.vue:15
+#: src/views/config/config.ts:9 src/views/domain/DomainEdit.vue:36
+#: src/views/domain/DomainList.vue:15
 msgid "Name"
 msgstr "Name"
 
@@ -730,7 +740,12 @@ msgstr ""
 msgid "Theme"
 msgstr ""
 
-#: src/views/domain/DomainList.vue:41 src/views/user/User.vue:37
+#: src/language/constants.ts:23 src/views/config/config.ts:14
+msgid "Type"
+msgstr ""
+
+#: src/views/config/config.ts:29 src/views/domain/DomainList.vue:41
+#: src/views/user/User.vue:37
 msgid "Updated at"
 msgstr "Updated at"
 

+ 16 - 0
frontend/src/language/messages.pot

@@ -11,6 +11,7 @@ msgstr ""
 msgid "Access Logs"
 msgstr ""
 
+#: src/views/config/config.ts:36
 #: src/views/domain/DomainList.vue:47
 #: src/views/user/User.vue:43
 msgid "Action"
@@ -203,6 +204,10 @@ msgstr ""
 msgid "Development Mode"
 msgstr ""
 
+#: src/views/config/config.ts:20
+msgid "Dir"
+msgstr ""
+
 #: src/views/domain/ngx_conf/directive/DirectiveAdd.vue:20
 msgid "Directive"
 msgstr ""
@@ -320,6 +325,10 @@ msgstr ""
 msgid "Failed to get certificate information"
 msgstr ""
 
+#: src/views/config/config.ts:22
+msgid "File"
+msgstr ""
+
 #: src/views/other/Error.vue:3
 #: src/views/other/Error.vue:4
 msgid "File Not Found"
@@ -456,6 +465,7 @@ msgstr ""
 msgid "Modify Config"
 msgstr ""
 
+#: src/views/config/config.ts:9
 #: src/views/domain/DomainEdit.vue:36
 #: src/views/domain/DomainList.vue:15
 msgid "Name"
@@ -747,6 +757,12 @@ msgstr ""
 msgid "Theme"
 msgstr ""
 
+#: src/language/constants.ts:23
+#: src/views/config/config.ts:14
+msgid "Type"
+msgstr ""
+
+#: src/views/config/config.ts:29
 #: src/views/domain/DomainList.vue:41
 #: src/views/user/User.vue:37
 msgid "Updated at"

파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 0 - 0
frontend/src/language/translations.json


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


+ 18 - 3
frontend/src/language/zh_CN/app.po

@@ -20,7 +20,8 @@ msgstr "关于"
 msgid "Access Logs"
 msgstr "访问日志"
 
-#: src/views/domain/DomainList.vue:47 src/views/user/User.vue:43
+#: src/views/config/config.ts:36 src/views/domain/DomainList.vue:47
+#: src/views/user/User.vue:43
 msgid "Action"
 msgstr "操作"
 
@@ -204,6 +205,10 @@ msgstr "删除站点: %{site_name}"
 msgid "Development Mode"
 msgstr "开发模式"
 
+#: src/views/config/config.ts:20
+msgid "Dir"
+msgstr "目录"
+
 #: src/views/domain/ngx_conf/directive/DirectiveAdd.vue:20
 msgid "Directive"
 msgstr "指令"
@@ -307,6 +312,10 @@ msgstr "启用失败 %{msg}"
 msgid "Failed to get certificate information"
 msgstr "获取证书信息失败"
 
+#: src/views/config/config.ts:22
+msgid "File"
+msgstr "文件"
+
 #: src/views/other/Error.vue:3 src/views/other/Error.vue:4
 msgid "File Not Found"
 msgstr "未找到文件"
@@ -440,7 +449,8 @@ msgstr "修改"
 msgid "Modify Config"
 msgstr "修改配置文件"
 
-#: src/views/domain/DomainEdit.vue:36 src/views/domain/DomainList.vue:15
+#: src/views/config/config.ts:9 src/views/domain/DomainEdit.vue:36
+#: src/views/domain/DomainList.vue:15
 msgid "Name"
 msgstr "名称"
 
@@ -717,7 +727,12 @@ msgstr "用户名或密码错误"
 msgid "Theme"
 msgstr "主题"
 
-#: src/views/domain/DomainList.vue:41 src/views/user/User.vue:37
+#: src/language/constants.ts:23 src/views/config/config.ts:14
+msgid "Type"
+msgstr "类型"
+
+#: src/views/config/config.ts:29 src/views/domain/DomainList.vue:41
+#: src/views/user/User.vue:37
 msgid "Updated at"
 msgstr "修改时间"
 

+ 18 - 3
frontend/src/language/zh_TW/app.po

@@ -21,7 +21,8 @@ msgstr "關於"
 msgid "Access Logs"
 msgstr "訪問日誌"
 
-#: src/views/domain/DomainList.vue:47 src/views/user/User.vue:43
+#: src/views/config/config.ts:36 src/views/domain/DomainList.vue:47
+#: src/views/user/User.vue:43
 msgid "Action"
 msgstr "操作"
 
@@ -205,6 +206,10 @@ msgstr "刪除站點:%{site_name}"
 msgid "Development Mode"
 msgstr "開發模式"
 
+#: src/views/config/config.ts:20
+msgid "Dir"
+msgstr ""
+
 #: src/views/domain/ngx_conf/directive/DirectiveAdd.vue:20
 msgid "Directive"
 msgstr "指令"
@@ -308,6 +313,10 @@ msgstr "啟用失敗 %{msg}"
 msgid "Failed to get certificate information"
 msgstr "獲取證書信息失敗"
 
+#: src/views/config/config.ts:22
+msgid "File"
+msgstr ""
+
 #: src/views/other/Error.vue:3 src/views/other/Error.vue:4
 msgid "File Not Found"
 msgstr "未找到檔案"
@@ -443,7 +452,8 @@ msgstr "修改"
 msgid "Modify Config"
 msgstr "修改配置"
 
-#: src/views/domain/DomainEdit.vue:36 src/views/domain/DomainList.vue:15
+#: src/views/config/config.ts:9 src/views/domain/DomainEdit.vue:36
+#: src/views/domain/DomainList.vue:15
 msgid "Name"
 msgstr "名稱"
 
@@ -724,7 +734,12 @@ msgstr "用戶名或密碼不正確"
 msgid "Theme"
 msgstr "外觀樣式"
 
-#: src/views/domain/DomainList.vue:41 src/views/user/User.vue:37
+#: src/language/constants.ts:23 src/views/config/config.ts:14
+msgid "Type"
+msgstr ""
+
+#: src/views/config/config.ts:29 src/views/domain/DomainList.vue:41
+#: src/views/user/User.vue:37
 msgid "Updated at"
 msgstr "修改時間"
 

+ 10 - 1
frontend/src/routes/index.ts

@@ -10,7 +10,8 @@ import {
     InfoCircleOutlined,
     UserOutlined,
     FileTextOutlined,
-    SettingOutlined
+    SettingOutlined,
+    SafetyCertificateOutlined
 } from '@ant-design/icons-vue'
 
 const {$gettext} = gettext
@@ -91,6 +92,14 @@ export const routes = [
                     hiddenInSidebar: true
                 }
             },
+            {
+                path: 'cert',
+                name: () => $gettext('Certification'),
+                component: () => import('@/views/cert/Cert.vue'),
+                meta: {
+                    icon: SafetyCertificateOutlined
+                }
+            },
             {
                 path: 'terminal',
                 name: () => $gettext('Terminal'),

+ 1 - 1
frontend/src/version.json

@@ -1 +1 @@
-{"version":"1.7.0","build_id":62,"total_build":132}
+{"version":"1.7.0","build_id":63,"total_build":133}

+ 88 - 0
frontend/src/views/cert/Cert.vue

@@ -0,0 +1,88 @@
+<script setup lang="tsx">
+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'
+
+const {$gettext} = useGettext()
+
+const columns = [{
+    title: () => $gettext('Name'),
+    dataIndex: 'name',
+    sorter: true,
+    pithy: true,
+    customRender: (args: customRender) => {
+        const {text, record} = args
+        if (!text) {
+            return h('div', record.domain)
+        }
+        return h('div', text)
+    },
+    edit: {
+        type: input
+    },
+    search: true
+}, {
+    title: () => $gettext('Domain'),
+    dataIndex: 'domain',
+    sorter: true,
+    pithy: true,
+    edit: {
+        type: input
+    },
+    search: true
+}, {
+    title: () => $gettext('Auto Cert'),
+    dataIndex: 'auto_cert',
+    customRender: (args: customRender) => {
+        const template: any = []
+        const {text, column} = args
+        if (text === true || text > 0) {
+            template.push(<Badge status="success"/>)
+            template.push($gettext('Enabled'))
+        } else {
+            template.push(<Badge status="error"/>)
+            template.push($gettext('Disabled'))
+        }
+        return h('div', template)
+    },
+    sorter: true,
+    pithy: true
+}, {
+    title: () => $gettext('SSL Certificate Path'),
+    dataIndex: 'ssl_certificate_path',
+    edit: {
+        type: input
+    },
+    display: false
+}, {
+    title: () => $gettext('SSL Certificate Key Path'),
+    dataIndex: 'ssl_certificate_key_path',
+    edit: {
+        type: input
+    },
+    display: false
+}, {
+    title: () => $gettext('Updated at'),
+    dataIndex: 'updated_at',
+    customRender: datetime,
+    sorter: true,
+    pithy: true
+}, {
+    title: () => $gettext('Action'),
+    dataIndex: 'action'
+}]
+</script>
+
+<template>
+    <std-curd :title="$gettext('Certification')" :api="cert" :columns="columns"
+              row-key="name"
+    />
+</template>
+
+<style lang="less" scoped>
+
+</style>

+ 0 - 1
frontend/src/views/config/config.tsx → frontend/src/views/config/config.ts

@@ -3,7 +3,6 @@ import gettext from '@/gettext'
 
 const {$gettext} = gettext
 
-import {Badge} from 'ant-design-vue'
 import {h} from 'vue'
 
 const configColumns = [{

+ 11 - 0
frontend/src/views/template/Template.vue

@@ -0,0 +1,11 @@
+<script setup lang="ts">
+
+</script>
+
+<template>
+
+</template>
+
+<style lang="less" scoped>
+
+</style>

+ 1 - 1
frontend/version.json

@@ -1 +1 @@
-{"version":"1.7.0","build_id":62,"total_build":132}
+{"version":"1.7.0","build_id":63,"total_build":133}

+ 108 - 1
server/api/cert.go

@@ -6,6 +6,7 @@ import (
 	"github.com/0xJacky/Nginx-UI/server/pkg/nginx"
 	"github.com/gin-gonic/gin"
 	"github.com/gorilla/websocket"
+	"github.com/spf13/cast"
 	"log"
 	"net/http"
 	"strings"
@@ -117,7 +118,8 @@ func IssueCert(c *gin.Context) {
 	}
 
 	err = certModel.Updates(&model.Cert{
-		SSLCertificatePath: sslCertificatePath,
+		SSLCertificatePath:    sslCertificatePath,
+		SSLCertificateKeyPath: sslCertificateKeyPath,
 	})
 
 	if err != nil {
@@ -137,3 +139,108 @@ func IssueCert(c *gin.Context) {
 	}
 
 }
+
+func GetCertList(c *gin.Context) {
+	certList := model.GetCertList(c.Query("name"), c.Query("domain"))
+
+	c.JSON(http.StatusOK, gin.H{
+		"data": certList,
+	})
+}
+
+func GetCert(c *gin.Context) {
+	certModel, err := model.FirstCertByID(cast.ToInt(c.Param("id")))
+
+	if err != nil {
+		ErrHandler(c, err)
+		return
+	}
+
+	c.JSON(http.StatusOK, certModel)
+}
+
+func AddCert(c *gin.Context) {
+	var json struct {
+		Name                  string `json:"name" binding:"required"`
+		Domain                string `json:"domain" binding:"required"`
+		SSLCertificatePath    string `json:"ssl_certificate_path" binding:"required"`
+		SSLCertificateKeyPath string `json:"ssl_certificate_key_path" binding:"required"`
+	}
+	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
+	}
+
+	c.JSON(http.StatusOK, nil)
+}
+
+func ModifyCert(c *gin.Context) {
+	id := cast.ToInt(c.Param("id"))
+	certModel, err := model.FirstCertByID(id)
+
+	var json struct {
+		Name                  string `json:"name" binding:"required"`
+		Domain                string `json:"domain" binding:"required"`
+		SSLCertificatePath    string `json:"ssl_certificate_path" binding:"required"`
+		SSLCertificateKeyPath string `json:"ssl_certificate_key_path" binding:"required"`
+	}
+
+	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
+	}
+
+	c.JSON(http.StatusOK, certModel)
+}
+
+func RemoveCert(c *gin.Context) {
+	id := cast.ToInt(c.Param("id"))
+	certModel, err := model.FirstCertByID(id)
+
+	if err != nil {
+		ErrHandler(c, err)
+		return
+	}
+
+	err = certModel.Remove()
+
+	if err != nil {
+		ErrHandler(c, err)
+		return
+	}
+
+	c.JSON(http.StatusOK, nil)
+}

+ 333 - 317
server/api/domain.go

@@ -1,365 +1,381 @@
 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/config_list"
-	"github.com/0xJacky/Nginx-UI/server/pkg/nginx"
-	"github.com/gin-gonic/gin"
-	"log"
-	"net/http"
-	"os"
-	"path/filepath"
-	"strings"
-	"time"
+    "github.com/0xJacky/Nginx-UI/server/model"
+    "github.com/0xJacky/Nginx-UI/server/pkg/cert"
+    "github.com/0xJacky/Nginx-UI/server/pkg/config_list"
+    "github.com/0xJacky/Nginx-UI/server/pkg/nginx"
+    "github.com/gin-gonic/gin"
+    "log"
+    "net/http"
+    "os"
+    "path/filepath"
+    "strings"
+    "time"
 )
 
 func GetDomains(c *gin.Context) {
-	name := c.Query("name")
-	orderBy := c.Query("order_by")
-	sort := c.DefaultQuery("sort", "desc")
-
-	mySort := map[string]string{
-		"enabled": "bool",
-		"name":    "string",
-		"modify":  "time",
-	}
-
-	configFiles, err := os.ReadDir(nginx.GetNginxConfPath("sites-available"))
-
-	if err != nil {
-		ErrHandler(c, err)
-		return
-	}
-
-	enabledConfig, err := os.ReadDir(filepath.Join(nginx.GetNginxConfPath("sites-enabled")))
-
-	if err != nil {
-		ErrHandler(c, err)
-		return
-	}
-
-	enabledConfigMap := make(map[string]bool)
-	for i := range enabledConfig {
-		enabledConfigMap[enabledConfig[i].Name()] = true
-	}
-
-	var configs []gin.H
-
-	for i := range configFiles {
-		file := configFiles[i]
-		fileInfo, _ := file.Info()
-		if !file.IsDir() {
-			if name != "" && !strings.Contains(file.Name(), name) {
-				continue
-			}
-			configs = append(configs, gin.H{
-				"name":    file.Name(),
-				"size":    fileInfo.Size(),
-				"modify":  fileInfo.ModTime(),
-				"enabled": enabledConfigMap[file.Name()],
-			})
-		}
-	}
-
-	configs = config_list.Sort(orderBy, sort, mySort[orderBy], configs)
-
-	c.JSON(http.StatusOK, gin.H{
-		"data": configs,
-	})
+    name := c.Query("name")
+    orderBy := c.Query("order_by")
+    sort := c.DefaultQuery("sort", "desc")
+
+    mySort := map[string]string{
+        "enabled": "bool",
+        "name":    "string",
+        "modify":  "time",
+    }
+
+    configFiles, err := os.ReadDir(nginx.GetNginxConfPath("sites-available"))
+
+    if err != nil {
+        ErrHandler(c, err)
+        return
+    }
+
+    enabledConfig, err := os.ReadDir(filepath.Join(nginx.GetNginxConfPath("sites-enabled")))
+
+    if err != nil {
+        ErrHandler(c, err)
+        return
+    }
+
+    enabledConfigMap := make(map[string]bool)
+    for i := range enabledConfig {
+        enabledConfigMap[enabledConfig[i].Name()] = true
+    }
+
+    var configs []gin.H
+
+    for i := range configFiles {
+        file := configFiles[i]
+        fileInfo, _ := file.Info()
+        if !file.IsDir() {
+            if name != "" && !strings.Contains(file.Name(), name) {
+                continue
+            }
+            configs = append(configs, gin.H{
+                "name":    file.Name(),
+                "size":    fileInfo.Size(),
+                "modify":  fileInfo.ModTime(),
+                "enabled": enabledConfigMap[file.Name()],
+            })
+        }
+    }
+
+    configs = config_list.Sort(orderBy, sort, mySort[orderBy], configs)
+
+    c.JSON(http.StatusOK, gin.H{
+        "data": configs,
+    })
 }
 
 type CertificateInfo struct {
-	SubjectName string    `json:"subject_name"`
-	IssuerName  string    `json:"issuer_name"`
-	NotAfter    time.Time `json:"not_after"`
-	NotBefore   time.Time `json:"not_before"`
+    SubjectName string    `json:"subject_name"`
+    IssuerName  string    `json:"issuer_name"`
+    NotAfter    time.Time `json:"not_after"`
+    NotBefore   time.Time `json:"not_before"`
 }
 
 func GetDomain(c *gin.Context) {
-	rewriteName, ok := c.Get("rewriteConfigFileName")
+    rewriteName, ok := c.Get("rewriteConfigFileName")
 
-	name := c.Param("name")
+    name := c.Param("name")
 
-	// for modify filename
-	if ok {
-		name = rewriteName.(string)
-	}
+    // for modify filename
+    if ok {
+        name = rewriteName.(string)
+    }
 
-	path := filepath.Join(nginx.GetNginxConfPath("sites-available"), name)
+    path := filepath.Join(nginx.GetNginxConfPath("sites-available"), name)
 
-	enabled := true
-	if _, err := os.Stat(filepath.Join(nginx.GetNginxConfPath("sites-enabled"), name)); os.IsNotExist(err) {
-		enabled = false
-	}
+    enabled := true
+    if _, err := os.Stat(filepath.Join(nginx.GetNginxConfPath("sites-enabled"), name)); os.IsNotExist(err) {
+        enabled = false
+    }
 
-	config, err := nginx.ParseNgxConfig(path)
+    config, err := nginx.ParseNgxConfig(path)
 
-	if err != nil {
-		ErrHandler(c, err)
-		return
-	}
+    if err != nil {
+        ErrHandler(c, err)
+        return
+    }
 
-	certInfoMap := make(map[int]CertificateInfo)
-	var serverName string
-	for serverIdx, server := range config.Servers {
-		for _, directive := range server.Directives {
+    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 == "server_name" {
+                serverName = strings.ReplaceAll(directive.Params, " ", "_")
+                continue
+            }
 
-			if directive.Directive == "ssl_certificate" {
+            if directive.Directive == "ssl_certificate" {
 
-				pubKey, err := cert.GetCertInfo(directive.Params)
+                pubKey, err := cert.GetCertInfo(directive.Params)
 
-				if err != nil {
-					log.Println("Failed to get certificate information", err)
-					break
-				}
+                if err != nil {
+                    log.Println("Failed to get certificate information", err)
+                    break
+                }
 
-				certInfoMap[serverIdx] = CertificateInfo{
-					SubjectName: pubKey.Subject.CommonName,
-					IssuerName:  pubKey.Issuer.CommonName,
-					NotAfter:    pubKey.NotAfter,
-					NotBefore:   pubKey.NotBefore,
-				}
+                certInfoMap[serverIdx] = CertificateInfo{
+                    SubjectName: pubKey.Subject.CommonName,
+                    IssuerName:  pubKey.Issuer.CommonName,
+                    NotAfter:    pubKey.NotAfter,
+                    NotBefore:   pubKey.NotBefore,
+                }
 
-				break
-			}
-		}
-	}
+                break
+            }
+        }
+    }
 
-	_, err = model.FirstCert(serverName)
+    certModel, _ := model.FirstCert(serverName)
 
-	c.JSON(http.StatusOK, gin.H{
-		"enabled":   enabled,
-		"name":      name,
-		"config":    config.BuildConfig(),
-		"tokenized": config,
-		"auto_cert": err == nil,
-		"cert_info": certInfoMap,
-	})
+    c.JSON(http.StatusOK, gin.H{
+        "enabled":   enabled,
+        "name":      name,
+        "config":    config.BuildConfig(),
+        "tokenized": config,
+        "auto_cert": certModel.AutoCert == model.AutoCertEnabled,
+        "cert_info": certInfoMap,
+    })
 
 }
 
 func EditDomain(c *gin.Context) {
-	name := c.Param("name")
-
-	if name == "" {
-		c.JSON(http.StatusNotAcceptable, gin.H{
-			"message": "param name is empty",
-		})
-		return
-	}
-
-	var json struct {
-		Name    string `json:"name" binding:"required"`
-		Content string `json:"content"`
-	}
-
-	if !BindAndValid(c, &json) {
-		return
-	}
-
-	path := filepath.Join(nginx.GetNginxConfPath("sites-available"), name)
-
-	err := os.WriteFile(path, []byte(json.Content), 0644)
-	if err != nil {
-		ErrHandler(c, err)
-		return
-	}
-	enabledConfigFilePath := filepath.Join(nginx.GetNginxConfPath("sites-enabled"), name)
-	// rename the config file if needed
-	if name != json.Name {
-		newPath := filepath.Join(nginx.GetNginxConfPath("sites-available"), json.Name)
-		// recreate soft link
-		log.Println(enabledConfigFilePath)
-		if _, err = os.Stat(enabledConfigFilePath); err == nil {
-			log.Println(enabledConfigFilePath)
-			_ = os.Remove(enabledConfigFilePath)
-			enabledConfigFilePath = filepath.Join(nginx.GetNginxConfPath("sites-enabled"), json.Name)
-			err = os.Symlink(newPath, enabledConfigFilePath)
-
-			if err != nil {
-				ErrHandler(c, err)
-				return
-			}
-		}
-		err = os.Rename(path, newPath)
-		if err != nil {
-			ErrHandler(c, err)
-			return
-		}
-		name = json.Name
-		c.Set("rewriteConfigFileName", name)
-
-	}
-
-	enabledConfigFilePath = filepath.Join(nginx.GetNginxConfPath("sites-enabled"), name)
-	if _, err = os.Stat(enabledConfigFilePath); err == nil {
-		// Test nginx configuration
-		err = nginx.TestNginxConf()
-		if err != nil {
-			c.JSON(http.StatusInternalServerError, gin.H{
-				"message": err.Error(),
-			})
-			return
-		}
-
-		output := nginx.ReloadNginx()
-
-		if output != "" && strings.Contains(output, "error") {
-			c.JSON(http.StatusInternalServerError, gin.H{
-				"message": output,
-			})
-			return
-		}
-	}
-
-	GetDomain(c)
+    name := c.Param("name")
+
+    if name == "" {
+        c.JSON(http.StatusNotAcceptable, gin.H{
+            "message": "param name is empty",
+        })
+        return
+    }
+
+    var json struct {
+        Name    string `json:"name" binding:"required"`
+        Content string `json:"content"`
+    }
+
+    if !BindAndValid(c, &json) {
+        return
+    }
+
+    path := filepath.Join(nginx.GetNginxConfPath("sites-available"), name)
+
+    err := os.WriteFile(path, []byte(json.Content), 0644)
+    if err != nil {
+        ErrHandler(c, err)
+        return
+    }
+    enabledConfigFilePath := filepath.Join(nginx.GetNginxConfPath("sites-enabled"), name)
+    // rename the config file if needed
+    if name != json.Name {
+        newPath := filepath.Join(nginx.GetNginxConfPath("sites-available"), json.Name)
+        // recreate soft link
+        log.Println(enabledConfigFilePath)
+        if _, err = os.Stat(enabledConfigFilePath); err == nil {
+            log.Println(enabledConfigFilePath)
+            _ = os.Remove(enabledConfigFilePath)
+            enabledConfigFilePath = filepath.Join(nginx.GetNginxConfPath("sites-enabled"), json.Name)
+            err = os.Symlink(newPath, enabledConfigFilePath)
+
+            if err != nil {
+                ErrHandler(c, err)
+                return
+            }
+        }
+        err = os.Rename(path, newPath)
+        if err != nil {
+            ErrHandler(c, err)
+            return
+        }
+        name = json.Name
+        c.Set("rewriteConfigFileName", name)
+
+    }
+
+    enabledConfigFilePath = filepath.Join(nginx.GetNginxConfPath("sites-enabled"), name)
+    if _, err = os.Stat(enabledConfigFilePath); err == nil {
+        // Test nginx configuration
+        err = nginx.TestNginxConf()
+        if err != nil {
+            c.JSON(http.StatusInternalServerError, gin.H{
+                "message": err.Error(),
+            })
+            return
+        }
+
+        output := nginx.ReloadNginx()
+
+        if output != "" && strings.Contains(output, "error") {
+            c.JSON(http.StatusInternalServerError, gin.H{
+                "message": output,
+            })
+            return
+        }
+    }
+
+    GetDomain(c)
 }
 
 func EnableDomain(c *gin.Context) {
-	configFilePath := filepath.Join(nginx.GetNginxConfPath("sites-available"), c.Param("name"))
-	enabledConfigFilePath := filepath.Join(nginx.GetNginxConfPath("sites-enabled"), c.Param("name"))
-
-	_, err := os.Stat(configFilePath)
-
-	if err != nil {
-		ErrHandler(c, err)
-		return
-	}
-
-	if _, err = os.Stat(enabledConfigFilePath); os.IsNotExist(err) {
-		err = os.Symlink(configFilePath, enabledConfigFilePath)
-
-		if err != nil {
-			ErrHandler(c, err)
-			return
-		}
-	}
-
-	// Test nginx config, if not pass then rollback.
-	err = nginx.TestNginxConf()
-	if err != nil {
-		_ = os.Remove(enabledConfigFilePath)
-		c.JSON(http.StatusInternalServerError, gin.H{
-			"message": err.Error(),
-		})
-		return
-	}
-
-	output := nginx.ReloadNginx()
-
-	if output != "" && strings.Contains(output, "error") {
-		c.JSON(http.StatusInternalServerError, gin.H{
-			"message": output,
-		})
-		return
-	}
-
-	c.JSON(http.StatusOK, gin.H{
-		"message": "ok",
-	})
+    configFilePath := filepath.Join(nginx.GetNginxConfPath("sites-available"), c.Param("name"))
+    enabledConfigFilePath := filepath.Join(nginx.GetNginxConfPath("sites-enabled"), c.Param("name"))
+
+    _, err := os.Stat(configFilePath)
+
+    if err != nil {
+        ErrHandler(c, err)
+        return
+    }
+
+    if _, err = os.Stat(enabledConfigFilePath); os.IsNotExist(err) {
+        err = os.Symlink(configFilePath, enabledConfigFilePath)
+
+        if err != nil {
+            ErrHandler(c, err)
+            return
+        }
+    }
+
+    // Test nginx config, if not pass then rollback.
+    err = nginx.TestNginxConf()
+    if err != nil {
+        _ = os.Remove(enabledConfigFilePath)
+        c.JSON(http.StatusInternalServerError, gin.H{
+            "message": err.Error(),
+        })
+        return
+    }
+
+    output := nginx.ReloadNginx()
+
+    if output != "" && strings.Contains(output, "error") {
+        c.JSON(http.StatusInternalServerError, gin.H{
+            "message": output,
+        })
+        return
+    }
+
+    c.JSON(http.StatusOK, gin.H{
+        "message": "ok",
+    })
 }
 
 func DisableDomain(c *gin.Context) {
-	enabledConfigFilePath := filepath.Join(nginx.GetNginxConfPath("sites-enabled"), c.Param("name"))
-
-	_, err := os.Stat(enabledConfigFilePath)
-
-	if err != nil {
-		ErrHandler(c, err)
-		return
-	}
-
-	err = os.Remove(enabledConfigFilePath)
-
-	if err != nil {
-		ErrHandler(c, err)
-		return
-	}
-
-	// delete auto cert record
-	certModel := model.Cert{Domain: c.Param("name")}
-	err = certModel.Remove()
-	if err != nil {
-		ErrHandler(c, err)
-		return
-	}
-
-	output := nginx.ReloadNginx()
-
-	if output != "" {
-		c.JSON(http.StatusInternalServerError, gin.H{
-			"message": output,
-		})
-		return
-	}
-
-	c.JSON(http.StatusOK, gin.H{
-		"message": "ok",
-	})
+    enabledConfigFilePath := filepath.Join(nginx.GetNginxConfPath("sites-enabled"), c.Param("name"))
+
+    _, err := os.Stat(enabledConfigFilePath)
+
+    if err != nil {
+        ErrHandler(c, err)
+        return
+    }
+
+    err = os.Remove(enabledConfigFilePath)
+
+    if err != nil {
+        ErrHandler(c, err)
+        return
+    }
+
+    // delete auto cert record
+    certModel := model.Cert{Domain: c.Param("name")}
+    err = certModel.Remove()
+    if err != nil {
+        ErrHandler(c, err)
+        return
+    }
+
+    output := nginx.ReloadNginx()
+
+    if output != "" {
+        c.JSON(http.StatusInternalServerError, gin.H{
+            "message": output,
+        })
+        return
+    }
+
+    c.JSON(http.StatusOK, gin.H{
+        "message": "ok",
+    })
 }
 
 func DeleteDomain(c *gin.Context) {
-	var err error
-	name := c.Param("name")
-	availablePath := filepath.Join(nginx.GetNginxConfPath("sites-available"), name)
-	enabledPath := filepath.Join(nginx.GetNginxConfPath("sites-enabled"), name)
-
-	if _, err = os.Stat(availablePath); os.IsNotExist(err) {
-		c.JSON(http.StatusNotFound, gin.H{
-			"message": "site not found",
-		})
-		return
-	}
-
-	if _, err = os.Stat(enabledPath); err == nil {
-		c.JSON(http.StatusNotAcceptable, gin.H{
-			"message": "site is enabled",
-		})
-		return
-	}
-
-	certModel := model.Cert{Domain: name}
-	_ = certModel.Remove()
-
-	err = os.Remove(availablePath)
-
-	if err != nil {
-		ErrHandler(c, err)
-		return
-	}
-
-	c.JSON(http.StatusOK, gin.H{
-		"message": "ok",
-	})
+    var err error
+    name := c.Param("name")
+    availablePath := filepath.Join(nginx.GetNginxConfPath("sites-available"), name)
+    enabledPath := filepath.Join(nginx.GetNginxConfPath("sites-enabled"), name)
+
+    if _, err = os.Stat(availablePath); os.IsNotExist(err) {
+        c.JSON(http.StatusNotFound, gin.H{
+            "message": "site not found",
+        })
+        return
+    }
+
+    if _, err = os.Stat(enabledPath); err == nil {
+        c.JSON(http.StatusNotAcceptable, gin.H{
+            "message": "site is enabled",
+        })
+        return
+    }
+
+    certModel := model.Cert{Domain: name}
+    _ = certModel.Remove()
+
+    err = os.Remove(availablePath)
+
+    if err != nil {
+        ErrHandler(c, err)
+        return
+    }
+
+    c.JSON(http.StatusOK, gin.H{
+        "message": "ok",
+    })
 
 }
 
 func AddDomainToAutoCert(c *gin.Context) {
-	domain := c.Param("domain")
-
-	certModel, err := model.FirstOrCreateCert(domain)
-	if err != nil {
-		ErrHandler(c, err)
-		return
-	}
-	c.JSON(http.StatusOK, certModel)
+    domain := c.Param("domain")
+    domain = strings.ReplaceAll(domain, " ", "_")
+    certModel, err := model.FirstOrCreateCert(domain)
+
+    if err != nil {
+        ErrHandler(c, err)
+        return
+    }
+
+    err = certModel.Updates(&model.Cert{
+        AutoCert: model.AutoCertEnabled,
+    })
+
+    if err != nil {
+        ErrHandler(c, err)
+        return
+    }
+
+    c.JSON(http.StatusOK, certModel)
 }
 
 func RemoveDomainFromAutoCert(c *gin.Context) {
-	certModel := model.Cert{
-		Domain: c.Param("domain"),
-	}
-	err := certModel.Remove()
-
-	if err != nil {
-		ErrHandler(c, err)
-		return
-	}
-	c.JSON(http.StatusOK, nil)
+    domain := c.Param("domain")
+    domain = strings.ReplaceAll(domain, " ", "_")
+    certModel := model.Cert{
+        Domain: domain,
+    }
+
+    err := certModel.Updates(&model.Cert{
+        AutoCert: model.AutoCertDisabled,
+    })
+
+    if err != nil {
+        ErrHandler(c, err)
+        return
+    }
+    c.JSON(http.StatusOK, nil)
 }

+ 29 - 3
server/model/cert.go

@@ -6,10 +6,18 @@ import (
 	"path/filepath"
 )
 
+const (
+	AutoCertEnabled  = 1
+	AutoCertDisabled = -1
+)
+
 type Cert struct {
 	Model
-	Domain             string `json:"domain"`
-	SSLCertificatePath string `json:"ssl_certificate_path"`
+	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"`
 }
 
 func FirstCert(domain string) (c Cert, err error) {
@@ -27,7 +35,7 @@ func FirstOrCreateCert(domain string) (c Cert, err error) {
 
 func GetAutoCertList() (c []Cert) {
 	var t []Cert
-	db.Find(&t)
+	db.Where("auto_cert", AutoCertEnabled).Find(&t)
 
 	// check if this domain is enabled
 	enabledConfig, err := os.ReadDir(filepath.Join(nginx.GetNginxConfPath("sites-enabled")))
@@ -50,6 +58,24 @@ func GetAutoCertList() (c []Cert) {
 	return
 }
 
+func GetCertList(name, domain string) (c []Cert) {
+	tx := db
+	if name != "" {
+		tx = tx.Where("name LIKE ? or domain LIKE ?", "%"+name+"%", "%"+name+"%")
+	}
+	if domain != "" {
+		tx = tx.Where("domain LIKE ?", "%"+domain+"%")
+	}
+	tx.Find(&c)
+	return
+}
+
+func FirstCertByID(id int) (c Cert, err error) {
+	err = db.First(&c, id).Error
+
+	return
+}
+
 func (c *Cert) Updates(n *Cert) error {
 	return db.Model(c).Updates(n).Error
 }

+ 7 - 2
server/router/routers.go

@@ -77,10 +77,15 @@ func InitRouter() *gin.Engine {
 
 			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("cert/:domain", api.AddDomainToAutoCert)
+			g.POST("auto_cert/:domain", api.AddDomainToAutoCert)
 			// Delete domain from auto-renew cert list
-			g.DELETE("cert/:domain", api.RemoveDomainFromAutoCert)
+			g.DELETE("auto_cert/:domain", api.RemoveDomainFromAutoCert)
 
 			// pty
 			g.GET("pty", api.Pty)

이 변경점에서 너무 많은 파일들이 변경되어 몇몇 파일들은 표시되지 않았습니다.