wip: ssl manage panel #52, #29

This commit is contained in:
0xJacky 2023-01-03 00:24:51 +08:00
parent 4a3e32a921
commit a5b9bb88d6
No known key found for this signature in database
GPG key ID: B6E4A6E4A561BAF0
18 changed files with 622 additions and 295 deletions

5
frontend/src/api/cert.ts Normal file
View file

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

View file

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

View file

@ -17,7 +17,8 @@ msgstr "About"
msgid "Access Logs" msgid "Access Logs"
msgstr "" 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" msgid "Action"
msgstr "Action" msgstr "Action"
@ -205,6 +206,10 @@ msgstr ""
msgid "Development Mode" msgid "Development Mode"
msgstr "Development Mode" msgstr "Development Mode"
#: src/views/config/config.ts:20
msgid "Dir"
msgstr ""
#: src/views/domain/ngx_conf/directive/DirectiveAdd.vue:20 #: src/views/domain/ngx_conf/directive/DirectiveAdd.vue:20
msgid "Directive" msgid "Directive"
msgstr "Directive" msgstr "Directive"
@ -308,6 +313,10 @@ msgstr "Failed to enable %{msg}"
msgid "Failed to get certificate information" msgid "Failed to get certificate information"
msgstr "" msgstr ""
#: src/views/config/config.ts:22
msgid "File"
msgstr ""
#: src/views/other/Error.vue:3 src/views/other/Error.vue:4 #: src/views/other/Error.vue:3 src/views/other/Error.vue:4
msgid "File Not Found" msgid "File Not Found"
msgstr "File Not Found" msgstr "File Not Found"
@ -444,7 +453,8 @@ msgstr "Modify Config"
msgid "Modify Config" msgid "Modify Config"
msgstr "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" msgid "Name"
msgstr "Name" msgstr "Name"
@ -730,7 +740,12 @@ msgstr ""
msgid "Theme" msgid "Theme"
msgstr "" 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" msgid "Updated at"
msgstr "Updated at" msgstr "Updated at"

View file

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

File diff suppressed because one or more lines are too long

Binary file not shown.

View file

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

View file

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

View file

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

View file

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

View file

@ -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>

View file

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

View file

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

View file

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

View file

@ -6,6 +6,7 @@ import (
"github.com/0xJacky/Nginx-UI/server/pkg/nginx" "github.com/0xJacky/Nginx-UI/server/pkg/nginx"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/gorilla/websocket" "github.com/gorilla/websocket"
"github.com/spf13/cast"
"log" "log"
"net/http" "net/http"
"strings" "strings"
@ -117,7 +118,8 @@ func IssueCert(c *gin.Context) {
} }
err = certModel.Updates(&model.Cert{ err = certModel.Updates(&model.Cert{
SSLCertificatePath: sslCertificatePath, SSLCertificatePath: sslCertificatePath,
SSLCertificateKeyPath: sslCertificateKeyPath,
}) })
if err != nil { 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)
}

View file

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

View file

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

View file

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