Selaa lähdekoodia

feat: store auto-cert log to db

0xJacky 2 vuotta sitten
vanhempi
commit
a9aacce0ad

+ 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) {

+ 29 - 7
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'),
@@ -81,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 - 1
frontend/src/views/domain/cert/IssueCert.vue

@@ -61,7 +61,7 @@ function callback(ssl_certificate: string, ssl_certificate_key: string) {
 
 function change_auto_cert(r: boolean) {
     if (r) {
-        domain.add_auto_cert(props.config_name).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}))

+ 304 - 303
server/api/cert.go

@@ -1,349 +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)
-
-	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)
-
-	certDirName := strings.Join(buffer.ServerName, "_")
-	sslCertificatePath := nginx.GetConfPath("ssl", certDirName, "fullchain.cer")
-	sslCertificateKeyPath := nginx.GetConfPath("ssl", certDirName, "private.key")
-
-	certModel, err := model.FirstOrCreateCert(c.Param("name"))
-
-	if err != nil {
-		log.Println(err)
-	}
-
-	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
-	}
-
-	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,
-	})
+    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"`
-		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)
+    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,
-		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)
 }

+ 378 - 368
server/api/domain.go

@@ -1,430 +1,440 @@
 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/helper"
-	"github.com/0xJacky/Nginx-UI/server/pkg/nginx"
-	"github.com/gin-gonic/gin"
-	"log"
-	"net/http"
-	"os"
-	"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/helper"
+    "github.com/0xJacky/Nginx-UI/server/pkg/nginx"
+    "github.com/gin-gonic/gin"
+    "log"
+    "net/http"
+    "os"
+    "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.GetConfPath("sites-available"))
-
-	if err != nil {
-		ErrHandler(c, err)
-		return
-	}
-
-	enabledConfig, err := os.ReadDir(nginx.GetConfPath("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.GetConfPath("sites-available"))
+
+    if err != nil {
+        ErrHandler(c, err)
+        return
+    }
+
+    enabledConfig, err := os.ReadDir(nginx.GetConfPath("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 := nginx.GetConfPath("sites-available", name)
+    path := nginx.GetConfPath("sites-available", name)
 
-	enabled := true
-	if _, err := os.Stat(nginx.GetConfPath("sites-enabled", name)); os.IsNotExist(err) {
-		enabled = false
-	}
+    enabled := true
+    if _, err := os.Stat(nginx.GetConfPath("sites-enabled", name)); os.IsNotExist(err) {
+        enabled = false
+    }
 
-	c.Set("maybe_error", "nginx_config_syntax_error")
-	config, err := nginx.ParseNgxConfig(path)
+    c.Set("maybe_error", "nginx_config_syntax_error")
+    config, err := nginx.ParseNgxConfig(path)
 
-	if err != nil {
-		ErrHandler(c, err)
-		return
-	}
+    if err != nil {
+        ErrHandler(c, err)
+        return
+    }
 
-	c.Set("maybe_error", "")
+    c.Set("maybe_error", "")
 
-	certInfoMap := make(map[int]CertificateInfo)
-	for serverIdx, server := range config.Servers {
-		for _, directive := range server.Directives {
-			if directive.Directive == "ssl_certificate" {
+    certInfoMap := make(map[int]CertificateInfo)
+    for serverIdx, server := range config.Servers {
+        for _, directive := range server.Directives {
+            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
+            }
+        }
+    }
 
-	certModel, _ := model.FirstCert(name)
+    certModel, _ := model.FirstCert(name)
 
-	c.Set("maybe_error", "nginx_config_syntax_error")
+    c.Set("maybe_error", "nginx_config_syntax_error")
 
-	c.JSON(http.StatusOK, gin.H{
-		"enabled":   enabled,
-		"name":      name,
-		"config":    config.FmtCode(),
-		"tokenized": config,
-		"auto_cert": certModel.AutoCert == model.AutoCertEnabled,
-		"cert_info": certInfoMap,
-	})
+    c.JSON(http.StatusOK, gin.H{
+        "enabled":   enabled,
+        "name":      name,
+        "config":    config.FmtCode(),
+        "tokenized": config,
+        "auto_cert": certModel.AutoCert == model.AutoCertEnabled,
+        "cert_info": certInfoMap,
+    })
 
 }
 
 func SaveDomain(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" binding:"required"`
-		Overwrite bool   `json:"overwrite"`
-	}
-
-	if !BindAndValid(c, &json) {
-		return
-	}
-
-	path := nginx.GetConfPath("sites-available", name)
-
-	if !json.Overwrite && helper.FileExists(path) {
-		c.JSON(http.StatusNotAcceptable, gin.H{
-			"message": "File exists",
-		})
-		return
-	}
-
-	err := os.WriteFile(path, []byte(json.Content), 0644)
-	if err != nil {
-		ErrHandler(c, err)
-		return
-	}
-	enabledConfigFilePath := nginx.GetConfPath("sites-enabled", name)
-	// rename the config file if needed
-	if name != json.Name {
-		newPath := nginx.GetConfPath("sites-available", json.Name)
-		// check if dst file exists, do not rename
-		if helper.FileExists(newPath) {
-			c.JSON(http.StatusNotAcceptable, gin.H{
-				"message": "File exists",
-			})
-			return
-		}
-		// recreate soft link
-		if helper.FileExists(enabledConfigFilePath) {
-			_ = os.Remove(enabledConfigFilePath)
-			enabledConfigFilePath = nginx.GetConfPath("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 = nginx.GetConfPath("sites-enabled", name)
-	if helper.FileExists(enabledConfigFilePath) {
-		// Test nginx configuration
-		output := nginx.TestConf()
-		if nginx.GetLogLevel(output) >= nginx.Warn {
-			c.JSON(http.StatusInternalServerError, gin.H{
-				"message": output,
-				"error":   "nginx_config_syntax_error",
-			})
-			return
-		}
-
-		output = nginx.Reload()
-
-		if nginx.GetLogLevel(output) >= nginx.Warn {
-			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" binding:"required"`
+        Overwrite bool   `json:"overwrite"`
+    }
+
+    if !BindAndValid(c, &json) {
+        return
+    }
+
+    path := nginx.GetConfPath("sites-available", name)
+
+    if !json.Overwrite && helper.FileExists(path) {
+        c.JSON(http.StatusNotAcceptable, gin.H{
+            "message": "File exists",
+        })
+        return
+    }
+
+    err := os.WriteFile(path, []byte(json.Content), 0644)
+    if err != nil {
+        ErrHandler(c, err)
+        return
+    }
+    enabledConfigFilePath := nginx.GetConfPath("sites-enabled", name)
+    // rename the config file if needed
+    if name != json.Name {
+        newPath := nginx.GetConfPath("sites-available", json.Name)
+        // check if dst file exists, do not rename
+        if helper.FileExists(newPath) {
+            c.JSON(http.StatusNotAcceptable, gin.H{
+                "message": "File exists",
+            })
+            return
+        }
+        // recreate soft link
+        if helper.FileExists(enabledConfigFilePath) {
+            _ = os.Remove(enabledConfigFilePath)
+            enabledConfigFilePath = nginx.GetConfPath("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 = nginx.GetConfPath("sites-enabled", name)
+    if helper.FileExists(enabledConfigFilePath) {
+        // Test nginx configuration
+        output := nginx.TestConf()
+        if nginx.GetLogLevel(output) >= nginx.Warn {
+            c.JSON(http.StatusInternalServerError, gin.H{
+                "message": output,
+                "error":   "nginx_config_syntax_error",
+            })
+            return
+        }
+
+        output = nginx.Reload()
+
+        if nginx.GetLogLevel(output) >= nginx.Warn {
+            c.JSON(http.StatusInternalServerError, gin.H{
+                "message": output,
+            })
+            return
+        }
+    }
+
+    GetDomain(c)
 }
 
 func EnableDomain(c *gin.Context) {
-	configFilePath := nginx.GetConfPath("sites-available", c.Param("name"))
-	enabledConfigFilePath := nginx.GetConfPath("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 disable the site.
-	output := nginx.TestConf()
-
-	if nginx.GetLogLevel(output) >= nginx.Warn {
-		_ = os.Remove(enabledConfigFilePath)
-		c.JSON(http.StatusInternalServerError, gin.H{
-			"message": output,
-		})
-		return
-	}
-
-	output = nginx.Reload()
-
-	if nginx.GetLogLevel(output) >= nginx.Warn {
-		c.JSON(http.StatusInternalServerError, gin.H{
-			"message": output,
-		})
-		return
-	}
-
-	c.JSON(http.StatusOK, gin.H{
-		"message": "ok",
-	})
+    configFilePath := nginx.GetConfPath("sites-available", c.Param("name"))
+    enabledConfigFilePath := nginx.GetConfPath("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 disable the site.
+    output := nginx.TestConf()
+
+    if nginx.GetLogLevel(output) >= nginx.Warn {
+        _ = os.Remove(enabledConfigFilePath)
+        c.JSON(http.StatusInternalServerError, gin.H{
+            "message": output,
+        })
+        return
+    }
+
+    output = nginx.Reload()
+
+    if nginx.GetLogLevel(output) >= nginx.Warn {
+        c.JSON(http.StatusInternalServerError, gin.H{
+            "message": output,
+        })
+        return
+    }
+
+    c.JSON(http.StatusOK, gin.H{
+        "message": "ok",
+    })
 }
 
 func DisableDomain(c *gin.Context) {
-	enabledConfigFilePath := nginx.GetConfPath("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{Filename: c.Param("name")}
-	err = certModel.Remove()
-	if err != nil {
-		ErrHandler(c, err)
-		return
-	}
-
-	output := nginx.Reload()
-
-	if nginx.GetLogLevel(output) >= nginx.Warn {
-		c.JSON(http.StatusInternalServerError, gin.H{
-			"message": output,
-		})
-		return
-	}
-
-	c.JSON(http.StatusOK, gin.H{
-		"message": "ok",
-	})
+    enabledConfigFilePath := nginx.GetConfPath("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{Filename: c.Param("name")}
+    err = certModel.Remove()
+    if err != nil {
+        ErrHandler(c, err)
+        return
+    }
+
+    output := nginx.Reload()
+
+    if nginx.GetLogLevel(output) >= nginx.Warn {
+        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 := nginx.GetConfPath("sites-available", name)
-	enabledPath := nginx.GetConfPath("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{Filename: 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 := nginx.GetConfPath("sites-available", name)
+    enabledPath := nginx.GetConfPath("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{Filename: 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) {
-	name := c.Param("name")
-	certModel, err := model.FirstOrCreateCert(name)
+    name := c.Param("name")
 
-	if err != nil {
-		ErrHandler(c, err)
-		return
-	}
+    var json struct {
+        Domains []string `json:"domains"`
+    }
 
-	err = certModel.Updates(&model.Cert{
-		Name:     name,
-		AutoCert: model.AutoCertEnabled,
-	})
+    if !BindAndValid(c, &json) {
+        return
+    }
 
-	if err != nil {
-		ErrHandler(c, err)
-		return
-	}
+    certModel, err := model.FirstOrCreateCert(name)
 
-	c.JSON(http.StatusOK, certModel)
+    if err != nil {
+        ErrHandler(c, err)
+        return
+    }
+
+    err = certModel.Updates(&model.Cert{
+        Name:     name,
+        Domains:  json.Domains,
+        AutoCert: model.AutoCertEnabled,
+    })
+
+    if err != nil {
+        ErrHandler(c, err)
+        return
+    }
+
+    c.JSON(http.StatusOK, certModel)
 }
 
 func RemoveDomainFromAutoCert(c *gin.Context) {
-	name := c.Param("name")
-	certModel, err := model.FirstCert(name)
-
-	if err != nil {
-		ErrHandler(c, err)
-		return
-	}
-
-	err = certModel.Updates(&model.Cert{
-		AutoCert: model.AutoCertDisabled,
-	})
-
-	if err != nil {
-		ErrHandler(c, err)
-		return
-	}
-	c.JSON(http.StatusOK, nil)
+    name := c.Param("name")
+    certModel, err := model.FirstCert(name)
+
+    if err != nil {
+        ErrHandler(c, err)
+        return
+    }
+
+    err = certModel.Updates(&model.Cert{
+        AutoCert: model.AutoCertDisabled,
+    })
+
+    if err != nil {
+        ErrHandler(c, err)
+        return
+    }
+    c.JSON(http.StatusOK, nil)
 }
 
 func DuplicateSite(c *gin.Context) {
-	name := c.Param("name")
+    name := c.Param("name")
 
-	var json struct {
-		Name string `json:"name" binding:"required"`
-	}
+    var json struct {
+        Name string `json:"name" binding:"required"`
+    }
 
-	if !BindAndValid(c, &json) {
-		return
-	}
+    if !BindAndValid(c, &json) {
+        return
+    }
 
-	src := nginx.GetConfPath("sites-available", name)
-	dst := nginx.GetConfPath("sites-available", json.Name)
+    src := nginx.GetConfPath("sites-available", name)
+    dst := nginx.GetConfPath("sites-available", json.Name)
 
-	if helper.FileExists(dst) {
-		c.JSON(http.StatusNotAcceptable, gin.H{
-			"message": "File exists",
-		})
-		return
-	}
+    if helper.FileExists(dst) {
+        c.JSON(http.StatusNotAcceptable, gin.H{
+            "message": "File exists",
+        })
+        return
+    }
 
-	_, err := helper.CopyFile(src, dst)
+    _, err := helper.CopyFile(src, dst)
 
-	if err != nil {
-		ErrHandler(c, err)
-		return
-	}
+    if err != nil {
+        ErrHandler(c, err)
+        return
+    }
 
-	c.JSON(http.StatusOK, gin.H{
-		"dst": dst,
-	})
+    c.JSON(http.StatusOK, gin.H{
+        "dst": dst,
+    })
 }

+ 57 - 53
server/model/cert.go

@@ -1,97 +1,101 @@
 package model
 
 import (
-    "github.com/0xJacky/Nginx-UI/server/pkg/nginx"
-    "github.com/lib/pq"
-    "os"
+	"github.com/0xJacky/Nginx-UI/server/pkg/nginx"
+	"github.com/lib/pq"
+	"os"
 )
 
 const (
-    AutoCertEnabled  = 1
-    AutoCertDisabled = -1
+	AutoCertEnabled  = 1
+	AutoCertDisabled = -1
 )
 
 type CertDomains []string
 
 type Cert struct {
-    Model
-    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"`
+	Model
+	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(confName string) (c Cert, err error) {
-    err = db.First(&c, &Cert{
-        Filename: confName,
-    }).Error
+	err = db.First(&c, &Cert{
+		Filename: confName,
+	}).Error
 
-    return
+	return
 }
 
 func FirstOrCreateCert(confName string) (c Cert, err error) {
-    err = db.FirstOrCreate(&c, &Cert{Filename: confName}).Error
-    return
+	err = db.FirstOrCreate(&c, &Cert{Filename: confName}).Error
+	return
 }
 
 func (c *Cert) Insert() error {
-    return db.Create(c).Error
+	return db.Create(c).Error
 }
 
 func GetAutoCertList() (c []*Cert) {
-    var t []*Cert
-    db.Where("auto_cert", AutoCertEnabled).Find(&t)
+	var t []*Cert
+	db.Where("auto_cert", AutoCertEnabled).Find(&t)
 
-    // check if this domain is enabled
-    enabledConfig, err := os.ReadDir(nginx.GetConfPath("sites-enabled"))
+	// check if this domain is enabled
+	enabledConfig, err := os.ReadDir(nginx.GetConfPath("sites-enabled"))
 
-    if err != nil {
-        return
-    }
+	if err != nil {
+		return
+	}
 
-    enabledConfigMap := make(map[string]bool)
-    for i := range enabledConfig {
-        enabledConfigMap[enabledConfig[i].Name()] = true
-    }
+	enabledConfigMap := make(map[string]bool)
+	for i := range enabledConfig {
+		enabledConfigMap[enabledConfig[i].Name()] = true
+	}
 
-    for _, v := range t {
-        if enabledConfigMap[v.Filename] == true {
-            c = append(c, v)
-        }
-    }
+	for _, v := range t {
+		if enabledConfigMap[v.Filename] == true {
+			c = append(c, v)
+		}
+	}
 
-    return
+	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
+	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
+	err = db.First(&c, id).Error
 
-    return
+	return
 }
 
 func (c *Cert) Updates(n *Cert) error {
-    return db.Model(&Cert{}).Where("id", c.ID).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 {
-    if c.Filename == "" {
-        return db.Delete(c).Error
-    }
+	if c.Filename == "" {
+		return db.Delete(c).Error
+	}
 
-    return db.Where("filename", c.Filename).Delete(c).Error
+	return db.Where("filename", c.Filename).Delete(c).Error
 }

+ 94 - 88
server/pkg/cert/auto_cert.go

@@ -1,117 +1,123 @@
 package cert
 
 import (
-	"fmt"
-	"github.com/0xJacky/Nginx-UI/server/model"
-	"github.com/pkg/errors"
-	"log"
-	"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
+    buffer []string
+    cert   *model.Cert
 }
 
 func (t *AutoCertErrorLog) SetCertModel(cert *model.Cert) {
-	t.cert = 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)
+    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)
+    t.buffer = append(t.buffer, text+" "+err.Error())
+    log.Println("[AutoCert Error]", text, err)
 
-	if t.cert == nil {
-		return
-	}
+    if t.cert == nil {
+        return
+    }
 
-	_ = t.cert.Updates(&model.Cert{
-		Log: t.ToString(),
-	})
+    _ = 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)
-	}
+    for _, v := range t.buffer {
+        content += fmt.Sprintf("[AutoCert Error] %s\n", v)
+    }
 
-	return
+    return
 }
 
 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)
-		}
-		// store error log to db
-		_ = certModel.Updates(&model.Cert{
-			Log: errLog.ToString(),
-		})
-
-		close(logChan)
-	}
-	log.Println("[AutoCert] End")
+    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")
 }

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

@@ -92,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
     }
 
@@ -100,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
@@ -113,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, "_")
@@ -121,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
         }
     }
@@ -142,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
     }
 
@@ -152,4 +152,6 @@ func IssueCert(domain []string, logChan chan string, errChan chan error) {
     nginx.Reload()
 
     logChan <- "Finished"
+
+    close(logChan)
 }